@pascal-app/editor 0.4.0 → 0.6.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 +8 -7
- package/src/components/editor/editor-layout-v2.tsx +9 -0
- package/src/components/editor/floating-action-menu.tsx +341 -48
- package/src/components/editor/floating-building-action-menu.tsx +70 -0
- package/src/components/editor/floorplan-panel.tsx +1350 -722
- package/src/components/editor/index.tsx +221 -167
- package/src/components/editor/node-action-menu.tsx +40 -11
- package/src/components/editor/selection-manager.tsx +238 -10
- package/src/components/editor/site-edge-labels.tsx +9 -3
- package/src/components/editor/thumbnail-generator.tsx +422 -79
- package/src/components/editor/wall-measurement-label.tsx +120 -32
- 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/systems/stair/stair-edit-system.tsx +27 -5
- package/src/components/tools/building/move-building-tool.tsx +157 -0
- package/src/components/tools/ceiling/ceiling-hole-editor.tsx +1 -0
- package/src/components/tools/ceiling/move-ceiling-tool.tsx +257 -0
- package/src/components/tools/door/door-math.ts +1 -1
- package/src/components/tools/door/door-tool.tsx +31 -7
- package/src/components/tools/door/move-door-tool.tsx +27 -8
- package/src/components/tools/fence/curve-fence-tool.tsx +179 -0
- package/src/components/tools/fence/fence-drafting.ts +137 -0
- package/src/components/tools/fence/fence-tool.tsx +190 -0
- package/src/components/tools/fence/move-fence-endpoint-tool.tsx +327 -0
- package/src/components/tools/fence/move-fence-tool.tsx +231 -0
- package/src/components/tools/item/item-tool.tsx +3 -3
- package/src/components/tools/item/move-tool.tsx +16 -0
- package/src/components/tools/item/placement-math.ts +14 -6
- package/src/components/tools/item/placement-strategies.ts +17 -9
- package/src/components/tools/item/use-placement-coordinator.tsx +123 -16
- package/src/components/tools/roof/move-roof-tool.tsx +90 -26
- package/src/components/tools/roof/roof-tool.tsx +6 -6
- package/src/components/tools/select/box-select-tool.tsx +2 -2
- package/src/components/tools/shared/polygon-editor.tsx +98 -8
- package/src/components/tools/slab/move-slab-tool.tsx +182 -0
- package/src/components/tools/slab/slab-hole-editor.tsx +1 -0
- package/src/components/tools/slab/slab-tool.tsx +4 -4
- package/src/components/tools/stair/stair-defaults.ts +10 -0
- package/src/components/tools/stair/stair-tool.tsx +39 -8
- package/src/components/tools/tool-manager.tsx +54 -14
- package/src/components/tools/wall/curve-wall-tool.tsx +176 -0
- package/src/components/tools/wall/move-wall-endpoint-tool.tsx +322 -0
- package/src/components/tools/wall/move-wall-tool.tsx +356 -0
- package/src/components/tools/wall/wall-drafting.ts +331 -9
- package/src/components/tools/wall/wall-tool.tsx +19 -29
- package/src/components/tools/window/move-window-tool.tsx +27 -8
- package/src/components/tools/window/window-math.ts +1 -1
- package/src/components/tools/window/window-tool.tsx +31 -7
- package/src/components/tools/zone/zone-tool.tsx +7 -7
- package/src/components/ui/action-menu/control-modes.tsx +9 -4
- package/src/components/ui/action-menu/structure-tools.tsx +1 -0
- package/src/components/ui/command-palette/editor-commands.tsx +9 -4
- package/src/components/ui/command-palette/index.tsx +0 -1
- package/src/components/ui/controls/material-picker.tsx +127 -94
- package/src/components/ui/controls/slider-control.tsx +28 -14
- package/src/components/ui/helpers/building-helper.tsx +32 -0
- package/src/components/ui/helpers/helper-manager.tsx +2 -0
- package/src/components/ui/item-catalog/catalog-items.tsx +5 -0
- package/src/components/ui/panels/ceiling-panel.tsx +61 -17
- package/src/components/ui/panels/door-panel.tsx +5 -5
- package/src/components/ui/panels/fence-panel.tsx +269 -0
- package/src/components/ui/panels/item-panel.tsx +5 -5
- package/src/components/ui/panels/panel-manager.tsx +32 -27
- package/src/components/ui/panels/reference-panel.tsx +5 -4
- package/src/components/ui/panels/roof-panel.tsx +91 -22
- package/src/components/ui/panels/roof-segment-panel.tsx +23 -13
- package/src/components/ui/panels/slab-panel.tsx +63 -15
- package/src/components/ui/panels/stair-panel.tsx +377 -50
- package/src/components/ui/panels/stair-segment-panel.tsx +28 -17
- package/src/components/ui/panels/wall-panel.tsx +159 -11
- package/src/components/ui/panels/window-panel.tsx +5 -7
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +28 -17
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +65 -53
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +40 -25
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +69 -0
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +88 -72
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +14 -13
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +64 -53
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +32 -23
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +72 -51
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +40 -37
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +72 -51
- package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +13 -13
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +20 -17
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +62 -54
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +40 -25
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +27 -28
- package/src/components/ui/viewer-toolbar.tsx +55 -2
- package/src/components/viewer-overlay.tsx +26 -19
- package/src/hooks/use-auto-save.ts +3 -6
- package/src/hooks/use-contextual-tools.ts +25 -16
- package/src/hooks/use-grid-events.ts +13 -1
- package/src/hooks/use-keyboard.ts +7 -2
- package/src/index.tsx +2 -1
- package/src/lib/history.ts +20 -0
- package/src/lib/sfx-player.ts +96 -13
- package/src/store/use-editor.tsx +125 -10
|
@@ -16,7 +16,6 @@ import { sfxEmitter } from '../../../lib/sfx-bus'
|
|
|
16
16
|
import useEditor from '../../../store/use-editor'
|
|
17
17
|
import { ActionButton, ActionGroup } from '../controls/action-button'
|
|
18
18
|
import { MaterialPicker } from '../controls/material-picker'
|
|
19
|
-
import { MetricControl } from '../controls/metric-control'
|
|
20
19
|
import { PanelSection } from '../controls/panel-section'
|
|
21
20
|
import { SegmentedControl } from '../controls/segmented-control'
|
|
22
21
|
import { SliderControl } from '../controls/slider-control'
|
|
@@ -36,16 +35,14 @@ const ROOF_TYPE_OPTIONS_2: { label: string; value: RoofType }[] = [
|
|
|
36
35
|
]
|
|
37
36
|
|
|
38
37
|
export function RoofSegmentPanel() {
|
|
39
|
-
const
|
|
38
|
+
const selectedId = useViewer((s) => s.selection.selectedIds[0])
|
|
40
39
|
const setSelection = useViewer((s) => s.setSelection)
|
|
41
|
-
const nodes = useScene((s) => s.nodes)
|
|
42
40
|
const updateNode = useScene((s) => s.updateNode)
|
|
43
41
|
const setMovingNode = useEditor((s) => s.setMovingNode)
|
|
44
42
|
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
: undefined
|
|
43
|
+
const node = useScene((s) =>
|
|
44
|
+
selectedId ? (s.nodes[selectedId as AnyNode['id']] as RoofSegmentNode | undefined) : undefined,
|
|
45
|
+
)
|
|
49
46
|
|
|
50
47
|
const handleUpdate = useCallback(
|
|
51
48
|
(updates: Partial<RoofSegmentNode>) => {
|
|
@@ -57,7 +54,14 @@ export function RoofSegmentPanel() {
|
|
|
57
54
|
|
|
58
55
|
const handleMaterialChange = useCallback(
|
|
59
56
|
(material: MaterialSchema) => {
|
|
60
|
-
handleUpdate({ material })
|
|
57
|
+
handleUpdate({ material, materialPreset: undefined })
|
|
58
|
+
},
|
|
59
|
+
[handleUpdate],
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
const handleMaterialPresetChange = useCallback(
|
|
63
|
+
(materialPreset: string) => {
|
|
64
|
+
handleUpdate({ materialPreset, material: undefined })
|
|
61
65
|
},
|
|
62
66
|
[handleUpdate],
|
|
63
67
|
)
|
|
@@ -117,7 +121,7 @@ export function RoofSegmentPanel() {
|
|
|
117
121
|
}
|
|
118
122
|
}, [selectedId, node, setSelection])
|
|
119
123
|
|
|
120
|
-
if (!node
|
|
124
|
+
if (!(node && node.type === 'roof-segment' && selectedId)) return null
|
|
121
125
|
|
|
122
126
|
return (
|
|
123
127
|
<PanelWrapper
|
|
@@ -230,7 +234,7 @@ export function RoofSegmentPanel() {
|
|
|
230
234
|
</PanelSection>
|
|
231
235
|
|
|
232
236
|
<PanelSection title="Position">
|
|
233
|
-
<
|
|
237
|
+
<SliderControl
|
|
234
238
|
label="X"
|
|
235
239
|
max={50}
|
|
236
240
|
min={-50}
|
|
@@ -244,7 +248,7 @@ export function RoofSegmentPanel() {
|
|
|
244
248
|
unit="m"
|
|
245
249
|
value={Math.round(node.position[0] * 100) / 100}
|
|
246
250
|
/>
|
|
247
|
-
<
|
|
251
|
+
<SliderControl
|
|
248
252
|
label="Y"
|
|
249
253
|
max={50}
|
|
250
254
|
min={-50}
|
|
@@ -258,7 +262,7 @@ export function RoofSegmentPanel() {
|
|
|
258
262
|
unit="m"
|
|
259
263
|
value={Math.round(node.position[1] * 100) / 100}
|
|
260
264
|
/>
|
|
261
|
-
<
|
|
265
|
+
<SliderControl
|
|
262
266
|
label="Z"
|
|
263
267
|
max={50}
|
|
264
268
|
min={-50}
|
|
@@ -319,7 +323,13 @@ export function RoofSegmentPanel() {
|
|
|
319
323
|
</ActionGroup>
|
|
320
324
|
</PanelSection>
|
|
321
325
|
<PanelSection title="Material">
|
|
322
|
-
<MaterialPicker
|
|
326
|
+
<MaterialPicker
|
|
327
|
+
nodeType="roof-segment"
|
|
328
|
+
onChange={handleMaterialChange}
|
|
329
|
+
onSelectMaterialPreset={handleMaterialPresetChange}
|
|
330
|
+
selectedMaterialPreset={node.materialPreset}
|
|
331
|
+
value={node.material}
|
|
332
|
+
/>
|
|
323
333
|
</PanelSection>
|
|
324
334
|
</PanelWrapper>
|
|
325
335
|
)
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import { type AnyNode, type MaterialSchema, type SlabNode, useScene } from '@pascal-app/core'
|
|
4
4
|
import { useViewer } from '@pascal-app/viewer'
|
|
5
|
-
import { Edit, Plus, Trash2 } from 'lucide-react'
|
|
5
|
+
import { Edit, Move, Plus, Trash2 } from 'lucide-react'
|
|
6
6
|
import { useCallback, useEffect } from 'react'
|
|
7
|
+
import { sfxEmitter } from '../../../lib/sfx-bus'
|
|
7
8
|
import useEditor from '../../../store/use-editor'
|
|
8
9
|
import { ActionButton, ActionGroup } from '../controls/action-button'
|
|
9
10
|
import { MaterialPicker } from '../controls/material-picker'
|
|
@@ -12,15 +13,16 @@ import { SliderControl } from '../controls/slider-control'
|
|
|
12
13
|
import { PanelWrapper } from './panel-wrapper'
|
|
13
14
|
|
|
14
15
|
export function SlabPanel() {
|
|
15
|
-
const
|
|
16
|
+
const selectedId = useViewer((s) => s.selection.selectedIds[0])
|
|
16
17
|
const setSelection = useViewer((s) => s.setSelection)
|
|
17
|
-
const nodes = useScene((s) => s.nodes)
|
|
18
18
|
const updateNode = useScene((s) => s.updateNode)
|
|
19
19
|
const editingHole = useEditor((s) => s.editingHole)
|
|
20
20
|
const setEditingHole = useEditor((s) => s.setEditingHole)
|
|
21
|
+
const setMovingNode = useEditor((s) => s.setMovingNode)
|
|
21
22
|
|
|
22
|
-
const
|
|
23
|
-
|
|
23
|
+
const node = useScene((s) =>
|
|
24
|
+
selectedId ? (s.nodes[selectedId as AnyNode['id']] as SlabNode | undefined) : undefined,
|
|
25
|
+
)
|
|
24
26
|
|
|
25
27
|
const handleUpdate = useCallback(
|
|
26
28
|
(updates: Partial<SlabNode>) => {
|
|
@@ -30,9 +32,16 @@ export function SlabPanel() {
|
|
|
30
32
|
[selectedId, updateNode],
|
|
31
33
|
)
|
|
32
34
|
|
|
33
|
-
const
|
|
35
|
+
const handleMaterialPresetChange = useCallback(
|
|
36
|
+
(materialPreset: string) => {
|
|
37
|
+
handleUpdate({ materialPreset, material: undefined })
|
|
38
|
+
},
|
|
39
|
+
[handleUpdate],
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
const handleCustomMaterialChange = useCallback(
|
|
34
43
|
(material: MaterialSchema) => {
|
|
35
|
-
handleUpdate({ material })
|
|
44
|
+
handleUpdate({ material, materialPreset: undefined })
|
|
36
45
|
},
|
|
37
46
|
[handleUpdate],
|
|
38
47
|
)
|
|
@@ -75,7 +84,13 @@ export function SlabPanel() {
|
|
|
75
84
|
[cx - holeSize, cz + holeSize],
|
|
76
85
|
]
|
|
77
86
|
const currentHoles = node?.holes || []
|
|
78
|
-
|
|
87
|
+
const currentMetadata = currentHoles.map(
|
|
88
|
+
(_, index) => node?.holeMetadata?.[index] ?? { source: 'manual' as const },
|
|
89
|
+
)
|
|
90
|
+
handleUpdate({
|
|
91
|
+
holes: [...currentHoles, newHole],
|
|
92
|
+
holeMetadata: [...currentMetadata, { source: 'manual' }],
|
|
93
|
+
})
|
|
79
94
|
setEditingHole({ nodeId: selectedId, holeIndex: currentHoles.length })
|
|
80
95
|
}, [node, selectedId, handleUpdate, setEditingHole])
|
|
81
96
|
|
|
@@ -91,16 +106,28 @@ export function SlabPanel() {
|
|
|
91
106
|
(index: number) => {
|
|
92
107
|
if (!selectedId) return
|
|
93
108
|
const currentHoles = node?.holes || []
|
|
109
|
+
if (node?.holeMetadata?.[index]?.source === 'stair') return
|
|
94
110
|
const newHoles = currentHoles.filter((_, i) => i !== index)
|
|
95
|
-
|
|
111
|
+
const currentMetadata = currentHoles.map(
|
|
112
|
+
(_, metadataIndex) => node?.holeMetadata?.[metadataIndex] ?? { source: 'manual' as const },
|
|
113
|
+
)
|
|
114
|
+
const newMetadata = currentMetadata.filter((_, i) => i !== index)
|
|
115
|
+
handleUpdate({ holes: newHoles, holeMetadata: newMetadata })
|
|
96
116
|
if (editingHole?.nodeId === selectedId && editingHole?.holeIndex === index) {
|
|
97
117
|
setEditingHole(null)
|
|
98
118
|
}
|
|
99
119
|
},
|
|
100
|
-
[selectedId, node?.holes, handleUpdate, editingHole, setEditingHole],
|
|
120
|
+
[selectedId, node?.holes, node?.holeMetadata, handleUpdate, editingHole, setEditingHole],
|
|
101
121
|
)
|
|
102
122
|
|
|
103
|
-
|
|
123
|
+
const handleMove = useCallback(() => {
|
|
124
|
+
if (!node) return
|
|
125
|
+
sfxEmitter.emit('sfx:item-pick')
|
|
126
|
+
setMovingNode(node)
|
|
127
|
+
setSelection({ selectedIds: [] })
|
|
128
|
+
}, [node, setMovingNode, setSelection])
|
|
129
|
+
|
|
130
|
+
if (!(node && node.type === 'slab' && selectedId)) return null
|
|
104
131
|
|
|
105
132
|
const calculateArea = (polygon: Array<[number, number]>): number => {
|
|
106
133
|
if (polygon.length < 3) return 0
|
|
@@ -108,8 +135,11 @@ export function SlabPanel() {
|
|
|
108
135
|
const n = polygon.length
|
|
109
136
|
for (let i = 0; i < n; i++) {
|
|
110
137
|
const j = (i + 1) % n
|
|
111
|
-
|
|
112
|
-
|
|
138
|
+
const current = polygon[i]
|
|
139
|
+
const next = polygon[j]
|
|
140
|
+
if (!(current && next)) continue
|
|
141
|
+
area += current[0] * next[1]
|
|
142
|
+
area -= next[0] * current[1]
|
|
113
143
|
}
|
|
114
144
|
return Math.abs(area) / 2
|
|
115
145
|
}
|
|
@@ -157,6 +187,8 @@ export function SlabPanel() {
|
|
|
157
187
|
const holeArea = calculateArea(hole)
|
|
158
188
|
const isEditing =
|
|
159
189
|
editingHole?.nodeId === selectedId && editingHole?.holeIndex === index
|
|
190
|
+
const source = node.holeMetadata?.[index]?.source ?? 'manual'
|
|
191
|
+
const isAutoHole = source === 'stair'
|
|
160
192
|
return (
|
|
161
193
|
<div
|
|
162
194
|
className={`flex items-center justify-between rounded-lg border p-2 transition-colors ${
|
|
@@ -173,7 +205,8 @@ export function SlabPanel() {
|
|
|
173
205
|
Hole {index + 1} {isEditing && '(Editing)'}
|
|
174
206
|
</p>
|
|
175
207
|
<p className="text-[10px] text-muted-foreground">
|
|
176
|
-
{holeArea.toFixed(2)} m² · {hole.length} pts
|
|
208
|
+
{holeArea.toFixed(2)} m² · {hole.length} pts ·{' '}
|
|
209
|
+
{isAutoHole ? 'Auto stair cutout' : 'Manual'}
|
|
177
210
|
</p>
|
|
178
211
|
</div>
|
|
179
212
|
<div className="flex items-center gap-1">
|
|
@@ -183,6 +216,10 @@ export function SlabPanel() {
|
|
|
183
216
|
label="Done"
|
|
184
217
|
onClick={() => setEditingHole(null)}
|
|
185
218
|
/>
|
|
219
|
+
) : isAutoHole ? (
|
|
220
|
+
<div className="rounded-md bg-[#2C2C2E] px-2 py-1 text-[10px] text-muted-foreground">
|
|
221
|
+
Auto
|
|
222
|
+
</div>
|
|
186
223
|
) : (
|
|
187
224
|
<>
|
|
188
225
|
<button
|
|
@@ -221,7 +258,18 @@ export function SlabPanel() {
|
|
|
221
258
|
</div>
|
|
222
259
|
</PanelSection>
|
|
223
260
|
<PanelSection title="Material">
|
|
224
|
-
<MaterialPicker
|
|
261
|
+
<MaterialPicker
|
|
262
|
+
nodeType="slab"
|
|
263
|
+
onChange={handleCustomMaterialChange}
|
|
264
|
+
onSelectMaterialPreset={handleMaterialPresetChange}
|
|
265
|
+
selectedMaterialPreset={node.materialPreset}
|
|
266
|
+
value={node.material}
|
|
267
|
+
/>
|
|
268
|
+
</PanelSection>
|
|
269
|
+
<PanelSection title="Actions">
|
|
270
|
+
<ActionGroup>
|
|
271
|
+
<ActionButton icon={<Move className="h-3.5 w-3.5" />} label="Move" onClick={handleMove} />
|
|
272
|
+
</ActionGroup>
|
|
225
273
|
</PanelSection>
|
|
226
274
|
</PanelWrapper>
|
|
227
275
|
)
|