@viamrobotics/test-widgets 0.7.0 → 0.9.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 (98) hide show
  1. package/README.md +33 -0
  2. package/dist/client-map.d.ts +30 -30
  3. package/dist/client-map.js +32 -29
  4. package/dist/components/index.d.ts +22 -0
  5. package/dist/components/index.js +22 -0
  6. package/dist/components/is-moving.svelte +1 -4
  7. package/dist/components/mutation-section.svelte +1 -1
  8. package/dist/components/navigation-map/components/obstacle.svelte +2 -1
  9. package/dist/components/three/capsule-geometry.d.ts +10 -0
  10. package/dist/components/three/capsule-geometry.js +19 -0
  11. package/dist/components/three/index.d.ts +1 -0
  12. package/dist/components/three/index.js +1 -0
  13. package/dist/components/widgets/arm/arm.svelte +8 -5
  14. package/dist/components/widgets/arm/get-joint-positions-widget.svelte +36 -0
  15. package/dist/components/widgets/arm/get-joint-positions-widget.svelte.d.ts +7 -0
  16. package/dist/components/widgets/arm/is-moving-widget.svelte +20 -0
  17. package/dist/components/widgets/arm/is-moving-widget.svelte.d.ts +7 -0
  18. package/dist/components/widgets/arm/move-to-joint-positions-widget.svelte +9 -5
  19. package/dist/components/widgets/arm/move-to-position.svelte +31 -3
  20. package/dist/components/widgets/audio-input/get-properties-widget.svelte +43 -0
  21. package/dist/components/widgets/audio-input/get-properties-widget.svelte.d.ts +7 -0
  22. package/dist/components/widgets/audio-output/get-properties-widget.svelte +43 -0
  23. package/dist/components/widgets/audio-output/get-properties-widget.svelte.d.ts +7 -0
  24. package/dist/components/widgets/base/is-moving-widget.svelte +20 -0
  25. package/dist/components/widgets/base/is-moving-widget.svelte.d.ts +7 -0
  26. package/dist/components/widgets/base/quick-move.svelte +12 -5
  27. package/dist/components/widgets/camera/live-or-polling-video.svelte +8 -1
  28. package/dist/components/widgets/do-command/do-command.svelte +21 -7
  29. package/dist/components/widgets/do-command/do-command.svelte.d.ts +8 -1
  30. package/dist/components/widgets/encoder/encoder-position-type.d.ts +8 -0
  31. package/dist/components/widgets/encoder/encoder-position-type.js +20 -0
  32. package/dist/components/widgets/encoder/encoder.svelte +3 -20
  33. package/dist/components/widgets/encoder/get-position-widget.svelte +54 -0
  34. package/dist/components/widgets/encoder/get-position-widget.svelte.d.ts +7 -0
  35. package/dist/components/widgets/gantry/get-position-widget.svelte +43 -0
  36. package/dist/components/widgets/gantry/get-position-widget.svelte.d.ts +7 -0
  37. package/dist/components/widgets/gantry/is-moving-widget.svelte +20 -0
  38. package/dist/components/widgets/gantry/is-moving-widget.svelte.d.ts +7 -0
  39. package/dist/components/widgets/gripper/gripper.svelte +1 -1
  40. package/dist/components/widgets/gripper/is-holding-something.svelte +1 -4
  41. package/dist/components/widgets/gripper/is-moving-widget.svelte +20 -0
  42. package/dist/components/widgets/gripper/is-moving-widget.svelte.d.ts +7 -0
  43. package/dist/components/widgets/motion/component-name-select.svelte +29 -0
  44. package/dist/components/widgets/motion/component-name-select.svelte.d.ts +9 -0
  45. package/dist/components/widgets/motion/frame-system-config.d.ts +28 -0
  46. package/dist/components/widgets/motion/frame-system-config.js +30 -0
  47. package/dist/components/widgets/motion/motion.svelte +27 -0
  48. package/dist/components/widgets/motion/motion.svelte.d.ts +7 -0
  49. package/dist/components/widgets/motion/move-widget.svelte +84 -0
  50. package/dist/components/widgets/motion/move-widget.svelte.d.ts +7 -0
  51. package/dist/components/widgets/motion/move.svelte +127 -0
  52. package/dist/components/widgets/motion/move.svelte.d.ts +14 -0
  53. package/dist/components/widgets/motion/parse-move-args.d.ts +24 -0
  54. package/dist/components/widgets/motion/parse-move-args.js +23 -0
  55. package/dist/components/widgets/motion/parse-pasted-pose.d.ts +10 -0
  56. package/dist/components/widgets/motion/parse-pasted-pose.js +30 -0
  57. package/dist/components/widgets/motion/pose-in-frame-input.svelte +168 -0
  58. package/dist/components/widgets/motion/pose-in-frame-input.svelte.d.ts +10 -0
  59. package/dist/components/widgets/motor/is-moving-widget.svelte +20 -0
  60. package/dist/components/widgets/motor/is-moving-widget.svelte.d.ts +7 -0
  61. package/dist/components/widgets/motor/motor.svelte +2 -8
  62. package/dist/components/widgets/movement-sensor/get-accuracy-widget.svelte +38 -0
  63. package/dist/components/widgets/movement-sensor/get-accuracy-widget.svelte.d.ts +7 -0
  64. package/dist/components/widgets/movement-sensor/get-compass-heading-widget.svelte +38 -0
  65. package/dist/components/widgets/movement-sensor/get-compass-heading-widget.svelte.d.ts +7 -0
  66. package/dist/components/widgets/movement-sensor/get-orientation-widget.svelte +38 -0
  67. package/dist/components/widgets/movement-sensor/get-orientation-widget.svelte.d.ts +7 -0
  68. package/dist/components/widgets/movement-sensor/get-position-widget.svelte +38 -0
  69. package/dist/components/widgets/movement-sensor/get-position-widget.svelte.d.ts +7 -0
  70. package/dist/components/widgets/navigation/obstacles/meshes.svelte +1 -2
  71. package/dist/components/widgets/power-sensor/get-current-widget.svelte +38 -0
  72. package/dist/components/widgets/power-sensor/get-current-widget.svelte.d.ts +7 -0
  73. package/dist/components/widgets/power-sensor/get-power-widget.svelte +38 -0
  74. package/dist/components/widgets/power-sensor/get-power-widget.svelte.d.ts +7 -0
  75. package/dist/components/widgets/power-sensor/get-voltage-widget.svelte +38 -0
  76. package/dist/components/widgets/power-sensor/get-voltage-widget.svelte.d.ts +7 -0
  77. package/dist/components/widgets/servo/is-moving-widget.svelte +20 -0
  78. package/dist/components/widgets/servo/is-moving-widget.svelte.d.ts +7 -0
  79. package/dist/components/widgets/slam/get-position-widget.svelte +38 -0
  80. package/dist/components/widgets/slam/get-position-widget.svelte.d.ts +7 -0
  81. package/dist/components/widgets/vision-service/vision-service.svelte +9 -10
  82. package/dist/index.d.ts +6 -1
  83. package/dist/index.js +6 -1
  84. package/dist/is-known-resource.d.ts +3 -0
  85. package/dist/is-known-resource.js +5 -0
  86. package/dist/pip/context.svelte.d.ts +2 -0
  87. package/dist/pip/context.svelte.js +48 -20
  88. package/dist/registry.d.ts +28 -0
  89. package/dist/registry.js +176 -0
  90. package/dist/resource-triplet.d.ts +37 -0
  91. package/dist/resource-triplet.js +37 -0
  92. package/dist/resource-widget-types.d.ts +21 -0
  93. package/dist/resource-widget-types.js +1 -0
  94. package/dist/resource.d.ts +0 -9
  95. package/dist/resource.js +0 -72
  96. package/dist/show-resource-widget.d.ts +3 -0
  97. package/dist/show-resource-widget.js +10 -0
  98. package/package.json +23 -6
