@pascal-app/editor 0.7.0 → 0.9.1
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 -13
- package/src/components/editor/alignment-3d-guide-layer.tsx +160 -0
- package/src/components/editor/custom-camera-controls.tsx +755 -25
- package/src/components/editor/editor-layout-mobile.tsx +3 -3
- package/src/components/editor/editor-layout-v2.tsx +62 -54
- package/src/components/editor/export-manager.tsx +32 -3
- package/src/components/editor/first-person/build-collider-world.test.ts +149 -0
- package/src/components/editor/first-person/build-collider-world.ts +203 -87
- package/src/components/editor/first-person/bvh-ecctrl.tsx +127 -62
- package/src/components/editor/first-person-controls.tsx +836 -31
- package/src/components/editor/floating-action-menu.tsx +287 -196
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floorplan-panel.tsx +5837 -12006
- package/src/components/editor/grid.tsx +51 -17
- package/src/components/editor/group-move-handle.tsx +316 -0
- package/src/components/editor/group-rotate-handle.tsx +382 -0
- package/src/components/editor/group-transform-shared.test.ts +205 -0
- package/src/components/editor/group-transform-shared.ts +256 -0
- package/src/components/editor/handles/handle-arrow.tsx +523 -0
- package/src/components/editor/handles/use-handle-drag.ts +213 -0
- package/src/components/editor/index.tsx +234 -190
- package/src/components/editor/measurement-pill.tsx +75 -0
- package/src/components/editor/node-arrow-handles.tsx +1453 -0
- package/src/components/editor/selection-manager.tsx +435 -158
- package/src/components/editor/site-edge-labels.tsx +44 -20
- package/src/components/editor/slab-hole-highlights.tsx +466 -0
- package/src/components/editor/snapshot-capture-overlay.tsx +539 -0
- package/src/components/editor/thumbnail-generator.tsx +27 -64
- package/src/components/editor/use-floorplan-background-placement.ts +122 -35
- package/src/components/editor/use-floorplan-hit-testing.ts +24 -0
- package/src/components/editor/use-floorplan-scene-data.ts +14 -2
- package/src/components/editor/wall-measurement-label.tsx +11 -6
- package/src/components/editor/wall-move-side-handles.tsx +855 -0
- package/src/components/editor/wall-opening-highlights.tsx +181 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +150 -0
- package/src/components/editor-2d/floorplan-action-menu-layer.tsx +6 -1
- package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +142 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +5 -3
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +227 -0
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +616 -0
- package/src/components/editor-2d/floorplan-render-context.tsx +62 -0
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +75 -0
- package/src/components/editor-2d/floorplan-wall-move-ghost-layer.tsx +47 -0
- package/src/components/editor-2d/renderers/floorplan-draft-layer.tsx +6 -1
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +266 -0
- package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +108 -61
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +59 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +1963 -0
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +29 -14
- package/src/components/editor-2d/svg-paths.ts +10 -2
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +30 -9
- package/src/components/systems/ceiling/ceiling-system.tsx +12 -8
- package/src/components/systems/roof/roof-edit-system.tsx +86 -12
- package/src/components/systems/stair/stair-edit-system.tsx +2 -2
- package/src/components/systems/zone/zone-label-editor-system.tsx +0 -0
- package/src/components/systems/zone/zone-system.tsx +0 -0
- package/src/components/tools/elevator/elevator-defaults.ts +8 -0
- package/src/components/tools/elevator/elevator-tool.tsx +349 -0
- package/src/components/tools/elevator/move-elevator-tool.tsx +252 -0
- package/src/components/tools/fence/fence-drafting.ts +64 -22
- package/src/components/tools/item/move-tool.tsx +40 -107
- package/src/components/tools/item/placement-math.test.ts +10 -0
- package/src/components/tools/item/placement-math.ts +33 -9
- package/src/components/tools/item/placement-strategies.ts +201 -25
- package/src/components/tools/item/placement-types.ts +10 -1
- package/src/components/tools/item/use-draft-node.ts +20 -2
- package/src/components/tools/item/use-placement-coordinator.tsx +700 -299
- package/src/components/tools/registry/move-registry-node-tool.tsx +602 -0
- package/src/components/tools/roof/roof-tool.tsx +108 -12
- package/src/components/tools/select/box-select-state.ts +77 -0
- package/src/components/tools/select/box-select-tool.tsx +324 -543
- package/src/components/tools/select/plane-box-select-tool.tsx +577 -0
- package/src/components/tools/select/screen-rectangle-selection.ts +84 -0
- package/src/components/tools/select/select-candidates.test.ts +109 -0
- package/src/components/tools/select/select-candidates.ts +85 -0
- package/src/components/tools/shared/affordance-dispatch.ts +30 -0
- package/src/components/tools/shared/drag-bounding-box.tsx +171 -0
- package/src/components/tools/shared/floor-stack-preview.ts +25 -0
- package/src/components/tools/shared/fresh-placement-visibility.ts +61 -0
- package/src/components/tools/shared/placement-box-geometry.ts +123 -0
- package/src/components/tools/shared/placement-box.tsx +123 -0
- package/src/components/tools/shared/polygon-editor.tsx +617 -167
- package/src/components/tools/shared/segment-angle.ts +57 -1
- package/src/components/tools/site/site-boundary-editor.tsx +464 -12
- package/src/components/tools/site/site-flag-model.ts +18 -0
- package/src/components/tools/stair/stair-defaults.ts +1 -1
- package/src/components/tools/stair/stair-tool.tsx +269 -35
- package/src/components/tools/tool-manager.tsx +172 -56
- package/src/components/tools/wall/wall-drafting.ts +98 -98
- package/src/components/tools/wall/wall-snap-geometry.test.ts +79 -0
- package/src/components/tools/wall/wall-snap-geometry.ts +270 -0
- package/src/components/tools/zone/zone-tool.tsx +36 -11
- package/src/components/ui/action-menu/action-button.tsx +21 -1
- package/src/components/ui/action-menu/camera-actions.tsx +0 -0
- package/src/components/ui/action-menu/control-modes.tsx +5 -76
- package/src/components/ui/action-menu/furnish-tools.tsx +6 -92
- package/src/components/ui/action-menu/index.tsx +33 -196
- package/src/components/ui/action-menu/structure-tools.tsx +13 -88
- package/src/components/ui/action-menu/view-toggles.tsx +262 -37
- package/src/components/ui/command-palette/editor-commands.tsx +24 -17
- package/src/components/ui/command-palette/index.tsx +6 -256
- package/src/components/ui/controls/material-paint-panel.tsx +83 -0
- package/src/components/ui/controls/material-picker.tsx +17 -35
- package/src/components/ui/controls/metric-control.tsx +33 -11
- package/src/components/ui/floating-level-selector.tsx +48 -11
- package/src/components/ui/helpers/helper-manager.tsx +21 -17
- package/src/components/ui/helpers/registered-tool-helper.tsx +25 -0
- package/src/components/ui/item-catalog/catalog-items.tsx +1743 -315
- package/src/components/ui/item-catalog/item-catalog.tsx +91 -46
- package/src/components/ui/level-duplicate-dialog.tsx +5 -6
- package/src/components/ui/panels/node-display.ts +1 -0
- package/src/components/ui/panels/paint-panel.tsx +66 -41
- package/src/components/ui/panels/panel-manager.tsx +33 -74
- package/src/components/ui/panels/panel-wrapper.tsx +217 -12
- package/src/components/ui/panels/parametric-inspector.tsx +469 -0
- package/src/components/ui/panels/reference-panel.tsx +28 -13
- package/src/components/ui/primitives/dropdown-menu.tsx +12 -8
- package/src/components/ui/primitives/number-input.tsx +1 -1
- package/src/components/ui/primitives/sidebar.tsx +0 -0
- package/src/components/ui/scene-loader.tsx +1 -3
- package/src/components/ui/sidebar/app-sidebar.tsx +0 -0
- package/src/components/ui/sidebar/icon-rail.tsx +0 -0
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +263 -0
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +404 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +59 -9
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +6 -1
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +259 -0
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -1
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +5 -2
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +83 -0
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +89 -0
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +75 -0
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +2 -2
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +83 -0
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +8 -12
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +2 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +93 -0
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +41 -5
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +128 -0
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +5 -2
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +83 -0
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +68 -34
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -1
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +52 -3
- package/src/components/ui/sidebar/tab-bar.tsx +61 -1
- package/src/components/ui/slider.tsx +1 -1
- package/src/components/viewer-overlay.tsx +169 -52
- package/src/components/viewer-zone-system.tsx +1 -1
- package/src/hooks/use-auto-save.ts +24 -3
- package/src/hooks/use-ceiling-events.ts +175 -0
- package/src/hooks/use-drag-action.ts +124 -0
- package/src/hooks/use-keyboard.ts +125 -62
- package/src/hooks/use-selection.ts +64 -0
- package/src/index.tsx +243 -4
- package/src/lib/constants.ts +6 -2
- package/src/lib/editor-api.ts +62 -0
- package/src/lib/elevator-support.ts +96 -0
- package/src/lib/floorplan/apply-alignment.ts +111 -0
- package/src/lib/floorplan/index.ts +7 -0
- package/src/lib/floorplan/selection-tool.ts +40 -0
- package/src/lib/floorplan/stairs.ts +12 -1
- package/src/lib/fresh-planar-placement.test.ts +102 -0
- package/src/lib/fresh-planar-placement.ts +63 -0
- package/src/lib/history.ts +2 -1
- package/src/lib/level-duplication.test.ts +0 -2
- package/src/lib/level-duplication.ts +55 -43
- package/src/lib/level-name.ts +11 -0
- package/src/lib/material-paint.ts +212 -20
- package/src/lib/placement-metadata.ts +29 -0
- package/src/lib/planar-cursor-placement.test.ts +43 -0
- package/src/lib/planar-cursor-placement.ts +42 -0
- package/src/lib/roof-duplication.ts +2 -2
- package/src/lib/scene-bounds.ts +1 -1
- package/src/lib/scene-clipboard.ts +267 -0
- package/src/lib/scene.ts +13 -4
- package/src/lib/sfx-bus.ts +25 -1
- package/src/lib/sfx-player.ts +75 -27
- package/src/lib/site-boundary.ts +1 -0
- package/src/lib/stair-duplication.ts +2 -2
- package/src/lib/stair-levels.test.ts +146 -0
- package/src/lib/stair-levels.ts +177 -0
- package/src/lib/world-grid-snap.ts +199 -0
- package/src/lib/zone-content.ts +130 -0
- package/src/store/use-alignment-guides.ts +21 -0
- package/src/store/use-editor.tsx +342 -91
- package/src/store/use-placement-preview.ts +31 -0
- package/src/store/use-wall-move-ghosts.ts +36 -0
- package/src/store/use-wall-snap-indicator.ts +32 -0
- package/tsconfig.json +2 -1
- package/src/components/editor/preset-thumbnail-generator.tsx +0 -125
- package/src/components/editor-2d/renderers/floorplan-roof-layer.tsx +0 -113
- package/src/components/feedback-dialog.tsx +0 -265
- package/src/components/pascal-radio.tsx +0 -280
- package/src/components/preview-button.tsx +0 -16
- package/src/components/tools/building/move-building-tool.tsx +0 -157
- package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +0 -43
- package/src/components/tools/ceiling/ceiling-hole-editor.tsx +0 -49
- package/src/components/tools/ceiling/ceiling-tool.tsx +0 -465
- package/src/components/tools/ceiling/move-ceiling-tool.tsx +0 -257
- package/src/components/tools/column/column-tool.tsx +0 -97
- package/src/components/tools/column/move-column-tool.tsx +0 -105
- package/src/components/tools/door/door-math.ts +0 -110
- package/src/components/tools/door/door-tool.tsx +0 -324
- package/src/components/tools/door/move-door-tool.tsx +0 -412
- package/src/components/tools/fence/curve-fence-tool.tsx +0 -179
- package/src/components/tools/fence/fence-tool.tsx +0 -346
- package/src/components/tools/fence/move-fence-endpoint-tool.tsx +0 -438
- package/src/components/tools/fence/move-fence-tool.tsx +0 -298
- package/src/components/tools/item/item-tool.tsx +0 -26
- package/src/components/tools/roof/move-roof-tool.tsx +0 -359
- package/src/components/tools/slab/move-slab-tool.tsx +0 -182
- package/src/components/tools/slab/slab-boundary-editor.tsx +0 -43
- package/src/components/tools/slab/slab-hole-editor.tsx +0 -49
- package/src/components/tools/slab/slab-tool.tsx +0 -322
- package/src/components/tools/spawn/move-spawn-tool.tsx +0 -101
- package/src/components/tools/spawn/spawn-tool.tsx +0 -130
- package/src/components/tools/wall/curve-wall-tool.tsx +0 -176
- package/src/components/tools/wall/move-wall-endpoint-tool.tsx +0 -423
- package/src/components/tools/wall/move-wall-tool.tsx +0 -356
- package/src/components/tools/wall/wall-tool.tsx +0 -332
- package/src/components/tools/window/move-window-tool.tsx +0 -447
- package/src/components/tools/window/window-math.ts +0 -117
- package/src/components/tools/window/window-tool.tsx +0 -332
- package/src/components/ui/helpers/ceiling-helper.tsx +0 -20
- package/src/components/ui/helpers/slab-helper.tsx +0 -20
- package/src/components/ui/helpers/wall-helper.tsx +0 -20
- package/src/components/ui/panels/ceiling-panel.tsx +0 -250
- package/src/components/ui/panels/column-panel.tsx +0 -715
- package/src/components/ui/panels/door-panel.tsx +0 -1292
- package/src/components/ui/panels/fence-panel.tsx +0 -227
- package/src/components/ui/panels/item-panel.tsx +0 -306
- package/src/components/ui/panels/presets/presets-popover.tsx +0 -511
- package/src/components/ui/panels/roof-panel.tsx +0 -230
- package/src/components/ui/panels/roof-segment-panel.tsx +0 -311
- package/src/components/ui/panels/slab-panel.tsx +0 -250
- package/src/components/ui/panels/spawn-panel.tsx +0 -155
- package/src/components/ui/panels/stair-panel.tsx +0 -525
- package/src/components/ui/panels/stair-segment-panel.tsx +0 -325
- package/src/components/ui/panels/wall-panel.tsx +0 -177
- package/src/components/ui/panels/window-panel.tsx +0 -960
- package/src/components/ui/viewer-toolbar.tsx +0 -436
- package/src/contexts/presets-context.tsx +0 -121
- package/src/hooks/use-contextual-tools.ts +0 -61
|
@@ -8,18 +8,9 @@ import {
|
|
|
8
8
|
useScene,
|
|
9
9
|
} from '@pascal-app/core'
|
|
10
10
|
import { type HoverStyles, InteractiveSystem, useViewer, Viewer } from '@pascal-app/viewer'
|
|
11
|
-
import {
|
|
12
|
-
memo,
|
|
13
|
-
type ReactNode,
|
|
14
|
-
useCallback,
|
|
15
|
-
useEffect,
|
|
16
|
-
useLayoutEffect,
|
|
17
|
-
useRef,
|
|
18
|
-
useState,
|
|
19
|
-
} from 'react'
|
|
11
|
+
import { memo, type ReactNode, useCallback, useEffect, useRef, useState } from 'react'
|
|
20
12
|
import { ViewerOverlay } from '../../components/viewer-overlay'
|
|
21
13
|
import { ViewerZoneSystem } from '../../components/viewer-zone-system'
|
|
22
|
-
import { type PresetsAdapter, PresetsProvider } from '../../contexts/presets-context'
|
|
23
14
|
import { type SaveStatus, useAutoSave } from '../../hooks/use-auto-save'
|
|
24
15
|
import { useKeyboard } from '../../hooks/use-keyboard'
|
|
25
16
|
import {
|
|
@@ -61,31 +52,38 @@ import { FloatingActionMenu } from './floating-action-menu'
|
|
|
61
52
|
import { FloatingBuildingActionMenu } from './floating-building-action-menu'
|
|
62
53
|
import { FloorplanPanel } from './floorplan-panel'
|
|
63
54
|
import { Grid } from './grid'
|
|
64
|
-
import {
|
|
55
|
+
import { GroupMoveHandle } from './group-move-handle'
|
|
56
|
+
import { GroupRotateHandle } from './group-rotate-handle'
|
|
57
|
+
import { NodeArrowHandles } from './node-arrow-handles'
|
|
65
58
|
import { SelectionManager } from './selection-manager'
|
|
66
59
|
import { SiteEdgeLabels } from './site-edge-labels'
|
|
60
|
+
import { SlabHoleHighlights } from './slab-hole-highlights'
|
|
61
|
+
import { SnapshotCaptureOverlay } from './snapshot-capture-overlay'
|
|
67
62
|
import { type SnapshotCameraData, ThumbnailGenerator } from './thumbnail-generator'
|
|
68
63
|
import { WallMeasurementLabel } from './wall-measurement-label'
|
|
64
|
+
import { WallMoveSideHandles } from './wall-move-side-handles'
|
|
65
|
+
import { WallOpeningHighlights } from './wall-opening-highlights'
|
|
69
66
|
|
|
70
67
|
const CAMERA_CONTROLS_HINT_DISMISSED_STORAGE_KEY = 'editor-camera-controls-hint-dismissed:v1'
|
|
71
68
|
const DELETE_CURSOR_BADGE_COLOR = '#ef4444'
|
|
72
69
|
const DELETE_CURSOR_BADGE_OFFSET_X = 14
|
|
73
70
|
const DELETE_CURSOR_BADGE_OFFSET_Y = 14
|
|
74
|
-
const PAINT_CURSOR_BADGE_COLOR = '#
|
|
71
|
+
const PAINT_CURSOR_BADGE_COLOR = '#818cf8'
|
|
75
72
|
const PAINT_CURSOR_BADGE_DISABLED_COLOR = '#94a3b8'
|
|
76
73
|
const PAINT_CURSOR_BADGE_OFFSET_X = 14
|
|
77
74
|
const PAINT_CURSOR_BADGE_OFFSET_Y = 14
|
|
78
75
|
const EDITOR_HOVER_STYLES: HoverStyles = {
|
|
79
|
-
default: { visibleColor:
|
|
80
|
-
delete: { visibleColor:
|
|
81
|
-
'paint-ready': { visibleColor:
|
|
76
|
+
default: { visibleColor: 0x00_aa_ff, hiddenColor: 0xf3_ff_47, strength: 5, pulse: true },
|
|
77
|
+
delete: { visibleColor: 0xef_44_44, hiddenColor: 0x99_1b_1b, strength: 6, pulse: false },
|
|
78
|
+
'paint-ready': { visibleColor: 0xf5_9e_0b, hiddenColor: 0xfd_e0_68, strength: 5, pulse: true },
|
|
82
79
|
'paint-disabled': {
|
|
83
|
-
visibleColor:
|
|
84
|
-
hiddenColor:
|
|
80
|
+
visibleColor: 0x94_a3_b8,
|
|
81
|
+
hiddenColor: 0x47_55_69,
|
|
85
82
|
strength: 4,
|
|
86
83
|
pulse: false,
|
|
87
84
|
},
|
|
88
85
|
}
|
|
86
|
+
const EDITOR_DEFAULT_RENDER = { shading: 'solid' } as const
|
|
89
87
|
|
|
90
88
|
/**
|
|
91
89
|
* Wire up module-level singletons (spatial grid, space detection, SFX) for
|
|
@@ -123,12 +121,18 @@ export interface EditorProps {
|
|
|
123
121
|
sidebarTabs?: (SidebarTab & { component: React.ComponentType })[]
|
|
124
122
|
viewerToolbarLeft?: ReactNode
|
|
125
123
|
viewerToolbarRight?: ReactNode
|
|
124
|
+
/**
|
|
125
|
+
* Docked below the node inspector (v2). Hosts mount the "save as preset"
|
|
126
|
+
* affordance here so it reads as part of the inspector surface and shows
|
|
127
|
+
* only while a node is selected.
|
|
128
|
+
*/
|
|
129
|
+
inspectorFooter?: ReactNode
|
|
126
130
|
|
|
127
131
|
projectId?: string | null
|
|
128
132
|
|
|
129
133
|
// Persistence — defaults to localStorage when omitted
|
|
130
134
|
onLoad?: () => Promise<SceneGraph | null>
|
|
131
|
-
onSave?: (scene: SceneGraph) => Promise<void>
|
|
135
|
+
onSave?: (scene: SceneGraph, options?: { keepalive?: boolean }) => Promise<void>
|
|
132
136
|
onDirty?: () => void
|
|
133
137
|
onSaveStatusChange?: (status: SaveStatus) => void
|
|
134
138
|
|
|
@@ -151,9 +155,6 @@ export interface EditorProps {
|
|
|
151
155
|
sitePanelProps?: SitePanelProps
|
|
152
156
|
extraSidebarPanels?: ExtraPanel[]
|
|
153
157
|
|
|
154
|
-
// Presets storage backend (defaults to localStorage)
|
|
155
|
-
presetsAdapter?: PresetsAdapter
|
|
156
|
-
|
|
157
158
|
// Command palette fallback when no commands match
|
|
158
159
|
commandPaletteEmptyAction?: CommandPaletteEmptyAction
|
|
159
160
|
}
|
|
@@ -332,6 +333,7 @@ const EDITOR_CAMERA_CONTROL_HINTS: CameraControlHint[] = [
|
|
|
332
333
|
{
|
|
333
334
|
action: 'Pan',
|
|
334
335
|
keys: [{ value: 'Space' }, { value: 'Left click' }],
|
|
336
|
+
alternativeKeys: [{ value: 'Middle click' }],
|
|
335
337
|
},
|
|
336
338
|
{ action: 'Rotate', keys: [{ value: 'Right click' }] },
|
|
337
339
|
{ action: 'Zoom', keys: [{ value: 'Scroll' }] },
|
|
@@ -462,7 +464,7 @@ function ViewerCanvasControlsHint({
|
|
|
462
464
|
<div className="pointer-events-none absolute top-14 left-1/2 z-40 max-w-[calc(100%-2rem)] -translate-x-1/2">
|
|
463
465
|
<section
|
|
464
466
|
aria-label="Camera controls hint"
|
|
465
|
-
className="pointer-events-auto flex items-start gap-3 rounded-2xl border border-border/35 bg-background/90 px-3.5 py-2.5 shadow-
|
|
467
|
+
className="pointer-events-auto flex items-start gap-3 rounded-2xl border border-border/35 bg-background/90 px-3.5 py-2.5 shadow-elevation-4 backdrop-blur-xl"
|
|
466
468
|
>
|
|
467
469
|
<div className="grid min-w-0 flex-1 grid-cols-3 items-start divide-x divide-border/18">
|
|
468
470
|
{hints.map((hint) => (
|
|
@@ -526,67 +528,89 @@ function DeleteCursorBadge({ position }: { position: { x: number; y: number } })
|
|
|
526
528
|
|
|
527
529
|
function PaintCursorBadge({
|
|
528
530
|
position,
|
|
529
|
-
label,
|
|
530
531
|
disabled,
|
|
531
|
-
icon,
|
|
532
532
|
}: {
|
|
533
533
|
position: { x: number; y: number }
|
|
534
|
-
label: string
|
|
535
534
|
disabled: boolean
|
|
536
|
-
icon: string
|
|
537
535
|
}) {
|
|
538
536
|
const accentColor = disabled ? PAINT_CURSOR_BADGE_DISABLED_COLOR : PAINT_CURSOR_BADGE_COLOR
|
|
537
|
+
const lineHeight = 18
|
|
539
538
|
|
|
540
539
|
return (
|
|
541
540
|
<div
|
|
542
541
|
aria-hidden="true"
|
|
543
542
|
className="pointer-events-none absolute z-40"
|
|
544
543
|
style={{
|
|
545
|
-
left: position.x
|
|
546
|
-
top: position.y
|
|
544
|
+
left: position.x,
|
|
545
|
+
top: position.y,
|
|
547
546
|
}}
|
|
548
547
|
>
|
|
549
548
|
<div
|
|
550
|
-
className="
|
|
549
|
+
className="-translate-x-1/2 -translate-y-full absolute top-0 left-1/2"
|
|
550
|
+
style={{
|
|
551
|
+
backgroundColor: accentColor,
|
|
552
|
+
boxShadow: `0 0 12px ${accentColor}cc`,
|
|
553
|
+
height: lineHeight,
|
|
554
|
+
width: 2,
|
|
555
|
+
}}
|
|
556
|
+
/>
|
|
557
|
+
<div
|
|
558
|
+
className="absolute top-0 left-1/2 flex h-8 w-8 items-center justify-center rounded-xl border border-white/5 bg-zinc-900/95 shadow-[0_8px_16px_-4px_rgba(0,0,0,0.3),0_4px_8px_-4px_rgba(0,0,0,0.2)]"
|
|
551
559
|
style={{
|
|
552
560
|
boxShadow: `0 8px 16px -4px rgba(0,0,0,0.3), 0 4px 8px -4px rgba(0,0,0,0.2), 0 0 18px ${accentColor}22`,
|
|
561
|
+
transform: `translate(-50%, calc(-100% - ${lineHeight}px))`,
|
|
553
562
|
}}
|
|
554
563
|
>
|
|
555
|
-
|
|
564
|
+
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
565
|
+
<img
|
|
566
|
+
alt=""
|
|
556
567
|
aria-hidden="true"
|
|
557
|
-
className="drop-shadow-[0_2px_4px_rgba(0,0,0,0.5)]"
|
|
558
|
-
|
|
559
|
-
height={16}
|
|
560
|
-
icon={icon}
|
|
561
|
-
width={16}
|
|
568
|
+
className="h-5 w-5 object-contain drop-shadow-[0_2px_4px_rgba(0,0,0,0.5)]"
|
|
569
|
+
src="/icons/paint.png"
|
|
562
570
|
/>
|
|
563
|
-
<span className="font-medium text-[11px]" style={{ color: accentColor }}>
|
|
564
|
-
{label}
|
|
565
|
-
</span>
|
|
566
571
|
</div>
|
|
567
572
|
</div>
|
|
568
573
|
)
|
|
569
574
|
}
|
|
570
575
|
|
|
576
|
+
// Subscribes to `gridSnapStep` so the visible grid cell size matches whatever
|
|
577
|
+
// the wall draft tool snaps to — otherwise the cursor lands between visible
|
|
578
|
+
// grid lines when the user picks a finer snap (0.25 / 0.1 / 0.05).
|
|
579
|
+
function SnapAwareGrid() {
|
|
580
|
+
const gridSnapStep = useEditor((s) => s.gridSnapStep)
|
|
581
|
+
return <Grid cellColor="#aaa" cellSize={gridSnapStep} fadeDistance={500} sectionColor="#ccc" />
|
|
582
|
+
}
|
|
583
|
+
|
|
571
584
|
// ── Viewer scene content: memoized so <Viewer> doesn't re-render on mode/viewMode changes ──
|
|
572
585
|
|
|
573
586
|
const ViewerSceneContent = memo(function ViewerSceneContent({
|
|
574
587
|
isVersionPreviewMode,
|
|
575
588
|
isLoading,
|
|
576
589
|
isFirstPersonMode,
|
|
590
|
+
isStudioMode,
|
|
577
591
|
onThumbnailCapture,
|
|
578
592
|
}: {
|
|
579
593
|
isVersionPreviewMode: boolean
|
|
580
594
|
isLoading: boolean
|
|
581
595
|
isFirstPersonMode: boolean
|
|
596
|
+
isStudioMode: boolean
|
|
582
597
|
onThumbnailCapture?: (blob: Blob, cameraData: SnapshotCameraData) => void
|
|
583
598
|
}) {
|
|
599
|
+
// Studio mode is a clean render/snapshot surface — no selection or editing
|
|
600
|
+
// affordances. It mirrors version-preview's chrome gating on the canvas.
|
|
601
|
+
const noEditing = isVersionPreviewMode || isFirstPersonMode || isStudioMode
|
|
584
602
|
return (
|
|
585
603
|
<>
|
|
586
|
-
{!isFirstPersonMode && <SelectionManager />}
|
|
587
|
-
{!
|
|
588
|
-
{!
|
|
589
|
-
{!
|
|
604
|
+
{!(isFirstPersonMode || isStudioMode) && <SelectionManager />}
|
|
605
|
+
{!noEditing && <BoxSelectTool />}
|
|
606
|
+
{!noEditing && <NodeArrowHandles />}
|
|
607
|
+
{!noEditing && <GroupRotateHandle />}
|
|
608
|
+
{!noEditing && <GroupMoveHandle />}
|
|
609
|
+
{!noEditing && <WallOpeningHighlights />}
|
|
610
|
+
{!noEditing && <SlabHoleHighlights />}
|
|
611
|
+
{!noEditing && <WallMoveSideHandles />}
|
|
612
|
+
{!noEditing && <FloatingActionMenu />}
|
|
613
|
+
{!noEditing && <FloatingBuildingActionMenu />}
|
|
590
614
|
{!isFirstPersonMode && <WallMeasurementLabel />}
|
|
591
615
|
<ExportManager />
|
|
592
616
|
{isFirstPersonMode ? <ViewerZoneSystem /> : <ZoneSystem />}
|
|
@@ -594,16 +618,13 @@ const ViewerSceneContent = memo(function ViewerSceneContent({
|
|
|
594
618
|
<CeilingSelectionAffordanceSystem />
|
|
595
619
|
<RoofEditSystem />
|
|
596
620
|
<StairEditSystem />
|
|
597
|
-
{!(isLoading || isFirstPersonMode) &&
|
|
598
|
-
|
|
599
|
-
)}
|
|
600
|
-
{!(isLoading || isVersionPreviewMode || isFirstPersonMode) && <ToolManager />}
|
|
621
|
+
{!(isLoading || isFirstPersonMode) && <SnapAwareGrid />}
|
|
622
|
+
{!(isLoading || noEditing) && <ToolManager />}
|
|
601
623
|
{isFirstPersonMode && <FirstPersonControls />}
|
|
602
624
|
<CustomCameraControls />
|
|
603
625
|
<ThumbnailGenerator onThumbnailCapture={onThumbnailCapture} />
|
|
604
|
-
<PresetThumbnailGenerator />
|
|
605
626
|
{!isFirstPersonMode && <SiteEdgeLabels />}
|
|
606
|
-
|
|
627
|
+
<InteractiveSystem />
|
|
607
628
|
</>
|
|
608
629
|
)
|
|
609
630
|
})
|
|
@@ -676,7 +697,7 @@ function DeleteCursorLayer({
|
|
|
676
697
|
|
|
677
698
|
return (
|
|
678
699
|
<div
|
|
679
|
-
className="pointer-events-none"
|
|
700
|
+
className="pointer-events-none z-40"
|
|
680
701
|
ref={badgeRef}
|
|
681
702
|
style={{ display: 'none', position: 'absolute', left: 0, top: 0 }}
|
|
682
703
|
>
|
|
@@ -694,15 +715,12 @@ function PaintCursorLayer({
|
|
|
694
715
|
}) {
|
|
695
716
|
const mode = useEditor((s) => s.mode)
|
|
696
717
|
const activePaintMaterial = useEditor((s) => s.activePaintMaterial)
|
|
697
|
-
const
|
|
698
|
-
const badgeRef = useRef<HTMLDivElement>(null)
|
|
718
|
+
const [position, setPosition] = useState<{ x: number; y: number } | null>(null)
|
|
699
719
|
const active = mode === 'material-paint' && !isVersionPreviewMode
|
|
700
720
|
|
|
701
721
|
useEffect(() => {
|
|
702
722
|
if (!active) {
|
|
703
|
-
|
|
704
|
-
badgeRef.current.style.display = 'none'
|
|
705
|
-
}
|
|
723
|
+
setPosition(null)
|
|
706
724
|
return
|
|
707
725
|
}
|
|
708
726
|
const el = containerRef.current
|
|
@@ -710,20 +728,16 @@ function PaintCursorLayer({
|
|
|
710
728
|
let frame = 0
|
|
711
729
|
let nextX = 0
|
|
712
730
|
let nextY = 0
|
|
713
|
-
const badge = badgeRef.current
|
|
714
731
|
|
|
715
732
|
const flushPosition = () => {
|
|
716
733
|
frame = 0
|
|
717
|
-
|
|
718
|
-
badge.style.display = 'block'
|
|
719
|
-
badge.style.transform = `translate(${nextX + PAINT_CURSOR_BADGE_OFFSET_X}px, ${nextY + PAINT_CURSOR_BADGE_OFFSET_Y}px)`
|
|
734
|
+
setPosition({ x: nextX, y: nextY })
|
|
720
735
|
}
|
|
721
736
|
|
|
722
|
-
const
|
|
737
|
+
const updateFromEvent = (e: PointerEvent) => {
|
|
723
738
|
const rect = el.getBoundingClientRect()
|
|
724
739
|
nextX = e.clientX - rect.left
|
|
725
740
|
nextY = e.clientY - rect.top
|
|
726
|
-
|
|
727
741
|
if (frame === 0) {
|
|
728
742
|
frame = window.requestAnimationFrame(flushPosition)
|
|
729
743
|
}
|
|
@@ -733,17 +747,19 @@ function PaintCursorLayer({
|
|
|
733
747
|
window.cancelAnimationFrame(frame)
|
|
734
748
|
frame = 0
|
|
735
749
|
}
|
|
736
|
-
|
|
737
|
-
badge.style.display = 'none'
|
|
738
|
-
}
|
|
750
|
+
setPosition(null)
|
|
739
751
|
}
|
|
740
|
-
el.addEventListener('pointermove',
|
|
752
|
+
el.addEventListener('pointermove', updateFromEvent)
|
|
753
|
+
el.addEventListener('pointerenter', updateFromEvent)
|
|
754
|
+
el.addEventListener('pointerdown', updateFromEvent)
|
|
741
755
|
el.addEventListener('pointerleave', onLeave)
|
|
742
756
|
return () => {
|
|
743
757
|
if (frame !== 0) {
|
|
744
758
|
window.cancelAnimationFrame(frame)
|
|
745
759
|
}
|
|
746
|
-
el.removeEventListener('pointermove',
|
|
760
|
+
el.removeEventListener('pointermove', updateFromEvent)
|
|
761
|
+
el.removeEventListener('pointerenter', updateFromEvent)
|
|
762
|
+
el.removeEventListener('pointerdown', updateFromEvent)
|
|
747
763
|
el.removeEventListener('pointerleave', onLeave)
|
|
748
764
|
}
|
|
749
765
|
}, [active, containerRef])
|
|
@@ -753,29 +769,15 @@ function PaintCursorLayer({
|
|
|
753
769
|
(activePaintMaterial.material !== undefined ||
|
|
754
770
|
activePaintMaterial.materialPreset !== undefined),
|
|
755
771
|
)
|
|
756
|
-
const label = !hasMaterial ? 'Choose material' : `Paint ${activePaintTarget}`
|
|
757
|
-
const icon = 'mdi:format-color-fill'
|
|
758
772
|
|
|
759
|
-
|
|
760
|
-
if (!active && badgeRef.current) {
|
|
761
|
-
badgeRef.current.style.display = 'none'
|
|
762
|
-
}
|
|
763
|
-
}, [active])
|
|
764
|
-
|
|
765
|
-
if (!active) return null
|
|
773
|
+
if (!active || !position) return null
|
|
766
774
|
|
|
767
775
|
return (
|
|
768
776
|
<div
|
|
769
|
-
className="pointer-events-none"
|
|
770
|
-
|
|
771
|
-
style={{ display: 'none', position: 'absolute', left: 0, top: 0 }}
|
|
777
|
+
className="pointer-events-none absolute z-40"
|
|
778
|
+
style={{ left: 0, top: 0, transform: `translate(${position.x}px, ${position.y}px)` }}
|
|
772
779
|
>
|
|
773
|
-
<PaintCursorBadge
|
|
774
|
-
disabled={!hasMaterial}
|
|
775
|
-
icon={icon}
|
|
776
|
-
label={label}
|
|
777
|
-
position={{ x: 0, y: 0 }}
|
|
778
|
-
/>
|
|
780
|
+
<PaintCursorBadge disabled={!hasMaterial} position={{ x: 0, y: 0 }} />
|
|
779
781
|
</div>
|
|
780
782
|
)
|
|
781
783
|
}
|
|
@@ -786,16 +788,22 @@ function PaintCursorLayer({
|
|
|
786
788
|
const ViewerCanvas = memo(function ViewerCanvas({
|
|
787
789
|
isVersionPreviewMode,
|
|
788
790
|
isLoading,
|
|
791
|
+
isFirstPersonMode,
|
|
792
|
+
isStudioMode,
|
|
789
793
|
hasLoadedInitialScene,
|
|
790
794
|
showLoader,
|
|
791
|
-
|
|
795
|
+
sceneReadyKey,
|
|
796
|
+
onSceneReadyChange,
|
|
792
797
|
onThumbnailCapture,
|
|
793
798
|
}: {
|
|
794
799
|
isVersionPreviewMode: boolean
|
|
795
800
|
isLoading: boolean
|
|
801
|
+
isFirstPersonMode: boolean
|
|
802
|
+
isStudioMode: boolean
|
|
796
803
|
hasLoadedInitialScene: boolean
|
|
797
804
|
showLoader: boolean
|
|
798
|
-
|
|
805
|
+
sceneReadyKey: number
|
|
806
|
+
onSceneReadyChange: (ready: boolean) => void
|
|
799
807
|
onThumbnailCapture?: (blob: Blob, cameraData: SnapshotCameraData) => void
|
|
800
808
|
}) {
|
|
801
809
|
const viewMode = useEditor((s) => s.viewMode)
|
|
@@ -897,19 +905,24 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
897
905
|
) : null}
|
|
898
906
|
<SelectionPersistenceManager enabled={hasLoadedInitialScene && !showLoader} />
|
|
899
907
|
<Viewer
|
|
908
|
+
defaultRender={EDITOR_DEFAULT_RENDER}
|
|
900
909
|
hoverStyles={EDITOR_HOVER_STYLES}
|
|
910
|
+
onSceneReadyChange={onSceneReadyChange}
|
|
911
|
+
renderContext="editor"
|
|
912
|
+
sceneReadyKey={sceneReadyKey}
|
|
901
913
|
selectionManager={isFirstPersonMode ? 'default' : 'custom'}
|
|
902
914
|
>
|
|
903
915
|
<ViewerSceneContent
|
|
904
916
|
isFirstPersonMode={isFirstPersonMode}
|
|
905
|
-
isLoading={
|
|
917
|
+
isLoading={showLoader}
|
|
918
|
+
isStudioMode={isStudioMode}
|
|
906
919
|
isVersionPreviewMode={isVersionPreviewMode}
|
|
907
920
|
onThumbnailCapture={onThumbnailCapture}
|
|
908
921
|
/>
|
|
909
922
|
</Viewer>
|
|
910
923
|
</div>
|
|
911
924
|
</div>
|
|
912
|
-
{!(
|
|
925
|
+
{!(showLoader || isVersionPreviewMode) && <ZoneLabelEditorSystem />}
|
|
913
926
|
</ErrorBoundary>
|
|
914
927
|
)
|
|
915
928
|
})
|
|
@@ -922,6 +935,7 @@ export default function Editor({
|
|
|
922
935
|
sidebarTabs,
|
|
923
936
|
viewerToolbarLeft,
|
|
924
937
|
viewerToolbarRight,
|
|
938
|
+
inspectorFooter,
|
|
925
939
|
projectId,
|
|
926
940
|
onLoad,
|
|
927
941
|
onSave,
|
|
@@ -936,11 +950,13 @@ export default function Editor({
|
|
|
936
950
|
settingsPanelProps,
|
|
937
951
|
sitePanelProps,
|
|
938
952
|
extraSidebarPanels,
|
|
939
|
-
presetsAdapter,
|
|
940
953
|
commandPaletteEmptyAction,
|
|
941
954
|
}: EditorProps) {
|
|
942
955
|
const isFirstPersonMode = useEditor((s) => s.isFirstPersonMode)
|
|
943
|
-
|
|
956
|
+
const isStudioMode = useEditor((s) => s.workspaceMode === 'studio')
|
|
957
|
+
|
|
958
|
+
useKeyboard({ isVersionPreviewMode, disabled: isFirstPersonMode || isStudioMode })
|
|
959
|
+
|
|
944
960
|
const { isLoadingSceneRef } = useAutoSave({
|
|
945
961
|
onSave,
|
|
946
962
|
onDirty,
|
|
@@ -950,9 +966,10 @@ export default function Editor({
|
|
|
950
966
|
|
|
951
967
|
const [isSceneLoading, setIsSceneLoading] = useState(false)
|
|
952
968
|
const [hasLoadedInitialScene, setHasLoadedInitialScene] = useState(false)
|
|
969
|
+
const [sceneReadyKey, setSceneReadyKey] = useState(0)
|
|
970
|
+
const [isViewerSceneReady, setIsViewerSceneReady] = useState(false)
|
|
953
971
|
const isPreviewMode = useEditor((s) => s.isPreviewMode)
|
|
954
|
-
const
|
|
955
|
-
const wasFirstPersonModeRef = useRef(isFirstPersonMode)
|
|
972
|
+
const isCaptureMode = useEditor((s) => s.isCaptureMode)
|
|
956
973
|
|
|
957
974
|
const sidebarWidth = useSidebarStore((s) => s.width)
|
|
958
975
|
const isSidebarCollapsed = useSidebarStore((s) => s.isCollapsed)
|
|
@@ -970,39 +987,6 @@ export default function Editor({
|
|
|
970
987
|
}
|
|
971
988
|
}, [projectId])
|
|
972
989
|
|
|
973
|
-
useEffect(() => {
|
|
974
|
-
const wasFirstPersonMode = wasFirstPersonModeRef.current
|
|
975
|
-
wasFirstPersonModeRef.current = isFirstPersonMode
|
|
976
|
-
|
|
977
|
-
if (isFirstPersonMode && !wasFirstPersonMode) {
|
|
978
|
-
const viewer = useViewer.getState()
|
|
979
|
-
firstPersonPreviousLevelRef.current = viewer.selection.levelId
|
|
980
|
-
viewer.setCameraMode('perspective')
|
|
981
|
-
viewer.setWallMode('up')
|
|
982
|
-
viewer.setWalkthroughMode(true)
|
|
983
|
-
viewer.setSelection({ selectedIds: [], zoneId: null })
|
|
984
|
-
return
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
if (!(wasFirstPersonMode && !isFirstPersonMode)) return
|
|
988
|
-
|
|
989
|
-
const viewer = useViewer.getState()
|
|
990
|
-
const previousLevelId = firstPersonPreviousLevelRef.current
|
|
991
|
-
firstPersonPreviousLevelRef.current = null
|
|
992
|
-
viewer.setWalkthroughMode(false)
|
|
993
|
-
|
|
994
|
-
if (!previousLevelId) return
|
|
995
|
-
|
|
996
|
-
const previousLevelNode = useScene.getState().nodes[previousLevelId]
|
|
997
|
-
if (previousLevelNode?.type === 'level') {
|
|
998
|
-
viewer.setSelection({
|
|
999
|
-
levelId: previousLevelId,
|
|
1000
|
-
zoneId: null,
|
|
1001
|
-
selectedIds: [],
|
|
1002
|
-
})
|
|
1003
|
-
}
|
|
1004
|
-
}, [isFirstPersonMode])
|
|
1005
|
-
|
|
1006
990
|
// Load scene on mount (or when onLoad identity changes, e.g. project switch)
|
|
1007
991
|
useEffect(() => {
|
|
1008
992
|
let cancelled = false
|
|
@@ -1010,15 +994,24 @@ export default function Editor({
|
|
|
1010
994
|
async function load() {
|
|
1011
995
|
isLoadingSceneRef.current = true
|
|
1012
996
|
setHasLoadedInitialScene(false)
|
|
997
|
+
setIsViewerSceneReady(false)
|
|
1013
998
|
setIsSceneLoading(true)
|
|
999
|
+
useScene.getState().unloadScene()
|
|
1000
|
+
useViewer.getState().resetSelection()
|
|
1014
1001
|
|
|
1015
1002
|
try {
|
|
1016
1003
|
const sceneGraph = onLoad ? await onLoad() : loadSceneFromLocalStorage()
|
|
1017
1004
|
if (!cancelled) {
|
|
1018
1005
|
applySceneGraphToEditor(sceneGraph)
|
|
1006
|
+
setIsViewerSceneReady(false)
|
|
1007
|
+
setSceneReadyKey((key) => key + 1)
|
|
1019
1008
|
}
|
|
1020
1009
|
} catch {
|
|
1021
|
-
if (!cancelled)
|
|
1010
|
+
if (!cancelled) {
|
|
1011
|
+
applySceneGraphToEditor(null)
|
|
1012
|
+
setIsViewerSceneReady(false)
|
|
1013
|
+
setSceneReadyKey((key) => key + 1)
|
|
1014
|
+
}
|
|
1022
1015
|
} finally {
|
|
1023
1016
|
if (!cancelled) {
|
|
1024
1017
|
setIsSceneLoading(false)
|
|
@@ -1062,10 +1055,55 @@ export default function Editor({
|
|
|
1062
1055
|
}
|
|
1063
1056
|
}, [])
|
|
1064
1057
|
|
|
1065
|
-
const
|
|
1058
|
+
const handleSceneReadyChange = useCallback((ready: boolean) => {
|
|
1059
|
+
setIsViewerSceneReady(ready)
|
|
1060
|
+
}, [])
|
|
1061
|
+
|
|
1062
|
+
const showLoader = isLoading || isSceneLoading || !hasLoadedInitialScene || !isViewerSceneReady
|
|
1063
|
+
|
|
1064
|
+
const firstPersonPreviousLevelRef = useRef(useViewer.getState().selection.levelId)
|
|
1065
|
+
const wasFirstPersonModeRef = useRef(isFirstPersonMode)
|
|
1066
|
+
|
|
1067
|
+
useEffect(() => {
|
|
1068
|
+
const wasFirstPersonMode = wasFirstPersonModeRef.current
|
|
1069
|
+
wasFirstPersonModeRef.current = isFirstPersonMode
|
|
1070
|
+
|
|
1071
|
+
if (isFirstPersonMode && !wasFirstPersonMode) {
|
|
1072
|
+
const viewer = useViewer.getState()
|
|
1073
|
+
firstPersonPreviousLevelRef.current = viewer.selection.levelId
|
|
1074
|
+
viewer.setCameraMode('perspective')
|
|
1075
|
+
viewer.setWallMode('up')
|
|
1076
|
+
viewer.setWalkthroughMode(true)
|
|
1077
|
+
viewer.setSelection({ selectedIds: [], zoneId: null })
|
|
1078
|
+
return
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
if (!(wasFirstPersonMode && !isFirstPersonMode)) return
|
|
1082
|
+
|
|
1083
|
+
const viewer = useViewer.getState()
|
|
1084
|
+
const previousLevelId = firstPersonPreviousLevelRef.current
|
|
1085
|
+
firstPersonPreviousLevelRef.current = null
|
|
1086
|
+
viewer.setWalkthroughMode(false)
|
|
1087
|
+
|
|
1088
|
+
if (!previousLevelId) return
|
|
1089
|
+
|
|
1090
|
+
const previousLevelNode = useScene.getState().nodes[previousLevelId]
|
|
1091
|
+
if (previousLevelNode?.type === 'level') {
|
|
1092
|
+
viewer.setSelection({
|
|
1093
|
+
levelId: previousLevelId,
|
|
1094
|
+
zoneId: null,
|
|
1095
|
+
selectedIds: [],
|
|
1096
|
+
})
|
|
1097
|
+
}
|
|
1098
|
+
}, [isFirstPersonMode])
|
|
1066
1099
|
|
|
1067
1100
|
const previewViewerContent = (
|
|
1068
|
-
<Viewer
|
|
1101
|
+
<Viewer
|
|
1102
|
+
defaultRender={EDITOR_DEFAULT_RENDER}
|
|
1103
|
+
hoverStyles={EDITOR_HOVER_STYLES}
|
|
1104
|
+
renderContext="editor"
|
|
1105
|
+
selectionManager="default"
|
|
1106
|
+
>
|
|
1069
1107
|
<ExportManager />
|
|
1070
1108
|
<ViewerZoneSystem />
|
|
1071
1109
|
<CeilingSystem />
|
|
@@ -1073,7 +1111,6 @@ export default function Editor({
|
|
|
1073
1111
|
<StairEditSystem />
|
|
1074
1112
|
<CustomCameraControls />
|
|
1075
1113
|
<ThumbnailGenerator onThumbnailCapture={onThumbnailCapture} />
|
|
1076
|
-
<PresetThumbnailGenerator />
|
|
1077
1114
|
<InteractiveSystem />
|
|
1078
1115
|
</Viewer>
|
|
1079
1116
|
)
|
|
@@ -1083,8 +1120,11 @@ export default function Editor({
|
|
|
1083
1120
|
hasLoadedInitialScene={hasLoadedInitialScene}
|
|
1084
1121
|
isFirstPersonMode={isFirstPersonMode}
|
|
1085
1122
|
isLoading={isLoading}
|
|
1123
|
+
isStudioMode={isStudioMode}
|
|
1086
1124
|
isVersionPreviewMode={isVersionPreviewMode}
|
|
1125
|
+
onSceneReadyChange={handleSceneReadyChange}
|
|
1087
1126
|
onThumbnailCapture={onThumbnailCapture}
|
|
1127
|
+
sceneReadyKey={sceneReadyKey}
|
|
1088
1128
|
showLoader={showLoader}
|
|
1089
1129
|
/>
|
|
1090
1130
|
)
|
|
@@ -1109,18 +1149,19 @@ export default function Editor({
|
|
|
1109
1149
|
}
|
|
1110
1150
|
|
|
1111
1151
|
const tabBarTabs =
|
|
1112
|
-
sidebarTabs?.map(({ id, label, mobileDefaultSnap, mobileIcon }) => ({
|
|
1152
|
+
sidebarTabs?.map(({ id, label, mobileDefaultSnap, mobileIcon, icon }) => ({
|
|
1113
1153
|
id,
|
|
1114
1154
|
label,
|
|
1115
1155
|
mobileDefaultSnap,
|
|
1116
1156
|
mobileIcon,
|
|
1157
|
+
icon,
|
|
1117
1158
|
})) ?? []
|
|
1118
1159
|
|
|
1119
1160
|
return (
|
|
1120
|
-
|
|
1161
|
+
<>
|
|
1121
1162
|
{showLoader && (
|
|
1122
1163
|
<div className="fixed inset-0 z-60">
|
|
1123
|
-
<SceneLoader />
|
|
1164
|
+
<SceneLoader className="bg-background" />
|
|
1124
1165
|
</div>
|
|
1125
1166
|
)}
|
|
1126
1167
|
|
|
@@ -1135,21 +1176,29 @@ export default function Editor({
|
|
|
1135
1176
|
navbarSlot={navbarSlot}
|
|
1136
1177
|
overlays={
|
|
1137
1178
|
<>
|
|
1138
|
-
<FloatingLevelSelector />
|
|
1139
|
-
{!isVersionPreviewMode && (
|
|
1179
|
+
{!isCaptureMode && <FloatingLevelSelector />}
|
|
1180
|
+
{!(isVersionPreviewMode || isCaptureMode || isStudioMode) && (
|
|
1140
1181
|
<div className="pointer-events-auto">
|
|
1141
1182
|
<ActionMenu />
|
|
1142
1183
|
</div>
|
|
1143
1184
|
)}
|
|
1144
|
-
{!isVersionPreviewMode && (
|
|
1185
|
+
{!(isVersionPreviewMode || isCaptureMode || isStudioMode) && (
|
|
1186
|
+
<div className="pointer-events-auto">
|
|
1187
|
+
<PanelManager inspectorFooter={inspectorFooter} />
|
|
1188
|
+
</div>
|
|
1189
|
+
)}
|
|
1190
|
+
{!isCaptureMode && (
|
|
1145
1191
|
<div className="pointer-events-auto">
|
|
1146
|
-
<
|
|
1192
|
+
<HelperManager />
|
|
1147
1193
|
</div>
|
|
1148
1194
|
)}
|
|
1149
|
-
|
|
1150
|
-
<
|
|
1151
|
-
|
|
1195
|
+
{isFirstPersonMode && (
|
|
1196
|
+
<FirstPersonOverlay
|
|
1197
|
+
onExit={() => useEditor.getState().setFirstPersonMode(false)}
|
|
1198
|
+
/>
|
|
1199
|
+
)}
|
|
1152
1200
|
{viewerBanner}
|
|
1201
|
+
{projectId ? <SnapshotCaptureOverlay projectId={projectId} /> : null}
|
|
1153
1202
|
</>
|
|
1154
1203
|
}
|
|
1155
1204
|
renderTabContent={renderTabContent}
|
|
@@ -1159,17 +1208,11 @@ export default function Editor({
|
|
|
1159
1208
|
viewerToolbarLeft={viewerToolbarLeft}
|
|
1160
1209
|
viewerToolbarRight={viewerToolbarRight}
|
|
1161
1210
|
/>
|
|
1162
|
-
{/* First-person overlay — rendered on top of normal layout */}
|
|
1163
|
-
{isFirstPersonMode && (
|
|
1164
|
-
<div className="pointer-events-none fixed inset-0 z-50">
|
|
1165
|
-
<FirstPersonOverlay onExit={() => useEditor.getState().setFirstPersonMode(false)} />
|
|
1166
|
-
</div>
|
|
1167
|
-
)}
|
|
1168
1211
|
<EditorCommands />
|
|
1169
1212
|
<CommandPalette emptyAction={commandPaletteEmptyAction} />
|
|
1170
1213
|
</>
|
|
1171
1214
|
)}
|
|
1172
|
-
|
|
1215
|
+
</>
|
|
1173
1216
|
)
|
|
1174
1217
|
}
|
|
1175
1218
|
|
|
@@ -1180,51 +1223,52 @@ export default function Editor({
|
|
|
1180
1223
|
const overlayLeft = LAYOUT_PADDING + (isSidebarCollapsed ? 8 : sidebarWidth) + LAYOUT_GAP
|
|
1181
1224
|
|
|
1182
1225
|
return (
|
|
1183
|
-
<
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
<
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
)}
|
|
1226
|
+
<div className="dark flex h-full w-full gap-3 bg-neutral-100 p-3 text-foreground">
|
|
1227
|
+
{showLoader && (
|
|
1228
|
+
<div className="fixed inset-0 z-60">
|
|
1229
|
+
<SceneLoader className="bg-background" />
|
|
1230
|
+
</div>
|
|
1231
|
+
)}
|
|
1190
1232
|
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1233
|
+
{!isLoading && isPreviewMode ? (
|
|
1234
|
+
<>
|
|
1235
|
+
<ViewerOverlay onBack={() => useEditor.getState().setPreviewMode(false)} />
|
|
1236
|
+
<div className="h-full w-full">{previewViewerContent}</div>
|
|
1237
|
+
</>
|
|
1238
|
+
) : (
|
|
1239
|
+
<>
|
|
1240
|
+
{/* Sidebar */}
|
|
1241
|
+
<SidebarSlot>
|
|
1242
|
+
<AppSidebar
|
|
1243
|
+
appMenuButton={appMenuButton}
|
|
1244
|
+
commandPaletteEmptyAction={commandPaletteEmptyAction}
|
|
1245
|
+
extraPanels={extraSidebarPanels}
|
|
1246
|
+
settingsPanelProps={settingsPanelProps}
|
|
1247
|
+
sidebarTop={sidebarTop}
|
|
1248
|
+
sitePanelProps={sitePanelProps}
|
|
1249
|
+
/>
|
|
1250
|
+
</SidebarSlot>
|
|
1251
|
+
|
|
1252
|
+
{/* Viewer area */}
|
|
1253
|
+
<div className="relative flex-1 overflow-hidden rounded-xl">{viewerCanvas}</div>
|
|
1254
|
+
|
|
1255
|
+
{/* Fixed UI overlays scoped to the viewer area */}
|
|
1256
|
+
<ViewerOverlays left={overlayLeft}>
|
|
1257
|
+
<div className="pointer-events-auto">
|
|
1258
|
+
<ActionMenu />
|
|
1259
|
+
</div>
|
|
1260
|
+
<div className="pointer-events-auto">
|
|
1261
|
+
<PanelManager />
|
|
1262
|
+
</div>
|
|
1263
|
+
<div className="pointer-events-auto">
|
|
1264
|
+
<HelperManager />
|
|
1265
|
+
</div>
|
|
1266
|
+
{isFirstPersonMode && (
|
|
1267
|
+
<FirstPersonOverlay onExit={() => useEditor.getState().setFirstPersonMode(false)} />
|
|
1268
|
+
)}
|
|
1269
|
+
</ViewerOverlays>
|
|
1270
|
+
</>
|
|
1271
|
+
)}
|
|
1272
|
+
</div>
|
|
1229
1273
|
)
|
|
1230
1274
|
}
|