@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
|
@@ -3,12 +3,18 @@
|
|
|
3
3
|
import { Icon } from '@iconify/react'
|
|
4
4
|
import {
|
|
5
5
|
acquireSceneReadOnlyLease,
|
|
6
|
+
createI18n,
|
|
7
|
+
emitter,
|
|
6
8
|
getCatalogMaterialById,
|
|
7
9
|
getLibraryMaterialIdFromRef,
|
|
10
|
+
getMaterialSwatch,
|
|
8
11
|
getSceneMaterialIdFromRef,
|
|
9
12
|
initSpaceDetectionSync,
|
|
10
13
|
initSpatialGridSync,
|
|
14
|
+
type MessageDescriptor,
|
|
15
|
+
PascalI18nProvider,
|
|
11
16
|
spatialGridManager,
|
|
17
|
+
usePascalI18n,
|
|
12
18
|
useScene,
|
|
13
19
|
} from '@pascal-app/core'
|
|
14
20
|
import {
|
|
@@ -32,15 +38,18 @@ import {
|
|
|
32
38
|
useCallback,
|
|
33
39
|
useEffect,
|
|
34
40
|
useLayoutEffect,
|
|
41
|
+
useMemo,
|
|
35
42
|
useRef,
|
|
36
43
|
useState,
|
|
37
44
|
} from 'react'
|
|
38
45
|
import { ViewerOverlay } from '../../components/viewer-overlay'
|
|
39
46
|
import { ViewerZoneSystem } from '../../components/viewer-zone-system'
|
|
47
|
+
import { useAutoFrame } from '../../hooks/use-auto-frame'
|
|
40
48
|
import { type SaveStatus, useAutoSave } from '../../hooks/use-auto-save'
|
|
41
49
|
import { useKeyboard } from '../../hooks/use-keyboard'
|
|
42
50
|
import { useIsMobile } from '../../hooks/use-mobile'
|
|
43
51
|
import { useSaveShortcut } from '../../hooks/use-save-shortcut'
|
|
52
|
+
import { type HINT_KEY_NAMES, toolHintKey, toolHintKeyDescriptor } from '../../i18n/node-messages'
|
|
44
53
|
import { useCeilingEditSessionOwner } from '../../lib/ceiling-edit-session'
|
|
45
54
|
import { showsWholeBuilding, useEditorLevelDisplay } from '../../lib/editor-level-display'
|
|
46
55
|
import { isSceneView } from '../../lib/editor-views'
|
|
@@ -60,6 +69,7 @@ import {
|
|
|
60
69
|
type SceneGraph,
|
|
61
70
|
writePersistedSelection,
|
|
62
71
|
} from '../../lib/scene'
|
|
72
|
+
import { computeSceneBoundsXZ } from '../../lib/scene-bounds'
|
|
63
73
|
import { disposeSFXBus, initSFXBus } from '../../lib/sfx-bus'
|
|
64
74
|
import { useUnitFocusRules } from '../../lib/units'
|
|
65
75
|
import { cn } from '../../lib/utils'
|
|
@@ -71,7 +81,7 @@ import useSessionGroups from '../../store/use-session-groups'
|
|
|
71
81
|
import { paneOfView, VIEW_2D, VIEW_3D, type ViewPaneIndex } from '../../store/view-layout'
|
|
72
82
|
import { PointAskLayer } from '../point-ask/point-ask-layer'
|
|
73
83
|
import { CeilingSelectionAffordanceSystem } from '../systems/ceiling/ceiling-selection-affordance-system'
|
|
74
|
-
import { CeilingSystem } from '../systems/ceiling/ceiling-system'
|
|
84
|
+
import { CeilingSystem, CeilingTopsSync } from '../systems/ceiling/ceiling-system'
|
|
75
85
|
import { RoofEditSystem } from '../systems/roof/roof-edit-system'
|
|
76
86
|
import { SelectionAffordanceManager } from '../systems/selection-affordance-manager'
|
|
77
87
|
import { StairEditSystem } from '../systems/stair/stair-edit-system'
|
|
@@ -96,6 +106,8 @@ import { SettingsPanel, type SettingsPanelProps } from '../ui/sidebar/panels/set
|
|
|
96
106
|
import { SitePanel, type SitePanelProps } from '../ui/sidebar/panels/site-panel'
|
|
97
107
|
import type { SidebarTab } from '../ui/sidebar/tab-bar'
|
|
98
108
|
import { useHostPanels } from '../ui/sidebar/use-plugin-panels'
|
|
109
|
+
import { SKY_DOCK_HEIGHT, SkyControl } from '../viewer/sky-control'
|
|
110
|
+
import { useViewerUiSounds } from '../viewer/use-viewer-ui-sounds'
|
|
99
111
|
import { ViewerHoverLabel } from '../viewer/viewer-hover-label'
|
|
100
112
|
import { ParametricViewerRooms } from '../viewer/viewer-rooms'
|
|
101
113
|
import { ViewerSelectionManager } from '../viewer/viewer-selection-manager'
|
|
@@ -202,6 +214,8 @@ function initializeEditorRuntime(): () => void {
|
|
|
202
214
|
}
|
|
203
215
|
}
|
|
204
216
|
export interface EditorProps {
|
|
217
|
+
/** UI locale. Hosts may also call `setLocale` before mounting. */
|
|
218
|
+
locale?: string
|
|
205
219
|
// Layout version — 'v1' (default) or 'v2' (navbar + two-column)
|
|
206
220
|
layoutVersion?: 'v1' | 'v2'
|
|
207
221
|
// Viewport edge the tool menu docks to (desktop only; mobile stays at the bottom)
|
|
@@ -315,12 +329,22 @@ export interface EditorProps {
|
|
|
315
329
|
}
|
|
316
330
|
|
|
317
331
|
function EditorSceneCrashFallback() {
|
|
332
|
+
const i18n = usePascalI18n()
|
|
318
333
|
return (
|
|
319
334
|
<div className="fixed inset-0 z-80 flex items-center justify-center bg-background/95 p-4 text-foreground">
|
|
320
335
|
<div className="w-full max-w-md rounded-2xl border border-border/60 bg-background p-6 shadow-xl">
|
|
321
|
-
<h2 className="font-semibold text-lg">
|
|
336
|
+
<h2 className="font-semibold text-lg">
|
|
337
|
+
{i18n._({
|
|
338
|
+
id: 'editor.theEditorSceneFailedToRender',
|
|
339
|
+
message: 'The editor scene failed to render',
|
|
340
|
+
})}
|
|
341
|
+
</h2>
|
|
322
342
|
<p className="mt-2 text-muted-foreground text-sm">
|
|
323
|
-
|
|
343
|
+
{i18n._({
|
|
344
|
+
id: 'editor.youCanRetryTheSceneOr',
|
|
345
|
+
message:
|
|
346
|
+
'You can retry the scene or return home without reloading the whole app shell.',
|
|
347
|
+
})}
|
|
324
348
|
</p>
|
|
325
349
|
<div className="mt-4 flex items-center gap-2">
|
|
326
350
|
<button
|
|
@@ -328,13 +352,13 @@ function EditorSceneCrashFallback() {
|
|
|
328
352
|
onClick={() => window.location.reload()}
|
|
329
353
|
type="button"
|
|
330
354
|
>
|
|
331
|
-
Reload editor
|
|
355
|
+
{i18n._({ id: 'editor.reloadEditor', message: 'Reload editor' })}
|
|
332
356
|
</button>
|
|
333
357
|
<a
|
|
334
358
|
className="rounded-md border border-border bg-background px-3 py-2 font-medium text-sm hover:bg-accent/40"
|
|
335
359
|
href="/"
|
|
336
360
|
>
|
|
337
|
-
Back to home
|
|
361
|
+
{i18n._({ id: 'editor.backToHome', message: 'Back to home' })}
|
|
338
362
|
</a>
|
|
339
363
|
</div>
|
|
340
364
|
</div>
|
|
@@ -345,6 +369,7 @@ function EditorSceneCrashFallback() {
|
|
|
345
369
|
// ── Sidebar slot: in-flow, resizable, collapses to a grab strip ──────────────
|
|
346
370
|
|
|
347
371
|
function SidebarSlot({ children }: { children: ReactNode }) {
|
|
372
|
+
const i18n = usePascalI18n()
|
|
348
373
|
const width = useSidebarStore((s) => s.width)
|
|
349
374
|
const isCollapsed = useSidebarStore((s) => s.isCollapsed)
|
|
350
375
|
const setIsCollapsed = useSidebarStore((s) => s.setIsCollapsed)
|
|
@@ -416,7 +441,7 @@ function SidebarSlot({ children }: { children: ReactNode }) {
|
|
|
416
441
|
<div
|
|
417
442
|
className="absolute inset-0 z-10 cursor-col-resize transition-colors hover:bg-primary/20"
|
|
418
443
|
onPointerDown={handleGrabDown}
|
|
419
|
-
title=
|
|
444
|
+
title={i18n._({ id: 'editor.expandSidebar', message: 'Expand sidebar' })}
|
|
420
445
|
/>
|
|
421
446
|
) : (
|
|
422
447
|
children
|
|
@@ -494,26 +519,29 @@ const CAMERA_CONTROL_HINTS: CameraControlHint[] = [
|
|
|
494
519
|
{ action: 'Zoom', keys: [{ value: 'Scroll' }] },
|
|
495
520
|
]
|
|
496
521
|
|
|
497
|
-
const CAMERA_SHORTCUT_KEY_META: Record<
|
|
522
|
+
const CAMERA_SHORTCUT_KEY_META: Record<
|
|
523
|
+
string,
|
|
524
|
+
{ icon?: string; label: MessageDescriptor; text?: string }
|
|
525
|
+
> = {
|
|
498
526
|
'Left click': {
|
|
499
527
|
icon: 'ph:mouse-left-click-fill',
|
|
500
|
-
label: 'Left click',
|
|
528
|
+
label: hintKeyName('Left click'),
|
|
501
529
|
},
|
|
502
530
|
'Middle click': {
|
|
503
531
|
icon: 'qlementine-icons:mouse-middle-button-16',
|
|
504
|
-
label: 'Middle click',
|
|
532
|
+
label: hintKeyName('Middle click'),
|
|
505
533
|
},
|
|
506
534
|
'Right click': {
|
|
507
535
|
icon: 'ph:mouse-right-click-fill',
|
|
508
|
-
label: 'Right click',
|
|
536
|
+
label: hintKeyName('Right click'),
|
|
509
537
|
},
|
|
510
538
|
Scroll: {
|
|
511
539
|
icon: 'qlementine-icons:mouse-middle-button-16',
|
|
512
|
-
label: 'Scroll wheel',
|
|
540
|
+
label: hintKeyName('Scroll wheel'),
|
|
513
541
|
},
|
|
514
542
|
Space: {
|
|
515
543
|
icon: 'lucide:space',
|
|
516
|
-
label: 'Space',
|
|
544
|
+
label: hintKeyName('Space'),
|
|
517
545
|
},
|
|
518
546
|
}
|
|
519
547
|
|
|
@@ -544,8 +572,21 @@ function writeCameraControlsHintDismissed(dismissed: boolean) {
|
|
|
544
572
|
} catch {}
|
|
545
573
|
}
|
|
546
574
|
|
|
575
|
+
/** Key names shared with the tool HUD (`editor.toolHint.key.*`). */
|
|
576
|
+
function hintKeyName(key: (typeof HINT_KEY_NAMES)[number]): MessageDescriptor {
|
|
577
|
+
return toolHintKeyDescriptor(key) ?? { id: key, message: key }
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const CAMERA_HINT_ACTION_LABELS: Record<CameraHintAction, MessageDescriptor> = {
|
|
581
|
+
Pan: /* i18n */ { id: 'editor.cameraHint.pan', message: 'Pan' },
|
|
582
|
+
Rotate: /* i18n */ { id: 'editor.cameraHint.rotate', message: 'Rotate' },
|
|
583
|
+
Zoom: /* i18n */ { id: 'editor.cameraHint.zoom', message: 'Zoom' },
|
|
584
|
+
}
|
|
585
|
+
|
|
547
586
|
function InlineShortcutKey({ shortcutKey }: { shortcutKey: ShortcutKey }) {
|
|
548
|
-
const
|
|
587
|
+
const i18n = usePascalI18n()
|
|
588
|
+
const entry = CAMERA_SHORTCUT_KEY_META[shortcutKey.value]
|
|
589
|
+
const meta = entry ? { ...entry, label: i18n._(entry.label) } : undefined
|
|
549
590
|
|
|
550
591
|
if (meta?.icon) {
|
|
551
592
|
return (
|
|
@@ -563,7 +604,7 @@ function InlineShortcutKey({ shortcutKey }: { shortcutKey: ShortcutKey }) {
|
|
|
563
604
|
|
|
564
605
|
return (
|
|
565
606
|
<span className="font-medium text-[11px] text-foreground/90">
|
|
566
|
-
{meta?.text ?? shortcutKey.value}
|
|
607
|
+
{meta?.text ?? toolHintKey(shortcutKey.value, i18n)}
|
|
567
608
|
</span>
|
|
568
609
|
)
|
|
569
610
|
}
|
|
@@ -582,10 +623,11 @@ function ShortcutSequence({ keys }: { keys: ShortcutKey[] }) {
|
|
|
582
623
|
}
|
|
583
624
|
|
|
584
625
|
function CameraControlHintItem({ hint }: { hint: CameraControlHint }) {
|
|
626
|
+
const i18n = usePascalI18n()
|
|
585
627
|
return (
|
|
586
628
|
<div className="flex min-w-0 flex-col items-center gap-1.5 px-4 text-center first:pl-0 last:pr-0">
|
|
587
629
|
<span className="font-medium text-[10px] text-muted-foreground/60 tracking-[0.03em]">
|
|
588
|
-
{hint.action}
|
|
630
|
+
{i18n._(CAMERA_HINT_ACTION_LABELS[hint.action])}
|
|
589
631
|
</span>
|
|
590
632
|
<div className="flex flex-wrap items-center justify-center gap-1.5">
|
|
591
633
|
<ShortcutSequence keys={hint.keys} />
|
|
@@ -607,6 +649,7 @@ function ViewerCanvasControlsHint({
|
|
|
607
649
|
belowTopMenu?: boolean
|
|
608
650
|
onDismiss: () => void
|
|
609
651
|
}) {
|
|
652
|
+
const i18n = usePascalI18n()
|
|
610
653
|
const all = CAMERA_CONTROL_HINTS
|
|
611
654
|
// A host teaching one gesture at a time narrows this to the one it is asking
|
|
612
655
|
// for, and to nothing once it is done. Null — the default — is all of them.
|
|
@@ -626,7 +669,7 @@ function ViewerCanvasControlsHint({
|
|
|
626
669
|
style={belowTopMenu ? { top: TOP_MENU_CLEARANCE } : undefined}
|
|
627
670
|
>
|
|
628
671
|
<section
|
|
629
|
-
aria-label=
|
|
672
|
+
aria-label={i18n._({ id: 'editor.cameraControlsHint', message: 'Camera controls hint' })}
|
|
630
673
|
className="pointer-events-auto flex items-start gap-3 rounded-2xl border border-border/35 bg-background/90 px-3.5 py-2.5 shadow-elevation-4 backdrop-blur-xl"
|
|
631
674
|
>
|
|
632
675
|
<div
|
|
@@ -640,7 +683,10 @@ function ViewerCanvasControlsHint({
|
|
|
640
683
|
<Tooltip>
|
|
641
684
|
<TooltipTrigger asChild>
|
|
642
685
|
<button
|
|
643
|
-
aria-label=
|
|
686
|
+
aria-label={i18n._({
|
|
687
|
+
id: 'editor.dismissCameraControlsHint',
|
|
688
|
+
message: 'Dismiss camera controls hint',
|
|
689
|
+
})}
|
|
644
690
|
className="flex h-5 shrink-0 items-center justify-center self-center border-border/18 border-l pl-3 text-muted-foreground/70 transition-colors hover:text-foreground"
|
|
645
691
|
onClick={onDismiss}
|
|
646
692
|
type="button"
|
|
@@ -655,7 +701,7 @@ function ViewerCanvasControlsHint({
|
|
|
655
701
|
</button>
|
|
656
702
|
</TooltipTrigger>
|
|
657
703
|
<TooltipContent side="bottom" sideOffset={8}>
|
|
658
|
-
Dismiss
|
|
704
|
+
{i18n._({ id: 'editor.dismiss', message: 'Dismiss' })}
|
|
659
705
|
</TooltipContent>
|
|
660
706
|
</Tooltip>
|
|
661
707
|
</section>
|
|
@@ -733,7 +779,17 @@ function getActivePaintMaterialSwatchImageUrl(
|
|
|
733
779
|
const catalogId =
|
|
734
780
|
getLibraryMaterialIdFromRef(material?.materialPreset) ?? material?.material?.id ?? undefined
|
|
735
781
|
const catalogMaterial = getCatalogMaterialById(catalogId)
|
|
736
|
-
return catalogMaterial
|
|
782
|
+
return catalogMaterial ? (getMaterialSwatch(catalogMaterial).imageUrl ?? undefined) : undefined
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/** Whether the badge's image is a white albedo its colour tints (a siding's profile). */
|
|
786
|
+
function isActivePaintMaterialSwatchTinted(material: ActivePaintMaterial | null) {
|
|
787
|
+
if (material?.material?.texture?.url || getSceneMaterialIdFromRef(material?.materialPreset))
|
|
788
|
+
return false
|
|
789
|
+
const catalogId =
|
|
790
|
+
getLibraryMaterialIdFromRef(material?.materialPreset) ?? material?.material?.id ?? undefined
|
|
791
|
+
const catalogMaterial = getCatalogMaterialById(catalogId)
|
|
792
|
+
return catalogMaterial ? getMaterialSwatch(catalogMaterial).tinted : false
|
|
737
793
|
}
|
|
738
794
|
|
|
739
795
|
function PaintCursorBadge({
|
|
@@ -741,6 +797,7 @@ function PaintCursorBadge({
|
|
|
741
797
|
state,
|
|
742
798
|
swatchColor,
|
|
743
799
|
swatchImageUrl,
|
|
800
|
+
swatchTinted = false,
|
|
744
801
|
isEraser,
|
|
745
802
|
isPicker = false,
|
|
746
803
|
}: {
|
|
@@ -748,6 +805,8 @@ function PaintCursorBadge({
|
|
|
748
805
|
state: PaintCursorBadgeState
|
|
749
806
|
swatchColor: string
|
|
750
807
|
swatchImageUrl?: string
|
|
808
|
+
/** Multiply the image by the colour (a tinted albedo). */
|
|
809
|
+
swatchTinted?: boolean
|
|
751
810
|
isEraser: boolean
|
|
752
811
|
/** The eyedropper: the swatch is what a click would take. */
|
|
753
812
|
isPicker?: boolean
|
|
@@ -821,6 +880,7 @@ function PaintCursorBadge({
|
|
|
821
880
|
backgroundImage: swatchImageUrl
|
|
822
881
|
? `url(${JSON.stringify(swatchImageUrl)})`
|
|
823
882
|
: undefined,
|
|
883
|
+
backgroundBlendMode: swatchImageUrl && swatchTinted ? 'multiply' : undefined,
|
|
824
884
|
opacity: state === 'aim' ? 0.55 : 1,
|
|
825
885
|
}}
|
|
826
886
|
/>
|
|
@@ -1084,6 +1144,7 @@ function PaintCursorLayer({
|
|
|
1084
1144
|
const swatchMaterial = picking ? picked : activePaintMaterial
|
|
1085
1145
|
const swatchColor = getActivePaintMaterialSwatchColor(swatchMaterial, sceneMaterials)
|
|
1086
1146
|
const swatchImageUrl = getActivePaintMaterialSwatchImageUrl(swatchMaterial, sceneMaterials)
|
|
1147
|
+
const swatchTinted = isActivePaintMaterialSwatchTinted(swatchMaterial)
|
|
1087
1148
|
|
|
1088
1149
|
if (!active || !position) return null
|
|
1089
1150
|
|
|
@@ -1099,6 +1160,7 @@ function PaintCursorLayer({
|
|
|
1099
1160
|
state={badgeState}
|
|
1100
1161
|
swatchColor={swatchColor}
|
|
1101
1162
|
swatchImageUrl={swatchImageUrl}
|
|
1163
|
+
swatchTinted={swatchTinted}
|
|
1102
1164
|
/>
|
|
1103
1165
|
</div>
|
|
1104
1166
|
)
|
|
@@ -1150,6 +1212,9 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
1150
1212
|
const views = useEditorViews()
|
|
1151
1213
|
const isPreviewMode = useEditor((s) => s.isPreviewMode)
|
|
1152
1214
|
const isCaptureMode = useEditor((s) => s.isCaptureMode)
|
|
1215
|
+
const textures = useViewer((s) => s.textures)
|
|
1216
|
+
const isMobile = useIsMobile()
|
|
1217
|
+
const planOnRight = layout.split && layout.panes[1] === VIEW_2D
|
|
1153
1218
|
useUnitFocusRules()
|
|
1154
1219
|
const wholeBuilding = useEditor((s) =>
|
|
1155
1220
|
showsWholeBuilding({
|
|
@@ -1319,6 +1384,22 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
1319
1384
|
viewerSceneSlot={viewerSceneSlot}
|
|
1320
1385
|
/>
|
|
1321
1386
|
</Viewer>
|
|
1387
|
+
{!showLoader &&
|
|
1388
|
+
textures &&
|
|
1389
|
+
!isMobile &&
|
|
1390
|
+
!(
|
|
1391
|
+
isFirstPersonMode ||
|
|
1392
|
+
isStudioMode ||
|
|
1393
|
+
isVersionPreviewMode ||
|
|
1394
|
+
isCaptureMode ||
|
|
1395
|
+
isPreviewMode
|
|
1396
|
+
) ? (
|
|
1397
|
+
// Beside the compass (which moves to the plan's corner when the plan is on the right).
|
|
1398
|
+
<SkyControl
|
|
1399
|
+
height={SKY_DOCK_HEIGHT.editor}
|
|
1400
|
+
style={{ left: planOnRight ? 12 : 52 }}
|
|
1401
|
+
/>
|
|
1402
|
+
) : null}
|
|
1322
1403
|
</div>
|
|
1323
1404
|
{showLoader && loadingOverlay}
|
|
1324
1405
|
</div>
|
|
@@ -1327,6 +1408,7 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
1327
1408
|
const viewId = layout.panes[pane]
|
|
1328
1409
|
return (
|
|
1329
1410
|
<div
|
|
1411
|
+
aria-busy={isSceneDataLoading}
|
|
1330
1412
|
className="relative min-w-0 overflow-hidden"
|
|
1331
1413
|
data-view-pane={pane}
|
|
1332
1414
|
key={viewId}
|
|
@@ -1335,6 +1417,9 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
1335
1417
|
style={paneStyle(pane)}
|
|
1336
1418
|
>
|
|
1337
1419
|
<HostView key={viewId} view={views.find((view) => view.id === viewId)} />
|
|
1420
|
+
{/* A restored pane's plugin is known only once the project has loaded: until
|
|
1421
|
+
then it would read "This view isn't available". */}
|
|
1422
|
+
{isSceneDataLoading && loadingOverlay}
|
|
1338
1423
|
</div>
|
|
1339
1424
|
)
|
|
1340
1425
|
})}
|
|
@@ -1384,6 +1469,7 @@ function PreviewStage({
|
|
|
1384
1469
|
)
|
|
1385
1470
|
|
|
1386
1471
|
useViewerFloorDisplay()
|
|
1472
|
+
useViewerUiSounds()
|
|
1387
1473
|
const stageMode = isFirstPersonMode || !hasFloorplan ? '3d' : mode
|
|
1388
1474
|
const stageModes = hasFloorplan && !isFirstPersonMode ? undefined : (['3d'] as const)
|
|
1389
1475
|
|
|
@@ -1487,6 +1573,8 @@ function EditorContent({
|
|
|
1487
1573
|
useKeyboard({ isVersionPreviewMode, disabled: isFirstPersonMode || isStudioMode })
|
|
1488
1574
|
useGestureLifecycleOwner()
|
|
1489
1575
|
|
|
1576
|
+
useAutoFrame()
|
|
1577
|
+
|
|
1490
1578
|
const { beginSceneLoad, completeSceneLoad, saveNow } = useAutoSave({
|
|
1491
1579
|
guardAgainstSceneWipe,
|
|
1492
1580
|
onSave,
|
|
@@ -1658,6 +1746,21 @@ function EditorContent({
|
|
|
1658
1746
|
return () => window.clearTimeout(timer)
|
|
1659
1747
|
}, [hasLoadedInitialScene, isLoading, isSceneLoading, isViewerSceneReady, sceneReadyKey])
|
|
1660
1748
|
|
|
1749
|
+
// The useAutoFrame emit can be clobbered by the level-follow effect's
|
|
1750
|
+
// first-run pivot rest on fast (client-side navigation) loads. Re-emitting
|
|
1751
|
+
// here is the last word after every load-driven camera effect has run.
|
|
1752
|
+
// Once per load: a remounted viewer reports ready again, but only a new
|
|
1753
|
+
// load reframes; remounted controls rest their own pivot on the level.
|
|
1754
|
+
const framedSceneReadyKeyRef = useRef<number | null>(null)
|
|
1755
|
+
useEffect(() => {
|
|
1756
|
+
if (!isViewerSceneReady || framedSceneReadyKeyRef.current === sceneReadyKey) return
|
|
1757
|
+
framedSceneReadyKeyRef.current = sceneReadyKey
|
|
1758
|
+
const nodes = useScene.getState().nodes
|
|
1759
|
+
if (Object.keys(nodes).length === 0) return
|
|
1760
|
+
const bounds = computeSceneBoundsXZ(nodes)
|
|
1761
|
+
emitter.emit('camera-controls:fit-scene', bounds ? { bounds } : {})
|
|
1762
|
+
}, [isViewerSceneReady, sceneReadyKey])
|
|
1763
|
+
|
|
1661
1764
|
const isSceneDataLoading = isLoading || isSceneLoading || !hasLoadedInitialScene
|
|
1662
1765
|
const showLoader = isSceneDataLoading || !isViewerSceneReady
|
|
1663
1766
|
const visibleLoader =
|
|
@@ -1714,6 +1817,13 @@ function EditorContent({
|
|
|
1714
1817
|
}
|
|
1715
1818
|
}, [isFirstPersonMode, isPreviewMode])
|
|
1716
1819
|
|
|
1820
|
+
// The viewer's walkthrough says how the first-person camera moves, for the
|
|
1821
|
+
// viewer-side consumers that tell a walk from a drone flight.
|
|
1822
|
+
const firstPersonMovementMode = useEditor((s) => s.firstPersonMovementMode)
|
|
1823
|
+
useEffect(() => {
|
|
1824
|
+
if (isFirstPersonMode) useViewer.getState().setWalkthroughMovement(firstPersonMovementMode)
|
|
1825
|
+
}, [isFirstPersonMode, firstPersonMovementMode])
|
|
1826
|
+
|
|
1717
1827
|
const previewViewerContent = (
|
|
1718
1828
|
<Viewer
|
|
1719
1829
|
defaultRender={EDITOR_DEFAULT_RENDER}
|
|
@@ -1826,6 +1936,7 @@ function EditorContent({
|
|
|
1826
1936
|
return (
|
|
1827
1937
|
<>
|
|
1828
1938
|
<FloorplanModeCoordinator />
|
|
1939
|
+
<CeilingTopsSync />
|
|
1829
1940
|
{!isLoading && isPreviewMode ? (
|
|
1830
1941
|
<PreviewStage
|
|
1831
1942
|
isFirstPersonMode={isFirstPersonMode}
|
|
@@ -1912,6 +2023,7 @@ function EditorContent({
|
|
|
1912
2023
|
return (
|
|
1913
2024
|
<div className="flex h-full w-full gap-3 bg-neutral-100 p-3 text-foreground">
|
|
1914
2025
|
<FloorplanModeCoordinator />
|
|
2026
|
+
<CeilingTopsSync />
|
|
1915
2027
|
{!isLoading && isPreviewMode ? (
|
|
1916
2028
|
<PreviewStage
|
|
1917
2029
|
isFirstPersonMode={isFirstPersonMode}
|
|
@@ -1960,10 +2072,28 @@ function EditorContent({
|
|
|
1960
2072
|
)
|
|
1961
2073
|
}
|
|
1962
2074
|
|
|
1963
|
-
|
|
2075
|
+
function EditorFrame(props: EditorProps) {
|
|
1964
2076
|
return (
|
|
1965
2077
|
<Profiler id="editor" onRender={recordEditorRender}>
|
|
1966
2078
|
<EditorContent {...props} />
|
|
1967
2079
|
</Profiler>
|
|
1968
2080
|
)
|
|
1969
2081
|
}
|
|
2082
|
+
|
|
2083
|
+
function LocalizedEditor(props: EditorProps & { locale: string }) {
|
|
2084
|
+
const i18n = useMemo(() => createI18n({ locale: props.locale }), [props.locale])
|
|
2085
|
+
|
|
2086
|
+
return (
|
|
2087
|
+
<PascalI18nProvider i18n={i18n}>
|
|
2088
|
+
<EditorFrame {...props} />
|
|
2089
|
+
</PascalI18nProvider>
|
|
2090
|
+
)
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
export default function Editor(props: EditorProps) {
|
|
2094
|
+
return props.locale ? (
|
|
2095
|
+
<LocalizedEditor {...props} locale={props.locale} />
|
|
2096
|
+
) : (
|
|
2097
|
+
<EditorFrame {...props} />
|
|
2098
|
+
)
|
|
2099
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useIsMobile } from '../../hooks/use-mobile'
|
|
4
|
+
import { openExtension } from '../../lib/inspector-card-mode'
|
|
5
|
+
import { useInspectorExpanded } from '../../lib/inspector-expanded'
|
|
6
|
+
import { renderExtensionIcon, useInspectorExtensions } from '../ui/panels/inspector-extensions'
|
|
7
|
+
import { ActionMenuButton } from './action-menu-button'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* One action-menu button per plugin inspector extension of the selected node
|
|
11
|
+
* (`Plugin.inspectorExtensions`, e.g. a wall's Engineering): it opens the
|
|
12
|
+
* inspector card on that extension, or keeps it there. The extension that owns
|
|
13
|
+
* the card body for this node (`primaryWhen`) is already showing and gets none;
|
|
14
|
+
* the mobile sheet lists every extension under the kind's controls instead.
|
|
15
|
+
*/
|
|
16
|
+
export function InspectorExtensionActions() {
|
|
17
|
+
const isMobile = useIsMobile()
|
|
18
|
+
const { extensions, primaryExtension } = useInspectorExtensions()
|
|
19
|
+
const active = useInspectorExpanded((state) => (state.expanded ? state.activeExtensionId : null))
|
|
20
|
+
if (isMobile) return null
|
|
21
|
+
return extensions
|
|
22
|
+
.filter((extension) => extension !== primaryExtension)
|
|
23
|
+
.map((extension) => (
|
|
24
|
+
<ActionMenuButton
|
|
25
|
+
key={extension.id}
|
|
26
|
+
label={extension.title}
|
|
27
|
+
onClick={(event) => {
|
|
28
|
+
event.stopPropagation()
|
|
29
|
+
useInspectorExpanded.getState().applyMode(openExtension(extension.id))
|
|
30
|
+
}}
|
|
31
|
+
pressed={active === extension.id}
|
|
32
|
+
>
|
|
33
|
+
{renderExtensionIcon(extension.icon)}
|
|
34
|
+
</ActionMenuButton>
|
|
35
|
+
))
|
|
36
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { PascalI18nProvider, usePascalI18n } from '@pascal-app/core'
|
|
4
|
+
import { Html as DreiHtml } from '@react-three/drei'
|
|
5
|
+
import { type ComponentProps, forwardRef } from 'react'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* drei's `Html`, keeping the editor's i18n scope. `Html` renders its children
|
|
9
|
+
* in a separate React root, which sees none of the surrounding context, so a
|
|
10
|
+
* component inside it would translate with the default (English) scope instead
|
|
11
|
+
* of the editor's `?lang=` or `<Editor locale>` one.
|
|
12
|
+
*/
|
|
13
|
+
export const Html = forwardRef<HTMLDivElement, Omit<ComponentProps<typeof DreiHtml>, 'ref'>>(
|
|
14
|
+
function Html({ children, ...props }, ref) {
|
|
15
|
+
const i18n = usePascalI18n()
|
|
16
|
+
return (
|
|
17
|
+
<DreiHtml {...props} ref={ref}>
|
|
18
|
+
<PascalI18nProvider i18n={i18n}>{children}</PascalI18nProvider>
|
|
19
|
+
</DreiHtml>
|
|
20
|
+
)
|
|
21
|
+
},
|
|
22
|
+
)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import { formatLinearMeasurement, type MetricNotation } from '@pascal-app/core'
|
|
3
4
|
import { useViewer } from '@pascal-app/viewer'
|
|
4
5
|
import { type ForwardedRef, Fragment, forwardRef } from 'react'
|
|
5
|
-
import { formatLinearMeasurement, type MetricNotation } from '../../lib/measurements'
|
|
6
6
|
|
|
7
7
|
// Canonical in-world dimension formatter — metric metres or imperial
|
|
8
8
|
// feet/inches. Shared by every measurement readout so they read the same.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { Icon } from '@iconify/react'
|
|
4
|
+
import { usePascalI18n } from '@pascal-app/core'
|
|
4
5
|
import {
|
|
5
6
|
Copy,
|
|
6
7
|
Group,
|
|
@@ -65,70 +66,103 @@ export function NodeActionMenu({
|
|
|
65
66
|
onPointerEnter,
|
|
66
67
|
onPointerLeave,
|
|
67
68
|
children,
|
|
68
|
-
deleteLabel
|
|
69
|
+
deleteLabel,
|
|
69
70
|
deleteDisabledReason,
|
|
70
71
|
}: NodeActionMenuProps) {
|
|
72
|
+
const i18n = usePascalI18n()
|
|
71
73
|
return (
|
|
72
74
|
<div
|
|
73
75
|
className="pointer-events-auto flex items-center gap-1 rounded-lg border border-border bg-background/95 p-1 shadow-xl backdrop-blur-md"
|
|
76
|
+
data-node-action-menu
|
|
74
77
|
onPointerDown={onPointerDown}
|
|
75
78
|
onPointerEnter={onPointerEnter}
|
|
76
79
|
onPointerLeave={onPointerLeave}
|
|
77
80
|
onPointerUp={onPointerUp}
|
|
78
81
|
>
|
|
79
82
|
{onFind && (
|
|
80
|
-
<ActionMenuButton
|
|
83
|
+
<ActionMenuButton
|
|
84
|
+
label={i18n._({ id: 'editor.nodeActionMenu.findInCatalog', message: 'Find in catalog' })}
|
|
85
|
+
onClick={onFind}
|
|
86
|
+
>
|
|
81
87
|
<Search className="h-4 w-4" />
|
|
82
88
|
</ActionMenuButton>
|
|
83
89
|
)}
|
|
84
90
|
{onMove && (
|
|
85
|
-
<ActionMenuButton
|
|
91
|
+
<ActionMenuButton
|
|
92
|
+
label={i18n._({ id: 'editor.nodeActionMenu.move', message: 'Move' })}
|
|
93
|
+
onClick={onMove}
|
|
94
|
+
>
|
|
86
95
|
<Move className="h-4 w-4" />
|
|
87
96
|
</ActionMenuButton>
|
|
88
97
|
)}
|
|
89
98
|
{onEditMesh && (
|
|
90
|
-
<ActionMenuButton
|
|
99
|
+
<ActionMenuButton
|
|
100
|
+
label={i18n._({ id: 'editor.nodeActionMenu.editMesh', message: 'Edit mesh' })}
|
|
101
|
+
onClick={onEditMesh}
|
|
102
|
+
>
|
|
91
103
|
<PencilRuler className="h-4 w-4" />
|
|
92
104
|
</ActionMenuButton>
|
|
93
105
|
)}
|
|
94
106
|
{onGroup && (
|
|
95
|
-
<ActionMenuButton
|
|
107
|
+
<ActionMenuButton
|
|
108
|
+
keys={['Cmd/Ctrl', 'G']}
|
|
109
|
+
label={i18n._({ id: 'editor.nodeActionMenu.groupSelection', message: 'Group selection' })}
|
|
110
|
+
onClick={onGroup}
|
|
111
|
+
>
|
|
96
112
|
<Group className="h-4 w-4" />
|
|
97
113
|
</ActionMenuButton>
|
|
98
114
|
)}
|
|
99
115
|
{onUngroup && (
|
|
100
116
|
<ActionMenuButton
|
|
101
117
|
keys={['Cmd/Ctrl', 'Shift', 'G']}
|
|
102
|
-
label=
|
|
118
|
+
label={i18n._({
|
|
119
|
+
id: 'editor.nodeActionMenu.ungroupSelection',
|
|
120
|
+
message: 'Ungroup selection',
|
|
121
|
+
})}
|
|
103
122
|
onClick={onUngroup}
|
|
104
123
|
>
|
|
105
124
|
<Ungroup className="h-4 w-4" />
|
|
106
125
|
</ActionMenuButton>
|
|
107
126
|
)}
|
|
108
127
|
{onCurve && (
|
|
109
|
-
<ActionMenuButton
|
|
128
|
+
<ActionMenuButton
|
|
129
|
+
label={i18n._({ id: 'editor.nodeActionMenu.curve', message: 'Curve' })}
|
|
130
|
+
onClick={onCurve}
|
|
131
|
+
>
|
|
110
132
|
<Spline className="h-4 w-4" />
|
|
111
133
|
</ActionMenuButton>
|
|
112
134
|
)}
|
|
113
135
|
{onRotateLeft && (
|
|
114
|
-
<ActionMenuButton
|
|
136
|
+
<ActionMenuButton
|
|
137
|
+
label={i18n._({ id: 'editor.nodeActionMenu.rotateLeft', message: 'Rotate left' })}
|
|
138
|
+
onClick={onRotateLeft}
|
|
139
|
+
>
|
|
115
140
|
<RotateCcw className="h-4 w-4" />
|
|
116
141
|
</ActionMenuButton>
|
|
117
142
|
)}
|
|
118
143
|
{onRotateRight && (
|
|
119
|
-
<ActionMenuButton
|
|
144
|
+
<ActionMenuButton
|
|
145
|
+
label={i18n._({ id: 'editor.nodeActionMenu.rotateRight', message: 'Rotate right' })}
|
|
146
|
+
onClick={onRotateRight}
|
|
147
|
+
>
|
|
120
148
|
<RotateCw className="h-4 w-4" />
|
|
121
149
|
</ActionMenuButton>
|
|
122
150
|
)}
|
|
123
151
|
<RegistryActionContributions />
|
|
124
152
|
{children}
|
|
125
153
|
{onDuplicate && (
|
|
126
|
-
<ActionMenuButton
|
|
154
|
+
<ActionMenuButton
|
|
155
|
+
label={i18n._({ id: 'editor.nodeActionMenu.duplicate', message: 'Duplicate' })}
|
|
156
|
+
onClick={onDuplicate}
|
|
157
|
+
>
|
|
127
158
|
<Copy className="h-4 w-4" />
|
|
128
159
|
</ActionMenuButton>
|
|
129
160
|
)}
|
|
130
161
|
{onAddHole && (
|
|
131
|
-
<ActionMenuButton
|
|
162
|
+
<ActionMenuButton
|
|
163
|
+
label={i18n._({ id: 'editor.nodeActionMenu.cutOut', message: 'Cut out' })}
|
|
164
|
+
onClick={onAddHole}
|
|
165
|
+
>
|
|
132
166
|
<Icon height={16} icon="carbon:cut-out" width={16} />
|
|
133
167
|
</ActionMenuButton>
|
|
134
168
|
)}
|
|
@@ -138,7 +172,7 @@ export function NodeActionMenu({
|
|
|
138
172
|
disabled={!!deleteDisabledReason}
|
|
139
173
|
disabledReason={deleteDisabledReason}
|
|
140
174
|
keys={['Delete / Backspace']}
|
|
141
|
-
label={deleteLabel}
|
|
175
|
+
label={deleteLabel ?? i18n._({ id: 'editor.nodeActionMenu.delete', message: 'Delete' })}
|
|
142
176
|
onClick={onDelete}
|
|
143
177
|
>
|
|
144
178
|
<Trash2 className="h-4 w-4" />
|