@pascal-app/editor 1.0.1 → 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 +16 -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 +221 -128
- 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 +37 -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 +536 -487
- package/src/components/editor/footprint-height-handle.tsx +204 -0
- package/src/components/editor/grid.tsx +31 -7
- package/src/components/editor/group-actions.ts +59 -11
- package/src/components/editor/group-floating-action-menu.tsx +19 -9
- package/src/components/editor/group-move-3d.ts +4 -6
- package/src/components/editor/group-rotate-handle.tsx +81 -35
- package/src/components/editor/group-selection-box-3d.tsx +25 -17
- package/src/components/editor/group-transform-shared.test.ts +126 -1
- package/src/components/editor/group-transform-shared.ts +79 -73
- package/src/components/editor/handles/handle-arrow.tsx +72 -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 +272 -131
- package/src/components/editor/index.tsx +316 -176
- package/src/components/editor/join-walls-pill.tsx +44 -0
- package/src/components/editor/node-action-menu.tsx +70 -70
- package/src/components/editor/node-arrow-handles.tsx +167 -14
- 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 +132 -0
- 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 +114 -163
- package/src/components/editor/wall-opening-highlights.tsx +2 -3
- 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-group-move.tsx +17 -24
- 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-registered-tool-layer.tsx +33 -12
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +242 -126
- 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-measurement.tsx +166 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +87 -8
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +355 -245
- 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 +31 -66
- package/src/components/tools/__fixtures__/hosting.ts +10 -0
- package/src/components/tools/item/item-surface-placement.test.ts +3 -9
- package/src/components/tools/item/item-surface-strategy-resolver.test.ts +3 -9
- package/src/components/tools/item/placement-history.test.tsx +474 -0
- package/src/components/tools/item/placement-strategies.ts +63 -47
- package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +3 -3
- package/src/components/tools/item/use-draft-node.test.tsx +432 -1
- package/src/components/tools/item/use-draft-node.ts +207 -97
- package/src/components/tools/item/use-placement-coordinator.tsx +251 -111
- 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/item-surface-move.test.ts +10 -2
- package/src/components/tools/registry/item-surface-move.ts +46 -24
- package/src/components/tools/registry/item-surface-resolver.test.ts +2 -2
- package/src/components/tools/registry/move-registry-node-tool.tsx +26 -10
- 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/affordance-dispatch.test.ts +24 -0
- package/src/components/tools/shared/affordance-dispatch.ts +13 -0
- package/src/components/tools/shared/cursor-sphere.tsx +36 -12
- package/src/components/tools/shared/draft-measurement-label.tsx +38 -0
- package/src/components/tools/shared/pointer-support-cap.test.ts +27 -0
- package/src/components/tools/shared/pointer-support-cap.ts +17 -5
- package/src/components/tools/shared/surface-rejection.test.tsx +5 -4
- package/src/components/tools/shared/surface-rejection.tsx +14 -0
- 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 +82 -23
- 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 +101 -91
- package/src/components/ui/controls/material-picker.tsx +8 -67
- 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/controls/slider-control.tsx +76 -31
- 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 +348 -19
- package/src/components/ui/helpers/item-helper.tsx +8 -0
- package/src/components/ui/helpers/registered-tool-helper.tsx +10 -33
- package/src/components/ui/icon-ref.tsx +45 -1
- package/src/components/ui/item-catalog/item-catalog.tsx +5 -30
- 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-height-mode.tsx +27 -21
- 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 +7 -19
- 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/shortcut-token.tsx +4 -0
- 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/audio-settings-dialog.tsx +2 -2
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +615 -372
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +7 -7
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +1 -1
- 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-visibility.test.ts +35 -0
- package/src/components/viewer/floorplan-preview-visibility.ts +19 -0
- package/src/components/viewer/floorplan-preview.tsx +6 -18
- 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 +9 -9
- package/src/hooks/use-auto-save.integration.test.tsx +311 -0
- package/src/hooks/use-auto-save.ts +55 -14
- package/src/hooks/use-ceiling-events.test.ts +1 -0
- package/src/hooks/use-ceiling-events.ts +2 -0
- package/src/hooks/use-grid-events.test.ts +2 -0
- package/src/hooks/use-grid-events.ts +16 -11
- package/src/hooks/use-keyboard.test.ts +182 -0
- package/src/hooks/use-keyboard.ts +122 -91
- 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 +306 -39
- package/src/lib/catalog-panel-model.ts +39 -0
- package/src/lib/catalog-selection.test.ts +27 -0
- package/src/lib/catalog-selection.ts +8 -0
- 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/contextual-help.test.ts +27 -1
- package/src/lib/contextual-help.ts +16 -9
- 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 +15 -9
- package/src/lib/floorplan/floorplan-export.tsx +32 -8
- package/src/lib/floorplan/floorplan-extension.ts +48 -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 +191 -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 +544 -16
- package/src/lib/glb-export.ts +256 -125
- package/src/lib/grid-frame.test.ts +19 -0
- package/src/lib/grid-frame.ts +18 -0
- 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 +27 -1
- package/src/lib/keyboard-pan.test.ts +184 -0
- package/src/lib/keyboard-pan.ts +238 -0
- 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 +74 -0
- package/src/lib/material-paint-panel-model.ts +80 -0
- 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/overlay-transform.test.ts +28 -0
- package/src/lib/overlay-transform.ts +12 -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 +96 -0
- package/src/lib/panel-tool-options.test.ts +34 -0
- package/src/lib/panel-tool-options.ts +46 -0
- package/src/lib/parametric-node-update.ts +37 -0
- 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 +87 -0
- package/src/lib/scene.ts +39 -45
- 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 +17 -2
- package/src/lib/snapping-mode.ts +19 -7
- package/src/lib/spatial-drag-plane.test.ts +53 -0
- package/src/lib/spatial-drag-plane.ts +19 -0
- package/src/lib/spatial-pointer-input.test.ts +72 -0
- package/src/lib/spatial-pointer-input.ts +62 -0
- package/src/lib/spatial-snap.test.ts +47 -0
- package/src/lib/terrace-draft.test.ts +145 -0
- package/src/lib/terrace-draft.ts +213 -0
- package/src/lib/terrain-panel-model.ts +117 -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/world-grid-snap.test.ts +48 -0
- package/src/lib/world-grid-snap.ts +9 -5
- 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/rotation-snapping.test.ts +53 -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 +442 -323
- package/src/store/use-floorplan-draft-preview.ts +23 -1
- package/src/store/use-floorplan-marquee.ts +12 -2
- package/src/store/use-interaction-scope.ts +155 -6
- 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
|
@@ -1,30 +1,22 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from '@pascal-app/core'
|
|
10
|
-
import { useViewer } from '@pascal-app/viewer'
|
|
11
|
-
import { Eraser, Plus, RotateCcw } from 'lucide-react'
|
|
12
|
-
import { useEffect, useState } from 'react'
|
|
13
|
-
import {
|
|
14
|
-
buildResetSurfaceMaterialUpdates,
|
|
15
|
-
resolvePaintTargetFromSelection,
|
|
16
|
-
} from './../../../lib/material-paint'
|
|
17
|
-
import useEditor from './../../../store/use-editor'
|
|
3
|
+
import { type SceneMaterialId } from '@pascal-app/core'
|
|
4
|
+
import { Eraser, PaintBucket, Pentagon, Pipette, Plus, Square } from 'lucide-react'
|
|
5
|
+
import { useState } from 'react'
|
|
6
|
+
import { useMaterialPaintPanelModel } from '../../../lib/material-paint-panel-model'
|
|
7
|
+
import { type PaintMode, usePaintRegionMode } from '../../../lib/paint-region-mode'
|
|
8
|
+
import useEditor, { armMaterialPaint } from '../../../store/use-editor'
|
|
18
9
|
import { Button } from '../primitives/button'
|
|
19
10
|
import { Tooltip, TooltipContent, TooltipTrigger } from '../primitives/tooltip'
|
|
20
11
|
import { MaterialPicker } from './material-picker'
|
|
21
12
|
import { SceneMaterialList } from './scene-material-list'
|
|
13
|
+
import { SegmentedControl } from './segmented-control'
|
|
22
14
|
|
|
23
15
|
/**
|
|
24
16
|
* Material picker for paint mode. Embedders render this wherever paint controls
|
|
25
17
|
* belong (the community editor places it in the Build sidebar while paint mode
|
|
26
18
|
* is active). It fills its container's height and lays out as three bands: a
|
|
27
|
-
* fixed
|
|
19
|
+
* fixed mode header, a single scrolling catalog grid, and a fixed
|
|
28
20
|
* scene-material footer (always visible, with a `+` to add a custom material).
|
|
29
21
|
*/
|
|
30
22
|
export type MaterialPaintPanelProps = {
|
|
@@ -32,94 +24,110 @@ export type MaterialPaintPanelProps = {
|
|
|
32
24
|
onCreateMaterialRequest?: () => void
|
|
33
25
|
}
|
|
34
26
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
27
|
+
const PAINT_MODE_OPTIONS: { value: PaintMode; label: string; icon: typeof PaintBucket }[] = [
|
|
28
|
+
{ value: 'surface', label: 'Whole surface: a click paints the surface under it', icon: PaintBucket },
|
|
29
|
+
{
|
|
30
|
+
value: 'rectangle',
|
|
31
|
+
label: 'Rectangle: press anywhere on a wall, floor or ceiling, drag to the opposite corner',
|
|
32
|
+
icon: Square,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
value: 'polygon',
|
|
36
|
+
label: 'Polygon: click the points of a shape on a floor or ceiling',
|
|
37
|
+
icon: Pentagon,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
value: 'erase',
|
|
41
|
+
label: 'Erase: click a painted part to remove it, or a painted surface to reset it',
|
|
42
|
+
icon: Eraser,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
value: 'pick',
|
|
46
|
+
label: 'Pick: click a surface to paint with its material (or hold Alt / Option)',
|
|
47
|
+
icon: Pipette,
|
|
48
|
+
},
|
|
49
|
+
]
|
|
51
50
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Paint the whole surface, draw part of a wall, floor or ceiling first, erase, or
|
|
53
|
+
* pick a material off a surface. Erase
|
|
54
|
+
* is the eraser the paint path already knows (a click writes "no finish"), so a
|
|
55
|
+
* painted region goes and a whole-surface paint returns to its default. The row
|
|
56
|
+
* shows a choice only while paint mode is on; any pick (erase included) turns it
|
|
57
|
+
* on, so nothing here needs a colour first.
|
|
58
|
+
*/
|
|
59
|
+
function PaintModeControl() {
|
|
60
|
+
const painting = useEditor((s) => s.mode === 'material-paint')
|
|
61
|
+
const mode = usePaintRegionMode((s) => s.mode)
|
|
62
|
+
const select = (next: PaintMode) => {
|
|
63
|
+
if (painting) usePaintRegionMode.getState().setMode(next)
|
|
64
|
+
else armMaterialPaint(undefined, next)
|
|
62
65
|
}
|
|
66
|
+
return (
|
|
67
|
+
<div
|
|
68
|
+
className="shrink-0 space-y-1.5 pb-2"
|
|
69
|
+
data-paint-mode={painting ? mode : 'off'}
|
|
70
|
+
// A clicked option must not keep focus: the next shortcut (V) would light
|
|
71
|
+
// its focus ring and it would read as still chosen after paint mode ends.
|
|
72
|
+
onPointerUp={(event) => (event.target as HTMLElement).closest('button')?.blur()}
|
|
73
|
+
>
|
|
74
|
+
<div className="px-0.5 font-medium text-muted-foreground text-xs">
|
|
75
|
+
{painting && mode === 'erase' ? 'Erase' : painting && mode === 'pick' ? 'Pick material' : 'Paint'}
|
|
76
|
+
</div>
|
|
77
|
+
<SegmentedControl
|
|
78
|
+
mixed={!painting}
|
|
79
|
+
onChange={select}
|
|
80
|
+
options={PAINT_MODE_OPTIONS.map(({ value, label, icon: Icon }) => ({
|
|
81
|
+
value,
|
|
82
|
+
label: (
|
|
83
|
+
<Tooltip>
|
|
84
|
+
<TooltipTrigger asChild>
|
|
85
|
+
<span aria-label={label} className="flex items-center justify-center">
|
|
86
|
+
<Icon className="h-3.5 w-3.5" />
|
|
87
|
+
</span>
|
|
88
|
+
</TooltipTrigger>
|
|
89
|
+
<TooltipContent>{label}</TooltipContent>
|
|
90
|
+
</Tooltip>
|
|
91
|
+
),
|
|
92
|
+
}))}
|
|
93
|
+
value={mode}
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
63
98
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
color: '#ffffff',
|
|
76
|
-
roughness: 0.5,
|
|
77
|
-
metalness: 0,
|
|
78
|
-
opacity: 1,
|
|
79
|
-
transparent: false,
|
|
80
|
-
side: 'front',
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
})
|
|
84
|
-
armMaterialPaint({ materialPreset: toSceneMaterialRef(id), sourceTarget: activePaintTarget })
|
|
85
|
-
setAutoEditMaterialId(id)
|
|
86
|
-
}
|
|
99
|
+
export function MaterialPaintPanel({ onCreateMaterialRequest }: MaterialPaintPanelProps) {
|
|
100
|
+
const painting = useEditor((s) => s.mode === 'material-paint')
|
|
101
|
+
const erasing = usePaintRegionMode((s) => s.mode) === 'erase' && painting
|
|
102
|
+
const {
|
|
103
|
+
activePaintMaterial,
|
|
104
|
+
materialCount,
|
|
105
|
+
selectMaterial,
|
|
106
|
+
createCustomMaterial: createMaterial,
|
|
107
|
+
} = useMaterialPaintPanelModel()
|
|
108
|
+
const [autoEditMaterialId, setAutoEditMaterialId] = useState<SceneMaterialId | null>(null)
|
|
109
|
+
const createCustomMaterial = () => setAutoEditMaterialId(createMaterial())
|
|
87
110
|
|
|
88
111
|
return (
|
|
89
112
|
<div className="flex h-full min-h-0 w-full flex-col">
|
|
90
|
-
{/* Fixed:
|
|
91
|
-
<
|
|
92
|
-
<Button
|
|
93
|
-
aria-pressed={paintEraser}
|
|
94
|
-
className="flex-1"
|
|
95
|
-
onClick={() => setPaintEraser(!paintEraser)}
|
|
96
|
-
size="sm"
|
|
97
|
-
variant={paintEraser ? 'default' : 'outline'}
|
|
98
|
-
>
|
|
99
|
-
<Eraser />
|
|
100
|
-
Erase
|
|
101
|
-
</Button>
|
|
102
|
-
<Button
|
|
103
|
-
className="flex-1"
|
|
104
|
-
disabled={!canResetSelection}
|
|
105
|
-
onClick={resetSelection}
|
|
106
|
-
size="sm"
|
|
107
|
-
variant="outline"
|
|
108
|
-
>
|
|
109
|
-
<RotateCcw />
|
|
110
|
-
Reset all
|
|
111
|
-
</Button>
|
|
112
|
-
</div>
|
|
113
|
+
{/* Fixed: whole surface, part of a surface, or erase. */}
|
|
114
|
+
<PaintModeControl />
|
|
113
115
|
|
|
116
|
+
{/* Erasing paints nothing: no swatches to pick from, just what a click does. */}
|
|
117
|
+
{erasing ? (
|
|
118
|
+
<p className="px-0.5 py-1 text-muted-foreground text-xs" data-paint-erase-note>
|
|
119
|
+
Click a painted part of a wall, floor or ceiling to remove it, or a painted surface to reset it to
|
|
120
|
+
its default.
|
|
121
|
+
</p>
|
|
122
|
+
) : (
|
|
123
|
+
<>
|
|
114
124
|
{/* Scrolls: category tabs (fixed inside) + catalog grid (the scroll). */}
|
|
115
125
|
{/* A stable hook for host-app onboarding to point at. Static, and read
|
|
116
126
|
only from outside: nothing here depends on it. */}
|
|
117
127
|
<div className="min-h-0 flex-1" data-guide-target="paint-material">
|
|
118
128
|
<MaterialPicker
|
|
119
129
|
onCreateMaterialRequest={onCreateMaterialRequest}
|
|
120
|
-
onSelectMaterialPreset={
|
|
121
|
-
armMaterialPaint({ materialPreset, sourceTarget: activePaintTarget })
|
|
122
|
-
}}
|
|
130
|
+
onSelectMaterialPreset={selectMaterial}
|
|
123
131
|
selectedMaterialPreset={activePaintMaterial?.materialPreset}
|
|
124
132
|
/>
|
|
125
133
|
</div>
|
|
@@ -155,6 +163,8 @@ export function MaterialPaintPanel({ onCreateMaterialRequest }: MaterialPaintPan
|
|
|
155
163
|
)}
|
|
156
164
|
</div>
|
|
157
165
|
</div>
|
|
166
|
+
</>
|
|
167
|
+
)}
|
|
158
168
|
</div>
|
|
159
169
|
)
|
|
160
170
|
}
|
|
@@ -1,23 +1,11 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
getCatalogMaterialById,
|
|
5
|
-
getDynamicLibraryMaterials,
|
|
6
|
-
getLibraryMaterialIdFromRef,
|
|
7
|
-
getLibraryMaterialsVersion,
|
|
8
|
-
getMaterialsForCategory,
|
|
9
|
-
MATERIAL_CATEGORIES,
|
|
10
|
-
type MaterialCatalogItem,
|
|
11
|
-
type MaterialSource,
|
|
12
|
-
type MaterialTarget,
|
|
13
|
-
subscribeLibraryMaterials,
|
|
14
|
-
toLibraryMaterialRef,
|
|
15
|
-
} from '@pascal-app/core'
|
|
3
|
+
import { type MaterialTarget, toLibraryMaterialRef } from '@pascal-app/core'
|
|
16
4
|
import { Plus } from 'lucide-react'
|
|
17
|
-
import {
|
|
5
|
+
import { useMaterialCatalogModel, type MaterialSourceFilter } from '../../../lib/material-catalog-model'
|
|
18
6
|
import { triggerSFX } from '../../../lib/sfx-bus'
|
|
19
7
|
|
|
20
|
-
export type MaterialSourceFilter
|
|
8
|
+
export type { MaterialSourceFilter } from '../../../lib/material-catalog-model'
|
|
21
9
|
|
|
22
10
|
export type MaterialPickerProps = {
|
|
23
11
|
selectedMaterialPreset?: string
|
|
@@ -26,25 +14,14 @@ export type MaterialPickerProps = {
|
|
|
26
14
|
nodeType?: MaterialTarget
|
|
27
15
|
hideSideControl?: boolean
|
|
28
16
|
onCreateMaterialRequest?: () => void
|
|
17
|
+
/** Whether switching category tabs selects the tab's first material (paint mode does). */
|
|
18
|
+
selectOnCategoryChange?: boolean
|
|
29
19
|
}
|
|
30
20
|
|
|
31
|
-
|
|
32
|
-
// to the Pascal library — the combined list buried the curated set.
|
|
33
|
-
const SOURCE_FILTERS: { id: MaterialSourceFilter; label: string }[] = [
|
|
34
|
-
{ id: 'pascal', label: 'Pascal' },
|
|
35
|
-
{ id: 'mine', label: 'Mine' },
|
|
36
|
-
{ id: 'workspace', label: 'Workspace' },
|
|
37
|
-
{ id: 'community', label: 'Community' },
|
|
38
|
-
]
|
|
39
|
-
|
|
40
|
-
function getCategoryLabel(category: (typeof MATERIAL_CATEGORIES)[number]) {
|
|
21
|
+
function getCategoryLabel(category: string) {
|
|
41
22
|
return category.charAt(0).toUpperCase() + category.slice(1)
|
|
42
23
|
}
|
|
43
24
|
|
|
44
|
-
function filterBySource(items: MaterialCatalogItem[], filter: MaterialSourceFilter) {
|
|
45
|
-
return items.filter((item) => (item.source ?? 'pascal') === filter)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
25
|
/**
|
|
49
26
|
* Catalog material picker: a fixed row of category tabs and a source filter row
|
|
50
27
|
* over a scrollable grid of swatches. Scene-material creation lives in the
|
|
@@ -56,41 +33,9 @@ export function MaterialPicker({
|
|
|
56
33
|
onSelectMaterialPreset,
|
|
57
34
|
disabled = false,
|
|
58
35
|
onCreateMaterialRequest,
|
|
36
|
+
selectOnCategoryChange = true,
|
|
59
37
|
}: MaterialPickerProps) {
|
|
60
|
-
const
|
|
61
|
-
MATERIAL_CATEGORIES[0],
|
|
62
|
-
)
|
|
63
|
-
const [sourceFilter, setSourceFilter] = useState<MaterialSourceFilter>('pascal')
|
|
64
|
-
// Version counter so host registrations/unregistrations re-render the picker.
|
|
65
|
-
const libraryVersion = useSyncExternalStore(
|
|
66
|
-
subscribeLibraryMaterials,
|
|
67
|
-
getLibraryMaterialsVersion,
|
|
68
|
-
getLibraryMaterialsVersion,
|
|
69
|
-
)
|
|
70
|
-
const hasWorkspaceMaterials = useMemo(
|
|
71
|
-
() => getDynamicLibraryMaterials().some((item) => item.source === 'workspace'),
|
|
72
|
-
[libraryVersion],
|
|
73
|
-
)
|
|
74
|
-
const visibleSourceFilters = SOURCE_FILTERS.filter(
|
|
75
|
-
(filter) => filter.id !== 'workspace' || hasWorkspaceMaterials,
|
|
76
|
-
)
|
|
77
|
-
const availableCategories = MATERIAL_CATEGORIES.filter(
|
|
78
|
-
(category) => getMaterialsForCategory(category).length > 0,
|
|
79
|
-
)
|
|
80
|
-
const catalogItems = filterBySource(getMaterialsForCategory(selectedCategory), sourceFilter)
|
|
81
|
-
|
|
82
|
-
// Keep the visible category in sync with the externally-selected catalog
|
|
83
|
-
// material (a `scene:` ref matches no catalog entry, so the tab stays put).
|
|
84
|
-
useEffect(() => {
|
|
85
|
-
const catalogId = getLibraryMaterialIdFromRef(selectedMaterialPreset) ?? undefined
|
|
86
|
-
const entry = getCatalogMaterialById(catalogId)
|
|
87
|
-
if (entry?.category) setSelectedCategory(entry.category)
|
|
88
|
-
}, [selectedMaterialPreset])
|
|
89
|
-
|
|
90
|
-
const handleCatalogSelect = (materialId: string) => {
|
|
91
|
-
if (disabled) return
|
|
92
|
-
onSelectMaterialPreset?.(toLibraryMaterialRef(materialId))
|
|
93
|
-
}
|
|
38
|
+
const { selectedCategory, setSelectedCategory, sourceFilter, setSourceFilter, visibleSourceFilters, availableCategories, catalogItems, select: handleCatalogSelect } = useMaterialCatalogModel(selectedMaterialPreset, onSelectMaterialPreset, disabled, selectOnCategoryChange)
|
|
94
39
|
|
|
95
40
|
return (
|
|
96
41
|
<div
|
|
@@ -108,10 +53,6 @@ export function MaterialPicker({
|
|
|
108
53
|
key={category}
|
|
109
54
|
onClick={() => {
|
|
110
55
|
setSelectedCategory(category)
|
|
111
|
-
// Auto-select the first material in the category so the brush is
|
|
112
|
-
// immediately ready (and the swatch shows as selected).
|
|
113
|
-
const first = filterBySource(getMaterialsForCategory(category), sourceFilter)[0]
|
|
114
|
-
if (first) handleCatalogSelect(first.id)
|
|
115
56
|
}}
|
|
116
57
|
type="button"
|
|
117
58
|
>
|
|
@@ -10,21 +10,13 @@ import {
|
|
|
10
10
|
} from '@pascal-app/core'
|
|
11
11
|
import { Copy, Paintbrush, Pencil, Trash2 } from 'lucide-react'
|
|
12
12
|
import { useEffect, useMemo, useState } from 'react'
|
|
13
|
+
import { sceneMaterialUsageCounts } from '../../../lib/scene-material-refs'
|
|
13
14
|
import useEditor from '../../../store/use-editor'
|
|
14
15
|
import { Button } from '../primitives/button'
|
|
15
16
|
import { Input } from '../primitives/input'
|
|
16
17
|
import { Tooltip, TooltipContent, TooltipTrigger } from '../primitives/tooltip'
|
|
17
18
|
import { MaterialPropertiesEditor } from './material-properties-editor'
|
|
18
19
|
|
|
19
|
-
type SlotRecord = Record<string, string | undefined>
|
|
20
|
-
|
|
21
|
-
function getSlotRecord(node: unknown): SlotRecord | null {
|
|
22
|
-
if (!node || typeof node !== 'object' || !('slots' in node)) return null
|
|
23
|
-
const slots = (node as { slots?: unknown }).slots
|
|
24
|
-
if (!slots || typeof slots !== 'object' || Array.isArray(slots)) return null
|
|
25
|
-
return slots as SlotRecord
|
|
26
|
-
}
|
|
27
|
-
|
|
28
20
|
export function SceneMaterialList({ autoEditId }: { autoEditId?: SceneMaterialId | null }) {
|
|
29
21
|
const materials = useScene((state) => state.materials)
|
|
30
22
|
const nodes = useScene((state) => state.nodes)
|
|
@@ -40,29 +32,9 @@ export function SceneMaterialList({ autoEditId }: { autoEditId?: SceneMaterialId
|
|
|
40
32
|
[materials],
|
|
41
33
|
)
|
|
42
34
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
for (const [id] of materialEntries) {
|
|
48
|
-
counts.set(id, 0)
|
|
49
|
-
refToId.set(toSceneMaterialRef(id), id)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
for (const node of Object.values(nodes)) {
|
|
53
|
-
const slots = getSlotRecord(node)
|
|
54
|
-
if (!slots) continue
|
|
55
|
-
|
|
56
|
-
for (const value of Object.values(slots)) {
|
|
57
|
-
if (typeof value !== 'string') continue
|
|
58
|
-
const materialId = refToId.get(value)
|
|
59
|
-
if (!materialId) continue
|
|
60
|
-
counts.set(materialId, (counts.get(materialId) ?? 0) + 1)
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return counts
|
|
65
|
-
}, [materialEntries, nodes])
|
|
35
|
+
// Every place a scene material can be used — slots and the room, wall and
|
|
36
|
+
// footprint finishes — counted by the same walker saving and pasting use.
|
|
37
|
+
const usageCounts = useMemo(() => sceneMaterialUsageCounts(nodes), [nodes])
|
|
66
38
|
|
|
67
39
|
return (
|
|
68
40
|
<div className="space-y-2">
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { ChevronDown } from 'lucide-react'
|
|
4
|
+
import { cn } from '../../../lib/utils'
|
|
5
|
+
import {
|
|
6
|
+
DropdownMenu,
|
|
7
|
+
DropdownMenuContent,
|
|
8
|
+
DropdownMenuRadioGroup,
|
|
9
|
+
DropdownMenuRadioItem,
|
|
10
|
+
DropdownMenuTrigger,
|
|
11
|
+
} from '../primitives/dropdown-menu'
|
|
12
|
+
|
|
13
|
+
interface SelectControlProps<T extends string> {
|
|
14
|
+
label: string
|
|
15
|
+
value: T
|
|
16
|
+
onChange: (value: T) => void
|
|
17
|
+
options: { label: string; value: T }[]
|
|
18
|
+
className?: string
|
|
19
|
+
mixed?: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function SelectControl<T extends string>({
|
|
23
|
+
label,
|
|
24
|
+
value,
|
|
25
|
+
onChange,
|
|
26
|
+
options,
|
|
27
|
+
className,
|
|
28
|
+
mixed = false,
|
|
29
|
+
}: SelectControlProps<T>) {
|
|
30
|
+
const current = mixed ? 'Mixed' : options.find((option) => option.value === value)?.label
|
|
31
|
+
return (
|
|
32
|
+
<DropdownMenu>
|
|
33
|
+
<DropdownMenuTrigger asChild>
|
|
34
|
+
<button
|
|
35
|
+
aria-label={label}
|
|
36
|
+
className={cn(
|
|
37
|
+
'group flex h-10 w-full items-center justify-between gap-2 rounded-lg border border-border/50 bg-[#2C2C2E] px-3 text-sm transition-colors hover:bg-[#3e3e3e] data-[state=open]:bg-[#3e3e3e]',
|
|
38
|
+
className,
|
|
39
|
+
)}
|
|
40
|
+
type="button"
|
|
41
|
+
>
|
|
42
|
+
<span className="select-none text-muted-foreground transition-colors group-hover:text-foreground">
|
|
43
|
+
{label}
|
|
44
|
+
</span>
|
|
45
|
+
<span className="flex min-w-0 items-center gap-1.5 text-foreground">
|
|
46
|
+
<span className="truncate">{current}</span>
|
|
47
|
+
<ChevronDown className="size-3.5 shrink-0 text-muted-foreground" />
|
|
48
|
+
</span>
|
|
49
|
+
</button>
|
|
50
|
+
</DropdownMenuTrigger>
|
|
51
|
+
<DropdownMenuContent align="end" className="min-w-(--radix-dropdown-menu-trigger-width)">
|
|
52
|
+
<DropdownMenuRadioGroup
|
|
53
|
+
onValueChange={(next) => onChange(next as T)}
|
|
54
|
+
value={mixed ? '' : value}
|
|
55
|
+
>
|
|
56
|
+
{options.map((option) => (
|
|
57
|
+
<DropdownMenuRadioItem key={option.value} value={option.value}>
|
|
58
|
+
{option.label}
|
|
59
|
+
</DropdownMenuRadioItem>
|
|
60
|
+
))}
|
|
61
|
+
</DropdownMenuRadioGroup>
|
|
62
|
+
</DropdownMenuContent>
|
|
63
|
+
</DropdownMenu>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
@@ -11,6 +11,8 @@ interface SliderControlProps {
|
|
|
11
11
|
value: number
|
|
12
12
|
onChange: (value: number) => void
|
|
13
13
|
onCommit?: (value: number) => void
|
|
14
|
+
onCancel?: () => void
|
|
15
|
+
previewWhileTyping?: boolean
|
|
14
16
|
min?: number
|
|
15
17
|
max?: number
|
|
16
18
|
precision?: number
|
|
@@ -54,6 +56,8 @@ export function SliderControl({
|
|
|
54
56
|
value,
|
|
55
57
|
onChange,
|
|
56
58
|
onCommit,
|
|
59
|
+
onCancel,
|
|
60
|
+
previewWhileTyping = false,
|
|
57
61
|
min = Number.NEGATIVE_INFINITY,
|
|
58
62
|
max = Number.POSITIVE_INFINITY,
|
|
59
63
|
precision: storedPrecision = 0,
|
|
@@ -87,6 +91,9 @@ export function SliderControl({
|
|
|
87
91
|
anchorValue: number
|
|
88
92
|
stepMultiplier: number
|
|
89
93
|
} | null>(null)
|
|
94
|
+
const cancelRef = useRef(onCancel)
|
|
95
|
+
cancelRef.current = onCancel
|
|
96
|
+
const editingRef = useRef(false)
|
|
90
97
|
const labelRef = useRef<HTMLDivElement>(null)
|
|
91
98
|
const shown = dragDisplay ?? value
|
|
92
99
|
const valueRef = useRef(shown)
|
|
@@ -217,45 +224,74 @@ export function SliderControl({
|
|
|
217
224
|
[onChange, onCommit, restoreOnCommit],
|
|
218
225
|
)
|
|
219
226
|
|
|
227
|
+
const cancelEdit = useCallback(() => {
|
|
228
|
+
const drag = dragRef.current
|
|
229
|
+
dragRef.current = null
|
|
230
|
+
editingRef.current = false
|
|
231
|
+
if (drag) {
|
|
232
|
+
if (!onCancel) onChange(drag.originValue)
|
|
233
|
+
useScene.temporal.getState().resume()
|
|
234
|
+
}
|
|
235
|
+
onCancel?.()
|
|
236
|
+
setIsDragging(false)
|
|
237
|
+
setIsEditing(false)
|
|
238
|
+
setDragDisplay(null)
|
|
239
|
+
}, [onCancel, onChange])
|
|
240
|
+
|
|
241
|
+
useEffect(
|
|
242
|
+
() => () => {
|
|
243
|
+
if (dragRef.current) useScene.temporal.getState().resume()
|
|
244
|
+
if (dragRef.current || editingRef.current) cancelRef.current?.()
|
|
245
|
+
},
|
|
246
|
+
[],
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
useEffect(() => {
|
|
250
|
+
if (!isDragging) return
|
|
251
|
+
const escape = (event: KeyboardEvent) => {
|
|
252
|
+
if (event.key === 'Escape') cancelEdit()
|
|
253
|
+
}
|
|
254
|
+
window.addEventListener('keydown', escape)
|
|
255
|
+
return () => window.removeEventListener('keydown', escape)
|
|
256
|
+
}, [isDragging, cancelEdit])
|
|
257
|
+
|
|
220
258
|
const handleValueClick = useCallback(() => {
|
|
259
|
+
editingRef.current = true
|
|
221
260
|
setIsEditing(true)
|
|
222
261
|
setInputValue(toDisplay(value).toFixed(precision))
|
|
223
262
|
}, [value, precision, toDisplay])
|
|
224
263
|
|
|
264
|
+
const parseInputValue = useCallback(
|
|
265
|
+
(text: string) => {
|
|
266
|
+
const spec = lingoUnitSpec(unit)
|
|
267
|
+
const parsed = spec
|
|
268
|
+
? parseMeasurement(text, spec, {
|
|
269
|
+
bareUnit: parseUnit ?? spec.unitId,
|
|
270
|
+
system: isImperial ? 'us' : 'metric',
|
|
271
|
+
})
|
|
272
|
+
: null
|
|
273
|
+
const bare = Number(text)
|
|
274
|
+
const stored = parsed ?? (text.trim() && Number.isFinite(bare) ? toStored(bare) : null)
|
|
275
|
+
return stored === null
|
|
276
|
+
? null
|
|
277
|
+
: clamp(toStored(Number.parseFloat(toDisplay(stored).toFixed(precision))))
|
|
278
|
+
},
|
|
279
|
+
[unit, parseUnit, isImperial, toStored, clamp, toDisplay, precision],
|
|
280
|
+
)
|
|
281
|
+
|
|
225
282
|
const submitValue = useCallback(() => {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
})
|
|
232
|
-
: null
|
|
233
|
-
if (stored === null) {
|
|
234
|
-
// Fallback: a bare number typed in the DISPLAY unit → convert to stored.
|
|
235
|
-
const numValue = Number.parseFloat(inputValue)
|
|
236
|
-
stored = Number.isFinite(numValue) ? toStored(numValue) : null
|
|
237
|
-
}
|
|
238
|
-
if (stored === null) {
|
|
283
|
+
if (!editingRef.current) return
|
|
284
|
+
editingRef.current = false
|
|
285
|
+
const nextValue = parseInputValue(inputValue)
|
|
286
|
+
if (nextValue === null) {
|
|
287
|
+
onCancel?.()
|
|
239
288
|
setInputValue(toDisplay(value).toFixed(precision))
|
|
240
289
|
} else {
|
|
241
|
-
const nextValue = clamp(toStored(Number.parseFloat(toDisplay(stored).toFixed(precision))))
|
|
242
290
|
onChange(nextValue)
|
|
243
291
|
onCommit?.(nextValue)
|
|
244
292
|
}
|
|
245
293
|
setIsEditing(false)
|
|
246
|
-
}, [
|
|
247
|
-
inputValue,
|
|
248
|
-
unit,
|
|
249
|
-
isImperial,
|
|
250
|
-
parseUnit,
|
|
251
|
-
onChange,
|
|
252
|
-
onCommit,
|
|
253
|
-
clamp,
|
|
254
|
-
precision,
|
|
255
|
-
value,
|
|
256
|
-
toDisplay,
|
|
257
|
-
toStored,
|
|
258
|
-
])
|
|
294
|
+
}, [inputValue, parseInputValue, onCancel, onChange, onCommit, toDisplay, value, precision])
|
|
259
295
|
|
|
260
296
|
const spec = lingoUnitSpec(unit)
|
|
261
297
|
const hint =
|
|
@@ -274,8 +310,7 @@ export function SliderControl({
|
|
|
274
310
|
if (e.key === 'Enter') {
|
|
275
311
|
submitValue()
|
|
276
312
|
} else if (e.key === 'Escape') {
|
|
277
|
-
|
|
278
|
-
setIsEditing(false)
|
|
313
|
+
cancelEdit()
|
|
279
314
|
} else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
|
|
280
315
|
e.preventDefault()
|
|
281
316
|
const direction = e.key === 'ArrowUp' ? 1 : -1
|
|
@@ -285,7 +320,7 @@ export function SliderControl({
|
|
|
285
320
|
setInputValue(toDisplay(newV).toFixed(precision))
|
|
286
321
|
}
|
|
287
322
|
},
|
|
288
|
-
[submitValue, value, precision, step, applyDisplayDelta, onChange, toDisplay],
|
|
323
|
+
[submitValue, cancelEdit, value, precision, step, applyDisplayDelta, onChange, toDisplay],
|
|
289
324
|
)
|
|
290
325
|
|
|
291
326
|
const displayValue = toDisplay(shown)
|
|
@@ -309,6 +344,10 @@ export function SliderControl({
|
|
|
309
344
|
onPointerDown={handleLabelPointerDown}
|
|
310
345
|
onPointerMove={handleLabelPointerMove}
|
|
311
346
|
onPointerUp={handleLabelPointerUp}
|
|
347
|
+
onPointerCancel={cancelEdit}
|
|
348
|
+
onLostPointerCapture={() => {
|
|
349
|
+
if (dragRef.current) cancelEdit()
|
|
350
|
+
}}
|
|
312
351
|
ref={labelRef}
|
|
313
352
|
>
|
|
314
353
|
{/* Grip dots — 2×3 grid */}
|
|
@@ -340,7 +379,13 @@ export function SliderControl({
|
|
|
340
379
|
autoFocus
|
|
341
380
|
className="w-14 bg-transparent p-0 text-right font-mono text-foreground outline-none selection:bg-primary/30"
|
|
342
381
|
onBlur={submitValue}
|
|
343
|
-
onChange={(e) =>
|
|
382
|
+
onChange={(e) => {
|
|
383
|
+
setInputValue(e.target.value)
|
|
384
|
+
if (previewWhileTyping) {
|
|
385
|
+
const next = parseInputValue(e.target.value)
|
|
386
|
+
if (next !== null) onChange(next)
|
|
387
|
+
}
|
|
388
|
+
}}
|
|
344
389
|
onKeyDown={handleInputKeyDown}
|
|
345
390
|
type="text"
|
|
346
391
|
value={inputValue}
|