@viamrobotics/test-widgets 0.6.1 → 0.8.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 (104) hide show
  1. package/README.md +1 -1
  2. package/dist/api-docs-href.d.ts +8 -0
  3. package/dist/api-docs-href.js +17 -0
  4. package/dist/client-map.d.ts +30 -30
  5. package/dist/client-map.js +32 -29
  6. package/dist/components/angle-unit-toggle.svelte +1 -4
  7. package/dist/components/api-section.svelte +21 -23
  8. package/dist/components/api-section.svelte.d.ts +2 -0
  9. package/dist/components/index.d.ts +21 -0
  10. package/dist/components/index.js +21 -0
  11. package/dist/components/is-moving.svelte +4 -5
  12. package/dist/components/is-moving.svelte.d.ts +1 -0
  13. package/dist/components/mutation-section.svelte +20 -23
  14. package/dist/components/mutation-section.svelte.d.ts +2 -0
  15. package/dist/components/section-title.svelte +63 -0
  16. package/dist/components/section-title.svelte.d.ts +14 -0
  17. package/dist/components/widgets/arm/arm.svelte +25 -29
  18. package/dist/components/widgets/arm/get-joint-positions-widget.svelte +36 -0
  19. package/dist/components/widgets/arm/get-joint-positions-widget.svelte.d.ts +7 -0
  20. package/dist/components/widgets/arm/is-moving-widget.svelte +20 -0
  21. package/dist/components/widgets/arm/is-moving-widget.svelte.d.ts +7 -0
  22. package/dist/components/widgets/arm/joint-position-editor.svelte +89 -0
  23. package/dist/components/widgets/arm/joint-position-editor.svelte.d.ts +11 -0
  24. package/dist/components/widgets/arm/joint-position-quick-move.svelte +85 -0
  25. package/dist/components/widgets/arm/joint-position-quick-move.svelte.d.ts +9 -0
  26. package/dist/components/widgets/arm/joint-position-slider.svelte +36 -0
  27. package/dist/components/widgets/arm/joint-position-slider.svelte.d.ts +9 -0
  28. package/dist/components/widgets/arm/move-to-joint-positions-widget.svelte +9 -5
  29. package/dist/components/widgets/arm/move-to-joint-positions.svelte +73 -127
  30. package/dist/components/widgets/arm/move-to-joint-positions.svelte.d.ts +1 -0
  31. package/dist/components/widgets/arm/move-to-position.svelte +31 -3
  32. package/dist/components/widgets/audio-input/audio-input.svelte +2 -0
  33. package/dist/components/widgets/audio-input/get-properties-widget.svelte +43 -0
  34. package/dist/components/widgets/audio-input/get-properties-widget.svelte.d.ts +7 -0
  35. package/dist/components/widgets/audio-output/audio-output.svelte +2 -0
  36. package/dist/components/widgets/audio-output/get-properties-widget.svelte +43 -0
  37. package/dist/components/widgets/audio-output/get-properties-widget.svelte.d.ts +7 -0
  38. package/dist/components/widgets/base/base.svelte +9 -1
  39. package/dist/components/widgets/base/is-moving-widget.svelte +20 -0
  40. package/dist/components/widgets/base/is-moving-widget.svelte.d.ts +7 -0
  41. package/dist/components/widgets/base/quick-move.svelte +12 -5
  42. package/dist/components/widgets/camera/camera.svelte +156 -142
  43. package/dist/components/widgets/camera/live-or-polling-video.svelte +9 -1
  44. package/dist/components/widgets/discovery/discovery.svelte +1 -0
  45. package/dist/components/widgets/encoder/encoder-position-type.d.ts +8 -0
  46. package/dist/components/widgets/encoder/encoder-position-type.js +20 -0
  47. package/dist/components/widgets/encoder/encoder.svelte +5 -20
  48. package/dist/components/widgets/encoder/get-position-widget.svelte +54 -0
  49. package/dist/components/widgets/encoder/get-position-widget.svelte.d.ts +7 -0
  50. package/dist/components/widgets/gantry/gantry.svelte +28 -5
  51. package/dist/components/widgets/gantry/get-position-widget.svelte +43 -0
  52. package/dist/components/widgets/gantry/get-position-widget.svelte.d.ts +7 -0
  53. package/dist/components/widgets/gantry/is-moving-widget.svelte +20 -0
  54. package/dist/components/widgets/gantry/is-moving-widget.svelte.d.ts +7 -0
  55. package/dist/components/widgets/generic/generic.svelte +8 -1
  56. package/dist/components/widgets/gripper/gripper.svelte +11 -5
  57. package/dist/components/widgets/gripper/is-holding-something.svelte +2 -4
  58. package/dist/components/widgets/gripper/is-moving-widget.svelte +20 -0
  59. package/dist/components/widgets/gripper/is-moving-widget.svelte.d.ts +7 -0
  60. package/dist/components/widgets/input-controller/input-controller.svelte +2 -1
  61. package/dist/components/widgets/motor/is-moving-widget.svelte +20 -0
  62. package/dist/components/widgets/motor/is-moving-widget.svelte.d.ts +7 -0
  63. package/dist/components/widgets/motor/motor.svelte +13 -9
  64. package/dist/components/widgets/movement-sensor/get-accuracy-widget.svelte +38 -0
  65. package/dist/components/widgets/movement-sensor/get-accuracy-widget.svelte.d.ts +7 -0
  66. package/dist/components/widgets/movement-sensor/get-compass-heading-widget.svelte +38 -0
  67. package/dist/components/widgets/movement-sensor/get-compass-heading-widget.svelte.d.ts +7 -0
  68. package/dist/components/widgets/movement-sensor/get-orientation-widget.svelte +38 -0
  69. package/dist/components/widgets/movement-sensor/get-orientation-widget.svelte.d.ts +7 -0
  70. package/dist/components/widgets/movement-sensor/get-position-widget.svelte +38 -0
  71. package/dist/components/widgets/movement-sensor/get-position-widget.svelte.d.ts +7 -0
  72. package/dist/components/widgets/movement-sensor/movement-sensor.svelte +53 -26
  73. package/dist/components/widgets/power-sensor/get-current-widget.svelte +38 -0
  74. package/dist/components/widgets/power-sensor/get-current-widget.svelte.d.ts +7 -0
  75. package/dist/components/widgets/power-sensor/get-power-widget.svelte +38 -0
  76. package/dist/components/widgets/power-sensor/get-power-widget.svelte.d.ts +7 -0
  77. package/dist/components/widgets/power-sensor/get-voltage-widget.svelte +38 -0
  78. package/dist/components/widgets/power-sensor/get-voltage-widget.svelte.d.ts +7 -0
  79. package/dist/components/widgets/power-sensor/power-sensor.svelte +4 -0
  80. package/dist/components/widgets/sensor/sensor.svelte +1 -0
  81. package/dist/components/widgets/servo/is-moving-widget.svelte +20 -0
  82. package/dist/components/widgets/servo/is-moving-widget.svelte.d.ts +7 -0
  83. package/dist/components/widgets/servo/servo.svelte +10 -2
  84. package/dist/components/widgets/slam/get-position-widget.svelte +38 -0
  85. package/dist/components/widgets/slam/get-position-widget.svelte.d.ts +7 -0
  86. package/dist/components/widgets/slam/move-on-map.svelte +4 -1
  87. package/dist/components/widgets/slam/slam.svelte +4 -1
  88. package/dist/components/widgets/vision-service/context.svelte.d.ts +1 -0
  89. package/dist/components/widgets/vision-service/context.svelte.js +7 -0
  90. package/dist/components/widgets/vision-service/detection-row.svelte +38 -0
  91. package/dist/components/widgets/vision-service/detection-row.svelte.d.ts +8 -0
  92. package/dist/components/widgets/vision-service/detection.svelte +7 -4
  93. package/dist/components/widgets/vision-service/legend.svelte +14 -11
  94. package/dist/components/widgets/vision-service/vision-service.svelte +9 -10
  95. package/dist/index.d.ts +5 -1
  96. package/dist/index.js +6 -1
  97. package/dist/pip/context.svelte.d.ts +2 -0
  98. package/dist/pip/context.svelte.js +48 -20
  99. package/dist/resource-triplet.d.ts +37 -0
  100. package/dist/resource-triplet.js +37 -0
  101. package/dist/resource-widget.js +187 -0
  102. package/dist/resource.d.ts +0 -9
  103. package/dist/resource.js +0 -72
  104. package/package.json +7 -4
