@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
@@ -1,395 +0,0 @@
1
- 'use client'
2
-
3
- import { Icon as IconifyIcon } from '@iconify/react'
4
- import { useViewer } from '@pascal-app/viewer'
5
- import { Check, ChevronsLeft, ChevronsRight, Columns2, Eye, Footprints, Moon, Sun } from 'lucide-react'
6
- import { useCallback } from 'react'
7
- import { cn } from '../../lib/utils'
8
- import useEditor from '../../store/use-editor'
9
- import type { GridSnapStep, ViewMode } from '../../store/use-editor'
10
- import {
11
- DropdownMenu,
12
- DropdownMenuContent,
13
- DropdownMenuItem,
14
- DropdownMenuTrigger,
15
- } from './primitives/dropdown-menu'
16
- import { useSidebarStore } from './primitives/sidebar'
17
- import { Tooltip, TooltipContent, TooltipTrigger } from './primitives/tooltip'
18
-
19
- // ── Shared styles ───────────────────────────────────────────────────────────
20
-
21
- /** Container for a group of buttons — no padding, overflow-hidden clips children flush. */
22
- const TOOLBAR_CONTAINER =
23
- 'inline-flex h-8 items-stretch overflow-hidden rounded-xl border border-border bg-background/90 shadow-2xl backdrop-blur-md'
24
-
25
- /** Ghost button inside a container — flush edges, no individual border/radius. */
26
- const TOOLBAR_BTN =
27
- 'flex items-center justify-center w-8 text-muted-foreground/80 transition-colors hover:bg-white/8 hover:text-foreground/90'
28
-
29
- // ── View mode segmented control ─────────────────────────────────────────────
30
-
31
- const VIEW_MODES: { id: ViewMode; label: string; icon: React.ReactNode }[] = [
32
- {
33
- id: '3d',
34
- label: '3D',
35
- icon: <img alt="" className="h-3.5 w-3.5 object-contain" src="/icons/building.png" />,
36
- },
37
- {
38
- id: '2d',
39
- label: '2D',
40
- icon: <img alt="" className="h-3.5 w-3.5 object-contain" src="/icons/blueprint.png" />,
41
- },
42
- {
43
- id: 'split',
44
- label: 'Split',
45
- icon: <Columns2 className="h-3 w-3" />,
46
- },
47
- ]
48
-
49
- function ViewModeControl() {
50
- const viewMode = useEditor((s) => s.viewMode)
51
- const setViewMode = useEditor((s) => s.setViewMode)
52
-
53
- return (
54
- <div className={TOOLBAR_CONTAINER}>
55
- {VIEW_MODES.map((mode) => {
56
- const isActive = viewMode === mode.id
57
- return (
58
- <button
59
- className={cn(
60
- 'flex items-center justify-center gap-1.5 px-2.5 font-medium text-xs transition-colors',
61
- isActive
62
- ? 'bg-white/10 text-foreground'
63
- : 'text-muted-foreground/70 hover:bg-white/8 hover:text-muted-foreground',
64
- )}
65
- key={mode.id}
66
- onClick={() => setViewMode(mode.id)}
67
- type="button"
68
- >
69
- {mode.icon}
70
- <span>{mode.label}</span>
71
- </button>
72
- )
73
- })}
74
- </div>
75
- )
76
- }
77
-
78
- // ── Collapse sidebar button ─────────────────────────────────────────────────
79
-
80
- function CollapseSidebarButton() {
81
- const isCollapsed = useSidebarStore((s) => s.isCollapsed)
82
- const setIsCollapsed = useSidebarStore((s) => s.setIsCollapsed)
83
-
84
- const toggle = useCallback(() => {
85
- setIsCollapsed(!isCollapsed)
86
- }, [isCollapsed, setIsCollapsed])
87
-
88
- return (
89
- <div className={TOOLBAR_CONTAINER}>
90
- <button
91
- className={TOOLBAR_BTN}
92
- onClick={toggle}
93
- title={isCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
94
- type="button"
95
- >
96
- {isCollapsed ? <ChevronsRight className="h-4 w-4" /> : <ChevronsLeft className="h-4 w-4" />}
97
- </button>
98
- </div>
99
- )
100
- }
101
-
102
- // ── Right toolbar buttons ───────────────────────────────────────────────────
103
-
104
- function WalkthroughButton() {
105
- const isFirstPersonMode = useEditor((s) => s.isFirstPersonMode)
106
- const setFirstPersonMode = useEditor((s) => s.setFirstPersonMode)
107
-
108
- const toggle = () => {
109
- setFirstPersonMode(!isFirstPersonMode)
110
- }
111
-
112
- return (
113
- <Tooltip>
114
- <TooltipTrigger asChild>
115
- <button
116
- className={cn(
117
- TOOLBAR_BTN,
118
- isFirstPersonMode && 'bg-emerald-500/15 text-emerald-400 hover:bg-emerald-500/20',
119
- )}
120
- onClick={toggle}
121
- type="button"
122
- >
123
- <Footprints className="h-4 w-4" />
124
- </button>
125
- </TooltipTrigger>
126
- <TooltipContent side="bottom">Walkthrough</TooltipContent>
127
- </Tooltip>
128
- )
129
- }
130
-
131
- function UnitToggle() {
132
- const unit = useViewer((s) => s.unit)
133
- const setUnit = useViewer((s) => s.setUnit)
134
-
135
- return (
136
- <Tooltip>
137
- <TooltipTrigger asChild>
138
- <button
139
- className={TOOLBAR_BTN}
140
- onClick={() => setUnit(unit === 'metric' ? 'imperial' : 'metric')}
141
- type="button"
142
- >
143
- <span className="font-semibold text-[10px]">{unit === 'metric' ? 'm' : 'ft'}</span>
144
- </button>
145
- </TooltipTrigger>
146
- <TooltipContent side="bottom">
147
- {unit === 'metric' ? 'Metric (m)' : 'Imperial (ft)'}
148
- </TooltipContent>
149
- </Tooltip>
150
- )
151
- }
152
-
153
- function ThemeToggle() {
154
- const theme = useViewer((s) => s.theme)
155
- const setTheme = useViewer((s) => s.setTheme)
156
-
157
- return (
158
- <Tooltip>
159
- <TooltipTrigger asChild>
160
- <button
161
- className={cn(TOOLBAR_BTN, theme === 'dark' ? 'text-indigo-400/60' : 'text-amber-400/60')}
162
- onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
163
- type="button"
164
- >
165
- {theme === 'dark' ? <Moon className="h-3.5 w-3.5" /> : <Sun className="h-3.5 w-3.5" />}
166
- </button>
167
- </TooltipTrigger>
168
- <TooltipContent side="bottom">{theme === 'dark' ? 'Dark' : 'Light'}</TooltipContent>
169
- </Tooltip>
170
- )
171
- }
172
-
173
- // ── Level mode toggle ───────────────────────────────────────────────────────
174
-
175
- const levelModeOrder = ['stacked', 'exploded', 'solo'] as const
176
- const levelModeLabels: Record<string, string> = {
177
- manual: 'Stack',
178
- stacked: 'Stack',
179
- exploded: 'Exploded',
180
- solo: 'Solo',
181
- }
182
-
183
- const gridSnapOrder: GridSnapStep[] = [0.5, 0.25, 0.1, 0.05]
184
- const gridSnapLabels: Record<GridSnapStep, string> = {
185
- 0.5: '0.50',
186
- 0.25: '0.25',
187
- 0.1: '0.10',
188
- 0.05: '0.05',
189
- }
190
-
191
- function formatGridSnapStep(step: GridSnapStep): string {
192
- return gridSnapLabels[step]
193
- }
194
-
195
- function LevelModeToggle() {
196
- const levelMode = useViewer((s) => s.levelMode)
197
- const setLevelMode = useViewer((s) => s.setLevelMode)
198
-
199
- const cycle = () => {
200
- if (levelMode === 'manual') {
201
- setLevelMode('stacked')
202
- return
203
- }
204
- const idx = levelModeOrder.indexOf(levelMode as (typeof levelModeOrder)[number])
205
- const next = levelModeOrder[(idx + 1) % levelModeOrder.length]
206
- if (next) setLevelMode(next)
207
- }
208
-
209
- const isDefault = levelMode === 'stacked' || levelMode === 'manual'
210
-
211
- return (
212
- <Tooltip>
213
- <TooltipTrigger asChild>
214
- <button
215
- className={cn(
216
- TOOLBAR_BTN,
217
- 'w-auto gap-1.5 px-2.5',
218
- !isDefault && 'bg-white/10 text-foreground/90',
219
- )}
220
- onClick={cycle}
221
- type="button"
222
- >
223
- {levelMode === 'solo' ? (
224
- <IconifyIcon height={14} icon="lucide:diamond" width={14} />
225
- ) : levelMode === 'exploded' ? (
226
- <IconifyIcon height={14} icon="charm:stack-pop" width={14} />
227
- ) : (
228
- <IconifyIcon height={14} icon="charm:stack-push" width={14} />
229
- )}
230
- <span className="font-medium text-xs">{levelModeLabels[levelMode] ?? 'Stack'}</span>
231
- </button>
232
- </TooltipTrigger>
233
- <TooltipContent side="bottom">
234
- Levels: {levelMode === 'manual' ? 'Manual' : levelModeLabels[levelMode]}
235
- </TooltipContent>
236
- </Tooltip>
237
- )
238
- }
239
-
240
- function GridSnapToggle() {
241
- const gridSnapStep = useEditor((s) => s.gridSnapStep)
242
- const setGridSnapStep = useEditor((s) => s.setGridSnapStep)
243
-
244
- return (
245
- <DropdownMenu>
246
- <Tooltip>
247
- <TooltipTrigger asChild>
248
- <DropdownMenuTrigger asChild>
249
- <button className={cn(TOOLBAR_BTN, 'w-auto gap-1.5 px-2.5')} type="button">
250
- <IconifyIcon height={14} icon="lucide:grid-2x2" width={14} />
251
- <span className="font-medium text-xs">{formatGridSnapStep(gridSnapStep)}</span>
252
- </button>
253
- </DropdownMenuTrigger>
254
- </TooltipTrigger>
255
- <TooltipContent side="bottom">Grid snap: {formatGridSnapStep(gridSnapStep)}</TooltipContent>
256
- </Tooltip>
257
- <DropdownMenuContent align="center" side="bottom">
258
- {gridSnapOrder.map((step) => {
259
- const isActive = step === gridSnapStep
260
- return (
261
- <DropdownMenuItem key={step} onSelect={() => setGridSnapStep(step)}>
262
- <span className="flex min-w-12 items-center justify-between gap-3">
263
- <span>{formatGridSnapStep(step)}</span>
264
- {isActive ? <Check className="h-3.5 w-3.5" /> : <span className="h-3.5 w-3.5" />}
265
- </span>
266
- </DropdownMenuItem>
267
- )
268
- })}
269
- </DropdownMenuContent>
270
- </DropdownMenu>
271
- )
272
- }
273
-
274
- // ── Wall mode toggle ────────────────────────────────────────────────────────
275
-
276
- const wallModeOrder = ['cutaway', 'up', 'down'] as const
277
- const wallModeConfig: Record<string, { icon: string; label: string }> = {
278
- up: { icon: '/icons/room.png', label: 'Full height' },
279
- cutaway: { icon: '/icons/wallcut.png', label: 'Cutaway' },
280
- down: { icon: '/icons/walllow.png', label: 'Low' },
281
- }
282
-
283
- function WallModeToggle() {
284
- const wallMode = useViewer((s) => s.wallMode)
285
- const setWallMode = useViewer((s) => s.setWallMode)
286
-
287
- const cycle = () => {
288
- const idx = wallModeOrder.indexOf(wallMode as (typeof wallModeOrder)[number])
289
- const next = wallModeOrder[(idx + 1) % wallModeOrder.length]
290
- if (next) setWallMode(next)
291
- }
292
-
293
- const config = wallModeConfig[wallMode] ?? wallModeConfig.cutaway!
294
-
295
- return (
296
- <Tooltip>
297
- <TooltipTrigger asChild>
298
- <button
299
- className={cn(
300
- TOOLBAR_BTN,
301
- 'w-auto gap-1.5 px-2.5',
302
- wallMode !== 'cutaway'
303
- ? 'bg-white/10'
304
- : 'opacity-60 grayscale hover:opacity-100 hover:grayscale-0',
305
- )}
306
- onClick={cycle}
307
- type="button"
308
- >
309
- <img alt={config.label} className="h-4 w-4 object-contain" src={config.icon} />
310
- <span className="font-medium text-xs">{config.label}</span>
311
- </button>
312
- </TooltipTrigger>
313
- <TooltipContent side="bottom">Walls: {config.label}</TooltipContent>
314
- </Tooltip>
315
- )
316
- }
317
-
318
- // ── Camera mode toggle ──────────────────────────────────────────────────────
319
-
320
- function CameraModeToggle() {
321
- const cameraMode = useViewer((s) => s.cameraMode)
322
- const setCameraMode = useViewer((s) => s.setCameraMode)
323
-
324
- return (
325
- <Tooltip>
326
- <TooltipTrigger asChild>
327
- <button
328
- className={cn(
329
- TOOLBAR_BTN,
330
- cameraMode === 'orthographic' && 'bg-white/10 text-foreground/90',
331
- )}
332
- onClick={() =>
333
- setCameraMode(cameraMode === 'perspective' ? 'orthographic' : 'perspective')
334
- }
335
- type="button"
336
- >
337
- {cameraMode === 'perspective' ? (
338
- <IconifyIcon height={16} icon="icon-park-outline:perspective" width={16} />
339
- ) : (
340
- <IconifyIcon height={16} icon="vaadin:grid" width={16} />
341
- )}
342
- </button>
343
- </TooltipTrigger>
344
- <TooltipContent side="bottom">
345
- {cameraMode === 'perspective' ? 'Perspective' : 'Orthographic'}
346
- </TooltipContent>
347
- </Tooltip>
348
- )
349
- }
350
-
351
- function PreviewButton() {
352
- return (
353
- <Tooltip>
354
- <TooltipTrigger asChild>
355
- <button
356
- className="flex items-center gap-1.5 px-2.5 font-medium text-muted-foreground/80 text-xs transition-colors hover:bg-white/8 hover:text-foreground/90"
357
- onClick={() => useEditor.getState().setPreviewMode(true)}
358
- type="button"
359
- >
360
- <Eye className="h-3.5 w-3.5 shrink-0" />
361
- <span>Preview</span>
362
- </button>
363
- </TooltipTrigger>
364
- <TooltipContent side="bottom">Preview mode</TooltipContent>
365
- </Tooltip>
366
- )
367
- }
368
-
369
- // ── Composed toolbar sections ───────────────────────────────────────────────
370
-
371
- export function ViewerToolbarLeft() {
372
- return (
373
- <>
374
- <CollapseSidebarButton />
375
- <ViewModeControl />
376
- </>
377
- )
378
- }
379
-
380
- export function ViewerToolbarRight() {
381
- return (
382
- <div className={TOOLBAR_CONTAINER}>
383
- <LevelModeToggle />
384
- <WallModeToggle />
385
- <GridSnapToggle />
386
- <div className="my-1.5 w-px bg-border/50" />
387
- <UnitToggle />
388
- <ThemeToggle />
389
- <CameraModeToggle />
390
- <div className="my-1.5 w-px bg-border/50" />
391
- <WalkthroughButton />
392
- <PreviewButton />
393
- </div>
394
- )
395
- }