@pascal-app/editor 1.0.6 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +18 -15
- package/src/components/editor/action-menu-button.tsx +4 -1
- package/src/components/editor/alignment-3d-guide-layer.tsx +102 -11
- package/src/components/editor/bake-thumbnail.tsx +3 -2
- package/src/components/editor/custom-camera-controls.tsx +110 -42
- package/src/components/editor/duplicate-node.ts +2 -1
- package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
- package/src/components/editor/first-person-controls.tsx +77 -68
- package/src/components/editor/fit-scene-framing.test.ts +318 -0
- package/src/components/editor/fit-scene-framing.ts +142 -0
- package/src/components/editor/floating-action-menu.tsx +31 -11
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floor-openings-overlay.tsx +1 -1
- package/src/components/editor/floor-region-controls.tsx +2 -2
- package/src/components/editor/floorplan-panel.test.ts +220 -0
- package/src/components/editor/floorplan-panel.tsx +458 -140
- package/src/components/editor/floorplan-wall-typing-keys.test.tsx +152 -0
- package/src/components/editor/floorplan-wall-typing-keys.ts +79 -0
- package/src/components/editor/footprint-height-handle.tsx +2 -2
- package/src/components/editor/grid.tsx +24 -15
- package/src/components/editor/group-actions.ts +19 -1
- package/src/components/editor/group-floating-action-menu.tsx +1 -1
- package/src/components/editor/group-move-3d.ts +27 -2
- package/src/components/editor/group-selection-box-3d.tsx +4 -1
- package/src/components/editor/group-transform-shared.ts +23 -1
- package/src/components/editor/index.tsx +150 -20
- package/src/components/editor/inspector-extension-actions.tsx +36 -0
- package/src/components/editor/localized-html.tsx +22 -0
- package/src/components/editor/measurement-pill.tsx +1 -1
- package/src/components/editor/node-action-menu.tsx +46 -12
- package/src/components/editor/node-arrow-handles.tsx +2 -3
- package/src/components/editor/open-wall-ends-3d-layer.tsx +2 -3
- package/src/components/editor/open-wall-ends-hint.tsx +1 -1
- package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
- package/src/components/editor/quick-measurement-card.tsx +1 -1
- package/src/components/editor/registry-action-contributions.test.tsx +67 -6
- package/src/components/editor/registry-action-contributions.tsx +115 -15
- package/src/components/editor/riser-diagram-panel.tsx +59 -9
- package/src/components/editor/room-controls.tsx +4 -3
- package/src/components/editor/room-handles-2d.tsx +158 -7
- package/src/components/editor/room-handles.tsx +244 -54
- package/src/components/editor/room-highlight.tsx +2 -0
- package/src/components/editor/room-labels.tsx +33 -17
- package/src/components/editor/selection-manager.tsx +15 -102
- package/src/components/editor/separator-end-grip.tsx +74 -0
- package/src/components/editor/site-edge-labels.tsx +7 -3
- package/src/components/editor/thumbnail-generator.test.ts +15 -6
- package/src/components/editor/thumbnail-generator.tsx +26 -1
- package/src/components/editor/use-floorplan-background-placement.ts +55 -52
- package/src/components/editor/views/view-bar.tsx +111 -26
- package/src/components/editor/wall-measurement-label.tsx +31 -15
- package/src/components/editor/wall-move-side-handles.tsx +15 -7
- package/src/components/editor/wall-pull-grip.tsx +193 -0
- package/src/components/editor/wall-region-handles.tsx +2 -2
- package/src/components/editor/wall-snap-beacon-layer.tsx +134 -33
- package/src/components/editor-2d/floorplan-adjacent-walls-layer.test.ts +76 -0
- package/src/components/editor-2d/floorplan-adjacent-walls-layer.tsx +186 -0
- package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +63 -3
- package/src/components/editor-2d/floorplan-group-move.tsx +32 -4
- package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +6 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +33 -24
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +12 -5
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +2 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +29 -5
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +96 -4
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +52 -12
- package/src/components/editor-2d/renderers/floorplan-draft-measurement.tsx +7 -1
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +13 -1
- package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.test.tsx +35 -0
- package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.tsx +65 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +34 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +211 -39
- package/src/components/procedural-items/inspector.tsx +59 -12
- package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +2 -0
- package/src/components/systems/ceiling/ceiling-system.test.ts +141 -0
- package/src/components/systems/ceiling/ceiling-system.tsx +53 -18
- package/src/components/systems/roof/roof-edit-system.test.tsx +15 -1
- package/src/components/systems/roof/roof-edit-system.tsx +30 -19
- package/src/components/tools/distribution/distribution-run-tool.test.tsx +371 -3
- package/src/components/tools/distribution/distribution-run-tool.tsx +200 -58
- package/src/components/tools/item/move-tool.tsx +1 -6
- package/src/components/tools/item/placement-history.test.tsx +1 -1
- package/src/components/tools/item/placement-strategies.test.ts +64 -1
- package/src/components/tools/item/placement-strategies.ts +5 -9
- package/src/components/tools/item/use-placement-coordinator.tsx +31 -35
- package/src/components/tools/paint-region/wall-paint-region-tool.tsx +1 -1
- package/src/components/tools/registry/item-surface-move.test.ts +11 -0
- package/src/components/tools/registry/move-registry-node-tool.test.ts +42 -1
- package/src/components/tools/registry/move-registry-node-tool.tsx +38 -14
- package/src/components/tools/select/select-candidates.test.ts +45 -0
- package/src/components/tools/select/select-candidates.ts +13 -2
- package/src/components/tools/shared/cursor-sphere.tsx +10 -3
- package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
- package/src/components/tools/shared/placement-box.tsx +2 -6
- package/src/components/tools/shared/placement-coordinate-input.tsx +94 -0
- package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
- package/src/components/tools/shared/segment-angle.ts +0 -4
- package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
- package/src/components/tools/tool-manager.tsx +1 -24
- package/src/components/tools/wall/wall-drafting.test.ts +299 -23
- package/src/components/tools/wall/wall-drafting.ts +132 -24
- package/src/components/tools/wall/wall-pull-snap.test.ts +274 -0
- package/src/components/tools/wall/wall-pull-snap.ts +563 -0
- package/src/components/tools/wall/wall-snap-geometry.test.ts +113 -1
- package/src/components/tools/wall/wall-snap-geometry.ts +137 -0
- package/src/components/tools/wall/wall-typed-entry.test.ts +99 -0
- package/src/components/tools/wall/wall-typed-entry.ts +70 -0
- package/src/components/tools/zone/zone-tool.tsx +3 -3
- package/src/components/ui/action-menu/action-button.tsx +2 -0
- package/src/components/ui/action-menu/camera-actions.tsx +8 -7
- package/src/components/ui/action-menu/control-modes.tsx +9 -5
- package/src/components/ui/action-menu/follow-pascal-pill.tsx +3 -1
- package/src/components/ui/action-menu/follow-pascal-toggle.tsx +7 -1
- package/src/components/ui/action-menu/furnish-tools.tsx +7 -6
- package/src/components/ui/action-menu/index.tsx +1 -0
- package/src/components/ui/action-menu/measurement-control.tsx +35 -29
- package/src/components/ui/action-menu/point-ask-button.tsx +4 -3
- package/src/components/ui/action-menu/structure-tools.tsx +25 -23
- package/src/components/ui/action-menu/view-toggles.test.ts +18 -0
- package/src/components/ui/action-menu/view-toggles.tsx +256 -51
- package/src/components/ui/build-panel/build-panel.tsx +163 -25
- package/src/components/ui/command-palette/editor-commands.tsx +38 -11
- package/src/components/ui/controls/finish-swatch.tsx +45 -19
- package/src/components/ui/controls/material-picker.tsx +24 -17
- package/src/components/ui/controls/metric-control.test.ts +52 -0
- package/src/components/ui/controls/metric-control.tsx +62 -24
- package/src/components/ui/controls/segmented-control.tsx +5 -2
- package/src/components/ui/controls/slider-control.test.tsx +23 -0
- package/src/components/ui/controls/slider-control.tsx +47 -19
- package/src/components/ui/controls/tool-options-panel.tsx +74 -44
- package/src/components/ui/floating-level-selector.tsx +100 -48
- package/src/components/ui/helpers/building-helper.tsx +5 -3
- package/src/components/ui/helpers/contextual-helper-panel.tsx +97 -37
- package/src/components/ui/helpers/helper-manager.test.tsx +106 -3
- package/src/components/ui/helpers/helper-manager.tsx +136 -57
- package/src/components/ui/helpers/item-helper.tsx +6 -4
- package/src/components/ui/helpers/registered-tool-helper.tsx +3 -1
- package/src/components/ui/level-duplicate-dialog.tsx +32 -23
- package/src/components/ui/level-wall-copy-prompt.tsx +124 -0
- package/src/components/ui/panels/collections/collections-popover.tsx +11 -11
- package/src/components/ui/panels/deck-boards-fields.tsx +136 -0
- package/src/components/ui/panels/floor-foundation-panel.tsx +114 -39
- package/src/components/ui/panels/floor-opening-panel.tsx +74 -26
- package/src/components/ui/panels/inspector-extensions.tsx +66 -0
- package/src/components/ui/panels/mobile-panel-sheet.tsx +3 -1
- package/src/components/ui/panels/mobile-selection-bar.tsx +34 -28
- package/src/components/ui/panels/multi-field-value.test.ts +27 -0
- package/src/components/ui/panels/multi-field-value.ts +20 -1
- package/src/components/ui/panels/multi-height-mode.tsx +11 -10
- package/src/components/ui/panels/multi-parametric-inspector.tsx +122 -34
- package/src/components/ui/panels/multi-selection-panel.tsx +76 -36
- package/src/components/ui/panels/node-display.ts +35 -24
- package/src/components/ui/panels/panel-manager.tsx +9 -5
- package/src/components/ui/panels/panel-wrapper.tsx +63 -127
- package/src/components/ui/panels/parametric-field-control.tsx +13 -10
- package/src/components/ui/panels/parametric-inspector.tsx +18 -12
- package/src/components/ui/panels/reference-panel.tsx +51 -35
- package/src/components/ui/panels/room-construction-rows.tsx +465 -66
- package/src/components/ui/panels/room-name-options.test.ts +36 -3
- package/src/components/ui/panels/room-name-options.ts +110 -11
- package/src/components/ui/panels/room-openings-field.tsx +22 -13
- package/src/components/ui/panels/room-panel.tsx +207 -100
- package/src/components/ui/panels/shape-choice.tsx +7 -5
- package/src/components/ui/panels/wall-region-list.tsx +7 -7
- package/src/components/ui/primitives/shortcut-token.tsx +10 -6
- package/src/components/ui/right-stack.test.ts +13 -6
- package/src/components/ui/right-stack.tsx +21 -10
- package/src/components/ui/sidebar/icon-rail.tsx +11 -9
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +10 -10
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +10 -9
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +3 -0
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +102 -32
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +5 -3
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +7 -5
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/default-names.ts +75 -0
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +118 -72
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +36 -3
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +7 -4
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +8 -2
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +8 -5
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +9 -4
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +8 -7
- package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +7 -6
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +16 -1
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +15 -8
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/zone-membership-checkbox.tsx +3 -1
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +19 -9
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +25 -14
- package/src/components/ui/sidebar/tab-bar.tsx +4 -1
- package/src/components/ui/sidebar/use-plugin-panels.tsx +19 -10
- package/src/components/ui/snap-target-badge.tsx +7 -6
- package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +721 -0
- package/src/components/ui/wall-type/wall-type-menu.tsx +167 -0
- package/src/components/viewer/floorplan-compass-button.tsx +12 -3
- package/src/components/viewer/floorplan-preview.tsx +27 -19
- package/src/components/viewer/render-settings-menu.tsx +294 -0
- package/src/components/viewer/sky-control.tsx +710 -0
- package/src/components/viewer/use-viewer-ui-sounds.test.ts +15 -0
- package/src/components/viewer/use-viewer-ui-sounds.ts +46 -0
- package/src/components/viewer/viewer-controls-bar.tsx +278 -260
- package/src/components/viewer/viewer-hover-label.tsx +12 -4
- package/src/components/viewer/viewer-rooms.tsx +62 -5
- package/src/components/viewer/viewer-scene-header.tsx +28 -17
- package/src/components/viewer/viewer-stage-switcher.tsx +4 -2
- package/src/components/viewer-overlay.tsx +8 -0
- package/src/components/walkthrough-hud.tsx +75 -2
- package/src/hooks/use-keyboard.test.ts +85 -4
- package/src/hooks/use-keyboard.ts +65 -7
- package/src/hooks/use-selected-room.ts +12 -1
- package/src/i18n/floor-footprint-name.ts +30 -0
- package/src/i18n/level-name.ts +39 -0
- package/src/i18n/locales/en/messages.po +15849 -0
- package/src/i18n/locales/en/messages.ts +1 -0
- package/src/i18n/locales/en-XA/messages.ts +1 -0
- package/src/i18n/locales/es/messages.po +39331 -0
- package/src/i18n/locales/es/messages.ts +1 -0
- package/src/i18n/locales/fr/messages.po +39331 -0
- package/src/i18n/locales/fr/messages.ts +1 -0
- package/src/i18n/locales/ja/messages.po +39331 -0
- package/src/i18n/locales/ja/messages.ts +1 -0
- package/src/i18n/locales/zh-CN/messages.po +39331 -0
- package/src/i18n/locales/zh-CN/messages.ts +1 -0
- package/src/i18n/locales.test.ts +63 -0
- package/src/i18n/locales.ts +32 -0
- package/src/i18n/message-slot.tsx +25 -0
- package/src/i18n/node-messages.ts +169 -0
- package/src/i18n/pseudo-locale.ts +17 -0
- package/src/i18n/published-entry.test.ts +17 -0
- package/src/i18n/scene-theme-name.ts +16 -0
- package/src/index.tsx +224 -45
- package/src/lib/add-storey.test.ts +3 -3
- package/src/lib/contextual-help.test.ts +20 -1
- package/src/lib/contextual-help.ts +101 -19
- package/src/lib/continuation.ts +38 -11
- package/src/lib/elevation-guides.ts +21 -6
- package/src/lib/elevator-support.ts +1 -72
- package/src/lib/floor-footprints.test.ts +3 -2
- package/src/lib/floor-footprints.ts +1 -9
- package/src/lib/floor-foundation-support.test.ts +15 -6
- package/src/lib/floor-opening-draft.ts +1 -1
- package/src/lib/floor-region-session.ts +1 -1
- package/src/lib/floorplan/dimension-drive.test.ts +36 -2
- package/src/lib/floorplan/dimension-drive.ts +68 -3
- package/src/lib/floorplan/floorplan-export.test.ts +16 -5
- package/src/lib/floorplan/floorplan-export.tsx +13 -16
- package/src/lib/floorplan/floorplan-extension.test.ts +11 -12
- package/src/lib/floorplan/floorplan-extension.ts +37 -18
- package/src/lib/floorplan/floorplan-mode.test.ts +13 -6
- package/src/lib/floorplan/floorplan-mode.ts +14 -12
- package/src/lib/floorplan/floorplan-plugin-install.test.tsx +1 -1
- package/src/lib/floorplan/floorplan-readonly.ts +3 -3
- package/src/lib/floorplan/open-wall-ends.ts +2 -1
- package/src/lib/floorplan/schedules.ts +10 -6
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +46 -1
- package/src/lib/floorplan/site-plan/site-annotations.ts +3 -0
- package/src/lib/floorplan/site-plan/site-parts.ts +6 -3
- package/src/lib/geometry-script/author.test.ts +114 -0
- package/src/lib/geometry-script/author.ts +55 -23
- package/src/lib/geometry-script/client.ts +3 -0
- package/src/lib/geometry-script/finish-scripts.ts +7 -7
- package/src/lib/gesture-lifecycle.ts +3 -1
- package/src/lib/glb-export.test.ts +327 -0
- package/src/lib/glb-export.ts +70 -2
- package/src/lib/grid-frame.test.ts +56 -3
- package/src/lib/grid-frame.ts +28 -1
- package/src/lib/history.test.ts +2 -1
- package/src/lib/hud-title.ts +48 -24
- package/src/lib/ifc-export.test.ts +41 -1
- package/src/lib/inspector-card-mode.test.ts +3 -31
- package/src/lib/inspector-card-mode.ts +5 -10
- package/src/lib/inspector-expanded.test.ts +13 -0
- package/src/lib/inspector-expanded.ts +13 -1
- package/src/lib/interaction/scope.ts +1 -0
- package/src/lib/keyboard-pan.test.ts +7 -0
- package/src/lib/keyboard-pan.ts +9 -2
- package/src/lib/layer-thickness.test.ts +22 -0
- package/src/lib/layer-thickness.ts +79 -0
- package/src/lib/level-selection.test.ts +18 -2
- package/src/lib/level-selection.ts +31 -8
- package/src/lib/linear-display.ts +1 -1
- package/src/lib/live-gesture-round-trip.test.ts +1 -1
- package/src/lib/lot/drop-in.test.ts +59 -1
- package/src/lib/lot/drop-in.ts +42 -50
- package/src/lib/material-swatch-style.ts +24 -0
- package/src/lib/measurement-label.test.ts +1 -1
- package/src/lib/measurement-parser.ts +7 -3
- package/src/lib/measurements.test.ts +24 -6
- package/src/lib/measurements.ts +1 -203
- package/src/lib/mezzanine-selection.test.ts +3 -1
- package/src/lib/node-action-movement.test.tsx +115 -0
- package/src/lib/node-action-movement.ts +2 -0
- package/src/lib/orbit-near-plane.test.ts +12 -0
- package/src/lib/orbit-near-plane.ts +13 -0
- package/src/lib/paint-regions.test.ts +8 -5
- package/src/lib/paint-scope.test.ts +53 -0
- package/src/lib/paint-scope.ts +18 -4
- package/src/lib/panel-rows.ts +17 -8
- package/src/lib/placement-metadata.ts +4 -0
- package/src/lib/point-ask/describe-target.ts +6 -8
- package/src/lib/point-ask/region.ts +1 -1
- package/src/lib/print-shell-compiler.ts +12 -1
- package/src/lib/print-wall-solids.test.ts +11 -0
- package/src/lib/print-wall-solids.ts +9 -0
- package/src/lib/reference-scale.ts +83 -0
- package/src/lib/room-assembly-overlay.ts +1 -1
- package/src/lib/room-built-on.test.ts +28 -17
- package/src/lib/room-built-on.ts +21 -17
- package/src/lib/room-construction-commands.ts +3 -2
- package/src/lib/room-construction.test.ts +28 -1
- package/src/lib/room-construction.ts +14 -0
- package/src/lib/room-controls-phase6.test.ts +141 -1
- package/src/lib/room-handle-drag.ts +554 -58
- package/src/lib/room-name-catalog.test.ts +8 -0
- package/src/lib/room-name-catalog.ts +8 -1
- package/src/lib/room-name-messages.test.ts +132 -0
- package/src/lib/room-name-messages.ts +147 -0
- package/src/lib/room-name-storage.test.ts +334 -0
- package/src/lib/room-push-dimensions.test.ts +38 -1
- package/src/lib/room-push-dimensions.ts +226 -2
- package/src/lib/room-selection.test.ts +23 -6
- package/src/lib/scene-bounds.test.ts +144 -0
- package/src/lib/scene-bounds.ts +79 -15
- package/src/lib/scene-clipboard.test.ts +70 -49
- package/src/lib/scene-clipboard.ts +11 -5
- package/src/lib/selection-cycle.ts +63 -0
- package/src/lib/separator-edit.test.ts +431 -0
- package/src/lib/separator-edit.ts +179 -0
- package/src/lib/separator-lines.ts +38 -0
- package/src/lib/sfx-bus.ts +27 -1
- package/src/lib/sfx-player.ts +16 -0
- package/src/lib/snapping-mode.test.ts +23 -1
- package/src/lib/tool-cursor-icon.ts +5 -1
- package/src/lib/tool-shortcuts.ts +130 -0
- package/src/lib/viewer-selection.test.ts +28 -24
- package/src/lib/viewer-selection.ts +21 -6
- package/src/lib/wall-draw-variant.test.ts +7 -6
- package/src/lib/wall-draw-variant.ts +107 -6
- package/src/lib/wall-nudge.test.ts +44 -0
- package/src/lib/wall-nudge.ts +131 -0
- package/src/lib/wall-parallel-snap.test.ts +83 -0
- package/src/lib/wall-parallel-snap.ts +176 -0
- package/src/lib/wall-placement-pointer.test.ts +23 -0
- package/src/lib/wall-placement-pointer.ts +18 -0
- package/src/lib/wall-pull.test.ts +245 -0
- package/src/lib/wall-pull.ts +209 -0
- package/src/lib/wall-push-line-up.test.ts +132 -0
- package/src/lib/wall-region-list.ts +17 -6
- package/src/lib/wall-run-snap.test.ts +188 -0
- package/src/lib/wall-run-snap.ts +83 -0
- package/src/lib/wall-type.test.ts +125 -0
- package/src/lib/wall-type.ts +119 -0
- package/src/lib/window-interaction.test.ts +44 -0
- package/src/lib/world-grid-snap.test.ts +139 -2
- package/src/lib/world-grid-snap.ts +102 -105
- package/src/store/use-alignment-guides.ts +27 -1
- package/src/store/use-editor.tsx +53 -0
- package/src/store/use-floorplan-annotation-visibility.test.ts +64 -0
- package/src/store/use-floorplan-annotation-visibility.ts +47 -22
- package/src/store/use-placement-typing.test.ts +165 -0
- package/src/store/use-placement-typing.ts +186 -0
- package/src/store/use-wall-snap-indicator.ts +15 -2
- package/src/components/tools/elevator/elevator-defaults.ts +0 -8
- package/src/components/tools/elevator/elevator-tool.tsx +0 -372
- package/src/components/tools/elevator/move-elevator-tool.tsx +0 -262
- package/src/lib/door-interaction.ts +0 -102
- package/src/lib/measurement-label.ts +0 -68
- package/src/lib/window-interaction.ts +0 -100
|
@@ -6,16 +6,18 @@ import {
|
|
|
6
6
|
floorPlacedCollides,
|
|
7
7
|
nodeRegistry,
|
|
8
8
|
type TerrainVerb,
|
|
9
|
-
useScene,
|
|
9
|
+
useScene, usePascalI18n,
|
|
10
10
|
} from '@pascal-app/core'
|
|
11
11
|
import { useViewer } from '@pascal-app/viewer'
|
|
12
12
|
import { useEffect, useMemo, useState, useSyncExternalStore } from 'react'
|
|
13
13
|
import { useShallow } from 'zustand/react/shallow'
|
|
14
14
|
import { useIsMobile } from '../../../hooks/use-mobile'
|
|
15
15
|
import {
|
|
16
|
+
AXIS_MOVE_DRAG_LABEL,
|
|
16
17
|
type ContextualShortcutHint,
|
|
17
18
|
GROUP_MOVE_DRAG_LABEL,
|
|
18
19
|
GROUP_ROTATE_DRAG_LABEL,
|
|
20
|
+
PATH_POINT_DRAG_LABEL,
|
|
19
21
|
ROTATE_HANDLE_DRAG_LABEL,
|
|
20
22
|
resolveRotateHandleHelpHints,
|
|
21
23
|
resolveSelectModeHelpHints,
|
|
@@ -42,7 +44,7 @@ import {
|
|
|
42
44
|
terrainHudTitle,
|
|
43
45
|
toolHudTitle,
|
|
44
46
|
} from '../../../lib/hud-title'
|
|
45
|
-
import type { ReshapeKind } from '../../../lib/interaction/scope'
|
|
47
|
+
import type { InteractionScope, ReshapeKind } from '../../../lib/interaction/scope'
|
|
46
48
|
import {
|
|
47
49
|
type MezzanineShape,
|
|
48
50
|
useMezzanineDraft,
|
|
@@ -94,7 +96,7 @@ function reshapingHints(reshape: ReshapeKind): ContextualShortcutHint[] {
|
|
|
94
96
|
: 'Move corner'
|
|
95
97
|
return [
|
|
96
98
|
{ keys: ['Drag'], label: action },
|
|
97
|
-
{ keys: ['Esc'], label: 'Cancel' },
|
|
99
|
+
{ keys: ['Esc'], label: /* i18n */ { id: 'editor.helpers.helperManager.cancel', message: 'Cancel' } },
|
|
98
100
|
]
|
|
99
101
|
}
|
|
100
102
|
|
|
@@ -106,8 +108,8 @@ function reshapingHints(reshape: ReshapeKind): ContextualShortcutHint[] {
|
|
|
106
108
|
function terrainSculptHints(verb: TerrainVerb, sampling: boolean): ContextualShortcutHint[] {
|
|
107
109
|
if (sampling) {
|
|
108
110
|
return [
|
|
109
|
-
{ keys: ['Click'], label: 'Pick target height' },
|
|
110
|
-
{ keys: ['Esc'], label: 'Cancel picking' },
|
|
111
|
+
{ keys: ['Click'], label: /* i18n */ { id: 'editor.helpers.helperManager.pickTargetHeight', message: 'Pick target height' } },
|
|
112
|
+
{ keys: ['Esc'], label: /* i18n */ { id: 'editor.helpers.helperManager.cancelPicking', message: 'Cancel picking' } },
|
|
111
113
|
]
|
|
112
114
|
}
|
|
113
115
|
const action =
|
|
@@ -122,8 +124,8 @@ function terrainSculptHints(verb: TerrainVerb, sampling: boolean): ContextualSho
|
|
|
122
124
|
{ keys: ['Drag'], label: action },
|
|
123
125
|
// Nested, so the two render as alternatives ("[ / ]") rather than a chord —
|
|
124
126
|
// a flat `['[', ']']` joins with "+" and would read as "press both".
|
|
125
|
-
{ keys: [['[', ']']], label: 'Brush size' },
|
|
126
|
-
{ keys: ['Esc'], label: 'Cancel stroke' },
|
|
127
|
+
{ keys: [['[', ']']], label: /* i18n */ { id: 'editor.helpers.helperManager.brushSize', message: 'Brush size' } },
|
|
128
|
+
{ keys: ['Esc'], label: /* i18n */ { id: 'editor.helpers.helperManager.cancelStroke', message: 'Cancel stroke' } },
|
|
127
129
|
]
|
|
128
130
|
}
|
|
129
131
|
|
|
@@ -141,12 +143,12 @@ const EMPTY_CONTEXTUAL_HINTS: ContextualShortcutHint[] = []
|
|
|
141
143
|
export function paintHints(mode: PaintMode): ContextualShortcutHint[] {
|
|
142
144
|
if (mode === 'pick')
|
|
143
145
|
return [
|
|
144
|
-
{ keys: ['Left click'], label: 'Paint with this material' },
|
|
145
|
-
{ keys: ['Esc'], label: 'Cancel' },
|
|
146
|
+
{ keys: ['Left click'], label: /* i18n */ { id: 'editor.helpers.helperManager.paintWithThisMaterial', message: 'Paint with this material' } },
|
|
147
|
+
{ keys: ['Esc'], label: /* i18n */ { id: 'editor.helpers.helperManager.cancel', message: 'Cancel' } },
|
|
146
148
|
]
|
|
147
149
|
if (mode === 'erase')
|
|
148
|
-
return [{ keys: ['Left click'], label: 'Remove a painted part, or reset a surface' }]
|
|
149
|
-
if (mode === 'surface') return [{ keys: [['Alt', 'Option']], label: 'Hold to pick a material' }]
|
|
150
|
+
return [{ keys: ['Left click'], label: /* i18n */ { id: 'editor.helpers.helperManager.removeAPaintedPartOrReset', message: 'Remove a painted part, or reset a surface' } }]
|
|
151
|
+
if (mode === 'surface') return [{ keys: [['Alt', 'Option']], label: /* i18n */ { id: 'editor.helpers.helperManager.holdToPickAMaterial', message: 'Hold to pick a material' } }]
|
|
150
152
|
return []
|
|
151
153
|
}
|
|
152
154
|
|
|
@@ -156,43 +158,45 @@ export function paintRegionHints(
|
|
|
156
158
|
hovering = true,
|
|
157
159
|
): ContextualShortcutHint[] {
|
|
158
160
|
const gesture: Record<typeof mode, ContextualShortcutHint> = {
|
|
159
|
-
rectangle: { keys: ['Drag'], label: 'Press anywhere, drag to the opposite corner' },
|
|
160
|
-
polygon: { keys: ['Left click'], label: 'Add point · click the first to close' },
|
|
161
|
+
rectangle: { keys: ['Drag'], label: /* i18n */ { id: 'editor.helpers.helperManager.pressAnywhereDragToTheOpposite', message: 'Press anywhere, drag to the opposite corner' } },
|
|
162
|
+
polygon: { keys: ['Left click'], label: /* i18n */ { id: 'editor.helpers.helperManager.addPointClickTheFirstTo', message: 'Add point · click the first to close' } },
|
|
161
163
|
}
|
|
162
164
|
// Off anything the sub-mode draws on, the HUD says where to go instead.
|
|
163
165
|
if (!hovering)
|
|
164
166
|
return [
|
|
165
167
|
...(notice ? [{ keys: ['!'], label: notice, active: true }] : []),
|
|
166
168
|
{ keys: ['Hover'], label: paintRegionHoverHint(mode), active: true },
|
|
167
|
-
{ keys: ['Esc'], label: 'Cancel' },
|
|
169
|
+
{ keys: ['Esc'], label: /* i18n */ { id: 'editor.helpers.helperManager.cancel', message: 'Cancel' } },
|
|
168
170
|
]
|
|
169
171
|
return [
|
|
170
172
|
...(notice ? [{ keys: ['!'], label: notice, active: true }] : []),
|
|
171
173
|
gesture[mode],
|
|
172
|
-
...(mode === 'polygon' ? [{ keys: ['Backspace'], label: 'Remove last point' }] : []),
|
|
173
|
-
{ keys: ['Esc'], label: 'Cancel' },
|
|
174
|
+
...(mode === 'polygon' ? [{ keys: ['Backspace'], label: /* i18n */ { id: 'editor.helpers.helperManager.removeLastPoint', message: 'Remove last point' } }] : []),
|
|
175
|
+
{ keys: ['Esc'], label: /* i18n */ { id: 'editor.helpers.helperManager.cancel', message: 'Cancel' } },
|
|
174
176
|
]
|
|
175
177
|
}
|
|
178
|
+
const DRAW_MEZZANINE = /* i18n */ { id: 'editor.helpers.helperManager.drawTheMezzanine', message: 'Draw the mezzanine' }
|
|
179
|
+
const DRAW_OPENING = /* i18n */ { id: 'editor.helpers.helperManager.drawTheOpening', message: 'Draw the opening' }
|
|
176
180
|
// "Add mezzanine": the outline gesture for the shape picked in the room panel,
|
|
177
181
|
// then Esc; the snapping chip sits below.
|
|
178
182
|
export function mezzanineHints(shape: MezzanineShape): ContextualShortcutHint[] {
|
|
179
183
|
return shape === 'rectangle'
|
|
180
184
|
? [
|
|
181
|
-
{ keys: ['Left click'], label: 'Set one corner, then the opposite' },
|
|
182
|
-
{ keys: ['Drag'], label:
|
|
183
|
-
{ keys: ['Esc'], label: 'Cancel' },
|
|
185
|
+
{ keys: ['Left click'], label: /* i18n */ { id: 'editor.helpers.helperManager.setOneCornerThenTheOpposite', message: 'Set one corner, then the opposite' } },
|
|
186
|
+
{ keys: ['Drag'], label: DRAW_MEZZANINE },
|
|
187
|
+
{ keys: ['Esc'], label: /* i18n */ { id: 'editor.helpers.helperManager.cancel', message: 'Cancel' } },
|
|
184
188
|
]
|
|
185
189
|
: [
|
|
186
|
-
{ keys: ['Left click'], label: 'Add point · click the first to close' },
|
|
187
|
-
{ keys: ['Backspace'], label: 'Remove last point' },
|
|
188
|
-
{ keys: ['Enter'], label: 'Finish' },
|
|
189
|
-
{ keys: ['Esc'], label: 'Cancel' },
|
|
190
|
+
{ keys: ['Left click'], label: /* i18n */ { id: 'editor.helpers.helperManager.addPointClickTheFirstTo', message: 'Add point · click the first to close' } },
|
|
191
|
+
{ keys: ['Backspace'], label: /* i18n */ { id: 'editor.helpers.helperManager.removeLastPoint', message: 'Remove last point' } },
|
|
192
|
+
{ keys: ['Enter'], label: /* i18n */ { id: 'editor.helpers.helperManager.finish', message: 'Finish' } },
|
|
193
|
+
{ keys: ['Esc'], label: /* i18n */ { id: 'editor.helpers.helperManager.cancel', message: 'Cancel' } },
|
|
190
194
|
]
|
|
191
195
|
}
|
|
192
196
|
/** "Cut opening": the outline gesture for the chosen shape, then Esc. */
|
|
193
197
|
export function openingHints(shape: OpeningShape): ContextualShortcutHint[] {
|
|
194
198
|
return mezzanineHints(shape).map((hint) =>
|
|
195
|
-
hint.label
|
|
199
|
+
typeof hint.label !== 'string' && hint.label.id === DRAW_MEZZANINE.id ? { ...hint, label: DRAW_OPENING } : hint,
|
|
196
200
|
)
|
|
197
201
|
}
|
|
198
202
|
const TERRACE_CHIP_HINTS = [TERRACE_SHAPE_HINT]
|
|
@@ -200,22 +204,22 @@ const TERRACE_CHIP_HINTS = [TERRACE_SHAPE_HINT]
|
|
|
200
204
|
export function terraceHints(shape: TerraceShape): ContextualShortcutHint[] {
|
|
201
205
|
return shape === 'rectangle'
|
|
202
206
|
? [
|
|
203
|
-
{ keys: ['Left click'], label: 'Set one corner, then the opposite' },
|
|
204
|
-
{ keys: ['Drag'], label: 'Draw the terrace' },
|
|
205
|
-
{ keys: ['Esc'], label: 'Done' },
|
|
207
|
+
{ keys: ['Left click'], label: /* i18n */ { id: 'editor.helpers.helperManager.setOneCornerThenTheOpposite', message: 'Set one corner, then the opposite' } },
|
|
208
|
+
{ keys: ['Drag'], label: /* i18n */ { id: 'editor.helpers.helperManager.drawTheTerrace', message: 'Draw the terrace' } },
|
|
209
|
+
{ keys: ['Esc'], label: /* i18n */ { id: 'editor.helpers.helperManager.done', message: 'Done' } },
|
|
206
210
|
]
|
|
207
211
|
: [
|
|
208
|
-
{ keys: ['Left click'], label: 'Add point · click the first to close' },
|
|
209
|
-
{ keys: ['Backspace'], label: 'Remove last point' },
|
|
210
|
-
{ keys: ['Enter'], label: 'Finish' },
|
|
211
|
-
{ keys: ['Esc'], label: 'Done' },
|
|
212
|
+
{ keys: ['Left click'], label: /* i18n */ { id: 'editor.helpers.helperManager.addPointClickTheFirstTo', message: 'Add point · click the first to close' } },
|
|
213
|
+
{ keys: ['Backspace'], label: /* i18n */ { id: 'editor.helpers.helperManager.removeLastPoint', message: 'Remove last point' } },
|
|
214
|
+
{ keys: ['Enter'], label: /* i18n */ { id: 'editor.helpers.helperManager.finish', message: 'Finish' } },
|
|
215
|
+
{ keys: ['Esc'], label: /* i18n */ { id: 'editor.helpers.helperManager.done', message: 'Done' } },
|
|
212
216
|
]
|
|
213
217
|
}
|
|
214
218
|
export const ROOM_DIVIDE_HINTS: ContextualShortcutHint[] = [
|
|
215
|
-
{ keys: ['Left click'], label: 'Add point' },
|
|
216
|
-
{ keys: ['Backspace'], label: 'Remove last point' },
|
|
217
|
-
{ keys: ['Enter'], label: 'Finish at the edge' },
|
|
218
|
-
{ keys: ['Esc'], label: 'Cancel' },
|
|
219
|
+
{ keys: ['Left click'], label: /* i18n */ { id: 'editor.helpers.helperManager.addPoint', message: 'Add point' } },
|
|
220
|
+
{ keys: ['Backspace'], label: /* i18n */ { id: 'editor.helpers.helperManager.removeLastPoint', message: 'Remove last point' } },
|
|
221
|
+
{ keys: ['Enter'], label: /* i18n */ { id: 'editor.helpers.helperManager.finishAtTheEdge', message: 'Finish at the edge' } },
|
|
222
|
+
{ keys: ['Esc'], label: /* i18n */ { id: 'editor.helpers.helperManager.cancel', message: 'Cancel' } },
|
|
219
223
|
]
|
|
220
224
|
const NO_CONTEXTUAL_HELP_SUBSCRIPTION = () => () => {}
|
|
221
225
|
|
|
@@ -277,22 +281,76 @@ export function mezzanineGesture(
|
|
|
277
281
|
|
|
278
282
|
export const MEZZANINE_GESTURE_HINTS: Record<MezzanineGesture, ContextualShortcutHint[]> = {
|
|
279
283
|
raise: [
|
|
280
|
-
{ keys: ['Drag'], label: 'Raise or lower' },
|
|
281
|
-
{ keys: ['Esc'], label: 'Cancel' },
|
|
284
|
+
{ keys: ['Drag'], label: /* i18n */ { id: 'editor.helpers.helperManager.raiseOrLower', message: 'Raise or lower' } },
|
|
285
|
+
{ keys: ['Esc'], label: /* i18n */ { id: 'editor.helpers.helperManager.cancel', message: 'Cancel' } },
|
|
282
286
|
],
|
|
283
287
|
resize: [
|
|
284
|
-
{ keys: ['Drag'], label: 'Resize' },
|
|
285
|
-
{ keys: ['Esc'], label: 'Cancel' },
|
|
288
|
+
{ keys: ['Drag'], label: /* i18n */ { id: 'editor.helpers.helperManager.resize', message: 'Resize' } },
|
|
289
|
+
{ keys: ['Esc'], label: /* i18n */ { id: 'editor.helpers.helperManager.cancel', message: 'Cancel' } },
|
|
286
290
|
],
|
|
287
291
|
move: [
|
|
288
|
-
{ keys: ['Left click'], label: 'Place inside the room' },
|
|
289
|
-
{ keys: [['R', 'T']], label: 'Rotate ±45°' },
|
|
290
|
-
{ keys: ['Alt'], label: 'Free move' },
|
|
291
|
-
{ keys: ['Esc'], label: 'Cancel' },
|
|
292
|
+
{ keys: ['Left click'], label: /* i18n */ { id: 'editor.helpers.helperManager.placeInsideTheRoom', message: 'Place inside the room' } },
|
|
293
|
+
{ keys: [['R', 'T']], label: /* i18n */ { id: 'editor.helpers.helperManager.rotate45', message: 'Rotate ±45°' } },
|
|
294
|
+
{ keys: ['Alt'], label: /* i18n */ { id: 'editor.helpers.helperManager.freeMove', message: 'Free move' } },
|
|
295
|
+
{ keys: ['Esc'], label: /* i18n */ { id: 'editor.helpers.helperManager.cancel', message: 'Cancel' } },
|
|
292
296
|
],
|
|
293
297
|
}
|
|
294
298
|
|
|
299
|
+
export type MepHandleGesture = 'edit-path' | 'move'
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* A drag on an MEP handle — a run point, where Alt detaches the joint, or a
|
|
303
|
+
* fitting, run or run segment slid along an axis — or null. Its HUD names the
|
|
304
|
+
* kind and the gesture, not "Resize".
|
|
305
|
+
*/
|
|
306
|
+
export function mepHandleGesture(
|
|
307
|
+
drag: { nodeId: string; label: string } | null,
|
|
308
|
+
): MepHandleGesture | null {
|
|
309
|
+
if (drag?.label === PATH_POINT_DRAG_LABEL) return 'edit-path'
|
|
310
|
+
if (drag?.label === AXIS_MOVE_DRAG_LABEL) return 'move'
|
|
311
|
+
return null
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export function mepHandleGestureHints(
|
|
315
|
+
gesture: MepHandleGesture,
|
|
316
|
+
altPressed: boolean,
|
|
317
|
+
): ContextualShortcutHint[] {
|
|
318
|
+
const cancel: ContextualShortcutHint = {
|
|
319
|
+
keys: ['Esc'],
|
|
320
|
+
label: /* i18n */ { id: 'editor.helpers.helperManager.cancel', message: 'Cancel' },
|
|
321
|
+
}
|
|
322
|
+
if (gesture === 'move') return [cancel]
|
|
323
|
+
return [
|
|
324
|
+
{
|
|
325
|
+
keys: ['Alt'],
|
|
326
|
+
label: /* i18n */ { id: 'editor.helpers.helperManager.detachTheJoint', message: 'Detach the joint' },
|
|
327
|
+
active: altPressed,
|
|
328
|
+
},
|
|
329
|
+
cancel,
|
|
330
|
+
]
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Whether the armed tool's own hints (`toolHints`) win over the node in hand: a registry tool that
|
|
335
|
+
* holds its draft in a placing scope (the roof's footprint, a fitting, a block) says what its clicks
|
|
336
|
+
* do, where the generic rows misnamed it ("Move roof · Left click: Place" while a click draws a
|
|
337
|
+
* footprint).
|
|
338
|
+
*/
|
|
339
|
+
export function toolHintsOwnPlacing(
|
|
340
|
+
scope: InteractionScope,
|
|
341
|
+
tool: string | null,
|
|
342
|
+
toolHintCount: number,
|
|
343
|
+
): boolean {
|
|
344
|
+
return (
|
|
345
|
+
scope.kind === 'placing' &&
|
|
346
|
+
scope.driver === 'registry-tool' &&
|
|
347
|
+
scope.nodeType === tool &&
|
|
348
|
+
toolHintCount > 0
|
|
349
|
+
)
|
|
350
|
+
}
|
|
351
|
+
|
|
295
352
|
export function HelperManager() {
|
|
353
|
+
const i18n = usePascalI18n()
|
|
296
354
|
const mode = useEditor((s) => s.mode)
|
|
297
355
|
const tool = useEditor((s) => s.tool)
|
|
298
356
|
const terrainVerb = useEditor((s) => s.terrainVerb)
|
|
@@ -318,6 +376,10 @@ export function HelperManager() {
|
|
|
318
376
|
const reshapingNode = useReshapingNode()
|
|
319
377
|
const activeHandleDrag = useActiveHandleDrag()
|
|
320
378
|
const mezzanineHandle = useScene((s) => mezzanineGesture(activeHandleDrag, s.nodes))
|
|
379
|
+
const mepHandle = mepHandleGesture(activeHandleDrag)
|
|
380
|
+
const mepHandleKind = useScene((s) =>
|
|
381
|
+
mepHandle && activeHandleDrag ? s.nodes[activeHandleDrag.nodeId as AnyNodeId]?.type : undefined,
|
|
382
|
+
)
|
|
321
383
|
const selectedIds = useViewer((s) => s.selection.selectedIds)
|
|
322
384
|
const isMobile = useIsMobile()
|
|
323
385
|
const modifiers = useActiveModifierKeys()
|
|
@@ -417,7 +479,7 @@ export function HelperManager() {
|
|
|
417
479
|
if (activeHandleDrag?.label === GROUP_MOVE_DRAG_LABEL) {
|
|
418
480
|
return (
|
|
419
481
|
<ContextualHelperPanel
|
|
420
|
-
hints={[{ keys: ['R / T'], label: 'Rotate the selection ±45°' }]}
|
|
482
|
+
hints={[{ keys: ['R / T'], label: i18n._({ id: 'editor.helpers.helperManager.rotateTheSelection45', message: 'Rotate the selection ±45°' }) }]}
|
|
421
483
|
snapContext={snapContext}
|
|
422
484
|
title={MOVE_SELECTION_HUD_TITLE}
|
|
423
485
|
/>
|
|
@@ -440,10 +502,10 @@ export function HelperManager() {
|
|
|
440
502
|
return (
|
|
441
503
|
<ContextualHelperPanel
|
|
442
504
|
hints={[
|
|
443
|
-
{ keys: ['Left click'], label: 'Place' },
|
|
444
|
-
{ keys: [['R', 'T']], label: 'Rotate ±45°' },
|
|
445
|
-
{ keys: ['Alt'], label: 'Free move · slide doors and windows' },
|
|
446
|
-
{ keys: ['Esc'], label: 'Cancel' },
|
|
505
|
+
{ keys: ['Left click'], label: i18n._({ id: 'editor.helpers.helperManager.place', message: 'Place' }) },
|
|
506
|
+
{ keys: [['R', 'T']], label: i18n._({ id: 'editor.helpers.helperManager.rotate45', message: 'Rotate ±45°' }) },
|
|
507
|
+
{ keys: ['Alt'], label: i18n._({ id: 'editor.helpers.helperManager.freeMoveSlideDoorsAndWindows', message: 'Free move · slide doors and windows' }) },
|
|
508
|
+
{ keys: ['Esc'], label: i18n._({ id: 'editor.helpers.helperManager.cancel', message: 'Cancel' }) },
|
|
447
509
|
]}
|
|
448
510
|
snapContext={snapContext}
|
|
449
511
|
title={MOVE_ROOM_HUD_TITLE}
|
|
@@ -496,6 +558,20 @@ export function HelperManager() {
|
|
|
496
558
|
)
|
|
497
559
|
}
|
|
498
560
|
|
|
561
|
+
if (mepHandle) {
|
|
562
|
+
return (
|
|
563
|
+
<ContextualHelperPanel
|
|
564
|
+
hints={mepHandleGestureHints(mepHandle, modifiers.alt)}
|
|
565
|
+
snapContext={snapContext}
|
|
566
|
+
title={
|
|
567
|
+
mepHandleKind
|
|
568
|
+
? nodeKindHudTitle(mepHandleKind, mepHandle === 'move' ? 'move' : 'edit')
|
|
569
|
+
: null
|
|
570
|
+
}
|
|
571
|
+
/>
|
|
572
|
+
)
|
|
573
|
+
}
|
|
574
|
+
|
|
499
575
|
// A single-node resize arrow is still an active snapping interaction. Roof
|
|
500
576
|
// width/depth handles opt into grid snapping, so keep the mode and grid-step
|
|
501
577
|
// controls visible for the whole drag instead of falling through to idle
|
|
@@ -504,8 +580,8 @@ export function HelperManager() {
|
|
|
504
580
|
return (
|
|
505
581
|
<ContextualHelperPanel
|
|
506
582
|
hints={[
|
|
507
|
-
{ keys: ['Drag'], label: 'Resize' },
|
|
508
|
-
{ keys: ['Esc'], label: 'Cancel' },
|
|
583
|
+
{ keys: ['Drag'], label: i18n._({ id: 'editor.helpers.helperManager.resize', message: 'Resize' }) },
|
|
584
|
+
{ keys: ['Esc'], label: i18n._({ id: 'editor.helpers.helperManager.cancel', message: 'Cancel' }) },
|
|
509
585
|
]}
|
|
510
586
|
snapContext={snapContext}
|
|
511
587
|
title={RESIZE_HUD_TITLE}
|
|
@@ -522,7 +598,7 @@ export function HelperManager() {
|
|
|
522
598
|
? nodeRegistry.get(reshapingNode.type)?.affordanceHints?.[scope.reshape]
|
|
523
599
|
: undefined
|
|
524
600
|
const title = reshapingNode
|
|
525
|
-
? nodeKindHudTitle(reshapingNode.type, scope.reshape === 'split' ? '
|
|
601
|
+
? nodeKindHudTitle(reshapingNode.type, scope.reshape === 'split' ? 'split' : 'edit')
|
|
526
602
|
: null
|
|
527
603
|
if (hints) {
|
|
528
604
|
return (
|
|
@@ -543,12 +619,15 @@ export function HelperManager() {
|
|
|
543
619
|
)
|
|
544
620
|
}
|
|
545
621
|
|
|
546
|
-
if (
|
|
622
|
+
if (
|
|
623
|
+
movingNode &&
|
|
624
|
+
!toolHintsOwnPlacing(scope, tool, tool ? (nodeRegistry.get(tool)?.toolHints?.length ?? 0) : 0)
|
|
625
|
+
) {
|
|
547
626
|
// A fresh placement (e.g. a positioned preset like a shelf) advertises its
|
|
548
627
|
// once/repeat continuation, exactly like the GLB item tool — but an existing
|
|
549
628
|
// node being *moved* is not a placement, so it gets no continuation chip.
|
|
550
629
|
const isFreshPlacement = isFreshPlacementMetadata(movingNode.metadata)
|
|
551
|
-
const movingTitle = nodeKindHudTitle(movingNode.type, isFreshPlacement ? undefined : '
|
|
630
|
+
const movingTitle = nodeKindHudTitle(movingNode.type, isFreshPlacement ? undefined : 'move')
|
|
552
631
|
if (movingNode.type === 'building') return <BuildingHelper showRotate title={movingTitle} />
|
|
553
632
|
const movingContinuationContext = isFreshPlacement
|
|
554
633
|
? keyCyclableContinuationContext(continuationContextOf(movingNode.type))
|
|
@@ -611,7 +690,7 @@ export function HelperManager() {
|
|
|
611
690
|
<ContextualHelperPanel
|
|
612
691
|
hints={contextualEditHints}
|
|
613
692
|
snapContext={snapContext}
|
|
614
|
-
title={contextualHelpNode ? nodeKindHudTitle(contextualHelpNode.type, '
|
|
693
|
+
title={contextualHelpNode ? nodeKindHudTitle(contextualHelpNode.type, 'edit') : null}
|
|
615
694
|
/>
|
|
616
695
|
)
|
|
617
696
|
}
|
|
@@ -639,9 +718,9 @@ export function HelperManager() {
|
|
|
639
718
|
return (
|
|
640
719
|
<ContextualHelperPanel
|
|
641
720
|
hints={[
|
|
642
|
-
{ keys: ['Hover'], label: 'Inspect surface dimensions' },
|
|
643
|
-
{ keys: ['Click'], label: 'Pin measurement lens' },
|
|
644
|
-
{ keys: ['Esc'], label: 'Exit smart measure' },
|
|
721
|
+
{ keys: ['Hover'], label: i18n._({ id: 'editor.helpers.helperManager.inspectSurfaceDimensions', message: 'Inspect surface dimensions' }) },
|
|
722
|
+
{ keys: ['Click'], label: i18n._({ id: 'editor.helpers.helperManager.pinMeasurementLens', message: 'Pin measurement lens' }) },
|
|
723
|
+
{ keys: ['Esc'], label: i18n._({ id: 'editor.helpers.helperManager.exitSmartMeasure', message: 'Exit smart measure' }) },
|
|
645
724
|
]}
|
|
646
725
|
title={SMART_MEASURE_HUD_TITLE}
|
|
647
726
|
/>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { usePascalI18n } from '@pascal-app/core'
|
|
1
2
|
import type { ContinuationContext } from '../../../lib/continuation'
|
|
2
3
|
import type { HudTitle } from '../../../lib/hud-title'
|
|
3
4
|
import type { SnapContext } from '../../../lib/snapping-mode'
|
|
@@ -33,17 +34,18 @@ export function ItemHelper({
|
|
|
33
34
|
extraHints = [],
|
|
34
35
|
replacesPlace = false,
|
|
35
36
|
}: ItemHelperProps) {
|
|
37
|
+
const i18n = usePascalI18n()
|
|
36
38
|
return (
|
|
37
39
|
<ContextualHelperPanel
|
|
38
40
|
continuationContext={continuationContext}
|
|
39
41
|
notice={notice}
|
|
40
42
|
hints={[
|
|
41
43
|
// Where a click does something else (joining a corner), that row says so instead.
|
|
42
|
-
...(replacesPlace ? [] : [{ keys: ['Left click'], label: 'Place' }]),
|
|
44
|
+
...(replacesPlace ? [] : [{ keys: ['Left click'], label: i18n._({ id: 'editor.helpers.itemHelper.place', message: 'Place' }) }]),
|
|
43
45
|
...extraHints,
|
|
44
|
-
{ keys: ['R', 'T'], label: 'Rotate' },
|
|
45
|
-
...(showForce ? [{ keys: ['Alt'], label: 'Force place' }] : []),
|
|
46
|
-
{ keys: [showEsc ? 'Esc' : 'Right click'], label: 'Cancel' },
|
|
46
|
+
{ keys: ['R', 'T'], label: i18n._({ id: 'editor.helpers.itemHelper.rotate', message: 'Rotate' }) },
|
|
47
|
+
...(showForce ? [{ keys: ['Alt'], label: i18n._({ id: 'editor.helpers.itemHelper.forcePlace', message: 'Force place' }) }] : []),
|
|
48
|
+
{ keys: [showEsc ? 'Esc' : 'Right click'], label: i18n._({ id: 'editor.helpers.itemHelper.cancel', message: 'Cancel' }) },
|
|
47
49
|
]}
|
|
48
50
|
snapContext={snapContext}
|
|
49
51
|
title={title}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { usePascalI18n } from '@pascal-app/core'
|
|
1
2
|
import type { ToolHint } from '@pascal-app/core'
|
|
2
3
|
import type { ContinuationContext } from '../../../lib/continuation'
|
|
3
4
|
import type { HudTitle } from '../../../lib/hud-title'
|
|
@@ -29,6 +30,7 @@ export function RegisteredToolHelper({
|
|
|
29
30
|
title?: HudTitle | null
|
|
30
31
|
notice?: string | null
|
|
31
32
|
}) {
|
|
33
|
+
const i18n = usePascalI18n()
|
|
32
34
|
const visible = useVisibleToolHints(hints)
|
|
33
35
|
if (visible.length === 0 && !snapContext && !continuationContext && !notice) return null
|
|
34
36
|
// Hints carrying a live-state `chip` render as mode chips next to the
|
|
@@ -44,7 +46,7 @@ export function RegisteredToolHelper({
|
|
|
44
46
|
const isBypassHint = hint.key === 'Shift'
|
|
45
47
|
return {
|
|
46
48
|
keys: [hint.key],
|
|
47
|
-
label: shiftPressed && isBypassHint ? 'Guided constraints bypassed' : hint.label,
|
|
49
|
+
label: shiftPressed && isBypassHint ? i18n._({ id: 'editor.helpers.registeredToolHelper.guidedConstraintsBypassed', message: 'Guided constraints bypassed' }) : hint.label,
|
|
48
50
|
active: shiftPressed && isBypassHint,
|
|
49
51
|
}
|
|
50
52
|
})}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import type { LevelNode } from '@pascal-app/core'
|
|
3
|
+
import type { LevelNode, MessageDescriptor } from '@pascal-app/core'
|
|
4
4
|
import { useEffect, useState } from 'react'
|
|
5
5
|
import type { LevelDuplicatePreset } from '@pascal-app/core/building'
|
|
6
|
-
import {
|
|
6
|
+
import { useLocale, usePascalI18n } from '@pascal-app/core'
|
|
7
|
+
import { localizedLevelName } from '../../i18n/level-name'
|
|
7
8
|
import { cn } from '../../lib/utils'
|
|
8
9
|
import {
|
|
9
10
|
Dialog,
|
|
@@ -16,36 +17,31 @@ import {
|
|
|
16
17
|
|
|
17
18
|
const DUPLICATE_PRESETS: Array<{
|
|
18
19
|
id: LevelDuplicatePreset
|
|
19
|
-
label:
|
|
20
|
-
description:
|
|
20
|
+
label: MessageDescriptor
|
|
21
|
+
description: MessageDescriptor
|
|
21
22
|
}> = [
|
|
22
23
|
{
|
|
23
24
|
id: 'everything',
|
|
24
|
-
label: 'Everything',
|
|
25
|
-
description: 'Structure, materials, furniture, and references.',
|
|
25
|
+
label: /* i18n */ { id: 'editor.levelDuplicateDialog.everything', message: 'Everything' },
|
|
26
|
+
description: /* i18n */ { id: 'editor.levelDuplicateDialog.structureMaterialsFurnitureAndReferences', message: 'Structure, materials, furniture, and references.' },
|
|
26
27
|
},
|
|
27
28
|
{
|
|
28
29
|
id: 'structure',
|
|
29
|
-
label: 'Structure only',
|
|
30
|
-
description: 'Walls, slabs, roofs, stairs, windows, and doors without finishes.',
|
|
30
|
+
label: /* i18n */ { id: 'editor.levelDuplicateDialog.structureOnly', message: 'Structure only' },
|
|
31
|
+
description: /* i18n */ { id: 'editor.levelDuplicateDialog.wallsSlabsRoofsStairsWindowsAnd', message: 'Walls, slabs, roofs, stairs, windows, and doors without finishes.' },
|
|
31
32
|
},
|
|
32
33
|
{
|
|
33
34
|
id: 'structure-materials',
|
|
34
|
-
label: 'Structure + materials',
|
|
35
|
-
description: 'Structure with the current material and finish assignments.',
|
|
35
|
+
label: /* i18n */ { id: 'editor.levelDuplicateDialog.structureMaterials', message: 'Structure + materials' },
|
|
36
|
+
description: /* i18n */ { id: 'editor.levelDuplicateDialog.structureWithTheCurrentMaterialAnd', message: 'Structure with the current material and finish assignments.' },
|
|
36
37
|
},
|
|
37
38
|
{
|
|
38
39
|
id: 'structure-furniture',
|
|
39
|
-
label: 'Structure + furniture',
|
|
40
|
-
description: 'Structure, finishes, and placed items, without guide references.',
|
|
40
|
+
label: /* i18n */ { id: 'editor.levelDuplicateDialog.structureFurniture', message: 'Structure + furniture' },
|
|
41
|
+
description: /* i18n */ { id: 'editor.levelDuplicateDialog.structureFinishesAndPlacedItemsWithout', message: 'Structure, finishes, and placed items, without guide references.' },
|
|
41
42
|
},
|
|
42
43
|
]
|
|
43
44
|
|
|
44
|
-
function getLevelLabel(level: LevelNode | null) {
|
|
45
|
-
if (!level) return 'this level'
|
|
46
|
-
return getLevelDisplayName(level)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
45
|
export function LevelDuplicateDialog({
|
|
50
46
|
open,
|
|
51
47
|
level,
|
|
@@ -57,6 +53,8 @@ export function LevelDuplicateDialog({
|
|
|
57
53
|
onConfirm: (preset: LevelDuplicatePreset) => void
|
|
58
54
|
onOpenChange: (open: boolean) => void
|
|
59
55
|
}) {
|
|
56
|
+
const i18n = usePascalI18n()
|
|
57
|
+
const locale = useLocale()
|
|
60
58
|
const [preset, setPreset] = useState<LevelDuplicatePreset>('everything')
|
|
61
59
|
|
|
62
60
|
useEffect(() => {
|
|
@@ -69,8 +67,19 @@ export function LevelDuplicateDialog({
|
|
|
69
67
|
<Dialog onOpenChange={onOpenChange} open={open}>
|
|
70
68
|
<DialogContent className="sm:max-w-md" showCloseButton={false}>
|
|
71
69
|
<DialogHeader>
|
|
72
|
-
<DialogTitle>Duplicate Level</DialogTitle>
|
|
73
|
-
<DialogDescription>
|
|
70
|
+
<DialogTitle>{i18n._({ id: 'editor.levelDuplicateDialog.duplicateLevel', message: 'Duplicate Level' })}</DialogTitle>
|
|
71
|
+
<DialogDescription>
|
|
72
|
+
{level
|
|
73
|
+
? i18n._({
|
|
74
|
+
id: 'editor.levelDuplicateDialog.chooseWhatToCopyFromLevel',
|
|
75
|
+
message: 'Choose what to copy from {levelName}.',
|
|
76
|
+
values: { levelName: localizedLevelName(level, i18n, locale) },
|
|
77
|
+
})
|
|
78
|
+
: i18n._({
|
|
79
|
+
id: 'editor.levelDuplicateDialog.chooseWhatToCopyFromThisLevel',
|
|
80
|
+
message: 'Choose what to copy from this level.',
|
|
81
|
+
})}
|
|
82
|
+
</DialogDescription>
|
|
74
83
|
</DialogHeader>
|
|
75
84
|
|
|
76
85
|
<div className="grid gap-2">
|
|
@@ -86,8 +95,8 @@ export function LevelDuplicateDialog({
|
|
|
86
95
|
onClick={() => setPreset(option.id)}
|
|
87
96
|
type="button"
|
|
88
97
|
>
|
|
89
|
-
<div className="font-medium text-sm">{option.label}</div>
|
|
90
|
-
<div className="mt-1 text-muted-foreground text-xs">{option.description}</div>
|
|
98
|
+
<div className="font-medium text-sm">{i18n._(option.label)}</div>
|
|
99
|
+
<div className="mt-1 text-muted-foreground text-xs">{i18n._(option.description)}</div>
|
|
91
100
|
</button>
|
|
92
101
|
))}
|
|
93
102
|
</div>
|
|
@@ -98,14 +107,14 @@ export function LevelDuplicateDialog({
|
|
|
98
107
|
onClick={() => onOpenChange(false)}
|
|
99
108
|
type="button"
|
|
100
109
|
>
|
|
101
|
-
Cancel
|
|
110
|
+
{i18n._({ id: 'editor.levelDuplicateDialog.cancel', message: 'Cancel' })}
|
|
102
111
|
</button>
|
|
103
112
|
<button
|
|
104
113
|
className="cursor-pointer rounded-md bg-primary px-4 py-2 text-primary-foreground text-sm transition-opacity hover:opacity-90"
|
|
105
114
|
onClick={() => onConfirm(preset)}
|
|
106
115
|
type="button"
|
|
107
116
|
>
|
|
108
|
-
Duplicate
|
|
117
|
+
{i18n._({ id: 'editor.levelDuplicateDialog.duplicate', message: 'Duplicate' })}
|
|
109
118
|
</button>
|
|
110
119
|
</DialogFooter>
|
|
111
120
|
</DialogContent>
|