Spaces:
Running
Running
Update UI
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- Dockerfile +1 -1
- bun.lock +245 -0
- components.json +16 -0
- package.json +15 -0
- src-python/src/main.py +40 -38
- src/app.css +122 -1
- src/lib/components/{scene → 3d}/Floor.svelte +6 -16
- src/lib/components/3d/Robot.svelte +53 -0
- src/lib/components/3d/Selectable.svelte +105 -0
- src/lib/components/{scene → 3d}/robot/URDF/createRobot.svelte.ts +0 -0
- src/lib/components/{scene → 3d}/robot/URDF/interfaces/IUrdfBox.ts +0 -0
- src/lib/components/{scene → 3d}/robot/URDF/interfaces/IUrdfCylinder.ts +0 -0
- src/lib/components/{scene → 3d}/robot/URDF/interfaces/IUrdfJoint.ts +0 -0
- src/lib/components/{scene → 3d}/robot/URDF/interfaces/IUrdfLink.ts +0 -0
- src/lib/components/{scene → 3d}/robot/URDF/interfaces/IUrdfMesh.ts +0 -0
- src/lib/components/{scene → 3d}/robot/URDF/interfaces/IUrdfRobot.ts +0 -0
- src/lib/components/{scene → 3d}/robot/URDF/interfaces/IUrdfVisual.ts +0 -0
- src/lib/components/{scene → 3d}/robot/URDF/interfaces/index.ts +0 -0
- src/lib/components/{scene → 3d}/robot/URDF/mesh/DAE.svelte +0 -0
- src/lib/components/{scene → 3d}/robot/URDF/mesh/OBJ.svelte +0 -0
- src/lib/components/{scene → 3d}/robot/URDF/mesh/STL.svelte +0 -0
- src/lib/components/{scene → 3d}/robot/URDF/primitives/UrdfJoint.svelte +22 -19
- src/lib/components/{scene → 3d}/robot/URDF/primitives/UrdfLink.svelte +4 -4
- src/lib/components/{scene → 3d}/robot/URDF/primitives/UrdfThree.svelte +0 -0
- src/lib/components/{scene → 3d}/robot/URDF/primitives/UrdfVisual.svelte +14 -11
- src/lib/components/{scene → 3d}/robot/URDF/runes/urdf_state.svelte.ts +0 -0
- src/lib/components/{scene → 3d}/robot/URDF/utils/UrdfParser.ts +0 -0
- src/lib/components/{scene → 3d}/robot/URDF/utils/helper.ts +0 -0
- src/lib/components/ButtonBar.svelte +161 -0
- src/lib/components/ControlsSheet.svelte +76 -0
- src/lib/components/Overlay.svelte +36 -0
- src/lib/components/PanelWrapper.svelte +32 -0
- src/lib/components/RobotStatusDemo.svelte +65 -0
- src/lib/components/SettingsSheet.svelte +109 -0
- src/lib/components/StatusCard.svelte +79 -0
- src/lib/components/panel/ControlPanel.svelte +1 -0
- src/lib/components/panel/RobotControlPanel.old.svelte +659 -0
- src/lib/components/panel/RobotControlPanel.svelte +448 -383
- src/lib/components/panel/SettingsPanel.svelte +1 -1
- src/lib/components/scene/Robot.svelte +0 -44
- src/lib/components/scene/Selectable.svelte +0 -68
- src/lib/components/ui/accordion/accordion-content.svelte +25 -0
- src/lib/components/ui/accordion/accordion-item.svelte +17 -0
- src/lib/components/ui/accordion/accordion-root.svelte +16 -0
- src/lib/components/ui/accordion/accordion-trigger.svelte +32 -0
- src/lib/components/ui/accordion/index.ts +16 -0
- src/lib/components/ui/alert-dialog/alert-dialog-action.svelte +18 -0
- src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte +18 -0
- src/lib/components/ui/alert-dialog/alert-dialog-content.svelte +27 -0
- src/lib/components/ui/alert-dialog/alert-dialog-description.svelte +17 -0
Dockerfile
CHANGED
@@ -61,4 +61,4 @@ WORKDIR $HOME/app
|
|
61 |
EXPOSE 7860
|
62 |
|
63 |
# Start the FastAPI server (serves both frontend and backend)
|
64 |
-
CMD ["sh", "-c", "cd src-python && uv run python start_server.py"]
|
|
|
61 |
EXPOSE 7860
|
62 |
|
63 |
# Start the FastAPI server (serves both frontend and backend)
|
64 |
+
CMD ["sh", "-c", "EXPOSE_FRONTEND=true cd src-python && uv run python start_server.py"]
|
bun.lock
CHANGED
@@ -10,28 +10,43 @@
|
|
10 |
"clsx": "^2.1.1",
|
11 |
"tailwind-merge": "^3.3.0",
|
12 |
"three": "^0.177.0",
|
|
|
13 |
"zod": "^3.25.42",
|
14 |
},
|
15 |
"devDependencies": {
|
16 |
"@eslint/compat": "^1.2.5",
|
17 |
"@eslint/js": "^9.18.0",
|
|
|
|
|
|
|
|
|
18 |
"@sveltejs/adapter-auto": "^6.0.0",
|
19 |
"@sveltejs/adapter-static": "^3.0.8",
|
20 |
"@sveltejs/kit": "^2.16.0",
|
21 |
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
22 |
"@tailwindcss/vite": "^4.0.0",
|
|
|
|
|
|
|
23 |
"eslint": "^9.18.0",
|
24 |
"eslint-config-prettier": "^10.0.1",
|
25 |
"eslint-plugin-svelte": "^3.0.0",
|
26 |
"globals": "^16.0.0",
|
|
|
|
|
|
|
27 |
"prettier": "^3.4.2",
|
28 |
"prettier-plugin-svelte": "^3.3.3",
|
29 |
"prettier-plugin-tailwindcss": "^0.6.11",
|
30 |
"svelte": "^5.0.0",
|
31 |
"svelte-check": "^4.0.0",
|
|
|
|
|
32 |
"tailwindcss": "^4.0.0",
|
|
|
33 |
"typescript": "^5.0.0",
|
34 |
"typescript-eslint": "^8.20.0",
|
|
|
35 |
"vite": "^6.2.6",
|
36 |
},
|
37 |
},
|
@@ -39,6 +54,14 @@
|
|
39 |
"packages": {
|
40 |
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
|
41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
"@dimforge/rapier3d-compat": ["@dimforge/rapier3d-compat@0.12.0", "", {}, "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow=="],
|
43 |
|
44 |
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA=="],
|
@@ -111,6 +134,12 @@
|
|
111 |
|
112 |
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.3.1", "", { "dependencies": { "@eslint/core": "^0.14.0", "levn": "^0.4.1" } }, "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w=="],
|
113 |
|
|
|
|
|
|
|
|
|
|
|
|
|
114 |
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
|
115 |
|
116 |
"@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="],
|
@@ -119,6 +148,16 @@
|
|
119 |
|
120 |
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
|
121 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
122 |
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
|
123 |
|
124 |
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="],
|
@@ -131,6 +170,16 @@
|
|
131 |
|
132 |
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
|
133 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
134 |
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
135 |
|
136 |
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
|
@@ -139,6 +188,8 @@
|
|
139 |
|
140 |
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
|
141 |
|
|
|
|
|
142 |
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.41.1", "", { "os": "android", "cpu": "arm" }, "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw=="],
|
143 |
|
144 |
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.41.1", "", { "os": "android", "cpu": "arm64" }, "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA=="],
|
@@ -191,6 +242,8 @@
|
|
191 |
|
192 |
"@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@4.0.1", "", { "dependencies": { "debug": "^4.3.7" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^5.0.0", "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw=="],
|
193 |
|
|
|
|
|
194 |
"@tailwindcss/node": ["@tailwindcss/node@4.1.8", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.8" } }, "sha512-OWwBsbC9BFAJelmnNcrKuf+bka2ZxCE2A4Ft53Tkg4uoiE67r/PMEYwCsourC26E+kmxfwE0hVzMdxqeW+xu7Q=="],
|
195 |
|
196 |
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.8", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.8", "@tailwindcss/oxide-darwin-arm64": "4.1.8", "@tailwindcss/oxide-darwin-x64": "4.1.8", "@tailwindcss/oxide-freebsd-x64": "4.1.8", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.8", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.8", "@tailwindcss/oxide-linux-arm64-musl": "4.1.8", "@tailwindcss/oxide-linux-x64-gnu": "4.1.8", "@tailwindcss/oxide-linux-x64-musl": "4.1.8", "@tailwindcss/oxide-wasm32-wasi": "4.1.8", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.8", "@tailwindcss/oxide-win32-x64-msvc": "4.1.8" } }, "sha512-d7qvv9PsM5N3VNKhwVUhpK6r4h9wtLkJ6lz9ZY9aeZgrUWk1Z8VPyqyDT9MZlem7GTGseRQHkeB1j3tC7W1P+A=="],
|
@@ -221,6 +274,8 @@
|
|
221 |
|
222 |
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.8", "", { "dependencies": { "@tailwindcss/node": "4.1.8", "@tailwindcss/oxide": "4.1.8", "tailwindcss": "4.1.8" }, "peerDependencies": { "vite": "^5.2.0 || ^6" } }, "sha512-CQ+I8yxNV5/6uGaJjiuymgw0kEQiNKRinYbZXPdx1fk5WgiyReG0VaUx/Xq6aVNSUNJFzxm6o8FNKS5aMaim5A=="],
|
223 |
|
|
|
|
|
224 |
"@threejs-kit/instanced-sprite-mesh": ["@threejs-kit/instanced-sprite-mesh@2.5.1", "", { "dependencies": { "diet-sprite": "^0.0.1", "earcut": "^2.2.4", "maath": "^0.10.7", "three-instanced-uniforms-mesh": "^0.52.4", "troika-three-utils": "^0.52.4" }, "peerDependencies": { "three": ">=0.170.0" } }, "sha512-pmt1ALRhbHhCJQTj2FuthH6PeLIeaM4hOuS2JO3kWSwlnvx/9xuUkjFR3JOi/myMqsH7pSsLIROSaBxDfttjeA=="],
|
225 |
|
226 |
"@threlte/core": ["@threlte/core@8.0.4", "", { "dependencies": { "mitt": "^3.0.1" }, "peerDependencies": { "svelte": ">=5", "three": ">=0.155" } }, "sha512-kg0nTq00RqgTExIEKkyO/yCupCt73lybuFZObKHCRaY7dPcCvRD4tHkyKIFeYLYV4R6u1nqIuzfgDzYJqHbLlQ=="],
|
@@ -231,10 +286,14 @@
|
|
231 |
|
232 |
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
|
233 |
|
|
|
|
|
234 |
"@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="],
|
235 |
|
236 |
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
237 |
|
|
|
|
|
238 |
"@types/stats.js": ["@types/stats.js@0.17.4", "", {}, "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA=="],
|
239 |
|
240 |
"@types/three": ["@types/three@0.176.0", "", { "dependencies": { "@dimforge/rapier3d-compat": "^0.12.0", "@tweenjs/tween.js": "~23.1.3", "@types/stats.js": "*", "@types/webxr": "*", "@webgpu/types": "*", "fflate": "~0.8.2", "meshoptimizer": "~0.18.1" } }, "sha512-FwfPXxCqOtP7EdYMagCFePNKoG1AGBDUEVKtluv2BTVRpSt7b+X27xNsirPCTCqY1pGYsPUzaM3jgWP7dXSxlw=="],
|
@@ -281,6 +340,8 @@
|
|
281 |
|
282 |
"bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="],
|
283 |
|
|
|
|
|
284 |
"brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
|
285 |
|
286 |
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
@@ -301,28 +362,96 @@
|
|
301 |
|
302 |
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
303 |
|
|
|
|
|
304 |
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
305 |
|
|
|
|
|
306 |
"cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="],
|
307 |
|
308 |
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
309 |
|
|
|
|
|
310 |
"cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
|
311 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
312 |
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
313 |
|
314 |
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
|
315 |
|
316 |
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
317 |
|
|
|
|
|
318 |
"detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
|
319 |
|
320 |
"devalue": ["devalue@5.1.1", "", {}, "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw=="],
|
321 |
|
322 |
"diet-sprite": ["diet-sprite@0.0.1", "", {}, "sha512-zSHI2WDAn1wJqJYxcmjWfJv3Iw8oL9reQIbEyx2x2/EZ4/qmUTIo8/5qOCurnAcq61EwtJJaZ0XTy2NRYqpB5A=="],
|
323 |
|
|
|
|
|
324 |
"earcut": ["earcut@2.2.4", "", {}, "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ=="],
|
325 |
|
|
|
|
|
|
|
|
|
|
|
|
|
326 |
"enhanced-resolve": ["enhanced-resolve@5.18.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg=="],
|
327 |
|
328 |
"esbuild": ["esbuild@0.25.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.5", "@esbuild/android-arm": "0.25.5", "@esbuild/android-arm64": "0.25.5", "@esbuild/android-x64": "0.25.5", "@esbuild/darwin-arm64": "0.25.5", "@esbuild/darwin-x64": "0.25.5", "@esbuild/freebsd-arm64": "0.25.5", "@esbuild/freebsd-x64": "0.25.5", "@esbuild/linux-arm": "0.25.5", "@esbuild/linux-arm64": "0.25.5", "@esbuild/linux-ia32": "0.25.5", "@esbuild/linux-loong64": "0.25.5", "@esbuild/linux-mips64el": "0.25.5", "@esbuild/linux-ppc64": "0.25.5", "@esbuild/linux-riscv64": "0.25.5", "@esbuild/linux-s390x": "0.25.5", "@esbuild/linux-x64": "0.25.5", "@esbuild/netbsd-arm64": "0.25.5", "@esbuild/netbsd-x64": "0.25.5", "@esbuild/openbsd-arm64": "0.25.5", "@esbuild/openbsd-x64": "0.25.5", "@esbuild/sunos-x64": "0.25.5", "@esbuild/win32-arm64": "0.25.5", "@esbuild/win32-ia32": "0.25.5", "@esbuild/win32-x64": "0.25.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ=="],
|
@@ -351,8 +480,12 @@
|
|
351 |
|
352 |
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
|
353 |
|
|
|
|
|
354 |
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
355 |
|
|
|
|
|
356 |
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
357 |
|
358 |
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
|
@@ -389,12 +522,18 @@
|
|
389 |
|
390 |
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
391 |
|
|
|
|
|
392 |
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
393 |
|
394 |
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
|
395 |
|
396 |
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
|
397 |
|
|
|
|
|
|
|
|
|
398 |
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
399 |
|
400 |
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
@@ -421,6 +560,10 @@
|
|
421 |
|
422 |
"known-css-properties": ["known-css-properties@0.36.0", "", {}, "sha512-A+9jP+IUmuQsNdsLdcg6Yt7voiMF/D4K83ew0OpJtpu+l34ef7LaohWV0Rc6KNvzw6ZDizkqfyB5JznZnzuKQA=="],
|
423 |
|
|
|
|
|
|
|
|
|
424 |
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
425 |
|
426 |
"lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="],
|
@@ -447,10 +590,14 @@
|
|
447 |
|
448 |
"lilconfig": ["lilconfig@2.1.0", "", {}, "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="],
|
449 |
|
|
|
|
|
450 |
"locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
|
451 |
|
452 |
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
453 |
|
|
|
|
|
454 |
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
|
455 |
|
456 |
"maath": ["maath@0.10.8", "", { "peerDependencies": { "@types/three": ">=0.134.0", "three": ">=0.134.0" } }, "sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g=="],
|
@@ -473,6 +620,10 @@
|
|
473 |
|
474 |
"mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="],
|
475 |
|
|
|
|
|
|
|
|
|
476 |
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
|
477 |
|
478 |
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
|
@@ -489,16 +640,24 @@
|
|
489 |
|
490 |
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
491 |
|
|
|
|
|
|
|
|
|
492 |
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
493 |
|
494 |
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
495 |
|
496 |
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
497 |
|
|
|
|
|
498 |
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
499 |
|
500 |
"picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
|
501 |
|
|
|
|
|
502 |
"postcss": ["postcss@8.5.4", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w=="],
|
503 |
|
504 |
"postcss-load-config": ["postcss-load-config@3.1.4", "", { "dependencies": { "lilconfig": "^2.0.5", "yaml": "^1.10.2" }, "peerDependencies": { "postcss": ">=8.0.9", "ts-node": ">=9.0.0" }, "optionalPeers": ["postcss", "ts-node"] }, "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg=="],
|
@@ -509,6 +668,10 @@
|
|
509 |
|
510 |
"postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
|
511 |
|
|
|
|
|
|
|
|
|
512 |
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
513 |
|
514 |
"prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="],
|
@@ -519,6 +682,8 @@
|
|
519 |
|
520 |
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
521 |
|
|
|
|
|
522 |
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
523 |
|
524 |
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
@@ -529,12 +694,20 @@
|
|
529 |
|
530 |
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
531 |
|
|
|
|
|
532 |
"rollup": ["rollup@4.41.1", "", { "dependencies": { "@types/estree": "1.0.7" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.41.1", "@rollup/rollup-android-arm64": "4.41.1", "@rollup/rollup-darwin-arm64": "4.41.1", "@rollup/rollup-darwin-x64": "4.41.1", "@rollup/rollup-freebsd-arm64": "4.41.1", "@rollup/rollup-freebsd-x64": "4.41.1", "@rollup/rollup-linux-arm-gnueabihf": "4.41.1", "@rollup/rollup-linux-arm-musleabihf": "4.41.1", "@rollup/rollup-linux-arm64-gnu": "4.41.1", "@rollup/rollup-linux-arm64-musl": "4.41.1", "@rollup/rollup-linux-loongarch64-gnu": "4.41.1", "@rollup/rollup-linux-powerpc64le-gnu": "4.41.1", "@rollup/rollup-linux-riscv64-gnu": "4.41.1", "@rollup/rollup-linux-riscv64-musl": "4.41.1", "@rollup/rollup-linux-s390x-gnu": "4.41.1", "@rollup/rollup-linux-x64-gnu": "4.41.1", "@rollup/rollup-linux-x64-musl": "4.41.1", "@rollup/rollup-win32-arm64-msvc": "4.41.1", "@rollup/rollup-win32-ia32-msvc": "4.41.1", "@rollup/rollup-win32-x64-msvc": "4.41.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw=="],
|
533 |
|
534 |
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
535 |
|
|
|
|
|
|
|
|
|
536 |
"sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
|
537 |
|
|
|
|
|
538 |
"semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
539 |
|
540 |
"set-cookie-parser": ["set-cookie-parser@2.7.1", "", {}, "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="],
|
@@ -549,6 +722,8 @@
|
|
549 |
|
550 |
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
551 |
|
|
|
|
|
552 |
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
553 |
|
554 |
"svelte": ["svelte@5.33.10", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^1.4.6", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-/yArPQIBoQS2p86LKnvJywOXkVHeEXnFgrDPSxkEfIAEkykopYuy2bF6UUqHG4IbZlJD6OurLxJT8Kn7kTk9WA=="],
|
@@ -557,8 +732,16 @@
|
|
557 |
|
558 |
"svelte-eslint-parser": ["svelte-eslint-parser@1.2.0", "", { "dependencies": { "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.0.0", "espree": "^10.0.0", "postcss": "^8.4.49", "postcss-scss": "^4.0.9", "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" }, "optionalPeers": ["svelte"] }, "sha512-mbPtajIeuiyU80BEyGvwAktBeTX7KCr5/0l+uRGLq1dafwRNrjfM5kHGJScEBlPG3ipu6dJqfW/k0/fujvIEVw=="],
|
559 |
|
|
|
|
|
|
|
|
|
|
|
|
|
560 |
"tailwind-merge": ["tailwind-merge@3.3.0", "", {}, "sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ=="],
|
561 |
|
|
|
|
|
562 |
"tailwindcss": ["tailwindcss@4.1.8", "", {}, "sha512-kjeW8gjdxasbmFKpVGrGd5T4i40mV5J2Rasw48QARfYeQ8YS9x02ON9SFWax3Qf616rt4Cp3nVNIj6Hd1mP3og=="],
|
563 |
|
564 |
"tapable": ["tapable@2.2.2", "", {}, "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg=="],
|
@@ -573,8 +756,14 @@
|
|
573 |
|
574 |
"three-perf": ["three-perf@github:jerzakm/three-perf#322d7d3", { "dependencies": { "troika-three-text": "^0.52.0", "tweakpane": "^3.1.10" }, "peerDependencies": { "three": ">=0.170" } }, "jerzakm-three-perf-322d7d3"],
|
575 |
|
|
|
|
|
576 |
"three-viewport-gizmo": ["three-viewport-gizmo@2.2.0", "", { "peerDependencies": { "three": ">=0.162.0 <1.0.0" } }, "sha512-Jo9Liur1rUmdKk75FZumLU/+hbF+RtJHi1qsKZpntjKlCYScK6tjbYoqvJ9M+IJphrlQJF5oReFW7Sambh0N4Q=="],
|
577 |
|
|
|
|
|
|
|
|
|
578 |
"tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
|
579 |
|
580 |
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
@@ -589,6 +778,10 @@
|
|
589 |
|
590 |
"ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="],
|
591 |
|
|
|
|
|
|
|
|
|
592 |
"tweakpane": ["tweakpane@3.1.10", "", {}, "sha512-rqwnl/pUa7+inhI2E9ayGTqqP0EPOOn/wVvSWjZsRbZUItzNShny7pzwL3hVlaN4m9t/aZhsP0aFQ9U5VVR2VQ=="],
|
593 |
|
594 |
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
@@ -597,12 +790,20 @@
|
|
597 |
|
598 |
"typescript-eslint": ["typescript-eslint@8.33.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.33.0", "@typescript-eslint/parser": "8.33.0", "@typescript-eslint/utils": "8.33.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-5YmNhF24ylCsvdNW2oJwMzTbaeO4bg90KeGtMjUw0AGtHksgEPLRTUil+coHwCfiu4QjVJFnjp94DmU6zV7DhQ=="],
|
599 |
|
|
|
|
|
600 |
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
601 |
|
602 |
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
603 |
|
|
|
|
|
604 |
"vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="],
|
605 |
|
|
|
|
|
|
|
|
|
606 |
"vitefu": ["vitefu@1.0.6", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" }, "optionalPeers": ["vite"] }, "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA=="],
|
607 |
|
608 |
"webgl-sdf-generator": ["webgl-sdf-generator@1.1.1", "", {}, "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA=="],
|
@@ -627,6 +828,12 @@
|
|
627 |
|
628 |
"@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
|
629 |
|
|
|
|
|
|
|
|
|
|
|
|
|
630 |
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="],
|
631 |
|
632 |
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="],
|
@@ -643,10 +850,48 @@
|
|
643 |
|
644 |
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
645 |
|
|
|
|
|
|
|
|
|
646 |
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
647 |
|
648 |
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
649 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
650 |
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
651 |
}
|
652 |
}
|
|
|
10 |
"clsx": "^2.1.1",
|
11 |
"tailwind-merge": "^3.3.0",
|
12 |
"three": "^0.177.0",
|
13 |
+
"threlte-postprocessing": "^0.0.9",
|
14 |
"zod": "^3.25.42",
|
15 |
},
|
16 |
"devDependencies": {
|
17 |
"@eslint/compat": "^1.2.5",
|
18 |
"@eslint/js": "^9.18.0",
|
19 |
+
"@iconify/json": "^2.2.344",
|
20 |
+
"@iconify/tailwind4": "^1.0.6",
|
21 |
+
"@internationalized/date": "^3.5.6",
|
22 |
+
"@lucide/svelte": "^0.511.0",
|
23 |
"@sveltejs/adapter-auto": "^6.0.0",
|
24 |
"@sveltejs/adapter-static": "^3.0.8",
|
25 |
"@sveltejs/kit": "^2.16.0",
|
26 |
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
27 |
"@tailwindcss/vite": "^4.0.0",
|
28 |
+
"@tanstack/table-core": "^8.20.5",
|
29 |
+
"bits-ui": "^2.4.1",
|
30 |
+
"embla-carousel-svelte": "^8.6.0",
|
31 |
"eslint": "^9.18.0",
|
32 |
"eslint-config-prettier": "^10.0.1",
|
33 |
"eslint-plugin-svelte": "^3.0.0",
|
34 |
"globals": "^16.0.0",
|
35 |
+
"layerchart": "2.0.0-next.10",
|
36 |
+
"mode-watcher": "^1.0.6",
|
37 |
+
"paneforge": "^1.0.0-next.5",
|
38 |
"prettier": "^3.4.2",
|
39 |
"prettier-plugin-svelte": "^3.3.3",
|
40 |
"prettier-plugin-tailwindcss": "^0.6.11",
|
41 |
"svelte": "^5.0.0",
|
42 |
"svelte-check": "^4.0.0",
|
43 |
+
"svelte-sonner": "^1.0.1",
|
44 |
+
"tailwind-variants": "^1.0.0",
|
45 |
"tailwindcss": "^4.0.0",
|
46 |
+
"tw-animate-css": "^1.3.3",
|
47 |
"typescript": "^5.0.0",
|
48 |
"typescript-eslint": "^8.20.0",
|
49 |
+
"vaul-svelte": "^1.0.0-next.7",
|
50 |
"vite": "^6.2.6",
|
51 |
},
|
52 |
},
|
|
|
54 |
"packages": {
|
55 |
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
|
56 |
|
57 |
+
"@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="],
|
58 |
+
|
59 |
+
"@antfu/utils": ["@antfu/utils@8.1.1", "", {}, "sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ=="],
|
60 |
+
|
61 |
+
"@dagrejs/dagre": ["@dagrejs/dagre@1.1.4", "", { "dependencies": { "@dagrejs/graphlib": "2.2.4" } }, "sha512-QUTc54Cg/wvmlEUxB+uvoPVKFazM1H18kVHBQNmK2NbrDR5ihOCR6CXLnDSZzMcSQKJtabPUWridBOlJM3WkDg=="],
|
62 |
+
|
63 |
+
"@dagrejs/graphlib": ["@dagrejs/graphlib@2.2.4", "", {}, "sha512-mepCf/e9+SKYy1d02/UkvSy6+6MoyXhVxP8lLDfA7BPE1X1d4dR0sZznmbM8/XVJ1GPM+Svnx7Xj6ZweByWUkw=="],
|
64 |
+
|
65 |
"@dimforge/rapier3d-compat": ["@dimforge/rapier3d-compat@0.12.0", "", {}, "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow=="],
|
66 |
|
67 |
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA=="],
|
|
|
134 |
|
135 |
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.3.1", "", { "dependencies": { "@eslint/core": "^0.14.0", "levn": "^0.4.1" } }, "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w=="],
|
136 |
|
137 |
+
"@floating-ui/core": ["@floating-ui/core@1.7.1", "", { "dependencies": { "@floating-ui/utils": "^0.2.9" } }, "sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw=="],
|
138 |
+
|
139 |
+
"@floating-ui/dom": ["@floating-ui/dom@1.7.1", "", { "dependencies": { "@floating-ui/core": "^1.7.1", "@floating-ui/utils": "^0.2.9" } }, "sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ=="],
|
140 |
+
|
141 |
+
"@floating-ui/utils": ["@floating-ui/utils@0.2.9", "", {}, "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="],
|
142 |
+
|
143 |
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
|
144 |
|
145 |
"@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="],
|
|
|
148 |
|
149 |
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
|
150 |
|
151 |
+
"@iconify/json": ["@iconify/json@2.2.344", "", { "dependencies": { "@iconify/types": "*", "pathe": "^1.1.2" } }, "sha512-wY+TYoq4WSAaNunAbecUS+5S4VobTW/3cWpTLEWezRW9Wv4V3MqguNirNN3c/jSq/dfIMJNvr9Gz+N9iNFMInA=="],
|
152 |
+
|
153 |
+
"@iconify/tailwind4": ["@iconify/tailwind4@1.0.6", "", { "dependencies": { "@iconify/types": "^2.0.0", "@iconify/utils": "^2.2.1" }, "peerDependencies": { "tailwindcss": ">= 4" } }, "sha512-43ZXe+bC7CuE2LCgROdqbQeFYJi/J7L/k1UpSy8KDQlWVsWxPzLSWbWhlJx4uRYLOh1NRyw02YlDOgzBOFNd+A=="],
|
154 |
+
|
155 |
+
"@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
|
156 |
+
|
157 |
+
"@iconify/utils": ["@iconify/utils@2.3.0", "", { "dependencies": { "@antfu/install-pkg": "^1.0.0", "@antfu/utils": "^8.1.0", "@iconify/types": "^2.0.0", "debug": "^4.4.0", "globals": "^15.14.0", "kolorist": "^1.8.0", "local-pkg": "^1.0.0", "mlly": "^1.7.4" } }, "sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA=="],
|
158 |
+
|
159 |
+
"@internationalized/date": ["@internationalized/date@3.8.1", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-PgVE6B6eIZtzf9Gu5HvJxRK3ufUFz9DhspELuhW/N0GuMGMTLvPQNRkHP2hTuP9lblOk+f+1xi96sPiPXANXAA=="],
|
160 |
+
|
161 |
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
|
162 |
|
163 |
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="],
|
|
|
170 |
|
171 |
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
|
172 |
|
173 |
+
"@layerstack/svelte-actions": ["@layerstack/svelte-actions@1.0.1-next.2", "", { "dependencies": { "@floating-ui/dom": "^1.7.0", "@layerstack/utils": "1.1.0-next.2", "d3-array": "^3.2.4", "d3-scale": "^4.0.2", "date-fns": "^4.1.0", "lodash-es": "^4.17.21" } }, "sha512-s4iGrFIt+obdHP+Mzre+drCiZttqs5UYSw6CfQs1D2IeqTFkkbvRvYosh47flMi6ElN4psCrCcwn8KntClhMow=="],
|
174 |
+
|
175 |
+
"@layerstack/svelte-state": ["@layerstack/svelte-state@0.1.0-next.7", "", { "dependencies": { "@layerstack/utils": "2.0.0-next.3" } }, "sha512-9JQ5EyN/ghcSBKXjv1ueiM0TlsJv4eEWgW8mvd4dqxMLUzs+/fPJlXlxqBqvH/T64yauxvFUfytCGPnx1lRJsA=="],
|
176 |
+
|
177 |
+
"@layerstack/tailwind": ["@layerstack/tailwind@2.0.0-next.4", "", { "dependencies": { "@layerstack/utils": "^1.1.0-next.2", "clsx": "^2.1.1", "culori": "^4.0.1", "d3-array": "^3.2.4", "date-fns": "^4.1.0", "lodash-es": "^4.17.21", "tailwind-merge": "^3.2.0", "tailwindcss": "^4.1.5" } }, "sha512-46QUWvMaYGlGmi17vfT2o6mV7/0ZaWi0qJ4vjPKrayBsxn1mVjqX0cbW7EXC6JnmMVYt5CZu0768rWCM4nGCzA=="],
|
178 |
+
|
179 |
+
"@layerstack/utils": ["@layerstack/utils@2.0.0-next.3", "", { "dependencies": { "d3-array": "^3.2.4", "date-fns": "^4.1.0", "lodash-es": "^4.17.21" } }, "sha512-sTOF9cdh5lb9bqWW10kWwo4toSTjGOH4RNTr++59obVvdGp/hXo6LAN2WsdzkK9QFqBgEpuJH0rZ+D77lMSdeQ=="],
|
180 |
+
|
181 |
+
"@lucide/svelte": ["@lucide/svelte@0.511.0", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-aLCSPMUJmHlCuLXzXENXa4Z1NV2mN1iAZAFKk4bEbey+/MdsNlu+/DqwVkgW3Yvj6p8y8Vn5xZ2v9CLmPlA6Vw=="],
|
182 |
+
|
183 |
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
184 |
|
185 |
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
|
|
|
188 |
|
189 |
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
|
190 |
|
191 |
+
"@rollup/pluginutils": ["@rollup/pluginutils@5.1.4", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ=="],
|
192 |
+
|
193 |
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.41.1", "", { "os": "android", "cpu": "arm" }, "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw=="],
|
194 |
|
195 |
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.41.1", "", { "os": "android", "cpu": "arm64" }, "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA=="],
|
|
|
242 |
|
243 |
"@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@4.0.1", "", { "dependencies": { "debug": "^4.3.7" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^5.0.0", "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw=="],
|
244 |
|
245 |
+
"@swc/helpers": ["@swc/helpers@0.5.17", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A=="],
|
246 |
+
|
247 |
"@tailwindcss/node": ["@tailwindcss/node@4.1.8", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.8" } }, "sha512-OWwBsbC9BFAJelmnNcrKuf+bka2ZxCE2A4Ft53Tkg4uoiE67r/PMEYwCsourC26E+kmxfwE0hVzMdxqeW+xu7Q=="],
|
248 |
|
249 |
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.8", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.8", "@tailwindcss/oxide-darwin-arm64": "4.1.8", "@tailwindcss/oxide-darwin-x64": "4.1.8", "@tailwindcss/oxide-freebsd-x64": "4.1.8", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.8", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.8", "@tailwindcss/oxide-linux-arm64-musl": "4.1.8", "@tailwindcss/oxide-linux-x64-gnu": "4.1.8", "@tailwindcss/oxide-linux-x64-musl": "4.1.8", "@tailwindcss/oxide-wasm32-wasi": "4.1.8", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.8", "@tailwindcss/oxide-win32-x64-msvc": "4.1.8" } }, "sha512-d7qvv9PsM5N3VNKhwVUhpK6r4h9wtLkJ6lz9ZY9aeZgrUWk1Z8VPyqyDT9MZlem7GTGseRQHkeB1j3tC7W1P+A=="],
|
|
|
274 |
|
275 |
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.8", "", { "dependencies": { "@tailwindcss/node": "4.1.8", "@tailwindcss/oxide": "4.1.8", "tailwindcss": "4.1.8" }, "peerDependencies": { "vite": "^5.2.0 || ^6" } }, "sha512-CQ+I8yxNV5/6uGaJjiuymgw0kEQiNKRinYbZXPdx1fk5WgiyReG0VaUx/Xq6aVNSUNJFzxm6o8FNKS5aMaim5A=="],
|
276 |
|
277 |
+
"@tanstack/table-core": ["@tanstack/table-core@8.21.3", "", {}, "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg=="],
|
278 |
+
|
279 |
"@threejs-kit/instanced-sprite-mesh": ["@threejs-kit/instanced-sprite-mesh@2.5.1", "", { "dependencies": { "diet-sprite": "^0.0.1", "earcut": "^2.2.4", "maath": "^0.10.7", "three-instanced-uniforms-mesh": "^0.52.4", "troika-three-utils": "^0.52.4" }, "peerDependencies": { "three": ">=0.170.0" } }, "sha512-pmt1ALRhbHhCJQTj2FuthH6PeLIeaM4hOuS2JO3kWSwlnvx/9xuUkjFR3JOi/myMqsH7pSsLIROSaBxDfttjeA=="],
|
280 |
|
281 |
"@threlte/core": ["@threlte/core@8.0.4", "", { "dependencies": { "mitt": "^3.0.1" }, "peerDependencies": { "svelte": ">=5", "three": ">=0.155" } }, "sha512-kg0nTq00RqgTExIEKkyO/yCupCt73lybuFZObKHCRaY7dPcCvRD4tHkyKIFeYLYV4R6u1nqIuzfgDzYJqHbLlQ=="],
|
|
|
286 |
|
287 |
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
|
288 |
|
289 |
+
"@types/draco3d": ["@types/draco3d@1.4.10", "", {}, "sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw=="],
|
290 |
+
|
291 |
"@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="],
|
292 |
|
293 |
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
294 |
|
295 |
+
"@types/offscreencanvas": ["@types/offscreencanvas@2019.7.3", "", {}, "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A=="],
|
296 |
+
|
297 |
"@types/stats.js": ["@types/stats.js@0.17.4", "", {}, "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA=="],
|
298 |
|
299 |
"@types/three": ["@types/three@0.176.0", "", { "dependencies": { "@dimforge/rapier3d-compat": "^0.12.0", "@tweenjs/tween.js": "~23.1.3", "@types/stats.js": "*", "@types/webxr": "*", "@webgpu/types": "*", "fflate": "~0.8.2", "meshoptimizer": "~0.18.1" } }, "sha512-FwfPXxCqOtP7EdYMagCFePNKoG1AGBDUEVKtluv2BTVRpSt7b+X27xNsirPCTCqY1pGYsPUzaM3jgWP7dXSxlw=="],
|
|
|
340 |
|
341 |
"bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="],
|
342 |
|
343 |
+
"bits-ui": ["bits-ui@2.4.1", "", { "dependencies": { "@floating-ui/core": "^1.7.0", "@floating-ui/dom": "^1.7.0", "css.escape": "^1.5.1", "esm-env": "^1.1.2", "runed": "^0.28.0", "svelte-toolbelt": "^0.9.1", "tabbable": "^6.2.0" }, "peerDependencies": { "@internationalized/date": "^3.8.1", "svelte": "^5.33.0" } }, "sha512-Z0qZkPgtxP5dkEOyZqCK2PQ1oFXsD0AlfFMtsrMLJqaYtjUBoQGosHVKEH03M9q4G8kxVnG74Zb0F9pS0X4+6w=="],
|
344 |
+
|
345 |
"brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
|
346 |
|
347 |
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
|
|
362 |
|
363 |
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
364 |
|
365 |
+
"commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],
|
366 |
+
|
367 |
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
368 |
|
369 |
+
"confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="],
|
370 |
+
|
371 |
"cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="],
|
372 |
|
373 |
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
374 |
|
375 |
+
"css.escape": ["css.escape@1.5.1", "", {}, "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg=="],
|
376 |
+
|
377 |
"cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
|
378 |
|
379 |
+
"culori": ["culori@4.0.1", "", {}, "sha512-LSnjA6HuIUOlkfKVbzi2OlToZE8OjFi667JWN9qNymXVXzGDmvuP60SSgC+e92sd7B7158f7Fy3Mb6rXS5EDPw=="],
|
380 |
+
|
381 |
+
"d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="],
|
382 |
+
|
383 |
+
"d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="],
|
384 |
+
|
385 |
+
"d3-delaunay": ["d3-delaunay@6.0.4", "", { "dependencies": { "delaunator": "5" } }, "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A=="],
|
386 |
+
|
387 |
+
"d3-dispatch": ["d3-dispatch@3.0.1", "", {}, "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg=="],
|
388 |
+
|
389 |
+
"d3-dsv": ["d3-dsv@3.0.1", "", { "dependencies": { "commander": "7", "iconv-lite": "0.6", "rw": "1" }, "bin": { "csv2json": "bin/dsv2json.js", "csv2tsv": "bin/dsv2dsv.js", "dsv2dsv": "bin/dsv2dsv.js", "dsv2json": "bin/dsv2json.js", "json2csv": "bin/json2dsv.js", "json2dsv": "bin/json2dsv.js", "json2tsv": "bin/json2dsv.js", "tsv2csv": "bin/dsv2dsv.js", "tsv2json": "bin/dsv2json.js" } }, "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q=="],
|
390 |
+
|
391 |
+
"d3-force": ["d3-force@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-quadtree": "1 - 3", "d3-timer": "1 - 3" } }, "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg=="],
|
392 |
+
|
393 |
+
"d3-format": ["d3-format@3.1.0", "", {}, "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA=="],
|
394 |
+
|
395 |
+
"d3-geo": ["d3-geo@3.1.1", "", { "dependencies": { "d3-array": "2.5.0 - 3" } }, "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q=="],
|
396 |
+
|
397 |
+
"d3-geo-voronoi": ["d3-geo-voronoi@2.1.0", "", { "dependencies": { "d3-array": "3", "d3-delaunay": "6", "d3-geo": "3", "d3-tricontour": "1" } }, "sha512-kqE4yYuOjPbKdBXG0xztCacPwkVSK2REF1opSNrnqqtXJmNcM++UbwQ8SxvwP6IQTj9RvIjjK4qeiVsEfj0Z2Q=="],
|
398 |
+
|
399 |
+
"d3-hierarchy": ["d3-hierarchy@3.1.2", "", {}, "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA=="],
|
400 |
+
|
401 |
+
"d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="],
|
402 |
+
|
403 |
+
"d3-interpolate-path": ["d3-interpolate-path@2.3.0", "", {}, "sha512-tZYtGXxBmbgHsIc9Wms6LS5u4w6KbP8C09a4/ZYc4KLMYYqub57rRBUgpUr2CIarIrJEpdAWWxWQvofgaMpbKQ=="],
|
404 |
+
|
405 |
+
"d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="],
|
406 |
+
|
407 |
+
"d3-quadtree": ["d3-quadtree@3.0.1", "", {}, "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw=="],
|
408 |
+
|
409 |
+
"d3-random": ["d3-random@3.0.1", "", {}, "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ=="],
|
410 |
+
|
411 |
+
"d3-sankey": ["d3-sankey@0.12.3", "", { "dependencies": { "d3-array": "1 - 2", "d3-shape": "^1.2.0" } }, "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ=="],
|
412 |
+
|
413 |
+
"d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="],
|
414 |
+
|
415 |
+
"d3-scale-chromatic": ["d3-scale-chromatic@3.1.0", "", { "dependencies": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" } }, "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ=="],
|
416 |
+
|
417 |
+
"d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="],
|
418 |
+
|
419 |
+
"d3-tile": ["d3-tile@1.0.0", "", {}, "sha512-79fnTKpPMPDS5xQ0xuS9ir0165NEwwkFpe/DSOmc2Gl9ldYzKKRDWogmTTE8wAJ8NA7PMapNfEcyKhI9Lxdu5Q=="],
|
420 |
+
|
421 |
+
"d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="],
|
422 |
+
|
423 |
+
"d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="],
|
424 |
+
|
425 |
+
"d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="],
|
426 |
+
|
427 |
+
"d3-tricontour": ["d3-tricontour@1.0.2", "", { "dependencies": { "d3-delaunay": "6", "d3-scale": "4" } }, "sha512-HIRxHzHagPtUPNabjOlfcyismJYIsc+Xlq4mlsts4e8eAcwyq9Tgk/sYdyhlBpQ0MHwVquc/8j+e29YjXnmxeA=="],
|
428 |
+
|
429 |
+
"date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="],
|
430 |
+
|
431 |
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
432 |
|
433 |
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
|
434 |
|
435 |
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
436 |
|
437 |
+
"delaunator": ["delaunator@5.0.1", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw=="],
|
438 |
+
|
439 |
"detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
|
440 |
|
441 |
"devalue": ["devalue@5.1.1", "", {}, "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw=="],
|
442 |
|
443 |
"diet-sprite": ["diet-sprite@0.0.1", "", {}, "sha512-zSHI2WDAn1wJqJYxcmjWfJv3Iw8oL9reQIbEyx2x2/EZ4/qmUTIo8/5qOCurnAcq61EwtJJaZ0XTy2NRYqpB5A=="],
|
444 |
|
445 |
+
"draco3d": ["draco3d@1.5.7", "", {}, "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ=="],
|
446 |
+
|
447 |
"earcut": ["earcut@2.2.4", "", {}, "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ=="],
|
448 |
|
449 |
+
"embla-carousel": ["embla-carousel@8.6.0", "", {}, "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA=="],
|
450 |
+
|
451 |
+
"embla-carousel-reactive-utils": ["embla-carousel-reactive-utils@8.6.0", "", { "peerDependencies": { "embla-carousel": "8.6.0" } }, "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A=="],
|
452 |
+
|
453 |
+
"embla-carousel-svelte": ["embla-carousel-svelte@8.6.0", "", { "dependencies": { "embla-carousel": "8.6.0", "embla-carousel-reactive-utils": "8.6.0" }, "peerDependencies": { "svelte": "^3.49.0 || ^4.0.0 || ^5.0.0" } }, "sha512-ZDsKk8Sdv+AUTygMYcwZjfRd1DTh+JSUzxkOo8b9iKAkYjg+39mzbY/lwHsE3jXSpKxdKWS69hPSNuzlOGtR2Q=="],
|
454 |
+
|
455 |
"enhanced-resolve": ["enhanced-resolve@5.18.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg=="],
|
456 |
|
457 |
"esbuild": ["esbuild@0.25.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.5", "@esbuild/android-arm": "0.25.5", "@esbuild/android-arm64": "0.25.5", "@esbuild/android-x64": "0.25.5", "@esbuild/darwin-arm64": "0.25.5", "@esbuild/darwin-x64": "0.25.5", "@esbuild/freebsd-arm64": "0.25.5", "@esbuild/freebsd-x64": "0.25.5", "@esbuild/linux-arm": "0.25.5", "@esbuild/linux-arm64": "0.25.5", "@esbuild/linux-ia32": "0.25.5", "@esbuild/linux-loong64": "0.25.5", "@esbuild/linux-mips64el": "0.25.5", "@esbuild/linux-ppc64": "0.25.5", "@esbuild/linux-riscv64": "0.25.5", "@esbuild/linux-s390x": "0.25.5", "@esbuild/linux-x64": "0.25.5", "@esbuild/netbsd-arm64": "0.25.5", "@esbuild/netbsd-x64": "0.25.5", "@esbuild/openbsd-arm64": "0.25.5", "@esbuild/openbsd-x64": "0.25.5", "@esbuild/sunos-x64": "0.25.5", "@esbuild/win32-arm64": "0.25.5", "@esbuild/win32-ia32": "0.25.5", "@esbuild/win32-x64": "0.25.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ=="],
|
|
|
480 |
|
481 |
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
|
482 |
|
483 |
+
"estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
484 |
+
|
485 |
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
486 |
|
487 |
+
"exsolve": ["exsolve@1.0.5", "", {}, "sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg=="],
|
488 |
+
|
489 |
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
490 |
|
491 |
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
|
|
|
522 |
|
523 |
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
524 |
|
525 |
+
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
|
526 |
+
|
527 |
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
528 |
|
529 |
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
|
530 |
|
531 |
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
|
532 |
|
533 |
+
"inline-style-parser": ["inline-style-parser@0.2.4", "", {}, "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q=="],
|
534 |
+
|
535 |
+
"internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="],
|
536 |
+
|
537 |
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
538 |
|
539 |
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
|
|
560 |
|
561 |
"known-css-properties": ["known-css-properties@0.36.0", "", {}, "sha512-A+9jP+IUmuQsNdsLdcg6Yt7voiMF/D4K83ew0OpJtpu+l34ef7LaohWV0Rc6KNvzw6ZDizkqfyB5JznZnzuKQA=="],
|
562 |
|
563 |
+
"kolorist": ["kolorist@1.8.0", "", {}, "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ=="],
|
564 |
+
|
565 |
+
"layerchart": ["layerchart@2.0.0-next.10", "", { "dependencies": { "@dagrejs/dagre": "^1.1.4", "@layerstack/svelte-actions": "1.0.1-next.2", "@layerstack/svelte-state": "0.1.0-next.7", "@layerstack/tailwind": "2.0.0-next.4", "@layerstack/utils": "2.0.0-next.3", "d3-array": "^3.2.4", "d3-color": "^3.1.0", "d3-delaunay": "^6.0.4", "d3-dsv": "^3.0.1", "d3-force": "^3.0.0", "d3-geo": "^3.1.1", "d3-geo-voronoi": "^2.1.0", "d3-hierarchy": "^3.1.2", "d3-interpolate": "^3.0.1", "d3-interpolate-path": "^2.3.0", "d3-path": "^3.1.0", "d3-quadtree": "^3.0.1", "d3-random": "^3.0.1", "d3-sankey": "^0.12.3", "d3-scale": "^4.0.2", "d3-scale-chromatic": "^3.1.0", "d3-shape": "^3.2.0", "d3-tile": "^1.0.0", "d3-time": "^3.1.0", "date-fns": "^4.1.0", "lodash-es": "^4.17.21", "runed": "^0.28.0" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-Jw0EMWRDjecl0esHlHeyqObhPJCOXD9eQLWOu3I37+XCHXDHp4QL/5avcmBIwflmLE1e/fVGoMaeTZjyezZGhA=="],
|
566 |
+
|
567 |
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
568 |
|
569 |
"lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="],
|
|
|
590 |
|
591 |
"lilconfig": ["lilconfig@2.1.0", "", {}, "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="],
|
592 |
|
593 |
+
"local-pkg": ["local-pkg@1.1.1", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.0.1", "quansync": "^0.2.8" } }, "sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg=="],
|
594 |
+
|
595 |
"locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
|
596 |
|
597 |
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
598 |
|
599 |
+
"lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="],
|
600 |
+
|
601 |
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
|
602 |
|
603 |
"maath": ["maath@0.10.8", "", { "peerDependencies": { "@types/three": ">=0.134.0", "three": ">=0.134.0" } }, "sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g=="],
|
|
|
620 |
|
621 |
"mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="],
|
622 |
|
623 |
+
"mlly": ["mlly@1.7.4", "", { "dependencies": { "acorn": "^8.14.0", "pathe": "^2.0.1", "pkg-types": "^1.3.0", "ufo": "^1.5.4" } }, "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw=="],
|
624 |
+
|
625 |
+
"mode-watcher": ["mode-watcher@1.0.7", "", { "dependencies": { "runed": "^0.25.0", "svelte-toolbelt": "^0.7.1" }, "peerDependencies": { "svelte": "^5.27.0" } }, "sha512-ZGA7ZGdOvBJeTQkzdBOnXSgTkO6U6iIFWJoyGCTt6oHNg9XP9NBvS26De+V4W2aqI+B0yYXUskFG2VnEo3zyMQ=="],
|
626 |
+
|
627 |
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
|
628 |
|
629 |
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
|
|
|
640 |
|
641 |
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
642 |
|
643 |
+
"package-manager-detector": ["package-manager-detector@1.3.0", "", {}, "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ=="],
|
644 |
+
|
645 |
+
"paneforge": ["paneforge@1.0.0-next.5", "", { "dependencies": { "runed": "^0.23.4", "svelte-toolbelt": "^0.7.1" }, "peerDependencies": { "svelte": "^5.20.0" } }, "sha512-1ArDM+GMEO+o6pixEAFobhTkWkyxUDdHyw2bKruvQIXBStJmdRP7HoV4jNBZ/2i9UHDzmczxJzA3D2tKa91phw=="],
|
646 |
+
|
647 |
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
648 |
|
649 |
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
650 |
|
651 |
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
652 |
|
653 |
+
"pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="],
|
654 |
+
|
655 |
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
656 |
|
657 |
"picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
|
658 |
|
659 |
+
"pkg-types": ["pkg-types@2.1.0", "", { "dependencies": { "confbox": "^0.2.1", "exsolve": "^1.0.1", "pathe": "^2.0.3" } }, "sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A=="],
|
660 |
+
|
661 |
"postcss": ["postcss@8.5.4", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w=="],
|
662 |
|
663 |
"postcss-load-config": ["postcss-load-config@3.1.4", "", { "dependencies": { "lilconfig": "^2.0.5", "yaml": "^1.10.2" }, "peerDependencies": { "postcss": ">=8.0.9", "ts-node": ">=9.0.0" }, "optionalPeers": ["postcss", "ts-node"] }, "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg=="],
|
|
|
668 |
|
669 |
"postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
|
670 |
|
671 |
+
"postprocessing": ["postprocessing@6.37.3", "", { "peerDependencies": { "three": ">= 0.157.0 < 0.177.0" } }, "sha512-2FzfZg41Ml/ddvSalLdCz7H53Wmr/ZvK238fauTG1CI97+65BDgo1dNH6AKhAWc5q9ngpyG5EYiXVbNCl2t1iA=="],
|
672 |
+
|
673 |
+
"potpack": ["potpack@1.0.2", "", {}, "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ=="],
|
674 |
+
|
675 |
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
676 |
|
677 |
"prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="],
|
|
|
682 |
|
683 |
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
684 |
|
685 |
+
"quansync": ["quansync@0.2.10", "", {}, "sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A=="],
|
686 |
+
|
687 |
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
688 |
|
689 |
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
|
|
694 |
|
695 |
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
696 |
|
697 |
+
"robust-predicates": ["robust-predicates@3.0.2", "", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="],
|
698 |
+
|
699 |
"rollup": ["rollup@4.41.1", "", { "dependencies": { "@types/estree": "1.0.7" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.41.1", "@rollup/rollup-android-arm64": "4.41.1", "@rollup/rollup-darwin-arm64": "4.41.1", "@rollup/rollup-darwin-x64": "4.41.1", "@rollup/rollup-freebsd-arm64": "4.41.1", "@rollup/rollup-freebsd-x64": "4.41.1", "@rollup/rollup-linux-arm-gnueabihf": "4.41.1", "@rollup/rollup-linux-arm-musleabihf": "4.41.1", "@rollup/rollup-linux-arm64-gnu": "4.41.1", "@rollup/rollup-linux-arm64-musl": "4.41.1", "@rollup/rollup-linux-loongarch64-gnu": "4.41.1", "@rollup/rollup-linux-powerpc64le-gnu": "4.41.1", "@rollup/rollup-linux-riscv64-gnu": "4.41.1", "@rollup/rollup-linux-riscv64-musl": "4.41.1", "@rollup/rollup-linux-s390x-gnu": "4.41.1", "@rollup/rollup-linux-x64-gnu": "4.41.1", "@rollup/rollup-linux-x64-musl": "4.41.1", "@rollup/rollup-win32-arm64-msvc": "4.41.1", "@rollup/rollup-win32-ia32-msvc": "4.41.1", "@rollup/rollup-win32-x64-msvc": "4.41.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw=="],
|
700 |
|
701 |
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
702 |
|
703 |
+
"runed": ["runed@0.28.0", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-k2xx7RuO9hWcdd9f+8JoBeqWtYrm5CALfgpkg2YDB80ds/QE4w0qqu34A7fqiAwiBBSBQOid7TLxwxVC27ymWQ=="],
|
704 |
+
|
705 |
+
"rw": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="],
|
706 |
+
|
707 |
"sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
|
708 |
|
709 |
+
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
710 |
+
|
711 |
"semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
712 |
|
713 |
"set-cookie-parser": ["set-cookie-parser@2.7.1", "", {}, "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="],
|
|
|
722 |
|
723 |
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
724 |
|
725 |
+
"style-to-object": ["style-to-object@1.0.8", "", { "dependencies": { "inline-style-parser": "0.2.4" } }, "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g=="],
|
726 |
+
|
727 |
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
728 |
|
729 |
"svelte": ["svelte@5.33.10", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^1.4.6", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-/yArPQIBoQS2p86LKnvJywOXkVHeEXnFgrDPSxkEfIAEkykopYuy2bF6UUqHG4IbZlJD6OurLxJT8Kn7kTk9WA=="],
|
|
|
732 |
|
733 |
"svelte-eslint-parser": ["svelte-eslint-parser@1.2.0", "", { "dependencies": { "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.0.0", "espree": "^10.0.0", "postcss": "^8.4.49", "postcss-scss": "^4.0.9", "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" }, "optionalPeers": ["svelte"] }, "sha512-mbPtajIeuiyU80BEyGvwAktBeTX7KCr5/0l+uRGLq1dafwRNrjfM5kHGJScEBlPG3ipu6dJqfW/k0/fujvIEVw=="],
|
734 |
|
735 |
+
"svelte-sonner": ["svelte-sonner@1.0.4", "", { "dependencies": { "runed": "^0.26.0" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-ctm9jeV0Rf3im2J6RU1emccrJFjRSdNSPsLlxaF62TLZw9bB1D40U/U7+wqEgohJY/X7FBdghdj0BFQF/IqKPQ=="],
|
736 |
+
|
737 |
+
"svelte-toolbelt": ["svelte-toolbelt@0.9.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.28.0", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.30.2" } }, "sha512-wBX6MtYw/kpht80j5zLpxJyR9soLizXPIAIWEVd9llAi17SR44ZdG291bldjB7r/K5duC0opDFcuhk2cA1hb8g=="],
|
738 |
+
|
739 |
+
"tabbable": ["tabbable@6.2.0", "", {}, "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="],
|
740 |
+
|
741 |
"tailwind-merge": ["tailwind-merge@3.3.0", "", {}, "sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ=="],
|
742 |
|
743 |
+
"tailwind-variants": ["tailwind-variants@1.0.0", "", { "dependencies": { "tailwind-merge": "3.0.2" }, "peerDependencies": { "tailwindcss": "*" } }, "sha512-2WSbv4ulEEyuBKomOunut65D8UZwxrHoRfYnxGcQNnHqlSCp2+B7Yz2W+yrNDrxRodOXtGD/1oCcKGNBnUqMqA=="],
|
744 |
+
|
745 |
"tailwindcss": ["tailwindcss@4.1.8", "", {}, "sha512-kjeW8gjdxasbmFKpVGrGd5T4i40mV5J2Rasw48QARfYeQ8YS9x02ON9SFWax3Qf616rt4Cp3nVNIj6Hd1mP3og=="],
|
746 |
|
747 |
"tapable": ["tapable@2.2.2", "", {}, "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg=="],
|
|
|
756 |
|
757 |
"three-perf": ["three-perf@github:jerzakm/three-perf#322d7d3", { "dependencies": { "troika-three-text": "^0.52.0", "tweakpane": "^3.1.10" }, "peerDependencies": { "three": ">=0.170" } }, "jerzakm-three-perf-322d7d3"],
|
758 |
|
759 |
+
"three-stdlib": ["three-stdlib@2.36.0", "", { "dependencies": { "@types/draco3d": "^1.4.0", "@types/offscreencanvas": "^2019.6.4", "@types/webxr": "^0.5.2", "draco3d": "^1.4.1", "fflate": "^0.6.9", "potpack": "^1.0.1" }, "peerDependencies": { "three": ">=0.128.0" } }, "sha512-kv0Byb++AXztEGsULgMAs8U2jgUdz6HPpAB/wDJnLiLlaWQX2APHhiTJIN7rqW+Of0eRgcp7jn05U1BsCP3xBA=="],
|
760 |
+
|
761 |
"three-viewport-gizmo": ["three-viewport-gizmo@2.2.0", "", { "peerDependencies": { "three": ">=0.162.0 <1.0.0" } }, "sha512-Jo9Liur1rUmdKk75FZumLU/+hbF+RtJHi1qsKZpntjKlCYScK6tjbYoqvJ9M+IJphrlQJF5oReFW7Sambh0N4Q=="],
|
762 |
|
763 |
+
"threlte-postprocessing": ["threlte-postprocessing@0.0.9", "", { "dependencies": { "@threlte/core": "^8.0.1", "@threlte/extras": "^9.0.1", "postprocessing": "^6.36.7", "three": "^0.173.0", "three-stdlib": "^2.35.13", "vite-plugin-glsl": "^1.3.1", "vite-plugin-string": "^1.2.3" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-LzxJXh/z+DeT8xfmIKGCRTw5sOAGaoLXjuxMu9qw84DCmrAueGjXqCHOgPSVF7nbye0ecQYomlUIctcw32by1w=="],
|
764 |
+
|
765 |
+
"tinyexec": ["tinyexec@1.0.1", "", {}, "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw=="],
|
766 |
+
|
767 |
"tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
|
768 |
|
769 |
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
|
|
778 |
|
779 |
"ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="],
|
780 |
|
781 |
+
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
782 |
+
|
783 |
+
"tw-animate-css": ["tw-animate-css@1.3.3", "", {}, "sha512-tXE2TRWrskc4TU3RDd7T8n8Np/wCfoeH9gz22c7PzYqNPQ9FBGFbWWzwL0JyHcFp+jHozmF76tbHfPAx22ua2Q=="],
|
784 |
+
|
785 |
"tweakpane": ["tweakpane@3.1.10", "", {}, "sha512-rqwnl/pUa7+inhI2E9ayGTqqP0EPOOn/wVvSWjZsRbZUItzNShny7pzwL3hVlaN4m9t/aZhsP0aFQ9U5VVR2VQ=="],
|
786 |
|
787 |
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
|
|
790 |
|
791 |
"typescript-eslint": ["typescript-eslint@8.33.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.33.0", "@typescript-eslint/parser": "8.33.0", "@typescript-eslint/utils": "8.33.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-5YmNhF24ylCsvdNW2oJwMzTbaeO4bg90KeGtMjUw0AGtHksgEPLRTUil+coHwCfiu4QjVJFnjp94DmU6zV7DhQ=="],
|
792 |
|
793 |
+
"ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="],
|
794 |
+
|
795 |
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
796 |
|
797 |
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
798 |
|
799 |
+
"vaul-svelte": ["vaul-svelte@1.0.0-next.7", "", { "dependencies": { "runed": "^0.23.2", "svelte-toolbelt": "^0.7.1" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-7zN7Bi3dFQixvvbUJY9uGDe7Ws/dGZeBQR2pXdXmzQiakjrxBvWo0QrmsX3HK+VH+SZOltz378cmgmCS9f9rSg=="],
|
800 |
+
|
801 |
"vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="],
|
802 |
|
803 |
+
"vite-plugin-glsl": ["vite-plugin-glsl@1.4.2", "", { "dependencies": { "@rollup/pluginutils": "^5.1.4" }, "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" } }, "sha512-GQHx5tUPFna1zOrX9tbpSHLSVEv/wXZGPEey3Pk8ktHR/OJCEg82Vx2oE9d8mOeMhLfelrtpXg0TBD4riRs39Q=="],
|
804 |
+
|
805 |
+
"vite-plugin-string": ["vite-plugin-string@1.2.3", "", { "dependencies": { "@rollup/pluginutils": ">=4.1.0" }, "peerDependencies": { "vite": ">=2" } }, "sha512-zw2jL0c4B6CAvxO8PshX04494jTcqJjNH2kW1AugBH+fImRY0evdNtVgmeS1i1VFdua/OFhL7fMqIPh0uF21/Q=="],
|
806 |
+
|
807 |
"vitefu": ["vitefu@1.0.6", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" }, "optionalPeers": ["vite"] }, "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA=="],
|
808 |
|
809 |
"webgl-sdf-generator": ["webgl-sdf-generator@1.1.1", "", {}, "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA=="],
|
|
|
828 |
|
829 |
"@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
|
830 |
|
831 |
+
"@iconify/utils/globals": ["globals@15.15.0", "", {}, "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg=="],
|
832 |
+
|
833 |
+
"@layerstack/svelte-actions/@layerstack/utils": ["@layerstack/utils@1.1.0-next.2", "", { "dependencies": { "d3-array": "^3.2.4", "date-fns": "^4.1.0", "lodash-es": "^4.17.21" } }, "sha512-9Y4TOAE0iIVvnrf1mkft79rF/vTO84bZuc4hjhJB7XguFjy2f00pEfrDjF+IqleWImMFfRu31K2IMM6wRWEBXQ=="],
|
834 |
+
|
835 |
+
"@layerstack/tailwind/@layerstack/utils": ["@layerstack/utils@1.1.0-next.2", "", { "dependencies": { "d3-array": "^3.2.4", "date-fns": "^4.1.0", "lodash-es": "^4.17.21" } }, "sha512-9Y4TOAE0iIVvnrf1mkft79rF/vTO84bZuc4hjhJB7XguFjy2f00pEfrDjF+IqleWImMFfRu31K2IMM6wRWEBXQ=="],
|
836 |
+
|
837 |
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="],
|
838 |
|
839 |
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="],
|
|
|
850 |
|
851 |
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
852 |
|
853 |
+
"d3-sankey/d3-array": ["d3-array@2.12.1", "", { "dependencies": { "internmap": "^1.0.0" } }, "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ=="],
|
854 |
+
|
855 |
+
"d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="],
|
856 |
+
|
857 |
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
858 |
|
859 |
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
860 |
|
861 |
+
"mlly/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
862 |
+
|
863 |
+
"mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
|
864 |
+
|
865 |
+
"mode-watcher/runed": ["runed@0.25.0", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-7+ma4AG9FT2sWQEA0Egf6mb7PBT2vHyuHail1ie8ropfSjvZGtEAx8YTmUjv/APCsdRRxEVvArNjALk9zFSOrg=="],
|
866 |
+
|
867 |
+
"mode-watcher/svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="],
|
868 |
+
|
869 |
+
"paneforge/runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="],
|
870 |
+
|
871 |
+
"paneforge/svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="],
|
872 |
+
|
873 |
+
"pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
874 |
+
|
875 |
+
"svelte-sonner/runed": ["runed@0.26.0", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-qWFv0cvLVRd8pdl/AslqzvtQyEn5KaIugEernwg9G98uJVSZcs/ygvPBvF80LA46V8pwRvSKnaVLDI3+i2wubw=="],
|
876 |
+
|
877 |
+
"tailwind-variants/tailwind-merge": ["tailwind-merge@3.0.2", "", {}, "sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw=="],
|
878 |
+
|
879 |
+
"three-stdlib/fflate": ["fflate@0.6.10", "", {}, "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg=="],
|
880 |
+
|
881 |
+
"threlte-postprocessing/three": ["three@0.173.0", "", {}, "sha512-AUwVmViIEUgBwxJJ7stnF0NkPpZxx1aZ6WiAbQ/Qq61h6I9UR4grXtZDmO8mnlaNORhHnIBlXJ1uBxILEKuVyw=="],
|
882 |
+
|
883 |
+
"vaul-svelte/runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="],
|
884 |
+
|
885 |
+
"vaul-svelte/svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="],
|
886 |
+
|
887 |
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
|
888 |
+
|
889 |
+
"d3-sankey/d3-array/internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="],
|
890 |
+
|
891 |
+
"d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="],
|
892 |
+
|
893 |
+
"mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
|
894 |
+
|
895 |
+
"mode-watcher/svelte-toolbelt/runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="],
|
896 |
}
|
897 |
}
|
components.json
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"$schema": "https://next.shadcn-svelte.com/schema.json",
|
3 |
+
"tailwind": {
|
4 |
+
"css": "src/app.css",
|
5 |
+
"baseColor": "stone"
|
6 |
+
},
|
7 |
+
"aliases": {
|
8 |
+
"components": "$lib/components",
|
9 |
+
"utils": "$lib/utils",
|
10 |
+
"ui": "$lib/components/ui",
|
11 |
+
"hooks": "$lib/hooks",
|
12 |
+
"lib": "$lib"
|
13 |
+
},
|
14 |
+
"typescript": true,
|
15 |
+
"registry": "https://next.shadcn-svelte.com/registry"
|
16 |
+
}
|
package.json
CHANGED
@@ -16,23 +16,37 @@
|
|
16 |
"devDependencies": {
|
17 |
"@eslint/compat": "^1.2.5",
|
18 |
"@eslint/js": "^9.18.0",
|
|
|
|
|
|
|
|
|
19 |
"@sveltejs/adapter-auto": "^6.0.0",
|
20 |
"@sveltejs/adapter-static": "^3.0.8",
|
21 |
"@sveltejs/kit": "^2.16.0",
|
22 |
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
23 |
"@tailwindcss/vite": "^4.0.0",
|
|
|
|
|
|
|
24 |
"eslint": "^9.18.0",
|
25 |
"eslint-config-prettier": "^10.0.1",
|
26 |
"eslint-plugin-svelte": "^3.0.0",
|
27 |
"globals": "^16.0.0",
|
|
|
|
|
|
|
28 |
"prettier": "^3.4.2",
|
29 |
"prettier-plugin-svelte": "^3.3.3",
|
30 |
"prettier-plugin-tailwindcss": "^0.6.11",
|
31 |
"svelte": "^5.0.0",
|
32 |
"svelte-check": "^4.0.0",
|
|
|
|
|
33 |
"tailwindcss": "^4.0.0",
|
|
|
34 |
"typescript": "^5.0.0",
|
35 |
"typescript-eslint": "^8.20.0",
|
|
|
36 |
"vite": "^6.2.6"
|
37 |
},
|
38 |
"dependencies": {
|
@@ -42,6 +56,7 @@
|
|
42 |
"clsx": "^2.1.1",
|
43 |
"tailwind-merge": "^3.3.0",
|
44 |
"three": "^0.177.0",
|
|
|
45 |
"zod": "^3.25.42"
|
46 |
}
|
47 |
}
|
|
|
16 |
"devDependencies": {
|
17 |
"@eslint/compat": "^1.2.5",
|
18 |
"@eslint/js": "^9.18.0",
|
19 |
+
"@iconify/json": "^2.2.344",
|
20 |
+
"@iconify/tailwind4": "^1.0.6",
|
21 |
+
"@internationalized/date": "^3.5.6",
|
22 |
+
"@lucide/svelte": "^0.511.0",
|
23 |
"@sveltejs/adapter-auto": "^6.0.0",
|
24 |
"@sveltejs/adapter-static": "^3.0.8",
|
25 |
"@sveltejs/kit": "^2.16.0",
|
26 |
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
27 |
"@tailwindcss/vite": "^4.0.0",
|
28 |
+
"@tanstack/table-core": "^8.20.5",
|
29 |
+
"bits-ui": "^2.4.1",
|
30 |
+
"embla-carousel-svelte": "^8.6.0",
|
31 |
"eslint": "^9.18.0",
|
32 |
"eslint-config-prettier": "^10.0.1",
|
33 |
"eslint-plugin-svelte": "^3.0.0",
|
34 |
"globals": "^16.0.0",
|
35 |
+
"layerchart": "2.0.0-next.10",
|
36 |
+
"mode-watcher": "^1.0.6",
|
37 |
+
"paneforge": "^1.0.0-next.5",
|
38 |
"prettier": "^3.4.2",
|
39 |
"prettier-plugin-svelte": "^3.3.3",
|
40 |
"prettier-plugin-tailwindcss": "^0.6.11",
|
41 |
"svelte": "^5.0.0",
|
42 |
"svelte-check": "^4.0.0",
|
43 |
+
"svelte-sonner": "^1.0.1",
|
44 |
+
"tailwind-variants": "^1.0.0",
|
45 |
"tailwindcss": "^4.0.0",
|
46 |
+
"tw-animate-css": "^1.3.3",
|
47 |
"typescript": "^5.0.0",
|
48 |
"typescript-eslint": "^8.20.0",
|
49 |
+
"vaul-svelte": "^1.0.0-next.7",
|
50 |
"vite": "^6.2.6"
|
51 |
},
|
52 |
"dependencies": {
|
|
|
56 |
"clsx": "^2.1.1",
|
57 |
"tailwind-merge": "^3.3.0",
|
58 |
"three": "^0.177.0",
|
59 |
+
"threlte-postprocessing": "^0.0.9",
|
60 |
"zod": "^3.25.42"
|
61 |
}
|
62 |
}
|
src-python/src/main.py
CHANGED
@@ -641,50 +641,52 @@ async def sync_slave_with_current_state(robot_id: str, websocket: WebSocket):
|
|
641 |
|
642 |
# ============= FRONTEND SERVING ROUTES (Must be last) =============
|
643 |
|
644 |
-
|
645 |
-
|
646 |
-
|
647 |
-
|
648 |
-
|
649 |
-
|
650 |
-
|
651 |
-
|
652 |
-
|
653 |
-
|
654 |
-
|
655 |
-
|
656 |
-
|
657 |
-
|
658 |
-
|
659 |
-
|
660 |
-
|
661 |
-
|
662 |
-
|
663 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
664 |
return {
|
665 |
"message": "Frontend not built. Run 'bun run build' to build the frontend."
|
666 |
}
|
667 |
|
668 |
-
|
669 |
-
|
670 |
-
|
671 |
-
|
672 |
-
|
673 |
-
|
674 |
-
|
675 |
-
|
676 |
-
|
677 |
-
|
678 |
-
|
679 |
-
|
680 |
-
if not static_dir:
|
681 |
raise HTTPException(status_code=404, detail="Frontend not found")
|
682 |
|
683 |
-
index_file = os.path.join(static_dir, "index.html")
|
684 |
-
if os.path.exists(index_file):
|
685 |
-
return FileResponse(index_file)
|
686 |
-
raise HTTPException(status_code=404, detail="Frontend not found")
|
687 |
-
|
688 |
|
689 |
if __name__ == "__main__":
|
690 |
import uvicorn
|
|
|
641 |
|
642 |
# ============= FRONTEND SERVING ROUTES (Must be last) =============
|
643 |
|
644 |
+
EXPOSE_FRONTEND = os.getenv("EXPOSE_FRONTEND", "false").lower() == "true"
|
645 |
+
|
646 |
+
if EXPOSE_FRONTEND:
|
647 |
+
# Serve static assets from the _app directory
|
648 |
+
@app.get("/_app/{path:path}")
|
649 |
+
async def serve_app_assets(path: str):
|
650 |
+
"""Serve Svelte app assets"""
|
651 |
+
static_dir = get_static_dir()
|
652 |
+
if not static_dir:
|
653 |
+
raise HTTPException(status_code=404, detail="Frontend not found")
|
654 |
+
|
655 |
+
file_path = os.path.join(static_dir, "_app", path)
|
656 |
+
if os.path.exists(file_path) and os.path.isfile(file_path):
|
657 |
+
return FileResponse(file_path)
|
658 |
+
raise HTTPException(status_code=404, detail="File not found")
|
659 |
+
|
660 |
+
@app.get("/")
|
661 |
+
async def serve_frontend():
|
662 |
+
"""Serve the main frontend page"""
|
663 |
+
static_dir = get_static_dir()
|
664 |
+
if not static_dir:
|
665 |
+
return {
|
666 |
+
"message": "Frontend not built. Run 'bun run build' to build the frontend."
|
667 |
+
}
|
668 |
+
|
669 |
+
index_file = os.path.join(static_dir, "index.html")
|
670 |
+
if os.path.exists(index_file):
|
671 |
+
return FileResponse(index_file)
|
672 |
return {
|
673 |
"message": "Frontend not built. Run 'bun run build' to build the frontend."
|
674 |
}
|
675 |
|
676 |
+
# Catch-all route for client-side routing (SPA fallback) - MUST BE LAST
|
677 |
+
@app.get("/{path:path}")
|
678 |
+
async def serve_spa_fallback(path: str):
|
679 |
+
"""Serve the frontend for client-side routing"""
|
680 |
+
# For all paths not handled by other routes, serve the index.html (SPA)
|
681 |
+
static_dir = get_static_dir()
|
682 |
+
if not static_dir:
|
683 |
+
raise HTTPException(status_code=404, detail="Frontend not found")
|
684 |
+
|
685 |
+
index_file = os.path.join(static_dir, "index.html")
|
686 |
+
if os.path.exists(index_file):
|
687 |
+
return FileResponse(index_file)
|
|
|
688 |
raise HTTPException(status_code=404, detail="Frontend not found")
|
689 |
|
|
|
|
|
|
|
|
|
|
|
690 |
|
691 |
if __name__ == "__main__":
|
692 |
import uvicorn
|
src/app.css
CHANGED
@@ -1 +1,122 @@
|
|
1 |
-
@import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@import "tailwindcss";
|
2 |
+
|
3 |
+
@import "tw-animate-css";
|
4 |
+
@plugin "@iconify/tailwind4";
|
5 |
+
|
6 |
+
@custom-variant dark (&:is(.dark *));
|
7 |
+
|
8 |
+
:root {
|
9 |
+
--radius: 0.625rem;
|
10 |
+
--background: oklch(1 0 0);
|
11 |
+
--foreground: oklch(0.147 0.004 49.25);
|
12 |
+
--card: oklch(1 0 0);
|
13 |
+
--card-foreground: oklch(0.147 0.004 49.25);
|
14 |
+
--popover: oklch(1 0 0);
|
15 |
+
--popover-foreground: oklch(0.147 0.004 49.25);
|
16 |
+
--primary: oklch(0.216 0.006 56.043);
|
17 |
+
--primary-foreground: oklch(0.985 0.001 106.423);
|
18 |
+
--secondary: oklch(0.97 0.001 106.424);
|
19 |
+
--secondary-foreground: oklch(0.216 0.006 56.043);
|
20 |
+
--muted: oklch(0.97 0.001 106.424);
|
21 |
+
--muted-foreground: oklch(0.553 0.013 58.071);
|
22 |
+
--accent: oklch(0.97 0.001 106.424);
|
23 |
+
--accent-foreground: oklch(0.216 0.006 56.043);
|
24 |
+
--destructive: oklch(0.577 0.245 27.325);
|
25 |
+
--border: oklch(0.923 0.003 48.717);
|
26 |
+
--input: oklch(0.923 0.003 48.717);
|
27 |
+
--ring: oklch(0.709 0.01 56.259);
|
28 |
+
--chart-1: oklch(0.646 0.222 41.116);
|
29 |
+
--chart-2: oklch(0.6 0.118 184.704);
|
30 |
+
--chart-3: oklch(0.398 0.07 227.392);
|
31 |
+
--chart-4: oklch(0.828 0.189 84.429);
|
32 |
+
--chart-5: oklch(0.769 0.188 70.08);
|
33 |
+
--sidebar: oklch(0.985 0.001 106.423);
|
34 |
+
--sidebar-foreground: oklch(0.147 0.004 49.25);
|
35 |
+
--sidebar-primary: oklch(0.216 0.006 56.043);
|
36 |
+
--sidebar-primary-foreground: oklch(0.985 0.001 106.423);
|
37 |
+
--sidebar-accent: oklch(0.97 0.001 106.424);
|
38 |
+
--sidebar-accent-foreground: oklch(0.216 0.006 56.043);
|
39 |
+
--sidebar-border: oklch(0.923 0.003 48.717);
|
40 |
+
--sidebar-ring: oklch(0.709 0.01 56.259);
|
41 |
+
}
|
42 |
+
|
43 |
+
.dark {
|
44 |
+
--background: oklch(0.147 0.004 49.25);
|
45 |
+
--foreground: oklch(0.985 0.001 106.423);
|
46 |
+
--card: oklch(0.216 0.006 56.043);
|
47 |
+
--card-foreground: oklch(0.985 0.001 106.423);
|
48 |
+
--popover: oklch(0.216 0.006 56.043);
|
49 |
+
--popover-foreground: oklch(0.985 0.001 106.423);
|
50 |
+
--primary: oklch(0.923 0.003 48.717);
|
51 |
+
--primary-foreground: oklch(0.216 0.006 56.043);
|
52 |
+
--secondary: oklch(0.268 0.007 34.298);
|
53 |
+
--secondary-foreground: oklch(0.985 0.001 106.423);
|
54 |
+
--muted: oklch(0.268 0.007 34.298);
|
55 |
+
--muted-foreground: oklch(0.709 0.01 56.259);
|
56 |
+
--accent: oklch(0.268 0.007 34.298);
|
57 |
+
--accent-foreground: oklch(0.985 0.001 106.423);
|
58 |
+
--destructive: oklch(0.704 0.191 22.216);
|
59 |
+
--border: oklch(1 0 0 / 10%);
|
60 |
+
--input: oklch(1 0 0 / 15%);
|
61 |
+
--ring: oklch(0.553 0.013 58.071);
|
62 |
+
--chart-1: oklch(0.488 0.243 264.376);
|
63 |
+
--chart-2: oklch(0.696 0.17 162.48);
|
64 |
+
--chart-3: oklch(0.769 0.188 70.08);
|
65 |
+
--chart-4: oklch(0.627 0.265 303.9);
|
66 |
+
--chart-5: oklch(0.645 0.246 16.439);
|
67 |
+
--sidebar: oklch(0.216 0.006 56.043);
|
68 |
+
--sidebar-foreground: oklch(0.985 0.001 106.423);
|
69 |
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
70 |
+
--sidebar-primary-foreground: oklch(0.985 0.001 106.423);
|
71 |
+
--sidebar-accent: oklch(0.268 0.007 34.298);
|
72 |
+
--sidebar-accent-foreground: oklch(0.985 0.001 106.423);
|
73 |
+
--sidebar-border: oklch(1 0 0 / 10%);
|
74 |
+
--sidebar-ring: oklch(0.553 0.013 58.071);
|
75 |
+
}
|
76 |
+
|
77 |
+
@theme inline {
|
78 |
+
--radius-sm: calc(var(--radius) - 4px);
|
79 |
+
--radius-md: calc(var(--radius) - 2px);
|
80 |
+
--radius-lg: var(--radius);
|
81 |
+
--radius-xl: calc(var(--radius) + 4px);
|
82 |
+
--color-background: var(--background);
|
83 |
+
--color-foreground: var(--foreground);
|
84 |
+
--color-card: var(--card);
|
85 |
+
--color-card-foreground: var(--card-foreground);
|
86 |
+
--color-popover: var(--popover);
|
87 |
+
--color-popover-foreground: var(--popover-foreground);
|
88 |
+
--color-primary: var(--primary);
|
89 |
+
--color-primary-foreground: var(--primary-foreground);
|
90 |
+
--color-secondary: var(--secondary);
|
91 |
+
--color-secondary-foreground: var(--secondary-foreground);
|
92 |
+
--color-muted: var(--muted);
|
93 |
+
--color-muted-foreground: var(--muted-foreground);
|
94 |
+
--color-accent: var(--accent);
|
95 |
+
--color-accent-foreground: var(--accent-foreground);
|
96 |
+
--color-destructive: var(--destructive);
|
97 |
+
--color-border: var(--border);
|
98 |
+
--color-input: var(--input);
|
99 |
+
--color-ring: var(--ring);
|
100 |
+
--color-chart-1: var(--chart-1);
|
101 |
+
--color-chart-2: var(--chart-2);
|
102 |
+
--color-chart-3: var(--chart-3);
|
103 |
+
--color-chart-4: var(--chart-4);
|
104 |
+
--color-chart-5: var(--chart-5);
|
105 |
+
--color-sidebar: var(--sidebar);
|
106 |
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
107 |
+
--color-sidebar-primary: var(--sidebar-primary);
|
108 |
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
109 |
+
--color-sidebar-accent: var(--sidebar-accent);
|
110 |
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
111 |
+
--color-sidebar-border: var(--sidebar-border);
|
112 |
+
--color-sidebar-ring: var(--sidebar-ring);
|
113 |
+
}
|
114 |
+
|
115 |
+
@layer base {
|
116 |
+
* {
|
117 |
+
@apply border-border outline-ring/50;
|
118 |
+
}
|
119 |
+
body {
|
120 |
+
@apply bg-background text-foreground;
|
121 |
+
}
|
122 |
+
}
|
src/lib/components/{scene → 3d}/Floor.svelte
RENAMED
@@ -2,19 +2,15 @@
|
|
2 |
import { T } from '@threlte/core';
|
3 |
import * as THREE from 'three';
|
4 |
|
5 |
-
/**
|
6 |
-
* Creates a grid texture for the ground plane
|
7 |
-
* Matches the original React implementation for visual consistency
|
8 |
-
*/
|
9 |
function createGridTexture(): THREE.CanvasTexture {
|
10 |
-
const size =
|
11 |
const canvas = document.createElement('canvas');
|
12 |
canvas.width = canvas.height = size;
|
13 |
const ctx = canvas.getContext('2d');
|
14 |
|
15 |
if (ctx) {
|
16 |
-
// Base color
|
17 |
-
ctx.fillStyle = '#
|
18 |
ctx.fillRect(0, 0, size, size);
|
19 |
|
20 |
// Grid lines
|
@@ -31,26 +27,20 @@
|
|
31 |
|
32 |
const texture = new THREE.CanvasTexture(canvas);
|
33 |
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
|
34 |
-
texture.repeat.set(
|
35 |
return texture;
|
36 |
}
|
37 |
|
38 |
-
// Create texture reactively using Svelte 5 runes
|
39 |
const gridTexture = createGridTexture();
|
40 |
</script>
|
41 |
|
42 |
-
<!-- Ground plane with grid texture matching React version -->
|
43 |
<T.Mesh rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
|
44 |
-
<T.PlaneGeometry args={[
|
45 |
<T.MeshPhysicalMaterial
|
46 |
color={0x808080}
|
47 |
-
metalness={0.7}
|
48 |
-
roughness={0.3}
|
49 |
-
reflectivity={0.1}
|
50 |
-
clearcoat={0.3}
|
51 |
side={THREE.DoubleSide}
|
52 |
transparent
|
53 |
-
opacity={
|
54 |
map={gridTexture}
|
55 |
/>
|
56 |
</T.Mesh>
|
|
|
2 |
import { T } from '@threlte/core';
|
3 |
import * as THREE from 'three';
|
4 |
|
|
|
|
|
|
|
|
|
5 |
function createGridTexture(): THREE.CanvasTexture {
|
6 |
+
const size = 100;
|
7 |
const canvas = document.createElement('canvas');
|
8 |
canvas.width = canvas.height = size;
|
9 |
const ctx = canvas.getContext('2d');
|
10 |
|
11 |
if (ctx) {
|
12 |
+
// Base color - updated to match Tailwind's bg-stone-500
|
13 |
+
ctx.fillStyle = '#71717A';
|
14 |
ctx.fillRect(0, 0, size, size);
|
15 |
|
16 |
// Grid lines
|
|
|
27 |
|
28 |
const texture = new THREE.CanvasTexture(canvas);
|
29 |
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
|
30 |
+
texture.repeat.set(10, 10);
|
31 |
return texture;
|
32 |
}
|
33 |
|
|
|
34 |
const gridTexture = createGridTexture();
|
35 |
</script>
|
36 |
|
|
|
37 |
<T.Mesh rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
|
38 |
+
<T.PlaneGeometry args={[10, 10]} />
|
39 |
<T.MeshPhysicalMaterial
|
40 |
color={0x808080}
|
|
|
|
|
|
|
|
|
41 |
side={THREE.DoubleSide}
|
42 |
transparent
|
43 |
+
opacity={1}
|
44 |
map={gridTexture}
|
45 |
/>
|
46 |
</T.Mesh>
|
src/lib/components/3d/Robot.svelte
ADDED
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { T } from '@threlte/core';
|
3 |
+
import { getRootLinks } from '$lib/components/3d/robot/URDF/utils/UrdfParser';
|
4 |
+
import UrdfLink from '$lib/components/3d/robot/URDF/primitives/UrdfLink.svelte';
|
5 |
+
import { robotManager } from '$lib/robot/RobotManager.svelte';
|
6 |
+
import Selectable from './Selectable.svelte';
|
7 |
+
|
8 |
+
interface Props {}
|
9 |
+
|
10 |
+
let {}: Props = $props();
|
11 |
+
|
12 |
+
// Get all robots from the manager
|
13 |
+
const robots = $derived(robotManager.robots);
|
14 |
+
</script>
|
15 |
+
|
16 |
+
{#each robots as robot, index (robot.id)}
|
17 |
+
{@const xPosition = index * 5}
|
18 |
+
<T.Group position.x={xPosition} position.y={0} position.z={0} quaternion={[0, 0, 0, 1]} scale={[10, 10, 10]} rotation={[-Math.PI / 2, 0, 0]}>
|
19 |
+
<Selectable
|
20 |
+
hoverColor="#ff6b6b"
|
21 |
+
hoverOpacity={0.5}
|
22 |
+
enableEdit={true}
|
23 |
+
>
|
24 |
+
{#snippet content({ isHighlighted })}
|
25 |
+
{#each getRootLinks(robot.robotState.robot) as link}
|
26 |
+
<UrdfLink
|
27 |
+
robot={robot.robotState.robot}
|
28 |
+
{link}
|
29 |
+
textScale={0.2}
|
30 |
+
showName={isHighlighted}
|
31 |
+
showVisual={true}
|
32 |
+
showCollision={false}
|
33 |
+
visualColor="#333333"
|
34 |
+
visualOpacity={isHighlighted ? 0.1 : 1.0}
|
35 |
+
collisionOpacity={1.0}
|
36 |
+
collisionColor="#813d9c"
|
37 |
+
jointNames={isHighlighted}
|
38 |
+
joints={isHighlighted}
|
39 |
+
jointColor="#62a0ea"
|
40 |
+
jointIndicatorColor="#f66151"
|
41 |
+
nameHeight={0.1}
|
42 |
+
selectedLink={robot.robotState.selection.selectedLink}
|
43 |
+
selectedJoint={robot.robotState.selection.selectedJoint}
|
44 |
+
highlightColor="#ffa348"
|
45 |
+
showLine={isHighlighted}
|
46 |
+
opacity={1}
|
47 |
+
isInteractive={false}
|
48 |
+
/>
|
49 |
+
{/each}
|
50 |
+
{/snippet}
|
51 |
+
</Selectable>
|
52 |
+
</T.Group>
|
53 |
+
{/each}
|
src/lib/components/3d/Selectable.svelte
ADDED
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { T } from '@threlte/core';
|
3 |
+
import { TransformControls, interactivity } from '@threlte/extras';
|
4 |
+
import type { Snippet } from 'svelte';
|
5 |
+
import { Spring } from 'svelte/motion';
|
6 |
+
import { useCursor } from '@threlte/extras';
|
7 |
+
import { setContext } from 'svelte';
|
8 |
+
|
9 |
+
interface Props {
|
10 |
+
content: Snippet<[{ isHighlighted: boolean }]> // renderable
|
11 |
+
enableEdit?: boolean;
|
12 |
+
hoverColor?: string;
|
13 |
+
defaultColor?: string;
|
14 |
+
hoverOpacity?: number;
|
15 |
+
}
|
16 |
+
|
17 |
+
let {
|
18 |
+
content,
|
19 |
+
enableEdit = $bindable(true),
|
20 |
+
hoverColor = '#ffa348',
|
21 |
+
hoverOpacity = 0.5,
|
22 |
+
}: Props = $props();
|
23 |
+
|
24 |
+
interactivity();
|
25 |
+
const scale = new Spring(1);
|
26 |
+
|
27 |
+
// Position state to persist transforms
|
28 |
+
let position = $state<[number, number, number]>([0, 0, 0]);
|
29 |
+
let rotation = $state<[number, number, number]>([0, 0, 0]);
|
30 |
+
|
31 |
+
// Hover state
|
32 |
+
let isHovered = $state(false);
|
33 |
+
let isSelected = $state(false);
|
34 |
+
let isHighlighted = $derived(enableEdit && (isSelected || isHovered));
|
35 |
+
|
36 |
+
$effect(() => {
|
37 |
+
// If isHighlighted is true, set the color to hoverColor and opacity to hoverOpacity
|
38 |
+
if (isHighlighted) {
|
39 |
+
scale.target = 1.05;
|
40 |
+
} else {
|
41 |
+
scale.target = 1;
|
42 |
+
}
|
43 |
+
});
|
44 |
+
|
45 |
+
const { onPointerEnter, onPointerLeave } = useCursor();
|
46 |
+
|
47 |
+
// Handle keyboard events for deselection
|
48 |
+
$effect(() => {
|
49 |
+
const handleKeyDown = (event: KeyboardEvent) => {
|
50 |
+
if (event.key === 'Escape' && isSelected) {
|
51 |
+
isSelected = false;
|
52 |
+
}
|
53 |
+
};
|
54 |
+
|
55 |
+
if (isSelected) {
|
56 |
+
document.addEventListener('keydown', handleKeyDown);
|
57 |
+
return () => {
|
58 |
+
document.removeEventListener('keydown', handleKeyDown);
|
59 |
+
};
|
60 |
+
}
|
61 |
+
});
|
62 |
+
|
63 |
+
// Handle transform changes
|
64 |
+
const handleTransform = (ref: any) => {
|
65 |
+
ref.updateMatrix();
|
66 |
+
// Update our position state from the object's current position
|
67 |
+
position = ref.position.toArray();
|
68 |
+
rotation = ref.rotation.toArray();
|
69 |
+
};
|
70 |
+
</script>
|
71 |
+
|
72 |
+
<T.Group
|
73 |
+
{position}
|
74 |
+
{rotation}
|
75 |
+
onclick={() => {
|
76 |
+
isSelected = true;
|
77 |
+
|
78 |
+
}}
|
79 |
+
onpointerenter={() => {
|
80 |
+
onPointerEnter();
|
81 |
+
isHovered = true;
|
82 |
+
}}
|
83 |
+
onpointerleave={() => {
|
84 |
+
onPointerLeave();
|
85 |
+
isHovered = false;
|
86 |
+
}}
|
87 |
+
scale={scale.current}
|
88 |
+
>
|
89 |
+
{#snippet children({ ref })}
|
90 |
+
{@render content({ isHighlighted })}
|
91 |
+
{#if isSelected && enableEdit}
|
92 |
+
<TransformControls
|
93 |
+
object={ref}
|
94 |
+
mode="translate"
|
95 |
+
showY={false}
|
96 |
+
axis="XZ"
|
97 |
+
space="world"
|
98 |
+
on:objectChange={() => handleTransform(ref)}
|
99 |
+
on:mouseUp={() => handleTransform(ref)}
|
100 |
+
/>
|
101 |
+
{/if}
|
102 |
+
{/snippet}
|
103 |
+
</T.Group>
|
104 |
+
|
105 |
+
<!-- From https://github.com/brean/urdf-viewer -->
|
src/lib/components/{scene → 3d}/robot/URDF/createRobot.svelte.ts
RENAMED
File without changes
|
src/lib/components/{scene → 3d}/robot/URDF/interfaces/IUrdfBox.ts
RENAMED
File without changes
|
src/lib/components/{scene → 3d}/robot/URDF/interfaces/IUrdfCylinder.ts
RENAMED
File without changes
|
src/lib/components/{scene → 3d}/robot/URDF/interfaces/IUrdfJoint.ts
RENAMED
File without changes
|
src/lib/components/{scene → 3d}/robot/URDF/interfaces/IUrdfLink.ts
RENAMED
File without changes
|
src/lib/components/{scene → 3d}/robot/URDF/interfaces/IUrdfMesh.ts
RENAMED
File without changes
|
src/lib/components/{scene → 3d}/robot/URDF/interfaces/IUrdfRobot.ts
RENAMED
File without changes
|
src/lib/components/{scene → 3d}/robot/URDF/interfaces/IUrdfVisual.ts
RENAMED
File without changes
|
src/lib/components/{scene → 3d}/robot/URDF/interfaces/index.ts
RENAMED
File without changes
|
src/lib/components/{scene → 3d}/robot/URDF/mesh/DAE.svelte
RENAMED
File without changes
|
src/lib/components/{scene → 3d}/robot/URDF/mesh/OBJ.svelte
RENAMED
File without changes
|
src/lib/components/{scene → 3d}/robot/URDF/mesh/STL.svelte
RENAMED
File without changes
|
src/lib/components/{scene → 3d}/robot/URDF/primitives/UrdfJoint.svelte
RENAMED
@@ -10,7 +10,6 @@
|
|
10 |
Text,
|
11 |
type InteractivityProps
|
12 |
} from '@threlte/extras';
|
13 |
-
import Selectable from '../../../Selectable.svelte';
|
14 |
|
15 |
import type IUrdfLink from '../interfaces/IUrdfLink';
|
16 |
import type IUrdfRobot from '../interfaces/IUrdfRobot';
|
@@ -72,20 +71,6 @@
|
|
72 |
</script>
|
73 |
|
74 |
{@html `<!-- Joint ${joint.name} (${joint.type}) -->`}
|
75 |
-
{#if showName}
|
76 |
-
<Billboard
|
77 |
-
position.x={joint.origin_xyz[0]}
|
78 |
-
position.y={joint.origin_xyz[1]}
|
79 |
-
position.z={joint.origin_xyz[2]}
|
80 |
-
>
|
81 |
-
<Text
|
82 |
-
scale={nameHeight}
|
83 |
-
color={selectedJoint == joint ? highlightColor : jointColor}
|
84 |
-
text={joint.name}
|
85 |
-
characters="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
86 |
-
></Text>
|
87 |
-
</Billboard>
|
88 |
-
{/if}
|
89 |
|
90 |
<!-- draw line from parent-frame to joint origin -->
|
91 |
{#if showLine}
|
@@ -100,8 +85,8 @@
|
|
100 |
</T.Line>
|
101 |
{/if}
|
102 |
|
103 |
-
<
|
104 |
-
<T.Group rotation={joint.rotation
|
105 |
{#if joint.child}
|
106 |
<UrdfLink
|
107 |
{robot}
|
@@ -121,7 +106,7 @@
|
|
121 |
{selectedLink}
|
122 |
{selectedJoint}
|
123 |
{highlightColor}
|
124 |
-
showLine
|
125 |
opacity={1}
|
126 |
isInteractive={true}
|
127 |
/>
|
@@ -143,6 +128,24 @@
|
|
143 |
</T.Mesh>
|
144 |
{/if}
|
145 |
</T.Group>
|
146 |
-
</
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
147 |
|
148 |
<!-- From https://github.com/brean/urdf-viewer -->
|
|
|
10 |
Text,
|
11 |
type InteractivityProps
|
12 |
} from '@threlte/extras';
|
|
|
13 |
|
14 |
import type IUrdfLink from '../interfaces/IUrdfLink';
|
15 |
import type IUrdfRobot from '../interfaces/IUrdfRobot';
|
|
|
71 |
</script>
|
72 |
|
73 |
{@html `<!-- Joint ${joint.name} (${joint.type}) -->`}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
74 |
|
75 |
<!-- draw line from parent-frame to joint origin -->
|
76 |
{#if showLine}
|
|
|
85 |
</T.Line>
|
86 |
{/if}
|
87 |
|
88 |
+
<T.Group position={joint.origin_xyz} rotation={joint.origin_rpy}>
|
89 |
+
<T.Group rotation={joint.rotation}>
|
90 |
{#if joint.child}
|
91 |
<UrdfLink
|
92 |
{robot}
|
|
|
106 |
{selectedLink}
|
107 |
{selectedJoint}
|
108 |
{highlightColor}
|
109 |
+
{showLine}
|
110 |
opacity={1}
|
111 |
isInteractive={true}
|
112 |
/>
|
|
|
128 |
</T.Mesh>
|
129 |
{/if}
|
130 |
</T.Group>
|
131 |
+
</T.Group>
|
132 |
+
|
133 |
+
{#if showName}
|
134 |
+
<Billboard
|
135 |
+
position.x={joint.origin_xyz[0] + 0.04}
|
136 |
+
position.y={joint.origin_xyz[1] + 0.01}
|
137 |
+
position.z={joint.origin_xyz[2] + 0.03}
|
138 |
+
renderOrder={999}
|
139 |
+
frustumCulled={false}
|
140 |
+
>
|
141 |
+
<Text
|
142 |
+
scale={nameHeight}
|
143 |
+
color={selectedJoint == joint ? highlightColor : jointColor}
|
144 |
+
text={joint.name}
|
145 |
+
characters="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
146 |
+
renderOrder={999}
|
147 |
+
></Text>
|
148 |
+
</Billboard>
|
149 |
+
{/if}
|
150 |
|
151 |
<!-- From https://github.com/brean/urdf-viewer -->
|
src/lib/components/{scene → 3d}/robot/URDF/primitives/UrdfLink.svelte
RENAMED
@@ -59,10 +59,10 @@
|
|
59 |
|
60 |
{@html `<!-- Link ${link.name} -->`}
|
61 |
{#if showName}
|
62 |
-
|
63 |
<Text anchorY={-0.2} scale={textScale} text={link.name} characters="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
64 |
></Text>
|
65 |
-
</Billboard>
|
66 |
{/if}
|
67 |
|
68 |
{#if showVisual}
|
@@ -85,10 +85,10 @@
|
|
85 |
{/each}
|
86 |
{/if}
|
87 |
|
88 |
-
{#each getChildJoints(robot, link) as joint}
|
89 |
<UrdfJoint
|
90 |
{robot}
|
91 |
-
|
92 |
{selectedJoint}
|
93 |
{selectedLink}
|
94 |
{showName}
|
|
|
59 |
|
60 |
{@html `<!-- Link ${link.name} -->`}
|
61 |
{#if showName}
|
62 |
+
<Billboard position.x={0} position.y={0} position.z={0}>
|
63 |
<Text anchorY={-0.2} scale={textScale} text={link.name} characters="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
64 |
></Text>
|
65 |
+
</Billboard>
|
66 |
{/if}
|
67 |
|
68 |
{#if showVisual}
|
|
|
85 |
{/each}
|
86 |
{/if}
|
87 |
|
88 |
+
{#each getChildJoints(robot, link) as joint (joint.name)}
|
89 |
<UrdfJoint
|
90 |
{robot}
|
91 |
+
{joint}
|
92 |
{selectedJoint}
|
93 |
{selectedLink}
|
94 |
{showName}
|
src/lib/components/{scene → 3d}/robot/URDF/primitives/UrdfThree.svelte
RENAMED
File without changes
|
src/lib/components/{scene → 3d}/robot/URDF/primitives/UrdfVisual.svelte
RENAMED
@@ -8,6 +8,7 @@
|
|
8 |
import { interactivity, type InteractivityProps } from '@threlte/extras';
|
9 |
import type IUrdfLink from '../interfaces/IUrdfLink';
|
10 |
import { DoubleSide, type Side } from 'three';
|
|
|
11 |
|
12 |
type Props = InteractivityProps & {
|
13 |
visual: IUrdfVisual;
|
@@ -36,17 +37,19 @@
|
|
36 |
...restProps
|
37 |
}: Props = $props();
|
38 |
|
39 |
-
|
|
|
40 |
? numberArrayToColor(visual.color_rgba.slice(0, 3) as [number, number, number])
|
41 |
: defaultColor;
|
|
|
42 |
|
43 |
</script>
|
44 |
|
45 |
{#if visual.type === 'mesh'}
|
46 |
{#if visual.geometry.type === 'stl'}
|
47 |
<STL
|
48 |
-
{
|
49 |
-
{opacity}
|
50 |
filename={visual.geometry.filename}
|
51 |
scale={visual.geometry.scale}
|
52 |
{position}
|
@@ -59,8 +62,8 @@
|
|
59 |
/>
|
60 |
{:else if visual.geometry.type === 'obj'}
|
61 |
<OBJ
|
62 |
-
{
|
63 |
-
{opacity}
|
64 |
scale={visual.geometry.scale}
|
65 |
filename={visual.geometry.filename}
|
66 |
{position}
|
@@ -74,8 +77,8 @@
|
|
74 |
{:else if visual.geometry.type === 'dae'}
|
75 |
<DAE
|
76 |
filename={visual.geometry.filename}
|
77 |
-
{
|
78 |
-
{opacity}
|
79 |
scale={visual.geometry.scale}
|
80 |
{position}
|
81 |
{rotation}
|
@@ -92,8 +95,8 @@
|
|
92 |
args={[visual.geometry.radius, visual.geometry.radius, visual.geometry.length]}
|
93 |
/>
|
94 |
<T.MeshBasicMaterial
|
95 |
-
{
|
96 |
-
opacity={opacity
|
97 |
transparent={opacity < 1.0}
|
98 |
/>
|
99 |
</T.Mesh>
|
@@ -101,8 +104,8 @@
|
|
101 |
<T.Mesh castShadow receiveShadow scale={visual.geometry.size} {onclick}>
|
102 |
<T.BoxGeometry />
|
103 |
<T.MeshBasicMaterial
|
104 |
-
{
|
105 |
-
opacity={opacity
|
106 |
transparent={opacity < 1.0}
|
107 |
/>
|
108 |
</T.Mesh>
|
|
|
8 |
import { interactivity, type InteractivityProps } from '@threlte/extras';
|
9 |
import type IUrdfLink from '../interfaces/IUrdfLink';
|
10 |
import { DoubleSide, type Side } from 'three';
|
11 |
+
import { getContext } from 'svelte';
|
12 |
|
13 |
type Props = InteractivityProps & {
|
14 |
visual: IUrdfVisual;
|
|
|
37 |
...restProps
|
38 |
}: Props = $props();
|
39 |
|
40 |
+
// Use context color only when hovering, otherwise use original logic
|
41 |
+
const baseColor = visual?.color_rgba
|
42 |
? numberArrayToColor(visual.color_rgba.slice(0, 3) as [number, number, number])
|
43 |
: defaultColor;
|
44 |
+
|
45 |
|
46 |
</script>
|
47 |
|
48 |
{#if visual.type === 'mesh'}
|
49 |
{#if visual.geometry.type === 'stl'}
|
50 |
<STL
|
51 |
+
color={baseColor}
|
52 |
+
opacity={opacity}
|
53 |
filename={visual.geometry.filename}
|
54 |
scale={visual.geometry.scale}
|
55 |
{position}
|
|
|
62 |
/>
|
63 |
{:else if visual.geometry.type === 'obj'}
|
64 |
<OBJ
|
65 |
+
color={baseColor}
|
66 |
+
opacity={opacity}
|
67 |
scale={visual.geometry.scale}
|
68 |
filename={visual.geometry.filename}
|
69 |
{position}
|
|
|
77 |
{:else if visual.geometry.type === 'dae'}
|
78 |
<DAE
|
79 |
filename={visual.geometry.filename}
|
80 |
+
color={baseColor}
|
81 |
+
opacity={opacity}
|
82 |
scale={visual.geometry.scale}
|
83 |
{position}
|
84 |
{rotation}
|
|
|
95 |
args={[visual.geometry.radius, visual.geometry.radius, visual.geometry.length]}
|
96 |
/>
|
97 |
<T.MeshBasicMaterial
|
98 |
+
color={baseColor}
|
99 |
+
opacity={opacity}
|
100 |
transparent={opacity < 1.0}
|
101 |
/>
|
102 |
</T.Mesh>
|
|
|
104 |
<T.Mesh castShadow receiveShadow scale={visual.geometry.size} {onclick}>
|
105 |
<T.BoxGeometry />
|
106 |
<T.MeshBasicMaterial
|
107 |
+
color={baseColor}
|
108 |
+
opacity={opacity}
|
109 |
transparent={opacity < 1.0}
|
110 |
/>
|
111 |
</T.Mesh>
|
src/lib/components/{scene → 3d}/robot/URDF/runes/urdf_state.svelte.ts
RENAMED
File without changes
|
src/lib/components/{scene → 3d}/robot/URDF/utils/UrdfParser.ts
RENAMED
File without changes
|
src/lib/components/{scene → 3d}/robot/URDF/utils/helper.ts
RENAMED
File without changes
|
src/lib/components/ButtonBar.svelte
ADDED
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { Button } from '$lib/components/ui/button';
|
3 |
+
import { Badge } from '$lib/components/ui/badge';
|
4 |
+
import * as Popover from '$lib/components/ui/popover';
|
5 |
+
import { toast } from 'svelte-sonner';
|
6 |
+
import { cn } from '$lib/utils';
|
7 |
+
import { robotManager } from "$lib/robot/RobotManager.svelte";
|
8 |
+
import { robotUrdfConfigMap } from "$lib/configs/robotUrdfConfig";
|
9 |
+
|
10 |
+
interface Props {
|
11 |
+
controlsOpen?: boolean;
|
12 |
+
onToggleControls?: () => void;
|
13 |
+
}
|
14 |
+
|
15 |
+
let {
|
16 |
+
controlsOpen = $bindable(false),
|
17 |
+
onToggleControls
|
18 |
+
}: Props = $props();
|
19 |
+
|
20 |
+
let isCreating = $state(false);
|
21 |
+
let selectedRobotType = $state("so-arm100"); // Default to SO-100
|
22 |
+
let showRobotSelector = $state(false);
|
23 |
+
|
24 |
+
// Get available robot types
|
25 |
+
const robotTypes = Object.keys(robotUrdfConfigMap);
|
26 |
+
|
27 |
+
// Function to add robot using the proper robot creation logic
|
28 |
+
async function addRobot() {
|
29 |
+
if (isCreating) return;
|
30 |
+
|
31 |
+
console.log(`Creating ${selectedRobotType} robot...`);
|
32 |
+
isCreating = true;
|
33 |
+
|
34 |
+
try {
|
35 |
+
const urdfConfig = robotUrdfConfigMap[selectedRobotType];
|
36 |
+
|
37 |
+
if (!urdfConfig) {
|
38 |
+
throw new Error(`Unknown robot type: ${selectedRobotType}`);
|
39 |
+
}
|
40 |
+
|
41 |
+
const robotId = `robot-${Date.now()}`;
|
42 |
+
console.log('Creating robot with ID:', robotId, 'and config:', urdfConfig);
|
43 |
+
|
44 |
+
const robot = await robotManager.createRobot(robotId, urdfConfig);
|
45 |
+
console.log('Robot created successfully:', robot);
|
46 |
+
|
47 |
+
toast.success("Robot Added", {
|
48 |
+
description: `${selectedRobotType} robot ${robotId.slice(0, 12)}... created successfully.`
|
49 |
+
});
|
50 |
+
|
51 |
+
showRobotSelector = false; // Close selector on success
|
52 |
+
|
53 |
+
} catch (error) {
|
54 |
+
console.error('Robot creation failed:', error);
|
55 |
+
toast.error("Failed to Add Robot", {
|
56 |
+
description: `Could not create ${selectedRobotType} robot: ${error}`
|
57 |
+
});
|
58 |
+
} finally {
|
59 |
+
isCreating = false;
|
60 |
+
}
|
61 |
+
}
|
62 |
+
|
63 |
+
// Quick add SO-100 function
|
64 |
+
async function quickAddSO100() {
|
65 |
+
selectedRobotType = "so-arm100";
|
66 |
+
await addRobot();
|
67 |
+
}
|
68 |
+
</script>
|
69 |
+
|
70 |
+
<!-- Button Bar Container -->
|
71 |
+
<div class="fixed top-4 left-4 z-50 flex gap-2">
|
72 |
+
<!-- Add Robot Button with Dropdown -->
|
73 |
+
<div class="flex">
|
74 |
+
<!-- Main Add Button (SO-100) -->
|
75 |
+
<Button
|
76 |
+
variant="default"
|
77 |
+
size="sm"
|
78 |
+
onclick={quickAddSO100}
|
79 |
+
disabled={isCreating}
|
80 |
+
class="shadow-lg bg-green-600 hover:bg-green-500 disabled:bg-gray-600 text-white border-green-500 transition-all duration-200 group rounded-r-none border-r-0"
|
81 |
+
>
|
82 |
+
<span class={cn(
|
83 |
+
"size-4 mr-2 transition-transform duration-200",
|
84 |
+
isCreating ? "icon-[mdi--loading] animate-spin" : "icon-[mdi--plus] group-hover:rotate-90"
|
85 |
+
)}></span>
|
86 |
+
{isCreating ? 'Creating...' : 'Add SO-100'}
|
87 |
+
</Button>
|
88 |
+
|
89 |
+
<!-- Dropdown Button -->
|
90 |
+
<Popover.Root bind:open={showRobotSelector}>
|
91 |
+
<Popover.Trigger>
|
92 |
+
{#snippet child({ props })}
|
93 |
+
<Button
|
94 |
+
{...props}
|
95 |
+
variant="default"
|
96 |
+
size="sm"
|
97 |
+
disabled={isCreating}
|
98 |
+
class="shadow-lg bg-green-600 hover:bg-green-500 disabled:bg-gray-600 text-white border-green-500 transition-all duration-200 rounded-l-none border-l border-green-400 px-2"
|
99 |
+
>
|
100 |
+
<span class="icon-[mdi--chevron-down] size-4"></span>
|
101 |
+
</Button>
|
102 |
+
{/snippet}
|
103 |
+
</Popover.Trigger>
|
104 |
+
<Popover.Content class="w-64 p-3 bg-slate-800 border-slate-600">
|
105 |
+
<div class="space-y-3">
|
106 |
+
<h4 class="text-sm font-medium text-slate-100">Select Robot Type</h4>
|
107 |
+
<div class="space-y-2">
|
108 |
+
{#each robotTypes as robotType}
|
109 |
+
<Button
|
110 |
+
variant={selectedRobotType === robotType ? "default" : "ghost"}
|
111 |
+
size="sm"
|
112 |
+
onclick={async () => {
|
113 |
+
selectedRobotType = robotType;
|
114 |
+
await addRobot();
|
115 |
+
}}
|
116 |
+
disabled={isCreating}
|
117 |
+
class="w-full justify-start text-left"
|
118 |
+
>
|
119 |
+
<span class="icon-[mdi--robot] size-4 mr-2"></span>
|
120 |
+
{robotType}
|
121 |
+
{#if robotType === "so-arm100"}
|
122 |
+
<Badge variant="secondary" class="ml-auto text-xs">SO-100</Badge>
|
123 |
+
{/if}
|
124 |
+
</Button>
|
125 |
+
{/each}
|
126 |
+
</div>
|
127 |
+
</div>
|
128 |
+
</Popover.Content>
|
129 |
+
</Popover.Root>
|
130 |
+
</div>
|
131 |
+
|
132 |
+
<!-- Controls Button -->
|
133 |
+
<Button
|
134 |
+
variant="outline"
|
135 |
+
size="sm"
|
136 |
+
onclick={onToggleControls}
|
137 |
+
class={cn(
|
138 |
+
"shadow-lg transition-all duration-200 border-slate-600 backdrop-blur-sm group",
|
139 |
+
controlsOpen
|
140 |
+
? "bg-blue-600/90 text-white hover:bg-blue-500 border-blue-500"
|
141 |
+
: "bg-slate-800/90 text-slate-100 hover:bg-slate-700"
|
142 |
+
)}
|
143 |
+
>
|
144 |
+
<span class={cn(
|
145 |
+
"size-4 mr-2 transition-transform duration-200",
|
146 |
+
controlsOpen
|
147 |
+
? "icon-[mdi--close] group-hover:rotate-90"
|
148 |
+
: "icon-[mdi--gamepad-variant] group-hover:scale-110"
|
149 |
+
)}></span>
|
150 |
+
{controlsOpen ? 'Close' : 'Controls'}
|
151 |
+
|
152 |
+
{#if !controlsOpen}
|
153 |
+
<Badge
|
154 |
+
variant="secondary"
|
155 |
+
class="ml-2 bg-blue-400/20 text-blue-300 border-blue-400/30 text-xs"
|
156 |
+
>
|
157 |
+
Robot
|
158 |
+
</Badge>
|
159 |
+
{/if}
|
160 |
+
</Button>
|
161 |
+
</div>
|
src/lib/components/ControlsSheet.svelte
ADDED
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { Button } from '$lib/components/ui/button';
|
3 |
+
import * as Sheet from '$lib/components/ui/sheet';
|
4 |
+
import { Badge } from '$lib/components/ui/badge';
|
5 |
+
import { Separator } from '$lib/components/ui/separator';
|
6 |
+
import ControlPanel from '$lib/components/panel/ControlPanel.svelte';
|
7 |
+
|
8 |
+
interface Props {
|
9 |
+
isOpen?: boolean;
|
10 |
+
}
|
11 |
+
|
12 |
+
let { isOpen = $bindable(false) }: Props = $props();
|
13 |
+
|
14 |
+
function toggleSheet() {
|
15 |
+
isOpen = !isOpen;
|
16 |
+
}
|
17 |
+
</script>
|
18 |
+
|
19 |
+
<!-- Controls Sheet -->
|
20 |
+
<Sheet.Root bind:open={isOpen}>
|
21 |
+
<Sheet.Content
|
22 |
+
side="right"
|
23 |
+
class="w-80 sm:w-96 p-0 bg-gradient-to-b from-slate-700 to-slate-800 text-white border-l border-slate-600"
|
24 |
+
>
|
25 |
+
<!-- Header -->
|
26 |
+
<Sheet.Header class="border-b border-slate-600 bg-slate-700/80 backdrop-blur-sm p-6">
|
27 |
+
<div class="flex items-center justify-between">
|
28 |
+
<div class="flex items-center gap-3">
|
29 |
+
<span class="icon-[mdi--gamepad-variant] size-6 text-blue-400"></span>
|
30 |
+
<div>
|
31 |
+
<Sheet.Title class="text-xl font-semibold text-slate-100">
|
32 |
+
Robot Controls
|
33 |
+
</Sheet.Title>
|
34 |
+
<p class="text-sm text-slate-400 mt-1">
|
35 |
+
Control your robot
|
36 |
+
</p>
|
37 |
+
</div>
|
38 |
+
</div>
|
39 |
+
<Badge variant="secondary" class="bg-blue-400 text-slate-900 font-medium px-3">
|
40 |
+
Active
|
41 |
+
</Badge>
|
42 |
+
</div>
|
43 |
+
</Sheet.Header>
|
44 |
+
|
45 |
+
<!-- Content -->
|
46 |
+
<div class="flex-1 overflow-y-auto scrollbar-thin scrollbar-track-slate-700 scrollbar-thumb-slate-500 px-4">
|
47 |
+
<div class="space-y-4">
|
48 |
+
<div class="flex items-center gap-2 text-sm text-slate-300">
|
49 |
+
<span class="icon-[mdi--information-outline] size-4"></span>
|
50 |
+
Control your robot's movements and actions
|
51 |
+
</div>
|
52 |
+
<Separator class="bg-slate-600" />
|
53 |
+
<ControlPanel />
|
54 |
+
</div>
|
55 |
+
</div>
|
56 |
+
|
57 |
+
<!-- Footer -->
|
58 |
+
<div class="border-t border-slate-600 bg-slate-800/50 p-4">
|
59 |
+
<div class="flex items-center justify-between">
|
60 |
+
<div class="flex items-center gap-2 text-xs text-slate-400">
|
61 |
+
<span class="icon-[mdi--gamepad-variant] size-4"></span>
|
62 |
+
<span>Robot Controls</span>
|
63 |
+
</div>
|
64 |
+
<Button
|
65 |
+
variant="ghost"
|
66 |
+
size="sm"
|
67 |
+
onclick={toggleSheet}
|
68 |
+
class="text-slate-400 hover:text-slate-100 text-xs px-2 py-1 h-auto"
|
69 |
+
>
|
70 |
+
<span class="icon-[mdi--close] size-3 mr-1"></span>
|
71 |
+
Close
|
72 |
+
</Button>
|
73 |
+
</div>
|
74 |
+
</div>
|
75 |
+
</Sheet.Content>
|
76 |
+
</Sheet.Root>
|
src/lib/components/Overlay.svelte
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import ButtonBar from '$lib/components/ButtonBar.svelte';
|
3 |
+
import ControlsSheet from '$lib/components/ControlsSheet.svelte';
|
4 |
+
import SettingsSheet from '$lib/components/SettingsSheet.svelte';
|
5 |
+
|
6 |
+
interface Props {
|
7 |
+
// Optional props for controlling the state externally if needed
|
8 |
+
controlsOpen?: boolean;
|
9 |
+
settingsOpen?: boolean;
|
10 |
+
}
|
11 |
+
|
12 |
+
let {
|
13 |
+
controlsOpen = $bindable(false),
|
14 |
+
settingsOpen = $bindable(false)
|
15 |
+
}: Props = $props();
|
16 |
+
|
17 |
+
function toggleControls() {
|
18 |
+
controlsOpen = !controlsOpen;
|
19 |
+
}
|
20 |
+
|
21 |
+
function toggleSettings() {
|
22 |
+
settingsOpen = !settingsOpen;
|
23 |
+
}
|
24 |
+
</script>
|
25 |
+
|
26 |
+
<!-- Button Bar with Add Robot and Controls (Top Left) -->
|
27 |
+
<ButtonBar
|
28 |
+
bind:controlsOpen
|
29 |
+
onToggleControls={toggleControls}
|
30 |
+
/>
|
31 |
+
|
32 |
+
<!-- Controls Sheet (Right Side) -->
|
33 |
+
<ControlsSheet bind:isOpen={controlsOpen} />
|
34 |
+
|
35 |
+
<!-- Settings Button and Sheet (Top Right, Left Side) -->
|
36 |
+
<SettingsSheet bind:isOpen={settingsOpen} />
|
src/lib/components/PanelWrapper.svelte
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import * as Card from '$lib/components/ui/card';
|
3 |
+
import { Separator } from '$lib/components/ui/separator';
|
4 |
+
|
5 |
+
interface Props {
|
6 |
+
title?: string;
|
7 |
+
subtitle?: string;
|
8 |
+
children: any;
|
9 |
+
}
|
10 |
+
|
11 |
+
let { title, subtitle, children }: Props = $props();
|
12 |
+
</script>
|
13 |
+
|
14 |
+
<Card.Root class="bg-slate-700/50 border-slate-600">
|
15 |
+
{#if title}
|
16 |
+
<Card.Header class="pb-3">
|
17 |
+
<Card.Title class="text-slate-100 flex items-center gap-2">
|
18 |
+
{title}
|
19 |
+
</Card.Title>
|
20 |
+
{#if subtitle}
|
21 |
+
<Card.Description class="text-slate-400 text-sm">
|
22 |
+
{subtitle}
|
23 |
+
</Card.Description>
|
24 |
+
{/if}
|
25 |
+
</Card.Header>
|
26 |
+
<Separator class="bg-slate-600" />
|
27 |
+
{/if}
|
28 |
+
|
29 |
+
<Card.Content class="p-4 space-y-4">
|
30 |
+
{@render children()}
|
31 |
+
</Card.Content>
|
32 |
+
</Card.Root>
|
src/lib/components/RobotStatusDemo.svelte
ADDED
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import StatusCard from '$lib/components/StatusCard.svelte';
|
3 |
+
import { Button } from '$lib/components/ui/button';
|
4 |
+
import { Badge } from '$lib/components/ui/badge';
|
5 |
+
import { environment } from '$lib/runes/env.svelte';
|
6 |
+
|
7 |
+
// Sample usage of StatusCard component
|
8 |
+
const robotStats = $derived(() => {
|
9 |
+
const robotCount = Object.keys(environment.robots).length;
|
10 |
+
return {
|
11 |
+
robotCount,
|
12 |
+
status: (robotCount > 0 ? 'active' : 'inactive') as 'active' | 'inactive',
|
13 |
+
lastUpdate: new Date().toLocaleTimeString()
|
14 |
+
};
|
15 |
+
});
|
16 |
+
</script>
|
17 |
+
|
18 |
+
<div class="space-y-4">
|
19 |
+
<h3 class="text-lg font-semibold text-slate-100 mb-4">Robot Status Overview</h3>
|
20 |
+
|
21 |
+
<div class="grid grid-cols-1 gap-3">
|
22 |
+
<StatusCard
|
23 |
+
title="Connected Robots"
|
24 |
+
status={robotStats().status}
|
25 |
+
value={robotStats().robotCount}
|
26 |
+
description="Active robot instances"
|
27 |
+
icon="icon-[mdi--robot]"
|
28 |
+
>
|
29 |
+
{#if robotStats().robotCount > 0}
|
30 |
+
<div class="flex flex-wrap gap-1">
|
31 |
+
{#each Object.entries(environment.robots) as [id, robot]}
|
32 |
+
<Badge variant="outline" class="text-xs text-slate-300 border-slate-500">
|
33 |
+
{id.slice(0, 8)}...
|
34 |
+
</Badge>
|
35 |
+
{/each}
|
36 |
+
</div>
|
37 |
+
{:else}
|
38 |
+
<p class="text-xs text-slate-500">No robots currently active</p>
|
39 |
+
{/if}
|
40 |
+
</StatusCard>
|
41 |
+
|
42 |
+
<StatusCard
|
43 |
+
title="System Status"
|
44 |
+
status="active"
|
45 |
+
description="All systems operational"
|
46 |
+
icon="icon-[mdi--check-circle]"
|
47 |
+
>
|
48 |
+
<div class="text-xs text-slate-400">
|
49 |
+
Last updated: {robotStats().lastUpdate}
|
50 |
+
</div>
|
51 |
+
</StatusCard>
|
52 |
+
|
53 |
+
<StatusCard
|
54 |
+
title="Control Mode"
|
55 |
+
status="active"
|
56 |
+
value="Manual"
|
57 |
+
description="Direct joint control enabled"
|
58 |
+
icon="icon-[mdi--gamepad-variant]"
|
59 |
+
>
|
60 |
+
<Button size="sm" variant="outline" class="w-full text-xs">
|
61 |
+
Switch to Autonomous
|
62 |
+
</Button>
|
63 |
+
</StatusCard>
|
64 |
+
</div>
|
65 |
+
</div>
|
src/lib/components/SettingsSheet.svelte
ADDED
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { Button } from '$lib/components/ui/button';
|
3 |
+
import * as Sheet from '$lib/components/ui/sheet';
|
4 |
+
import { Badge } from '$lib/components/ui/badge';
|
5 |
+
import { Separator } from '$lib/components/ui/separator';
|
6 |
+
import SettingsPanel from '$lib/components/panel/SettingsPanel.svelte';
|
7 |
+
import { cn } from '$lib/utils';
|
8 |
+
|
9 |
+
interface Props {
|
10 |
+
isOpen?: boolean;
|
11 |
+
}
|
12 |
+
|
13 |
+
let { isOpen = $bindable(false) }: Props = $props();
|
14 |
+
|
15 |
+
function toggleSheet() {
|
16 |
+
isOpen = !isOpen;
|
17 |
+
}
|
18 |
+
</script>
|
19 |
+
|
20 |
+
<!-- Settings Trigger Button -->
|
21 |
+
<div class="fixed top-4 right-4 z-50">
|
22 |
+
<Button
|
23 |
+
variant="outline"
|
24 |
+
size="sm"
|
25 |
+
onclick={toggleSheet}
|
26 |
+
class={cn(
|
27 |
+
"shadow-lg transition-all duration-200 border-slate-600 backdrop-blur-sm group",
|
28 |
+
isOpen
|
29 |
+
? "bg-orange-600/90 text-white hover:bg-orange-500 border-orange-500"
|
30 |
+
: "bg-slate-800/90 text-slate-100 hover:bg-slate-700"
|
31 |
+
)}
|
32 |
+
>
|
33 |
+
<span class={cn(
|
34 |
+
"size-4 mr-2 transition-transform duration-200",
|
35 |
+
isOpen
|
36 |
+
? "icon-[mdi--close] group-hover:rotate-90"
|
37 |
+
: "icon-[mdi--cog] group-hover:rotate-45"
|
38 |
+
)}></span>
|
39 |
+
{isOpen ? 'Close' : 'Settings'}
|
40 |
+
|
41 |
+
{#if !isOpen}
|
42 |
+
<Badge
|
43 |
+
variant="secondary"
|
44 |
+
class="ml-2 bg-orange-400/20 text-orange-300 border-orange-400/30 text-xs"
|
45 |
+
>
|
46 |
+
Joints
|
47 |
+
</Badge>
|
48 |
+
{/if}
|
49 |
+
</Button>
|
50 |
+
</div>
|
51 |
+
|
52 |
+
<!-- Settings Sheet -->
|
53 |
+
<Sheet.Root bind:open={isOpen}>
|
54 |
+
<Sheet.Content
|
55 |
+
side="left"
|
56 |
+
class="w-80 sm:w-96 p-0 bg-gradient-to-b from-slate-700 to-slate-800 text-white border-r border-slate-600"
|
57 |
+
>
|
58 |
+
<!-- Header -->
|
59 |
+
<Sheet.Header class="border-b border-slate-600 bg-slate-700/80 backdrop-blur-sm p-6">
|
60 |
+
<div class="flex items-center justify-between">
|
61 |
+
<div class="flex items-center gap-3">
|
62 |
+
<span class="icon-[mdi--cog] size-6 text-orange-400"></span>
|
63 |
+
<div>
|
64 |
+
<Sheet.Title class="text-xl font-semibold text-slate-100">
|
65 |
+
Robot Settings
|
66 |
+
</Sheet.Title>
|
67 |
+
<p class="text-sm text-slate-400 mt-1">
|
68 |
+
Configure joints and robot parameters
|
69 |
+
</p>
|
70 |
+
</div>
|
71 |
+
</div>
|
72 |
+
<Badge variant="secondary" class="bg-orange-400 text-slate-900 font-medium px-3">
|
73 |
+
Config
|
74 |
+
</Badge>
|
75 |
+
</div>
|
76 |
+
</Sheet.Header>
|
77 |
+
|
78 |
+
<!-- Content -->
|
79 |
+
<div class="flex-1 overflow-y-auto scrollbar-thin scrollbar-track-slate-700 scrollbar-thumb-slate-500 p-4">
|
80 |
+
<div class="space-y-4">
|
81 |
+
<div class="flex items-center gap-2 text-sm text-slate-300">
|
82 |
+
<span class="icon-[mdi--wrench] size-4"></span>
|
83 |
+
Configure joints and robot parameters
|
84 |
+
</div>
|
85 |
+
<Separator class="bg-slate-600" />
|
86 |
+
<SettingsPanel />
|
87 |
+
</div>
|
88 |
+
</div>
|
89 |
+
|
90 |
+
<!-- Footer -->
|
91 |
+
<div class="border-t border-slate-600 bg-slate-800/50 p-4">
|
92 |
+
<div class="flex items-center justify-between">
|
93 |
+
<div class="flex items-center gap-2 text-xs text-slate-400">
|
94 |
+
<span class="icon-[mdi--cog] size-4"></span>
|
95 |
+
<span>Robot Settings</span>
|
96 |
+
</div>
|
97 |
+
<Button
|
98 |
+
variant="ghost"
|
99 |
+
size="sm"
|
100 |
+
onclick={toggleSheet}
|
101 |
+
class="text-slate-400 hover:text-slate-100 text-xs px-2 py-1 h-auto"
|
102 |
+
>
|
103 |
+
<span class="icon-[mdi--close] size-3 mr-1"></span>
|
104 |
+
Close
|
105 |
+
</Button>
|
106 |
+
</div>
|
107 |
+
</div>
|
108 |
+
</Sheet.Content>
|
109 |
+
</Sheet.Root>
|
src/lib/components/StatusCard.svelte
ADDED
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import * as Card from '$lib/components/ui/card';
|
3 |
+
import { Badge } from '$lib/components/ui/badge';
|
4 |
+
import { cn } from '$lib/utils';
|
5 |
+
|
6 |
+
interface Props {
|
7 |
+
title: string;
|
8 |
+
status?: 'active' | 'inactive' | 'warning' | 'error';
|
9 |
+
value?: string | number;
|
10 |
+
description?: string;
|
11 |
+
icon?: string;
|
12 |
+
class?: string;
|
13 |
+
children?: any;
|
14 |
+
}
|
15 |
+
|
16 |
+
let {
|
17 |
+
title,
|
18 |
+
status = 'inactive',
|
19 |
+
value,
|
20 |
+
description,
|
21 |
+
icon,
|
22 |
+
class: className = '',
|
23 |
+
children
|
24 |
+
}: Props = $props();
|
25 |
+
|
26 |
+
const statusConfig = {
|
27 |
+
active: { bg: 'bg-green-400/10', text: 'text-green-400', border: 'border-green-400/30' },
|
28 |
+
inactive: { bg: 'bg-slate-400/10', text: 'text-slate-400', border: 'border-slate-400/30' },
|
29 |
+
warning: { bg: 'bg-yellow-400/10', text: 'text-yellow-400', border: 'border-yellow-400/30' },
|
30 |
+
error: { bg: 'bg-red-400/10', text: 'text-red-400', border: 'border-red-400/30' }
|
31 |
+
};
|
32 |
+
|
33 |
+
const currentStatus = statusConfig[status];
|
34 |
+
</script>
|
35 |
+
|
36 |
+
<Card.Root class={cn("bg-slate-700/40 border-slate-600/50 hover:border-slate-500/70 transition-colors", className)}>
|
37 |
+
<Card.Content class="p-4">
|
38 |
+
<div class="flex items-start justify-between">
|
39 |
+
<div class="flex items-start gap-3">
|
40 |
+
{#if icon}
|
41 |
+
<div class={cn("p-2 rounded-md", currentStatus.bg)}>
|
42 |
+
<span class={cn(icon, "size-4", currentStatus.text)}></span>
|
43 |
+
</div>
|
44 |
+
{/if}
|
45 |
+
|
46 |
+
<div class="space-y-1">
|
47 |
+
<h4 class="text-sm font-medium text-slate-200 leading-none">
|
48 |
+
{title}
|
49 |
+
</h4>
|
50 |
+
{#if description}
|
51 |
+
<p class="text-xs text-slate-400">
|
52 |
+
{description}
|
53 |
+
</p>
|
54 |
+
{/if}
|
55 |
+
</div>
|
56 |
+
</div>
|
57 |
+
|
58 |
+
<div class="flex flex-col items-end gap-2">
|
59 |
+
<Badge
|
60 |
+
variant="outline"
|
61 |
+
class={cn("text-xs capitalize", currentStatus.bg, currentStatus.text, currentStatus.border)}
|
62 |
+
>
|
63 |
+
{status}
|
64 |
+
</Badge>
|
65 |
+
{#if value !== undefined}
|
66 |
+
<span class="text-lg font-mono font-semibold text-slate-100">
|
67 |
+
{value}
|
68 |
+
</span>
|
69 |
+
{/if}
|
70 |
+
</div>
|
71 |
+
</div>
|
72 |
+
|
73 |
+
{#if children}
|
74 |
+
<div class="mt-3 pt-3 border-t border-slate-600/50">
|
75 |
+
{@render children()}
|
76 |
+
</div>
|
77 |
+
{/if}
|
78 |
+
</Card.Content>
|
79 |
+
</Card.Root>
|
src/lib/components/panel/ControlPanel.svelte
CHANGED
@@ -9,6 +9,7 @@
|
|
9 |
|
10 |
<div class="space-y-6">
|
11 |
<!-- Robot Control Section -->
|
|
|
12 |
<RobotControlPanel />
|
13 |
|
14 |
</div>
|
|
|
9 |
|
10 |
<div class="space-y-6">
|
11 |
<!-- Robot Control Section -->
|
12 |
+
<!-- Note: Robot creation is now handled by the main ButtonBar -->
|
13 |
<RobotControlPanel />
|
14 |
|
15 |
</div>
|
src/lib/components/panel/RobotControlPanel.old.svelte
ADDED
@@ -0,0 +1,659 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { robotManager } from "$lib/robot/RobotManager.svelte";
|
3 |
+
import { robotUrdfConfigMap } from "$lib/configs/robotUrdfConfig";
|
4 |
+
import type { Robot } from "$lib/robot/Robot.svelte";
|
5 |
+
import { getApiBaseUrl, getWebSocketBaseUrl, getEnvironmentInfo } from "$lib/utils/config";
|
6 |
+
|
7 |
+
interface Props {}
|
8 |
+
|
9 |
+
let {}: Props = $props();
|
10 |
+
|
11 |
+
// Local state
|
12 |
+
let selectedRobotType = $state("so-arm100");
|
13 |
+
let isCreating = $state(false);
|
14 |
+
let error = $state<string | undefined>(undefined);
|
15 |
+
|
16 |
+
// Robot selection modal state
|
17 |
+
let showRobotSelectionModal = $state(false);
|
18 |
+
let availableServerRobots = $state<{ id: string; name: string; robot_type: string }[]>([]);
|
19 |
+
let selectedServerRobotId = $state<string>("");
|
20 |
+
let pendingLocalRobot: Robot | null = $state(null);
|
21 |
+
|
22 |
+
// Reactive values
|
23 |
+
const robots = $derived(robotManager.robots);
|
24 |
+
const robotsWithSlaves = $derived(robotManager.robotsWithSlaves.length);
|
25 |
+
const robotsWithMaster = $derived(robotManager.robotsWithMaster.length);
|
26 |
+
|
27 |
+
// Get URLs from configuration
|
28 |
+
const apiBaseUrl = getApiBaseUrl();
|
29 |
+
const wsBaseUrl = getWebSocketBaseUrl();
|
30 |
+
|
31 |
+
// Log environment info for debugging
|
32 |
+
console.log('Environment info:', getEnvironmentInfo());
|
33 |
+
|
34 |
+
async function createRobot() {
|
35 |
+
if (isCreating) return;
|
36 |
+
|
37 |
+
console.log('Creating robot...');
|
38 |
+
isCreating = true;
|
39 |
+
error = undefined;
|
40 |
+
|
41 |
+
try {
|
42 |
+
const urdfConfig = robotUrdfConfigMap[selectedRobotType];
|
43 |
+
if (!urdfConfig) {
|
44 |
+
throw new Error(`Unknown robot type: ${selectedRobotType}`);
|
45 |
+
}
|
46 |
+
|
47 |
+
const robotId = `robot-${Date.now()}`;
|
48 |
+
console.log('Creating robot with ID:', robotId, 'and config:', urdfConfig);
|
49 |
+
const robot = await robotManager.createRobot(robotId, urdfConfig);
|
50 |
+
console.log('Robot created successfully:', robot);
|
51 |
+
|
52 |
+
} catch (err) {
|
53 |
+
error = `Failed to create robot: ${err}`;
|
54 |
+
console.error('Robot creation failed:', err);
|
55 |
+
} finally {
|
56 |
+
isCreating = false;
|
57 |
+
}
|
58 |
+
}
|
59 |
+
|
60 |
+
// Master connection functions
|
61 |
+
async function connectDemoSequences(robot: Robot) {
|
62 |
+
try {
|
63 |
+
await robotManager.connectDemoSequences(robot.id);
|
64 |
+
} catch (err) {
|
65 |
+
error = `Failed to connect demo sequences: ${err}`;
|
66 |
+
console.error(err);
|
67 |
+
}
|
68 |
+
}
|
69 |
+
|
70 |
+
async function connectRemoteServerMaster(robot: Robot) {
|
71 |
+
try {
|
72 |
+
const config: import('$lib/types/robotDriver').MasterDriverConfig = {
|
73 |
+
type: "remote-server",
|
74 |
+
url: wsBaseUrl,
|
75 |
+
apiKey: undefined,
|
76 |
+
pollInterval: 100
|
77 |
+
};
|
78 |
+
await robotManager.connectMaster(robot.id, config);
|
79 |
+
} catch (err) {
|
80 |
+
error = `Failed to connect remote server: ${err}`;
|
81 |
+
console.error(err);
|
82 |
+
}
|
83 |
+
}
|
84 |
+
|
85 |
+
async function disconnectMaster(robot: Robot) {
|
86 |
+
try {
|
87 |
+
await robotManager.disconnectMaster(robot.id);
|
88 |
+
} catch (err) {
|
89 |
+
error = `Failed to disconnect master: ${err}`;
|
90 |
+
console.error(err);
|
91 |
+
}
|
92 |
+
}
|
93 |
+
|
94 |
+
// Slave connection functions
|
95 |
+
async function connectMockSlave(robot: Robot) {
|
96 |
+
try {
|
97 |
+
await robotManager.connectMockSlave(robot.id, 50);
|
98 |
+
} catch (err) {
|
99 |
+
error = `Failed to connect mock slave: ${err}`;
|
100 |
+
console.error(err);
|
101 |
+
}
|
102 |
+
}
|
103 |
+
|
104 |
+
async function connectUSBSlave(robot: Robot) {
|
105 |
+
try {
|
106 |
+
await robotManager.connectUSBSlave(robot.id);
|
107 |
+
} catch (err) {
|
108 |
+
error = `Failed to connect USB slave: ${err}`;
|
109 |
+
console.error(err);
|
110 |
+
}
|
111 |
+
}
|
112 |
+
|
113 |
+
async function connectRemoteServerSlave(robot: Robot) {
|
114 |
+
try {
|
115 |
+
// First, fetch available robots from the server
|
116 |
+
const response = await fetch(`${apiBaseUrl}/api/robots`);
|
117 |
+
|
118 |
+
if (!response.ok) {
|
119 |
+
throw new Error(`Server responded with ${response.status}: ${response.statusText}`);
|
120 |
+
}
|
121 |
+
|
122 |
+
const robots = await response.json();
|
123 |
+
|
124 |
+
if (robots.length === 0) {
|
125 |
+
error = "No robots available on the server. Create a robot on the server first.";
|
126 |
+
return;
|
127 |
+
}
|
128 |
+
|
129 |
+
// Show modal for robot selection
|
130 |
+
availableServerRobots = robots;
|
131 |
+
pendingLocalRobot = robot;
|
132 |
+
selectedServerRobotId = robots[0]?.id || "";
|
133 |
+
showRobotSelectionModal = true;
|
134 |
+
|
135 |
+
} catch (err) {
|
136 |
+
error = `Failed to fetch server robots: ${err}`;
|
137 |
+
console.error(err);
|
138 |
+
}
|
139 |
+
}
|
140 |
+
|
141 |
+
async function confirmRobotSelection() {
|
142 |
+
if (!pendingLocalRobot || !selectedServerRobotId) return;
|
143 |
+
|
144 |
+
try {
|
145 |
+
await robotManager.connectRemoteServerSlave(
|
146 |
+
pendingLocalRobot.id,
|
147 |
+
wsBaseUrl,
|
148 |
+
undefined,
|
149 |
+
selectedServerRobotId
|
150 |
+
);
|
151 |
+
|
152 |
+
// Close modal
|
153 |
+
showRobotSelectionModal = false;
|
154 |
+
pendingLocalRobot = null;
|
155 |
+
|
156 |
+
} catch (err) {
|
157 |
+
error = `Failed to connect remote server slave: ${err}`;
|
158 |
+
console.error(err);
|
159 |
+
}
|
160 |
+
}
|
161 |
+
|
162 |
+
function cancelRobotSelection() {
|
163 |
+
showRobotSelectionModal = false;
|
164 |
+
pendingLocalRobot = null;
|
165 |
+
selectedServerRobotId = "";
|
166 |
+
}
|
167 |
+
|
168 |
+
async function disconnectSlave(robot: Robot, slaveId: string) {
|
169 |
+
try {
|
170 |
+
await robotManager.disconnectSlave(robot.id, slaveId);
|
171 |
+
} catch (err) {
|
172 |
+
error = `Failed to disconnect slave: ${err}`;
|
173 |
+
console.error(err);
|
174 |
+
}
|
175 |
+
}
|
176 |
+
|
177 |
+
// Robot management
|
178 |
+
async function calibrateRobot(robot: Robot) {
|
179 |
+
try {
|
180 |
+
await robot.calibrateRobot();
|
181 |
+
} catch (err) {
|
182 |
+
error = `Failed to calibrate: ${err}`;
|
183 |
+
console.error(err);
|
184 |
+
}
|
185 |
+
}
|
186 |
+
|
187 |
+
async function moveToRest(robot: Robot) {
|
188 |
+
try {
|
189 |
+
await robot.moveToRestPosition();
|
190 |
+
} catch (err) {
|
191 |
+
error = `Failed to move to rest: ${err}`;
|
192 |
+
console.error(err);
|
193 |
+
}
|
194 |
+
}
|
195 |
+
|
196 |
+
function clearCalibration(robot: Robot) {
|
197 |
+
robot.clearCalibration();
|
198 |
+
}
|
199 |
+
|
200 |
+
async function removeRobot(robot: Robot) {
|
201 |
+
try {
|
202 |
+
await robotManager.removeRobot(robot.id);
|
203 |
+
} catch (err) {
|
204 |
+
error = `Failed to remove robot: ${err}`;
|
205 |
+
console.error(err);
|
206 |
+
}
|
207 |
+
}
|
208 |
+
|
209 |
+
function clearError() {
|
210 |
+
error = undefined;
|
211 |
+
}
|
212 |
+
|
213 |
+
// Helper functions
|
214 |
+
function getConnectionStatusText(robot: Robot): string {
|
215 |
+
const hasActiveMaster = robot.controlState.hasActiveMaster;
|
216 |
+
const connectedSlaves = robot.connectedSlaves.length;
|
217 |
+
const totalSlaves = robot.slaves.length;
|
218 |
+
|
219 |
+
if (hasActiveMaster && connectedSlaves > 0) {
|
220 |
+
return `Master + ${connectedSlaves}/${totalSlaves} Slaves`;
|
221 |
+
} else if (hasActiveMaster) {
|
222 |
+
return `Master Only`;
|
223 |
+
} else if (connectedSlaves > 0) {
|
224 |
+
return `${connectedSlaves}/${totalSlaves} Slaves`;
|
225 |
+
} else {
|
226 |
+
return "Manual Control";
|
227 |
+
}
|
228 |
+
}
|
229 |
+
|
230 |
+
function getConnectionStatusColor(robot: Robot): string {
|
231 |
+
const hasActiveMaster = robot.controlState.hasActiveMaster;
|
232 |
+
const connectedSlaves = robot.connectedSlaves.length;
|
233 |
+
|
234 |
+
if (hasActiveMaster && connectedSlaves > 0) {
|
235 |
+
return "green"; // Full master-slave setup
|
236 |
+
} else if (hasActiveMaster || connectedSlaves > 0) {
|
237 |
+
return "yellow"; // Partial connection
|
238 |
+
} else {
|
239 |
+
return "red"; // No connections
|
240 |
+
}
|
241 |
+
}
|
242 |
+
|
243 |
+
async function connectUSBMaster(robot: Robot) {
|
244 |
+
try {
|
245 |
+
await robotManager.connectUSBMaster(robot.id, {
|
246 |
+
pollInterval: 200,
|
247 |
+
smoothing: true
|
248 |
+
});
|
249 |
+
} catch (err) {
|
250 |
+
error = `Failed to connect USB master: ${err}`;
|
251 |
+
console.error(err);
|
252 |
+
}
|
253 |
+
}
|
254 |
+
</script>
|
255 |
+
|
256 |
+
<div class="space-y-6 p-4">
|
257 |
+
<div class="flex items-center justify-between">
|
258 |
+
<h2 class="text-xl font-bold text-slate-100">Robot Control - Master/Slave Architecture</h2>
|
259 |
+
<div class="text-sm text-slate-400">
|
260 |
+
{robots.length} robots, {robotsWithMaster} with masters, {robotsWithSlaves} with slaves
|
261 |
+
</div>
|
262 |
+
</div>
|
263 |
+
|
264 |
+
<!-- Error Display -->
|
265 |
+
{#if error}
|
266 |
+
<div class="bg-red-500/20 border border-red-500 rounded-lg p-3 flex items-center justify-between">
|
267 |
+
<span class="text-red-200 text-sm">{error}</span>
|
268 |
+
<button
|
269 |
+
onclick={clearError}
|
270 |
+
class="text-red-200 hover:text-white"
|
271 |
+
>×</button>
|
272 |
+
</div>
|
273 |
+
{/if}
|
274 |
+
|
275 |
+
<!-- Create Robot Section (Minimized - main creation now in ButtonBar) -->
|
276 |
+
<div class="bg-slate-800 rounded-lg p-3 space-y-3">
|
277 |
+
<div class="flex items-center justify-between">
|
278 |
+
<h3 class="text-md font-semibold text-slate-100">Advanced Robot Creation</h3>
|
279 |
+
<div class="text-xs text-slate-400">Use ButtonBar for quick SO-100 creation</div>
|
280 |
+
</div>
|
281 |
+
|
282 |
+
<div class="flex gap-3">
|
283 |
+
<select
|
284 |
+
bind:value={selectedRobotType}
|
285 |
+
class="flex-1 px-3 py-2 bg-slate-700 border border-slate-600 rounded-md text-slate-100 text-sm"
|
286 |
+
>
|
287 |
+
{#each Object.keys(robotUrdfConfigMap) as robotType}
|
288 |
+
<option value={robotType}>{robotType}</option>
|
289 |
+
{/each}
|
290 |
+
</select>
|
291 |
+
|
292 |
+
<button
|
293 |
+
onclick={createRobot}
|
294 |
+
disabled={isCreating}
|
295 |
+
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-slate-600 disabled:cursor-not-allowed text-white rounded-md transition-colors text-sm"
|
296 |
+
>
|
297 |
+
{isCreating ? "Creating..." : "Create"}
|
298 |
+
</button>
|
299 |
+
</div>
|
300 |
+
</div>
|
301 |
+
|
302 |
+
<!-- Robots List -->
|
303 |
+
<div class="space-y-3">
|
304 |
+
{#each robots as robot (robot.id)}
|
305 |
+
<div class="bg-slate-800 rounded-lg p-4">
|
306 |
+
<div class="flex items-center justify-between mb-3">
|
307 |
+
<div>
|
308 |
+
<h4 class="font-semibold text-slate-100">{robot.id}</h4>
|
309 |
+
<div class="text-sm text-slate-400">
|
310 |
+
Status:
|
311 |
+
<span class="text-{getConnectionStatusColor(robot)}-400">
|
312 |
+
{getConnectionStatusText(robot)}
|
313 |
+
</span>
|
314 |
+
{#if robot.controlState.lastCommandSource !== "none"}
|
315 |
+
<span class="text-blue-400 ml-2">• Last: {robot.controlState.lastCommandSource}</span>
|
316 |
+
{/if}
|
317 |
+
{#if robot.isCalibrated}
|
318 |
+
<span class="text-green-400 ml-2">• Calibrated</span>
|
319 |
+
{/if}
|
320 |
+
</div>
|
321 |
+
</div>
|
322 |
+
|
323 |
+
<button
|
324 |
+
onclick={() => removeRobot(robot)}
|
325 |
+
class="px-3 py-1 bg-red-600 hover:bg-red-700 text-white text-sm rounded transition-colors"
|
326 |
+
>
|
327 |
+
Remove
|
328 |
+
</button>
|
329 |
+
</div>
|
330 |
+
|
331 |
+
<!-- Master Controls -->
|
332 |
+
<div class="mb-4 p-3 bg-orange-500/10 border border-orange-500/30 rounded-lg">
|
333 |
+
<div class="text-sm text-orange-200 mb-2">
|
334 |
+
<strong>Masters (Control Sources)</strong>
|
335 |
+
{#if robot.controlState.hasActiveMaster}
|
336 |
+
- <span class="text-green-400">Connected: {robot.controlState.masterName}</span>
|
337 |
+
{:else}
|
338 |
+
- <span class="text-slate-400">None Connected</span>
|
339 |
+
{/if}
|
340 |
+
</div>
|
341 |
+
|
342 |
+
<div class="flex flex-wrap gap-2">
|
343 |
+
<button
|
344 |
+
class="px-3 py-1.5 bg-orange-500 text-white rounded text-sm hover:bg-orange-600 transition-colors disabled:bg-slate-600 disabled:cursor-not-allowed"
|
345 |
+
onclick={() => connectDemoSequences(robot)}
|
346 |
+
disabled={robot.controlState.hasActiveMaster}
|
347 |
+
>
|
348 |
+
Demo Sequences
|
349 |
+
</button>
|
350 |
+
|
351 |
+
<button
|
352 |
+
class="px-3 py-1.5 bg-purple-500 text-white rounded text-sm hover:bg-purple-600 transition-colors disabled:bg-slate-600 disabled:cursor-not-allowed"
|
353 |
+
onclick={() => connectRemoteServerMaster(robot)}
|
354 |
+
disabled={robot.controlState.hasActiveMaster}
|
355 |
+
>
|
356 |
+
Connect Remote Server
|
357 |
+
</button>
|
358 |
+
|
359 |
+
<button
|
360 |
+
class="px-3 py-1.5 bg-blue-500 text-white rounded text-sm hover:bg-blue-600 transition-colors disabled:bg-slate-600 disabled:cursor-not-allowed"
|
361 |
+
onclick={() => connectUSBMaster(robot)}
|
362 |
+
disabled={robot.controlState.hasActiveMaster}
|
363 |
+
>
|
364 |
+
Connect USB Master
|
365 |
+
</button>
|
366 |
+
|
367 |
+
{#if robot.controlState.hasActiveMaster}
|
368 |
+
<button
|
369 |
+
class="px-3 py-1.5 bg-red-500 text-white rounded text-sm hover:bg-red-600 transition-colors"
|
370 |
+
onclick={() => disconnectMaster(robot)}
|
371 |
+
>
|
372 |
+
Disconnect Master
|
373 |
+
</button>
|
374 |
+
{/if}
|
375 |
+
</div>
|
376 |
+
</div>
|
377 |
+
|
378 |
+
<!-- Slave Controls -->
|
379 |
+
<div class="mb-4 p-3 bg-blue-500/10 border border-blue-500/30 rounded-lg">
|
380 |
+
<div class="text-sm text-blue-200 mb-2">
|
381 |
+
<strong>Slaves (Execution Targets)</strong>
|
382 |
+
- <span class="text-green-400">{robot.connectedSlaves.length} Connected</span>
|
383 |
+
/ <span class="text-slate-400">{robot.slaves.length} Total</span>
|
384 |
+
</div>
|
385 |
+
|
386 |
+
<div class="flex gap-2 mb-2">
|
387 |
+
<button
|
388 |
+
onclick={() => connectMockSlave(robot)}
|
389 |
+
class="px-3 py-1 bg-yellow-600 hover:bg-yellow-700 text-white text-sm rounded transition-colors"
|
390 |
+
>
|
391 |
+
Add Mock Slave
|
392 |
+
</button>
|
393 |
+
<button
|
394 |
+
onclick={() => connectUSBSlave(robot)}
|
395 |
+
class="px-3 py-1 bg-green-600 hover:bg-green-700 text-white text-sm rounded transition-colors"
|
396 |
+
>
|
397 |
+
Add USB Slave
|
398 |
+
</button>
|
399 |
+
<button
|
400 |
+
onclick={() => connectRemoteServerSlave(robot)}
|
401 |
+
class="px-3 py-1 bg-purple-600 hover:bg-purple-700 text-white text-sm rounded transition-colors"
|
402 |
+
>
|
403 |
+
Add Remote Server Slave
|
404 |
+
</button>
|
405 |
+
</div>
|
406 |
+
|
407 |
+
{#if robot.slaves.length > 0}
|
408 |
+
<div class="text-xs text-blue-300">
|
409 |
+
<strong>Connected Slaves:</strong>
|
410 |
+
{#each robot.slaves as slave}
|
411 |
+
<div class="flex items-center justify-between mt-1">
|
412 |
+
<span>{slave.name} ({slave.id})</span>
|
413 |
+
<button
|
414 |
+
onclick={() => disconnectSlave(robot, slave.id)}
|
415 |
+
class="px-2 py-1 bg-red-500 hover:bg-red-600 text-white text-xs rounded"
|
416 |
+
>
|
417 |
+
Remove
|
418 |
+
</button>
|
419 |
+
</div>
|
420 |
+
{/each}
|
421 |
+
</div>
|
422 |
+
{/if}
|
423 |
+
</div>
|
424 |
+
|
425 |
+
<!-- Calibration Section (for USB slaves) -->
|
426 |
+
{#if robot.connectedSlaves.some(slave => slave.name.includes("USB"))}
|
427 |
+
<div class="mb-3 p-3 bg-orange-500/10 border border-orange-500/30 rounded-lg">
|
428 |
+
<div class="text-sm text-orange-200 mb-2">
|
429 |
+
<strong>USB Robot Calibration</strong>
|
430 |
+
</div>
|
431 |
+
{#if !robot.isCalibrated}
|
432 |
+
<div class="text-xs text-orange-300 mb-2">
|
433 |
+
1. Manually position your robot to match the digital twin's rest pose<br>
|
434 |
+
2. Click "Calibrate" when positioned correctly
|
435 |
+
</div>
|
436 |
+
<div class="flex gap-2">
|
437 |
+
<button
|
438 |
+
onclick={() => moveToRest(robot)}
|
439 |
+
class="px-3 py-1 bg-blue-600 hover:bg-blue-700 text-white text-sm rounded transition-colors"
|
440 |
+
>
|
441 |
+
Show Rest Pose
|
442 |
+
</button>
|
443 |
+
<button
|
444 |
+
onclick={() => calibrateRobot(robot)}
|
445 |
+
class="px-3 py-1 bg-green-600 hover:bg-green-700 text-white text-sm rounded transition-colors"
|
446 |
+
>
|
447 |
+
Calibrate
|
448 |
+
</button>
|
449 |
+
</div>
|
450 |
+
{:else}
|
451 |
+
<div class="text-xs text-green-300 mb-2">
|
452 |
+
✓ Robot calibrated at {robot.calibrationState.calibrationTime?.toLocaleTimeString()}
|
453 |
+
</div>
|
454 |
+
<button
|
455 |
+
onclick={() => clearCalibration(robot)}
|
456 |
+
class="px-3 py-1 bg-gray-600 hover:bg-gray-700 text-white text-sm rounded transition-colors"
|
457 |
+
>
|
458 |
+
Clear Calibration
|
459 |
+
</button>
|
460 |
+
{/if}
|
461 |
+
</div>
|
462 |
+
{/if}
|
463 |
+
|
464 |
+
<!-- Manual Joint Controls (only when no master) -->
|
465 |
+
{#if robot.manualControlEnabled}
|
466 |
+
<div class="space-y-3">
|
467 |
+
<h5 class="text-sm font-medium text-slate-300">Manual Control ({robot.activeJoints.length} active joints)</h5>
|
468 |
+
{#each robot.activeJoints as joint}
|
469 |
+
<div class="joint-control">
|
470 |
+
<div class="joint-header">
|
471 |
+
<span class="joint-name">{joint.name}</span>
|
472 |
+
<div class="joint-values">
|
473 |
+
<span class="virtual-value">{joint.virtualValue.toFixed(0)}°</span>
|
474 |
+
{#if joint.realValue !== undefined}
|
475 |
+
<span class="real-value" title="Real robot position">
|
476 |
+
Real: {joint.realValue.toFixed(0)}°
|
477 |
+
</span>
|
478 |
+
{:else}
|
479 |
+
<span class="real-value disconnected">N/A</span>
|
480 |
+
{/if}
|
481 |
+
</div>
|
482 |
+
</div>
|
483 |
+
<input
|
484 |
+
type="range"
|
485 |
+
min="-180"
|
486 |
+
max="180"
|
487 |
+
step="1"
|
488 |
+
value={joint.virtualValue}
|
489 |
+
oninput={(e) => {
|
490 |
+
const target = e.target as HTMLInputElement;
|
491 |
+
robot.updateJointValue(joint.name, parseFloat(target.value));
|
492 |
+
}}
|
493 |
+
class="joint-slider"
|
494 |
+
/>
|
495 |
+
</div>
|
496 |
+
{/each}
|
497 |
+
|
498 |
+
{#if robot.activeJoints.length === 0}
|
499 |
+
<div class="text-sm text-slate-500 italic">No active joints</div>
|
500 |
+
{/if}
|
501 |
+
</div>
|
502 |
+
{:else}
|
503 |
+
<div class="p-3 bg-purple-500/10 border border-purple-500/30 rounded-lg">
|
504 |
+
<div class="text-sm text-purple-200">
|
505 |
+
🎮 <strong>Master Control Active</strong><br>
|
506 |
+
<span class="text-xs text-purple-300">
|
507 |
+
Robot is controlled by: {robot.controlState.masterName}<br>
|
508 |
+
Manual controls are disabled. Disconnect master to regain manual control.
|
509 |
+
</span>
|
510 |
+
</div>
|
511 |
+
</div>
|
512 |
+
{/if}
|
513 |
+
</div>
|
514 |
+
{/each}
|
515 |
+
|
516 |
+
{#if robots.length === 0}
|
517 |
+
<div class="text-center text-slate-500 py-8">
|
518 |
+
No robots created yet. Create one above to get started with the master-slave architecture.
|
519 |
+
</div>
|
520 |
+
{/if}
|
521 |
+
</div>
|
522 |
+
</div>
|
523 |
+
|
524 |
+
<!-- Robot Selection Modal -->
|
525 |
+
{#if showRobotSelectionModal}
|
526 |
+
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
527 |
+
<div class="bg-slate-800 rounded-lg p-6 w-96 max-w-full mx-4">
|
528 |
+
<h3 class="text-lg font-semibold text-slate-100 mb-4">Select Server Robot</h3>
|
529 |
+
|
530 |
+
<div class="mb-4">
|
531 |
+
<label class="block text-sm font-medium text-slate-300 mb-2">
|
532 |
+
Available robots on server:
|
533 |
+
</label>
|
534 |
+
<select
|
535 |
+
bind:value={selectedServerRobotId}
|
536 |
+
class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-md text-slate-100"
|
537 |
+
>
|
538 |
+
{#each availableServerRobots as serverRobot}
|
539 |
+
<option value={serverRobot.id}>
|
540 |
+
{serverRobot.name} ({serverRobot.id}) - {serverRobot.robot_type}
|
541 |
+
</option>
|
542 |
+
{/each}
|
543 |
+
</select>
|
544 |
+
</div>
|
545 |
+
|
546 |
+
<div class="text-sm text-slate-400 mb-4">
|
547 |
+
This will connect your local robot "{pendingLocalRobot?.id}" as a slave to
|
548 |
+
receive commands from the selected server robot.
|
549 |
+
</div>
|
550 |
+
|
551 |
+
<div class="flex gap-3 justify-end">
|
552 |
+
<button
|
553 |
+
onclick={cancelRobotSelection}
|
554 |
+
class="px-4 py-2 bg-slate-600 hover:bg-slate-700 text-white rounded-md transition-colors"
|
555 |
+
>
|
556 |
+
Cancel
|
557 |
+
</button>
|
558 |
+
<button
|
559 |
+
onclick={confirmRobotSelection}
|
560 |
+
class="px-4 py-2 bg-purple-600 hover:bg-purple-700 text-white rounded-md transition-colors"
|
561 |
+
disabled={!selectedServerRobotId}
|
562 |
+
>
|
563 |
+
Connect Slave
|
564 |
+
</button>
|
565 |
+
</div>
|
566 |
+
</div>
|
567 |
+
</div>
|
568 |
+
{/if}
|
569 |
+
|
570 |
+
<style>
|
571 |
+
.joint-control {
|
572 |
+
background: rgba(71, 85, 105, 0.3);
|
573 |
+
border-radius: 8px;
|
574 |
+
padding: 12px;
|
575 |
+
border: 1px solid rgba(71, 85, 105, 0.5);
|
576 |
+
}
|
577 |
+
|
578 |
+
.joint-header {
|
579 |
+
display: flex;
|
580 |
+
justify-content: space-between;
|
581 |
+
align-items: center;
|
582 |
+
margin-bottom: 8px;
|
583 |
+
}
|
584 |
+
|
585 |
+
.joint-name {
|
586 |
+
font-weight: 500;
|
587 |
+
color: #e2e8f0;
|
588 |
+
font-size: 14px;
|
589 |
+
}
|
590 |
+
|
591 |
+
.joint-values {
|
592 |
+
display: flex;
|
593 |
+
gap: 12px;
|
594 |
+
font-size: 12px;
|
595 |
+
}
|
596 |
+
|
597 |
+
.virtual-value {
|
598 |
+
color: #60a5fa;
|
599 |
+
font-weight: 500;
|
600 |
+
}
|
601 |
+
|
602 |
+
.real-value {
|
603 |
+
color: #34d399;
|
604 |
+
font-weight: 500;
|
605 |
+
}
|
606 |
+
|
607 |
+
.real-value.disconnected {
|
608 |
+
color: #f87171;
|
609 |
+
}
|
610 |
+
|
611 |
+
.joint-slider {
|
612 |
+
width: 100%;
|
613 |
+
height: 6px;
|
614 |
+
background: #374151;
|
615 |
+
border-radius: 3px;
|
616 |
+
outline: none;
|
617 |
+
cursor: pointer;
|
618 |
+
appearance: none;
|
619 |
+
}
|
620 |
+
|
621 |
+
.joint-slider::-webkit-slider-thumb {
|
622 |
+
appearance: none;
|
623 |
+
width: 18px;
|
624 |
+
height: 18px;
|
625 |
+
border-radius: 50%;
|
626 |
+
background: #3b82f6;
|
627 |
+
cursor: pointer;
|
628 |
+
border: 2px solid #1e293b;
|
629 |
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
630 |
+
transition: all 0.15s ease;
|
631 |
+
}
|
632 |
+
|
633 |
+
.joint-slider::-webkit-slider-thumb:hover {
|
634 |
+
background: #2563eb;
|
635 |
+
transform: scale(1.1);
|
636 |
+
}
|
637 |
+
|
638 |
+
.joint-slider::-moz-range-thumb {
|
639 |
+
width: 18px;
|
640 |
+
height: 18px;
|
641 |
+
border-radius: 50%;
|
642 |
+
background: #3b82f6;
|
643 |
+
cursor: pointer;
|
644 |
+
border: 2px solid #1e293b;
|
645 |
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
646 |
+
transition: all 0.15s ease;
|
647 |
+
}
|
648 |
+
|
649 |
+
.joint-slider::-moz-range-track {
|
650 |
+
height: 6px;
|
651 |
+
background: #374151;
|
652 |
+
border-radius: 3px;
|
653 |
+
border: none;
|
654 |
+
}
|
655 |
+
|
656 |
+
.joint-slider:focus {
|
657 |
+
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
|
658 |
+
}
|
659 |
+
</style>
|
src/lib/components/panel/RobotControlPanel.svelte
CHANGED
@@ -1,16 +1,20 @@
|
|
1 |
<script lang="ts">
|
2 |
import { robotManager } from "$lib/robot/RobotManager.svelte";
|
3 |
-
import { robotUrdfConfigMap } from "$lib/configs/robotUrdfConfig";
|
4 |
import type { Robot } from "$lib/robot/Robot.svelte";
|
5 |
import { getApiBaseUrl, getWebSocketBaseUrl, getEnvironmentInfo } from "$lib/utils/config";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
|
7 |
interface Props {}
|
8 |
|
9 |
let {}: Props = $props();
|
10 |
|
11 |
// Local state
|
12 |
-
let selectedRobotType = $state("so-arm100");
|
13 |
-
let isCreating = $state(false);
|
14 |
let error = $state<string | undefined>(undefined);
|
15 |
|
16 |
// Robot selection modal state
|
@@ -31,36 +35,11 @@
|
|
31 |
// Log environment info for debugging
|
32 |
console.log('Environment info:', getEnvironmentInfo());
|
33 |
|
34 |
-
async function createRobot() {
|
35 |
-
if (isCreating) return;
|
36 |
-
|
37 |
-
console.log('Creating robot...');
|
38 |
-
isCreating = true;
|
39 |
-
error = undefined;
|
40 |
-
|
41 |
-
try {
|
42 |
-
const urdfConfig = robotUrdfConfigMap[selectedRobotType];
|
43 |
-
if (!urdfConfig) {
|
44 |
-
throw new Error(`Unknown robot type: ${selectedRobotType}`);
|
45 |
-
}
|
46 |
-
|
47 |
-
const robotId = `robot-${Date.now()}`;
|
48 |
-
console.log('Creating robot with ID:', robotId, 'and config:', urdfConfig);
|
49 |
-
const robot = await robotManager.createRobot(robotId, urdfConfig);
|
50 |
-
console.log('Robot created successfully:', robot);
|
51 |
-
|
52 |
-
} catch (err) {
|
53 |
-
error = `Failed to create robot: ${err}`;
|
54 |
-
console.error('Robot creation failed:', err);
|
55 |
-
} finally {
|
56 |
-
isCreating = false;
|
57 |
-
}
|
58 |
-
}
|
59 |
-
|
60 |
// Master connection functions
|
61 |
async function connectDemoSequences(robot: Robot) {
|
62 |
try {
|
63 |
await robotManager.connectDemoSequences(robot.id);
|
|
|
64 |
} catch (err) {
|
65 |
error = `Failed to connect demo sequences: ${err}`;
|
66 |
console.error(err);
|
@@ -76,15 +55,30 @@
|
|
76 |
pollInterval: 100
|
77 |
};
|
78 |
await robotManager.connectMaster(robot.id, config);
|
|
|
79 |
} catch (err) {
|
80 |
error = `Failed to connect remote server: ${err}`;
|
81 |
console.error(err);
|
82 |
}
|
83 |
}
|
84 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
85 |
async function disconnectMaster(robot: Robot) {
|
86 |
try {
|
87 |
await robotManager.disconnectMaster(robot.id);
|
|
|
88 |
} catch (err) {
|
89 |
error = `Failed to disconnect master: ${err}`;
|
90 |
console.error(err);
|
@@ -95,6 +89,7 @@
|
|
95 |
async function connectMockSlave(robot: Robot) {
|
96 |
try {
|
97 |
await robotManager.connectMockSlave(robot.id, 50);
|
|
|
98 |
} catch (err) {
|
99 |
error = `Failed to connect mock slave: ${err}`;
|
100 |
console.error(err);
|
@@ -104,6 +99,7 @@
|
|
104 |
async function connectUSBSlave(robot: Robot) {
|
105 |
try {
|
106 |
await robotManager.connectUSBSlave(robot.id);
|
|
|
107 |
} catch (err) {
|
108 |
error = `Failed to connect USB slave: ${err}`;
|
109 |
console.error(err);
|
@@ -152,6 +148,7 @@
|
|
152 |
// Close modal
|
153 |
showRobotSelectionModal = false;
|
154 |
pendingLocalRobot = null;
|
|
|
155 |
|
156 |
} catch (err) {
|
157 |
error = `Failed to connect remote server slave: ${err}`;
|
@@ -168,6 +165,7 @@
|
|
168 |
async function disconnectSlave(robot: Robot, slaveId: string) {
|
169 |
try {
|
170 |
await robotManager.disconnectSlave(robot.id, slaveId);
|
|
|
171 |
} catch (err) {
|
172 |
error = `Failed to disconnect slave: ${err}`;
|
173 |
console.error(err);
|
@@ -178,6 +176,7 @@
|
|
178 |
async function calibrateRobot(robot: Robot) {
|
179 |
try {
|
180 |
await robot.calibrateRobot();
|
|
|
181 |
} catch (err) {
|
182 |
error = `Failed to calibrate: ${err}`;
|
183 |
console.error(err);
|
@@ -187,6 +186,7 @@
|
|
187 |
async function moveToRest(robot: Robot) {
|
188 |
try {
|
189 |
await robot.moveToRestPosition();
|
|
|
190 |
} catch (err) {
|
191 |
error = `Failed to move to rest: ${err}`;
|
192 |
console.error(err);
|
@@ -200,6 +200,7 @@
|
|
200 |
async function removeRobot(robot: Robot) {
|
201 |
try {
|
202 |
await robotManager.removeRobot(robot.id);
|
|
|
203 |
} catch (err) {
|
204 |
error = `Failed to remove robot: ${err}`;
|
205 |
console.error(err);
|
@@ -227,395 +228,459 @@
|
|
227 |
}
|
228 |
}
|
229 |
|
230 |
-
function
|
231 |
const hasActiveMaster = robot.controlState.hasActiveMaster;
|
232 |
const connectedSlaves = robot.connectedSlaves.length;
|
233 |
|
234 |
if (hasActiveMaster && connectedSlaves > 0) {
|
235 |
-
return "
|
236 |
} else if (hasActiveMaster || connectedSlaves > 0) {
|
237 |
-
return "
|
238 |
} else {
|
239 |
-
return "
|
240 |
-
}
|
241 |
-
}
|
242 |
-
|
243 |
-
async function connectUSBMaster(robot: Robot) {
|
244 |
-
try {
|
245 |
-
await robotManager.connectUSBMaster(robot.id, {
|
246 |
-
pollInterval: 200,
|
247 |
-
smoothing: true
|
248 |
-
});
|
249 |
-
} catch (err) {
|
250 |
-
error = `Failed to connect USB master: ${err}`;
|
251 |
-
console.error(err);
|
252 |
}
|
253 |
}
|
254 |
</script>
|
255 |
|
256 |
-
<div class="space-y-6
|
257 |
-
|
258 |
-
|
259 |
-
<div
|
260 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
261 |
</div>
|
262 |
</div>
|
263 |
|
264 |
<!-- Error Display -->
|
265 |
{#if error}
|
266 |
-
<
|
267 |
-
<span class="
|
268 |
-
<
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
|
|
|
|
|
|
273 |
{/if}
|
274 |
|
275 |
-
<!-- Create Robot Section -->
|
276 |
-
<div class="bg-slate-800 rounded-lg p-4 space-y-4">
|
277 |
-
<h3 class="text-lg font-semibold text-slate-100">Create Robot</h3>
|
278 |
-
|
279 |
-
<div class="flex gap-3">
|
280 |
-
<select
|
281 |
-
bind:value={selectedRobotType}
|
282 |
-
class="flex-1 px-3 py-2 bg-slate-700 border border-slate-600 rounded-md text-slate-100"
|
283 |
-
>
|
284 |
-
{#each Object.keys(robotUrdfConfigMap) as robotType}
|
285 |
-
<option value={robotType}>{robotType}</option>
|
286 |
-
{/each}
|
287 |
-
</select>
|
288 |
-
|
289 |
-
<button
|
290 |
-
onclick={createRobot}
|
291 |
-
disabled={isCreating}
|
292 |
-
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-slate-600 disabled:cursor-not-allowed text-white rounded-md transition-colors"
|
293 |
-
>
|
294 |
-
{isCreating ? "Creating..." : "Create Robot"}
|
295 |
-
</button>
|
296 |
-
</div>
|
297 |
-
</div>
|
298 |
-
|
299 |
<!-- Robots List -->
|
300 |
-
|
301 |
-
|
302 |
-
<
|
303 |
-
<div class="flex items-center
|
304 |
-
<
|
305 |
-
|
306 |
-
|
307 |
-
|
308 |
-
|
309 |
-
{getConnectionStatusText(robot)}
|
310 |
-
</span>
|
311 |
-
{#if robot.controlState.lastCommandSource !== "none"}
|
312 |
-
<span class="text-blue-400 ml-2">• Last: {robot.controlState.lastCommandSource}</span>
|
313 |
-
{/if}
|
314 |
-
{#if robot.isCalibrated}
|
315 |
-
<span class="text-green-400 ml-2">• Calibrated</span>
|
316 |
-
{/if}
|
317 |
-
</div>
|
318 |
-
</div>
|
319 |
-
|
320 |
-
<button
|
321 |
-
onclick={() => removeRobot(robot)}
|
322 |
-
class="px-3 py-1 bg-red-600 hover:bg-red-700 text-white text-sm rounded transition-colors"
|
323 |
-
>
|
324 |
-
Remove
|
325 |
-
</button>
|
326 |
</div>
|
327 |
-
|
328 |
-
|
329 |
-
|
330 |
-
|
331 |
-
|
332 |
-
|
333 |
-
|
334 |
-
|
335 |
-
|
336 |
-
|
337 |
-
|
338 |
-
|
339 |
-
|
340 |
-
|
341 |
-
|
342 |
-
|
343 |
-
|
344 |
-
|
345 |
-
|
346 |
-
|
347 |
-
|
348 |
-
|
349 |
-
|
350 |
-
|
351 |
-
|
352 |
-
|
353 |
-
|
354 |
-
|
355 |
-
|
356 |
-
<button
|
357 |
-
class="px-3 py-1.5 bg-blue-500 text-white rounded text-sm hover:bg-blue-600 transition-colors disabled:bg-slate-600 disabled:cursor-not-allowed"
|
358 |
-
onclick={() => connectUSBMaster(robot)}
|
359 |
-
disabled={robot.controlState.hasActiveMaster}
|
360 |
-
>
|
361 |
-
Connect USB Master
|
362 |
-
</button>
|
363 |
-
|
364 |
-
{#if robot.controlState.hasActiveMaster}
|
365 |
-
<button
|
366 |
-
class="px-3 py-1.5 bg-red-500 text-white rounded text-sm hover:bg-red-600 transition-colors"
|
367 |
-
onclick={() => disconnectMaster(robot)}
|
368 |
-
>
|
369 |
-
Disconnect Master
|
370 |
-
</button>
|
371 |
-
{/if}
|
372 |
-
</div>
|
373 |
-
</div>
|
374 |
-
|
375 |
-
<!-- Slave Controls -->
|
376 |
-
<div class="mb-4 p-3 bg-blue-500/10 border border-blue-500/30 rounded-lg">
|
377 |
-
<div class="text-sm text-blue-200 mb-2">
|
378 |
-
<strong>Slaves (Execution Targets)</strong>
|
379 |
-
- <span class="text-green-400">{robot.connectedSlaves.length} Connected</span>
|
380 |
-
/ <span class="text-slate-400">{robot.slaves.length} Total</span>
|
381 |
-
</div>
|
382 |
-
|
383 |
-
<div class="flex gap-2 mb-2">
|
384 |
-
<button
|
385 |
-
onclick={() => connectMockSlave(robot)}
|
386 |
-
class="px-3 py-1 bg-yellow-600 hover:bg-yellow-700 text-white text-sm rounded transition-colors"
|
387 |
-
>
|
388 |
-
Add Mock Slave
|
389 |
-
</button>
|
390 |
-
<button
|
391 |
-
onclick={() => connectUSBSlave(robot)}
|
392 |
-
class="px-3 py-1 bg-green-600 hover:bg-green-700 text-white text-sm rounded transition-colors"
|
393 |
-
>
|
394 |
-
Add USB Slave
|
395 |
-
</button>
|
396 |
-
<button
|
397 |
-
onclick={() => connectRemoteServerSlave(robot)}
|
398 |
-
class="px-3 py-1 bg-purple-600 hover:bg-purple-700 text-white text-sm rounded transition-colors"
|
399 |
-
>
|
400 |
-
Add Remote Server Slave
|
401 |
-
</button>
|
402 |
-
</div>
|
403 |
-
|
404 |
-
{#if robot.slaves.length > 0}
|
405 |
-
<div class="text-xs text-blue-300">
|
406 |
-
<strong>Connected Slaves:</strong>
|
407 |
-
{#each robot.slaves as slave}
|
408 |
-
<div class="flex items-center justify-between mt-1">
|
409 |
-
<span>{slave.name} ({slave.id})</span>
|
410 |
-
<button
|
411 |
-
onclick={() => disconnectSlave(robot, slave.id)}
|
412 |
-
class="px-2 py-1 bg-red-500 hover:bg-red-600 text-white text-xs rounded"
|
413 |
-
>
|
414 |
-
Remove
|
415 |
-
</button>
|
416 |
</div>
|
417 |
-
{/each}
|
418 |
-
</div>
|
419 |
-
{/if}
|
420 |
-
</div>
|
421 |
-
|
422 |
-
<!-- Calibration Section (for USB slaves) -->
|
423 |
-
{#if robot.connectedSlaves.some(slave => slave.name.includes("USB"))}
|
424 |
-
<div class="mb-3 p-3 bg-orange-500/10 border border-orange-500/30 rounded-lg">
|
425 |
-
<div class="text-sm text-orange-200 mb-2">
|
426 |
-
<strong>USB Robot Calibration</strong>
|
427 |
-
</div>
|
428 |
-
{#if !robot.isCalibrated}
|
429 |
-
<div class="text-xs text-orange-300 mb-2">
|
430 |
-
1. Manually position your robot to match the digital twin's rest pose<br>
|
431 |
-
2. Click "Calibrate" when positioned correctly
|
432 |
</div>
|
433 |
-
|
434 |
-
|
435 |
-
|
436 |
-
|
437 |
-
|
438 |
-
|
439 |
-
|
440 |
-
|
441 |
-
|
442 |
-
|
443 |
-
|
444 |
-
|
445 |
-
|
446 |
-
|
447 |
-
|
448 |
-
|
449 |
-
|
450 |
-
|
451 |
-
|
452 |
-
|
453 |
-
|
454 |
-
|
455 |
-
Clear Calibration
|
456 |
-
</button>
|
457 |
-
{/if}
|
458 |
-
</div>
|
459 |
-
{/if}
|
460 |
-
|
461 |
-
<!-- Manual Joint Controls (only when no master) -->
|
462 |
-
{#if robot.manualControlEnabled}
|
463 |
-
<div class="space-y-3">
|
464 |
-
<h5 class="text-sm font-medium text-slate-300">Manual Control ({robot.activeJoints.length} active joints)</h5>
|
465 |
-
{#each robot.activeJoints as joint}
|
466 |
-
<div class="joint-control">
|
467 |
-
<div class="joint-header">
|
468 |
-
<span class="joint-name">{joint.name}</span>
|
469 |
-
<div class="joint-values">
|
470 |
-
<span class="virtual-value">{joint.virtualValue.toFixed(0)}°</span>
|
471 |
-
{#if joint.realValue !== undefined}
|
472 |
-
<span class="real-value" title="Real robot position">
|
473 |
-
Real: {joint.realValue.toFixed(0)}°
|
474 |
-
</span>
|
475 |
{:else}
|
476 |
-
<
|
|
|
|
|
477 |
{/if}
|
478 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
479 |
</div>
|
480 |
-
|
481 |
-
|
482 |
-
|
483 |
-
|
484 |
-
|
485 |
-
|
486 |
-
|
487 |
-
|
488 |
-
|
489 |
-
|
490 |
-
|
491 |
-
|
492 |
-
|
493 |
-
|
494 |
-
|
495 |
-
|
496 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
497 |
{/if}
|
498 |
-
|
499 |
-
|
500 |
-
|
501 |
-
|
502 |
-
|
503 |
-
|
504 |
-
|
505 |
-
|
506 |
-
|
507 |
-
|
508 |
-
|
509 |
-
|
510 |
-
|
511 |
-
|
512 |
-
|
513 |
-
|
514 |
-
|
515 |
-
|
516 |
-
|
517 |
-
|
518 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
519 |
</div>
|
520 |
|
521 |
<!-- Robot Selection Modal -->
|
522 |
{#if showRobotSelectionModal}
|
523 |
-
<div class="fixed inset-0 bg-black
|
524 |
-
<
|
525 |
-
<
|
526 |
-
|
527 |
-
|
528 |
-
|
529 |
-
|
530 |
-
|
531 |
-
|
532 |
-
|
533 |
-
|
534 |
-
|
535 |
-
|
536 |
-
|
537 |
-
|
538 |
-
|
539 |
-
|
540 |
-
|
541 |
-
|
542 |
-
|
543 |
-
|
544 |
-
|
545 |
-
|
546 |
-
|
547 |
-
|
548 |
-
|
549 |
-
<
|
550 |
-
|
551 |
-
|
552 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
553 |
Cancel
|
554 |
-
</
|
555 |
-
<
|
556 |
onclick={confirmRobotSelection}
|
557 |
-
class="px-4 py-2 bg-purple-600 hover:bg-purple-700 text-white rounded-md transition-colors"
|
558 |
disabled={!selectedServerRobotId}
|
|
|
559 |
>
|
|
|
560 |
Connect Slave
|
561 |
-
</
|
562 |
-
</
|
563 |
-
</
|
564 |
</div>
|
565 |
{/if}
|
566 |
|
567 |
<style>
|
568 |
-
.
|
569 |
-
background: rgba(71, 85, 105, 0.3);
|
570 |
-
border-radius: 8px;
|
571 |
-
padding: 12px;
|
572 |
-
border: 1px solid rgba(71, 85, 105, 0.5);
|
573 |
-
}
|
574 |
-
|
575 |
-
.joint-header {
|
576 |
-
display: flex;
|
577 |
-
justify-content: space-between;
|
578 |
-
align-items: center;
|
579 |
-
margin-bottom: 8px;
|
580 |
-
}
|
581 |
-
|
582 |
-
.joint-name {
|
583 |
-
font-weight: 500;
|
584 |
-
color: #e2e8f0;
|
585 |
-
font-size: 14px;
|
586 |
-
}
|
587 |
-
|
588 |
-
.joint-values {
|
589 |
-
display: flex;
|
590 |
-
gap: 12px;
|
591 |
-
font-size: 12px;
|
592 |
-
}
|
593 |
-
|
594 |
-
.virtual-value {
|
595 |
-
color: #60a5fa;
|
596 |
-
font-weight: 500;
|
597 |
-
}
|
598 |
-
|
599 |
-
.real-value {
|
600 |
-
color: #34d399;
|
601 |
-
font-weight: 500;
|
602 |
-
}
|
603 |
-
|
604 |
-
.real-value.disconnected {
|
605 |
-
color: #f87171;
|
606 |
-
}
|
607 |
-
|
608 |
-
.joint-slider {
|
609 |
-
width: 100%;
|
610 |
-
height: 6px;
|
611 |
-
background: #374151;
|
612 |
-
border-radius: 3px;
|
613 |
-
outline: none;
|
614 |
-
cursor: pointer;
|
615 |
-
appearance: none;
|
616 |
-
}
|
617 |
-
|
618 |
-
.joint-slider::-webkit-slider-thumb {
|
619 |
appearance: none;
|
620 |
width: 18px;
|
621 |
height: 18px;
|
@@ -627,12 +692,12 @@
|
|
627 |
transition: all 0.15s ease;
|
628 |
}
|
629 |
|
630 |
-
.
|
631 |
background: #2563eb;
|
632 |
transform: scale(1.1);
|
633 |
}
|
634 |
|
635 |
-
.
|
636 |
width: 18px;
|
637 |
height: 18px;
|
638 |
border-radius: 50%;
|
@@ -643,14 +708,14 @@
|
|
643 |
transition: all 0.15s ease;
|
644 |
}
|
645 |
|
646 |
-
.
|
647 |
height: 6px;
|
648 |
background: #374151;
|
649 |
border-radius: 3px;
|
650 |
border: none;
|
651 |
}
|
652 |
|
653 |
-
.
|
654 |
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
|
655 |
}
|
656 |
</style>
|
|
|
1 |
<script lang="ts">
|
2 |
import { robotManager } from "$lib/robot/RobotManager.svelte";
|
|
|
3 |
import type { Robot } from "$lib/robot/Robot.svelte";
|
4 |
import { getApiBaseUrl, getWebSocketBaseUrl, getEnvironmentInfo } from "$lib/utils/config";
|
5 |
+
import * as Card from '$lib/components/ui/card';
|
6 |
+
import { Button } from '$lib/components/ui/button';
|
7 |
+
import { Badge } from '$lib/components/ui/badge';
|
8 |
+
import { Separator } from '$lib/components/ui/separator';
|
9 |
+
import * as Alert from '$lib/components/ui/alert';
|
10 |
+
import { Progress } from '$lib/components/ui/progress';
|
11 |
+
import { cn } from '$lib/utils';
|
12 |
|
13 |
interface Props {}
|
14 |
|
15 |
let {}: Props = $props();
|
16 |
|
17 |
// Local state
|
|
|
|
|
18 |
let error = $state<string | undefined>(undefined);
|
19 |
|
20 |
// Robot selection modal state
|
|
|
35 |
// Log environment info for debugging
|
36 |
console.log('Environment info:', getEnvironmentInfo());
|
37 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
// Master connection functions
|
39 |
async function connectDemoSequences(robot: Robot) {
|
40 |
try {
|
41 |
await robotManager.connectDemoSequences(robot.id);
|
42 |
+
clearError();
|
43 |
} catch (err) {
|
44 |
error = `Failed to connect demo sequences: ${err}`;
|
45 |
console.error(err);
|
|
|
55 |
pollInterval: 100
|
56 |
};
|
57 |
await robotManager.connectMaster(robot.id, config);
|
58 |
+
clearError();
|
59 |
} catch (err) {
|
60 |
error = `Failed to connect remote server: ${err}`;
|
61 |
console.error(err);
|
62 |
}
|
63 |
}
|
64 |
|
65 |
+
async function connectUSBMaster(robot: Robot) {
|
66 |
+
try {
|
67 |
+
await robotManager.connectUSBMaster(robot.id, {
|
68 |
+
pollInterval: 200,
|
69 |
+
smoothing: true
|
70 |
+
});
|
71 |
+
clearError();
|
72 |
+
} catch (err) {
|
73 |
+
error = `Failed to connect USB master: ${err}`;
|
74 |
+
console.error(err);
|
75 |
+
}
|
76 |
+
}
|
77 |
+
|
78 |
async function disconnectMaster(robot: Robot) {
|
79 |
try {
|
80 |
await robotManager.disconnectMaster(robot.id);
|
81 |
+
clearError();
|
82 |
} catch (err) {
|
83 |
error = `Failed to disconnect master: ${err}`;
|
84 |
console.error(err);
|
|
|
89 |
async function connectMockSlave(robot: Robot) {
|
90 |
try {
|
91 |
await robotManager.connectMockSlave(robot.id, 50);
|
92 |
+
clearError();
|
93 |
} catch (err) {
|
94 |
error = `Failed to connect mock slave: ${err}`;
|
95 |
console.error(err);
|
|
|
99 |
async function connectUSBSlave(robot: Robot) {
|
100 |
try {
|
101 |
await robotManager.connectUSBSlave(robot.id);
|
102 |
+
clearError();
|
103 |
} catch (err) {
|
104 |
error = `Failed to connect USB slave: ${err}`;
|
105 |
console.error(err);
|
|
|
148 |
// Close modal
|
149 |
showRobotSelectionModal = false;
|
150 |
pendingLocalRobot = null;
|
151 |
+
clearError();
|
152 |
|
153 |
} catch (err) {
|
154 |
error = `Failed to connect remote server slave: ${err}`;
|
|
|
165 |
async function disconnectSlave(robot: Robot, slaveId: string) {
|
166 |
try {
|
167 |
await robotManager.disconnectSlave(robot.id, slaveId);
|
168 |
+
clearError();
|
169 |
} catch (err) {
|
170 |
error = `Failed to disconnect slave: ${err}`;
|
171 |
console.error(err);
|
|
|
176 |
async function calibrateRobot(robot: Robot) {
|
177 |
try {
|
178 |
await robot.calibrateRobot();
|
179 |
+
clearError();
|
180 |
} catch (err) {
|
181 |
error = `Failed to calibrate: ${err}`;
|
182 |
console.error(err);
|
|
|
186 |
async function moveToRest(robot: Robot) {
|
187 |
try {
|
188 |
await robot.moveToRestPosition();
|
189 |
+
clearError();
|
190 |
} catch (err) {
|
191 |
error = `Failed to move to rest: ${err}`;
|
192 |
console.error(err);
|
|
|
200 |
async function removeRobot(robot: Robot) {
|
201 |
try {
|
202 |
await robotManager.removeRobot(robot.id);
|
203 |
+
clearError();
|
204 |
} catch (err) {
|
205 |
error = `Failed to remove robot: ${err}`;
|
206 |
console.error(err);
|
|
|
228 |
}
|
229 |
}
|
230 |
|
231 |
+
function getConnectionStatusVariant(robot: Robot): "default" | "secondary" | "destructive" | "outline" {
|
232 |
const hasActiveMaster = robot.controlState.hasActiveMaster;
|
233 |
const connectedSlaves = robot.connectedSlaves.length;
|
234 |
|
235 |
if (hasActiveMaster && connectedSlaves > 0) {
|
236 |
+
return "default"; // Full master-slave setup (green)
|
237 |
} else if (hasActiveMaster || connectedSlaves > 0) {
|
238 |
+
return "secondary"; // Partial connection (blue)
|
239 |
} else {
|
240 |
+
return "outline"; // No connections (gray)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
241 |
}
|
242 |
}
|
243 |
</script>
|
244 |
|
245 |
+
<div class="space-y-6">
|
246 |
+
<!-- Header -->
|
247 |
+
<div class="flex flex-col gap-2 items-center justify-between">
|
248 |
+
<div>
|
249 |
+
<h2 class="text-2xl font-bold text-slate-100">Robot Control Center</h2>
|
250 |
+
<p class="text-sm text-slate-400 mt-1">Master/Slave Architecture</p>
|
251 |
+
</div>
|
252 |
+
<div class="flex items-center gap-3">
|
253 |
+
<Badge variant="outline" class="text-slate-300">
|
254 |
+
<span class="icon-[mdi--robot] size-4 mr-1"></span>
|
255 |
+
{robots.length} Robots
|
256 |
+
</Badge>
|
257 |
+
<Badge variant="secondary" class="text-slate-300">
|
258 |
+
<span class="icon-[mdi--connection] size-4 mr-1"></span>
|
259 |
+
{robotsWithMaster} Masters
|
260 |
+
</Badge>
|
261 |
+
<Badge variant="secondary" class="text-slate-300">
|
262 |
+
<span class="icon-[mdi--devices] size-4 mr-1"></span>
|
263 |
+
{robotsWithSlaves} Slaves
|
264 |
+
</Badge>
|
265 |
</div>
|
266 |
</div>
|
267 |
|
268 |
<!-- Error Display -->
|
269 |
{#if error}
|
270 |
+
<Alert.Root variant="destructive">
|
271 |
+
<span class="icon-[mdi--alert-circle] size-4"></span>
|
272 |
+
<Alert.Title>Error</Alert.Title>
|
273 |
+
<Alert.Description class="flex items-center justify-between">
|
274 |
+
<span>{error}</span>
|
275 |
+
<Button variant="ghost" size="sm" onclick={clearError} class="text-red-200 hover:text-white h-auto p-1">
|
276 |
+
<span class="icon-[mdi--close] size-4"></span>
|
277 |
+
</Button>
|
278 |
+
</Alert.Description>
|
279 |
+
</Alert.Root>
|
280 |
{/if}
|
281 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
282 |
<!-- Robots List -->
|
283 |
+
{#if robots.length === 0}
|
284 |
+
<Card.Root class="bg-slate-800/50 border-slate-600">
|
285 |
+
<Card.Content class="p-8 text-center">
|
286 |
+
<div class="flex flex-col items-center gap-3">
|
287 |
+
<span class="icon-[mdi--robot-confused] size-12 text-slate-500"></span>
|
288 |
+
<h3 class="text-lg font-medium text-slate-300">No Robots Available</h3>
|
289 |
+
<p class="text-sm text-slate-500 max-w-md">
|
290 |
+
Create a robot using the "Add Robot" button in the top-left to get started with the master-slave architecture.
|
291 |
+
</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
292 |
</div>
|
293 |
+
</Card.Content>
|
294 |
+
</Card.Root>
|
295 |
+
{:else}
|
296 |
+
<div class="space-y-4">
|
297 |
+
{#each robots as robot (robot.id)}
|
298 |
+
<Card.Root class="bg-slate-800/60 border-slate-600 hover:border-slate-500 transition-all">
|
299 |
+
<Card.Header>
|
300 |
+
<div class="flex items-center justify-between">
|
301 |
+
<div class="flex items-center gap-3">
|
302 |
+
<span class="icon-[mdi--robot] size-6 text-blue-400"></span>
|
303 |
+
<div>
|
304 |
+
<Card.Title class="text-slate-100 text-lg">{robot.id}</Card.Title>
|
305 |
+
<div class="flex items-center gap-2 mt-1">
|
306 |
+
<Badge variant={getConnectionStatusVariant(robot)} class="text-xs text-slate-300">
|
307 |
+
{getConnectionStatusText(robot)}
|
308 |
+
</Badge>
|
309 |
+
{#if robot.controlState.lastCommandSource !== "none"}
|
310 |
+
<Badge variant="outline" class="text-blue-300 text-xs">
|
311 |
+
Last: {robot.controlState.lastCommandSource}
|
312 |
+
</Badge>
|
313 |
+
{/if}
|
314 |
+
{#if robot.isCalibrated}
|
315 |
+
<Badge variant="default" class="bg-green-600 text-xs">
|
316 |
+
<span class="icon-[mdi--check-circle] size-3 mr-1"></span>
|
317 |
+
Calibrated
|
318 |
+
</Badge>
|
319 |
+
{/if}
|
320 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
321 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
322 |
</div>
|
323 |
+
|
324 |
+
</div>
|
325 |
+
<Button variant="destructive" size="sm" onclick={() => removeRobot(robot)}>
|
326 |
+
<span class="icon-[mdi--delete] size-4 mr-1"></span>
|
327 |
+
Remove
|
328 |
+
</Button>
|
329 |
+
</Card.Header>
|
330 |
+
|
331 |
+
<Card.Content class="space-y-4">
|
332 |
+
<!-- Master Controls -->
|
333 |
+
<Card.Root class="bg-orange-500/10 border-orange-500/30">
|
334 |
+
<Card.Header>
|
335 |
+
<Card.Title class="text-orange-200 text-sm flex flex-col gap-2 text-pretty">
|
336 |
+
<div class="flex items-center gap-2 text-lg">
|
337 |
+
<span class="icon-[mdi--account-supervisor] size-6"></span>
|
338 |
+
Masters (Sources)
|
339 |
+
</div>
|
340 |
+
<div class="flex items-center gap-2">
|
341 |
+
{#if robot.controlState.hasActiveMaster}
|
342 |
+
<Badge variant="default" class="bg-green-600 text-xs ml-auto">
|
343 |
+
Connected: {robot.controlState.masterName}
|
344 |
+
</Badge>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
345 |
{:else}
|
346 |
+
<Badge variant="outline" class="text-slate-400 text-xs ml-auto">
|
347 |
+
None Connected
|
348 |
+
</Badge>
|
349 |
{/if}
|
350 |
</div>
|
351 |
+
</Card.Title>
|
352 |
+
</Card.Header>
|
353 |
+
<Card.Content class="pt-2">
|
354 |
+
<div class="flex flex-col gap-2">
|
355 |
+
<Button
|
356 |
+
variant="secondary"
|
357 |
+
size="sm"
|
358 |
+
onclick={() => connectDemoSequences(robot)}
|
359 |
+
disabled={robot.controlState.hasActiveMaster}
|
360 |
+
class="bg-orange-600 hover:bg-orange-700 text-white disabled:bg-slate-600"
|
361 |
+
>
|
362 |
+
<span class="icon-[mdi--play-circle] size-4 mr-1"></span>
|
363 |
+
Demo Sequences
|
364 |
+
</Button>
|
365 |
+
|
366 |
+
<Button
|
367 |
+
variant="secondary"
|
368 |
+
size="sm"
|
369 |
+
onclick={() => connectRemoteServerMaster(robot)}
|
370 |
+
disabled={robot.controlState.hasActiveMaster}
|
371 |
+
class="bg-purple-600 hover:bg-purple-700 text-white disabled:bg-slate-600"
|
372 |
+
>
|
373 |
+
<span class="icon-[mdi--cloud] size-4 mr-1"></span>
|
374 |
+
Remote Server
|
375 |
+
</Button>
|
376 |
+
|
377 |
+
<Button
|
378 |
+
variant="secondary"
|
379 |
+
size="sm"
|
380 |
+
onclick={() => connectUSBMaster(robot)}
|
381 |
+
disabled={robot.controlState.hasActiveMaster}
|
382 |
+
class="bg-blue-600 hover:bg-blue-700 text-white disabled:bg-slate-600"
|
383 |
+
>
|
384 |
+
<span class="icon-[mdi--usb] size-4 mr-1"></span>
|
385 |
+
USB Master
|
386 |
+
</Button>
|
387 |
+
|
388 |
+
{#if robot.controlState.hasActiveMaster}
|
389 |
+
<Button
|
390 |
+
variant="destructive"
|
391 |
+
size="sm"
|
392 |
+
onclick={() => disconnectMaster(robot)}
|
393 |
+
>
|
394 |
+
<span class="icon-[mdi--close-circle] size-4 mr-1"></span>
|
395 |
+
Disconnect Master
|
396 |
+
</Button>
|
397 |
+
{/if}
|
398 |
</div>
|
399 |
+
</Card.Content>
|
400 |
+
</Card.Root>
|
401 |
+
|
402 |
+
<!-- Slave Controls -->
|
403 |
+
<Card.Root class="bg-blue-500/10 border-blue-500/30">
|
404 |
+
<Card.Header>
|
405 |
+
<Card.Title class="text-blue-200 text-sm flex flex-col gap-2 text-pretty">
|
406 |
+
<div class="flex items-center gap-2 text-lg">
|
407 |
+
<span class="icon-[mdi--devices] size-6"></span>
|
408 |
+
Slaves (Targets)
|
409 |
+
</div>
|
410 |
+
<div class="flex items-center gap-2">
|
411 |
+
<Badge variant="default" class="bg-green-600 text-xs ml-auto">
|
412 |
+
{robot.connectedSlaves.length} Connected
|
413 |
+
</Badge>
|
414 |
+
<Badge variant="outline" class="text-slate-400 text-xs">
|
415 |
+
/ {robot.slaves.length} Total
|
416 |
+
</Badge>
|
417 |
+
</div>
|
418 |
+
</Card.Title>
|
419 |
+
</Card.Header>
|
420 |
+
<Card.Content class="pt-2 space-y-3">
|
421 |
+
<div class="flex flex-col gap-2">
|
422 |
+
<Button
|
423 |
+
variant="secondary"
|
424 |
+
size="sm"
|
425 |
+
onclick={() => connectMockSlave(robot)}
|
426 |
+
class="bg-yellow-600 hover:bg-yellow-700 text-white"
|
427 |
+
>
|
428 |
+
<span class="icon-[mdi--robot-confused] size-4 mr-1"></span>
|
429 |
+
Add Mock Slave
|
430 |
+
</Button>
|
431 |
+
<Button
|
432 |
+
variant="secondary"
|
433 |
+
size="sm"
|
434 |
+
onclick={() => connectUSBSlave(robot)}
|
435 |
+
class="bg-green-600 hover:bg-green-700 text-white"
|
436 |
+
>
|
437 |
+
<span class="icon-[mdi--usb] size-4 mr-1"></span>
|
438 |
+
Add USB Slave
|
439 |
+
</Button>
|
440 |
+
<Button
|
441 |
+
variant="secondary"
|
442 |
+
size="sm"
|
443 |
+
onclick={() => connectRemoteServerSlave(robot)}
|
444 |
+
class="bg-purple-600 hover:bg-purple-700 text-white"
|
445 |
+
>
|
446 |
+
<span class="icon-[mdi--cloud] size-4 mr-1"></span>
|
447 |
+
Add Remote Server Slave
|
448 |
+
</Button>
|
449 |
+
</div>
|
450 |
+
|
451 |
+
{#if robot.slaves.length > 0}
|
452 |
+
<div class="space-y-2">
|
453 |
+
<p class="text-xs text-blue-300 font-medium">Connected Slaves:</p>
|
454 |
+
<div class="space-y-1">
|
455 |
+
{#each robot.slaves as slave}
|
456 |
+
<div class="flex items-center justify-between p-2 bg-slate-700/50 rounded-md">
|
457 |
+
<div class="flex items-center gap-2">
|
458 |
+
<span class="icon-[mdi--circle] size-2 text-green-400"></span>
|
459 |
+
<span class="text-sm text-slate-300">{slave.name}</span>
|
460 |
+
<Badge variant="outline" class="text-xs">{slave.id}</Badge>
|
461 |
+
</div>
|
462 |
+
<Button
|
463 |
+
variant="destructive"
|
464 |
+
size="sm"
|
465 |
+
onclick={() => disconnectSlave(robot, slave.id)}
|
466 |
+
class="h-6 px-2 text-xs"
|
467 |
+
>
|
468 |
+
<span class="icon-[mdi--close] size-3"></span>
|
469 |
+
</Button>
|
470 |
+
</div>
|
471 |
+
{/each}
|
472 |
+
</div>
|
473 |
+
</div>
|
474 |
+
{/if}
|
475 |
+
</Card.Content>
|
476 |
+
</Card.Root>
|
477 |
+
|
478 |
+
<!-- Calibration Section (for USB slaves) -->
|
479 |
+
{#if robot.connectedSlaves.some(slave => slave.name.includes("USB"))}
|
480 |
+
<Card.Root class="bg-emerald-500/10 border-emerald-500/30">
|
481 |
+
<Card.Header>
|
482 |
+
<Card.Title class="text-emerald-200 text-sm flex items-center gap-2">
|
483 |
+
<span class="icon-[mdi--crosshairs-gps] size-4"></span>
|
484 |
+
USB Robot Calibration
|
485 |
+
{#if robot.isCalibrated}
|
486 |
+
<Badge variant="default" class="bg-green-600 text-xs ml-auto">
|
487 |
+
<span class="icon-[mdi--check] size-3 mr-1"></span>
|
488 |
+
Calibrated
|
489 |
+
</Badge>
|
490 |
+
{:else}
|
491 |
+
<Badge variant="destructive" class="text-xs ml-auto">
|
492 |
+
<span class="icon-[mdi--alert] size-3 mr-1"></span>
|
493 |
+
Not Calibrated
|
494 |
+
</Badge>
|
495 |
+
{/if}
|
496 |
+
</Card.Title>
|
497 |
+
</Card.Header>
|
498 |
+
<Card.Content class="pt-2">
|
499 |
+
{#if !robot.isCalibrated}
|
500 |
+
<div class="space-y-3">
|
501 |
+
<Alert.Root>
|
502 |
+
<span class="icon-[mdi--information] size-4"></span>
|
503 |
+
<Alert.Title>Calibration Steps</Alert.Title>
|
504 |
+
<Alert.Description>
|
505 |
+
<ol class="text-xs space-y-1 mt-2">
|
506 |
+
<li>1. Manually position your robot to match the digital twin's rest pose</li>
|
507 |
+
<li>2. Click "Calibrate" when positioned correctly</li>
|
508 |
+
</ol>
|
509 |
+
</Alert.Description>
|
510 |
+
</Alert.Root>
|
511 |
+
<div class="flex gap-2">
|
512 |
+
<Button
|
513 |
+
variant="outline"
|
514 |
+
size="sm"
|
515 |
+
onclick={() => moveToRest(robot)}
|
516 |
+
>
|
517 |
+
<span class="icon-[mdi--human-handsup] size-4 mr-1"></span>
|
518 |
+
Show Rest Pose
|
519 |
+
</Button>
|
520 |
+
<Button
|
521 |
+
variant="default"
|
522 |
+
size="sm"
|
523 |
+
onclick={() => calibrateRobot(robot)}
|
524 |
+
class="bg-green-600 hover:bg-green-700"
|
525 |
+
>
|
526 |
+
<span class="icon-[mdi--crosshairs-gps] size-4 mr-1"></span>
|
527 |
+
Calibrate
|
528 |
+
</Button>
|
529 |
+
</div>
|
530 |
+
</div>
|
531 |
+
{:else}
|
532 |
+
<div class="space-y-3">
|
533 |
+
<div class="flex items-center gap-2 text-sm text-green-300">
|
534 |
+
<span class="icon-[mdi--check-circle] size-4"></span>
|
535 |
+
Robot calibrated at {robot.calibrationState.calibrationTime?.toLocaleTimeString()}
|
536 |
+
</div>
|
537 |
+
<Button
|
538 |
+
variant="outline"
|
539 |
+
size="sm"
|
540 |
+
onclick={() => clearCalibration(robot)}
|
541 |
+
>
|
542 |
+
<span class="icon-[mdi--refresh] size-4 mr-1"></span>
|
543 |
+
Clear Calibration
|
544 |
+
</Button>
|
545 |
+
</div>
|
546 |
+
{/if}
|
547 |
+
</Card.Content>
|
548 |
+
</Card.Root>
|
549 |
{/if}
|
550 |
+
|
551 |
+
<!-- Manual Joint Controls or Master Control Status -->
|
552 |
+
{#if robot.manualControlEnabled}
|
553 |
+
<Card.Root class="bg-slate-700/30 border-slate-600">
|
554 |
+
<Card.Header>
|
555 |
+
<Card.Title class="text-slate-200 text-sm flex flex-col gap-2 text-pretty">
|
556 |
+
<div class="flex items-center gap-2 text-lg">
|
557 |
+
<span class="icon-[mdi--tune] size-6"></span>
|
558 |
+
Manual Control
|
559 |
+
</div>
|
560 |
+
<div class="flex items-center gap-2">
|
561 |
+
<Badge variant="default" class="bg-green-600 text-xs ml-auto">
|
562 |
+
{robot.activeJoints.length} Active Joints
|
563 |
+
</Badge>
|
564 |
+
</div>
|
565 |
+
</Card.Title>
|
566 |
+
</Card.Header>
|
567 |
+
<Card.Content class="pt-2">
|
568 |
+
{#if robot.activeJoints.length === 0}
|
569 |
+
<p class="text-sm text-slate-500 italic text-center py-4">No active joints</p>
|
570 |
+
{:else}
|
571 |
+
<div class="space-y-4">
|
572 |
+
{#each robot.activeJoints as joint}
|
573 |
+
{@const lower = (joint.urdfJoint.limit?.lower != undefined) ? (joint.urdfJoint.limit.lower * 180) / Math.PI : -180}
|
574 |
+
{@const upper = (joint.urdfJoint.limit?.upper != undefined) ? (joint.urdfJoint.limit.upper * 180) / Math.PI : 180}
|
575 |
+
<div class="space-y-2 p-3 bg-slate-800/50 rounded-lg border border-slate-600">
|
576 |
+
<span class="text-sm font-medium text-slate-300">{joint.name}</span>
|
577 |
+
<div class="space-y-2">
|
578 |
+
<input
|
579 |
+
type="range"
|
580 |
+
min={lower}
|
581 |
+
max={upper}
|
582 |
+
step="1"
|
583 |
+
value={joint.virtualValue}
|
584 |
+
oninput={(e) => {
|
585 |
+
const target = e.target as HTMLInputElement;
|
586 |
+
robot.updateJointValue(joint.name, parseFloat(target.value));
|
587 |
+
}}
|
588 |
+
class="w-full h-2 bg-slate-600 rounded-lg appearance-none cursor-pointer slider"
|
589 |
+
/>
|
590 |
+
<div class="flex justify-between text-xs text-slate-500">
|
591 |
+
<span>-180°</span>
|
592 |
+
<span>180°</span>
|
593 |
+
</div>
|
594 |
+
</div>
|
595 |
+
<div class="flex justify-between items-center">
|
596 |
+
|
597 |
+
<div class="flex items-center gap-3 text-xs">
|
598 |
+
<span class="text-blue-400 font-mono">Virtual: {joint.virtualValue.toFixed(0)}°</span>
|
599 |
+
{#if joint.realValue !== undefined}
|
600 |
+
<span class="text-green-400 font-mono">Real: {joint.realValue.toFixed(0)}°</span>
|
601 |
+
{:else}
|
602 |
+
<span class="text-red-400 font-mono">Real: N/A</span>
|
603 |
+
{/if}
|
604 |
+
</div>
|
605 |
+
</div>
|
606 |
+
</div>
|
607 |
+
{/each}
|
608 |
+
</div>
|
609 |
+
{/if}
|
610 |
+
</Card.Content>
|
611 |
+
</Card.Root>
|
612 |
+
{:else}
|
613 |
+
<Alert.Root class="bg-purple-500/10 border-purple-500/30">
|
614 |
+
<span class="icon-[mdi--gamepad-variant] size-4"></span>
|
615 |
+
<Alert.Title class="text-purple-200">Master Control Active</Alert.Title>
|
616 |
+
<Alert.Description class="text-purple-300">
|
617 |
+
Robot is controlled by: <strong>{robot.controlState.masterName}</strong><br>
|
618 |
+
Manual controls are disabled. Disconnect master to regain manual control.
|
619 |
+
</Alert.Description>
|
620 |
+
</Alert.Root>
|
621 |
+
{/if}
|
622 |
+
</Card.Content>
|
623 |
+
</Card.Root>
|
624 |
+
{/each}
|
625 |
+
</div>
|
626 |
+
{/if}
|
627 |
</div>
|
628 |
|
629 |
<!-- Robot Selection Modal -->
|
630 |
{#if showRobotSelectionModal}
|
631 |
+
<div class="fixed inset-0 bg-black/50 flex items-center justify-center z-50 backdrop-blur-sm">
|
632 |
+
<Card.Root class="w-96 max-w-full mx-4 bg-slate-800 border-slate-600">
|
633 |
+
<Card.Header>
|
634 |
+
<Card.Title class="text-slate-100 flex items-center gap-2">
|
635 |
+
<span class="icon-[mdi--robot] size-5"></span>
|
636 |
+
Select Server Robot
|
637 |
+
</Card.Title>
|
638 |
+
<Card.Description class="text-slate-400">
|
639 |
+
Choose which server robot to connect as a slave target
|
640 |
+
</Card.Description>
|
641 |
+
</Card.Header>
|
642 |
+
<Card.Content class="space-y-4">
|
643 |
+
<div class="space-y-2">
|
644 |
+
<label class="text-sm font-medium text-slate-300">Available robots on server:</label>
|
645 |
+
<select
|
646 |
+
bind:value={selectedServerRobotId}
|
647 |
+
class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-md text-slate-100 text-sm"
|
648 |
+
>
|
649 |
+
{#each availableServerRobots as serverRobot}
|
650 |
+
<option value={serverRobot.id}>
|
651 |
+
{serverRobot.name} ({serverRobot.id}) - {serverRobot.robot_type}
|
652 |
+
</option>
|
653 |
+
{/each}
|
654 |
+
</select>
|
655 |
+
</div>
|
656 |
+
|
657 |
+
<Alert.Root>
|
658 |
+
<span class="icon-[mdi--information] size-4"></span>
|
659 |
+
<Alert.Description>
|
660 |
+
This will connect your local robot <strong>"{pendingLocalRobot?.id}"</strong> as a slave to
|
661 |
+
receive commands from the selected server robot.
|
662 |
+
</Alert.Description>
|
663 |
+
</Alert.Root>
|
664 |
+
</Card.Content>
|
665 |
+
<Card.Footer class="flex gap-3 justify-end">
|
666 |
+
<Button variant="outline" onclick={cancelRobotSelection}>
|
667 |
Cancel
|
668 |
+
</Button>
|
669 |
+
<Button
|
670 |
onclick={confirmRobotSelection}
|
|
|
671 |
disabled={!selectedServerRobotId}
|
672 |
+
class="bg-purple-600 hover:bg-purple-700"
|
673 |
>
|
674 |
+
<span class="icon-[mdi--link] size-4 mr-1"></span>
|
675 |
Connect Slave
|
676 |
+
</Button>
|
677 |
+
</Card.Footer>
|
678 |
+
</Card.Root>
|
679 |
</div>
|
680 |
{/if}
|
681 |
|
682 |
<style>
|
683 |
+
.slider::-webkit-slider-thumb {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
684 |
appearance: none;
|
685 |
width: 18px;
|
686 |
height: 18px;
|
|
|
692 |
transition: all 0.15s ease;
|
693 |
}
|
694 |
|
695 |
+
.slider::-webkit-slider-thumb:hover {
|
696 |
background: #2563eb;
|
697 |
transform: scale(1.1);
|
698 |
}
|
699 |
|
700 |
+
.slider::-moz-range-thumb {
|
701 |
width: 18px;
|
702 |
height: 18px;
|
703 |
border-radius: 50%;
|
|
|
708 |
transition: all 0.15s ease;
|
709 |
}
|
710 |
|
711 |
+
.slider::-moz-range-track {
|
712 |
height: 6px;
|
713 |
background: #374151;
|
714 |
border-radius: 3px;
|
715 |
border: none;
|
716 |
}
|
717 |
|
718 |
+
.slider:focus {
|
719 |
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
|
720 |
}
|
721 |
</style>
|
src/lib/components/panel/SettingsPanel.svelte
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
<script lang="ts">
|
2 |
import { environment } from '$lib/runes/env.svelte';
|
3 |
-
import type IUrdfJoint from '$lib/components/
|
4 |
|
5 |
interface Props {}
|
6 |
|
|
|
1 |
<script lang="ts">
|
2 |
import { environment } from '$lib/runes/env.svelte';
|
3 |
+
import type IUrdfJoint from '$lib/components/3d/robot/URDF/interfaces/IUrdfJoint';
|
4 |
|
5 |
interface Props {}
|
6 |
|
src/lib/components/scene/Robot.svelte
DELETED
@@ -1,44 +0,0 @@
|
|
1 |
-
<script lang="ts">
|
2 |
-
import { T } from '@threlte/core';
|
3 |
-
import { getRootLinks } from '$lib/components/scene/robot/URDF/utils/UrdfParser';
|
4 |
-
import UrdfLink from '$lib/components/scene/robot/URDF/primitives/UrdfLink.svelte';
|
5 |
-
import { robotManager } from '$lib/robot/RobotManager.svelte';
|
6 |
-
|
7 |
-
interface Props {}
|
8 |
-
|
9 |
-
let {}: Props = $props();
|
10 |
-
|
11 |
-
// Get all robots from the manager
|
12 |
-
const robots = $derived(robotManager.robots);
|
13 |
-
</script>
|
14 |
-
|
15 |
-
{#each robots as robot, index (robot.id)}
|
16 |
-
{@const xPosition = index * 5} <!-- Space robots 5 units apart -->
|
17 |
-
<T.Group position.x={xPosition} position.y={0} position.z={0} quaternion={[0, 0, 0, 1]} scale={[10, 10, 10]} rotation={[-Math.PI / 2, 0, 0]}>
|
18 |
-
{#each getRootLinks(robot.robotState.robot) as link}
|
19 |
-
<UrdfLink
|
20 |
-
robot={robot.robotState.robot}
|
21 |
-
{link}
|
22 |
-
textScale={0.2}
|
23 |
-
showName={true}
|
24 |
-
showVisual={true}
|
25 |
-
showCollision={false}
|
26 |
-
visualColor="#333333"
|
27 |
-
visualOpacity={0.7}
|
28 |
-
collisionOpacity={0.7}
|
29 |
-
collisionColor="#813d9c"
|
30 |
-
jointNames={true}
|
31 |
-
joints={true}
|
32 |
-
jointColor="#62a0ea"
|
33 |
-
jointIndicatorColor="#f66151"
|
34 |
-
nameHeight={0.1}
|
35 |
-
selectedLink={robot.robotState.selection.selectedLink}
|
36 |
-
selectedJoint={robot.robotState.selection.selectedJoint}
|
37 |
-
highlightColor="#ffa348"
|
38 |
-
showLine={false}
|
39 |
-
opacity={1}
|
40 |
-
isInteractive={false}
|
41 |
-
/>
|
42 |
-
{/each}
|
43 |
-
</T.Group>
|
44 |
-
{/each}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/lib/components/scene/Selectable.svelte
DELETED
@@ -1,68 +0,0 @@
|
|
1 |
-
<script lang="ts">
|
2 |
-
import { T } from '@threlte/core';
|
3 |
-
import { TransformControls, interactivity } from '@threlte/extras';
|
4 |
-
import type { Object3D } from 'three';
|
5 |
-
import type { Snippet } from 'svelte';
|
6 |
-
import type IUrdfJoint from './robot/URDF/interfaces/IUrdfJoint';
|
7 |
-
import { updateOrigin } from './robot/URDF/utils/UrdfParser';
|
8 |
-
import type { IUrdfVisual } from './robot/URDF/interfaces/IUrdfVisual';
|
9 |
-
import type { TransformControlsMode } from 'three/examples/jsm/Addons.js';
|
10 |
-
|
11 |
-
interface Props {
|
12 |
-
origin: IUrdfJoint | IUrdfVisual;
|
13 |
-
children?: Snippet; // renderable
|
14 |
-
selected?: boolean;
|
15 |
-
translationSnap?: number;
|
16 |
-
scaleSnap?: number;
|
17 |
-
rotationSnap?: number;
|
18 |
-
tool?: TransformControlsMode;
|
19 |
-
enableEdit?: boolean;
|
20 |
-
}
|
21 |
-
|
22 |
-
let {
|
23 |
-
origin,
|
24 |
-
children,
|
25 |
-
selected = false,
|
26 |
-
translationSnap = 0.1,
|
27 |
-
scaleSnap = 0.1,
|
28 |
-
rotationSnap = 0.1,
|
29 |
-
tool = 'translate',
|
30 |
-
enableEdit = true
|
31 |
-
}: Props = $props();
|
32 |
-
|
33 |
-
const updateData = (obj: Object3D) => {
|
34 |
-
origin.origin_xyz = obj.position.toArray();
|
35 |
-
origin.origin_rpy = [obj.rotation.x, obj.rotation.y, obj.rotation.z];
|
36 |
-
updateOrigin(origin);
|
37 |
-
};
|
38 |
-
|
39 |
-
const onobjectChange = (event: any) => {
|
40 |
-
if (!event.target) {
|
41 |
-
return;
|
42 |
-
}
|
43 |
-
const obj = event.target.object;
|
44 |
-
updateData(obj);
|
45 |
-
};
|
46 |
-
|
47 |
-
interactivity();
|
48 |
-
</script>
|
49 |
-
|
50 |
-
{#if selected && enableEdit}
|
51 |
-
<TransformControls
|
52 |
-
{translationSnap}
|
53 |
-
{scaleSnap}
|
54 |
-
rotationSnap={Math.PI / rotationSnap}
|
55 |
-
position={origin.origin_xyz}
|
56 |
-
rotation={origin.origin_rpy}
|
57 |
-
mode={tool}
|
58 |
-
{onobjectChange}
|
59 |
-
>
|
60 |
-
{@render children?.()}
|
61 |
-
</TransformControls>
|
62 |
-
{:else}
|
63 |
-
<T.Group position={origin.origin_xyz} rotation={origin.origin_rpy}>
|
64 |
-
{@render children?.()}
|
65 |
-
</T.Group>
|
66 |
-
{/if}
|
67 |
-
|
68 |
-
<!-- From https://github.com/brean/urdf-viewer -->
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/lib/components/ui/accordion/accordion-content.svelte
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { Accordion as AccordionPrimitive } from "bits-ui";
|
3 |
+
import { cn, type WithoutChild } from "$lib/utils.js";
|
4 |
+
|
5 |
+
let {
|
6 |
+
ref = $bindable(null),
|
7 |
+
class: className,
|
8 |
+
children,
|
9 |
+
...restProps
|
10 |
+
}: WithoutChild<AccordionPrimitive.ContentProps> = $props();
|
11 |
+
</script>
|
12 |
+
|
13 |
+
<AccordionPrimitive.Content
|
14 |
+
bind:ref
|
15 |
+
data-slot="accordion-content"
|
16 |
+
class={cn(
|
17 |
+
"data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm",
|
18 |
+
className
|
19 |
+
)}
|
20 |
+
{...restProps}
|
21 |
+
>
|
22 |
+
<div class="pb-4 pt-0">
|
23 |
+
{@render children?.()}
|
24 |
+
</div>
|
25 |
+
</AccordionPrimitive.Content>
|
src/lib/components/ui/accordion/accordion-item.svelte
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { Accordion as AccordionPrimitive } from "bits-ui";
|
3 |
+
import { cn } from "$lib/utils.js";
|
4 |
+
|
5 |
+
let {
|
6 |
+
ref = $bindable(null),
|
7 |
+
class: className,
|
8 |
+
...restProps
|
9 |
+
}: AccordionPrimitive.ItemProps = $props();
|
10 |
+
</script>
|
11 |
+
|
12 |
+
<AccordionPrimitive.Item
|
13 |
+
bind:ref
|
14 |
+
data-slot="accordion-item"
|
15 |
+
class={cn("border-b last:border-b-0", className)}
|
16 |
+
{...restProps}
|
17 |
+
/>
|
src/lib/components/ui/accordion/accordion-root.svelte
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { Accordion as AccordionPrimitive } from "bits-ui";
|
3 |
+
|
4 |
+
let {
|
5 |
+
ref = $bindable(null),
|
6 |
+
value = $bindable(),
|
7 |
+
...restProps
|
8 |
+
}: AccordionPrimitive.RootProps = $props();
|
9 |
+
</script>
|
10 |
+
|
11 |
+
<AccordionPrimitive.Root
|
12 |
+
bind:ref
|
13 |
+
bind:value={value as never}
|
14 |
+
data-slot="accordion"
|
15 |
+
{...restProps}
|
16 |
+
/>
|
src/lib/components/ui/accordion/accordion-trigger.svelte
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { Accordion as AccordionPrimitive } from "bits-ui";
|
3 |
+
import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
|
4 |
+
import { cn, type WithoutChild } from "$lib/utils.js";
|
5 |
+
|
6 |
+
let {
|
7 |
+
ref = $bindable(null),
|
8 |
+
class: className,
|
9 |
+
level = 3,
|
10 |
+
children,
|
11 |
+
...restProps
|
12 |
+
}: WithoutChild<AccordionPrimitive.TriggerProps> & {
|
13 |
+
level?: AccordionPrimitive.HeaderProps["level"];
|
14 |
+
} = $props();
|
15 |
+
</script>
|
16 |
+
|
17 |
+
<AccordionPrimitive.Header {level} class="flex">
|
18 |
+
<AccordionPrimitive.Trigger
|
19 |
+
data-slot="accordion-trigger"
|
20 |
+
bind:ref
|
21 |
+
class={cn(
|
22 |
+
"focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium outline-none transition-all hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
|
23 |
+
className
|
24 |
+
)}
|
25 |
+
{...restProps}
|
26 |
+
>
|
27 |
+
{@render children?.()}
|
28 |
+
<ChevronDownIcon
|
29 |
+
class="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200"
|
30 |
+
/>
|
31 |
+
</AccordionPrimitive.Trigger>
|
32 |
+
</AccordionPrimitive.Header>
|
src/lib/components/ui/accordion/index.ts
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import Root from "./accordion-root.svelte";
|
2 |
+
import Content from "./accordion-content.svelte";
|
3 |
+
import Item from "./accordion-item.svelte";
|
4 |
+
import Trigger from "./accordion-trigger.svelte";
|
5 |
+
|
6 |
+
export {
|
7 |
+
Root,
|
8 |
+
Content,
|
9 |
+
Item,
|
10 |
+
Trigger,
|
11 |
+
//
|
12 |
+
Root as Accordion,
|
13 |
+
Content as AccordionContent,
|
14 |
+
Item as AccordionItem,
|
15 |
+
Trigger as AccordionTrigger,
|
16 |
+
};
|
src/lib/components/ui/alert-dialog/alert-dialog-action.svelte
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
3 |
+
import { buttonVariants } from "$lib/components/ui/button/index.js";
|
4 |
+
import { cn } from "$lib/utils.js";
|
5 |
+
|
6 |
+
let {
|
7 |
+
ref = $bindable(null),
|
8 |
+
class: className,
|
9 |
+
...restProps
|
10 |
+
}: AlertDialogPrimitive.ActionProps = $props();
|
11 |
+
</script>
|
12 |
+
|
13 |
+
<AlertDialogPrimitive.Action
|
14 |
+
bind:ref
|
15 |
+
data-slot="alert-dialog-action"
|
16 |
+
class={cn(buttonVariants(), className)}
|
17 |
+
{...restProps}
|
18 |
+
/>
|
src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
3 |
+
import { buttonVariants } from "$lib/components/ui/button/index.js";
|
4 |
+
import { cn } from "$lib/utils.js";
|
5 |
+
|
6 |
+
let {
|
7 |
+
ref = $bindable(null),
|
8 |
+
class: className,
|
9 |
+
...restProps
|
10 |
+
}: AlertDialogPrimitive.CancelProps = $props();
|
11 |
+
</script>
|
12 |
+
|
13 |
+
<AlertDialogPrimitive.Cancel
|
14 |
+
bind:ref
|
15 |
+
data-slot="alert-dialog-cancel"
|
16 |
+
class={cn(buttonVariants({ variant: "outline" }), className)}
|
17 |
+
{...restProps}
|
18 |
+
/>
|
src/lib/components/ui/alert-dialog/alert-dialog-content.svelte
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
3 |
+
import AlertDialogOverlay from "./alert-dialog-overlay.svelte";
|
4 |
+
import { cn, type WithoutChild, type WithoutChildrenOrChild } from "$lib/utils.js";
|
5 |
+
|
6 |
+
let {
|
7 |
+
ref = $bindable(null),
|
8 |
+
class: className,
|
9 |
+
portalProps,
|
10 |
+
...restProps
|
11 |
+
}: WithoutChild<AlertDialogPrimitive.ContentProps> & {
|
12 |
+
portalProps?: WithoutChildrenOrChild<AlertDialogPrimitive.PortalProps>;
|
13 |
+
} = $props();
|
14 |
+
</script>
|
15 |
+
|
16 |
+
<AlertDialogPrimitive.Portal {...portalProps}>
|
17 |
+
<AlertDialogOverlay />
|
18 |
+
<AlertDialogPrimitive.Content
|
19 |
+
bind:ref
|
20 |
+
data-slot="alert-dialog-content"
|
21 |
+
class={cn(
|
22 |
+
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed left-[50%] top-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
23 |
+
className
|
24 |
+
)}
|
25 |
+
{...restProps}
|
26 |
+
/>
|
27 |
+
</AlertDialogPrimitive.Portal>
|
src/lib/components/ui/alert-dialog/alert-dialog-description.svelte
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
3 |
+
import { cn } from "$lib/utils.js";
|
4 |
+
|
5 |
+
let {
|
6 |
+
ref = $bindable(null),
|
7 |
+
class: className,
|
8 |
+
...restProps
|
9 |
+
}: AlertDialogPrimitive.DescriptionProps = $props();
|
10 |
+
</script>
|
11 |
+
|
12 |
+
<AlertDialogPrimitive.Description
|
13 |
+
bind:ref
|
14 |
+
data-slot="alert-dialog-description"
|
15 |
+
class={cn("text-muted-foreground text-sm", className)}
|
16 |
+
{...restProps}
|
17 |
+
/>
|