@viamrobotics/motion-tools 1.9.1 → 1.10.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 (45) hide show
  1. package/dist/HoverUpdater.svelte.d.ts +19 -0
  2. package/dist/HoverUpdater.svelte.js +120 -0
  3. package/dist/components/App.svelte +28 -29
  4. package/dist/components/MeasureTool/MeasurePoint.svelte +3 -3
  5. package/dist/components/MeasureTool/MeasureTool.svelte +6 -6
  6. package/dist/components/SceneProviders.svelte +2 -0
  7. package/dist/components/hover/HoveredEntities.svelte +23 -0
  8. package/dist/components/hover/HoveredEntity.svelte +15 -0
  9. package/dist/components/hover/HoveredEntity.svelte.d.ts +3 -0
  10. package/dist/components/hover/HoveredEntityTooltip.svelte +70 -0
  11. package/dist/components/{HoveredEntityTooltip.svelte.d.ts → hover/HoveredEntityTooltip.svelte.d.ts} +2 -2
  12. package/dist/components/hover/LinkedHoveredEntity.svelte +55 -0
  13. package/dist/components/hover/LinkedHoveredEntity.svelte.d.ts +9 -0
  14. package/dist/components/overlay/AddRelationship.svelte +131 -0
  15. package/dist/components/overlay/AddRelationship.svelte.d.ts +7 -0
  16. package/dist/components/overlay/Details.svelte +35 -2
  17. package/dist/components/overlay/FloatingPanel.svelte +78 -0
  18. package/dist/components/overlay/FloatingPanel.svelte.d.ts +13 -0
  19. package/dist/components/overlay/{left-pane/RefreshRate.svelte → RefreshRate.svelte} +1 -1
  20. package/dist/components/overlay/ToggleGroup.svelte +22 -26
  21. package/dist/components/overlay/ToggleGroup.svelte.d.ts +6 -7
  22. package/dist/components/overlay/left-pane/TreeContainer.svelte +0 -2
  23. package/dist/components/overlay/settings/Settings.svelte +279 -0
  24. package/dist/components/overlay/settings/Tabs.svelte +54 -0
  25. package/dist/components/overlay/settings/Tabs.svelte.d.ts +12 -0
  26. package/dist/ecs/index.d.ts +1 -0
  27. package/dist/ecs/index.js +1 -0
  28. package/dist/ecs/relations.d.ts +7 -0
  29. package/dist/ecs/relations.js +7 -0
  30. package/dist/ecs/traits.d.ts +7 -2
  31. package/dist/ecs/traits.js +10 -5
  32. package/dist/hooks/useGeometries.svelte.js +1 -1
  33. package/dist/hooks/useLinked.svelte.d.ts +7 -0
  34. package/dist/hooks/useLinked.svelte.js +35 -0
  35. package/dist/hooks/useObjectEvents.svelte.js +35 -16
  36. package/dist/hooks/usePointcloudObjects.svelte.js +1 -1
  37. package/dist/hooks/usePointclouds.svelte.js +3 -3
  38. package/dist/hooks/usePose.svelte.js +1 -1
  39. package/package.json +4 -1
  40. package/dist/components/HoveredEntities.svelte +0 -19
  41. package/dist/components/HoveredEntityTooltip.svelte +0 -242
  42. package/dist/components/overlay/left-pane/Settings.svelte +0 -221
  43. /package/dist/components/{HoveredEntities.svelte.d.ts → hover/HoveredEntities.svelte.d.ts} +0 -0
  44. /package/dist/components/overlay/{left-pane/RefreshRate.svelte.d.ts → RefreshRate.svelte.d.ts} +0 -0
  45. /package/dist/components/overlay/{left-pane → settings}/Settings.svelte.d.ts +0 -0
