@pascal-app/editor 0.8.0 → 0.9.2
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 +15 -13
- package/src/components/editor/alignment-3d-guide-layer.tsx +168 -0
- package/src/components/editor/bake-exporter.tsx +47 -0
- package/src/components/editor/custom-camera-controls.tsx +795 -28
- package/src/components/editor/editor-layout-mobile.tsx +8 -3
- package/src/components/editor/editor-layout-v2.tsx +76 -51
- package/src/components/editor/export-manager.tsx +69 -32
- package/src/components/editor/fence-tangent-lines-3d.tsx +87 -0
- package/src/components/editor/first-person/build-collider-world.test.ts +182 -0
- package/src/components/editor/first-person/build-collider-world.ts +228 -83
- package/src/components/editor/first-person-controls.tsx +943 -27
- package/src/components/editor/floating-action-menu.tsx +676 -208
- package/src/components/editor/floating-building-action-menu.tsx +2 -2
- package/src/components/editor/floorplan-background-selection.test.ts +58 -0
- package/src/components/editor/floorplan-background-selection.ts +3 -2
- package/src/components/editor/floorplan-panel.tsx +6916 -12137
- package/src/components/editor/grid.tsx +197 -36
- package/src/components/editor/group-actions.ts +457 -0
- package/src/components/editor/group-floating-action-menu.tsx +130 -0
- package/src/components/editor/group-move-3d.ts +397 -0
- package/src/components/editor/group-rotate-handle.tsx +420 -0
- package/src/components/editor/group-selection-box-3d.tsx +173 -0
- package/src/components/editor/group-transform-shared.test.ts +396 -0
- package/src/components/editor/group-transform-shared.ts +489 -0
- package/src/components/editor/handles/handle-arrow.tsx +581 -0
- package/src/components/editor/handles/use-handle-drag.ts +246 -0
- package/src/components/editor/index.tsx +412 -162
- package/src/components/editor/measurement-pill.tsx +115 -0
- package/src/components/editor/node-action-menu.tsx +14 -1
- package/src/components/editor/node-arrow-handles.tsx +1646 -0
- package/src/components/editor/opening-guides-3d-layer.tsx +144 -0
- package/src/components/editor/quick-measurement-card.tsx +70 -0
- package/src/components/editor/quick-measurement-hud.tsx +28 -0
- package/src/components/editor/riser-diagram-panel.tsx +137 -0
- package/src/components/editor/selection-manager.tsx +1106 -483
- package/src/components/editor/site-edge-labels.tsx +45 -21
- package/src/components/editor/slab-hole-highlights.tsx +474 -0
- package/src/components/editor/snapshot-capture-overlay.tsx +369 -155
- package/src/components/editor/three-context-bridge.ts +22 -0
- package/src/components/editor/thumbnail-generator.tsx +140 -41
- package/src/components/editor/use-floorplan-background-placement.ts +190 -35
- package/src/components/editor/use-floorplan-hit-testing.ts +24 -0
- package/src/components/editor/use-floorplan-scene-data.ts +19 -7
- package/src/components/editor/use-mesh-settle-epoch.ts +26 -0
- package/src/components/editor/wall-measurement-label.tsx +14 -19
- package/src/components/editor/wall-move-side-handles.tsx +870 -0
- package/src/components/editor/wall-opening-highlights.tsx +181 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +304 -0
- package/src/components/editor-2d/floorplan-action-menu-layer.tsx +15 -6
- 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-group-action-menu.tsx +87 -0
- package/src/components/editor-2d/floorplan-group-move.tsx +701 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +113 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1656 -0
- package/src/components/editor-2d/floorplan-quick-measure-layer.tsx +212 -0
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +392 -0
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +857 -0
- package/src/components/editor-2d/floorplan-render-context.tsx +62 -0
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +77 -0
- package/src/components/editor-2d/floorplan-wall-move-ghost-layer.tsx +47 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +266 -0
- package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +15 -0
- package/src/components/editor-2d/renderers/floorplan-label-angle.ts +14 -0
- package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +108 -66
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +62 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +308 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +3325 -0
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +24 -9
- package/src/components/editor-2d/renderers/floorplan-voronoi-layer.tsx +128 -0
- package/src/components/editor-2d/svg-paths.ts +10 -2
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +439 -57
- package/src/components/systems/ceiling/ceiling-system.tsx +109 -12
- package/src/components/systems/roof/roof-edit-system.tsx +1851 -22
- package/src/components/systems/selection-affordance-manager.tsx +38 -0
- package/src/components/systems/stair/stair-edit-system.tsx +1 -1
- package/src/components/systems/zone/zone-label-editor-system.tsx +14 -1
- package/src/components/systems/zone/zone-system.tsx +42 -13
- package/src/components/tools/elevator/elevator-defaults.ts +8 -0
- package/src/components/tools/elevator/elevator-tool.tsx +375 -0
- package/src/components/tools/elevator/move-elevator-tool.tsx +263 -0
- package/src/components/tools/fence/fence-drafting.ts +141 -27
- package/src/components/tools/item/move-tool.tsx +45 -105
- package/src/components/tools/item/placement-math.test.ts +22 -0
- package/src/components/tools/item/placement-math.ts +59 -7
- package/src/components/tools/item/placement-strategies.ts +432 -16
- package/src/components/tools/item/placement-types.ts +22 -1
- package/src/components/tools/item/use-draft-node.ts +51 -2
- package/src/components/tools/item/use-placement-coordinator.tsx +1399 -256
- package/src/components/tools/registry/move-registry-node-tool.tsx +1064 -0
- package/src/components/tools/roof/roof-tool.tsx +350 -28
- package/src/components/tools/select/box-select-state.ts +88 -0
- package/src/components/tools/select/box-select-tool.tsx +442 -520
- package/src/components/tools/select/marquee-geometry.test.ts +161 -0
- package/src/components/tools/select/marquee-geometry.ts +155 -0
- package/src/components/tools/select/plane-box-select-tool.tsx +570 -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/cursor-sphere.tsx +62 -26
- package/src/components/tools/shared/drag-bounding-box.tsx +195 -0
- package/src/components/tools/shared/facing-indicator.tsx +87 -0
- package/src/components/tools/shared/facing-pose-indicator.tsx +54 -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 +305 -0
- package/src/components/tools/shared/polygon-editor.tsx +866 -175
- package/src/components/tools/shared/segment-angle.ts +56 -0
- package/src/components/tools/site/site-boundary-editor.tsx +520 -16
- package/src/components/tools/site/site-flag-model.ts +18 -0
- package/src/components/tools/stair/stair-defaults.ts +2 -1
- package/src/components/tools/stair/stair-tool.tsx +342 -37
- package/src/components/tools/tool-manager.tsx +239 -62
- package/src/components/tools/wall/wall-drafting.test.ts +330 -0
- package/src/components/tools/wall/wall-drafting.ts +248 -171
- package/src/components/tools/wall/wall-snap-geometry.test.ts +123 -0
- package/src/components/tools/wall/wall-snap-geometry.ts +331 -0
- package/src/components/tools/zone/zone-boundary-editor.tsx +5 -1
- package/src/components/tools/zone/zone-tool.tsx +86 -82
- package/src/components/ui/action-menu/action-button.tsx +21 -1
- package/src/components/ui/action-menu/camera-actions.tsx +24 -17
- package/src/components/ui/action-menu/control-modes.tsx +40 -170
- package/src/components/ui/action-menu/furnish-tools.tsx +5 -5
- package/src/components/ui/action-menu/index.tsx +3 -117
- package/src/components/ui/action-menu/measurement-control.tsx +188 -0
- package/src/components/ui/action-menu/structure-tools.tsx +27 -93
- package/src/components/ui/action-menu/view-toggles.tsx +279 -90
- package/src/components/ui/command-palette/editor-commands.tsx +20 -15
- package/src/components/ui/command-palette/index.tsx +5 -3
- package/src/components/ui/controls/material-paint-panel.tsx +152 -0
- package/src/components/ui/controls/material-picker.tsx +87 -177
- package/src/components/ui/controls/material-properties-editor.tsx +108 -0
- package/src/components/ui/controls/metric-control.tsx +142 -46
- package/src/components/ui/controls/scene-material-list.tsx +247 -0
- package/src/components/ui/controls/slider-control.tsx +88 -38
- package/src/components/ui/floating-level-selector.tsx +47 -10
- package/src/components/ui/helpers/building-helper.tsx +10 -23
- package/src/components/ui/helpers/contextual-helper-panel.tsx +428 -0
- package/src/components/ui/helpers/helper-manager.tsx +235 -23
- package/src/components/ui/helpers/item-helper.tsx +28 -32
- package/src/components/ui/helpers/registered-tool-helper.tsx +59 -0
- package/src/components/ui/helpers/roof-helper.tsx +10 -12
- package/src/components/ui/icon-ref.tsx +47 -0
- package/src/components/ui/item-catalog/catalog-items.tsx +40 -1
- package/src/components/ui/item-catalog/item-catalog.tsx +16 -36
- package/src/components/ui/level-duplicate-dialog.tsx +2 -1
- package/src/components/ui/panels/node-display.ts +17 -16
- package/src/components/ui/panels/panel-manager.tsx +55 -58
- package/src/components/ui/panels/panel-wrapper.tsx +238 -12
- package/src/components/ui/panels/parametric-inspector.tsx +482 -0
- package/src/components/ui/panels/reference-panel.tsx +54 -19
- package/src/components/ui/primitives/dropdown-menu.tsx +12 -8
- package/src/components/ui/primitives/shortcut-token.tsx +39 -3
- package/src/components/ui/primitives/sidebar.tsx +1 -0
- package/src/components/ui/scene-loader.tsx +1 -3
- package/src/components/ui/sidebar/app-sidebar.tsx +5 -1
- package/src/components/ui/sidebar/icon-rail.tsx +50 -31
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +263 -0
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +82 -8
- package/src/components/ui/sidebar/panels/plugins-panel.tsx +218 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +122 -41
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +66 -7
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +267 -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 +9 -5
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +86 -0
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -2
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +92 -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 +3 -3
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +86 -0
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +42 -21
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +31 -17
- 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 +140 -0
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +48 -8
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +132 -0
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +9 -5
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +86 -0
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +7 -1
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +2 -2
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +112 -39
- package/src/components/ui/sidebar/panels/site-panel/tree-structure.ts +36 -0
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -2
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -3
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +51 -2
- package/src/components/ui/sidebar/tab-bar.tsx +75 -1
- package/src/components/ui/sidebar/use-plugin-panels.tsx +132 -0
- package/src/components/ui/snap-target-badge.test.tsx +40 -0
- package/src/components/ui/snap-target-badge.tsx +88 -0
- package/src/components/viewer-overlay.tsx +253 -61
- package/src/components/viewer-zone-system.tsx +7 -2
- package/src/hooks/use-auto-save.test.ts +14 -0
- package/src/hooks/use-auto-save.ts +64 -10
- package/src/hooks/use-ceiling-events.ts +176 -0
- package/src/hooks/use-drag-action.ts +127 -0
- package/src/hooks/use-keyboard.ts +493 -95
- package/src/hooks/use-selection.ts +64 -0
- package/src/index.tsx +451 -6
- package/src/lib/active-placement-surface.ts +35 -0
- package/src/lib/ceiling-plan-snap.ts +24 -0
- package/src/lib/constants.ts +6 -2
- package/src/lib/contextual-help.test.ts +112 -0
- package/src/lib/contextual-help.ts +144 -0
- package/src/lib/continuation.ts +64 -0
- package/src/lib/direct-manipulation.test.ts +192 -0
- package/src/lib/direct-manipulation.ts +109 -0
- package/src/lib/editor-api.ts +50 -0
- package/src/lib/elevator-support.ts +96 -0
- package/src/lib/floorplan/apply-alignment.test.ts +25 -0
- package/src/lib/floorplan/apply-alignment.ts +117 -0
- package/src/lib/floorplan/floorplan-export.tsx +364 -0
- package/src/lib/floorplan/geometry.ts +53 -0
- package/src/lib/floorplan/index.ts +10 -0
- package/src/lib/floorplan/items.ts +5 -2
- package/src/lib/floorplan/plan-coords.ts +21 -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 +339 -0
- package/src/lib/fresh-planar-placement.ts +130 -0
- package/src/lib/glb-export.test.ts +441 -0
- package/src/lib/glb-export.ts +874 -0
- package/src/lib/history.ts +11 -1
- package/src/lib/interaction/hot-set.test.ts +133 -0
- package/src/lib/interaction/hot-set.ts +67 -0
- package/src/lib/interaction/overlay-policy.test.ts +51 -0
- package/src/lib/interaction/overlay-policy.ts +59 -0
- package/src/lib/interaction/scope.ts +211 -0
- package/src/lib/level-duplication.ts +54 -42
- package/src/lib/level-selection.ts +2 -2
- package/src/lib/material-paint.ts +227 -48
- package/src/lib/measurement-kind.test.ts +30 -0
- package/src/lib/measurement-kind.ts +19 -0
- package/src/lib/measurement-label.test.ts +47 -0
- package/src/lib/measurement-label.ts +68 -0
- package/src/lib/measurement-parser.ts +116 -0
- package/src/lib/measurements.test.ts +183 -0
- package/src/lib/measurements.ts +199 -0
- package/src/lib/paint-scope.test.ts +454 -0
- package/src/lib/paint-scope.ts +461 -0
- package/src/lib/placement-drag-release.ts +23 -0
- package/src/lib/placement-metadata.ts +29 -0
- package/src/lib/planar-cursor-placement.test.ts +100 -0
- package/src/lib/planar-cursor-placement.ts +42 -0
- package/src/lib/plugin-panels.test.ts +19 -0
- package/src/lib/plugin-panels.ts +91 -0
- package/src/lib/quick-measurement.test.ts +77 -0
- package/src/lib/quick-measurement.ts +109 -0
- package/src/lib/roof-duplication.ts +7 -7
- package/src/lib/roof-hover-outline-proxy.ts +22 -0
- package/src/lib/roof-wall-hit.ts +164 -0
- package/src/lib/scene-clipboard.test.ts +196 -0
- package/src/lib/scene-clipboard.ts +332 -0
- package/src/lib/scene.ts +40 -8
- package/src/lib/selection-routing.test.ts +298 -0
- package/src/lib/selection-routing.ts +175 -0
- package/src/lib/sfx/index.ts +1 -0
- package/src/lib/sfx/movement-tick.test.ts +65 -0
- package/src/lib/sfx/movement-tick.ts +18 -0
- package/src/lib/sfx-bus.ts +69 -10
- package/src/lib/sfx-player.test.ts +124 -0
- package/src/lib/sfx-player.ts +148 -55
- package/src/lib/site-boundary.ts +1 -0
- package/src/lib/slab-plan-snap.test.ts +93 -0
- package/src/lib/slab-plan-snap.ts +108 -0
- package/src/lib/snapping-mode.test.ts +138 -0
- package/src/lib/snapping-mode.ts +179 -0
- package/src/lib/stair-duplication.ts +4 -4
- package/src/lib/stair-levels.test.ts +146 -0
- package/src/lib/stair-levels.ts +177 -0
- package/src/lib/surface-plan-snap.test.ts +71 -0
- package/src/lib/surface-plan-snap.ts +256 -0
- package/src/lib/use-linear-display.ts +40 -0
- package/src/lib/world-grid-snap.ts +231 -0
- package/src/lib/zone-content.ts +130 -0
- package/src/store/use-alignment-guides.ts +21 -0
- package/src/store/use-direct-manipulation-feedback.ts +20 -0
- package/src/store/use-editor.tsx +767 -114
- package/src/store/use-facing-pose.ts +44 -0
- package/src/store/use-fence-curve-draft.ts +21 -0
- package/src/store/use-floorplan-draft-preview.ts +101 -0
- package/src/store/use-floorplan-marquee.ts +50 -0
- package/src/store/use-interaction-scope.test.ts +186 -0
- package/src/store/use-interaction-scope.ts +132 -0
- package/src/store/use-measurement-draft.test.ts +530 -0
- package/src/store/use-measurement-draft.ts +543 -0
- package/src/store/use-opening-guides.ts +41 -0
- package/src/store/use-placement-preview.ts +39 -0
- package/src/store/use-quick-measurement-hud.test.ts +53 -0
- package/src/store/use-quick-measurement-hud.ts +77 -0
- package/src/store/use-segment-draft-chain.ts +28 -0
- package/src/store/use-stair-build-preview.ts +43 -0
- package/src/store/use-wall-move-ghosts.ts +36 -0
- package/src/store/use-wall-snap-indicator.ts +34 -0
- package/src/components/editor/first-person/bvh-ecctrl.tsx +0 -860
- 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/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 -264
- 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 -178
- package/src/components/tools/fence/fence-tool.tsx +0 -346
- package/src/components/tools/fence/move-fence-endpoint-tool.tsx +0 -441
- package/src/components/tools/fence/move-fence-tool.tsx +0 -302
- package/src/components/tools/item/item-tool.tsx +0 -26
- package/src/components/tools/roof/move-roof-tool.tsx +0 -364
- 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 -178
- package/src/components/tools/wall/move-wall-endpoint-tool.tsx +0 -426
- package/src/components/tools/wall/move-wall-tool.tsx +0 -358
- 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 -249
- package/src/components/ui/panels/column-panel.tsx +0 -759
- package/src/components/ui/panels/door-panel.tsx +0 -1299
- package/src/components/ui/panels/fence-panel.tsx +0 -222
- package/src/components/ui/panels/item-panel.tsx +0 -306
- package/src/components/ui/panels/paint-panel.tsx +0 -163
- package/src/components/ui/panels/presets/presets-popover.tsx +0 -511
- package/src/components/ui/panels/roof-panel.tsx +0 -280
- 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 -161
- package/src/components/ui/panels/stair-panel.tsx +0 -577
- package/src/components/ui/panels/stair-segment-panel.tsx +0 -325
- package/src/components/ui/panels/wall-panel.tsx +0 -273
- package/src/components/ui/panels/window-panel.tsx +0 -968
- package/src/contexts/presets-context.tsx +0 -121
- package/src/hooks/use-contextual-tools.ts +0 -61
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import { useScene } from '@pascal-app/core'
|
|
4
4
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
5
|
+
import {
|
|
6
|
+
lingoUnitSpec,
|
|
7
|
+
measurementHint,
|
|
8
|
+
parseMeasurement,
|
|
9
|
+
} from '../../../lib/measurement-parser'
|
|
10
|
+
import { useLinearDisplay } from '../../../lib/use-linear-display'
|
|
5
11
|
import { cn } from '../../../lib/utils'
|
|
6
12
|
|
|
7
13
|
interface SliderControlProps {
|
|
@@ -59,10 +65,17 @@ export function SliderControl({
|
|
|
59
65
|
unit = '',
|
|
60
66
|
restoreOnCommit = true,
|
|
61
67
|
}: SliderControlProps) {
|
|
68
|
+
// Display/storage conversion so the value honors the metric/imperial toggle.
|
|
69
|
+
// `value`, `onChange`, `onCommit`, `min`/`max`/`clamp` are always in the
|
|
70
|
+
// stored unit (meters for `unit === 'm'`); the step, drag deltas, text field
|
|
71
|
+
// and rendered number are in the DISPLAY unit (feet when imperial). For
|
|
72
|
+
// metric and non-length units these conversions are the identity.
|
|
73
|
+
const { isImperial, displayUnit, toDisplay, toStored } = useLinearDisplay(unit, precision)
|
|
74
|
+
|
|
62
75
|
const [isEditing, setIsEditing] = useState(false)
|
|
63
76
|
const [isDragging, setIsDragging] = useState(false)
|
|
64
77
|
const [isHovered, setIsHovered] = useState(false)
|
|
65
|
-
const [inputValue, setInputValue] = useState(value.toFixed(precision))
|
|
78
|
+
const [inputValue, setInputValue] = useState(toDisplay(value).toFixed(precision))
|
|
66
79
|
|
|
67
80
|
const dragRef = useRef<{
|
|
68
81
|
// Original value at drag start — preserved across modifier re-anchors so
|
|
@@ -80,12 +93,23 @@ export function SliderControl({
|
|
|
80
93
|
valueRef.current = value
|
|
81
94
|
|
|
82
95
|
const clamp = useCallback((val: number) => Math.min(Math.max(val, min), max), [min, max])
|
|
96
|
+
// Apply a signed display-unit delta to a stored value, rounding in the
|
|
97
|
+
// display unit and clamping in the stored unit.
|
|
98
|
+
const applyDisplayDelta = useCallback(
|
|
99
|
+
(storedValue: number, displayDelta: number, displayStep: number) =>
|
|
100
|
+
clamp(
|
|
101
|
+
toStored(
|
|
102
|
+
Number.parseFloat((toDisplay(storedValue) + displayDelta).toFixed(stepPrecision(displayStep))),
|
|
103
|
+
),
|
|
104
|
+
),
|
|
105
|
+
[clamp, toDisplay, toStored],
|
|
106
|
+
)
|
|
83
107
|
|
|
84
108
|
useEffect(() => {
|
|
85
109
|
if (!isEditing) {
|
|
86
|
-
setInputValue(value.toFixed(precision))
|
|
110
|
+
setInputValue(toDisplay(value).toFixed(precision))
|
|
87
111
|
}
|
|
88
|
-
}, [value, precision, isEditing])
|
|
112
|
+
}, [value, precision, isEditing, toDisplay])
|
|
89
113
|
|
|
90
114
|
// Wheel support on the label
|
|
91
115
|
useEffect(() => {
|
|
@@ -96,14 +120,13 @@ export function SliderControl({
|
|
|
96
120
|
e.preventDefault()
|
|
97
121
|
const direction = e.deltaY < 0 ? 1 : -1
|
|
98
122
|
const s = getAdjustedStep(step, e)
|
|
99
|
-
const
|
|
100
|
-
const final = Number.parseFloat(newValue.toFixed(stepPrecision(s)))
|
|
123
|
+
const final = applyDisplayDelta(valueRef.current, direction * s, s)
|
|
101
124
|
if (final !== valueRef.current) onChange(final)
|
|
102
125
|
onCommit?.(final)
|
|
103
126
|
}
|
|
104
127
|
el.addEventListener('wheel', handleWheel, { passive: false })
|
|
105
128
|
return () => el.removeEventListener('wheel', handleWheel)
|
|
106
|
-
}, [isEditing, step,
|
|
129
|
+
}, [isEditing, step, applyDisplayDelta, onChange, onCommit])
|
|
107
130
|
|
|
108
131
|
// Arrow key support while hovered
|
|
109
132
|
useEffect(() => {
|
|
@@ -115,15 +138,14 @@ export function SliderControl({
|
|
|
115
138
|
if (direction !== 0) {
|
|
116
139
|
e.preventDefault()
|
|
117
140
|
const s = getAdjustedStep(step, e)
|
|
118
|
-
const
|
|
119
|
-
const final = Number.parseFloat(newValue.toFixed(stepPrecision(s)))
|
|
141
|
+
const final = applyDisplayDelta(valueRef.current, direction * s, s)
|
|
120
142
|
if (final !== valueRef.current) onChange(final)
|
|
121
143
|
onCommit?.(final)
|
|
122
144
|
}
|
|
123
145
|
}
|
|
124
146
|
window.addEventListener('keydown', handleKeyDown)
|
|
125
147
|
return () => window.removeEventListener('keydown', handleKeyDown)
|
|
126
|
-
}, [isHovered, isEditing, step,
|
|
148
|
+
}, [isHovered, isEditing, step, applyDisplayDelta, onChange, onCommit])
|
|
127
149
|
|
|
128
150
|
const handleLabelPointerDown = useCallback(
|
|
129
151
|
(e: React.PointerEvent<HTMLDivElement>) => {
|
|
@@ -160,15 +182,13 @@ export function SliderControl({
|
|
|
160
182
|
const dx = e.clientX - anchorX
|
|
161
183
|
const s = step * multiplier
|
|
162
184
|
// 4 px per step at default sensitivity
|
|
163
|
-
const newValue =
|
|
164
|
-
Number.parseFloat((anchorValue + (dx / 4) * s).toFixed(stepPrecision(s))),
|
|
165
|
-
)
|
|
185
|
+
const newValue = applyDisplayDelta(anchorValue, (dx / 4) * s, s)
|
|
166
186
|
if (newValue !== valueRef.current) {
|
|
167
187
|
valueRef.current = newValue
|
|
168
188
|
onChange(newValue)
|
|
169
189
|
}
|
|
170
190
|
},
|
|
171
|
-
[step,
|
|
191
|
+
[step, applyDisplayDelta, onChange],
|
|
172
192
|
)
|
|
173
193
|
|
|
174
194
|
const handleLabelPointerUp = useCallback(
|
|
@@ -195,49 +215,65 @@ export function SliderControl({
|
|
|
195
215
|
|
|
196
216
|
const handleValueClick = useCallback(() => {
|
|
197
217
|
setIsEditing(true)
|
|
198
|
-
setInputValue(value.toFixed(precision))
|
|
199
|
-
}, [value, precision])
|
|
218
|
+
setInputValue(toDisplay(value).toFixed(precision))
|
|
219
|
+
}, [value, precision, toDisplay])
|
|
200
220
|
|
|
201
221
|
const submitValue = useCallback(() => {
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
222
|
+
const spec = lingoUnitSpec(unit)
|
|
223
|
+
let stored = spec
|
|
224
|
+
? parseMeasurement(inputValue, spec, {
|
|
225
|
+
bareUnit: isImperial ? 'ft' : spec.unitId,
|
|
226
|
+
system: isImperial ? 'us' : 'metric',
|
|
227
|
+
})
|
|
228
|
+
: null
|
|
229
|
+
if (stored === null) {
|
|
230
|
+
// Fallback: a bare number typed in the DISPLAY unit → convert to stored.
|
|
231
|
+
const numValue = Number.parseFloat(inputValue)
|
|
232
|
+
stored = Number.isFinite(numValue) ? toStored(numValue) : null
|
|
233
|
+
}
|
|
234
|
+
if (stored === null) {
|
|
235
|
+
setInputValue(toDisplay(value).toFixed(precision))
|
|
205
236
|
} else {
|
|
206
|
-
const nextValue = clamp(Number.parseFloat(
|
|
237
|
+
const nextValue = clamp(toStored(Number.parseFloat(toDisplay(stored).toFixed(precision))))
|
|
207
238
|
onChange(nextValue)
|
|
208
239
|
onCommit?.(nextValue)
|
|
209
240
|
}
|
|
210
241
|
setIsEditing(false)
|
|
211
|
-
}, [inputValue, onChange, onCommit, clamp, precision, value])
|
|
242
|
+
}, [inputValue, unit, isImperial, onChange, onCommit, clamp, precision, value, toDisplay, toStored])
|
|
243
|
+
|
|
244
|
+
const spec = lingoUnitSpec(unit)
|
|
245
|
+
const hint =
|
|
246
|
+
isEditing && spec
|
|
247
|
+
? measurementHint(inputValue, spec, {
|
|
248
|
+
bareUnit: isImperial ? 'ft' : spec.unitId,
|
|
249
|
+
system: isImperial ? 'us' : 'metric',
|
|
250
|
+
displayUnit: isImperial ? 'ft' : spec.unitId,
|
|
251
|
+
precision,
|
|
252
|
+
clamp,
|
|
253
|
+
})
|
|
254
|
+
: null
|
|
212
255
|
|
|
213
256
|
const handleInputKeyDown = useCallback(
|
|
214
257
|
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
215
258
|
if (e.key === 'Enter') {
|
|
216
259
|
submitValue()
|
|
217
260
|
} else if (e.key === 'Escape') {
|
|
218
|
-
setInputValue(value.toFixed(precision))
|
|
261
|
+
setInputValue(toDisplay(value).toFixed(precision))
|
|
219
262
|
setIsEditing(false)
|
|
220
|
-
} else if (e.key === 'ArrowUp') {
|
|
263
|
+
} else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
|
|
221
264
|
e.preventDefault()
|
|
265
|
+
const direction = e.key === 'ArrowUp' ? 1 : -1
|
|
222
266
|
const adjustedStep = getAdjustedStep(step, e)
|
|
223
|
-
const newV =
|
|
224
|
-
Number.parseFloat((value + adjustedStep).toFixed(stepPrecision(adjustedStep))),
|
|
225
|
-
)
|
|
267
|
+
const newV = applyDisplayDelta(value, direction * adjustedStep, adjustedStep)
|
|
226
268
|
onChange(newV)
|
|
227
|
-
setInputValue(newV.toFixed(precision))
|
|
228
|
-
} else if (e.key === 'ArrowDown') {
|
|
229
|
-
e.preventDefault()
|
|
230
|
-
const adjustedStep = getAdjustedStep(step, e)
|
|
231
|
-
const newV = clamp(
|
|
232
|
-
Number.parseFloat((value - adjustedStep).toFixed(stepPrecision(adjustedStep))),
|
|
233
|
-
)
|
|
234
|
-
onChange(newV)
|
|
235
|
-
setInputValue(newV.toFixed(precision))
|
|
269
|
+
setInputValue(toDisplay(newV).toFixed(precision))
|
|
236
270
|
}
|
|
237
271
|
},
|
|
238
|
-
[submitValue, value, precision, step,
|
|
272
|
+
[submitValue, value, precision, step, applyDisplayDelta, onChange, toDisplay],
|
|
239
273
|
)
|
|
240
274
|
|
|
275
|
+
const displayValue = toDisplay(value)
|
|
276
|
+
|
|
241
277
|
return (
|
|
242
278
|
<div
|
|
243
279
|
className={cn(
|
|
@@ -279,6 +315,11 @@ export function SliderControl({
|
|
|
279
315
|
<div className="flex items-center text-xs">
|
|
280
316
|
{isEditing ? (
|
|
281
317
|
<>
|
|
318
|
+
{hint && (
|
|
319
|
+
<span className="mr-1 shrink-0 whitespace-nowrap text-[10px] text-muted-foreground/50 tabular-nums">
|
|
320
|
+
{hint}
|
|
321
|
+
</span>
|
|
322
|
+
)}
|
|
282
323
|
<input
|
|
283
324
|
autoFocus
|
|
284
325
|
className="w-14 bg-transparent p-0 text-right font-mono text-foreground outline-none selection:bg-primary/30"
|
|
@@ -288,7 +329,16 @@ export function SliderControl({
|
|
|
288
329
|
type="text"
|
|
289
330
|
value={inputValue}
|
|
290
331
|
/>
|
|
291
|
-
{
|
|
332
|
+
{displayUnit && (
|
|
333
|
+
<span
|
|
334
|
+
className={cn(
|
|
335
|
+
'ml-[1px] transition-opacity duration-150',
|
|
336
|
+
hint ? 'opacity-0' : 'text-muted-foreground/40',
|
|
337
|
+
)}
|
|
338
|
+
>
|
|
339
|
+
{displayUnit}
|
|
340
|
+
</span>
|
|
341
|
+
)}
|
|
292
342
|
</>
|
|
293
343
|
) : (
|
|
294
344
|
<div
|
|
@@ -296,9 +346,9 @@ export function SliderControl({
|
|
|
296
346
|
onClick={handleValueClick}
|
|
297
347
|
>
|
|
298
348
|
<span className="font-mono tabular-nums tracking-tight" suppressHydrationWarning>
|
|
299
|
-
{Number(
|
|
349
|
+
{Number(displayValue.toFixed(precision)).toFixed(precision)}
|
|
300
350
|
</span>
|
|
301
|
-
{
|
|
351
|
+
{displayUnit && <span className="ml-[1px] text-muted-foreground">{displayUnit}</span>}
|
|
302
352
|
</div>
|
|
303
353
|
)}
|
|
304
354
|
</div>
|
|
@@ -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 '@pascal-app/core'
|
|
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,7 +166,7 @@ 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
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',
|
|
@@ -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,32 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ContextualHelperPanel } from './contextual-helper-panel'
|
|
2
2
|
|
|
3
3
|
interface BuildingHelperProps {
|
|
4
4
|
showRotate?: boolean
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
// Rotate is one hint with both keys (R / T) — never two separate
|
|
8
|
+
// counterclockwise / clockwise rows — to match every other placement helper.
|
|
7
9
|
export function BuildingHelper({ showRotate }: BuildingHelperProps) {
|
|
8
10
|
return (
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
<div className="flex items-center gap-2 text-sm">
|
|
17
|
-
<ShortcutToken value="R" />
|
|
18
|
-
<span className="text-muted-foreground">Rotate counterclockwise</span>
|
|
19
|
-
</div>
|
|
20
|
-
<div className="flex items-center gap-2 text-sm">
|
|
21
|
-
<ShortcutToken value="T" />
|
|
22
|
-
<span className="text-muted-foreground">Rotate clockwise</span>
|
|
23
|
-
</div>
|
|
24
|
-
</>
|
|
25
|
-
)}
|
|
26
|
-
<div className="flex items-center gap-2 text-sm">
|
|
27
|
-
<ShortcutToken value="Esc" />
|
|
28
|
-
<span className="text-muted-foreground">Cancel</span>
|
|
29
|
-
</div>
|
|
30
|
-
</div>
|
|
11
|
+
<ContextualHelperPanel
|
|
12
|
+
hints={[
|
|
13
|
+
{ keys: ['Left click'], label: 'Place building' },
|
|
14
|
+
...(showRotate ? [{ keys: ['R', 'T'], label: 'Rotate' }] : []),
|
|
15
|
+
{ keys: ['Esc'], label: 'Cancel' },
|
|
16
|
+
]}
|
|
17
|
+
/>
|
|
31
18
|
)
|
|
32
19
|
}
|