@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
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
useScene,
|
|
27
27
|
} from '@pascal-app/core'
|
|
28
28
|
import { useViewer } from '@pascal-app/viewer'
|
|
29
|
-
import { Copy, GripVertical, MoreVertical, Plus, Trash2 } from 'lucide-react'
|
|
29
|
+
import { ClipboardPaste, Copy, GripVertical, MoreVertical, Plus, Trash2 } from 'lucide-react'
|
|
30
30
|
import {
|
|
31
31
|
type ButtonHTMLAttributes,
|
|
32
32
|
type CSSProperties,
|
|
@@ -34,13 +34,21 @@ import {
|
|
|
34
34
|
useEffect,
|
|
35
35
|
useRef,
|
|
36
36
|
useState,
|
|
37
|
+
useSyncExternalStore,
|
|
37
38
|
} from 'react'
|
|
38
39
|
import { useShallow } from 'zustand/react/shallow'
|
|
39
40
|
import {
|
|
40
41
|
buildLevelDuplicateCreateOps,
|
|
41
42
|
type LevelDuplicatePreset,
|
|
42
43
|
} from '../../lib/level-duplication'
|
|
44
|
+
import { getDefaultLevelName, getLevelDisplayName } from '../../lib/level-name'
|
|
43
45
|
import { deleteLevelWithFallbackSelection } from '../../lib/level-selection'
|
|
46
|
+
import {
|
|
47
|
+
getEditorClipboardSnapshot,
|
|
48
|
+
pasteEditorClipboardToLevel,
|
|
49
|
+
subscribeEditorClipboard,
|
|
50
|
+
} from '../../lib/scene-clipboard'
|
|
51
|
+
import { sfxEmitter } from '../../lib/sfx-bus'
|
|
44
52
|
import { cn } from '../../lib/utils'
|
|
45
53
|
import { LevelDuplicateDialog } from './level-duplicate-dialog'
|
|
46
54
|
import {
|
|
@@ -53,10 +61,6 @@ import {
|
|
|
53
61
|
} from './primitives/dialog'
|
|
54
62
|
import { Popover, PopoverContent, PopoverTrigger } from './primitives/popover'
|
|
55
63
|
|
|
56
|
-
function getLevelDisplayLabel(level: LevelNode) {
|
|
57
|
-
return level.name || `Level ${level.level}`
|
|
58
|
-
}
|
|
59
|
-
|
|
60
64
|
// ── Inline rename input for a level row ─────────────────────────────────────
|
|
61
65
|
|
|
62
66
|
function LevelInlineRename({
|
|
@@ -69,7 +73,7 @@ function LevelInlineRename({
|
|
|
69
73
|
onStopEditing: () => void
|
|
70
74
|
}) {
|
|
71
75
|
const updateNode = useScene((s) => s.updateNode)
|
|
72
|
-
const defaultName =
|
|
76
|
+
const defaultName = getDefaultLevelName(level.level)
|
|
73
77
|
const [value, setValue] = useState(level.name || '')
|
|
74
78
|
const inputRef = useRef<HTMLInputElement>(null)
|
|
75
79
|
|
|
@@ -126,6 +130,7 @@ function LevelRow({
|
|
|
126
130
|
dragHandleRef,
|
|
127
131
|
onSelect,
|
|
128
132
|
onDuplicate,
|
|
133
|
+
onPaste,
|
|
129
134
|
onRequestDelete,
|
|
130
135
|
}: {
|
|
131
136
|
level: LevelNode
|
|
@@ -135,6 +140,7 @@ function LevelRow({
|
|
|
135
140
|
dragHandleRef?: (element: HTMLButtonElement | null) => void
|
|
136
141
|
onSelect: () => void
|
|
137
142
|
onDuplicate: (preset?: LevelDuplicatePreset) => void
|
|
143
|
+
onPaste?: () => void
|
|
138
144
|
onRequestDelete: () => void
|
|
139
145
|
}) {
|
|
140
146
|
const [duplicateDialogOpen, setDuplicateDialogOpen] = useState(false)
|
|
@@ -160,9 +166,9 @@ function LevelRow({
|
|
|
160
166
|
>
|
|
161
167
|
<button
|
|
162
168
|
{...dragHandleProps}
|
|
163
|
-
aria-label={`Reorder ${
|
|
169
|
+
aria-label={`Reorder ${getLevelDisplayName(level)}`}
|
|
164
170
|
className={cn(
|
|
165
|
-
'ml-0.5 flex h-6 w-4 shrink-0 touch-none
|
|
171
|
+
'ml-0.5 flex h-6 w-4 shrink-0 cursor-grab touch-none items-center justify-center rounded-md text-muted-foreground/35 opacity-0 transition-colors hover:bg-white/5 hover:text-foreground focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/50 group-hover/level:opacity-100',
|
|
166
172
|
isDragging && 'cursor-grabbing opacity-100',
|
|
167
173
|
)}
|
|
168
174
|
onClick={(e) => {
|
|
@@ -183,10 +189,10 @@ function LevelRow({
|
|
|
183
189
|
e.stopPropagation()
|
|
184
190
|
setIsEditing(true)
|
|
185
191
|
}}
|
|
186
|
-
title={
|
|
192
|
+
title={getLevelDisplayName(level)}
|
|
187
193
|
type="button"
|
|
188
194
|
>
|
|
189
|
-
<span className="truncate">{
|
|
195
|
+
<span className="truncate">{getLevelDisplayName(level)}</span>
|
|
190
196
|
</button>
|
|
191
197
|
|
|
192
198
|
{/* Vertical three-dot menu — inside the pill */}
|
|
@@ -223,6 +229,19 @@ function LevelRow({
|
|
|
223
229
|
<Copy className="h-3 w-3" />
|
|
224
230
|
Duplicate with options...
|
|
225
231
|
</button>
|
|
232
|
+
{onPaste && (
|
|
233
|
+
<button
|
|
234
|
+
className="flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-left text-muted-foreground text-xs transition-colors hover:bg-white/10 hover:text-foreground"
|
|
235
|
+
onClick={(e) => {
|
|
236
|
+
e.stopPropagation()
|
|
237
|
+
onPaste()
|
|
238
|
+
}}
|
|
239
|
+
type="button"
|
|
240
|
+
>
|
|
241
|
+
<ClipboardPaste className="h-3 w-3" />
|
|
242
|
+
Paste copied selection
|
|
243
|
+
</button>
|
|
244
|
+
)}
|
|
226
245
|
<button
|
|
227
246
|
className="flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-left text-muted-foreground text-xs transition-colors hover:bg-white/10 hover:text-red-400"
|
|
228
247
|
onClick={(e) => {
|
|
@@ -256,12 +275,14 @@ function SortableLevelRow({
|
|
|
256
275
|
isSelected,
|
|
257
276
|
onSelect,
|
|
258
277
|
onDuplicate,
|
|
278
|
+
onPaste,
|
|
259
279
|
onRequestDelete,
|
|
260
280
|
}: {
|
|
261
281
|
level: LevelNode
|
|
262
282
|
isSelected: boolean
|
|
263
283
|
onSelect: () => void
|
|
264
284
|
onDuplicate: (preset?: LevelDuplicatePreset) => void
|
|
285
|
+
onPaste?: () => void
|
|
265
286
|
onRequestDelete: () => void
|
|
266
287
|
}) {
|
|
267
288
|
const {
|
|
@@ -291,6 +312,7 @@ function SortableLevelRow({
|
|
|
291
312
|
isSelected={isSelected}
|
|
292
313
|
level={level}
|
|
293
314
|
onDuplicate={onDuplicate}
|
|
315
|
+
onPaste={onPaste}
|
|
294
316
|
onRequestDelete={onRequestDelete}
|
|
295
317
|
onSelect={onSelect}
|
|
296
318
|
/>
|
|
@@ -310,6 +332,11 @@ export function FloatingLevelSelector() {
|
|
|
310
332
|
|
|
311
333
|
const [deletingLevel, setDeletingLevel] = useState<LevelNode | null>(null)
|
|
312
334
|
const [draggingLevelId, setDraggingLevelId] = useState<string | null>(null)
|
|
335
|
+
const clipboardSnapshot = useSyncExternalStore(
|
|
336
|
+
subscribeEditorClipboard,
|
|
337
|
+
getEditorClipboardSnapshot,
|
|
338
|
+
getEditorClipboardSnapshot,
|
|
339
|
+
)
|
|
313
340
|
const sensors = useSensors(
|
|
314
341
|
useSensor(PointerSensor, {
|
|
315
342
|
activationConstraint: { distance: 4 },
|
|
@@ -424,6 +451,13 @@ export function FloatingLevelSelector() {
|
|
|
424
451
|
[createNodes, levels, resolvedBuildingId, setSelection, updateNodes],
|
|
425
452
|
)
|
|
426
453
|
|
|
454
|
+
const handlePasteToLevel = useCallback((level: LevelNode) => {
|
|
455
|
+
const result = pasteEditorClipboardToLevel(level.id)
|
|
456
|
+
if (result?.pastedIds.length) {
|
|
457
|
+
sfxEmitter.emit('sfx:item-place')
|
|
458
|
+
}
|
|
459
|
+
}, [])
|
|
460
|
+
|
|
427
461
|
const handleDragStart = useCallback((event: DragStartEvent) => {
|
|
428
462
|
setDraggingLevelId(String(event.active.id))
|
|
429
463
|
}, [])
|
|
@@ -523,6 +557,9 @@ export function FloatingLevelSelector() {
|
|
|
523
557
|
isSelected={isSelected}
|
|
524
558
|
level={level}
|
|
525
559
|
onDuplicate={(preset) => handleDuplicateLevel(level, preset)}
|
|
560
|
+
onPaste={
|
|
561
|
+
clipboardSnapshot ? () => handlePasteToLevel(level) : undefined
|
|
562
|
+
}
|
|
526
563
|
onRequestDelete={() => setDeletingLevel(level)}
|
|
527
564
|
onSelect={() =>
|
|
528
565
|
setSelection(
|
|
@@ -559,7 +596,7 @@ export function FloatingLevelSelector() {
|
|
|
559
596
|
<DialogTitle>Delete level</DialogTitle>
|
|
560
597
|
<DialogDescription>
|
|
561
598
|
Are you sure you want to delete{' '}
|
|
562
|
-
<strong>{deletingLevel ?
|
|
599
|
+
<strong>{deletingLevel ? getLevelDisplayName(deletingLevel) : ''}</strong>? All
|
|
563
600
|
walls, floors, and objects on this level will be permanently removed.
|
|
564
601
|
</DialogDescription>
|
|
565
602
|
</DialogHeader>
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import { nodeRegistry } from '@pascal-app/core'
|
|
4
|
+
import { useIsMobile } from '../../../hooks/use-mobile'
|
|
3
5
|
import useEditor from '../../../store/use-editor'
|
|
4
6
|
import { BuildingHelper } from './building-helper'
|
|
5
|
-
import { CeilingHelper } from './ceiling-helper'
|
|
6
7
|
import { ItemHelper } from './item-helper'
|
|
8
|
+
import { RegisteredToolHelper } from './registered-tool-helper'
|
|
7
9
|
import { RoofHelper } from './roof-helper'
|
|
8
|
-
import { SlabHelper } from './slab-helper'
|
|
9
|
-
import { WallHelper } from './wall-helper'
|
|
10
10
|
|
|
11
11
|
export function HelperManager() {
|
|
12
12
|
const mode = useEditor((s) => s.mode)
|
|
13
13
|
const tool = useEditor((s) => s.tool)
|
|
14
14
|
const movingNode = useEditor((state) => state.movingNode)
|
|
15
|
+
const isMobile = useIsMobile()
|
|
16
|
+
|
|
17
|
+
// Helpers are keyboard-driven hints (Esc, R, etc.) — irrelevant on touch.
|
|
18
|
+
if (isMobile) return null
|
|
15
19
|
|
|
16
20
|
if (movingNode) {
|
|
17
21
|
if (movingNode.type === 'building') return <BuildingHelper showRotate />
|
|
@@ -22,19 +26,19 @@ export function HelperManager() {
|
|
|
22
26
|
return null
|
|
23
27
|
}
|
|
24
28
|
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
case 'ceiling':
|
|
34
|
-
return <CeilingHelper />
|
|
35
|
-
case 'roof':
|
|
36
|
-
return <RoofHelper />
|
|
37
|
-
default:
|
|
38
|
-
return null
|
|
29
|
+
// Registry-first: kinds with `def.toolHints` render through the generic
|
|
30
|
+
// `RegisteredToolHelper`. Today that covers ceiling / door / fence /
|
|
31
|
+
// item / shelf / slab / spawn / wall / window.
|
|
32
|
+
if (tool) {
|
|
33
|
+
const def = nodeRegistry.get(tool)
|
|
34
|
+
if (def?.toolHints && def.toolHints.length > 0) {
|
|
35
|
+
return <RegisteredToolHelper hints={def.toolHints} />
|
|
36
|
+
}
|
|
39
37
|
}
|
|
38
|
+
|
|
39
|
+
// Legacy fallback — only `roof` remains because it hasn't migrated to
|
|
40
|
+
// `def.tool` / `def.toolHints` yet (no Stage D port). When roof
|
|
41
|
+
// migrates, this switch deletes outright.
|
|
42
|
+
if (tool === 'roof') return <RoofHelper />
|
|
43
|
+
return null
|
|
40
44
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ToolHint } from '@pascal-app/core'
|
|
2
|
+
import { ShortcutToken } from '../primitives/shortcut-token'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generic helper panel rendered from `def.toolHints` data. Matches the
|
|
6
|
+
* visual styling of the hand-written `<WallHelper>` / `<ItemHelper>` /
|
|
7
|
+
* etc. so registry-driven kinds get a consistent look without each kind
|
|
8
|
+
* writing its own component.
|
|
9
|
+
*
|
|
10
|
+
* Drops the need for per-kind helper files entirely — kinds declare
|
|
11
|
+
* their hints as static data in their `NodeDefinition`.
|
|
12
|
+
*/
|
|
13
|
+
export function RegisteredToolHelper({ hints }: { hints: ToolHint[] }) {
|
|
14
|
+
if (hints.length === 0) return null
|
|
15
|
+
return (
|
|
16
|
+
<div className="pointer-events-none fixed top-1/2 right-4 z-40 flex -translate-y-1/2 flex-col gap-2 rounded-lg border border-border bg-background/95 px-4 py-3 shadow-lg backdrop-blur-md">
|
|
17
|
+
{hints.map((hint) => (
|
|
18
|
+
<div className="flex items-center gap-2 text-sm" key={`${hint.key}:${hint.label}`}>
|
|
19
|
+
<ShortcutToken value={hint.key} />
|
|
20
|
+
<span className="text-muted-foreground">{hint.label}</span>
|
|
21
|
+
</div>
|
|
22
|
+
))}
|
|
23
|
+
</div>
|
|
24
|
+
)
|
|
25
|
+
}
|