@pascal-app/editor 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/package.json +13 -9
  2. package/src/components/editor/bottom-sheet.tsx +149 -0
  3. package/src/components/editor/custom-camera-controls.tsx +74 -5
  4. package/src/components/editor/editor-layout-mobile.tsx +264 -0
  5. package/src/components/editor/editor-layout-v2.tsx +24 -3
  6. package/src/components/editor/first-person/build-collider-world.ts +363 -0
  7. package/src/components/editor/first-person/bvh-ecctrl.tsx +860 -0
  8. package/src/components/editor/first-person-controls.tsx +496 -143
  9. package/src/components/editor/floating-action-menu.tsx +32 -55
  10. package/src/components/editor/floorplan-background-selection.ts +113 -0
  11. package/src/components/editor/floorplan-panel.tsx +9861 -3297
  12. package/src/components/editor/index.tsx +295 -32
  13. package/src/components/editor/selection-manager.tsx +575 -13
  14. package/src/components/editor/snapshot-capture-overlay.tsx +465 -0
  15. package/src/components/editor/thumbnail-generator.tsx +56 -68
  16. package/src/components/editor/use-floorplan-background-placement.ts +257 -0
  17. package/src/components/editor/use-floorplan-hit-testing.ts +171 -0
  18. package/src/components/editor/use-floorplan-scene-data.ts +189 -0
  19. package/src/components/editor/wall-measurement-label.tsx +267 -36
  20. package/src/components/editor-2d/floorplan-action-menu-layer.tsx +95 -0
  21. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +160 -0
  22. package/src/components/editor-2d/floorplan-hotkey-handlers.tsx +92 -0
  23. package/src/components/editor-2d/renderers/floorplan-draft-layer.tsx +124 -0
  24. package/src/components/editor-2d/renderers/floorplan-marquee-layer.tsx +58 -0
  25. package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +202 -0
  26. package/src/components/editor-2d/renderers/floorplan-roof-layer.tsx +113 -0
  27. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +474 -0
  28. package/src/components/editor-2d/svg-paths.ts +119 -0
  29. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +10 -12
  30. package/src/components/systems/roof/roof-edit-system.tsx +1 -1
  31. package/src/components/systems/stair/stair-edit-system.tsx +1 -1
  32. package/src/components/systems/zone/zone-label-editor-system.tsx +0 -0
  33. package/src/components/systems/zone/zone-system.tsx +0 -0
  34. package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +1 -0
  35. package/src/components/tools/ceiling/ceiling-hole-editor.tsx +1 -0
  36. package/src/components/tools/ceiling/ceiling-tool.tsx +5 -5
  37. package/src/components/tools/ceiling/move-ceiling-tool.tsx +9 -2
  38. package/src/components/tools/column/column-tool.tsx +97 -0
  39. package/src/components/tools/column/move-column-tool.tsx +105 -0
  40. package/src/components/tools/door/door-tool.tsx +7 -0
  41. package/src/components/tools/door/move-door-tool.tsx +28 -8
  42. package/src/components/tools/fence/curve-fence-tool.tsx +4 -5
  43. package/src/components/tools/fence/fence-drafting.ts +10 -3
  44. package/src/components/tools/fence/fence-tool.tsx +160 -4
  45. package/src/components/tools/fence/move-fence-endpoint-tool.tsx +139 -25
  46. package/src/components/tools/fence/move-fence-tool.tsx +111 -40
  47. package/src/components/tools/item/move-tool.tsx +7 -1
  48. package/src/components/tools/item/placement-math.ts +32 -5
  49. package/src/components/tools/item/placement-strategies.ts +110 -31
  50. package/src/components/tools/item/placement-types.ts +7 -0
  51. package/src/components/tools/item/use-draft-node.ts +1 -0
  52. package/src/components/tools/item/use-placement-coordinator.tsx +558 -52
  53. package/src/components/tools/roof/move-roof-tool.tsx +29 -17
  54. package/src/components/tools/select/box-select-tool.tsx +12 -17
  55. package/src/components/tools/shared/polygon-editor.tsx +153 -28
  56. package/src/components/tools/shared/segment-angle.ts +156 -0
  57. package/src/components/tools/slab/slab-boundary-editor.tsx +1 -0
  58. package/src/components/tools/slab/slab-hole-editor.tsx +1 -0
  59. package/src/components/tools/spawn/move-spawn-tool.tsx +101 -0
  60. package/src/components/tools/spawn/spawn-tool.tsx +130 -0
  61. package/src/components/tools/tool-manager.tsx +20 -5
  62. package/src/components/tools/wall/curve-wall-tool.tsx +8 -6
  63. package/src/components/tools/wall/move-wall-endpoint-tool.tsx +131 -27
  64. package/src/components/tools/wall/move-wall-tool.tsx +6 -4
  65. package/src/components/tools/wall/wall-drafting.ts +18 -9
  66. package/src/components/tools/wall/wall-tool.tsx +136 -4
  67. package/src/components/tools/window/move-window-tool.tsx +18 -0
  68. package/src/components/tools/window/window-tool.tsx +5 -0
  69. package/src/components/tools/zone/zone-tool.tsx +20 -5
  70. package/src/components/ui/action-menu/camera-actions.tsx +37 -33
  71. package/src/components/ui/action-menu/control-modes.tsx +34 -1
  72. package/src/components/ui/action-menu/furnish-tools.tsx +6 -92
  73. package/src/components/ui/action-menu/index.tsx +98 -59
  74. package/src/components/ui/action-menu/structure-tools.tsx +2 -0
  75. package/src/components/ui/action-menu/view-toggles.tsx +418 -41
  76. package/src/components/ui/command-palette/editor-commands.tsx +24 -5
  77. package/src/components/ui/command-palette/index.tsx +4 -255
  78. package/src/components/ui/controls/material-picker.tsx +154 -164
  79. package/src/components/ui/controls/slider-control.tsx +66 -18
  80. package/src/components/ui/floating-level-selector.tsx +286 -55
  81. package/src/components/ui/helpers/helper-manager.tsx +10 -0
  82. package/src/components/ui/item-catalog/catalog-items.tsx +2563 -1239
  83. package/src/components/ui/item-catalog/item-catalog.tsx +96 -187
  84. package/src/components/ui/level-duplicate-dialog.tsx +113 -0
  85. package/src/components/ui/panels/ceiling-panel.tsx +3 -28
  86. package/src/components/ui/panels/column-panel.tsx +759 -0
  87. package/src/components/ui/panels/door-panel.tsx +989 -290
  88. package/src/components/ui/panels/fence-panel.tsx +2 -49
  89. package/src/components/ui/panels/mobile-panel-sheet.tsx +108 -0
  90. package/src/components/ui/panels/mobile-selection-bar.tsx +100 -0
  91. package/src/components/ui/panels/node-display.ts +39 -0
  92. package/src/components/ui/panels/paint-panel.tsx +163 -0
  93. package/src/components/ui/panels/panel-manager.tsx +208 -28
  94. package/src/components/ui/panels/panel-wrapper.tsx +48 -39
  95. package/src/components/ui/panels/reference-panel.tsx +253 -5
  96. package/src/components/ui/panels/roof-panel.tsx +13 -64
  97. package/src/components/ui/panels/roof-segment-panel.tsx +0 -25
  98. package/src/components/ui/panels/slab-panel.tsx +4 -30
  99. package/src/components/ui/panels/spawn-panel.tsx +161 -0
  100. package/src/components/ui/panels/stair-panel.tsx +20 -74
  101. package/src/components/ui/panels/stair-segment-panel.tsx +0 -25
  102. package/src/components/ui/panels/wall-panel.tsx +10 -8
  103. package/src/components/ui/panels/window-panel.tsx +668 -139
  104. package/src/components/ui/primitives/number-input.tsx +1 -1
  105. package/src/components/ui/primitives/sidebar.tsx +0 -0
  106. package/src/components/ui/sidebar/app-sidebar.tsx +0 -0
  107. package/src/components/ui/sidebar/icon-rail.tsx +0 -0
  108. package/src/components/ui/sidebar/mobile-tab-bar.tsx +46 -0
  109. package/src/components/ui/sidebar/panels/items-panel/index.tsx +330 -0
  110. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +0 -0
  111. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +2 -2
  112. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +2 -2
  113. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +0 -0
  114. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +77 -0
  115. package/src/components/ui/sidebar/panels/site-panel/index.tsx +105 -22
  116. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +2 -2
  117. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +0 -0
  118. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +76 -0
  119. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +11 -3
  120. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +10 -5
  121. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +1 -1
  122. package/src/components/ui/sidebar/tab-bar.tsx +3 -0
  123. package/src/components/ui/slider.tsx +1 -1
  124. package/src/components/viewer-overlay.tsx +0 -0
  125. package/src/components/viewer-zone-system.tsx +0 -0
  126. package/src/hooks/use-auto-frame.ts +45 -0
  127. package/src/hooks/use-auto-save.ts +14 -0
  128. package/src/hooks/use-keyboard.ts +74 -7
  129. package/src/hooks/use-mobile.ts +12 -12
  130. package/src/index.tsx +8 -1
  131. package/src/lib/door-interaction.ts +88 -0
  132. package/src/lib/floorplan/geometry.ts +263 -0
  133. package/src/lib/floorplan/index.ts +38 -0
  134. package/src/lib/floorplan/items.ts +179 -0
  135. package/src/lib/floorplan/selection-tool.ts +231 -0
  136. package/src/lib/floorplan/stairs.ts +478 -0
  137. package/src/lib/floorplan/types.ts +57 -0
  138. package/src/lib/floorplan/walls.ts +23 -0
  139. package/src/lib/guide-events.ts +10 -0
  140. package/src/lib/level-duplication.test.ts +70 -0
  141. package/src/lib/level-duplication.ts +153 -0
  142. package/src/lib/local-guide-image.ts +42 -0
  143. package/src/lib/material-paint.ts +284 -0
  144. package/src/lib/roof-duplication.ts +214 -0
  145. package/src/lib/scene-bounds.test.ts +183 -0
  146. package/src/lib/scene-bounds.ts +169 -0
  147. package/src/lib/scene.ts +0 -0
  148. package/src/lib/sfx-bus.ts +2 -0
  149. package/src/lib/sfx-player.ts +5 -5
  150. package/src/lib/stair-duplication.ts +126 -0
  151. package/src/lib/window-interaction.ts +86 -0
  152. package/src/store/use-editor.tsx +186 -62
  153. package/tsconfig.json +2 -1
  154. package/src/components/feedback-dialog.tsx +0 -265
  155. package/src/components/pascal-radio.tsx +0 -280
  156. package/src/components/preview-button.tsx +0 -16
  157. package/src/components/ui/viewer-toolbar.tsx +0 -395
