@viamrobotics/test-widgets 0.6.1 → 0.7.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 (51) 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/components/angle-unit-toggle.svelte +1 -4
  5. package/dist/components/api-section.svelte +21 -23
  6. package/dist/components/api-section.svelte.d.ts +2 -0
  7. package/dist/components/index.d.ts +1 -0
  8. package/dist/components/index.js +1 -0
  9. package/dist/components/is-moving.svelte +3 -1
  10. package/dist/components/is-moving.svelte.d.ts +1 -0
  11. package/dist/components/mutation-section.svelte +19 -22
  12. package/dist/components/mutation-section.svelte.d.ts +2 -0
  13. package/dist/components/section-title.svelte +63 -0
  14. package/dist/components/section-title.svelte.d.ts +14 -0
  15. package/dist/components/widgets/arm/arm.svelte +17 -24
  16. package/dist/components/widgets/arm/joint-position-editor.svelte +89 -0
  17. package/dist/components/widgets/arm/joint-position-editor.svelte.d.ts +11 -0
  18. package/dist/components/widgets/arm/joint-position-quick-move.svelte +85 -0
  19. package/dist/components/widgets/arm/joint-position-quick-move.svelte.d.ts +9 -0
  20. package/dist/components/widgets/arm/joint-position-slider.svelte +36 -0
  21. package/dist/components/widgets/arm/joint-position-slider.svelte.d.ts +9 -0
  22. package/dist/components/widgets/arm/move-to-joint-positions.svelte +73 -127
  23. package/dist/components/widgets/arm/move-to-joint-positions.svelte.d.ts +1 -0
  24. package/dist/components/widgets/audio-input/audio-input.svelte +2 -0
  25. package/dist/components/widgets/audio-output/audio-output.svelte +2 -0
  26. package/dist/components/widgets/base/base.svelte +9 -1
  27. package/dist/components/widgets/camera/camera.svelte +156 -142
  28. package/dist/components/widgets/camera/live-or-polling-video.svelte +1 -0
  29. package/dist/components/widgets/discovery/discovery.svelte +1 -0
  30. package/dist/components/widgets/encoder/encoder.svelte +2 -0
  31. package/dist/components/widgets/gantry/gantry.svelte +28 -5
  32. package/dist/components/widgets/generic/generic.svelte +8 -1
  33. package/dist/components/widgets/gripper/gripper.svelte +11 -5
  34. package/dist/components/widgets/gripper/is-holding-something.svelte +1 -0
  35. package/dist/components/widgets/input-controller/input-controller.svelte +2 -1
  36. package/dist/components/widgets/motor/motor.svelte +11 -1
  37. package/dist/components/widgets/movement-sensor/movement-sensor.svelte +53 -26
  38. package/dist/components/widgets/power-sensor/power-sensor.svelte +4 -0
  39. package/dist/components/widgets/sensor/sensor.svelte +1 -0
  40. package/dist/components/widgets/servo/servo.svelte +10 -2
  41. package/dist/components/widgets/slam/move-on-map.svelte +4 -1
  42. package/dist/components/widgets/slam/slam.svelte +4 -1
  43. package/dist/components/widgets/vision-service/context.svelte.d.ts +1 -0
  44. package/dist/components/widgets/vision-service/context.svelte.js +7 -0
  45. package/dist/components/widgets/vision-service/detection-row.svelte +38 -0
  46. package/dist/components/widgets/vision-service/detection-row.svelte.d.ts +8 -0
  47. package/dist/components/widgets/vision-service/detection.svelte +7 -4
  48. package/dist/components/widgets/vision-service/legend.svelte +14 -11
  49. package/dist/index.d.ts +1 -0
  50. package/dist/index.js +1 -0
  51. package/package.json +5 -2
package/README.md CHANGED
@@ -10,7 +10,7 @@ The playground (`pnpm dev`) can be used to develop the test-cards against prod r
10
10
 
11
11
  This is useful if you need to validate the sdk against specific behavior of modules or need to replicate a bug from another robot (why replicate locally when you could just develop directly against the robot with the bug?).
12
12
 
13
- To setup your playground, create a `.env.local` in the test-cards directory with the following format (no need to create two robots):
13
+ To setup your playground, create a `.env.local` in the test-widgets directory with the following format (no need to create two robots):
14
14
 
