@viamrobotics/motion-tools 1.12.3 → 1.13.1

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 (95) hide show
  1. package/dist/FrameConfigUpdater.svelte.js +1 -1
  2. package/dist/WorldObject.svelte.js +26 -13
  3. package/dist/buf/draw/v1/service_connect.d.ts +14 -25
  4. package/dist/buf/draw/v1/service_connect.js +14 -25
  5. package/dist/buf/draw/v1/service_pb.d.ts +61 -76
  6. package/dist/buf/draw/v1/service_pb.js +58 -111
  7. package/dist/color.js +3 -3
  8. package/dist/components/App.svelte +5 -6
  9. package/dist/components/Camera.svelte +1 -7
  10. package/dist/components/Camera.svelte.d.ts +0 -1
  11. package/dist/components/CameraControls.svelte +2 -2
  12. package/dist/components/{Arrows → Entities/Arrows}/ArrowGroups.svelte +3 -3
  13. package/dist/components/{Arrows → Entities/Arrows}/Arrows.svelte +6 -6
  14. package/dist/components/{Arrows → Entities/Arrows}/Arrows.svelte.d.ts +1 -1
  15. package/dist/components/{Entities.svelte → Entities/Entities.svelte} +5 -1
  16. package/dist/components/{Frame.svelte → Entities/Frame.svelte} +8 -8
  17. package/dist/components/{GLTF.svelte → Entities/GLTF.svelte} +5 -5
  18. package/dist/components/{Geometry.svelte → Entities/Geometry.svelte} +48 -53
  19. package/dist/components/{Label.svelte → Entities/Label.svelte} +1 -1
  20. package/dist/components/{Line.svelte → Entities/Line.svelte} +2 -2
  21. package/dist/components/{Points.svelte → Entities/Points.svelte} +5 -5
  22. package/dist/components/{Pose.svelte → Entities/Pose.svelte} +3 -3
  23. package/dist/{hooks/useObjectEvents.svelte.d.ts → components/Entities/hooks/useEntityEvents.svelte.d.ts} +1 -1
  24. package/dist/{hooks/useObjectEvents.svelte.js → components/Entities/hooks/useEntityEvents.svelte.js} +6 -6
  25. package/dist/components/FileDrop/file-names.js +6 -3
  26. package/dist/components/FileDrop/snapshot-dropper.js +8 -4
  27. package/dist/components/FileDrop/useFileDrop.svelte.js +9 -6
  28. package/dist/components/Lasso/Lasso.svelte +4 -4
  29. package/dist/components/PCD.svelte +14 -6
  30. package/dist/components/PCD.svelte.d.ts +2 -0
  31. package/dist/components/Scene.svelte +1 -3
  32. package/dist/components/SceneProviders.svelte +2 -0
  33. package/dist/components/Selected.svelte +9 -9
  34. package/dist/components/StaticGeometries.svelte +6 -3
  35. package/dist/components/overlay/AddRelationship.svelte +1 -1
  36. package/dist/components/overlay/Details.svelte +3 -3
  37. package/dist/components/overlay/FloatingPanel.svelte +3 -0
  38. package/dist/components/overlay/FloatingPanel.svelte.d.ts +1 -0
  39. package/dist/components/overlay/LiveUpdatesBanner.svelte +4 -6
  40. package/dist/components/overlay/Logs.svelte +75 -0
  41. package/dist/components/overlay/left-pane/TreeContainer.svelte +0 -3
  42. package/dist/components/overlay/settings/Settings.svelte +11 -13
  43. package/dist/components/overlay/widgets/Camera.svelte +11 -9
  44. package/dist/components/xr/ArmTeleop.svelte +33 -33
  45. package/dist/components/xr/CameraFeed.svelte +21 -23
  46. package/dist/components/xr/JointLimitsWidget.svelte +19 -5
  47. package/dist/components/xr/XRConfigPanel.svelte +17 -16
  48. package/dist/components/xr/XRControllerSettings.svelte +5 -4
  49. package/dist/components/xr/XRToast.svelte +11 -6
  50. package/dist/ecs/relations.d.ts +1 -0
  51. package/dist/ecs/relations.js +1 -0
  52. package/dist/ecs/useQuery.svelte.js +3 -3
  53. package/dist/format.js +1 -1
  54. package/dist/hooks/use3DModels.svelte.js +35 -36
  55. package/dist/hooks/useConfigFrames.svelte.d.ts +9 -0
  56. package/dist/hooks/useConfigFrames.svelte.js +87 -0
  57. package/dist/hooks/useDrawAPI.svelte.js +1 -1
  58. package/dist/hooks/useFramelessComponents.svelte.js +1 -1
  59. package/dist/hooks/useFrames.svelte.d.ts +1 -6
  60. package/dist/hooks/useFrames.svelte.js +93 -192
  61. package/dist/hooks/useGeometries.svelte.js +1 -1
  62. package/dist/hooks/useLinked.svelte.js +5 -4
  63. package/dist/hooks/usePartConfig.svelte.js +17 -18
  64. package/dist/hooks/usePointcloudObjects.svelte.js +4 -4
  65. package/dist/hooks/usePointclouds.svelte.js +2 -4
  66. package/dist/hooks/usePose.svelte.js +3 -7
  67. package/dist/hooks/useResizable.svelte.js +4 -3
  68. package/dist/hooks/useSettings.svelte.js +2 -2
  69. package/dist/hooks/useVisibility.svelte.js +0 -12
  70. package/dist/hooks/useWeblabs.svelte.js +3 -2
  71. package/dist/hooks/useWorldState.svelte.js +6 -3
  72. package/dist/loaders/pcd/index.js +2 -1
  73. package/dist/loaders/pcd/worker.inline.d.ts +1 -1
  74. package/dist/loaders/pcd/worker.inline.js +1 -1
  75. package/dist/loaders/pcd/worker.js +1 -1
  76. package/dist/snapshot.js +7 -5
  77. package/dist/three/InstancedArrows/InstancedArrows.js +1 -1
  78. package/dist/three/InstancedArrows/box.js +1 -1
  79. package/dist/three/InstancedArrows/raycast.js +12 -12
  80. package/package.json +19 -11
  81. package/dist/components/overlay/left-pane/Logs.svelte +0 -52
  82. /package/dist/components/{Arrows → Entities/Arrows}/ArrowGroups.svelte.d.ts +0 -0
  83. /package/dist/components/{Entities.svelte.d.ts → Entities/Entities.svelte.d.ts} +0 -0
  84. /package/dist/components/{Frame.svelte.d.ts → Entities/Frame.svelte.d.ts} +0 -0
  85. /package/dist/components/{GLTF.svelte.d.ts → Entities/GLTF.svelte.d.ts} +0 -0
  86. /package/dist/components/{Geometry.svelte.d.ts → Entities/Geometry.svelte.d.ts} +0 -0
  87. /package/dist/components/{Label.svelte.d.ts → Entities/Label.svelte.d.ts} +0 -0
  88. /package/dist/components/{Line.svelte.d.ts → Entities/Line.svelte.d.ts} +0 -0
  89. /package/dist/components/{LineDots.svelte → Entities/LineDots.svelte} +0 -0
  90. /package/dist/components/{LineDots.svelte.d.ts → Entities/LineDots.svelte.d.ts} +0 -0
  91. /package/dist/components/{LineGeometry.svelte → Entities/LineGeometry.svelte} +0 -0
  92. /package/dist/components/{LineGeometry.svelte.d.ts → Entities/LineGeometry.svelte.d.ts} +0 -0
  93. /package/dist/components/{Points.svelte.d.ts → Entities/Points.svelte.d.ts} +0 -0
  94. /package/dist/components/{Pose.svelte.d.ts → Entities/Pose.svelte.d.ts} +0 -0
  95. /package/dist/components/overlay/{left-pane/Logs.svelte.d.ts → Logs.svelte.d.ts} +0 -0