@@ -23,6 +23,7 @@ import {
23
23
  Moon,
24
24
  MousePointer2,
25
25
  Package,
26
+ PaintBucket,
26
27
  PencilLine,
27
28
  Plus,
28
29
  Redo2,
@@ -34,8 +35,8 @@ import {
34
35
  Video,
35
36
  } from 'lucide-react'
36
37
  import { useEffect } from 'react'
37
- import { deleteLevelWithFallbackSelection } from '../../../lib/level-selection'
38
38
  import { runRedo, runUndo } from '../../../lib/history'
39
+ import { deleteLevelWithFallbackSelection } from '../../../lib/level-selection'
39
40
  import { useCommandRegistry } from '../../../store/use-command-registry'
40
41
  import type { StructureTool } from '../../../store/use-editor'
41
42
  import useEditor from '../../../store/use-editor'
@@ -49,6 +50,7 @@ export function EditorCommands() {
49
50
  const setMode = useEditor((s) => s.setMode)
50
51
  const setTool = useEditor((s) => s.setTool)
51
52
  const setStructureLayer = useEditor((s) => s.setStructureLayer)
53
+ const primeMaterialPaintFromSelection = useEditor((s) => s.primeMaterialPaintFromSelection)
52
54
  const isPreviewMode = useEditor((s) => s.isPreviewMode)
53
55
  const setPreviewMode = useEditor((s) => s.setPreviewMode)
54
56
 
@@ -150,6 +152,21 @@ export function EditorCommands() {
150
152
  useScene.getState().deleteNodes(selectedIds as any[])
151
153
  }),
152
154
  },
