@pascal-app/editor 0.5.1 → 0.7.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.
- package/package.json +12 -7
- package/src/components/editor/bottom-sheet.tsx +149 -0
- package/src/components/editor/custom-camera-controls.tsx +75 -7
- package/src/components/editor/editor-layout-mobile.tsx +264 -0
- package/src/components/editor/editor-layout-v2.tsx +29 -0
- package/src/components/editor/first-person/build-collider-world.ts +365 -0
- package/src/components/editor/first-person/bvh-ecctrl.tsx +795 -0
- package/src/components/editor/first-person-controls.tsx +496 -143
- package/src/components/editor/floating-action-menu.tsx +281 -83
- package/src/components/editor/floating-building-action-menu.tsx +4 -3
- package/src/components/editor/floorplan-background-selection.ts +113 -0
- package/src/components/editor/floorplan-panel.tsx +10442 -3275
- package/src/components/editor/index.tsx +270 -20
- package/src/components/editor/node-action-menu.tsx +14 -1
- package/src/components/editor/selection-manager.tsx +766 -12
- package/src/components/editor/site-edge-labels.tsx +9 -3
- package/src/components/editor/thumbnail-generator.tsx +350 -157
- package/src/components/editor/use-floorplan-background-placement.ts +257 -0
- package/src/components/editor/use-floorplan-hit-testing.ts +171 -0
- package/src/components/editor/use-floorplan-scene-data.ts +189 -0
- package/src/components/editor/wall-measurement-label.tsx +377 -58
- package/src/components/editor-2d/floorplan-action-menu-layer.tsx +95 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +160 -0
- package/src/components/editor-2d/floorplan-hotkey-handlers.tsx +92 -0
- package/src/components/editor-2d/renderers/floorplan-draft-layer.tsx +119 -0
- package/src/components/editor-2d/renderers/floorplan-marquee-layer.tsx +58 -0
- package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +197 -0
- package/src/components/editor-2d/renderers/floorplan-roof-layer.tsx +113 -0
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +474 -0
- package/src/components/editor-2d/svg-paths.ts +119 -0
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +272 -0
- package/src/components/systems/roof/roof-edit-system.tsx +5 -5
- package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +1 -0
- package/src/components/tools/ceiling/ceiling-hole-editor.tsx +2 -0
- package/src/components/tools/ceiling/ceiling-tool.tsx +5 -5
- package/src/components/tools/ceiling/move-ceiling-tool.tsx +257 -0
- package/src/components/tools/column/column-tool.tsx +97 -0
- package/src/components/tools/column/move-column-tool.tsx +105 -0
- package/src/components/tools/door/door-tool.tsx +19 -0
- package/src/components/tools/door/move-door-tool.tsx +38 -8
- package/src/components/tools/fence/curve-fence-tool.tsx +179 -0
- package/src/components/tools/fence/fence-drafting.ts +27 -8
- package/src/components/tools/fence/fence-tool.tsx +159 -3
- package/src/components/tools/fence/move-fence-endpoint-tool.tsx +438 -0
- package/src/components/tools/fence/move-fence-tool.tsx +102 -27
- package/src/components/tools/item/move-tool.tsx +19 -1
- package/src/components/tools/item/placement-math.ts +44 -7
- package/src/components/tools/item/placement-strategies.ts +111 -33
- package/src/components/tools/item/placement-types.ts +7 -0
- package/src/components/tools/item/use-draft-node.ts +2 -0
- package/src/components/tools/item/use-placement-coordinator.tsx +701 -61
- package/src/components/tools/roof/move-roof-tool.tsx +111 -43
- package/src/components/tools/shared/polygon-editor.tsx +244 -29
- package/src/components/tools/shared/segment-angle.ts +156 -0
- package/src/components/tools/slab/move-slab-tool.tsx +182 -0
- package/src/components/tools/slab/slab-boundary-editor.tsx +1 -0
- package/src/components/tools/slab/slab-hole-editor.tsx +2 -0
- package/src/components/tools/spawn/move-spawn-tool.tsx +101 -0
- package/src/components/tools/spawn/spawn-tool.tsx +130 -0
- package/src/components/tools/stair/stair-tool.tsx +11 -3
- package/src/components/tools/tool-manager.tsx +30 -3
- package/src/components/tools/wall/curve-wall-tool.tsx +176 -0
- package/src/components/tools/wall/move-wall-endpoint-tool.tsx +423 -0
- package/src/components/tools/wall/move-wall-tool.tsx +356 -0
- package/src/components/tools/wall/wall-drafting.ts +348 -17
- package/src/components/tools/wall/wall-tool.tsx +134 -2
- package/src/components/tools/window/move-window-tool.tsx +28 -0
- package/src/components/tools/window/window-tool.tsx +17 -0
- package/src/components/ui/action-menu/camera-actions.tsx +37 -33
- package/src/components/ui/action-menu/control-modes.tsx +37 -5
- package/src/components/ui/action-menu/index.tsx +91 -1
- package/src/components/ui/action-menu/structure-tools.tsx +2 -0
- package/src/components/ui/action-menu/view-toggles.tsx +424 -35
- package/src/components/ui/command-palette/editor-commands.tsx +27 -5
- package/src/components/ui/command-palette/index.tsx +0 -1
- package/src/components/ui/controls/material-picker.tsx +189 -169
- package/src/components/ui/controls/slider-control.tsx +88 -26
- package/src/components/ui/floating-level-selector.tsx +286 -55
- package/src/components/ui/helpers/helper-manager.tsx +5 -0
- package/src/components/ui/item-catalog/catalog-items.tsx +1121 -1219
- package/src/components/ui/item-catalog/item-catalog.tsx +42 -175
- package/src/components/ui/level-duplicate-dialog.tsx +115 -0
- package/src/components/ui/panels/ceiling-panel.tsx +47 -27
- package/src/components/ui/panels/column-panel.tsx +715 -0
- package/src/components/ui/panels/door-panel.tsx +986 -294
- package/src/components/ui/panels/fence-panel.tsx +55 -12
- package/src/components/ui/panels/item-panel.tsx +5 -5
- package/src/components/ui/panels/mobile-panel-sheet.tsx +108 -0
- package/src/components/ui/panels/mobile-selection-bar.tsx +100 -0
- package/src/components/ui/panels/node-display.ts +39 -0
- package/src/components/ui/panels/paint-panel.tsx +138 -0
- package/src/components/ui/panels/panel-manager.tsx +241 -30
- package/src/components/ui/panels/panel-wrapper.tsx +48 -39
- package/src/components/ui/panels/reference-panel.tsx +243 -9
- package/src/components/ui/panels/roof-panel.tsx +30 -62
- package/src/components/ui/panels/roof-segment-panel.tsx +8 -23
- package/src/components/ui/panels/slab-panel.tsx +46 -24
- package/src/components/ui/panels/spawn-panel.tsx +155 -0
- package/src/components/ui/panels/stair-panel.tsx +117 -69
- package/src/components/ui/panels/stair-segment-panel.tsx +13 -27
- package/src/components/ui/panels/wall-panel.tsx +71 -17
- package/src/components/ui/panels/window-panel.tsx +665 -146
- package/src/components/ui/sidebar/mobile-tab-bar.tsx +46 -0
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +2 -2
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +9 -5
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +77 -0
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +138 -56
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +9 -5
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +82 -0
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +3 -3
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +12 -6
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +15 -8
- package/src/components/ui/sidebar/tab-bar.tsx +3 -0
- package/src/components/ui/viewer-toolbar.tsx +96 -2
- package/src/components/viewer-overlay.tsx +25 -19
- package/src/hooks/use-auto-frame.ts +45 -0
- package/src/hooks/use-contextual-tools.ts +14 -13
- package/src/hooks/use-keyboard.ts +67 -9
- package/src/hooks/use-mobile.ts +12 -12
- package/src/index.tsx +2 -1
- package/src/lib/door-interaction.ts +88 -0
- package/src/lib/floorplan/geometry.ts +263 -0
- package/src/lib/floorplan/index.ts +38 -0
- package/src/lib/floorplan/items.ts +179 -0
- package/src/lib/floorplan/selection-tool.ts +231 -0
- package/src/lib/floorplan/stairs.ts +478 -0
- package/src/lib/floorplan/types.ts +57 -0
- package/src/lib/floorplan/walls.ts +23 -0
- package/src/lib/guide-events.ts +10 -0
- package/src/lib/history.ts +20 -0
- package/src/lib/level-duplication.test.ts +72 -0
- package/src/lib/level-duplication.ts +153 -0
- package/src/lib/local-guide-image.ts +42 -0
- package/src/lib/material-paint.ts +284 -0
- package/src/lib/roof-duplication.ts +214 -0
- package/src/lib/scene-bounds.test.ts +183 -0
- package/src/lib/scene-bounds.ts +169 -0
- package/src/lib/sfx-player.ts +96 -13
- package/src/lib/stair-duplication.ts +126 -0
- package/src/lib/window-interaction.ts +86 -0
- package/src/store/use-editor.tsx +279 -15
|
@@ -1,60 +1,271 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
type AnyNode,
|
|
5
|
+
type AnyNodeId,
|
|
6
|
+
type BuildingNode,
|
|
7
|
+
type CeilingNode,
|
|
8
|
+
type ColumnNode,
|
|
9
|
+
type DoorNode,
|
|
10
|
+
type FenceNode,
|
|
11
|
+
type ItemNode,
|
|
12
|
+
type RoofNode,
|
|
13
|
+
type RoofSegmentNode,
|
|
14
|
+
type SlabNode,
|
|
15
|
+
type StairNode,
|
|
16
|
+
type StairSegmentNode,
|
|
17
|
+
useScene,
|
|
18
|
+
type WallNode,
|
|
19
|
+
type WindowNode,
|
|
20
|
+
} from '@pascal-app/core'
|
|
4
21
|
import { useViewer } from '@pascal-app/viewer'
|
|
22
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
23
|
+
import { useIsMobile } from '../../../hooks/use-mobile'
|
|
24
|
+
import { sfxEmitter } from '../../../lib/sfx-bus'
|
|
5
25
|
import useEditor from '../../../store/use-editor'
|
|
6
26
|
import { CeilingPanel } from './ceiling-panel'
|
|
27
|
+
import { ColumnPanel } from './column-panel'
|
|
7
28
|
import { DoorPanel } from './door-panel'
|
|
8
29
|
import { FencePanel } from './fence-panel'
|
|
9
30
|
import { ItemPanel } from './item-panel'
|
|
31
|
+
import { MobilePanelSheet } from './mobile-panel-sheet'
|
|
32
|
+
import { MobileSelectionBar } from './mobile-selection-bar'
|
|
33
|
+
import { getNodeDisplay } from './node-display'
|
|
34
|
+
import { PaintPanel } from './paint-panel'
|
|
10
35
|
import { ReferencePanel } from './reference-panel'
|
|
11
36
|
import { RoofPanel } from './roof-panel'
|
|
12
37
|
import { RoofSegmentPanel } from './roof-segment-panel'
|
|
13
38
|
import { SlabPanel } from './slab-panel'
|
|
39
|
+
import { SpawnPanel } from './spawn-panel'
|
|
14
40
|
import { StairPanel } from './stair-panel'
|
|
15
41
|
import { StairSegmentPanel } from './stair-segment-panel'
|
|
16
42
|
import { WallPanel } from './wall-panel'
|
|
17
43
|
import { WindowPanel } from './window-panel'
|
|
18
44
|
|
|
45
|
+
type MovableNode =
|
|
46
|
+
| ItemNode
|
|
47
|
+
| WindowNode
|
|
48
|
+
| DoorNode
|
|
49
|
+
| CeilingNode
|
|
50
|
+
| ColumnNode
|
|
51
|
+
| SlabNode
|
|
52
|
+
| WallNode
|
|
53
|
+
| FenceNode
|
|
54
|
+
| RoofNode
|
|
55
|
+
| RoofSegmentNode
|
|
56
|
+
| StairNode
|
|
57
|
+
| StairSegmentNode
|
|
58
|
+
| BuildingNode
|
|
59
|
+
|
|
60
|
+
const MOVABLE_TYPES = new Set<string>([
|
|
61
|
+
'item',
|
|
62
|
+
'window',
|
|
63
|
+
'door',
|
|
64
|
+
'ceiling',
|
|
65
|
+
'column',
|
|
66
|
+
'slab',
|
|
67
|
+
'wall',
|
|
68
|
+
'fence',
|
|
69
|
+
'roof',
|
|
70
|
+
'roof-segment',
|
|
71
|
+
'stair',
|
|
72
|
+
'stair-segment',
|
|
73
|
+
'building',
|
|
74
|
+
])
|
|
75
|
+
|
|
76
|
+
function isMovableNode(node: AnyNode | null): node is MovableNode {
|
|
77
|
+
return !!node && MOVABLE_TYPES.has(node.type)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function panelForType(type: string | null) {
|
|
81
|
+
if (!type) return null
|
|
82
|
+
switch (type) {
|
|
83
|
+
case 'item':
|
|
84
|
+
return <ItemPanel />
|
|
85
|
+
case 'roof':
|
|
86
|
+
return <RoofPanel />
|
|
87
|
+
case 'roof-segment':
|
|
88
|
+
return <RoofSegmentPanel />
|
|
89
|
+
case 'stair':
|
|
90
|
+
return <StairPanel />
|
|
91
|
+
case 'stair-segment':
|
|
92
|
+
return <StairSegmentPanel />
|
|
93
|
+
case 'slab':
|
|
94
|
+
return <SlabPanel />
|
|
95
|
+
case 'ceiling':
|
|
96
|
+
return <CeilingPanel />
|
|
97
|
+
case 'column':
|
|
98
|
+
return <ColumnPanel />
|
|
99
|
+
case 'wall':
|
|
100
|
+
return <WallPanel />
|
|
101
|
+
case 'fence':
|
|
102
|
+
return <FencePanel />
|
|
103
|
+
case 'door':
|
|
104
|
+
return <DoorPanel />
|
|
105
|
+
case 'window':
|
|
106
|
+
return <WindowPanel />
|
|
107
|
+
default:
|
|
108
|
+
return null
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function MobilePanelLayer({
|
|
113
|
+
node,
|
|
114
|
+
panel,
|
|
115
|
+
isReference,
|
|
116
|
+
}: {
|
|
117
|
+
node: AnyNode | null
|
|
118
|
+
panel: React.ReactNode
|
|
119
|
+
isReference: boolean
|
|
120
|
+
}) {
|
|
121
|
+
const setSelection = useViewer((s) => s.setSelection)
|
|
122
|
+
const setSelectedReferenceId = useEditor((s) => s.setSelectedReferenceId)
|
|
123
|
+
const setMovingNode = useEditor((s) => s.setMovingNode)
|
|
124
|
+
const deleteNode = useScene((s) => s.deleteNode)
|
|
125
|
+
const [isSheetOpen, setIsSheetOpen] = useState(false)
|
|
126
|
+
|
|
127
|
+
// Reset sheet open state when the selection changes / clears
|
|
128
|
+
const selectionKey = node?.id ?? (isReference ? 'reference' : null)
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
setIsSheetOpen(false)
|
|
131
|
+
}, [selectionKey])
|
|
132
|
+
|
|
133
|
+
const clearSelection = useCallback(() => {
|
|
134
|
+
setSelection({ selectedIds: [] })
|
|
135
|
+
setSelectedReferenceId(null)
|
|
136
|
+
}, [setSelection, setSelectedReferenceId])
|
|
137
|
+
|
|
138
|
+
const handleMove = useCallback(() => {
|
|
139
|
+
if (!isMovableNode(node)) return
|
|
140
|
+
sfxEmitter.emit('sfx:item-pick')
|
|
141
|
+
setMovingNode(node)
|
|
142
|
+
clearSelection()
|
|
143
|
+
}, [node, setMovingNode, clearSelection])
|
|
144
|
+
|
|
145
|
+
const handleDuplicate = useCallback(() => {
|
|
146
|
+
if (!isMovableNode(node)) return
|
|
147
|
+
sfxEmitter.emit('sfx:item-pick')
|
|
148
|
+
const cloned = structuredClone(node) as MovableNode & { id?: AnyNodeId }
|
|
149
|
+
delete (cloned as { id?: AnyNodeId }).id
|
|
150
|
+
const prevMeta =
|
|
151
|
+
cloned.metadata && typeof cloned.metadata === 'object' && !Array.isArray(cloned.metadata)
|
|
152
|
+
? (cloned.metadata as Record<string, unknown>)
|
|
153
|
+
: {}
|
|
154
|
+
cloned.metadata = { ...prevMeta, isNew: true }
|
|
155
|
+
setMovingNode(cloned as MovableNode)
|
|
156
|
+
clearSelection()
|
|
157
|
+
}, [node, setMovingNode, clearSelection])
|
|
158
|
+
|
|
159
|
+
const handleDelete = useCallback(() => {
|
|
160
|
+
if (!node) return
|
|
161
|
+
sfxEmitter.emit('sfx:item-delete')
|
|
162
|
+
deleteNode(node.id)
|
|
163
|
+
clearSelection()
|
|
164
|
+
}, [node, deleteNode, clearSelection])
|
|
165
|
+
|
|
166
|
+
if (!(node || isReference)) return null
|
|
167
|
+
|
|
168
|
+
const display = getNodeDisplay(node)
|
|
169
|
+
|
|
170
|
+
return (
|
|
171
|
+
<>
|
|
172
|
+
{node && (
|
|
173
|
+
<MobileSelectionBar
|
|
174
|
+
node={node}
|
|
175
|
+
onDelete={handleDelete}
|
|
176
|
+
onDuplicate={handleDuplicate}
|
|
177
|
+
onEdit={() => setIsSheetOpen((v) => !v)}
|
|
178
|
+
onMove={handleMove}
|
|
179
|
+
/>
|
|
180
|
+
)}
|
|
181
|
+
<MobilePanelSheet
|
|
182
|
+
icon={display.icon}
|
|
183
|
+
onClose={() => setIsSheetOpen(false)}
|
|
184
|
+
open={isSheetOpen}
|
|
185
|
+
title={display.label}
|
|
186
|
+
>
|
|
187
|
+
{panel}
|
|
188
|
+
</MobilePanelSheet>
|
|
189
|
+
</>
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
|
|
19
193
|
export function PanelManager() {
|
|
194
|
+
const isMobile = useIsMobile()
|
|
20
195
|
const selectedIds = useViewer((s) => s.selection.selectedIds)
|
|
21
196
|
const selectedReferenceId = useEditor((s) => s.selectedReferenceId)
|
|
22
|
-
const
|
|
197
|
+
const isPaintPanelOpen = useEditor((s) => s.isPaintPanelOpen)
|
|
198
|
+
const mode = useEditor((s) => s.mode)
|
|
199
|
+
const activePaintMaterial = useEditor((s) => s.activePaintMaterial)
|
|
200
|
+
// Only subscribe to the *type* of the single-selected node — string primitive
|
|
201
|
+
// so we don't re-render on unrelated scene mutations.
|
|
202
|
+
const selectedNodeType = useScene((s) => {
|
|
203
|
+
if (selectedIds.length !== 1) return null
|
|
204
|
+
const id = selectedIds[0]
|
|
205
|
+
return id ? (s.nodes[id as AnyNodeId]?.type ?? null) : null
|
|
206
|
+
})
|
|
207
|
+
const selectedNode = useScene((s) => {
|
|
208
|
+
if (selectedIds.length !== 1) return null
|
|
209
|
+
const id = selectedIds[0]
|
|
210
|
+
return id ? (s.nodes[id as AnyNodeId] ?? null) : null
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
if (isMobile) {
|
|
214
|
+
if (selectedReferenceId) {
|
|
215
|
+
return <MobilePanelLayer isReference={true} node={null} panel={<ReferencePanel />} />
|
|
216
|
+
}
|
|
217
|
+
return (
|
|
218
|
+
<MobilePanelLayer
|
|
219
|
+
isReference={false}
|
|
220
|
+
node={selectedNode}
|
|
221
|
+
panel={panelForType(selectedNodeType)}
|
|
222
|
+
/>
|
|
223
|
+
)
|
|
224
|
+
}
|
|
23
225
|
|
|
24
226
|
// Show reference panel if a reference is selected
|
|
25
227
|
if (selectedReferenceId) {
|
|
26
228
|
return <ReferencePanel />
|
|
27
229
|
}
|
|
28
230
|
|
|
231
|
+
if (
|
|
232
|
+
isPaintPanelOpen &&
|
|
233
|
+
mode === 'material-paint' &&
|
|
234
|
+
activePaintMaterial?.material?.properties &&
|
|
235
|
+
!activePaintMaterial.materialPreset
|
|
236
|
+
) {
|
|
237
|
+
return <PaintPanel />
|
|
238
|
+
}
|
|
239
|
+
|
|
29
240
|
// Show appropriate panel based on selected node type
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
241
|
+
if (selectedNodeType) {
|
|
242
|
+
switch (selectedNodeType) {
|
|
243
|
+
case 'item':
|
|
244
|
+
return <ItemPanel />
|
|
245
|
+
case 'roof':
|
|
246
|
+
return <RoofPanel />
|
|
247
|
+
case 'roof-segment':
|
|
248
|
+
return <RoofSegmentPanel />
|
|
249
|
+
case 'stair':
|
|
250
|
+
return <StairPanel />
|
|
251
|
+
case 'stair-segment':
|
|
252
|
+
return <StairSegmentPanel />
|
|
253
|
+
case 'slab':
|
|
254
|
+
return <SlabPanel />
|
|
255
|
+
case 'spawn':
|
|
256
|
+
return <SpawnPanel />
|
|
257
|
+
case 'ceiling':
|
|
258
|
+
return <CeilingPanel />
|
|
259
|
+
case 'column':
|
|
260
|
+
return <ColumnPanel />
|
|
261
|
+
case 'wall':
|
|
262
|
+
return <WallPanel />
|
|
263
|
+
case 'fence':
|
|
264
|
+
return <FencePanel />
|
|
265
|
+
case 'door':
|
|
266
|
+
return <DoorPanel />
|
|
267
|
+
case 'window':
|
|
268
|
+
return <WindowPanel />
|
|
58
269
|
}
|
|
59
270
|
}
|
|
60
271
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { ChevronLeft, RotateCcw, X } from 'lucide-react'
|
|
4
4
|
import Image from 'next/image'
|
|
5
|
+
import { useIsMobile } from '../../../hooks/use-mobile'
|
|
5
6
|
import { cn } from '../../../lib/utils'
|
|
6
7
|
|
|
7
8
|
interface PanelWrapperProps {
|
|
@@ -25,53 +26,61 @@ export function PanelWrapper({
|
|
|
25
26
|
className,
|
|
26
27
|
width = 320, // default width
|
|
27
28
|
}: PanelWrapperProps) {
|
|
29
|
+
const isMobile = useIsMobile()
|
|
30
|
+
|
|
28
31
|
return (
|
|
29
32
|
<div
|
|
30
33
|
className={cn(
|
|
31
|
-
|
|
34
|
+
isMobile
|
|
35
|
+
? 'flex h-full w-full flex-col overflow-hidden bg-transparent dark:text-foreground'
|
|
36
|
+
: 'pointer-events-auto fixed top-20 right-4 z-50 flex max-h-[calc(100dvh-100px)] flex-col overflow-hidden rounded-xl border border-border/50 bg-sidebar/95 shadow-2xl backdrop-blur-xl dark:text-foreground',
|
|
32
37
|
className,
|
|
33
38
|
)}
|
|
34
|
-
style={{ width }}
|
|
39
|
+
style={isMobile ? undefined : { width }}
|
|
35
40
|
>
|
|
36
|
-
{/* Header */}
|
|
37
|
-
|
|
38
|
-
<div className="flex items-center
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
41
|
+
{/* Header — desktop only; mobile sheet provides its own header */}
|
|
42
|
+
{!isMobile && (
|
|
43
|
+
<div className="flex items-center justify-between border-border/50 border-b px-3 py-3">
|
|
44
|
+
<div className="flex items-center gap-2">
|
|
45
|
+
{onBack && (
|
|
46
|
+
<button
|
|
47
|
+
className="mr-1 flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-[#3e3e3e] hover:text-foreground"
|
|
48
|
+
onClick={onBack}
|
|
49
|
+
type="button"
|
|
50
|
+
>
|
|
51
|
+
<ChevronLeft className="h-4 w-4" />
|
|
52
|
+
</button>
|
|
53
|
+
)}
|
|
54
|
+
{icon && (
|
|
55
|
+
<Image alt="" className="shrink-0 object-contain" height={16} src={icon} width={16} />
|
|
56
|
+
)}
|
|
57
|
+
<h2 className="truncate font-semibold text-foreground text-sm tracking-tight">
|
|
58
|
+
{title}
|
|
59
|
+
</h2>
|
|
60
|
+
</div>
|
|
53
61
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
62
|
+
<div className="flex items-center gap-1">
|
|
63
|
+
{onReset && (
|
|
64
|
+
<button
|
|
65
|
+
className="flex h-7 w-7 items-center justify-center rounded-md bg-[#2C2C2E] text-muted-foreground transition-colors hover:bg-[#3e3e3e] hover:text-foreground"
|
|
66
|
+
onClick={onReset}
|
|
67
|
+
type="button"
|
|
68
|
+
>
|
|
69
|
+
<RotateCcw className="h-4 w-4" />
|
|
70
|
+
</button>
|
|
71
|
+
)}
|
|
72
|
+
{onClose && (
|
|
73
|
+
<button
|
|
74
|
+
className="flex h-7 w-7 items-center justify-center rounded-md bg-[#2C2C2E] text-muted-foreground transition-colors hover:bg-[#3e3e3e] hover:text-foreground"
|
|
75
|
+
onClick={onClose}
|
|
76
|
+
type="button"
|
|
77
|
+
>
|
|
78
|
+
<X className="h-4 w-4" />
|
|
79
|
+
</button>
|
|
80
|
+
)}
|
|
81
|
+
</div>
|
|
73
82
|
</div>
|
|
74
|
-
|
|
83
|
+
)}
|
|
75
84
|
|
|
76
85
|
{/* Content */}
|
|
77
86
|
<div className="no-scrollbar flex min-h-0 flex-1 flex-col overflow-y-auto">{children}</div>
|