@viamrobotics/motion-tools 1.9.1 → 1.11.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 (136) hide show
  1. package/dist/HoverUpdater.svelte.d.ts +16 -0
  2. package/dist/HoverUpdater.svelte.js +78 -0
  3. package/dist/WorldObject.svelte.d.ts +27 -0
  4. package/dist/WorldObject.svelte.js +8 -55
  5. package/dist/{draw → buf/draw}/v1/drawing_pb.d.ts +6 -0
  6. package/dist/{draw → buf/draw}/v1/drawing_pb.js +7 -0
  7. package/dist/buf/draw/v1/service_connect.d.ts +122 -0
  8. package/dist/buf/draw/v1/service_connect.js +126 -0
  9. package/dist/buf/draw/v1/service_pb.d.ts +382 -0
  10. package/dist/buf/draw/v1/service_pb.js +612 -0
  11. package/dist/components/App.svelte +28 -30
  12. package/dist/components/Arrows/Arrows.svelte +16 -3
  13. package/dist/components/FileDrop/file-dropper.d.ts +1 -1
  14. package/dist/components/FileDrop/snapshot-dropper.js +1 -1
  15. package/dist/components/FileDrop/useFileDrop.svelte.d.ts +2 -1
  16. package/dist/components/Frame.svelte +1 -1
  17. package/dist/components/Geometry.svelte +113 -71
  18. package/dist/components/Geometry.svelte.d.ts +6 -7
  19. package/dist/components/MeasureTool/MeasurePoint.svelte +3 -3
  20. package/dist/components/MeasureTool/MeasureTool.svelte +6 -6
  21. package/dist/components/SceneProviders.svelte +4 -0
  22. package/dist/components/Snapshot.svelte +1 -1
  23. package/dist/components/Snapshot.svelte.d.ts +1 -1
  24. package/dist/components/hover/HoveredEntities.svelte +23 -0
  25. package/dist/components/hover/HoveredEntity.svelte +15 -0
  26. package/dist/components/hover/HoveredEntity.svelte.d.ts +3 -0
  27. package/dist/components/hover/HoveredEntityTooltip.svelte +70 -0
  28. package/dist/components/{HoveredEntityTooltip.svelte.d.ts → hover/HoveredEntityTooltip.svelte.d.ts} +2 -2
  29. package/dist/components/hover/LinkedHoveredEntity.svelte +55 -0
  30. package/dist/components/hover/LinkedHoveredEntity.svelte.d.ts +9 -0
  31. package/dist/components/overlay/AddRelationship.svelte +131 -0
  32. package/dist/components/overlay/AddRelationship.svelte.d.ts +7 -0
  33. package/dist/components/overlay/Details.svelte +55 -2
  34. package/dist/components/overlay/FloatingPanel.svelte +78 -0
  35. package/dist/components/overlay/FloatingPanel.svelte.d.ts +13 -0
  36. package/dist/components/overlay/{left-pane/RefreshRate.svelte → RefreshRate.svelte} +1 -1
  37. package/dist/components/overlay/ToggleGroup.svelte +22 -26
  38. package/dist/components/overlay/ToggleGroup.svelte.d.ts +6 -7
  39. package/dist/components/overlay/left-pane/TreeContainer.svelte +0 -4
  40. package/dist/components/overlay/settings/Settings.svelte +330 -0
  41. package/dist/components/overlay/settings/Tabs.svelte +54 -0
  42. package/dist/components/overlay/settings/Tabs.svelte.d.ts +12 -0
  43. package/dist/components/overlay/widgets/Camera.svelte +20 -12
  44. package/dist/components/xr/ArmTeleop.svelte +469 -0
  45. package/dist/components/xr/ArmTeleop.svelte.d.ts +10 -0
  46. package/dist/components/xr/CameraFeed.svelte +191 -47
  47. package/dist/components/xr/CameraFeed.svelte.d.ts +7 -0
  48. package/dist/components/xr/Controllers.svelte +45 -38
  49. package/dist/components/xr/Controllers.svelte.d.ts +2 -17
  50. package/dist/components/xr/Hands.svelte +2 -4
  51. package/dist/components/xr/JointLimitsWidget.svelte +209 -0
  52. package/dist/components/xr/JointLimitsWidget.svelte.d.ts +13 -0
  53. package/dist/components/xr/OriginMarker.svelte +1 -15
  54. package/dist/components/xr/XR.svelte +78 -5
  55. package/dist/components/xr/XRConfigPanel.svelte +449 -0
  56. package/dist/components/xr/XRConfigPanel.svelte.d.ts +11 -0
  57. package/dist/components/xr/XRControllerSettings.svelte +240 -0
  58. package/dist/components/xr/XRControllerSettings.svelte.d.ts +3 -0
  59. package/dist/components/xr/XRToast.svelte +215 -0
  60. package/dist/components/xr/XRToast.svelte.d.ts +3 -0
  61. package/dist/components/xr/math.d.ts +14 -0
  62. package/dist/components/xr/math.js +26 -0
  63. package/dist/components/xr/toasts.svelte.d.ts +20 -0
  64. package/dist/components/xr/toasts.svelte.js +32 -0
  65. package/dist/components/xr/useOrigin.svelte.d.ts +2 -2
  66. package/dist/components/xr/useOrigin.svelte.js +4 -4
  67. package/dist/ecs/index.d.ts +1 -0
  68. package/dist/ecs/index.js +1 -0
  69. package/dist/ecs/relations.d.ts +7 -0
  70. package/dist/ecs/relations.js +7 -0
  71. package/dist/ecs/traits.d.ts +15 -1
  72. package/dist/ecs/traits.js +19 -5
  73. package/dist/ecs/useTrait.svelte.d.ts +3 -3
  74. package/dist/frame.d.ts +0 -3
  75. package/dist/hooks/useArmKinematics.svelte.d.ts +12 -0
  76. package/dist/hooks/useArmKinematics.svelte.js +31 -0
  77. package/dist/hooks/useGeometries.svelte.js +47 -36
  78. package/dist/hooks/useLinked.svelte.d.ts +7 -0
  79. package/dist/hooks/useLinked.svelte.js +35 -0
  80. package/dist/hooks/useObjectEvents.svelte.js +52 -16
  81. package/dist/hooks/usePartConfig.svelte.d.ts +0 -35
  82. package/dist/hooks/usePartConfig.svelte.js +2 -2
  83. package/dist/hooks/usePointcloudObjects.svelte.js +45 -64
  84. package/dist/hooks/usePointclouds.svelte.js +13 -9
  85. package/dist/hooks/usePose.svelte.js +5 -2
  86. package/dist/hooks/useResourceByName.svelte.d.ts +7 -0
  87. package/dist/hooks/useResourceByName.svelte.js +2 -2
  88. package/dist/hooks/useSettings.svelte.d.ts +14 -0
  89. package/dist/hooks/useSettings.svelte.js +10 -0
  90. package/dist/hooks/useWorldState.svelte.d.ts +0 -8
  91. package/dist/lib.d.ts +1 -3
  92. package/dist/lib.js +1 -3
  93. package/dist/snapshot.d.ts +2 -2
  94. package/dist/snapshot.js +2 -2
  95. package/dist/three/InstancedArrows/raycast.d.ts +2 -4
  96. package/dist/three/InstancedArrows/raycast.js +5 -5
  97. package/dist/transform.js +1 -0
  98. package/package.json +7 -5
  99. package/dist/assert.d.ts +0 -14
  100. package/dist/assert.js +0 -21
  101. package/dist/components/BatchedGeometry.svelte +0 -0
  102. package/dist/components/BatchedGeometry.svelte.d.ts +0 -26
  103. package/dist/components/Detections.svelte +0 -41
  104. package/dist/components/Detections.svelte.d.ts +0 -3
  105. package/dist/components/DetectionsPlane.svelte +0 -23
  106. package/dist/components/DetectionsPlane.svelte.d.ts +0 -21
  107. package/dist/components/Geometry2.svelte +0 -211
  108. package/dist/components/Geometry2.svelte.d.ts +0 -19
  109. package/dist/components/HoveredEntities.svelte +0 -19
  110. package/dist/components/HoveredEntityTooltip.svelte +0 -242
  111. package/dist/components/overlay/left-pane/Settings.svelte +0 -221
  112. package/dist/components/overlay/left-pane/Widgets.svelte +0 -65
  113. package/dist/components/overlay/left-pane/Widgets.svelte.d.ts +0 -3
  114. package/dist/entries.d.ts +0 -1
  115. package/dist/entries.js +0 -3
  116. package/dist/hooks/index.d.ts +0 -0
  117. package/dist/hooks/index.js +0 -1
  118. package/dist/test.d.ts +0 -1
  119. package/dist/test.js +0 -1
  120. package/dist/three/BoxHelper.d.ts +0 -50
  121. package/dist/three/BoxHelper.js +0 -134
  122. /package/dist/{common → buf/common}/v1/common_pb.d.ts +0 -0
  123. /package/dist/{common → buf/common}/v1/common_pb.js +0 -0
  124. /package/dist/{draw → buf/draw}/v1/metadata_pb.d.ts +0 -0
  125. /package/dist/{draw → buf/draw}/v1/metadata_pb.js +0 -0
  126. /package/dist/{draw → buf/draw}/v1/scene_pb.d.ts +0 -0
  127. /package/dist/{draw → buf/draw}/v1/scene_pb.js +0 -0
  128. /package/dist/{draw → buf/draw}/v1/snapshot_pb.d.ts +0 -0
  129. /package/dist/{draw → buf/draw}/v1/snapshot_pb.js +0 -0
  130. /package/dist/{draw → buf/draw}/v1/transforms_pb.d.ts +0 -0
  131. /package/dist/{draw → buf/draw}/v1/transforms_pb.js +0 -0
  132. /package/dist/components/{HoveredEntities.svelte.d.ts → hover/HoveredEntities.svelte.d.ts} +0 -0
  133. /package/dist/components/overlay/{left-pane/RefreshRate.svelte.d.ts → RefreshRate.svelte.d.ts} +0 -0
  134. /package/dist/components/overlay/{left-pane → settings}/Settings.svelte.d.ts +0 -0
  135. /package/dist/components/{BentPlaneGeometry.svelte → xr/BentPlaneGeometry.svelte} +0 -0
  136. /package/dist/components/{BentPlaneGeometry.svelte.d.ts → xr/BentPlaneGeometry.svelte.d.ts} +0 -0