155
+ {
156
+ id: 'editor.mode.material-paint',
157
+ label: 'Material Paint',
158
+ group: 'Scene',
159
+ icon: <PaintBucket className="h-4 w-4" />,
160
+ keywords: ['paint', 'material', 'texture', 'bucket', 'surface'],
161
+ shortcut: ['P'],
162
+ execute: () =>
163
+ run(() => {
164
+ primeMaterialPaintFromSelection()
165
+ setPhase('structure')
166
+ setStructureLayer('elements')
167
+ setMode('material-paint')
168
+ }),
169
+ },
153
170
 
154
171
  // ── Levels ───────────────────────────────────────────────────────────
155
172
  {
@@ -277,12 +294,14 @@ export function EditorCommands() {
277
294
  },
278
295
  {
279
296
  id: 'editor.viewer.camera-snapshot',
280
- label: 'Camera Snapshot',
297
+ label: 'Take Snapshot',
281
298
  group: 'Viewer Controls',
282
299
  icon: <Camera className="h-4 w-4" />,
283
300
  keywords: ['camera', 'snapshot', 'capture', 'save', 'view', 'bookmark'],
284
- navigate: true,
285
- execute: () => navigateTo('camera-view'),
301
+ execute: () => {
302
+ setOpen(false)
303
+ useEditor.getState().setCaptureMode(true)
304
+ },
286
305
  },
287
306
 
288
307
  // ── View ─────────────────────────────────────────────────────────────
@@ -355,7 +374,7 @@ export function EditorCommands() {
355
374
  icon: <Box className="h-4 w-4" />,
356
375
  keywords: ['export', 'glb', 'gltf', '3d', 'model', 'download'],
357
376
  execute: () => run(() => exportScene()),
358
- } as const,
377
+ },
359
378
  ]
