@pascal-app/editor 0.5.1 → 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 +255 -34
- package/src/components/editor/floating-building-action-menu.tsx +4 -3
- package/src/components/editor/floorplan-panel.tsx +1323 -713
- package/src/components/editor/index.tsx +2 -0
- package/src/components/editor/node-action-menu.tsx +14 -1
- package/src/components/editor/selection-manager.tsx +200 -8
- package/src/components/editor/site-edge-labels.tsx +9 -3
- package/src/components/editor/thumbnail-generator.tsx +319 -157
- 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/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-tool.tsx +12 -0
- package/src/components/tools/door/move-door-tool.tsx +10 -0
- package/src/components/tools/fence/curve-fence-tool.tsx +179 -0
- package/src/components/tools/fence/fence-drafting.ts +19 -7
- package/src/components/tools/fence/move-fence-endpoint-tool.tsx +327 -0
- package/src/components/tools/fence/move-fence-tool.tsx +8 -0
- package/src/components/tools/item/move-tool.tsx +9 -0
- package/src/components/tools/item/placement-math.ts +14 -6
- package/src/components/tools/item/placement-strategies.ts +2 -2
- package/src/components/tools/item/use-placement-coordinator.tsx +42 -10
- package/src/components/tools/roof/move-roof-tool.tsx +89 -28
- 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/stair/stair-tool.tsx +11 -3
- package/src/components/tools/tool-manager.tsx +12 -0
- 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/window/move-window-tool.tsx +10 -0
- package/src/components/tools/window/window-tool.tsx +12 -0
- package/src/components/ui/action-menu/control-modes.tsx +9 -4
- 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/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 +97 -12
- package/src/components/ui/panels/item-panel.tsx +5 -5
- package/src/components/ui/panels/panel-manager.tsx +31 -29
- 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 +173 -19
- 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 +7 -3
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +7 -3
- 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 +29 -32
- 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 +7 -3
- 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/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 +3 -3
- 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 +7 -3
- package/src/components/ui/viewer-toolbar.tsx +55 -2
- package/src/components/viewer-overlay.tsx +25 -19
- package/src/hooks/use-contextual-tools.ts +14 -13
- package/src/hooks/use-keyboard.ts +3 -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 +118 -10
package/src/store/use-editor.tsx
CHANGED
|
@@ -1,23 +1,31 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import type { AssetInput } from '@pascal-app/core'
|
|
4
3
|
import {
|
|
4
|
+
type AnyNodeId,
|
|
5
|
+
type AssetInput,
|
|
5
6
|
type BuildingNode,
|
|
7
|
+
type CeilingNode,
|
|
6
8
|
type DoorNode,
|
|
7
9
|
type FenceNode,
|
|
8
10
|
type ItemNode,
|
|
9
11
|
type LevelNode,
|
|
12
|
+
type RoofSurfaceMaterialRole,
|
|
10
13
|
type RoofNode,
|
|
11
14
|
type RoofSegmentNode,
|
|
15
|
+
type SlabNode,
|
|
12
16
|
type Space,
|
|
17
|
+
type StairSurfaceMaterialRole,
|
|
13
18
|
type StairNode,
|
|
14
19
|
type StairSegmentNode,
|
|
15
20
|
useScene,
|
|
21
|
+
type WallNode,
|
|
22
|
+
type WallSurfaceSide,
|
|
16
23
|
type WindowNode,
|
|
17
24
|
} from '@pascal-app/core'
|
|
18
25
|
import { useViewer } from '@pascal-app/viewer'
|
|
19
26
|
import { create } from 'zustand'
|
|
20
27
|
import { persist } from 'zustand/middleware'
|
|
28
|
+
import { getDefaultCatalogItem } from '../components/ui/item-catalog/catalog-items'
|
|
21
29
|
|
|
22
30
|
const DEFAULT_ACTIVE_SIDEBAR_PANEL = 'site'
|
|
23
31
|
const DEFAULT_FLOORPLAN_PANE_RATIO = 0.5
|
|
@@ -66,10 +74,28 @@ export type CatalogCategory =
|
|
|
66
74
|
export type StructureLayer = 'zones' | 'elements'
|
|
67
75
|
|
|
68
76
|
export type FloorplanSelectionTool = 'click' | 'marquee'
|
|
77
|
+
export type GridSnapStep = 0.5 | 0.25 | 0.1 | 0.05
|
|
69
78
|
|
|
70
79
|
// Combined tool type
|
|
71
80
|
export type Tool = SiteTool | StructureTool | FurnishTool
|
|
72
81
|
|
|
82
|
+
export type MovingWallEndpoint = {
|
|
83
|
+
wall: WallNode
|
|
84
|
+
endpoint: 'start' | 'end'
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export type MovingFenceEndpoint = {
|
|
88
|
+
fence: FenceNode
|
|
89
|
+
endpoint: 'start' | 'end'
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export type MaterialTargetRole = WallSurfaceSide | StairSurfaceMaterialRole | RoofSurfaceMaterialRole
|
|
93
|
+
|
|
94
|
+
export type SelectedMaterialTarget = {
|
|
95
|
+
nodeId: AnyNodeId
|
|
96
|
+
role: MaterialTargetRole
|
|
97
|
+
}
|
|
98
|
+
|
|
73
99
|
type EditorState = {
|
|
74
100
|
phase: Phase
|
|
75
101
|
setPhase: (phase: Phase) => void
|
|
@@ -88,6 +114,9 @@ type EditorState = {
|
|
|
88
114
|
| WindowNode
|
|
89
115
|
| DoorNode
|
|
90
116
|
| FenceNode
|
|
117
|
+
| CeilingNode
|
|
118
|
+
| SlabNode
|
|
119
|
+
| WallNode
|
|
91
120
|
| RoofNode
|
|
92
121
|
| RoofSegmentNode
|
|
93
122
|
| StairNode
|
|
@@ -100,6 +129,9 @@ type EditorState = {
|
|
|
100
129
|
| WindowNode
|
|
101
130
|
| DoorNode
|
|
102
131
|
| FenceNode
|
|
132
|
+
| CeilingNode
|
|
133
|
+
| SlabNode
|
|
134
|
+
| WallNode
|
|
103
135
|
| RoofNode
|
|
104
136
|
| RoofSegmentNode
|
|
105
137
|
| StairNode
|
|
@@ -107,6 +139,16 @@ type EditorState = {
|
|
|
107
139
|
| BuildingNode
|
|
108
140
|
| null,
|
|
109
141
|
) => void
|
|
142
|
+
movingWallEndpoint: MovingWallEndpoint | null
|
|
143
|
+
setMovingWallEndpoint: (value: MovingWallEndpoint | null) => void
|
|
144
|
+
movingFenceEndpoint: MovingFenceEndpoint | null
|
|
145
|
+
setMovingFenceEndpoint: (value: MovingFenceEndpoint | null) => void
|
|
146
|
+
curvingWall: WallNode | null
|
|
147
|
+
setCurvingWall: (wall: WallNode | null) => void
|
|
148
|
+
curvingFence: FenceNode | null
|
|
149
|
+
setCurvingFence: (fence: FenceNode | null) => void
|
|
150
|
+
selectedMaterialTarget: SelectedMaterialTarget | null
|
|
151
|
+
setSelectedMaterialTarget: (target: SelectedMaterialTarget | null) => void
|
|
110
152
|
selectedReferenceId: string | null
|
|
111
153
|
setSelectedReferenceId: (id: string | null) => void
|
|
112
154
|
// Space detection for cutaway mode
|
|
@@ -131,6 +173,8 @@ type EditorState = {
|
|
|
131
173
|
setFloorplanHovered: (hovered: boolean) => void
|
|
132
174
|
floorplanSelectionTool: FloorplanSelectionTool
|
|
133
175
|
setFloorplanSelectionTool: (tool: FloorplanSelectionTool) => void
|
|
176
|
+
gridSnapStep: GridSnapStep
|
|
177
|
+
setGridSnapStep: (step: GridSnapStep) => void
|
|
134
178
|
// First-person walkthrough mode (street view)
|
|
135
179
|
isFirstPersonMode: boolean
|
|
136
180
|
_viewModeBeforeFirstPerson: ViewMode | null
|
|
@@ -151,7 +195,11 @@ export type PersistedEditorUiState = Pick<
|
|
|
151
195
|
|
|
152
196
|
type PersistedEditorLayoutState = Pick<
|
|
153
197
|
EditorState,
|
|
154
|
-
|
|
198
|
+
| 'activeSidebarPanel'
|
|
199
|
+
| 'floorplanPaneRatio'
|
|
200
|
+
| 'splitOrientation'
|
|
201
|
+
| 'floorplanSelectionTool'
|
|
202
|
+
| 'gridSnapStep'
|
|
155
203
|
>
|
|
156
204
|
type PersistedEditorState = PersistedEditorUiState & PersistedEditorLayoutState
|
|
157
205
|
|
|
@@ -170,8 +218,11 @@ export const DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE: PersistedEditorLayoutState =
|
|
|
170
218
|
floorplanPaneRatio: DEFAULT_FLOORPLAN_PANE_RATIO,
|
|
171
219
|
splitOrientation: 'horizontal',
|
|
172
220
|
floorplanSelectionTool: 'click',
|
|
221
|
+
gridSnapStep: 0.5,
|
|
173
222
|
}
|
|
174
223
|
|
|
224
|
+
const GRID_SNAP_STEPS: GridSnapStep[] = [0.5, 0.25, 0.1, 0.05]
|
|
225
|
+
|
|
175
226
|
function normalizeModeForPhase(phase: Phase, mode: Mode | undefined): Mode {
|
|
176
227
|
if (phase === 'site') {
|
|
177
228
|
return 'select'
|
|
@@ -274,6 +325,9 @@ function normalizePersistedEditorLayoutState(
|
|
|
274
325
|
floorplanPaneRatio: normalizeFloorplanPaneRatio(state?.floorplanPaneRatio),
|
|
275
326
|
splitOrientation: state?.splitOrientation === 'vertical' ? 'vertical' : 'horizontal',
|
|
276
327
|
floorplanSelectionTool: state?.floorplanSelectionTool === 'marquee' ? 'marquee' : 'click',
|
|
328
|
+
gridSnapStep: GRID_SNAP_STEPS.includes(state?.gridSnapStep as GridSnapStep)
|
|
329
|
+
? (state?.gridSnapStep as GridSnapStep)
|
|
330
|
+
: DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE.gridSnapStep,
|
|
277
331
|
}
|
|
278
332
|
}
|
|
279
333
|
|
|
@@ -333,6 +387,10 @@ export function selectDefaultBuildingAndLevel() {
|
|
|
333
387
|
}
|
|
334
388
|
}
|
|
335
389
|
|
|
390
|
+
function getDefaultSelectedItemForCategory(category: CatalogCategory | null): AssetInput | null {
|
|
391
|
+
return getDefaultCatalogItem(category)
|
|
392
|
+
}
|
|
393
|
+
|
|
336
394
|
const useEditor = create<EditorState>()(
|
|
337
395
|
persist(
|
|
338
396
|
(set, get) => ({
|
|
@@ -354,7 +412,11 @@ const useEditor = create<EditorState>()(
|
|
|
354
412
|
} else if (phase === 'structure') {
|
|
355
413
|
set({ tool: 'wall', catalogCategory: null })
|
|
356
414
|
} else if (phase === 'furnish') {
|
|
357
|
-
set({
|
|
415
|
+
set({
|
|
416
|
+
tool: 'item',
|
|
417
|
+
catalogCategory: 'furniture',
|
|
418
|
+
selectedItem: getDefaultSelectedItemForCategory('furniture'),
|
|
419
|
+
})
|
|
358
420
|
}
|
|
359
421
|
} else {
|
|
360
422
|
// Reset to select mode and clear tool/catalog when switching phases
|
|
@@ -394,8 +456,15 @@ const useEditor = create<EditorState>()(
|
|
|
394
456
|
} else if (phase === 'structure' && structureLayer === 'elements') {
|
|
395
457
|
set({ tool: 'wall' })
|
|
396
458
|
} else if (phase === 'furnish') {
|
|
397
|
-
set({
|
|
459
|
+
set({
|
|
460
|
+
tool: 'item',
|
|
461
|
+
catalogCategory: 'furniture',
|
|
462
|
+
selectedItem: getDefaultSelectedItemForCategory('furniture'),
|
|
463
|
+
})
|
|
398
464
|
}
|
|
465
|
+
} else if (phase === 'furnish' && tool === 'item' && !get().selectedItem) {
|
|
466
|
+
const category = get().catalogCategory ?? 'furniture'
|
|
467
|
+
set({ selectedItem: getDefaultSelectedItemForCategory(category) })
|
|
399
468
|
}
|
|
400
469
|
}
|
|
401
470
|
// When leaving build mode, clear tool
|
|
@@ -423,13 +492,27 @@ const useEditor = create<EditorState>()(
|
|
|
423
492
|
})
|
|
424
493
|
},
|
|
425
494
|
catalogCategory: DEFAULT_PERSISTED_EDITOR_UI_STATE.catalogCategory,
|
|
426
|
-
setCatalogCategory: (category) =>
|
|
495
|
+
setCatalogCategory: (category) =>
|
|
496
|
+
set((state) => ({
|
|
497
|
+
catalogCategory: category,
|
|
498
|
+
selectedItem:
|
|
499
|
+
category !== null &&
|
|
500
|
+
state.phase === 'furnish' &&
|
|
501
|
+
state.mode === 'build' &&
|
|
502
|
+
state.tool === 'item'
|
|
503
|
+
? getDefaultSelectedItemForCategory(category)
|
|
504
|
+
: state.selectedItem,
|
|
505
|
+
})),
|
|
427
506
|
selectedItem: null,
|
|
428
507
|
setSelectedItem: (item) => set({ selectedItem: item }),
|
|
429
508
|
movingNode: null as
|
|
430
509
|
| ItemNode
|
|
431
510
|
| WindowNode
|
|
432
511
|
| DoorNode
|
|
512
|
+
| FenceNode
|
|
513
|
+
| CeilingNode
|
|
514
|
+
| SlabNode
|
|
515
|
+
| WallNode
|
|
433
516
|
| RoofNode
|
|
434
517
|
| RoofSegmentNode
|
|
435
518
|
| StairNode
|
|
@@ -437,6 +520,16 @@ const useEditor = create<EditorState>()(
|
|
|
437
520
|
| BuildingNode
|
|
438
521
|
| null,
|
|
439
522
|
setMovingNode: (node) => set({ movingNode: node }),
|
|
523
|
+
movingWallEndpoint: null,
|
|
524
|
+
setMovingWallEndpoint: (value) => set({ movingWallEndpoint: value }),
|
|
525
|
+
movingFenceEndpoint: null,
|
|
526
|
+
setMovingFenceEndpoint: (value) => set({ movingFenceEndpoint: value }),
|
|
527
|
+
curvingWall: null,
|
|
528
|
+
setCurvingWall: (wall) => set({ curvingWall: wall }),
|
|
529
|
+
curvingFence: null,
|
|
530
|
+
setCurvingFence: (fence) => set({ curvingFence: fence }),
|
|
531
|
+
selectedMaterialTarget: null,
|
|
532
|
+
setSelectedMaterialTarget: (target) => set({ selectedMaterialTarget: target }),
|
|
440
533
|
selectedReferenceId: null,
|
|
441
534
|
setSelectedReferenceId: (id) => set({ selectedReferenceId: id }),
|
|
442
535
|
spaces: {},
|
|
@@ -468,6 +561,8 @@ const useEditor = create<EditorState>()(
|
|
|
468
561
|
setFloorplanHovered: (hovered) => set({ isFloorplanHovered: hovered }),
|
|
469
562
|
floorplanSelectionTool: 'click' as FloorplanSelectionTool,
|
|
470
563
|
setFloorplanSelectionTool: (tool) => set({ floorplanSelectionTool: tool }),
|
|
564
|
+
gridSnapStep: DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE.gridSnapStep,
|
|
565
|
+
setGridSnapStep: (step) => set({ gridSnapStep: step }),
|
|
471
566
|
allowUndergroundCamera: false,
|
|
472
567
|
setAllowUndergroundCamera: (enabled) => set({ allowUndergroundCamera: enabled }),
|
|
473
568
|
isFirstPersonMode: false,
|
|
@@ -504,11 +599,23 @@ const useEditor = create<EditorState>()(
|
|
|
504
599
|
}),
|
|
505
600
|
{
|
|
506
601
|
name: 'pascal-editor-ui-preferences',
|
|
507
|
-
merge: (persistedState, currentState) =>
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
602
|
+
merge: (persistedState, currentState) => {
|
|
603
|
+
const mergedState = {
|
|
604
|
+
...currentState,
|
|
605
|
+
...normalizePersistedEditorUiState(persistedState as Partial<PersistedEditorState>),
|
|
606
|
+
...normalizePersistedEditorLayoutState(persistedState as Partial<PersistedEditorState>),
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
return {
|
|
610
|
+
...mergedState,
|
|
611
|
+
selectedItem:
|
|
612
|
+
mergedState.phase === 'furnish' &&
|
|
613
|
+
mergedState.mode === 'build' &&
|
|
614
|
+
mergedState.tool === 'item'
|
|
615
|
+
? getDefaultSelectedItemForCategory(mergedState.catalogCategory ?? 'furniture')
|
|
616
|
+
: currentState.selectedItem,
|
|
617
|
+
}
|
|
618
|
+
},
|
|
512
619
|
partialize: (state) => ({
|
|
513
620
|
phase: state.phase,
|
|
514
621
|
mode: state.mode,
|
|
@@ -521,6 +628,7 @@ const useEditor = create<EditorState>()(
|
|
|
521
628
|
floorplanPaneRatio: state.floorplanPaneRatio,
|
|
522
629
|
splitOrientation: state.splitOrientation,
|
|
523
630
|
floorplanSelectionTool: state.floorplanSelectionTool,
|
|
631
|
+
gridSnapStep: state.gridSnapStep,
|
|
524
632
|
}),
|
|
525
633
|
},
|
|
526
634
|
),
|