@pascal-app/editor 1.0.3 → 1.0.5
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/README.md +106 -0
- package/package.json +42 -38
- package/src/components/editor/__bench__/room-label-placement.bench.ts +108 -0
- package/src/components/editor/action-menu-button.tsx +83 -0
- package/src/components/editor/bake-thumbnail.tsx +307 -5
- package/src/components/editor/capture-level-picker.tsx +72 -0
- package/src/components/editor/construction-reveal-audio.test.tsx +150 -0
- package/src/components/editor/construction-reveal-audio.tsx +40 -0
- package/src/components/editor/custom-camera-controls.tsx +275 -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 +37 -26
- package/src/components/editor/editor-layout-v2.tsx +132 -59
- package/src/components/editor/export-manager.tsx +47 -26
- package/src/components/editor/first-person/build-collider-world.test.ts +320 -0
- package/src/components/editor/first-person/build-collider-world.ts +28 -7
- package/src/components/editor/first-person/ground-holes.test.ts +128 -0
- package/src/components/editor/first-person/ground-holes.ts +130 -0
- package/src/components/editor/first-person/mechanism-targets.test.ts +162 -0
- package/src/components/editor/first-person/mechanism-targets.ts +88 -0
- package/src/components/editor/first-person-controls.tsx +220 -9
- package/src/components/editor/floating-action-menu.tsx +63 -207
- package/src/components/editor/floor-aware-camera-controls.test.ts +466 -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-mode-coordinator.tsx +2 -2
- package/src/components/editor/floorplan-panel.tsx +414 -363
- package/src/components/editor/follow-pascal.test.tsx +912 -0
- package/src/components/editor/follow-pascal.tsx +354 -0
- package/src/components/editor/footprint-height-handle.tsx +204 -0
- package/src/components/editor/group-actions.ts +56 -2
- package/src/components/editor/group-move-3d.ts +2 -0
- package/src/components/editor/group-rotate-handle.tsx +2 -0
- package/src/components/editor/group-transform-shared.test.ts +460 -0
- 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 +409 -197
- 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/riser-diagram-panel.tsx +14 -7
- 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 +233 -0
- package/src/components/editor/room-labels.test.ts +850 -0
- package/src/components/editor/room-labels.tsx +1890 -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 +474 -419
- package/src/components/editor/slab-hole-highlights.tsx +5 -2
- package/src/components/editor/snapshot-capture-overlay.tsx +7 -2
- package/src/components/editor/snapshot-capture.test.ts +119 -60
- package/src/components/editor/snapshot-capture.ts +36 -5
- package/src/components/editor/thumbnail-generator.test.ts +654 -0
- package/src/components/editor/thumbnail-generator.tsx +1103 -223
- package/src/components/editor/use-floorplan-background-placement.ts +34 -12
- 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 +663 -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.test.tsx +54 -0
- package/src/components/editor/views/view-bar.tsx +343 -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 +8 -4
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
- package/src/components/editor-2d/floorplan-group-move.tsx +3 -0
- 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 +2 -2
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +116 -106
- 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 +319 -233
- package/src/components/editor-2d/renderers/floorplan-reveal.test.tsx +254 -0
- package/src/components/editor-2d/renderers/floorplan-reveal.ts +141 -0
- package/src/components/editor-2d/svg-paths.ts +12 -15
- package/src/components/point-ask/bubble.tsx +538 -0
- package/src/components/point-ask/hover-chip.tsx +91 -0
- package/src/components/point-ask/mode-pill.tsx +92 -0
- package/src/components/point-ask/optional-motion.ts +22 -0
- package/src/components/point-ask/outline-corners.tsx +93 -0
- package/src/components/point-ask/pins.tsx +711 -0
- package/src/components/point-ask/point-ask-layer.tsx +83 -0
- package/src/components/point-ask/point-ask-styles.ts +105 -0
- package/src/components/point-ask/region-marquee-styles.ts +15 -0
- package/src/components/point-ask/region-marquee.tsx +64 -0
- package/src/components/procedural-items/inspector.tsx +40 -15
- package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +12 -2
- 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/roof/roof-edit-system.tsx +1 -0
- 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/distribution/connection-compatibility.test.ts +34 -0
- package/src/components/tools/distribution/connection-compatibility.ts +43 -0
- package/src/components/tools/distribution/distribution-public.ts +9 -0
- package/src/components/tools/distribution/distribution-run-contract.test.ts +26 -0
- package/src/components/tools/distribution/distribution-run-contract.ts +94 -0
- package/src/components/tools/distribution/distribution-run-tool.test.ts +241 -0
- package/src/components/tools/distribution/distribution-run-tool.test.tsx +125 -0
- package/src/components/tools/distribution/distribution-run-tool.tsx +1323 -0
- package/src/components/tools/distribution/draw-alignment.ts +35 -0
- package/src/components/tools/distribution/ports.test.ts +56 -0
- package/src/components/tools/distribution/ports.ts +378 -0
- package/src/components/tools/distribution/run-cursor.test.ts +135 -0
- package/src/components/tools/distribution/run-cursor.ts +36 -0
- package/src/components/tools/distribution/run-direction-feedback.test.ts +60 -0
- package/src/components/tools/distribution/run-direction-feedback.tsx +251 -0
- package/src/components/tools/distribution/run-port-snap.test.ts +79 -0
- package/src/components/tools/distribution/run-port-snap.ts +25 -0
- package/src/components/tools/distribution/run-screen-direction.test.ts +23 -0
- package/src/components/tools/distribution/run-screen-direction.ts +30 -0
- package/src/components/tools/elevator/elevator-tool.tsx +3 -6
- package/src/components/tools/elevator/move-elevator-tool.tsx +2 -3
- package/src/components/tools/item/item-surface-strategy-resolver.test.ts +67 -68
- package/src/components/tools/item/placement-history.test.tsx +512 -0
- package/src/components/tools/item/placement-strategies.test.ts +46 -1
- package/src/components/tools/item/placement-strategies.ts +11 -10
- package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +56 -56
- 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 +148 -95
- 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 +62 -63
- package/src/components/tools/registry/item-surface-move.ts +2 -0
- package/src/components/tools/registry/item-surface-resolver.test.ts +26 -26
- package/src/components/tools/registry/move-registry-node-tool.test.ts +268 -1
- package/src/components/tools/registry/move-registry-node-tool.tsx +66 -30
- 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/tool-manager.tsx +59 -24
- package/src/components/tools/wall/wall-drafting.test.ts +175 -47
- package/src/components/tools/zone/zone-tool.tsx +4 -12
- package/src/components/ui/action-menu/action-button.tsx +9 -1
- package/src/components/ui/action-menu/camera-actions.tsx +9 -4
- package/src/components/ui/action-menu/control-modes.tsx +15 -42
- package/src/components/ui/action-menu/follow-pascal-pill.tsx +32 -0
- package/src/components/ui/action-menu/follow-pascal-toggle.tsx +36 -0
- package/src/components/ui/action-menu/index.tsx +56 -33
- package/src/components/ui/action-menu/measurement-control.tsx +14 -19
- package/src/components/ui/action-menu/placement.ts +20 -0
- package/src/components/ui/action-menu/point-ask-button.tsx +52 -0
- package/src/components/ui/action-menu/view-toggles.tsx +47 -42
- package/src/components/ui/build-panel/build-panel.tsx +242 -0
- package/src/components/ui/command-palette/editor-commands.tsx +7 -42
- package/src/components/ui/command-palette/index.tsx +50 -39
- package/src/components/ui/command-palette/ranked-groups.test.ts +31 -0
- package/src/components/ui/controls/action-button.tsx +5 -2
- 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/metric-control.tsx +1 -1
- package/src/components/ui/controls/scene-material-list.tsx +4 -32
- package/src/components/ui/controls/segmented-control.tsx +21 -8
- package/src/components/ui/controls/select-control.tsx +65 -0
- package/src/components/ui/controls/slider-control.tsx +1 -1
- package/src/components/ui/controls/toggle-control.tsx +3 -3
- package/src/components/ui/controls/tool-options-panel.tsx +1 -1
- package/src/components/ui/floating-level-selector.tsx +159 -59
- package/src/components/ui/helpers/building-helper.tsx +4 -1
- package/src/components/ui/helpers/contextual-helper-panel.tsx +181 -72
- package/src/components/ui/helpers/helper-manager.test.tsx +121 -0
- package/src/components/ui/helpers/helper-manager.tsx +331 -14
- package/src/components/ui/helpers/item-helper.tsx +17 -1
- 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 +28 -22
- 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 +4 -4
- package/src/components/ui/panels/mobile-selection-bar.tsx +8 -5
- 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 +161 -5
- 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 +130 -62
- package/src/components/ui/panels/parametric-field-control.tsx +3 -3
- package/src/components/ui/panels/parametric-inspector.tsx +7 -7
- 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 +790 -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/badge.tsx +8 -0
- package/src/components/ui/primitives/color-dot.tsx +36 -24
- package/src/components/ui/primitives/sidebar.tsx +3 -1
- package/src/components/ui/right-stack.test.ts +29 -0
- package/src/components/ui/right-stack.tsx +164 -0
- package/src/components/ui/sidebar/app-sidebar.tsx +1 -1
- package/src/components/ui/sidebar/icon-rail.tsx +8 -3
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +6 -6
- package/src/components/ui/sidebar/panels/settings-panel/animation-speed-setting.tsx +40 -0
- package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +7 -8
- package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.test.tsx +170 -0
- package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +92 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +138 -27
- package/src/components/ui/sidebar/panels/settings-panel/interface-theme.test.tsx +20 -0
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +5 -5
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +4 -10
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -11
- 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 +181 -223
- 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 +13 -37
- 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 +2 -4
- package/src/components/ui/sidebar/tab-bar.tsx +14 -4
- 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-controls-bar.tsx +18 -6
- package/src/components/viewer/viewer-hover-label.tsx +34 -0
- package/src/components/viewer/viewer-rooms.tsx +191 -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 +215 -43
- package/src/hooks/use-auto-save.ts +60 -9
- package/src/hooks/use-ceiling-events.test.ts +123 -118
- package/src/hooks/use-grid-events.test.ts +105 -105
- package/src/hooks/use-keyboard.test.ts +243 -19
- package/src/hooks/use-keyboard.ts +118 -83
- 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 +71 -0
- package/src/hooks/use-placement-notice.ts +73 -0
- package/src/hooks/use-selected-room.ts +143 -0
- package/src/index.tsx +312 -58
- package/src/lib/__fixtures__/owner-victor-seattle-parking.json +1 -0
- package/src/lib/__fixtures__/owner-villa-aurelia-basement.json +1 -0
- package/src/lib/add-storey.test.ts +177 -0
- package/src/lib/add-storey.ts +59 -0
- package/src/lib/below-grade.test.tsx +171 -0
- package/src/lib/below-grade.ts +133 -0
- package/src/lib/camera-facing.test.ts +24 -0
- package/src/lib/camera-facing.ts +24 -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/continuation.test.ts +19 -1
- package/src/lib/continuation.ts +25 -4
- package/src/lib/direct-manipulation.ts +2 -0
- package/src/lib/door-interaction.ts +14 -0
- package/src/lib/download-project-file.ts +17 -0
- package/src/lib/editor-level-display.test.ts +96 -0
- package/src/lib/editor-level-display.ts +55 -0
- package/src/lib/editor-views.ts +178 -0
- package/src/lib/export-geometry-wait.test.ts +90 -0
- package/src/lib/export-texture-utils.ts +2 -4
- 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 +284 -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 +363 -27
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +177 -8
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +60 -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/pdfkit-standard-fonts.d.ts +5 -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 +124 -0
- package/src/lib/floorplan/sheet-export.ts +318 -0
- package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +768 -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 +230 -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 +157 -0
- package/src/lib/floorplan/site-plan/geometry.test.ts +269 -0
- package/src/lib/floorplan/site-plan/geometry.ts +142 -0
- package/src/lib/floorplan/site-plan/index.ts +55 -0
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +483 -0
- package/src/lib/floorplan/site-plan/site-annotations.ts +1084 -0
- package/src/lib/floorplan/site-plan/site-parts.ts +287 -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 +70 -0
- package/src/lib/floorplan/types.ts +1 -27
- package/src/lib/follow-pascal-exterior.test-fixture.ts +17 -0
- package/src/lib/follow-pascal-exterior.test.ts +171 -0
- package/src/lib/follow-pascal-exterior.ts +39 -0
- package/src/lib/follow-pascal-presentation.test.ts +203 -0
- package/src/lib/follow-pascal-presentation.ts +129 -0
- package/src/lib/follow-pascal-sight.ts +84 -0
- package/src/lib/follow-pascal-targets.test.ts +406 -0
- package/src/lib/follow-pascal-targets.ts +198 -0
- package/src/lib/follow-pascal.test.ts +350 -0
- package/src/lib/follow-pascal.ts +284 -0
- package/src/lib/fresh-planar-placement.test.ts +148 -0
- package/src/lib/fresh-planar-placement.ts +114 -37
- package/src/lib/geometry-script/author.ts +157 -0
- package/src/lib/geometry-script/client.ts +159 -0
- package/src/lib/geometry-script/finish-scripts.ts +127 -0
- package/src/lib/geometry-script/protocol.ts +13 -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 +636 -126
- package/src/lib/glb-export.ts +241 -120
- package/src/lib/history.test.ts +18 -7
- 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 +22 -0
- package/src/lib/keyboard-pan.test.ts +129 -7
- package/src/lib/keyboard-pan.ts +161 -12
- package/src/lib/level-selection.test.ts +57 -0
- package/src/lib/level-selection.ts +16 -1
- 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 +187 -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 +392 -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/measurement-kind.test.ts +6 -8
- package/src/lib/measurements.test.ts +23 -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 +201 -0
- package/src/lib/orbit-floor-clearance.ts +154 -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 +122 -0
- package/src/lib/paint-pass-through.ts +78 -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 +267 -25
- package/src/lib/panel-rows.ts +21 -0
- package/src/lib/parametric-node-update.ts +16 -7
- 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 +65 -0
- package/src/lib/plugin-panels.test.ts +25 -0
- package/src/lib/plugin-panels.ts +94 -0
- package/src/lib/point-ask/__fixtures__/consumer-browser.tsx +76 -0
- package/src/lib/point-ask/__fixtures__/house.ts +81 -0
- package/src/lib/point-ask/ask-created.test.ts +195 -0
- package/src/lib/point-ask/ask-flow.test.ts +380 -0
- package/src/lib/point-ask/build-context.test.ts +173 -0
- package/src/lib/point-ask/build-context.ts +94 -0
- package/src/lib/point-ask/capture-crop.ts +129 -0
- package/src/lib/point-ask/choreography.ts +69 -0
- package/src/lib/point-ask/compare.test.ts +145 -0
- package/src/lib/point-ask/compare.ts +73 -0
- package/src/lib/point-ask/describe-target.test.ts +165 -0
- package/src/lib/point-ask/describe-target.ts +234 -0
- package/src/lib/point-ask/dictation.ts +79 -0
- package/src/lib/point-ask/edge-marker.test.ts +61 -0
- package/src/lib/point-ask/edge-marker.ts +27 -0
- package/src/lib/point-ask/frame-loop.ts +25 -0
- package/src/lib/point-ask/history-marks.test.ts +94 -0
- package/src/lib/point-ask/history-marks.ts +39 -0
- package/src/lib/point-ask/host-api.ts +32 -0
- package/src/lib/point-ask/outline-box.test.ts +30 -0
- package/src/lib/point-ask/outline-box.ts +11 -0
- package/src/lib/point-ask/overlay-metrics.ts +6 -0
- package/src/lib/point-ask/pick.test.ts +220 -0
- package/src/lib/point-ask/pick.ts +121 -0
- package/src/lib/point-ask/pin-machine.test.ts +494 -0
- package/src/lib/point-ask/pin-machine.ts +217 -0
- package/src/lib/point-ask/placement.test.ts +203 -0
- package/src/lib/point-ask/placement.ts +141 -0
- package/src/lib/point-ask/pointer-tracker.ts +34 -0
- package/src/lib/point-ask/projector.ts +83 -0
- package/src/lib/point-ask/region.test.ts +157 -0
- package/src/lib/point-ask/region.ts +136 -0
- package/src/lib/point-ask/retry.test.ts +217 -0
- package/src/lib/point-ask/retry.ts +37 -0
- package/src/lib/point-ask/scene-commit.test.ts +312 -0
- package/src/lib/point-ask/scene-commit.ts +32 -0
- package/src/lib/point-ask/session.ts +1391 -0
- package/src/lib/point-ask/spring-keyframes.test.ts +45 -0
- package/src/lib/point-ask/spring-keyframes.ts +38 -0
- package/src/lib/point-ask/spring.test.ts +63 -0
- package/src/lib/point-ask/spring.ts +107 -0
- package/src/lib/point-ask/suggestions.test.ts +80 -0
- package/src/lib/point-ask/suggestions.ts +125 -0
- package/src/lib/point-ask/target-scope.test.ts +622 -0
- package/src/lib/point-ask/target-scope.ts +338 -0
- package/src/lib/point-ask/types.ts +48 -0
- package/src/lib/portable-export.test.ts +34 -1
- package/src/lib/portable-export.ts +119 -53
- package/src/lib/print-feature-thickness.ts +10 -1
- package/src/lib/print-roof-solids.test.ts +39 -1
- package/src/lib/print-roof-solids.ts +43 -10
- package/src/lib/print-shell-compiler-manifold-core.ts +1 -1
- 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 +265 -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 +1017 -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 +610 -1
- package/src/lib/scene-clipboard.ts +269 -58
- 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 +54 -75
- package/src/lib/session-writes.test.ts +120 -0
- package/src/lib/session-writes.ts +205 -0
- package/src/lib/sfx-bus.ts +13 -0
- package/src/lib/sfx-player.test.ts +70 -9
- package/src/lib/sfx-player.ts +6 -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/stair-levels.test.ts +25 -0
- package/src/lib/stair-levels.ts +22 -3
- package/src/lib/terrace-draft.test.ts +145 -0
- package/src/lib/terrace-draft.ts +214 -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 +580 -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-placement-pointer.test.ts +42 -0
- package/src/lib/wall-placement-pointer.ts +57 -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-changes.ts +35 -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-audio.test.ts +146 -0
- package/src/store/use-audio.tsx +127 -3
- package/src/store/use-construction-reveal.test.ts +173 -0
- package/src/store/use-construction-reveal.ts +145 -0
- package/src/store/use-corner-snap-hint.test.ts +24 -0
- package/src/store/use-corner-snap-hint.ts +47 -0
- 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-follow-pascal.test.ts +62 -0
- package/src/store/use-follow-pascal.ts +53 -0
- package/src/store/use-point-ask.ts +247 -0
- package/src/store/use-quick-measurement-hud.ts +3 -3
- package/src/store/use-stair-build-preview.ts +33 -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 +201 -0
- package/src/store/view-layout.ts +199 -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/render-current-markup.tsx +16 -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/tools/stair/stair-click-guard.test.ts +0 -58
- package/src/components/tools/stair/stair-click-guard.ts +0 -72
- package/src/components/tools/stair/stair-defaults.ts +0 -18
- package/src/components/tools/stair/stair-tool.tsx +0 -634
- 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
|
@@ -34,17 +34,18 @@ export function AudioSettingsDialog() {
|
|
|
34
34
|
<DialogContent className="sm:max-w-[425px]">
|
|
35
35
|
<DialogHeader>
|
|
36
36
|
<DialogTitle>Audio settings</DialogTitle>
|
|
37
|
-
<DialogDescription>
|
|
37
|
+
<DialogDescription>
|
|
38
|
+
Volume levels apply to every tab. Muting applies to this tab only.
|
|
39
|
+
</DialogDescription>
|
|
38
40
|
</DialogHeader>
|
|
39
41
|
<div className="space-y-6 py-4">
|
|
40
42
|
{/* Master Volume */}
|
|
41
43
|
<div className="space-y-2">
|
|
42
44
|
<div className="flex items-center justify-between">
|
|
43
|
-
<label className="font-medium text-sm">Master
|
|
45
|
+
<label className="font-medium text-sm">Master volume</label>
|
|
44
46
|
<span className="text-muted-foreground text-sm">{masterVolume}%</span>
|
|
45
47
|
</div>
|
|
46
48
|
<Slider
|
|
47
|
-
disabled={muted}
|
|
48
49
|
max={100}
|
|
49
50
|
onValueChange={(value) => value[0] !== undefined && setMasterVolume(value[0])}
|
|
50
51
|
step={1}
|
|
@@ -55,11 +56,10 @@ export function AudioSettingsDialog() {
|
|
|
55
56
|
{/* Radio Volume */}
|
|
56
57
|
<div className="space-y-2">
|
|
57
58
|
<div className="flex items-center justify-between">
|
|
58
|
-
<label className="font-medium text-sm">Radio
|
|
59
|
+
<label className="font-medium text-sm">Radio volume</label>
|
|
59
60
|
<span className="text-muted-foreground text-sm">{radioVolume}%</span>
|
|
60
61
|
</div>
|
|
61
62
|
<Slider
|
|
62
|
-
disabled={muted}
|
|
63
63
|
max={100}
|
|
64
64
|
onValueChange={(value) => value[0] !== undefined && setRadioVolume(value[0])}
|
|
65
65
|
step={1}
|
|
@@ -70,11 +70,10 @@ export function AudioSettingsDialog() {
|
|
|
70
70
|
{/* SFX Volume */}
|
|
71
71
|
<div className="space-y-2">
|
|
72
72
|
<div className="flex items-center justify-between">
|
|
73
|
-
<label className="font-medium text-sm">Sound
|
|
73
|
+
<label className="font-medium text-sm">Sound effects</label>
|
|
74
74
|
<span className="text-muted-foreground text-sm">{sfxVolume}%</span>
|
|
75
75
|
</div>
|
|
76
76
|
<Slider
|
|
77
|
-
disabled={muted}
|
|
78
77
|
max={100}
|
|
79
78
|
onValueChange={(value) => value[0] !== undefined && setSfxVolume(value[0])}
|
|
80
79
|
step={1}
|
|
@@ -90,7 +89,7 @@ export function AudioSettingsDialog() {
|
|
|
90
89
|
variant={muted ? 'default' : 'outline'}
|
|
91
90
|
>
|
|
92
91
|
{muted ? <VolumeX className="size-4" /> : <Volume2 className="size-4" />}
|
|
93
|
-
{muted ? 'Unmute
|
|
92
|
+
{muted ? 'Unmute this tab' : 'Mute this tab'}
|
|
94
93
|
</Button>
|
|
95
94
|
</div>
|
|
96
95
|
</div>
|
package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.test.tsx
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from 'bun:test'
|
|
2
|
+
import {
|
|
3
|
+
type AnyNodeDefinition,
|
|
4
|
+
BaseNode,
|
|
5
|
+
loadPlugin,
|
|
6
|
+
nodeRegistry,
|
|
7
|
+
nodeType,
|
|
8
|
+
objectId,
|
|
9
|
+
useScene,
|
|
10
|
+
} from '@pascal-app/core'
|
|
11
|
+
import type { ConstructionRevealLevel } from '@pascal-app/viewer'
|
|
12
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
13
|
+
import useConstructionReveal, {
|
|
14
|
+
chooseConstructionLevel,
|
|
15
|
+
FRAMING_KIND,
|
|
16
|
+
resolveConstructionLevel,
|
|
17
|
+
} from '../../../../../store/use-construction-reveal'
|
|
18
|
+
import { AnimationSpeedSetting } from './animation-speed-setting'
|
|
19
|
+
import {
|
|
20
|
+
ConstructionAnimationSetting,
|
|
21
|
+
constructionLevelOptions,
|
|
22
|
+
FRAMING_NEEDS_ARCHITECT,
|
|
23
|
+
} from './construction-animation-setting'
|
|
24
|
+
|
|
25
|
+
// The framing plugin carries the Framing level: without it in the project the choice is greyed out with the
|
|
26
|
+
// reason as its tip, and the level that plays is Full.
|
|
27
|
+
|
|
28
|
+
const framing = {
|
|
29
|
+
kind: FRAMING_KIND,
|
|
30
|
+
schemaVersion: 1,
|
|
31
|
+
schema: BaseNode.extend({ id: objectId('framing'), type: nodeType(FRAMING_KIND) }),
|
|
32
|
+
category: 'structure',
|
|
33
|
+
defaults: () => ({}),
|
|
34
|
+
capabilities: {},
|
|
35
|
+
geometry: () => null,
|
|
36
|
+
} as unknown as AnyNodeDefinition
|
|
37
|
+
|
|
38
|
+
let restore: () => void = () => {}
|
|
39
|
+
beforeAll(async () => {
|
|
40
|
+
restore = nodeRegistry._snapshot()
|
|
41
|
+
nodeRegistry._reset()
|
|
42
|
+
await loadPlugin({ id: 'fixture:framing', apiVersion: 1, nodes: [framing] })
|
|
43
|
+
})
|
|
44
|
+
afterAll(() => restore())
|
|
45
|
+
|
|
46
|
+
// A server render reads a store's initial state, so the state to draw is put there.
|
|
47
|
+
const sceneInitial = useScene.getInitialState() as { installedPlugins: string[] }
|
|
48
|
+
const revealInitial = useConstructionReveal.getInitialState() as {
|
|
49
|
+
level: string
|
|
50
|
+
chosen: boolean
|
|
51
|
+
speed: number
|
|
52
|
+
}
|
|
53
|
+
const was = { plugins: sceneInitial.installedPlugins, ...revealInitial }
|
|
54
|
+
|
|
55
|
+
function project(options: { installed: boolean; level?: string; chosen?: boolean; speed?: number }) {
|
|
56
|
+
sceneInitial.installedPlugins = options.installed ? ['fixture:framing'] : []
|
|
57
|
+
revealInitial.level = options.level ?? 'full'
|
|
58
|
+
revealInitial.chosen = options.chosen ?? false
|
|
59
|
+
revealInitial.speed = options.speed ?? 1
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
beforeEach(() => project({ installed: false }))
|
|
63
|
+
afterEach(() => {
|
|
64
|
+
sceneInitial.installedPlugins = was.plugins
|
|
65
|
+
Object.assign(revealInitial, { level: was.level, chosen: was.chosen, speed: was.speed })
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const optionOf = (markup: string, label: string) => {
|
|
69
|
+
const match = new RegExp(`<button[^>]*>(?:(?!</button>).)*${label}(?:(?!</button>).)*</button>`).exec(
|
|
70
|
+
markup,
|
|
71
|
+
)
|
|
72
|
+
return match?.[0] ?? ''
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
describe('the levels the control offers', () => {
|
|
76
|
+
test('without the plugin, Framing is disabled and its tip says it needs the Architect plugin', () => {
|
|
77
|
+
const framingOption = constructionLevelOptions(false).find((o) => o.value === 'framing')!
|
|
78
|
+
expect(framingOption.disabled).toBe(true)
|
|
79
|
+
expect(framingOption.title).toBe(FRAMING_NEEDS_ARCHITECT)
|
|
80
|
+
expect(FRAMING_NEEDS_ARCHITECT).toBe('Framing needs the Architect plugin.')
|
|
81
|
+
for (const option of constructionLevelOptions(false).filter((o) => o.value !== 'framing'))
|
|
82
|
+
expect(option.disabled).toBe(false)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test('with the plugin, every level is on offer', () => {
|
|
86
|
+
expect(constructionLevelOptions(true).every((option) => option.disabled === false)).toBe(true)
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
describe('the construction animation control, drawn', () => {
|
|
91
|
+
test('without the plugin: Framing is greyed out and Full is the level that plays', () => {
|
|
92
|
+
project({ installed: false })
|
|
93
|
+
const markup = renderToStaticMarkup(<ConstructionAnimationSetting />)
|
|
94
|
+
expect(optionOf(markup, 'Framing')).toContain('aria-disabled="true"')
|
|
95
|
+
expect(optionOf(markup, 'Full')).toContain('aria-pressed="true"')
|
|
96
|
+
expect(optionOf(markup, 'Full')).not.toContain('aria-disabled')
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('with the plugin and no choice of the person’s own: Framing plays, nothing is greyed', () => {
|
|
100
|
+
project({ installed: true })
|
|
101
|
+
const markup = renderToStaticMarkup(<ConstructionAnimationSetting />)
|
|
102
|
+
expect(optionOf(markup, 'Framing')).toContain('aria-pressed="true"')
|
|
103
|
+
expect(markup).not.toContain('aria-disabled')
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('with the plugin and a choice of Full: Full is kept', () => {
|
|
107
|
+
project({ installed: true, level: 'full', chosen: true })
|
|
108
|
+
const markup = renderToStaticMarkup(<ConstructionAnimationSetting />)
|
|
109
|
+
expect(optionOf(markup, 'Full')).toContain('aria-pressed="true"')
|
|
110
|
+
expect(optionOf(markup, 'Framing')).toContain('aria-pressed="false"')
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test('a saved choice of Framing falls back to Full while the plugin is gone, greyed out', () => {
|
|
114
|
+
project({ installed: false, level: 'framing', chosen: true })
|
|
115
|
+
const markup = renderToStaticMarkup(<ConstructionAnimationSetting />)
|
|
116
|
+
expect(optionOf(markup, 'Full')).toContain('aria-pressed="true"')
|
|
117
|
+
expect(optionOf(markup, 'Framing')).toContain('aria-disabled="true"')
|
|
118
|
+
expect(optionOf(markup, 'Framing')).toContain('aria-pressed="false"')
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
describe('clicking the level that plays', () => {
|
|
123
|
+
// A saved Framing plays as Full while the plugin is gone; the control shows Full pressed, and a click on
|
|
124
|
+
// the pressed option is still a click. It must not turn the kept Framing into a chosen Full.
|
|
125
|
+
const click = (level: ConstructionRevealLevel, installed: boolean) =>
|
|
126
|
+
chooseConstructionLevel(level, installed)
|
|
127
|
+
|
|
128
|
+
beforeEach(() => useConstructionReveal.setState({ level: 'framing', chosen: true }))
|
|
129
|
+
afterEach(() => useConstructionReveal.setState({ level: 'full', chosen: false }))
|
|
130
|
+
|
|
131
|
+
test('the Full shown for a saved Framing keeps the Framing when it is clicked, and it plays again with the plugin', () => {
|
|
132
|
+
click('full', false)
|
|
133
|
+
expect(useConstructionReveal.getState()).toMatchObject({ level: 'framing', chosen: true })
|
|
134
|
+
expect(resolveConstructionLevel(useConstructionReveal.getState(), true)).toBe('framing')
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
test('another level is a choice and replaces it', () => {
|
|
138
|
+
click('simple', false)
|
|
139
|
+
expect(useConstructionReveal.getState()).toMatchObject({ level: 'simple', chosen: true })
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test('with no choice of the person’s own, a click on the level that plays is a choice of it', () => {
|
|
143
|
+
useConstructionReveal.setState({ level: 'full', chosen: false })
|
|
144
|
+
click('full', false)
|
|
145
|
+
expect(useConstructionReveal.getState()).toMatchObject({ level: 'full', chosen: true })
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
describe('the Pro badge', () => {
|
|
150
|
+
// The plugin that draws the framing is a Pro one: the option carries the plugin manager's mark,
|
|
151
|
+
// greyed out or not, and no other level does.
|
|
152
|
+
test('Framing carries it, greyed out or on offer; the other levels do not', () => {
|
|
153
|
+
for (const installed of [false, true]) {
|
|
154
|
+
project({ installed })
|
|
155
|
+
const markup = renderToStaticMarkup(<ConstructionAnimationSetting />)
|
|
156
|
+
expect(optionOf(markup, 'Framing')).toMatch(/>Pro</)
|
|
157
|
+
for (const label of ['Off', 'Simple', 'Full']) expect(optionOf(markup, label)).not.toMatch(/>Pro</)
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
describe('the animation speed control, drawn', () => {
|
|
163
|
+
test('offers the five steps and shows the saved one', () => {
|
|
164
|
+
project({ installed: true, speed: 1.5 })
|
|
165
|
+
const markup = renderToStaticMarkup(<AnimationSpeedSetting />)
|
|
166
|
+
for (const label of ['×0.5', '×0.75', '×1', '×1.5', '×2']) expect(markup).toContain(label)
|
|
167
|
+
expect(optionOf(markup, '×1.5')).toContain('aria-pressed="true"')
|
|
168
|
+
expect(optionOf(markup, '×0.5')).toContain('aria-pressed="false"')
|
|
169
|
+
})
|
|
170
|
+
})
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { ConstructionRevealLevel } from '@pascal-app/viewer'
|
|
4
|
+
import {
|
|
5
|
+
chooseConstructionLevel,
|
|
6
|
+
useConstructionRevealLevel,
|
|
7
|
+
useFramingAvailable,
|
|
8
|
+
} from '../../../../../store/use-construction-reveal'
|
|
9
|
+
import { SegmentedControl } from '../../../controls/segmented-control'
|
|
10
|
+
import { Badge } from '../../../primitives/badge'
|
|
11
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from '../../../primitives/tooltip'
|
|
12
|
+
|
|
13
|
+
/** Why Framing is greyed out. */
|
|
14
|
+
export const FRAMING_NEEDS_ARCHITECT = 'Framing needs the Architect plugin.'
|
|
15
|
+
|
|
16
|
+
const OPTIONS: { label: string; value: ConstructionRevealLevel; title: string; badge?: string }[] = [
|
|
17
|
+
{ label: 'Off', value: 'off', title: 'What the AI builds appears at once.' },
|
|
18
|
+
{
|
|
19
|
+
label: 'Simple',
|
|
20
|
+
value: 'simple',
|
|
21
|
+
title: 'Walls rise and the rest grows in, piece by piece. No falls, no dust, no sound.',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
label: 'Full',
|
|
25
|
+
value: 'full',
|
|
26
|
+
title: 'Pieces drop into place with dust, the roof assembles, one sound per phase.',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
label: 'Framing',
|
|
30
|
+
value: 'framing',
|
|
31
|
+
title: 'Full, plus the framing members dropping in first, where the framing plugin draws them.',
|
|
32
|
+
// The plugin that draws the framing is a Pro one.
|
|
33
|
+
badge: 'Pro',
|
|
34
|
+
},
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The four levels as the control offers them: Framing is greyed out, with the reason as its tip,
|
|
39
|
+
* where the project has no plugin that draws the framing.
|
|
40
|
+
*/
|
|
41
|
+
export function constructionLevelOptions(framingAvailable: boolean) {
|
|
42
|
+
return OPTIONS.map((option) =>
|
|
43
|
+
option.value === 'framing' && !framingAvailable
|
|
44
|
+
? { ...option, title: FRAMING_NEEDS_ARCHITECT, disabled: true }
|
|
45
|
+
: { ...option, disabled: false },
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** How much of an AI build plays as a construction. */
|
|
50
|
+
export function ConstructionAnimationSetting() {
|
|
51
|
+
// What plays: the person's choice, or Framing where the framing plugin is installed and Full where it is not.
|
|
52
|
+
const level = useConstructionRevealLevel()
|
|
53
|
+
const framingAvailable = useFramingAvailable()
|
|
54
|
+
const current = OPTIONS.find((option) => option.value === level)
|
|
55
|
+
const options = constructionLevelOptions(framingAvailable).map((option) => {
|
|
56
|
+
const label = option.badge ? (
|
|
57
|
+
<span className="inline-flex items-center gap-1.5">
|
|
58
|
+
{option.label}
|
|
59
|
+
<Badge label={option.badge} />
|
|
60
|
+
</span>
|
|
61
|
+
) : (
|
|
62
|
+
option.label
|
|
63
|
+
)
|
|
64
|
+
if (!option.disabled) return { ...option, label }
|
|
65
|
+
return {
|
|
66
|
+
...option,
|
|
67
|
+
title: undefined,
|
|
68
|
+
label: (
|
|
69
|
+
<Tooltip>
|
|
70
|
+
<TooltipTrigger asChild>
|
|
71
|
+
<span>{label}</span>
|
|
72
|
+
</TooltipTrigger>
|
|
73
|
+
<TooltipContent>{option.title}</TooltipContent>
|
|
74
|
+
</Tooltip>
|
|
75
|
+
),
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
return (
|
|
79
|
+
<div className="space-y-2 py-1">
|
|
80
|
+
<div className="space-y-0.5">
|
|
81
|
+
<p className="font-medium text-sm">Construction animation</p>
|
|
82
|
+
<p className="text-muted-foreground text-xs">{current?.title}</p>
|
|
83
|
+
</div>
|
|
84
|
+
<SegmentedControl
|
|
85
|
+
aria-label="Construction animation"
|
|
86
|
+
onChange={(next) => chooseConstructionLevel(next, framingAvailable)}
|
|
87
|
+
options={options}
|
|
88
|
+
value={level}
|
|
89
|
+
/>
|
|
90
|
+
</div>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { downloadProjectFile } from '../../../../../lib/download-project-file'
|
|
1
2
|
import {
|
|
3
|
+
getProjectFileSink,
|
|
2
4
|
clearSceneHistory,
|
|
3
5
|
DEFAULT_LEVEL_HEIGHT,
|
|
4
6
|
emitter,
|
|
7
|
+
getArtifactStore,
|
|
5
8
|
getLevelDisplayName,
|
|
6
9
|
LevelNode,
|
|
7
10
|
type AnyNodeId,
|
|
@@ -25,9 +28,12 @@ import {
|
|
|
25
28
|
Layers,
|
|
26
29
|
ListTree,
|
|
27
30
|
Map as MapIcon,
|
|
31
|
+
Monitor,
|
|
32
|
+
Moon,
|
|
28
33
|
Save,
|
|
29
34
|
ChevronDown,
|
|
30
35
|
Send,
|
|
36
|
+
Sun,
|
|
31
37
|
Trash2,
|
|
32
38
|
Upload,
|
|
33
39
|
} from 'lucide-react'
|
|
@@ -54,6 +60,7 @@ import {
|
|
|
54
60
|
sendGlbToLocalApp,
|
|
55
61
|
waitForLocalImport,
|
|
56
62
|
} from '../../../../../lib/send-to-app'
|
|
63
|
+
import { SegmentedControl } from './../../../../../components/ui/controls/segmented-control'
|
|
57
64
|
import { Button } from './../../../../../components/ui/primitives/button'
|
|
58
65
|
import {
|
|
59
66
|
Dialog,
|
|
@@ -64,11 +71,16 @@ import {
|
|
|
64
71
|
import { Input } from './../../../../../components/ui/primitives/input'
|
|
65
72
|
import { Switch } from './../../../../../components/ui/primitives/switch'
|
|
66
73
|
import { cn } from './../../../../../lib/utils'
|
|
74
|
+
import { refusedObjectsNotice } from './../../../../../components/editor/group-actions'
|
|
75
|
+
import { addStoreyBelow, addStoreyOnTop } from './../../../../../lib/add-storey'
|
|
67
76
|
import { deleteLevelWithFallbackSelection } from './../../../../../lib/level-selection'
|
|
77
|
+
import { bringBuildArtifacts } from './../../../../../lib/scene-clipboard'
|
|
68
78
|
import useEditor, { selectDefaultBuildingAndLevel } from './../../../../../store/use-editor'
|
|
69
79
|
import useFloorplanMode from './../../../../../store/use-floorplan-mode'
|
|
70
80
|
import { type SendToAppStep, useSendToApp } from './../../../../../store/use-send-to-app'
|
|
71
81
|
import { AudioSettingsDialog } from './audio-settings-dialog'
|
|
82
|
+
import { AnimationSpeedSetting } from './animation-speed-setting'
|
|
83
|
+
import { ConstructionAnimationSetting } from './construction-animation-setting'
|
|
72
84
|
import { KeyboardShortcutsDialog } from './keyboard-shortcuts-dialog'
|
|
73
85
|
import { LoadBuildDialog, type PendingImport } from './load-build-dialog'
|
|
74
86
|
import { PrintExportButton } from './print-export-button'
|
|
@@ -101,6 +113,7 @@ const MODEL_EXPORT_FORMATS = [
|
|
|
101
113
|
{ format: 'usdz', label: 'USDZ' },
|
|
102
114
|
{ format: 'stl', label: 'STL' },
|
|
103
115
|
{ format: 'obj', label: 'OBJ' },
|
|
116
|
+
{ format: 'ifc', label: 'IFC' },
|
|
104
117
|
] as const
|
|
105
118
|
|
|
106
119
|
type ModelExportFormat = (typeof MODEL_EXPORT_FORMATS)[number]['format']
|
|
@@ -271,7 +284,8 @@ function SettingsSubheading({ children }: { children: ReactNode }) {
|
|
|
271
284
|
return <h4 className="font-medium text-muted-foreground text-xs">{children}</h4>
|
|
272
285
|
}
|
|
273
286
|
|
|
274
|
-
|
|
287
|
+
/** One on/off preference: a label, a line on what it does, a switch. Hosts use it for their rows. */
|
|
288
|
+
export function SettingsSwitchRow({
|
|
275
289
|
checked,
|
|
276
290
|
description,
|
|
277
291
|
label,
|
|
@@ -296,6 +310,65 @@ function SettingsSwitchRow({
|
|
|
296
310
|
)
|
|
297
311
|
}
|
|
298
312
|
|
|
313
|
+
export type InterfaceTheme = 'system' | 'light' | 'dark'
|
|
314
|
+
|
|
315
|
+
const INTERFACE_THEME_OPTIONS: { label: ReactNode; value: InterfaceTheme }[] = [
|
|
316
|
+
{
|
|
317
|
+
label: (
|
|
318
|
+
<>
|
|
319
|
+
<Monitor className="size-3.5" />
|
|
320
|
+
System
|
|
321
|
+
</>
|
|
322
|
+
),
|
|
323
|
+
value: 'system',
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
label: (
|
|
327
|
+
<>
|
|
328
|
+
<Sun className="size-3.5" />
|
|
329
|
+
Light
|
|
330
|
+
</>
|
|
331
|
+
),
|
|
332
|
+
value: 'light',
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
label: (
|
|
336
|
+
<>
|
|
337
|
+
<Moon className="size-3.5" />
|
|
338
|
+
Dark
|
|
339
|
+
</>
|
|
340
|
+
),
|
|
341
|
+
value: 'dark',
|
|
342
|
+
},
|
|
343
|
+
]
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* The interface theme as a System / Light / Dark row. The editor applies no theme itself: its
|
|
347
|
+
* chrome follows the host's `.dark` class, so a host that owns the setting passes this row.
|
|
348
|
+
*/
|
|
349
|
+
export function InterfaceThemeRow({
|
|
350
|
+
onChange,
|
|
351
|
+
value,
|
|
352
|
+
}: {
|
|
353
|
+
onChange: (value: InterfaceTheme) => void
|
|
354
|
+
value: InterfaceTheme
|
|
355
|
+
}) {
|
|
356
|
+
const labelId = useId()
|
|
357
|
+
return (
|
|
358
|
+
<div className="flex flex-col gap-2 py-1 @sm:flex-row @sm:items-center @sm:justify-between @sm:gap-4">
|
|
359
|
+
<div className="min-w-0 space-y-0.5">
|
|
360
|
+
<p className="font-medium text-sm" id={labelId}>
|
|
361
|
+
Interface theme
|
|
362
|
+
</p>
|
|
363
|
+
<p className="text-muted-foreground text-xs">System follows your device’s appearance.</p>
|
|
364
|
+
</div>
|
|
365
|
+
<div aria-labelledby={labelId} className="w-full shrink-0 @sm:w-60" role="group">
|
|
366
|
+
<SegmentedControl onChange={onChange} options={INTERFACE_THEME_OPTIONS} value={value} />
|
|
367
|
+
</div>
|
|
368
|
+
</div>
|
|
369
|
+
)
|
|
370
|
+
}
|
|
371
|
+
|
|
299
372
|
export interface SettingsPanelProps {
|
|
300
373
|
/** Merged onto the scrolling root so hosts can restyle padding (e.g. inside a dialog). */
|
|
301
374
|
className?: string
|
|
@@ -307,6 +380,11 @@ export interface SettingsPanelProps {
|
|
|
307
380
|
field: 'isPrivate' | 'showScansPublic' | 'showGuidesPublic',
|
|
308
381
|
value: boolean,
|
|
309
382
|
) => Promise<void>
|
|
383
|
+
/**
|
|
384
|
+
* Rows a host adds under Preferences, for its own features and settings (SettingsSwitchRow,
|
|
385
|
+
* InterfaceThemeRow).
|
|
386
|
+
*/
|
|
387
|
+
preferences?: ReactNode
|
|
310
388
|
}
|
|
311
389
|
|
|
312
390
|
function FloorSettingsSection() {
|
|
@@ -342,20 +420,14 @@ function FloorSettingsSection() {
|
|
|
342
420
|
|
|
343
421
|
if (!building) return null
|
|
344
422
|
|
|
345
|
-
const
|
|
346
|
-
const newLevel =
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
})
|
|
352
|
-
createNode(newLevel, building.id)
|
|
423
|
+
const addFloor = () => {
|
|
424
|
+
const newLevel = addStoreyOnTop(building.id)
|
|
425
|
+
setSelection({ buildingId: building.id, levelId: newLevel.id })
|
|
426
|
+
}
|
|
427
|
+
const addBasement = () => {
|
|
428
|
+
const newLevel = addStoreyBelow(building.id)
|
|
353
429
|
setSelection({ buildingId: building.id, levelId: newLevel.id })
|
|
354
430
|
}
|
|
355
|
-
|
|
356
|
-
const addFloor = () =>
|
|
357
|
-
addLevel(levels.length === 0 ? 0 : Math.max(0, ...levels.map((level) => level.level)) + 1)
|
|
358
|
-
const addBasement = () => addLevel(Math.min(0, ...levels.map((level) => level.level)) - 1)
|
|
359
431
|
|
|
360
432
|
return (
|
|
361
433
|
<div className="space-y-3">
|
|
@@ -431,6 +503,7 @@ export function SettingsPanel({
|
|
|
431
503
|
projectName,
|
|
432
504
|
projectVisibility,
|
|
433
505
|
onVisibilityChange,
|
|
506
|
+
preferences,
|
|
434
507
|
}: SettingsPanelProps = {}) {
|
|
435
508
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
436
509
|
const copyResetTimeoutRef = useRef<number | null>(null)
|
|
@@ -540,24 +613,32 @@ export function SettingsPanel({
|
|
|
540
613
|
// Materials ride along: nodes reference them by `scene:<id>` slot
|
|
541
614
|
// refs, so a save without the table produces a file whose custom
|
|
542
615
|
// finishes revert to defaults on the very Load Build path below.
|
|
543
|
-
|
|
616
|
+
// The project rides along too: a scripted object's artifacts live there, and
|
|
617
|
+
// loading the file into another project copies them over as a paste does.
|
|
618
|
+
const sceneData = {
|
|
619
|
+
nodes,
|
|
620
|
+
rootNodeIds,
|
|
621
|
+
installedPlugins,
|
|
622
|
+
materials,
|
|
623
|
+
collections,
|
|
624
|
+
projectId: useViewer.getState().projectId,
|
|
625
|
+
}
|
|
544
626
|
const json = JSON.stringify(sceneData, null, 2)
|
|
545
627
|
const blob = new Blob([json], { type: 'application/json' })
|
|
546
|
-
const url = URL.createObjectURL(blob)
|
|
547
|
-
const link = document.createElement('a')
|
|
548
|
-
link.href = url
|
|
549
628
|
const date = new Date().toISOString().split('T')[0]
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
629
|
+
setModelExportError(null)
|
|
630
|
+
void downloadProjectFile(blob, `layout_${date}.json`).catch((error: unknown) => {
|
|
631
|
+
setModelExportError(error instanceof Error ? error.message : 'Could not save the file')
|
|
632
|
+
})
|
|
553
633
|
}
|
|
554
634
|
|
|
555
635
|
const handleFileLoad = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
556
636
|
const file = e.target.files?.[0]
|
|
557
637
|
if (!file) return
|
|
558
638
|
|
|
639
|
+
const fileSink = getProjectFileSink()
|
|
559
640
|
const reader = new FileReader()
|
|
560
|
-
reader.onload = (event) => {
|
|
641
|
+
reader.onload = async (event) => {
|
|
561
642
|
const text = event.target?.result as string
|
|
562
643
|
let parsed: unknown
|
|
563
644
|
try {
|
|
@@ -584,10 +665,20 @@ export function SettingsPanel({
|
|
|
584
665
|
})
|
|
585
666
|
return
|
|
586
667
|
}
|
|
668
|
+
const result = validateBuildJson(parsed)
|
|
669
|
+
if (result.ok) {
|
|
670
|
+
try {
|
|
671
|
+
await fileSink?.({ blob: file, name: file.name })
|
|
672
|
+
} catch (error) {
|
|
673
|
+
setModelExportError(error instanceof Error ? error.message : 'Could not save the source file')
|
|
674
|
+
return
|
|
675
|
+
}
|
|
676
|
+
if (fileSink !== getProjectFileSink()) return
|
|
677
|
+
}
|
|
587
678
|
setPendingImport({
|
|
588
679
|
fileName: file.name,
|
|
589
680
|
fileSizeBytes: file.size,
|
|
590
|
-
result
|
|
681
|
+
result,
|
|
591
682
|
})
|
|
592
683
|
}
|
|
593
684
|
reader.readAsText(file)
|
|
@@ -596,7 +687,20 @@ export function SettingsPanel({
|
|
|
596
687
|
e.target.value = ''
|
|
597
688
|
}
|
|
598
689
|
|
|
599
|
-
const handleConfirmImport = (
|
|
690
|
+
const handleConfirmImport = async (file: ParsedBuildJson) => {
|
|
691
|
+
const targetProjectId = useViewer.getState().projectId
|
|
692
|
+
const artifactStore = getArtifactStore()
|
|
693
|
+
const { build: parsed, refusedIds, refusal } = await bringBuildArtifacts(file)
|
|
694
|
+
if (
|
|
695
|
+
targetProjectId !== useViewer.getState().projectId ||
|
|
696
|
+
artifactStore !== getArtifactStore()
|
|
697
|
+
)
|
|
698
|
+
return
|
|
699
|
+
if (refusal) {
|
|
700
|
+
useFloorplanMode
|
|
701
|
+
.getState()
|
|
702
|
+
.showNotice(refusedObjectsNotice(refusedIds.length, refusal, 'loaded'))
|
|
703
|
+
}
|
|
600
704
|
const currentScene = useScene.getState()
|
|
601
705
|
setScene(
|
|
602
706
|
parsed.nodes as Parameters<typeof setScene>[0],
|
|
@@ -626,7 +730,7 @@ export function SettingsPanel({
|
|
|
626
730
|
// old scene (or land on the empty intermediate `unloadScene` state).
|
|
627
731
|
clearSceneHistory()
|
|
628
732
|
resetSelection()
|
|
629
|
-
setPhase('
|
|
733
|
+
setPhase('building')
|
|
630
734
|
selectDefaultBuildingAndLevel()
|
|
631
735
|
}
|
|
632
736
|
|
|
@@ -688,6 +792,7 @@ export function SettingsPanel({
|
|
|
688
792
|
setModelExportWarning(null)
|
|
689
793
|
try {
|
|
690
794
|
const artifact = await modelExport(format, {
|
|
795
|
+
projectName,
|
|
691
796
|
onlyVisible: exportOnlyVisible,
|
|
692
797
|
excludedNodeTypes,
|
|
693
798
|
includedPresentationIds:
|
|
@@ -903,7 +1008,7 @@ export function SettingsPanel({
|
|
|
903
1008
|
<legend className="px-1 font-medium text-sm">Include in file</legend>
|
|
904
1009
|
<p className="text-muted-foreground text-xs">
|
|
905
1010
|
Choose which procedural content is baked into model files. GLB and USDZ use the
|
|
906
|
-
textured portable path; STL and
|
|
1011
|
+
textured portable path; STL, OBJ and IFC remain geometry-only.
|
|
907
1012
|
</p>
|
|
908
1013
|
{exportableNodeTypes.length > 0 ? (
|
|
909
1014
|
<div className="space-y-2 pt-1">
|
|
@@ -1157,7 +1262,13 @@ export function SettingsPanel({
|
|
|
1157
1262
|
/>
|
|
1158
1263
|
</SettingsSection>
|
|
1159
1264
|
|
|
1160
|
-
<SettingsSection
|
|
1265
|
+
<SettingsSection
|
|
1266
|
+
description="Sound levels, the construction animation and keyboard shortcuts."
|
|
1267
|
+
title="Preferences"
|
|
1268
|
+
>
|
|
1269
|
+
<ConstructionAnimationSetting />
|
|
1270
|
+
<AnimationSpeedSetting />
|
|
1271
|
+
{preferences}
|
|
1161
1272
|
<div className="grid gap-2 @sm:grid-cols-2">
|
|
1162
1273
|
<AudioSettingsDialog />
|
|
1163
1274
|
<KeyboardShortcutsDialog />
|
|
@@ -1177,7 +1288,7 @@ export function SettingsPanel({
|
|
|
1177
1288
|
</Button>
|
|
1178
1289
|
</div>
|
|
1179
1290
|
</DialogTrigger>
|
|
1180
|
-
<DialogContent className="h-[80vh] max-w-[95vw] gap-0 overflow-hidden border-0 bg-[#1e1e1e] p-0 shadow-none sm:max-w-5xl">
|
|
1291
|
+
<DialogContent className="dark h-[80vh] max-w-[95vw] gap-0 overflow-hidden border-0 bg-[#1e1e1e] p-0 shadow-none sm:max-w-5xl">
|
|
1181
1292
|
<DialogTitle className="sr-only">Scene Graph</DialogTitle>
|
|
1182
1293
|
<div
|
|
1183
1294
|
className="flex h-full min-h-0 w-full min-w-0 *:h-full *:w-full *:overflow-y-auto"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
3
|
+
import { InterfaceThemeRow, SettingsPanel } from '.'
|
|
4
|
+
|
|
5
|
+
const pressedLabels = (html: string) =>
|
|
6
|
+
[...html.matchAll(/aria-pressed="true"[^>]*>(?:<[^>]+>)*([^<]+)/g)].map((match) => match[1])
|
|
7
|
+
|
|
8
|
+
describe('Interface theme setting', () => {
|
|
9
|
+
test("the row shows the host's value pressed", () => {
|
|
10
|
+
const html = renderToStaticMarkup(<InterfaceThemeRow onChange={() => {}} value="light" />)
|
|
11
|
+
expect(html).toContain('Interface theme')
|
|
12
|
+
expect(pressedLabels(html)).toEqual(['Light'])
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test('Settings shows the row a host passes, and none without it', () => {
|
|
16
|
+
const row = <InterfaceThemeRow onChange={() => {}} value="system" />
|
|
17
|
+
expect(renderToStaticMarkup(<SettingsPanel preferences={row} />)).toContain('Interface theme')
|
|
18
|
+
expect(renderToStaticMarkup(<SettingsPanel />)).not.toContain('Interface theme')
|
|
19
|
+
})
|
|
20
|
+
})
|
|
@@ -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',
|