360
379
  : []),
361
380
  {
@@ -27,15 +27,13 @@ interface CommandPaletteStore {
27
27
  setInputValue: (value: string) => void
28
28
  navigateTo: (page: string) => void
29
29
  goBack: () => void
30
- cameraScope: { nodeId: string; label: string } | null
31
- setCameraScope: (scope: { nodeId: string; label: string } | null) => void
32
30
  }
33
31
 
34
32
  export const useCommandPalette = create<CommandPaletteStore>((set, get) => ({
35
33
  open: false,
36
34
  setOpen: (open) => {
37
35
  set({ open })
38
- if (!open) set({ pages: [], inputValue: '', cameraScope: null, mode: 'command' })
36
+ if (!open) set({ pages: [], inputValue: '', mode: 'command' })
39
37
  },
40
38
  mode: 'command',
41
39
  setMode: (mode) => set({ mode }),
@@ -44,12 +42,8 @@ export const useCommandPalette = create<CommandPaletteStore>((set, get) => ({
44
42
  setInputValue: (value) => set({ inputValue: value }),
45
43
  navigateTo: (page) => set((s) => ({ pages: [...s.pages, page], inputValue: '' })),
46
44
  goBack: () => {
47
- const { pages } = get()
48
- if (pages[pages.length - 1] === 'camera-scope') set({ cameraScope: null })
49
45
  set((s) => ({ pages: s.pages.slice(0, -1), inputValue: '' }))
50
46
  },
51
- cameraScope: null,
52
- setCameraScope: (scope) => set({ cameraScope: scope }),
53
47
  }))
54
48
 
55
49
  // ---------------------------------------------------------------------------
@@ -157,8 +151,6 @@ const PAGE_LABEL: Record<string, string> = {
157
151
  'level-mode': 'Level Mode',
158
152
  'rename-level': 'Rename Level',
159
153
  'goto-level': 'Go to Level',
160
- 'camera-view': 'Camera Snapshot',
161
- 'camera-scope': '',
162
154
  }
163
155
 
164
156
  // ---------------------------------------------------------------------------
@@ -196,19 +188,8 @@ function EmptyActionItem({ action }: { action: CommandPaletteEmptyAction }) {
196
188
  // Main component
197
189
  // ---------------------------------------------------------------------------
198
190
  export function CommandPalette({ emptyAction }: { emptyAction?: CommandPaletteEmptyAction }) {
199
- const {
200
- open,
201
- setOpen,
202
- mode,
203
- setMode,
204
- pages,
205
- inputValue,
206
- setInputValue,
207
- navigateTo,
208
- goBack,
209
- cameraScope,
210
- setCameraScope,
211
- } = useCommandPalette()
191
+ const { open, setOpen, mode, setMode, pages, inputValue, setInputValue, navigateTo, goBack } =
192
+ useCommandPalette()
212
193
 
213
194
  const [meta, setMeta] = useState('⌘')
214
195
  const [isFullscreen, setIsFullscreen] = useState(false)
@@ -233,11 +214,6 @@ export function CommandPalette({ emptyAction }: { emptyAction?: CommandPaletteEm
233
214
  ),
234
215
  )
235
216
 
236
- const cameraScopeNode = useScene((s) =>
237
- cameraScope ? s.nodes[cameraScope.nodeId as AnyNodeId] : null,
238
- )
239
- const hasScopeSnapshot = !!(cameraScopeNode as any)?.camera
240
-
241
217
  // Platform detection
242
218
  useEffect(() => {
243
219
  setMeta(/Mac|iPhone|iPad|iPod/.test(navigator.platform) ? '⌘' : 'Ctrl')
@@ -279,7 +255,6 @@ export function CommandPalette({ emptyAction }: { emptyAction?: CommandPaletteEm
279
255
  solo: 'Solo',
280
256
  }
281
257
 
282
- // Camera snapshot helpers (used by sub-pages registered via EditorCommands)
283
258
  const confirmRename = () => {
284
259
  if (!(activeLevelId && inputValue.trim())) return
285
260
  run(() => {
@@ -287,29 +262,6 @@ export function CommandPalette({ emptyAction }: { emptyAction?: CommandPaletteEm
287
262
  })
288
263
  }
289
264
 
290
- const takeSnapshot = () => {
291
- if (!cameraScope) return
292
- import('@pascal-app/core').then(({ emitter }) => {
293
- run(() =>
294
- emitter.emit('camera-controls:capture', { nodeId: cameraScope.nodeId as AnyNodeId }),
295
- )
296
- })
297
- }
298
-
299
- const viewSnapshot = () => {
300
- if (!(cameraScope && hasScopeSnapshot)) return
301
- import('@pascal-app/core').then(({ emitter }) => {
302
- run(() => emitter.emit('camera-controls:view', { nodeId: cameraScope.nodeId as AnyNodeId }))
303
- })
304
- }
305
-
306
- const clearSnapshot = () => {
307
- if (!(cameraScope && hasScopeSnapshot)) return
308
- run(() => {
309
- useScene.getState().updateNode(cameraScope.nodeId as AnyNodeId, { camera: undefined } as any)
310
- })
311
- }
312
-
313
265
  // ---------------------------------------------------------------------------
314
266
  // Group registered actions by group (preserving insertion order)
315
267
  // ---------------------------------------------------------------------------
@@ -363,9 +315,7 @@ export function CommandPalette({ emptyAction }: { emptyAction?: CommandPaletteEm
363
315
  onClick={goBack}
364
316
  type="button"
365
317
  >
366
- {page === 'camera-scope'
367
- ? (cameraScope?.label ?? 'Snapshot')
368
- : (PAGE_LABEL[page] ?? views.get(page)?.label ?? page)}
318
+ {PAGE_LABEL[page] ?? views.get(page)?.label ?? page}
369
319
  </button>
370
320
  )}
371
321
  <Command.Input
@@ -500,207 +450,6 @@ export function CommandPalette({ emptyAction }: { emptyAction?: CommandPaletteEm
500
450
  </Command.Item>
501
451
  </Command.Group>
502
452
  )}
503
-
504
- {/* ── Camera Snapshot: scope picker ─────────────────────────── */}
505
- {page === 'camera-view' && (
506
- <Command.Group heading="Camera Snapshot — Select Scope">
507
- <OptionItem
508
- icon={
509
- <svg
510
- className="h-4 w-4"
511
- fill="none"
512
- stroke="currentColor"
513
- strokeWidth={2}
514
- viewBox="0 0 24 24"
515
- >
516
- <path d="M3 3h18v18H3z" strokeLinecap="round" strokeLinejoin="round" />
517
- <path d="M3 9h18M9 21V9" strokeLinecap="round" strokeLinejoin="round" />
518
- </svg>
519
- }
520
- label="Site"
521
- onSelect={() => {
522
- const { rootNodeIds } = useScene.getState()
523
- const siteId = rootNodeIds[0]
524
- if (siteId) {
525
- setCameraScope({ nodeId: siteId, label: 'Site' })
526
- navigateTo('camera-scope')
527
- }
528
- }}
529
- />
530
- <OptionItem
531
- icon={
532
- <svg
533
- className="h-4 w-4"
534
- fill="none"
535
- stroke="currentColor"
536
- strokeWidth={2}
537
- viewBox="0 0 24 24"
538
- >
539
- <path
540
- d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
541
- strokeLinecap="round"
542
- strokeLinejoin="round"
543
- />
544
- <polyline
545
- points="9 22 9 12 15 12 15 22"
546
- strokeLinecap="round"
547
- strokeLinejoin="round"
548
- />
549
- </svg>
550
- }
551
- label="Building"
552
- onSelect={() => {
553
- const building = Object.values(useScene.getState().nodes).find(
554
- (n) => n.type === 'building',
555
- )
556
- if (building) {
557
- setCameraScope({ nodeId: building.id, label: 'Building' })
558
- navigateTo('camera-scope')
559
- }
560
- }}
561
- />
562
- <OptionItem
563
- disabled={!activeLevelId}
564
- icon={
565
- <svg
566
- className="h-4 w-4"
567
- fill="none"
568
- stroke="currentColor"
569
- strokeWidth={2}
570
- viewBox="0 0 24 24"
571
- >
572
- <path
573
- d="M12 2L2 7l10 5 10-5-10-5z"
574
- strokeLinecap="round"
575
- strokeLinejoin="round"
576
- />
577
- <path
578
- d="M2 17l10 5 10-5M2 12l10 5 10-5"
579
- strokeLinecap="round"
580
- strokeLinejoin="round"
581
- />
582
- </svg>
583
- }
584
- label="Level"
585
- onSelect={() => {
586
- if (activeLevelId) {
587
- setCameraScope({ nodeId: activeLevelId, label: 'Level' })
588
- navigateTo('camera-scope')
589
- }
590
- }}
591
- />
592
- <OptionItem
593
- disabled={!useViewer.getState().selection.selectedIds.length}
594
- icon={
595
- <svg
596
- className="h-4 w-4"
597
- fill="none"
598
- stroke="currentColor"
599
- strokeWidth={2}
600
- viewBox="0 0 24 24"
601
- >
602
- <path d="M5 3l14 9-14 9V3z" strokeLinecap="round" strokeLinejoin="round" />
603
- </svg>
604
- }
605
- label="Selection"
606
- onSelect={() => {
607
- const firstId = useViewer.getState().selection.selectedIds[0]
608
- if (firstId) {
609
- setCameraScope({ nodeId: firstId, label: 'Selection' })
610
- navigateTo('camera-scope')
611
- }
612
- }}
613
- />
614
- </Command.Group>
615
- )}
616
-
617
- {/* ── Camera Snapshot: actions for selected scope ───────────── */}
618
- {page === 'camera-scope' && cameraScope && (
619
- <Command.Group heading={`${cameraScope.label} Snapshot`}>
620
- <OptionItem
621
- icon={
622
- <svg
623
- className="h-4 w-4"
624
- fill="none"
625
- stroke="currentColor"
626
- strokeWidth={2}
627
- viewBox="0 0 24 24"
628
- >
629
- <path
630
- d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"
631
- strokeLinecap="round"
632
- strokeLinejoin="round"
633
- />
634
- <circle
635
- cx="12"
636
- cy="13"
637
- r="4"
638
- strokeLinecap="round"
639
- strokeLinejoin="round"
640
- />
641
- </svg>
642
- }
643
- label={hasScopeSnapshot ? 'Update Snapshot' : 'Take Snapshot'}
644
- onSelect={takeSnapshot}
645
- />
646
- {hasScopeSnapshot && (
647
- <OptionItem
648
- icon={
649
- <svg
650
- className="h-4 w-4"
651
- fill="none"
652
- stroke="currentColor"
653
- strokeWidth={2}
654
- viewBox="0 0 24 24"
655
- >
656
- <path
657
- d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"
658
- strokeLinecap="round"
659
- strokeLinejoin="round"
660
- />
661
- <circle
662
- cx="12"
663
- cy="12"
664
- r="3"
665
- strokeLinecap="round"
666
- strokeLinejoin="round"
667
- />
668
- </svg>
669
- }
670
- label="View Snapshot"
671
- onSelect={viewSnapshot}
672
- />
673
- )}
674
- {hasScopeSnapshot && (
675
- <OptionItem
676
- icon={
677
- <svg
678
- className="h-4 w-4"
679
- fill="none"
680
- stroke="currentColor"
681
- strokeWidth={2}
682
- viewBox="0 0 24 24"
683
- >
684
- <polyline
685
- points="3 6 5 6 21 6"
686
- strokeLinecap="round"
687
- strokeLinejoin="round"
688
- />
689
- <path
690
- d="M19 6l-1 14H6L5 6"
691
- strokeLinecap="round"
692
- strokeLinejoin="round"
693
- />
694
- <path d="M10 11v6M14 11v6" strokeLinecap="round" strokeLinejoin="round" />
695
- <path d="M9 6V4h6v2" strokeLinecap="round" strokeLinejoin="round" />
696
- </svg>
697
- }
698
- label="Clear Snapshot"
699
- onSelect={clearSnapshot}
700
- />
701
- )}
702
- </Command.Group>
703
- )}
704
453
  </Command.List>
705
454
 
706
455
  {/* Footer hint */}