@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
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import {
|
|
2
|
+
emitter,
|
|
3
|
+
type SnapshotCapturedEvent,
|
|
4
|
+
type SnapshotCaptureFailedEvent,
|
|
5
|
+
} from '@pascal-app/core'
|
|
6
|
+
import { useViewer } from '@pascal-app/viewer'
|
|
7
|
+
import { canvasRect } from './projector'
|
|
8
|
+
import type { Rect } from './types'
|
|
9
|
+
|
|
10
|
+
// The picture an ask carries as evidence (spec 2.4): the targets' screen box padded 25%, at least
|
|
11
|
+
// 512 px on its long side, rendered by the snapshot pipeline as an ephemeral frame so nothing is
|
|
12
|
+
// stored. Started at pointer-down, so it is ready by the time the person has typed.
|
|
13
|
+
|
|
14
|
+
const CROP_LONG_SIDE_MIN = 512
|
|
15
|
+
const CROP_LONG_SIDE_MAX = 768
|
|
16
|
+
const CAPTURE_TIMEOUT_MS = 12_000
|
|
17
|
+
|
|
18
|
+
export type CapturedCrop = { dataUrl: string; width: number; height: number }
|
|
19
|
+
|
|
20
|
+
/** The target's box padded by `pad` of its size on every side, and kept inside the canvas. */
|
|
21
|
+
export function paddedCropRect(
|
|
22
|
+
box: Rect,
|
|
23
|
+
canvas: { width: number; height: number },
|
|
24
|
+
pad = 0.25,
|
|
25
|
+
): Rect {
|
|
26
|
+
const w = Math.max(48, box.x1 - box.x0)
|
|
27
|
+
const h = Math.max(48, box.y1 - box.y0)
|
|
28
|
+
const cx = (box.x0 + box.x1) / 2
|
|
29
|
+
const cy = (box.y0 + box.y1) / 2
|
|
30
|
+
const half = { x: (w * (1 + pad * 2)) / 2, y: (h * (1 + pad * 2)) / 2 }
|
|
31
|
+
return {
|
|
32
|
+
x0: Math.max(0, cx - half.x),
|
|
33
|
+
y0: Math.max(0, cy - half.y),
|
|
34
|
+
x1: Math.min(canvas.width, cx + half.x),
|
|
35
|
+
y1: Math.min(canvas.height, cy + half.y),
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A rectangle as 0-1 fractions of the frame, for the snapshot pipeline's `cropRegion`. */
|
|
40
|
+
export function toCropRegion(rect: Rect, canvas: { width: number; height: number }) {
|
|
41
|
+
return {
|
|
42
|
+
x: rect.x0 / canvas.width,
|
|
43
|
+
y: rect.y0 / canvas.height,
|
|
44
|
+
width: (rect.x1 - rect.x0) / canvas.width,
|
|
45
|
+
height: (rect.y1 - rect.y0) / canvas.height,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function blobToScaledDataUrl(blob: Blob): Promise<CapturedCrop> {
|
|
50
|
+
const bitmap = await createImageBitmap(blob)
|
|
51
|
+
const long = Math.max(bitmap.width, bitmap.height)
|
|
52
|
+
const scale =
|
|
53
|
+
long > CROP_LONG_SIDE_MAX ? CROP_LONG_SIDE_MAX / long : long < CROP_LONG_SIDE_MIN ? 1 : 1
|
|
54
|
+
const width = Math.round(bitmap.width * scale)
|
|
55
|
+
const height = Math.round(bitmap.height * scale)
|
|
56
|
+
const canvas = document.createElement('canvas')
|
|
57
|
+
canvas.width = width
|
|
58
|
+
canvas.height = height
|
|
59
|
+
const context = canvas.getContext('2d')
|
|
60
|
+
if (!context) throw new Error('No 2D canvas to scale the picture')
|
|
61
|
+
context.drawImage(bitmap, 0, 0, width, height)
|
|
62
|
+
bitmap.close()
|
|
63
|
+
return { dataUrl: canvas.toDataURL('image/jpeg', 0.84), width, height }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const BUSY = 'Another snapshot is being captured'
|
|
67
|
+
const BUSY_RETRY_MS = 1_500
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* One crop of the current view over `screenRect` (viewport pixels); once more after 1.5 s when
|
|
71
|
+
* another capture (the agent's own view) holds the renderer. Rejects when the 3D canvas cannot
|
|
72
|
+
* answer in time (a hidden tab, a busy renderer): the ask then goes without a picture.
|
|
73
|
+
*/
|
|
74
|
+
export async function captureViewCrop(
|
|
75
|
+
screenRect: Rect,
|
|
76
|
+
options: { pad?: number } = {},
|
|
77
|
+
): Promise<CapturedCrop> {
|
|
78
|
+
try {
|
|
79
|
+
return await captureOnce(screenRect, options)
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (!(error instanceof Error && error.message.startsWith(BUSY))) throw error
|
|
82
|
+
await new Promise((resolve) => setTimeout(resolve, BUSY_RETRY_MS))
|
|
83
|
+
return captureOnce(screenRect, options)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function captureOnce(screenRect: Rect, options: { pad?: number }): Promise<CapturedCrop> {
|
|
88
|
+
const rect = canvasRect()
|
|
89
|
+
const projectId = useViewer.getState().projectId
|
|
90
|
+
if (!rect || !projectId) return Promise.reject(new Error('The 3D view is not ready'))
|
|
91
|
+
const local: Rect = {
|
|
92
|
+
x0: screenRect.x0 - rect.left,
|
|
93
|
+
y0: screenRect.y0 - rect.top,
|
|
94
|
+
x1: screenRect.x1 - rect.left,
|
|
95
|
+
y1: screenRect.y1 - rect.top,
|
|
96
|
+
}
|
|
97
|
+
const cropRegion = toCropRegion(paddedCropRect(local, rect, options.pad ?? 0.25), rect)
|
|
98
|
+
const requestId = `point-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`
|
|
99
|
+
return new Promise((resolve, reject) => {
|
|
100
|
+
const stop = () => {
|
|
101
|
+
clearTimeout(timer)
|
|
102
|
+
emitter.off('snapshot:captured', onCaptured)
|
|
103
|
+
emitter.off('snapshot:capture-failed', onFailed)
|
|
104
|
+
}
|
|
105
|
+
const onCaptured = (event: SnapshotCapturedEvent) => {
|
|
106
|
+
if (event.requestId !== requestId) return
|
|
107
|
+
stop()
|
|
108
|
+
blobToScaledDataUrl(event.blob).then(resolve, reject)
|
|
109
|
+
}
|
|
110
|
+
const onFailed = (event: SnapshotCaptureFailedEvent) => {
|
|
111
|
+
if (event.requestId !== requestId) return
|
|
112
|
+
stop()
|
|
113
|
+
reject(new Error(event.error))
|
|
114
|
+
}
|
|
115
|
+
const timer = setTimeout(() => {
|
|
116
|
+
stop()
|
|
117
|
+
reject(new Error('No picture came back from the 3D view'))
|
|
118
|
+
}, CAPTURE_TIMEOUT_MS)
|
|
119
|
+
emitter.on('snapshot:captured', onCaptured)
|
|
120
|
+
emitter.on('snapshot:capture-failed', onFailed)
|
|
121
|
+
emitter.emit('camera-controls:generate-thumbnail', {
|
|
122
|
+
projectId,
|
|
123
|
+
requestId,
|
|
124
|
+
ephemeral: true,
|
|
125
|
+
captureMode: 'area',
|
|
126
|
+
cropRegion,
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// The numbers Point and ask moves by, in one place: the direction's tokens
|
|
2
|
+
// (chat-juice-direction.md), the choreography section of point-and-ask-spec.md, and where the two
|
|
3
|
+
// differ, the prototype's (pascal-builds.html), which the owner played and called the bar.
|
|
4
|
+
|
|
5
|
+
export const EASE_OUT = 'cubic-bezier(0.23, 1, 0.32, 1)'
|
|
6
|
+
export const EASE_IN_OUT = 'cubic-bezier(0.77, 0, 0.175, 1)'
|
|
7
|
+
|
|
8
|
+
export type SpringTokens = { damping: number; response: number }
|
|
9
|
+
|
|
10
|
+
/** Critically damped: the default for anything a person can touch. */
|
|
11
|
+
export const SPRING: SpringTokens = { damping: 1, response: 0.35 }
|
|
12
|
+
/** A little bounce, only for things that carry momentum. */
|
|
13
|
+
export const MOMENTUM: SpringTokens = { damping: 0.8, response: 0.35 }
|
|
14
|
+
/** The outline's four corners travelling between targets. */
|
|
15
|
+
export const CORNER_SPRING: SpringTokens = { damping: 1, response: 0.16 }
|
|
16
|
+
|
|
17
|
+
// Durations (ms).
|
|
18
|
+
export const PRESS_MS = 120
|
|
19
|
+
export const ENTER_MS = 180
|
|
20
|
+
export const CARD_MS = 220
|
|
21
|
+
/** An exit is quicker than an entrance. */
|
|
22
|
+
export const BUBBLE_CLOSE_MS = 150
|
|
23
|
+
export const CARD_CLOSE_MS = 120
|
|
24
|
+
export const PIN_RETIRE_FADE_MS = 180
|
|
25
|
+
export const CHECK_STROKE_MS = 240
|
|
26
|
+
export const CHECK_PULSE_MS = 380
|
|
27
|
+
export const PIN_DROP_MS = 320
|
|
28
|
+
export const PIN_RING_MS = 560
|
|
29
|
+
export const PIN_RING_DELAY_MS = 160
|
|
30
|
+
// The prototype's flash, longer than the spec's 120 ms: a shutter has to be seen to be felt.
|
|
31
|
+
export const REGION_FLASH_MS = 280
|
|
32
|
+
export const SUGGESTION_STAGGER_MS = 30
|
|
33
|
+
export const SUGGESTION_DELAY_MS = 40
|
|
34
|
+
export const CHIP_FADE_MS = 120
|
|
35
|
+
|
|
36
|
+
// Thresholds and dwell (ms or px).
|
|
37
|
+
export const CLICK_SLOP_PX = 6
|
|
38
|
+
export const DWELL_MS = 120
|
|
39
|
+
export const SIDE_HYSTERESIS_MS = 150
|
|
40
|
+
export const TAP_LATCH_MS = 200
|
|
41
|
+
export const CARD_AUTO_MS = 4000
|
|
42
|
+
export const CARD_CLOSE_DELAY_MS = 400
|
|
43
|
+
// A card the camera carried from under a still pointer stays this long, for the pointer to reach it.
|
|
44
|
+
export const CARD_CARRIED_MS = 6000
|
|
45
|
+
export const PIN_RETIRE_MS = 8000
|
|
46
|
+
// A press on a done pin that stays down this long shows the scene as it was before the turn.
|
|
47
|
+
export const PIN_HOLD_MS = 220
|
|
48
|
+
// While held, the pointer may drift this far from where it pressed before the hold ends.
|
|
49
|
+
export const PIN_HOLD_SLOP_PX = 36
|
|
50
|
+
export const THIN_TARGET_PX = 6
|
|
51
|
+
|
|
52
|
+
// Layout (px).
|
|
53
|
+
export const BUBBLE_W = 320
|
|
54
|
+
export const BUBBLE_GAP = 16
|
|
55
|
+
export const VIEWPORT_INSET = 12
|
|
56
|
+
export const BIG_BOX_DISC = 48
|
|
57
|
+
export const MAX_TARGETS = 12
|
|
58
|
+
export const PIN_DISC = 24
|
|
59
|
+
export const PIN_HIT = 32
|
|
60
|
+
export const PIN_HIT_TOUCH = 44
|
|
61
|
+
export const CARD_W = 250
|
|
62
|
+
export const CHIP_OFFSET = 14
|
|
63
|
+
|
|
64
|
+
// Motion shapes.
|
|
65
|
+
/** The bubble grows from the click: 0.92 -> 1 with opacity and a 4 px blur (the prototype's, not the spec's 0.96). */
|
|
66
|
+
export const BUBBLE_OPEN_SCALE = 0.92
|
|
67
|
+
export const BUBBLE_OPEN_BLUR_PX = 4
|
|
68
|
+
export const PIN_DROP_PX = 8
|
|
69
|
+
export const CHIP_RISE_PX = 4
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { createCompare } from './compare'
|
|
3
|
+
|
|
4
|
+
// Hold a control, see the scene as it was before the turn; let go, and it is back. The preview is
|
|
5
|
+
// the core's (it writes nothing: its own tests say so); what this decides is when one may start,
|
|
6
|
+
// and that it always ends. Written first: a hold that starts under a turn that is writing, or with
|
|
7
|
+
// no state to show; a second hold that leaves the first up; a release that is missed.
|
|
8
|
+
|
|
9
|
+
const snapshot = { nodes: {} } as never
|
|
10
|
+
|
|
11
|
+
function setup(options: { before?: unknown; working?: boolean; begin?: unknown } = {}) {
|
|
12
|
+
const ended: string[] = []
|
|
13
|
+
const changes: (string | null)[] = []
|
|
14
|
+
let began = 0
|
|
15
|
+
const compare = createCompare({
|
|
16
|
+
before: () => ('before' in options ? (options.before as never) : snapshot),
|
|
17
|
+
turnsWorking: () => options.working ?? false,
|
|
18
|
+
begin: () => {
|
|
19
|
+
began++
|
|
20
|
+
if (options.begin === null) return null
|
|
21
|
+
return { end: () => ended.push('end') }
|
|
22
|
+
},
|
|
23
|
+
onChange: (askId) => changes.push(askId),
|
|
24
|
+
})
|
|
25
|
+
return { compare, ended, changes, began: () => began }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('why a hold is refused', () => {
|
|
29
|
+
test('says a turn is writing', () => {
|
|
30
|
+
const { compare } = setup({ working: true })
|
|
31
|
+
compare.start('ask_1')
|
|
32
|
+
expect(compare.refusal()).toBe('turn-working')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('says history cannot show the before', () => {
|
|
36
|
+
const { compare } = setup({ before: null })
|
|
37
|
+
compare.start('ask_1')
|
|
38
|
+
expect(compare.refusal()).toBe('no-before')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('says the scene is held by something else', () => {
|
|
42
|
+
const { compare } = setup({ begin: null })
|
|
43
|
+
compare.start('ask_1')
|
|
44
|
+
expect(compare.refusal()).toBe('history-held')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('says nothing after a hold that started', () => {
|
|
48
|
+
const { compare } = setup({ working: true })
|
|
49
|
+
compare.start('ask_1')
|
|
50
|
+
const next = setup()
|
|
51
|
+
next.compare.start('ask_1')
|
|
52
|
+
expect(next.compare.refusal()).toBeNull()
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe('start', () => {
|
|
57
|
+
test('shows the before of that ask and says whose', () => {
|
|
58
|
+
const { compare, changes } = setup()
|
|
59
|
+
|
|
60
|
+
expect(compare.start('ask_1')).toBe(true)
|
|
61
|
+
|
|
62
|
+
expect(compare.current()).toBe('ask_1')
|
|
63
|
+
expect(changes).toEqual(['ask_1'])
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('does not start while a turn is writing: it would be swallowed or land on the wrong state', () => {
|
|
67
|
+
const { compare, began } = setup({ working: true })
|
|
68
|
+
|
|
69
|
+
expect(compare.start('ask_1')).toBe(false)
|
|
70
|
+
|
|
71
|
+
expect(began()).toBe(0)
|
|
72
|
+
expect(compare.current()).toBeNull()
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('does not start when history cannot say what it was before', () => {
|
|
76
|
+
const { compare, began } = setup({ before: null })
|
|
77
|
+
|
|
78
|
+
expect(compare.start('ask_1')).toBe(false)
|
|
79
|
+
|
|
80
|
+
expect(began()).toBe(0)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('does not start when the scene would not take a preview (a gesture holds it)', () => {
|
|
84
|
+
const { compare, changes } = setup({ begin: null })
|
|
85
|
+
|
|
86
|
+
expect(compare.start('ask_1')).toBe(false)
|
|
87
|
+
|
|
88
|
+
expect(compare.current()).toBeNull()
|
|
89
|
+
expect(changes).toEqual([])
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('a second hold ends the first before it starts', () => {
|
|
93
|
+
const { compare, ended, changes } = setup()
|
|
94
|
+
compare.start('ask_1')
|
|
95
|
+
|
|
96
|
+
compare.start('ask_2')
|
|
97
|
+
|
|
98
|
+
expect(ended).toEqual(['end'])
|
|
99
|
+
expect(compare.current()).toBe('ask_2')
|
|
100
|
+
expect(changes).toEqual(['ask_1', null, 'ask_2'])
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
describe('end', () => {
|
|
105
|
+
test('lets the scene go and clears the tag', () => {
|
|
106
|
+
const { compare, ended, changes } = setup()
|
|
107
|
+
compare.start('ask_1')
|
|
108
|
+
|
|
109
|
+
compare.end()
|
|
110
|
+
|
|
111
|
+
expect(ended).toEqual(['end'])
|
|
112
|
+
expect(compare.current()).toBeNull()
|
|
113
|
+
expect(changes).toEqual(['ask_1', null])
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test('is idempotent: a pointer-up, a blur and a key can all arrive', () => {
|
|
117
|
+
const { compare, ended, changes } = setup()
|
|
118
|
+
compare.start('ask_1')
|
|
119
|
+
|
|
120
|
+
compare.end()
|
|
121
|
+
compare.end()
|
|
122
|
+
compare.end()
|
|
123
|
+
|
|
124
|
+
expect(ended).toEqual(['end'])
|
|
125
|
+
expect(changes).toEqual(['ask_1', null])
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
test('with nothing held is nothing', () => {
|
|
129
|
+
const { compare, changes } = setup()
|
|
130
|
+
|
|
131
|
+
compare.end()
|
|
132
|
+
|
|
133
|
+
expect(changes).toEqual([])
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
test('when the scene let go by itself (a write), the tag follows', () => {
|
|
137
|
+
const { compare, changes } = setup()
|
|
138
|
+
compare.start('ask_1')
|
|
139
|
+
|
|
140
|
+
compare.released()
|
|
141
|
+
|
|
142
|
+
expect(compare.current()).toBeNull()
|
|
143
|
+
expect(changes).toEqual(['ask_1', null])
|
|
144
|
+
})
|
|
145
|
+
})
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { SceneSnapshot } from '@pascal-app/core'
|
|
2
|
+
|
|
3
|
+
// Hold a control on a pin or its card to see the scene as it was before that turn (the owner,
|
|
4
|
+
// 8 October: "I hold and I see the previous state in 3D"). The core's scene preview does the
|
|
5
|
+
// showing and writes nothing; this decides when a hold may start and keeps the person's view of it
|
|
6
|
+
// (whose, for the "Before" tag) in step with the preview, which ends on its own when a write is
|
|
7
|
+
// about to land.
|
|
8
|
+
|
|
9
|
+
export type CompareDeps = {
|
|
10
|
+
/** The scene as it was before the ask's turn, from the history Undo would restore; null when unknown. */
|
|
11
|
+
before: (askId: string) => SceneSnapshot | null
|
|
12
|
+
/** A turn is writing the scene: it would swallow the preview's writes or land on the wrong state. */
|
|
13
|
+
turnsWorking: () => boolean
|
|
14
|
+
/** The core's `beginScenePreview`: null when the scene will not take one. */
|
|
15
|
+
begin: (snapshot: SceneSnapshot) => { end: () => void } | null
|
|
16
|
+
/** Whose before is on screen, or null: the tag follows. */
|
|
17
|
+
onChange: (askId: string | null) => void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Why a hold did not start, so the person is told instead of left holding a button that does nothing. */
|
|
21
|
+
export type CompareRefusal = 'turn-working' | 'no-before' | 'history-held'
|
|
22
|
+
|
|
23
|
+
export type Compare = {
|
|
24
|
+
/** Starts showing that ask's before; false when it may not. Ends a hold already up. */
|
|
25
|
+
start: (askId: string) => boolean
|
|
26
|
+
/** Why the last `start` returned false; null after one that started. */
|
|
27
|
+
refusal: () => CompareRefusal | null
|
|
28
|
+
/** Lets go. Idempotent. */
|
|
29
|
+
end: () => void
|
|
30
|
+
/** The preview ended by itself (a write was about to land): clears the tag. */
|
|
31
|
+
released: () => void
|
|
32
|
+
current: () => string | null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function createCompare(deps: CompareDeps): Compare {
|
|
36
|
+
let held: { askId: string; preview: { end: () => void } } | null = null
|
|
37
|
+
let refused: CompareRefusal | null = null
|
|
38
|
+
|
|
39
|
+
const set = (askId: string | null) => deps.onChange(askId)
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
start(askId) {
|
|
43
|
+
const refuse = (reason: CompareRefusal) => {
|
|
44
|
+
refused = reason
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
if (deps.turnsWorking()) return refuse('turn-working')
|
|
48
|
+
const snapshot = deps.before(askId)
|
|
49
|
+
if (!snapshot) return refuse('no-before')
|
|
50
|
+
if (held) this.end()
|
|
51
|
+
const preview = deps.begin(snapshot)
|
|
52
|
+
if (!preview) return refuse('history-held')
|
|
53
|
+
refused = null
|
|
54
|
+
held = { askId, preview }
|
|
55
|
+
set(askId)
|
|
56
|
+
return true
|
|
57
|
+
},
|
|
58
|
+
end() {
|
|
59
|
+
if (!held) return
|
|
60
|
+
const { preview } = held
|
|
61
|
+
held = null
|
|
62
|
+
preview.end()
|
|
63
|
+
set(null)
|
|
64
|
+
},
|
|
65
|
+
released() {
|
|
66
|
+
if (!held) return
|
|
67
|
+
held = null
|
|
68
|
+
set(null)
|
|
69
|
+
},
|
|
70
|
+
refusal: () => refused,
|
|
71
|
+
current: () => held?.askId ?? null,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import type { AnyNode } from '@pascal-app/core'
|
|
3
|
+
import { house } from './__fixtures__/house'
|
|
4
|
+
import { describeTarget } from './describe-target'
|
|
5
|
+
|
|
6
|
+
// Point and ask names what the finger is on, in the viewer's unit, for the label chip and the
|
|
7
|
+
// bubble's context row, and in metres for the agent (the context's `size`). Written first: a size
|
|
8
|
+
// in the wrong unit; a wall that does not say which room it bounds; a window's sill missing from the
|
|
9
|
+
// context; an element of a kind nobody described showing no name at all.
|
|
10
|
+
|
|
11
|
+
const nodes = house()
|
|
12
|
+
const metric = { unit: 'metric' } as const
|
|
13
|
+
const at = (id: string, unit: Parameters<typeof describeTarget>[2] = metric) =>
|
|
14
|
+
describeTarget(nodes[id] as AnyNode, nodes, unit)
|
|
15
|
+
|
|
16
|
+
describe('a room', () => {
|
|
17
|
+
test('is named by its zone, sized by its area, and is the kind "room"', () => {
|
|
18
|
+
const room = at('zone_kitchen')
|
|
19
|
+
|
|
20
|
+
expect(room).toMatchObject({
|
|
21
|
+
id: 'zone_kitchen',
|
|
22
|
+
type: 'zone',
|
|
23
|
+
kind: 'room',
|
|
24
|
+
name: 'Kitchen',
|
|
25
|
+
size: '12.0 m²',
|
|
26
|
+
levelId: 'level_0',
|
|
27
|
+
})
|
|
28
|
+
expect(room.sizes).toEqual({ area: 12, width: 4, depth: 3 })
|
|
29
|
+
expect(room.zoneId).toBeUndefined()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('a room with no name is "Room"', () => {
|
|
33
|
+
const unnamed = { ...nodes.zone_kitchen, name: '' } as AnyNode
|
|
34
|
+
|
|
35
|
+
expect(describeTarget(unnamed, { ...nodes, zone_kitchen: unnamed }, metric).name).toBe('Room')
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
describe('a wall', () => {
|
|
40
|
+
test('says which room it bounds, its length by its height, and the room it is in', () => {
|
|
41
|
+
const wall = at('wall_north')
|
|
42
|
+
|
|
43
|
+
expect(wall).toMatchObject({ kind: 'wall', name: 'Wall · Kitchen', zoneId: 'zone_kitchen' })
|
|
44
|
+
expect(wall.sizes.length).toBeCloseTo(4, 6)
|
|
45
|
+
expect(wall.sizes.thickness).toBeGreaterThan(0)
|
|
46
|
+
expect(wall.size).toBe(`4.00 × ${wall.sizes.height!.toFixed(2)} m`)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('a wall bounding no room is just "Wall"', () => {
|
|
50
|
+
const { zone_kitchen: _gone, ...without } = nodes
|
|
51
|
+
const wall = describeTarget(nodes.wall_north as AnyNode, without as typeof nodes, metric)
|
|
52
|
+
|
|
53
|
+
expect(wall.name).toBe('Wall')
|
|
54
|
+
expect(wall.zoneId).toBeUndefined()
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
describe('a window and a door', () => {
|
|
59
|
+
test('a window: width by height, its sill in the context, its wall as parent', () => {
|
|
60
|
+
const window = at('window_north')
|
|
61
|
+
|
|
62
|
+
expect(window).toMatchObject({
|
|
63
|
+
kind: 'window',
|
|
64
|
+
name: 'Window',
|
|
65
|
+
size: '1.20 × 1.40 m',
|
|
66
|
+
parentId: 'wall_north',
|
|
67
|
+
zoneId: 'zone_kitchen',
|
|
68
|
+
})
|
|
69
|
+
expect(window.sizes.width).toBeCloseTo(1.2, 6)
|
|
70
|
+
expect(window.sizes.height).toBeCloseTo(1.4, 6)
|
|
71
|
+
expect(window.sizes.sill).toBeCloseTo(0.7, 6)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('a door stands on the floor: sill 0', () => {
|
|
75
|
+
const door = at('door_front')
|
|
76
|
+
|
|
77
|
+
expect(door).toMatchObject({ kind: 'door', name: 'Door', size: '0.90 × 2.10 m' })
|
|
78
|
+
expect(door.sizes.sill).toBeCloseTo(0, 6)
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
describe('an item', () => {
|
|
83
|
+
test('is named by the person’s name, sized by its width, and sits in the room it stands in', () => {
|
|
84
|
+
const sofa = at('item_sofa')
|
|
85
|
+
|
|
86
|
+
expect(sofa).toMatchObject({
|
|
87
|
+
kind: 'item',
|
|
88
|
+
name: 'Sofa',
|
|
89
|
+
size: '2.10 m wide',
|
|
90
|
+
zoneId: 'zone_kitchen',
|
|
91
|
+
})
|
|
92
|
+
expect(sofa.parentId).toBeUndefined()
|
|
93
|
+
expect(sofa.sizes).toEqual({ width: 2.1, depth: 0.95, height: 0.85 })
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
test('with no name it is the asset’s, and then just "Item"', () => {
|
|
97
|
+
const unnamed = { ...nodes.item_sofa, name: undefined } as AnyNode
|
|
98
|
+
expect(describeTarget(unnamed, nodes, metric).name).toBe('Oslo 3-seat')
|
|
99
|
+
|
|
100
|
+
const bare = {
|
|
101
|
+
...nodes.item_sofa,
|
|
102
|
+
name: undefined,
|
|
103
|
+
asset: { ...(nodes.item_sofa as never as { asset: object }).asset, name: '' },
|
|
104
|
+
} as AnyNode
|
|
105
|
+
expect(describeTarget(bare, nodes, metric).name).toBe('Item')
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
describe('units', () => {
|
|
110
|
+
test('imperial reads in feet, inches and square feet', () => {
|
|
111
|
+
const imperial = { unit: 'imperial' } as const
|
|
112
|
+
|
|
113
|
+
expect(at('zone_kitchen', imperial).size).toBe('129.2 ft²')
|
|
114
|
+
expect(at('window_north', imperial).size).toBe(`3'11" × 4'7"`)
|
|
115
|
+
expect(at('item_sofa', imperial).size).toBe(`6'11" wide`)
|
|
116
|
+
// The context stays in metres whatever the viewer shows.
|
|
117
|
+
expect(at('window_north', imperial).sizes.width).toBeCloseTo(1.2, 6)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
test('millimetres when the viewer shows them', () => {
|
|
121
|
+
const mm = { unit: 'metric', metricNotation: 'millimeters' } as const
|
|
122
|
+
|
|
123
|
+
expect(at('window_north', mm).size).toBe('1200 × 1400 mm')
|
|
124
|
+
expect(at('item_sofa', mm).size).toBe('2100 mm wide')
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
describe('everything else', () => {
|
|
129
|
+
test('a kind nobody described still has a name and a type', () => {
|
|
130
|
+
const odd = {
|
|
131
|
+
id: 'column_1',
|
|
132
|
+
type: 'column',
|
|
133
|
+
parentId: 'level_0',
|
|
134
|
+
name: 'Pillar',
|
|
135
|
+
} as unknown as AnyNode
|
|
136
|
+
const anonymous = { id: 'fence_1', type: 'fence', parentId: 'level_0' } as unknown as AnyNode
|
|
137
|
+
|
|
138
|
+
expect(describeTarget(odd, { ...nodes, column_1: odd }, metric)).toMatchObject({
|
|
139
|
+
kind: 'other',
|
|
140
|
+
name: 'Pillar',
|
|
141
|
+
type: 'column',
|
|
142
|
+
})
|
|
143
|
+
expect(describeTarget(anonymous, { ...nodes, fence_1: anonymous }, metric).name).toBe('Fence')
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
test('a roof segment is the kind "roof" and a stair the kind "stair"', () => {
|
|
147
|
+
const segment = {
|
|
148
|
+
id: 'roof_segment_1',
|
|
149
|
+
type: 'roof-segment',
|
|
150
|
+
parentId: 'roof_1',
|
|
151
|
+
width: 8,
|
|
152
|
+
depth: 6,
|
|
153
|
+
} as unknown as AnyNode
|
|
154
|
+
const stair = {
|
|
155
|
+
id: 'stair_1',
|
|
156
|
+
type: 'stair',
|
|
157
|
+
parentId: 'level_0',
|
|
158
|
+
width: 1,
|
|
159
|
+
totalRise: 2.7,
|
|
160
|
+
} as unknown as AnyNode
|
|
161
|
+
|
|
162
|
+
expect(describeTarget(segment, nodes, metric).kind).toBe('roof')
|
|
163
|
+
expect(describeTarget(stair, nodes, metric).kind).toBe('stair')
|
|
164
|
+
})
|
|
165
|
+
})
|