15
15
  ```json
16
16
  VITE_PLAYGROUND_ROBOTS='
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Returns the docs URL for a given resource API + method, or undefined
3
+ * when the method has no dedicated anchor (e.g. GetStatus, GetSourceNames).
4
+ *
5
+ * @param api - RDK API string, e.g. "rdk:component:movement_sensor"
6
+ * @param method - camelCase method name, e.g. "getPosition"
7
+ */
8
+ export declare const apiDocsHref: (api: string, method: string) => string | undefined;
@@ -0,0 +1,17 @@
1
+ const NO_LINK_METHODS = new Set(['getStatus', 'getSourceNames']);
2
+ /**
3
+ * Returns the docs URL for a given resource API + method, or undefined
4
+ * when the method has no dedicated anchor (e.g. GetStatus, GetSourceNames).
5
+ *
6
+ * @param api - RDK API string, e.g. "rdk:component:movement_sensor"
7
+ * @param method - camelCase method name, e.g. "getPosition"
8
+ */
9
+ export const apiDocsHref = (api, method) => {
10
+ if (NO_LINK_METHODS.has(method))
11
+ return undefined;
12
+ const isService = api.includes(':service:');
13
+ // RDK API strings use underscores (movement_sensor); docs URLs use hyphens (movement-sensor)
14
+ const subtype = api.split(':').at(-1).replaceAll('_', '-');
15
+ const kind = isService ? 'services' : 'components';
16
+ return `https://docs.viam.com/reference/apis/${kind}/${subtype}/#${method.toLowerCase()}`;
17
+ };
@@ -15,10 +15,7 @@
15
15
  aria-label={useRadians ? 'Switch to Degrees' : 'Switch to Radians'}
16
16
  class="text-gray-6 hover:border-medium hover:bg-medium active:bg-gray-2 justify-items-end p-0.5"
17
17
  >
18
- <Icon
19
- name="sync"
20
- size="xs"
21
- />
18
+ <Icon name="sync" />
22
19
  </button>
23
20
 
24
21
  <span slot="description">
@@ -2,14 +2,17 @@
2
2
  import type { Snippet } from 'svelte'
3
3
  import type { HTMLAttributes } from 'svelte/elements'
4
4
 
5
- import { Icon, Tooltip } from '@viamrobotics/prime-core'
6
5
  import { twMerge } from 'tailwind-merge'
7
6
 
7
+ import SectionTitle from './section-title.svelte'
8
+
8
9
  interface Props extends HTMLAttributes<HTMLElement> {
9
10
  title?: string | undefined
10
11
  tooltip?: string | undefined
11
12
  description?: string | undefined
12
13
  bottomText?: string | undefined
14
+ /** RDK API string; presence renders the title as a linked monospace method name */
15
+ api?: string | undefined
13
16
  class?: string
14
17
  children?: Snippet
15
18
  }
@@ -19,6 +22,7 @@
19
22
  tooltip,
20
23
  description,
21
24
  bottomText,
25
+ api,
22
26
  class: className = '',
23
27
  children,
24
28
  ...rest
@@ -29,32 +33,26 @@
29
33
 
30
34
  <section
31
35
  class={twMerge('flex grow flex-col gap-4 p-4', className)}
32
- aria-labelledby={headingID}
36
+ aria-labelledby={title ? headingID : undefined}
33
37
  {...rest}
34
38
  >