@@ -0,0 +1,330 @@
1
+ <script lang="ts">
2
+ import { Switch, Input } from '@viamrobotics/prime-core'
3
+ import { Portal } from '@threlte/extras'
4
+ import RefreshRate from '../RefreshRate.svelte'
5
+ import { useSettings } from '../../../hooks/useSettings.svelte'
6
+ import { useResourceNames } from '@viamrobotics/svelte-sdk'
7
+ import { usePartID } from '../../../hooks/usePartID.svelte'
8
+ import { RefreshRates, useMachineSettings } from '../../../hooks/useMachineSettings.svelte'
9
+ import { useGeometries } from '../../../hooks/useGeometries.svelte'
10
+ import { usePointClouds } from '../../../hooks/usePointclouds.svelte'
11
+ import { useThrelte } from '@threlte/core'
12
+ import { useRefetchPoses } from '../../../hooks/useRefetchPoses'
13
+ import FloatingPanel from '../FloatingPanel.svelte'
14
+ import DashboardButton from '../dashboard/Button.svelte'
15
+ import Tabs from './Tabs.svelte'
16
+ import { PersistedState } from 'runed'
17
+ import ToggleGroup from '../ToggleGroup.svelte'
18
+ import XRControllerSettings from '../../xr/XRControllerSettings.svelte'
19
+
20
+ const { invalidate } = useThrelte()
21
+ const partID = usePartID()
22
+ const cameras = useResourceNames(() => partID.current, 'camera')
23
+ const visionServices = useResourceNames(() => partID.current, 'vision')
24
+ const settings = useSettings()
25
+ const { disabledCameras, disabledVisionServices } = useMachineSettings()
26
+ const geometries = useGeometries()
27
+ const pointclouds = usePointClouds()
28
+ const { refetchPoses } = useRefetchPoses()
29
+
30
+ // Invalidate the renderer for any settings change
31
+ $effect(() => {
32
+ for (const key in settings.current) {
33
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
34
+ settings.current[key as keyof typeof settings.current]
35
+ }
36
+
37
+ invalidate()
38
+ })
39
+
40
+ const currentRobotCameraWidgets = $derived(
41
+ settings.current.openCameraWidgets[partID.current] || []
42
+ )
43
+
44
+ const isOpen = new PersistedState('settings-is-open', false)
45
+ const activeTab = new PersistedState('settings-active-tab', 'Connection')
46
+ </script>
47
+
48
+ <Portal id="dashboard">
49
+ <fieldset>
50
+ <DashboardButton
51
+ active
52
+ icon="cog"
53
+ description="Settings"
54
+ onclick={() => {
55
+ isOpen.current = !isOpen.current
56
+ }}
57
+ />
58
+ </fieldset>
59
+ </Portal>
60
+
61
+ {#snippet SectionTitle(title: string)}
62
+ <h3 class="border-gray-3 border-b py-1 text-sm"><strong>{title}</strong></h3>
63
+ {/snippet}
64
+
65
+ {#snippet Connection()}
66
+ <div class="flex flex-col gap-2.5 text-xs">
67
+ {@render SectionTitle('Polling rates')}
68
+
69
+ <RefreshRate
70
+ id={RefreshRates.poses}
71
+ label="Poses"
72
+ allowLive
73
+ onManualRefetch={() => {
74
+ refetchPoses()
75
+ geometries.refetch()
76
+ }}
77
+ />
78
+ <RefreshRate
79
+ id={RefreshRates.pointclouds}
80
+ label="Pointclouds"
81
+ onManualRefetch={() => {
82
+ pointclouds.refetch()
83
+ }}
84
+ />
85
+ </div>
86
+ {/snippet}
87
+
88
+ {#snippet Pointclouds()}
89
+ <div class="flex flex-col gap-1 text-xs">
90
+ <label class="flex items-center justify-between gap-2">
91
+ Default point size
92
+
93
+ <div class="w-20">
94
+ <Input
95
+ bind:value={settings.current.pointSize}
96
+ on:keydown={(event) => event.stopImmediatePropagation()}
97
+ />
98
+ </div>
99
+ </label>
100
+
101
+ <label class="flex items-center justify-between gap-2">
102
+ Default point color
103
+
104
+ <div class="w-20">
105
+ <Input
106
+ type="color"
107
+ bind:value={settings.current.pointColor}
108
+ on:keydown={(event) => event.stopImmediatePropagation()}
109
+ />
110
+ </div>
111
+ </label>
112
+
113
+ {@render SectionTitle('Enabled cameras')}
114
+
115
+ {#each cameras.current as camera (camera)}
116
+ <div class="flex items-center justify-between py-0.5 text-xs">
117
+ {camera.name}
118
+ <Switch
119
+ on={disabledCameras.get(camera.name) !== true}
120
+ on:change={(event) => {
121
+ disabledCameras.set(camera.name, !event.detail)
122
+ }}
123
+ />
124
+ </div>
125
+ {:else}
126
+ No cameras detected
127
+ {/each}
128
+ </div>
129
+ {/snippet}
130
+
131
+ {#snippet Vision()}
132
+ <div class="text-gray-9 flex flex-col gap-1 text-xs">
133
+ {@render SectionTitle('Enabled vision services')}
134
+
135
+ {#each visionServices.current as visionService (visionService)}
136
+ <div class="flex items-center justify-between py-0.5">
137
+ {visionService.name}
138
+ <Switch
139
+ on={disabledVisionServices.get(visionService.name) !== true}
140
+ on:change={(event) => {
141
+ disabledVisionServices.set(visionService.name, !event.detail)
142
+ }}
143
+ />
144
+ </div>
145
+ {:else}
146
+ No vision services detected
147
+ {/each}
148
+ </div>
149
+ {/snippet}
150
+
151
+ {#snippet Scene()}
152
+ <div class="text-gray-9 flex flex-col gap-1 text-xs">
153
+ <label class="flex items-center justify-between gap-2 py-1">
154
+ Arm Models
155
+
156
+ <ToggleGroup
157
+ multiple
158
+ options={[
159
+ {
160
+ label: 'Colliders',
161
+ value: 'colliders',
162
+ selected: settings.current.renderArmModels.includes('colliders'),
163
+ },
164
+ {
165
+ label: 'Model',
166
+ value: 'model',
167
+ selected: settings.current.renderArmModels.includes('model'),
168
+ },
169
+ ]}
170
+ onSelect={(value) => {
171
+ settings.current.renderArmModels = (value.join('+') || 'colliders') as
172
+ | 'colliders'
173
+ | 'model'
174
+ | 'colliders+model'
175
+
176
+ console.log(settings.current.renderArmModels)
177
+ }}
178
+ />
179
+ </label>
180
+
181
+ <label class="flex items-center justify-between gap-2">
182
+ Single item hover details <Switch bind:on={settings.current.renderSubEntityHoverDetail} />
183
+ </label>
184
+
185
+ <label class="flex items-center justify-between gap-2">
186
+ Object labels <Switch bind:on={settings.current.enableLabels} />
187
+ </label>
188
+
189
+ {@render SectionTitle('Grid')}
190
+
191
+ <label class="flex items-center justify-between gap-2 py-1">
192
+ Visible <Switch bind:on={settings.current.grid} />
193
+ </label>
194
+
195
+ <label class="flex items-center justify-between gap-2">
196
+ Cell size (m)
197
+
198
+ <div class="w-20">
199
+ <Input
200
+ bind:value={settings.current.gridCellSize}
201
+ on:keydown={(event) => event.stopImmediatePropagation()}
202
+ />
203
+ </div>
204
+ </label>
205
+
206
+ <label class="flex items-center justify-between gap-2">
207
+ Section size (m)
208
+
209
+ <div class="w-20">
210
+ <Input
211
+ bind:value={settings.current.gridSectionSize}
212
+ on:keydown={(event) => event.stopImmediatePropagation()}
213
+ />
214
+ </div>
215
+ </label>
216
+
217
+ <label class="flex items-center justify-between gap-2">
218
+ Fade distance (m)
219
+
220
+ <div class="w-20">
221
+ <Input
222
+ bind:value={settings.current.gridFadeDistance}
223
+ on:keydown={(event) => event.stopImmediatePropagation()}
224
+ />
225
+ </div>
226
+ </label>
227
+
228
+ {@render SectionTitle('Lines')}
229
+
230
+ <label class="flex items-center justify-between gap-2">
231
+ Thickness
232
+
233
+ <div class="w-20">
234
+ <Input
235
+ bind:value={settings.current.lineWidth}
236
+ on:keydown={(event) => event.stopImmediatePropagation()}
237
+ />
238
+ </div>
239
+ </label>
240
+
241
+ <label class="flex items-center justify-between gap-2">
242
+ Dot size
243
+
244
+ <div class="w-20">
245
+ <Input
246
+ bind:value={settings.current.lineDotSize}
247
+ on:keydown={(event) => event.stopImmediatePropagation()}
248
+ />
249
+ </div>
250
+ </label>
251
+ </div>
252
+ {/snippet}
253
+
254
+ {#snippet Stats()}
255
+ <div class="flex w-full flex-col gap-2.5 text-xs">
256
+ <label class="flex items-center justify-between gap-2">
257
+ Query devtools <Switch bind:on={settings.current.enableQueryDevtools} />
258
+ </label>
259
+
260
+ <label class="flex items-center justify-between gap-2">
261
+ Render stats <Switch bind:on={settings.current.renderStats} />
262
+ </label>
263
+ </div>
264
+ {/snippet}
265
+
266
+ {#snippet XR()}
267
+ <div class="flex flex-col gap-2.5 text-xs">
268
+ <XRControllerSettings />
269
+ </div>
270
+ {/snippet}
271
+
272
+ {#snippet Widgets()}
273
+ <div class="text-gray-9 flex flex-col gap-1 text-xs">
274
+ <label class="flex items-center justify-between gap-2 py-1">
275
+ Arm positions
276
+ <Switch bind:on={settings.current.enableArmPositionsWidget} />
277
+ </label>
278
+
279
+ {@render SectionTitle('Camera widgets')}
280
+
281
+ {#each cameras.current as camera (camera)}
282
+ {@const isWidgetOpen = currentRobotCameraWidgets.includes(camera.name)}
283
+ <div class="flex items-center justify-between gap-2 py-0.5">
284
+ <span class="min-w-0 truncate">{camera.name}</span>
285
+ <Switch
286
+ on={isWidgetOpen}
287
+ on:change={(event) => {
288
+ if (event.detail) {
289
+ settings.current.openCameraWidgets = {
290
+ ...settings.current.openCameraWidgets,
291
+ [partID.current]: [...currentRobotCameraWidgets, camera.name],
292
+ }
293
+ } else {
294
+ settings.current.openCameraWidgets = {
295
+ ...settings.current.openCameraWidgets,
296
+ [partID.current]: currentRobotCameraWidgets.filter(
297
+ (widget) => widget !== camera.name
298
+ ),
299
+ }
300
+ }
301
+ }}
302
+ />
303
+ </div>
304
+ {:else}
305
+ No cameras detected
306
+ {/each}
307
+ </div>
308
+ {/snippet}
309
+
310
+ <FloatingPanel
311
+ title="Settings"
312
+ bind:isOpen={isOpen.current}
313
+ defaultSize={{ width: 460, height: 500 }}
314
+ >
315
+ <Tabs
316
+ defaultTab={activeTab.current}
317
+ items={[
318
+ { label: 'Connection', content: Connection },
319
+ { label: 'Scene', content: Scene },
320
+ { label: 'Pointclouds', content: Pointclouds },
321
+ { label: 'Vision', content: Vision },
322
+ { label: 'Widgets', content: Widgets },
323
+ { label: 'Stats', content: Stats },
324
+ ...('xr' in navigator ? [{ label: 'VR / AR', content: XR }] : []),
325
+ ]}
326
+ onValueChange={(value) => {
327
+ activeTab.current = value
328
+ }}
329
+ />
330
+ </FloatingPanel>
@@ -0,0 +1,54 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte'
3
+ import * as tabs from '@zag-js/tabs'
4
+ import { useMachine, normalizeProps } from '@zag-js/svelte'
5
+
6
+ interface Props {
7
+ defaultTab?: string
8
+ onValueChange: (value: string) => void
9
+ items: {
10
+ label: string
11
+ content: Snippet
12
+ }[]
13
+ }
14
+
15
+ let { defaultTab, items, onValueChange }: Props = $props()
16
+
17
+ const id = $props.id()
18
+ const service = useMachine(tabs.machine, () => ({
19
+ id,
20
+ defaultValue: defaultTab,
21
+ onValueChange: (details) => onValueChange(details.value),
22
+ }))
23
+
24
+ const api = $derived(tabs.connect(service, normalizeProps))
25
+ </script>
26
+
27
+ <div
28
+ {...api.getRootProps()}
29
+ class="flex h-full gap-2 overflow-hidden"
30
+ >
31
+ <div
32
+ {...api.getListProps()}
33
+ class="bg-gray-1 flex h-full flex-col items-start p-2 text-sm"
34
+ >
35
+ {#each items as item (item.label)}
36
+ <button
37
+ {...api.getTriggerProps({ value: item.label })}
38
+ class="text-gray-8 w-full py-1 pr-8 pl-3 text-left"
39
+ class:bg-gray-2={api.focusedValue === item.label}
40
+ >
41
+ {item.label}
42
+ </button>
43
+ {/each}
44
+ </div>
45
+
46
+ {#each items as item (item.label)}
47
+ <div
48
+ {...api.getContentProps({ value: item.label })}
49
+ class="h-full w-full overflow-y-auto p-4"
50
+ >
51
+ {@render item.content()}
52
+ </div>
53
+ {/each}
54
+ </div>
@@ -0,0 +1,12 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ defaultTab?: string;
4
+ onValueChange: (value: string) => void;
5
+ items: {
6
+ label: string;
7
+ content: Snippet;
8
+ }[];
9
+ }
10
+ declare const Tabs: import("svelte").Component<Props, {}, "">;
11
+ type Tabs = ReturnType<typeof Tabs>;
12
+ export default Tabs;
@@ -1,8 +1,8 @@
1
1
  <script lang="ts">
2
2
  import { draggable } from '@neodrag/svelte'
3
3
  import { Icon, Select } from '@viamrobotics/prime-core'
4
- import { CameraStream, useRobotClient } from '@viamrobotics/svelte-sdk'
5
- import { StreamClient } from '@viamrobotics/sdk'
4
+ import { CameraStream, useRobotClient, useConnectionStatus } from '@viamrobotics/svelte-sdk'
5
+ import { StreamClient, MachineConnectionEvent } from '@viamrobotics/sdk'
6
6
  import { useSettings } from '../../../hooks/useSettings.svelte'
7
7
  import { usePartID } from '../../../hooks/usePartID.svelte'
8
8
  import { useEnvironment } from '../../../hooks/useEnvironment.svelte'
@@ -17,6 +17,7 @@
17
17
  const settings = useSettings()
18
18
  const partID = usePartID()
19
19
  const client = useRobotClient(() => partID.current)
20
+ const connectionStatus = useConnectionStatus(() => partID.current)
20
21
  const environment = useEnvironment()
21
22
 
22
23
  let dragElement = $state.raw<HTMLElement>()
@@ -70,13 +71,18 @@
70
71
  }
71
72
  }
72
73
 
73
- // Create a single StreamClient instance per robot client
74
- let streamClient = $derived(client.current ? new StreamClient(client.current) : undefined)
74
+ // Only create StreamClient when connection is fully established
75
+ let streamClient = $derived(
76
+ client.current && connectionStatus.current === MachineConnectionEvent.CONNECTED
77
+ ? new StreamClient(client.current)
78
+ : undefined
79
+ )
75
80
 
76
81
  $effect(() => {
77
82
  if (streamClient) {
78
83
  isLoading = true
79
84
  error = undefined
85
+
80
86
  streamClient
81
87
  .getOptions(name)
82
88
  .then((options) => {
@@ -164,14 +170,16 @@
164
170
  class="relative min-h-0 w-full flex-1 overflow-hidden bg-black [&_img]:h-full [&_img]:w-full [&_img]:object-fill [&_video]:h-full [&_video]:w-full [&_video]:object-fill"
165
171
  style:aspect-ratio={aspectRatio}
166
172
  >
167
- {#key environment.current.viewerMode === 'monitor'}
168
- <CameraStream
169
- {name}
170
- partID={partID.current}
171
- onloadedmetadata={onMediaLoad}
172
- onload={onMediaLoad}
173
- />
174
- {/key}
173
+ {#if connectionStatus.current === MachineConnectionEvent.CONNECTED}
174
+ {#key environment.current.viewerMode === 'monitor'}
175
+ <CameraStream
176
+ {name}
177
+ partID={partID.current}
178
+ onloadedmetadata={onMediaLoad}
179
+ onload={onMediaLoad}
180
+ />
181
+ {/key}
182
+ {/if}
175
183
 
176
184
  <!-- FPS Pill -->
177
185
  {#if fps > 0}