@@ -0,0 +1,38 @@
1
+ <script lang="ts">
2
+ import { MovementSensorClient } 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 CompassHeading from './compass-heading.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
+ MovementSensorClient,
19
+ () => partID,
20
+ () => resourceName
21
+ )
22
+
23
+ const query = createResourceQuery(client, 'getCompassHeading', { refetchInterval: 500 })
24
+ </script>
25
+
26
+ <ApiSection
27
+ title="GetCompassHeading"
28
+ class="grow"
29
+ >
30
+ <Query
31
+ {query}
32
+ contentCx="h-6"
33
+ >
34
+ {#if query.data !== undefined}
35
+ <CompassHeading 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 GetCompassHeadingWidget: import("svelte").Component<Props, {}, "">;
6
+ type GetCompassHeadingWidget = ReturnType<typeof GetCompassHeadingWidget>;
7
+ export default GetCompassHeadingWidget;
@@ -0,0 +1,38 @@
1
+ <script lang="ts">
2
+ import { MovementSensorClient } 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 Orientation from './orientation.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
+ MovementSensorClient,
19
+ () => partID,
20
+ () => resourceName
21
+ )
22
+
23
+ const query = createResourceQuery(client, 'getOrientation', { refetchInterval: 500 })
24
+ </script>
25
+
26
+ <ApiSection
27
+ title="GetOrientation"
28
+ class="grow"
29
+ >
30
+ <Query
31
+ {query}
32
+ contentCx="h-6"
33
+ >
34
+ {#if query.data !== undefined}
35
+ <Orientation 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 GetOrientationWidget: import("svelte").Component<Props, {}, "">;
6
+ type GetOrientationWidget = ReturnType<typeof GetOrientationWidget>;
7
+ export default GetOrientationWidget;
@@ -0,0 +1,38 @@
1
+ <script lang="ts">
2
+ import { MovementSensorClient } 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
+ MovementSensorClient,
19
+ () => partID,
20
+ () => resourceName
21
+ )
22
+
23
+ const query = createResourceQuery(client, 'getPosition', { refetchInterval: 500 })
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 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 GetPositionWidget: import("svelte").Component<Props, {}, "">;
6
+ type GetPositionWidget = ReturnType<typeof GetPositionWidget>;
7
+ export default GetPositionWidget;
@@ -2,10 +2,9 @@
2
2
  import type { GeoGeometry } from '@viamrobotics/sdk'