35
39
  {#if title}
36
40
  <div class="flex flex-col gap-0.5">
37
- <h3
38
- class="flex flex-row items-center gap-1 text-sm font-semibold"
39
- id={headingID}
40
- >
41
- {title}
42
- {#if tooltip}
43
- <Tooltip>
44
- <Icon
45
- name="information-outline"
46
- cx="text-gray-6"
47
- />
48
-
49
- <p
50
- slot="description"
51
- class="text-xs whitespace-pre-line"
52
- >
53
- {tooltip}
54
- </p>
55
- </Tooltip>
56
- {/if}
57
- </h3>
41
+ {#if api}
42
+ <SectionTitle
43
+ {title}
44
+ {tooltip}
45
+ {api}
46
+ headingId={headingID}
47
+ />
48
+ {:else}
49
+ <h3
50
+ class="flex flex-row items-center gap-1 text-sm font-semibold"
51
+ id={headingID}
52
+ >
53
+ {title}
54
+ </h3>
55
+ {/if}
58
56
  {#if description}
59
57
  <p class="text-subtle-2 text-xs">{description}</p>
60
58
  {/if}
@@ -5,6 +5,8 @@ interface Props extends HTMLAttributes<HTMLElement> {
5
5
  tooltip?: string | undefined;
6
6
  description?: string | undefined;
7
7
  bottomText?: string | undefined;
8
+ /** RDK API string; presence renders the title as a linked monospace method name */
9
+ api?: string | undefined;
8
10
  class?: string;
9
11
  children?: Snippet;
10
12
  }
@@ -1,4 +1,5 @@
1
1
  export { default as ConnectionStatus } from './connection-status.svelte';
2
+ export { default as SectionTitle } from './section-title.svelte';
2
3
  export { default as ArmWidget } from './widgets/arm/arm.svelte';
3
4
  export { default as ArmMoveToJointPositionsWidget } from './widgets/arm/move-to-joint-positions-widget.svelte';
4
5
  export { default as ArmMoveToPositionWidget } from './widgets/arm/move-to-position-widget.svelte';
@@ -1,4 +1,5 @@
1
1
  export { default as ConnectionStatus } from './connection-status.svelte';
2
+ export { default as SectionTitle } from './section-title.svelte';
2
3
  export { default as ArmWidget } from './widgets/arm/arm.svelte';
3
4
  export { default as ArmMoveToJointPositionsWidget } from './widgets/arm/move-to-joint-positions-widget.svelte';
4
5
  export { default as ArmMoveToPositionWidget } from './widgets/arm/move-to-position-widget.svelte';
@@ -33,10 +33,11 @@
33
33
  client: Client
34
34
  partID: string
35
35
  resourceName: string
36
+ api: string
36
37
  children?: Snippet
37
38
  }
38
39
 
39
- const { client: clientClass, partID, resourceName, children }: Props = $props()
40
+ const { client: clientClass, partID, resourceName, api, children }: Props = $props()
40
41
 
41
42
  const client = $derived(
42
43
  createResourceClient<Arm | Base | Gantry | Gripper | Motor | Servo>(
@@ -51,6 +52,7 @@
51
52
 
52
53
  <ApiSection
53
54
  title="IsMoving"
55
+ {api}
54
56
  bottomText="Updates automatically"
55
57
  class="grow"
56
58
  >
@@ -5,6 +5,7 @@ interface Props {
5
5
  client: Client;
6
6
  partID: string;
7
7
  resourceName: string;
8
+ api: string;
8
9
  children?: Snippet;
9
10
  }
10
11
  declare const IsMoving: import("svelte").Component<Props, {}, "">;
@@ -1,16 +1,18 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte'
3
3
 
4
- import { Icon, Tooltip } from '@viamrobotics/prime-core'
5
4
  import { twMerge } from 'tailwind-merge'
6
5
 
7
6
  import ErrorDisplay from './error.svelte'
7
+ import SectionTitle from './section-title.svelte'
8
8
 
9
9
  interface Props {
10
10
  title: string
11
11
  tooltip?: string | undefined
12
12
  description?: string | undefined
13
13
  lastError: Error | null
14
+ /** RDK API string; presence renders the title as a linked monospace method name */
15
+ api?: string | undefined
14
16
  class?: string
15
17
  titleInput?: Snippet
16
18
  error?: Snippet
@@ -22,6 +24,7 @@
22
24
  tooltip,
23
25
  description,
24
26
  lastError = null,
27
+ api,
25
28
  class: className,
26
29
  titleInput,
27
30
  error,
@@ -37,27 +40,21 @@
37
40
  >
38
41
  <div class={twMerge('flex grow flex-row flex-wrap gap-2', className)}>
39
42
  <div class="flex max-w-[200px] grow flex-col gap-0.5 pr-4">
40
- <h3
41
- class="flex flex-row items-center gap-1 text-sm font-semibold"
42
- id={headingID}
43
- >
44
- {title}
45
- {#if tooltip}
46
- <Tooltip>
47
- <Icon
48
- name="information-outline"
49
- cx="text-gray-6"
50
- />
51
-
52
- <p
53
- slot="description"
54
- class="text-xs whitespace-pre-line"
55
- >
56
- {tooltip}
57
- </p>
58
- </Tooltip>
59
- {/if}
60
- </h3>
43
+ {#if api}
44
+ <SectionTitle
45
+ {title}
46
+ {tooltip}
47
+ {api}
48
+ headingId={headingID}
49
+ />
50
+ {:else}
51
+ <h3
52
+ class="flex flex-row items-center gap-1 text-sm font-semibold"
53
+ id={headingID}
54
+ >
55
+ {title}
56
+ </h3>
57
+ {/if}
61
58
  {#if description}
62
59
  <p class="text-subtle-2 text-xs">{description}</p>
63
60
  {/if}
@@ -4,6 +4,8 @@ interface Props {
4
4
  tooltip?: string | undefined;
5
5
  description?: string | undefined;
6
6
  lastError: Error | null;
7
+ /** RDK API string; presence renders the title as a linked monospace method name */
8
+ api?: string | undefined;
7
9
  class?: string;
8
10
  titleInput?: Snippet;
9
11
  error?: Snippet;
@@ -0,0 +1,63 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte'
3
+
4
+ import { Icon, Tooltip } from '@viamrobotics/prime-core'
5
+
6
+ import { apiDocsHref } from '../api-docs-href'
7
+
8
+ interface Props {
9
+ title: string
10
+ tooltip?: string | undefined
11
+ /** RDK API string, e.g. "rdk:component:camera". Required — SectionTitle is only for headers that map to an API method; the docs link is built from this plus the title. */
12
+ api: string
13
+ /** Optional external ID for the <h3>; generated internally if omitted */
14
+ headingId?: string | undefined
15
+ /** Inline content rendered after the title inside the <h3> (e.g. unit labels) */
16
+ suffix?: Snippet
17
+ }
18
+
19
+ const { title, tooltip, api, headingId, suffix }: Props = $props()
20
+
21
+ const generatedID = $props.id()
22
+ const id = $derived(headingId ?? generatedID)
23
+
24
+ // Method names are the camelCase form of the PascalCase title (GetPosition → getPosition).
25
+ const method = $derived(title.charAt(0).toLowerCase() + title.slice(1))
26
+ const href = $derived(apiDocsHref(api, method))
27
+ </script>
28
+
29
+ <h3
30
+ class="flex flex-row items-center gap-1 text-sm font-semibold"
31
+ {id}
32
+ >
33
+ {#if href}
34
+ <a
35
+ {href}
36
+ target="_blank"
37
+ rel="noopener noreferrer external"
38
+ class="decoration-gray-5 hover:decoration-default font-mono underline underline-offset-3"
39
+ >
40
+ {title}
41
+ </a>
42
+ {:else}
43
+ <span class="font-mono">{title}</span>
44
+ {/if}
45
+ {#if suffix}
46
+ {@render suffix()}
47
+ {/if}
48
+ {#if tooltip}
49
+ <Tooltip>
50
+ <Icon
51
+ name="information-outline"
52
+ cx="text-gray-6"
53
+ />
54
+
55
+ <p
56
+ slot="description"
57
+ class="text-xs whitespace-pre-line"
58
+ >
59
+ {tooltip}
60
+ </p>
61
+ </Tooltip>
62
+ {/if}
63
+ </h3>
@@ -0,0 +1,14 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ title: string;
4
+ tooltip?: string | undefined;
5
+ /** RDK API string, e.g. "rdk:component:camera". Required — SectionTitle is only for headers that map to an API method; the docs link is built from this plus the title. */
6
+ api: string;
7
+ /** Optional external ID for the <h3>; generated internally if omitted */
8
+ headingId?: string | undefined;
9
+ /** Inline content rendered after the title inside the <h3> (e.g. unit labels) */
10
+ suffix?: Snippet;
11
+ }
12
+ declare const SectionTitle: import("svelte").Component<Props, {}, "">;
13
+ type SectionTitle = ReturnType<typeof SectionTitle>;
14
+ export default SectionTitle;
@@ -19,7 +19,6 @@
19
19
  import { getJointPositionLimits, type KinematicsJSON } from './joint-position-limits'
20
20
  import MoveToJointPositions from './move-to-joint-positions.svelte'
21
21
  import MoveToPosition from './move-to-position.svelte'
22
- import QuickMove from './quick-move.svelte'
23
22
 
24
23
  interface Props {
25
24
  partID: string
@@ -38,9 +37,9 @@
38
37
  const jointPositionsQuery = createResourceQuery(client, 'getJointPositions', options)
39
38
  const endPositionQuery = createResourceQuery(client, 'getEndPosition', options)
40
39
  const kinematicsQuery = createResourceQuery(client, 'getKinematics', options)
40
+ const isMovingQuery = createResourceQuery(client, 'isMoving', options)
41
41
 
42
42
  const moveToJointPosMutation = createResourceMutation(client, 'moveToJointPositions')
43
- const quickMoveToJointPosMutation = createResourceMutation(client, 'moveToJointPositions')
44
43
  const stopMutation = createResourceMutation(client, 'stop')
45
44
  const moveToPosMutation = createResourceMutation(client, 'moveToPosition')
46
45
 
@@ -48,10 +47,6 @@
48
47
  moveToJointPosMutation.mutate([jointPositionsList], {})
49
48
  }
50
49
 
51
- const quickMoveToJointPositions = (jointPositionsList: number[]) => {
52
- quickMoveToJointPosMutation.mutate([jointPositionsList], {})
53
- }
54
-
55
50
  const moveToPosition = (position: Pose) => {
56
51
  moveToPosMutation.mutate([position], {})
57
52
  }
@@ -62,10 +57,11 @@
62
57
  <div class="flex flex-col gap-4 lg:flex-row lg:gap-0 lg:divide-x">
63
58
  <!-- Main control sections -->
64
59
  <div
65
- class="flex flex-col gap-4 lg:grid lg:grow lg:grid-cols-2 lg:gap-0 lg:divide-x xl:grid-cols-4"
60
+ class="flex flex-col gap-4 lg:grid lg:grow lg:grid-cols-2 lg:gap-0 lg:divide-x xl:grid-cols-3"
66
61
  >
67
62
  <ApiSection
68
63
  title="GetJointPositions"
64
+ api="rdk:component:arm"
69
65
  bottomText="Updates automatically"
70
66
  >
71
67
  <Query query={jointPositionsQuery}>
@@ -74,7 +70,10 @@
74
70
  {/if}
75
71
  </Query>
76
72
  </ApiSection>
77
- <ApiSection title="MoveToJointPositions">
73
+ <ApiSection
74
+ title="MoveToJointPositions"
75
+ api="rdk:component:arm"
76
+ >
78
77
  <Queries queries={[jointPositionsQuery, kinematicsQuery]}>
79
78
  {#if jointPositionsQuery.data && kinematicsQuery.data}
80
79
  <MoveToJointPositions
@@ -82,11 +81,15 @@
82
81
  {moveToJointPositions}
83
82
  lastError={moveToJointPosMutation.error}
84
83
  jointLimitsDegrees={getJointPositionLimits(kinematicsQuery.data as KinematicsJSON)}
84
+ isMoving={isMovingQuery.data ?? false}
85
85
  />
86
86
  {/if}
87
87
  </Queries>
88
88
  </ApiSection>
89
- <ApiSection title="MoveToPosition">
89
+ <ApiSection
90
+ title="MoveToPosition"
91
+ api="rdk:component:arm"
92
+ >
90
93
  <Query query={endPositionQuery}>
91
94
  {#if endPositionQuery.data}
92
95
  <MoveToPosition
@@ -97,27 +100,16 @@
97
100
  {/if}
98
101
  </Query>
99
102
  </ApiSection>
100
- <ApiSection
101
- title="Quick move"
102
- bottomText="Press a button to execute"
103
- >
104
- <Query query={jointPositionsQuery}>
105
- {#if jointPositionsQuery.data}
106
- <QuickMove
107
- positions={jointPositionsQuery.data.values}
108
- moveToJointPositions={quickMoveToJointPositions}
109
- lastError={quickMoveToJointPosMutation.error}
110
- />
111
- {/if}
112
- </Query>
113
- </ApiSection>
114
103
  </div>
115
104
 
116
105
  <!-- Control actions sidebar -->
117
106
  <div
118
107
  class="flex flex-row gap-4 lg:ml-auto lg:w-full lg:max-w-40 lg:flex-col lg:gap-0 lg:divide-y"
119
108
  >
120
- <ApiSection title="Stop">
109
+ <ApiSection
110
+ title="Stop"
111
+ api="rdk:component:arm"
112
+ >
121
113
  <StopButton
122
114
  error={stopMutation.error}
123
115
  onStop={() => {
@@ -127,6 +119,7 @@
127
119
  </ApiSection>
128
120
  <IsMoving
129
121
  client={ArmClient}
122
+ api="rdk:component:arm"
130
123
  {partID}
131
124
  {resourceName}
132
125
  />
@@ -0,0 +1,89 @@
1
+ <script lang="ts">
2
+ import { Button, Icon, Tooltip } from '@viamrobotics/prime-core'
3
+
4
+ import Table from '../../table.svelte'
5
+
6
+ import { type JointLimit } from './joint-position-limits'
7
+ import JointPositionSlider from './joint-position-slider.svelte'
8
+
9
+ interface Props {
10
+ desiredPositions: number[]
11
+ positions: number[]
12
+ jointLimitsDegrees: JointLimit[]
13
+ useRadians: boolean
14
+ moveToJointPositions: (jointPositions: number[]) => void
15
+ }
16
+
17
+ let {
18
+ desiredPositions = $bindable(),
19
+ positions,
20
+ jointLimitsDegrees,
21
+ useRadians,
22
+ moveToJointPositions,
23
+ }: Props = $props()
24
+
25
+ const getSliderMin = (index: number): number => jointLimitsDegrees[index]?.minDegrees ?? -180
26
+ const getSliderMax = (index: number): number => jointLimitsDegrees[index]?.maxDegrees ?? 180
27
+
28
+ const execute = () => {
29
+ moveToJointPositions([...desiredPositions])
30
+ }
31
+
32
+ const resetToZero = () => {
33
+ desiredPositions = positions.map(() => 0)
34
+ }
35
+
36
+ const resetToCurrent = () => {
37
+ desiredPositions = [...positions]
38
+ }
39
+ </script>
40
+
41
+ <Table>
42
+ <thead>
43
+ <tr>
44
+ <th>Joint</th>
45
+ <th>Position ({useRadians ? 'radians' : 'degrees'})</th>
46
+ </tr>
47
+ </thead>
48
+ <tbody>
49
+ {#each [...desiredPositions.keys()] as index (index)}
50
+ <tr>
51
+ <th scope="row">{index}</th>
52
+ <th>
53
+ <JointPositionSlider
54
+ bind:value={desiredPositions[index]}
55
+ minDegrees={getSliderMin(index)}
56
+ maxDegrees={getSliderMax(index)}
57
+ {useRadians}
58
+ />
59
+ </th>
60
+ </tr>
61
+ {/each}
62
+ </tbody>
63
+ </Table>
64
+
65
+ <div class="mb-2 flex flex-col gap-2">
66
+ <span class="flex flex-row gap-2">
67
+ <h4 class="text-xs font-semibold">Quick set</h4>
68
+ <Tooltip>
69
+ <Icon
70
+ name="information-outline"
71
+ cx="text-gray-6"
72
+ />
73
+ <span slot="description">Will update the slider values but will not execute</span>
74
+ </Tooltip>
75
+ </span>
76
+ <div class="flex flex-col gap-2 sm:flex-row">
77
+ <Button onclick={resetToZero}>Zero</Button>
78
+ <Button onclick={resetToCurrent}>Current position</Button>
79
+ </div>
80
+ </div>
81
+
82
+ <Button
83
+ class="mt-auto w-fit"
84
+ icon="play-circle-outline"
85
+ variant="dark"
86
+ onclick={execute}
87
+ >
88
+ Execute
89
+ </Button>
@@ -0,0 +1,11 @@
1
+ import { type JointLimit } from './joint-position-limits';
2
+ interface Props {
3
+ desiredPositions: number[];
4
+ positions: number[];
5
+ jointLimitsDegrees: JointLimit[];
6
+ useRadians: boolean;
7
+ moveToJointPositions: (jointPositions: number[]) => void;
8
+ }
9
+ declare const JointPositionEditor: import("svelte").Component<Props, {}, "desiredPositions">;
10
+ type JointPositionEditor = ReturnType<typeof JointPositionEditor>;
11
+ export default JointPositionEditor;
@@ -0,0 +1,85 @@
1
+ <script lang="ts">
2
+ import { Button, Icon, Tooltip } from '@viamrobotics/prime-core'
3
+
4
+ import Table from '../../table.svelte'
5
+ import { degreesToRadians, formatNumeric } from '../../../format'
6
+
7
+ const INCREMENT_DEGREES = 5
8
+
9
+ interface Props {
10
+ positions: number[]
11
+ moveToJointPositions: (jointPositions: number[]) => void
12
+ useRadians: boolean
13
+ isMoving: boolean
14
+ }
15
+
16
+ const { positions, moveToJointPositions, useRadians, isMoving }: Props = $props()
17
+
18
+ const toDisplayAngle = (degrees: number) => (useRadians ? degreesToRadians(degrees) : degrees)
19
+
20
+ const currentDisplayPositions = $derived(positions.map((degrees) => toDisplayAngle(degrees)))
21
+
22
+ const quickMove = (index: number, increment: number) => {
23
+ if (isMoving) {
24
+ return
25
+ }
26
+ const clonedPositions = positions.map((position, positionIndex) =>
27
+ positionIndex === index ? position + increment : position
28
+ )
29
+ moveToJointPositions(clonedPositions)
30
+ }
31
+ </script>
32
+
33
+ <Tooltip>
34
+ <span class="flex items-center gap-1 text-xs text-amber-600">
35
+ <Icon
36
+ name="alert"
37
+ size="sm"
38
+ cx="text-amber-600"
39
+ />
40
+ Quick move executes immediately
41
+ </span>
42
+ <span slot="description">
43
+ Each ±5° button sends a move command as soon as it is pressed. Buttons are disabled while the
44
+ arm is moving.
45
+ </span>
46
+ </Tooltip>
47
+
48
+ <Table>
49
+ <thead>
50
+ <tr>
51
+ <th>Joint</th>
52
+ <th>Move (degrees)</th>
53
+ </tr>
54
+ </thead>
55
+ <tbody>
56
+ {#each positions as position, index (index)}
57
+ {@const currentValue = currentDisplayPositions[index] ?? position}
58
+ {@const unit = useRadians ? ' rad' : '°'}
59
+ <tr>
60
+ <th scope="row">{index}</th>
61
+ <th>
62
+ <div class="flex h-full max-h-6.5 w-full items-center justify-center gap-1.5">
63
+ <Button
64
+ aria-label="Decrease joint {index} by 5 degrees"
65
+ disabled={isMoving}
66
+ onclick={() => quickMove(index, -INCREMENT_DEGREES)}
67
+ >
68
+ −5°
69
+ </Button>
70
+ <span class="min-w-16 grow text-center text-xs text-gray-700 tabular-nums">
71
+ {formatNumeric(currentValue)}{unit}
72
+ </span>
73
+ <Button
74
+ aria-label="Increase joint {index} by 5 degrees"
75
+ disabled={isMoving}
76
+ onclick={() => quickMove(index, INCREMENT_DEGREES)}
77
+ >
78
+ +5°
79
+ </Button>
80
+ </div>
81
+ </th>
82
+ </tr>
83
+ {/each}
84
+ </tbody>
85
+ </Table>
@@ -0,0 +1,9 @@
1
+ interface Props {
2
+ positions: number[];
3
+ moveToJointPositions: (jointPositions: number[]) => void;
4
+ useRadians: boolean;
5
+ isMoving: boolean;
6
+ }
7
+ declare const JointPositionQuickMove: import("svelte").Component<Props, {}, "">;
8
+ type JointPositionQuickMove = ReturnType<typeof JointPositionQuickMove>;
9
+ export default JointPositionQuickMove;