@@ -0,0 +1,19 @@
1
+ import { Vector3 } from 'three';
2
+ import type { Entity } from 'koota';
3
+ import type { IntersectionEvent } from '@threlte/extras';
4
+ export interface HoverInfo {
5
+ index: number;
6
+ x: number;
7
+ y: number;
8
+ z: number;
9
+ oX: number;
10
+ oY: number;
11
+ oZ: number;
12
+ theta: number;
13
+ }
14
+ export declare const getClosestArrow: (positions: Float32Array, point: Vector3) => HoverInfo;
15
+ export declare const getClosestPoint: (positions: Float32Array, point: Vector3) => HoverInfo;
16
+ export declare const getPointAtIndex: (positions: Float32Array, index: number) => HoverInfo | null;
17
+ export declare const getArrowAtIndex: (positions: Float32Array, index: number) => HoverInfo | null;
18
+ export declare const updateHoverInfo: (entity: Entity, hoverEvent: IntersectionEvent<MouseEvent>) => HoverInfo | null;
19
+ export declare const getLinkedHoverInfo: (index: number, linkedEntity: Entity) => HoverInfo | null;
@@ -0,0 +1,120 @@
1
+ import { Vector3 } from 'three';
2
+ import { traits } from './ecs';
3
+ const hoverPosition = new Vector3();
4
+ export const getClosestArrow = (positions, point) => {
5
+ let smallestDistance = Infinity;
6
+ let index = -1;
7
+ for (let i = 0; i < positions.length; i += 6) {
8
+ const x = positions[i] / 1000;
9
+ const y = positions[i + 1] / 1000;
10
+ const z = positions[i + 2] / 1000;
11
+ const distance = point.distanceToSquared({ x, y, z });
12
+ if (distance < smallestDistance) {
13
+ smallestDistance = distance;
14
+ index = i;
15
+ }
16
+ }
17
+ return {
18
+ index: Math.floor(index / 6),
19
+ x: positions[index] / 1000,
20
+ y: positions[index + 1] / 1000,
21
+ z: positions[index + 2] / 1000,
22
+ oX: positions[index + 3],
23
+ oY: positions[index + 4],
24
+ oZ: positions[index + 5],
25
+ theta: 0,
26
+ };
27
+ };
28
+ export const getClosestPoint = (positions, point) => {
29
+ let smallestDistance = Infinity;
30
+ let index = -1;
31
+ for (let i = 0; i < positions.length; i += 3) {
32
+ const x = positions[i];
33
+ const y = positions[i + 1];
34
+ const z = positions[i + 2];
35
+ const distance = point.distanceToSquared({ x, y, z });
36
+ if (distance < smallestDistance) {
37
+ smallestDistance = distance;
38
+ index = i;
39
+ }
40
+ }
41
+ return {
42
+ index: Math.floor(index / 3),
43
+ x: positions[index],
44
+ y: positions[index + 1],
45
+ z: positions[index + 2],
46
+ oX: 0,
47
+ oY: 0,
48
+ oZ: 0,
49
+ theta: 0,
50
+ };
51
+ };
52
+ export const getPointAtIndex = (positions, index) => {
53
+ if (index < 0 || index >= positions.length / 3) {
54
+ return null;
55
+ }
56
+ return {
57
+ index,
58
+ x: positions[index * 3],
59
+ y: positions[index * 3 + 1],
60
+ z: positions[index * 3 + 2],
61
+ oX: 0,
62
+ oY: 0,
63
+ oZ: 0,
64
+ theta: 0,
65
+ };
66
+ };
67
+ export const getArrowAtIndex = (positions, index) => {
68
+ if (index < 0 || index >= positions.length / 6) {
69
+ return null;
70
+ }
71
+ return {
72
+ index,
73
+ x: positions[index * 6] / 1000,
74
+ y: positions[index * 6 + 1] / 1000,
75
+ z: positions[index * 6 + 2] / 1000,
76
+ oX: positions[index * 6 + 3],
77
+ oY: positions[index * 6 + 4],
78
+ oZ: positions[index * 6 + 5],
79
+ theta: 0,
80
+ };
81
+ };
82
+ export const updateHoverInfo = (entity, hoverEvent) => {
83
+ const { index, point } = hoverEvent;
84
+ if (index === -1) {
85
+ return null;
86
+ }
87
+ hoverPosition.set(point.x, point.y, point.z);
88
+ let hoverInfo = null;
89
+ if (entity.has(traits.Arrows)) {
90
+ const closestArrow = getClosestArrow(entity.get(traits.Positions), hoverPosition);
91
+ if (closestArrow) {
92
+ hoverInfo = closestArrow;
93
+ }
94
+ }
95
+ else if (entity.has(traits.Points)) {
96
+ const positions = entity.get(traits.BufferGeometry)?.attributes.position.array;
97
+ const closestPoint = getClosestPoint(positions, hoverPosition);
98
+ if (closestPoint) {
99
+ hoverInfo = closestPoint;
100
+ }
101
+ }
102
+ return hoverInfo;
103
+ };
104
+ export const getLinkedHoverInfo = (index, linkedEntity) => {
105
+ if (linkedEntity.has(traits.Arrows)) {
106
+ const closestArrow = getArrowAtIndex(linkedEntity.get(traits.Positions), index);
107
+ if (closestArrow) {
108
+ return closestArrow;
109
+ }
110
+ }
111
+ else if (linkedEntity.has(traits.Points)) {
112
+ const positions = linkedEntity.get(traits.BufferGeometry)?.attributes.position
113
+ .array;
114
+ const closestPoint = getPointAtIndex(positions, index);
115
+ if (closestPoint) {
116
+ return closestPoint;
117
+ }
118
+ }
119
+ return null;
120
+ };
@@ -27,7 +27,8 @@
27
27
  type DrawConnectionConfig,
