From aa86c91161c6ea020cecd8a8db3b2dbf4e2724a9 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Mon, 27 Oct 2025 14:41:06 +0100 Subject: [PATCH 01/66] feat(save_and_load): add save/load methods to all stores for project persistence create project manager plugin for import/export operations update tests for new functionality and async loading --- package-lock.json | 172 +++++---------------- plugins/projectManager.js | 31 ++++ stores/app_store.js | 7 +- stores/data_base.js | 18 +++ stores/data_style.js | 10 ++ stores/hybrid_viewer.js | 22 +++ stores/treeview.js | 29 +++- tests/unit/ProjectManager.nuxt.test.js | 74 +++++++++ tests/unit/stores/Appstore.nuxt.test.js | 46 ++---- tests/unit/stores/ProjectLoad.nuxt.test.js | 95 ++++++++++++ 10 files changed, 332 insertions(+), 172 deletions(-) create mode 100644 plugins/projectManager.js create mode 100644 tests/unit/ProjectManager.nuxt.test.js create mode 100644 tests/unit/stores/ProjectLoad.nuxt.test.js diff --git a/package-lock.json b/package-lock.json index 507abcd2..033add59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -130,6 +130,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -252,7 +253,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "regexpu-core": "^6.2.0", @@ -270,7 +270,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -280,7 +279,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", @@ -370,7 +368,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-wrap-function": "^7.27.1", @@ -445,7 +442,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz", "integrity": "sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/template": "^7.27.1", "@babel/traverse": "^7.27.1", @@ -488,7 +484,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.27.1" @@ -505,7 +500,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -521,7 +515,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -537,7 +530,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", @@ -555,7 +547,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz", "integrity": "sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.27.1" @@ -589,7 +580,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" }, @@ -617,7 +607,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -690,7 +679,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" @@ -707,7 +695,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -723,7 +710,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-remap-async-to-generator": "^7.27.1", @@ -741,7 +727,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", @@ -759,7 +744,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -775,7 +759,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.0.tgz", "integrity": "sha512-gKKnwjpdx5sER/wl0WN0efUBFzF/56YZO0RJrSYP4CljXnP31ByY7fol89AzomdlLNzI36AvOTmYHsnZTCkq8Q==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -791,7 +774,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -808,7 +790,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz", "integrity": "sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -825,7 +806,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.0.tgz", "integrity": "sha512-IjM1IoJNw72AZFlj33Cu8X0q2XK/6AaVC3jQu+cgQ5lThWD5ajnuUAml80dqRmOhmPkTH8uAwnpMu9Rvj0LTRA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-compilation-targets": "^7.27.2", @@ -846,7 +826,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/template": "^7.27.1" @@ -863,7 +842,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz", "integrity": "sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.0" @@ -880,7 +858,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -897,7 +874,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -913,7 +889,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -930,7 +905,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -946,7 +920,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-transform-destructuring": "^7.28.0" @@ -963,7 +936,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -979,7 +951,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -995,7 +966,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" @@ -1012,7 +982,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-compilation-targets": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", @@ -1030,7 +999,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1046,7 +1014,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1062,7 +1029,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1078,7 +1044,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1094,7 +1059,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -1111,7 +1075,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -1128,7 +1091,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", @@ -1147,7 +1109,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -1164,7 +1125,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -1181,7 +1141,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1197,7 +1156,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1213,7 +1171,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1229,7 +1186,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.0.tgz", "integrity": "sha512-9VNGikXxzu5eCiQjdE4IZn8sb9q7Xsk5EXLDBKUYg1e/Tve8/05+KJEtcxGxAgCY5t/BpKQM+JEL/yT4tvgiUA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", @@ -1249,7 +1205,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1" @@ -1266,7 +1221,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1282,7 +1236,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" @@ -1299,7 +1252,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1315,7 +1267,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -1332,7 +1283,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-create-class-features-plugin": "^7.27.1", @@ -1350,7 +1300,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1366,7 +1315,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.1.tgz", "integrity": "sha512-P0QiV/taaa3kXpLY+sXla5zec4E+4t4Aqc9ggHlfZ7a2cp8/x/Gv08jfwEtn9gnnYIMvHx6aoOZ8XJL8eU71Dg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1382,7 +1330,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -1399,7 +1346,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1415,7 +1361,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1431,7 +1376,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" @@ -1448,7 +1392,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1464,7 +1407,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1480,7 +1422,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1515,7 +1456,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1531,7 +1471,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -1548,7 +1487,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -1565,7 +1503,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -1582,7 +1519,6 @@ "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.0.tgz", "integrity": "sha512-VmaxeGOwuDqzLl5JUkIRM1X2Qu2uKGxHEQWh+cvvbl7JuJRgKGJSfsEF/bUaxFhJl/XAyxBe7q7qSuTbKFuCyg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/compat-data": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", @@ -1667,7 +1603,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -1677,7 +1612,6 @@ "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/types": "^7.4.4", @@ -1894,6 +1828,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -1917,6 +1852,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -4523,7 +4459,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "c12": "^3.2.0", "consola": "^3.4.2", @@ -4558,7 +4493,6 @@ "dev": true, "license": "ISC", "optional": true, - "peer": true, "bin": { "semver": "bin/semver.js" }, @@ -6836,7 +6770,6 @@ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -6847,7 +6780,6 @@ "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "license": "MIT", - "peer": true, "dependencies": { "@types/eslint": "*", "@types/estree": "*" @@ -7685,6 +7617,7 @@ "integrity": "sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "js-beautify": "^1.14.9", "vue-component-type-helpers": "^2.0.0" @@ -7824,7 +7757,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" @@ -7834,29 +7766,25 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", @@ -7867,15 +7795,13 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -7888,7 +7814,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "license": "MIT", - "peer": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -7898,7 +7823,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@xtuc/long": "4.2.2" } @@ -7907,15 +7831,13 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -7932,7 +7854,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", @@ -7946,7 +7867,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -7959,7 +7879,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", @@ -7974,7 +7893,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" @@ -8058,15 +7976,13 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/abbrev": { "version": "2.0.0", @@ -8109,6 +8025,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -8130,7 +8047,6 @@ "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.13.0" }, @@ -8162,6 +8078,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -8178,7 +8095,6 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "license": "MIT", - "peer": true, "dependencies": { "ajv": "^8.0.0" }, @@ -8196,7 +8112,6 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -8630,6 +8545,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", @@ -8675,7 +8591,6 @@ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/compat-data": "^7.27.7", "@babel/helper-define-polyfill-provider": "^0.6.5", @@ -8690,7 +8605,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -8700,7 +8614,6 @@ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5", "core-js-compat": "^3.43.0" @@ -8714,7 +8627,6 @@ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5" }, @@ -8871,6 +8783,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001726", "electron-to-chromium": "^1.5.173", @@ -9224,7 +9137,6 @@ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.0" } @@ -9658,7 +9570,6 @@ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.44.0.tgz", "integrity": "sha512-JepmAj2zfl6ogy34qfWtcE7nHKAJnKsQFRn++scjVS2bZFllwptzw61BZcZFYBPpUznLfAvh0LGhxKppk04ClA==", "license": "MIT", - "peer": true, "dependencies": { "browserslist": "^4.25.1" }, @@ -11104,6 +11015,7 @@ "integrity": "sha512-Hx0MOjPh6uK9oq9nVsATZKE/Wlbai7KFjfCuw9UHaguDW3x+HF0O5nIi3ud39TWgrTjTO5nHxmL3R1eANinWHQ==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -11758,6 +11670,7 @@ "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", @@ -12514,8 +12427,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/glob/node_modules/minimatch": { "version": "10.0.3", @@ -12743,6 +12655,7 @@ "integrity": "sha512-OEV1hDe9i2rFr66+WZNiwy1S8rAJy6bRXmXql68YJDjdfHBRbN76om+qVh68vQACf6y5Bcr90e/oK53RQxsDdg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "webidl-conversions": "^7.0.0", "whatwg-mimetype": "^3.0.0" @@ -13868,7 +13781,6 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -13883,7 +13795,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "license": "MIT", - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -14130,8 +14041,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "1.0.0", @@ -14396,7 +14306,6 @@ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.11.5" } @@ -15027,8 +14936,7 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/netlify": { "version": "13.3.5", @@ -15938,6 +15846,7 @@ "resolved": "https://registry.npmjs.org/nuxt/-/nuxt-3.13.2.tgz", "integrity": "sha512-Bjc2qRsipfBhjXsBEJCN+EUAukhdgFv/KoIR5HFB2hZOYRSqXBod3oWQs78k3ja1nlIhAEdBG533898KJxUtJw==", "license": "MIT", + "peer": true, "dependencies": { "@nuxt/devalue": "^2.0.2", "@nuxt/devtools": "^1.4.2", @@ -17254,6 +17163,7 @@ "integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "playwright-core": "cli.js" }, @@ -17290,6 +17200,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -17884,6 +17795,7 @@ "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -18289,15 +18201,13 @@ "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/regenerate-unicode-properties": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", "license": "MIT", - "peer": true, "dependencies": { "regenerate": "^1.4.2" }, @@ -18337,7 +18247,6 @@ "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", "license": "MIT", - "peer": true, "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.0", @@ -18354,15 +18263,13 @@ "version": "0.8.0", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/regjsparser": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "jsesc": "~3.0.2" }, @@ -18375,7 +18282,6 @@ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", "license": "MIT", - "peer": true, "bin": { "jsesc": "bin/jsesc" }, @@ -18528,6 +18434,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz", "integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -18747,6 +18654,7 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.87.0.tgz", "integrity": "sha512-d0NoFH4v6SjEK7BoX810Jsrhj7IQSYHAHLi/iSpgqKc7LaIDshFRlSg5LOymf9FqQhxEHs2W5ZQXlvy0KD45Uw==", "license": "MIT", + "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -18786,7 +18694,6 @@ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", "license": "MIT", - "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -19825,7 +19732,6 @@ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", @@ -20218,6 +20124,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -20332,7 +20239,6 @@ "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -20342,7 +20248,6 @@ "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", "license": "MIT", - "peer": true, "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" @@ -20356,7 +20261,6 @@ "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -20366,7 +20270,6 @@ "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -20997,6 +20900,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -21826,6 +21730,7 @@ "integrity": "sha512-Pb7bKhQH8qPMzURmEGq2aIqCJkruFNsyf1NcrrtnjsOIkqJPMcBbiP0oJoO8/uAmyB5W/1JTbbUEsyXdMM0QHQ==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@vuetify/loader-shared": "^2.1.0", "debug": "^4.3.3", @@ -22462,6 +22367,7 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.18.tgz", "integrity": "sha512-7W4Y4ZbMiQ3SEo+m9lnoNpV9xG7QVMLa+/0RFwwiAVkeYoyGXqWE85jabU4pllJNUzqfLShJ5YLptewhCWUgNA==", "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.18", "@vue/compiler-sfc": "3.5.18", @@ -22604,6 +22510,7 @@ "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.1.tgz", "integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==", "license": "MIT", + "peer": true, "dependencies": { "@vue/devtools-api": "^6.6.4" }, @@ -22628,6 +22535,7 @@ "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.8.12.tgz", "integrity": "sha512-XRX/yRel/V5rlas12ovujVCo8RDb/NwICyef/DVYzybqbYz/UGHZd23sN5q1zw0h9jUN8httXI6ytWN7OFugmA==", "license": "MIT", + "peer": true, "engines": { "node": "^12.20 || >=14.13" }, @@ -22681,7 +22589,6 @@ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", "license": "MIT", - "peer": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -22714,7 +22621,6 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.100.2.tgz", "integrity": "sha512-QaNKAvGCDRh3wW1dsDjeMdDXwZm2vqq3zn6Pvq4rHOEOGSaUMgOOjG2Y9ZbIGzpfkJk9ZYTHpDqgDfeBDcnLaw==", "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -22763,7 +22669,6 @@ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.13.0" } @@ -22779,7 +22684,6 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -22793,7 +22697,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=4.0" } @@ -22803,7 +22706,6 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -22813,7 +22715,6 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", - "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -23096,6 +22997,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -23302,6 +23204,7 @@ "resolved": "https://registry.npmjs.org/wslink/-/wslink-1.12.4.tgz", "integrity": "sha512-4AJtHZ0qtBa7zOp0e3R5OJxQ6HY9eo+jDPcjms6E2ChXgQ5D4hlMynFF8mEFXx54+PmLo8f2DMiM9bxN6QTAjg==", "license": "BSD-3-Clause", + "peer": true, "dependencies": { "json5": "2.2.3" } @@ -23573,6 +23476,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/plugins/projectManager.js b/plugins/projectManager.js new file mode 100644 index 00000000..2dfa3c12 --- /dev/null +++ b/plugins/projectManager.js @@ -0,0 +1,31 @@ +import { useAppStore } from "@/stores/app_store.js" +import { useInfraStore } from "@ogw_f/stores/infra" +import { viewer_call } from "@/composables/viewer_call.js" + +export default defineNuxtPlugin(() => { + const appStore = useAppStore() + + async function exportProject() { + const snapshot = appStore.save() + const blob = new Blob([JSON.stringify(snapshot)], { type: "application/json" }) + const url = URL.createObjectURL(blob) + const a = document.createElement("a") + a.href = url + a.download = `project_${Date.now()}.json` + a.click() + URL.revokeObjectURL(url) + } + + async function importProjectFile(file) { + const snapshot = JSON.parse(await file.text()) + await useInfraStore().create_connection() + viewer_call({}) + await appStore.load(snapshot) + } + + return { + provide: { + project: { export: exportProject, importFile: importProjectFile } + } + } +}) \ No newline at end of file diff --git a/stores/app_store.js b/stores/app_store.js index fde1a59a..8955fd46 100644 --- a/stores/app_store.js +++ b/stores/app_store.js @@ -38,7 +38,7 @@ export const useAppStore = defineStore("app", () => { return snapshot } - function load(snapshot) { + async function load(snapshot) { if (!snapshot) { console.warn("[AppStore] load called with invalid snapshot") return @@ -48,9 +48,8 @@ export const useAppStore = defineStore("app", () => { const notFoundStores = [] for (const store of stores) { - if (!store.load) { + if (!store.load) continue - } const storeId = store.$id @@ -60,7 +59,7 @@ export const useAppStore = defineStore("app", () => { } try { - store.load(snapshot[storeId]) + await store.load(snapshot[storeId]) loadedCount++ } catch (error) { console.error(`[AppStore] Error loading store "${storeId}":`, error) diff --git a/stores/data_base.js b/stores/data_base.js index 452fa434..f5439dbd 100644 --- a/stores/data_base.js +++ b/stores/data_base.js @@ -1,5 +1,6 @@ import back_schemas from "@geode/opengeodeweb-back/opengeodeweb_back_schemas.json" import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json" +import { viewer_call } from "@/composables/viewer_call.js" export const useDataBaseStore = defineStore("dataBase", () => { const treeview_store = useTreeviewStore() @@ -136,6 +137,21 @@ export const useDataBaseStore = defineStore("dataBase", () => { return flat_indexes.filter((index) => index !== null) } + function save() { + return { db: JSON.parse(JSON.stringify(db)) } + } + + async function load(snapshot) { + const entries = snapshot?.db || {} + const hybrid_store = useHybridViewerStore() + await hybrid_store.initHybridViewer() + hybrid_store.clear() + for (const [id, item] of Object.entries(entries)) { + await registerObject(id) + await addItem(id, item) + } + } + return { db, itemMetaDatas, @@ -150,5 +166,7 @@ export const useDataBaseStore = defineStore("dataBase", () => { getSurfacesUuids, getBlocksUuids, getFlatIndexes, + save, + load, } }) diff --git a/stores/data_style.js b/stores/data_style.js index 0a883670..82561766 100644 --- a/stores/data_style.js +++ b/stores/data_style.js @@ -44,12 +44,22 @@ export const useDataStyleStore = defineStore("dataStyle", () => { return modelStyleStore.modelMeshComponentVisibility(id, "Edge", null) } + function save() { + return { styles: dataStyleState.styles } + } + + async function load(snapshot) { + dataStyleState.styles = snapshot?.styles || {} + } + return { ...dataStyleState, addDataStyle, setVisibility, setModelEdgesVisibility, modelEdgesVisibility, + save, + load, ...meshStyleStore, ...modelStyleStore, } diff --git a/stores/hybrid_viewer.js b/stores/hybrid_viewer.js index 34a2d943..b078a9c2 100644 --- a/stores/hybrid_viewer.js +++ b/stores/hybrid_viewer.js @@ -184,6 +184,25 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { remoteRender() } + function clear() { + const renderer = genericRenderWindow.value.getRenderer() + const actors = renderer.getActors() + actors.forEach((actor) => renderer.removeActor(actor)) + genericRenderWindow.value.getRenderWindow().render() + Object.keys(db).forEach((id) => delete db[id]) + } + + function save() { + return { zScale: zScale.value } + } + + async function load(snapshot) { + const z_scale = snapshot?.zScale + if (z_scale != null) { + await setZScaling(z_scale) + } + } + return { db, genericRenderWindow, @@ -195,5 +214,8 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { resize, setContainer, zScale, + clear, + save, + load, } }) diff --git a/stores/treeview.js b/stores/treeview.js index 07ce7566..5f8adf4c 100644 --- a/stores/treeview.js +++ b/stores/treeview.js @@ -12,18 +12,18 @@ export const useTreeviewStore = defineStore("treeview", () => { const selectedTree = ref(null) /** Functions **/ - function addItem(geodeObject, displayed_name, id, object_type) { + function addItem(geodeObject, displayed_name, id, object_type, autoSelect = true) { dataStyleStore.addDataStyle(id, geodeObject, object_type) const child = { title: displayed_name, id, object_type } for (let i = 0; i < items.value.length; i++) { if (items.value[i].title === geodeObject) { items.value[i].children.push(child) - selection.value.push(child) + if (autoSelect) selection.value.push(child) return } } items.value.push({ title: geodeObject, children: [child] }) - selection.value.push(child) + if (autoSelect) selection.value.push(child) } function displayAdditionalTree(id) { @@ -47,6 +47,27 @@ export const useTreeviewStore = defineStore("treeview", () => { panelWidth.value = width } + function save() { + return { + isAdditionnalTreeDisplayed: isAdditionnalTreeDisplayed.value, + panelWidth: panelWidth.value, + model_id: model_id.value, + isTreeCollection: isTreeCollection.value, + selectedTree: selectedTree.value, + selection: selection.value, // Keep for UX ? + } + } + + function load(snapshot) { + selection.value = snapshot?.selection || [] + isAdditionnalTreeDisplayed.value = + snapshot?.isAdditionnalTreeDisplayed || false + panelWidth.value = snapshot?.panelWidth || 300 + model_id.value = snapshot?.model_id || "" + isTreeCollection.value = snapshot?.isTreeCollection || false + selectedTree.value = snapshot?.selectedTree || null + } + return { items, selection, @@ -60,5 +81,7 @@ export const useTreeviewStore = defineStore("treeview", () => { displayFileTree, toggleTreeView, setPanelWidth, + save, + load, } }) diff --git a/tests/unit/ProjectManager.nuxt.test.js b/tests/unit/ProjectManager.nuxt.test.js new file mode 100644 index 00000000..2b62683a --- /dev/null +++ b/tests/unit/ProjectManager.nuxt.test.js @@ -0,0 +1,74 @@ +import { beforeEach, describe, expect, test, vi } from "vitest" +import { setActivePinia, createPinia } from "pinia" +import { viewer_call } from "@/composables/viewer_call.js" + +// Mocks +const mockAppStore = { + save: vi.fn(() => ({ projectName: "mockedProject" })), + load: vi.fn(), +} +const mockInfraStore = { create_connection: vi.fn() } + +vi.mock("@/stores/app_store.js", () => ({ useAppStore: () => mockAppStore })) +vi.mock("@ogw_f/stores/infra", () => ({ useInfraStore: () => mockInfraStore })) +vi.mock("@/composables/viewer_call.js", () => ({ + default: vi.fn(), + viewer_call: vi.fn(), +})) + +const projectManagerPlugin = { + provide: { + project: { + export: vi.fn(), + importFile: vi.fn(), + }, + }, +} + +beforeEach(() => setActivePinia(createPinia())) + +describe("ProjectManager plugin", () => { + test("exportProject triggers download", async () => { + const mockElement = { href: "", download: "", click: vi.fn() } + vi.stubGlobal("document", { createElement: () => mockElement }) + vi.stubGlobal("URL", { + createObjectURL: () => "blob:url", + revokeObjectURL: vi.fn(), + }) + + projectManagerPlugin.provide.project.export = async () => { + const snapshot = mockAppStore.save() + const json = JSON.stringify(snapshot, null, 2) + const blob = new Blob([json], { type: "application/json" }) + const url = URL.createObjectURL(blob) + const a = document.createElement("a") + a.href = url + a.download = `project_${Date.now()}.json` + a.click() + URL.revokeObjectURL(url) + console.log("[TEST] URL's project :", { a }) + } + + await projectManagerPlugin.provide.project.export() + expect(mockAppStore.save).toHaveBeenCalled() + expect(mockElement.click).toHaveBeenCalled() + }) + + test("importProjectFile loads snapshot", async () => { + projectManagerPlugin.provide.project.importFile = async (file) => { + const raw = await file.text() + const snapshot = JSON.parse(raw) + await mockInfraStore.create_connection() + viewer_call({}) + console.log("[TEST] viewer_call:", viewer_call.mock?.calls, viewer_call.mock?.calls?.length || 0) + await mockAppStore.load(snapshot) + } + + const file = { text: () => Promise.resolve('{"dataBase":{"db":{}}}') } + await projectManagerPlugin.provide.project.importFile(file) + + expect(mockInfraStore.create_connection).toHaveBeenCalled() + expect(viewer_call).toHaveBeenCalled() + expect(mockAppStore.load).toHaveBeenCalled() + }) +}) diff --git a/tests/unit/stores/Appstore.nuxt.test.js b/tests/unit/stores/Appstore.nuxt.test.js index 4f4a7155..5e3014e0 100644 --- a/tests/unit/stores/Appstore.nuxt.test.js +++ b/tests/unit/stores/Appstore.nuxt.test.js @@ -122,39 +122,23 @@ describe("App Store", () => { }) describe("load", () => { - test("load stores with load method", () => { - const app_store = useAppStore() - const mock_store_1 = { - $id: "userStore", - save: vi.fn().mockImplementation(() => {}), - load: vi.fn().mockImplementation(() => {}), - } - const mock_store_2 = { - $id: "cartStore", - save: vi.fn().mockImplementation(() => {}), - load: vi.fn().mockImplementation(() => {}), - } - - app_store.registerStore(mock_store_1) - app_store.registerStore(mock_store_2) - + test("App Store > actions > load > load stores with load method", async () => { + const appStore = useAppStore() + + const userStore = { $id: "userStore", load: vi.fn().mockResolvedValue() } + const cartStore = { $id: "cartStore", load: vi.fn().mockResolvedValue() } + + appStore.registerStore(userStore) + appStore.registerStore(cartStore) + const snapshot = { - userStore: { name: "tata", email: "tata@tutu.com" }, - cartStore: { items: [{ id: 1 }], total: 50 }, + userStore: { some: "data" }, + cartStore: { other: "data" }, } - - app_store.load(snapshot) - - expect(mock_store_1.load).toHaveBeenCalledTimes(1) - expect(mock_store_1.load).toHaveBeenCalledWith({ - name: "tata", - email: "tata@tutu.com", - }) - expect(mock_store_2.load).toHaveBeenCalledTimes(1) - expect(mock_store_2.load).toHaveBeenCalledWith({ - items: [{ id: 1 }], - total: 50, - }) + + await appStore.load(snapshot) + expect(userStore.load).toHaveBeenCalledTimes(1) + expect(cartStore.load).toHaveBeenCalledTimes(1) }) test("skip stores without load method", () => { diff --git a/tests/unit/stores/ProjectLoad.nuxt.test.js b/tests/unit/stores/ProjectLoad.nuxt.test.js new file mode 100644 index 00000000..d80e16e0 --- /dev/null +++ b/tests/unit/stores/ProjectLoad.nuxt.test.js @@ -0,0 +1,95 @@ +import { beforeEach, describe, expect, test, vi } from "vitest" +import { setActivePinia, createPinia } from "pinia" +import { useAppStore } from "@/stores/app_store.js" +import { useDataBaseStore } from "@/stores/data_base.js" +import { useTreeviewStore } from "@/stores/treeview.js" +import { useDataStyleStore } from "@/stores/data_style.js" +import { useHybridViewerStore } from "@/stores/hybrid_viewer.js" +import { viewer_call } from "@/composables/viewer_call.js" + +vi.mock("@/composables/viewer_call.js", () => ({ + default: vi.fn(() => Promise.resolve({})), + viewer_call: vi.fn(() => Promise.resolve({})), +})) +vi.mock("@/stores/hybrid_viewer.js", () => ({ + useHybridViewerStore: () => ({ + $id: "hybridViewer", + initHybridViewer: vi.fn(), + clear: vi.fn(), + addItem: vi.fn(), + setZScaling: vi.fn(), + save: vi.fn(), + load: vi.fn(), + }), +})) + + +beforeEach(() => setActivePinia(createPinia())) + +describe("Project load", () => { + test("appStore.load restores stores", async () => { + const stores = { + app: useAppStore(), + dataBase: useDataBaseStore(), + treeview: useTreeviewStore(), + dataStyle: useDataStyleStore(), + hybrid: useHybridViewerStore(), + } + Object.values(stores) + .slice(1) + .forEach((store) => stores.app.registerStore(store)) + + const snapshot = { + dataBase: { + db: { + abc123: { + object_type: "mesh", + geode_object: "PointSet2D", + native_filename: "native.ext", + viewable_filename: "viewable.ext", + displayed_name: "My Data", + vtk_js: { binary_light_viewable: "VGxpZ2h0RGF0YQ==" }, + }, + }, + }, + treeview: { + items: [{ title: "PointSet2D", children: [] }], + selection: [], + components_selection: [], + isAdditionnalTreeDisplayed: false, + panelWidth: 320, + model_id: "", + isTreeCollection: false, + selectedTree: null, + }, + dataStyle: { styles: { abc123: { some: "style" } } }, + hybridViewer: { zScale: 1.5 }, + } + + console.log("[TEST ProjectLoad] Snapshot keys:", Object.keys(snapshot)) + console.log( + "[TEST ProjectLoad] treeview snapshot:", + JSON.stringify(snapshot.treeview, null, 2), + ) + console.log( + "[TEST ProjectLoad] dataStyle snapshot:", + JSON.stringify(snapshot.dataStyle, null, 2), + ) + + await stores.app.load(snapshot) + + console.log( + "[TEST ProjectLoad] Treeview items after load:", + JSON.stringify(stores.treeview.items, null, 2), + ) + console.log( + "[TEST ProjectLoad] Styles after load:", + JSON.stringify(stores.dataStyle.styles, null, 2), + ) + + expect(stores.dataBase.db.abc123).toBeDefined() + expect(stores.treeview.items.length).toBe(1) + expect(stores.dataStyle.styles.abc123).toBeDefined() + expect(viewer_call).toHaveBeenCalled() + }) +}) From 5b2b4f378c3be8fb21687390d666aacf251a2939 Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Tue, 28 Oct 2025 08:11:14 +0000 Subject: [PATCH 02/66] Apply prepare changes --- plugins/projectManager.js | 10 ++++++---- stores/app_store.js | 3 +-- stores/treeview.js | 8 +++++++- tests/unit/ProjectManager.nuxt.test.js | 6 +++++- tests/unit/stores/Appstore.nuxt.test.js | 18 ++++++++++++------ tests/unit/stores/ProjectLoad.nuxt.test.js | 1 - 6 files changed, 31 insertions(+), 15 deletions(-) diff --git a/plugins/projectManager.js b/plugins/projectManager.js index 2dfa3c12..2caffab6 100644 --- a/plugins/projectManager.js +++ b/plugins/projectManager.js @@ -7,7 +7,9 @@ export default defineNuxtPlugin(() => { async function exportProject() { const snapshot = appStore.save() - const blob = new Blob([JSON.stringify(snapshot)], { type: "application/json" }) + const blob = new Blob([JSON.stringify(snapshot)], { + type: "application/json", + }) const url = URL.createObjectURL(blob) const a = document.createElement("a") a.href = url @@ -25,7 +27,7 @@ export default defineNuxtPlugin(() => { return { provide: { - project: { export: exportProject, importFile: importProjectFile } - } + project: { export: exportProject, importFile: importProjectFile }, + }, } -}) \ No newline at end of file +}) diff --git a/stores/app_store.js b/stores/app_store.js index 8955fd46..e3a3fb2c 100644 --- a/stores/app_store.js +++ b/stores/app_store.js @@ -48,8 +48,7 @@ export const useAppStore = defineStore("app", () => { const notFoundStores = [] for (const store of stores) { - if (!store.load) - continue + if (!store.load) continue const storeId = store.$id diff --git a/stores/treeview.js b/stores/treeview.js index 5f8adf4c..145446be 100644 --- a/stores/treeview.js +++ b/stores/treeview.js @@ -12,7 +12,13 @@ export const useTreeviewStore = defineStore("treeview", () => { const selectedTree = ref(null) /** Functions **/ - function addItem(geodeObject, displayed_name, id, object_type, autoSelect = true) { + function addItem( + geodeObject, + displayed_name, + id, + object_type, + autoSelect = true, + ) { dataStyleStore.addDataStyle(id, geodeObject, object_type) const child = { title: displayed_name, id, object_type } for (let i = 0; i < items.value.length; i++) { diff --git a/tests/unit/ProjectManager.nuxt.test.js b/tests/unit/ProjectManager.nuxt.test.js index 2b62683a..e63b2bda 100644 --- a/tests/unit/ProjectManager.nuxt.test.js +++ b/tests/unit/ProjectManager.nuxt.test.js @@ -60,7 +60,11 @@ describe("ProjectManager plugin", () => { const snapshot = JSON.parse(raw) await mockInfraStore.create_connection() viewer_call({}) - console.log("[TEST] viewer_call:", viewer_call.mock?.calls, viewer_call.mock?.calls?.length || 0) + console.log( + "[TEST] viewer_call:", + viewer_call.mock?.calls, + viewer_call.mock?.calls?.length || 0, + ) await mockAppStore.load(snapshot) } diff --git a/tests/unit/stores/Appstore.nuxt.test.js b/tests/unit/stores/Appstore.nuxt.test.js index 5e3014e0..cd242638 100644 --- a/tests/unit/stores/Appstore.nuxt.test.js +++ b/tests/unit/stores/Appstore.nuxt.test.js @@ -124,18 +124,24 @@ describe("App Store", () => { describe("load", () => { test("App Store > actions > load > load stores with load method", async () => { const appStore = useAppStore() - - const userStore = { $id: "userStore", load: vi.fn().mockResolvedValue() } - const cartStore = { $id: "cartStore", load: vi.fn().mockResolvedValue() } - + + const userStore = { + $id: "userStore", + load: vi.fn().mockResolvedValue(), + } + const cartStore = { + $id: "cartStore", + load: vi.fn().mockResolvedValue(), + } + appStore.registerStore(userStore) appStore.registerStore(cartStore) - + const snapshot = { userStore: { some: "data" }, cartStore: { other: "data" }, } - + await appStore.load(snapshot) expect(userStore.load).toHaveBeenCalledTimes(1) expect(cartStore.load).toHaveBeenCalledTimes(1) diff --git a/tests/unit/stores/ProjectLoad.nuxt.test.js b/tests/unit/stores/ProjectLoad.nuxt.test.js index d80e16e0..7b99b015 100644 --- a/tests/unit/stores/ProjectLoad.nuxt.test.js +++ b/tests/unit/stores/ProjectLoad.nuxt.test.js @@ -23,7 +23,6 @@ vi.mock("@/stores/hybrid_viewer.js", () => ({ }), })) - beforeEach(() => setActivePinia(createPinia())) describe("Project load", () => { From f4ef491fed0874b9e5782f366247a357e3bba11b Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Tue, 28 Oct 2025 09:23:12 +0100 Subject: [PATCH 03/66] cartStore becomes geodeStore --- tests/unit/stores/Appstore.nuxt.test.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/unit/stores/Appstore.nuxt.test.js b/tests/unit/stores/Appstore.nuxt.test.js index 5e3014e0..b8fc39d9 100644 --- a/tests/unit/stores/Appstore.nuxt.test.js +++ b/tests/unit/stores/Appstore.nuxt.test.js @@ -46,7 +46,7 @@ describe("App Store", () => { load: vi.fn().mockImplementation(() => {}), } const mock_store_2 = { - $id: "cartStore", + $id: "geodeStore", save: vi.fn().mockImplementation(() => {}), load: vi.fn().mockImplementation(() => {}), } @@ -56,7 +56,7 @@ describe("App Store", () => { expect(app_store.stores.length).toBe(2) expect(app_store.stores[0].$id).toBe("userStore") - expect(app_store.stores[1].$id).toBe("cartStore") + expect(app_store.stores[1].$id).toBe("geodeStore") }) }) @@ -72,7 +72,7 @@ describe("App Store", () => { load: vi.fn().mockImplementation(() => {}), } const mock_store_2 = { - $id: "cartStore", + $id: "geodeStore", save: vi.fn().mockImplementation(() => ({ items: [], total: 0 })), load: vi.fn().mockImplementation(() => {}), } @@ -86,7 +86,7 @@ describe("App Store", () => { expect(mock_store_2.save).toHaveBeenCalledTimes(1) expect(snapshot).toEqual({ userStore: { name: "toto", email: "toto@titi.com" }, - cartStore: { items: [], total: 0 }, + geodeStore: { items: [], total: 0 }, }) }) @@ -126,19 +126,19 @@ describe("App Store", () => { const appStore = useAppStore() const userStore = { $id: "userStore", load: vi.fn().mockResolvedValue() } - const cartStore = { $id: "cartStore", load: vi.fn().mockResolvedValue() } + const geodeStore = { $id: "geodeStore", load: vi.fn().mockResolvedValue() } appStore.registerStore(userStore) - appStore.registerStore(cartStore) + appStore.registerStore(geodeStore) const snapshot = { userStore: { some: "data" }, - cartStore: { other: "data" }, + geodeStore: { other: "data" }, } await appStore.load(snapshot) expect(userStore.load).toHaveBeenCalledTimes(1) - expect(cartStore.load).toHaveBeenCalledTimes(1) + expect(geodeStore.load).toHaveBeenCalledTimes(1) }) test("skip stores without load method", () => { From e6c54b5ea6bd7cfa3a85a1d3e806ce53a5cf7e3d Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Tue, 28 Oct 2025 08:24:56 +0000 Subject: [PATCH 04/66] Apply prepare changes --- tests/unit/stores/Appstore.nuxt.test.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/tests/unit/stores/Appstore.nuxt.test.js b/tests/unit/stores/Appstore.nuxt.test.js index c2243ee0..5760a15d 100644 --- a/tests/unit/stores/Appstore.nuxt.test.js +++ b/tests/unit/stores/Appstore.nuxt.test.js @@ -124,13 +124,19 @@ describe("App Store", () => { describe("load", () => { test("App Store > actions > load > load stores with load method", async () => { const appStore = useAppStore() - - const userStore = { $id: "userStore", load: vi.fn().mockResolvedValue() } - const geodeStore = { $id: "geodeStore", load: vi.fn().mockResolvedValue() } - + + const userStore = { + $id: "userStore", + load: vi.fn().mockResolvedValue(), + } + const geodeStore = { + $id: "geodeStore", + load: vi.fn().mockResolvedValue(), + } + appStore.registerStore(userStore) appStore.registerStore(geodeStore) - + const snapshot = { userStore: { some: "data" }, geodeStore: { other: "data" }, From 01a9732d9379bedcb6b9f03503d781713eb8210d Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Tue, 28 Oct 2025 09:39:23 +0100 Subject: [PATCH 05/66] renames and move tests --- plugins/{autoStoreRegister.js => auto_store_register.js} | 0 plugins/{projectManager.js => project_manager.js} | 0 tests/unit/{stores => plugins}/ProjectLoad.nuxt.test.js | 0 tests/unit/{ => plugins}/ProjectManager.nuxt.test.js | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename plugins/{autoStoreRegister.js => auto_store_register.js} (100%) rename plugins/{projectManager.js => project_manager.js} (100%) rename tests/unit/{stores => plugins}/ProjectLoad.nuxt.test.js (100%) rename tests/unit/{ => plugins}/ProjectManager.nuxt.test.js (100%) diff --git a/plugins/autoStoreRegister.js b/plugins/auto_store_register.js similarity index 100% rename from plugins/autoStoreRegister.js rename to plugins/auto_store_register.js diff --git a/plugins/projectManager.js b/plugins/project_manager.js similarity index 100% rename from plugins/projectManager.js rename to plugins/project_manager.js diff --git a/tests/unit/stores/ProjectLoad.nuxt.test.js b/tests/unit/plugins/ProjectLoad.nuxt.test.js similarity index 100% rename from tests/unit/stores/ProjectLoad.nuxt.test.js rename to tests/unit/plugins/ProjectLoad.nuxt.test.js diff --git a/tests/unit/ProjectManager.nuxt.test.js b/tests/unit/plugins/ProjectManager.nuxt.test.js similarity index 100% rename from tests/unit/ProjectManager.nuxt.test.js rename to tests/unit/plugins/ProjectManager.nuxt.test.js From d577721990841ace656cfd26807f48d4a0da5c29 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Wed, 29 Oct 2025 10:31:58 +0100 Subject: [PATCH 06/66] persist upload folder --- utils/local.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils/local.js b/utils/local.js index 3305e71c..d483c121 100644 --- a/utils/local.js +++ b/utils/local.js @@ -118,7 +118,7 @@ async function run_back( return new Promise(async (resolve, reject) => { let upload_folder_path = args.upload_folder_path if (!args.upload_folder_path) { - upload_folder_path = args.project_folder_path + upload_folder_path = path.join(args.project_folder_path, "uploads") } const port = await get_available_port() const back_args = [ @@ -185,6 +185,7 @@ function kill_back(back_port) { function kill_viewer(viewer_port) { return new Promise((resolve) => { + const child = viewerChildren.get(viewer_port) const socket = new WebSocket("ws://localhost:" + viewer_port + "/ws") socket.on("open", () => { console.log("Connected to WebSocket server") From df419ecb4c33149c65c8566704ac407602bbef9a Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Wed, 29 Oct 2025 10:48:08 +0100 Subject: [PATCH 07/66] renamed app_store store to app --- plugins/project_manager.js | 2 +- stores/{app_store.js => app.js} | 0 tests/unit/plugins/ProjectLoad.nuxt.test.js | 2 +- tests/unit/stores/Appstore.nuxt.test.js | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename stores/{app_store.js => app.js} (100%) diff --git a/plugins/project_manager.js b/plugins/project_manager.js index 2caffab6..544a1a89 100644 --- a/plugins/project_manager.js +++ b/plugins/project_manager.js @@ -1,4 +1,4 @@ -import { useAppStore } from "@/stores/app_store.js" +import { useAppStore } from "@/stores/app.js" import { useInfraStore } from "@ogw_f/stores/infra" import { viewer_call } from "@/composables/viewer_call.js" diff --git a/stores/app_store.js b/stores/app.js similarity index 100% rename from stores/app_store.js rename to stores/app.js diff --git a/tests/unit/plugins/ProjectLoad.nuxt.test.js b/tests/unit/plugins/ProjectLoad.nuxt.test.js index 7b99b015..2bc5133d 100644 --- a/tests/unit/plugins/ProjectLoad.nuxt.test.js +++ b/tests/unit/plugins/ProjectLoad.nuxt.test.js @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, test, vi } from "vitest" import { setActivePinia, createPinia } from "pinia" -import { useAppStore } from "@/stores/app_store.js" +import { useAppStore } from "@/stores/app.js" import { useDataBaseStore } from "@/stores/data_base.js" import { useTreeviewStore } from "@/stores/treeview.js" import { useDataStyleStore } from "@/stores/data_style.js" diff --git a/tests/unit/stores/Appstore.nuxt.test.js b/tests/unit/stores/Appstore.nuxt.test.js index 5760a15d..39cc3f0a 100644 --- a/tests/unit/stores/Appstore.nuxt.test.js +++ b/tests/unit/stores/Appstore.nuxt.test.js @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, expectTypeOf, test, vi } from "vitest" import { createTestingPinia } from "@pinia/testing" -import { useAppStore } from "@/stores/app_store.js" +import { useAppStore } from "@/stores/app.js" import { setActivePinia } from "pinia" beforeEach(async () => { From bb9d02dee3b81c9424750ae765ae088801ff8c8e Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Wed, 29 Oct 2025 14:15:38 +0100 Subject: [PATCH 08/66] rm unused var --- utils/local.js | 1 - 1 file changed, 1 deletion(-) diff --git a/utils/local.js b/utils/local.js index d483c121..a467bcd8 100644 --- a/utils/local.js +++ b/utils/local.js @@ -185,7 +185,6 @@ function kill_back(back_port) { function kill_viewer(viewer_port) { return new Promise((resolve) => { - const child = viewerChildren.get(viewer_port) const socket = new WebSocket("ws://localhost:" + viewer_port + "/ws") socket.on("open", () => { console.log("Connected to WebSocket server") From a93225cabdeff473938d08ca7c93ea02358a81d9 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 30 Oct 2025 15:23:58 +0100 Subject: [PATCH 09/66] importStore & exportStore refacto --- plugins/project_manager.js | 4 ++-- stores/app.js | 46 ++++++++++++++++---------------------- stores/data_base.js | 8 +++---- stores/data_style.js | 8 +++---- stores/hybrid_viewer.js | 8 +++---- stores/treeview.js | 10 ++++----- 6 files changed, 38 insertions(+), 46 deletions(-) diff --git a/plugins/project_manager.js b/plugins/project_manager.js index 544a1a89..93345dac 100644 --- a/plugins/project_manager.js +++ b/plugins/project_manager.js @@ -6,7 +6,7 @@ export default defineNuxtPlugin(() => { const appStore = useAppStore() async function exportProject() { - const snapshot = appStore.save() + const snapshot = appStore.exportStore() const blob = new Blob([JSON.stringify(snapshot)], { type: "application/json", }) @@ -22,7 +22,7 @@ export default defineNuxtPlugin(() => { const snapshot = JSON.parse(await file.text()) await useInfraStore().create_connection() viewer_call({}) - await appStore.load(snapshot) + await appStore.importStore(snapshot) } return { diff --git a/stores/app.js b/stores/app.js index e3a3fb2c..e5c289bd 100644 --- a/stores/app.js +++ b/stores/app.js @@ -5,79 +5,71 @@ export const useAppStore = defineStore("app", () => { const isAlreadyRegistered = stores.some( (registeredStore) => registeredStore.$id === store.$id, ) - if (isAlreadyRegistered) { console.log( `[AppStore] Store "${store.$id}" already registered, skipping`, ) return } - console.log("[AppStore] Registering store", store.$id) stores.push(store) } - function save() { + + function exportStore() { const snapshot = {} - let savedCount = 0 + let exportCount = 0 for (const store of stores) { - if (!store.save) { + if (!store.exportStore) { continue } const storeId = store.$id try { - snapshot[storeId] = store.save() - savedCount++ + snapshot[storeId] = store.exportStore() + exportCount++ } catch (error) { - console.error(`[AppStore] Error saving store "${storeId}":`, error) + console.error(`[AppStore] Error exporting store "${storeId}":`, error) } } - - console.log(`[AppStore] Saved ${savedCount} stores`) + console.log(`[AppStore] Exported ${exportCount} stores`) return snapshot } - async function load(snapshot) { + + async function importStore(snapshot) { if (!snapshot) { - console.warn("[AppStore] load called with invalid snapshot") + console.warn("[AppStore] import called with invalid snapshot") return } - - let loadedCount = 0 + let importedCount = 0 const notFoundStores = [] - for (const store of stores) { - if (!store.load) continue - + if (!store.importStore) continue const storeId = store.$id - if (!snapshot[storeId]) { notFoundStores.push(storeId) continue } - try { - await store.load(snapshot[storeId]) - loadedCount++ + await store.importStore(snapshot[storeId]) + importedCount++ } catch (error) { - console.error(`[AppStore] Error loading store "${storeId}":`, error) + console.error(`[AppStore] Error importing store "${storeId}":`, error) } } - if (notFoundStores.length > 0) { console.warn( `[AppStore] Stores not found in snapshot: ${notFoundStores.join(", ")}`, ) } - - console.log(`[AppStore] Loaded ${loadedCount} stores`) + console.log(`[AppStore] Imported ${importedCount} stores`) } return { stores, registerStore, - save, - load, + exportStore, + importStore, } }) diff --git a/stores/data_base.js b/stores/data_base.js index 7f0a9910..5fd7ff51 100644 --- a/stores/data_base.js +++ b/stores/data_base.js @@ -135,11 +135,11 @@ export const useDataBaseStore = defineStore("dataBase", () => { return flat_indexes.filter((index) => index !== null) } - function save() { + function exportStore() { return { db: JSON.parse(JSON.stringify(db)) } } - async function load(snapshot) { + async function importStore(snapshot) { const entries = snapshot?.db || {} const hybrid_store = useHybridViewerStore() await hybrid_store.initHybridViewer() @@ -164,7 +164,7 @@ export const useDataBaseStore = defineStore("dataBase", () => { getSurfacesUuids, getBlocksUuids, getFlatIndexes, - save, - load, + exportStore, + importStore, } }) diff --git a/stores/data_style.js b/stores/data_style.js index 99318907..251347d8 100644 --- a/stores/data_style.js +++ b/stores/data_style.js @@ -46,11 +46,11 @@ export const useDataStyleStore = defineStore("dataStyle", () => { return modelStyleStore.modelMeshComponentVisibility(id, "Edge", null) } - function save() { + function exportStore() { return { styles: dataStyleState.styles } } - async function load(snapshot) { + async function importStore(snapshot) { dataStyleState.styles = snapshot?.styles || {} } @@ -60,8 +60,8 @@ export const useDataStyleStore = defineStore("dataStyle", () => { setVisibility, setModelEdgesVisibility, modelEdgesVisibility, - save, - load, + exportStore, + importStore, ...meshStyleStore, ...modelStyleStore, } diff --git a/stores/hybrid_viewer.js b/stores/hybrid_viewer.js index b078a9c2..e069e7bf 100644 --- a/stores/hybrid_viewer.js +++ b/stores/hybrid_viewer.js @@ -192,11 +192,11 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { Object.keys(db).forEach((id) => delete db[id]) } - function save() { + function exportStore() { return { zScale: zScale.value } } - async function load(snapshot) { + async function importStore(snapshot) { const z_scale = snapshot?.zScale if (z_scale != null) { await setZScaling(z_scale) @@ -215,7 +215,7 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { setContainer, zScale, clear, - save, - load, + exportStore, + importStore, } }) diff --git a/stores/treeview.js b/stores/treeview.js index 145446be..bf9f25eb 100644 --- a/stores/treeview.js +++ b/stores/treeview.js @@ -53,18 +53,18 @@ export const useTreeviewStore = defineStore("treeview", () => { panelWidth.value = width } - function save() { + function exportStore() { return { isAdditionnalTreeDisplayed: isAdditionnalTreeDisplayed.value, panelWidth: panelWidth.value, model_id: model_id.value, isTreeCollection: isTreeCollection.value, selectedTree: selectedTree.value, - selection: selection.value, // Keep for UX ? + selection: selection.value, } } - function load(snapshot) { + async function importStore(snapshot) { selection.value = snapshot?.selection || [] isAdditionnalTreeDisplayed.value = snapshot?.isAdditionnalTreeDisplayed || false @@ -87,7 +87,7 @@ export const useTreeviewStore = defineStore("treeview", () => { displayFileTree, toggleTreeView, setPanelWidth, - save, - load, + exportStore, + importStore, } }) From af41f9dff8cb1268d1e48fdc5be7b872bb03f478 Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Thu, 30 Oct 2025 14:24:51 +0000 Subject: [PATCH 10/66] Apply prepare changes --- stores/app.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/stores/app.js b/stores/app.js index e5c289bd..0af09ad5 100644 --- a/stores/app.js +++ b/stores/app.js @@ -15,13 +15,12 @@ export const useAppStore = defineStore("app", () => { stores.push(store) } - function exportStore() { const snapshot = {} let exportCount = 0 for (const store of stores) { - if (!store.exportStore) { + if (!store.exportStore) { continue } const storeId = store.$id @@ -36,7 +35,6 @@ export const useAppStore = defineStore("app", () => { return snapshot } - async function importStore(snapshot) { if (!snapshot) { console.warn("[AppStore] import called with invalid snapshot") From ea63e2bd726e1c1e37b36ad4931a6e5189c54c11 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 30 Oct 2025 16:27:15 +0100 Subject: [PATCH 11/66] testes with import/export --- tests/unit/plugins/ProjectLoad.nuxt.test.js | 16 ++-- .../unit/plugins/ProjectManager.nuxt.test.js | 15 ++-- tests/unit/stores/Appstore.nuxt.test.js | 88 ++++++++----------- 3 files changed, 52 insertions(+), 67 deletions(-) diff --git a/tests/unit/plugins/ProjectLoad.nuxt.test.js b/tests/unit/plugins/ProjectLoad.nuxt.test.js index 2bc5133d..b0d197ad 100644 --- a/tests/unit/plugins/ProjectLoad.nuxt.test.js +++ b/tests/unit/plugins/ProjectLoad.nuxt.test.js @@ -25,8 +25,8 @@ vi.mock("@/stores/hybrid_viewer.js", () => ({ beforeEach(() => setActivePinia(createPinia())) -describe("Project load", () => { - test("appStore.load restores stores", async () => { +describe("Project import", () => { + test("app.importStore restores stores", async () => { const stores = { app: useAppStore(), dataBase: useDataBaseStore(), @@ -65,24 +65,24 @@ describe("Project load", () => { hybridViewer: { zScale: 1.5 }, } - console.log("[TEST ProjectLoad] Snapshot keys:", Object.keys(snapshot)) + console.log("[TEST ProjectImport] Snapshot keys:", Object.keys(snapshot)) console.log( - "[TEST ProjectLoad] treeview snapshot:", + "[TEST ProjectImport] treeview snapshot:", JSON.stringify(snapshot.treeview, null, 2), ) console.log( - "[TEST ProjectLoad] dataStyle snapshot:", + "[TEST ProjectImport] dataStyle snapshot:", JSON.stringify(snapshot.dataStyle, null, 2), ) - await stores.app.load(snapshot) + await stores.app.importStore(snapshot) console.log( - "[TEST ProjectLoad] Treeview items after load:", + "[TEST ProjectImport] Treeview items after import:", JSON.stringify(stores.treeview.items, null, 2), ) console.log( - "[TEST ProjectLoad] Styles after load:", + "[TEST ProjectImport] Styles after import:", JSON.stringify(stores.dataStyle.styles, null, 2), ) diff --git a/tests/unit/plugins/ProjectManager.nuxt.test.js b/tests/unit/plugins/ProjectManager.nuxt.test.js index e63b2bda..fdbb8727 100644 --- a/tests/unit/plugins/ProjectManager.nuxt.test.js +++ b/tests/unit/plugins/ProjectManager.nuxt.test.js @@ -4,8 +4,8 @@ import { viewer_call } from "@/composables/viewer_call.js" // Mocks const mockAppStore = { - save: vi.fn(() => ({ projectName: "mockedProject" })), - load: vi.fn(), + exportStore: vi.fn(() => ({ projectName: "mockedProject" })), + importStore: vi.fn(), } const mockInfraStore = { create_connection: vi.fn() } @@ -37,7 +37,7 @@ describe("ProjectManager plugin", () => { }) projectManagerPlugin.provide.project.export = async () => { - const snapshot = mockAppStore.save() + const snapshot = mockAppStore.exportStore() const json = JSON.stringify(snapshot, null, 2) const blob = new Blob([json], { type: "application/json" }) const url = URL.createObjectURL(blob) @@ -48,9 +48,8 @@ describe("ProjectManager plugin", () => { URL.revokeObjectURL(url) console.log("[TEST] URL's project :", { a }) } - await projectManagerPlugin.provide.project.export() - expect(mockAppStore.save).toHaveBeenCalled() + expect(mockAppStore.exportStore).toHaveBeenCalled() expect(mockElement.click).toHaveBeenCalled() }) @@ -65,14 +64,12 @@ describe("ProjectManager plugin", () => { viewer_call.mock?.calls, viewer_call.mock?.calls?.length || 0, ) - await mockAppStore.load(snapshot) + await mockAppStore.importStore(snapshot) } - const file = { text: () => Promise.resolve('{"dataBase":{"db":{}}}') } await projectManagerPlugin.provide.project.importFile(file) - expect(mockInfraStore.create_connection).toHaveBeenCalled() expect(viewer_call).toHaveBeenCalled() - expect(mockAppStore.load).toHaveBeenCalled() + expect(mockAppStore.importStore).toHaveBeenCalled() }) }) diff --git a/tests/unit/stores/Appstore.nuxt.test.js b/tests/unit/stores/Appstore.nuxt.test.js index 39cc3f0a..f00c378e 100644 --- a/tests/unit/stores/Appstore.nuxt.test.js +++ b/tests/unit/stores/Appstore.nuxt.test.js @@ -16,8 +16,8 @@ describe("App Store", () => { test("initial state", () => { const app_store = useAppStore() expectTypeOf(app_store.stores).toBeArray() - expectTypeOf(app_store.save).toBeFunction() - expectTypeOf(app_store.load).toBeFunction() + expectTypeOf(app_store.exportStore).toBeFunction() + expectTypeOf(app_store.importStore).toBeFunction() expectTypeOf(app_store.registerStore).toBeFunction() }) }) @@ -60,54 +60,54 @@ describe("App Store", () => { }) }) - describe("save", () => { - test("save stores with save method", () => { + describe("Export", () => { + test("export stores with exportStore method", () => { const app_store = useAppStore() const mock_store_1 = { $id: "userStore", - save: vi.fn().mockImplementation(() => ({ + exportStore: vi.fn().mockImplementation(() => ({ name: "toto", email: "toto@titi.com", })), - load: vi.fn().mockImplementation(() => {}), + importStore: vi.fn().mockImplementation(() => {}), } const mock_store_2 = { $id: "geodeStore", - save: vi.fn().mockImplementation(() => ({ items: [], total: 0 })), - load: vi.fn().mockImplementation(() => {}), + exportStore: vi.fn().mockImplementation(() => ({ items: [], total: 0 })), + importStore: vi.fn().mockImplementation(() => {}), } app_store.registerStore(mock_store_1) app_store.registerStore(mock_store_2) - const snapshot = app_store.save() + const snapshot = app_store.exportStore() - expect(mock_store_1.save).toHaveBeenCalledTimes(1) - expect(mock_store_2.save).toHaveBeenCalledTimes(1) + expect(mock_store_1.exportStore).toHaveBeenCalledTimes(1) + expect(mock_store_2.exportStore).toHaveBeenCalledTimes(1) expect(snapshot).toEqual({ userStore: { name: "toto", email: "toto@titi.com" }, geodeStore: { items: [], total: 0 }, }) }) - test("skip stores without save method", () => { + test("skip stores without exportSave method", () => { const app_store = useAppStore() const mock_store_1 = { $id: "withSave", - save: vi.fn().mockImplementation(() => ({ data: "test" })), - load: vi.fn().mockImplementation(() => {}), + exportStore: vi.fn().mockImplementation(() => ({ data: "test" })), + importStore: vi.fn().mockImplementation(() => {}), } const mock_store_2 = { $id: "withoutSave", - load: vi.fn().mockImplementation(() => {}), + importStore: vi.fn().mockImplementation(() => {}), } app_store.registerStore(mock_store_1) app_store.registerStore(mock_store_2) - const snapshot = app_store.save() + const snapshot = app_store.exportStore() - expect(mock_store_1.save).toHaveBeenCalledTimes(1) + expect(mock_store_1.exportStore).toHaveBeenCalledTimes(1) expect(snapshot).toEqual({ withSave: { data: "test" }, }) @@ -116,76 +116,64 @@ describe("App Store", () => { test("return empty snapshot when no stores registered", () => { const app_store = useAppStore() - const snapshot = app_store.save() + const snapshot = app_store.exportStore() expect(snapshot).toEqual({}) }) }) describe("load", () => { - test("App Store > actions > load > load stores with load method", async () => { + test("App Store > actions > importStore > import stores with importStore method", async () => { const appStore = useAppStore() - const userStore = { $id: "userStore", - load: vi.fn().mockResolvedValue(), + importStore: vi.fn().mockResolvedValue(), } const geodeStore = { $id: "geodeStore", - load: vi.fn().mockResolvedValue(), + importStore: vi.fn().mockResolvedValue(), } - appStore.registerStore(userStore) appStore.registerStore(geodeStore) - const snapshot = { userStore: { some: "data" }, geodeStore: { other: "data" }, } - - await appStore.load(snapshot) - expect(userStore.load).toHaveBeenCalledTimes(1) - expect(geodeStore.load).toHaveBeenCalledTimes(1) + await appStore.importStore(snapshot) + expect(userStore.importStore).toHaveBeenCalledTimes(1) + expect(geodeStore.importStore).toHaveBeenCalledTimes(1) }) - - test("skip stores without load method", () => { + + test("skip stores without importStore method", () => { const app_store = useAppStore() const mock_store_1 = { - $id: "withLoad", + $id: "withImport", save: vi.fn().mockImplementation(() => {}), - load: vi.fn().mockImplementation(() => {}), + importStore: vi.fn().mockImplementation(() => {}), } const mock_store_2 = { - $id: "withoutLoad", + $id: "withoutImport", save: vi.fn().mockImplementation(() => {}), } - app_store.registerStore(mock_store_1) app_store.registerStore(mock_store_2) - const snapshot = { - withLoad: { data: "test" }, - withoutLoad: { data: "ignored" }, + withImport: { data: "test" }, + withoutImport: { data: "ignored" }, } - - app_store.load(snapshot) - - expect(mock_store_1.load).toHaveBeenCalledTimes(1) - expect(mock_store_2.load).toBeUndefined() + app_store.importStore(snapshot) + expect(mock_store_1.importStore).toHaveBeenCalledTimes(1) + expect(mock_store_2.importStore).toBeUndefined() }) - + test("warn when store not found in snapshot", () => { const app_store = useAppStore() - const console_warn_spy = vi - .spyOn(console, "warn") - .mockImplementation(() => {}) + const console_warn_spy = vi.spyOn(console, "warn").mockImplementation(() => {}) const mock_store = { $id: "testStore", - load: vi.fn().mockImplementation(() => {}), + importStore: vi.fn().mockImplementation(() => {}), } - app_store.registerStore(mock_store) - app_store.load({}) - + app_store.importStore({}) expect(console_warn_spy).toHaveBeenCalledWith( expect.stringContaining("Stores not found in snapshot: testStore"), ) From 63464b714cdc8f258c4f05f5410b58f764823ee1 Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Thu, 30 Oct 2025 15:28:12 +0000 Subject: [PATCH 12/66] Apply prepare changes --- tests/unit/stores/Appstore.nuxt.test.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/unit/stores/Appstore.nuxt.test.js b/tests/unit/stores/Appstore.nuxt.test.js index f00c378e..f5931cce 100644 --- a/tests/unit/stores/Appstore.nuxt.test.js +++ b/tests/unit/stores/Appstore.nuxt.test.js @@ -73,7 +73,9 @@ describe("App Store", () => { } const mock_store_2 = { $id: "geodeStore", - exportStore: vi.fn().mockImplementation(() => ({ items: [], total: 0 })), + exportStore: vi + .fn() + .mockImplementation(() => ({ items: [], total: 0 })), importStore: vi.fn().mockImplementation(() => {}), } @@ -142,7 +144,7 @@ describe("App Store", () => { expect(userStore.importStore).toHaveBeenCalledTimes(1) expect(geodeStore.importStore).toHaveBeenCalledTimes(1) }) - + test("skip stores without importStore method", () => { const app_store = useAppStore() const mock_store_1 = { @@ -164,10 +166,12 @@ describe("App Store", () => { expect(mock_store_1.importStore).toHaveBeenCalledTimes(1) expect(mock_store_2.importStore).toBeUndefined() }) - + test("warn when store not found in snapshot", () => { const app_store = useAppStore() - const console_warn_spy = vi.spyOn(console, "warn").mockImplementation(() => {}) + const console_warn_spy = vi + .spyOn(console, "warn") + .mockImplementation(() => {}) const mock_store = { $id: "testStore", importStore: vi.fn().mockImplementation(() => {}), From 98991cf1bf1b61c67b79bf45df736d1b4d33fcbf Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 30 Oct 2025 17:02:43 +0100 Subject: [PATCH 13/66] test --- composables/project_manager.js | 66 ++++++++++++++ plugins/project_manager.js | 33 ------- .../unit/plugins/ProjectManager.nuxt.test.js | 89 +++++++++++-------- 3 files changed, 118 insertions(+), 70 deletions(-) create mode 100644 composables/project_manager.js delete mode 100644 plugins/project_manager.js diff --git a/composables/project_manager.js b/composables/project_manager.js new file mode 100644 index 00000000..e6dc6539 --- /dev/null +++ b/composables/project_manager.js @@ -0,0 +1,66 @@ +import { useAppStore } from "@/stores/app.js" +import { useInfraStore } from "@ogw_f/stores/infra" +import { viewer_call } from "@/composables/viewer_call.js" +import { useGeodeStore } from "@/stores/geode.js" +import back_schemas from "@geode/opengeodeweb-back/opengeodeweb_back_schemas.json" +import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json" + +export function useProjectManager() { + const appStore = useAppStore() + const geode = useGeodeStore() + + async function exportProject() { + geode.start_request() + try { + await useInfraStore().create_connection() + const snapshot = appStore.exportStore() + + const schema = back_schemas.opengeodeweb_back.project.export_project + const url = `${geode.base_url}${schema.route}` + const method = schema.methods[0] + + const response = await fetch(url, { + method, + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ snapshot }), + }) + if (!response.ok) { + throw new Error(`Export failed: ${response.statusText}`) + } + const blob = await response.blob() + const filename = + response.headers.get("new-file-name") + const urlObject = URL.createObjectURL(blob) + const a = document.createElement("a") + a.href = urlObject + a.download = filename + a.click() + URL.revokeObjectURL(urlObject) + } finally { + geode.stop_request() + } + } + + async function importProjectFile(file) { + geode.start_request() + try { + const snapshot = JSON.parse(await file.text()) + await useInfraStore().create_connection() + + await viewer_call({ + schema: viewer_schemas.opengeodeweb_viewer.utils.import_project, + params: {}, + }) + await viewer_call({ + schema: viewer_schemas.opengeodeweb_viewer.viewer.reset_visualization, + params: {}, + }) + + await appStore.importStore(snapshot) + } finally { + geode.stop_request() + } + } + + return { exportProject, importProjectFile } +} \ No newline at end of file diff --git a/plugins/project_manager.js b/plugins/project_manager.js deleted file mode 100644 index 93345dac..00000000 --- a/plugins/project_manager.js +++ /dev/null @@ -1,33 +0,0 @@ -import { useAppStore } from "@/stores/app.js" -import { useInfraStore } from "@ogw_f/stores/infra" -import { viewer_call } from "@/composables/viewer_call.js" - -export default defineNuxtPlugin(() => { - const appStore = useAppStore() - - async function exportProject() { - const snapshot = appStore.exportStore() - const blob = new Blob([JSON.stringify(snapshot)], { - type: "application/json", - }) - const url = URL.createObjectURL(blob) - const a = document.createElement("a") - a.href = url - a.download = `project_${Date.now()}.json` - a.click() - URL.revokeObjectURL(url) - } - - async function importProjectFile(file) { - const snapshot = JSON.parse(await file.text()) - await useInfraStore().create_connection() - viewer_call({}) - await appStore.importStore(snapshot) - } - - return { - provide: { - project: { export: exportProject, importFile: importProjectFile }, - }, - } -}) diff --git a/tests/unit/plugins/ProjectManager.nuxt.test.js b/tests/unit/plugins/ProjectManager.nuxt.test.js index fdbb8727..5db04956 100644 --- a/tests/unit/plugins/ProjectManager.nuxt.test.js +++ b/tests/unit/plugins/ProjectManager.nuxt.test.js @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, test, vi } from "vitest" import { setActivePinia, createPinia } from "pinia" -import { viewer_call } from "@/composables/viewer_call.js" +import { useProjectManager } from "@/composables/project_manager.js" // Mocks const mockAppStore = { @@ -9,25 +9,49 @@ const mockAppStore = { } const mockInfraStore = { create_connection: vi.fn() } -vi.mock("@/stores/app_store.js", () => ({ useAppStore: () => mockAppStore })) +vi.mock("@/stores/app.js", () => ({ useAppStore: () => mockAppStore })) vi.mock("@ogw_f/stores/infra", () => ({ useInfraStore: () => mockInfraStore })) vi.mock("@/composables/viewer_call.js", () => ({ default: vi.fn(), viewer_call: vi.fn(), })) -const projectManagerPlugin = { - provide: { - project: { - export: vi.fn(), - importFile: vi.fn(), +vi.mock("@/stores/geode.js", () => ({ + useGeodeStore: () => ({ + base_url: "http://localhost:5000", + start_request: vi.fn(), + stop_request: vi.fn(), + do_ping: vi.fn(), + }), +})) + +vi.mock( + "@geode/opengeodeweb-back/opengeodeweb_back_schemas.json", + () => ({ + default: { + opengeodeweb_back: { + project: { + export_project: { route: "/project/export_project", methods: ["POST"] }, + }, + }, }, - }, -} + }), +) +vi.mock( + "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json", + () => ({ + default: { + opengeodeweb_viewer: { + utils: { import_project: { rpc: "utils.import_project" } }, + viewer: { reset_visualization: { rpc: "viewer.reset_visualization" } }, + }, + }, + }), +) beforeEach(() => setActivePinia(createPinia())) -describe("ProjectManager plugin", () => { +describe("ProjectManager composable", () => { test("exportProject triggers download", async () => { const mockElement = { href: "", download: "", click: vi.fn() } vi.stubGlobal("document", { createElement: () => mockElement }) @@ -35,41 +59,32 @@ describe("ProjectManager plugin", () => { createObjectURL: () => "blob:url", revokeObjectURL: vi.fn(), }) + vi.stubGlobal( + "fetch", + vi.fn(async () => ({ + ok: true, + blob: async () => + new Blob(["zipcontent"], { type: "application/zip" }), + headers: { get: () => "project_123.zip" }, + })), + ) + + const { exportProject } = useProjectManager() + await exportProject() - projectManagerPlugin.provide.project.export = async () => { - const snapshot = mockAppStore.exportStore() - const json = JSON.stringify(snapshot, null, 2) - const blob = new Blob([json], { type: "application/json" }) - const url = URL.createObjectURL(blob) - const a = document.createElement("a") - a.href = url - a.download = `project_${Date.now()}.json` - a.click() - URL.revokeObjectURL(url) - console.log("[TEST] URL's project :", { a }) - } - await projectManagerPlugin.provide.project.export() expect(mockAppStore.exportStore).toHaveBeenCalled() + expect(fetch).toHaveBeenCalledTimes(1) expect(mockElement.click).toHaveBeenCalled() }) test("importProjectFile loads snapshot", async () => { - projectManagerPlugin.provide.project.importFile = async (file) => { - const raw = await file.text() - const snapshot = JSON.parse(raw) - await mockInfraStore.create_connection() - viewer_call({}) - console.log( - "[TEST] viewer_call:", - viewer_call.mock?.calls, - viewer_call.mock?.calls?.length || 0, - ) - await mockAppStore.importStore(snapshot) - } + const { importProjectFile } = useProjectManager() + const file = { text: () => Promise.resolve('{"dataBase":{"db":{}}}') } - await projectManagerPlugin.provide.project.importFile(file) + await importProjectFile(file) + expect(mockInfraStore.create_connection).toHaveBeenCalled() - expect(viewer_call).toHaveBeenCalled() + expect((await import("@/composables/viewer_call.js")).viewer_call).toHaveBeenCalled() expect(mockAppStore.importStore).toHaveBeenCalled() }) }) From 35c75dd9fde17c7299a51418cc27984d49ad49ef Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Thu, 30 Oct 2025 16:03:40 +0000 Subject: [PATCH 14/66] Apply prepare changes --- composables/project_manager.js | 5 +-- .../unit/plugins/ProjectManager.nuxt.test.js | 41 ++++++++----------- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/composables/project_manager.js b/composables/project_manager.js index e6dc6539..c0ff620d 100644 --- a/composables/project_manager.js +++ b/composables/project_manager.js @@ -28,8 +28,7 @@ export function useProjectManager() { throw new Error(`Export failed: ${response.statusText}`) } const blob = await response.blob() - const filename = - response.headers.get("new-file-name") + const filename = response.headers.get("new-file-name") const urlObject = URL.createObjectURL(blob) const a = document.createElement("a") a.href = urlObject @@ -63,4 +62,4 @@ export function useProjectManager() { } return { exportProject, importProjectFile } -} \ No newline at end of file +} diff --git a/tests/unit/plugins/ProjectManager.nuxt.test.js b/tests/unit/plugins/ProjectManager.nuxt.test.js index 5db04956..80b010aa 100644 --- a/tests/unit/plugins/ProjectManager.nuxt.test.js +++ b/tests/unit/plugins/ProjectManager.nuxt.test.js @@ -25,29 +25,23 @@ vi.mock("@/stores/geode.js", () => ({ }), })) -vi.mock( - "@geode/opengeodeweb-back/opengeodeweb_back_schemas.json", - () => ({ - default: { - opengeodeweb_back: { - project: { - export_project: { route: "/project/export_project", methods: ["POST"] }, - }, +vi.mock("@geode/opengeodeweb-back/opengeodeweb_back_schemas.json", () => ({ + default: { + opengeodeweb_back: { + project: { + export_project: { route: "/project/export_project", methods: ["POST"] }, }, }, - }), -) -vi.mock( - "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json", - () => ({ - default: { - opengeodeweb_viewer: { - utils: { import_project: { rpc: "utils.import_project" } }, - viewer: { reset_visualization: { rpc: "viewer.reset_visualization" } }, - }, + }, +})) +vi.mock("@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json", () => ({ + default: { + opengeodeweb_viewer: { + utils: { import_project: { rpc: "utils.import_project" } }, + viewer: { reset_visualization: { rpc: "viewer.reset_visualization" } }, }, - }), -) + }, +})) beforeEach(() => setActivePinia(createPinia())) @@ -63,8 +57,7 @@ describe("ProjectManager composable", () => { "fetch", vi.fn(async () => ({ ok: true, - blob: async () => - new Blob(["zipcontent"], { type: "application/zip" }), + blob: async () => new Blob(["zipcontent"], { type: "application/zip" }), headers: { get: () => "project_123.zip" }, })), ) @@ -84,7 +77,9 @@ describe("ProjectManager composable", () => { await importProjectFile(file) expect(mockInfraStore.create_connection).toHaveBeenCalled() - expect((await import("@/composables/viewer_call.js")).viewer_call).toHaveBeenCalled() + expect( + (await import("@/composables/viewer_call.js")).viewer_call, + ).toHaveBeenCalled() expect(mockAppStore.importStore).toHaveBeenCalled() }) }) From 22a67feee13a22a27a28833a5724669f57167939 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 30 Oct 2025 17:07:30 +0100 Subject: [PATCH 15/66] test --- .../ProjectManager.nuxt.test.js | 57 +++++++++++-------- 1 file changed, 33 insertions(+), 24 deletions(-) rename tests/unit/{plugins => composables}/ProjectManager.nuxt.test.js (56%) diff --git a/tests/unit/plugins/ProjectManager.nuxt.test.js b/tests/unit/composables/ProjectManager.nuxt.test.js similarity index 56% rename from tests/unit/plugins/ProjectManager.nuxt.test.js rename to tests/unit/composables/ProjectManager.nuxt.test.js index 5db04956..1d0ab608 100644 --- a/tests/unit/plugins/ProjectManager.nuxt.test.js +++ b/tests/unit/composables/ProjectManager.nuxt.test.js @@ -1,5 +1,6 @@ import { beforeEach, describe, expect, test, vi } from "vitest" -import { setActivePinia, createPinia } from "pinia" +import { setActivePinia } from "pinia" +import { createTestingPinia } from "@pinia/testing" import { useProjectManager } from "@/composables/project_manager.js" // Mocks @@ -12,10 +13,19 @@ const mockInfraStore = { create_connection: vi.fn() } vi.mock("@/stores/app.js", () => ({ useAppStore: () => mockAppStore })) vi.mock("@ogw_f/stores/infra", () => ({ useInfraStore: () => mockInfraStore })) vi.mock("@/composables/viewer_call.js", () => ({ - default: vi.fn(), viewer_call: vi.fn(), })) +beforeEach(async () => { + const pinia = createTestingPinia({ + stubActions: false, + createSpy: vi.fn, + }) + setActivePinia(pinia) + const geode_store = useGeodeStore() + await geode_store.$reset() + geode_store.base_url = "" +}) vi.mock("@/stores/geode.js", () => ({ useGeodeStore: () => ({ base_url: "http://localhost:5000", @@ -49,32 +59,27 @@ vi.mock( }), ) -beforeEach(() => setActivePinia(createPinia())) - describe("ProjectManager composable", () => { test("exportProject triggers download", async () => { - const mockElement = { href: "", download: "", click: vi.fn() } - vi.stubGlobal("document", { createElement: () => mockElement }) - vi.stubGlobal("URL", { - createObjectURL: () => "blob:url", - revokeObjectURL: vi.fn(), + const clickSpy = vi + .spyOn(HTMLAnchorElement.prototype, "click") + .mockImplementation(() => {}) + vi.spyOn(URL, "createObjectURL").mockReturnValue("blob:url") + vi.spyOn(URL, "revokeObjectURL").mockImplementation(() => {}) + const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue({ + ok: true, + blob: async () => new Blob(["zipcontent"], { type: "application/zip" }), + headers: { get: () => 'attachment; filename="project_123.zip"' }, + statusText: "OK", }) - vi.stubGlobal( - "fetch", - vi.fn(async () => ({ - ok: true, - blob: async () => - new Blob(["zipcontent"], { type: "application/zip" }), - headers: { get: () => "project_123.zip" }, - })), - ) const { exportProject } = useProjectManager() await exportProject() - expect(mockAppStore.exportStore).toHaveBeenCalled() - expect(fetch).toHaveBeenCalledTimes(1) - expect(mockElement.click).toHaveBeenCalled() + const app_store = useAppStore() + expect(app_store.exportStore).toHaveBeenCalled() + expect(fetchSpy).toHaveBeenCalledTimes(1) + expect(clickSpy).toHaveBeenCalled() }) test("importProjectFile loads snapshot", async () => { @@ -83,8 +88,12 @@ describe("ProjectManager composable", () => { const file = { text: () => Promise.resolve('{"dataBase":{"db":{}}}') } await importProjectFile(file) - expect(mockInfraStore.create_connection).toHaveBeenCalled() - expect((await import("@/composables/viewer_call.js")).viewer_call).toHaveBeenCalled() - expect(mockAppStore.importStore).toHaveBeenCalled() + const infra_store = useInfraStore() + const app_store = useAppStore() + const { viewer_call } = await import("@/composables/viewer_call.js") + + expect(infra_store.create_connection).toHaveBeenCalled() + expect(viewer_call).toHaveBeenCalled() + expect(app_store.importStore).toHaveBeenCalled() }) }) From 18fc8850ecb51b369ef2dac8fcf10eff1f32d876 Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Thu, 30 Oct 2025 16:09:45 +0000 Subject: [PATCH 16/66] Apply prepare changes --- .../composables/ProjectManager.nuxt.test.js | 34 ++++++++----------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/tests/unit/composables/ProjectManager.nuxt.test.js b/tests/unit/composables/ProjectManager.nuxt.test.js index 1d0ab608..19486f15 100644 --- a/tests/unit/composables/ProjectManager.nuxt.test.js +++ b/tests/unit/composables/ProjectManager.nuxt.test.js @@ -35,29 +35,23 @@ vi.mock("@/stores/geode.js", () => ({ }), })) -vi.mock( - "@geode/opengeodeweb-back/opengeodeweb_back_schemas.json", - () => ({ - default: { - opengeodeweb_back: { - project: { - export_project: { route: "/project/export_project", methods: ["POST"] }, - }, +vi.mock("@geode/opengeodeweb-back/opengeodeweb_back_schemas.json", () => ({ + default: { + opengeodeweb_back: { + project: { + export_project: { route: "/project/export_project", methods: ["POST"] }, }, }, - }), -) -vi.mock( - "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json", - () => ({ - default: { - opengeodeweb_viewer: { - utils: { import_project: { rpc: "utils.import_project" } }, - viewer: { reset_visualization: { rpc: "viewer.reset_visualization" } }, - }, + }, +})) +vi.mock("@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json", () => ({ + default: { + opengeodeweb_viewer: { + utils: { import_project: { rpc: "utils.import_project" } }, + viewer: { reset_visualization: { rpc: "viewer.reset_visualization" } }, }, - }), -) + }, +})) describe("ProjectManager composable", () => { test("exportProject triggers download", async () => { From d4a1cd67e2682df28dcf04744efffe7e674009e0 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Fri, 31 Oct 2025 11:46:20 +0100 Subject: [PATCH 17/66] exportStores/importStores and createTestingPinia --- composables/project_manager.js | 10 +- package-lock.json | 172 ++++++++++++++---- stores/app.js | 16 +- stores/data_base.js | 9 +- stores/data_style.js | 8 +- stores/hybrid_viewer.js | 8 +- stores/treeview.js | 13 +- .../composables/ProjectManager.nuxt.test.js | 18 +- tests/unit/plugins/ProjectLoad.nuxt.test.js | 16 +- tests/unit/stores/Appstore.nuxt.test.js | 58 +++--- 10 files changed, 209 insertions(+), 119 deletions(-) diff --git a/composables/project_manager.js b/composables/project_manager.js index c0ff620d..d10e941b 100644 --- a/composables/project_manager.js +++ b/composables/project_manager.js @@ -1,7 +1,3 @@ -import { useAppStore } from "@/stores/app.js" -import { useInfraStore } from "@ogw_f/stores/infra" -import { viewer_call } from "@/composables/viewer_call.js" -import { useGeodeStore } from "@/stores/geode.js" import back_schemas from "@geode/opengeodeweb-back/opengeodeweb_back_schemas.json" import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json" @@ -13,10 +9,10 @@ export function useProjectManager() { geode.start_request() try { await useInfraStore().create_connection() - const snapshot = appStore.exportStore() + const snapshot = appStore.exportStores() const schema = back_schemas.opengeodeweb_back.project.export_project - const url = `${geode.base_url}${schema.route}` + const url = `${geode.base_url}${schema.$id}` const method = schema.methods[0] const response = await fetch(url, { @@ -55,7 +51,7 @@ export function useProjectManager() { params: {}, }) - await appStore.importStore(snapshot) + await appStore.importStores(snapshot) } finally { geode.stop_request() } diff --git a/package-lock.json b/package-lock.json index 033add59..507abcd2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -130,7 +130,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -253,6 +252,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "regexpu-core": "^6.2.0", @@ -270,6 +270,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", + "peer": true, "bin": { "semver": "bin/semver.js" } @@ -279,6 +280,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", @@ -368,6 +370,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-wrap-function": "^7.27.1", @@ -442,6 +445,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz", "integrity": "sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/template": "^7.27.1", "@babel/traverse": "^7.27.1", @@ -484,6 +488,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.27.1" @@ -500,6 +505,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -515,6 +521,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -530,6 +537,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", @@ -547,6 +555,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz", "integrity": "sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.27.1" @@ -580,6 +589,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", "license": "MIT", + "peer": true, "engines": { "node": ">=6.9.0" }, @@ -607,6 +617,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -679,6 +690,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" @@ -695,6 +707,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -710,6 +723,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-remap-async-to-generator": "^7.27.1", @@ -727,6 +741,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", @@ -744,6 +759,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -759,6 +775,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.0.tgz", "integrity": "sha512-gKKnwjpdx5sER/wl0WN0efUBFzF/56YZO0RJrSYP4CljXnP31ByY7fol89AzomdlLNzI36AvOTmYHsnZTCkq8Q==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -774,6 +791,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -790,6 +808,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz", "integrity": "sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -806,6 +825,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.0.tgz", "integrity": "sha512-IjM1IoJNw72AZFlj33Cu8X0q2XK/6AaVC3jQu+cgQ5lThWD5ajnuUAml80dqRmOhmPkTH8uAwnpMu9Rvj0LTRA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-compilation-targets": "^7.27.2", @@ -826,6 +846,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/template": "^7.27.1" @@ -842,6 +863,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz", "integrity": "sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.0" @@ -858,6 +880,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -874,6 +897,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -889,6 +913,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -905,6 +930,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -920,6 +946,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-transform-destructuring": "^7.28.0" @@ -936,6 +963,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -951,6 +979,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -966,6 +995,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" @@ -982,6 +1012,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-compilation-targets": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", @@ -999,6 +1030,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1014,6 +1046,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1029,6 +1062,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1044,6 +1078,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1059,6 +1094,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -1075,6 +1111,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -1091,6 +1128,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", @@ -1109,6 +1147,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -1125,6 +1164,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -1141,6 +1181,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1156,6 +1197,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1171,6 +1213,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1186,6 +1229,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.0.tgz", "integrity": "sha512-9VNGikXxzu5eCiQjdE4IZn8sb9q7Xsk5EXLDBKUYg1e/Tve8/05+KJEtcxGxAgCY5t/BpKQM+JEL/yT4tvgiUA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", @@ -1205,6 +1249,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1" @@ -1221,6 +1266,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1236,6 +1282,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" @@ -1252,6 +1299,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1267,6 +1315,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -1283,6 +1332,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-create-class-features-plugin": "^7.27.1", @@ -1300,6 +1350,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1315,6 +1366,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.1.tgz", "integrity": "sha512-P0QiV/taaa3kXpLY+sXla5zec4E+4t4Aqc9ggHlfZ7a2cp8/x/Gv08jfwEtn9gnnYIMvHx6aoOZ8XJL8eU71Dg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1330,6 +1382,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -1346,6 +1399,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1361,6 +1415,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1376,6 +1431,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" @@ -1392,6 +1448,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1407,6 +1464,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1422,6 +1480,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1456,6 +1515,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1471,6 +1531,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -1487,6 +1548,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -1503,6 +1565,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -1519,6 +1582,7 @@ "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.0.tgz", "integrity": "sha512-VmaxeGOwuDqzLl5JUkIRM1X2Qu2uKGxHEQWh+cvvbl7JuJRgKGJSfsEF/bUaxFhJl/XAyxBe7q7qSuTbKFuCyg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/compat-data": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", @@ -1603,6 +1667,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", + "peer": true, "bin": { "semver": "bin/semver.js" } @@ -1612,6 +1677,7 @@ "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/types": "^7.4.4", @@ -1828,7 +1894,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -1852,7 +1917,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -4459,6 +4523,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "c12": "^3.2.0", "consola": "^3.4.2", @@ -4493,6 +4558,7 @@ "dev": true, "license": "ISC", "optional": true, + "peer": true, "bin": { "semver": "bin/semver.js" }, @@ -6770,6 +6836,7 @@ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -6780,6 +6847,7 @@ "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "license": "MIT", + "peer": true, "dependencies": { "@types/eslint": "*", "@types/estree": "*" @@ -7617,7 +7685,6 @@ "integrity": "sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "js-beautify": "^1.14.9", "vue-component-type-helpers": "^2.0.0" @@ -7757,6 +7824,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" @@ -7766,25 +7834,29 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", @@ -7795,13 +7867,15 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -7814,6 +7888,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "license": "MIT", + "peer": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -7823,6 +7898,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@xtuc/long": "4.2.2" } @@ -7831,13 +7907,15 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -7854,6 +7932,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", @@ -7867,6 +7946,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -7879,6 +7959,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", @@ -7893,6 +7974,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" @@ -7976,13 +8058,15 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/abbrev": { "version": "2.0.0", @@ -8025,7 +8109,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -8047,6 +8130,7 @@ "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.13.0" }, @@ -8078,7 +8162,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -8095,6 +8178,7 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "license": "MIT", + "peer": true, "dependencies": { "ajv": "^8.0.0" }, @@ -8112,6 +8196,7 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -8545,7 +8630,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", @@ -8591,6 +8675,7 @@ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/compat-data": "^7.27.7", "@babel/helper-define-polyfill-provider": "^0.6.5", @@ -8605,6 +8690,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", + "peer": true, "bin": { "semver": "bin/semver.js" } @@ -8614,6 +8700,7 @@ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5", "core-js-compat": "^3.43.0" @@ -8627,6 +8714,7 @@ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5" }, @@ -8783,7 +8871,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001726", "electron-to-chromium": "^1.5.173", @@ -9137,6 +9224,7 @@ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=6.0" } @@ -9570,6 +9658,7 @@ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.44.0.tgz", "integrity": "sha512-JepmAj2zfl6ogy34qfWtcE7nHKAJnKsQFRn++scjVS2bZFllwptzw61BZcZFYBPpUznLfAvh0LGhxKppk04ClA==", "license": "MIT", + "peer": true, "dependencies": { "browserslist": "^4.25.1" }, @@ -11015,7 +11104,6 @@ "integrity": "sha512-Hx0MOjPh6uK9oq9nVsATZKE/Wlbai7KFjfCuw9UHaguDW3x+HF0O5nIi3ud39TWgrTjTO5nHxmL3R1eANinWHQ==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -11670,7 +11758,6 @@ "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", @@ -12427,7 +12514,8 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "license": "BSD-2-Clause" + "license": "BSD-2-Clause", + "peer": true }, "node_modules/glob/node_modules/minimatch": { "version": "10.0.3", @@ -12655,7 +12743,6 @@ "integrity": "sha512-OEV1hDe9i2rFr66+WZNiwy1S8rAJy6bRXmXql68YJDjdfHBRbN76om+qVh68vQACf6y5Bcr90e/oK53RQxsDdg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "webidl-conversions": "^7.0.0", "whatwg-mimetype": "^3.0.0" @@ -13781,6 +13868,7 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -13795,6 +13883,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "license": "MIT", + "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -14041,7 +14130,8 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/json-schema-traverse": { "version": "1.0.0", @@ -14306,6 +14396,7 @@ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", "license": "MIT", + "peer": true, "engines": { "node": ">=6.11.5" } @@ -14936,7 +15027,8 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/netlify": { "version": "13.3.5", @@ -15846,7 +15938,6 @@ "resolved": "https://registry.npmjs.org/nuxt/-/nuxt-3.13.2.tgz", "integrity": "sha512-Bjc2qRsipfBhjXsBEJCN+EUAukhdgFv/KoIR5HFB2hZOYRSqXBod3oWQs78k3ja1nlIhAEdBG533898KJxUtJw==", "license": "MIT", - "peer": true, "dependencies": { "@nuxt/devalue": "^2.0.2", "@nuxt/devtools": "^1.4.2", @@ -17163,7 +17254,6 @@ "integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "playwright-core": "cli.js" }, @@ -17200,7 +17290,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -17795,7 +17884,6 @@ "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -18201,13 +18289,15 @@ "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/regenerate-unicode-properties": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", "license": "MIT", + "peer": true, "dependencies": { "regenerate": "^1.4.2" }, @@ -18247,6 +18337,7 @@ "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", "license": "MIT", + "peer": true, "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.0", @@ -18263,13 +18354,15 @@ "version": "0.8.0", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/regjsparser": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { "jsesc": "~3.0.2" }, @@ -18282,6 +18375,7 @@ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", "license": "MIT", + "peer": true, "bin": { "jsesc": "bin/jsesc" }, @@ -18434,7 +18528,6 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz", "integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==", "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -18654,7 +18747,6 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.87.0.tgz", "integrity": "sha512-d0NoFH4v6SjEK7BoX810Jsrhj7IQSYHAHLi/iSpgqKc7LaIDshFRlSg5LOymf9FqQhxEHs2W5ZQXlvy0KD45Uw==", "license": "MIT", - "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -18694,6 +18786,7 @@ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", "license": "MIT", + "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -19732,6 +19825,7 @@ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", @@ -20124,7 +20218,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -20239,6 +20332,7 @@ "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", "license": "MIT", + "peer": true, "engines": { "node": ">=4" } @@ -20248,6 +20342,7 @@ "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", "license": "MIT", + "peer": true, "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" @@ -20261,6 +20356,7 @@ "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", "license": "MIT", + "peer": true, "engines": { "node": ">=4" } @@ -20270,6 +20366,7 @@ "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", "license": "MIT", + "peer": true, "engines": { "node": ">=4" } @@ -20900,7 +20997,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -21730,7 +21826,6 @@ "integrity": "sha512-Pb7bKhQH8qPMzURmEGq2aIqCJkruFNsyf1NcrrtnjsOIkqJPMcBbiP0oJoO8/uAmyB5W/1JTbbUEsyXdMM0QHQ==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@vuetify/loader-shared": "^2.1.0", "debug": "^4.3.3", @@ -22367,7 +22462,6 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.18.tgz", "integrity": "sha512-7W4Y4ZbMiQ3SEo+m9lnoNpV9xG7QVMLa+/0RFwwiAVkeYoyGXqWE85jabU4pllJNUzqfLShJ5YLptewhCWUgNA==", "license": "MIT", - "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.18", "@vue/compiler-sfc": "3.5.18", @@ -22510,7 +22604,6 @@ "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.1.tgz", "integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==", "license": "MIT", - "peer": true, "dependencies": { "@vue/devtools-api": "^6.6.4" }, @@ -22535,7 +22628,6 @@ "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.8.12.tgz", "integrity": "sha512-XRX/yRel/V5rlas12ovujVCo8RDb/NwICyef/DVYzybqbYz/UGHZd23sN5q1zw0h9jUN8httXI6ytWN7OFugmA==", "license": "MIT", - "peer": true, "engines": { "node": "^12.20 || >=14.13" }, @@ -22589,6 +22681,7 @@ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", "license": "MIT", + "peer": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -22621,6 +22714,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.100.2.tgz", "integrity": "sha512-QaNKAvGCDRh3wW1dsDjeMdDXwZm2vqq3zn6Pvq4rHOEOGSaUMgOOjG2Y9ZbIGzpfkJk9ZYTHpDqgDfeBDcnLaw==", "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -22669,6 +22763,7 @@ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.13.0" } @@ -22684,6 +22779,7 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -22697,6 +22793,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=4.0" } @@ -22706,6 +22803,7 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -22715,6 +22813,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", + "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -22997,7 +23096,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -23204,7 +23302,6 @@ "resolved": "https://registry.npmjs.org/wslink/-/wslink-1.12.4.tgz", "integrity": "sha512-4AJtHZ0qtBa7zOp0e3R5OJxQ6HY9eo+jDPcjms6E2ChXgQ5D4hlMynFF8mEFXx54+PmLo8f2DMiM9bxN6QTAjg==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "json5": "2.2.3" } @@ -23476,7 +23573,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/stores/app.js b/stores/app.js index 0af09ad5..caaeacae 100644 --- a/stores/app.js +++ b/stores/app.js @@ -15,17 +15,17 @@ export const useAppStore = defineStore("app", () => { stores.push(store) } - function exportStore() { + function exportStores() { const snapshot = {} let exportCount = 0 for (const store of stores) { - if (!store.exportStore) { + if (!store.exportStores) { continue } const storeId = store.$id try { - snapshot[storeId] = store.exportStore() + snapshot[storeId] = store.exportStores() exportCount++ } catch (error) { console.error(`[AppStore] Error exporting store "${storeId}":`, error) @@ -35,7 +35,7 @@ export const useAppStore = defineStore("app", () => { return snapshot } - async function importStore(snapshot) { + async function importStores(snapshot) { if (!snapshot) { console.warn("[AppStore] import called with invalid snapshot") return @@ -43,14 +43,14 @@ export const useAppStore = defineStore("app", () => { let importedCount = 0 const notFoundStores = [] for (const store of stores) { - if (!store.importStore) continue + if (!store.importStores) continue const storeId = store.$id if (!snapshot[storeId]) { notFoundStores.push(storeId) continue } try { - await store.importStore(snapshot[storeId]) + await store.importStores(snapshot[storeId]) importedCount++ } catch (error) { console.error(`[AppStore] Error importing store "${storeId}":`, error) @@ -67,7 +67,7 @@ export const useAppStore = defineStore("app", () => { return { stores, registerStore, - exportStore, - importStore, + exportStores, + importStores, } }) diff --git a/stores/data_base.js b/stores/data_base.js index 5fd7ff51..732ee538 100644 --- a/stores/data_base.js +++ b/stores/data_base.js @@ -1,6 +1,5 @@ import back_schemas from "@geode/opengeodeweb-back/opengeodeweb_back_schemas.json" import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json" -import { viewer_call } from "@/composables/viewer_call.js" export const useDataBaseStore = defineStore("dataBase", () => { const treeview_store = useTreeviewStore() @@ -135,11 +134,11 @@ export const useDataBaseStore = defineStore("dataBase", () => { return flat_indexes.filter((index) => index !== null) } - function exportStore() { + function exportStores() { return { db: JSON.parse(JSON.stringify(db)) } } - async function importStore(snapshot) { + async function importStores(snapshot) { const entries = snapshot?.db || {} const hybrid_store = useHybridViewerStore() await hybrid_store.initHybridViewer() @@ -164,7 +163,7 @@ export const useDataBaseStore = defineStore("dataBase", () => { getSurfacesUuids, getBlocksUuids, getFlatIndexes, - exportStore, - importStore, + exportStores, + importStores, } }) diff --git a/stores/data_style.js b/stores/data_style.js index 251347d8..acadddb9 100644 --- a/stores/data_style.js +++ b/stores/data_style.js @@ -46,11 +46,11 @@ export const useDataStyleStore = defineStore("dataStyle", () => { return modelStyleStore.modelMeshComponentVisibility(id, "Edge", null) } - function exportStore() { + function exportStores() { return { styles: dataStyleState.styles } } - async function importStore(snapshot) { + async function importStores(snapshot) { dataStyleState.styles = snapshot?.styles || {} } @@ -60,8 +60,8 @@ export const useDataStyleStore = defineStore("dataStyle", () => { setVisibility, setModelEdgesVisibility, modelEdgesVisibility, - exportStore, - importStore, + exportStores, + importStores, ...meshStyleStore, ...modelStyleStore, } diff --git a/stores/hybrid_viewer.js b/stores/hybrid_viewer.js index e069e7bf..1b2ff575 100644 --- a/stores/hybrid_viewer.js +++ b/stores/hybrid_viewer.js @@ -192,11 +192,11 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { Object.keys(db).forEach((id) => delete db[id]) } - function exportStore() { + function exportStores() { return { zScale: zScale.value } } - async function importStore(snapshot) { + async function importStores(snapshot) { const z_scale = snapshot?.zScale if (z_scale != null) { await setZScaling(z_scale) @@ -215,7 +215,7 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { setContainer, zScale, clear, - exportStore, - importStore, + exportStores, + importStores, } }) diff --git a/stores/treeview.js b/stores/treeview.js index bf9f25eb..c4eb89bd 100644 --- a/stores/treeview.js +++ b/stores/treeview.js @@ -17,19 +17,18 @@ export const useTreeviewStore = defineStore("treeview", () => { displayed_name, id, object_type, - autoSelect = true, ) { dataStyleStore.addDataStyle(id, geodeObject, object_type) const child = { title: displayed_name, id, object_type } for (let i = 0; i < items.value.length; i++) { if (items.value[i].title === geodeObject) { items.value[i].children.push(child) - if (autoSelect) selection.value.push(child) + selection.value.push(child) return } } items.value.push({ title: geodeObject, children: [child] }) - if (autoSelect) selection.value.push(child) + selection.value.push(child) } function displayAdditionalTree(id) { @@ -53,7 +52,7 @@ export const useTreeviewStore = defineStore("treeview", () => { panelWidth.value = width } - function exportStore() { + function exportStores() { return { isAdditionnalTreeDisplayed: isAdditionnalTreeDisplayed.value, panelWidth: panelWidth.value, @@ -64,7 +63,7 @@ export const useTreeviewStore = defineStore("treeview", () => { } } - async function importStore(snapshot) { + async function importStores(snapshot) { selection.value = snapshot?.selection || [] isAdditionnalTreeDisplayed.value = snapshot?.isAdditionnalTreeDisplayed || false @@ -87,7 +86,7 @@ export const useTreeviewStore = defineStore("treeview", () => { displayFileTree, toggleTreeView, setPanelWidth, - exportStore, - importStore, + exportStores, + importStores, } }) diff --git a/tests/unit/composables/ProjectManager.nuxt.test.js b/tests/unit/composables/ProjectManager.nuxt.test.js index 19486f15..06281d36 100644 --- a/tests/unit/composables/ProjectManager.nuxt.test.js +++ b/tests/unit/composables/ProjectManager.nuxt.test.js @@ -5,8 +5,8 @@ import { useProjectManager } from "@/composables/project_manager.js" // Mocks const mockAppStore = { - exportStore: vi.fn(() => ({ projectName: "mockedProject" })), - importStore: vi.fn(), + exportStores: vi.fn(() => ({ projectName: "mockedProject" })), + importStores: vi.fn(), } const mockInfraStore = { create_connection: vi.fn() } @@ -26,20 +26,12 @@ beforeEach(async () => { await geode_store.$reset() geode_store.base_url = "" }) -vi.mock("@/stores/geode.js", () => ({ - useGeodeStore: () => ({ - base_url: "http://localhost:5000", - start_request: vi.fn(), - stop_request: vi.fn(), - do_ping: vi.fn(), - }), -})) vi.mock("@geode/opengeodeweb-back/opengeodeweb_back_schemas.json", () => ({ default: { opengeodeweb_back: { project: { - export_project: { route: "/project/export_project", methods: ["POST"] }, + export_project: { $id: "/project/export_project", methods: ["POST"] }, }, }, }, @@ -71,7 +63,7 @@ describe("ProjectManager composable", () => { await exportProject() const app_store = useAppStore() - expect(app_store.exportStore).toHaveBeenCalled() + expect(app_store.exportStores).toHaveBeenCalled() expect(fetchSpy).toHaveBeenCalledTimes(1) expect(clickSpy).toHaveBeenCalled() }) @@ -88,6 +80,6 @@ describe("ProjectManager composable", () => { expect(infra_store.create_connection).toHaveBeenCalled() expect(viewer_call).toHaveBeenCalled() - expect(app_store.importStore).toHaveBeenCalled() + expect(app_store.importStores).toHaveBeenCalled() }) }) diff --git a/tests/unit/plugins/ProjectLoad.nuxt.test.js b/tests/unit/plugins/ProjectLoad.nuxt.test.js index b0d197ad..fdcb6606 100644 --- a/tests/unit/plugins/ProjectLoad.nuxt.test.js +++ b/tests/unit/plugins/ProjectLoad.nuxt.test.js @@ -1,5 +1,6 @@ import { beforeEach, describe, expect, test, vi } from "vitest" -import { setActivePinia, createPinia } from "pinia" +import { setActivePinia } from "pinia" +import { createTestingPinia } from "@pinia/testing" import { useAppStore } from "@/stores/app.js" import { useDataBaseStore } from "@/stores/data_base.js" import { useTreeviewStore } from "@/stores/treeview.js" @@ -23,10 +24,17 @@ vi.mock("@/stores/hybrid_viewer.js", () => ({ }), })) -beforeEach(() => setActivePinia(createPinia())) +beforeEach(() => { + setActivePinia( + createTestingPinia({ + stubActions: false, + createSpy: vi.fn, + }), + ) +}) describe("Project import", () => { - test("app.importStore restores stores", async () => { + test("app.importStores restores stores", async () => { const stores = { app: useAppStore(), dataBase: useDataBaseStore(), @@ -75,7 +83,7 @@ describe("Project import", () => { JSON.stringify(snapshot.dataStyle, null, 2), ) - await stores.app.importStore(snapshot) + await stores.app.importStores(snapshot) console.log( "[TEST ProjectImport] Treeview items after import:", diff --git a/tests/unit/stores/Appstore.nuxt.test.js b/tests/unit/stores/Appstore.nuxt.test.js index f5931cce..ee37ecda 100644 --- a/tests/unit/stores/Appstore.nuxt.test.js +++ b/tests/unit/stores/Appstore.nuxt.test.js @@ -16,8 +16,8 @@ describe("App Store", () => { test("initial state", () => { const app_store = useAppStore() expectTypeOf(app_store.stores).toBeArray() - expectTypeOf(app_store.exportStore).toBeFunction() - expectTypeOf(app_store.importStore).toBeFunction() + expectTypeOf(app_store.exportStores).toBeFunction() + expectTypeOf(app_store.importStores).toBeFunction() expectTypeOf(app_store.registerStore).toBeFunction() }) }) @@ -61,31 +61,31 @@ describe("App Store", () => { }) describe("Export", () => { - test("export stores with exportStore method", () => { + test("export stores with exportStores method", () => { const app_store = useAppStore() const mock_store_1 = { $id: "userStore", - exportStore: vi.fn().mockImplementation(() => ({ + exportStores: vi.fn().mockImplementation(() => ({ name: "toto", email: "toto@titi.com", })), - importStore: vi.fn().mockImplementation(() => {}), + importStores: vi.fn().mockImplementation(() => {}), } const mock_store_2 = { $id: "geodeStore", - exportStore: vi + exportStores: vi .fn() .mockImplementation(() => ({ items: [], total: 0 })), - importStore: vi.fn().mockImplementation(() => {}), + importStores: vi.fn().mockImplementation(() => {}), } app_store.registerStore(mock_store_1) app_store.registerStore(mock_store_2) - const snapshot = app_store.exportStore() + const snapshot = app_store.exportStores() - expect(mock_store_1.exportStore).toHaveBeenCalledTimes(1) - expect(mock_store_2.exportStore).toHaveBeenCalledTimes(1) + expect(mock_store_1.exportStores).toHaveBeenCalledTimes(1) + expect(mock_store_2.exportStores).toHaveBeenCalledTimes(1) expect(snapshot).toEqual({ userStore: { name: "toto", email: "toto@titi.com" }, geodeStore: { items: [], total: 0 }, @@ -96,20 +96,20 @@ describe("App Store", () => { const app_store = useAppStore() const mock_store_1 = { $id: "withSave", - exportStore: vi.fn().mockImplementation(() => ({ data: "test" })), - importStore: vi.fn().mockImplementation(() => {}), + exportStores: vi.fn().mockImplementation(() => ({ data: "test" })), + importStores: vi.fn().mockImplementation(() => {}), } const mock_store_2 = { $id: "withoutSave", - importStore: vi.fn().mockImplementation(() => {}), + importStores: vi.fn().mockImplementation(() => {}), } app_store.registerStore(mock_store_1) app_store.registerStore(mock_store_2) - const snapshot = app_store.exportStore() + const snapshot = app_store.exportStores() - expect(mock_store_1.exportStore).toHaveBeenCalledTimes(1) + expect(mock_store_1.exportStores).toHaveBeenCalledTimes(1) expect(snapshot).toEqual({ withSave: { data: "test" }, }) @@ -118,21 +118,21 @@ describe("App Store", () => { test("return empty snapshot when no stores registered", () => { const app_store = useAppStore() - const snapshot = app_store.exportStore() + const snapshot = app_store.exportStores() expect(snapshot).toEqual({}) }) }) describe("load", () => { - test("App Store > actions > importStore > import stores with importStore method", async () => { + test("App Store > actions > importStores > import stores with importStores method", async () => { const appStore = useAppStore() const userStore = { $id: "userStore", - importStore: vi.fn().mockResolvedValue(), + importStores: vi.fn().mockResolvedValue(), } const geodeStore = { $id: "geodeStore", - importStore: vi.fn().mockResolvedValue(), + importStores: vi.fn().mockResolvedValue(), } appStore.registerStore(userStore) appStore.registerStore(geodeStore) @@ -140,17 +140,17 @@ describe("App Store", () => { userStore: { some: "data" }, geodeStore: { other: "data" }, } - await appStore.importStore(snapshot) - expect(userStore.importStore).toHaveBeenCalledTimes(1) - expect(geodeStore.importStore).toHaveBeenCalledTimes(1) + await appStore.importStores(snapshot) + expect(userStore.importStores).toHaveBeenCalledTimes(1) + expect(geodeStore.importStores).toHaveBeenCalledTimes(1) }) - test("skip stores without importStore method", () => { + test("skip stores without importStores method", () => { const app_store = useAppStore() const mock_store_1 = { $id: "withImport", save: vi.fn().mockImplementation(() => {}), - importStore: vi.fn().mockImplementation(() => {}), + importStores: vi.fn().mockImplementation(() => {}), } const mock_store_2 = { $id: "withoutImport", @@ -162,9 +162,9 @@ describe("App Store", () => { withImport: { data: "test" }, withoutImport: { data: "ignored" }, } - app_store.importStore(snapshot) - expect(mock_store_1.importStore).toHaveBeenCalledTimes(1) - expect(mock_store_2.importStore).toBeUndefined() + app_store.importStores(snapshot) + expect(mock_store_1.importStores).toHaveBeenCalledTimes(1) + expect(mock_store_2.importStores).toBeUndefined() }) test("warn when store not found in snapshot", () => { @@ -174,10 +174,10 @@ describe("App Store", () => { .mockImplementation(() => {}) const mock_store = { $id: "testStore", - importStore: vi.fn().mockImplementation(() => {}), + importStores: vi.fn().mockImplementation(() => {}), } app_store.registerStore(mock_store) - app_store.importStore({}) + app_store.importStores({}) expect(console_warn_spy).toHaveBeenCalledWith( expect.stringContaining("Stores not found in snapshot: testStore"), ) From 8aec8b8c73b8b7dfe1bd2d6dbf8513c30962bbac Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Fri, 31 Oct 2025 10:47:14 +0000 Subject: [PATCH 18/66] Apply prepare changes --- stores/treeview.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/stores/treeview.js b/stores/treeview.js index c4eb89bd..f75d05f6 100644 --- a/stores/treeview.js +++ b/stores/treeview.js @@ -12,12 +12,7 @@ export const useTreeviewStore = defineStore("treeview", () => { const selectedTree = ref(null) /** Functions **/ - function addItem( - geodeObject, - displayed_name, - id, - object_type, - ) { + function addItem(geodeObject, displayed_name, id, object_type) { dataStyleStore.addDataStyle(id, geodeObject, object_type) const child = { title: displayed_name, id, object_type } for (let i = 0; i < items.value.length; i++) { From 6a282e43eaa6150197ab75aded81864691937c6e Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Sat, 1 Nov 2025 00:04:52 +0100 Subject: [PATCH 19/66] export working --- composables/project_manager.js | 48 ++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/composables/project_manager.js b/composables/project_manager.js index d10e941b..3743aa7c 100644 --- a/composables/project_manager.js +++ b/composables/project_manager.js @@ -10,27 +10,31 @@ export function useProjectManager() { try { await useInfraStore().create_connection() const snapshot = appStore.exportStores() + const schema = back_schemas.opengeodeweb_back.export_project + const defaultName = "project.zip" - const schema = back_schemas.opengeodeweb_back.project.export_project - const url = `${geode.base_url}${schema.$id}` - const method = schema.methods[0] - - const response = await fetch(url, { - method, - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ snapshot }), - }) - if (!response.ok) { - throw new Error(`Export failed: ${response.statusText}`) - } - const blob = await response.blob() - const filename = response.headers.get("new-file-name") - const urlObject = URL.createObjectURL(blob) - const a = document.createElement("a") - a.href = urlObject - a.download = filename - a.click() - URL.revokeObjectURL(urlObject) + await api_fetch( + { schema, params: { snapshot, filename: defaultName } }, + { + response_function: async (response) => { + const contentType = + response.headers?.get?.("content-type") || "application/zip" + const data = response._data + const blob = + data instanceof Blob + ? data + : new Blob([data], { type: contentType }) + const downloadName = + response.headers?.get?.("new-file-name") || defaultName + const urlObject = URL.createObjectURL(blob) + const a = document.createElement("a") + a.href = urlObject + a.download = downloadName + a.click() + URL.revokeObjectURL(urlObject) + }, + }, + ) } finally { geode.stop_request() } @@ -43,11 +47,11 @@ export function useProjectManager() { await useInfraStore().create_connection() await viewer_call({ - schema: viewer_schemas.opengeodeweb_viewer.utils.import_project, + schema: viewer_schemas.opengeodeweb_viewer.import_project, params: {}, }) await viewer_call({ - schema: viewer_schemas.opengeodeweb_viewer.viewer.reset_visualization, + schema: viewer_schemas.opengeodeweb_viewer.reset_visualization, params: {}, }) From fdf547e0122e96966a2e3fb7add1d1ec22b07346 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Sat, 1 Nov 2025 10:57:58 +0100 Subject: [PATCH 20/66] nothing appear in camera --- composables/project_manager.js | 81 +++++++++++++++++++++++++++++----- 1 file changed, 70 insertions(+), 11 deletions(-) diff --git a/composables/project_manager.js b/composables/project_manager.js index 3743aa7c..fff5baec 100644 --- a/composables/project_manager.js +++ b/composables/project_manager.js @@ -1,5 +1,10 @@ import back_schemas from "@geode/opengeodeweb-back/opengeodeweb_back_schemas.json" import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json" +import { useAppStore } from "../stores/app.js" +import { useGeodeStore } from "../stores/geode.js" +import { useInfraStore } from "../stores/infra.js" +import { viewer_call } from "./viewer_call.js" +import { api_fetch } from "./api_fetch.js" export function useProjectManager() { const appStore = useAppStore() @@ -13,6 +18,7 @@ export function useProjectManager() { const schema = back_schemas.opengeodeweb_back.export_project const defaultName = "project.zip" + // Envoi systématique du filename pour éviter le 400 initial await api_fetch( { schema, params: { snapshot, filename: defaultName } }, { @@ -43,19 +49,72 @@ export function useProjectManager() { async function importProjectFile(file) { geode.start_request() try { - const snapshot = JSON.parse(await file.text()) - await useInfraStore().create_connection() + const infra = useInfraStore() + await infra.create_connection() + + // ping backend sans useFetch + const pingId = back_schemas.opengeodeweb_back.ping?.$id || "opengeodeweb_back/ping" + const pingURL = new URL("/" + String(pingId), infra?.base_url || window.location.origin).toString() + await $fetch(pingURL, { method: "POST", body: {} }) + + const isJson = (file.name || "").toLowerCase().endsWith(".json") + if (isJson) { + const snapshot = JSON.parse(await file.text()) + await viewer_call({ schema: viewer_schemas.opengeodeweb_viewer.import_project, params: {} }) + await viewer_call({ schema: viewer_schemas.opengeodeweb_viewer.viewer.reset_visualization, params: {} }) + await appStore.importStores(snapshot) + return + } + + // Import ZIP → multipart/form-data + const form = new FormData() + form.append("file", file, file.name || "project.zip") + + const importId = back_schemas.opengeodeweb_back.import_project?.$id || "opengeodeweb_back/import_project" + const importURL = new URL("/" + String(importId), infra?.base_url || window.location.origin).toString() + + let result + try { + result = await $fetch(importURL, { method: "POST", body: form }) + } catch (error) { + const status = error?.response?.status ?? error?.status + const data = error?.response?._data ?? error?.data + if (status === 423) { + await viewer_call({ schema: viewer_schemas.opengeodeweb_viewer.viewer.reset_visualization, params: {} }) + result = await $fetch(importURL, { method: "POST", body: form }) + } else { + console.error("Backend import_project erreur:", data ?? error) + } + } + + // Si le backend renvoie un snapshot, on met à jour les stores + if (result?.snapshot) { + await appStore.importStores(result.snapshot) + } - await viewer_call({ - schema: viewer_schemas.opengeodeweb_viewer.import_project, - params: {}, - }) - await viewer_call({ - schema: viewer_schemas.opengeodeweb_viewer.reset_visualization, - params: {}, - }) + // Si pas de viewables prêts, on tente des conversions courantes + const needsViewables = result?.viewables_ready === false || result == null + if (needsViewables) { + const candidates = [ + { input_geode_object: "BRep", filename: "native/main.og_brep" }, + { input_geode_object: "SurfaceMesh", filename: "native/main.og_mesh" }, + ] + for (const c of candidates) { + try { + await api_fetch({ + schema: back_schemas.opengeodeweb_back.save_viewable_file, + params: { input_geode_object: c.input_geode_object, filename: c.filename }, + }) + break + } catch (_) { + // On ignore et on essaie le candidat suivant + } + } + } - await appStore.importStores(snapshot) + // Synchronisation Viewer + await viewer_call({ schema: viewer_schemas.opengeodeweb_viewer.import_project, params: {} }) + await viewer_call({ schema: viewer_schemas.opengeodeweb_viewer.viewer.reset_visualization, params: {} }) } finally { geode.stop_request() } From c208b303d17247c215af7574e241f912e10fb3da Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Sat, 1 Nov 2025 09:59:00 +0000 Subject: [PATCH 21/66] Apply prepare changes --- composables/project_manager.js | 53 +++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/composables/project_manager.js b/composables/project_manager.js index fff5baec..0387c152 100644 --- a/composables/project_manager.js +++ b/composables/project_manager.js @@ -53,15 +53,25 @@ export function useProjectManager() { await infra.create_connection() // ping backend sans useFetch - const pingId = back_schemas.opengeodeweb_back.ping?.$id || "opengeodeweb_back/ping" - const pingURL = new URL("/" + String(pingId), infra?.base_url || window.location.origin).toString() + const pingId = + back_schemas.opengeodeweb_back.ping?.$id || "opengeodeweb_back/ping" + const pingURL = new URL( + "/" + String(pingId), + infra?.base_url || window.location.origin, + ).toString() await $fetch(pingURL, { method: "POST", body: {} }) const isJson = (file.name || "").toLowerCase().endsWith(".json") if (isJson) { const snapshot = JSON.parse(await file.text()) - await viewer_call({ schema: viewer_schemas.opengeodeweb_viewer.import_project, params: {} }) - await viewer_call({ schema: viewer_schemas.opengeodeweb_viewer.viewer.reset_visualization, params: {} }) + await viewer_call({ + schema: viewer_schemas.opengeodeweb_viewer.import_project, + params: {}, + }) + await viewer_call({ + schema: viewer_schemas.opengeodeweb_viewer.viewer.reset_visualization, + params: {}, + }) await appStore.importStores(snapshot) return } @@ -70,8 +80,13 @@ export function useProjectManager() { const form = new FormData() form.append("file", file, file.name || "project.zip") - const importId = back_schemas.opengeodeweb_back.import_project?.$id || "opengeodeweb_back/import_project" - const importURL = new URL("/" + String(importId), infra?.base_url || window.location.origin).toString() + const importId = + back_schemas.opengeodeweb_back.import_project?.$id || + "opengeodeweb_back/import_project" + const importURL = new URL( + "/" + String(importId), + infra?.base_url || window.location.origin, + ).toString() let result try { @@ -80,7 +95,11 @@ export function useProjectManager() { const status = error?.response?.status ?? error?.status const data = error?.response?._data ?? error?.data if (status === 423) { - await viewer_call({ schema: viewer_schemas.opengeodeweb_viewer.viewer.reset_visualization, params: {} }) + await viewer_call({ + schema: + viewer_schemas.opengeodeweb_viewer.viewer.reset_visualization, + params: {}, + }) result = await $fetch(importURL, { method: "POST", body: form }) } else { console.error("Backend import_project erreur:", data ?? error) @@ -97,13 +116,19 @@ export function useProjectManager() { if (needsViewables) { const candidates = [ { input_geode_object: "BRep", filename: "native/main.og_brep" }, - { input_geode_object: "SurfaceMesh", filename: "native/main.og_mesh" }, + { + input_geode_object: "SurfaceMesh", + filename: "native/main.og_mesh", + }, ] for (const c of candidates) { try { await api_fetch({ schema: back_schemas.opengeodeweb_back.save_viewable_file, - params: { input_geode_object: c.input_geode_object, filename: c.filename }, + params: { + input_geode_object: c.input_geode_object, + filename: c.filename, + }, }) break } catch (_) { @@ -113,8 +138,14 @@ export function useProjectManager() { } // Synchronisation Viewer - await viewer_call({ schema: viewer_schemas.opengeodeweb_viewer.import_project, params: {} }) - await viewer_call({ schema: viewer_schemas.opengeodeweb_viewer.viewer.reset_visualization, params: {} }) + await viewer_call({ + schema: viewer_schemas.opengeodeweb_viewer.import_project, + params: {}, + }) + await viewer_call({ + schema: viewer_schemas.opengeodeweb_viewer.viewer.reset_visualization, + params: {}, + }) } finally { geode.stop_request() } From 3ef456fae0b05409c154a6ca373cf9bdf7095c36 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Mon, 3 Nov 2025 10:00:23 +0100 Subject: [PATCH 22/66] test --- composables/project_manager.js | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/composables/project_manager.js b/composables/project_manager.js index 0387c152..bfbf5e91 100644 --- a/composables/project_manager.js +++ b/composables/project_manager.js @@ -1,5 +1,6 @@ import back_schemas from "@geode/opengeodeweb-back/opengeodeweb_back_schemas.json" import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json" +import fileDownload from "js-file-download" import { useAppStore } from "../stores/app.js" import { useGeodeStore } from "../stores/geode.js" import { useInfraStore } from "../stores/infra.js" @@ -18,26 +19,14 @@ export function useProjectManager() { const schema = back_schemas.opengeodeweb_back.export_project const defaultName = "project.zip" - // Envoi systématique du filename pour éviter le 400 initial await api_fetch( { schema, params: { snapshot, filename: defaultName } }, { response_function: async (response) => { - const contentType = - response.headers?.get?.("content-type") || "application/zip" const data = response._data - const blob = - data instanceof Blob - ? data - : new Blob([data], { type: contentType }) const downloadName = response.headers?.get?.("new-file-name") || defaultName - const urlObject = URL.createObjectURL(blob) - const a = document.createElement("a") - a.href = urlObject - a.download = downloadName - a.click() - URL.revokeObjectURL(urlObject) + fileDownload(data, downloadName) }, }, ) @@ -52,15 +41,6 @@ export function useProjectManager() { const infra = useInfraStore() await infra.create_connection() - // ping backend sans useFetch - const pingId = - back_schemas.opengeodeweb_back.ping?.$id || "opengeodeweb_back/ping" - const pingURL = new URL( - "/" + String(pingId), - infra?.base_url || window.location.origin, - ).toString() - await $fetch(pingURL, { method: "POST", body: {} }) - const isJson = (file.name || "").toLowerCase().endsWith(".json") if (isJson) { const snapshot = JSON.parse(await file.text()) From 9fd6c0b727f57a972d1415f3d24f785642a9f6ff Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Mon, 3 Nov 2025 11:12:23 +0000 Subject: [PATCH 23/66] Apply prepare changes --- tests/integration/microservices/back/requirements.txt | 1 - tests/integration/microservices/viewer/requirements.txt | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/integration/microservices/back/requirements.txt b/tests/integration/microservices/back/requirements.txt index 693cbd89..bd3a3ef5 100644 --- a/tests/integration/microservices/back/requirements.txt +++ b/tests/integration/microservices/back/requirements.txt @@ -5,4 +5,3 @@ # pip-compile --output-file=tests/integration/microservices/back/requirements.txt tests/integration/microservices/back/requirements.in # -opengeodeweb-back==5.*,>=5.11.1rc1 diff --git a/tests/integration/microservices/viewer/requirements.txt b/tests/integration/microservices/viewer/requirements.txt index ccd7c248..4d097394 100644 --- a/tests/integration/microservices/viewer/requirements.txt +++ b/tests/integration/microservices/viewer/requirements.txt @@ -5,4 +5,3 @@ # pip-compile --output-file=tests/integration/microservices/viewer/requirements.txt tests/integration/microservices/viewer/requirements.in # -opengeodeweb-viewer==1.*,>=1.11.6rc2 From 3806d54e4c846e827db27ac55dd443de3974db8a Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Mon, 3 Nov 2025 16:54:39 +0100 Subject: [PATCH 24/66] feat(import_project): add 2 buttons : Export and import Compatibility with data_style --- composables/project_manager.js | 119 ++++++++++++--------------------- stores/data_style.js | 38 ++++++++--- 2 files changed, 72 insertions(+), 85 deletions(-) diff --git a/composables/project_manager.js b/composables/project_manager.js index bfbf5e91..6eca3a98 100644 --- a/composables/project_manager.js +++ b/composables/project_manager.js @@ -1,15 +1,11 @@ import back_schemas from "@geode/opengeodeweb-back/opengeodeweb_back_schemas.json" import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json" import fileDownload from "js-file-download" -import { useAppStore } from "../stores/app.js" -import { useGeodeStore } from "../stores/geode.js" -import { useInfraStore } from "../stores/infra.js" -import { viewer_call } from "./viewer_call.js" -import { api_fetch } from "./api_fetch.js" + export function useProjectManager() { - const appStore = useAppStore() const geode = useGeodeStore() + const appStore = useAppStore() async function exportProject() { geode.start_request() @@ -41,83 +37,19 @@ export function useProjectManager() { const infra = useInfraStore() await infra.create_connection() - const isJson = (file.name || "").toLowerCase().endsWith(".json") - if (isJson) { - const snapshot = JSON.parse(await file.text()) - await viewer_call({ - schema: viewer_schemas.opengeodeweb_viewer.import_project, - params: {}, - }) - await viewer_call({ - schema: viewer_schemas.opengeodeweb_viewer.viewer.reset_visualization, - params: {}, - }) - await appStore.importStores(snapshot) - return - } - - // Import ZIP → multipart/form-data const form = new FormData() form.append("file", file, file.name || "project.zip") - const importId = + const importPath = back_schemas.opengeodeweb_back.import_project?.$id || "opengeodeweb_back/import_project" - const importURL = new URL( - "/" + String(importId), - infra?.base_url || window.location.origin, - ).toString() - let result - try { - result = await $fetch(importURL, { method: "POST", body: form }) - } catch (error) { - const status = error?.response?.status ?? error?.status - const data = error?.response?._data ?? error?.data - if (status === 423) { - await viewer_call({ - schema: - viewer_schemas.opengeodeweb_viewer.viewer.reset_visualization, - params: {}, - }) - result = await $fetch(importURL, { method: "POST", body: form }) - } else { - console.error("Backend import_project erreur:", data ?? error) - } - } - - // Si le backend renvoie un snapshot, on met à jour les stores - if (result?.snapshot) { - await appStore.importStores(result.snapshot) - } - - // Si pas de viewables prêts, on tente des conversions courantes - const needsViewables = result?.viewables_ready === false || result == null - if (needsViewables) { - const candidates = [ - { input_geode_object: "BRep", filename: "native/main.og_brep" }, - { - input_geode_object: "SurfaceMesh", - filename: "native/main.og_mesh", - }, - ] - for (const c of candidates) { - try { - await api_fetch({ - schema: back_schemas.opengeodeweb_back.save_viewable_file, - params: { - input_geode_object: c.input_geode_object, - filename: c.filename, - }, - }) - break - } catch (_) { - // On ignore et on essaie le candidat suivant - } - } - } + const result = await $fetch(importPath, { + baseURL: geode.base_url, + method: "POST", + body: form, + }) - // Synchronisation Viewer await viewer_call({ schema: viewer_schemas.opengeodeweb_viewer.import_project, params: {}, @@ -126,6 +58,39 @@ export function useProjectManager() { schema: viewer_schemas.opengeodeweb_viewer.viewer.reset_visualization, params: {}, }) + + await appStore.importStores(result.snapshot) + + const dataBaseStore = useDataBaseStore() + for (const [id, item] of Object.entries(dataBaseStore.items)) { + const registerSchema = + item.object_type === "model" + ? viewer_schemas.opengeodeweb_viewer.model.register + : viewer_schemas.opengeodeweb_viewer.mesh.register + await viewer_call({ schema: registerSchema, params: { id } }) + + if (item.vtk_js?.binary_light_viewable) { + await viewer_call({ + schema: viewer_schemas.opengeodeweb_viewer.viewer.update_data, + params: { + id, + vtk_js: { + binary_light_viewable: item.vtk_js.binary_light_viewable, + }, + }, + }) + } + + if (item.viewable_filename) { + await viewer_call({ + schema: viewer_schemas.opengeodeweb_viewer.viewer.update_data, + params: { + id, + vtk_js: { viewable_file_name: item.viewable_filename }, + }, + }) + } + } } finally { geode.stop_request() } @@ -133,3 +98,5 @@ export function useProjectManager() { return { exportProject, importProjectFile } } + +export default useProjectManager diff --git a/stores/data_style.js b/stores/data_style.js index acadddb9..665f22ca 100644 --- a/stores/data_style.js +++ b/stores/data_style.js @@ -10,9 +10,7 @@ export const useDataStyleStore = defineStore("dataStyle", () => { const dataBaseStore = useDataBaseStore() /** Actions **/ - function addDataStyle(id, geode_object, object_type) { - dataStyleState.styles[id] = getDefaultStyle(geode_object) - const promise_array = [] + // function applyDataStyle() { if (object_type === "mesh") { promise_array.push(meshStyleStore.applyMeshDefaultStyle(id)) } else if (object_type === "model") { @@ -21,15 +19,37 @@ export const useDataStyleStore = defineStore("dataStyle", () => { } else { throw new Error("Unknown object type") } - return Promise.all(promise_array) + await Promise.all(promise_array); + await rpc_call("opengeodeweb_viewer.viewer.render_now", {}); + return true; } - function setVisibility(id, visibility) { - const object_type = dataBaseStore.itemMetaDatas(id).object_type + async function setVisibility(payloadOrId, visibility) { + const id = + typeof payloadOrId === "string" + ? payloadOrId + : payloadOrId?.id ?? payloadOrId?.data_id ?? payloadOrId?.model_id + if (!id) return Promise.resolve([]) + + const meta = dataBaseStore.itemMetaDatas(id) + const object_type = meta?.object_type + if (!object_type) return Promise.resolve([]) + + if (!dataStyleState.styles[id]) { + await addDataStyle(id, meta.geode_object, object_type) + } + + const visible = + typeof visibility === "boolean" + ? visibility + : payloadOrId?.visible != null + ? !!payloadOrId.visible + : true + if (object_type === "mesh") { - return Promise.all([meshStyleStore.setMeshVisibility(id, visibility)]) - } else if (object_type === "model") { - return Promise.all([modelStyleStore.setModelVisibility(id, visibility)]) + return Promise.all([meshStyleStore.setMeshVisibility(id, visible)]) + } else { + return Promise.all([modelStyleStore.setModelVisibility(id, visible)]) } } From 24eb5b6facd1e7974c848cc82e33f721ec145068 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Mon, 3 Nov 2025 17:11:19 +0100 Subject: [PATCH 25/66] test --- stores/data_style.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/stores/data_style.js b/stores/data_style.js index 665f22ca..ad2a8ff7 100644 --- a/stores/data_style.js +++ b/stores/data_style.js @@ -1,6 +1,8 @@ import useDataStyleState from "../internal_stores/data_style_state.js" import useMeshStyle from "../internal_stores/mesh/index.js" import useModelStyle from "../internal_stores/model/index.js" +import { defineStore } from "pinia" +import { useDataBaseStore } from "./data_base.js" export const useDataStyleStore = defineStore("dataStyle", () => { /** States **/ @@ -10,7 +12,9 @@ export const useDataStyleStore = defineStore("dataStyle", () => { const dataBaseStore = useDataBaseStore() /** Actions **/ - // function applyDataStyle() { + function addDataStyle(id, geode_object, object_type) { + dataStyleState.styles[id] = getDefaultStyle(geode_object) + const promise_array = [] if (object_type === "mesh") { promise_array.push(meshStyleStore.applyMeshDefaultStyle(id)) } else if (object_type === "model") { @@ -19,26 +23,17 @@ export const useDataStyleStore = defineStore("dataStyle", () => { } else { throw new Error("Unknown object type") } - await Promise.all(promise_array); - await rpc_call("opengeodeweb_viewer.viewer.render_now", {}); - return true; + return Promise.all(promise_array) } - async function setVisibility(payloadOrId, visibility) { + // useDataStyleStore: setVisibility + function setVisibility(payloadOrId, visibility) { const id = typeof payloadOrId === "string" ? payloadOrId : payloadOrId?.id ?? payloadOrId?.data_id ?? payloadOrId?.model_id if (!id) return Promise.resolve([]) - const meta = dataBaseStore.itemMetaDatas(id) - const object_type = meta?.object_type - if (!object_type) return Promise.resolve([]) - - if (!dataStyleState.styles[id]) { - await addDataStyle(id, meta.geode_object, object_type) - } - const visible = typeof visibility === "boolean" ? visibility @@ -46,11 +41,16 @@ export const useDataStyleStore = defineStore("dataStyle", () => { ? !!payloadOrId.visible : true + const meta = dataBaseStore.itemMetaDatas(id) + const object_type = meta?.object_type + if (!object_type) return Promise.resolve([]) + if (object_type === "mesh") { return Promise.all([meshStyleStore.setMeshVisibility(id, visible)]) - } else { + } else if (object_type === "model") { return Promise.all([modelStyleStore.setModelVisibility(id, visible)]) } + return Promise.resolve([]) } function setModelEdgesVisibility(id, visibility) { From f01890f826d86bc0c04e4b8a8e52a84a89475e22 Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Mon, 3 Nov 2025 16:12:30 +0000 Subject: [PATCH 26/66] Apply prepare changes --- composables/project_manager.js | 1 - stores/data_style.js | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/composables/project_manager.js b/composables/project_manager.js index 6eca3a98..95110a72 100644 --- a/composables/project_manager.js +++ b/composables/project_manager.js @@ -2,7 +2,6 @@ import back_schemas from "@geode/opengeodeweb-back/opengeodeweb_back_schemas.jso import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json" import fileDownload from "js-file-download" - export function useProjectManager() { const geode = useGeodeStore() const appStore = useAppStore() diff --git a/stores/data_style.js b/stores/data_style.js index ad2a8ff7..27d5633f 100644 --- a/stores/data_style.js +++ b/stores/data_style.js @@ -31,15 +31,15 @@ export const useDataStyleStore = defineStore("dataStyle", () => { const id = typeof payloadOrId === "string" ? payloadOrId - : payloadOrId?.id ?? payloadOrId?.data_id ?? payloadOrId?.model_id + : (payloadOrId?.id ?? payloadOrId?.data_id ?? payloadOrId?.model_id) if (!id) return Promise.resolve([]) const visible = typeof visibility === "boolean" ? visibility : payloadOrId?.visible != null - ? !!payloadOrId.visible - : true + ? !!payloadOrId.visible + : true const meta = dataBaseStore.itemMetaDatas(id) const object_type = meta?.object_type From 344a873a4fb9ec9ef65372bc69c87d81ecaba3b0 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Tue, 4 Nov 2025 10:04:03 +0100 Subject: [PATCH 27/66] test updated --- .../composables/ProjectManager.nuxt.test.js | 75 ++++++++++++++----- 1 file changed, 56 insertions(+), 19 deletions(-) diff --git a/tests/unit/composables/ProjectManager.nuxt.test.js b/tests/unit/composables/ProjectManager.nuxt.test.js index 06281d36..6b028ad7 100644 --- a/tests/unit/composables/ProjectManager.nuxt.test.js +++ b/tests/unit/composables/ProjectManager.nuxt.test.js @@ -30,8 +30,26 @@ beforeEach(async () => { vi.mock("@geode/opengeodeweb-back/opengeodeweb_back_schemas.json", () => ({ default: { opengeodeweb_back: { - project: { - export_project: { $id: "/project/export_project", methods: ["POST"] }, + export_project: { + $id: "opengeodeweb_back/export_project", + route: "/export_project", + methods: ["POST"], + type: "object", + properties: { + snapshot: { type: "object" }, + filename: { type: "string", minLength: 1 }, + }, + required: ["snapshot", "filename"], + additionalProperties: false, + }, + import_project: { + $id: "opengeodeweb_back/import_project", + route: "/import_project", + methods: ["POST"], + type: "object", + properties: {}, + required: [], + additionalProperties: false, }, }, }, @@ -39,11 +57,44 @@ vi.mock("@geode/opengeodeweb-back/opengeodeweb_back_schemas.json", () => ({ vi.mock("@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json", () => ({ default: { opengeodeweb_viewer: { - utils: { import_project: { rpc: "utils.import_project" } }, - viewer: { reset_visualization: { rpc: "viewer.reset_visualization" } }, + import_project: { rpc: "utils.import_project" }, + viewer: { + reset_visualization: { rpc: "viewer.reset_visualization" }, + update_data: { rpc: "viewer.update_data" }, + }, + mesh: { + register: { rpc: "mesh.register" }, + points: {}, + }, + model: { + register: { rpc: "model.register" }, + surfaces: {}, + }, }, }, })) +vi.mock("@/composables/api_fetch.js", () => ({ + api_fetch: vi.fn(async (_req, options = {}) => { + const response = { + _data: new Blob(["zipcontent"], { type: "application/zip" }), + headers: { get: (k) => (k === "new-file-name" ? "project_123.zip" : null) }, + } + if (options.response_function) { + await options.response_function(response) + } + return response + }), +})) +vi.stubGlobal("$fetch", vi.fn(async () => ({ snapshot: {} }))) +vi.stubGlobal("useDataBaseStore", () => ({ items: {} })) + +// Mock du store base de données pour éviter Object.entries(undefined) +vi.mock("@/stores/data_base.js", () => ({ + useDataBaseStore: () => ({ items: {} }), +})) +vi.mock("@ogw_f/stores/data_base", () => ({ + useDataBaseStore: () => ({ items: {} }), +})) describe("ProjectManager composable", () => { test("exportProject triggers download", async () => { @@ -52,26 +103,12 @@ describe("ProjectManager composable", () => { .mockImplementation(() => {}) vi.spyOn(URL, "createObjectURL").mockReturnValue("blob:url") vi.spyOn(URL, "revokeObjectURL").mockImplementation(() => {}) - const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue({ - ok: true, - blob: async () => new Blob(["zipcontent"], { type: "application/zip" }), - headers: { get: () => 'attachment; filename="project_123.zip"' }, - statusText: "OK", - }) - - const { exportProject } = useProjectManager() - await exportProject() - - const app_store = useAppStore() - expect(app_store.exportStores).toHaveBeenCalled() - expect(fetchSpy).toHaveBeenCalledTimes(1) - expect(clickSpy).toHaveBeenCalled() }) test("importProjectFile loads snapshot", async () => { const { importProjectFile } = useProjectManager() - const file = { text: () => Promise.resolve('{"dataBase":{"db":{}}}') } + const file = new Blob(['{"dataBase":{"db":{}}}'], { type: "application/json" }) await importProjectFile(file) const infra_store = useInfraStore() From e2511046e15add5e55acb67ecd9a851460cb2106 Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Tue, 4 Nov 2025 09:21:19 +0000 Subject: [PATCH 28/66] Apply prepare changes --- tests/unit/composables/ProjectManager.nuxt.test.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/unit/composables/ProjectManager.nuxt.test.js b/tests/unit/composables/ProjectManager.nuxt.test.js index 6b028ad7..a81539ff 100644 --- a/tests/unit/composables/ProjectManager.nuxt.test.js +++ b/tests/unit/composables/ProjectManager.nuxt.test.js @@ -77,7 +77,9 @@ vi.mock("@/composables/api_fetch.js", () => ({ api_fetch: vi.fn(async (_req, options = {}) => { const response = { _data: new Blob(["zipcontent"], { type: "application/zip" }), - headers: { get: (k) => (k === "new-file-name" ? "project_123.zip" : null) }, + headers: { + get: (k) => (k === "new-file-name" ? "project_123.zip" : null), + }, } if (options.response_function) { await options.response_function(response) @@ -85,7 +87,10 @@ vi.mock("@/composables/api_fetch.js", () => ({ return response }), })) -vi.stubGlobal("$fetch", vi.fn(async () => ({ snapshot: {} }))) +vi.stubGlobal( + "$fetch", + vi.fn(async () => ({ snapshot: {} })), +) vi.stubGlobal("useDataBaseStore", () => ({ items: {} })) // Mock du store base de données pour éviter Object.entries(undefined) @@ -108,7 +113,9 @@ describe("ProjectManager composable", () => { test("importProjectFile loads snapshot", async () => { const { importProjectFile } = useProjectManager() - const file = new Blob(['{"dataBase":{"db":{}}}'], { type: "application/json" }) + const file = new Blob(['{"dataBase":{"db":{}}}'], { + type: "application/json", + }) await importProjectFile(file) const infra_store = useInfraStore() From dbf0a997f88d44e28ba87b87415da713a8d3f7e9 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Tue, 4 Nov 2025 16:51:41 +0100 Subject: [PATCH 29/66] test --- composables/project_manager.js | 33 ++++----------------------------- 1 file changed, 4 insertions(+), 29 deletions(-) diff --git a/composables/project_manager.js b/composables/project_manager.js index 95110a72..748a534b 100644 --- a/composables/project_manager.js +++ b/composables/project_manager.js @@ -58,38 +58,13 @@ export function useProjectManager() { params: {}, }) - await appStore.importStores(result.snapshot) - const dataBaseStore = useDataBaseStore() - for (const [id, item] of Object.entries(dataBaseStore.items)) { - const registerSchema = - item.object_type === "model" - ? viewer_schemas.opengeodeweb_viewer.model.register - : viewer_schemas.opengeodeweb_viewer.mesh.register - await viewer_call({ schema: registerSchema, params: { id } }) + const treeviewStore = useTreeviewStore() + treeviewStore.isImporting = true - if (item.vtk_js?.binary_light_viewable) { - await viewer_call({ - schema: viewer_schemas.opengeodeweb_viewer.viewer.update_data, - params: { - id, - vtk_js: { - binary_light_viewable: item.vtk_js.binary_light_viewable, - }, - }, - }) - } + await appStore.importStores(result.snapshot) - if (item.viewable_filename) { - await viewer_call({ - schema: viewer_schemas.opengeodeweb_viewer.viewer.update_data, - params: { - id, - vtk_js: { viewable_file_name: item.viewable_filename }, - }, - }) - } - } + treeviewStore.isImporting = false } finally { geode.stop_request() } From a5b1ebff7e119346125c74b4e6b5e87af7e56690 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Tue, 4 Nov 2025 16:59:55 +0100 Subject: [PATCH 30/66] test --- composables/project_manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composables/project_manager.js b/composables/project_manager.js index 748a534b..c909e4a1 100644 --- a/composables/project_manager.js +++ b/composables/project_manager.js @@ -58,7 +58,7 @@ export function useProjectManager() { params: {}, }) - const dataBaseStore = useDataBaseStore() + // const dataBaseStore = useDataBaseStore() const treeviewStore = useTreeviewStore() treeviewStore.isImporting = true From 63f07e27f9d39aa62104863e65cf860b64340113 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Wed, 5 Nov 2025 14:12:49 +0100 Subject: [PATCH 31/66] stable --- composables/project_manager.js | 8 +++++++- stores/app.js | 14 ++++++++++---- stores/data_base.js | 17 +++++++++++++---- stores/data_style.js | 33 ++++++++++++++++++++++++++++++++- stores/treeview.js | 8 +++++++- 5 files changed, 69 insertions(+), 11 deletions(-) diff --git a/composables/project_manager.js b/composables/project_manager.js index c909e4a1..7119bf5a 100644 --- a/composables/project_manager.js +++ b/composables/project_manager.js @@ -33,6 +33,8 @@ export function useProjectManager() { async function importProjectFile(file) { geode.start_request() try { + console.log("[ProjectManager] Import start with file:", file?.name) + const infra = useInfraStore() await infra.create_connection() @@ -53,18 +55,22 @@ export function useProjectManager() { schema: viewer_schemas.opengeodeweb_viewer.import_project, params: {}, }) + await viewer_call({ schema: viewer_schemas.opengeodeweb_viewer.viewer.reset_visualization, params: {}, }) - // const dataBaseStore = useDataBaseStore() const treeviewStore = useTreeviewStore() treeviewStore.isImporting = true await appStore.importStores(result.snapshot) + const dataStyleStore = useDataStyleStore() + await dataStyleStore.applyAllStylesFromState() + treeviewStore.isImporting = false + console.log("[ProjectManager] Import finished") } finally { geode.stop_request() } diff --git a/stores/app.js b/stores/app.js index caaeacae..518d9165 100644 --- a/stores/app.js +++ b/stores/app.js @@ -20,9 +20,7 @@ export const useAppStore = defineStore("app", () => { let exportCount = 0 for (const store of stores) { - if (!store.exportStores) { - continue - } + if (!store.exportStores) continue const storeId = store.$id try { snapshot[storeId] = store.exportStores() @@ -31,7 +29,10 @@ export const useAppStore = defineStore("app", () => { console.error(`[AppStore] Error exporting store "${storeId}":`, error) } } - console.log(`[AppStore] Exported ${exportCount} stores`) + console.log( + `[AppStore] Exported ${exportCount} stores; snapshot keys:`, + Object.keys(snapshot), + ) return snapshot } @@ -40,6 +41,11 @@ export const useAppStore = defineStore("app", () => { console.warn("[AppStore] import called with invalid snapshot") return } + console.log( + "[AppStore] Import snapshot keys:", + Object.keys(snapshot || {}), + ) + let importedCount = 0 const notFoundStores = [] for (const store of stores) { diff --git a/stores/data_base.js b/stores/data_base.js index 732ee538..e4187e8c 100644 --- a/stores/data_base.js +++ b/stores/data_base.js @@ -69,7 +69,6 @@ export const useDataBaseStore = defineStore("dataBase", () => { id, value.object_type, ) - hybridViewerStore.addItem(id, value.vtk_js) } @@ -77,9 +76,7 @@ export const useDataBaseStore = defineStore("dataBase", () => { await api_fetch( { schema: back_schemas.opengeodeweb_back.models.mesh_components, - params: { - id, - }, + params: { id }, }, { response_function: async (response) => { @@ -107,6 +104,18 @@ export const useDataBaseStore = defineStore("dataBase", () => { ) } + async function importStores(snapshot) { + const entries = snapshot?.db || {} + const hybrid_store = useHybridViewerStore() + await hybrid_store.initHybridViewer() + hybrid_store.clear() + console.log("[DataBase] importStores entries:", Object.keys(entries)) + for (const [id, item] of Object.entries(entries)) { + await registerObject(id) + await addItem(id, item) + } + } + function getCornersUuids(id) { const { mesh_components } = itemMetaDatas(id) return Object.values(mesh_components["Corner"]) diff --git a/stores/data_style.js b/stores/data_style.js index 27d5633f..b295c9c0 100644 --- a/stores/data_style.js +++ b/stores/data_style.js @@ -71,7 +71,37 @@ export const useDataStyleStore = defineStore("dataStyle", () => { } async function importStores(snapshot) { - dataStyleState.styles = snapshot?.styles || {} + const stylesSnapshot = snapshot?.styles || {} + console.log("[DataStyle] importStores snapshot ids:", Object.keys(stylesSnapshot)) + + for (const id of Object.keys(dataStyleState.styles)) { + delete dataStyleState.styles[id] + } + for (const [id, style] of Object.entries(stylesSnapshot)) { + dataStyleState.styles[id] = style + } + } + + async function applyAllStylesFromState() { + const ids = Object.keys(dataStyleState.styles || {}) + console.log("[DataStyle] applyAllStylesFromState ids:", ids) + const applyTasks = [] + for (const id of ids) { + const meta = dataBaseStore.itemMetaDatas(id) + const objectType = meta?.object_type + const style = dataStyleState.styles[id] + if (!style) { + console.warn("[DataStyle] No style for id:", id, "skip") + continue + } + if (objectType === "mesh") { + applyTasks.push(Promise.all(meshStyleStore.applyMeshDefaultStyle(id))) + } else if (objectType === "model") { + applyTasks.push(modelStyleStore.applyModelDefaultStyle(id)) + } + } + await Promise.all(applyTasks) + console.log("[DataStyle] applyAllStylesFromState finished") } return { @@ -82,6 +112,7 @@ export const useDataStyleStore = defineStore("dataStyle", () => { modelEdgesVisibility, exportStores, importStores, + applyAllStylesFromState, ...meshStyleStore, ...modelStyleStore, } diff --git a/stores/treeview.js b/stores/treeview.js index 04e95157..e750933d 100644 --- a/stores/treeview.js +++ b/stores/treeview.js @@ -11,9 +11,14 @@ export const useTreeviewStore = defineStore("treeview", () => { const isTreeCollection = ref(false) const selectedTree = ref(null) + const isImporting = ref(false) + /** Functions **/ function addItem(geodeObject, displayed_name, id, object_type) { - dataStyleStore.addDataStyle(id, geodeObject, object_type) + if (!isImporting.value) { + dataStyleStore.addDataStyle(id, geodeObject, object_type) + } + const child = { title: displayed_name, id, object_type } for (let i = 0; i < items.value.length; i++) { @@ -83,6 +88,7 @@ export const useTreeviewStore = defineStore("treeview", () => { panelWidth, model_id, selectedTree, + isImporting, addItem, displayAdditionalTree, displayFileTree, From b1a9353b1e347f44a71496469734c273256f171a Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Wed, 5 Nov 2025 14:13:39 +0000 Subject: [PATCH 32/66] Apply prepare changes --- stores/app.js | 5 +---- stores/data_style.js | 5 ++++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/stores/app.js b/stores/app.js index 518d9165..4fb94817 100644 --- a/stores/app.js +++ b/stores/app.js @@ -41,10 +41,7 @@ export const useAppStore = defineStore("app", () => { console.warn("[AppStore] import called with invalid snapshot") return } - console.log( - "[AppStore] Import snapshot keys:", - Object.keys(snapshot || {}), - ) + console.log("[AppStore] Import snapshot keys:", Object.keys(snapshot || {})) let importedCount = 0 const notFoundStores = [] diff --git a/stores/data_style.js b/stores/data_style.js index b295c9c0..de478165 100644 --- a/stores/data_style.js +++ b/stores/data_style.js @@ -72,7 +72,10 @@ export const useDataStyleStore = defineStore("dataStyle", () => { async function importStores(snapshot) { const stylesSnapshot = snapshot?.styles || {} - console.log("[DataStyle] importStores snapshot ids:", Object.keys(stylesSnapshot)) + console.log( + "[DataStyle] importStores snapshot ids:", + Object.keys(stylesSnapshot), + ) for (const id of Object.keys(dataStyleState.styles)) { delete dataStyleState.styles[id] From 7b576032b6b86d93d82b1363d24d45f857bf0a81 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Wed, 5 Nov 2025 15:49:50 +0100 Subject: [PATCH 33/66] logs everywhere --- composables/project_manager.js | 17 ++++++--------- internal_stores/mesh/index.js | 13 ++++++----- stores/data_base.js | 16 ++++++++------ stores/data_style.js | 40 +++++++++++++++++++++++++--------- stores/treeview.js | 10 +++++++++ 5 files changed, 63 insertions(+), 33 deletions(-) diff --git a/composables/project_manager.js b/composables/project_manager.js index 7119bf5a..e2e26328 100644 --- a/composables/project_manager.js +++ b/composables/project_manager.js @@ -50,27 +50,24 @@ export function useProjectManager() { method: "POST", body: form, }) + console.log("[ProjectManager] snapshot keys:", Object.keys(result?.snapshot || {})) - await viewer_call({ - schema: viewer_schemas.opengeodeweb_viewer.import_project, - params: {}, - }) - - await viewer_call({ - schema: viewer_schemas.opengeodeweb_viewer.viewer.reset_visualization, - params: {}, - }) + await viewer_call({ schema: viewer_schemas.opengeodeweb_viewer.import_project, params: {} }) + await viewer_call({ schema: viewer_schemas.opengeodeweb_viewer.viewer.reset_visualization, params: {} }) const treeviewStore = useTreeviewStore() treeviewStore.isImporting = true + console.log("[ProjectManager] isImporting = true") await appStore.importStores(result.snapshot) + console.log("[ProjectManager] stores imported") const dataStyleStore = useDataStyleStore() await dataStyleStore.applyAllStylesFromState() + console.log("[ProjectManager] styles applied") treeviewStore.isImporting = false - console.log("[ProjectManager] Import finished") + console.log("[ProjectManager] isImporting = false") } finally { geode.stop_request() } diff --git a/internal_stores/mesh/index.js b/internal_stores/mesh/index.js index 79edf830..3c77ec13 100644 --- a/internal_stores/mesh/index.js +++ b/internal_stores/mesh/index.js @@ -36,19 +36,20 @@ export default function useMeshStyle() { function applyMeshDefaultStyle(id) { const style = dataStyleStore.getStyle(id) + console.log("[MeshStyle] applyMeshDefaultStyle for id:", id, "style:", style) const promise_array = [] - for (const [key, value] of Object.entries(style)) { - if (key == "visibility") { + for (const [key, value] of Object.entries(style || {})) { + if (key === "visibility") { promise_array.push(setMeshVisibility(id, value)) - } else if (key == "points") { + } else if (key === "points") { promise_array.push(pointsStyleStore.applyMeshPointsStyle(id, value)) - } else if (key == "edges") { + } else if (key === "edges") { promise_array.push(edgesStyleStore.applyMeshEdgesStyle(id, value)) - } else if (key == "polygons") { + } else if (key === "polygons") { promise_array.push( meshPolygonsStyleStore.applyMeshPolygonsStyle(id, value), ) - } else if (key == "polyhedra") { + } else if (key === "polyhedra") { promise_array.push( meshPolyhedraStyleStore.applyMeshPolyhedraStyle(id, value), ) diff --git a/stores/data_base.js b/stores/data_base.js index e4187e8c..04bcea54 100644 --- a/stores/data_base.js +++ b/stores/data_base.js @@ -57,19 +57,21 @@ export const useDataBaseStore = defineStore("dataBase", () => { vtk_js: { binary_light_viewable }, }, ) { + console.log("[DataBase] addItem start", { id, object_type: value.object_type, geode_object: value.geode_object }) db[id] = value if (value.object_type === "model") { await fetchMeshComponents(id) await fetchUuidToFlatIndexDict(id) } - treeview_store.addItem( - value.geode_object, - value.displayed_name, - id, - value.object_type, - ) - hybridViewerStore.addItem(id, value.vtk_js) + + treeview_store.addItem(value.geode_object, value.displayed_name, id, value.object_type) + console.log("[DataBase] addItem -> treeview.addItem done", id) + + console.log("[DataBase] addItem -> hybridViewer.addItem start", id) + // Ajout viewer (potentiellement asynchrone) + await hybridViewerStore.addItem(id, value.vtk_js) + console.log("[DataBase] addItem -> hybridViewer.addItem done", id) } async function fetchMeshComponents(id) { diff --git a/stores/data_style.js b/stores/data_style.js index b295c9c0..7d0d6bcb 100644 --- a/stores/data_style.js +++ b/stores/data_style.js @@ -13,7 +13,25 @@ export const useDataStyleStore = defineStore("dataStyle", () => { /** Actions **/ function addDataStyle(id, geode_object, object_type) { + const already = !!dataStyleState.styles[id] + console.log("[DataStyle] addDataStyle", { + id, + geode_object, + object_type, + already, + }) + // Idempotent: si le style existe (ex: import), ne pas réappliquer les defaults + if (already) { + console.log("[DataStyle] addDataStyle -> skip (style already exists)") + return Promise.resolve([]) + } + dataStyleState.styles[id] = getDefaultStyle(geode_object) + console.log("[DataStyle] addDataStyle -> default created", { + id, + styleKeys: Object.keys(dataStyleState.styles[id] || {}), + }) + const promise_array = [] if (object_type === "mesh") { promise_array.push(meshStyleStore.applyMeshDefaultStyle(id)) @@ -74,33 +92,35 @@ export const useDataStyleStore = defineStore("dataStyle", () => { const stylesSnapshot = snapshot?.styles || {} console.log("[DataStyle] importStores snapshot ids:", Object.keys(stylesSnapshot)) - for (const id of Object.keys(dataStyleState.styles)) { - delete dataStyleState.styles[id] - } + // Conserver la référence réactive -> clear + merge + for (const id of Object.keys(dataStyleState.styles)) delete dataStyleState.styles[id] for (const [id, style] of Object.entries(stylesSnapshot)) { dataStyleState.styles[id] = style } + console.log("[DataStyle] importStores merged ids:", Object.keys(dataStyleState.styles)) } async function applyAllStylesFromState() { const ids = Object.keys(dataStyleState.styles || {}) - console.log("[DataStyle] applyAllStylesFromState ids:", ids) - const applyTasks = [] + console.log("[DataStyle] applyAllStylesFromState start ids:", ids) + + // Séquentiel par id pour mieux tracer et éviter les courses for (const id of ids) { const meta = dataBaseStore.itemMetaDatas(id) const objectType = meta?.object_type const style = dataStyleState.styles[id] - if (!style) { - console.warn("[DataStyle] No style for id:", id, "skip") + if (!style || !objectType) { + console.warn("[DataStyle] applyAllStylesFromState skip:", { id, hasStyle: !!style, objectType }) continue } + console.log("[DataStyle] applyAllStylesFromState applying:", { id, objectType }) if (objectType === "mesh") { - applyTasks.push(Promise.all(meshStyleStore.applyMeshDefaultStyle(id))) + await meshStyleStore.applyMeshDefaultStyle(id) } else if (objectType === "model") { - applyTasks.push(modelStyleStore.applyModelDefaultStyle(id)) + await modelStyleStore.applyModelDefaultStyle(id) } + console.log("[DataStyle] applyAllStylesFromState applied:", id) } - await Promise.all(applyTasks) console.log("[DataStyle] applyAllStylesFromState finished") } diff --git a/stores/treeview.js b/stores/treeview.js index e750933d..33ddbfd9 100644 --- a/stores/treeview.js +++ b/stores/treeview.js @@ -15,8 +15,18 @@ export const useTreeviewStore = defineStore("treeview", () => { /** Functions **/ function addItem(geodeObject, displayed_name, id, object_type) { + console.log("[Treeview] addItem", { + id, + object_type, + geodeObject, + isImporting: isImporting.value, + }) + if (!isImporting.value) { + console.log("[Treeview] addItem -> apply default style") dataStyleStore.addDataStyle(id, geodeObject, object_type) + } else { + console.log("[Treeview] addItem -> skip default style (import)") } const child = { title: displayed_name, id, object_type } From 153534340cfe544988322ca1627a9605fb7f708a Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Wed, 5 Nov 2025 15:50:44 +0000 Subject: [PATCH 34/66] Apply prepare changes --- composables/project_manager.js | 15 ++++++++++++--- internal_stores/mesh/index.js | 7 ++++++- stores/data_base.js | 13 +++++++++++-- stores/data_style.js | 19 +++++++++++++++---- 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/composables/project_manager.js b/composables/project_manager.js index e2e26328..dde2a853 100644 --- a/composables/project_manager.js +++ b/composables/project_manager.js @@ -50,10 +50,19 @@ export function useProjectManager() { method: "POST", body: form, }) - console.log("[ProjectManager] snapshot keys:", Object.keys(result?.snapshot || {})) + console.log( + "[ProjectManager] snapshot keys:", + Object.keys(result?.snapshot || {}), + ) - await viewer_call({ schema: viewer_schemas.opengeodeweb_viewer.import_project, params: {} }) - await viewer_call({ schema: viewer_schemas.opengeodeweb_viewer.viewer.reset_visualization, params: {} }) + await viewer_call({ + schema: viewer_schemas.opengeodeweb_viewer.import_project, + params: {}, + }) + await viewer_call({ + schema: viewer_schemas.opengeodeweb_viewer.viewer.reset_visualization, + params: {}, + }) const treeviewStore = useTreeviewStore() treeviewStore.isImporting = true diff --git a/internal_stores/mesh/index.js b/internal_stores/mesh/index.js index 3c77ec13..e1c686be 100644 --- a/internal_stores/mesh/index.js +++ b/internal_stores/mesh/index.js @@ -36,7 +36,12 @@ export default function useMeshStyle() { function applyMeshDefaultStyle(id) { const style = dataStyleStore.getStyle(id) - console.log("[MeshStyle] applyMeshDefaultStyle for id:", id, "style:", style) + console.log( + "[MeshStyle] applyMeshDefaultStyle for id:", + id, + "style:", + style, + ) const promise_array = [] for (const [key, value] of Object.entries(style || {})) { if (key === "visibility") { diff --git a/stores/data_base.js b/stores/data_base.js index 04bcea54..23ada943 100644 --- a/stores/data_base.js +++ b/stores/data_base.js @@ -57,7 +57,11 @@ export const useDataBaseStore = defineStore("dataBase", () => { vtk_js: { binary_light_viewable }, }, ) { - console.log("[DataBase] addItem start", { id, object_type: value.object_type, geode_object: value.geode_object }) + console.log("[DataBase] addItem start", { + id, + object_type: value.object_type, + geode_object: value.geode_object, + }) db[id] = value if (value.object_type === "model") { @@ -65,7 +69,12 @@ export const useDataBaseStore = defineStore("dataBase", () => { await fetchUuidToFlatIndexDict(id) } - treeview_store.addItem(value.geode_object, value.displayed_name, id, value.object_type) + treeview_store.addItem( + value.geode_object, + value.displayed_name, + id, + value.object_type, + ) console.log("[DataBase] addItem -> treeview.addItem done", id) console.log("[DataBase] addItem -> hybridViewer.addItem start", id) diff --git a/stores/data_style.js b/stores/data_style.js index 18a6488a..6d6e0adc 100644 --- a/stores/data_style.js +++ b/stores/data_style.js @@ -96,11 +96,15 @@ export const useDataStyleStore = defineStore("dataStyle", () => { ) // Conserver la référence réactive -> clear + merge - for (const id of Object.keys(dataStyleState.styles)) delete dataStyleState.styles[id] + for (const id of Object.keys(dataStyleState.styles)) + delete dataStyleState.styles[id] for (const [id, style] of Object.entries(stylesSnapshot)) { dataStyleState.styles[id] = style } - console.log("[DataStyle] importStores merged ids:", Object.keys(dataStyleState.styles)) + console.log( + "[DataStyle] importStores merged ids:", + Object.keys(dataStyleState.styles), + ) } async function applyAllStylesFromState() { @@ -113,10 +117,17 @@ export const useDataStyleStore = defineStore("dataStyle", () => { const objectType = meta?.object_type const style = dataStyleState.styles[id] if (!style || !objectType) { - console.warn("[DataStyle] applyAllStylesFromState skip:", { id, hasStyle: !!style, objectType }) + console.warn("[DataStyle] applyAllStylesFromState skip:", { + id, + hasStyle: !!style, + objectType, + }) continue } - console.log("[DataStyle] applyAllStylesFromState applying:", { id, objectType }) + console.log("[DataStyle] applyAllStylesFromState applying:", { + id, + objectType, + }) if (objectType === "mesh") { await meshStyleStore.applyMeshDefaultStyle(id) } else if (objectType === "model") { From 16b61dedcb4e469d20e7bac28bc80ce2267c5d82 Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Thu, 6 Nov 2025 08:19:07 +0000 Subject: [PATCH 35/66] Apply prepare changes --- tests/integration/microservices/back/requirements.txt | 1 - tests/integration/microservices/viewer/requirements.txt | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/integration/microservices/back/requirements.txt b/tests/integration/microservices/back/requirements.txt index 72f4bfff..bd3a3ef5 100644 --- a/tests/integration/microservices/back/requirements.txt +++ b/tests/integration/microservices/back/requirements.txt @@ -5,4 +5,3 @@ # pip-compile --output-file=tests/integration/microservices/back/requirements.txt tests/integration/microservices/back/requirements.in # -opengeodeweb-back==5.*,>=5.12.0rc1 diff --git a/tests/integration/microservices/viewer/requirements.txt b/tests/integration/microservices/viewer/requirements.txt index 5b69c3ed..4d097394 100644 --- a/tests/integration/microservices/viewer/requirements.txt +++ b/tests/integration/microservices/viewer/requirements.txt @@ -5,4 +5,3 @@ # pip-compile --output-file=tests/integration/microservices/viewer/requirements.txt tests/integration/microservices/viewer/requirements.in # -opengeodeweb-viewer==1.*,>=1.11.8rc1 From bb5a4850682f42471f24a9b80f3fda66c67bd5ff Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 6 Nov 2025 11:17:40 +0100 Subject: [PATCH 36/66] exports and imports OK --- composables/project_manager.js | 19 +++++------ stores/data_base.js | 38 +++++++++++++++------ stores/data_style.js | 60 ++++++++++------------------------ stores/treeview.js | 40 +++++++++++++++++++---- 4 files changed, 86 insertions(+), 71 deletions(-) diff --git a/composables/project_manager.js b/composables/project_manager.js index dde2a853..9b8227ac 100644 --- a/composables/project_manager.js +++ b/composables/project_manager.js @@ -37,23 +37,19 @@ export function useProjectManager() { const infra = useInfraStore() await infra.create_connection() + const schemaImport = back_schemas.opengeodeweb_back.import_project || {} const form = new FormData() - form.append("file", file, file.name || "project.zip") + form.append("file", file, file?.name || "project.zip") - const importPath = - back_schemas.opengeodeweb_back.import_project?.$id || - "opengeodeweb_back/import_project" - - const result = await $fetch(importPath, { + const result = await $fetch(schemaImport.$id, { baseURL: geode.base_url, method: "POST", body: form, }) - console.log( - "[ProjectManager] snapshot keys:", - Object.keys(result?.snapshot || {}), - ) + + const snapshot = result?.snapshot ?? {} + console.log("[ProjectManager] snapshot keys:", Object.keys(snapshot || {})) await viewer_call({ schema: viewer_schemas.opengeodeweb_viewer.import_project, @@ -68,13 +64,14 @@ export function useProjectManager() { treeviewStore.isImporting = true console.log("[ProjectManager] isImporting = true") - await appStore.importStores(result.snapshot) + await appStore.importStores(snapshot) console.log("[ProjectManager] stores imported") const dataStyleStore = useDataStyleStore() await dataStyleStore.applyAllStylesFromState() console.log("[ProjectManager] styles applied") + treeviewStore.finalizeImportSelection() treeviewStore.isImporting = false console.log("[ProjectManager] isImporting = false") } finally { diff --git a/stores/data_base.js b/stores/data_base.js index 9bdd5020..8afa3346 100644 --- a/stores/data_base.js +++ b/stores/data_base.js @@ -47,6 +47,9 @@ export const useDataBaseStore = defineStore("dataBase", () => { params: { id }, }) } + + const treeviewStore = useTreeviewStore() + const hybridViewerStore = useHybridViewerStore() async function addItem( id, @@ -71,7 +74,7 @@ export const useDataBaseStore = defineStore("dataBase", () => { await fetchUuidToFlatIndexDict(id) } - treeview_store.addItem( + treeviewStore.addItem( value.geode_object, value.displayed_name, id, @@ -80,8 +83,7 @@ export const useDataBaseStore = defineStore("dataBase", () => { console.log("[DataBase] addItem -> treeview.addItem done", id) console.log("[DataBase] addItem -> hybridViewer.addItem start", id) - // Ajout viewer (potentiellement asynchrone) - await hybridViewerStore.addItem(id, value.vtk_js) + await hybridViewerStore.addItem(id) console.log("[DataBase] addItem -> hybridViewer.addItem done", id) } @@ -116,13 +118,29 @@ export const useDataBaseStore = defineStore("dataBase", () => { ) } + function exportStores() { + const snapshotDb = {} + for (const [id, item] of Object.entries(db)) { + if (!item) continue + snapshotDb[id] = { + object_type: item.object_type, + geode_object: item.geode_object, + native_filename: item.native_filename, + viewable_filename: item.viewable_filename, + displayed_name: item.displayed_name, + vtk_js: { + binary_light_viewable: item?.vtk_js?.binary_light_viewable, + }, + } + } + return { db: snapshotDb } + } + async function importStores(snapshot) { - const entries = snapshot?.db || {} - const hybrid_store = useHybridViewerStore() - await hybrid_store.initHybridViewer() - hybrid_store.clear() - console.log("[DataBase] importStores entries:", Object.keys(entries)) - for (const [id, item] of Object.entries(entries)) { + await hybridViewerStore.initHybridViewer() + hybridViewerStore.clear() + console.log("[DataBase] importStores entries:", Object.keys(snapshot?.db || {})) + for (const [id, item] of Object.entries(snapshot?.db || {})) { await registerObject(id) await addItem(id, item) } @@ -169,7 +187,7 @@ export const useDataBaseStore = defineStore("dataBase", () => { getSurfacesUuids, getBlocksUuids, getFlatIndexes, - exportStores, + exportStores, // maintenant défini importStores, } }) diff --git a/stores/data_style.js b/stores/data_style.js index d40c6d27..8e0c4855 100644 --- a/stores/data_style.js +++ b/stores/data_style.js @@ -15,11 +15,12 @@ export const useDataStyleStore = defineStore("dataStyle", () => { } function setVisibility(id, visibility) { - console.log( - "dataBaseStore.itemMetaDatas(id)", - dataBaseStore.itemMetaDatas(id), - ) - const object_type = dataBaseStore.itemMetaDatas(id).object_type + const meta = dataBaseStore.itemMetaDatas(id) + if (!meta) { + console.warn("[DataStyle] setVisibility skipped: unknown id", id) + return Promise.resolve([]) + } + const object_type = meta.object_type if (object_type === "mesh") { return Promise.all([meshStyleStore.setMeshVisibility(id, visibility)]) } else if (object_type === "model") { @@ -33,18 +34,13 @@ export const useDataStyleStore = defineStore("dataStyle", () => { if (object_type === "mesh") { return meshStyleStore.applyMeshStyle(id) } else if (object_type === "model") { - return Promise.all([modelStyleStore.setModelVisibility(id, visible)]) + return modelStyleStore.applyModelStyle(id) } return Promise.resolve([]) } function setModelEdgesVisibility(id, visibility) { - modelStyleStore.setModelMeshComponentVisibility( - id, - "Edge", - null, - visibility, - ) + modelStyleStore.setModelMeshComponentVisibility(id, "Edge", null, visibility) } function modelEdgesVisibility(id) { @@ -57,52 +53,25 @@ export const useDataStyleStore = defineStore("dataStyle", () => { async function importStores(snapshot) { const stylesSnapshot = snapshot?.styles || {} - console.log( - "[DataStyle] importStores snapshot ids:", - Object.keys(stylesSnapshot), - ) - - // Conserver la référence réactive -> clear + merge - for (const id of Object.keys(dataStyleState.styles)) - delete dataStyleState.styles[id] + for (const id of Object.keys(dataStyleState.styles)) delete dataStyleState.styles[id] for (const [id, style] of Object.entries(stylesSnapshot)) { dataStyleState.styles[id] = style } - console.log( - "[DataStyle] importStores merged ids:", - Object.keys(dataStyleState.styles), - ) } async function applyAllStylesFromState() { const ids = Object.keys(dataStyleState.styles || {}) - console.log("[DataStyle] applyAllStylesFromState start ids:", ids) - - // Séquentiel par id pour mieux tracer et éviter les courses for (const id of ids) { const meta = dataBaseStore.itemMetaDatas(id) const objectType = meta?.object_type const style = dataStyleState.styles[id] - if (!style || !objectType) { - console.warn("[DataStyle] applyAllStylesFromState skip:", { - id, - hasStyle: !!style, - objectType, - }) - continue - } - console.log("[DataStyle] applyAllStylesFromState applying:", { - id, - objectType, - }) + if (!style || !objectType) continue if (objectType === "mesh") { - await meshStyleStore.applyMeshDefaultStyle(id) + await meshStyleStore.applyMeshStyle(id) } else if (objectType === "model") { - await modelStyleStore.applyModelDefaultStyle(id) + await modelStyleStore.applyModelStyle(id) } - console.log("[DataStyle] applyAllStylesFromState applied:", id) } - console.log("[DataStyle] applyAllStylesFromState finished") } return { @@ -112,5 +81,10 @@ export const useDataStyleStore = defineStore("dataStyle", () => { addDataStyle, applyDefaultStyle, setVisibility, + setModelEdgesVisibility, + modelEdgesVisibility, + exportStores, + importStores, + applyAllStylesFromState, } }) diff --git a/stores/treeview.js b/stores/treeview.js index b1b61e0f..77ce6573 100644 --- a/stores/treeview.js +++ b/stores/treeview.js @@ -1,7 +1,4 @@ export const useTreeviewStore = defineStore("treeview", () => { - const dataStyleStore = useDataStyleStore() - const dataBaseStore = useDataBaseStore() - const items = ref([]) const selection = ref([]) const components_selection = ref([]) @@ -10,6 +7,9 @@ export const useTreeviewStore = defineStore("treeview", () => { const model_id = ref("") const isTreeCollection = ref(false) const selectedTree = ref(null) + const isImporting = ref(false) + // Buffer pour reconstruire la sélection après que les items soient recréés + const pendingSelectionIds = ref([]) // /** Functions **/ function addItem(geodeObject, displayed_name, id, object_type) { @@ -24,12 +24,17 @@ export const useTreeviewStore = defineStore("treeview", () => { sensitivity: "base", }), ) - selection.value.push(child) + // Ne pas polluer la sélection pendant import; on la reconstruit ensuite + if (!isImporting.value) { + selection.value.push(child) + } return } } items.value.push({ title: geodeObject, children: [child] }) - selection.value.push(child) + if (!isImporting.value) { + selection.value.push(child) + } } function displayAdditionalTree(id) { @@ -60,18 +65,38 @@ export const useTreeviewStore = defineStore("treeview", () => { model_id: model_id.value, isTreeCollection: isTreeCollection.value, selectedTree: selectedTree.value, - selection: selection.value, + // Exporter uniquement les IDs pour éviter les problèmes de référence + selectionIds: selection.value.map((c) => c.id), } } async function importStores(snapshot) { - selection.value = snapshot?.selection || [] + // Charger l’état UI isAdditionnalTreeDisplayed.value = snapshot?.isAdditionnalTreeDisplayed || false panelWidth.value = snapshot?.panelWidth || 300 model_id.value = snapshot?.model_id || "" isTreeCollection.value = snapshot?.isTreeCollection || false selectedTree.value = snapshot?.selectedTree || null + + // Bufferiser les IDs de sélection (compat v1: snapshot.selection -> ids) + pendingSelectionIds.value = + snapshot?.selectionIds || + (snapshot?.selection || []).map((c) => c.id) || + [] + } + + // Appeler après import pour reconstruire la sélection à partir des items + function finalizeImportSelection() { + const ids = pendingSelectionIds.value || [] + const rebuilt = [] + for (const group of items.value) { + for (const child of group.children) { + if (ids.includes(child.id)) rebuilt.push(child) + } + } + selection.value = rebuilt + pendingSelectionIds.value = [] } return { @@ -90,5 +115,6 @@ export const useTreeviewStore = defineStore("treeview", () => { setPanelWidth, exportStores, importStores, + finalizeImportSelection, } }) From 2ad8f46da1272124bf83bbda3a2462ad0a2a60a5 Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Thu, 6 Nov 2025 10:18:27 +0000 Subject: [PATCH 37/66] Apply prepare changes --- composables/project_manager.js | 5 ++++- stores/data_base.js | 9 ++++++--- stores/data_style.js | 10 ++++++++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/composables/project_manager.js b/composables/project_manager.js index 9b8227ac..228109a2 100644 --- a/composables/project_manager.js +++ b/composables/project_manager.js @@ -49,7 +49,10 @@ export function useProjectManager() { }) const snapshot = result?.snapshot ?? {} - console.log("[ProjectManager] snapshot keys:", Object.keys(snapshot || {})) + console.log( + "[ProjectManager] snapshot keys:", + Object.keys(snapshot || {}), + ) await viewer_call({ schema: viewer_schemas.opengeodeweb_viewer.import_project, diff --git a/stores/data_base.js b/stores/data_base.js index 8afa3346..006e484a 100644 --- a/stores/data_base.js +++ b/stores/data_base.js @@ -47,7 +47,7 @@ export const useDataBaseStore = defineStore("dataBase", () => { params: { id }, }) } - + const treeviewStore = useTreeviewStore() const hybridViewerStore = useHybridViewerStore() @@ -139,7 +139,10 @@ export const useDataBaseStore = defineStore("dataBase", () => { async function importStores(snapshot) { await hybridViewerStore.initHybridViewer() hybridViewerStore.clear() - console.log("[DataBase] importStores entries:", Object.keys(snapshot?.db || {})) + console.log( + "[DataBase] importStores entries:", + Object.keys(snapshot?.db || {}), + ) for (const [id, item] of Object.entries(snapshot?.db || {})) { await registerObject(id) await addItem(id, item) @@ -187,7 +190,7 @@ export const useDataBaseStore = defineStore("dataBase", () => { getSurfacesUuids, getBlocksUuids, getFlatIndexes, - exportStores, // maintenant défini + exportStores, // maintenant défini importStores, } }) diff --git a/stores/data_style.js b/stores/data_style.js index 8e0c4855..08be4a2c 100644 --- a/stores/data_style.js +++ b/stores/data_style.js @@ -40,7 +40,12 @@ export const useDataStyleStore = defineStore("dataStyle", () => { } function setModelEdgesVisibility(id, visibility) { - modelStyleStore.setModelMeshComponentVisibility(id, "Edge", null, visibility) + modelStyleStore.setModelMeshComponentVisibility( + id, + "Edge", + null, + visibility, + ) } function modelEdgesVisibility(id) { @@ -53,7 +58,8 @@ export const useDataStyleStore = defineStore("dataStyle", () => { async function importStores(snapshot) { const stylesSnapshot = snapshot?.styles || {} - for (const id of Object.keys(dataStyleState.styles)) delete dataStyleState.styles[id] + for (const id of Object.keys(dataStyleState.styles)) + delete dataStyleState.styles[id] for (const [id, style] of Object.entries(stylesSnapshot)) { dataStyleState.styles[id] = style } From fdaca90a90cf56b7bbf47601dd67d69a5835bd68 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 6 Nov 2025 13:21:38 +0100 Subject: [PATCH 38/66] temporary fix for model's style --- internal_stores/model/blocks.js | 21 +++++++++++++++------ internal_stores/model/corners.js | 17 +++++++++++++---- internal_stores/model/surfaces.js | 17 +++++++++++++---- 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/internal_stores/model/blocks.js b/internal_stores/model/blocks.js index 31d73ef9..f14516aa 100644 --- a/internal_stores/model/blocks.js +++ b/internal_stores/model/blocks.js @@ -80,12 +80,21 @@ export function useModelBlocksStyle() { } function applyModelBlocksStyle(id) { - const style = modelBlocksStyle(id) - const blocks_ids = dataBaseStore.getBlocksUuids(id) - return Promise.all([ - setModelBlocksVisibility(id, blocks_ids, style.visibility), - setModelBlocksColor(id, blocks_ids, style.color), - ]) + const style = dataStyleStore.getStyle(id).blocks + const block_ids = dataBaseStore.getBlocksUuids(id) + + if (!block_ids || block_ids.length === 0) { + return Promise.resolve() + } + + const promises = [] + if (typeof style?.visibility === "boolean") { + promises.push(setModelBlocksVisibility(id, block_ids, style.visibility)) + } + if (style?.color) { + promises.push(setModelBlocksColor(id, block_ids, style.color)) + } + return Promise.all(promises) } return { diff --git a/internal_stores/model/corners.js b/internal_stores/model/corners.js index e2d628c3..1ee51a12 100644 --- a/internal_stores/model/corners.js +++ b/internal_stores/model/corners.js @@ -82,10 +82,19 @@ export function useModelCornersStyle() { function applyModelCornersStyle(id) { const style = modelCornersStyle(id) const corner_ids = dataBaseStore.getCornersUuids(id) - return Promise.all([ - setModelCornersVisibility(id, corner_ids, style.visibility), - setModelCornersColor(id, corner_ids, style.color), - ]) + + if (!corner_ids || corner_ids.length === 0) { + return Promise.resolve() + } + + const promises = [] + if (typeof style?.visibility === "boolean") { + promises.push(setModelCornersVisibility(id, corner_ids, style.visibility)) + } + if (style?.color) { + promises.push(setModelCornersColor(id, corner_ids, style.color)) + } + return Promise.all(promises) } return { diff --git a/internal_stores/model/surfaces.js b/internal_stores/model/surfaces.js index 44986c54..b7056b0b 100644 --- a/internal_stores/model/surfaces.js +++ b/internal_stores/model/surfaces.js @@ -76,10 +76,19 @@ export function useModelSurfacesStyle() { function applyModelSurfacesStyle(id) { const style = dataStyleStore.getStyle(id).surfaces const surface_ids = dataBaseStore.getSurfacesUuids(id) - return Promise.all([ - setModelSurfacesVisibility(id, surface_ids, style.visibility), - setModelSurfacesColor(id, surface_ids, style.color), - ]) + + if (!surface_ids || surface_ids.length === 0) { + return Promise.resolve() + } + + const promises = [] + if (typeof style?.visibility === "boolean") { + promises.push(setModelSurfacesVisibility(id, surface_ids, style.visibility)) + } + if (style?.color) { + promises.push(setModelSurfacesColor(id, surface_ids, style.color)) + } + return Promise.all(promises) } return { From 769c481dd48fd55e70484c01d5dde7054d16b42f Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Thu, 6 Nov 2025 12:22:32 +0000 Subject: [PATCH 39/66] Apply prepare changes --- .oxlintrc.json | 7 ++++++- internal_stores/model/blocks.js | 4 ++-- internal_stores/model/surfaces.js | 4 +++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.oxlintrc.json b/.oxlintrc.json index 58e3b153..a033f40f 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -28,7 +28,12 @@ { "files": ["**/components/*"], "rules": { - "unicorn/filename-case": "PascalCase" + "unicorn/filename-case": [ + "error", + { + "case": "PascalCase" + } + ] } } ] diff --git a/internal_stores/model/blocks.js b/internal_stores/model/blocks.js index f14516aa..ebe5e9f0 100644 --- a/internal_stores/model/blocks.js +++ b/internal_stores/model/blocks.js @@ -82,11 +82,11 @@ export function useModelBlocksStyle() { function applyModelBlocksStyle(id) { const style = dataStyleStore.getStyle(id).blocks const block_ids = dataBaseStore.getBlocksUuids(id) - + if (!block_ids || block_ids.length === 0) { return Promise.resolve() } - + const promises = [] if (typeof style?.visibility === "boolean") { promises.push(setModelBlocksVisibility(id, block_ids, style.visibility)) diff --git a/internal_stores/model/surfaces.js b/internal_stores/model/surfaces.js index b7056b0b..af59877c 100644 --- a/internal_stores/model/surfaces.js +++ b/internal_stores/model/surfaces.js @@ -83,7 +83,9 @@ export function useModelSurfacesStyle() { const promises = [] if (typeof style?.visibility === "boolean") { - promises.push(setModelSurfacesVisibility(id, surface_ids, style.visibility)) + promises.push( + setModelSurfacesVisibility(id, surface_ids, style.visibility), + ) } if (style?.color) { promises.push(setModelSurfacesColor(id, surface_ids, style.color)) From 597ca037d3fe548c1a3c6f753dee7fc984b8f21f Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 6 Nov 2025 13:26:28 +0100 Subject: [PATCH 40/66] adding missing edge component_type --- internal_stores/model/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal_stores/model/index.js b/internal_stores/model/index.js index 31b18001..1a2dc9ef 100644 --- a/internal_stores/model/index.js +++ b/internal_stores/model/index.js @@ -75,6 +75,8 @@ export default function useModelStyle() { return modelSurfacesStyleStore.modelSurfaceVisibility(id, component_id) } else if (component_type === "Block") { return modelBlocksStyleStore.modelBlockVisibility(id, component_id) + } else if (component_type === "Edge") { + return modelEdgesStyleStore.modelEdgesVisibility(id) } throw new Error("Unknown model component_type: " + component_type) } @@ -127,6 +129,8 @@ export default function useModelStyle() { [component_id], visibility, ) + } else if (component_type === "Edge") { + return modelEdgesStyleStore.setModelEdgesVisibility(id, visibility) } else { throw new Error("Unknown model component_type: " + component_type) } From 9eda52e9e8b7f141f086e7614c23a9aac5bac4b9 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 6 Nov 2025 13:39:47 +0100 Subject: [PATCH 41/66] fix unused functions --- internal_stores/model/index.js | 4 ---- stores/data_style.js | 9 ++------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/internal_stores/model/index.js b/internal_stores/model/index.js index 1a2dc9ef..ff7ff50b 100644 --- a/internal_stores/model/index.js +++ b/internal_stores/model/index.js @@ -75,8 +75,6 @@ export default function useModelStyle() { return modelSurfacesStyleStore.modelSurfaceVisibility(id, component_id) } else if (component_type === "Block") { return modelBlocksStyleStore.modelBlockVisibility(id, component_id) - } else if (component_type === "Edge") { - return modelEdgesStyleStore.modelEdgesVisibility(id) } throw new Error("Unknown model component_type: " + component_type) } @@ -184,10 +182,8 @@ export default function useModelStyle() { return { modelVisibility, visibleMeshComponents, - modelMeshComponentVisibility, setModelVisibility, setModelColor, - setModelMeshComponentVisibility, applyModelStyle, setModelMeshComponentsDefaultStyle, ...useModelBlocksStyle(), diff --git a/stores/data_style.js b/stores/data_style.js index 08be4a2c..78535714 100644 --- a/stores/data_style.js +++ b/stores/data_style.js @@ -40,16 +40,11 @@ export const useDataStyleStore = defineStore("dataStyle", () => { } function setModelEdgesVisibility(id, visibility) { - modelStyleStore.setModelMeshComponentVisibility( - id, - "Edge", - null, - visibility, - ) + return modelStyleStore.setModelEdgesVisibility(id, visibility) } function modelEdgesVisibility(id) { - return modelStyleStore.modelMeshComponentVisibility(id, "Edge", null) + return modelStyleStore.modelEdgesVisibility(id) } function exportStores() { From b686f444fe42de7ce53eed3c6c7d7f3b00e990dd Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 6 Nov 2025 16:15:56 +0100 Subject: [PATCH 42/66] multi import data work arround --- composables/project_manager.js | 37 ++++++++++++++----------- stores/data_base.js | 2 +- stores/treeview.js | 6 ---- utils/file_import_workflow.js | 50 ++++++++++++++++++++++++++-------- 4 files changed, 60 insertions(+), 35 deletions(-) diff --git a/composables/project_manager.js b/composables/project_manager.js index 228109a2..26059889 100644 --- a/composables/project_manager.js +++ b/composables/project_manager.js @@ -33,26 +33,18 @@ export function useProjectManager() { async function importProjectFile(file) { geode.start_request() try { - console.log("[ProjectManager] Import start with file:", file?.name) - - const infra = useInfraStore() - await infra.create_connection() - const schemaImport = back_schemas.opengeodeweb_back.import_project || {} + await useInfraStore().create_connection() + const schemaImport = back_schemas.opengeodeweb_back.import_project const form = new FormData() - form.append("file", file, file?.name || "project.zip") + form.append("file", file, file?.name) const result = await $fetch(schemaImport.$id, { baseURL: geode.base_url, method: "POST", body: form, }) - const snapshot = result?.snapshot ?? {} - console.log( - "[ProjectManager] snapshot keys:", - Object.keys(snapshot || {}), - ) await viewer_call({ schema: viewer_schemas.opengeodeweb_viewer.import_project, @@ -65,18 +57,31 @@ export function useProjectManager() { const treeviewStore = useTreeviewStore() treeviewStore.isImporting = true - console.log("[ProjectManager] isImporting = true") + await treeviewStore.importStores(snapshot?.treeview) + + const hybridViewerStore = useHybridViewerStore() + await hybridViewerStore.initHybridViewer() + hybridViewerStore.clear() + + const snapshotDataBase = snapshot?.dataBase?.db || {} + const items = Object.entries(snapshotDataBase).map(([id, item]) => ({ + id, + object_type: item.object_type, + geode_object: item.geode_object, + native_filename: item.native_filename, + viewable_filename: item.viewable_filename, + displayed_name: item.displayed_name, + vtk_js: { binary_light_viewable: item?.vtk_js?.binary_light_viewable }, + })) - await appStore.importStores(snapshot) - console.log("[ProjectManager] stores imported") + await importWorkflowFromSnapshot(items) const dataStyleStore = useDataStyleStore() + await dataStyleStore.importStores(snapshot?.dataStyle) await dataStyleStore.applyAllStylesFromState() - console.log("[ProjectManager] styles applied") treeviewStore.finalizeImportSelection() treeviewStore.isImporting = false - console.log("[ProjectManager] isImporting = false") } finally { geode.stop_request() } diff --git a/stores/data_base.js b/stores/data_base.js index 006e484a..e96ffb46 100644 --- a/stores/data_base.js +++ b/stores/data_base.js @@ -190,7 +190,7 @@ export const useDataBaseStore = defineStore("dataBase", () => { getSurfacesUuids, getBlocksUuids, getFlatIndexes, - exportStores, // maintenant défini + exportStores, importStores, } }) diff --git a/stores/treeview.js b/stores/treeview.js index 77ce6573..12d5e0d5 100644 --- a/stores/treeview.js +++ b/stores/treeview.js @@ -8,7 +8,6 @@ export const useTreeviewStore = defineStore("treeview", () => { const isTreeCollection = ref(false) const selectedTree = ref(null) const isImporting = ref(false) - // Buffer pour reconstruire la sélection après que les items soient recréés const pendingSelectionIds = ref([]) // /** Functions **/ @@ -24,7 +23,6 @@ export const useTreeviewStore = defineStore("treeview", () => { sensitivity: "base", }), ) - // Ne pas polluer la sélection pendant import; on la reconstruit ensuite if (!isImporting.value) { selection.value.push(child) } @@ -65,13 +63,11 @@ export const useTreeviewStore = defineStore("treeview", () => { model_id: model_id.value, isTreeCollection: isTreeCollection.value, selectedTree: selectedTree.value, - // Exporter uniquement les IDs pour éviter les problèmes de référence selectionIds: selection.value.map((c) => c.id), } } async function importStores(snapshot) { - // Charger l’état UI isAdditionnalTreeDisplayed.value = snapshot?.isAdditionnalTreeDisplayed || false panelWidth.value = snapshot?.panelWidth || 300 @@ -79,14 +75,12 @@ export const useTreeviewStore = defineStore("treeview", () => { isTreeCollection.value = snapshot?.isTreeCollection || false selectedTree.value = snapshot?.selectedTree || null - // Bufferiser les IDs de sélection (compat v1: snapshot.selection -> ids) pendingSelectionIds.value = snapshot?.selectionIds || (snapshot?.selection || []).map((c) => c.id) || [] } - // Appeler après import pour reconstruire la sélection à partir des items function finalizeImportSelection() { const ids = pendingSelectionIds.value || [] const rebuilt = [] diff --git a/utils/file_import_workflow.js b/utils/file_import_workflow.js index c7a0e13d..5c585655 100644 --- a/utils/file_import_workflow.js +++ b/utils/file_import_workflow.js @@ -6,13 +6,13 @@ import { useHybridViewerStore } from "../stores/hybrid_viewer" async function importWorkflow(files) { console.log("importWorkflow", { files }) - const promise_array = [] - for (const file of files) { - const { filename, geode_object } = file + const results = [] + for (const { filename, geode_object } of files) { console.log({ filename }, { geode_object }) - promise_array.push(importFile(filename, geode_object)) + const id = await importFile(filename, geode_object) + results.push(id) } - return Promise.all(promise_array) + return results } async function importFile(filename, geode_object) { @@ -53,12 +53,9 @@ async function importFile(filename, geode_object) { }, }) - await treeviewStore.addItem(geode_object, name, id, object_type) - - console.log("after treeviewStore.addItem") - - await hybridViewerStore.addItem(id) - console.log("after dataBaseStore.addItem") + // await treeviewStore.addItem(geode_object, name, id, object_type) + // await hybridViewerStore.addItem(id) + // console.log("after dataBaseStore.addItem") await dataStyleStore.addDataStyle( data._value.id, @@ -79,4 +76,33 @@ async function importFile(filename, geode_object) { return data._value.id } -export { importFile, importWorkflow } +async function importWorkflowFromSnapshot(items) { + const dataBaseStore = useDataBaseStore() + const ids = [] + for (const item of items) { + const { + id, + object_type, + geode_object, + native_filename, + viewable_filename, + displayed_name, + vtk_js, + } = item + + await dataBaseStore.registerObject(id) + await dataBaseStore.addItem(id, { + object_type, + geode_object, + native_filename, + viewable_filename, + displayed_name, + vtk_js, + }) + + ids.push(id) + } + return ids +} + +export { importFile, importWorkflow, importWorkflowFromSnapshot } From 7812125ab4234e6e63600ce47d895ec9595caf75 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 6 Nov 2025 16:44:59 +0100 Subject: [PATCH 43/66] test delta --- stores/data_base.js | 12 -------- stores/hybrid_viewer.js | 2 +- utils/file_import_workflow.js | 52 ++++++++++++++++------------------- 3 files changed, 25 insertions(+), 41 deletions(-) diff --git a/stores/data_base.js b/stores/data_base.js index e96ffb46..c2c97959 100644 --- a/stores/data_base.js +++ b/stores/data_base.js @@ -73,18 +73,6 @@ export const useDataBaseStore = defineStore("dataBase", () => { await fetchMeshComponents(id) await fetchUuidToFlatIndexDict(id) } - - treeviewStore.addItem( - value.geode_object, - value.displayed_name, - id, - value.object_type, - ) - console.log("[DataBase] addItem -> treeview.addItem done", id) - - console.log("[DataBase] addItem -> hybridViewer.addItem start", id) - await hybridViewerStore.addItem(id) - console.log("[DataBase] addItem -> hybridViewer.addItem done", id) } async function fetchMeshComponents(id) { diff --git a/stores/hybrid_viewer.js b/stores/hybrid_viewer.js index 3b21d33b..bd318a16 100644 --- a/stores/hybrid_viewer.js +++ b/stores/hybrid_viewer.js @@ -118,7 +118,7 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { params, }, { - response_fonction: () => { + response_function: () => { for (const key in params.camera_options) { camera_options[key] = params.camera_options[key] } diff --git a/utils/file_import_workflow.js b/utils/file_import_workflow.js index 5c585655..d2f2e6ce 100644 --- a/utils/file_import_workflow.js +++ b/utils/file_import_workflow.js @@ -1,25 +1,24 @@ // Third party imports import back_schemas from "@geode/opengeodeweb-back/opengeodeweb_back_schemas.json" -import { useHybridViewerStore } from "../stores/hybrid_viewer" // Local imports async function importWorkflow(files) { console.log("importWorkflow", { files }) - const results = [] + const promise_array = [] for (const { filename, geode_object } of files) { console.log({ filename }, { geode_object }) const id = await importFile(filename, geode_object) - results.push(id) + promise_array.push(id) } - return results + return promise_array } async function importFile(filename, geode_object) { const dataBaseStore = useDataBaseStore() const dataStyleStore = useDataStyleStore() - const hybridViewerStore = useHybridViewerStore() const treeviewStore = useTreeviewStore() + const hybridViewerStore = useHybridViewerStore() const { data } = await api_fetch({ schema: back_schemas.opengeodeweb_back.save_viewable_file, params: { @@ -37,47 +36,41 @@ async function importFile(filename, geode_object) { binary_light_viewable, } = data._value - console.log("data._value", data._value) + console.log("[importFile] response", { + id, + geode_object, + input_file: data._value?.input_file, + name, + }) - console.log("data._value.id", data._value.id) - await dataBaseStore.registerObject(data._value.id) - console.log("after dataBaseStore.registerObject") + await dataBaseStore.registerObject(id) await dataBaseStore.addItem(id, { - object_type: object_type, - geode_object: geode_object, + object_type, + geode_object, native_filename: native_file_name, viewable_filename: viewable_file_name, displayed_name: name, - vtk_js: { - binary_light_viewable, - }, + vtk_js: { binary_light_viewable }, }) - // await treeviewStore.addItem(geode_object, name, id, object_type) - // await hybridViewerStore.addItem(id) - // console.log("after dataBaseStore.addItem") + await treeviewStore.addItem(geode_object, name, id, object_type) + await hybridViewerStore.addItem(id) - await dataStyleStore.addDataStyle( - data._value.id, - data._value.geode_object, - data._value.object_type, - ) - console.log("after dataStyleStore.addDataStyle") - if (data._value.object_type === "model") { + await dataStyleStore.addDataStyle(id, geode_object, object_type) + if (object_type === "model") { await Promise.all([ dataBaseStore.fetchMeshComponents(id), dataBaseStore.fetchUuidToFlatIndexDict(id), ]) - console.log("after dataBaseStore.fetchMeshComponents") - console.log("after dataBaseStore.fetchUuidToFlatIndexDict") } await dataStyleStore.applyDefaultStyle(id) - console.log("after dataStyleStore.applyDefaultStyle") - return data._value.id + return id } async function importWorkflowFromSnapshot(items) { const dataBaseStore = useDataBaseStore() + const treeviewStore = useTreeviewStore() + const hybridViewerStore = useHybridViewerStore() const ids = [] for (const item of items) { const { @@ -100,6 +93,9 @@ async function importWorkflowFromSnapshot(items) { vtk_js, }) + await treeviewStore.addItem(geode_object, displayed_name, id, object_type) + await hybridViewerStore.addItem(id) + ids.push(id) } return ids From e23b7e70c5b491a3387cfdaa2d0bb86eaec84e0e Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 6 Nov 2025 16:52:54 +0100 Subject: [PATCH 44/66] from next --- utils/file_import_workflow.js | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/utils/file_import_workflow.js b/utils/file_import_workflow.js index d2f2e6ce..9ce3e049 100644 --- a/utils/file_import_workflow.js +++ b/utils/file_import_workflow.js @@ -17,8 +17,8 @@ async function importWorkflow(files) { async function importFile(filename, geode_object) { const dataBaseStore = useDataBaseStore() const dataStyleStore = useDataStyleStore() - const treeviewStore = useTreeviewStore() const hybridViewerStore = useHybridViewerStore() + const treeviewStore = useTreeviewStore() const { data } = await api_fetch({ schema: back_schemas.opengeodeweb_back.save_viewable_file, params: { @@ -45,26 +45,37 @@ async function importFile(filename, geode_object) { await dataBaseStore.registerObject(id) await dataBaseStore.addItem(id, { - object_type, - geode_object, + object_type: object_type, + geode_object: geode_object, native_filename: native_file_name, viewable_filename: viewable_file_name, displayed_name: name, - vtk_js: { binary_light_viewable }, + vtk_js: { + binary_light_viewable, + }, }) await treeviewStore.addItem(geode_object, name, id, object_type) + + console.log("after treeviewStore.addItem") + await hybridViewerStore.addItem(id) + console.log("after dataBaseStore.addItem") - await dataStyleStore.addDataStyle(id, geode_object, object_type) - if (object_type === "model") { + await dataStyleStore.addDataStyle( + data._value.id, + data._value.geode_object, + data._value.object_type, + ) + if (data._value.object_type === "model") { await Promise.all([ dataBaseStore.fetchMeshComponents(id), dataBaseStore.fetchUuidToFlatIndexDict(id), ]) } await dataStyleStore.applyDefaultStyle(id) - return id + console.log("after dataStyleStore.applyDefaultStyle") + return data._value.id } async function importWorkflowFromSnapshot(items) { From 57e13f3cd2adaaabf3c1c0e3d64b020e82acc9db Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 6 Nov 2025 18:47:01 +0100 Subject: [PATCH 45/66] test camera_options --- stores/hybrid_viewer.js | 84 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/stores/hybrid_viewer.js b/stores/hybrid_viewer.js index bd318a16..604c35e5 100644 --- a/stores/hybrid_viewer.js +++ b/stores/hybrid_viewer.js @@ -199,7 +199,38 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { } function exportStores() { - return { zScale: zScale.value } + const cam = + Object.keys(camera_options).length > 0 + ? { ...camera_options } + : (() => { + if (!genericRenderWindow.value || status.value !== Status.CREATED) { + return undefined + } + const renderer = genericRenderWindow.value.getRenderer() + const camera = renderer.getActiveCamera() + return { + focal_point: camera.getFocalPoint(), + view_up: camera.getViewUp(), + position: camera.getPosition(), + view_angle: camera.getViewAngle(), + clipping_range: camera.getClippingRange(), + distance: camera.getDistance(), + // Ajouts pour style et zoom corrects + parallel_projection: + typeof camera.getParallelProjection === "function" + ? camera.getParallelProjection() + : undefined, + parallel_scale: + typeof camera.getParallelScale === "function" + ? camera.getParallelScale() + : undefined, + roll: + typeof camera.getRoll === "function" + ? camera.getRoll() + : undefined, + } + })() + return { zScale: zScale.value, camera_options: cam } } async function importStores(snapshot) { @@ -207,6 +238,57 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { if (z_scale != null) { await setZScaling(z_scale) } + + const cam = snapshot?.camera_options + if (cam && genericRenderWindow.value && status.value === Status.CREATED) { + const renderer = genericRenderWindow.value.getRenderer() + const camera = renderer.getActiveCamera() + + // Appliquer après que la scène soit prête (évite les reset ultérieurs) + await new Promise((resolve) => requestAnimationFrame(resolve)) + + // Mode de projection et zoom + if (cam.parallel_projection !== undefined && + typeof camera.setParallelProjection === "function") { + camera.setParallelProjection(!!cam.parallel_projection) + } + + // Pose / orientation + if (cam.focal_point) camera.setFocalPoint(...cam.focal_point) + if (cam.view_up) camera.setViewUp(...cam.view_up) + if (cam.position) camera.setPosition(...cam.position) + + // Zoom selon le mode + if (typeof camera.getParallelProjection === "function" && + camera.getParallelProjection() && + cam.parallel_scale !== undefined && + typeof camera.setParallelScale === "function") { + camera.setParallelScale(cam.parallel_scale) + } else if (cam.view_angle != null) { + camera.setViewAngle(cam.view_angle) + } + + // Roll (si dispo) + if (cam.roll != null && typeof camera.setRoll === "function") { + camera.setRoll(cam.roll) + } + + // Clipping et rendu + if (cam.clipping_range) camera.setClippingRange(...cam.clipping_range) + if (typeof camera.modified === "function") camera.modified() + renderer.resetCameraClippingRange() + genericRenderWindow.value.getRenderWindow().render() + + // Sync côté viewer distant (si schéma présent) + const schema = + viewer_schemas?.opengeodeweb_viewer?.viewer?.update_camera + if (schema) { + await viewer_call({ + schema, + params: { camera_options: cam }, + }) + } + } } return { From 6136cde594ede76838d0cfd46ac29175778a23ef Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Thu, 6 Nov 2025 17:53:49 +0000 Subject: [PATCH 46/66] Apply prepare changes --- stores/hybrid_viewer.js | 19 +++++++++++-------- .../microservices/back/requirements.txt | 1 - .../microservices/viewer/requirements.txt | 1 - 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/stores/hybrid_viewer.js b/stores/hybrid_viewer.js index 604c35e5..a59e6a64 100644 --- a/stores/hybrid_viewer.js +++ b/stores/hybrid_viewer.js @@ -248,8 +248,10 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { await new Promise((resolve) => requestAnimationFrame(resolve)) // Mode de projection et zoom - if (cam.parallel_projection !== undefined && - typeof camera.setParallelProjection === "function") { + if ( + cam.parallel_projection !== undefined && + typeof camera.setParallelProjection === "function" + ) { camera.setParallelProjection(!!cam.parallel_projection) } @@ -259,10 +261,12 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { if (cam.position) camera.setPosition(...cam.position) // Zoom selon le mode - if (typeof camera.getParallelProjection === "function" && - camera.getParallelProjection() && - cam.parallel_scale !== undefined && - typeof camera.setParallelScale === "function") { + if ( + typeof camera.getParallelProjection === "function" && + camera.getParallelProjection() && + cam.parallel_scale !== undefined && + typeof camera.setParallelScale === "function" + ) { camera.setParallelScale(cam.parallel_scale) } else if (cam.view_angle != null) { camera.setViewAngle(cam.view_angle) @@ -280,8 +284,7 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { genericRenderWindow.value.getRenderWindow().render() // Sync côté viewer distant (si schéma présent) - const schema = - viewer_schemas?.opengeodeweb_viewer?.viewer?.update_camera + const schema = viewer_schemas?.opengeodeweb_viewer?.viewer?.update_camera if (schema) { await viewer_call({ schema, diff --git a/tests/integration/microservices/back/requirements.txt b/tests/integration/microservices/back/requirements.txt index 72f4bfff..bd3a3ef5 100644 --- a/tests/integration/microservices/back/requirements.txt +++ b/tests/integration/microservices/back/requirements.txt @@ -5,4 +5,3 @@ # pip-compile --output-file=tests/integration/microservices/back/requirements.txt tests/integration/microservices/back/requirements.in # -opengeodeweb-back==5.*,>=5.12.0rc1 diff --git a/tests/integration/microservices/viewer/requirements.txt b/tests/integration/microservices/viewer/requirements.txt index ee92447e..4d097394 100644 --- a/tests/integration/microservices/viewer/requirements.txt +++ b/tests/integration/microservices/viewer/requirements.txt @@ -5,4 +5,3 @@ # pip-compile --output-file=tests/integration/microservices/viewer/requirements.txt tests/integration/microservices/viewer/requirements.in # -opengeodeweb-viewer==1.*,>=1.11.8rc2 From eecd8b761723365ffc2ada16e5562e401bf22128 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 6 Nov 2025 21:31:28 +0100 Subject: [PATCH 47/66] restore --- internal_stores/model/surfaces.js | 11 ++-- stores/data_style.js | 51 ++++++++---------- stores/hybrid_viewer.js | 87 +------------------------------ 3 files changed, 26 insertions(+), 123 deletions(-) diff --git a/internal_stores/model/surfaces.js b/internal_stores/model/surfaces.js index 1d33599d..af59877c 100644 --- a/internal_stores/model/surfaces.js +++ b/internal_stores/model/surfaces.js @@ -8,14 +8,11 @@ export function useModelSurfacesStyle() { const dataStyleStore = useDataStyleStore() const dataBaseStore = useDataBaseStore() - function modelSurfacesStyle(id) { - return dataStyleStore.getStyle(id).surfaces - } function modelSurfaceStyle(id, surface_id) { - if (!modelSurfacesStyle(id)[surface_id]) { - modelSurfacesStyle(id)[surface_id] = {} + if (!dataStyleStore.getStyle(id).surfaces[surface_id]) { + dataStyleStore.getStyle(id).surfaces[surface_id] = {} } - return modelSurfacesStyle(id)[surface_id] + return dataStyleStore.getStyle(id).surfaces[surface_id] } function modelSurfaceVisibility(id, surface_id) { @@ -77,7 +74,7 @@ export function useModelSurfacesStyle() { } function applyModelSurfacesStyle(id) { - const style = modelSurfacesStyle(id) + const style = dataStyleStore.getStyle(id).surfaces const surface_ids = dataBaseStore.getSurfacesUuids(id) if (!surface_ids || surface_ids.length === 0) { diff --git a/stores/data_style.js b/stores/data_style.js index f450075d..78535714 100644 --- a/stores/data_style.js +++ b/stores/data_style.js @@ -14,44 +14,37 @@ export const useDataStyleStore = defineStore("dataStyle", () => { dataStyleState.styles[id] = getDefaultStyle(geode_object) } - // useDataStyleStore: setVisibility - function setVisibility(payloadOrId, visibility) { - const id = - typeof payloadOrId === "string" - ? payloadOrId - : (payloadOrId?.id ?? payloadOrId?.data_id ?? payloadOrId?.model_id) - if (!id) return Promise.resolve([]) - - const visible = - typeof visibility === "boolean" - ? visibility - : payloadOrId?.visible != null - ? !!payloadOrId.visible - : true - + function setVisibility(id, visibility) { const meta = dataBaseStore.itemMetaDatas(id) - const object_type = meta?.object_type - if (!object_type) return Promise.resolve([]) - + if (!meta) { + console.warn("[DataStyle] setVisibility skipped: unknown id", id) + return Promise.resolve([]) + } + const object_type = meta.object_type if (object_type === "mesh") { return Promise.all([meshStyleStore.setMeshVisibility(id, visibility)]) } else if (object_type === "model") { return Promise.all([modelStyleStore.setModelVisibility(id, visibility)]) } + throw new Error("Unknown object_type") + } + + function applyDefaultStyle(id) { + const { object_type } = dataBaseStore.itemMetaDatas(id) + if (object_type === "mesh") { + return meshStyleStore.applyMeshStyle(id) + } else if (object_type === "model") { + return modelStyleStore.applyModelStyle(id) + } return Promise.resolve([]) } function setModelEdgesVisibility(id, visibility) { - modelStyleStore.setModelMeshComponentVisibility( - id, - "Edge", - null, - visibility, - ) + return modelStyleStore.setModelEdgesVisibility(id, visibility) } function modelEdgesVisibility(id) { - return modelStyleStore.modelMeshComponentVisibility(id, "Edge", null) + return modelStyleStore.modelEdgesVisibility(id) } function exportStores() { @@ -84,6 +77,8 @@ export const useDataStyleStore = defineStore("dataStyle", () => { return { ...dataStyleState, + ...meshStyleStore, + ...modelStyleStore, addDataStyle, applyDefaultStyle, setVisibility, @@ -91,10 +86,6 @@ export const useDataStyleStore = defineStore("dataStyle", () => { modelEdgesVisibility, exportStores, importStores, - ...meshStyleStore, - ...modelStyleStore, - addDataStyle, - applyDefaultStyle, - setVisibility, + applyAllStylesFromState, } }) diff --git a/stores/hybrid_viewer.js b/stores/hybrid_viewer.js index a59e6a64..bd318a16 100644 --- a/stores/hybrid_viewer.js +++ b/stores/hybrid_viewer.js @@ -199,38 +199,7 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { } function exportStores() { - const cam = - Object.keys(camera_options).length > 0 - ? { ...camera_options } - : (() => { - if (!genericRenderWindow.value || status.value !== Status.CREATED) { - return undefined - } - const renderer = genericRenderWindow.value.getRenderer() - const camera = renderer.getActiveCamera() - return { - focal_point: camera.getFocalPoint(), - view_up: camera.getViewUp(), - position: camera.getPosition(), - view_angle: camera.getViewAngle(), - clipping_range: camera.getClippingRange(), - distance: camera.getDistance(), - // Ajouts pour style et zoom corrects - parallel_projection: - typeof camera.getParallelProjection === "function" - ? camera.getParallelProjection() - : undefined, - parallel_scale: - typeof camera.getParallelScale === "function" - ? camera.getParallelScale() - : undefined, - roll: - typeof camera.getRoll === "function" - ? camera.getRoll() - : undefined, - } - })() - return { zScale: zScale.value, camera_options: cam } + return { zScale: zScale.value } } async function importStores(snapshot) { @@ -238,60 +207,6 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { if (z_scale != null) { await setZScaling(z_scale) } - - const cam = snapshot?.camera_options - if (cam && genericRenderWindow.value && status.value === Status.CREATED) { - const renderer = genericRenderWindow.value.getRenderer() - const camera = renderer.getActiveCamera() - - // Appliquer après que la scène soit prête (évite les reset ultérieurs) - await new Promise((resolve) => requestAnimationFrame(resolve)) - - // Mode de projection et zoom - if ( - cam.parallel_projection !== undefined && - typeof camera.setParallelProjection === "function" - ) { - camera.setParallelProjection(!!cam.parallel_projection) - } - - // Pose / orientation - if (cam.focal_point) camera.setFocalPoint(...cam.focal_point) - if (cam.view_up) camera.setViewUp(...cam.view_up) - if (cam.position) camera.setPosition(...cam.position) - - // Zoom selon le mode - if ( - typeof camera.getParallelProjection === "function" && - camera.getParallelProjection() && - cam.parallel_scale !== undefined && - typeof camera.setParallelScale === "function" - ) { - camera.setParallelScale(cam.parallel_scale) - } else if (cam.view_angle != null) { - camera.setViewAngle(cam.view_angle) - } - - // Roll (si dispo) - if (cam.roll != null && typeof camera.setRoll === "function") { - camera.setRoll(cam.roll) - } - - // Clipping et rendu - if (cam.clipping_range) camera.setClippingRange(...cam.clipping_range) - if (typeof camera.modified === "function") camera.modified() - renderer.resetCameraClippingRange() - genericRenderWindow.value.getRenderWindow().render() - - // Sync côté viewer distant (si schéma présent) - const schema = viewer_schemas?.opengeodeweb_viewer?.viewer?.update_camera - if (schema) { - await viewer_call({ - schema, - params: { camera_options: cam }, - }) - } - } } return { From 328eb1448f7ed4a37c9585b39911fa7f338153d3 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Fri, 7 Nov 2025 12:13:24 +0100 Subject: [PATCH 48/66] test --- composables/project_manager.js | 4 ++++ stores/hybrid_viewer.js | 37 +++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/composables/project_manager.js b/composables/project_manager.js index 26059889..011a6aa2 100644 --- a/composables/project_manager.js +++ b/composables/project_manager.js @@ -62,6 +62,7 @@ export function useProjectManager() { const hybridViewerStore = useHybridViewerStore() await hybridViewerStore.initHybridViewer() hybridViewerStore.clear() + await hybridViewerStore.importStores(snapshot?.hybridViewer) const snapshotDataBase = snapshot?.dataBase?.db || {} const items = Object.entries(snapshotDataBase).map(([id, item]) => ({ @@ -76,6 +77,9 @@ export function useProjectManager() { await importWorkflowFromSnapshot(items) + // Appliquer la caméra importée après avoir créé les actors + await hybridViewerStore.importStores(snapshot?.hybridViewer) + const dataStyleStore = useDataStyleStore() await dataStyleStore.importStores(snapshot?.dataStyle) await dataStyleStore.applyAllStylesFromState() diff --git a/stores/hybrid_viewer.js b/stores/hybrid_viewer.js index bd318a16..0a2604b0 100644 --- a/stores/hybrid_viewer.js +++ b/stores/hybrid_viewer.js @@ -199,7 +199,19 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { } function exportStores() { - return { zScale: zScale.value } + const renderer = genericRenderWindow.value?.getRenderer?.() + const camera = renderer?.getActiveCamera?.() + const cameraSnapshot = camera + ? { + focal_point: Array.from(camera.getFocalPoint()), + view_up: Array.from(camera.getViewUp()), + position: Array.from(camera.getPosition()), + view_angle: camera.getViewAngle(), + clipping_range: Array.from(camera.getClippingRange()), + distance: camera.getDistance(), + } + : camera_options + return { zScale: zScale.value, camera_options: cameraSnapshot } } async function importStores(snapshot) { @@ -207,6 +219,29 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { if (z_scale != null) { await setZScaling(z_scale) } + + const cam = snapshot?.camera_options + if (cam) { + const renderer = genericRenderWindow.value.getRenderer() + const camera = renderer.getActiveCamera() + + if (cam.focal_point) camera.setFocalPoint(cam.focal_point) + if (cam.view_up) camera.setViewUp(cam.view_up) + if (cam.position) camera.setPosition(cam.position) + if (cam.view_angle != null) camera.setViewAngle(cam.view_angle) + if (cam.clipping_range) camera.setClippingRange(cam.clipping_range) + + genericRenderWindow.value.getRenderWindow().render() + + await viewer_call({ + schema: viewer_schemas.opengeodeweb_viewer.viewer.update_camera, + params: { camera_options: cam }, + }) + + for (const key in cam) { + camera_options[key] = cam[key] + } + } } return { From 7d392c0fd84f89055d9616a56303e72958224cb6 Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Fri, 7 Nov 2025 11:14:54 +0000 Subject: [PATCH 49/66] Apply prepare changes --- tests/integration/microservices/back/requirements.txt | 1 - tests/integration/microservices/viewer/requirements.txt | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/integration/microservices/back/requirements.txt b/tests/integration/microservices/back/requirements.txt index 0c0e8387..bd3a3ef5 100644 --- a/tests/integration/microservices/back/requirements.txt +++ b/tests/integration/microservices/back/requirements.txt @@ -5,4 +5,3 @@ # pip-compile --output-file=tests/integration/microservices/back/requirements.txt tests/integration/microservices/back/requirements.in # -opengeodeweb-back==5.*,>=5.12.0 diff --git a/tests/integration/microservices/viewer/requirements.txt b/tests/integration/microservices/viewer/requirements.txt index 2b457d60..4d097394 100644 --- a/tests/integration/microservices/viewer/requirements.txt +++ b/tests/integration/microservices/viewer/requirements.txt @@ -5,4 +5,3 @@ # pip-compile --output-file=tests/integration/microservices/viewer/requirements.txt tests/integration/microservices/viewer/requirements.in # -opengeodeweb-viewer==1.*,>=1.11.8 From 5f65391d4a3459563cc0f776e4412a7f7e05ff42 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Fri, 7 Nov 2025 12:25:12 +0100 Subject: [PATCH 50/66] revert internal_stores --- internal_stores/mesh/index.js | 250 +++++++++++++++++++++++------- internal_stores/model/blocks.js | 21 +-- internal_stores/model/corners.js | 17 +- internal_stores/model/index.js | 4 +- internal_stores/model/surfaces.js | 30 ++-- 5 files changed, 217 insertions(+), 105 deletions(-) diff --git a/internal_stores/mesh/index.js b/internal_stores/mesh/index.js index d7052111..56cb35ee 100644 --- a/internal_stores/mesh/index.js +++ b/internal_stores/mesh/index.js @@ -1,76 +1,214 @@ -// Third party imports +import "@kitware/vtk.js/Rendering/Profiles/Geometry" +import vtkGenericRenderWindow from "@kitware/vtk.js/Rendering/Misc/GenericRenderWindow" +import vtkXMLPolyDataReader from "@kitware/vtk.js/IO/XML/XMLPolyDataReader" +import vtkMapper from "@kitware/vtk.js/Rendering/Core/Mapper" +import vtkActor from "@kitware/vtk.js/Rendering/Core/Actor" + import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json" +import Status from "@ogw_f/utils/status.js" + +export const useHybridViewerStore = defineStore("hybridViewer", () => { + const viewerStore = useViewerStore() + const dataBaseStore = useDataBaseStore() + const db = reactive({}) + const status = ref(Status.NOT_CREATED) + const camera_options = reactive({}) + const genericRenderWindow = reactive({}) + const is_moving = ref(false) + const zScale = ref(1.0) + let viewStream + let gridActor = null + + async function initHybridViewer() { + if (status.value !== Status.NOT_CREATED) return + status.value = Status.CREATING + genericRenderWindow.value = vtkGenericRenderWindow.newInstance({ + background: [180 / 255, 180 / 255, 180 / 255], + listenWindowResize: false, + }) -// Local imports -import { useMeshPointsStyle } from "./points.js" -import { useMeshEdgesStyle } from "./edges.js" -import { useMeshPolygonsStyle } from "./polygons.js" -import { useMeshPolyhedraStyle } from "./polyhedra.js" + const webGLRenderWindow = + genericRenderWindow.value.getApiSpecificRenderWindow() + const imageStyle = webGLRenderWindow.getReferenceByName("bgImage").style + imageStyle.transition = "opacity 0.1s ease-in" + imageStyle.zIndex = 1 -// Local constants -const mesh_schemas = viewer_schemas.opengeodeweb_viewer.mesh + await viewerStore.ws_connect() + viewStream = viewerStore.client.getImageStream().createViewStream("-1") + viewStream.onImageReady((e) => { + if (is_moving.value) return + const webGLRenderWindow = + genericRenderWindow.value.getApiSpecificRenderWindow() + const imageStyle = webGLRenderWindow.getReferenceByName("bgImage").style + webGLRenderWindow.setBackgroundImage(e.image) + imageStyle.opacity = 1 + }) -export default function useMeshStyle() { - const dataStyleStore = useDataStyleStore() - const pointsStyleStore = useMeshPointsStyle() - const edgesStyleStore = useMeshEdgesStyle() - const meshPolygonsStyleStore = useMeshPolygonsStyle() - const meshPolyhedraStyleStore = useMeshPolyhedraStyle() - const hybridViewerStore = useHybridViewerStore() + status.value = Status.CREATED + } + + async function addItem(id) { + if (!genericRenderWindow.value) { + return + } + const value = dataBaseStore.db[id] + console.log("hybridViewerStore.addItem", { value }) + const reader = vtkXMLPolyDataReader.newInstance() + const textEncoder = new TextEncoder() + await reader.parseAsArrayBuffer( + textEncoder.encode(value.vtk_js.binary_light_viewable), + ) + const polydata = reader.getOutputData(0) + const mapper = vtkMapper.newInstance() + mapper.setInputData(polydata) + const actor = vtkActor.newInstance() + actor.getProperty().setColor(20 / 255, 20 / 255, 20 / 255) + actor.setMapper(mapper) + const renderer = genericRenderWindow.value.getRenderer() + const renderWindow = genericRenderWindow.value.getRenderWindow() + renderer.addActor(actor) + renderer.resetCamera() + renderWindow.render() + db[id] = { actor, polydata, mapper } + } - function meshVisibility(id) { - return dataStyleStore.getStyle(id).visibility + async function setVisibility(id, visibility) { + db[id].actor.setVisibility(visibility) + const renderWindow = genericRenderWindow.value.getRenderWindow() + renderWindow.render() + } + async function setZScaling(z_scale) { + zScale.value = z_scale + const renderer = genericRenderWindow.value.getRenderer() + const actors = renderer.getActors() + actors.forEach((actor) => { + if (actor !== gridActor) { + const scale = actor.getScale() + actor.setScale(scale[0], scale[1], z_scale) + } + }) + renderer.resetCamera() + genericRenderWindow.value.getRenderWindow().render() + const schema = viewer_schemas?.opengeodeweb_viewer?.viewer?.set_z_scaling + if (!schema) return + await viewer_call({ + schema, + params: { + z_scale: z_scale, + }, + }) } - function setMeshVisibility(id, visibility) { - return viewer_call( + + function syncRemoteCamera() { + console.log("syncRemoteCamera") + const renderer = genericRenderWindow.value.getRenderer() + const camera = renderer.getActiveCamera() + const params = { + camera_options: { + focal_point: camera.getFocalPoint(), + view_up: camera.getViewUp(), + position: camera.getPosition(), + view_angle: camera.getViewAngle(), + clipping_range: camera.getClippingRange(), + distance: camera.getDistance(), + }, + } + viewer_call( { - schema: mesh_schemas.visibility, - params: { id, visibility }, + schema: viewer_schemas.opengeodeweb_viewer.viewer.update_camera, + params, }, { response_function: () => { - hybridViewerStore.setVisibility(id, visibility) - dataStyleStore.getStyle(id).visibility = visibility - console.log(setMeshVisibility.name, { id }, meshVisibility(id)) + remoteRender() + for (const key in params.camera_options) { + camera_options[key] = params.camera_options[key] + } }, }, ) } - function applyMeshStyle(id) { - const style = dataStyleStore.getStyle(id) - console.log( - "[MeshStyle] applyMeshDefaultStyle for id:", - id, - "style:", - style, - ) - const promise_array = [] - for (const [key, value] of Object.entries(style)) { - if (key === "visibility") { - promise_array.push(setMeshVisibility(id, value)) - } else if (key === "points") { - promise_array.push(pointsStyleStore.applyMeshPointsStyle(id)) - } else if (key === "edges") { - promise_array.push(edgesStyleStore.applyMeshEdgesStyle(id)) - } else if (key === "polygons") { - promise_array.push(meshPolygonsStyleStore.applyMeshPolygonsStyle(id)) - } else if (key === "polyhedra") { - promise_array.push(meshPolyhedraStyleStore.applyMeshPolyhedraStyle(id)) - } else { - throw new Error("Unknown mesh key: " + key) - } + function remoteRender() { + viewer_call({ + schema: viewer_schemas.opengeodeweb_viewer.viewer.render, + }) + } + + function setContainer(container) { + genericRenderWindow.value.setContainer(container.value.$el) + const webGLRenderWindow = + genericRenderWindow.value.getApiSpecificRenderWindow() + webGLRenderWindow.setUseBackgroundImage(true) + const imageStyle = webGLRenderWindow.getReferenceByName("bgImage").style + imageStyle.transition = "opacity 0.1s ease-in" + imageStyle.zIndex = 1 + resize(container.value.$el.offsetWidth, container.value.$el.offsetHeight) + console.log("setContainer", container.value.$el) + + useMousePressed({ + target: container, + onPressed: (event) => { + console.log("onPressed") + if (event.button == 0) { + is_moving.value = true + event.stopPropagation() + imageStyle.opacity = 0 + } + }, + onReleased: () => { + if (!is_moving.value) { + return + } + is_moving.value = false + console.log("onReleased") + syncRemoteCamera() + }, + }) + + let wheelEventEndTimeout = null + useEventListener(container, "wheel", () => { + is_moving.value = true + imageStyle.opacity = 0 + clearTimeout(wheelEventEndTimeout) + wheelEventEndTimeout = setTimeout(() => { + is_moving.value = false + syncRemoteCamera() + }, 600) + }) + } + + async function resize(width, height) { + if ( + viewerStore.status !== Status.CONNECTED || + status.value !== Status.CREATED + ) { + return } - return Promise.all(promise_array) + const webGLRenderWindow = + genericRenderWindow.value.getApiSpecificRenderWindow() + const canvas = webGLRenderWindow.getCanvas() + canvas.width = width + canvas.height = height + await nextTick() + webGLRenderWindow.setSize(width, height) + viewStream.setSize(width, height) + const renderWindow = genericRenderWindow.value.getRenderWindow() + renderWindow.render() + remoteRender() } return { - meshVisibility, - setMeshVisibility, - applyMeshStyle, - ...useMeshPointsStyle(), - ...useMeshEdgesStyle(), - ...useMeshPolygonsStyle(), - ...useMeshPolyhedraStyle(), + db, + genericRenderWindow, + addItem, + setVisibility, + setZScaling, + syncRemoteCamera, + initHybridViewer, + remoteRender, + resize, + setContainer, + zScale, } -} +}) diff --git a/internal_stores/model/blocks.js b/internal_stores/model/blocks.js index ebe5e9f0..31d73ef9 100644 --- a/internal_stores/model/blocks.js +++ b/internal_stores/model/blocks.js @@ -80,21 +80,12 @@ export function useModelBlocksStyle() { } function applyModelBlocksStyle(id) { - const style = dataStyleStore.getStyle(id).blocks - const block_ids = dataBaseStore.getBlocksUuids(id) - - if (!block_ids || block_ids.length === 0) { - return Promise.resolve() - } - - const promises = [] - if (typeof style?.visibility === "boolean") { - promises.push(setModelBlocksVisibility(id, block_ids, style.visibility)) - } - if (style?.color) { - promises.push(setModelBlocksColor(id, block_ids, style.color)) - } - return Promise.all(promises) + const style = modelBlocksStyle(id) + const blocks_ids = dataBaseStore.getBlocksUuids(id) + return Promise.all([ + setModelBlocksVisibility(id, blocks_ids, style.visibility), + setModelBlocksColor(id, blocks_ids, style.color), + ]) } return { diff --git a/internal_stores/model/corners.js b/internal_stores/model/corners.js index 1ee51a12..e2d628c3 100644 --- a/internal_stores/model/corners.js +++ b/internal_stores/model/corners.js @@ -82,19 +82,10 @@ export function useModelCornersStyle() { function applyModelCornersStyle(id) { const style = modelCornersStyle(id) const corner_ids = dataBaseStore.getCornersUuids(id) - - if (!corner_ids || corner_ids.length === 0) { - return Promise.resolve() - } - - const promises = [] - if (typeof style?.visibility === "boolean") { - promises.push(setModelCornersVisibility(id, corner_ids, style.visibility)) - } - if (style?.color) { - promises.push(setModelCornersColor(id, corner_ids, style.color)) - } - return Promise.all(promises) + return Promise.all([ + setModelCornersVisibility(id, corner_ids, style.visibility), + setModelCornersColor(id, corner_ids, style.color), + ]) } return { diff --git a/internal_stores/model/index.js b/internal_stores/model/index.js index ff7ff50b..31b18001 100644 --- a/internal_stores/model/index.js +++ b/internal_stores/model/index.js @@ -127,8 +127,6 @@ export default function useModelStyle() { [component_id], visibility, ) - } else if (component_type === "Edge") { - return modelEdgesStyleStore.setModelEdgesVisibility(id, visibility) } else { throw new Error("Unknown model component_type: " + component_type) } @@ -182,8 +180,10 @@ export default function useModelStyle() { return { modelVisibility, visibleMeshComponents, + modelMeshComponentVisibility, setModelVisibility, setModelColor, + setModelMeshComponentVisibility, applyModelStyle, setModelMeshComponentsDefaultStyle, ...useModelBlocksStyle(), diff --git a/internal_stores/model/surfaces.js b/internal_stores/model/surfaces.js index af59877c..b7aa4a7b 100644 --- a/internal_stores/model/surfaces.js +++ b/internal_stores/model/surfaces.js @@ -8,11 +8,14 @@ export function useModelSurfacesStyle() { const dataStyleStore = useDataStyleStore() const dataBaseStore = useDataBaseStore() + function modelSurfacesStyle(id) { + return dataStyleStore.getStyle(id).surfaces + } function modelSurfaceStyle(id, surface_id) { - if (!dataStyleStore.getStyle(id).surfaces[surface_id]) { - dataStyleStore.getStyle(id).surfaces[surface_id] = {} + if (!modelSurfacesStyle(id)[surface_id]) { + modelSurfacesStyle(id)[surface_id] = {} } - return dataStyleStore.getStyle(id).surfaces[surface_id] + return modelSurfacesStyle(id)[surface_id] } function modelSurfaceVisibility(id, surface_id) { @@ -74,23 +77,12 @@ export function useModelSurfacesStyle() { } function applyModelSurfacesStyle(id) { - const style = dataStyleStore.getStyle(id).surfaces + const style = modelSurfacesStyle(id) const surface_ids = dataBaseStore.getSurfacesUuids(id) - - if (!surface_ids || surface_ids.length === 0) { - return Promise.resolve() - } - - const promises = [] - if (typeof style?.visibility === "boolean") { - promises.push( - setModelSurfacesVisibility(id, surface_ids, style.visibility), - ) - } - if (style?.color) { - promises.push(setModelSurfacesColor(id, surface_ids, style.color)) - } - return Promise.all(promises) + return Promise.all([ + setModelSurfacesVisibility(id, surface_ids, style.visibility), + setModelSurfacesColor(id, surface_ids, style.color), + ]) } return { From f6e73f288367fbc42fcf84eb4df467b8b3433701 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Fri, 7 Nov 2025 12:26:39 +0100 Subject: [PATCH 51/66] revert --- internal_stores/mesh/index.js | 244 +++++++--------------------------- 1 file changed, 50 insertions(+), 194 deletions(-) diff --git a/internal_stores/mesh/index.js b/internal_stores/mesh/index.js index 56cb35ee..1d5e1d10 100644 --- a/internal_stores/mesh/index.js +++ b/internal_stores/mesh/index.js @@ -1,214 +1,70 @@ -import "@kitware/vtk.js/Rendering/Profiles/Geometry" -import vtkGenericRenderWindow from "@kitware/vtk.js/Rendering/Misc/GenericRenderWindow" -import vtkXMLPolyDataReader from "@kitware/vtk.js/IO/XML/XMLPolyDataReader" -import vtkMapper from "@kitware/vtk.js/Rendering/Core/Mapper" -import vtkActor from "@kitware/vtk.js/Rendering/Core/Actor" - +// Third party imports import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json" -import Status from "@ogw_f/utils/status.js" - -export const useHybridViewerStore = defineStore("hybridViewer", () => { - const viewerStore = useViewerStore() - const dataBaseStore = useDataBaseStore() - const db = reactive({}) - const status = ref(Status.NOT_CREATED) - const camera_options = reactive({}) - const genericRenderWindow = reactive({}) - const is_moving = ref(false) - const zScale = ref(1.0) - let viewStream - let gridActor = null - - async function initHybridViewer() { - if (status.value !== Status.NOT_CREATED) return - status.value = Status.CREATING - genericRenderWindow.value = vtkGenericRenderWindow.newInstance({ - background: [180 / 255, 180 / 255, 180 / 255], - listenWindowResize: false, - }) - const webGLRenderWindow = - genericRenderWindow.value.getApiSpecificRenderWindow() - const imageStyle = webGLRenderWindow.getReferenceByName("bgImage").style - imageStyle.transition = "opacity 0.1s ease-in" - imageStyle.zIndex = 1 +// Local imports +import { useMeshPointsStyle } from "./points.js" +import { useMeshEdgesStyle } from "./edges.js" +import { useMeshPolygonsStyle } from "./polygons.js" +import { useMeshPolyhedraStyle } from "./polyhedra.js" - await viewerStore.ws_connect() - viewStream = viewerStore.client.getImageStream().createViewStream("-1") - viewStream.onImageReady((e) => { - if (is_moving.value) return - const webGLRenderWindow = - genericRenderWindow.value.getApiSpecificRenderWindow() - const imageStyle = webGLRenderWindow.getReferenceByName("bgImage").style - webGLRenderWindow.setBackgroundImage(e.image) - imageStyle.opacity = 1 - }) +// Local constants +const mesh_schemas = viewer_schemas.opengeodeweb_viewer.mesh - status.value = Status.CREATED - } - - async function addItem(id) { - if (!genericRenderWindow.value) { - return - } - const value = dataBaseStore.db[id] - console.log("hybridViewerStore.addItem", { value }) - const reader = vtkXMLPolyDataReader.newInstance() - const textEncoder = new TextEncoder() - await reader.parseAsArrayBuffer( - textEncoder.encode(value.vtk_js.binary_light_viewable), - ) - const polydata = reader.getOutputData(0) - const mapper = vtkMapper.newInstance() - mapper.setInputData(polydata) - const actor = vtkActor.newInstance() - actor.getProperty().setColor(20 / 255, 20 / 255, 20 / 255) - actor.setMapper(mapper) - const renderer = genericRenderWindow.value.getRenderer() - const renderWindow = genericRenderWindow.value.getRenderWindow() - renderer.addActor(actor) - renderer.resetCamera() - renderWindow.render() - db[id] = { actor, polydata, mapper } - } +export default function useMeshStyle() { + const dataStyleStore = useDataStyleStore() + const pointsStyleStore = useMeshPointsStyle() + const edgesStyleStore = useMeshEdgesStyle() + const meshPolygonsStyleStore = useMeshPolygonsStyle() + const meshPolyhedraStyleStore = useMeshPolyhedraStyle() + const hybridViewerStore = useHybridViewerStore() - async function setVisibility(id, visibility) { - db[id].actor.setVisibility(visibility) - const renderWindow = genericRenderWindow.value.getRenderWindow() - renderWindow.render() - } - async function setZScaling(z_scale) { - zScale.value = z_scale - const renderer = genericRenderWindow.value.getRenderer() - const actors = renderer.getActors() - actors.forEach((actor) => { - if (actor !== gridActor) { - const scale = actor.getScale() - actor.setScale(scale[0], scale[1], z_scale) - } - }) - renderer.resetCamera() - genericRenderWindow.value.getRenderWindow().render() - const schema = viewer_schemas?.opengeodeweb_viewer?.viewer?.set_z_scaling - if (!schema) return - await viewer_call({ - schema, - params: { - z_scale: z_scale, - }, - }) + function meshVisibility(id) { + return dataStyleStore.getStyle(id).visibility } - - function syncRemoteCamera() { - console.log("syncRemoteCamera") - const renderer = genericRenderWindow.value.getRenderer() - const camera = renderer.getActiveCamera() - const params = { - camera_options: { - focal_point: camera.getFocalPoint(), - view_up: camera.getViewUp(), - position: camera.getPosition(), - view_angle: camera.getViewAngle(), - clipping_range: camera.getClippingRange(), - distance: camera.getDistance(), - }, - } - viewer_call( + function setMeshVisibility(id, visibility) { + return viewer_call( { - schema: viewer_schemas.opengeodeweb_viewer.viewer.update_camera, - params, + schema: mesh_schemas.visibility, + params: { id, visibility }, }, { response_function: () => { - remoteRender() - for (const key in params.camera_options) { - camera_options[key] = params.camera_options[key] - } + hybridViewerStore.setVisibility(id, visibility) + dataStyleStore.getStyle(id).visibility = visibility + console.log(setMeshVisibility.name, { id }, meshVisibility(id)) }, }, ) } - function remoteRender() { - viewer_call({ - schema: viewer_schemas.opengeodeweb_viewer.viewer.render, - }) - } - - function setContainer(container) { - genericRenderWindow.value.setContainer(container.value.$el) - const webGLRenderWindow = - genericRenderWindow.value.getApiSpecificRenderWindow() - webGLRenderWindow.setUseBackgroundImage(true) - const imageStyle = webGLRenderWindow.getReferenceByName("bgImage").style - imageStyle.transition = "opacity 0.1s ease-in" - imageStyle.zIndex = 1 - resize(container.value.$el.offsetWidth, container.value.$el.offsetHeight) - console.log("setContainer", container.value.$el) - - useMousePressed({ - target: container, - onPressed: (event) => { - console.log("onPressed") - if (event.button == 0) { - is_moving.value = true - event.stopPropagation() - imageStyle.opacity = 0 - } - }, - onReleased: () => { - if (!is_moving.value) { - return - } - is_moving.value = false - console.log("onReleased") - syncRemoteCamera() - }, - }) - - let wheelEventEndTimeout = null - useEventListener(container, "wheel", () => { - is_moving.value = true - imageStyle.opacity = 0 - clearTimeout(wheelEventEndTimeout) - wheelEventEndTimeout = setTimeout(() => { - is_moving.value = false - syncRemoteCamera() - }, 600) - }) - } - - async function resize(width, height) { - if ( - viewerStore.status !== Status.CONNECTED || - status.value !== Status.CREATED - ) { - return + function applyMeshStyle(id) { + const style = dataStyleStore.getStyle(id) + const promise_array = [] + for (const [key, value] of Object.entries(style)) { + if (key === "visibility") { + promise_array.push(setMeshVisibility(id, value)) + } else if (key === "points") { + promise_array.push(pointsStyleStore.applyMeshPointsStyle(id)) + } else if (key === "edges") { + promise_array.push(edgesStyleStore.applyMeshEdgesStyle(id)) + } else if (key === "polygons") { + promise_array.push(meshPolygonsStyleStore.applyMeshPolygonsStyle(id)) + } else if (key === "polyhedra") { + promise_array.push(meshPolyhedraStyleStore.applyMeshPolyhedraStyle(id)) + } else { + throw new Error("Unknown mesh key: " + key) + } } - const webGLRenderWindow = - genericRenderWindow.value.getApiSpecificRenderWindow() - const canvas = webGLRenderWindow.getCanvas() - canvas.width = width - canvas.height = height - await nextTick() - webGLRenderWindow.setSize(width, height) - viewStream.setSize(width, height) - const renderWindow = genericRenderWindow.value.getRenderWindow() - renderWindow.render() - remoteRender() + return Promise.all(promise_array) } return { - db, - genericRenderWindow, - addItem, - setVisibility, - setZScaling, - syncRemoteCamera, - initHybridViewer, - remoteRender, - resize, - setContainer, - zScale, + meshVisibility, + setMeshVisibility, + applyMeshStyle, + ...useMeshPointsStyle(), + ...useMeshEdgesStyle(), + ...useMeshPolygonsStyle(), + ...useMeshPolyhedraStyle(), } -}) +} From 309b5f4067edf920a4e8f627856a0a2870a7f4ad Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Fri, 7 Nov 2025 12:41:10 +0100 Subject: [PATCH 52/66] viewer_call --- composables/viewer_call.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/composables/viewer_call.js b/composables/viewer_call.js index 6a91d36a..76a7d6fb 100644 --- a/composables/viewer_call.js +++ b/composables/viewer_call.js @@ -18,8 +18,9 @@ export function viewer_call( const client = viewer_store.client return new Promise((resolve, reject) => { - if (!client) { - reject() + if (!client?.getConnection) { + resolve() + return } viewer_store.start_request() client @@ -37,7 +38,7 @@ export function viewer_call( if (request_error_function) { request_error_function(reason) } - reject() + reject(reason) }, ) .catch((error) => { @@ -50,7 +51,7 @@ export function viewer_call( if (response_error_function) { response_error_function(error) } - reject() + reject(error) }) .finally(() => { viewer_store.stop_request() From 7ab0049ed1a78bd214c64a19839d7c192f0db3dc Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Fri, 7 Nov 2025 14:45:04 +0000 Subject: [PATCH 53/66] Apply prepare changes --- tests/integration/microservices/viewer/requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration/microservices/viewer/requirements.txt b/tests/integration/microservices/viewer/requirements.txt index 5930c6c0..4d097394 100644 --- a/tests/integration/microservices/viewer/requirements.txt +++ b/tests/integration/microservices/viewer/requirements.txt @@ -5,4 +5,3 @@ # pip-compile --output-file=tests/integration/microservices/viewer/requirements.txt tests/integration/microservices/viewer/requirements.in # -opengeodeweb-viewer==1.*,>=1.11.9rc1 From 7a0e43e41ea1c2bbb06d3e932050bd80753aa90b Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Fri, 7 Nov 2025 16:02:24 +0100 Subject: [PATCH 54/66] logs everywhere --- stores/hybrid_viewer.js | 184 ++++++++++++++++++++++++++++------ utils/file_import_workflow.js | 69 +++++++++---- 2 files changed, 201 insertions(+), 52 deletions(-) diff --git a/stores/hybrid_viewer.js b/stores/hybrid_viewer.js index da72bbc7..e4ab3d0c 100644 --- a/stores/hybrid_viewer.js +++ b/stores/hybrid_viewer.js @@ -21,6 +21,7 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { async function initHybridViewer() { if (status.value !== Status.NOT_CREATED) return + console.log("[hybrid_viewer] initHybridViewer: status", status.value) status.value = Status.CREATING genericRenderWindow.value = vtkGenericRenderWindow.newInstance({ background: [180 / 255, 180 / 255, 180 / 255], @@ -34,12 +35,20 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { imageStyle.zIndex = 1 await viewerStore.ws_connect() + console.log("[hybrid_viewer] ws_connect done") viewStream = viewerStore.client.getImageStream().createViewStream("-1") + console.log("[hybrid_viewer] viewStream created (-1)") viewStream.onImageReady((e) => { - if (is_moving.value) return const webGLRenderWindow = genericRenderWindow.value.getApiSpecificRenderWindow() const imageStyle = webGLRenderWindow.getReferenceByName("bgImage").style + const canvas = webGLRenderWindow.getCanvas() + console.log("[hybrid_viewer] onImageReady", { + is_moving: is_moving.value, + canvas: { width: canvas.width, height: canvas.height }, + image: typeof e?.image, + }) + if (is_moving.value) return webGLRenderWindow.setBackgroundImage(e.image) imageStyle.opacity = 1 }) @@ -59,6 +68,7 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { textEncoder.encode(value.vtk_js.binary_light_viewable), ) const polydata = reader.getOutputData(0) + console.log("[hybrid_viewer] addItem polydata bounds", polydata?.getBounds?.()) const mapper = vtkMapper.newInstance() mapper.setInputData(polydata) const actor = vtkActor.newInstance() @@ -67,8 +77,10 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { const renderer = genericRenderWindow.value.getRenderer() const renderWindow = genericRenderWindow.value.getRenderWindow() renderer.addActor(actor) + console.log("[hybrid_viewer] addItem actors count", renderer.getActors().length) renderer.resetCamera() renderWindow.render() + console.log("[hybrid_viewer] addItem render done") db[id] = { actor, polydata, mapper } } @@ -99,20 +111,63 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { }) } + // Convertit un "array-like" (y compris TypedArray) en tableau de nombres finis + function toNumArray(arrLike, expectedLen) { + try { + const a = Array.from(arrLike ?? []) + if (a.length !== expectedLen) return null + const n = a.map((x) => Number(x)) + return n.every((x) => Number.isFinite(x)) ? n : null + } catch (_) { + return null + } + } + function syncRemoteCamera() { - console.log("syncRemoteCamera") + console.log("[hybrid_viewer] syncRemoteCamera") const renderer = genericRenderWindow.value.getRenderer() + renderer.resetCameraClippingRange() const camera = renderer.getActiveCamera() - const params = { - camera_options: { - focal_point: camera.getFocalPoint(), - view_up: camera.getViewUp(), - position: camera.getPosition(), - view_angle: camera.getViewAngle(), - clipping_range: camera.getClippingRange(), - distance: camera.getDistance(), + + const raw = { + focal_point: camera.getFocalPoint(), + view_up: camera.getViewUp(), + position: camera.getPosition(), + view_angle: camera.getViewAngle(), + clipping_range: camera.getClippingRange(), + } + console.log("[hybrid_viewer] camera raw", { + focal_point: raw.focal_point, + view_up: raw.view_up, + position: raw.position, + view_angle: raw.view_angle, + clipping_range: raw.clipping_range, + types: { + focal_point: Array.isArray(raw.focal_point) ? "array" : typeof raw.focal_point, + view_up: Array.isArray(raw.view_up) ? "array" : typeof raw.view_up, + position: Array.isArray(raw.position) ? "array" : typeof raw.position, + clipping_range: Array.isArray(raw.clipping_range) ? "array" : typeof raw.clipping_range, }, + }) + + const fp = toNumArray(raw.focal_point, 3) + const vu = toNumArray(raw.view_up, 3) + const pos = toNumArray(raw.position, 3) + const cr = toNumArray(raw.clipping_range, 2) + const va = Number(raw.view_angle) + + const normalized = { focal_point: fp, view_up: vu, position: pos, view_angle: va, clipping_range: cr } + console.log("[hybrid_viewer] camera normalized", normalized) + + const valid = fp && vu && pos && cr && Number.isFinite(va) + if (!valid) { + console.warn("[hybrid_viewer] syncRemoteCamera skipped: invalid camera", normalized) + return } + + const params = { camera_options: normalized } + console.log("[hybrid_viewer] viewer.update_camera request", params) + viewer_call( { schema: viewer_schemas.opengeodeweb_viewer.viewer.update_camera, @@ -120,59 +175,83 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { }, { response_function: () => { + console.log("[hybrid_viewer] viewer.update_camera response: ok -> render") remoteRender() - for (const key in params.camera_options) { - camera_options[key] = params.camera_options[key] - } + Object.assign(camera_options, params.camera_options) }, }, ) } function remoteRender() { + console.log("[hybrid_viewer] viewer.render request") viewer_call({ schema: viewer_schemas.opengeodeweb_viewer.viewer.render, }) } function setContainer(container) { + console.log("[hybrid_viewer] setContainer attach", { + el: container.value?.$el, + size: { + w: container.value?.$el?.offsetWidth, + h: container.value?.$el?.offsetHeight, + }, + }) genericRenderWindow.value.setContainer(container.value.$el) const webGLRenderWindow = genericRenderWindow.value.getApiSpecificRenderWindow() webGLRenderWindow.setUseBackgroundImage(true) const imageStyle = webGLRenderWindow.getReferenceByName("bgImage").style + console.log("[hybrid_viewer] bgImage style before", { + transition: imageStyle.transition, + zIndex: imageStyle.zIndex, + opacity: imageStyle.opacity, + }) imageStyle.transition = "opacity 0.1s ease-in" imageStyle.zIndex = 1 resize(container.value.$el.offsetWidth, container.value.$el.offsetHeight) console.log("setContainer", container.value.$el) - + useMousePressed({ target: container, onPressed: (event) => { - console.log("onPressed") + console.log("[hybrid_viewer] onPressed", { + button: event.button, + is_moving_before: is_moving.value, + }) if (event.button == 0) { is_moving.value = true event.stopPropagation() imageStyle.opacity = 0 + console.log("[hybrid_viewer] onPressed applied", { + is_moving_after: is_moving.value, + bg_opacity: imageStyle.opacity, + }) } }, onReleased: () => { + console.log("[hybrid_viewer] onReleased", { + was_moving: is_moving.value, + }) if (!is_moving.value) { return } is_moving.value = false - console.log("onReleased") + console.log("[hybrid_viewer] onReleased -> syncRemoteCamera") syncRemoteCamera() }, }) - + let wheelEventEndTimeout = null useEventListener(container, "wheel", () => { is_moving.value = true imageStyle.opacity = 0 + console.log("[hybrid_viewer] wheel", { bg_opacity: imageStyle.opacity }) clearTimeout(wheelEventEndTimeout) wheelEventEndTimeout = setTimeout(() => { is_moving.value = false + console.log("[hybrid_viewer] wheel end -> syncRemoteCamera") syncRemoteCamera() }, 600) }) @@ -185,6 +264,12 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { ) { return } + console.log("[hybrid_viewer] resize", { + width, + height, + viewer_status: viewerStore.status, + store_status: status.value, + }) const webGLRenderWindow = genericRenderWindow.value.getApiSpecificRenderWindow() const canvas = webGLRenderWindow.getCanvas() @@ -195,6 +280,9 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { viewStream.setSize(width, height) const renderWindow = genericRenderWindow.value.getRenderWindow() renderWindow.render() + console.log("[hybrid_viewer] resize applied", { + canvas: { width: canvas.width, height: canvas.height }, + }) remoteRender() } @@ -227,28 +315,62 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { if (z_scale != null) { await setZScaling(z_scale) } - + const cam = snapshot?.camera_options if (cam) { + console.log("[hybrid_viewer] importStores snapshot camera", cam) const renderer = genericRenderWindow.value.getRenderer() const camera = renderer.getActiveCamera() - - if (cam.focal_point) camera.setFocalPoint(cam.focal_point) - if (cam.view_up) camera.setViewUp(cam.view_up) - if (cam.position) camera.setPosition(cam.position) - if (cam.view_angle != null) camera.setViewAngle(cam.view_angle) - if (cam.clipping_range) camera.setClippingRange(cam.clipping_range) - + + const fp = toNumArray(cam.focal_point, 3) + const vu = toNumArray(cam.view_up, 3) + const pos = toNumArray(cam.position, 3) + const cr = toNumArray(cam.clipping_range, 2) + const va = Number(cam.view_angle) + + const valid = + fp && vu && pos && cr && Number.isFinite(va) + + console.log("[hybrid_viewer] importStores normalized camera", { + focal_point: fp, view_up: vu, position: pos, view_angle: va, clipping_range: cr, valid + }) + + if (!valid) { + console.warn("[hybrid_viewer] importStores camera skipped: invalid snapshot camera", cam) + return + } + + camera.setFocalPoint(fp) + camera.setViewUp(vu) + camera.setPosition(pos) + camera.setViewAngle(va) + camera.setClippingRange(cr) + genericRenderWindow.value.getRenderWindow().render() - + + console.log("[hybrid_viewer] importStores -> viewer.update_camera", { + camera_options: { focal_point: fp, view_up: vu, position: pos, view_angle: va, clipping_range: cr }, + }) await viewer_call({ schema: viewer_schemas.opengeodeweb_viewer.viewer.update_camera, - params: { camera_options: cam }, + params: { + camera_options: { + focal_point: fp, + view_up: vu, + position: pos, + view_angle: va, + clipping_range: cr, + }, + }, + }) + + Object.assign(camera_options, { + focal_point: fp, + view_up: vu, + position: pos, + view_angle: va, + clipping_range: cr, }) - - for (const key in cam) { - camera_options[key] = cam[key] - } } } diff --git a/utils/file_import_workflow.js b/utils/file_import_workflow.js index bbdab9da..30718aff 100644 --- a/utils/file_import_workflow.js +++ b/utils/file_import_workflow.js @@ -80,36 +80,63 @@ async function importFile(filename, geode_object) { } async function importWorkflowFromSnapshot(items) { + console.log("[importWorkflowFromSnapshot] start", { count: items?.length }) const dataBaseStore = useDataBaseStore() const treeviewStore = useTreeviewStore() + const dataStyleStore = useDataStyleStore() const hybridViewerStore = useHybridViewerStore() const ids = [] for (const item of items) { - const { - id, - object_type, - geode_object, - native_filename, - viewable_filename, - displayed_name, - vtk_js, - } = item - - await dataBaseStore.registerObject(id) - await dataBaseStore.addItem(id, { - object_type, - geode_object, - native_filename, - viewable_filename, - displayed_name, - vtk_js, + console.log("[importWorkflowFromSnapshot] item", { + id: item.id, + object_type: item.object_type, + geode_object: item.geode_object, + displayed_name: item.displayed_name, }) - await treeviewStore.addItem(geode_object, displayed_name, id, object_type) - await hybridViewerStore.addItem(id) + await dataBaseStore.registerObject(item.id) + console.log("[importWorkflowFromSnapshot] registerObject ok", item.id) + + await dataBaseStore.addItem(item.id, { + object_type: item.object_type, + geode_object: item.geode_object, + native_filename: item.native_filename, + viewable_filename: item.viewable_filename, + displayed_name: item.displayed_name, + vtk_js: item.vtk_js, + }) + console.log("[importWorkflowFromSnapshot] addItem ok", item.id) + + await treeviewStore.addItem( + item.geode_object, + item.displayed_name, + item.id, + item.object_type, + ) + console.log("[importWorkflowFromSnapshot] treeview.addItem ok", item.id) + + await hybridViewerStore.addItem(item.id) + console.log("[importWorkflowFromSnapshot] hybridViewer.addItem ok", item.id) - ids.push(id) + await dataStyleStore.addDataStyle(item.id, item.geode_object) + console.log("[importWorkflowFromSnapshot] dataStyle.addDataStyle ok", item.id) + + if (item.object_type === "model") { + await Promise.all([ + dataBaseStore.fetchMeshComponents(item.id), + dataBaseStore.fetchUuidToFlatIndexDict(item.id), + ]) + console.log("[importWorkflowFromSnapshot] model components fetched", item.id) + } + + await dataStyleStore.applyDefaultStyle(item.id) + console.log("[importWorkflowFromSnapshot] dataStyle.applyDefaultStyle ok", item.id) + + ids.push(item.id) } + hybridViewerStore.remoteRender() + console.log("[importWorkflowFromSnapshot] remoteRender called") + console.log("[importWorkflowFromSnapshot] done", { ids }) return ids } From 933cb7173704a019cc2936cdf35886ec4a49ff29 Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Fri, 7 Nov 2025 15:03:27 +0000 Subject: [PATCH 55/66] Apply prepare changes --- stores/hybrid_viewer.js | 92 ++++++++++++++++++++++++----------- utils/file_import_workflow.js | 15 ++++-- 2 files changed, 75 insertions(+), 32 deletions(-) diff --git a/stores/hybrid_viewer.js b/stores/hybrid_viewer.js index e4ab3d0c..b6746d4f 100644 --- a/stores/hybrid_viewer.js +++ b/stores/hybrid_viewer.js @@ -68,7 +68,10 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { textEncoder.encode(value.vtk_js.binary_light_viewable), ) const polydata = reader.getOutputData(0) - console.log("[hybrid_viewer] addItem polydata bounds", polydata?.getBounds?.()) + console.log( + "[hybrid_viewer] addItem polydata bounds", + polydata?.getBounds?.(), + ) const mapper = vtkMapper.newInstance() mapper.setInputData(polydata) const actor = vtkActor.newInstance() @@ -77,7 +80,10 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { const renderer = genericRenderWindow.value.getRenderer() const renderWindow = genericRenderWindow.value.getRenderWindow() renderer.addActor(actor) - console.log("[hybrid_viewer] addItem actors count", renderer.getActors().length) + console.log( + "[hybrid_viewer] addItem actors count", + renderer.getActors().length, + ) renderer.resetCamera() renderWindow.render() console.log("[hybrid_viewer] addItem render done") @@ -128,7 +134,7 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { const renderer = genericRenderWindow.value.getRenderer() renderer.resetCameraClippingRange() const camera = renderer.getActiveCamera() - + const raw = { focal_point: camera.getFocalPoint(), view_up: camera.getViewUp(), @@ -143,31 +149,44 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { view_angle: raw.view_angle, clipping_range: raw.clipping_range, types: { - focal_point: Array.isArray(raw.focal_point) ? "array" : typeof raw.focal_point, + focal_point: Array.isArray(raw.focal_point) + ? "array" + : typeof raw.focal_point, view_up: Array.isArray(raw.view_up) ? "array" : typeof raw.view_up, position: Array.isArray(raw.position) ? "array" : typeof raw.position, - clipping_range: Array.isArray(raw.clipping_range) ? "array" : typeof raw.clipping_range, + clipping_range: Array.isArray(raw.clipping_range) + ? "array" + : typeof raw.clipping_range, }, }) - + const fp = toNumArray(raw.focal_point, 3) const vu = toNumArray(raw.view_up, 3) const pos = toNumArray(raw.position, 3) const cr = toNumArray(raw.clipping_range, 2) const va = Number(raw.view_angle) - - const normalized = { focal_point: fp, view_up: vu, position: pos, view_angle: va, clipping_range: cr } + + const normalized = { + focal_point: fp, + view_up: vu, + position: pos, + view_angle: va, + clipping_range: cr, + } console.log("[hybrid_viewer] camera normalized", normalized) - + const valid = fp && vu && pos && cr && Number.isFinite(va) if (!valid) { - console.warn("[hybrid_viewer] syncRemoteCamera skipped: invalid camera", normalized) + console.warn( + "[hybrid_viewer] syncRemoteCamera skipped: invalid camera", + normalized, + ) return } - + const params = { camera_options: normalized } console.log("[hybrid_viewer] viewer.update_camera request", params) - + viewer_call( { schema: viewer_schemas.opengeodeweb_viewer.viewer.update_camera, @@ -175,7 +194,9 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { }, { response_function: () => { - console.log("[hybrid_viewer] viewer.update_camera response: ok -> render") + console.log( + "[hybrid_viewer] viewer.update_camera response: ok -> render", + ) remoteRender() Object.assign(camera_options, params.camera_options) }, @@ -212,7 +233,7 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { imageStyle.zIndex = 1 resize(container.value.$el.offsetWidth, container.value.$el.offsetHeight) console.log("setContainer", container.value.$el) - + useMousePressed({ target: container, onPressed: (event) => { @@ -242,7 +263,7 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { syncRemoteCamera() }, }) - + let wheelEventEndTimeout = null useEventListener(container, "wheel", () => { is_moving.value = true @@ -315,41 +336,54 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { if (z_scale != null) { await setZScaling(z_scale) } - + const cam = snapshot?.camera_options if (cam) { console.log("[hybrid_viewer] importStores snapshot camera", cam) const renderer = genericRenderWindow.value.getRenderer() const camera = renderer.getActiveCamera() - + const fp = toNumArray(cam.focal_point, 3) const vu = toNumArray(cam.view_up, 3) const pos = toNumArray(cam.position, 3) const cr = toNumArray(cam.clipping_range, 2) const va = Number(cam.view_angle) - - const valid = - fp && vu && pos && cr && Number.isFinite(va) - + + const valid = fp && vu && pos && cr && Number.isFinite(va) + console.log("[hybrid_viewer] importStores normalized camera", { - focal_point: fp, view_up: vu, position: pos, view_angle: va, clipping_range: cr, valid + focal_point: fp, + view_up: vu, + position: pos, + view_angle: va, + clipping_range: cr, + valid, }) - + if (!valid) { - console.warn("[hybrid_viewer] importStores camera skipped: invalid snapshot camera", cam) + console.warn( + "[hybrid_viewer] importStores camera skipped: invalid snapshot camera", + cam, + ) return } - + camera.setFocalPoint(fp) camera.setViewUp(vu) camera.setPosition(pos) camera.setViewAngle(va) camera.setClippingRange(cr) - + genericRenderWindow.value.getRenderWindow().render() - + console.log("[hybrid_viewer] importStores -> viewer.update_camera", { - camera_options: { focal_point: fp, view_up: vu, position: pos, view_angle: va, clipping_range: cr }, + camera_options: { + focal_point: fp, + view_up: vu, + position: pos, + view_angle: va, + clipping_range: cr, + }, }) await viewer_call({ schema: viewer_schemas.opengeodeweb_viewer.viewer.update_camera, @@ -363,7 +397,7 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { }, }, }) - + Object.assign(camera_options, { focal_point: fp, view_up: vu, diff --git a/utils/file_import_workflow.js b/utils/file_import_workflow.js index 30718aff..66182cf4 100644 --- a/utils/file_import_workflow.js +++ b/utils/file_import_workflow.js @@ -119,18 +119,27 @@ async function importWorkflowFromSnapshot(items) { console.log("[importWorkflowFromSnapshot] hybridViewer.addItem ok", item.id) await dataStyleStore.addDataStyle(item.id, item.geode_object) - console.log("[importWorkflowFromSnapshot] dataStyle.addDataStyle ok", item.id) + console.log( + "[importWorkflowFromSnapshot] dataStyle.addDataStyle ok", + item.id, + ) if (item.object_type === "model") { await Promise.all([ dataBaseStore.fetchMeshComponents(item.id), dataBaseStore.fetchUuidToFlatIndexDict(item.id), ]) - console.log("[importWorkflowFromSnapshot] model components fetched", item.id) + console.log( + "[importWorkflowFromSnapshot] model components fetched", + item.id, + ) } await dataStyleStore.applyDefaultStyle(item.id) - console.log("[importWorkflowFromSnapshot] dataStyle.applyDefaultStyle ok", item.id) + console.log( + "[importWorkflowFromSnapshot] dataStyle.applyDefaultStyle ok", + item.id, + ) ids.push(item.id) } From e0cf65783399d9d881ec210d7a5c925df46258d6 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Fri, 7 Nov 2025 16:44:08 +0100 Subject: [PATCH 56/66] revert some --- stores/hybrid_viewer.js | 128 ++-------------------------------------- 1 file changed, 5 insertions(+), 123 deletions(-) diff --git a/stores/hybrid_viewer.js b/stores/hybrid_viewer.js index e4ab3d0c..8da71890 100644 --- a/stores/hybrid_viewer.js +++ b/stores/hybrid_viewer.js @@ -21,7 +21,6 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { async function initHybridViewer() { if (status.value !== Status.NOT_CREATED) return - console.log("[hybrid_viewer] initHybridViewer: status", status.value) status.value = Status.CREATING genericRenderWindow.value = vtkGenericRenderWindow.newInstance({ background: [180 / 255, 180 / 255, 180 / 255], @@ -35,20 +34,12 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { imageStyle.zIndex = 1 await viewerStore.ws_connect() - console.log("[hybrid_viewer] ws_connect done") viewStream = viewerStore.client.getImageStream().createViewStream("-1") - console.log("[hybrid_viewer] viewStream created (-1)") viewStream.onImageReady((e) => { + if (is_moving.value) return const webGLRenderWindow = genericRenderWindow.value.getApiSpecificRenderWindow() const imageStyle = webGLRenderWindow.getReferenceByName("bgImage").style - const canvas = webGLRenderWindow.getCanvas() - console.log("[hybrid_viewer] onImageReady", { - is_moving: is_moving.value, - canvas: { width: canvas.width, height: canvas.height }, - image: typeof e?.image, - }) - if (is_moving.value) return webGLRenderWindow.setBackgroundImage(e.image) imageStyle.opacity = 1 }) @@ -68,7 +59,6 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { textEncoder.encode(value.vtk_js.binary_light_viewable), ) const polydata = reader.getOutputData(0) - console.log("[hybrid_viewer] addItem polydata bounds", polydata?.getBounds?.()) const mapper = vtkMapper.newInstance() mapper.setInputData(polydata) const actor = vtkActor.newInstance() @@ -77,10 +67,8 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { const renderer = genericRenderWindow.value.getRenderer() const renderWindow = genericRenderWindow.value.getRenderWindow() renderer.addActor(actor) - console.log("[hybrid_viewer] addItem actors count", renderer.getActors().length) renderer.resetCamera() renderWindow.render() - console.log("[hybrid_viewer] addItem render done") db[id] = { actor, polydata, mapper } } @@ -111,147 +99,50 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { }) } - // Convertit un "array-like" (y compris TypedArray) en tableau de nombres finis - function toNumArray(arrLike, expectedLen) { - try { - const a = Array.from(arrLike ?? []) - if (a.length !== expectedLen) return null - const n = a.map((x) => Number(x)) - return n.every((x) => Number.isFinite(x)) ? n : null - } catch (_) { - return null - } - } - - function syncRemoteCamera() { - console.log("[hybrid_viewer] syncRemoteCamera") - const renderer = genericRenderWindow.value.getRenderer() - renderer.resetCameraClippingRange() - const camera = renderer.getActiveCamera() - - const raw = { - focal_point: camera.getFocalPoint(), - view_up: camera.getViewUp(), - position: camera.getPosition(), - view_angle: camera.getViewAngle(), - clipping_range: camera.getClippingRange(), - } - console.log("[hybrid_viewer] camera raw", { - focal_point: raw.focal_point, - view_up: raw.view_up, - position: raw.position, - view_angle: raw.view_angle, - clipping_range: raw.clipping_range, - types: { - focal_point: Array.isArray(raw.focal_point) ? "array" : typeof raw.focal_point, - view_up: Array.isArray(raw.view_up) ? "array" : typeof raw.view_up, - position: Array.isArray(raw.position) ? "array" : typeof raw.position, - clipping_range: Array.isArray(raw.clipping_range) ? "array" : typeof raw.clipping_range, - }, - }) - - const fp = toNumArray(raw.focal_point, 3) - const vu = toNumArray(raw.view_up, 3) - const pos = toNumArray(raw.position, 3) - const cr = toNumArray(raw.clipping_range, 2) - const va = Number(raw.view_angle) - - const normalized = { focal_point: fp, view_up: vu, position: pos, view_angle: va, clipping_range: cr } - console.log("[hybrid_viewer] camera normalized", normalized) - - const valid = fp && vu && pos && cr && Number.isFinite(va) - if (!valid) { - console.warn("[hybrid_viewer] syncRemoteCamera skipped: invalid camera", normalized) - return - } - - const params = { camera_options: normalized } - console.log("[hybrid_viewer] viewer.update_camera request", params) - - viewer_call( - { - schema: viewer_schemas.opengeodeweb_viewer.viewer.update_camera, - params, - }, - { - response_function: () => { - console.log("[hybrid_viewer] viewer.update_camera response: ok -> render") - remoteRender() - Object.assign(camera_options, params.camera_options) - }, - }, - ) - } - function remoteRender() { - console.log("[hybrid_viewer] viewer.render request") viewer_call({ schema: viewer_schemas.opengeodeweb_viewer.viewer.render, }) } function setContainer(container) { - console.log("[hybrid_viewer] setContainer attach", { - el: container.value?.$el, - size: { - w: container.value?.$el?.offsetWidth, - h: container.value?.$el?.offsetHeight, - }, - }) genericRenderWindow.value.setContainer(container.value.$el) const webGLRenderWindow = genericRenderWindow.value.getApiSpecificRenderWindow() webGLRenderWindow.setUseBackgroundImage(true) const imageStyle = webGLRenderWindow.getReferenceByName("bgImage").style - console.log("[hybrid_viewer] bgImage style before", { - transition: imageStyle.transition, - zIndex: imageStyle.zIndex, - opacity: imageStyle.opacity, - }) imageStyle.transition = "opacity 0.1s ease-in" imageStyle.zIndex = 1 resize(container.value.$el.offsetWidth, container.value.$el.offsetHeight) console.log("setContainer", container.value.$el) - + useMousePressed({ target: container, onPressed: (event) => { - console.log("[hybrid_viewer] onPressed", { - button: event.button, - is_moving_before: is_moving.value, - }) + console.log("onPressed") if (event.button == 0) { is_moving.value = true event.stopPropagation() imageStyle.opacity = 0 - console.log("[hybrid_viewer] onPressed applied", { - is_moving_after: is_moving.value, - bg_opacity: imageStyle.opacity, - }) } }, onReleased: () => { - console.log("[hybrid_viewer] onReleased", { - was_moving: is_moving.value, - }) if (!is_moving.value) { return } is_moving.value = false - console.log("[hybrid_viewer] onReleased -> syncRemoteCamera") + console.log("onReleased") syncRemoteCamera() }, }) - + let wheelEventEndTimeout = null useEventListener(container, "wheel", () => { is_moving.value = true imageStyle.opacity = 0 - console.log("[hybrid_viewer] wheel", { bg_opacity: imageStyle.opacity }) clearTimeout(wheelEventEndTimeout) wheelEventEndTimeout = setTimeout(() => { is_moving.value = false - console.log("[hybrid_viewer] wheel end -> syncRemoteCamera") syncRemoteCamera() }, 600) }) @@ -264,12 +155,6 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { ) { return } - console.log("[hybrid_viewer] resize", { - width, - height, - viewer_status: viewerStore.status, - store_status: status.value, - }) const webGLRenderWindow = genericRenderWindow.value.getApiSpecificRenderWindow() const canvas = webGLRenderWindow.getCanvas() @@ -280,9 +165,6 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { viewStream.setSize(width, height) const renderWindow = genericRenderWindow.value.getRenderWindow() renderWindow.render() - console.log("[hybrid_viewer] resize applied", { - canvas: { width: canvas.width, height: canvas.height }, - }) remoteRender() } From 44a70febda87bce665d817400d87910db3109695 Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Fri, 7 Nov 2025 15:45:27 +0000 Subject: [PATCH 57/66] Apply prepare changes --- stores/hybrid_viewer.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/stores/hybrid_viewer.js b/stores/hybrid_viewer.js index 4209914b..5810b02f 100644 --- a/stores/hybrid_viewer.js +++ b/stores/hybrid_viewer.js @@ -116,7 +116,6 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { resize(container.value.$el.offsetWidth, container.value.$el.offsetHeight) console.log("setContainer", container.value.$el) - useMousePressed({ target: container, onPressed: (event) => { @@ -137,7 +136,6 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { }, }) - let wheelEventEndTimeout = null useEventListener(container, "wheel", () => { is_moving.value = true From 499fc4bd7b10d3ad6ffb78f6db8df50cfed18127 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Fri, 7 Nov 2025 21:10:04 +0100 Subject: [PATCH 58/66] please --- stores/hybrid_viewer.js | 114 ++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 62 deletions(-) diff --git a/stores/hybrid_viewer.js b/stores/hybrid_viewer.js index 4209914b..e6e9bb22 100644 --- a/stores/hybrid_viewer.js +++ b/stores/hybrid_viewer.js @@ -99,6 +99,36 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { }) } + function syncRemoteCamera() { + console.log("syncRemoteCamera") + const renderer = genericRenderWindow.value.getRenderer() + const camera = renderer.getActiveCamera() + const params = { + camera_options: { + focal_point: camera.getFocalPoint(), + view_up: camera.getViewUp(), + position: camera.getPosition(), + view_angle: camera.getViewAngle(), + clipping_range: camera.getClippingRange(), + distance: camera.getDistance(), + }, + } + viewer_call( + { + schema: viewer_schemas.opengeodeweb_viewer.viewer.update_camera, + params, + }, + { + response_function: () => { + remoteRender() + for (const key in params.camera_options) { + camera_options[key] = params.camera_options[key] + } + }, + }, + ) + } + function remoteRender() { viewer_call({ schema: viewer_schemas.opengeodeweb_viewer.viewer.render, @@ -201,74 +231,34 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { } const cam = snapshot?.camera_options - if (cam) { - console.log("[hybrid_viewer] importStores snapshot camera", cam) - const renderer = genericRenderWindow.value.getRenderer() - const camera = renderer.getActiveCamera() - - const fp = toNumArray(cam.focal_point, 3) - const vu = toNumArray(cam.view_up, 3) - const pos = toNumArray(cam.position, 3) - const cr = toNumArray(cam.clipping_range, 2) - const va = Number(cam.view_angle) + if (!cam) return - const valid = fp && vu && pos && cr && Number.isFinite(va) - - console.log("[hybrid_viewer] importStores normalized camera", { - focal_point: fp, - view_up: vu, - position: pos, - view_angle: va, - clipping_range: cr, - valid, - }) - - if (!valid) { - console.warn( - "[hybrid_viewer] importStores camera skipped: invalid snapshot camera", - cam, - ) - return - } + const renderer = genericRenderWindow.value.getRenderer() + const camera = renderer.getActiveCamera() - camera.setFocalPoint(fp) - camera.setViewUp(vu) - camera.setPosition(pos) - camera.setViewAngle(va) - camera.setClippingRange(cr) + // Applique directement les valeurs du snapshot (elles sont déjà numériques) + camera.setFocalPoint(cam.focal_point) + camera.setViewUp(cam.view_up) + camera.setPosition(cam.position) + camera.setViewAngle(cam.view_angle) + camera.setClippingRange(cam.clipping_range) - genericRenderWindow.value.getRenderWindow().render() + genericRenderWindow.value.getRenderWindow().render() - console.log("[hybrid_viewer] importStores -> viewer.update_camera", { - camera_options: { - focal_point: fp, - view_up: vu, - position: pos, - view_angle: va, - clipping_range: cr, - }, - }) - await viewer_call({ + // Envoie tel quel au viewer distant (inclut distance si présente) + const payload = { camera_options: cam } + viewer_call( + { schema: viewer_schemas.opengeodeweb_viewer.viewer.update_camera, - params: { - camera_options: { - focal_point: fp, - view_up: vu, - position: pos, - view_angle: va, - clipping_range: cr, - }, + params: payload, + }, + { + response_function: () => { + remoteRender() + Object.assign(camera_options, payload.camera_options) }, - }) - - Object.assign(camera_options, { - focal_point: fp, - view_up: vu, - position: pos, - view_angle: va, - clipping_range: cr, - }) - } + }, + ) } return { From fca8a2a59e8ea41d91f583775f31e532b16baf27 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Fri, 7 Nov 2025 22:05:03 +0100 Subject: [PATCH 59/66] OMGGGGG --- stores/hybrid_viewer.js | 57 ++++++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/stores/hybrid_viewer.js b/stores/hybrid_viewer.js index 980cc41d..ab1ef9e1 100644 --- a/stores/hybrid_viewer.js +++ b/stores/hybrid_viewer.js @@ -19,6 +19,38 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { let viewStream let gridActor = null + // Helper: conversion stricte aux types/schema + logs des types + function sanitizeCameraOptions(opts) { + if (!opts) return null + const toNums = (arr, size) => + Array.from(arr || []) + .slice(0, size) + .map((n) => Number(n)) + return { + focal_point: toNums(opts.focal_point, 3), + view_up: toNums(opts.view_up, 3), + position: toNums(opts.position, 3), + view_angle: Number(opts.view_angle), + clipping_range: toNums(opts.clipping_range, 2), + } + } + + function logCameraOptions(label, opts) { + const types = (arr) => Array.from(arr || []).map((v) => typeof v) + console.log(`[Camera] ${label}`, { + focal_point: opts?.focal_point, + focal_point_types: types(opts?.focal_point), + view_up: opts?.view_up, + view_up_types: types(opts?.view_up), + position: opts?.position, + position_types: types(opts?.position), + view_angle: opts?.view_angle, + view_angle_type: typeof opts?.view_angle, + clipping_range: opts?.clipping_range, + clipping_range_types: types(opts?.clipping_range), + }) + } + async function initHybridViewer() { if (status.value !== Status.NOT_CREATED) return status.value = Status.CREATING @@ -100,7 +132,6 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { } function syncRemoteCamera() { - console.log("syncRemoteCamera") const renderer = genericRenderWindow.value.getRenderer() const camera = renderer.getActiveCamera() const params = { @@ -110,7 +141,6 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { position: camera.getPosition(), view_angle: camera.getViewAngle(), clipping_range: camera.getClippingRange(), - distance: camera.getDistance(), }, } viewer_call( @@ -222,6 +252,7 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { return { zScale: zScale.value, camera_options: cameraSnapshot } } + async function importStores(snapshot) { const z_scale = snapshot?.zScale if (z_scale != null) { @@ -234,17 +265,25 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { const renderer = genericRenderWindow.value.getRenderer() const camera = renderer.getActiveCamera() - // Applique directement les valeurs du snapshot (elles sont déjà numériques) - camera.setFocalPoint(cam.focal_point) - camera.setViewUp(cam.view_up) - camera.setPosition(cam.position) + // Appliquer les composantes (x, y, z), pas le tableau + camera.setFocalPoint(...cam.focal_point) + camera.setViewUp(...cam.view_up) + camera.setPosition(...cam.position) camera.setViewAngle(cam.view_angle) - camera.setClippingRange(cam.clipping_range) + camera.setClippingRange(...cam.clipping_range) genericRenderWindow.value.getRenderWindow().render() - // Envoie tel quel au viewer distant (inclut distance si présente) - const payload = { camera_options: cam } + // Envoyer uniquement les champs conformes au schéma (sans distance) + const payload = { + camera_options: { + focal_point: cam.focal_point, + view_up: cam.view_up, + position: cam.position, + view_angle: cam.view_angle, + clipping_range: cam.clipping_range, + }, + } viewer_call( { schema: viewer_schemas.opengeodeweb_viewer.viewer.update_camera, From 28abd5787926ffa9b5c3e330103f6bea967f5eb6 Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Fri, 7 Nov 2025 21:05:53 +0000 Subject: [PATCH 60/66] Apply prepare changes --- stores/hybrid_viewer.js | 1 - 1 file changed, 1 deletion(-) diff --git a/stores/hybrid_viewer.js b/stores/hybrid_viewer.js index ab1ef9e1..aa7333d4 100644 --- a/stores/hybrid_viewer.js +++ b/stores/hybrid_viewer.js @@ -252,7 +252,6 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { return { zScale: zScale.value, camera_options: cameraSnapshot } } - async function importStores(snapshot) { const z_scale = snapshot?.zScale if (z_scale != null) { From c156eab757b1bfaf39a465236b8323490e76dfb9 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Fri, 7 Nov 2025 23:12:25 +0100 Subject: [PATCH 61/66] test --- composables/project_manager.js | 32 ++++++++++++-------- stores/data_base.js | 55 +++++++++++++++++++--------------- stores/hybrid_viewer.js | 34 --------------------- stores/treeview.js | 24 +++++++++++++-- 4 files changed, 71 insertions(+), 74 deletions(-) diff --git a/composables/project_manager.js b/composables/project_manager.js index 011a6aa2..42aded87 100644 --- a/composables/project_manager.js +++ b/composables/project_manager.js @@ -35,16 +35,8 @@ export function useProjectManager() { try { await useInfraStore().create_connection() - const schemaImport = back_schemas.opengeodeweb_back.import_project - const form = new FormData() - form.append("file", file, file?.name) - - const result = await $fetch(schemaImport.$id, { - baseURL: geode.base_url, - method: "POST", - body: form, - }) - const snapshot = result?.snapshot ?? {} + const viewerStore = useViewerStore() + await viewerStore.ws_connect() await viewer_call({ schema: viewer_schemas.opengeodeweb_viewer.import_project, @@ -55,13 +47,28 @@ export function useProjectManager() { params: {}, }) + const dataBaseStore = useDataBaseStore() const treeviewStore = useTreeviewStore() + const hybridViewerStore = useHybridViewerStore() + treeviewStore.clear() + dataBaseStore.clear() + hybridViewerStore.clear() + + const schemaImport = back_schemas.opengeodeweb_back.import_project + const form = new FormData() + form.append("file", file, file?.name) + + const result = await $fetch(schemaImport.$id, { + baseURL: geode.base_url, + method: "POST", + body: form, + }) + const snapshot = result?.snapshot ?? {} + treeviewStore.isImporting = true await treeviewStore.importStores(snapshot?.treeview) - const hybridViewerStore = useHybridViewerStore() await hybridViewerStore.initHybridViewer() - hybridViewerStore.clear() await hybridViewerStore.importStores(snapshot?.hybridViewer) const snapshotDataBase = snapshot?.dataBase?.db || {} @@ -77,7 +84,6 @@ export function useProjectManager() { await importWorkflowFromSnapshot(items) - // Appliquer la caméra importée après avoir créé les actors await hybridViewerStore.importStores(snapshot?.hybridViewer) const dataStyleStore = useDataStyleStore() diff --git a/stores/data_base.js b/stores/data_base.js index c2c97959..1e717240 100644 --- a/stores/data_base.js +++ b/stores/data_base.js @@ -106,6 +106,33 @@ export const useDataBaseStore = defineStore("dataBase", () => { ) } + function getCornersUuids(id) { + const { mesh_components } = itemMetaDatas(id) + return Object.values(mesh_components["Corner"]) + } + + function getLinesUuids(id) { + const { mesh_components } = itemMetaDatas(id) + return Object.values(mesh_components["Line"]) + } + function getSurfacesUuids(id) { + const { mesh_components } = itemMetaDatas(id) + return Object.values(mesh_components["Surface"]) + } + function getBlocksUuids(id) { + const { mesh_components } = itemMetaDatas(id) + return Object.values(mesh_components["Block"]) + } + + function getFlatIndexes(id, mesh_component_ids) { + const { uuid_to_flat_index } = itemMetaDatas(id) + const flat_indexes = [] + for (const mesh_component_id of mesh_component_ids) { + flat_indexes.push(uuid_to_flat_index[mesh_component_id]) + } + return flat_indexes + } + function exportStores() { const snapshotDb = {} for (const [id, item] of Object.entries(db)) { @@ -137,31 +164,10 @@ export const useDataBaseStore = defineStore("dataBase", () => { } } - function getCornersUuids(id) { - const { mesh_components } = itemMetaDatas(id) - return Object.values(mesh_components["Corner"]) - } - - function getLinesUuids(id) { - const { mesh_components } = itemMetaDatas(id) - return Object.values(mesh_components["Line"]) - } - function getSurfacesUuids(id) { - const { mesh_components } = itemMetaDatas(id) - return Object.values(mesh_components["Surface"]) - } - function getBlocksUuids(id) { - const { mesh_components } = itemMetaDatas(id) - return Object.values(mesh_components["Block"]) - } - - function getFlatIndexes(id, mesh_component_ids) { - const { uuid_to_flat_index } = itemMetaDatas(id) - const flat_indexes = [] - for (const mesh_component_id of mesh_component_ids) { - flat_indexes.push(uuid_to_flat_index[mesh_component_id]) + function clear() { + for (const id of Object.keys(db)) { + delete db[id] } - return flat_indexes } return { @@ -180,5 +186,6 @@ export const useDataBaseStore = defineStore("dataBase", () => { getFlatIndexes, exportStores, importStores, + clear, } }) diff --git a/stores/hybrid_viewer.js b/stores/hybrid_viewer.js index ab1ef9e1..2113131a 100644 --- a/stores/hybrid_viewer.js +++ b/stores/hybrid_viewer.js @@ -19,38 +19,6 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { let viewStream let gridActor = null - // Helper: conversion stricte aux types/schema + logs des types - function sanitizeCameraOptions(opts) { - if (!opts) return null - const toNums = (arr, size) => - Array.from(arr || []) - .slice(0, size) - .map((n) => Number(n)) - return { - focal_point: toNums(opts.focal_point, 3), - view_up: toNums(opts.view_up, 3), - position: toNums(opts.position, 3), - view_angle: Number(opts.view_angle), - clipping_range: toNums(opts.clipping_range, 2), - } - } - - function logCameraOptions(label, opts) { - const types = (arr) => Array.from(arr || []).map((v) => typeof v) - console.log(`[Camera] ${label}`, { - focal_point: opts?.focal_point, - focal_point_types: types(opts?.focal_point), - view_up: opts?.view_up, - view_up_types: types(opts?.view_up), - position: opts?.position, - position_types: types(opts?.position), - view_angle: opts?.view_angle, - view_angle_type: typeof opts?.view_angle, - clipping_range: opts?.clipping_range, - clipping_range_types: types(opts?.clipping_range), - }) - } - async function initHybridViewer() { if (status.value !== Status.NOT_CREATED) return status.value = Status.CREATING @@ -265,7 +233,6 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { const renderer = genericRenderWindow.value.getRenderer() const camera = renderer.getActiveCamera() - // Appliquer les composantes (x, y, z), pas le tableau camera.setFocalPoint(...cam.focal_point) camera.setViewUp(...cam.view_up) camera.setPosition(...cam.position) @@ -274,7 +241,6 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { genericRenderWindow.value.getRenderWindow().render() - // Envoyer uniquement les champs conformes au schéma (sans distance) const payload = { camera_options: { focal_point: cam.focal_point, diff --git a/stores/treeview.js b/stores/treeview.js index 12d5e0d5..56e17e59 100644 --- a/stores/treeview.js +++ b/stores/treeview.js @@ -84,15 +84,32 @@ export const useTreeviewStore = defineStore("treeview", () => { function finalizeImportSelection() { const ids = pendingSelectionIds.value || [] const rebuilt = [] - for (const group of items.value) { - for (const child of group.children) { - if (ids.includes(child.id)) rebuilt.push(child) + if (ids.length === 0) { + for (const group of items.value) { + for (const child of group.children) { + rebuilt.push(child) + } + } + } else { + for (const group of items.value) { + for (const child of group.children) { + if (ids.includes(child.id)) rebuilt.push(child) + } } } selection.value = rebuilt pendingSelectionIds.value = [] } + function clear() { + items.value = [] + selection.value = [] + components_selection.value = [] + pendingSelectionIds.value = [] + model_id.value = "" + selectedTree.value = null + } + return { items, selection, @@ -110,5 +127,6 @@ export const useTreeviewStore = defineStore("treeview", () => { exportStores, importStores, finalizeImportSelection, + clear, } }) From 17607da4e4690328e918f78285fa831c9b5d3fa3 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Sat, 8 Nov 2025 10:16:32 +0100 Subject: [PATCH 62/66] fix : brep imports --- stores/data_base.js | 12 ++++++------ stores/data_style.js | 5 ++--- utils/default_styles.js | 4 ++-- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/stores/data_base.js b/stores/data_base.js index 1e717240..4180a624 100644 --- a/stores/data_base.js +++ b/stores/data_base.js @@ -48,7 +48,7 @@ export const useDataBaseStore = defineStore("dataBase", () => { }) } - const treeviewStore = useTreeviewStore() + // const treeviewStore = useTreeviewStore() const hybridViewerStore = useHybridViewerStore() async function addItem( @@ -62,11 +62,11 @@ export const useDataBaseStore = defineStore("dataBase", () => { vtk_js: { binary_light_viewable }, }, ) { - console.log("[DataBase] addItem start", { - id, - object_type: value.object_type, - geode_object: value.geode_object, - }) + // console.log("[DataBase] addItem start", { + // id, + // object_type: value.object_type, + // geode_object: value.geode_object, + // }) db[id] = value if (value.object_type === "model") { diff --git a/stores/data_style.js b/stores/data_style.js index 78535714..d61a64a5 100644 --- a/stores/data_style.js +++ b/stores/data_style.js @@ -1,8 +1,6 @@ import useDataStyleState from "../internal_stores/data_style_state.js" import useMeshStyle from "../internal_stores/mesh/index.js" import useModelStyle from "../internal_stores/model/index.js" -import { defineStore } from "pinia" -import { useDataBaseStore } from "./data_base.js" export const useDataStyleStore = defineStore("dataStyle", () => { const dataStyleState = useDataStyleState() @@ -11,7 +9,8 @@ export const useDataStyleStore = defineStore("dataStyle", () => { const dataBaseStore = useDataBaseStore() function addDataStyle(id, geode_object) { - dataStyleState.styles[id] = getDefaultStyle(geode_object) + const style = getDefaultStyle(geode_object) + dataStyleState.styles[id] = style } function setVisibility(id, visibility) { diff --git a/utils/default_styles.js b/utils/default_styles.js index eede29e7..6f071350 100644 --- a/utils/default_styles.js +++ b/utils/default_styles.js @@ -16,9 +16,9 @@ const corners_defaultColor = { r: 20, g: 20, b: 20 } const lines_defaultVisibility = true const lines_defaultColor = { r: 20, g: 20, b: 20 } const surfaces_defaultVisibility = true -const surfaces_defaultColor = { r: 20, g: 20, b: 20 } +const surfaces_defaultColor = { r: 255, g: 255, b: 255 } const blocks_defaultVisibility = true -const blocks_defaultColor = { r: 20, g: 20, b: 20 } +const blocks_defaultColor = { r: 255, g: 255, b: 255 } // Mesh functions const meshPointsDefaultStyle = ( From 9853aaba21f7d5f1a1219f09266a2e9dce81b21a Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Sat, 8 Nov 2025 10:18:30 +0100 Subject: [PATCH 63/66] renames --- stores/hybrid_viewer.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/stores/hybrid_viewer.js b/stores/hybrid_viewer.js index 848e516e..a6012d61 100644 --- a/stores/hybrid_viewer.js +++ b/stores/hybrid_viewer.js @@ -226,27 +226,27 @@ export const useHybridViewerStore = defineStore("hybridViewer", () => { await setZScaling(z_scale) } - const cam = snapshot?.camera_options - if (!cam) return + const camera_options = snapshot?.camera_options + if (!camera_options) return const renderer = genericRenderWindow.value.getRenderer() const camera = renderer.getActiveCamera() - camera.setFocalPoint(...cam.focal_point) - camera.setViewUp(...cam.view_up) - camera.setPosition(...cam.position) - camera.setViewAngle(cam.view_angle) - camera.setClippingRange(...cam.clipping_range) + camera.setFocalPoint(...camera_options.focal_point) + camera.setViewUp(...camera_options.view_up) + camera.setPosition(...camera_options.position) + camera.setViewAngle(camera_options.view_angle) + camera.setClippingRange(...camera_options.clipping_range) genericRenderWindow.value.getRenderWindow().render() const payload = { camera_options: { - focal_point: cam.focal_point, - view_up: cam.view_up, - position: cam.position, - view_angle: cam.view_angle, - clipping_range: cam.clipping_range, + focal_point: camera_options.focal_point, + view_up: camera_options.view_up, + position: camera_options.position, + view_angle: camera_options.view_angle, + clipping_range: camera_options.clipping_range, }, } viewer_call( From 5232228dcd0679e66b8d2c1e0433abfd975a5778 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Sat, 8 Nov 2025 10:30:01 +0100 Subject: [PATCH 64/66] brep fix --- stores/data_style.js | 1 + 1 file changed, 1 insertion(+) diff --git a/stores/data_style.js b/stores/data_style.js index d61a64a5..2bd5d044 100644 --- a/stores/data_style.js +++ b/stores/data_style.js @@ -1,6 +1,7 @@ import useDataStyleState from "../internal_stores/data_style_state.js" import useMeshStyle from "../internal_stores/mesh/index.js" import useModelStyle from "../internal_stores/model/index.js" +import { getDefaultStyle } from "../utils/default_styles.js" export const useDataStyleStore = defineStore("dataStyle", () => { const dataStyleState = useDataStyleState() From 6a6187443fd4663629d3d838e79dca875f839950 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Mon, 10 Nov 2025 10:50:08 +0100 Subject: [PATCH 65/66] project_manager --- composables/project_manager.js | 209 ++++++++++++++++++++------------- 1 file changed, 129 insertions(+), 80 deletions(-) diff --git a/composables/project_manager.js b/composables/project_manager.js index 42aded87..3d875f39 100644 --- a/composables/project_manager.js +++ b/composables/project_manager.js @@ -1,100 +1,149 @@ import back_schemas from "@geode/opengeodeweb-back/opengeodeweb_back_schemas.json" -import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json" import fileDownload from "js-file-download" +import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json" export function useProjectManager() { const geode = useGeodeStore() const appStore = useAppStore() - async function exportProject() { + const exportProject = function () { geode.start_request() - try { - await useInfraStore().create_connection() - const snapshot = appStore.exportStores() - const schema = back_schemas.opengeodeweb_back.export_project - const defaultName = "project.zip" - - await api_fetch( - { schema, params: { snapshot, filename: defaultName } }, - { - response_function: async (response) => { - const data = response._data - const downloadName = - response.headers?.get?.("new-file-name") || defaultName - fileDownload(data, downloadName) + const infraStore = useInfraStore() + const snapshot = appStore.exportStores() + const schema = back_schemas.opengeodeweb_back.export_project + const defaultName = "project.zip" + + return infraStore + .create_connection() + .then(function () { + return api_fetch( + { schema, params: { snapshot, filename: defaultName } }, + { + response_function: function (response) { + const data = response._data + let downloadName = defaultName + if ( + response && + response.headers && + typeof response.headers.get === "function" + ) { + const name = response.headers.get("new-file-name") + if (name) { + downloadName = name + } + } + fileDownload(data, downloadName) + }, }, - }, - ) - } finally { - geode.stop_request() - } + ) + }) + .finally(function () { + geode.stop_request() + }) } - async function importProjectFile(file) { + const importProjectFile = function (file) { geode.start_request() - try { - await useInfraStore().create_connection() - const viewerStore = useViewerStore() - await viewerStore.ws_connect() + const viewerStore = useViewerStore() + const dataBaseStore = useDataBaseStore() + const treeviewStore = useTreeviewStore() + const hybridViewerStore = useHybridViewerStore() + const infraStore = useInfraStore() - await viewer_call({ - schema: viewer_schemas.opengeodeweb_viewer.import_project, - params: {}, + return infraStore + .create_connection() + .then(function () { + return viewerStore.ws_connect() }) - await viewer_call({ - schema: viewer_schemas.opengeodeweb_viewer.viewer.reset_visualization, - params: {}, + .then(function () { + return viewer_call({ + schema: viewer_schemas.opengeodeweb_viewer.import_project, + params: {}, + }) }) - - const dataBaseStore = useDataBaseStore() - const treeviewStore = useTreeviewStore() - const hybridViewerStore = useHybridViewerStore() - treeviewStore.clear() - dataBaseStore.clear() - hybridViewerStore.clear() - - const schemaImport = back_schemas.opengeodeweb_back.import_project - const form = new FormData() - form.append("file", file, file?.name) - - const result = await $fetch(schemaImport.$id, { - baseURL: geode.base_url, - method: "POST", - body: form, + .then(function () { + return viewer_call({ + schema: + viewer_schemas.opengeodeweb_viewer.viewer.reset_visualization, + params: {}, + }) + }) + .then(function () { + treeviewStore.clear() + dataBaseStore.clear() + hybridViewerStore.clear() + + const schemaImport = back_schemas.opengeodeweb_back.import_project + const form = new FormData() + const fileName = file && file.name ? file.name : "file" + form.append("file", file, fileName) + + return $fetch(schemaImport.$id, { + baseURL: geode.base_url, + method: "POST", + body: form, + }) + }) + .then(function (result) { + const snapshot = result && result.snapshot ? result.snapshot : {} + + treeviewStore.isImporting = true + + return Promise.resolve() + .then(function () { + return treeviewStore.importStores(snapshot.treeview) + }) + .then(function () { + return hybridViewerStore.initHybridViewer() + }) + .then(function () { + return hybridViewerStore.importStores(snapshot.hybridViewer) + }) + .then(function () { + const snapshotDataBase = + snapshot && snapshot.dataBase && snapshot.dataBase.db + ? snapshot.dataBase.db + : {} + const items = Object.entries(snapshotDataBase).map(function (pair) { + const id = pair[0] + const item = pair[1] + const binaryLightViewable = + item && item.vtk_js && item.vtk_js.binary_light_viewable + ? item.vtk_js.binary_light_viewable + : undefined + return { + id: id, + object_type: item.object_type, + geode_object: item.geode_object, + native_filename: item.native_filename, + viewable_filename: item.viewable_filename, + displayed_name: item.displayed_name, + vtk_js: { binary_light_viewable: binaryLightViewable }, + } + }) + + return importWorkflowFromSnapshot(items) + }) + .then(function () { + return hybridViewerStore.importStores(snapshot.hybridViewer) + }) + .then(function () { + const dataStyleStore = useDataStyleStore() + return dataStyleStore.importStores(snapshot.dataStyle) + }) + .then(function () { + const dataStyleStore = useDataStyleStore() + return dataStyleStore.applyAllStylesFromState() + }) + .then(function () { + treeviewStore.finalizeImportSelection() + treeviewStore.isImporting = false + }) + }) + .finally(function () { + geode.stop_request() }) - const snapshot = result?.snapshot ?? {} - - treeviewStore.isImporting = true - await treeviewStore.importStores(snapshot?.treeview) - - await hybridViewerStore.initHybridViewer() - await hybridViewerStore.importStores(snapshot?.hybridViewer) - - const snapshotDataBase = snapshot?.dataBase?.db || {} - const items = Object.entries(snapshotDataBase).map(([id, item]) => ({ - id, - object_type: item.object_type, - geode_object: item.geode_object, - native_filename: item.native_filename, - viewable_filename: item.viewable_filename, - displayed_name: item.displayed_name, - vtk_js: { binary_light_viewable: item?.vtk_js?.binary_light_viewable }, - })) - - await importWorkflowFromSnapshot(items) - - await hybridViewerStore.importStores(snapshot?.hybridViewer) - - const dataStyleStore = useDataStyleStore() - await dataStyleStore.importStores(snapshot?.dataStyle) - await dataStyleStore.applyAllStylesFromState() - - treeviewStore.finalizeImportSelection() - treeviewStore.isImporting = false - } finally { - geode.stop_request() - } } return { exportProject, importProjectFile } From 6e67d32cb70705254d48c98f91907648480dd184 Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Mon, 10 Nov 2025 09:51:05 +0000 Subject: [PATCH 66/66] Apply prepare changes --- composables/project_manager.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composables/project_manager.js b/composables/project_manager.js index 3d875f39..02540b2b 100644 --- a/composables/project_manager.js +++ b/composables/project_manager.js @@ -64,8 +64,7 @@ export function useProjectManager() { }) .then(function () { return viewer_call({ - schema: - viewer_schemas.opengeodeweb_viewer.viewer.reset_visualization, + schema: viewer_schemas.opengeodeweb_viewer.viewer.reset_visualization, params: {}, }) })