@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
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { Icon } from '@iconify/react'
|
|
4
3
|
import {
|
|
5
4
|
type AnyNode,
|
|
6
5
|
type AnyNodeId,
|
|
7
|
-
getInspectorExtensions,
|
|
8
|
-
type IconRef,
|
|
9
6
|
type InspectorExtension,
|
|
10
|
-
|
|
11
|
-
useScene,
|
|
7
|
+
useScene, usePascalI18n,
|
|
12
8
|
} from '@pascal-app/core'
|
|
13
|
-
import { useViewer } from '@pascal-app/viewer'
|
|
14
9
|
import { ChevronDown, ChevronLeft, GripHorizontal, RotateCcw, X } from 'lucide-react'
|
|
15
10
|
import {
|
|
16
11
|
type ComponentType,
|
|
@@ -21,18 +16,12 @@ import {
|
|
|
21
16
|
useContext,
|
|
22
17
|
useEffect,
|
|
23
18
|
useLayoutEffect,
|
|
24
|
-
useMemo,
|
|
25
19
|
useRef,
|
|
26
20
|
useState,
|
|
27
21
|
} from 'react'
|
|
28
22
|
import { useIsMobile } from '../../../hooks/use-mobile'
|
|
29
|
-
import { IconRefImage } from '../icon-ref'
|
|
30
23
|
import { NodeIconImage } from '../node-icon-image'
|
|
31
|
-
import {
|
|
32
|
-
resolveActiveExtension,
|
|
33
|
-
toggleCard,
|
|
34
|
-
toggleExtension,
|
|
35
|
-
} from '../../../lib/inspector-card-mode'
|
|
24
|
+
import { resolveActiveExtension, toggleCard } from '../../../lib/inspector-card-mode'
|
|
36
25
|
import { cn } from '../../../lib/utils'
|
|
37
26
|
import { useInspectorExpanded } from '../../../lib/inspector-expanded'
|
|
38
27
|
import { ActionButton, ActionGroup } from '../controls/action-button'
|
|
@@ -40,6 +29,7 @@ import { PanelSection } from '../controls/panel-section'
|
|
|
40
29
|
import { ErrorBoundary } from '../primitives/error-boundary'
|
|
41
30
|
import { useInRightStack } from '../right-stack'
|
|
42
31
|
import { CollectionsPopover } from './collections/collections-popover'
|
|
32
|
+
import { renderExtensionIcon, useInspectorExtensions } from './inspector-extensions'
|
|
43
33
|
|
|
44
34
|
const DRAG_MARGIN = 8
|
|
45
35
|
// Pointer travel (px) below which a header press is treated as a click
|
|
@@ -55,7 +45,7 @@ function clamp(value: number, min: number, max: number): number {
|
|
|
55
45
|
* `data-viewer-bounds`) so it can't slide under the sidebar or top bar.
|
|
56
46
|
* Falls back to the viewport when the marker isn't found.
|
|
57
47
|
*/
|
|
58
|
-
function getDragBounds(el: HTMLElement | null): {
|
|
48
|
+
function getDragBounds(el: HTMLElement | null, inStack = false): {
|
|
59
49
|
left: number
|
|
60
50
|
top: number
|
|
61
51
|
right: number
|
|
@@ -66,7 +56,15 @@ function getDragBounds(el: HTMLElement | null): {
|
|
|
66
56
|
if (!rect) {
|
|
67
57
|
return { left: 0, top: 0, right: window.innerWidth, bottom: window.innerHeight }
|
|
68
58
|
}
|
|
69
|
-
|
|
59
|
+
const stack = inStack ? el?.closest('[data-right-stack]') : null
|
|
60
|
+
const stackRect = stack?.getBoundingClientRect()
|
|
61
|
+
const cardRect = stack?.querySelector('[data-right-stack-card]')?.getBoundingClientRect()
|
|
62
|
+
return {
|
|
63
|
+
left: rect.left,
|
|
64
|
+
top: stackRect ? stackRect.top - DRAG_MARGIN : rect.top,
|
|
65
|
+
right: rect.right,
|
|
66
|
+
bottom: cardRect && cardRect.height > 0 ? cardRect.top : rect.bottom,
|
|
67
|
+
}
|
|
70
68
|
}
|
|
71
69
|
|
|
72
70
|
/**
|
|
@@ -119,6 +117,7 @@ export function PanelWrapper({
|
|
|
119
117
|
className,
|
|
120
118
|
width = 320, // default width
|
|
121
119
|
}: PanelWrapperProps) {
|
|
120
|
+
const i18n = usePascalI18n()
|
|
122
121
|
const isMobile = useIsMobile()
|
|
123
122
|
const inStack = useInRightStack()
|
|
124
123
|
const contextFooter = useContext(InspectorFooterContext)
|
|
@@ -131,35 +130,16 @@ export function PanelWrapper({
|
|
|
131
130
|
// The wrapper self-resolves the selected node instead of taking a prop:
|
|
132
131
|
// kind-owned `customPanel`s (wall, slab, …) render their own
|
|
133
132
|
// <PanelWrapper>, so this is the one spot every inspector card flows
|
|
134
|
-
// through. Extensions only apply to a single-node selection
|
|
135
|
-
|
|
136
|
-
const selectedId =
|
|
137
|
-
s.selection.selectedIds.length === 1 ? s.selection.selectedIds[0] : undefined,
|
|
138
|
-
) as AnyNodeId | undefined
|
|
139
|
-
// Subscribe to the selected node's *type* only — a string primitive that
|
|
140
|
-
// doesn't change as fields are edited (same trick as ParametricInspector).
|
|
141
|
-
const selectedType = useScene((s) => (selectedId ? s.nodes[selectedId]?.type ?? null : null))
|
|
142
|
-
const installedPlugins = useScene((s) => s.installedPlugins)
|
|
143
|
-
const extensions = useMemo(() => {
|
|
144
|
-
// re-derive when plugin extensions register after mount (async plugin load)
|
|
145
|
-
void registryVersion
|
|
146
|
-
if (!selectedType) return []
|
|
147
|
-
return getInspectorExtensions(selectedType).filter(
|
|
148
|
-
(extension) => !extension.pluginId || installedPlugins.includes(extension.pluginId),
|
|
149
|
-
)
|
|
150
|
-
}, [selectedType, installedPlugins, registryVersion])
|
|
133
|
+
// through. Extensions only apply to a single-node selection; the action
|
|
134
|
+
// menu opens them (`InspectorExtensionActions`).
|
|
135
|
+
const { selectedId, extensions, primaryExtension } = useInspectorExtensions()
|
|
151
136
|
|
|
152
137
|
// Which extension's content fills the card body (extension mode). The two
|
|
153
138
|
// expanded modes are EITHER/OR: extension mode replaces the regular
|
|
154
139
|
// controls; null shows the regular controls with no extension sections.
|
|
155
|
-
// See `lib/inspector-card-mode.ts` for the transition table.
|
|
156
|
-
const [activeExtensionId, setActiveExtensionId] = useState<string | null>(null)
|
|
157
140
|
// Stale ids (kind changed, plugin gated off) fall back to regular mode.
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return node ? extensions.find((extension) => extension.primaryWhen?.(node))?.id : undefined
|
|
161
|
-
})
|
|
162
|
-
const primaryExtension = extensions.find((extension) => extension.id === primaryExtensionId)
|
|
141
|
+
// See `lib/inspector-card-mode.ts` for the transition table.
|
|
142
|
+
const activeExtensionId = useInspectorExpanded((state) => state.activeExtensionId)
|
|
163
143
|
const activeExtension = primaryExtension ?? resolveActiveExtension(activeExtensionId, extensions)
|
|
164
144
|
|
|
165
145
|
// The panel is collapsed to just its header until the user expands it. The
|
|
@@ -174,14 +154,6 @@ export function PanelWrapper({
|
|
|
174
154
|
if (primaryExtension) setCollapsed(false)
|
|
175
155
|
}, [primaryExtension, selectedId, setCollapsed])
|
|
176
156
|
|
|
177
|
-
const applyMode = useCallback(
|
|
178
|
-
(next: { collapsed: boolean; activeExtensionId: string | null }) => {
|
|
179
|
-
setCollapsed(next.collapsed)
|
|
180
|
-
setActiveExtensionId(next.activeExtensionId)
|
|
181
|
-
},
|
|
182
|
-
[setCollapsed],
|
|
183
|
-
)
|
|
184
|
-
|
|
185
157
|
// Chevron / header press — collapsed → regular, regular → collapsed,
|
|
186
158
|
// extension mode → regular (exit the extension first, stay expanded).
|
|
187
159
|
const handleCardToggle = useCallback(() => {
|
|
@@ -189,14 +161,12 @@ export function PanelWrapper({
|
|
|
189
161
|
setCollapsed(!collapsed)
|
|
190
162
|
return
|
|
191
163
|
}
|
|
192
|
-
applyMode(toggleCard({ collapsed, activeExtensionId }))
|
|
193
|
-
}, [
|
|
164
|
+
useInspectorExpanded.getState().applyMode(toggleCard({ collapsed, activeExtensionId }))
|
|
165
|
+
}, [collapsed, activeExtensionId, primaryExtension, setCollapsed])
|
|
194
166
|
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
useEffect(() => {
|
|
198
|
-
if (collapsed) setActiveExtensionId(null)
|
|
199
|
-
}, [collapsed])
|
|
167
|
+
// Extension mode is an affordance of the card it was opened on: a card that
|
|
168
|
+
// closes (selection cleared or changed kind) leaves the next one regular.
|
|
169
|
+
useEffect(() => () => useInspectorExpanded.setState({ activeExtensionId: null }), [])
|
|
200
170
|
|
|
201
171
|
// Drag-to-reposition from the header. `offset` is a translation applied on
|
|
202
172
|
// top of the default `top-20 right-4` anchor; null until first dragged.
|
|
@@ -225,7 +195,7 @@ export function PanelWrapper({
|
|
|
225
195
|
if ((e.target as HTMLElement).closest('button, input, label')) return
|
|
226
196
|
const rect = panelRef.current?.getBoundingClientRect()
|
|
227
197
|
if (!rect) return
|
|
228
|
-
const bounds = getDragBounds(panelRef.current)
|
|
198
|
+
const bounds = getDragBounds(panelRef.current, inStack)
|
|
229
199
|
const base = offset ?? { x: 0, y: 0 }
|
|
230
200
|
dragRef.current = {
|
|
231
201
|
startX: e.clientX,
|
|
@@ -245,7 +215,7 @@ export function PanelWrapper({
|
|
|
245
215
|
setIsDragging(true)
|
|
246
216
|
e.currentTarget.setPointerCapture(e.pointerId)
|
|
247
217
|
},
|
|
248
|
-
[offset],
|
|
218
|
+
[offset, inStack],
|
|
249
219
|
)
|
|
250
220
|
|
|
251
221
|
const handleHeaderPointerMove = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
|
|
@@ -276,22 +246,37 @@ export function PanelWrapper({
|
|
|
276
246
|
[handleCardToggle],
|
|
277
247
|
)
|
|
278
248
|
|
|
279
|
-
//
|
|
280
|
-
// collapsed — nudge it back inside the viewer bounds.
|
|
249
|
+
// Keep repositioned panels inside the available space as the card or viewer resizes.
|
|
281
250
|
useLayoutEffect(() => {
|
|
282
|
-
if (isMobile
|
|
251
|
+
if (isMobile) return
|
|
283
252
|
const el = panelRef.current
|
|
284
253
|
if (!el) return
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
254
|
+
const fit = () => {
|
|
255
|
+
const rect = el.getBoundingClientRect()
|
|
256
|
+
const bounds = getDragBounds(el, inStack)
|
|
257
|
+
const left = clamp(rect.left, bounds.left + DRAG_MARGIN, bounds.right - rect.width - DRAG_MARGIN)
|
|
258
|
+
const top = clamp(rect.top, bounds.top + DRAG_MARGIN, bounds.bottom - rect.height - DRAG_MARGIN)
|
|
259
|
+
const dx = left - rect.left
|
|
260
|
+
const dy = top - rect.top
|
|
261
|
+
if (Math.abs(dx) > 0.5 || Math.abs(dy) > 0.5) {
|
|
262
|
+
setOffset((prev) => ({ x: (prev?.x ?? 0) + dx, y: (prev?.y ?? 0) + dy }))
|
|
263
|
+
}
|
|
293
264
|
}
|
|
294
|
-
|
|
265
|
+
fit()
|
|
266
|
+
const resize = new ResizeObserver(fit)
|
|
267
|
+
resize.observe(el)
|
|
268
|
+
const stack = el.closest('[data-right-stack]')
|
|
269
|
+
if (stack) {
|
|
270
|
+
resize.observe(stack)
|
|
271
|
+
const card = stack.querySelector('[data-right-stack-card]')
|
|
272
|
+
if (card) resize.observe(card)
|
|
273
|
+
}
|
|
274
|
+
window.addEventListener('resize', fit)
|
|
275
|
+
return () => {
|
|
276
|
+
resize.disconnect()
|
|
277
|
+
window.removeEventListener('resize', fit)
|
|
278
|
+
}
|
|
279
|
+
}, [collapsed, isMobile, inStack])
|
|
295
280
|
|
|
296
281
|
return (
|
|
297
282
|
<div
|
|
@@ -302,7 +287,7 @@ export function PanelWrapper({
|
|
|
302
287
|
: inStack
|
|
303
288
|
? // In the shared right column: it grows down and scrolls on itself,
|
|
304
289
|
// leaving the shortcuts card below it its room (`right-stack.tsx`).
|
|
305
|
-
'pointer-events-auto flex max-h-
|
|
290
|
+
'pointer-events-auto flex max-h-[var(--inspector-available-height)] min-h-0 flex-[0_1_auto] flex-col overflow-hidden rounded-xl border border-border/50 bg-sidebar/95 shadow-2xl backdrop-blur-xl dark:text-foreground'
|
|
306
291
|
// Cap height at `100dvh - 154px` so a tall panel's bottom edge
|
|
307
292
|
// aligns flush with the top of the floating bottom action bar.
|
|
308
293
|
// Combined with `top-20` (80px), the panel's bottom sits at
|
|
@@ -329,7 +314,7 @@ export function PanelWrapper({
|
|
|
329
314
|
{!isMobile && (
|
|
330
315
|
<div
|
|
331
316
|
className={cn(
|
|
332
|
-
'relative flex select-none items-center justify-between px-3 py-3',
|
|
317
|
+
'relative flex shrink-0 select-none items-center justify-between px-3 py-3',
|
|
333
318
|
!collapsed && 'border-border/50 border-b',
|
|
334
319
|
isDragging ? 'cursor-grabbing' : 'cursor-grab',
|
|
335
320
|
)}
|
|
@@ -341,6 +326,7 @@ export function PanelWrapper({
|
|
|
341
326
|
<div className={cn('flex min-w-0 items-center gap-2', titleContent && 'flex-1 pr-2')}>
|
|
342
327
|
{onBack && (
|
|
343
328
|
<button
|
|
329
|
+
aria-label={i18n._({ id: 'editor.panels.panelWrapper.back', message: 'Back' })}
|
|
344
330
|
className="mr-1 flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-black/8 dark:hover:bg-[#3e3e3e] hover:text-foreground"
|
|
345
331
|
onClick={onBack}
|
|
346
332
|
type="button"
|
|
@@ -388,6 +374,7 @@ export function PanelWrapper({
|
|
|
388
374
|
<div className="flex items-center gap-1">
|
|
389
375
|
{onReset && (
|
|
390
376
|
<button
|
|
377
|
+
aria-label={i18n._({ id: 'editor.panels.panelWrapper.reset', message: 'Reset' })}
|
|
391
378
|
className="flex h-7 w-7 items-center justify-center rounded-md bg-black/4 dark:bg-[#2C2C2E] text-muted-foreground transition-colors hover:bg-black/8 dark:hover:bg-[#3e3e3e] hover:text-foreground"
|
|
392
379
|
onClick={onReset}
|
|
393
380
|
type="button"
|
|
@@ -395,37 +382,9 @@ export function PanelWrapper({
|
|
|
395
382
|
<RotateCcw className="h-4 w-4" />
|
|
396
383
|
</button>
|
|
397
384
|
)}
|
|
398
|
-
{/* Extension mode buttons — one icon per registered inspector
|
|
399
|
-
extension, left of the chevron. Click swaps the card body to
|
|
400
|
-
ONLY that extension's content (either/or with the regular
|
|
401
|
-
controls); the active icon (highlighted) or the chevron
|
|
402
|
-
returns to the regular controls. */}
|
|
403
|
-
{extensions.filter((extension) => extension !== primaryExtension).map((extension) => {
|
|
404
|
-
const isActive = !collapsed && activeExtensionId === extension.id
|
|
405
|
-
return (
|
|
406
|
-
<button
|
|
407
|
-
aria-label={isActive ? `Close ${extension.title}` : `Open ${extension.title}`}
|
|
408
|
-
aria-pressed={isActive}
|
|
409
|
-
className={cn(
|
|
410
|
-
'flex h-7 w-7 items-center justify-center rounded-md transition-colors',
|
|
411
|
-
isActive
|
|
412
|
-
? 'bg-cyan-500/20 text-cyan-700 dark:text-cyan-400 hover:bg-cyan-500/30'
|
|
413
|
-
: 'bg-black/4 dark:bg-[#2C2C2E] text-muted-foreground hover:bg-black/8 dark:hover:bg-[#3e3e3e] hover:text-foreground',
|
|
414
|
-
)}
|
|
415
|
-
key={extension.id}
|
|
416
|
-
onClick={() =>
|
|
417
|
-
applyMode(toggleExtension({ collapsed, activeExtensionId }, extension.id))
|
|
418
|
-
}
|
|
419
|
-
title={extension.title}
|
|
420
|
-
type="button"
|
|
421
|
-
>
|
|
422
|
-
{renderExtensionIcon(extension.icon)}
|
|
423
|
-
</button>
|
|
424
|
-
)
|
|
425
|
-
})}
|
|
426
385
|
<button
|
|
427
386
|
aria-expanded={!collapsed}
|
|
428
|
-
aria-label={collapsed ? 'Expand panel' : 'Collapse panel'}
|
|
387
|
+
aria-label={collapsed ? i18n._({ id: 'editor.panels.panelWrapper.expandPanel', message: 'Expand panel' }) : i18n._({ id: 'editor.panels.panelWrapper.collapsePanel', message: 'Collapse panel' })}
|
|
429
388
|
className="flex h-7 w-7 items-center justify-center rounded-md bg-black/4 dark:bg-[#2C2C2E] text-muted-foreground transition-colors hover:bg-black/8 dark:hover:bg-[#3e3e3e] hover:text-foreground"
|
|
430
389
|
onClick={handleCardToggle}
|
|
431
390
|
type="button"
|
|
@@ -436,6 +395,7 @@ export function PanelWrapper({
|
|
|
436
395
|
</button>
|
|
437
396
|
{onClose && (
|
|
438
397
|
<button
|
|
398
|
+
aria-label={i18n._({ id: 'editor.panels.panelWrapper.close', message: 'Close' })}
|
|
439
399
|
className="flex h-7 w-7 items-center justify-center rounded-md bg-black/4 dark:bg-[#2C2C2E] text-muted-foreground transition-colors hover:bg-black/8 dark:hover:bg-[#3e3e3e] hover:text-foreground"
|
|
440
400
|
onClick={onClose}
|
|
441
401
|
type="button"
|
|
@@ -473,10 +433,10 @@ export function PanelWrapper({
|
|
|
473
433
|
)}
|
|
474
434
|
{children}
|
|
475
435
|
{inspectedIds.length > 0 && (
|
|
476
|
-
<PanelSection title=
|
|
436
|
+
<PanelSection title={i18n._({ id: 'editor.panels.panelWrapper.collections', message: 'Collections' })}>
|
|
477
437
|
<ActionGroup>
|
|
478
438
|
<CollectionsPopover nodeIds={inspectedIds}>
|
|
479
|
-
<ActionButton label=
|
|
439
|
+
<ActionButton label={i18n._({ id: 'editor.panels.panelWrapper.manageCollections', message: 'Manage collections…' })} />
|
|
480
440
|
</CollectionsPopover>
|
|
481
441
|
</ActionGroup>
|
|
482
442
|
</PanelSection>
|
|
@@ -507,30 +467,6 @@ export function PanelWrapper({
|
|
|
507
467
|
|
|
508
468
|
// ─── Plugin inspector extensions ──────────────────────────────────────
|
|
509
469
|
|
|
510
|
-
/** 16px icon for an extension's header button — mirrors the parametric
|
|
511
|
-
* inspector's `renderIcon` (plain <img> so no next/image server deps). */
|
|
512
|
-
function renderExtensionIcon(ref: IconRef): React.ReactNode {
|
|
513
|
-
if (ref.kind === 'url') {
|
|
514
|
-
return <IconRefImage className="h-4 w-4 shrink-0" src={ref.src} />
|
|
515
|
-
}
|
|
516
|
-
if (ref.kind === 'iconify') {
|
|
517
|
-
return <Icon height={16} icon={ref.name} width={16} />
|
|
518
|
-
}
|
|
519
|
-
if (ref.kind === 'svg') {
|
|
520
|
-
return (
|
|
521
|
-
<svg height={16} viewBox={ref.viewBox} width={16}>
|
|
522
|
-
<path d={ref.path} fill="currentColor" />
|
|
523
|
-
</svg>
|
|
524
|
-
)
|
|
525
|
-
}
|
|
526
|
-
const LazyIcon = lazy(ref.module)
|
|
527
|
-
return (
|
|
528
|
-
<Suspense fallback={null}>
|
|
529
|
-
<LazyIcon />
|
|
530
|
-
</Suspense>
|
|
531
|
-
)
|
|
532
|
-
}
|
|
533
|
-
|
|
534
470
|
// `React.lazy` once per loader so the resolved component keeps a stable
|
|
535
471
|
// identity across renders — same WeakMap pattern as `resolveCustomPanel`
|
|
536
472
|
// in parametric-inspector.tsx.
|
|
@@ -582,14 +518,14 @@ function InspectorExtensionContent({
|
|
|
582
518
|
extension: InspectorExtension
|
|
583
519
|
nodeId: AnyNodeId
|
|
584
520
|
}) {
|
|
521
|
+
const i18n = usePascalI18n()
|
|
585
522
|
const node = useScene((s) => s.nodes[nodeId])
|
|
586
523
|
if (!node) return null
|
|
587
524
|
const Extension = resolveExtensionComponent(extension)
|
|
588
525
|
return (
|
|
589
526
|
<ErrorBoundary fallback={
|
|
590
527
|
<p className="p-1 text-muted-foreground text-xs">
|
|
591
|
-
“{
|
|
592
|
-
</p>
|
|
528
|
+
{i18n._({ id: 'editor.panels.panelWrapper.titleHitAnErrorAndWas', message: '“{title}” hit an error and was unloaded for this session.', values: { title: extension.title } })}</p>
|
|
593
529
|
}>
|
|
594
530
|
<Suspense fallback={null}>
|
|
595
531
|
<Extension node={node} />
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import { usePascalI18n } from '@pascal-app/core'
|
|
3
4
|
import type { AnyNode, ParamField } from '@pascal-app/core'
|
|
4
5
|
import { SegmentedControl } from '../controls/segmented-control'
|
|
5
6
|
import { SliderControl } from '../controls/slider-control'
|
|
6
7
|
import { ToggleControl } from '../controls/toggle-control'
|
|
7
|
-
import {
|
|
8
|
+
import { inspectorFieldDescriptor, inspectorOptionDescriptor } from '../../../i18n/node-messages'
|
|
9
|
+
import { precisionForStep } from './parametric-field-utils'
|
|
8
10
|
|
|
9
11
|
interface ParametricFieldControlProps {
|
|
10
12
|
field: ParamField<AnyNode>
|
|
@@ -21,16 +23,17 @@ export function ParametricFieldControl({
|
|
|
21
23
|
onChange,
|
|
22
24
|
onCommit,
|
|
23
25
|
}: ParametricFieldControlProps) {
|
|
26
|
+
const i18n = usePascalI18n()
|
|
24
27
|
const key = String(field.key)
|
|
25
28
|
|
|
26
29
|
switch (field.kind) {
|
|
27
30
|
case 'number': {
|
|
28
|
-
const num = typeof value === 'number' ? value : 0
|
|
31
|
+
const num = typeof value === 'number' ? value : (field.default ?? 0)
|
|
29
32
|
const step = field.step ?? 0.01
|
|
30
33
|
const precision = precisionForStep(step)
|
|
31
34
|
return (
|
|
32
35
|
<SliderControl
|
|
33
|
-
label={
|
|
36
|
+
label={i18n._(inspectorFieldDescriptor(key))}
|
|
34
37
|
max={field.max}
|
|
35
38
|
min={field.min}
|
|
36
39
|
mixed={mixed}
|
|
@@ -50,7 +53,7 @@ export function ParametricFieldControl({
|
|
|
50
53
|
return (
|
|
51
54
|
<ToggleControl
|
|
52
55
|
checked={checked}
|
|
53
|
-
label={
|
|
56
|
+
label={i18n._(inspectorFieldDescriptor(key))}
|
|
54
57
|
mixed={mixed}
|
|
55
58
|
onChange={(next) => {
|
|
56
59
|
const patch = { [key]: next } as Partial<AnyNode>
|
|
@@ -73,14 +76,14 @@ export function ParametricFieldControl({
|
|
|
73
76
|
<SegmentedControl
|
|
74
77
|
mixed={mixed}
|
|
75
78
|
onChange={apply}
|
|
76
|
-
options={field.options.map((opt) => ({ label:
|
|
79
|
+
options={field.options.map((opt) => ({ label: i18n._(inspectorOptionDescriptor(opt)), value: opt }))}
|
|
77
80
|
value={str}
|
|
78
81
|
/>
|
|
79
82
|
)
|
|
80
83
|
}
|
|
81
84
|
return (
|
|
82
85
|
<div className="flex items-center justify-between px-3 py-2">
|
|
83
|
-
<span className="text-foreground/80 text-xs">{
|
|
86
|
+
<span className="text-foreground/80 text-xs">{i18n._(inspectorFieldDescriptor(key))}</span>
|
|
84
87
|
<select
|
|
85
88
|
className="rounded-md border border-border/50 bg-black/4 dark:bg-[#2C2C2E] px-2 py-1 text-foreground text-xs focus:outline-none focus:ring-1 focus:ring-foreground/30"
|
|
86
89
|
onChange={(e) => apply(e.target.value)}
|
|
@@ -88,12 +91,12 @@ export function ParametricFieldControl({
|
|
|
88
91
|
>
|
|
89
92
|
{mixed && (
|
|
90
93
|
<option disabled value="">
|
|
91
|
-
Mixed
|
|
94
|
+
{i18n._({ id: 'editor.panels.parametricFieldControl.mixed', message: 'Mixed' })}
|
|
92
95
|
</option>
|
|
93
96
|
)}
|
|
94
97
|
{field.options.map((opt) => (
|
|
95
98
|
<option key={opt} value={opt}>
|
|
96
|
-
{
|
|
99
|
+
{i18n._(inspectorOptionDescriptor(opt))}
|
|
97
100
|
</option>
|
|
98
101
|
))}
|
|
99
102
|
</select>
|
|
@@ -110,7 +113,7 @@ export function ParametricFieldControl({
|
|
|
110
113
|
}
|
|
111
114
|
return (
|
|
112
115
|
<div className="flex items-center justify-between px-3 py-2">
|
|
113
|
-
<span className="text-foreground/80 text-xs">{
|
|
116
|
+
<span className="text-foreground/80 text-xs">{i18n._(inspectorFieldDescriptor(key))}</span>
|
|
114
117
|
<div className="flex items-center gap-2">
|
|
115
118
|
<input
|
|
116
119
|
className="h-6 w-8 cursor-pointer rounded border border-border/50 bg-transparent"
|
|
@@ -122,7 +125,7 @@ export function ParametricFieldControl({
|
|
|
122
125
|
className="w-20 rounded-md border border-border/50 bg-black/4 dark:bg-[#2C2C2E] px-2 py-1 text-foreground text-xs focus:outline-none focus:ring-1 focus:ring-foreground/30"
|
|
123
126
|
onChange={(e) => apply(e.target.value)}
|
|
124
127
|
type="text"
|
|
125
|
-
value={mixed ? 'Mixed' : str}
|
|
128
|
+
value={mixed ? i18n._({ id: 'editor.panels.mixed', message: 'Mixed' }) : str}
|
|
126
129
|
/>
|
|
127
130
|
</div>
|
|
128
131
|
</div>
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
type ParamAction,
|
|
9
9
|
type ParamField,
|
|
10
10
|
useScene,
|
|
11
|
-
type ZoneNode,
|
|
11
|
+
type ZoneNode, usePascalI18n,
|
|
12
12
|
} from '@pascal-app/core'
|
|
13
13
|
import { useViewer } from '@pascal-app/viewer'
|
|
14
14
|
import { Icon } from '@iconify/react'
|
|
@@ -24,6 +24,7 @@ import { ActionButton, ActionGroup } from '../controls/action-button'
|
|
|
24
24
|
import { PanelSection } from '../controls/panel-section'
|
|
25
25
|
import { ParametricFieldControl } from './parametric-field-control'
|
|
26
26
|
import { InspectorFooterContext, PanelWrapper } from './panel-wrapper'
|
|
27
|
+
import { inspectorActionDescriptor, inspectorGroupDescriptor, nodeLabel } from '../../../i18n/node-messages'
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* Auto-derived right-panel inspector for any registry-backed node.
|
|
@@ -46,6 +47,7 @@ export function ParametricInspector({
|
|
|
46
47
|
nodeId,
|
|
47
48
|
onClose,
|
|
48
49
|
}: { footer?: React.ReactNode; nodeId?: AnyNodeId; onClose?: () => void } = {}) {
|
|
50
|
+
const i18n = usePascalI18n()
|
|
49
51
|
const selectedIdFromSelection = useViewer((s) => s.selection.selectedIds[0]) as
|
|
50
52
|
| AnyNodeId
|
|
51
53
|
| undefined
|
|
@@ -127,7 +129,7 @@ export function ParametricInspector({
|
|
|
127
129
|
}
|
|
128
130
|
|
|
129
131
|
const presentation = def.presentation
|
|
130
|
-
const title = presentation?.label
|
|
132
|
+
const title = presentation?.label && nodeType ? nodeLabel(nodeType, presentation.label, i18n) : (nodeType ?? '')
|
|
131
133
|
const iconNode = renderIcon(presentation?.icon)
|
|
132
134
|
const canMove = !!def.capabilities.movable
|
|
133
135
|
const canDelete = def.capabilities.deletable !== false
|
|
@@ -149,7 +151,7 @@ export function ParametricInspector({
|
|
|
149
151
|
<PanelSection
|
|
150
152
|
defaultExpanded={group.defaultExpanded}
|
|
151
153
|
key={`${nodeType}-${group.label}-${gi}`}
|
|
152
|
-
title={group.label}
|
|
154
|
+
title={i18n._(inspectorGroupDescriptor(group.label))}
|
|
153
155
|
>
|
|
154
156
|
{group.fields.map((field, fi) => (
|
|
155
157
|
<FieldRenderer
|
|
@@ -167,10 +169,10 @@ export function ParametricInspector({
|
|
|
167
169
|
</Suspense>
|
|
168
170
|
)}
|
|
169
171
|
{(canMove || canDelete || (parametrics.actions && parametrics.actions.length > 0)) && (
|
|
170
|
-
<PanelSection title=
|
|
172
|
+
<PanelSection title={i18n._({ id: 'editor.panels.parametricInspector.actions', message: 'Actions' })}>
|
|
171
173
|
<ActionGroup className={isZone ? 'flex-col' : parametrics.actions?.length ? 'grid grid-cols-2 gap-2' : undefined}>
|
|
172
174
|
{canMove && (
|
|
173
|
-
<ActionButton icon={<Move className="h-4 w-4" />} label=
|
|
175
|
+
<ActionButton icon={<Move className="h-4 w-4" />} label={i18n._({ id: 'editor.panels.parametricInspector.move', message: 'Move' })} onClick={handleMove} />
|
|
174
176
|
)}
|
|
175
177
|
{parametrics.actions?.map((action, i) => (
|
|
176
178
|
<ParamActionButton action={action} key={`paramaction-${i}`} nodeId={selectedId} />
|
|
@@ -181,13 +183,13 @@ export function ParametricInspector({
|
|
|
181
183
|
<ActionButton
|
|
182
184
|
className="w-full flex-none"
|
|
183
185
|
icon={<Trash2 className="h-4 w-4 text-red-600 dark:text-red-400" />}
|
|
184
|
-
label=
|
|
186
|
+
label={i18n._({ id: 'editor.panels.parametricInspector.delete', message: 'Delete' })}
|
|
185
187
|
onClick={() => handleDelete(false)}
|
|
186
188
|
/>
|
|
187
189
|
<ActionButton
|
|
188
190
|
className="w-full flex-none"
|
|
189
191
|
icon={<Trash2 className="h-4 w-4 text-red-600 dark:text-red-400" />}
|
|
190
|
-
label=
|
|
192
|
+
label={i18n._({ id: 'editor.panels.parametricInspector.deleteWithContents', message: 'Delete with contents' })}
|
|
191
193
|
onClick={() => handleDelete(true)}
|
|
192
194
|
/>
|
|
193
195
|
</>
|
|
@@ -195,7 +197,7 @@ export function ParametricInspector({
|
|
|
195
197
|
<ActionButton
|
|
196
198
|
className="border-red-500/40 text-red-700 dark:text-red-200 hover:bg-red-500/15"
|
|
197
199
|
icon={<Trash2 className="h-4 w-4" />}
|
|
198
|
-
label=
|
|
200
|
+
label={i18n._({ id: 'editor.panels.parametricInspector.delete', message: 'Delete' })}
|
|
199
201
|
onClick={() => handleDelete()}
|
|
200
202
|
/>
|
|
201
203
|
))}
|
|
@@ -212,6 +214,7 @@ export function ParametricInspector({
|
|
|
212
214
|
// from re-rendering it. The click handler re-reads the live node so the
|
|
213
215
|
// handler always acts on current state.
|
|
214
216
|
function ParamActionButton({ action, nodeId }: { action: ParamAction<AnyNode>; nodeId: AnyNodeId }) {
|
|
217
|
+
const i18n = usePascalI18n()
|
|
215
218
|
const disabled = useScene((s) => {
|
|
216
219
|
if (!action.enabledIf) return false
|
|
217
220
|
const n = s.nodes[nodeId]
|
|
@@ -225,7 +228,7 @@ function ParamActionButton({ action, nodeId }: { action: ParamAction<AnyNode>; n
|
|
|
225
228
|
<img alt="" className="h-4 w-4 shrink-0 object-contain" src={action.iconSrc} />
|
|
226
229
|
) : undefined
|
|
227
230
|
}
|
|
228
|
-
label={action.label}
|
|
231
|
+
label={i18n._(inspectorActionDescriptor(action.label))}
|
|
229
232
|
onClick={() => {
|
|
230
233
|
const live = useScene.getState().nodes[nodeId]
|
|
231
234
|
if (live) action.onClick(live as AnyNode)
|
|
@@ -311,10 +314,13 @@ function FieldRenderer({ field, nodeId, onUpdate }: FieldRendererProps) {
|
|
|
311
314
|
// visibleIf may consult other fields on the node — subscribe to its boolean
|
|
312
315
|
// result so we re-evaluate when relevant.
|
|
313
316
|
const visible = useScene((s) => {
|
|
314
|
-
const visibleIf =
|
|
315
|
-
|
|
317
|
+
const { visibleIf, appliesTo } = field as {
|
|
318
|
+
visibleIf?: (n: AnyNode) => boolean
|
|
319
|
+
appliesTo?: (n: AnyNode) => boolean
|
|
320
|
+
}
|
|
321
|
+
if (!(visibleIf || appliesTo)) return true
|
|
316
322
|
const n = s.nodes[nodeId]
|
|
317
|
-
return n ? visibleIf(n as AnyNode) : false
|
|
323
|
+
return n ? (visibleIf?.(n as AnyNode) ?? true) && (appliesTo?.(n as AnyNode) ?? true) : false
|
|
318
324
|
})
|
|
319
325
|
if (!visible) return null
|
|
320
326
|
|