@viamrobotics/motion-tools 1.34.3 → 1.34.5

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.
@@ -30,7 +30,6 @@
30
30
  import HoveredEntities from './hover/HoveredEntities.svelte'
31
31
  import AddFrames from './overlay/AddFrames.svelte'
32
32
  import LiveUpdatesBanner from './overlay/LiveUpdatesBanner.svelte'
33
- import Logs from './overlay/Logs.svelte'
34
33
  import ArmPositions from './overlay/widgets/ArmPositions.svelte'
35
34
  import Camera from './overlay/widgets/Camera.svelte'
36
35
  import FramePov from './overlay/widgets/FramePov.svelte'
@@ -173,7 +172,6 @@
173
172
  <PortalTarget id="dom" />
174
173
 
175
174
  <Settings {settingsTabs} />
176
- <Logs />
177
175
  <AddFrames />
178
176
  </div>
179
177
  </SceneProviders>
@@ -13,7 +13,6 @@
13
13
  import { provideGeometries } from '../hooks/useGeometries.svelte'
14
14
  import { provideInheritedInvisible } from '../hooks/useInheritedInvisible.svelte'
15
15
  import { provideLinkedEntities } from '../hooks/useLinked.svelte'
16
- import { provideLogs } from '../hooks/useLogs.svelte'
17
16
  import { usePartID } from '../hooks/usePartID.svelte'
18
17
  import { providePointcloudObjects } from '../hooks/usePointcloudObjects.svelte'
19
18
  import { providePointclouds } from '../hooks/usePointclouds.svelte'
@@ -30,7 +29,6 @@
30
29
  const partID = usePartID()
31
30
 
32
31
  provideTransformControls()
33
- provideLogs()
34
32
 
35
33
  provideHierarchy()
36
34
  provideWorldMatrix()
@@ -5,11 +5,11 @@ import { getContext, setContext, untrack } from 'svelte';
5
5
  import { Matrix4 } from 'three';
6
6
  import { resourceNameToColor, subtypeToColor } from '../color';
7
7
  import { hierarchy, traits, useWorld } from '../ecs';
8
+ import { useLogs } from '../plugins';
8
9
  import { createPose, isPoseEqual, poseToMatrix } from '../transform';
9
10
  import { useConfigFrames } from './useConfigFrames.svelte';
10
11
  import { useEnvironment } from './useEnvironment.svelte';
11
12
  import { useFrameEditSession } from './useFrameEditSession.svelte';
12
- import { useLogs } from './useLogs.svelte';
13
13
  import { usePartConfig } from './usePartConfig.svelte';
14
14
  import { useResourceByName } from './useResourceByName.svelte';
15
15
  const key = Symbol('frames-context');
@@ -7,9 +7,9 @@ import { resourceColors } from '../color';
7
7
  import { RefetchRates } from '../components/overlay/RefreshRate.svelte';
8
8
  import { hierarchy, traits, useWorld } from '../ecs';
9
9
  import { updateGeometryTrait } from '../ecs/traits';
10
+ import { useLogs } from '../plugins';
10
11
  import { createPose, poseToMatrix } from '../transform';
11
12
  import { useEnvironment } from './useEnvironment.svelte';
12
- import { useLogs } from './useLogs.svelte';
13
13
  import { useResourceByName } from './useResourceByName.svelte';
14
14
  import { RefreshRates, useSettings } from './useSettings.svelte';
15
15
  const key = Symbol('geometries-context');
@@ -6,9 +6,9 @@ import { ColorFormat } from '../buf/draw/v1/metadata_pb';
6
6
  import { RefetchRates } from '../components/overlay/RefreshRate.svelte';
7
7
  import { hierarchy, traits, useWorld } from '../ecs';
8
8
  import { parsePcdInWorker } from '../lib';
9
+ import { useLogs } from '../plugins';
9
10
  import { createPose } from '../transform';
10
11
  import { useEnvironment } from './useEnvironment.svelte';
11
- import { useLogs } from './useLogs.svelte';
12
12
  import { RefreshRates, useSettings } from './useSettings.svelte';
13
13
  const key = Symbol('pointcloud-object-context');
