@viamrobotics/motion-tools 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +20 -0
  3. package/dist/WorldObject.d.ts +33 -0
  4. package/dist/WorldObject.js +18 -0
  5. package/dist/assert.d.ts +14 -0
  6. package/dist/assert.js +21 -0
  7. package/dist/color.d.ts +8 -0
  8. package/dist/color.js +14 -0
  9. package/dist/components/App.svelte +63 -0
  10. package/dist/components/App.svelte.d.ts +8 -0
  11. package/dist/components/AxesHelper.svelte +17 -0
  12. package/dist/components/AxesHelper.svelte.d.ts +4 -0
  13. package/dist/components/BentPlaneGeometry.svelte +52 -0
  14. package/dist/components/BentPlaneGeometry.svelte.d.ts +29 -0
  15. package/dist/components/Camera.svelte +45 -0
  16. package/dist/components/Camera.svelte.d.ts +5 -0
  17. package/dist/components/CameraControls.svelte +82 -0
  18. package/dist/components/CameraControls.svelte.d.ts +9 -0
  19. package/dist/components/Details.svelte +161 -0
  20. package/dist/components/Details.svelte.d.ts +3 -0
  21. package/dist/components/Detections.svelte +41 -0
  22. package/dist/components/Detections.svelte.d.ts +3 -0
  23. package/dist/components/DetectionsPlane.svelte +23 -0
  24. package/dist/components/DetectionsPlane.svelte.d.ts +21 -0
  25. package/dist/components/DomPortal.svelte +20 -0
  26. package/dist/components/DomPortal.svelte.d.ts +5 -0
  27. package/dist/components/Focus.svelte +49 -0
  28. package/dist/components/Focus.svelte.d.ts +3 -0
  29. package/dist/components/Frame.svelte +112 -0
  30. package/dist/components/Frame.svelte.d.ts +16 -0
  31. package/dist/components/Frames.svelte +54 -0
  32. package/dist/components/Frames.svelte.d.ts +18 -0
  33. package/dist/components/Pointcloud.svelte +55 -0
  34. package/dist/components/Pointcloud.svelte.d.ts +10 -0
  35. package/dist/components/Pointclouds.svelte +21 -0
  36. package/dist/components/Pointclouds.svelte.d.ts +18 -0
  37. package/dist/components/Pose.svelte +19 -0
  38. package/dist/components/Pose.svelte.d.ts +12 -0
  39. package/dist/components/RefreshRate.svelte +47 -0
  40. package/dist/components/RefreshRate.svelte.d.ts +8 -0
  41. package/dist/components/Scene.svelte +81 -0
  42. package/dist/components/Scene.svelte.d.ts +7 -0
  43. package/dist/components/SceneProviders.svelte +41 -0
  44. package/dist/components/SceneProviders.svelte.d.ts +9 -0
  45. package/dist/components/Selected.svelte +44 -0
  46. package/dist/components/Selected.svelte.d.ts +3 -0
  47. package/dist/components/Shapes.svelte +49 -0
  48. package/dist/components/Shapes.svelte.d.ts +18 -0
  49. package/dist/components/StaticGeometries.svelte +79 -0
  50. package/dist/components/StaticGeometries.svelte.d.ts +18 -0
  51. package/dist/components/Tree/Settings.svelte +54 -0
  52. package/dist/components/Tree/Settings.svelte.d.ts +18 -0
  53. package/dist/components/Tree/Tree.svelte +204 -0
  54. package/dist/components/Tree/Tree.svelte.d.ts +10 -0
  55. package/dist/components/Tree/TreeContainer.svelte +70 -0
  56. package/dist/components/Tree/TreeContainer.svelte.d.ts +3 -0
  57. package/dist/components/Tree/buildTree.d.ts +11 -0
  58. package/dist/components/Tree/buildTree.js +29 -0
  59. package/dist/components/Tree/useExpanded.svelte.d.ts +5 -0
  60. package/dist/components/Tree/useExpanded.svelte.js +21 -0
  61. package/dist/components/WorldObject.svelte +27 -0
  62. package/dist/components/WorldObject.svelte.d.ts +11 -0
  63. package/dist/components/XR.svelte +20 -0
  64. package/dist/components/XR.svelte.d.ts +3 -0
  65. package/dist/components/models/README.md +5 -0
  66. package/dist/components/null-states/Connection.svelte +0 -0
  67. package/dist/components/null-states/Connection.svelte.d.ts +26 -0
  68. package/dist/components/portal/Portal.svelte +25 -0
  69. package/dist/components/portal/Portal.svelte.d.ts +8 -0
  70. package/dist/components/portal/PortalTarget.svelte +18 -0
  71. package/dist/components/portal/PortalTarget.svelte.d.ts +6 -0
  72. package/dist/components/portal/index.d.ts +2 -0
  73. package/dist/components/portal/index.js +2 -0
  74. package/dist/components/portal/usePortalContext.svelte.d.ts +5 -0
  75. package/dist/components/portal/usePortalContext.svelte.js +8 -0
  76. package/dist/components/xr/CameraFeed.svelte +81 -0
  77. package/dist/components/xr/CameraFeed.svelte.d.ts +6 -0
  78. package/dist/components/xr/Controllers.svelte +71 -0
  79. package/dist/components/xr/Controllers.svelte.d.ts +3 -0
  80. package/dist/components/xr/Draggable.svelte +101 -0
  81. package/dist/components/xr/Draggable.svelte.d.ts +11 -0
  82. package/dist/components/xr/HandCollider.svelte +19 -0
  83. package/dist/components/xr/HandCollider.svelte.d.ts +18 -0
  84. package/dist/components/xr/Hands.svelte +24 -0
  85. package/dist/components/xr/Hands.svelte.d.ts +18 -0
  86. package/dist/components/xr/OriginMarker.svelte +100 -0
  87. package/dist/components/xr/OriginMarker.svelte.d.ts +3 -0
  88. package/dist/components/xr/PointDistance.svelte +52 -0
  89. package/dist/components/xr/PointDistance.svelte.d.ts +3 -0
  90. package/dist/hooks/index.d.ts +1 -0
  91. package/dist/hooks/index.js +1 -0
  92. package/dist/hooks/useConnectionConfigs.svelte.d.ts +17 -0
  93. package/dist/hooks/useConnectionConfigs.svelte.js +40 -0
  94. package/dist/hooks/useControls.svelte.d.ts +7 -0
  95. package/dist/hooks/useControls.svelte.js +16 -0
  96. package/dist/hooks/useDraggable.svelte.d.ts +2 -0
  97. package/dist/hooks/useDraggable.svelte.js +25 -0
  98. package/dist/hooks/useFrames.svelte.d.ts +9 -0
  99. package/dist/hooks/useFrames.svelte.js +50 -0
  100. package/dist/hooks/useGeometries.svelte.d.ts +7 -0
  101. package/dist/hooks/useGeometries.svelte.js +58 -0
  102. package/dist/hooks/useMotionClient.svelte.d.ts +8 -0
  103. package/dist/hooks/useMotionClient.svelte.js +31 -0
  104. package/dist/hooks/useObjectEvents.svelte.d.ts +9 -0
  105. package/dist/hooks/useObjectEvents.svelte.js +31 -0
  106. package/dist/hooks/useObjects.svelte.d.ts +7 -0
  107. package/dist/hooks/useObjects.svelte.js +33 -0
  108. package/dist/hooks/usePartID.svelte.d.ts +6 -0
  109. package/dist/hooks/usePartID.svelte.js +14 -0
  110. package/dist/hooks/usePersistentUUIDs.svelte.d.ts +5 -0
  111. package/dist/hooks/usePersistentUUIDs.svelte.js +14 -0
  112. package/dist/hooks/usePointclouds.svelte.d.ts +7 -0
  113. package/dist/hooks/usePointclouds.svelte.js +58 -0
  114. package/dist/hooks/usePose.svelte.d.ts +3 -0
  115. package/dist/hooks/usePose.svelte.js +44 -0
  116. package/dist/hooks/usePoses.svelte.d.ts +12 -0
  117. package/dist/hooks/usePoses.svelte.js +63 -0
  118. package/dist/hooks/useRefreshRates.svelte.d.ts +5 -0
  119. package/dist/hooks/useRefreshRates.svelte.js +23 -0
  120. package/dist/hooks/useSelection.svelte.d.ts +40 -0
  121. package/dist/hooks/useSelection.svelte.js +92 -0
  122. package/dist/hooks/useShapes.svelte.d.ts +17 -0
  123. package/dist/hooks/useShapes.svelte.js +264 -0
  124. package/dist/hooks/useStaticGeometries.svelte.d.ts +9 -0
  125. package/dist/hooks/useStaticGeometries.svelte.js +37 -0
  126. package/dist/hooks/useVisibility.svelte.d.ts +5 -0
  127. package/dist/hooks/useVisibility.svelte.js +22 -0
  128. package/dist/hooks/xr/useAnchors.svelte.d.ts +0 -0
  129. package/dist/hooks/xr/useAnchors.svelte.js +1 -0
  130. package/dist/index.d.ts +1 -0
  131. package/dist/index.js +1 -0
  132. package/dist/keybindings.d.ts +12 -0
  133. package/dist/keybindings.js +12 -0
  134. package/dist/loaders/pcd/index.d.ts +4 -0
  135. package/dist/loaders/pcd/index.js +13 -0
  136. package/dist/loaders/pcd/worker.d.ts +1 -0
  137. package/dist/loaders/pcd/worker.js +26 -0
  138. package/dist/three/AxesHelper.d.ts +5 -0
  139. package/dist/three/AxesHelper.js +35 -0
  140. package/dist/three/BatchedArrow.d.ts +30 -0
  141. package/dist/three/BatchedArrow.js +126 -0
  142. package/dist/three/BoxHelper.d.ts +50 -0
  143. package/dist/three/BoxHelper.js +134 -0
  144. package/dist/three/CapsuleGeometry.d.ts +10 -0
  145. package/dist/three/CapsuleGeometry.js +17 -0
  146. package/dist/transform.d.ts +11 -0
  147. package/dist/transform.js +65 -0
  148. package/package.json +110 -0
