@pascal-app/editor 1.0.0-beta.4 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/bunfig.toml +4 -0
- package/package.json +15 -8
- package/scripts/generate-print-golden-house.ts +89 -0
- package/src/components/editor/alignment-3d-guide-layer.tsx +12 -3
- package/src/components/editor/bake-exporter.tsx +1 -0
- package/src/components/editor/camera-dragging-lifecycle.test.ts +41 -0
- package/src/components/editor/camera-dragging-lifecycle.ts +10 -2
- package/src/components/editor/capture-camera-rig.tsx +59 -0
- package/src/components/editor/custom-camera-controls.tsx +20 -11
- package/src/components/editor/editor-layout-mobile.tsx +6 -6
- package/src/components/editor/editor-layout-v2.tsx +17 -5
- package/src/components/editor/elevation-3d-guide-layer.tsx +3 -2
- package/src/components/editor/export-manager.tsx +175 -33
- package/src/components/editor/first-person/build-collider-world.test.ts +30 -0
- package/src/components/editor/first-person-controls.tsx +182 -57
- package/src/components/editor/floating-action-menu.tsx +55 -55
- package/src/components/editor/floorplan-background-selection.test.ts +31 -4
- package/src/components/editor/floorplan-background-selection.ts +27 -7
- package/src/components/editor/floorplan-panel.tsx +81 -57
- package/src/components/editor/grid.tsx +12 -7
- package/src/components/editor/group-actions.ts +34 -50
- package/src/components/editor/group-floating-action-menu.tsx +30 -15
- package/src/components/editor/group-move-3d.ts +51 -21
- package/src/components/editor/group-transform-shared.test.ts +78 -0
- package/src/components/editor/group-transform-shared.ts +74 -7
- package/src/components/editor/handles/handle-arrow-raycast.test.ts +86 -0
- package/src/components/editor/handles/handle-arrow.tsx +109 -48
- package/src/components/editor/handles/linear-resize-drag.ts +67 -0
- package/src/components/editor/handles/resize-snap.test.ts +46 -0
- package/src/components/editor/handles/resize-snap.ts +10 -1
- package/src/components/editor/handles/use-handle-drag.ts +18 -1
- package/src/components/editor/index.tsx +252 -46
- package/src/components/editor/measurement-pill.tsx +12 -12
- package/src/components/editor/node-action-menu.tsx +42 -1
- package/src/components/editor/node-arrow-handles.tsx +47 -58
- package/src/components/editor/opening-guides-3d-layer.tsx +13 -4
- package/src/components/editor/selection-manager.tsx +132 -47
- package/src/components/editor/site-edge-labels.tsx +2 -1
- package/src/components/editor/snapshot-capture-overlay.tsx +309 -41
- package/src/components/editor/snapshot-capture.test.ts +422 -0
- package/src/components/editor/snapshot-capture.ts +145 -0
- package/src/components/editor/thumbnail-generator.tsx +295 -232
- package/src/components/editor/use-floorplan-background-placement.ts +53 -5
- package/src/components/editor/wall-measurement-label.tsx +3 -2
- package/src/components/editor/wall-move-side-handles.tsx +299 -7
- package/src/components/editor-2d/floorplan-group-action-menu.tsx +23 -16
- package/src/components/editor-2d/floorplan-group-move.tsx +67 -33
- package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +3 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +129 -38
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +14 -1
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +16 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +2 -0
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +52 -3
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +196 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +204 -114
- package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +402 -0
- package/src/components/systems/ceiling/ceiling-bracket-batch.ts +371 -0
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +347 -0
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +281 -305
- package/src/components/systems/roof/roof-edit-system.test.tsx +120 -0
- package/src/components/systems/roof/roof-edit-system.tsx +10 -19
- package/src/components/systems/selection-affordance-manager.tsx +59 -9
- package/src/components/systems/selection-affordance-services.ts +19 -0
- package/src/components/systems/stair/stair-edit-system.tsx +5 -2
- package/src/components/tools/fence/fence-drafting.test.ts +100 -0
- package/src/components/tools/fence/fence-drafting.ts +6 -25
- package/src/components/tools/item/draft-creation.test.ts +12 -0
- package/src/components/tools/item/draft-creation.ts +10 -0
- package/src/components/tools/item/face-host-commit.test.ts +65 -0
- package/src/components/tools/item/face-host-commit.ts +56 -0
- package/src/components/tools/item/face-host-preview.test.ts +77 -0
- package/src/components/tools/item/face-host-preview.ts +82 -0
- package/src/components/tools/item/move-tool.tsx +4 -3
- package/src/components/tools/item/placement-math.test.ts +11 -1
- package/src/components/tools/item/placement-math.ts +0 -20
- package/src/components/tools/item/placement-strategies.test.ts +494 -0
- package/src/components/tools/item/placement-strategies.ts +192 -24
- package/src/components/tools/item/placement-surface.test.ts +61 -0
- package/src/components/tools/item/placement-surface.ts +24 -0
- package/src/components/tools/item/placement-types.ts +6 -0
- package/src/components/tools/item/test-face-host.ts +121 -0
- package/src/components/tools/item/use-draft-node.test.tsx +336 -0
- package/src/components/tools/item/use-draft-node.ts +26 -4
- package/src/components/tools/item/use-placement-coordinator.tsx +447 -180
- package/src/components/tools/registered-tool-install-lifecycle.test.tsx +256 -0
- package/src/components/tools/registry/move-registry-node-tool.test.ts +10 -0
- package/src/components/tools/registry/move-registry-node-tool.tsx +306 -110
- package/src/components/tools/registry-tool-context.tsx +30 -0
- package/src/components/tools/select/box-select-tool.tsx +3 -1
- package/src/components/tools/select/marquee-footprint.test.ts +53 -0
- package/src/components/tools/select/marquee-footprint.ts +29 -0
- package/src/components/tools/shared/placement-box.tsx +63 -8
- package/src/components/tools/shared/placement-dimension-guides.tsx +121 -0
- package/src/components/tools/shared/pointer-support-cap.test.ts +319 -17
- package/src/components/tools/shared/pointer-support-cap.ts +45 -8
- package/src/components/tools/site/terrain-brush-cursor.tsx +2 -1
- package/src/components/tools/site/terrain-sculpt-grid.tsx +9 -5
- package/src/components/tools/site/terrain-sculpt-tool.tsx +1 -1
- package/src/components/tools/stair/stair-tool.tsx +127 -74
- package/src/components/tools/tool-manager.tsx +25 -8
- package/src/components/tools/wall/wall-drafting.test.ts +527 -4
- package/src/components/tools/wall/wall-drafting.ts +55 -462
- package/src/components/ui/action-menu/control-modes.tsx +9 -6
- package/src/components/ui/action-menu/index.tsx +11 -1
- package/src/components/ui/command-palette/editor-commands.tsx +12 -13
- package/src/components/ui/command-palette/index.tsx +7 -2
- package/src/components/ui/controls/material-paint-panel.tsx +6 -4
- package/src/components/ui/controls/material-picker.tsx +4 -4
- package/src/components/ui/controls/scene-material-list.tsx +6 -5
- package/src/components/ui/controls/segmented-control.tsx +8 -1
- package/src/components/ui/controls/slider-control.tsx +19 -3
- package/src/components/ui/controls/toggle-control.tsx +18 -5
- package/src/components/ui/controls/tool-options-panel.tsx +126 -0
- package/src/components/ui/floating-level-selector.tsx +23 -6
- package/src/components/ui/helpers/contextual-helper-panel.tsx +9 -0
- package/src/components/ui/helpers/helper-manager.tsx +47 -11
- package/src/components/ui/helpers/item-helper.tsx +1 -1
- package/src/components/ui/helpers/registered-tool-helper.tsx +21 -0
- package/src/components/ui/panels/homogeneous-selection.test.ts +80 -0
- package/src/components/ui/panels/homogeneous-selection.ts +47 -0
- package/src/components/ui/panels/mobile-selection-bar.tsx +11 -5
- package/src/components/ui/panels/multi-field-value.test.ts +209 -0
- package/src/components/ui/panels/multi-field-value.ts +163 -0
- package/src/components/ui/panels/multi-height-mode.tsx +185 -0
- package/src/components/ui/panels/multi-parametric-inspector.tsx +263 -0
- package/src/components/ui/panels/multi-selection-panel.tsx +78 -26
- package/src/components/ui/panels/node-display.ts +4 -0
- package/src/components/ui/panels/panel-manager.tsx +77 -3
- package/src/components/ui/panels/panel-wrapper.tsx +228 -12
- package/src/components/ui/panels/parametric-field-control.tsx +173 -0
- package/src/components/ui/panels/parametric-field-utils.ts +18 -0
- package/src/components/ui/panels/parametric-inspector.tsx +29 -162
- package/src/components/ui/panels/reference-panel.tsx +31 -1
- package/src/components/ui/primitives/shortcut-token.tsx +23 -1
- package/src/components/ui/scene-loader.tsx +35 -0
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +7 -2
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +390 -36
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +31 -23
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.test.ts +140 -0
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +109 -0
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +19 -5
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +128 -46
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +5 -2
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +28 -11
- package/src/components/ui/sidebar/panels/site-panel/tree-node.test.ts +8 -0
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +22 -7
- package/src/components/ui/sidebar/tab-bar.tsx +9 -1
- package/src/components/ui/sidebar/use-plugin-panels.tsx +17 -2
- package/src/components/viewer/floorplan-compass-button.tsx +50 -0
- package/src/components/viewer/floorplan-preview-geometry.test.ts +107 -0
- package/src/components/viewer/floorplan-preview-geometry.ts +281 -0
- package/src/components/viewer/floorplan-preview-navigation.test.ts +43 -0
- package/src/components/viewer/floorplan-preview-navigation.ts +62 -0
- package/src/components/viewer/floorplan-preview.tsx +1069 -0
- package/src/components/viewer/use-viewer-camera-navigation-sync.ts +140 -0
- package/src/components/viewer/viewer-scene-header.tsx +2 -1
- package/src/components/viewer/viewer-stage-modes.test.ts +38 -0
- package/src/components/viewer/viewer-stage-modes.ts +49 -0
- package/src/components/viewer/viewer-stage-switcher.tsx +95 -0
- package/src/components/viewer/viewer-stage.test.tsx +47 -0
- package/src/components/viewer/viewer-stage.tsx +221 -0
- package/src/components/viewer-overlay.tsx +13 -30
- package/src/components/viewer-zone-system.tsx +6 -2
- package/src/components/walkthrough-hud.tsx +19 -10
- package/src/hooks/use-auto-save.test.ts +122 -1
- package/src/hooks/use-auto-save.ts +119 -13
- package/src/hooks/use-ceiling-events.test.ts +122 -0
- package/src/hooks/use-drag-action.ts +17 -3
- package/src/hooks/use-grid-events.test.ts +240 -0
- package/src/hooks/use-grid-events.ts +190 -11
- package/src/hooks/use-keyboard.test.ts +244 -0
- package/src/hooks/use-keyboard.ts +164 -63
- package/src/hooks/use-registered-tool-enabled.ts +20 -0
- package/src/hooks/use-save-shortcut.ts +33 -0
- package/src/index.tsx +109 -4
- package/src/lib/active-placement-surface.test.ts +16 -0
- package/src/lib/active-placement-surface.ts +14 -0
- package/src/lib/ceiling-surface-raycast.test.ts +64 -0
- package/src/lib/ceiling-surface-raycast.ts +12 -0
- package/src/lib/contextual-help-extension.ts +17 -0
- package/src/lib/contextual-help.test.ts +8 -0
- package/src/lib/contextual-help.ts +8 -0
- package/src/lib/continuation.test.ts +11 -0
- package/src/lib/continuation.ts +8 -1
- package/src/lib/direct-manipulation.test.ts +132 -0
- package/src/lib/direct-manipulation.ts +38 -1
- package/src/lib/editor-api.ts +6 -3
- package/src/lib/elevation-guides.test.ts +28 -1
- package/src/lib/elevation-guides.ts +22 -7
- package/src/lib/floating-menu-scale.test.ts +29 -0
- package/src/lib/floating-menu-scale.ts +14 -0
- package/src/lib/floorplan/apply-alignment.test.ts +47 -2
- package/src/lib/floorplan/apply-alignment.ts +10 -1
- package/src/lib/floorplan/floorplan-export.test.ts +397 -1
- package/src/lib/floorplan/floorplan-export.tsx +111 -16
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +26 -0
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +32 -24
- package/src/lib/floorplan/floorplan-readonly.test.ts +47 -0
- package/src/lib/floorplan/floorplan-readonly.ts +85 -0
- package/src/lib/floorplan-grid-event-point.test.ts +29 -0
- package/src/lib/floorplan-grid-event-point.ts +13 -0
- package/src/lib/glb-export.test.ts +1169 -21
- package/src/lib/glb-export.ts +629 -101
- package/src/lib/grid-event-presentation.ts +16 -0
- package/src/lib/history.test.ts +523 -77
- package/src/lib/history.ts +133 -14
- package/src/lib/host-tree-children.test.ts +27 -0
- package/src/lib/host-tree-children.ts +79 -0
- package/src/lib/inspector-card-mode.test.ts +92 -0
- package/src/lib/inspector-card-mode.ts +60 -0
- package/src/lib/interaction/hot-set.test.ts +10 -1
- package/src/lib/interaction/hot-set.ts +3 -1
- package/src/lib/interaction/overlay-policy.test.ts +33 -1
- package/src/lib/interaction/overlay-policy.ts +16 -0
- package/src/lib/interaction/registered-drafting.ts +36 -0
- package/src/lib/interaction/scope.ts +31 -0
- package/src/lib/level-print-export.test.ts +484 -0
- package/src/lib/level-print-export.ts +526 -0
- package/src/lib/local-project-presentation-persistence.test.ts +302 -0
- package/src/lib/local-project-presentation-persistence.ts +335 -0
- package/src/lib/material-paint.ts +2 -0
- package/src/lib/measurement-parser.ts +3 -0
- package/src/lib/measurements.test.ts +4 -0
- package/src/lib/model-export.ts +29 -0
- package/src/lib/paint-preview-owner.ts +71 -0
- package/src/lib/paint-scope.test.ts +58 -2
- package/src/lib/paint-scope.ts +8 -2
- package/src/lib/planar-cursor-placement.test.ts +178 -1
- package/src/lib/planar-cursor-placement.ts +61 -5
- package/src/lib/plugin-panels.test.ts +92 -1
- package/src/lib/plugin-panels.ts +48 -0
- package/src/lib/portable-export.test.ts +366 -0
- package/src/lib/portable-export.ts +969 -0
- package/src/lib/print-3mf.test.ts +86 -0
- package/src/lib/print-3mf.ts +161 -0
- package/src/lib/print-content-scope.test.ts +107 -0
- package/src/lib/print-content-scope.ts +90 -0
- package/src/lib/print-export.test.ts +294 -0
- package/src/lib/print-export.ts +735 -0
- package/src/lib/print-feature-thickness.test.ts +147 -0
- package/src/lib/print-feature-thickness.ts +180 -0
- package/src/lib/print-golden-house.test-fixture.ts +352 -0
- package/src/lib/print-golden-house.test.ts +249 -0
- package/src/lib/print-roof-solids.test.ts +133 -0
- package/src/lib/print-roof-solids.ts +630 -0
- package/src/lib/print-shell-compiler-baseline.test.ts +419 -0
- package/src/lib/print-shell-compiler-baseline.ts +225 -0
- package/src/lib/print-shell-compiler-manifold-core.ts +330 -0
- package/src/lib/print-shell-compiler-manifold-worker.ts +187 -0
- package/src/lib/print-shell-compiler-manifold.worker.ts +20 -0
- package/src/lib/print-shell-compiler-mesh-data.ts +32 -0
- package/src/lib/print-shell-compiler-protocol.ts +30 -0
- package/src/lib/print-shell-compiler.ts +273 -0
- package/src/lib/print-wall-solids.test.ts +194 -0
- package/src/lib/print-wall-solids.ts +328 -0
- package/src/lib/rigid-plan-svg-transform.test.ts +35 -0
- package/src/lib/rigid-plan-svg-transform.ts +24 -0
- package/src/lib/scene.ts +22 -8
- package/src/lib/selection-routing.test.ts +125 -6
- package/src/lib/selection-routing.ts +34 -1
- package/src/lib/session-groups.test.ts +88 -0
- package/src/lib/session-groups.ts +201 -0
- package/src/lib/sfx-bus.ts +4 -0
- package/src/lib/sfx-player.ts +8 -0
- package/src/lib/snapping-mode.test.ts +32 -4
- package/src/lib/snapping-mode.ts +11 -7
- package/src/lib/structured-clone-fallback.test.ts +45 -0
- package/src/lib/structured-clone-fallback.ts +24 -0
- package/src/lib/usdz-export.ts +38 -0
- package/src/lib/walkthrough-pointer-lock.ts +47 -0
- package/src/store/live-draft-preview-stores.test.ts +3 -0
- package/src/store/terrain-sculpt-mode.test.ts +3 -6
- package/src/store/tool-mode.test.ts +87 -0
- package/src/store/use-camera-hint-focus.ts +48 -0
- package/src/store/use-editor.tsx +299 -188
- package/src/store/use-floorplan-draft-preview.ts +9 -0
- package/src/store/use-interaction-scope.test.ts +23 -1
- package/src/store/use-interaction-scope.ts +63 -2
- package/src/store/use-placement-preview.ts +56 -3
- package/src/store/use-session-groups.ts +103 -0
- package/src/components/tools/roof/roof-tool.tsx +0 -656
- package/src/components/ui/helpers/roof-helper.tsx +0 -14
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grab pointer lock on the viewer canvas for a walkthrough (walk / drone)
|
|
3
|
+
* entry. Must run synchronously inside a user-gesture task — callers flip the
|
|
4
|
+
* first-person flags in a `flushSync` first so the controls are mounted when
|
|
5
|
+
* the lock lands.
|
|
6
|
+
*
|
|
7
|
+
* `retryWhile`: the browser's re-lock cooldown (~1.25s after any unlock)
|
|
8
|
+
* rejects the request outright, which bites the natural "free the cursor,
|
|
9
|
+
* immediately pick the other camera" flow. When given, one delayed retry
|
|
10
|
+
* fires after the cooldown — only while the predicate still holds.
|
|
11
|
+
*/
|
|
12
|
+
export function requestWalkthroughPointerLock(options?: { retryWhile?: () => boolean }) {
|
|
13
|
+
const canvas = document.querySelector<HTMLCanvasElement>('[data-pascal-viewer-3d] canvas')
|
|
14
|
+
if (!canvas) return
|
|
15
|
+
|
|
16
|
+
if (!canvas.hasAttribute('tabindex')) {
|
|
17
|
+
canvas.tabIndex = -1
|
|
18
|
+
}
|
|
19
|
+
canvas.focus({ preventScroll: true })
|
|
20
|
+
|
|
21
|
+
if (document.pointerLockElement === canvas) return
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
// The request can also reject ASYNC (browser cooldown after a recent
|
|
25
|
+
// unlock) — swallow it like the P-resume path; clicking the canvas
|
|
26
|
+
// re-requests once the cooldown passes.
|
|
27
|
+
const result = canvas.requestPointerLock?.() as Promise<void> | undefined
|
|
28
|
+
if (result && typeof result.catch === 'function') {
|
|
29
|
+
result.catch(() => {
|
|
30
|
+
const retryWhile = options?.retryWhile
|
|
31
|
+
if (!retryWhile) return
|
|
32
|
+
window.setTimeout(() => {
|
|
33
|
+
if (!retryWhile()) return
|
|
34
|
+
if (document.pointerLockElement === canvas) return
|
|
35
|
+
try {
|
|
36
|
+
const retried = canvas.requestPointerLock?.() as Promise<void> | undefined
|
|
37
|
+
if (retried && typeof retried.catch === 'function') retried.catch(() => {})
|
|
38
|
+
} catch {
|
|
39
|
+
// Best effort — clicking the canvas still locks.
|
|
40
|
+
}
|
|
41
|
+
}, 1400)
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -85,6 +85,7 @@ describe('live draft preview stores', () => {
|
|
|
85
85
|
{
|
|
86
86
|
angle: 90,
|
|
87
87
|
branchAngle: 90,
|
|
88
|
+
damperAngle: 0,
|
|
88
89
|
diameter: 6,
|
|
89
90
|
diameter2: 6,
|
|
90
91
|
ductMaterial: 'sheet-metal' as const,
|
|
@@ -94,6 +95,8 @@ describe('live draft preview stores', () => {
|
|
|
94
95
|
id: 'duct-fitting_live-draft-0' as const,
|
|
95
96
|
metadata: {},
|
|
96
97
|
object: 'node' as const,
|
|
98
|
+
panelHeight: 0.15,
|
|
99
|
+
panelWidth: 0.25,
|
|
97
100
|
parentId: 'level_1' as const,
|
|
98
101
|
position: [1, 2, 3] as [number, number, number],
|
|
99
102
|
rotation: [0, 0, 0] as [number, number, number],
|
|
@@ -52,8 +52,7 @@ describe('terrain-sculpt mode lifecycle', () => {
|
|
|
52
52
|
|
|
53
53
|
test('a phase switch out of site drops the scope', () => {
|
|
54
54
|
useEditor.getState().setMode('terrain-sculpt')
|
|
55
|
-
//
|
|
56
|
-
// exactly why the scope sync is its own function.
|
|
55
|
+
// The phase setter delegates its mode rewrite to the ToolMode transition.
|
|
57
56
|
useEditor.getState().setPhase('structure')
|
|
58
57
|
expect(useInteractionScope.getState().scope.kind).toBe('idle')
|
|
59
58
|
})
|
|
@@ -153,10 +152,8 @@ describe('the brush and the 3D canvas travel together', () => {
|
|
|
153
152
|
|
|
154
153
|
describe('the modes that hide the editing canvas release the brush', () => {
|
|
155
154
|
// Preview / walkthrough / studio all unmount `ToolManager` (via `noEditing`),
|
|
156
|
-
// so the sculpt tool that would release the scope on unmount is gone.
|
|
157
|
-
//
|
|
158
|
-
// bug class `setPhase` had: a `sculpting` scope leaked here disables selection
|
|
159
|
-
// across the whole editor with nothing left mounted to clear it.
|
|
155
|
+
// so the sculpt tool that would release the scope on unmount is gone. Their
|
|
156
|
+
// ToolMode transitions must release the scope before the canvas disappears.
|
|
160
157
|
test('entering preview releases it', () => {
|
|
161
158
|
useEditor.getState().setMode('terrain-sculpt')
|
|
162
159
|
useEditor.getState().setPreviewMode(true)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
|
2
|
+
import useEditor, { normalizePersistedEditorUiState } from './use-editor'
|
|
3
|
+
|
|
4
|
+
function resetToolMode() {
|
|
5
|
+
useEditor.getState().setPhase('structure')
|
|
6
|
+
useEditor.getState().setStructureLayer('elements')
|
|
7
|
+
useEditor.getState().armToolMode({ mode: 'select' })
|
|
8
|
+
useEditor.getState().setActivePaintMaterial(null)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
beforeEach(resetToolMode)
|
|
12
|
+
afterEach(resetToolMode)
|
|
13
|
+
|
|
14
|
+
describe('ToolMode transition', () => {
|
|
15
|
+
test('choosing a paint swatch while selected arms material paint', () => {
|
|
16
|
+
useEditor.getState().armToolMode({ mode: 'select' })
|
|
17
|
+
useEditor.getState().armMaterialPaint({
|
|
18
|
+
materialPreset: 'library:test-paint',
|
|
19
|
+
sourceTarget: 'wall',
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
expect(useEditor.getState().toolMode).toEqual({ mode: 'material-paint' })
|
|
23
|
+
expect(useEditor.getState().mode).toBe('material-paint')
|
|
24
|
+
expect(useEditor.getState().tool).toBeNull()
|
|
25
|
+
expect(useEditor.getState().activePaintMaterial?.materialPreset).toBe('library:test-paint')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('leaving build clears the materialized tool', () => {
|
|
29
|
+
useEditor.getState().armToolMode({ mode: 'build', tool: 'wall' })
|
|
30
|
+
useEditor.getState().armToolMode({ mode: 'select' })
|
|
31
|
+
|
|
32
|
+
expect(useEditor.getState().toolMode).toEqual({ mode: 'select' })
|
|
33
|
+
expect(useEditor.getState().mode).toBe('select')
|
|
34
|
+
expect(useEditor.getState().tool).toBeNull()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('the setMode compatibility wrapper elects a default build tool', () => {
|
|
38
|
+
useEditor.getState().setMode('build')
|
|
39
|
+
|
|
40
|
+
expect(useEditor.getState().toolMode).toEqual({ mode: 'build', tool: 'wall' })
|
|
41
|
+
expect(useEditor.getState().mode).toBe('build')
|
|
42
|
+
expect(useEditor.getState().tool).toBe('wall')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('setTool enters build and null exits it', () => {
|
|
46
|
+
useEditor.getState().armToolMode({ mode: 'select' })
|
|
47
|
+
useEditor.getState().setTool('slab')
|
|
48
|
+
|
|
49
|
+
expect(useEditor.getState().toolMode).toEqual({ mode: 'build', tool: 'slab' })
|
|
50
|
+
expect(useEditor.getState().mode).toBe('build')
|
|
51
|
+
expect(useEditor.getState().tool).toBe('slab')
|
|
52
|
+
|
|
53
|
+
useEditor.getState().setTool(null)
|
|
54
|
+
expect(useEditor.getState().toolMode).toEqual({ mode: 'select' })
|
|
55
|
+
expect(useEditor.getState().tool).toBeNull()
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
describe('persisted ToolMode normalization', () => {
|
|
60
|
+
test('elects a default for build with a null tool', () => {
|
|
61
|
+
const state = normalizePersistedEditorUiState({
|
|
62
|
+
phase: 'structure',
|
|
63
|
+
toolMode: { mode: 'build', tool: null as never },
|
|
64
|
+
mode: 'select',
|
|
65
|
+
tool: 'slab',
|
|
66
|
+
structureLayer: 'elements',
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
expect(state.toolMode).toEqual({ mode: 'build', tool: 'wall' })
|
|
70
|
+
expect(state.mode).toBe('build')
|
|
71
|
+
expect(state.tool).toBe('wall')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('clears a persisted tool from a non-build mode', () => {
|
|
75
|
+
const state = normalizePersistedEditorUiState({
|
|
76
|
+
phase: 'structure',
|
|
77
|
+
toolMode: { mode: 'select' },
|
|
78
|
+
mode: 'build',
|
|
79
|
+
tool: 'wall',
|
|
80
|
+
structureLayer: 'elements',
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
expect(state.toolMode).toEqual({ mode: 'select' })
|
|
84
|
+
expect(state.mode).toBe('select')
|
|
85
|
+
expect(state.tool).toBeNull()
|
|
86
|
+
})
|
|
87
|
+
})
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { create } from 'zustand'
|
|
2
|
+
|
|
3
|
+
/** The camera actions the canvas hint can name. */
|
|
4
|
+
export type CameraHintAction = 'Pan' | 'Rotate' | 'Zoom'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Which camera controls the canvas hint is currently allowed to name.
|
|
8
|
+
*
|
|
9
|
+
* `null` — the default — means all of them, which is what the editor shows on
|
|
10
|
+
* its own: a first-time user has no idea which button does what, and the panel
|
|
11
|
+
* is there to answer that once.
|
|
12
|
+
*
|
|
13
|
+
* A host teaching the camera one gesture at a time needs the opposite. Showing
|
|
14
|
+
* three controls while asking for one turns the answer into a search, and
|
|
15
|
+
* leaving all three up after the lesson leaves a permanent widget over the
|
|
16
|
+
* canvas explaining something the user has just been walked through. So a host
|
|
17
|
+
* can narrow the panel to the gesture it is asking for, and to nothing at all
|
|
18
|
+
* once it is done — an empty list hides the panel outright.
|
|
19
|
+
*
|
|
20
|
+
* Deliberately a store rather than a prop: the thing that knows which gesture
|
|
21
|
+
* is being taught is a host surface several levels away from the canvas, and
|
|
22
|
+
* threading it through would put a teaching concern in every component between.
|
|
23
|
+
* The editor knows only "show these actions"; it never learns why.
|
|
24
|
+
*/
|
|
25
|
+
type CameraHintFocus = {
|
|
26
|
+
actions: readonly CameraHintAction[] | null
|
|
27
|
+
focus: (actions: readonly CameraHintAction[] | null) => void
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const useCameraHintFocus = create<CameraHintFocus>((set) => ({
|
|
31
|
+
actions: null,
|
|
32
|
+
focus: (actions) =>
|
|
33
|
+
set((state) => {
|
|
34
|
+
const current = state.actions
|
|
35
|
+
if (
|
|
36
|
+
current === actions ||
|
|
37
|
+
(current !== null &&
|
|
38
|
+
actions !== null &&
|
|
39
|
+
current.length === actions.length &&
|
|
40
|
+
current.every((action, index) => action === actions[index]))
|
|
41
|
+
) {
|
|
42
|
+
return state
|
|
43
|
+
}
|
|
44
|
+
return { actions }
|
|
45
|
+
}),
|
|
46
|
+
}))
|
|
47
|
+
|
|
48
|
+
export default useCameraHintFocus
|