@@ -0,0 +1,38 @@
1
+ <script lang="ts">
2
+ import { PowerSensorClient } from '@viamrobotics/sdk'
3
+ import { createResourceClient, createResourceQuery } from '@viamrobotics/svelte-sdk'
4
+
5
+ import ApiSection from '../../api-section.svelte'
6
+ import Query from '../../query.svelte'
7
+
8
+ import PowerReading from './power-reading.svelte'
9
+
10
+ interface Props {
11
+ partID: string
12
+ resourceName: string
13
+ }
14
+
15
+ const { partID, resourceName }: Props = $props()
16
+
17
+ const client = createResourceClient(
18
+ PowerSensorClient,
19
+ () => partID,
20
+ () => resourceName
21
+ )
22
+
23
+ const query = createResourceQuery(client, 'getPower', { refetchInterval: 500 })
24
+ </script>
25
+
26
+ <ApiSection
27
+ title="GetPower"
28
+ class="grow"
29
+ >
30
+ <Query
31
+ {query}
32
+ contentCx="h-6"
33
+ >
34
+ {#if query.data !== undefined}
35
+ <PowerReading data={query.data} />
36
+ {/if}
37
+ </Query>
38
+ </ApiSection>
@@ -0,0 +1,7 @@
1
+ interface Props {
2
+ partID: string;
3
+ resourceName: string;
4
+ }
5
+ declare const GetPowerWidget: import("svelte").Component<Props, {}, "">;
6
+ type GetPowerWidget = ReturnType<typeof GetPowerWidget>;
7
+ export default GetPowerWidget;
@@ -0,0 +1,38 @@
1
+ <script lang="ts">
2
+ import { PowerSensorClient } from '@viamrobotics/sdk'
3
+ import { createResourceClient, createResourceQuery } from '@viamrobotics/svelte-sdk'
4
+
5
+ import ApiSection from '../../api-section.svelte'
6
+ import Query from '../../query.svelte'
7
+
8
+ import VoltageReading from './voltage-reading.svelte'
9
+
10
+ interface Props {
11
+ partID: string
12
+ resourceName: string
13
+ }
14
+
15
+ const { partID, resourceName }: Props = $props()
16
+
17
+ const client = createResourceClient(
18
+ PowerSensorClient,
19
+ () => partID,
20
+ () => resourceName
21
+ )
22
+
23
+ const query = createResourceQuery(client, 'getVoltage', { refetchInterval: 500 })
24
+ </script>
25
+
26
+ <ApiSection
27
+ title="GetVoltage"
28
+ class="grow"
29
+ >
30
+ <Query
31
+ {query}
32
+ contentCx="h-6"
33
+ >
34
+ {#if query.data !== undefined}
35
+ <VoltageReading data={query.data} />
36
+ {/if}
37
+ </Query>
38
+ </ApiSection>
@@ -0,0 +1,7 @@
1
+ interface Props {
2
+ partID: string;
3
+ resourceName: string;
4
+ }
5
+ declare const GetVoltageWidget: import("svelte").Component<Props, {}, "">;
6
+ type GetVoltageWidget = ReturnType<typeof GetVoltageWidget>;
7
+ export default GetVoltageWidget;
@@ -66,6 +66,7 @@
66
66
  <div class="grid w-full grid-cols-3 divide-x">
67
67
  <ApiSection
68
68
  title="GetCurrent"
69
+ api="rdk:component:power_sensor"
69
70
  class="pb-5"
70
71
  >
71
72
  <Query
@@ -79,6 +80,7 @@
79
80
  </ApiSection>
80
81
  <ApiSection
81
82
  title="GetVoltage"
83
+ api="rdk:component:power_sensor"
82
84
  class="pb-5"
83
85
  >
84
86
  <Query
@@ -92,6 +94,7 @@
92
94
  </ApiSection>
93
95
  <ApiSection
94
96
  title="GetPower"
97
+ api="rdk:component:power_sensor"
95
98
  class="pb-5"
96
99
  >
97
100
  <Query
@@ -107,6 +110,7 @@
107
110
 
108
111
  <ApiSection
109
112
  title="GetReadings"
113
+ api="rdk:component:power_sensor"
110
114
  description="Get all the measurements and data that this power sensor provides"
111
115
  >
112
116
  <Switch
@@ -44,6 +44,7 @@
44
44
 
45
45
  <ApiSection
46
46
  title="GetReadings"
47
+ api="rdk:component:sensor"
47
48
  class="relative"
48
49
  >
49
50
  <Query
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ import { ServoClient } from '@viamrobotics/sdk'
3
+
4
+ import IsMoving from '../../is-moving.svelte'
5
+ import { ResourceTriplets } from '../../../resource-triplet'
6
+
7
+ interface Props {
8
+ partID: string
9
+ resourceName: string
10
+ }
11
+
12
+ const { partID, resourceName }: Props = $props()
13
+ </script>
14
+
15
+ <IsMoving
16
+ client={ServoClient}
17
+ api={ResourceTriplets.Servo}
18
+ {partID}
19
+ {resourceName}
20
+ />
@@ -0,0 +1,7 @@
1
+ interface Props {
2
+ partID: string;
3
+ resourceName: string;
4
+ }
5
+ declare const IsMovingWidget: import("svelte").Component<Props, {}, "">;
6
+ type IsMovingWidget = ReturnType<typeof IsMovingWidget>;
7
+ export default IsMovingWidget;
@@ -52,6 +52,7 @@
52
52
  <div class="grid grow grid-cols-3 divide-x">
53
53
  <ApiSection
54
54
  title="GetPosition"
55
+ api="rdk:component:servo"
55
56
  bottomText="Updates automatically"
56
57
  >
57
58
  <Query query={positionQuery}>
@@ -65,7 +66,10 @@
65
66
  {/if}
66
67
  </Query>
67
68
  </ApiSection>
68
- <ApiSection title="Move">
69
+ <ApiSection
70
+ title="Move"
71
+ api="rdk:component:servo"
72
+ >
69
73
  <Query query={positionQuery}>
70
74
  {#if positionQuery.data !== undefined}
71
75
  <Move
@@ -92,7 +96,10 @@
92
96
  </ApiSection>
93
97
  </div>
94
98
  <div class="ml-auto flex w-full max-w-40 flex-col divide-y">
95
- <ApiSection title="Stop">
99
+ <ApiSection
100
+ title="Stop"
101
+ api="rdk:component:servo"
102
+ >
96
103
  <StopButton
97
104
  error={stopMutation.error}
98
105
  onStop={() => {
@@ -102,6 +109,7 @@
102
109
  </ApiSection>
103
110
  <IsMoving
104
111
  client={ServoClient}
112
+ api="rdk:component:servo"
105
113
  {partID}
106
114
  {resourceName}
107
115
  />
@@ -0,0 +1,38 @@
1
+ <script lang="ts">
2
+ import { SlamClient } from '@viamrobotics/sdk'
3
+ import { createResourceClient, createResourceQuery } from '@viamrobotics/svelte-sdk'
4
+
5
+ import ApiSection from '../../api-section.svelte'
6
+ import Query from '../../query.svelte'
7
+
8
+ import Position from './position.svelte'
9
+
10
+ interface Props {
11
+ partID: string
12
+ resourceName: string
13
+ }
14
+
15
+ const { partID, resourceName }: Props = $props()
16
+
17
+ const client = createResourceClient(
18
+ SlamClient,
19
+ () => partID,
20
+ () => resourceName
21
+ )
22
+
23
+ const query = createResourceQuery(client, 'getPosition', { refetchInterval: 1000 })
24
+ </script>
25
+
26
+ <ApiSection
27
+ title="GetPosition"
28
+ class="grow"
29
+ >
30
+ <Query
31
+ {query}
32
+ contentCx="h-6"
33
+ >
34
+ {#if query.data !== undefined}
35
+ <Position position={query.data} />
36
+ {/if}
37
+ </Query>
38
+ </ApiSection>
@@ -0,0 +1,7 @@
1
+ interface Props {
2
+ partID: string;
3
+ resourceName: string;
4
+ }
5
+ declare const GetPositionWidget: import("svelte").Component<Props, {}, "">;
6
+ type GetPositionWidget = ReturnType<typeof GetPositionWidget>;
7
+ export default GetPositionWidget;
@@ -22,7 +22,10 @@
22
22
  let planDeviationM = $state<number>()
23
23
  </script>
24
24
 
25
- <ApiSection title="MoveOnMap">
25
+ <ApiSection
26
+ title="MoveOnMap"
27
+ api="rdk:service:motion"
28
+ >
26
29
  <Label>
27
30
  Plan deviation <abbr>(m)</abbr>
28
31
 
@@ -200,7 +200,10 @@
200
200
  />
201
201
  </div>
202
202
 
203
- <ApiSection title="GetPosition">
203
+ <ApiSection
204
+ title="GetPosition"
205
+ api="rdk:service:slam"
206
+ >
204
207
  <Queries
205
208
  queries={[propertiesQuery, positionQuery]}
206
209
  contentCx="h-6"
@@ -16,6 +16,7 @@ export interface DetectionsContext {
16
16
  readonly current: Detection[];
17
17
  readonly byLabel: Record<string, DetectionGroup>;
18
18
  readonly hovered: SvelteSet<string>;
19
+ selected: string | null;
19
20
  }
20
21
  export declare const DETECTIONS_CONTEXT_KEY: unique symbol;
21
22
  export declare const createDetectionContext: (getDetections: () => DetectionPb[]) => DetectionsContext;
@@ -29,6 +29,7 @@ export const createDetectionContext = (getDetections) => {
29
29
  const detections = $derived(addIdsToDetections(getDetections()));
30
30
  const detectionsByLabel = $derived(categorize(detections));
31
31
  const hovered = new SvelteSet();
32
+ let selected = $state(null);
32
33
  const context = {
33
34
  get current() {
34
35
  return detections;
@@ -37,6 +38,12 @@ export const createDetectionContext = (getDetections) => {
37
38
  return detectionsByLabel;
38
39
  },
39
40
  hovered,
41
+ get selected() {
42
+ return selected;
43
+ },
44
+ set selected(value) {
45
+ selected = value;
46
+ },
40
47
  };
41
48
  return context;
42
49
  };
@@ -0,0 +1,38 @@
1
+ <script lang="ts">
2
+ import type { Detection } from './context.svelte.js'
3
+
4
+ import { useDetections } from './context.svelte.js'
5
+
6
+ interface Props {
7
+ detection: Detection
8
+ label: string
9
+ }
10
+
11
+ let { detection, label }: Props = $props()
12
+
13
+ const context = useDetections()
14
+
15
+ let node: HTMLElement | undefined
16
+
17
+ $effect(() => {
18
+ if (context.selected === detection.id && node) {
19
+ node.scrollIntoView({ block: 'nearest' })
20
+ }
21
+ })
22
+ </script>
23
+
24
+ <button
25
+ bind:this={node}
26
+ class={[
27
+ 'hover:bg-light w-full py-1 pl-11 text-left',
28
+ (context.hovered.has(detection.id) || context.selected === detection.id) && 'bg-light',
29
+ ]}
30
+ onpointerenter={() => context.hovered.add(detection.id)}
31
+ onpointerleave={() => context.hovered.delete(detection.id)}
32
+ onclick={() => {
33
+ context.selected = context.selected === detection.id ? null : detection.id
34
+ }}
35
+ >
36
+ {label}
37
+ <span class="text-subtle-2 pl-1">{detection.confidence}%</span>
38
+ </button>
@@ -0,0 +1,8 @@
1
+ import type { Detection } from './context.svelte.ts';
2
+ interface Props {
3
+ detection: Detection;
4
+ label: string;
5
+ }
6
+ declare const DetectionRow: import("svelte").Component<Props, {}, "">;
7
+ type DetectionRow = ReturnType<typeof DetectionRow>;
8
+ export default DetectionRow;
@@ -23,6 +23,9 @@
23
23
 
24
24
  let hovering = $state(false)
25
25
 
26
+ const isSelected = $derived(context.selected === detection.id)
27
+ const isActive = $derived(isSelected || context.hovered.has(detection.id))
28
+
26
29
  // These are ballparking, but seem good enough for nearly all cases
27
30
  const isDetectionNearTop = $derived(factoredyMin < 30)
28
31
  const isDetectionNearRight = $derived(factoredxMin > $size.width * 0.66)
@@ -32,10 +35,7 @@
32
35
  positionType="absolute"
33
36
  positionLeft={factoredxMin}
34
37
  positionTop={factoredyMin}
35
- borderColor="#aaa"
36
- hover={{
37
- borderColor: detection.color,
38
- }}
38
+ borderColor={isActive ? detection.color : '#aaa'}
39
39
  borderWidth={2}
40
40
  width={factoredxMax - factoredxMin}
41
41
  height={factoredyMax - factoredyMin}
@@ -47,6 +47,9 @@
47
47
  context.hovered.delete(detection.id)
48
48
  hovering = false
49
49
  }}
50
+ onclick={() => {
51
+ context.selected = context.selected === detection.id ? null : detection.id
52
+ }}
50
53
  >
51
54
  {#if hovering}
52
55
  <Text
@@ -5,6 +5,7 @@
5
5
  import { SvelteSet } from 'svelte/reactivity'
6
6
 
7
7
  import { useDetections } from './context.svelte'
8
+ import DetectionRow from './detection-row.svelte'
8
9
 
9
10
  interface Props {
10
11
  classifications?: Classification[]
@@ -46,6 +47,16 @@
46
47
  }
47
48
  })
48
49
 
50
+ $effect.pre(() => {
51
+ if (context.selected === undefined) return
52
+ for (const [label, { detections }] of Object.entries(context.byLabel)) {
53
+ if (detections.some((d) => d.id === context.selected)) {
54
+ expandedLabels.add(label)
55
+ break
56
+ }
57
+ }
58
+ })
59
+
49
60
  const compareConfidence = (a: Classification, b: Classification) => {
50
61
  return b.confidence - a.confidence
51
62
  }
@@ -113,18 +124,10 @@
113
124
  <ul>
114
125
  {#each detections as detection (detection.id)}
115
126
  <li>
116
- <button
117
- class="hover:bg-light w-full py-1 pl-11 text-left {context.hovered.has(
118
- detection.id
119
- )
120
- ? 'bg-light'
121
- : ''}"
122
- onpointerenter={() => context.hovered.add(detection.id)}
123
- onpointerleave={() => context.hovered.delete(detection.id)}
124
- >
127
+ <DetectionRow
128
+ {detection}
125
129
  {label}
126
- <span class="text-subtle-2 pl-1">{detection.confidence}%</span>
127
- </button>
130
+ />
128
131
  </li>
129
132
  {/each}
130
133
  </ul>
@@ -147,16 +147,15 @@
147
147
  </Select>
148
148
  </Label>
149
149
  </div>
150
- <Label
151
- cx="w-fit"
152
- position="top"
153
- >
154
- Is remote?
155
- <Switch
156
- slot="input"
157
- bind:on={isRemote}
158
- />
159
- </Label>
150
+ <div class="w-fit">
151
+ <Label position="top">
152
+ Is remote?
153
+ <Switch
154
+ slot="input"
155
+ bind:on={isRemote}
156
+ />
157
+ </Label>
158
+ </div>
160
159
  </div>
161
160
 
162
161
  <h6 class="text-subtle-1 font-semibold">Detections/Classifications</h6>
package/dist/index.d.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  export { createAddImageToDatasetContext, type ImageData, useAddImageToDataset, } from './add-image-to-dataset';
2
+ export { apiDocsHref } from './api-docs-href';
2
3
  export { clientForResource } from './client-map';
3
4
  export * from './components';
4
5
  export { getResourceAPI } from './get-resource-api';
5
6
  export { providePip, usePip } from './pip/context.svelte';
6
- export { hasWidget, showResourceWidget, widgetForResource } from './resource';
7
+ export { ResourceTriplets } from './resource-triplet';
8
+ export { apiWidgetsForResource, availableAPIWidgets, isKnownResource,
9
+ /** @deprecated use `isKnownResource` instead. Will be deleted in the next release */
10
+ isKnownResource as hasWidget, type ResourceAPIWidget, showResourceWidget, widgetForResource, } from './resource-widget';
package/dist/index.js CHANGED
@@ -1,6 +1,11 @@
1
1
  export { createAddImageToDatasetContext, useAddImageToDataset, } from './add-image-to-dataset';
2
+ export { apiDocsHref } from './api-docs-href';
2
3
  export { clientForResource } from './client-map';
3
4
  export * from './components';
4
5
  export { getResourceAPI } from './get-resource-api';
5
6
  export { providePip, usePip } from './pip/context.svelte';
6
- export { hasWidget, showResourceWidget, widgetForResource } from './resource';
7
+ export { ResourceTriplets } from './resource-triplet';
8
+ export { apiWidgetsForResource, availableAPIWidgets, isKnownResource,
9
+ // TODO: Delete `hasWidget`
10
+ /** @deprecated use `isKnownResource` instead. Will be deleted in the next release */
11
+ isKnownResource as hasWidget, showResourceWidget, widgetForResource, } from './resource-widget';
@@ -2,6 +2,8 @@ interface PipContext {
2
2
  /** Called by PictureInPictureButton to start or stop PiP. */
3
3
  toggle(resourceName: string): Promise<void>;
4
4
  setRate(rate: 'live' | number | false): void;
5
+ /** Register the media stream from the main camera view when its card is open. */
6
+ setStream(resourceName: string, stream: MediaStream | null): void;
5
7
  error: Error | undefined;
6
8
  /** Whether the PictureInPicture is active */
7
9
  readyState: 'inactive' | 'loading' | 'active';
@@ -14,12 +14,21 @@ export function providePip(partID) {
14
14
  const canvasCtx = canvas.getContext('2d');
15
15
  const canvasStream = canvas.captureStream();
16
16
  const img = document.createElement('img');
17
+ const externalStreams = new Map();
18
+ let externalStreamVersion = $state(0);
17
19
  let error = $state();
18
20
  let readyState = $state('inactive');
19
21
  let rate = $state('live');
20
22
  let activeName = $state();
21
- const cameraClient = $derived(activeName ? createResourceClient(CameraClient, partID, () => activeName ?? '') : undefined);
22
- const streamClient = $derived(activeName ? createStreamClient(partID, () => activeName ?? '') : undefined);
23
+ const externalStream = $derived.by(() => {
24
+ void externalStreamVersion;
25
+ return activeName ? externalStreams.get(activeName) : undefined;
26
+ });
27
+ const needsOwnedStream = $derived(activeName !== undefined && !externalStream);
28
+ const cameraClient = $derived(needsOwnedStream && activeName
29
+ ? createResourceClient(CameraClient, partID, () => activeName ?? '')
30
+ : undefined);
31
+ const streamClient = $derived(needsOwnedStream && activeName ? createStreamClient(partID, () => activeName ?? '') : undefined);
23
32
  const imageQuery = $derived(cameraClient?.current
24
33
  ? createResourceQuery(cameraClient, 'getImages', () => ({
25
34
  enabled: rate !== 'live',
@@ -27,22 +36,39 @@ export function providePip(partID) {
27
36
  refetchIntervalInBackground: activeName === cameraClient?.current?.name,
28
37
  }))
29
38
  : undefined);
30
- const mediaStream = $derived((rate === 'live' ? streamClient?.mediaStream : canvasStream) ?? null);
39
+ const ownedMediaStream = $derived(needsOwnedStream ? ((rate === 'live' ? streamClient?.mediaStream : canvasStream) ?? null) : null);
40
+ const playbackStream = $derived(externalStream ?? ownedMediaStream);
41
+ const setStream = (resourceName, stream) => {
42
+ if (stream) {
43
+ externalStreams.set(resourceName, stream);
44
+ }
45
+ else {
46
+ externalStreams.delete(resourceName);
47
+ }
48
+ externalStreamVersion++;
49
+ };
31
50
  $effect(() => {
32
51
  document.body.append(video);
52
+ const handleLeave = () => {
53
+ readyState = 'inactive';
54
+ activeName = undefined;
55
+ error = undefined;
56
+ };
57
+ video.addEventListener('leavepictureinpicture', handleLeave);
33
58
  return () => {
59
+ video.removeEventListener('leavepictureinpicture', handleLeave);
34
60
  video.remove();
35
61
  };
36
62
  });
37
63
  $effect(() => {
38
- video.srcObject = mediaStream;
64
+ video.srcObject = playbackStream;
39
65
  return () => {
40
66
  video.srcObject = null;
41
67
  };
42
68
  });
43
69
  $effect(() => {
44
70
  const currentRate = rate;
45
- if (currentRate === 'live')
71
+ if (!needsOwnedStream || currentRate === 'live')
46
72
  return;
47
73
  const image = imageQuery?.data?.images[0];
48
74
  if (!image)
@@ -64,12 +90,11 @@ export function providePip(partID) {
64
90
  img.removeEventListener('load', drawImage);
65
91
  };
66
92
  });
67
- const toggle = async () => {
68
- if (activeName === undefined &&
69
- readyState === 'active' &&
70
- document.pictureInPictureElement === video) {
93
+ const toggle = async (resourceName) => {
94
+ if (activeName === resourceName && document.pictureInPictureElement === video) {
71
95
  try {
72
96
  await document.exitPictureInPicture();
97
+ activeName = undefined;
73
98
  readyState = 'inactive';
74
99
  error = undefined;
75
100
  }
@@ -78,28 +103,31 @@ export function providePip(partID) {
78
103
  }
79
104
  return;
80
105
  }
106
+ activeName = resourceName;
81
107
  readyState = 'loading';
82
- // Wait for the new stream's metadata to load before requesting PiP,
83
- // otherwise the browser throws InvalidStateError.
84
- if (video.readyState < 1 /* HAVE_METADATA */) {
85
- await new Promise((resolve) => {
86
- video.addEventListener('loadedmetadata', () => resolve(), { once: true });
87
- });
88
- }
89
108
  try {
109
+ const stream = ownedMediaStream;
110
+ video.srcObject = stream;
111
+ // Wait for the stream's metadata to load before requesting PiP,
112
+ // otherwise the browser throws InvalidStateError.
113
+ if (video.readyState < 1 /* HAVE_METADATA */) {
114
+ await new Promise((resolve) => {
115
+ video.addEventListener('loadedmetadata', () => resolve(), { once: true });
116
+ });
117
+ }
90
118
  await video.requestPictureInPicture();
91
119
  readyState = 'active';
92
120
  error = undefined;
93
121
  }
94
122
  catch (nextError) {
123
+ readyState = 'inactive';
124
+ activeName = undefined;
95
125
  error = nextError;
96
126
  }
97
127
  };
98
128
  const context = {
99
- async toggle(resourceName) {
100
- activeName = activeName === resourceName ? undefined : resourceName;
101
- await toggle();
102
- },
129
+ toggle,
130
+ setStream,
103
131
  setRate(newRate) {
104
132
  rate = newRate;
105
133
  },
@@ -0,0 +1,37 @@
1
+ /** The canonical set of Viam resource API triplets. */
2
+ export declare const ResourceTriplets: {
3
+ readonly Arm: "rdk:component:arm";
4
+ readonly AudioInput: "rdk:component:audio_in";
5
+ readonly AudioOutput: "rdk:component:audio_out";
6
+ readonly Base: "rdk:component:base";
7
+ readonly Board: "rdk:component:board";
8
+ readonly Button: "rdk:component:button";
9
+ readonly Camera: "rdk:component:camera";
10
+ readonly Encoder: "rdk:component:encoder";
11
+ readonly Gantry: "rdk:component:gantry";
12
+ readonly GenericComponent: "rdk:component:generic";
13
+ readonly Gripper: "rdk:component:gripper";
14
+ readonly InputController: "rdk:component:input_controller";
15
+ readonly Motor: "rdk:component:motor";
16
+ readonly MovementSensor: "rdk:component:movement_sensor";
17
+ readonly PoseTracker: "rdk:component:pose_tracker";
18
+ readonly PowerSensor: "rdk:component:power_sensor";
19
+ readonly Sensor: "rdk:component:sensor";
20
+ readonly Servo: "rdk:component:servo";
21
+ readonly Switch: "rdk:component:switch";
22
+ readonly BaseRemoteControl: "rdk:service:base_remote_control";
23
+ readonly DataManager: "rdk:service:data_manager";
24
+ readonly Discovery: "rdk:service:discovery";
25
+ readonly GenericService: "rdk:service:generic";
26
+ readonly MLModel: "rdk:service:mlmodel";
27
+ readonly Motion: "rdk:service:motion";
28
+ readonly Navigation: "rdk:service:navigation";
29
+ readonly Slam: "rdk:service:slam";
30
+ readonly Sensors: "rdk:service:sensors";
31
+ readonly Shell: "rdk:service:shell";
32
+ readonly Video: "rdk:service:video";
33
+ readonly Vision: "rdk:service:vision";
34
+ readonly WorldStateStore: "rdk:service:world_state_store";
35
+ };
36
+ /** A Viam resource API triplet, e.g. `'rdk:component:arm'`. */
37
+ export type ResourceTriplet = (typeof ResourceTriplets)[keyof typeof ResourceTriplets];