28
28
  } from '../hooks/useDrawConnectionConfig.svelte'
29
29
  import Camera from './overlay/widgets/Camera.svelte'
30
- import HoveredEntities from './HoveredEntities.svelte'
30
+ import HoveredEntities from './hover/HoveredEntities.svelte'
31
+ import Settings from './overlay/settings/Settings.svelte'
31
32
 
32
33
  interface LocalConfigProps {
33
34
  getLocalPartConfig: () => Struct
@@ -125,37 +126,35 @@
125
126
 
126
127
  <XR {@attach domPortal(root)} />
127
128
 
128
- <Dashboard
129
- {@attach domPortal(root)}
130
- {dashboard}
131
- />
132
-
133
129
  {#if settings.current.renderSubEntityHoverDetail}
134
- <HoveredEntities {@attach domPortal(root)} />
135
- {/if}
136
- <Details {@attach domPortal(root)} />
137
- {#if environment.current.isStandalone}
138
- <LiveUpdatesBanner {@attach domPortal(root)} />
139
- {/if}
140
-
141
- {#if !focus}
142
- <TreeContainer {@attach domPortal(root)} />
143
- {/if}
144
-
145
- {#if !focus && settings.current.enableArmPositionsWidget}
146
- <ArmPositions {@attach domPortal(root)} />
147
- {/if}
148
-
149
- {#if !focus}
150
- {#each currentRobotCameraWidgets as cameraName (cameraName)}
151
- <Camera
152
- name={cameraName}
153
- {@attach domPortal(root)}
154
- />
155
- {/each}
130
+ <HoveredEntities />
156
131
  {/if}
157
132
 
158
- <FileDrop {@attach domPortal(root)} />
133
+ <!-- Overlays that need Threlte context -->
134
+ <div {@attach domPortal(root)}>
135
+ <FileDrop />
136
+ <Dashboard {dashboard} />
137
+ <Details />
138
+ <Settings />
139
+
140
+ {#if environment.current.isStandalone}
141
+ <LiveUpdatesBanner />
142
+ {/if}
143
+
144
+ {#if !focus}
145
+ <TreeContainer />
146
+ {/if}
147
+
148
+ {#if !focus && settings.current.enableArmPositionsWidget}
149
+ <ArmPositions />
150
+ {/if}
151
+
152
+ {#if !focus}
153
+ {#each currentRobotCameraWidgets as cameraName (cameraName)}
154
+ <Camera name={cameraName} />
155
+ {/each}
156
+ {/if}
157
+ </div>
159
158
  {/snippet}
160
159
  </SceneProviders>
161
160
  </Canvas>
@@ -28,21 +28,21 @@
28
28
  <div class="flex justify-between">
29
29
  <span class="text-subtle-2">x</span>
30
30
  <div>
31
- {position[0].toFixed(2)}<span class="text-subtle-2">m</span>
31
+ {position[0].toFixed(3)}<span class="text-subtle-2">m</span>
32
32
  </div>
33
33
  </div>
34
34
 
35
35
  <div class="flex justify-between">
36
36
  <span class="text-subtle-2">y</span>
37
37
  <div>
38
- {position[1].toFixed(2)}<span class="text-subtle-2">m</span>
38
+ {position[1].toFixed(3)}<span class="text-subtle-2">m</span>
39
39
  </div>
40
40
  </div>
41
41
 
42
42
  <div class="flex justify-between">
43
43
  <span class="text-subtle-2">z</span>
44
44
  <div>
45
- {position[2].toFixed(2)}<span class="text-subtle-2">m</span>
45
+ {position[2].toFixed(3)}<span class="text-subtle-2">m</span>
46
46
  </div>
47
47
  </div>
48
48
  </HTML>
@@ -104,12 +104,12 @@
104
104
  Enabled axes
105
105
  <ToggleGroup
106
106
  multiple
107
- buttons={[
108
- { value: 'x', on: settings.current.enableMeasureAxisX },
109
- { value: 'y', on: settings.current.enableMeasureAxisY },
110
- { value: 'z', on: settings.current.enableMeasureAxisZ },
107
+ options={[
108
+ { label: 'x', selected: settings.current.enableMeasureAxisX },
109
+ { label: 'y', selected: settings.current.enableMeasureAxisY },
110
+ { label: 'z', selected: settings.current.enableMeasureAxisZ },
111
111
  ]}
112
- onclick={(details) => {
112
+ onSelect={(details) => {
113
113
  settings.current.enableMeasureAxisX = details.includes('x')
114
114
  settings.current.enableMeasureAxisY = details.includes('y')
115
115
  settings.current.enableMeasureAxisZ = details.includes('z')
@@ -168,7 +168,7 @@
168
168
  zIndexRange={[3, 0]}
169
169
  >
170
170
  <div class="border border-black bg-white px-1 py-0.5 text-xs">
171
- {p1.distanceTo(p2).toFixed(2)}<span class="text-subtle-2">m</span>
171
+ {p1.distanceTo(p2).toFixed(3)}<span class="text-subtle-2">m</span>
172
172
  </div>
173
173
  </HTML>
174
174
  {/if}
@@ -21,6 +21,7 @@
21
21
  import { provideResourceByName } from '../hooks/useResourceByName.svelte'
22
22
  import { provide3DModels } from '../hooks/use3DModels.svelte'
23
23
  import { providePointcloudObjects } from '../hooks/usePointcloudObjects.svelte'
24
+ import { provideLinkedEntities } from '../hooks/useLinked.svelte'
24
25
 
25
26
  interface Props {
26
27
  cameraPose?: CameraPose
@@ -51,6 +52,7 @@
51
52
  provideFramelessComponents()
52
53
 
53
54
  const { focus } = provideSelection()
55
+ provideLinkedEntities()
54
56
  </script>
55
57
 
56
58
  {@render children({ focus: focus.current !== undefined })}
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import { traits, useTrait } from '../../ecs'
3
+ import { useSelectedEntity } from '../../hooks/useSelection.svelte'
4
+ import { useFocusedEntity } from '../../hooks/useSelection.svelte'
5
+ import HoveredEntity from './HoveredEntity.svelte'
6
+ import LinkedHoveredEntity from './LinkedHoveredEntity.svelte'
7
+ import { useLinkedEntities } from '../../hooks/useLinked.svelte'
8
+
9
+ const selectedEntity = useSelectedEntity()
10
+ const focusedEntity = useFocusedEntity()
11
+ const linkedEntities = useLinkedEntities()
12
+
13
+ const displayEntity = $derived(selectedEntity.current ?? focusedEntity.current)
14
+ const isHovered = useTrait(() => displayEntity, traits.Hovered)
15
+ </script>
16
+
17
+ {#if isHovered}
18
+ <HoveredEntity />
19
+
20
+ {#each linkedEntities.current as entity (entity)}
21
+ <LinkedHoveredEntity linkedEntity={entity} />
22
+ {/each}
23
+ {/if}
@@ -0,0 +1,15 @@
1
+ <script lang="ts">
2
+ import { traits, useTrait } from '../../ecs'
3
+ import HoveredEntityTooltip from './HoveredEntityTooltip.svelte'
4
+ import { useFocusedEntity, useSelectedEntity } from '../../hooks/useSelection.svelte'
5
+
6
+ const selectedEntity = useSelectedEntity()
7
+ const focusedEntity = useFocusedEntity()
8
+
9
+ const displayEntity = $derived(selectedEntity.current ?? focusedEntity.current)
10
+ const hoverInfo = useTrait(() => displayEntity, traits.InstancedPose)
11
+ </script>
12
+
13
+ {#if hoverInfo.current}
14
+ <HoveredEntityTooltip hoverInfo={hoverInfo.current} />
15
+ {/if}
@@ -0,0 +1,3 @@
1
+ declare const HoveredEntity: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type HoveredEntity = ReturnType<typeof HoveredEntity>;
3
+ export default HoveredEntity;
@@ -0,0 +1,70 @@
1
+ <script lang="ts">
2
+ import { HTML } from '@threlte/extras'
3
+ import { type HoverInfo } from '../../HoverUpdater.svelte'
4
+
5
+ interface Props {
6
+ hoverInfo: HoverInfo
7
+ }
8
+
9
+ let { hoverInfo }: Props = $props()
10
+ </script>
11
+
12
+ {#if hoverInfo}
13
+ <HTML
14
+ position={[hoverInfo.x, hoverInfo.y, hoverInfo.z]}
15
+ class="pointer-events-none"
16
+ zIndexRange={[3, 0]}
17
+ >
18
+ <div
19
+ class="border-medium pointer-events-none relative -mb-2 -translate-x-1/2 -translate-y-full border bg-white px-3 py-2.5 text-xs shadow-md"
20
+ >
21
+ <!-- Arrow -->
22
+ <div
23
+ class="border-medium absolute -bottom-[5px] left-1/2 size-2.5 -translate-x-1/2 rotate-45 border-r border-b bg-white"
24
+ ></div>
25
+
26
+ <div class="flex flex-col gap-2.5">
27
+ <div>
28
+ <div class="mb-1"><strong class="font-semibold">index</strong></div>
29
+ <div>{hoverInfo.index}</div>
30
+ </div>
31
+
32
+ <div>
33
+ <div class="mb-1">
34
+ <strong class="font-semibold">world position</strong>
35
+ <span class="text-subtle-2"> (m)</span>
36
+ </div>
37
+ <div class="flex gap-3">
38
+ <div>
39
+ <span class="text-subtle-2 mr-1">x </span>{hoverInfo.x.toFixed(2)}
40
+ </div>
41
+ <div>
42
+ <span class="text-subtle-2 mr-1">y </span>{hoverInfo.y.toFixed(2)}
43
+ </div>
44
+ <div>
45
+ <span class="text-subtle-2 mr-1">z </span>{hoverInfo.z.toFixed(2)}
46
+ </div>
47
+ </div>
48
+ </div>
49
+
50
+ <div>
51
+ <div class="mb-1">
52
+ <strong class="font-semibold">world orientation</strong>
53
+ <span class="text-subtle-2"> (deg)</span>
54
+ </div>
55
+ <div class="flex gap-3">
56
+ <div>
57
+ <span class="text-subtle-2 mr-1">x </span>{hoverInfo.oX.toFixed(2)}
58
+ </div>
59
+ <div>
60
+ <span class="text-subtle-2 mr-1">y </span>{hoverInfo.oY.toFixed(2)}
61
+ </div>
62
+ <div>
63
+ <span class="text-subtle-2 mr-1">z </span>{hoverInfo.oZ.toFixed(2)}
64
+ </div>
65
+ </div>
66
+ </div>
67
+ </div>
68
+ </div>
69
+ </HTML>
70
+ {/if}
@@ -1,6 +1,6 @@
1
- import type { Entity } from 'koota';
1
+ import { type HoverInfo } from '../../HoverUpdater.svelte';
2
2
  interface Props {
3
- hoveredEntity: Entity;
3
+ hoverInfo: HoverInfo;
4
4
  }
5
5
  declare const HoveredEntityTooltip: import("svelte").Component<Props, {}, "">;
6
6
  type HoveredEntityTooltip = ReturnType<typeof HoveredEntityTooltip>;
@@ -0,0 +1,55 @@
1
+ <script
2
+ lang="ts"
3
+ module
4
+ >
5
+ import { Parser } from 'expr-eval'
6
+
7
+ export const parser = new Parser()
8
+ </script>
9
+
10
+ <script lang="ts">
11
+ import { relations, traits } from '../../ecs'
12
+ import type { Entity } from 'koota'
13
+ import HoveredEntityTooltip from './HoveredEntityTooltip.svelte'
14
+ import { getLinkedHoverInfo, type HoverInfo } from '../../HoverUpdater.svelte'
15
+ import { useSelectedEntity } from '../../hooks/useSelection.svelte'
16
+ import { useFocusedEntity } from '../../hooks/useSelection.svelte'
17
+ import { useTrait } from '../../ecs'
18
+ import { SubEntityLinkType } from '../../ecs/relations'
19
+
20
+ interface Props {
21
+ linkedEntity: Entity
22
+ }
23
+
24
+ let { linkedEntity }: Props = $props()
25
+
26
+ const selectedEntity = useSelectedEntity()
27
+ const focusedEntity = useFocusedEntity()
28
+ const displayEntity = $derived(selectedEntity.current ?? focusedEntity.current)
29
+
30
+ const displayedHoverInfo = useTrait(() => displayEntity, traits.InstancedPose)
31
+
32
+ let hoverInfo = $state.raw<HoverInfo | null>(null)
33
+
34
+ $effect(() => {
35
+ if (displayEntity && displayedHoverInfo.current) {
36
+ const linkType = displayEntity?.get(relations.SubEntityLink(linkedEntity))?.type
37
+ if (linkType !== SubEntityLinkType.HoverLink) {
38
+ return
39
+ }
40
+ // Index Mapping is a formula with the variable 'index' in it, available operations can be found here: https://github.com/silentmatt/expr-eval/tree/master
41
+ const indexMapping =
42
+ displayEntity?.get(relations.SubEntityLink(linkedEntity))?.indexMapping ?? 'index'
43
+ const expression = parser.parse(indexMapping)
44
+ const resolvedIndex = expression.evaluate({ index: displayedHoverInfo.current.index })
45
+ const linkedHoverInfo = getLinkedHoverInfo(resolvedIndex, linkedEntity)
46
+ hoverInfo = linkedHoverInfo
47
+ } else {
48
+ hoverInfo = null
49
+ }
50
+ })
51
+ </script>
52
+
53
+ {#if hoverInfo}
54
+ <HoveredEntityTooltip {hoverInfo} />
55
+ {/if}
@@ -0,0 +1,9 @@
1
+ import { Parser } from 'expr-eval';
2
+ export declare const parser: Parser;
3
+ import type { Entity } from 'koota';
4
+ interface Props {
5
+ linkedEntity: Entity;
6
+ }
7
+ declare const LinkedHoveredEntity: import("svelte").Component<Props, {}, "">;
8
+ type LinkedHoveredEntity = ReturnType<typeof LinkedHoveredEntity>;
9
+ export default LinkedHoveredEntity;
@@ -0,0 +1,131 @@
1
+ <script lang="ts">
2
+ import type { Entity } from 'koota'
3
+ import { Button, Select, Input } from '@viamrobotics/prime-core'
4
+ import { traits, useQuery, relations, useTrait } from '../../ecs'
5
+ import { SubEntityLinkType } from '../../ecs/relations'
6
+
7
+ interface Props {
8
+ entity: Entity | undefined
9
+ }
10
+
11
+ const { entity }: Props = $props()
12
+
13
+ const allEntities = useQuery(traits.Name)
14
+ const name = useTrait(() => entity, traits.Name)
15
+ const entityNames = $derived.by(() => {
16
+ const currentEntityName = name.current
17
+ return allEntities.current
18
+ .map((e: Entity) => e.get(traits.Name))
19
+ .filter((n: string | undefined): n is string => n !== undefined && n !== currentEntityName)
20
+ .sort()
21
+ })
22
+
23
+ let showRelationshipOptions = $state(false)
24
+ let selectedRelationshipType = $state<string>('')
25
+ let selectedRelationshipEntity = $state<string>('')
26
+ let relationshipFormula = $state('index')
27
+
28
+ const linkType = $derived.by(() => {
29
+ return selectedRelationshipType === SubEntityLinkType.HoverLink
30
+ ? SubEntityLinkType.HoverLink
31
+ : null
32
+ })
33
+
34
+ function resetForm() {
35
+ selectedRelationshipType = ''
36
+ selectedRelationshipEntity = ''
37
+ relationshipFormula = 'index'
38
+ }
39
+
40
+ function handleAdd() {
41
+ if (!entity || !relationshipFormula.includes('index')) return
42
+ const selectedEntity = allEntities.current.find(
43
+ (e: Entity) => e.get(traits.Name) === selectedRelationshipEntity
44
+ )
45
+ if (selectedEntity) {
46
+ entity.add(
47
+ relations.SubEntityLink(selectedEntity, {
48
+ indexMapping: relationshipFormula,
49
+ type: linkType,
50
+ })
51
+ )
52
+ }
53
+ showRelationshipOptions = false
54
+ resetForm()
55
+ }
56
+ </script>
57
+
58
+ <Button
59
+ class="mt-2 w-full"
60
+ icon={showRelationshipOptions ? undefined : 'plus'}
61
+ variant={showRelationshipOptions ? 'dark' : 'primary'}
62
+ onclick={() => {
63
+ if (showRelationshipOptions) {
64
+ resetForm()
65
+ }
66
+ showRelationshipOptions = !showRelationshipOptions
67
+ }}>{showRelationshipOptions ? 'Cancel' : 'Add Relationship'}</Button
68
+ >
69
+
70
+ {#if showRelationshipOptions}
71
+ <div class="mt-2 flex flex-col gap-2">
72
+ <div>
73
+ <label
74
+ for="relationship-type-select"
75
+ class="text-subtle-2 mb-1 block text-xs">Relationship type</label
76
+ >
77
+ <Select
78
+ id="relationship-type-select"
79
+ aria-label="Select relationship type"
80
+ value={selectedRelationshipType}
81
+ onchange={(event: InputEvent) => {
82
+ selectedRelationshipType = (event.target as HTMLSelectElement).value as 'HoverLink'
83
+ }}
84
+ >
85
+ <option value="">Select a relationship type...</option>
86
+ <option value="HoverLink">HoverLink</option>
87
+ </Select>
88
+ </div>
89
+ <div>
90
+ <label
91
+ for="relationship-entity-select"
92
+ class="text-subtle-2 mb-1 block text-xs">Entity</label
93
+ >
94
+ <Select
95
+ id="relationship-entity-select"
96
+ aria-label="Select entity for relationship"
97
+ value={selectedRelationshipEntity}
98
+ onchange={(event: InputEvent) => {
99
+ selectedRelationshipEntity = (event.target as HTMLSelectElement).value
100
+ }}
101
+ >
102
+ <option value="">Select an entity...</option>
103
+ {#each entityNames as entityName (entityName)}
104
+ <option value={entityName}>{entityName}</option>
105
+ {/each}
106
+ </Select>
107
+ </div>
108
+ <div>
109
+ <label
110
+ for="relationship-formula-input"
111
+ class="text-subtle-2 mb-1 block text-xs">Index mapping</label
112
+ >
113
+ <Input
114
+ on:keydown={(e) => e.stopPropagation()}
115
+ id="relationship-formula-input"
116
+ aria-label="Math formula for index mapping"
117
+ bind:value={relationshipFormula}
118
+ placeholder="index"
119
+ />
120
+ </div>
121
+ <div>
122
+ <Button
123
+ class="w-full"
124
+ variant="primary"
125
+ onclick={handleAdd}
126
+ >
127
+ Add
128
+ </Button>
129
+ </div>
130
+ </div>
131
+ {/if}
@@ -0,0 +1,7 @@
1
+ import type { Entity } from 'koota';
2
+ interface Props {
3
+ entity: Entity | undefined;
4
+ }
5
+ declare const AddRelationship: import("svelte").Component<Props, {}, "">;
6
+ type AddRelationship = ReturnType<typeof AddRelationship>;
7
+ export default AddRelationship;