@viamrobotics/motion-tools 1.12.2 → 1.12.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.
@@ -17,7 +17,6 @@
17
17
  import FileDrop from './FileDrop/FileDrop.svelte'
18
18
  import { provideWeblabs } from '../hooks/useWeblabs.svelte'
19
19
  import { providePartConfig } from '../hooks/usePartConfig.svelte'
20
- import { useViamClient } from '@viamrobotics/svelte-sdk'
21
20
  import LiveUpdatesBanner from './overlay/LiveUpdatesBanner.svelte'
22
21
  import ArmPositions from './overlay/widgets/ArmPositions.svelte'
23
22
  import { provideEnvironment } from '../hooks/useEnvironment.svelte'
@@ -33,10 +32,10 @@
33
32
  import { useXR } from '@threlte/xr'
34
33
 
35
34
  interface LocalConfigProps {
36
- getLocalPartConfig: () => Struct
35
+ current: Struct
36
+ isDirty: boolean
37
+ componentToFragId: Record<string, string>
37
38
  setLocalPartConfig: (config: Struct) => void
38
- isDirty: () => boolean
39
- getComponentToFragId: () => Record<string, string>
40
39
  }
41
40
 
42
41
  interface Props {
@@ -65,7 +64,6 @@
65
64
 
66
65
  provideWorld()
67
66
 
68
- const appClient = useViamClient()
69
67
  const settings = provideSettings()
70
68
  const environment = provideEnvironment()
71
69
  const currentRobotCameraWidgets = $derived(settings.current.openCameraWidgets[partID] || [])
@@ -82,25 +80,10 @@
82
80
 
83
81
  let root = $state.raw<HTMLElement>()
84
82
 
85
- providePartConfig(() => {
86
- if (localConfigProps) {
87
- return {
88
- appEmbeddedPartConfigProps: {
89
- isDirty: () => localConfigProps.isDirty(),
90
- getLocalPartConfig: () => localConfigProps.getLocalPartConfig(),
91
- setLocalPartConfig: (config: Struct) => localConfigProps.setLocalPartConfig(config),
92
- getComponentToFragId: () => localConfigProps.getComponentToFragId(),
93
- },
94
- }
95
- } else {
96
- return {
97
- standalonePartConfigProps: {
98
- viamClient: () => appClient?.current,
99
- partID: () => partID,
100
- },
101
- }
102
- }
103
- })
83
+ providePartConfig(
84
+ () => partID,
85
+ () => localConfigProps
86
+ )
104
87
 
105
88
  $effect.pre(() => {
106
89
  if (localConfigProps) {
@@ -3,10 +3,10 @@ import type { Struct } from '@viamrobotics/sdk';
3
3
  import type { CameraPose } from '../hooks/useControls.svelte';
4
4
  import { type DrawConnectionConfig } from '../hooks/useDrawConnectionConfig.svelte';
5
5
  interface LocalConfigProps {
6
- getLocalPartConfig: () => Struct;
6
+ current: Struct;
7
+ isDirty: boolean;
8
+ componentToFragId: Record<string, string>;
7
9
  setLocalPartConfig: (config: Struct) => void;
8
- isDirty: () => boolean;
9
- getComponentToFragId: () => Record<string, string>;
10
10
  }
11
11
  interface Props {
12
12
  partID?: string;
@@ -175,7 +175,7 @@
175
175
  is={LineMaterial}
176
176
  {color}
177
177
  width={lineWidth.current ? lineWidth.current * 0.001 : 0.5}
178
- depthTest={materialProps.current?.depthTest}
178
+ depthTest={materialProps.current?.depthTest ?? true}
179
179
  />
180
180
  {:else}
181
181
  {@const currentOpacity = opacity.current ?? 0.7}
@@ -185,7 +185,7 @@
185
185
  transparent={currentOpacity < 1}
186
186
  depthWrite={currentOpacity === 1}
187
187
  opacity={currentOpacity}
188
- depthTest={materialProps.current?.depthTest}
188
+ depthTest={materialProps.current?.depthTest ?? true}
189
189
  />
190
190
 
191
191
  {#if geo && (renderMode.includes('colliders') || !model)}
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { ShaderMaterial, Vector3 } from 'three'
3
3
  import { T } from '@threlte/core'
4
- import { Grid, interactivity, PerfMonitor, PortalTarget } from '@threlte/extras'
4
+ import { Environment, Grid, interactivity, PerfMonitor, PortalTarget } from '@threlte/extras'
5
5
  import Entities from './Entities.svelte'
6
6
  import Selected from './Selected.svelte'
7
7
  import Focus from './Focus.svelte'
@@ -18,6 +18,7 @@
18
18
  import PointerMissBox from './PointerMissBox.svelte'
19
19
  import BatchedArrows from './BatchedArrows.svelte'
20
20
  import Arrows from './Arrows/ArrowGroups.svelte'
21
+ import hdrImage from '../assets/ferndale_studio_11_1k.hdr'
21
22
 
22
23
  interface Props {
23
24
  children?: Snippet
@@ -54,6 +55,8 @@
54
55
  <PerfMonitor anchorX="right" />
55
56
  {/if}
56
57
 
58
+ <Environment url={hdrImage} />
59
+
57
60
  <T.Group
58
61
  position={origin.position}
59
62
  rotation.x={$isPresenting ? -Math.PI / 2 : 0}
@@ -17,7 +17,7 @@
17
17
  if (event.key.toLowerCase() === 's') {
18
18
  event.preventDefault()
19
19
  event.stopImmediatePropagation()
20
- partConfig.saveLocalPartConfig()
20
+ partConfig.save()
21
21
  }
22
22
  }
23
23
  }}
@@ -43,7 +43,7 @@
43
43
  <Button
44
44
  class="cursor-pointer text-blue-600"
45
45
  onclick={() => {
46
- partConfig.resetLocalPartConfig()
46
+ partConfig.discardChanges()
47
47
  }}
48
48
  >
49
49
  Discard
@@ -54,7 +54,7 @@
54
54
  aria-label="Save"
55
55
  class="cursor-pointer text-blue-600"
56
56
  onclick={() => {
57
- partConfig.saveLocalPartConfig()
57
+ partConfig.save()
58
58
  }}
59
59
  >
60
60
  <div class="flex gap-2">
@@ -7,8 +7,17 @@ export declare const resource: {
7
7
  y: number;
8
8
  z: number;
9
9
  };
10
+ orientation: {
11
+ type: "ov_degrees";
12
+ value: {
13
+ x: number;
14
+ y: number;
15
+ z: number;
16
+ th: number;
17
+ };
18
+ };
10
19
  geometry: {
11
- type: string;
20
+ type: "box";
12
21
  x: number;
13
22
  y: number;
14
23
  z: number;
@@ -3,6 +3,7 @@ export const resource = {
3
3
  frame: {
4
4
  parent: 'parent_frame',
5
5
  translation: { x: 10, y: 20, z: 30 },
6
+ orientation: { type: 'ov_degrees', value: { x: 0, y: 0, z: 1, th: 0 } },
6
7
  geometry: {
7
8
  type: 'box',
8
9
  x: 10,
@@ -9,7 +9,7 @@
9
9
  </script>
10
10
 
11
11
  <div
12
- class="absolute top-2 z-4 flex w-full justify-center gap-2"
12
+ class="absolute top-2 z-4 flex w-full items-center justify-center gap-2"
13
13
  {...rest}
14
14
  >
15
15
  <!-- camera view -->
@@ -4,6 +4,7 @@ import { getContext, setContext } from 'svelte';
4
4
  import { useSettings } from './useSettings.svelte';
5
5
  import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
6
6
  import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
7
+ import { isInstanceOf } from '@threlte/core';
7
8
  const gltfLoader = new GLTFLoader();
8
9
  const dracoLoader = new DRACOLoader();
9
10
  dracoLoader.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.5.6/');
@@ -38,6 +39,16 @@ export const provide3DModels = (partID) => {
38
39
  const arrayBuffer = model.mesh.buffer.slice(model.mesh.byteOffset, model.mesh.byteOffset + model.mesh.byteLength);
39
40
  const gltfModel = await gltfLoader.parseAsync(arrayBuffer, '');
40
41
  next[prefix][id] = gltfModel.scene;
42
+ gltfModel.scene.traverse((object) => {
43
+ if (isInstanceOf(object, 'Mesh')) {
44
+ const { material } = object;
45
+ console.log(material);
46
+ if (isInstanceOf(material, 'MeshStandardMaterial')) {
47
+ material.roughness = 0.3;
48
+ material.metalness = 0.1;
49
+ }
50
+ }
51
+ });
41
52
  }
42
53
  current = next;
43
54
  }
@@ -6,7 +6,7 @@ export const provideFramelessComponents = () => {
6
6
  const partConfig = usePartConfig();
7
7
  const frames = useFrames();
8
8
  const current = $derived.by(() => {
9
- const components = partConfig.localPartConfig.toJson()?.components;
9
+ const { components } = partConfig.current;
10
10
  const partComponentsWIthNoFrame = components
11
11
  ?.filter((component) => component.frame === undefined)
12
12
  .map((component) => component.name) ?? [];
@@ -52,7 +52,7 @@ export const provideFrames = (partID) => {
52
52
  return frames;
53
53
  });
54
54
  const [configFrames, configUnsetFrameNames] = $derived.by(() => {
55
- const components = partConfig.localPartConfig.toJson().components;
55
+ const { components } = partConfig.current;
56
56
  const results = {};
57
57
  const unsetResults = [];
58
58
  for (const { name, frame } of components ?? []) {
@@ -68,7 +68,7 @@ export const provideFrames = (partID) => {
68
68
  return [results, unsetResults];
69
69
  });
70
70
  const [fragmentFrames, fragmentUnsetFrameNames] = $derived.by(() => {
71
- const { fragment_mods: fragmentMods = [] } = partConfig.localPartConfig.toJson() ?? {};
71
+ const { fragment_mods: fragmentMods = [] } = partConfig.current;
72
72
  const fragmentDefinedComponents = Object.keys(partConfig.componentNameToFragmentId);
73
73
  const results = {};
74
74
  const unsetResults = [];
@@ -1,6 +1,5 @@
1
1
  import { type Frame } from '../frame';
2
2
  import { Struct, Pose } from '@viamrobotics/sdk';
3
- import type { ViamClient } from '@viamrobotics/sdk';
4
3
  export interface PartConfig {
5
4
  components: {
6
5
  name: string;
@@ -11,31 +10,23 @@ export interface PartConfig {
11
10
  mods: any[];
12
11
  }[];
13
12
  }
14
- interface PartConfigParams {
15
- appEmbeddedPartConfigProps?: AppEmbeddedPartConfigProps;
16
- standalonePartConfigProps?: StandalonePartConfigProps;
17
- }
18
13
  interface PartConfigContext {
14
+ current: PartConfig;
15
+ isDirty: boolean;
16
+ hasEditPermissions: boolean;
17
+ componentNameToFragmentId: Record<string, string>;
19
18
  updateFrame: (componentName: string, referenceFrame: string, pose: Pose, geometry?: Frame['geometry']) => void;
20
- saveLocalPartConfig: () => void;
21
- resetLocalPartConfig: () => void;
22
19
  deleteFrame: (componentName: string) => void;
23
20
  createFrame: (componentName: string) => void;
24
- componentNameToFragmentId: Record<string, string>;
25
- localPartConfig: Struct;
26
- isDirty: boolean;
27
- hasEditPermissions: boolean;
21
+ save: () => void;
22
+ discardChanges: () => void;
28
23
  }
29
- export declare const providePartConfig: (params: () => PartConfigParams) => void;
24
+ export declare const providePartConfig: (partID: () => string, params: () => AppEmbeddedPartConfigProps | undefined) => void;
30
25
  export declare const usePartConfig: () => PartConfigContext;
31
26
  interface AppEmbeddedPartConfigProps {
32
- isDirty: () => boolean;
33
- getLocalPartConfig: () => Struct;
27
+ current: Struct;
28
+ isDirty: boolean;
29
+ componentToFragId: Record<string, string>;
34
30
  setLocalPartConfig: (config: Struct) => void;
35
- getComponentToFragId: () => Record<string, string>;
36
- }
37
- interface StandalonePartConfigProps {
38
- viamClient: () => ViamClient | undefined;
39
- partID: () => string;
40
31
  }
41
32
  export {};
@@ -1,25 +1,19 @@
1
1
  import { createFrame } from '../frame';
2
2
  import { createPoseFromFrame } from '../transform';
3
3
  import { Struct, Pose } from '@viamrobotics/sdk';
4
+ import { createAppMutation, createAppQuery } from '@viamrobotics/svelte-sdk';
4
5
  import { getContext, setContext } from 'svelte';
5
6
  const key = Symbol('part-config-context');
6
- export const providePartConfig = (params) => {
7
- const { appEmbeddedPartConfigProps, standalonePartConfigProps } = params();
8
- let _localPartConfig;
9
- if (appEmbeddedPartConfigProps) {
10
- _localPartConfig = new AppEmbeddedPartConfig(appEmbeddedPartConfigProps);
11
- }
12
- else if (standalonePartConfigProps) {
13
- _localPartConfig = new StandalonePartConfig(standalonePartConfigProps);
14
- }
15
- else {
16
- throw new Error('No part config provided');
17
- }
7
+ export const providePartConfig = (partID, params) => {
8
+ const props = $derived(params());
9
+ const config = $derived(props ? useEmbeddedPartConfig(props) : useStandalonePartConfig(partID));
10
+ const getCurrent = () => {
11
+ return (config.current.toJson?.() ?? { components: [] });
12
+ };
13
+ const current = $derived(getCurrent());
18
14
  const createFragmentFrame = (fragmentId, componentName) => {
19
- const newConfig = _localPartConfig.getLocalPartConfig().toJson();
20
- if (newConfig.fragment_mods === undefined) {
21
- newConfig.fragment_mods = [];
22
- }
15
+ const newConfig = getCurrent();
16
+ newConfig.fragment_mods ??= [];
23
17
  let fragmentMod = newConfig.fragment_mods.find((mod) => mod.fragment_id === fragmentId);
24
18
  if (fragmentMod === undefined) {
25
19
  fragmentMod = {
@@ -35,23 +29,19 @@ export const providePartConfig = (params) => {
35
29
  },
36
30
  };
37
31
  fragmentMod.mods.push(frame);
38
- const configStruct = Struct.fromJson(newConfig);
39
- _localPartConfig.setLocalPartConfig(configStruct);
32
+ config.set(newConfig);
40
33
  };
41
34
  const createPartFrame = (componentName) => {
42
- const newConfig = _localPartConfig.getLocalPartConfig().toJson();
35
+ const newConfig = getCurrent();
43
36
  const component = newConfig?.components?.find((comp) => comp.name === componentName);
44
37
  if (component) {
45
38
  component.frame = createFrame();
46
39
  }
47
- const configStruct = Struct.fromJson(newConfig);
48
- _localPartConfig.setLocalPartConfig(configStruct);
40
+ config.set(newConfig);
49
41
  };
50
42
  const updateFragmentFrame = (fragmentId, componentName, referenceFrame, framePosition, frameGeometry) => {
51
- const newConfig = _localPartConfig.getLocalPartConfig().toJson();
52
- if (newConfig.fragment_mods === undefined) {
53
- newConfig.fragment_mods = [];
54
- }
43
+ const newConfig = getCurrent();
44
+ newConfig.fragment_mods ??= [];
55
45
  let fragmentMod = newConfig.fragment_mods.find((mod) => mod.fragment_id === fragmentId);
56
46
  if (fragmentMod === undefined) {
57
47
  fragmentMod = {
@@ -99,11 +89,12 @@ export const providePartConfig = (params) => {
99
89
  else {
100
90
  fragmentMod.mods.push(frame);
101
91
  }
102
- _localPartConfig.setLocalPartConfig(Struct.fromJson(newConfig));
92
+ config.set(newConfig);
103
93
  };
104
94
  const updatePartFrame = (componentName, referenceFrame, pose, geometry) => {
105
- const newConfig = _localPartConfig.getLocalPartConfig().toJson();
106
- const component = newConfig?.components?.find((comp) => comp.name === componentName);
95
+ const newConfig = getCurrent();
96
+ const component = newConfig.components?.find(({ name }) => name === componentName);
97
+ console.log('hi', newConfig, componentName);
107
98
  if (!component) {
108
99
  return;
109
100
  }
@@ -131,24 +122,20 @@ export const providePartConfig = (params) => {
131
122
  }
132
123
  }
133
124
  }
134
- const configStruct = Struct.fromJson(newConfig);
135
- _localPartConfig.setLocalPartConfig(configStruct);
125
+ config.set(newConfig);
136
126
  };
137
127
  const deletePartFrame = (componentName) => {
138
- const newConfig = _localPartConfig.getLocalPartConfig().toJson();
128
+ const newConfig = getCurrent();
139
129
  const component = newConfig?.components?.find((comp) => comp.name === componentName);
140
130
  if (!component) {
141
131
  return;
142
132
  }
143
133
  delete component.frame;
144
- const configStruct = Struct.fromJson(newConfig);
145
- _localPartConfig.setLocalPartConfig(configStruct);
134
+ config.set(newConfig);
146
135
  };
147
136
  const deleteFragmentFrame = (fragmentId, componentName) => {
148
- const newConfig = _localPartConfig.getLocalPartConfig().toJson();
149
- if (newConfig.fragment_mods === undefined) {
150
- newConfig.fragment_mods = [];
151
- }
137
+ const newConfig = getCurrent();
138
+ newConfig.fragment_mods ??= [];
152
139
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
153
140
  let fragmentMod = newConfig.fragment_mods.find((mod) => mod.fragment_id === fragmentId);
154
141
  if (fragmentMod === undefined) {
@@ -164,12 +151,23 @@ export const providePartConfig = (params) => {
164
151
  [modUnSetPath]: '',
165
152
  },
166
153
  });
167
- const configStruct = Struct.fromJson(newConfig);
168
- _localPartConfig.setLocalPartConfig(configStruct);
154
+ config.set(newConfig);
169
155
  };
170
156
  setContext(key, {
157
+ get current() {
158
+ return current;
159
+ },
160
+ get componentNameToFragmentId() {
161
+ return config.componentNameToFragmentId;
162
+ },
163
+ get isDirty() {
164
+ return config.isDirty;
165
+ },
166
+ get hasEditPermissions() {
167
+ return config.hasEditPermissions;
168
+ },
171
169
  updateFrame: (componentName, referenceFrame, framePosition, frameGeometry) => {
172
- const fragmentId = _localPartConfig.componentNameToFragmentId()[componentName];
170
+ const fragmentId = config.componentNameToFragmentId[componentName];
173
171
  if (fragmentId !== undefined) {
174
172
  updateFragmentFrame(fragmentId, componentName, referenceFrame, framePosition, frameGeometry);
175
173
  }
@@ -178,7 +176,7 @@ export const providePartConfig = (params) => {
178
176
  }
179
177
  },
180
178
  deleteFrame: (componentName) => {
181
- const fragmentId = _localPartConfig.componentNameToFragmentId()[componentName];
179
+ const fragmentId = config.componentNameToFragmentId[componentName];
182
180
  if (fragmentId !== undefined) {
183
181
  deleteFragmentFrame(fragmentId, componentName);
184
182
  }
@@ -187,7 +185,7 @@ export const providePartConfig = (params) => {
187
185
  }
188
186
  },
189
187
  createFrame: (componentName) => {
190
- const fragmentId = _localPartConfig.componentNameToFragmentId()[componentName];
188
+ const fragmentId = config.componentNameToFragmentId[componentName];
191
189
  if (fragmentId !== undefined) {
192
190
  createFragmentFrame(fragmentId, componentName);
193
191
  }
@@ -195,137 +193,112 @@ export const providePartConfig = (params) => {
195
193
  createPartFrame(componentName);
196
194
  }
197
195
  },
198
- saveLocalPartConfig: () => {
199
- _localPartConfig.saveLocalPartConfig?.();
200
- },
201
- resetLocalPartConfig: () => {
202
- _localPartConfig.resetLocalPartConfig?.();
196
+ save: () => config.save?.(),
197
+ discardChanges: () => config.discardChanges?.(),
198
+ });
199
+ };
200
+ export const usePartConfig = () => {
201
+ return getContext(key);
202
+ };
203
+ const useEmbeddedPartConfig = (props) => {
204
+ return {
205
+ hasEditPermissions: true,
206
+ get isDirty() {
207
+ return props.isDirty;
203
208
  },
204
- get localPartConfig() {
205
- return _localPartConfig.getLocalPartConfig();
209
+ get current() {
210
+ return props.current ?? new Struct();
206
211
  },
207
212
  get componentNameToFragmentId() {
208
- return _localPartConfig.componentNameToFragmentId();
209
- },
210
- get isDirty() {
211
- return _localPartConfig.isDirty();
213
+ return props.componentToFragId;
212
214
  },
213
- get hasEditPermissions() {
214
- return _localPartConfig.hasEditPermissions();
215
+ set(config) {
216
+ const struct = Struct.fromJson(config);
217
+ return props.setLocalPartConfig(struct);
215
218
  },
216
- });
217
- };
218
- export const usePartConfig = () => {
219
- return getContext(key);
219
+ };
220
220
  };
221
- class AppEmbeddedPartConfig {
222
- _appEmbeddedPartConfigProps;
223
- constructor(appEmbeddedPartConfigProps) {
224
- this._appEmbeddedPartConfigProps = appEmbeddedPartConfigProps;
225
- }
226
- isDirty() {
227
- return this._appEmbeddedPartConfigProps.isDirty();
228
- }
229
- getLocalPartConfig() {
230
- return this._appEmbeddedPartConfigProps.getLocalPartConfig();
231
- }
232
- setLocalPartConfig(config) {
233
- return this._appEmbeddedPartConfigProps.setLocalPartConfig(config);
234
- }
235
- componentNameToFragmentId() {
236
- return this._appEmbeddedPartConfigProps.getComponentToFragId();
237
- }
238
- hasEditPermissions() {
239
- return true;
240
- }
241
- }
242
- class StandalonePartConfig {
243
- _standalonePartConfigProps;
244
- _isDirty = $state(false);
245
- _hasEditPermissions = $state(false);
246
- _networkPartConfig = $state();
247
- _localPartConfig = $state();
248
- _partName = $state();
249
- _componentNameToFragmentId = $state();
250
- constructor(standalonePartConfigProps) {
251
- this._standalonePartConfigProps = standalonePartConfigProps;
252
- $effect.pre(() => {
253
- const initLocalConfig = async () => {
254
- const partResponse = await standalonePartConfigProps
255
- .viamClient()
256
- ?.appClient.getRobotPart(standalonePartConfigProps.partID());
257
- if (JSON.parse(partResponse?.configJson ?? 'null') === null) {
258
- // no config returned here indicates this api key has no permission to update config
259
- return;
260
- }
261
- this._hasEditPermissions = true;
262
- const configJson = JSON.parse(partResponse?.configJson ?? '{}');
263
- this._networkPartConfig = Struct.fromJson(configJson);
264
- this._localPartConfig = Struct.fromJson(configJson);
265
- this._partName = partResponse?.part?.name;
266
- const componentNameToFragmentId = {};
267
- const fragmentRequests = [];
268
- if (configJson.fragments) {
269
- for (const fragmentId of configJson.fragments) {
270
- //TODO: right now the json could be just a list of strings or an object with an id prop
271
- const fragId = typeof fragmentId === 'string' ? fragmentId : fragmentId.id;
272
- fragmentRequests.push(standalonePartConfigProps.viamClient()?.appClient.getFragment(fragId));
273
- }
274
- const fragementResponses = await Promise.all(fragmentRequests);
275
- for (const fragmentResponse of fragementResponses) {
276
- const fragmentId = fragmentResponse?.id;
277
- if (!fragmentId) {
278
- continue;
279
- }
280
- const components = fragmentResponse?.fragment?.fields['components']?.kind;
281
- if (components?.case === 'listValue') {
282
- for (const component of components.value.values) {
283
- if (component.kind.case === 'structValue') {
284
- const componentName = component.kind.value.fields['name'].kind;
285
- if (componentName.case === 'stringValue') {
286
- componentNameToFragmentId[componentName.value] = fragmentId;
287
- }
288
- }
289
- }
221
+ const useStandalonePartConfig = (partID) => {
222
+ const partQuery = createAppQuery('getRobotPart', () => [partID()], {
223
+ refetchInterval: false,
224
+ });
225
+ const partName = $derived(partQuery.data?.part?.name);
226
+ const configJSON = $derived.by(() => {
227
+ if (!partQuery.data?.configJson) {
228
+ return undefined;
229
+ }
230
+ try {
231
+ return JSON.parse(partQuery.data.configJson);
232
+ }
233
+ catch {
234
+ return undefined;
235
+ }
236
+ });
237
+ let networkPartConfig = $derived(configJSON ? Struct.fromJson(configJSON) : undefined);
238
+ let current = $state.raw();
239
+ let isDirty = $state(false);
240
+ const hasEditPermissions = $derived(networkPartConfig !== undefined);
241
+ const fragmentQueries = $derived((configJSON?.fragments ?? []).map((fragmentId) => {
242
+ const id = typeof fragmentId === 'string' ? fragmentId : fragmentId.id;
243
+ return createAppQuery('getFragment', () => [id], { refetchInterval: false });
244
+ }));
245
+ const componentNameToFragmentId = $derived.by(() => {
246
+ const results = {};
247
+ for (const query of fragmentQueries) {
248
+ if (!query.data) {
249
+ continue;
250
+ }
251
+ const fragmentId = query.data.id;
252
+ const components = query.data?.fragment?.fields['components']?.kind;
253
+ if (components?.case === 'listValue') {
254
+ for (const component of components.value.values) {
255
+ if (component.kind.case === 'structValue') {
256
+ const componentName = component.kind.value.fields['name']?.kind;
257
+ if (componentName.case === 'stringValue') {
258
+ results[componentName.value] = fragmentId;
290
259
  }
291
260
  }
292
- this._componentNameToFragmentId = componentNameToFragmentId;
293
261
  }
294
- };
295
- initLocalConfig();
296
- });
297
- }
298
- getLocalPartConfig() {
299
- return this._localPartConfig ?? new Struct();
300
- }
301
- setLocalPartConfig(config) {
302
- this._localPartConfig = config;
303
- this._isDirty = true;
304
- }
305
- isDirty() {
306
- return this._isDirty;
307
- }
308
- hasEditPermissions() {
309
- return this._hasEditPermissions;
310
- }
311
- componentNameToFragmentId() {
312
- return this._componentNameToFragmentId ?? {};
313
- }
314
- async saveLocalPartConfig() {
315
- if (!this._localPartConfig || !this._partName) {
316
- return;
262
+ }
317
263
  }
318
- this._networkPartConfig = this._localPartConfig;
319
- await this._standalonePartConfigProps
320
- .viamClient()
321
- ?.appClient.updateRobotPart(this._standalonePartConfigProps.partID(), this._partName, this._localPartConfig);
322
- this._isDirty = false;
323
- }
324
- async resetLocalPartConfig() {
325
- if (!this._networkPartConfig) {
264
+ return results;
265
+ });
266
+ $effect.pre(() => {
267
+ if (!networkPartConfig) {
268
+ // no config returned here indicates this api key has no permission to update config
326
269
  return;
327
270
  }
328
- this._localPartConfig = this._networkPartConfig;
329
- this._isDirty = false;
330
- }
331
- }
271
+ current = networkPartConfig;
272
+ });
273
+ const updateRobotPartMutation = createAppMutation('updateRobotPart');
274
+ return {
275
+ get current() {
276
+ return current ?? new Struct();
277
+ },
278
+ get isDirty() {
279
+ return isDirty;
280
+ },
281
+ get hasEditPermissions() {
282
+ return hasEditPermissions;
283
+ },
284
+ get componentNameToFragmentId() {
285
+ return componentNameToFragmentId;
286
+ },
287
+ set(config) {
288
+ current = Struct.fromJson(config);
289
+ isDirty = true;
290
+ },
291
+ async save() {
292
+ if (!current || !partName) {
293
+ return;
294
+ }
295
+ networkPartConfig = current;
296
+ await updateRobotPartMutation.mutateAsync([partID(), partName, current]);
297
+ isDirty = false;
298
+ },
299
+ discardChanges() {
300
+ current = networkPartConfig;
301
+ isDirty = false;
302
+ },
303
+ };
304
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viamrobotics/motion-tools",
3
- "version": "1.12.2",
3
+ "version": "1.12.3",
4
4
  "description": "Motion visualization with Viam",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",