3
3
 
4
4
  import { T } from '@threlte/core'
5
- import { CapsuleGeometry } from '@viamrobotics/motion-tools/lib'
6
5
  import { type BufferGeometry, MathUtils } from 'three'
7
6
 
8
- import { AxesHelper } from '../../../three'
7
+ import { AxesHelper, CapsuleGeometry } from '../../../three'
9
8
 
10
9
  import { getColor } from './color'
11
10
 
@@ -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 CurrentReading from './current-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, 'getCurrent', { refetchInterval: 500 })
24
+ </script>
25
+
26
+ <ApiSection
27
+ title="GetCurrent"
28
+ class="grow"
29
+ >
30
+ <Query
31
+ {query}
32
+ contentCx="h-6"
33
+ >
34
+ {#if query.data !== undefined}
35
+ <CurrentReading 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 GetCurrentWidget: import("svelte").Component<Props, {}, "">;
6
+ type GetCurrentWidget = ReturnType<typeof GetCurrentWidget>;
7
+ export default GetCurrentWidget;
@@ -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;
@@ -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;
@@ -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;
@@ -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
@@ -3,5 +3,10 @@ export { apiDocsHref } from './api-docs-href';
3
3
  export { clientForResource } from './client-map';
4
4
  export * from './components';
5
5
  export { getResourceAPI } from './get-resource-api';
6
+ export { isKnownResource,
7
+ /** @deprecated use `isKnownResource` instead. Will be deleted in the next release */
8
+ isKnownResource as hasWidget, } from './is-known-resource';
6
9
  export { providePip, usePip } from './pip/context.svelte';
7
- export { hasWidget, showResourceWidget, widgetForResource } from './resource';
10
+ export { ResourceTriplets } from './resource-triplet';
11
+ export type { ResourceAPIWidget, ResourceWidget, ResourceWidgetProps, } from './resource-widget-types';
12
+ export { showResourceWidget } from './show-resource-widget';
package/dist/index.js CHANGED
@@ -3,5 +3,10 @@ export { apiDocsHref } from './api-docs-href';
3
3
  export { clientForResource } from './client-map';
4
4
  export * from './components';
5
5
  export { getResourceAPI } from './get-resource-api';
6
+ export { isKnownResource,
7
+ // TODO: Delete `hasWidget`
8
+ /** @deprecated use `isKnownResource` instead. Will be deleted in the next release */
9
+ isKnownResource as hasWidget, } from './is-known-resource';
6
10
  export { providePip, usePip } from './pip/context.svelte';
7
- export { hasWidget, showResourceWidget, widgetForResource } from './resource';
11
+ export { ResourceTriplets } from './resource-triplet';
12
+ export { showResourceWidget } from './show-resource-widget';
@@ -0,0 +1,3 @@
1
+ import type { ResourceName } from '@viamrobotics/sdk';
2
+ /** Whether a resource's API is a recognized Viam resource triplet. */
3
+ export declare const isKnownResource: (resource: ResourceName) => boolean;
@@ -0,0 +1,5 @@
1
+ import { getResourceAPI } from "./get-resource-api.js";
2
+ import { ResourceTriplets } from "./resource-triplet.js";
3
+ const knownResources = new Set(Object.values(ResourceTriplets));
4
+ /** Whether a resource's API is a recognized Viam resource triplet. */
5
+ export const isKnownResource = (resource) => knownResources.has(getResourceAPI(resource));
@@ -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,28 @@
1
+ import type { ResourceName } from '@viamrobotics/sdk';
2
+ import type { ResourceAPIWidget, ResourceWidget } from './resource-widget-types.ts';
3
+ import { type ResourceTriplet } from './resource-triplet.ts';
4
+ /**
5
+ * Returns a resource's individual API widgets. Each entry carries a stable `id`, a
6
+ * display `label`, and the `widgets` to render with `{ partID, resourceName }`.
7
+ *
8
+ * Returns `[]` for a resource with a card but no standalone API widgets, and for
9
+ * unrecognized resources.
10
+ *
11
+ * @example
12
+ * apiWidgetsForResource(gripperResourceName)
13
+ * // [{ id: 'open-grab', label: 'Open / Grab', widgets: [GripperOpenWidget, GripperGrabWidget] }, ...]
14
+ */
15
+ export declare const apiWidgetsForResource: (resource: ResourceName) => ResourceAPIWidget[];
16
+ /**
17
+ * Returns every resource triplet that has a test card, mapped to its API widgets.
18
+ * Use this to enumerate the full catalog, e.g. a menu spanning every resource type;
19
+ * for a single resource, prefer `apiWidgetsForResource`.
20
+ *
21
+ * @example
22
+ * availableAPIWidgets()[ResourceTriplets.Gripper]
23
+ * // [{ id: 'open-grab', label: 'Open / Grab', widgets: [GripperOpenWidget, GripperGrabWidget] }, ...]
24
+ */
25
+ export declare const availableAPIWidgets: () => Partial<Record<ResourceTriplet, ResourceAPIWidget[]>>;
26
+ /** Returns the full composite test card for a resource, or `undefined` if none exists. */
27
+ export declare const widgetForResource: (resource: ResourceName) => ResourceWidget | undefined;
28
+ export type { ResourceAPIWidget, ResourceWidget, ResourceWidgetProps, } from './resource-widget-types.ts';