@viamrobotics/test-widgets 0.11.1 → 0.11.3

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.
@@ -57,75 +57,77 @@
57
57
 
58
58
  <ConnectionStatus {partID}>
59
59
  {#snippet connected()}
60
- <div class="flex flex-col gap-4 lg:flex-row lg:gap-0 lg:divide-x">
61
- <!-- Main control sections -->
62
- <div
63
- class="flex flex-col gap-4 lg:grid lg:grow lg:grid-cols-2 lg:gap-0 lg:divide-x xl:grid-cols-3"
64
- >
65
- <ApiSection
66
- title="GetJointPositions"
67
- api="rdk:component:arm"
68
- bottomText="Updates automatically"
60
+ <div class="@container">
61
+ <div class="flex flex-col gap-4 @2xl:flex-row @2xl:gap-0 @2xl:divide-x">
62
+ <!-- Main control sections -->
63
+ <div
64
+ class="flex flex-col gap-4 @2xl:grid @2xl:grow @2xl:grid-cols-2 @2xl:gap-0 @2xl:divide-x @4xl:grid-cols-3"
69
65
  >
70
- <Query query={jointPositionsQuery}>
71
- {#if jointPositionsQuery.data}
72
- <GetJointPositions positions={jointPositionsQuery.data.values} />
73
- {/if}
74
- </Query>
75
- </ApiSection>
76
- <ApiSection
77
- title="MoveToJointPositions"
78
- api="rdk:component:arm"
79
- >
80
- <Query query={jointPositionsQuery}>
81
- {#if jointPositionsQuery.data}
82
- <MoveToJointPositions
83
- positions={jointPositionsQuery.data.values}
84
- {moveToJointPositions}
85
- lastError={moveToJointPosMutation.error}
86
- {jointLimitsDegrees}
87
- isMoving={isMovingQuery.data ?? false}
88
- />
89
- {/if}
90
- </Query>
91
- </ApiSection>
92
- <ApiSection
93
- title="MoveToPosition"
94
- api="rdk:component:arm"
95
- >
96
- <Query query={endPositionQuery}>
97
- {#if endPositionQuery.data}
98
- <MoveToPosition
99
- endPosition={endPositionQuery.data}
100
- {moveToPosition}
101
- lastError={moveToPosMutation.error}
102
- />
103
- {/if}
104
- </Query>
105
- </ApiSection>
106
- </div>
66
+ <ApiSection
67
+ title="GetJointPositions"
68
+ api="rdk:component:arm"
69
+ bottomText="Updates automatically"
70
+ >
71
+ <Query query={jointPositionsQuery}>
72
+ {#if jointPositionsQuery.data}
73
+ <GetJointPositions positions={jointPositionsQuery.data.values} />
74
+ {/if}
75
+ </Query>
76
+ </ApiSection>
77
+ <ApiSection
78
+ title="MoveToJointPositions"
79
+ api="rdk:component:arm"
80
+ >
81
+ <Query query={jointPositionsQuery}>
82
+ {#if jointPositionsQuery.data}
83
+ <MoveToJointPositions
84
+ positions={jointPositionsQuery.data.values}
85
+ {moveToJointPositions}
86
+ lastError={moveToJointPosMutation.error}
87
+ {jointLimitsDegrees}
88
+ isMoving={isMovingQuery.data ?? false}
89
+ />
90
+ {/if}
91
+ </Query>
92
+ </ApiSection>
93
+ <ApiSection
94
+ title="MoveToPosition"
95
+ api="rdk:component:arm"
96
+ >
97
+ <Query query={endPositionQuery}>
98
+ {#if endPositionQuery.data}
99
+ <MoveToPosition
100
+ endPosition={endPositionQuery.data}
101
+ {moveToPosition}
102
+ lastError={moveToPosMutation.error}
103
+ />
104
+ {/if}
105
+ </Query>
106
+ </ApiSection>
107
+ </div>
107
108
 
108
- <!-- Control actions sidebar -->
109
- <div
110
- 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"
111
- >
112
- <ApiSection
113
- title="Stop"
114
- api="rdk:component:arm"
109
+ <!-- Control actions sidebar -->
110
+ <div
111
+ class="flex flex-row gap-4 @2xl:ml-auto @2xl:w-full @2xl:max-w-40 @2xl:flex-col @2xl:gap-0 @2xl:divide-y"
115
112
  >
116
- <StopButton
117
- error={stopMutation.error}
118
- onStop={() => {
119
- stopMutation.mutate([], {})
120
- }}
113
+ <ApiSection
114
+ title="Stop"
115
+ api="rdk:component:arm"
116
+ >
117
+ <StopButton
118
+ error={stopMutation.error}
119
+ onStop={() => {
120
+ stopMutation.mutate([], {})
121
+ }}
122
+ />
123
+ </ApiSection>
124
+ <IsMoving
125
+ client={ArmClient}
126
+ api="rdk:component:arm"
127
+ {partID}
128
+ {resourceName}
121
129
  />
122
- </ApiSection>
123
- <IsMoving
124
- client={ArmClient}
125
- api="rdk:component:arm"
126
- {partID}
127
- {resourceName}
128
- />
130
+ </div>
129
131
  </div>
130
132
  </div>
131
133
  {/snippet}
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { Icon, Tooltip } from '@viamrobotics/prime-core'
2
+ import { Icon, ToggleButtons, Tooltip } from '@viamrobotics/prime-core'
3
3
 
4
4
  import AngleUnitToggle from '../../angle-unit-toggle.svelte'
5
5
  import CopyButton from '../../copy-button.svelte'
@@ -11,7 +11,9 @@
11
11
  import { type JointLimit } from './joint-position-limits'
12
12
  import JointPositionQuickMove from './joint-position-quick-move.svelte'
13
13
 
14
- type ControlMode = 'jointPositions' | 'quickMove'
14
+ type ControlMode = 'Joint Positions' | 'Quick Move'
15
+
16
+ const CONTROL_MODES: ControlMode[] = ['Joint Positions', 'Quick Move']
15
17
 
16
18
  interface Props {
17
19
  positions: number[]
@@ -32,9 +34,9 @@
32
34
  // svelte-ignore state_referenced_locally
33
35
  let desiredPositions = $state([...positions]) // in degrees
34
36
  let useRadians = $state(false)
35
- let controlMode = $state<ControlMode>('jointPositions')
37
+ let controlMode = $state<ControlMode>('Joint Positions')
36
38
 
37
- const isQuickMoveMode = $derived(controlMode === 'quickMove')
39
+ const isQuickMoveMode = $derived(controlMode === 'Quick Move')
38
40
 
39
41
  const getJointMin = (index: number): number => jointLimitsDegrees[index]?.minDegrees ?? -180
40
42
  const getJointMax = (index: number): number => jointLimitsDegrees[index]?.maxDegrees ?? 180
@@ -44,8 +46,11 @@
44
46
  const displayPositions = $derived(desiredPositions.map((degrees) => toDisplayAngle(degrees)))
45
47
  const copyData = $derived(`[${displayPositions.map((v) => formatNumeric(v)).join(', ')}]`)
46
48
 
47
- const toggleMode = () => {
48
- controlMode = isQuickMoveMode ? 'jointPositions' : 'quickMove'
49
+ const handleModeChange = ({ detail }: CustomEvent<string>) => {
50
+ const nextMode = CONTROL_MODES.find((mode) => mode === detail)
51
+ if (nextMode) {
52
+ controlMode = nextMode
53
+ }
49
54
  }
50
55
 
51
56
  const handlePaste = (data: string): boolean => {
@@ -63,48 +68,35 @@
63
68
  </script>
64
69
 
65
70
  <div class="flex min-w-0 flex-col gap-4">
66
- <div class="flex items-center justify-between">
67
- <span class="flex flex-row items-center gap-1 text-sm">
68
- Joint Positions
69
- <Tooltip>
70
- <Icon
71
- name="information-outline"
72
- cx="text-gray-6"
73
- />
74
- <span slot="description">
75
- Joint position limits are based solely on the arm kinematics and do not take into account
76
- motion service limit overrides.
77
- </span>
78
- </Tooltip>
79
- </span>
71
+ <div class="flex flex-wrap items-center justify-between gap-2">
72
+ <ToggleButtons
73
+ role="group"
74
+ aria-label="Control mode"
75
+ options={CONTROL_MODES}
76
+ selected={controlMode}
77
+ on:input={handleModeChange}
78
+ />
80
79
  <div class="flex items-center gap-1">
81
- <div class="flex gap-1">
82
- {#if !isQuickMoveMode}
83
- <CopyButton data={copyData} />
84
- <PasteButton onPaste={handlePaste} />
85
- {/if}
86
- <AngleUnitToggle
87
- {useRadians}
88
- onToggle={() => {
89
- useRadians = !useRadians
90
- }}
91
- />
92
- </div>
93
- <Tooltip>
94
- <button
95
- onclick={toggleMode}
96
- aria-label={isQuickMoveMode ? 'Exit quick move mode' : 'Enter quick move mode'}
97
- class={[
98
- 'hover:border-medium hover:bg-medium active:bg-gray-2 rounded p-0.5',
99
- isQuickMoveMode ? 'text-amber-600' : 'text-gray-6',
100
- ]}
101
- >
102
- <Icon name="lightning-bolt-outline" />
103
- </button>
104
- <span slot="description">
105
- {isQuickMoveMode ? 'Exit quick move mode' : 'Enter quick move mode'}
106
- </span>
107
- </Tooltip>
80
+ {#if !isQuickMoveMode}
81
+ <Tooltip>
82
+ <Icon
83
+ name="information-outline"
84
+ cx="text-gray-6"
85
+ />
86
+ <span slot="description">
87
+ Joint position limits are based solely on the arm kinematics and do not take into
88
+ account motion service limit overrides.
89
+ </span>
90
+ </Tooltip>
91
+ <CopyButton data={copyData} />
92
+ <PasteButton onPaste={handlePaste} />
93
+ {/if}
94
+ <AngleUnitToggle
95
+ {useRadians}
96
+ onToggle={() => {
97
+ useRadians = !useRadians
98
+ }}
99
+ />
108
100
  </div>
109
101
  </div>
110
102
 
@@ -405,7 +405,7 @@
405
405
  lastError={resolutionMutation.error}
406
406
  class="-m-4 mt-4"
407
407
  >
408
- <div class="flex w-full flex-row justify-between">
408
+ <div class="flex w-full flex-wrap justify-between gap-2">
409
409
  <Label cx="max-w-[96px] gap-1 text-xs">
410
410
  <span class="flex gap-1 whitespace-nowrap">
411
411
  Resolution
@@ -61,42 +61,44 @@
61
61
  />
62
62
  </div>
63
63
 
64
- <div class="grid w-full grid-cols-2 divide-x">
65
- <ApiSection
66
- title="GetPosition"
67
- api="rdk:component:encoder"
68
- tooltip="Relative encoders return ticks since last zeroing. Absolute encoders return degrees."
69
- class="gap-3"
70
- >
71
- <Queries queries={[propertiesQuery, positionQuery]}>
72
- {#if positionQuery.data !== undefined}
73
- {@const [position, encoderPositionType] = positionQuery.data}
74
- <div class="font-roboto-mono flex flex-col gap-2 text-sm">
75
- <Position
76
- {position}
77
- {encoderPositionType}
78
- />
79
- </div>
80
- {/if}
81
- </Queries>
82
- </ApiSection>
64
+ <div class="@container">
65
+ <div class="grid w-full grid-cols-1 divide-y @lg:grid-cols-2 @lg:divide-x @lg:divide-y-0">
66
+ <ApiSection
67
+ title="GetPosition"
68
+ api="rdk:component:encoder"
69
+ tooltip="Relative encoders return ticks since last zeroing. Absolute encoders return degrees."
70
+ class="gap-3"
71
+ >
72
+ <Queries queries={[propertiesQuery, positionQuery]}>
73
+ {#if positionQuery.data !== undefined}
74
+ {@const [position, encoderPositionType] = positionQuery.data}
75
+ <div class="font-roboto-mono flex flex-col gap-2 text-sm">
76
+ <Position
77
+ {position}
78
+ {encoderPositionType}
79
+ />
80
+ </div>
81
+ {/if}
82
+ </Queries>
83
+ </ApiSection>
83
84
 
84
- <ApiSection
85
- title="ResetPosition"
86
- api="rdk:component:encoder"
87
- description="Set the current position as the new zero position"
88
- >
89
- <Button
90
- icon="play-circle-outline"
91
- class="w-fit"
92
- onclick={() => {
93
- resetMutation.mutate([], {})
94
- }}
85
+ <ApiSection
86
+ title="ResetPosition"
87
+ api="rdk:component:encoder"
88
+ description="Set the current position as the new zero position"
95
89
  >
96
- Execute
97
- </Button>
98
- <ErrorDisplay lastError={resetMutation.error} />
99
- </ApiSection>
90
+ <Button
91
+ icon="play-circle-outline"
92
+ class="w-fit"
93
+ onclick={() => {
94
+ resetMutation.mutate([], {})
95
+ }}
96
+ >
97
+ Execute
98
+ </Button>
99
+ <ErrorDisplay lastError={resetMutation.error} />
100
+ </ApiSection>
101
+ </div>
100
102
  </div>
101
103
  {/snippet}
102
104
  </ConnectionStatus>
@@ -39,7 +39,7 @@
39
39
 
40
40
  <ConnectionStatus {partID}>
41
41
  {#snippet connected()}
42
- <div class="flex flex-row items-center justify-between p-4 pb-3">
42
+ <div class="flex flex-wrap items-center justify-between gap-2 p-4 pb-3">
43
43
  <RefetchController
44
44
  {refetchInterval}
45
45
  queries={[eventsQuery]}
@@ -43,7 +43,7 @@
43
43
 
44
44
  <ConnectionStatus {partID}>
45
45
  {#snippet connected()}
46
- <div class="flex gap-4 p-4 pb-3">
46
+ <div class="flex flex-wrap gap-4 p-4 pb-3">
47
47
  <RefetchController
48
48
  allowLive
49
49
  {refetchInterval}
@@ -24,7 +24,7 @@
24
24
  const lng = $derived(safeReadCoordinate(coordinate?.longitude))
25
25
  </script>
26
26
 
27
- <div class="relative h-[300px] w-full lg:h-auto lg:w-1/2">
27
+ <div class="relative h-[300px] w-full @4xl:h-auto @4xl:w-1/2">
28
28
  <MapLibre
29
29
  zoom={15}
30
30
  options={{ attributionControl: { compact: false } }}
@@ -103,150 +103,152 @@
103
103
  query={propertiesQuery}
104
104
  contentCx="p-4 h-14"
105
105
  >
106
- <div class="flex flex-wrap text-xs lg:flex-nowrap">
107
- <div class="flex w-full flex-col gap-5 py-4 pr-6 pl-4 lg:w-1/4">
108
- {#if propertiesQuery.data?.positionSupported}
109
- <div class="flex flex-col gap-2">
110
- <SectionTitle
111
- title="GetPosition"
112
- api={MS_API}
113
- />
114
- <Query
115
- query={positionQuery}
116
- contentCx="h-6"
117
- >
118
- {#if positionQuery.data !== undefined}
119
- <Position data={positionQuery.data} />
120
- {/if}
121
- </Query>
122
- </div>
123
- {/if}
106
+ <div class="@container">
107
+ <div class="flex flex-wrap text-xs @4xl:flex-nowrap">
108
+ <div class="flex w-full flex-col gap-5 py-4 pr-6 pl-4 @4xl:w-1/4">
109
+ {#if propertiesQuery.data?.positionSupported}
110
+ <div class="flex flex-col gap-2">
111
+ <SectionTitle
112
+ title="GetPosition"
113
+ api={MS_API}
114
+ />
115
+ <Query
116
+ query={positionQuery}
117
+ contentCx="h-6"
118
+ >
119
+ {#if positionQuery.data !== undefined}
120
+ <Position data={positionQuery.data} />
121
+ {/if}
122
+ </Query>
123
+ </div>
124
+ {/if}
124
125
 
125
- {#if propertiesQuery.data?.orientationSupported}
126
- <div class="flex flex-col gap-2">
127
- <SectionTitle
128
- title="GetOrientation"
129
- api={MS_API}
130
- >
131
- {#snippet suffix()}<span class="text-subtle-2 text-xs font-normal">(º)</span
132
- >{/snippet}
133
- </SectionTitle>
134
- <Query
135
- query={orientationQuery}
136
- contentCx="h-6"
137
- >
138
- {#if orientationQuery.data !== undefined}
139
- <Orientation data={orientationQuery.data} />
140
- {/if}
141
- </Query>
142
- </div>
143
- {/if}
126
+ {#if propertiesQuery.data?.orientationSupported}
127
+ <div class="flex flex-col gap-2">
128
+ <SectionTitle
129
+ title="GetOrientation"
130
+ api={MS_API}
131
+ >
132
+ {#snippet suffix()}<span class="text-subtle-2 text-xs font-normal">(º)</span
133
+ >{/snippet}
134
+ </SectionTitle>
135
+ <Query
136
+ query={orientationQuery}
137
+ contentCx="h-6"
138
+ >
139
+ {#if orientationQuery.data !== undefined}
140
+ <Orientation data={orientationQuery.data} />
141
+ {/if}
142
+ </Query>
143
+ </div>
144
+ {/if}
144
145
 
145
- {#if propertiesQuery.data?.compassHeadingSupported}
146
- <div class="flex flex-col gap-2">
147
- <SectionTitle
148
- title="GetCompassHeading"
149
- api={MS_API}
150
- >
151
- {#snippet suffix()}<span class="text-subtle-2 text-xs font-normal">(º)</span
152
- >{/snippet}
153
- </SectionTitle>
154
- <Query
155
- query={compassHeadingQuery}
156
- contentCx="h-6"
157
- >
158
- {#if compassHeadingQuery.data !== undefined}
159
- <CompassHeading data={compassHeadingQuery.data} />
160
- {/if}
161
- </Query>
162
- </div>
163
- {/if}
164
- </div>
146
+ {#if propertiesQuery.data?.compassHeadingSupported}
147
+ <div class="flex flex-col gap-2">
148
+ <SectionTitle
149
+ title="GetCompassHeading"
150
+ api={MS_API}
151
+ >
152
+ {#snippet suffix()}<span class="text-subtle-2 text-xs font-normal">(º)</span
153
+ >{/snippet}
154
+ </SectionTitle>
155
+ <Query
156
+ query={compassHeadingQuery}
157
+ contentCx="h-6"
158
+ >
159
+ {#if compassHeadingQuery.data !== undefined}
160
+ <CompassHeading data={compassHeadingQuery.data} />
161
+ {/if}
162
+ </Query>
163
+ </div>
164
+ {/if}
165
+ </div>
165
166
 
166
- <div class="flex w-full flex-col gap-5 p-4 lg:w-1/4">
167
- {#if propertiesQuery.data?.angularVelocitySupported}
168
- <div class="flex flex-col gap-2">
169
- <SectionTitle
170
- title="GetAngularVelocity"
171
- api={MS_API}
172
- >
173
- {#snippet suffix()}<span class="text-subtle-2 text-xs font-normal">(º/s)</span
174
- >{/snippet}
175
- </SectionTitle>
176
- <Query
177
- query={angularVelocityQuery}
178
- contentCx="h-6"
179
- >
180
- {#if angularVelocityQuery.data !== undefined}
181
- <Vector3 data={angularVelocityQuery.data} />
182
- {/if}
183
- </Query>
184
- </div>
185
- {/if}
167
+ <div class="flex w-full flex-col gap-5 p-4 @4xl:w-1/4">
168
+ {#if propertiesQuery.data?.angularVelocitySupported}
169
+ <div class="flex flex-col gap-2">
170
+ <SectionTitle
171
+ title="GetAngularVelocity"
172
+ api={MS_API}
173
+ >
174
+ {#snippet suffix()}<span class="text-subtle-2 text-xs font-normal">(º/s)</span
175
+ >{/snippet}
176
+ </SectionTitle>
177
+ <Query
178
+ query={angularVelocityQuery}
179
+ contentCx="h-6"
180
+ >
181
+ {#if angularVelocityQuery.data !== undefined}
182
+ <Vector3 data={angularVelocityQuery.data} />
183
+ {/if}
184
+ </Query>
185
+ </div>
186
+ {/if}
186
187
 
187
- {#if propertiesQuery.data?.linearVelocitySupported}
188
- <div class="flex flex-col gap-2">
189
- <SectionTitle
190
- title="GetLinearVelocity"
191
- api={MS_API}
192
- >
193
- {#snippet suffix()}<span class="text-subtle-2 text-xs font-normal">(m/s)</span
194
- >{/snippet}
195
- </SectionTitle>
196
- <Query
197
- query={linearVelocityQuery}
198
- contentCx="h-6"
199
- >
200
- {#if linearVelocityQuery.data !== undefined}
201
- <Vector3 data={linearVelocityQuery.data} />
202
- {/if}
203
- </Query>
204
- </div>
205
- {/if}
188
+ {#if propertiesQuery.data?.linearVelocitySupported}
189
+ <div class="flex flex-col gap-2">
190
+ <SectionTitle
191
+ title="GetLinearVelocity"
192
+ api={MS_API}
193
+ >
194
+ {#snippet suffix()}<span class="text-subtle-2 text-xs font-normal">(m/s)</span
195
+ >{/snippet}
196
+ </SectionTitle>
197
+ <Query
198
+ query={linearVelocityQuery}
199
+ contentCx="h-6"
200
+ >
201
+ {#if linearVelocityQuery.data !== undefined}
202
+ <Vector3 data={linearVelocityQuery.data} />
203
+ {/if}
204
+ </Query>
205
+ </div>
206
+ {/if}
207
+
208
+ {#if propertiesQuery.data?.linearAccelerationSupported}
209
+ <div class="flex flex-col gap-2">
210
+ <SectionTitle
211
+ title="GetLinearAcceleration"
212
+ api={MS_API}
213
+ >
214
+ {#snippet suffix()}<span class="text-subtle-2 text-xs font-normal"
215
+ >(m/s<sup>2</sup>)</span
216
+ >{/snippet}
217
+ </SectionTitle>
218
+ <Query
219
+ query={linearAccelerationQuery}
220
+ contentCx="h-6"
221
+ >
222
+ {#if linearAccelerationQuery.data !== undefined}
223
+ <Vector3 data={linearAccelerationQuery.data} />
224
+ {/if}
225
+ </Query>
226
+ </div>
227
+ {/if}
206
228
 
207
- {#if propertiesQuery.data?.linearAccelerationSupported}
208
229
  <div class="flex flex-col gap-2">
209
230
  <SectionTitle
210
- title="GetLinearAcceleration"
231
+ title="GetAccuracy"
211
232
  api={MS_API}
212
- >
213
- {#snippet suffix()}<span class="text-subtle-2 text-xs font-normal"
214
- >(m/s<sup>2</sup>)</span
215
- >{/snippet}
216
- </SectionTitle>
233
+ />
217
234
  <Query
218
- query={linearAccelerationQuery}
235
+ query={accuracyQuery}
219
236
  contentCx="h-6"
220
237
  >
221
- {#if linearAccelerationQuery.data !== undefined}
222
- <Vector3 data={linearAccelerationQuery.data} />
238
+ {#if accuracyQuery.data !== undefined}
239
+ <Accuracy data={accuracyQuery.data} />
223
240
  {/if}
224
241
  </Query>
225
242
  </div>
226
- {/if}
243
+ </div>
227
244
 
228
- <div class="flex flex-col gap-2">
229
- <SectionTitle
230
- title="GetAccuracy"
231
- api={MS_API}
245
+ {#if propertiesQuery.data?.positionSupported}
246
+ <Map
247
+ coordinate={positionQuery.data?.coordinate}
248
+ rotation={orientationQuery.data?.oZ}
232
249
  />
233
- <Query
234
- query={accuracyQuery}
235
- contentCx="h-6"
236
- >
237
- {#if accuracyQuery.data !== undefined}
238
- <Accuracy data={accuracyQuery.data} />
239
- {/if}
240
- </Query>
241
- </div>
250
+ {/if}
242
251
  </div>
243
-
244
- {#if propertiesQuery.data?.positionSupported}
245
- <Map
246
- coordinate={positionQuery.data?.coordinate}
247
- rotation={orientationQuery.data?.oZ}
248
- />
249
- {/if}
250
252
  </div>
251
253
  </Query>
252
254
 
@@ -134,7 +134,7 @@
134
134
 
135
135
  <ConnectionStatus {partID}>
136
136
  {#snippet connected()}
137
- <div class="flex w-full justify-between p-4">
137
+ <div class="flex w-full flex-wrap justify-between gap-2 p-4">
138
138
  <RefetchController
139
139
  {refetchInterval}
140
140
  queries={[locationQuery, obstaclesQuery, waypointsQuery]}
@@ -152,85 +152,89 @@
152
152
  </div>
153
153
  </div>
154
154
 
155
- <div class="relative h-120 w-full items-stretch p-2 sm:flex">
156
- <div class="w-full max-w-62.5 p-2">
157
- <TabsBar variant="secondary">
158
- <Tab
159
- title="Obstacles"
160
- selected={tab.current === 'obstacles'}
161
- selectTab={() => (tab.current = 'obstacles')}
162
- />
163
- <Tab
164
- title="Waypoints"
165
- selected={tab.current === 'waypoints'}
166
- selectTab={() => (tab.current = 'waypoints')}
167
- />
168
- </TabsBar>
169
-
170
- <ul class="h-[calc(100%-32px)] overflow-auto">
171
- {#if tab.current === 'obstacles'}
172
- <ObstaclesLegend
173
- {map}
174
- {hovered}
175
- {obstacles}
176
- onEnter={setHovered}
177
- onLeave={setHovered}
155
+ <div class="@container">
156
+ <div class="relative flex w-full flex-col items-stretch p-2 @2xl:h-120 @2xl:flex-row">
157
+ <div class="w-full p-2 @2xl:max-w-62.5">
158
+ <TabsBar variant="secondary">
159
+ <Tab
160
+ title="Obstacles"
161
+ selected={tab.current === 'obstacles'}
162
+ selectTab={() => (tab.current = 'obstacles')}
178
163
  />
179
- {:else if tab.current === 'waypoints'}
180
- <WaypointsLegend
181
- {map}
182
- {hovered}
183
- {waypoints}
184
- onEnter={setHovered}
185
- onLeave={setHovered}
186
- onRemove={async (id) => {
187
- await removeWaypointMutation.mutateAsync([id])
188
- await waypointsQuery.refetch()
189
- }}
164
+ <Tab
165
+ title="Waypoints"
166
+ selected={tab.current === 'waypoints'}
167
+ selectTab={() => (tab.current = 'waypoints')}
190
168
  />
191
- {/if}
192
- </ul>
193
- </div>
169
+ </TabsBar>
170
+
171
+ <ul class="max-h-60 overflow-auto @2xl:h-[calc(100%-32px)] @2xl:max-h-none">
172
+ {#if tab.current === 'obstacles'}
173
+ <ObstaclesLegend
174
+ {map}
175
+ {hovered}
176
+ {obstacles}
177
+ onEnter={setHovered}
178
+ onLeave={setHovered}
179
+ />
180
+ {:else if tab.current === 'waypoints'}
181
+ <WaypointsLegend
182
+ {map}
183
+ {hovered}
184
+ {waypoints}
185
+ onEnter={setHovered}
186
+ onLeave={setHovered}
187
+ onRemove={async (id) => {
188
+ await removeWaypointMutation.mutateAsync([id])
189
+ await waypointsQuery.refetch()
190
+ }}
191
+ />
192
+ {/if}
193
+ </ul>
194
+ </div>
194
195
 
195
- <MapLibre
196
- class="relative grow"
197
- maxPitch={view === '3D' ? 60 : 0}
198
- mapProvider={MapProviders.googleMaps}
199
- mapProviderKey={import.meta.env.VITE_GOOGLE_MAPS_API_KEY}
200
- bind:map
201
- >
202
- <NavigationControls showZoom={false} />
196
+ <div class="relative h-80 @2xl:h-auto @2xl:grow">
197
+ <MapLibre
198
+ class="relative"
199
+ maxPitch={view === '3D' ? 60 : 0}
200
+ mapProvider={MapProviders.googleMaps}
201
+ mapProviderKey={import.meta.env.VITE_GOOGLE_MAPS_API_KEY}
202
+ bind:map
203
+ >
204
+ <NavigationControls showZoom={false} />
205
+
206
+ <div class="absolute top-5 right-14 z-10 flex items-center gap-2">
207
+ <ToggleButtons
208
+ options={['2D', '3D']}
209
+ selected={view}
210
+ on:input={handleViewSelect}
211
+ />
212
+ <SatelliteControls />
213
+ <CenterControls />
214
+ </div>
215
+
216
+ {#if locationQuery.data}
217
+ <DirectionalMarker position={locationQuery.data} />
218
+ {/if}
219
+
220
+ <Waypoints
221
+ {waypoints}
222
+ {addWayPoint}
223
+ tab={tab.current ?? 'obstacles'}
224
+ {hovered}
225
+ />
203
226
 
204
- <div class="absolute top-5 right-14 z-10 flex items-center gap-2">
205
- <ToggleButtons
206
- options={['2D', '3D']}
207
- selected={view}
208
- on:input={handleViewSelect}
209
- />
210
- <SatelliteControls />
211
- <CenterControls />
227
+ {#snippet layer()}
228
+ <Obstacles
229
+ {obstacles}
230
+ {hovered}
231
+ {setHovered}
232
+ {view}
233
+ />
234
+ {/snippet}
235
+ </MapLibre>
212
236
  </div>
213
-
214
- {#if locationQuery.data}
215
- <DirectionalMarker position={locationQuery.data} />
216
- {/if}
217
-
218
- <Waypoints
219
- {waypoints}
220
- {addWayPoint}
221
- tab={tab.current ?? 'obstacles'}
222
- {hovered}
223
- />
224
-
225
- {#snippet layer()}
226
- <Obstacles
227
- {obstacles}
228
- {hovered}
229
- {setHovered}
230
- {view}
231
- />
232
- {/snippet}
233
- </MapLibre>
237
+ </div>
234
238
  </div>
235
239
  {/snippet}
236
240
  </ConnectionStatus>
@@ -34,49 +34,58 @@
34
34
  {#if formattedOperations.length === 0}
35
35
  <p class="text-subtle-2 text-xs">No operations currently available</p>
36
36
  {:else}
37
- <table class="w-full table-fixed">
38
- <thead>
39
- <tr class="bg-light text-subtle-1 border text-left text-xs">
40
- <th class="w-1/4 p-2 font-normal">ID</th>
41
- <th class="w-1/4 p-2 font-normal">Session</th>
42
- <th class="w-1/4 p-2 font-normal">Method</th>
43
- <th class="w-1/6 p-2 font-normal">Elapsed Time</th>
44
- <th class="w-16"></th>
45
- </tr>
46
- </thead>
47
- <tbody class="text-xs">
48
- {#each formattedOperations as operation (operation.id)}
49
- <tr class="border border-b text-left">
50
- <td
51
- class="truncate p-2"
52
- title={operation.id}
53
- >{operation.id}
54
- </td>
55
- <td
56
- class="truncate p-2"
57
- title={operation.sessionId}
58
- >{operation.sessionId}
59
- </td>
60
- <td
61
- class="truncate p-2"
62
- title={operation.method}
63
- >{operation.method}
64
- </td>
65
- <td
66
- class="truncate p-2"
67
- title={`${formatNumeric(operation.elapsedTime, 0)} ms`}
68
- >
69
- {formatNumeric(operation.elapsedTime, 0)} ms
70
- </td>
71
- <td class="p-2">
72
- <Tooltip hoverDelayMS={500}>
73
- <Button onclick={() => cancelOperation(operation.id)}>Kill</Button>
74
-
75
- <span slot="description"> Request cancellation of the operation. </span>
76
- </Tooltip>
77
- </td>
37
+ <!-- Scroll region: keyboard users need focus on the wrapper to scroll the overflow (WCAG 2.1.1). -->
38
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
39
+ <div
40
+ class="overflow-x-auto"
41
+ role="region"
42
+ aria-label="Operations"
43
+ tabindex="0"
44
+ >
45
+ <table class="w-full min-w-md table-fixed">
46
+ <thead>
47
+ <tr class="bg-light text-subtle-1 border text-left text-xs">
48
+ <th class="w-1/4 p-2 font-normal">ID</th>
49
+ <th class="w-1/4 p-2 font-normal">Session</th>
50
+ <th class="w-1/4 p-2 font-normal">Method</th>
51
+ <th class="w-1/6 p-2 font-normal">Elapsed Time</th>
52
+ <th class="w-16"></th>
78
53
  </tr>
79
- {/each}
80
- </tbody>
81
- </table>
54
+ </thead>
55
+ <tbody class="text-xs">
56
+ {#each formattedOperations as operation (operation.id)}
57
+ <tr class="border border-b text-left">
58
+ <td
59
+ class="truncate p-2"
60
+ title={operation.id}
61
+ >{operation.id}
62
+ </td>
63
+ <td
64
+ class="truncate p-2"
65
+ title={operation.sessionId}
66
+ >{operation.sessionId}
67
+ </td>
68
+ <td
69
+ class="truncate p-2"
70
+ title={operation.method}
71
+ >{operation.method}
72
+ </td>
73
+ <td
74
+ class="truncate p-2"
75
+ title={`${formatNumeric(operation.elapsedTime, 0)} ms`}
76
+ >
77
+ {formatNumeric(operation.elapsedTime, 0)} ms
78
+ </td>
79
+ <td class="p-2">
80
+ <Tooltip hoverDelayMS={500}>
81
+ <Button onclick={() => cancelOperation(operation.id)}>Kill</Button>
82
+
83
+ <span slot="description"> Request cancellation of the operation. </span>
84
+ </Tooltip>
85
+ </td>
86
+ </tr>
87
+ {/each}
88
+ </tbody>
89
+ </table>
90
+ </div>
82
91
  {/if}
@@ -38,41 +38,50 @@
38
38
  {#if flattenedSessions.length === 0}
39
39
  <p class="text-subtle-2 text-xs">No sessions currently available</p>
40
40
  {:else}
41
- <table class="w-full table-fixed">
42
- <thead>
43
- <tr class="bg-light text-subtle-1 border text-left text-xs">
44
- <th class="w-1/4 p-2 font-normal">ID</th>
45
- <th class="w-1/8 p-2 font-normal">Type</th>
46
- <th class="w-1/4 p-2 font-normal">Remote Address</th>
47
- <th class="w-1/4 p-2 font-normal">Local Address</th>
48
- </tr>
49
- </thead>
50
- <tbody class="text-xs">
51
- {#each flattenedSessions as session (session.id)}
52
- <tr class="border border-b text-left">
53
- <td
54
- class="truncate p-2 text-left"
55
- title={session.id}
56
- >
57
- {session.id}
58
- {#if session.id === ourSessionId}
59
- <div class="text-subtle-2 text-xs">(Ours)</div>
60
- {/if}
61
- </td>
62
- <td class="truncate p-2">{session.typeString}</td>
63
- <td
64
- class="truncate p-2"
65
- title={session.remoteAddress}
66
- >{session.remoteAddress}
67
- </td>
68
- <td
69
- class="truncate p-2"
70
- title={session.localAddress}
71
- >
72
- {session.localAddress}
73
- </td>
41
+ <!-- Scroll region: keyboard users need focus on the wrapper to scroll the overflow (WCAG 2.1.1). -->
42
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
43
+ <div
44
+ class="overflow-x-auto"
45
+ role="region"
46
+ aria-label="Sessions"
47
+ tabindex="0"
48
+ >
49
+ <table class="w-full min-w-md table-fixed">
50
+ <thead>
51
+ <tr class="bg-light text-subtle-1 border text-left text-xs">
52
+ <th class="w-1/4 p-2 font-normal">ID</th>
53
+ <th class="w-1/8 p-2 font-normal">Type</th>
54
+ <th class="w-1/4 p-2 font-normal">Remote Address</th>
55
+ <th class="w-1/4 p-2 font-normal">Local Address</th>
74
56
  </tr>
75
- {/each}
76
- </tbody>
77
- </table>
57
+ </thead>
58
+ <tbody class="text-xs">
59
+ {#each flattenedSessions as session (session.id)}
60
+ <tr class="border border-b text-left">
61
+ <td
62
+ class="truncate p-2 text-left"
63
+ title={session.id}
64
+ >
65
+ {session.id}
66
+ {#if session.id === ourSessionId}
67
+ <div class="text-subtle-2 text-xs">(Ours)</div>
68
+ {/if}
69
+ </td>
70
+ <td class="truncate p-2">{session.typeString}</td>
71
+ <td
72
+ class="truncate p-2"
73
+ title={session.remoteAddress}
74
+ >{session.remoteAddress}
75
+ </td>
76
+ <td
77
+ class="truncate p-2"
78
+ title={session.localAddress}
79
+ >
80
+ {session.localAddress}
81
+ </td>
82
+ </tr>
83
+ {/each}
84
+ </tbody>
85
+ </table>
86
+ </div>
78
87
  {/if}
@@ -63,49 +63,51 @@
63
63
  />
64
64
  </div>
65
65
 
66
- <div class="grid w-full grid-cols-3 divide-x">
67
- <ApiSection
68
- title="GetCurrent"
69
- api="rdk:component:power_sensor"
70
- class="pb-5"
71
- >
72
- <Query
73
- query={currentQuery}
74
- contentCx="h-6"
66
+ <div class="@container">
67
+ <div class="grid w-full grid-cols-1 divide-y @2xl:grid-cols-3 @2xl:divide-x @2xl:divide-y-0">
68
+ <ApiSection
69
+ title="GetCurrent"
70
+ api="rdk:component:power_sensor"
71
+ class="pb-5"
75
72
  >
76
- {#if currentQuery.data !== undefined}
77
- <CurrentReading data={currentQuery.data} />
78
- {/if}
79
- </Query>
80
- </ApiSection>
81
- <ApiSection
82
- title="GetVoltage"
83
- api="rdk:component:power_sensor"
84
- class="pb-5"
85
- >
86
- <Query
87
- query={voltageQuery}
88
- contentCx="h-6"
73
+ <Query
74
+ query={currentQuery}
75
+ contentCx="h-6"
76
+ >
77
+ {#if currentQuery.data !== undefined}
78
+ <CurrentReading data={currentQuery.data} />
79
+ {/if}
80
+ </Query>
81
+ </ApiSection>
82
+ <ApiSection
83
+ title="GetVoltage"
84
+ api="rdk:component:power_sensor"
85
+ class="pb-5"
89
86
  >
90
- {#if voltageQuery.data !== undefined}
91
- <VoltageReading data={voltageQuery.data} />
92
- {/if}
93
- </Query>
94
- </ApiSection>
95
- <ApiSection
96
- title="GetPower"
97
- api="rdk:component:power_sensor"
98
- class="pb-5"
99
- >
100
- <Query
101
- query={powerQuery}
102
- contentCx="h-6"
87
+ <Query
88
+ query={voltageQuery}
89
+ contentCx="h-6"
90
+ >
91
+ {#if voltageQuery.data !== undefined}
92
+ <VoltageReading data={voltageQuery.data} />
93
+ {/if}
94
+ </Query>
95
+ </ApiSection>
96
+ <ApiSection
97
+ title="GetPower"
98
+ api="rdk:component:power_sensor"
99
+ class="pb-5"
103
100
  >
104
- {#if powerQuery.data !== undefined}
105
- <PowerReading data={powerQuery.data} />
106
- {/if}
107
- </Query>
108
- </ApiSection>
101
+ <Query
102
+ query={powerQuery}
103
+ contentCx="h-6"
104
+ >
105
+ {#if powerQuery.data !== undefined}
106
+ <PowerReading data={powerQuery.data} />
107
+ {/if}
108
+ </Query>
109
+ </ApiSection>
110
+ </div>
109
111
  </div>
110
112
 
111
113
  <ApiSection
@@ -49,7 +49,7 @@
49
49
  End position
50
50
  <abbr class="text-disabled">(m)</abbr>
51
51
  </h4>
52
- <div class="flex flex-row gap-2 px-3 py-2.5">
52
+ <div class="flex flex-wrap gap-2 px-3 py-2.5">
53
53
  <Label>
54
54
  X
55
55
 
@@ -191,78 +191,86 @@
191
191
  >.
192
192
  </div>
193
193
  {:else}
194
- <div class="flex divide-x">
195
- <div class="divide-y">
196
- <div class="m-4">
197
- <RefetchController
198
- {refetchInterval}
199
- queries={[positionQuery, pointCloudMapQuery]}
194
+ <div class="@container">
195
+ <div class="flex flex-col @2xl:flex-row @2xl:divide-x">
196
+ <div class="divide-y">
197
+ <div class="m-4">
198
+ <RefetchController
199
+ {refetchInterval}
200
+ queries={[positionQuery, pointCloudMapQuery]}
201
+ />
202
+ </div>
203
+
204
+ <ApiSection
205
+ title="GetPosition"
206
+ api="rdk:service:slam"
207
+ >
208
+ <Queries
209
+ queries={[propertiesQuery, positionQuery]}
210
+ contentCx="h-6"
211
+ >
212
+ {#if positionQuery.data !== undefined}
213
+ <Position position={positionQuery.data} />
214
+ {/if}
215
+ </Queries>
216
+ </ApiSection>
217
+
218
+ <ApiSection title="Motion">
219
+ <Label>
220
+ Base name
221
+
222
+ <Input
223
+ slot="input"
224
+ bind:value={baseName}
225
+ />
226
+ </Label>
227
+ <Label>
228
+ Motion name
229
+
230
+ <Input
231
+ slot="input"
232
+ placeholder="builtin"
233
+ bind:value={motionName}
234
+ />
235
+ </Label>
236
+ </ApiSection>
237
+
238
+ <MoveOnMap
239
+ {destination}
240
+ {updateDestination}
241
+ {moveOnMap}
242
+ {stopPlan}
243
+ lastError={moveOnMapMutation.error ?? stopPlanMutation.error}
200
244
  />
201
245
  </div>
202
246
 
203
- <ApiSection
204
- title="GetPosition"
205
- api="rdk:service:slam"
206
- >
247
+ <div class="flex w-full">
207
248
  <Queries
208
- queries={[propertiesQuery, positionQuery]}
209
- contentCx="h-6"
249
+ queries={[propertiesQuery, positionQuery, pointCloudMapQuery]}
250
+ contentCx="p-4 h-auto"
210
251
  >
211
- {#if positionQuery.data !== undefined}
212
- <Position position={positionQuery.data} />
252
+ {#if positionQuery.data?.pose !== undefined && pointCloudMapQuery.data !== undefined}
253
+ <div class="h-80 w-full @2xl:h-full">
254
+ <SlamMap2D
255
+ pointcloud={pointCloudMapQuery.data}
256
+ basePose={{
257
+ // Position is returned in millimeters, but the map uses meters
258
+ x: positionQuery.data.pose.x / 1000,
259
+ y: positionQuery.data.pose.y / 1000,
260
+ theta: positionQuery.data.pose.theta,
261
+ }}
262
+ {motionPath}
263
+ destination={destination
264
+ ? new Vector2(destination.x, destination.y)
265
+ : undefined}
266
+ helpers={true}
267
+ onClick={handleClick}
268
+ />
269
+ </div>
213
270
  {/if}
214
271
  </Queries>
215
- </ApiSection>
216
-
217
- <ApiSection title="Motion">
218
- <Label>
219
- Base name
220
-
221
- <Input
222
- slot="input"
223
- bind:value={baseName}
224
- />
225
- </Label>
226
- <Label>
227
- Motion name
228
-
229
- <Input
230
- slot="input"
231
- placeholder="builtin"
232
- bind:value={motionName}
233
- />
234
- </Label>
235
- </ApiSection>
236
-
237
- <MoveOnMap
238
- {destination}
239
- {updateDestination}
240
- {moveOnMap}
241
- {stopPlan}
242
- lastError={moveOnMapMutation.error ?? stopPlanMutation.error}
243
- />
272
+ </div>
244
273
  </div>
245
-
246
- <Queries
247
- queries={[propertiesQuery, positionQuery, pointCloudMapQuery]}
248
- contentCx="p-4 h-auto"
249
- >
250
- {#if positionQuery.data?.pose !== undefined && pointCloudMapQuery.data !== undefined}
251
- <SlamMap2D
252
- pointcloud={pointCloudMapQuery.data}
253
- basePose={{
254
- // Position is returned in millimeters, but the map uses meters
255
- x: positionQuery.data.pose.x / 1000,
256
- y: positionQuery.data.pose.y / 1000,
257
- theta: positionQuery.data.pose.theta,
258
- }}
259
- {motionPath}
260
- destination={destination ? new Vector2(destination.x, destination.y) : undefined}
261
- helpers={true}
262
- onClick={handleClick}
263
- />
264
- {/if}
265
- </Queries>
266
274
  </div>
267
275
  {/if}
268
276
  </Query>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viamrobotics/test-widgets",
3
- "version": "0.11.1",
3
+ "version": "0.11.3",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "wireit": {