@pascal-app/editor 1.0.3 → 1.0.4
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 +11 -9
- package/src/components/editor/action-menu-button.tsx +83 -0
- package/src/components/editor/bake-thumbnail.tsx +315 -4
- package/src/components/editor/capture-level-picker.tsx +72 -0
- package/src/components/editor/custom-camera-controls.tsx +202 -57
- package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
- package/src/components/editor/duplicate-node.ts +244 -0
- package/src/components/editor/editor-layout-mobile.tsx +36 -25
- package/src/components/editor/editor-layout-v2.tsx +131 -58
- package/src/components/editor/export-manager.tsx +34 -8
- package/src/components/editor/first-person/build-collider-world.test.ts +51 -0
- package/src/components/editor/first-person/build-collider-world.ts +6 -2
- package/src/components/editor/first-person/mechanism-targets.test.ts +75 -0
- package/src/components/editor/first-person/mechanism-targets.ts +36 -0
- package/src/components/editor/first-person-controls.tsx +205 -2
- package/src/components/editor/floating-action-menu.tsx +63 -207
- package/src/components/editor/floor-aware-camera-controls.test.ts +442 -0
- package/src/components/editor/floor-aware-camera-controls.ts +324 -0
- package/src/components/editor/floor-openings-overlay.tsx +243 -0
- package/src/components/editor/floor-region-controls.tsx +846 -0
- package/src/components/editor/floor-region-webgpu-materials.test.ts +12 -0
- package/src/components/editor/floor-surface.test.ts +165 -0
- package/src/components/editor/floor-surface.ts +182 -0
- package/src/components/editor/floorplan-background-selection.test.ts +11 -2
- package/src/components/editor/floorplan-background-selection.ts +1 -3
- package/src/components/editor/floorplan-panel.tsx +344 -342
- package/src/components/editor/footprint-height-handle.tsx +204 -0
- package/src/components/editor/group-actions.ts +54 -2
- package/src/components/editor/handles/handle-hit-priority.test.tsx +63 -0
- package/src/components/editor/handles/handle-hit-priority.tsx +37 -0
- package/src/components/editor/handles/swallow-next-click.test.ts +28 -0
- package/src/components/editor/handles/use-handle-drag.ts +269 -197
- package/src/components/editor/index.tsx +294 -169
- package/src/components/editor/join-walls-pill.tsx +44 -0
- package/src/components/editor/node-action-menu.tsx +68 -70
- package/src/components/editor/node-arrow-handles.tsx +25 -6
- package/src/components/editor/open-wall-ends-3d-layer.tsx +273 -0
- package/src/components/editor/open-wall-ends-hint.tsx +89 -0
- package/src/components/editor/quick-measurement-hud.tsx +3 -2
- package/src/components/editor/registry-action-contributions.test.tsx +63 -0
- package/src/components/editor/registry-action-contributions.tsx +80 -1
- package/src/components/editor/room-controls.tsx +847 -0
- package/src/components/editor/room-floor-highlight.tsx +128 -0
- package/src/components/editor/room-handles-2d.tsx +354 -0
- package/src/components/editor/room-handles.tsx +675 -0
- package/src/components/editor/room-highlight.tsx +225 -0
- package/src/components/editor/room-labels.test.ts +357 -0
- package/src/components/editor/room-labels.tsx +990 -0
- package/src/components/editor/room-transform-ghost.tsx +256 -0
- package/src/components/editor/room-webgpu-materials.test.ts +27 -0
- package/src/components/editor/selection-manager.tsx +451 -317
- package/src/components/editor/slab-hole-highlights.tsx +3 -0
- package/src/components/editor/snapshot-capture-overlay.tsx +4 -0
- package/src/components/editor/snapshot-capture.test.ts +52 -4
- package/src/components/editor/snapshot-capture.ts +30 -5
- package/src/components/editor/thumbnail-generator.test.ts +308 -0
- package/src/components/editor/thumbnail-generator.tsx +1035 -229
- package/src/components/editor/use-floorplan-background-placement.ts +31 -11
- package/src/components/editor/vector-edge-extractor.tsx +121 -0
- package/src/components/editor/views/host-view.tsx +61 -0
- package/src/components/editor/views/plugins-view.tsx +670 -0
- package/src/components/editor/views/scene-region.tsx +59 -0
- package/src/components/editor/views/use-editor-views.ts +57 -0
- package/src/components/editor/views/view-bar.tsx +220 -0
- package/src/components/editor/wall-justification.test.ts +140 -0
- package/src/components/editor/wall-move-side-handles.tsx +43 -142
- package/src/components/editor/wall-push-arrow.tsx +92 -0
- package/src/components/editor/wall-region-handles.tsx +509 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +3 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.test.tsx +86 -0
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +290 -0
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +113 -102
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +156 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +278 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +77 -0
- package/src/components/editor-2d/renderers/{floorplan-draft-wall-measurement.tsx → floorplan-draft-measurement.tsx} +8 -8
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +38 -8
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +291 -227
- package/src/components/editor-2d/svg-paths.ts +12 -15
- package/src/components/procedural-items/inspector.tsx +40 -15
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +97 -14
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +18 -18
- package/src/components/systems/zone/zone-system.test.ts +47 -0
- package/src/components/systems/zone/zone-system.tsx +29 -68
- package/src/components/tools/item/placement-history.test.tsx +474 -0
- package/src/components/tools/item/placement-strategies.ts +8 -4
- package/src/components/tools/item/use-draft-node.test.tsx +432 -1
- package/src/components/tools/item/use-draft-node.ts +166 -95
- package/src/components/tools/item/use-placement-coordinator.tsx +93 -46
- package/src/components/tools/paint-region/use-paint-tint.ts +37 -0
- package/src/components/tools/paint-region/wall-paint-region-tool.tsx +466 -0
- package/src/components/tools/registry/move-registry-node-tool.tsx +3 -1
- package/src/components/tools/select/box-select-tool.tsx +2 -12
- package/src/components/tools/select/plane-box-select-tool.test.tsx +79 -0
- package/src/components/tools/select/plane-box-select-tool.tsx +35 -22
- package/src/components/tools/select/select-candidates.test.ts +37 -4
- package/src/components/tools/select/select-candidates.ts +15 -11
- package/src/components/tools/shared/cursor-sphere.tsx +36 -12
- package/src/components/tools/site/site-boundary-editor.tsx +1 -2
- package/src/components/tools/stair/stair-defaults.ts +0 -3
- package/src/components/tools/stair/stair-tool.tsx +90 -42
- package/src/components/tools/tool-manager.tsx +46 -20
- package/src/components/tools/wall/wall-drafting.test.ts +19 -4
- package/src/components/tools/zone/zone-tool.tsx +4 -12
- package/src/components/ui/action-menu/action-button.tsx +3 -0
- package/src/components/ui/action-menu/camera-actions.tsx +2 -1
- package/src/components/ui/action-menu/control-modes.tsx +2 -35
- package/src/components/ui/action-menu/measurement-control.tsx +3 -10
- package/src/components/ui/build-panel/build-panel.tsx +235 -0
- package/src/components/ui/command-palette/editor-commands.tsx +4 -30
- package/src/components/ui/command-palette/index.tsx +5 -24
- package/src/components/ui/controls/finish-swatch.tsx +110 -0
- package/src/components/ui/controls/material-paint-panel.tsx +97 -26
- package/src/components/ui/controls/material-picker.tsx +4 -1
- package/src/components/ui/controls/scene-material-list.tsx +4 -32
- package/src/components/ui/controls/select-control.tsx +65 -0
- package/src/components/ui/floating-level-selector.tsx +9 -14
- package/src/components/ui/helpers/building-helper.tsx +4 -1
- package/src/components/ui/helpers/contextual-helper-panel.tsx +154 -70
- package/src/components/ui/helpers/helper-manager.test.tsx +69 -0
- package/src/components/ui/helpers/helper-manager.tsx +325 -14
- package/src/components/ui/helpers/item-helper.tsx +8 -0
- package/src/components/ui/helpers/registered-tool-helper.tsx +8 -1
- package/src/components/ui/icon-ref.tsx +45 -1
- package/src/components/ui/level-duplicate-dialog.tsx +1 -1
- package/src/components/ui/node-icon-image.tsx +16 -0
- package/src/components/ui/panels/collections/collections-popover.tsx +20 -14
- package/src/components/ui/panels/floor-foundation-panel.tsx +551 -0
- package/src/components/ui/panels/floor-opening-panel.tsx +143 -0
- package/src/components/ui/panels/mobile-panel-sheet.tsx +2 -2
- package/src/components/ui/panels/mobile-selection-bar.tsx +2 -2
- package/src/components/ui/panels/multi-field-value.test.ts +26 -3
- package/src/components/ui/panels/multi-field-value.ts +6 -3
- package/src/components/ui/panels/multi-parametric-inspector.tsx +52 -2
- package/src/components/ui/panels/multi-selection-panel.tsx +43 -4
- package/src/components/ui/panels/node-display.ts +8 -3
- package/src/components/ui/panels/panel-manager.tsx +103 -68
- package/src/components/ui/panels/panel-wrapper.tsx +78 -36
- package/src/components/ui/panels/parametric-field-control.tsx +1 -1
- package/src/components/ui/panels/parametric-inspector.tsx +4 -4
- package/src/components/ui/panels/reference-panel.tsx +2 -3
- package/src/components/ui/panels/room-construction-rows.tsx +774 -0
- package/src/components/ui/panels/room-name-options.test.ts +55 -0
- package/src/components/ui/panels/room-name-options.ts +47 -0
- package/src/components/ui/panels/room-openings-field.tsx +110 -0
- package/src/components/ui/panels/room-panel.tsx +725 -0
- package/src/components/ui/panels/shape-choice.tsx +58 -0
- package/src/components/ui/panels/wall-region-list.tsx +109 -0
- package/src/components/ui/primitives/color-dot.tsx +36 -24
- package/src/components/ui/primitives/sidebar.tsx +2 -0
- package/src/components/ui/right-stack.test.ts +29 -0
- package/src/components/ui/right-stack.tsx +145 -0
- package/src/components/ui/sidebar/icon-rail.tsx +5 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +31 -4
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +5 -5
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -4
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +197 -0
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +24 -11
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +176 -211
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +30 -8
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +4 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +1 -4
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +21 -13
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +0 -27
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -3
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +9 -4
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +0 -2
- package/src/components/ui/sidebar/tab-bar.tsx +10 -0
- package/src/components/ui/sidebar/use-plugin-panels.tsx +15 -5
- package/src/components/viewer/floorplan-compass-button.tsx +9 -1
- package/src/components/viewer/floorplan-preview.tsx +5 -6
- package/src/components/viewer/viewer-hover-label.tsx +34 -0
- package/src/components/viewer/viewer-rooms.tsx +175 -0
- package/src/components/viewer/viewer-scene-header.tsx +181 -145
- package/src/components/viewer/viewer-selection-manager.tsx +124 -0
- package/src/components/viewer/viewer-stage.tsx +2 -2
- package/src/components/viewer-zone-system.tsx +3 -8
- package/src/hooks/use-auto-save.integration.test.tsx +123 -9
- package/src/hooks/use-auto-save.ts +34 -8
- package/src/hooks/use-keyboard.test.ts +178 -3
- package/src/hooks/use-keyboard.ts +89 -71
- package/src/hooks/use-open-wall-ends.test.ts +99 -0
- package/src/hooks/use-open-wall-ends.ts +238 -0
- package/src/hooks/use-placement-notice.test.ts +48 -0
- package/src/hooks/use-placement-notice.ts +42 -0
- package/src/hooks/use-selected-room.ts +143 -0
- package/src/index.tsx +246 -39
- package/src/lib/ceiling-edit-session.test.ts +321 -0
- package/src/lib/ceiling-edit-session.ts +206 -0
- package/src/lib/ceiling-region-session.test.ts +179 -0
- package/src/lib/continuation.test.ts +19 -1
- package/src/lib/continuation.ts +25 -4
- package/src/lib/door-interaction.ts +14 -0
- package/src/lib/editor-level-display.test.ts +96 -0
- package/src/lib/editor-level-display.ts +48 -0
- package/src/lib/editor-views.ts +144 -0
- package/src/lib/export-geometry-wait.test.ts +90 -0
- package/src/lib/floor-edit-session.ts +153 -0
- package/src/lib/floor-footprints.test.ts +563 -0
- package/src/lib/floor-footprints.ts +395 -0
- package/src/lib/floor-foundation-support.test.ts +283 -0
- package/src/lib/floor-opening-draft.ts +218 -0
- package/src/lib/floor-openings.test.ts +216 -0
- package/src/lib/floor-region-geometry.ts +191 -0
- package/src/lib/floor-region-session.test.ts +485 -0
- package/src/lib/floor-region-session.ts +589 -0
- package/src/lib/floor-region-snap.ts +101 -0
- package/src/lib/floor-step-hover.test.ts +32 -0
- package/src/lib/floorplan/annotation-visibility.ts +17 -0
- package/src/lib/floorplan/dimension-drive.test.ts +360 -0
- package/src/lib/floorplan/dimension-drive.ts +479 -0
- package/src/lib/floorplan/floorplan-export.test.ts +12 -8
- package/src/lib/floorplan/floorplan-export.tsx +32 -8
- package/src/lib/floorplan/floorplan-extension.ts +32 -0
- package/src/lib/floorplan/floorplan-mode.test.ts +4 -0
- package/src/lib/floorplan/floorplan-mode.ts +2 -0
- package/src/lib/floorplan/floorplan-pdfkit-document.ts +339 -18
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +138 -8
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +56 -25
- package/src/lib/floorplan/floorplan-plugin-install.test.tsx +138 -0
- package/src/lib/floorplan/floorplan-readonly.ts +6 -0
- package/src/lib/floorplan/geometry.ts +13 -6
- package/src/lib/floorplan/index.ts +0 -9
- package/src/lib/floorplan/items.ts +5 -2
- package/src/lib/floorplan/marks.test.ts +213 -0
- package/src/lib/floorplan/marks.ts +238 -0
- package/src/lib/floorplan/open-wall-ends.test.ts +268 -0
- package/src/lib/floorplan/open-wall-ends.ts +162 -0
- package/src/lib/floorplan/pdf-fonts/GeistMono-Bold.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/GeistMono-Regular.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/LICENSE-GeistMono.txt +92 -0
- package/src/lib/floorplan/pdf-fonts/LICENSE-LiberationSans.txt +102 -0
- package/src/lib/floorplan/pdf-fonts/LiberationSans-Bold.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/LiberationSans-Regular.ttf +0 -0
- package/src/lib/floorplan/schedules.test.ts +189 -0
- package/src/lib/floorplan/schedules.ts +339 -0
- package/src/lib/floorplan/sheet-export.test.ts +100 -0
- package/src/lib/floorplan/sheet-export.ts +318 -0
- package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +779 -0
- package/src/lib/floorplan/site-plan/contours.test.ts +43 -0
- package/src/lib/floorplan/site-plan/contributors.ts +97 -0
- package/src/lib/floorplan/site-plan/coverage.ts +232 -0
- package/src/lib/floorplan/site-plan/drawing-type-switch.test.ts +15 -0
- package/src/lib/floorplan/site-plan/drawing-type-switch.tsx +86 -0
- package/src/lib/floorplan/site-plan/front-edge.test.ts +103 -0
- package/src/lib/floorplan/site-plan/front-edge.ts +156 -0
- package/src/lib/floorplan/site-plan/geometry.test.ts +275 -0
- package/src/lib/floorplan/site-plan/geometry.ts +557 -0
- package/src/lib/floorplan/site-plan/index.ts +71 -0
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +366 -0
- package/src/lib/floorplan/site-plan/site-annotations.ts +1083 -0
- package/src/lib/floorplan/site-plan/site-parts.ts +379 -0
- package/src/lib/floorplan/site-plan/site-plan-layer.tsx +138 -0
- package/src/lib/floorplan/site-plan/street-name.test.ts +69 -0
- package/src/lib/floorplan/site-plan/street-name.ts +302 -0
- package/src/lib/floorplan/site-plan/yards.test.ts +69 -0
- package/src/lib/floorplan/types.ts +1 -27
- package/src/lib/fresh-planar-placement.test.ts +36 -0
- package/src/lib/fresh-planar-placement.ts +59 -36
- package/src/lib/geometry-script/author.ts +152 -0
- package/src/lib/geometry-script/client.ts +152 -0
- package/src/lib/geometry-script/protocol.ts +12 -0
- package/src/lib/gesture-lifecycle.test.ts +595 -0
- package/src/lib/gesture-lifecycle.ts +290 -0
- package/src/lib/glb-export-zone-holes.test.ts +41 -0
- package/src/lib/glb-export.test.ts +485 -0
- package/src/lib/glb-export.ts +237 -119
- package/src/lib/history.test.ts +6 -0
- package/src/lib/history.ts +7 -0
- package/src/lib/hud-title.ts +109 -0
- package/src/lib/hydration-gated-storage.ts +49 -0
- package/src/lib/ifc-export.test.ts +174 -0
- package/src/lib/ifc-export.ts +201 -0
- package/src/lib/ifc-parts.ts +15 -0
- package/src/lib/inspector-expanded.test.ts +44 -0
- package/src/lib/inspector-expanded.ts +36 -0
- package/src/lib/interaction/scope.ts +18 -0
- package/src/lib/keyboard-pan.test.ts +129 -7
- package/src/lib/keyboard-pan.ts +161 -12
- package/src/lib/live-gesture-round-trip.test.ts +360 -0
- package/src/lib/lot/__fixtures__/map-dossier-miami-shores.json +1 -0
- package/src/lib/lot/__fixtures__/map-dossier-stpete.json +1 -0
- package/src/lib/lot/clean-ring.test.ts +186 -0
- package/src/lib/lot/clean-ring.ts +217 -0
- package/src/lib/lot/dossier.test.ts +202 -0
- package/src/lib/lot/dossier.ts +498 -0
- package/src/lib/lot/drop-in.test.ts +326 -0
- package/src/lib/lot/drop-in.ts +407 -0
- package/src/lib/lot/index.ts +43 -0
- package/src/lib/lot/lot-patch.test.ts +340 -0
- package/src/lib/lot/lot-patch.ts +394 -0
- package/src/lib/lot/parcel-provider.ts +43 -0
- package/src/lib/lot/terrain.test.ts +161 -0
- package/src/lib/lot/terrain.ts +266 -0
- package/src/lib/material-catalog-model.ts +3 -1
- package/src/lib/material-paint-panel-model.ts +9 -5
- package/src/lib/material-paint.ts +4 -0
- package/src/lib/mezzanine-actions.test.ts +342 -0
- package/src/lib/mezzanine-draft.test.ts +501 -0
- package/src/lib/mezzanine-draft.ts +278 -0
- package/src/lib/mezzanine-messages.ts +26 -0
- package/src/lib/mezzanine-selection.test.ts +207 -0
- package/src/lib/mezzanine-stairs.ts +46 -0
- package/src/lib/model-export.ts +3 -1
- package/src/lib/node-action-movement.ts +17 -6
- package/src/lib/node-display-label.ts +37 -0
- package/src/lib/orbit-floor-clearance.test.ts +165 -0
- package/src/lib/orbit-floor-clearance.ts +106 -0
- package/src/lib/paint-commit-route.test.ts +37 -0
- package/src/lib/paint-commit-route.ts +17 -0
- package/src/lib/paint-eyedropper.test.tsx +165 -0
- package/src/lib/paint-eyedropper.ts +99 -0
- package/src/lib/paint-pass-through.test.ts +111 -0
- package/src/lib/paint-pass-through.ts +76 -0
- package/src/lib/paint-region-hover.ts +25 -0
- package/src/lib/paint-region-mode.ts +154 -0
- package/src/lib/paint-regions.test.ts +166 -0
- package/src/lib/paint-regions.ts +192 -0
- package/src/lib/paint-scope.test.ts +317 -35
- package/src/lib/paint-scope.ts +266 -25
- package/src/lib/panel-rows.ts +21 -0
- package/src/lib/parametric-node-update.ts +1 -1
- package/src/lib/plate-paint-affected.test.ts +300 -0
- package/src/lib/plate-paint-affected.ts +177 -0
- package/src/lib/plugin-chat.ts +50 -0
- package/src/lib/plugin-panels.test.ts +25 -0
- package/src/lib/plugin-panels.ts +94 -0
- package/src/lib/print-shell-compiler.ts +1 -0
- package/src/lib/registered-draft-snap.ts +12 -0
- package/src/lib/roof-duplication.ts +10 -2
- package/src/lib/room-assembly-overlay.test.ts +275 -0
- package/src/lib/room-assembly-overlay.ts +266 -0
- package/src/lib/room-built-on.test.ts +379 -0
- package/src/lib/room-built-on.ts +138 -0
- package/src/lib/room-construction-commands.ts +712 -0
- package/src/lib/room-construction.test.ts +746 -0
- package/src/lib/room-construction.ts +385 -0
- package/src/lib/room-controls-phase6.test.ts +630 -0
- package/src/lib/room-divide-session.ts +523 -0
- package/src/lib/room-handle-drag.ts +1068 -0
- package/src/lib/room-name-catalog.test.ts +90 -0
- package/src/lib/room-name-catalog.ts +168 -0
- package/src/lib/room-push-dimensions.test.ts +311 -0
- package/src/lib/room-push-dimensions.ts +251 -0
- package/src/lib/room-selection-commands.ts +62 -0
- package/src/lib/room-selection.test.ts +973 -0
- package/src/lib/room-selection.ts +417 -0
- package/src/lib/room-structure-commands.ts +94 -0
- package/src/lib/room-structure.test.ts +921 -0
- package/src/lib/room-transform-session.ts +570 -0
- package/src/lib/room-zone-routing.test.ts +294 -0
- package/src/lib/room-zone-routing.ts +197 -0
- package/src/lib/scene-clipboard.test.ts +364 -0
- package/src/lib/scene-clipboard.ts +229 -48
- package/src/lib/scene-material-refs.test.ts +79 -0
- package/src/lib/scene-material-refs.ts +79 -0
- package/src/lib/scene.test.ts +24 -36
- package/src/lib/scene.ts +33 -57
- package/src/lib/selection-routing.test.ts +129 -45
- package/src/lib/selection-routing.ts +53 -74
- package/src/lib/session-writes.test.ts +120 -0
- package/src/lib/session-writes.ts +205 -0
- package/src/lib/sidebar-panel.ts +62 -0
- package/src/lib/site-boundary.test.ts +4 -6
- package/src/lib/site-boundary.ts +1 -1
- package/src/lib/snapping-mode.test.ts +4 -0
- package/src/lib/snapping-mode.ts +8 -1
- package/src/lib/terrace-draft.test.ts +145 -0
- package/src/lib/terrace-draft.ts +213 -0
- package/src/lib/tool-cursor-icon.test.ts +16 -0
- package/src/lib/tool-cursor-icon.ts +11 -0
- package/src/lib/typing-target.ts +9 -0
- package/src/lib/units.ts +77 -39
- package/src/lib/vector-edges.test.ts +58 -0
- package/src/lib/vector-edges.ts +400 -0
- package/src/lib/viewer-selection.test.ts +577 -0
- package/src/lib/viewer-selection.ts +569 -0
- package/src/lib/wall-draw-variant.test.ts +71 -0
- package/src/lib/wall-draw-variant.ts +91 -0
- package/src/lib/wall-paint-region-session.ts +246 -0
- package/src/lib/wall-paint-region.test.ts +373 -0
- package/src/lib/wall-polygon-draft.test.ts +262 -0
- package/src/lib/wall-polygon-draft.ts +286 -0
- package/src/lib/wall-push.test.ts +453 -0
- package/src/lib/wall-region-face.ts +352 -0
- package/src/lib/wall-region-handles.test.ts +304 -0
- package/src/lib/wall-region-handles.ts +368 -0
- package/src/lib/wall-region-list.test.ts +119 -0
- package/src/lib/wall-region-list.ts +136 -0
- package/src/lib/wall-region-snap.ts +110 -0
- package/src/lib/window-interaction.ts +14 -0
- package/src/lib/zone-content.ts +4 -1
- package/src/lib/zone-name.test.ts +17 -0
- package/src/lib/zone-name.ts +17 -0
- package/src/store/opens-in-select-mode.test.ts +223 -0
- package/src/store/paint-mode.test.ts +188 -0
- package/src/store/terrain-sculpt-mode.test.ts +19 -21
- package/src/store/tool-mode.test.ts +137 -6
- package/src/store/use-delete-confirmation.ts +28 -1
- package/src/store/use-drawing-view.test.ts +24 -3
- package/src/store/use-drawing-view.ts +29 -1
- package/src/store/use-editor.tsx +432 -321
- package/src/store/use-floorplan-draft-preview.ts +13 -0
- package/src/store/use-floorplan-marquee.ts +12 -2
- package/src/store/use-quick-measurement-hud.ts +3 -3
- package/src/store/use-stair-build-preview.ts +13 -5
- package/src/store/use-wall-move-ghosts.ts +4 -2
- package/src/store/use-wall-region-handles-selection.test.ts +222 -0
- package/src/store/use-wall-region-handles-selection.ts +139 -0
- package/src/store/view-layout.test.ts +175 -0
- package/src/store/view-layout.ts +196 -0
- package/src/test-utils/immediate-animation-frames.ts +16 -0
- package/src/test-utils/live-gesture-round-trip.ts +224 -0
- package/src/test-utils/selection-harness.ts +82 -0
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +0 -499
- package/src/components/systems/zone/zone-label-editor-system.tsx +0 -333
- package/src/components/tools/fence/fence-drafting.test.ts +0 -100
- package/src/components/tools/fence/fence-drafting.ts +0 -266
- package/src/components/ui/sidebar/panels/plugins-panel.tsx +0 -218
- package/src/components/viewer/viewer-units-panel.tsx +0 -116
- package/src/lib/floorplan/stairs.ts +0 -489
- package/src/lib/level-duplication.test.ts +0 -144
- package/src/lib/level-duplication.ts +0 -195
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Pentagon, Square } from 'lucide-react'
|
|
4
|
+
import { cn } from '../../../lib/utils'
|
|
5
|
+
|
|
6
|
+
export type OutlineShape = 'rectangle' | 'polygon'
|
|
7
|
+
|
|
8
|
+
const SHAPES = [
|
|
9
|
+
{ id: 'rectangle', label: 'Rectangle', Icon: Square },
|
|
10
|
+
{ id: 'polygon', label: 'Polygon', Icon: Pentagon },
|
|
11
|
+
] as const
|
|
12
|
+
|
|
13
|
+
const pill =
|
|
14
|
+
'inline-flex items-center gap-1 rounded-full border border-border/50 px-2 py-0.5 text-xs text-foreground transition-colors hover:bg-accent disabled:pointer-events-none disabled:opacity-50'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The outline shape, picked before drawing starts — the same two choices the
|
|
18
|
+
* Build panel's room tiles offer, so a cut or a mezzanine is started the way a
|
|
19
|
+
* room is.
|
|
20
|
+
*/
|
|
21
|
+
export function ShapeChoice({
|
|
22
|
+
label,
|
|
23
|
+
onPick,
|
|
24
|
+
disabled,
|
|
25
|
+
className,
|
|
26
|
+
data,
|
|
27
|
+
}: {
|
|
28
|
+
label: string
|
|
29
|
+
onPick: (shape: OutlineShape) => void
|
|
30
|
+
disabled?: boolean
|
|
31
|
+
className?: string
|
|
32
|
+
/** `data-shape-choice` hook for tests and proofs. */
|
|
33
|
+
data?: string
|
|
34
|
+
}) {
|
|
35
|
+
return (
|
|
36
|
+
<div
|
|
37
|
+
className={cn('flex min-h-7 items-center justify-between gap-2', className)}
|
|
38
|
+
data-shape-choice={data}
|
|
39
|
+
>
|
|
40
|
+
<span className="whitespace-nowrap">{label}</span>
|
|
41
|
+
<div className="flex items-center gap-1.5">
|
|
42
|
+
{SHAPES.map(({ id, label: shapeLabel, Icon }) => (
|
|
43
|
+
<button
|
|
44
|
+
className={pill}
|
|
45
|
+
data-shape={id}
|
|
46
|
+
disabled={disabled}
|
|
47
|
+
key={id}
|
|
48
|
+
onClick={() => onPick(id)}
|
|
49
|
+
type="button"
|
|
50
|
+
>
|
|
51
|
+
<Icon aria-hidden className="h-3 w-3" />
|
|
52
|
+
{shapeLabel}
|
|
53
|
+
</button>
|
|
54
|
+
))}
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type AnyNodeId,
|
|
5
|
+
useLiveNodeOverrides,
|
|
6
|
+
useScene,
|
|
7
|
+
type WallFaceRegion,
|
|
8
|
+
} from '@pascal-app/core'
|
|
9
|
+
import { useViewer } from '@pascal-app/viewer'
|
|
10
|
+
import { Trash2 } from 'lucide-react'
|
|
11
|
+
import { useMemo } from 'react'
|
|
12
|
+
import { removeWallRegion } from '../../../lib/paint-regions'
|
|
13
|
+
import { sfxEmitter } from '../../../lib/sfx-bus'
|
|
14
|
+
import { cn } from '../../../lib/utils'
|
|
15
|
+
import {
|
|
16
|
+
wallRegionCapNotice,
|
|
17
|
+
wallRegionFaceLabels,
|
|
18
|
+
wallRegionRows,
|
|
19
|
+
} from '../../../lib/wall-region-list'
|
|
20
|
+
import { useWallRegionSelection } from '../../../store/use-wall-region-handles-selection'
|
|
21
|
+
import { PanelSection } from '../controls/panel-section'
|
|
22
|
+
|
|
23
|
+
const ROW_ACTION_CLASS =
|
|
24
|
+
'flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-black/5 hover:text-foreground dark:hover:bg-white/10'
|
|
25
|
+
|
|
26
|
+
/** The wall panel's "Paint regions" section: one row per region, select or delete. */
|
|
27
|
+
export function WallPaintRegionList({ wallId }: { wallId: string }) {
|
|
28
|
+
const nodes = useScene((state) => state.nodes)
|
|
29
|
+
const materials = useScene((state) => state.materials)
|
|
30
|
+
const unit = useViewer((state) => state.unit)
|
|
31
|
+
const active = useWallRegionSelection((state) => state.active)
|
|
32
|
+
const setActive = useWallRegionSelection((state) => state.setActive)
|
|
33
|
+
// A handle drag previews through a live override; list the live bounds.
|
|
34
|
+
const liveRegions = useLiveNodeOverrides(
|
|
35
|
+
(state) =>
|
|
36
|
+
state.overrides.get(wallId as AnyNodeId)?.faceRegions as WallFaceRegion[] | undefined,
|
|
37
|
+
)
|
|
38
|
+
const sceneWall = nodes[wallId as AnyNodeId]
|
|
39
|
+
const wall = sceneWall?.type === 'wall' ? sceneWall : null
|
|
40
|
+
const faceLabels = useMemo(() => wallRegionFaceLabels(nodes, wallId), [nodes, wallId])
|
|
41
|
+
|
|
42
|
+
if (!wall) return null
|
|
43
|
+
const faceRegions = liveRegions ?? wall.faceRegions
|
|
44
|
+
if (!faceRegions?.length) return null
|
|
45
|
+
const rows = wallRegionRows({ faceRegions }, faceLabels, materials, unit)
|
|
46
|
+
const capNotice = wallRegionCapNotice({ faceRegions }, faceLabels)
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
// Focus inside this list still lets Delete target the active region.
|
|
50
|
+
<div className="contents" data-wall-region-list={wallId}>
|
|
51
|
+
<PanelSection title="Paint regions">
|
|
52
|
+
<div className="flex flex-col gap-1">
|
|
53
|
+
{rows.map((row) => {
|
|
54
|
+
const isActive = active?.wallId === wallId && active.regionId === row.id
|
|
55
|
+
return (
|
|
56
|
+
<div
|
|
57
|
+
className={cn(
|
|
58
|
+
'flex h-9 items-center gap-2 rounded-md px-1 transition-colors',
|
|
59
|
+
isActive ? 'bg-accent' : 'hover:bg-accent/50',
|
|
60
|
+
)}
|
|
61
|
+
data-wall-region-row={row.id}
|
|
62
|
+
key={row.id}
|
|
63
|
+
>
|
|
64
|
+
<button
|
|
65
|
+
aria-pressed={isActive}
|
|
66
|
+
className="flex min-w-0 flex-1 cursor-pointer items-center gap-2 text-left"
|
|
67
|
+
onClick={() => setActive({ wallId, regionId: row.id })}
|
|
68
|
+
type="button"
|
|
69
|
+
>
|
|
70
|
+
<span
|
|
71
|
+
aria-hidden="true"
|
|
72
|
+
className="h-4 w-4 shrink-0 rounded-sm border border-border/50 bg-center bg-cover"
|
|
73
|
+
style={{
|
|
74
|
+
backgroundColor: row.finish.color,
|
|
75
|
+
backgroundImage: row.finish.imageUrl
|
|
76
|
+
? `url(${row.finish.imageUrl})`
|
|
77
|
+
: undefined,
|
|
78
|
+
}}
|
|
79
|
+
/>
|
|
80
|
+
<span className="flex min-w-0 flex-col leading-tight">
|
|
81
|
+
<span className="truncate text-sm">{row.finish.name}</span>
|
|
82
|
+
<span className="truncate text-[11px] text-muted-foreground">
|
|
83
|
+
{row.faceLabel} · {row.boundsText}
|
|
84
|
+
</span>
|
|
85
|
+
</span>
|
|
86
|
+
</button>
|
|
87
|
+
<button
|
|
88
|
+
aria-label="Delete region"
|
|
89
|
+
className={ROW_ACTION_CLASS}
|
|
90
|
+
onClick={() => {
|
|
91
|
+
if (isActive) setActive(null)
|
|
92
|
+
if (removeWallRegion(wallId, row.id)) sfxEmitter.emit('sfx:structure-delete')
|
|
93
|
+
}}
|
|
94
|
+
title="Delete region"
|
|
95
|
+
type="button"
|
|
96
|
+
>
|
|
97
|
+
<Trash2 className="h-3 w-3" />
|
|
98
|
+
</button>
|
|
99
|
+
</div>
|
|
100
|
+
)
|
|
101
|
+
})}
|
|
102
|
+
{capNotice && (
|
|
103
|
+
<div className="px-1 text-[11px] text-muted-foreground">{capNotice}</div>
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
</PanelSection>
|
|
107
|
+
</div>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
@@ -1,43 +1,55 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import { ZONE_COLOR_PALETTE } from '@pascal-app/core'
|
|
3
4
|
import { useState } from 'react'
|
|
4
5
|
import { cn } from '../../../lib/utils'
|
|
5
6
|
import { Popover, PopoverContent, PopoverTrigger } from './popover'
|
|
7
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from './tooltip'
|
|
6
8
|
|
|
7
|
-
export const PALETTE_COLORS =
|
|
8
|
-
'#ef4444', // Red 0°
|
|
9
|
-
'#f97316', // Orange 30°
|
|
10
|
-
'#f59e0b', // Amber 45°
|
|
11
|
-
'#84cc16', // Lime 85°
|
|
12
|
-
'#22c55e', // Green 142°
|
|
13
|
-
'#10b981', // Emerald 160°
|
|
14
|
-
'#06b6d4', // Cyan 190°
|
|
15
|
-
'#3b82f6', // Blue 217°
|
|
16
|
-
'#6366f1', // Indigo 239°
|
|
17
|
-
'#a855f7', // Violet 270°
|
|
18
|
-
'#64748b', // Dark gray
|
|
19
|
-
'#cccccc', // Light gray
|
|
20
|
-
]
|
|
9
|
+
export const PALETTE_COLORS = ZONE_COLOR_PALETTE
|
|
21
10
|
|
|
22
11
|
interface ColorDotProps {
|
|
23
12
|
color: string
|
|
24
13
|
onChange: (color: string) => void
|
|
14
|
+
/** Says what the colour is for, on hover and to assistive tech. */
|
|
15
|
+
label?: string
|
|
16
|
+
side?: 'left' | 'bottom'
|
|
25
17
|
}
|
|
26
18
|
|
|
27
|
-
export function ColorDot({ color, onChange }: ColorDotProps) {
|
|
19
|
+
export function ColorDot({ color, onChange, label, side = 'left' }: ColorDotProps) {
|
|
28
20
|
const [open, setOpen] = useState(false)
|
|
21
|
+
const trigger = (
|
|
22
|
+
<PopoverTrigger asChild>
|
|
23
|
+
<button
|
|
24
|
+
aria-label={label}
|
|
25
|
+
className="relative h-3 w-3 shrink-0 cursor-pointer rounded-sm border border-border/50 transition-all hover:ring-1 hover:ring-ring/50"
|
|
26
|
+
data-color-dot
|
|
27
|
+
onClick={(e) => e.stopPropagation()}
|
|
28
|
+
style={{ backgroundColor: color }}
|
|
29
|
+
type="button"
|
|
30
|
+
/>
|
|
31
|
+
</PopoverTrigger>
|
|
32
|
+
)
|
|
29
33
|
|
|
30
34
|
return (
|
|
31
35
|
<Popover onOpenChange={setOpen} open={open}>
|
|
32
|
-
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
<PopoverContent
|
|
36
|
+
{label ? (
|
|
37
|
+
<Tooltip>
|
|
38
|
+
<TooltipTrigger asChild>{trigger}</TooltipTrigger>
|
|
39
|
+
<TooltipContent side="bottom">{label}</TooltipContent>
|
|
40
|
+
</Tooltip>
|
|
41
|
+
) : (
|
|
42
|
+
trigger
|
|
43
|
+
)}
|
|
44
|
+
<PopoverContent
|
|
45
|
+
align={side === 'bottom' ? 'start' : 'center'}
|
|
46
|
+
className="w-auto p-1.5"
|
|
47
|
+
// Keep the pick inside the palette: in a panel header, a pointerdown
|
|
48
|
+
// bubbling out of the portal would start dragging the panel.
|
|
49
|
+
onPointerDown={(event) => event.stopPropagation()}
|
|
50
|
+
side={side}
|
|
51
|
+
sideOffset={6}
|
|
52
|
+
>
|
|
41
53
|
<div className="grid grid-cols-4 gap-1">
|
|
42
54
|
{PALETTE_COLORS.map((c) => (
|
|
43
55
|
<button
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
TooltipTrigger,
|
|
25
25
|
} from './../../../components/ui/primitives/tooltip'
|
|
26
26
|
import { useIsMobile } from './../../../hooks/use-mobile'
|
|
27
|
+
import { hydrationGatedStorage } from './../../../lib/hydration-gated-storage'
|
|
27
28
|
import { cn } from './../../../lib/utils'
|
|
28
29
|
|
|
29
30
|
const SIDEBAR_COOKIE_NAME = 'sidebar_state'
|
|
@@ -62,6 +63,7 @@ export const useSidebarStore = create<SidebarStore>()(
|
|
|
62
63
|
}),
|
|
63
64
|
{
|
|
64
65
|
name: 'sidebar-preferences',
|
|
66
|
+
storage: hydrationGatedStorage((): boolean => useSidebarStore.persist.hasHydrated()),
|
|
65
67
|
partialize: (state) => ({ width: state.width, isCollapsed: state.isCollapsed }),
|
|
66
68
|
skipHydration: true,
|
|
67
69
|
},
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { expect, test } from 'bun:test'
|
|
2
|
+
import { rightStackFloors } from './right-stack'
|
|
3
|
+
|
|
4
|
+
const tall = { headerHeight: 52, cardHeight: 240 }
|
|
5
|
+
|
|
6
|
+
test('the inspector keeps 180 px, or its own height if shorter, above the full card', () => {
|
|
7
|
+
expect(rightStackFloors({ ...tall, naturalHeight: 500, stackHeight: 700 })).toEqual({
|
|
8
|
+
inspector: 180,
|
|
9
|
+
card: 240,
|
|
10
|
+
})
|
|
11
|
+
expect(rightStackFloors({ ...tall, naturalHeight: 120, stackHeight: 700 }).inspector).toBe(120)
|
|
12
|
+
expect(
|
|
13
|
+
rightStackFloors({ naturalHeight: 0, headerHeight: 0, cardHeight: 240, stackHeight: 700 }),
|
|
14
|
+
).toEqual({ inspector: 0, card: 240 })
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('on a short window the inspector gives way first, down to its header', () => {
|
|
18
|
+
expect(rightStackFloors({ ...tall, naturalHeight: 500, stackHeight: 400 })).toEqual({
|
|
19
|
+
inspector: 152,
|
|
20
|
+
card: 240,
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('on a very short window the inspector keeps its header and the card scrolls', () => {
|
|
25
|
+
expect(rightStackFloors({ ...tall, naturalHeight: 500, stackHeight: 258 })).toEqual({
|
|
26
|
+
inspector: 52,
|
|
27
|
+
card: 198,
|
|
28
|
+
})
|
|
29
|
+
})
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { createContext, type ReactNode, useContext, useEffect, useRef } from 'react'
|
|
4
|
+
import { useIsMobile } from '../../hooks/use-mobile'
|
|
5
|
+
|
|
6
|
+
const RightStackContext = createContext(false)
|
|
7
|
+
|
|
8
|
+
/** Whether a panel renders inside the shared right column (in flow) rather than on its own. */
|
|
9
|
+
export function useInRightStack() {
|
|
10
|
+
return useContext(RightStackContext)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** The inspector never shrinks below this (or its own height, if shorter) for the card, px. */
|
|
14
|
+
const INSPECTOR_MIN_HEIGHT = 180
|
|
15
|
+
|
|
16
|
+
/** Gap between the inspector and the card, px (`gap-2`). */
|
|
17
|
+
const GAP = 8
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The heights neither panel shrinks below. The inspector keeps 180 px (or its
|
|
21
|
+
* own height, if shorter) while the full card fits under it, and never less
|
|
22
|
+
* than its header row. The card shows in full whenever the column allows; on a
|
|
23
|
+
* very short window it gives way and scrolls on itself — both stay usable.
|
|
24
|
+
*/
|
|
25
|
+
export function rightStackFloors(args: {
|
|
26
|
+
naturalHeight: number
|
|
27
|
+
headerHeight: number
|
|
28
|
+
cardHeight: number
|
|
29
|
+
stackHeight: number
|
|
30
|
+
}): { inspector: number; card: number } {
|
|
31
|
+
const { naturalHeight, headerHeight, cardHeight, stackHeight } = args
|
|
32
|
+
const gap = naturalHeight > 0 && cardHeight > 0 ? GAP : 0
|
|
33
|
+
const header = Math.min(headerHeight, naturalHeight)
|
|
34
|
+
const inspector = Math.max(
|
|
35
|
+
header,
|
|
36
|
+
Math.min(INSPECTOR_MIN_HEIGHT, naturalHeight, stackHeight - cardHeight - gap),
|
|
37
|
+
)
|
|
38
|
+
const card = Math.max(0, Math.min(cardHeight, stackHeight - gap - inspector))
|
|
39
|
+
return { inspector, card }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** The inspector's full height: its header plus everything its scroller holds. */
|
|
43
|
+
function inspectorHeights(slot: HTMLElement): { natural: number; header: number } {
|
|
44
|
+
const panel = slot.querySelector<HTMLElement>('[data-panel-wrapper]')
|
|
45
|
+
if (!panel) return { natural: 0, header: 0 }
|
|
46
|
+
const header = panel.querySelector<HTMLElement>('[data-panel-header]')?.offsetHeight ?? 0
|
|
47
|
+
const scroller = panel.querySelector<HTMLElement>('[data-panel-scroll]')
|
|
48
|
+
if (!scroller) return { natural: panel.offsetHeight, header }
|
|
49
|
+
return { natural: panel.offsetHeight - scroller.clientHeight + scroller.scrollHeight, header }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The right-hand column: the shortcuts card shows in full at the bottom, and
|
|
54
|
+
* the node inspector takes the space above it, growing down from the top and
|
|
55
|
+
* scrolling on itself. On a very short window the inspector keeps its header
|
|
56
|
+
* and the card scrolls instead. Neither hides the other. On a phone the two
|
|
57
|
+
* keep their own layouts (sheet / no card).
|
|
58
|
+
*/
|
|
59
|
+
export function RightStack({ inspector, helper }: { inspector: ReactNode; helper: ReactNode }) {
|
|
60
|
+
const isMobile = useIsMobile()
|
|
61
|
+
const stackRef = useRef<HTMLDivElement>(null)
|
|
62
|
+
const inspectorRef = useRef<HTMLDivElement>(null)
|
|
63
|
+
const cardRef = useRef<HTMLDivElement>(null)
|
|
64
|
+
|
|
65
|
+
// `min(180px, its own height)` has no CSS form for a flex item that scrolls,
|
|
66
|
+
// so both floors are measured (`rightStackFloors`).
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (isMobile) return
|
|
69
|
+
const stack = stackRef.current
|
|
70
|
+
const slot = inspectorRef.current
|
|
71
|
+
const card = cardRef.current
|
|
72
|
+
if (!(stack && slot && card)) return
|
|
73
|
+
let frame = 0
|
|
74
|
+
const fit = () => {
|
|
75
|
+
frame = 0
|
|
76
|
+
const { natural, header } = inspectorHeights(slot)
|
|
77
|
+
const floors = rightStackFloors({
|
|
78
|
+
naturalHeight: natural,
|
|
79
|
+
headerHeight: header,
|
|
80
|
+
cardHeight: card.scrollHeight,
|
|
81
|
+
stackHeight: stack.clientHeight,
|
|
82
|
+
})
|
|
83
|
+
const inspectorMin = `${floors.inspector}px`
|
|
84
|
+
const cardMin = `${floors.card}px`
|
|
85
|
+
if (slot.style.minHeight !== inspectorMin) slot.style.minHeight = inspectorMin
|
|
86
|
+
if (card.style.minHeight !== cardMin) card.style.minHeight = cardMin
|
|
87
|
+
// The card is click-through; it takes the pointer only when it has to scroll.
|
|
88
|
+
const pointer = floors.card < card.scrollHeight ? 'auto' : ''
|
|
89
|
+
if (card.style.pointerEvents !== pointer) card.style.pointerEvents = pointer
|
|
90
|
+
}
|
|
91
|
+
const schedule = () => {
|
|
92
|
+
if (!frame) frame = requestAnimationFrame(fit)
|
|
93
|
+
}
|
|
94
|
+
const resize = new ResizeObserver(schedule)
|
|
95
|
+
resize.observe(stack)
|
|
96
|
+
resize.observe(card)
|
|
97
|
+
// Content growing inside the scroller changes no outer size, hence the mutation watch.
|
|
98
|
+
// The same for the card: a new tool swaps its rows without resizing the slot.
|
|
99
|
+
const mutation = new MutationObserver(schedule)
|
|
100
|
+
for (const target of [slot, card])
|
|
101
|
+
mutation.observe(target, {
|
|
102
|
+
subtree: true,
|
|
103
|
+
childList: true,
|
|
104
|
+
attributes: true,
|
|
105
|
+
attributeFilter: ['class', 'hidden', 'aria-expanded'],
|
|
106
|
+
characterData: true,
|
|
107
|
+
})
|
|
108
|
+
fit()
|
|
109
|
+
return () => {
|
|
110
|
+
if (frame) cancelAnimationFrame(frame)
|
|
111
|
+
resize.disconnect()
|
|
112
|
+
mutation.disconnect()
|
|
113
|
+
}
|
|
114
|
+
}, [isMobile])
|
|
115
|
+
|
|
116
|
+
if (isMobile) {
|
|
117
|
+
return (
|
|
118
|
+
<>
|
|
119
|
+
<div className="pointer-events-auto">{inspector}</div>
|
|
120
|
+
<div className="pointer-events-auto">{helper}</div>
|
|
121
|
+
</>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<RightStackContext.Provider value={true}>
|
|
127
|
+
<div
|
|
128
|
+
className="pointer-events-none fixed top-20 right-4 bottom-[74px] z-40 flex flex-col items-end gap-2"
|
|
129
|
+
data-right-stack
|
|
130
|
+
ref={stackRef}
|
|
131
|
+
>
|
|
132
|
+
<div className="flex min-h-0 flex-[0_1_auto] flex-col items-end" ref={inspectorRef}>
|
|
133
|
+
{inspector}
|
|
134
|
+
</div>
|
|
135
|
+
<div
|
|
136
|
+
className="no-scrollbar mt-auto flex min-h-0 flex-[0_1_auto] flex-col overflow-y-auto rounded-xl"
|
|
137
|
+
data-right-stack-card
|
|
138
|
+
ref={cardRef}
|
|
139
|
+
>
|
|
140
|
+
{helper}
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
</RightStackContext.Provider>
|
|
144
|
+
)
|
|
145
|
+
}
|
|
@@ -17,6 +17,11 @@ export type ExtraPanel = {
|
|
|
17
17
|
label: string
|
|
18
18
|
component: ComponentType
|
|
19
19
|
pluginId?: string
|
|
20
|
+
/** Rail entry that drives the stage (see `SidebarTab.noPanel`); `component` serves the v1 sidebar. */
|
|
21
|
+
noPanel?: boolean
|
|
22
|
+
onSelect?: () => void
|
|
23
|
+
stageActive?: boolean
|
|
24
|
+
onDeselect?: () => void
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
interface IconRailProps {
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
clearSceneHistory,
|
|
3
3
|
DEFAULT_LEVEL_HEIGHT,
|
|
4
4
|
emitter,
|
|
5
|
+
getArtifactStore,
|
|
5
6
|
getLevelDisplayName,
|
|
6
7
|
LevelNode,
|
|
7
8
|
type AnyNodeId,
|
|
@@ -64,7 +65,9 @@ import {
|
|
|
64
65
|
import { Input } from './../../../../../components/ui/primitives/input'
|
|
65
66
|
import { Switch } from './../../../../../components/ui/primitives/switch'
|
|
66
67
|
import { cn } from './../../../../../lib/utils'
|
|
68
|
+
import { refusedObjectsNotice } from './../../../../../components/editor/group-actions'
|
|
67
69
|
import { deleteLevelWithFallbackSelection } from './../../../../../lib/level-selection'
|
|
70
|
+
import { bringBuildArtifacts } from './../../../../../lib/scene-clipboard'
|
|
68
71
|
import useEditor, { selectDefaultBuildingAndLevel } from './../../../../../store/use-editor'
|
|
69
72
|
import useFloorplanMode from './../../../../../store/use-floorplan-mode'
|
|
70
73
|
import { type SendToAppStep, useSendToApp } from './../../../../../store/use-send-to-app'
|
|
@@ -101,6 +104,7 @@ const MODEL_EXPORT_FORMATS = [
|
|
|
101
104
|
{ format: 'usdz', label: 'USDZ' },
|
|
102
105
|
{ format: 'stl', label: 'STL' },
|
|
103
106
|
{ format: 'obj', label: 'OBJ' },
|
|
107
|
+
{ format: 'ifc', label: 'IFC' },
|
|
104
108
|
] as const
|
|
105
109
|
|
|
106
110
|
type ModelExportFormat = (typeof MODEL_EXPORT_FORMATS)[number]['format']
|
|
@@ -540,7 +544,16 @@ export function SettingsPanel({
|
|
|
540
544
|
// Materials ride along: nodes reference them by `scene:<id>` slot
|
|
541
545
|
// refs, so a save without the table produces a file whose custom
|
|
542
546
|
// finishes revert to defaults on the very Load Build path below.
|
|
543
|
-
|
|
547
|
+
// The project rides along too: a scripted object's artifacts live there, and
|
|
548
|
+
// loading the file into another project copies them over as a paste does.
|
|
549
|
+
const sceneData = {
|
|
550
|
+
nodes,
|
|
551
|
+
rootNodeIds,
|
|
552
|
+
installedPlugins,
|
|
553
|
+
materials,
|
|
554
|
+
collections,
|
|
555
|
+
projectId: useViewer.getState().projectId,
|
|
556
|
+
}
|
|
544
557
|
const json = JSON.stringify(sceneData, null, 2)
|
|
545
558
|
const blob = new Blob([json], { type: 'application/json' })
|
|
546
559
|
const url = URL.createObjectURL(blob)
|
|
@@ -596,7 +609,20 @@ export function SettingsPanel({
|
|
|
596
609
|
e.target.value = ''
|
|
597
610
|
}
|
|
598
611
|
|
|
599
|
-
const handleConfirmImport = (
|
|
612
|
+
const handleConfirmImport = async (file: ParsedBuildJson) => {
|
|
613
|
+
const targetProjectId = useViewer.getState().projectId
|
|
614
|
+
const artifactStore = getArtifactStore()
|
|
615
|
+
const { build: parsed, refusedIds, refusal } = await bringBuildArtifacts(file)
|
|
616
|
+
if (
|
|
617
|
+
targetProjectId !== useViewer.getState().projectId ||
|
|
618
|
+
artifactStore !== getArtifactStore()
|
|
619
|
+
)
|
|
620
|
+
return
|
|
621
|
+
if (refusal) {
|
|
622
|
+
useFloorplanMode
|
|
623
|
+
.getState()
|
|
624
|
+
.showNotice(refusedObjectsNotice(refusedIds.length, refusal, 'loaded'))
|
|
625
|
+
}
|
|
600
626
|
const currentScene = useScene.getState()
|
|
601
627
|
setScene(
|
|
602
628
|
parsed.nodes as Parameters<typeof setScene>[0],
|
|
@@ -626,7 +652,7 @@ export function SettingsPanel({
|
|
|
626
652
|
// old scene (or land on the empty intermediate `unloadScene` state).
|
|
627
653
|
clearSceneHistory()
|
|
628
654
|
resetSelection()
|
|
629
|
-
setPhase('
|
|
655
|
+
setPhase('building')
|
|
630
656
|
selectDefaultBuildingAndLevel()
|
|
631
657
|
}
|
|
632
658
|
|
|
@@ -688,6 +714,7 @@ export function SettingsPanel({
|
|
|
688
714
|
setModelExportWarning(null)
|
|
689
715
|
try {
|
|
690
716
|
const artifact = await modelExport(format, {
|
|
717
|
+
projectName,
|
|
691
718
|
onlyVisible: exportOnlyVisible,
|
|
692
719
|
excludedNodeTypes,
|
|
693
720
|
includedPresentationIds:
|
|
@@ -903,7 +930,7 @@ export function SettingsPanel({
|
|
|
903
930
|
<legend className="px-1 font-medium text-sm">Include in file</legend>
|
|
904
931
|
<p className="text-muted-foreground text-xs">
|
|
905
932
|
Choose which procedural content is baked into model files. GLB and USDZ use the
|
|
906
|
-
textured portable path; STL and
|
|
933
|
+
textured portable path; STL, OBJ and IFC remain geometry-only.
|
|
907
934
|
</p>
|
|
908
935
|
{exportableNodeTypes.length > 0 ? (
|
|
909
936
|
<div className="space-y-2 pt-1">
|
|
@@ -28,11 +28,11 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [
|
|
|
28
28
|
{
|
|
29
29
|
title: 'Editor Navigation',
|
|
30
30
|
shortcuts: [
|
|
31
|
-
{ keys: ['1'], action: 'Switch to Site
|
|
32
|
-
{ keys: ['2'], action: 'Switch to
|
|
33
|
-
{ keys: ['
|
|
34
|
-
{ keys: ['F'], action: '
|
|
35
|
-
{ keys: ['Z'], action: '
|
|
31
|
+
{ keys: ['1'], action: 'Switch to Site' },
|
|
32
|
+
{ keys: ['2'], action: 'Switch to Building' },
|
|
33
|
+
{ keys: ['\\'], action: 'Open or close the split view' },
|
|
34
|
+
{ keys: ['F'], action: 'Open the items catalog with the item tool' },
|
|
35
|
+
{ keys: ['Z'], action: 'Draw a zone' },
|
|
36
36
|
{
|
|
37
37
|
keys: ['Cmd/Ctrl', 'Arrow Up'],
|
|
38
38
|
action: 'Select next level in the active building',
|
|
@@ -3,8 +3,8 @@ import { useViewer } from '@pascal-app/viewer'
|
|
|
3
3
|
import Image from 'next/image'
|
|
4
4
|
import { memo, useCallback, useEffect, useState } from 'react'
|
|
5
5
|
import { useShallow } from 'zustand/react/shallow'
|
|
6
|
+
import { startCeilingEditFromTreeSelection } from './../../../../../lib/ceiling-edit-session'
|
|
6
7
|
import { formatAreaLabel } from './../../../../../lib/measurements'
|
|
7
|
-
import useEditor from './../../../../../store/use-editor'
|
|
8
8
|
import { InlineRenameInput } from './inline-rename-input'
|
|
9
9
|
import { focusTreeNode, handleTreeSelection, TreeNode, TreeNodeWrapper } from './tree-node'
|
|
10
10
|
import { TreeNodeActions } from './tree-node-actions'
|
|
@@ -63,9 +63,9 @@ export const CeilingTreeNode = memo(function CeilingTreeNode({
|
|
|
63
63
|
useViewer.getState().selection.selectedIds,
|
|
64
64
|
setSelection,
|
|
65
65
|
)
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
if (handled) return
|
|
67
|
+
// Selecting a ceiling in the scene graph opens its Edit ceiling session.
|
|
68
|
+
startCeilingEditFromTreeSelection(nodeId)
|
|
69
69
|
},
|
|
70
70
|
[nodeId, setSelection],
|
|
71
71
|
)
|
|
@@ -3,7 +3,6 @@ import { useViewer } from '@pascal-app/viewer'
|
|
|
3
3
|
import Image from 'next/image'
|
|
4
4
|
import { memo, useCallback, useState } from 'react'
|
|
5
5
|
import { SnapTargetIcon } from '../../../snap-target-badge'
|
|
6
|
-
import useEditor from './../../../../../store/use-editor'
|
|
7
6
|
import { InlineRenameInput } from './inline-rename-input'
|
|
8
7
|
import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
|
|
9
8
|
import { TreeNodeActions } from './tree-node-actions'
|
|
@@ -30,15 +29,12 @@ export const ChimneyTreeNode = memo(function ChimneyTreeNode({
|
|
|
30
29
|
const handleClick = useCallback(
|
|
31
30
|
(e: React.MouseEvent) => {
|
|
32
31
|
e.stopPropagation()
|
|
33
|
-
|
|
32
|
+
handleTreeSelection(
|
|
34
33
|
e,
|
|
35
34
|
nodeId,
|
|
36
35
|
useViewer.getState().selection.selectedIds,
|
|
37
36
|
setSelection,
|
|
38
37
|
)
|
|
39
|
-
if (!handled && useEditor.getState().phase === 'furnish') {
|
|
40
|
-
useEditor.getState().setPhase('structure')
|
|
41
|
-
}
|
|
42
38
|
},
|
|
43
39
|
[nodeId, setSelection],
|
|
44
40
|
)
|