14
14
  export const providePointcloudObjects = (partID) => {
@@ -6,8 +6,8 @@ import { ColorFormat } from '../buf/draw/v1/metadata_pb';
6
6
  import { RefetchRates } from '../components/overlay/RefreshRate.svelte';
7
7
  import { hierarchy, traits, useWorld } from '../ecs';
8
8
  import { parsePcdInWorker } from '../loaders/pcd';
9
+ import { useLogs } from '../plugins';
9
10
  import { useEnvironment } from './useEnvironment.svelte';
10
- import { useLogs } from './useLogs.svelte';
11
11
  import { RefreshRates, useSettings } from './useSettings.svelte';
12
12
  const key = Symbol('pointcloud-context');
13
13
  export const providePointclouds = (partID) => {
@@ -2,9 +2,9 @@ import { commonApi, MachineConnectionEvent, Pose } from '@viamrobotics/sdk';
2
2
  import { createRobotQuery, useConnectionStatus, useRobotClient } from '@viamrobotics/svelte-sdk';
3
3
  import { untrack } from 'svelte';
4
4
  import { RefetchRates } from '../components/overlay/RefreshRate.svelte';
5
+ import { useLogs } from '../plugins';
5
6
  import { useEnvironment } from './useEnvironment.svelte';
6
7
  import { useFrames } from './useFrames.svelte';
7
- import { useLogs } from './useLogs.svelte';
8
8
  import { usePartID } from './usePartID.svelte';
9
9
  import { useRefetchPoses } from './useRefetchPoses';
10
10
  import { useResourceByName } from './useResourceByName.svelte';
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  /** @deprecated MotionTools has been renamed to Visualizer. This export will be removed in v2. */
2
2
  export { default as MotionTools } from './components/App.svelte';
3
3
  export { default as Visualizer } from './components/App.svelte';
4
+ export { useSettings } from './hooks/useSettings.svelte';
4
5
  export { default as PCD } from './components/PCD.svelte';
5
6
  export * as relations from './ecs/relations';
6
7
  export * as traits from './ecs/traits';
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  /** @deprecated MotionTools has been renamed to Visualizer. This export will be removed in v2. */
2
2
  export { default as MotionTools } from './components/App.svelte';
3
3
  export { default as Visualizer } from './components/App.svelte';
4
+ export { useSettings } from './hooks/useSettings.svelte';
4
5
  // Plugins
5
6
  export { default as PCD } from './components/PCD.svelte';
6
7
  // ECS
@@ -11,7 +11,7 @@ import { asRGB, STRIDE } from '../../buffer';
11
11
  import { hierarchy, traits, useWorld } from '../../ecs';
12
12
  import { createBox, createCapsule, createSphere } from '../../geometry';
13
13
  import { useCameraControls } from '../../hooks/useControls.svelte';
14
- import { useLogs } from '../../hooks/useLogs.svelte';
14
+ import { useLogs } from '..';
15
15
  import { parsePlyInput } from '../../ply';
16
16
  import { createPose, createPoseFromFrame, poseToMatrix } from '../../transform';
17
17
  import { useDrawConnectionConfig } from './useDrawConnectionConfig.svelte';
@@ -0,0 +1,146 @@
1
+ <script lang="ts">
2
+ import { Portal } from '@threlte/extras'
3
+ import { PersistedState } from 'runed'
4
+
5
+ import DashboardButton from '../../components/overlay/dashboard/Button.svelte'
6
+ import FloatingPanel from '../../components/overlay/FloatingPanel.svelte'
7
+
8
+ import { provideLogs } from './useLogs.svelte'
9
+
10
+ const logs = provideLogs()
11
+
12
+ const isOpen = new PersistedState('logs-is-open', false)
13
+
14
+ let levels = new PersistedState('logs-selected-levels', {
15
+ info: true,
16
+ warn: true,
17
+ error: true,
18
+ })
19
+ </script>
20
+
21
+ <Portal id="dashboard">
22
+ <fieldset class="relative">
23
+ <DashboardButton
24
+ active={isOpen.current}
25
+ icon="article"
26
+ description="Logs"
27
+ onclick={() => {
28
+ isOpen.current = !isOpen.current
29
+ }}
30
+ />
31
+ {#if logs.warnings.length > 0}
32
+ <span
33
+ 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"
34
+ >
35
+ {logs.warnings.length}
36
+ </span>
37
+ {/if}
38
+
39
+ {#if logs.errors.length > 0}
40
+ <span
41
+ 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"
42
+ >
43
+ {logs.errors.length}
44
+ </span>
45
+ {/if}
46
+ </fieldset>
47
+ </Portal>
48
+
49
+ <Portal id="dom">
50
+ <FloatingPanel
51
+ title="Logs"
52
+ bind:isOpen={isOpen.current}
53
+ defaultSize={{ width: 240, height: 315 }}
54
+ resizable
55
+ >
56
+ <div class="flex h-full flex-col">
57
+ <div class="flex gap-1 px-3 py-2">
58
+ <button
59
+ type="button"
60
+ class={[
61
+ 'chip border px-2',
62
+ {
63
+ 'border-danger-dark bg-danger-dark text-white hover:border-red-700 hover:bg-red-700':
64
+ levels.current.error,
65
+ 'bg-light hover:bg-ghost-light hover:border-light border-light text-subtle-1':
66
+ !levels.current.error,
67
+ },
68
+ ]}
69
+ onclick={() => {
70
+ levels.current.error = !levels.current.error
71
+ }}
72
+ >
73
+ error
74
+ </button>
75
+
76
+ <button
77
+ type="button"
78
+ class={[
79
+ 'chip border',
80
+ {
81
+ 'border-amber-400 bg-amber-400 text-white hover:border-amber-500 hover:bg-amber-500':
82
+ levels.current.warn,
83
+ 'bg-light hover:bg-ghost-light hover:border-light border-light text-subtle-1':
84
+ !levels.current.warn,
85
+ },
86
+ ]}
87
+ onclick={() => {
88
+ levels.current.warn = !levels.current.warn
89
+ }}
90
+ >
91
+ warn
92
+ </button>
93
+
94
+ <button
95
+ type="button"
96
+ class={[
97
+ 'chip border',
98
+ {
99
+ 'border-blue-400 bg-blue-400 text-white hover:border-blue-500 hover:bg-blue-500':
100
+ levels.current.info,
101
+ 'bg-light hover:bg-ghost-light hover:border-light border-light text-subtle-1':
102
+ !levels.current.info,
103
+ },
104
+ ]}
105
+ onclick={() => {
106
+ levels.current.info = !levels.current.info
107
+ }}
108
+ >
109
+ info
110
+ </button>
111
+ </div>
112
+
113
+ <div class="flex flex-col gap-2 overflow-auto px-3 pb-3 text-xs">
114
+ {#each logs.current as log (log.uuid)}
115
+ {#if levels.current[log.level]}
116
+ <div>
117
+ <div class="flex flex-wrap items-center gap-1.5">
118
+ <div
119
+ class={[
120
+ 'h-2 w-2 rounded-full',
121
+ {
122
+ 'bg-danger-dark': log.level === 'error',
123
+ 'bg-amber-300': log.level === 'warn',
124
+ 'bg-blue-400': log.level === 'info',
125
+ },
126
+ ]}
127
+ ></div>
128
+ <div class="text-subtle-2">{log.timestamp}</div>
129
+ </div>
130
+ <div>
131
+ {#if log.count > 1}
132
+ <span class="mr-1 rounded bg-green-700 px-1 py-0.5 text-xs text-white">
133
+ {log.count}
134
+ </span>
135
+ {/if}
136
+ {log.message}
137
+ </div>
138
+ </div>
139
+ {/if}
140
+ {:else}
141
+ No logs
142
+ {/each}
143
+ </div>
144
+ </div>
145
+ </FloatingPanel>
146
+ </Portal>
@@ -12,6 +12,6 @@ interface Context {
12
12
  warnings: Log[];
13
13
  add(message: string, level?: Level): void;
14
14
  }
15
- export declare const provideLogs: () => void;
15
+ export declare const provideLogs: () => Context;
16
16
  export declare const useLogs: () => Context;
17
17
  export {};
@@ -1,7 +1,8 @@
1
- import { getContext, setContext, untrack } from 'svelte';
1
+ import { untrack } from 'svelte';
2
2
  import { MathUtils } from 'three';
3
- const key = Symbol('logs-context');
4
3
  const MAX_LOGS = 200;
4
+ // Logs is a singleton. We only have one logger per app and we need to access it anywhere.
5
+ let context;
5
6
  export const provideLogs = () => {
6
7
  // Plain insertion-ordered Map keyed by `${level}|${timestamp}|${message}`
7
8
  // drives storage; a single `$state` version counter drives reactivity.
@@ -23,7 +24,7 @@ export const provideLogs = () => {
23
24
  });
24
25
  const errors = $derived(all.filter((l) => l.level === 'error'));
25
26
  const warnings = $derived(all.filter((l) => l.level === 'warn'));
26
- setContext(key, {
27
+ context = {
27
28
  get current() {
28
29
  return all;
29
30
  },
@@ -58,8 +59,15 @@ export const provideLogs = () => {
58
59
  version++;
59
60
  });
60
61
  },
61
- });
62
+ };
63
+ return context;
62
64
  };
63
65
  export const useLogs = () => {
64
- return getContext(key);
66
+ // return a no-op context if the plugin isn't installed
67
+ return (context ?? {
68
+ current: [],
69
+ errors: [],
70
+ warnings: [],
71
+ add: () => undefined,
72
+ });
65
73
  };
@@ -16,11 +16,12 @@
16
16
  import { getTriangleBoxesFromIndices, getTriangleFromIndex, raycast } from './utils'
17
17
 
18
18
  interface Props {
19
- active?: boolean
19
+ enabled?: boolean
20
+ selecting?: boolean
20
21
  debug?: boolean
21
22
  }
22
23
 
23
- let { active = false, debug = false }: Props = $props()
24
+ let { enabled = false, selecting = false, debug = false }: Props = $props()
24
25
 
25
26
  const world = useWorld()
26
27
  const controls = useCameraControls()
@@ -37,7 +38,7 @@
37
38
  let drawing = false
38
39
 
39
40
  const onpointerdown = (event: PointerEvent) => {
40
- if (!event.shiftKey || !active) return
41
+ if (!selecting && !event.shiftKey) return
41
42
 
42
43
  const { x, y } = raycast(event, camera.current)
43
44
 
@@ -61,7 +62,7 @@
61
62
  }
62
63
 
63
64
  const onpointermove = (event: PointerEvent) => {
64
- if (!drawing || !active) return
65
+ if (!drawing) return
65
66
 
66
67
  let ellipse = world.query(selectionTraits.Ellipse).at(-1)
67
68
 
@@ -129,13 +130,13 @@
129
130
  }
130
131
 
131
132
  const onpointerleave = () => {
132
- if (!drawing || !active) return
133
+ if (!drawing) return
133
134
 
134
135
  onpointerup()
135
136
  }
136
137
 
137
138
  const onpointerup = () => {
138
- if (!drawing || !active) return
139
+ if (!drawing) return
139
140
 
140
141
  drawing = false
141
142
 
@@ -244,6 +245,8 @@
244
245
  }
245
246
 
246
247
  $effect(() => {
248
+ if (!enabled) return
249
+
247
250
  globalThis.addEventListener('keydown', onkeydown)
248
251
  globalThis.addEventListener('keyup', onkeyup)
249
252
  dom.addEventListener('pointerdown', onpointerdown)
@@ -263,26 +266,6 @@
263
266
 
264
267
  const ellipses = useQuery(selectionTraits.Ellipse)
265
268
 
266
- $effect(() => {
267
- if (!controls.current) return
268
-
269
- const currentControls = controls.current
270
-
271
- if ('minPolarAngle' in currentControls) {
272
- const { minPolarAngle, maxPolarAngle } = currentControls
273
-
274
- // Locks the camera to top down while this component is mounted
275
- currentControls.polarAngle = 0
276
- currentControls.minPolarAngle = 0
277
- currentControls.maxPolarAngle = 0
278
-
279
- return () => {
280
- currentControls.minPolarAngle = minPolarAngle
281
- currentControls.maxPolarAngle = maxPolarAngle
282
- }
283
- }
284
- })
285
-
286
269
  // On unmount, destroy all lasso related entities
287
270
  $effect(() => {
288
271
  return () => {
@@ -1,5 +1,6 @@
1
1
  interface Props {
2
- active?: boolean;
2
+ enabled?: boolean;
3
+ selecting?: boolean;
3
4
  debug?: boolean;
4
5
  }
5
6
  declare const Ellipse: import("svelte").Component<Props, {}, "">;
@@ -16,11 +16,12 @@
16
16
  import { getTriangleBoxesFromIndices, getTriangleFromIndex, raycast } from './utils'
17
17
 
18
18
  interface Props {
19
- active?: boolean
19
+ enabled?: boolean
20
+ selecting?: boolean
20
21
  debug?: boolean
21
22
  }
22
23
 
23
- let { active = false, debug = false }: Props = $props()
24
+ let { enabled = false, selecting = false, debug = false }: Props = $props()
24
25
 
25
26
  const world = useWorld()
26
27
  const controls = useCameraControls()
@@ -37,7 +38,7 @@
37
38
  let drawing = false
38
39
 
39
40
  const onpointerdown = (event: PointerEvent) => {
40
- if (!event.shiftKey || !active) return
41
+ if (!selecting && !event.shiftKey) return
41
42
 
42
43
  const { x, y } = raycast(event, camera.current)
43
44
 
@@ -60,7 +61,7 @@
60
61
  }
61
62
 
62
63
  const onpointermove = (event: PointerEvent) => {
63
- if (!drawing || !active) return
64
+ if (!drawing) return
64
65
 
65
66
  let lasso = world.query(selectionTraits.Lasso).at(-1)
66
67
 
@@ -100,13 +101,13 @@
100
101
  }
101
102
 
102
103
  const onpointerleave = () => {
103
- if (!drawing || !active) return
104
+ if (!drawing) return
104
105
 
105
106
  onpointerup()
106
107
  }
107
108
 
108
109
  const onpointerup = () => {
109
- if (!drawing || !active) return
110
+ if (!drawing) return
110
111
 
111
112
  drawing = false
112
113
 
@@ -225,6 +226,8 @@
225
226
  }
226
227
 
227
228
  $effect(() => {
229
+ if (!enabled) return
230
+
228
231
  globalThis.addEventListener('keydown', onkeydown)
229
232
  globalThis.addEventListener('keyup', onkeyup)
230
233
  dom.addEventListener('pointerdown', onpointerdown)
@@ -244,26 +247,6 @@
244
247
 
245
248
  const lassos = useQuery(selectionTraits.Lasso)
246
249
 
247
- $effect(() => {
248
- if (!controls.current) return
249
-
250
- const currentControls = controls.current
251
-
252
- if ('minPolarAngle' in currentControls) {
253
- const { minPolarAngle, maxPolarAngle } = currentControls
254
-
255
- // Locks the camera to top down while this component is mounted
256
- currentControls.polarAngle = 0
257
- currentControls.minPolarAngle = 0
258
- currentControls.maxPolarAngle = 0
259
-
260
- return () => {
261
- currentControls.minPolarAngle = minPolarAngle
262
- currentControls.maxPolarAngle = maxPolarAngle
263
- }
264
- }
265
- })
266
-
267
250
  // On unmount, destroy all lasso related entities
268
251
  $effect(() => {
269
252
  return () => {
@@ -1,5 +1,6 @@
1
1
  interface Props {
2
- active?: boolean;
2
+ enabled?: boolean;
3
+ selecting?: boolean;
3
4
  debug?: boolean;
4
5
  }
5
6
  declare const Lasso: import("svelte").Component<Props, {}, "">;
@@ -19,6 +19,10 @@
19
19
  interface Props {
20
20
  /** Whether to auto-enable lasso mode when the component mounts */
21
21
  enabled?: boolean
22
+
23
+ /** Allow manually going into selection state */
24
+ selecting?: boolean
25
+
22
26
  // TODO: remove once a Selected trait exists
23
27
  autoSelectNewEntities?: boolean
24
28
  children?: Snippet
@@ -26,7 +30,12 @@
26
30
 
27
31
  type SelectionType = 'lasso' | 'ellipse'
28
32
 
29
- let { enabled = false, autoSelectNewEntities = false, children }: Props = $props()
33
+ let {
34
+ enabled = false,
35
+ selecting = false,
36
+ autoSelectNewEntities = false,
37
+ children,
38
+ }: Props = $props()
30
39
 
31
40
  const { dom } = useThrelte()
32
41
  const world = useWorld()
@@ -113,7 +122,13 @@
113
122
  </Portal>
114
123
 
115
124
  {#if isSelectionMode && rect.height > 0 && rect.width > 0}
116
- <Ellipse active={selectionType === 'ellipse'} />
117
- <Lasso active={selectionType === 'lasso'} />
125
+ <Ellipse
126
+ enabled={selectionType === 'ellipse'}
127
+ {selecting}
128
+ />
129
+ <Lasso
130
+ enabled={selectionType === 'lasso'}
131
+ {selecting}
132
+ />
118
133
  {@render children?.()}
119
134
  {/if}
@@ -2,6 +2,8 @@ import type { Snippet } from 'svelte';
2
2
  interface Props {
3
3
  /** Whether to auto-enable lasso mode when the component mounts */
4
4
  enabled?: boolean;
5
+ /** Allow manually going into selection state */
6
+ selecting?: boolean;
5
7
  autoSelectNewEntities?: boolean;
6
8
  children?: Snippet;
7
9
  }
@@ -0,0 +1,25 @@
1
+ <script lang="ts">
2
+ import { useCameraControls } from '../../hooks/useControls.svelte'
3
+
4
+ const controls = useCameraControls()
5
+
6
+ $effect(() => {
7
+ if (!controls.current) return
8
+
9
+ const currentControls = controls.current
10
+
11
+ if ('minPolarAngle' in currentControls) {
12
+ const { minPolarAngle, maxPolarAngle } = currentControls
13
+
14
+ // Locks the camera to top down while this component is mounted
15
+ currentControls.polarAngle = 0
16
+ currentControls.minPolarAngle = 0
17
+ currentControls.maxPolarAngle = 0
18
+
19
+ return () => {
20
+ currentControls.minPolarAngle = minPolarAngle
21
+ currentControls.maxPolarAngle = maxPolarAngle
22
+ }
23
+ }
24
+ })
25
+ </script>
@@ -0,0 +1,3 @@
1
+ declare const TopDownLock: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type TopDownLock = ReturnType<typeof TopDownLock>;
3
+ export default TopDownLock;
@@ -3,9 +3,12 @@ export * as selectionTraits from './Selection/traits';
3
3
  export * as selectionRelations from './Selection/relations';
4
4
  export { useSelectionPlugin } from './Selection/useSelectionPlugin.svelte';
5
5
  export { default as MeasureTool } from './MeasureTool/MeasureTool.svelte';
6
+ export { default as TopDownLock } from './TopDownLock/TopDownLock.svelte';
6
7
  export { default as DrawService } from './DrawService/DrawService.svelte';
7
8
  export { default as Skybox } from './Skybox/Skybox.svelte';
8
9
  export { default as Debug } from './Debug/Debug.svelte';
10
+ export { default as Logs } from './Logs/Logs.svelte';
11
+ export { useLogs } from './Logs/useLogs.svelte';
9
12
  export { default as Focus } from './Focus/Focus.svelte';
10
13
  export { default as LLMSceneBuilder } from './LLMSceneBuilder/LLMSceneBuilder.svelte';
11
14
  export type { InferCallback, ComponentFrameInfo } from './LLMSceneBuilder/useSceneBuilder.svelte';
@@ -4,14 +4,20 @@ export * as selectionTraits from './Selection/traits';
4
4
  export * as selectionRelations from './Selection/relations';
5
5
  export { useSelectionPlugin } from './Selection/useSelectionPlugin.svelte';
6
6
  export { default as MeasureTool } from './MeasureTool/MeasureTool.svelte';
7
+ export { default as TopDownLock } from './TopDownLock/TopDownLock.svelte';
7
8
  // DrawService
8
9
  export { default as DrawService } from './DrawService/DrawService.svelte';
9
10
  // Skybox
10
11
  export { default as Skybox } from './Skybox/Skybox.svelte';
11
12
  // Debug
12
13
  export { default as Debug } from './Debug/Debug.svelte';
14
+ // Logs
15
+ export { default as Logs } from './Logs/Logs.svelte';
16
+ export { useLogs } from './Logs/useLogs.svelte';
17
+ // Focus
13
18
  export { default as Focus } from './Focus/Focus.svelte';
14
19
  // LLMSceneBuilder
15
20
  export { default as LLMSceneBuilder } from './LLMSceneBuilder/LLMSceneBuilder.svelte';
21
+ // XR
16
22
  export { default as XR } from './XR/XR.svelte';
17
23
  export { default as XRSettings } from './XR/XRSettings.svelte';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viamrobotics/motion-tools",
3
- "version": "1.34.3",
3
+ "version": "1.34.5",
4
4
  "description": "Motion visualization with Viam",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -30,7 +30,7 @@
30
30
  "@testing-library/svelte": "5.2.8",
31
31
  "@testing-library/user-event": "^14.6.1",
32
32
  "@threlte/core": "8.5.14",
33
- "@threlte/extras": "9.17.0",
33
+ "@threlte/extras": "9.21.0",
34
34
  "@threlte/rapier": "3.4.1",
35
35
  "@threlte/xr": "1.6.0",
36
36
  "@types/bun": "1.2.21",
@@ -1,144 +0,0 @@
1
- <script lang="ts">
2
- import { Portal } from '@threlte/extras'
3
- import { PersistedState } from 'runed'
4
-
5
- import { useLogs } from '../../hooks/useLogs.svelte'
6
-
7
- import DashboardButton from './dashboard/Button.svelte'
8
- import FloatingPanel from './FloatingPanel.svelte'
9
-
10
- const logs = useLogs()
11
-
12
- const isOpen = new PersistedState('logs-is-open', false)
13
-
14
- let levels = new PersistedState('logs-selected-levels', {
15
- info: true,
16
- warn: true,
17
- error: true,
18
- })
19
- </script>
20
-
21
- <Portal id="dashboard">
22
- <fieldset class="relative">
23
- <DashboardButton
24
- active={isOpen.current}
25
- icon="article"
26
- description="Logs"
27
- onclick={() => {
28
- isOpen.current = !isOpen.current
29
- }}
30
- />
31
- {#if logs.warnings.length > 0}
32
- <span
33
- 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"
34
- >
35
- {logs.warnings.length}
36
- </span>
37
- {/if}
38
-
39
- {#if logs.errors.length > 0}
40
- <span
41
- 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"
42
- >
43
- {logs.errors.length}
44
- </span>
45
- {/if}
46
- </fieldset>
47
- </Portal>
48
-
49
- <FloatingPanel
50
- title="Logs"
51
- bind:isOpen={isOpen.current}
52
- defaultSize={{ width: 240, height: 315 }}
53
- resizable
54
- >
55
- <div class="flex h-full flex-col">
56
- <div class="flex gap-1 px-3 py-2">
57
- <button
58
- type="button"
59
- class={[
60
- 'chip border px-2',
61
- {
62
- 'border-danger-dark bg-danger-dark text-white hover:border-red-700 hover:bg-red-700':
63
- levels.current.error,
64
- 'bg-light hover:bg-ghost-light hover:border-light border-light text-subtle-1':
65
- !levels.current.error,
66
- },
67
- ]}
68
- onclick={() => {
69
- levels.current.error = !levels.current.error
70
- }}
71
- >
72
- error
73
- </button>
74
-
75
- <button
76
- type="button"
77
- class={[
78
- 'chip border',
79
- {
80
- 'border-amber-400 bg-amber-400 text-white hover:border-amber-500 hover:bg-amber-500':
81
- levels.current.warn,
82
- 'bg-light hover:bg-ghost-light hover:border-light border-light text-subtle-1':
83
- !levels.current.warn,
84
- },
85
- ]}
86
- onclick={() => {
87
- levels.current.warn = !levels.current.warn
88
- }}
89
- >
90
- warn
91
- </button>
92
-
93
- <button
94
- type="button"
95
- class={[
96
- 'chip border',
97
- {
98
- 'border-blue-400 bg-blue-400 text-white hover:border-blue-500 hover:bg-blue-500':
99
- levels.current.info,
100
- 'bg-light hover:bg-ghost-light hover:border-light border-light text-subtle-1':
101
- !levels.current.info,
102
- },
103
- ]}
104
- onclick={() => {
105
- levels.current.info = !levels.current.info
106
- }}
107
- >
108
- info
109
- </button>
110
- </div>
111
-
112
- <div class="flex flex-col gap-2 overflow-auto px-3 pb-3 text-xs">
113
- {#each logs.current as log (log.uuid)}
114
- {#if levels.current[log.level]}
115
- <div>
116
- <div class="flex flex-wrap items-center gap-1.5">
117
- <div
118
- class={[
119
- 'h-2 w-2 rounded-full',
120
- {
121
- 'bg-danger-dark': log.level === 'error',
122
- 'bg-amber-300': log.level === 'warn',
123
- 'bg-blue-400': log.level === 'info',
124
- },
125
- ]}
126
- ></div>
127
- <div class="text-subtle-2">{log.timestamp}</div>
128
- </div>
129
- <div>
130
- {#if log.count > 1}
131
- <span class="mr-1 rounded bg-green-700 px-1 py-0.5 text-xs text-white">
132
- {log.count}
133
- </span>
134
- {/if}
135
- {log.message}
136
- </div>
137
- </div>
138
- {/if}
139
- {:else}
140
- No logs
141
- {/each}
142
- </div>
143
- </div>
144
- </FloatingPanel>