@@ -0,0 +1,41 @@
1
+ <script lang="ts">
2
+ import { provideFrames } from '../hooks/useFrames.svelte'
3
+ import { provideGeometries } from '../hooks/useGeometries.svelte'
4
+ import { providePointclouds } from '../hooks/usePointclouds.svelte'
5
+ import { providePoses } from '../hooks/usePoses.svelte'
6
+ import { usePartID } from '../hooks/usePartID.svelte'
7
+ import { provideSelection } from '../hooks/useSelection.svelte'
8
+ import { provideStaticGeometries } from '../hooks/useStaticGeometries.svelte'
9
+ import { provideVisibility } from '../hooks/useVisibility.svelte'
10
+ import { provideShapes } from '../hooks/useShapes.svelte'
11
+ import { provideRefreshRates } from '../hooks/useRefreshRates.svelte'
12
+ import { provideTransformControls } from '../hooks/useControls.svelte'
13
+ import type { Snippet } from 'svelte'
14
+ import { provideObjects } from '../hooks/useObjects.svelte'
15
+ import { provideMotionClient } from '../hooks/useMotionClient.svelte'
16
+
17
+ interface Props {
18
+ children: Snippet<[{ focus: boolean }]>
19
+ }
20
+
21
+ let { children }: Props = $props()
22
+
23
+ const partID = usePartID()
24
+
25
+ provideTransformControls()
26
+ provideVisibility()
27
+ provideRefreshRates()
28
+
29
+ provideStaticGeometries()
30
+ provideShapes()
31
+ provideFrames(() => partID.current)
32
+ providePoses(() => partID.current)
33
+ provideGeometries(() => partID.current)
34
+ providePointclouds(() => partID.current)
35
+ provideMotionClient(() => partID.current)
36
+ provideObjects()
37
+
38
+ const { focus } = provideSelection()
39
+ </script>
40
+
41
+ {@render children({ focus: focus.current !== undefined })}
@@ -0,0 +1,9 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ children: Snippet<[{
4
+ focus: boolean;
5
+ }]>;
6
+ }
7
+ declare const SceneProviders: import("svelte").Component<Props, {}, "">;
8
+ type SceneProviders = ReturnType<typeof SceneProviders>;
9
+ export default SceneProviders;
@@ -0,0 +1,44 @@
1
+ <script lang="ts">
2
+ import { Box3, Object3D } from 'three'
3
+ import { T, useTask, useThrelte } from '@threlte/core'
4
+ import { useSelectedObject } from '../hooks/useSelection.svelte'
5
+ import { BoxHelper } from '../three/BoxHelper'
6
+
7
+ const { scene } = useThrelte()
8
+
9
+ const box = new BoxHelper(new Object3D(), 0x000000)
10
+ const selected = useSelectedObject()
11
+
12
+ const { start, stop } = useTask(() => box.update(), { autoStart: false })
13
+
14
+ const box3 = new Box3()
15
+
16
+ $effect.pre(() => {
17
+ if (selected.current) {
18
+ start()
19
+ } else {
20
+ stop()
21
+ }
22
+ })
23
+
24
+ $effect.pre(() => {
25
+ if (!selected.current) {
26
+ box.visible = false
27
+ return
28
+ }
29
+
30
+ box.visible = true
31
+
32
+ if (selected.current.metadata.batched) {
33
+ selected.current.metadata.getBoundingBoxAt?.(box3)
34
+ box.setFromBox3(box3)
35
+ } else {
36
+ const object3d = scene.getObjectByProperty('uuid', selected.current.uuid)
37
+ if (object3d) {
38
+ box.setFromObject(object3d)
39
+ }
40
+ }
41
+ })
42
+ </script>
43
+
44
+ <T is={box} />
@@ -0,0 +1,3 @@
1
+ declare const Selected: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type Selected = ReturnType<typeof Selected>;
3
+ export default Selected;
@@ -0,0 +1,49 @@
1
+ <script lang="ts">
2
+ import { T } from '@threlte/core'
3
+ import { Portal, PortalTarget } from './portal'
4
+ import { useShapes } from '../hooks/useShapes.svelte'
5
+ import WorldObject from './WorldObject.svelte'
6
+ import Frame from './Frame.svelte'
7
+
8
+ const shapes = useShapes()
9
+ </script>
10
+
11
+ <T
12
+ name={shapes.object3ds.batchedArrow.object3d.name}
13
+ is={shapes.object3ds.batchedArrow.object3d}
14
+ dispose={false}
15
+ />
16
+
17
+ {#each shapes.meshes as object (object.uuid)}
18
+ <Portal id={object.referenceFrame}>
19
+ <Frame
20
+ uuid={object.uuid}
21
+ name={object.name}
22
+ pose={object.pose}
23
+ geometry={object.geometry}
24
+ metadata={object.metadata}
25
+ >
26
+ <PortalTarget id={object.name} />
27
+ </Frame>
28
+ </Portal>
29
+ {/each}
30
+
31
+ {#each shapes.nurbs as object (object.uuid)}
32
+ <Portal id={object.referenceFrame}>
33
+ <Frame
34
+ uuid={object.uuid}
35
+ name={object.name}
36
+ pose={object.pose}
37
+ geometry={object.geometry}
38
+ metadata={object.metadata}
39
+ >
40
+ <PortalTarget id={object.name} />
41
+ </Frame>
42
+ </Portal>
43
+ {/each}
44
+
45
+ {#each shapes.models as object (object.uuid)}
46
+ <WorldObject {object}>
47
+ <PortalTarget id={object.name} />
48
+ </WorldObject>
49
+ {/each}
@@ -0,0 +1,18 @@
1
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
+ $$bindings?: Bindings;
4
+ } & Exports;
5
+ (internal: unknown, props: {
6
+ $$events?: Events;
7
+ $$slots?: Slots;
8
+ }): Exports & {
9
+ $set?: any;
10
+ $on?: any;
11
+ };
12
+ z_$$bindings?: Bindings;
13
+ }
14
+ declare const Shapes: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type Shapes = InstanceType<typeof Shapes>;
18
+ export default Shapes;
@@ -0,0 +1,79 @@
1
+ <script lang="ts">
2
+ import { TransformControls } from '@threlte/extras'
3
+ import { useSelected } from '../hooks/useSelection.svelte'
4
+ import { useStaticGeometries } from '../hooks/useStaticGeometries.svelte'
5
+ import { useTransformControls } from '../hooks/useControls.svelte'
6
+ import { Keybindings } from '../keybindings'
7
+ import { PersistedState } from 'runed'
8
+ import { quaternionToPose, scaleToDimensions, vector3ToPose } from '../transform'
9
+ import { Quaternion, Vector3 } from 'three'
10
+ import Frame from './Frame.svelte'
11
+
12
+ type Modes = 'translate' | 'rotate' | 'scale'
13
+
14
+ const transformControls = useTransformControls()
15
+ const geometries = useStaticGeometries()
16
+ const selected = useSelected()
17
+
18
+ let mode = new PersistedState<Modes>('transform-mode', 'translate')
19
+
20
+ const quaternion = new Quaternion()
21
+ const vector3 = new Vector3()
22
+ </script>
23
+
24
+ <svelte:window
25
+ onkeydown={(event) => {
26
+ if (event.metaKey || event.ctrlKey) {
27
+ return
28
+ }
29
+
30
+ const key = event.key.toLowerCase()
31
+
32
+ if (key === Keybindings.ADD_GEOMETRY) {
33
+ geometries.add()
34
+ } else if (key === Keybindings.REMOVE_GEOMETRY) {
35
+ geometries.remove(selected.current ?? '')
36
+ } else if (key === Keybindings.TRANSLATE) {
37
+ mode.current = 'translate'
38
+ } else if (key === Keybindings.ROTATE) {
39
+ mode.current = 'rotate'
40
+ } else if (key === Keybindings.SCALE) {
41
+ mode.current = 'scale'
42
+ }
43
+ }}
44
+ />
45
+
46
+ {#each geometries.current as object (object.uuid)}
47
+ <Frame
48
+ uuid={object.uuid}
49
+ name={object.name}
50
+ pose={object.pose}
51
+ geometry={object.geometry}
52
+ metadata={object.metadata}
53
+ >
54
+ {#snippet children({ ref })}
55
+ {#if selected.current === ref.uuid}
56
+ {#key mode.current}
57
+ <TransformControls
58
+ object={ref}
59
+ mode={mode.current}
60
+ onmouseDown={() => transformControls.setActive(true)}
61
+ onmouseUp={() => {
62
+ transformControls.setActive(false)
63
+
64
+ if (mode.current === 'translate') {
65
+ vector3ToPose(ref.getWorldPosition(vector3), object.pose)
66
+ } else if (mode.current === 'rotate') {
67
+ quaternionToPose(ref.getWorldQuaternion(quaternion), object.pose)
68
+ ref.quaternion.copy(quaternion)
69
+ } else if (mode.current === 'scale' && object.geometry?.case === 'box') {
70
+ scaleToDimensions(ref.scale, object.geometry)
71
+ ref.scale.setScalar(1)
72
+ }
73
+ }}
74
+ />
75
+ {/key}
76
+ {/if}
77
+ {/snippet}
78
+ </Frame>
79
+ {/each}
@@ -0,0 +1,18 @@
1
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
+ $$bindings?: Bindings;
4
+ } & Exports;
5
+ (internal: unknown, props: {
6
+ $$events?: Events;
7
+ $$slots?: Slots;
8
+ }): Exports & {
9
+ $set?: any;
10
+ $on?: any;
11
+ };
12
+ z_$$bindings?: Bindings;
13
+ }
14
+ declare const StaticGeometries: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type StaticGeometries = InstanceType<typeof StaticGeometries>;
18
+ export default StaticGeometries;
@@ -0,0 +1,54 @@
1
+ <script lang="ts">
2
+ import { PersistedState } from 'runed'
3
+ import { Icon, Select } from '@viamrobotics/prime-core'
4
+ import RefreshRate from '../RefreshRate.svelte'
5
+ import { useMotionClient } from '../../hooks/useMotionClient.svelte'
6
+
7
+ const showSettings = new PersistedState('show-settings', false)
8
+
9
+ const motionClient = useMotionClient()
10
+ </script>
11
+
12
+ <button
13
+ class="border-medium w-full border-t p-2 text-left"
14
+ onclick={() => (showSettings.current = !showSettings.current)}
15
+ >
16
+ <h3 class="text-default flex items-center gap-1.5">
17
+ <Icon
18
+ name={showSettings.current ? 'unfold-more-horizontal' : 'unfold-less-horizontal'}
19
+ label="unfold more icon"
20
+ variant="ghost"
21
+ cx="size-6"
22
+ on:click={() => (showSettings.current = !showSettings.current)}
23
+ />
24
+ Settings
25
+ </h3>
26
+ </button>
27
+
28
+ {#if showSettings.current}
29
+ <div class="border-medium flex flex-col gap-2 border-t p-3">
30
+ <RefreshRate name="Frames">
31
+ <option value="0">Do not fetch</option>
32
+ <option value="1">Fetch on reconfigure</option>
33
+ </RefreshRate>
34
+ <RefreshRate name="Pointclouds" />
35
+ <RefreshRate name="Geometries" />
36
+ <RefreshRate name="Poses" />
37
+
38
+ <label class="flex flex-col gap-1">
39
+ Motion client
40
+ <Select
41
+ onchange={(event: InputEvent) => {
42
+ if (event.target instanceof HTMLSelectElement) {
43
+ motionClient.set(event.target.value)
44
+ }
45
+ }}
46
+ value={motionClient.current}
47
+ >
48
+ {#each motionClient.names as name (name)}
49
+ <option>{name}</option>
50
+ {/each}
51
+ </Select>
52
+ </label>
53
+ </div>
54
+ {/if}
@@ -0,0 +1,18 @@
1
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
+ $$bindings?: Bindings;
4
+ } & Exports;
5
+ (internal: unknown, props: {
6
+ $$events?: Events;
7
+ $$slots?: Slots;
8
+ }): Exports & {
9
+ $set?: any;
10
+ $on?: any;
11
+ };
12
+ z_$$bindings?: Bindings;
13
+ }
14
+ declare const Settings: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type Settings = InstanceType<typeof Settings>;
18
+ export default Settings;
@@ -0,0 +1,204 @@
1
+ <script lang="ts">
2
+ import * as tree from '@zag-js/tree-view'
3
+ import { useMachine, normalizeProps } from '@zag-js/svelte'
4
+ import { untrack } from 'svelte'
5
+ import { ChevronRight, Eye, EyeOff } from 'lucide-svelte'
6
+ import { useVisibility } from '../../hooks/useVisibility.svelte'
7
+ import type { TreeNode } from './buildTree'
8
+ import { useExpanded } from './useExpanded.svelte'
9
+ import { VirtualList } from 'svelte-virtuallists'
10
+ import { observe } from '@threlte/core'
11
+
12
+ const visibility = useVisibility()
13
+ const expanded = useExpanded()
14
+
15
+ interface Props {
16
+ rootNode: TreeNode
17
+ selections: string[]
18
+ onSelectionChange?: (event: tree.SelectionChangeDetails) => void
19
+ }
20
+
21
+ let { rootNode, selections, onSelectionChange }: Props = $props()
22
+
23
+ const collection = tree.collection<TreeNode>({
24
+ nodeToValue: (node) => node.id,
25
+ nodeToString: (node) => node.name,
26
+ rootNode,
27
+ })
28
+
29
+ const service = useMachine(tree.machine, {
30
+ collection,
31
+ onSelectionChange(details) {
32
+ onSelectionChange?.(details)
33
+ },
34
+ onExpandedChange(details) {
35
+ expanded.clear()
36
+ for (const value of details.expandedValue) {
37
+ expanded.add(value)
38
+ }
39
+ },
40
+ })
41
+
42
+ const api = $derived(tree.connect(service, normalizeProps))
43
+
44
+ observe(
45
+ () => [selections],
46
+ () => untrack(() => api.setSelectedValue(selections))
47
+ )
48
+
49
+ observe(
50
+ () => [expanded],
51
+ () => untrack(() => api.setExpandedValue([...expanded]))
52
+ )
53
+
54
+ const rootChildren = $derived(collection.rootNode.children ?? [])
55
+ </script>
56
+
57
+ {#snippet treeNode({
58
+ node,
59
+ indexPath,
60
+ api,
61
+ }: {
62
+ node: TreeNode
63
+ indexPath: number[]
64
+ api: tree.Api
65
+ })}
66
+ {@const nodeProps = { indexPath, node }}
67
+ {@const nodeState = api.getNodeState(nodeProps)}
68
+ {@const isVisible = visibility.get(node.id) ?? true}
69
+ {@const { selected } = nodeState}
70
+
71
+ {#if nodeState.isBranch}
72
+ {@const { expanded } = nodeState}
73
+ {@const { children = [] } = node}
74
+ <div
75
+ {...api.getBranchProps(nodeProps)}
76
+ class={{
77
+ 'text-disabled': !isVisible,
78
+ 'bg-medium': selected,
79
+ sticky: true,
80
+ }}
81
+ >
82
+ <div {...api.getBranchControlProps(nodeProps)}>
83
+ <span
84
+ {...api.getBranchIndicatorProps(nodeProps)}
85
+ class={{ 'rotate-90': expanded }}
86
+ >
87
+ <ChevronRight size={14} />
88
+ </span>
89
+ <span
90
+ class="flex items-center"
91
+ {...api.getBranchTextProps(nodeProps)}
92
+ >
93
+ {node.name}
94
+ </span>
95
+
96
+ <button
97
+ class="text-gray-6"
98
+ onclick={(event) => {
99
+ event.stopPropagation()
100
+ visibility.set(node.id, !isVisible)
101
+ }}
102
+ >
103
+ {#if isVisible}
104
+ <Eye size={14} />
105
+ {:else}
106
+ <EyeOff size={14} />
107
+ {/if}
108
+ </button>
109
+ </div>
110
+ <div {...api.getBranchContentProps(nodeProps)}>
111
+ <div {...api.getBranchIndentGuideProps(nodeProps)}></div>
112
+
113
+ {#each children as node, index (node.id)}
114
+ {@render treeNode({ node, indexPath: [...indexPath, index], api })}
115
+ {/each}
116
+ </div>
117
+ </div>
118
+ {:else}
119
+ <div
120
+ class={{ 'flex justify-between': true, 'text-disabled': !isVisible, 'bg-medium': selected }}
121
+ {...api.getItemProps(nodeProps)}
122
+ >
123
+ <span class="flex items-center gap-1.5">
124
+ {node.name}
125
+ </span>
126
+
127
+ <button
128
+ class="text-gray-6"
129
+ onclick={(event) => {
130
+ event.stopPropagation()
131
+ visibility.set(node.id, !isVisible)
132
+ }}
133
+ >
134
+ {#if isVisible}
135
+ <Eye size={14} />
136
+ {:else}
137
+ <EyeOff size={14} />
138
+ {/if}
139
+ </button>
140
+ </div>
141
+ {/if}
142
+ {/snippet}
143
+
144
+ <div class="root-node">
145
+ <div {...api.getRootProps() as object}>
146
+ <div class="border-medium border-b p-2">
147
+ <h3 {...api.getLabelProps() as object}>{rootNode.name}</h3>
148
+ </div>
149
+
150
+ <div
151
+ {...api.getTreeProps()}
152
+ class="w-[240px]"
153
+ >
154
+ {#if rootChildren.length === 0}
155
+ <p class="text-subtle-2 px-2 py-4">No objects displayed</p>
156
+ {:else}
157
+ <VirtualList
158
+ class="w-full"
159
+ style="height:{Math.min(8, Math.max(rootChildren.length, 5)) * 32}px;"
160
+ items={rootChildren}
161
+ >
162
+ {#snippet vl_slot({ index, item })}
163
+ {@render treeNode({ node: item, indexPath: [Number(index)], api })}
164
+ {/snippet}
165
+ </VirtualList>
166
+ {/if}
167
+ </div>
168
+ </div>
169
+ </div>
170
+
171
+ <style>
172
+ :global(:root) {
173
+ [data-scope='tree-view'][data-part='item'],
174
+ [data-scope='tree-view'][data-part='branch-control'] {
175
+ user-select: none;
176
+ --padding-inline: 16px;
177
+ padding-inline-start: calc(var(--depth) * var(--padding-inline));
178
+ padding-inline-end: var(--padding-inline);
179
+ display: flex;
180
+ align-items: center;
181
+ gap: 8px;
182
+ min-height: 32px;
183
+ }
184
+
185
+ [data-scope='tree-view'][data-part='item-text'],
186
+ [data-scope='tree-view'][data-part='branch-text'] {
187
+ flex: 1;
188
+ }
189
+
190
+ [data-scope='tree-view'][data-part='branch-content'] {
191
+ position: relative;
192
+ isolation: isolate;
193
+ }
194
+
195
+ [data-scope='tree-view'][data-part='branch-indent-guide'] {
196
+ position: absolute;
197
+ content: '';
198
+ border-left: 1px solid #eee;
199
+ height: 100%;
200
+ translate: calc(var(--depth) * 1.25rem);
201
+ z-index: 1;
202
+ }
203
+ }
204
+ </style>
@@ -0,0 +1,10 @@
1
+ import * as tree from '@zag-js/tree-view';
2
+ import type { TreeNode } from './buildTree';
3
+ interface Props {
4
+ rootNode: TreeNode;
5
+ selections: string[];
6
+ onSelectionChange?: (event: tree.SelectionChangeDetails) => void;
7
+ }
8
+ declare const Tree: import("svelte").Component<Props, {}, "">;
9
+ type Tree = ReturnType<typeof Tree>;
10
+ export default Tree;
@@ -0,0 +1,70 @@
1
+ <script lang="ts">
2
+ import { PersistedState } from 'runed'
3
+ import Tree from './Tree.svelte'
4
+ import { fly } from 'svelte/transition'
5
+ import { Keybindings } from '../../keybindings'
6
+ import { ListTree } from 'lucide-svelte'
7
+ import { buildTreeNodes, type TreeNode } from './buildTree'
8
+ import { useSelected } from '../../hooks/useSelection.svelte'
9
+ import { provideTreeExpandedContext } from './useExpanded.svelte'
10
+ import { isEqual } from 'lodash-es'
11
+ import { useObjects } from '../../hooks/useObjects.svelte'
12
+ import Settings from './Settings.svelte'
13
+
14
+ const showTreeview = new PersistedState('show-treeview', true)
15
+
16
+ provideTreeExpandedContext()
17
+
18
+ const selected = useSelected()
19
+ const objects = useObjects()
20
+
21
+ let rootNode = $state<TreeNode>({
22
+ id: 'world',
23
+ name: 'World',
24
+ children: [],
25
+ href: '/',
26
+ })
27
+
28
+ const nodes = $derived(buildTreeNodes(objects.current))
29
+
30
+ $effect.pre(() => {
31
+ if (!isEqual(rootNode.children, nodes)) {
32
+ rootNode.children = nodes
33
+ }
34
+ })
35
+ </script>
36
+
37
+ <svelte:window
38
+ onkeydown={({ key }) => {
39
+ if (key === Keybindings.TREEVIEW) {
40
+ showTreeview.current = !showTreeview.current
41
+ }
42
+ }}
43
+ />
44
+
45
+ <button
46
+ class="fixed top-2 left-2 p-2"
47
+ onclick={() => (showTreeview.current = !showTreeview.current)}
48
+ >
49
+ <ListTree />
50
+ </button>
51
+
52
+ {#if showTreeview.current}
53
+ <div
54
+ class="bg-extralight border-medium fixed top-0 left-0 m-2 overflow-y-auto border text-xs"
55
+ in:fly={{ duration: 250, x: -100 }}
56
+ out:fly={{ duration: 250, x: -100 }}
57
+ >
58
+ {#key rootNode}
59
+ <Tree
60
+ {rootNode}
61
+ selections={selected.current ? [selected.current] : []}
62
+ onSelectionChange={(event) => {
63
+ selected.set(event.selectedValue[0])
64
+ }}
65
+ />
66
+ {/key}
67
+
68
+ <Settings />
69
+ </div>
70
+ {/if}
@@ -0,0 +1,3 @@
1
+ declare const TreeContainer: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type TreeContainer = ReturnType<typeof TreeContainer>;
3
+ export default TreeContainer;
@@ -0,0 +1,11 @@
1
+ import type { WorldObject } from '../../WorldObject';
2
+ export interface TreeNode {
3
+ id: string;
4
+ name: string;
5
+ children?: TreeNode[];
6
+ href: string;
7
+ }
8
+ /**
9
+ * Creates a tree representing parent child / relationships from a set of frames.
10
+ */
11
+ export declare const buildTreeNodes: (objects: WorldObject[]) => TreeNode[];
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Creates a tree representing parent child / relationships from a set of frames.
3
+ */
4
+ export const buildTreeNodes = (objects) => {
5
+ const nodeMap = new Map();
6
+ const rootNodes = [];
7
+ for (const object of objects) {
8
+ const node = {
9
+ name: object.name,
10
+ id: object.uuid,
11
+ children: [],
12
+ href: `/selection/${object.name}`,
13
+ };
14
+ nodeMap.set(object.name, node);
15
+ if (object.referenceFrame === 'world') {
16
+ rootNodes.push(node);
17
+ }
18
+ }
19
+ for (const object of objects) {
20
+ if (object.referenceFrame !== 'world') {
21
+ const parentNode = nodeMap.get(object.referenceFrame);
22
+ const child = nodeMap.get(object.name);
23
+ if (parentNode && child) {
24
+ parentNode.children?.push(child);
25
+ }
26
+ }
27
+ }
28
+ return rootNodes;
29
+ };
@@ -0,0 +1,5 @@
1
+ import { SvelteSet } from 'svelte/reactivity';
2
+ type Context = SvelteSet<string>;
3
+ export declare const provideTreeExpandedContext: () => void;
4
+ export declare const useExpanded: () => Context;
5
+ export {};