@@ -2,12 +2,12 @@
2
2
  import { Points, PointsMaterial, OrthographicCamera } from 'three'
3
3
  import { T, useTask, useThrelte } from '@threlte/core'
4
4
  import { Portal } from '@threlte/extras'
5
- import { useObjectEvents } from '../hooks/useObjectEvents.svelte'
6
- import { poseToObject3d } from '../transform'
7
- import { useSettings } from '../hooks/useSettings.svelte'
5
+ import { useEntityEvents } from './hooks/useEntityEvents.svelte'
6
+ import { poseToObject3d } from '../../transform'
7
+ import { useSettings } from '../../hooks/useSettings.svelte'
8
8
  import type { Snippet } from 'svelte'
9
9
  import type { Entity } from 'koota'
10
- import { traits, useTrait } from '../ecs'
10
+ import { traits, useTrait } from '../../ecs'
11
11
 
12
12
  interface Props {
13
13
  entity: Entity
@@ -93,7 +93,7 @@
93
93
  }
94
94
  })
95
95
 
96
- const events = useObjectEvents(() => entity)
96
+ const events = useEntityEvents(() => entity)
97
97
 
98
98
  const { start, stop } = useTask(
99
99
  () => {
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
- import { traits, useTrait } from '../ecs'
3
- import { usePose } from '../hooks/usePose.svelte'
4
- import { matrixToPose, poseToMatrix } from '../transform'
2
+ import { traits, useTrait } from '../../ecs'
3
+ import { usePose } from '../../hooks/usePose.svelte'
4
+ import { matrixToPose, poseToMatrix } from '../../transform'
5
5
  import type { Pose } from '@viamrobotics/sdk'
6
6
  import type { Entity } from 'koota'
7
7
  import type { Snippet } from 'svelte'
@@ -1,6 +1,6 @@
1
1
  import { type IntersectionEvent } from '@threlte/extras';
2
2
  import type { Entity } from 'koota';
3
- export declare const useObjectEvents: (entity: () => Entity | undefined) => {
3
+ export declare const useEntityEvents: (entity: () => Entity | undefined) => {
4
4
  readonly visible: boolean;
5
5
  onpointerenter: (event: IntersectionEvent<MouseEvent>) => void;
6
6
  onpointermove: (event: IntersectionEvent<MouseEvent>) => void;
@@ -1,11 +1,11 @@
1
1
  import { useCursor } from '@threlte/extras';
2
- import { useFocusedEntity, useSelectedEntity } from './useSelection.svelte';
3
- import { useVisibility } from './useVisibility.svelte';
2
+ import { useFocusedEntity, useSelectedEntity } from '../../../hooks/useSelection.svelte';
3
+ import { useVisibility } from '../../../hooks/useVisibility.svelte';
4
4
  import { Vector2 } from 'three';
5
- import { traits } from '../ecs';
6
- import { updateHoverInfo } from '../HoverUpdater.svelte';
7
- import { createPose, matrixToPose, poseToMatrix } from '../transform';
8
- export const useObjectEvents = (entity) => {
5
+ import { traits } from '../../../ecs';
6
+ import { updateHoverInfo } from '../../../HoverUpdater.svelte';
7
+ import { createPose, matrixToPose, poseToMatrix } from '../../../transform';
8
+ export const useEntityEvents = (entity) => {
9
9
  const down = new Vector2();
10
10
  const selectedEntity = useSelectedEntity();
11
11
  const focusedEntity = useFocusedEntity();
@@ -26,13 +26,14 @@ const hasPrefix = (name) => {
26
26
  };
27
27
  const validatePrefix = (extension, prefix) => {
28
28
  switch (prefix) {
29
- case Prefixes.Snapshot:
29
+ case Prefixes.Snapshot: {
30
30
  if (extension !== Extensions.JSON &&
31
31
  extension !== Extensions.PB &&
32
32
  extension !== Extensions.PB_GZ) {
33
33
  return new FileNameError(`Only ${Extensions.JSON}, ${Extensions.PB} and ${Extensions.PB_GZ} snapshot files are supported.`);
34
34
  }
35
35
  break;
36
+ }
36
37
  }
37
38
  return undefined;
38
39
  };
@@ -76,12 +77,14 @@ export const readFile = (file, reader, extension) => {
76
77
  if (!extension)
77
78
  return;
78
79
  switch (extension) {
79
- case Extensions.JSON:
80
+ case Extensions.JSON: {
80
81
  return reader.readAsText(file);
82
+ }
81
83
  case Extensions.PCD:
82
84
  case Extensions.PLY:
83
85
  case Extensions.PB:
84
- case Extensions.PB_GZ:
86
+ case Extensions.PB_GZ: {
85
87
  return reader.readAsArrayBuffer(file);
88
+ }
86
89
  }
87
90
  };
@@ -81,16 +81,20 @@ const decodeGzip = async (params) => {
81
81
  };
82
82
  export const snapshotDropper = async (params) => {
83
83
  switch (params.extension) {
84
- case 'json':
84
+ case 'json': {
85
85
  return decodeJson(params);
86
- case 'pb':
86
+ }
87
+ case 'pb': {
87
88
  return decodeBinary(params);
88
- case 'pb.gz':
89
+ }
90
+ case 'pb.gz': {
89
91
  return decodeGzip(params);
90
- default:
92
+ }
93
+ default: {
91
94
  return {
92
95
  success: false,
93
96
  error: new FileDropperError(`Only ${Extensions.JSON}, ${Extensions.PB} and ${Extensions.PB_GZ} snapshot files are supported.`),
94
97
  };
98
+ }
95
99
  }
96
100
  };
@@ -4,14 +4,17 @@ import { plyDropper } from './ply-dropper';
4
4
  import { snapshotDropper } from './snapshot-dropper';
5
5
  const createFileDropper = (extension, prefix) => {
6
6
  switch (prefix) {
7
- case Prefixes.Snapshot:
7
+ case Prefixes.Snapshot: {
8
8
  return snapshotDropper;
9
+ }
9
10
  }
10
11
  switch (extension) {
11
- case Extensions.PCD:
12
+ case Extensions.PCD: {
12
13
  return pcdDropper;
13
- case Extensions.PLY:
14
+ }
15
+ case Extensions.PLY: {
14
16
  return plyDropper;
17
+ }
15
18
  }
16
19
  return undefined;
17
20
  };
@@ -74,11 +77,11 @@ export const useFileDrop = (onSuccess, onError) => {
74
77
  prefix,
75
78
  content,
76
79
  });
77
- if (!result.success) {
78
- handleError(result.error.message);
80
+ if (result.success) {
81
+ onSuccess(result);
79
82
  }
80
83
  else {
81
- onSuccess(result);
84
+ handleError(result.error.message);
82
85
  }
83
86
  });
84
87
  readFile(file, reader, extension);
@@ -245,16 +245,16 @@
245
245
  }
246
246
 
247
247
  $effect(() => {
248
- window.addEventListener('keydown', onkeydown)
249
- window.addEventListener('keyup', onkeyup)
248
+ globalThis.addEventListener('keydown', onkeydown)
249
+ globalThis.addEventListener('keyup', onkeyup)
250
250
  dom.addEventListener('pointerdown', onpointerdown)
251
251
  dom.addEventListener('pointermove', onpointermove)
252
252
  dom.addEventListener('pointerup', onpointerup)
253
253
  dom.addEventListener('pointerleave', onpointerleave)
254
254
 
255
255
  return () => {
256
- window.removeEventListener('keydown', onkeydown)
257
- window.removeEventListener('keyup', onkeyup)
256
+ globalThis.removeEventListener('keydown', onkeydown)
257
+ globalThis.removeEventListener('keyup', onkeyup)
258
258
  dom.removeEventListener('pointerdown', onpointerdown)
259
259
  dom.removeEventListener('pointermove', onpointermove)
260
260
  dom.removeEventListener('pointerup', onpointerup)
@@ -2,13 +2,15 @@
2
2
  import { parsePcdInWorker } from '../lib'
3
3
  import { traits, useWorld } from '../ecs'
4
4
  import { createBufferGeometry } from '../attribute'
5
- import type { Entity } from 'koota'
5
+ import type { ConfigurableTrait, Entity } from 'koota'
6
6
 
7
7
  interface Props {
8
8
  data: Uint8Array
9
+ name?: string
10
+ renderOrder?: number
9
11
  }
10
12
 
11
- let { data }: Props = $props()
13
+ let { data, name, renderOrder }: Props = $props()
12
14
 
13
15
  const world = useWorld()
14
16
 
@@ -18,11 +20,17 @@
18
20
  parsePcdInWorker(data).then(({ positions, colors }) => {
19
21
  const geometry = createBufferGeometry(positions, colors)
20
22
 
21
- entity = world.spawn(
22
- traits.Name('Random points'),
23
+ const entityTraits: ConfigurableTrait[] = [
24
+ traits.Name(name ?? 'Random points'),
23
25
  traits.Points,
24
- traits.BufferGeometry(geometry)
25
- )
26
+ traits.BufferGeometry(geometry),
27
+ ]
28
+
29
+ if (renderOrder) {
30
+ entityTraits.push(traits.RenderOrder(renderOrder))
31
+ }
32
+
33
+ entity = world.spawn(...entityTraits)
26
34
  })
27
35
 
28
36
  return () => {
@@ -1,5 +1,7 @@
1
1
  interface Props {
2
2
  data: Uint8Array;
3
+ name?: string;
4
+ renderOrder?: number;
3
5
  }
4
6
  declare const PCD: import("svelte").Component<Props, {}, "">;
5
7
  type PCD = ReturnType<typeof PCD>;
@@ -2,7 +2,7 @@
2
2
  import { ShaderMaterial, Vector3 } from 'three'
3
3
  import { T } from '@threlte/core'
4
4
  import { Environment, Grid, interactivity, PerfMonitor, PortalTarget } from '@threlte/extras'
5
- import Entities from './Entities.svelte'
5
+ import Entities from './Entities/Entities.svelte'
6
6
  import Selected from './Selected.svelte'
7
7
  import Focus from './Focus.svelte'
8
8
  import StaticGeometries from './StaticGeometries.svelte'
@@ -17,7 +17,6 @@
17
17
  import MeasureTool from './MeasureTool/MeasureTool.svelte'
18
18
  import PointerMissBox from './PointerMissBox.svelte'
19
19
  import BatchedArrows from './BatchedArrows.svelte'
20
- import Arrows from './Arrows/ArrowGroups.svelte'
21
20
  import hdrImage from '../assets/ferndale_studio_11_1k.hdr'
22
21
 
23
22
  interface Props {
@@ -103,7 +102,6 @@
103
102
 
104
103
  <Entities />
105
104
  <BatchedArrows />
106
- <Arrows />
107
105
  </T.Group>
108
106
 
109
107
  {@render children?.()}
@@ -23,6 +23,7 @@
23
23
  import { provide3DModels } from '../hooks/use3DModels.svelte'
24
24
  import { providePointcloudObjects } from '../hooks/usePointcloudObjects.svelte'
25
25
  import { provideLinkedEntities } from '../hooks/useLinked.svelte'
26
+ import { provideConfigFrames } from '../hooks/useConfigFrames.svelte'
26
27
 
27
28
  interface Props {
28
29
  cameraPose?: CameraPose
@@ -43,6 +44,7 @@
43
44
  provideDrawAPI()
44
45
 
45
46
  provideResourceByName(() => partID.current)
47
+ provideConfigFrames()
46
48
  provideFrames(() => partID.current)
47
49
  provideGeometries(() => partID.current)
48
50
  provide3DModels(() => partID.current)
@@ -15,19 +15,17 @@
15
15
  const selectedObject3d = useSelectedObject3d()
16
16
 
17
17
  const object = $derived.by(() => {
18
- if (!isInstanceOf(selectedObject3d.current, 'Mesh')) {
19
- return selectedObject3d.current
18
+ if (!selectedObject3d.current) {
19
+ return
20
20
  }
21
21
 
22
22
  // Create a clone in the case of meshes, which could be frames with geometries,
23
23
  // so that our bounding box doesn't include children
24
- const result = selectedObject3d.current?.clone(false)
25
-
26
- if (result) {
27
- selectedObject3d.current?.getWorldPosition(result.position)
28
- selectedObject3d.current?.getWorldQuaternion(result.quaternion)
29
- return result
24
+ if (isInstanceOf(selectedObject3d.current, 'Mesh')) {
25
+ return selectedObject3d.current?.clone(false)
30
26
  }
27
+
28
+ return selectedObject3d.current
31
29
  })
32
30
 
33
31
  const { start, stop } = useTask(
@@ -44,7 +42,9 @@
44
42
  mesh.getBoundingBoxAt(selectedEntity.instance, box3)
45
43
  obb.fromBox3(box3)
46
44
  obbHelper.setFromOBB(obb)
47
- } else {
45
+ } else if (isInstanceOf(selectedObject3d.current, 'Mesh')) {
46
+ selectedObject3d.current?.getWorldPosition(object.position)
47
+ selectedObject3d.current?.getWorldQuaternion(object.quaternion)
48
48
  obbHelper.setFromObject(object)
49
49
  }
50
50
 
@@ -12,7 +12,7 @@
12
12
  import { PressedKeys } from 'runed'
13
13
  import { quaternionToPose, vector3ToPose } from '../transform'
14
14
  import { Quaternion, Vector3 } from 'three'
15
- import Frame from './Frame.svelte'
15
+ import Frame from './Entities/Frame.svelte'
16
16
  import { useSettings } from '../hooks/useSettings.svelte'
17
17
  import { useWorld, traits } from '../ecs'
18
18
  import type { Entity } from 'koota'
@@ -39,7 +39,7 @@
39
39
  const entity = world.spawn(
40
40
  traits.Name(`custom geometry ${++index}`),
41
41
  traits.Pose,
42
- traits.Box({ x: 0.1, y: 0.1, z: 0.1 }),
42
+ traits.Box({ x: 100, y: 100, z: 100 }),
43
43
  traits.Removable
44
44
  )
45
45
 
@@ -87,7 +87,10 @@
87
87
  ref.quaternion.copy(quaternion)
88
88
  entity.set(traits.Pose, pose)
89
89
  } else if (box && mode === 'scale') {
90
- entity.set(traits.Box, ref.scale)
90
+ box.x *= ref.scale.x
91
+ box.y *= ref.scale.y
92
+ box.z *= ref.scale.z
93
+ entity.set(traits.Box, box)
91
94
  ref.scale.setScalar(1)
92
95
  }
93
96
  }}
@@ -17,7 +17,7 @@
17
17
  return allEntities.current
18
18
  .map((e: Entity) => e.get(traits.Name))
19
19
  .filter((n: string | undefined): n is string => n !== undefined && n !== currentEntityName)
20
- .sort()
20
+ .toSorted()
21
21
  })
22
22
 
23
23
  let showRelationshipOptions = $state(false)
@@ -21,7 +21,7 @@
21
21
  useFocusedObject3d,
22
22
  useSelectedObject3d,
23
23
  } from '../../hooks/useSelection.svelte'
24
- import { useFrames } from '../../hooks/useFrames.svelte'
24
+ import { useConfigFrames } from '../../hooks/useConfigFrames.svelte'
25
25
  import { usePartConfig } from '../../hooks/usePartConfig.svelte'
26
26
  import { FrameConfigUpdater } from '../../FrameConfigUpdater.svelte'
27
27
  import { useEnvironment } from '../../hooks/useEnvironment.svelte'
@@ -37,7 +37,7 @@
37
37
  const world = useWorld()
38
38
  const controls = useCameraControls()
39
39
  const resourceByName = useResourceByName()
40
- const frames = useFrames()
40
+ const configFrames = useConfigFrames()
41
41
  const partConfig = usePartConfig()
42
42
  const selectedEntity = useSelectedEntity()
43
43
  const selectedObject3d = useSelectedObject3d()
@@ -402,7 +402,7 @@
402
402
  {@render ParentFrame({
403
403
  ariaLabel: 'parent frame name',
404
404
  value: parent.current ?? 'world',
405
- options: frames.getParentFrameOptions(name.current ?? ''),
405
+ options: configFrames.getParentFrameOptions(name.current ?? ''),
406
406
  onChange: (value) => {
407
407
  detailConfigUpdater.setFrameParent(entity, value)
408
408
  },
@@ -10,6 +10,7 @@
10
10
  defaultSize?: { width: number; height: number }
11
11
  defaultPosition?: { x: number; y: number }
12
12
  exitable?: boolean
13
+ persistRect?: boolean
13
14
  strategy?: 'absolute' | 'fixed'
14
15
  isOpen?: boolean
15
16
  children: Snippet
@@ -19,6 +20,7 @@
19
20
  title = '',
20
21
  defaultSize = { width: 700, height: 500 },
21
22
  exitable = true,
23
+ persistRect = true,
22
24
  isOpen = $bindable(false),
23
25
  children,
24
26
  ...props
@@ -30,6 +32,7 @@
30
32
  defaultSize,
31
33
  resizable: false,
32
34
  allowOverflow: false,
35
+ persistRect,
33
36
  open: isOpen,
34
37
  ...props,
35
38
  }))
@@ -10,6 +10,7 @@ interface Props {
10
10
  y: number;
11
11
  };
12
12
  exitable?: boolean;
13
+ persistRect?: boolean;
13
14
  strategy?: 'absolute' | 'fixed';
14
15
  isOpen?: boolean;
15
16
  children: Snippet;
@@ -13,12 +13,10 @@
13
13
 
14
14
  <svelte:window
15
15
  onkeydown={(event) => {
16
- if (event.metaKey) {
17
- if (event.key.toLowerCase() === 's') {
18
- event.preventDefault()
19
- event.stopImmediatePropagation()
20
- partConfig.save()
21
- }
16
+ if (event.metaKey && event.key.toLowerCase() === 's') {
17
+ event.preventDefault()
18
+ event.stopImmediatePropagation()
19
+ partConfig.save()
22
20
  }
23
21
  }}
24
22
  />
@@ -0,0 +1,75 @@
1
+ <script lang="ts">
2
+ import { Portal } from '@threlte/extras'
3
+ import { useLogs } from '../../hooks/useLogs.svelte'
4
+ import FloatingPanel from './FloatingPanel.svelte'
5
+ import DashboardButton from './dashboard/Button.svelte'
6
+ import { PersistedState } from 'runed'
7
+
8
+ const logs = useLogs()
9
+
10
+ const isOpen = new PersistedState('logs-is-open', false)
11
+ </script>
12
+
13
+ <Portal id="dashboard">
14
+ <fieldset class="relative">
15
+ <DashboardButton
16
+ active
17
+ icon="article"
18
+ description="Logs"
19
+ onclick={() => {
20
+ isOpen.current = !isOpen.current
21
+ }}
22
+ />
23
+ {#if logs.warnings.length > 0}
24
+ <span
25
+ class="absolute z-4 -mt-1.5 -ml-1.5 h-4 w-4 rounded-full bg-yellow-700 text-center text-[10px] text-white"
26
+ >
27
+ {logs.warnings.length}
28
+ </span>
29
+ {/if}
30
+
31
+ {#if logs.errors.length > 0}
32
+ <span
33
+ class="absolute z-4 -mt-1.5 -ml-1.5 h-4 rounded-full bg-red-700 px-1.25 text-center text-[10px] text-white"
34
+ >
35
+ {logs.errors.length}
36
+ </span>
37
+ {/if}
38
+ </fieldset>
39
+ </Portal>
40
+
41
+ <FloatingPanel
42
+ title="Logs"
43
+ bind:isOpen={isOpen.current}
44
+ defaultSize={{ width: 240, height: 315 }}
45
+ >
46
+ <div class="flex h-70 flex-col gap-2 overflow-auto p-3 text-xs">
47
+ {#each logs.current as log (log.uuid)}
48
+ <div>
49
+ <div class="flex flex-wrap items-center gap-1.5">
50
+ <div
51
+ class={[
52
+ 'h-2 w-2 rounded-full',
53
+ {
54
+ 'bg-danger-dark': log.level === 'error',
55
+ 'bg-amber-300': log.level === 'warn',
56
+ 'bg-blue-400': log.level === 'info',
57
+ },
58
+ ]}
59
+ ></div>
60
+ <div class="text-subtle-2">{log.timestamp}</div>
61
+ </div>
62
+ <div>
63
+ {#if log.count > 1}
64
+ <span class="mr-1 rounded bg-green-700 px-1 py-0.5 text-xs text-white">
65
+ {log.count}
66
+ </span>
67
+ {/if}
68
+ {log.message}
69
+ </div>
70
+ </div>
71
+ {:else}
72
+ No logs
73
+ {/each}
74
+ </div>
75
+ </FloatingPanel>
@@ -3,7 +3,6 @@
3
3
  import Tree from './Tree.svelte'
4
4
  import { useSelectedEntity } from '../../../hooks/useSelection.svelte'
5
5
  import { provideTreeExpandedContext } from './useExpanded.svelte'
6
- import Logs from './Logs.svelte'
7
6
  import AddFrames from './AddFrames.svelte'
8
7
  import { useEnvironment } from '../../../hooks/useEnvironment.svelte'
9
8
  import { usePartID } from '../../../hooks/usePartID.svelte'
@@ -82,6 +81,4 @@
82
81
  {#if environment.current.isStandalone && partID.current && partConfig.hasEditPermissions}
83
82
  <AddFrames />
84
83
  {/if}
85
-
86
- <Logs />
87
84
  </div>
@@ -285,19 +285,17 @@
285
285
  <Switch
286
286
  on={isWidgetOpen}
287
287
  on:change={(event) => {
288
- if (event.detail) {
289
- settings.current.openCameraWidgets = {
290
- ...settings.current.openCameraWidgets,
291
- [partID.current]: [...currentRobotCameraWidgets, camera.name],
292
- }
293
- } else {
294
- settings.current.openCameraWidgets = {
295
- ...settings.current.openCameraWidgets,
296
- [partID.current]: currentRobotCameraWidgets.filter(
297
- (widget) => widget !== camera.name
298
- ),
299
- }
300
- }
288
+ settings.current.openCameraWidgets = event.detail
289
+ ? {
290
+ ...settings.current.openCameraWidgets,
291
+ [partID.current]: [...currentRobotCameraWidgets, camera.name],
292
+ }
293
+ : {
294
+ ...settings.current.openCameraWidgets,
295
+ [partID.current]: currentRobotCameraWidgets.filter(
296
+ (widget) => widget !== camera.name
297
+ ),
298
+ }
301
299
  }}
302
300
  />
303
301
  </div>
@@ -31,12 +31,14 @@
31
31
  let fpsInterval: ReturnType<typeof setInterval> | undefined
32
32
  let fpsCounterActive = false
33
33
 
34
+ const cleanup = () => {
35
+ if (fpsInterval) clearInterval(fpsInterval)
36
+ fpsCounterActive = false
37
+ }
38
+
34
39
  // Cleanup on destroy
35
40
  $effect(() => {
36
- return () => {
37
- if (fpsInterval) clearInterval(fpsInterval)
38
- fpsCounterActive = false
39
- }
41
+ return cleanup
40
42
  })
41
43
 
42
44
  const onMediaLoad = (e: Event) => {
@@ -89,8 +91,8 @@
89
91
  resolutions = options.map((opt) => ({ width: opt.width, height: opt.height }))
90
92
  isLoading = false
91
93
  })
92
- .catch((e) => {
93
- error = e instanceof Error ? e.message : 'Failed to get stream options'
94
+ .catch((error_) => {
95
+ error = error_ instanceof Error ? error_.message : 'Failed to get stream options'
94
96
  isLoading = false
95
97
  })
96
98
  }
@@ -101,13 +103,13 @@
101
103
  if (!target.value || !streamClient) return
102
104
 
103
105
  const [w, h] = target.value.split('x').map(Number)
104
- if (isNaN(w) || isNaN(h)) return
106
+ if (Number.isNaN(w) || Number.isNaN(h)) return
105
107
 
106
108
  try {
107
109
  await streamClient.setOptions(name, w, h)
108
110
  error = undefined
109
- } catch (err) {
110
- error = err instanceof Error ? err.message : 'Failed to set resolution'
111
+ } catch (error_) {
112
+ error = error_ instanceof Error ? error_.message : 'Failed to set resolution'
111
113
  }
112
114
  }
113
115
  </script>