@pascal-app/editor 1.0.7 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +9 -15
- package/src/components/editor/action-menu-button.tsx +1 -1
- package/src/components/editor/alignment-3d-guide-layer.tsx +1 -1
- package/src/components/editor/custom-camera-controls.tsx +101 -45
- package/src/components/editor/duplicate-node.ts +2 -1
- package/src/components/editor/editor-layout-v2.tsx +1 -1
- package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
- package/src/components/editor/first-person-controls.tsx +20 -1983
- package/src/components/editor/fit-scene-framing.test.ts +318 -0
- package/src/components/editor/fit-scene-framing.ts +142 -0
- package/src/components/editor/floating-action-menu.tsx +5 -3
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floor-openings-overlay.tsx +1 -1
- package/src/components/editor/floor-region-controls.tsx +8 -4
- package/src/components/editor/floorplan-navigation-presentation.test.ts +0 -40
- package/src/components/editor/floorplan-navigation-presentation.ts +0 -66
- package/src/components/editor/floorplan-panel.tsx +199 -30
- package/src/components/editor/floorplan-wall-typing-keys.test.tsx +152 -0
- package/src/components/editor/floorplan-wall-typing-keys.ts +79 -0
- package/src/components/editor/follow-pascal.test.tsx +1 -2
- package/src/components/editor/follow-pascal.tsx +1 -1
- package/src/components/editor/footprint-height-handle.tsx +2 -2
- package/src/components/editor/group-actions.ts +1 -2
- package/src/components/editor/group-floating-action-menu.tsx +1 -1
- package/src/components/editor/group-move-3d.ts +1 -2
- package/src/components/editor/group-rotate-handle.tsx +1 -2
- package/src/components/editor/group-transform-shared.ts +3 -1
- package/src/components/editor/handles/use-handle-drag.ts +1 -2
- package/src/components/editor/index.tsx +43 -13
- package/src/components/editor/inspector-extension-actions.tsx +1 -1
- package/src/components/editor/measurement-pill.tsx +1 -1
- package/src/components/editor/node-arrow-handles.tsx +2 -2
- package/src/components/editor/open-wall-ends-3d-layer.tsx +2 -3
- package/src/components/editor/open-wall-ends-hint.tsx +1 -1
- package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
- package/src/components/editor/quick-measurement-card.tsx +1 -1
- package/src/components/editor/room-controls.tsx +10 -6
- package/src/components/editor/room-handles-2d.tsx +3 -3
- package/src/components/editor/room-handles.tsx +4 -5
- package/src/components/editor/room-highlight.tsx +6 -105
- package/src/components/editor/room-labels.test.ts +8 -822
- package/src/components/editor/room-labels.tsx +41 -1670
- package/src/components/editor/room-webgpu-materials.test.ts +1 -1
- package/src/components/editor/selection-manager.tsx +5 -3
- package/src/components/editor/site-edge-labels.tsx +7 -3
- package/src/components/editor/snapshot-capture-overlay.tsx +2 -5
- package/src/components/editor/use-floorplan-background-placement.ts +2 -1
- package/src/components/editor/views/plugins-view.tsx +1 -1
- package/src/components/editor/views/view-bar.tsx +9 -9
- package/src/components/editor/wall-measurement-label.tsx +31 -15
- package/src/components/editor/wall-move-side-handles.tsx +11 -7
- package/src/components/editor/wall-pull-grip.tsx +2 -2
- package/src/components/editor/wall-region-handles.tsx +2 -2
- package/src/components/editor/wall-snap-beacon-layer.tsx +53 -32
- package/src/components/editor-2d/floorplan-group-move.tsx +3 -4
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +13 -13
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +2 -3
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +2 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +23 -6
- package/src/components/editor-2d/renderers/floorplan-annotation-layout.test.ts +0 -37
- package/src/components/editor-2d/renderers/floorplan-annotation-layout.ts +0 -30
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +13 -3
- package/src/components/editor-2d/renderers/floorplan-draft-measurement.tsx +7 -1
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +14 -3
- package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.test.tsx +35 -0
- package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.tsx +65 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +30 -10
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +39 -35
- package/src/components/systems/roof/roof-edit-system.test.tsx +15 -1
- package/src/components/systems/roof/roof-edit-system.tsx +33 -21
- package/src/components/tools/distribution/distribution-run-tool.test.tsx +371 -3
- package/src/components/tools/distribution/distribution-run-tool.tsx +151 -33
- package/src/components/tools/item/move-tool.tsx +1 -6
- package/src/components/tools/item/placement-history.test.tsx +1 -1
- package/src/components/tools/item/use-placement-coordinator.tsx +3 -4
- package/src/components/tools/paint-region/wall-paint-region-tool.tsx +7 -3
- package/src/components/tools/registry/move-registry-node-tool.test.ts +42 -1
- package/src/components/tools/registry/move-registry-node-tool.tsx +39 -16
- package/src/components/tools/shared/cursor-sphere.tsx +1 -1
- package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
- package/src/components/tools/shared/placement-box.tsx +2 -6
- package/src/components/tools/shared/placement-coordinate-input.tsx +94 -0
- package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
- package/src/components/tools/shared/segment-angle.ts +0 -4
- package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
- package/src/components/tools/tool-manager.tsx +1 -24
- package/src/components/tools/wall/wall-typed-entry.test.ts +99 -0
- package/src/components/tools/wall/wall-typed-entry.ts +70 -0
- package/src/components/ui/action-menu/camera-actions.tsx +2 -2
- package/src/components/ui/action-menu/control-modes.tsx +1 -1
- package/src/components/ui/action-menu/follow-pascal-pill.tsx +1 -1
- package/src/components/ui/action-menu/index.tsx +3 -3
- package/src/components/ui/action-menu/measurement-control.tsx +3 -3
- package/src/components/ui/action-menu/placement.ts +0 -18
- package/src/components/ui/action-menu/point-ask-button.tsx +1 -1
- package/src/components/ui/action-menu/view-toggles.tsx +5 -5
- package/src/components/ui/controls/finish-swatch.tsx +1 -1
- package/src/components/ui/controls/material-paint-panel.tsx +2 -2
- package/src/components/ui/controls/scene-material-list.tsx +2 -2
- package/src/components/ui/controls/select-control.tsx +1 -1
- package/src/components/ui/controls/slider-control.tsx +18 -6
- package/src/components/ui/controls/terrain-sculpt-panel.tsx +1 -1
- package/src/components/ui/controls/tool-options-panel.tsx +74 -44
- package/src/components/ui/floating-level-selector.tsx +3 -3
- package/src/components/ui/helpers/contextual-helper-panel.tsx +1 -1
- package/src/components/ui/helpers/helper-manager.test.tsx +54 -1
- package/src/components/ui/helpers/helper-manager.tsx +55 -1
- package/src/components/ui/level-duplicate-dialog.tsx +1 -1
- package/src/components/ui/panels/collections/collections-popover.tsx +2 -2
- package/src/components/ui/panels/floor-foundation-panel.tsx +3 -3
- package/src/components/ui/panels/floor-opening-panel.tsx +5 -5
- package/src/components/ui/panels/mobile-selection-bar.tsx +28 -24
- package/src/components/ui/panels/multi-height-mode.tsx +3 -3
- package/src/components/ui/panels/multi-selection-panel.tsx +1 -1
- package/src/components/ui/panels/node-display.ts +1 -1
- package/src/components/ui/panels/panel-manager.tsx +5 -3
- package/src/components/ui/panels/panel-wrapper.tsx +42 -19
- package/src/components/ui/panels/parametric-inspector.tsx +1 -1
- package/src/components/ui/panels/room-construction-rows.tsx +7 -7
- package/src/components/ui/panels/room-name-options.ts +1 -5
- package/src/components/ui/panels/room-openings-field.tsx +1 -1
- package/src/components/ui/panels/room-panel.tsx +7 -9
- package/src/components/ui/primitives/color-dot.tsx +2 -2
- package/src/components/ui/primitives/opacity-control.tsx +2 -2
- package/src/components/ui/primitives/sidebar.tsx +3 -3
- package/src/components/ui/right-stack.test.ts +13 -6
- package/src/components/ui/right-stack.tsx +22 -11
- package/src/components/ui/sidebar/icon-rail.tsx +1 -1
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
- package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +2 -2
- package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +1 -1
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +2 -2
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +1 -1
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +2 -6
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +3 -10
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +46 -7
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/tree-node-drag.tsx +14 -5
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -2
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +3 -3
- package/src/components/ui/sidebar/tab-bar.tsx +1 -1
- package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +2 -2
- package/src/components/ui/wall-type/wall-type-menu.tsx +1 -1
- package/src/components/viewer-overlay.tsx +1 -2
- package/src/hooks/use-auto-frame.ts +1 -2
- package/src/hooks/use-keyboard.ts +1 -2
- package/src/hooks/use-selected-room.ts +5 -7
- package/src/i18n/floor-footprint-name.ts +1 -1
- package/src/i18n/locales/en/messages.po +1036 -306
- package/src/i18n/locales/en/messages.ts +1 -1
- package/src/i18n/locales/en-XA/messages.ts +1 -1
- package/src/i18n/locales/es/messages.po +2267 -439
- package/src/i18n/locales/fr/messages.po +2267 -439
- package/src/i18n/locales/ja/messages.po +2267 -439
- package/src/i18n/locales/zh-CN/messages.po +2267 -439
- package/src/i18n/locales.test.ts +1 -1
- package/src/index.tsx +183 -178
- package/src/lib/below-grade.test.tsx +4 -4
- package/src/lib/camera-facing.test.ts +1 -1
- package/src/lib/camera-facing.ts +2 -3
- package/src/lib/contextual-help.ts +6 -0
- package/src/lib/editor-level-display.ts +1 -1
- package/src/lib/elevation-guides.ts +21 -6
- package/src/lib/elevator-support.ts +1 -72
- package/src/lib/floor-footprints.test.ts +3 -3
- package/src/lib/floor-footprints.ts +1 -9
- package/src/lib/floor-foundation-support.test.ts +8 -3
- package/src/lib/floor-opening-draft.ts +2 -2
- package/src/lib/floor-region-session.ts +1 -1
- package/src/lib/floorplan/annotation-visibility.test.ts +1 -1
- package/src/lib/floorplan/annotation-visibility.ts +1 -1
- package/src/lib/floorplan/floorplan-export.test.ts +1 -1
- package/src/lib/floorplan/floorplan-export.tsx +8 -11
- package/src/lib/floorplan/floorplan-extension.ts +4 -157
- package/src/lib/floorplan/floorplan-mode.ts +2 -1
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +1 -1
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +8 -6
- package/src/lib/floorplan/floorplan-plugin-install.test.tsx +3 -4
- package/src/lib/floorplan/geometry.ts +2 -112
- package/src/lib/floorplan/index.ts +0 -9
- package/src/lib/floorplan/items.ts +2 -1
- package/src/lib/floorplan/open-wall-ends.ts +2 -1
- package/src/lib/floorplan/schedules.ts +6 -3
- package/src/lib/floorplan/sheet-export.ts +7 -3
- package/src/lib/floorplan/site-plan/site-parts.ts +7 -1
- package/src/lib/floorplan/site-plan/site-plan-layer.tsx +1 -1
- package/src/lib/follow-pascal-presentation.ts +1 -1
- package/src/lib/gesture-lifecycle.ts +3 -1
- package/src/lib/glb-export.ts +1 -1
- package/src/lib/interaction/scope.ts +1 -0
- package/src/lib/layer-thickness.ts +1 -1
- package/src/lib/level-selection.test.ts +18 -2
- package/src/lib/level-selection.ts +31 -8
- package/src/lib/linear-display.ts +1 -1
- package/src/lib/live-gesture-round-trip.test.ts +1 -1
- package/src/lib/material-paint.ts +1 -1
- package/src/lib/measurement-label.test.ts +1 -1
- package/src/lib/measurements.test.ts +7 -5
- package/src/lib/mezzanine-selection.test.ts +1 -1
- package/src/lib/node-action-movement.test.tsx +115 -0
- package/src/lib/node-action-movement.ts +2 -0
- package/src/lib/paint-region-mode.ts +1 -1
- package/src/lib/placement-metadata.ts +4 -0
- package/src/lib/point-ask/describe-target.ts +6 -8
- package/src/lib/point-ask/region.ts +1 -1
- package/src/lib/point-ask/session.ts +1 -2
- package/src/lib/print-shell-compiler.ts +12 -1
- package/src/lib/print-wall-solids.test.ts +11 -0
- package/src/lib/print-wall-solids.ts +9 -0
- package/src/lib/roof-duplication.test.ts +100 -0
- package/src/lib/roof-duplication.ts +30 -5
- package/src/lib/room-built-on.test.ts +2 -5
- package/src/lib/room-built-on.ts +1 -15
- package/src/lib/room-construction-commands.ts +3 -2
- package/src/lib/room-controls-phase6.test.ts +1 -1
- package/src/lib/room-handle-drag.ts +8 -37
- package/src/lib/room-name-catalog.test.ts +2 -1
- package/src/lib/room-name-messages.test.ts +2 -1
- package/src/lib/room-name-messages.ts +1 -118
- package/src/lib/room-name-storage.test.ts +2 -1
- package/src/lib/room-push-dimensions.test.ts +1 -1
- package/src/lib/room-selection-commands.ts +2 -2
- package/src/lib/room-selection.test.ts +6 -6
- package/src/lib/room-transform-session.ts +1 -2
- package/src/lib/room-zone-routing.ts +2 -2
- package/src/lib/scene-clipboard.ts +2 -1
- package/src/lib/selection-routing.test.ts +0 -28
- package/src/lib/selection-routing.ts +1 -40
- package/src/lib/sfx-bus.ts +8 -32
- package/src/lib/sfx-player.test.ts +72 -1
- package/src/lib/sfx-player.ts +49 -337
- package/src/lib/snapping-mode.test.ts +23 -1
- package/src/lib/viewer-selection.test.ts +38 -10
- package/src/lib/wall-pull.test.ts +1 -1
- package/src/lib/wall-pull.ts +1 -27
- package/src/lib/wall-region-list.ts +3 -1
- package/src/lib/window-interaction.test.ts +4 -2
- package/src/store/use-editor.tsx +22 -26
- package/src/store/use-floorplan-annotation-visibility.ts +5 -5
- package/src/store/use-placement-typing.test.ts +165 -0
- package/src/store/use-placement-typing.ts +186 -0
- package/src/store/use-wall-region-handles-selection.ts +1 -2
- package/src/components/editor/__bench__/room-label-placement.bench.ts +0 -108
- package/src/components/editor/first-person/build-collider-world.test.ts +0 -596
- package/src/components/editor/first-person/build-collider-world.ts +0 -549
- package/src/components/editor/first-person/ground-holes.test.ts +0 -128
- package/src/components/editor/first-person/ground-holes.ts +0 -130
- package/src/components/editor/first-person/mechanism-targets.test.ts +0 -162
- package/src/components/editor/first-person/mechanism-targets.ts +0 -88
- package/src/components/editor/first-person/terrain-collider.test.ts +0 -132
- package/src/components/editor/first-person/terrain-collider.ts +0 -212
- package/src/components/editor/floorplan-camera-sync.test.ts +0 -249
- package/src/components/editor/floorplan-camera-sync.ts +0 -213
- package/src/components/editor/localized-html.tsx +0 -22
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.test.tsx +0 -236
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +0 -708
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +0 -385
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +0 -670
- package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +0 -89
- package/src/components/editor-2d/renderers/floorplan-label-angle.ts +0 -106
- package/src/components/editor-2d/svg-paths.ts +0 -124
- package/src/components/tools/elevator/elevator-defaults.ts +0 -8
- package/src/components/tools/elevator/elevator-tool.tsx +0 -372
- package/src/components/tools/elevator/move-elevator-tool.tsx +0 -262
- package/src/components/ui/action-menu/action-button.tsx +0 -89
- package/src/components/ui/action-menu/follow-pascal-toggle.tsx +0 -42
- package/src/components/ui/primitives/button.tsx +0 -69
- package/src/components/ui/primitives/dropdown-menu.tsx +0 -232
- package/src/components/ui/primitives/popover.tsx +0 -42
- package/src/components/ui/primitives/switch.tsx +0 -29
- package/src/components/ui/primitives/tooltip.tsx +0 -57
- package/src/components/ui/scene-loader.tsx +0 -73
- package/src/components/viewer/floorplan-compass-button.tsx +0 -67
- package/src/components/viewer/floorplan-preview-geometry.test.ts +0 -107
- package/src/components/viewer/floorplan-preview-geometry.ts +0 -281
- package/src/components/viewer/floorplan-preview-navigation.test.ts +0 -43
- package/src/components/viewer/floorplan-preview-navigation.ts +0 -62
- package/src/components/viewer/floorplan-preview-visibility.test.ts +0 -35
- package/src/components/viewer/floorplan-preview-visibility.ts +0 -19
- package/src/components/viewer/floorplan-preview.tsx +0 -1065
- package/src/components/viewer/render-settings-menu.tsx +0 -294
- package/src/components/viewer/sky-control.tsx +0 -710
- package/src/components/viewer/use-viewer-camera-navigation-sync.ts +0 -140
- package/src/components/viewer/use-viewer-ui-sounds.test.ts +0 -15
- package/src/components/viewer/use-viewer-ui-sounds.ts +0 -46
- package/src/components/viewer/viewer-controls-bar.tsx +0 -496
- package/src/components/viewer/viewer-hover-label.tsx +0 -42
- package/src/components/viewer/viewer-rooms.tsx +0 -248
- package/src/components/viewer/viewer-scene-header.tsx +0 -286
- package/src/components/viewer/viewer-selection-manager.tsx +0 -124
- package/src/components/viewer/viewer-stage-modes.test.ts +0 -38
- package/src/components/viewer/viewer-stage-modes.ts +0 -49
- package/src/components/viewer/viewer-stage-switcher.tsx +0 -97
- package/src/components/viewer/viewer-stage.test.tsx +0 -47
- package/src/components/viewer/viewer-stage.tsx +0 -221
- package/src/components/walkthrough-hud.tsx +0 -193
- package/src/hooks/use-mobile.ts +0 -19
- package/src/i18n/level-name.ts +0 -39
- package/src/i18n/scene-theme-name.ts +0 -16
- package/src/lib/below-grade.ts +0 -133
- package/src/lib/camera-pose.test.ts +0 -237
- package/src/lib/camera-pose.ts +0 -172
- package/src/lib/door-interaction.ts +0 -102
- package/src/lib/floorplan/floorplan-extension.test.ts +0 -42
- package/src/lib/floorplan/floorplan-readonly.test.ts +0 -47
- package/src/lib/floorplan/floorplan-readonly.ts +0 -95
- package/src/lib/floorplan/marks.test.ts +0 -213
- package/src/lib/floorplan/marks.ts +0 -238
- package/src/lib/measurement-label.ts +0 -68
- package/src/lib/measurements.ts +0 -225
- package/src/lib/node-display-label.ts +0 -37
- package/src/lib/room-assembly-overlay.test.ts +0 -275
- package/src/lib/room-assembly-overlay.ts +0 -265
- package/src/lib/room-selection.ts +0 -417
- package/src/lib/scene-bounds.test.ts +0 -183
- package/src/lib/scene-bounds.ts +0 -169
- package/src/lib/typing-target.ts +0 -9
- package/src/lib/viewer-selection.ts +0 -595
- package/src/lib/window-interaction.ts +0 -142
- package/src/lib/zone-content.ts +0 -133
- package/src/store/camera-pose-store.test.ts +0 -29
- package/src/store/camera-pose-store.ts +0 -27
- package/src/store/navigation-sync-pose-store.test.ts +0 -34
- package/src/store/navigation-sync-pose-store.ts +0 -27
- package/src/store/use-audio.test.ts +0 -146
- package/src/store/use-audio.tsx +0 -169
- package/src/store/use-first-person-hud.ts +0 -40
- package/src/store/use-follow-pascal.test.ts +0 -62
- package/src/store/use-follow-pascal.ts +0 -53
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type AnyNodeId,
|
|
3
|
-
type InteractiveState,
|
|
4
|
-
useInteractive,
|
|
5
|
-
useScene,
|
|
6
|
-
type WindowInteractiveState,
|
|
7
|
-
} from '@pascal-app/core'
|
|
8
|
-
|
|
9
|
-
export const WINDOW_TOGGLE_ANIMATION_MS = 520
|
|
10
|
-
|
|
11
|
-
export type WindowOpenAnimationOptions = {
|
|
12
|
-
persist?: boolean
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function isOperableWindowType(windowType: string | undefined) {
|
|
16
|
-
return (
|
|
17
|
-
windowType === 'sliding' ||
|
|
18
|
-
windowType === 'casement' ||
|
|
19
|
-
windowType === 'awning' ||
|
|
20
|
-
windowType === 'hopper' ||
|
|
21
|
-
windowType === 'tilt-turn' ||
|
|
22
|
-
windowType === 'single-hung' ||
|
|
23
|
-
windowType === 'double-hung' ||
|
|
24
|
-
windowType === 'louvered'
|
|
25
|
-
)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function getDisplayedWindowValue(
|
|
29
|
-
windowId: AnyNodeId,
|
|
30
|
-
nodeValue: number | undefined,
|
|
31
|
-
field: keyof WindowInteractiveState = 'operationState',
|
|
32
|
-
interactive: InteractiveState = useInteractive.getState(),
|
|
33
|
-
) {
|
|
34
|
-
const runtimeValue = interactive.windows[windowId]?.[field]
|
|
35
|
-
if (runtimeValue !== undefined) return runtimeValue
|
|
36
|
-
|
|
37
|
-
const queuedValue = interactive.windowAnimations[windowId]?.[field]?.from
|
|
38
|
-
if (queuedValue !== undefined) return queuedValue
|
|
39
|
-
|
|
40
|
-
return nodeValue ?? 0
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function startWindowOpenAnimation(
|
|
44
|
-
windowId: AnyNodeId,
|
|
45
|
-
field: keyof WindowInteractiveState,
|
|
46
|
-
from: number,
|
|
47
|
-
to: number,
|
|
48
|
-
options?: WindowOpenAnimationOptions,
|
|
49
|
-
) {
|
|
50
|
-
useInteractive.getState().startWindowAnimation(windowId, {
|
|
51
|
-
field,
|
|
52
|
-
from,
|
|
53
|
-
to,
|
|
54
|
-
startedAt: null,
|
|
55
|
-
durationMs: WINDOW_TOGGLE_ANIMATION_MS,
|
|
56
|
-
persist: options?.persist ?? true,
|
|
57
|
-
})
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function toggleWindowOpenState(windowId: AnyNodeId, options?: WindowOpenAnimationOptions) {
|
|
61
|
-
const node = useScene.getState().nodes[windowId]
|
|
62
|
-
if (
|
|
63
|
-
node?.type !== 'window' ||
|
|
64
|
-
node.openingKind === 'opening' ||
|
|
65
|
-
!isOperableWindowType(node.windowType)
|
|
66
|
-
) {
|
|
67
|
-
return
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const currentOpenAmount = getDisplayedWindowValue(windowId, node.operationState)
|
|
71
|
-
startWindowOpenAnimation(
|
|
72
|
-
windowId,
|
|
73
|
-
'operationState',
|
|
74
|
-
currentOpenAmount,
|
|
75
|
-
currentOpenAmount >= 0.5 ? 0 : 1,
|
|
76
|
-
options,
|
|
77
|
-
)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function closeWindowOpenState(windowId: AnyNodeId, options?: WindowOpenAnimationOptions) {
|
|
81
|
-
const node = useScene.getState().nodes[windowId]
|
|
82
|
-
if (
|
|
83
|
-
node?.type !== 'window' ||
|
|
84
|
-
node.openingKind === 'opening' ||
|
|
85
|
-
!isOperableWindowType(node.windowType)
|
|
86
|
-
) {
|
|
87
|
-
return
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const currentOpenAmount = getDisplayedWindowValue(windowId, node.operationState)
|
|
91
|
-
startWindowOpenAnimation(windowId, 'operationState', currentOpenAmount, 0, options)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export function openWindowOpenState(windowId: AnyNodeId, options?: WindowOpenAnimationOptions) {
|
|
95
|
-
const node = useScene.getState().nodes[windowId]
|
|
96
|
-
if (
|
|
97
|
-
node?.type !== 'window' ||
|
|
98
|
-
node.openingKind === 'opening' ||
|
|
99
|
-
!isOperableWindowType(node.windowType)
|
|
100
|
-
) {
|
|
101
|
-
return
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const currentOpenAmount = getDisplayedWindowValue(windowId, node.operationState)
|
|
105
|
-
startWindowOpenAnimation(windowId, 'operationState', currentOpenAmount, 1, options)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export type WindowShutterField = 'shutterOpen' | 'rollerShutterOpen'
|
|
109
|
-
|
|
110
|
-
export function getDisplayedWindowShutterValue(
|
|
111
|
-
windowId: AnyNodeId,
|
|
112
|
-
field: WindowShutterField,
|
|
113
|
-
nodeValue: number | undefined,
|
|
114
|
-
state: InteractiveState = useInteractive.getState(),
|
|
115
|
-
): number {
|
|
116
|
-
return getDisplayedWindowValue(windowId, nodeValue ?? 1, field, state)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/** Preview both shutter styles together, independently of the sash. */
|
|
120
|
-
export function setWindowShuttersOpenState(
|
|
121
|
-
windowId: AnyNodeId,
|
|
122
|
-
on: boolean,
|
|
123
|
-
options?: WindowOpenAnimationOptions,
|
|
124
|
-
): void {
|
|
125
|
-
const node = useScene.getState().nodes[windowId]
|
|
126
|
-
if (node?.type !== 'window' || node.source) return
|
|
127
|
-
const fields: WindowShutterField[] = []
|
|
128
|
-
if (node.shutters && node.shutters !== 'none') fields.push('shutterOpen')
|
|
129
|
-
if (node.rollerShutter === 'box' || node.rollerShutter === 'concealed')
|
|
130
|
-
fields.push('rollerShutterOpen')
|
|
131
|
-
if (!fields.length) return
|
|
132
|
-
useInteractive.getState().setMechanismPart(windowId, 'shutters', on)
|
|
133
|
-
for (const field of fields) {
|
|
134
|
-
startWindowOpenAnimation(
|
|
135
|
-
windowId,
|
|
136
|
-
field,
|
|
137
|
-
getDisplayedWindowShutterValue(windowId, field, node[field]),
|
|
138
|
-
on ? 1 : 0,
|
|
139
|
-
options,
|
|
140
|
-
)
|
|
141
|
-
}
|
|
142
|
-
}
|
package/src/lib/zone-content.ts
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type AnyNode,
|
|
3
|
-
type AnyNodeId,
|
|
4
|
-
type CeilingNode,
|
|
5
|
-
type ItemNode,
|
|
6
|
-
pointInPolygon2D,
|
|
7
|
-
pointOnSegment,
|
|
8
|
-
type SlabNode,
|
|
9
|
-
type WallNode,
|
|
10
|
-
type ZoneNode,
|
|
11
|
-
} from '@pascal-app/core'
|
|
12
|
-
|
|
13
|
-
type Point2D = [number, number]
|
|
14
|
-
|
|
15
|
-
const POINT_TOLERANCE = 0.5
|
|
16
|
-
const COLLINEAR_TOLERANCE = 1e-6
|
|
17
|
-
const SURFACE_POLYGON_TOLERANCE = 0.15
|
|
18
|
-
|
|
19
|
-
function getPointToSegmentDistance(point: Point2D, start: Point2D, end: Point2D): number {
|
|
20
|
-
const dx = end[0] - start[0]
|
|
21
|
-
const dz = end[1] - start[1]
|
|
22
|
-
const lengthSq = dx * dx + dz * dz
|
|
23
|
-
if (lengthSq === 0) return Math.hypot(point[0] - start[0], point[1] - start[1])
|
|
24
|
-
|
|
25
|
-
const rawT = ((point[0] - start[0]) * dx + (point[1] - start[1]) * dz) / lengthSq
|
|
26
|
-
const t = Math.max(0, Math.min(1, rawT))
|
|
27
|
-
const projected: Point2D = [start[0] + t * dx, start[1] + t * dz]
|
|
28
|
-
return Math.hypot(point[0] - projected[0], point[1] - projected[1])
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function pointInPolygonWithTolerance(point: Point2D, polygon: Point2D[]): boolean {
|
|
32
|
-
if (pointInPolygon2D(point, polygon, { includeBoundary: true })) return true
|
|
33
|
-
return polygon.some((start, index) => {
|
|
34
|
-
const end = polygon[(index + 1) % polygon.length]
|
|
35
|
-
return end ? getPointToSegmentDistance(point, start, end) <= POINT_TOLERANCE : false
|
|
36
|
-
})
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function polygonContainsWithTolerance(
|
|
40
|
-
outer: Point2D[],
|
|
41
|
-
inner: Point2D[],
|
|
42
|
-
tolerance: number,
|
|
43
|
-
): boolean {
|
|
44
|
-
return inner.every((point) => {
|
|
45
|
-
if (pointInPolygon2D(point, outer, { includeBoundary: true })) return true
|
|
46
|
-
return outer.some((start, index) => {
|
|
47
|
-
const end = outer[(index + 1) % outer.length]
|
|
48
|
-
return end ? getPointToSegmentDistance(point, start, end) <= tolerance : false
|
|
49
|
-
})
|
|
50
|
-
})
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function polygonMatchesZoneFootprint(
|
|
54
|
-
surfacePolygon: Point2D[],
|
|
55
|
-
footprint: Point2D[],
|
|
56
|
-
): boolean {
|
|
57
|
-
if (surfacePolygon.length < 3) return false
|
|
58
|
-
return (
|
|
59
|
-
polygonContainsWithTolerance(footprint, surfacePolygon, SURFACE_POLYGON_TOLERANCE) &&
|
|
60
|
-
polygonContainsWithTolerance(surfacePolygon, footprint, SURFACE_POLYGON_TOLERANCE)
|
|
61
|
-
)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function areSegmentsCollinear(a: Point2D, b: Point2D, c: Point2D, d: Point2D): boolean {
|
|
65
|
-
const abx = b[0] - a[0]
|
|
66
|
-
const abz = b[1] - a[1]
|
|
67
|
-
const acx = c[0] - a[0]
|
|
68
|
-
const acz = c[1] - a[1]
|
|
69
|
-
const adx = d[0] - a[0]
|
|
70
|
-
const adz = d[1] - a[1]
|
|
71
|
-
const crossC = abx * acz - abz * acx
|
|
72
|
-
const crossD = abx * adz - abz * adx
|
|
73
|
-
return Math.abs(crossC) <= COLLINEAR_TOLERANCE && Math.abs(crossD) <= COLLINEAR_TOLERANCE
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function segmentsOverlap(a: Point2D, b: Point2D, c: Point2D, d: Point2D): boolean {
|
|
77
|
-
const useX = Math.abs(b[0] - a[0]) >= Math.abs(b[1] - a[1])
|
|
78
|
-
const a0 = useX ? a[0] : a[1]
|
|
79
|
-
const a1 = useX ? b[0] : b[1]
|
|
80
|
-
const c0 = useX ? c[0] : c[1]
|
|
81
|
-
const c1 = useX ? d[0] : d[1]
|
|
82
|
-
const minA = Math.min(a0, a1)
|
|
83
|
-
const maxA = Math.max(a0, a1)
|
|
84
|
-
const minC = Math.min(c0, c1)
|
|
85
|
-
const maxC = Math.max(c0, c1)
|
|
86
|
-
return Math.max(minA, minC) <= Math.min(maxA, maxC) + COLLINEAR_TOLERANCE
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function wallLiesOnZoneBoundary(wall: WallNode, polygon: Point2D[]): boolean {
|
|
90
|
-
return polygon.some((start, index) => {
|
|
91
|
-
const end = polygon[(index + 1) % polygon.length]
|
|
92
|
-
if (!end) return false
|
|
93
|
-
return (
|
|
94
|
-
areSegmentsCollinear(wall.start, wall.end, start, end) &&
|
|
95
|
-
segmentsOverlap(wall.start, wall.end, start, end) &&
|
|
96
|
-
pointOnSegment(wall.start, start, end, POINT_TOLERANCE) &&
|
|
97
|
-
pointOnSegment(wall.end, start, end, POINT_TOLERANCE)
|
|
98
|
-
)
|
|
99
|
-
})
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function collectZoneContentIds(
|
|
103
|
-
nodes: Readonly<Record<AnyNodeId, AnyNode>>,
|
|
104
|
-
zone: ZoneNode,
|
|
105
|
-
): AnyNodeId[] {
|
|
106
|
-
const levelId = zone.parentId
|
|
107
|
-
if (!levelId) return []
|
|
108
|
-
|
|
109
|
-
const footprint = zone.polygon.map((point) => [point[0], point[1]] as Point2D)
|
|
110
|
-
const boundaryWalls = Object.values(nodes)
|
|
111
|
-
.filter((node): node is WallNode => node.type === 'wall' && node.parentId === levelId)
|
|
112
|
-
.filter((wall) => wallLiesOnZoneBoundary(wall, footprint))
|
|
113
|
-
const surfaces = Object.values(nodes)
|
|
114
|
-
.filter(
|
|
115
|
-
(node): node is SlabNode | CeilingNode =>
|
|
116
|
-
(node.type === 'slab' || node.type === 'ceiling') && node.parentId === levelId,
|
|
117
|
-
)
|
|
118
|
-
.filter((surface) => {
|
|
119
|
-
const polygon = surface.polygon.map((point) => [point[0], point[1]] as Point2D)
|
|
120
|
-
return polygonMatchesZoneFootprint(polygon, footprint)
|
|
121
|
-
})
|
|
122
|
-
const floorItems = Object.values(nodes)
|
|
123
|
-
.filter((node): node is ItemNode => node.type === 'item' && node.parentId === levelId)
|
|
124
|
-
.filter((item) => pointInPolygonWithTolerance([item.position[0], item.position[2]], footprint))
|
|
125
|
-
|
|
126
|
-
return Array.from(
|
|
127
|
-
new Set<AnyNodeId>([
|
|
128
|
-
...boundaryWalls.map((wall) => wall.id as AnyNodeId),
|
|
129
|
-
...surfaces.map((surface) => surface.id as AnyNodeId),
|
|
130
|
-
...floorItems.map((item) => item.id as AnyNodeId),
|
|
131
|
-
]),
|
|
132
|
-
)
|
|
133
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, test } from 'bun:test'
|
|
2
|
-
import type { CameraPose } from '@pascal-app/core'
|
|
3
|
-
import { cameraPoseStore, publishCameraPose, subscribeCameraPose } from './camera-pose-store'
|
|
4
|
-
|
|
5
|
-
const pose: CameraPose = {
|
|
6
|
-
position: [3, 4, 5],
|
|
7
|
-
projection: 'perspective',
|
|
8
|
-
target: [0, 1, 0],
|
|
9
|
-
viewWidth: 12,
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
afterEach(() => {
|
|
13
|
-
cameraPoseStore.setState({ pose: null })
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
describe('camera pose store', () => {
|
|
17
|
-
test('replays the latest pose and streams later poses to focused subscribers', () => {
|
|
18
|
-
publishCameraPose(pose)
|
|
19
|
-
const received: CameraPose[] = []
|
|
20
|
-
const unsubscribe = subscribeCameraPose((nextPose) => received.push(nextPose))
|
|
21
|
-
const nextPose = { ...pose, viewWidth: 8 }
|
|
22
|
-
|
|
23
|
-
publishCameraPose(nextPose)
|
|
24
|
-
unsubscribe()
|
|
25
|
-
publishCameraPose({ ...pose, viewWidth: 4 })
|
|
26
|
-
|
|
27
|
-
expect(received).toEqual([pose, nextPose])
|
|
28
|
-
})
|
|
29
|
-
})
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { CameraPose } from '@pascal-app/core'
|
|
2
|
-
import { subscribeWithSelector } from 'zustand/middleware'
|
|
3
|
-
import { createStore } from 'zustand/vanilla'
|
|
4
|
-
|
|
5
|
-
type CameraPoseState = {
|
|
6
|
-
pose: CameraPose | null
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export const cameraPoseStore = createStore<CameraPoseState>()(
|
|
10
|
-
subscribeWithSelector<CameraPoseState>(() => ({ pose: null })),
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
export function publishCameraPose(pose: CameraPose) {
|
|
14
|
-
cameraPoseStore.setState({ pose })
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function subscribeCameraPose(listener: (pose: CameraPose) => void) {
|
|
18
|
-
const currentPose = cameraPoseStore.getState().pose
|
|
19
|
-
if (currentPose) listener(currentPose)
|
|
20
|
-
|
|
21
|
-
return cameraPoseStore.subscribe(
|
|
22
|
-
(state) => state.pose,
|
|
23
|
-
(pose) => {
|
|
24
|
-
if (pose) listener(pose)
|
|
25
|
-
},
|
|
26
|
-
)
|
|
27
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, test } from 'bun:test'
|
|
2
|
-
import {
|
|
3
|
-
navigationSyncPoseStore,
|
|
4
|
-
publishNavigationSyncPoseToStore,
|
|
5
|
-
subscribeNavigationSyncPose,
|
|
6
|
-
} from './navigation-sync-pose-store'
|
|
7
|
-
import type { NavigationSyncPose } from './use-editor'
|
|
8
|
-
|
|
9
|
-
const pose = {
|
|
10
|
-
source: '2d' as const,
|
|
11
|
-
revision: 1,
|
|
12
|
-
target: [1, 2, 3] as [number, number, number],
|
|
13
|
-
azimuth: 0.5,
|
|
14
|
-
viewWidth: 12,
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
afterEach(() => {
|
|
18
|
-
navigationSyncPoseStore.setState({ pose: null })
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
describe('navigation sync pose store', () => {
|
|
22
|
-
test('replays the latest pose and streams only later pose writes', () => {
|
|
23
|
-
publishNavigationSyncPoseToStore(pose)
|
|
24
|
-
const received: NavigationSyncPose[] = []
|
|
25
|
-
const unsubscribe = subscribeNavigationSyncPose((nextPose) => received.push(nextPose))
|
|
26
|
-
const nextPose = { ...pose, revision: 2, viewWidth: 8 }
|
|
27
|
-
|
|
28
|
-
publishNavigationSyncPoseToStore(nextPose)
|
|
29
|
-
unsubscribe()
|
|
30
|
-
publishNavigationSyncPoseToStore({ ...pose, revision: 3 })
|
|
31
|
-
|
|
32
|
-
expect(received).toEqual([pose, nextPose])
|
|
33
|
-
})
|
|
34
|
-
})
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { subscribeWithSelector } from 'zustand/middleware'
|
|
2
|
-
import { createStore } from 'zustand/vanilla'
|
|
3
|
-
import type { NavigationSyncPose } from './use-editor'
|
|
4
|
-
|
|
5
|
-
type NavigationSyncPoseState = {
|
|
6
|
-
pose: NavigationSyncPose | null
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export const navigationSyncPoseStore = createStore<NavigationSyncPoseState>()(
|
|
10
|
-
subscribeWithSelector<NavigationSyncPoseState>(() => ({ pose: null })),
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
export function publishNavigationSyncPoseToStore(pose: NavigationSyncPose) {
|
|
14
|
-
navigationSyncPoseStore.setState({ pose })
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function subscribeNavigationSyncPose(listener: (pose: NavigationSyncPose) => void) {
|
|
18
|
-
const currentPose = navigationSyncPoseStore.getState().pose
|
|
19
|
-
if (currentPose) listener(currentPose)
|
|
20
|
-
|
|
21
|
-
return navigationSyncPoseStore.subscribe(
|
|
22
|
-
(state) => state.pose,
|
|
23
|
-
(pose) => {
|
|
24
|
-
if (pose) listener(pose)
|
|
25
|
-
},
|
|
26
|
-
)
|
|
27
|
-
}
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import { afterAll, beforeEach, describe, expect, test } from 'bun:test'
|
|
2
|
-
import useAudio, { AUDIO_MUTED_STORAGE_KEY, AUDIO_SETTINGS_STORAGE_KEY } from './use-audio'
|
|
3
|
-
|
|
4
|
-
function memoryStorage() {
|
|
5
|
-
const items = new Map<string, string>()
|
|
6
|
-
return {
|
|
7
|
-
items,
|
|
8
|
-
getItem: (key: string) => items.get(key) ?? null,
|
|
9
|
-
setItem: (key: string, value: string) => void items.set(key, value),
|
|
10
|
-
removeItem: (key: string) => void items.delete(key),
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const originals = {
|
|
15
|
-
localStorage: Object.getOwnPropertyDescriptor(globalThis, 'localStorage'),
|
|
16
|
-
sessionStorage: Object.getOwnPropertyDescriptor(globalThis, 'sessionStorage'),
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function use(name: 'localStorage' | 'sessionStorage', storage: ReturnType<typeof memoryStorage>) {
|
|
20
|
-
Object.defineProperty(globalThis, name, { configurable: true, value: storage })
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** One browser profile: its tabs share `localStorage`, each has its own `sessionStorage`. */
|
|
24
|
-
let browser = memoryStorage()
|
|
25
|
-
|
|
26
|
-
/** Another tab of the profile saves the settings: the browser stores them and tells this tab. */
|
|
27
|
-
function savedInAnotherTab(state: Record<string, unknown>) {
|
|
28
|
-
const value = JSON.stringify({ state, version: 0 })
|
|
29
|
-
browser.setItem(AUDIO_SETTINGS_STORAGE_KEY, value)
|
|
30
|
-
globalThis.dispatchEvent(
|
|
31
|
-
Object.assign(new Event('storage'), { key: AUDIO_SETTINGS_STORAGE_KEY, newValue: value }),
|
|
32
|
-
)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** The editor loading in a tab: a new one, or the same one reloaded (its session kept). */
|
|
36
|
-
async function loadIn(tab: ReturnType<typeof memoryStorage>) {
|
|
37
|
-
use('sessionStorage', tab)
|
|
38
|
-
await useAudio.persist.rehydrate()
|
|
39
|
-
return useAudio.getState()
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
beforeEach(() => {
|
|
43
|
-
browser = memoryStorage()
|
|
44
|
-
use('localStorage', browser)
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
afterAll(() => {
|
|
48
|
-
for (const [name, descriptor] of Object.entries(originals)) {
|
|
49
|
-
if (descriptor) Object.defineProperty(globalThis, name, descriptor)
|
|
50
|
-
else Reflect.deleteProperty(globalThis, name)
|
|
51
|
-
}
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
describe('muting a tab', () => {
|
|
55
|
-
test('a tab keeps its own mute across reloads, and a new tab starts from the last choice', async () => {
|
|
56
|
-
const first = memoryStorage()
|
|
57
|
-
const second = memoryStorage()
|
|
58
|
-
|
|
59
|
-
expect((await loadIn(first)).muted).toBe(false)
|
|
60
|
-
useAudio.getState().toggleMute()
|
|
61
|
-
|
|
62
|
-
expect((await loadIn(second)).muted).toBe(true)
|
|
63
|
-
useAudio.getState().setMuted(false)
|
|
64
|
-
|
|
65
|
-
expect((await loadIn(first)).muted).toBe(true)
|
|
66
|
-
expect((await loadIn(memoryStorage())).muted).toBe(false)
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
test("another tab's choice does not change a tab that never chose", async () => {
|
|
70
|
-
const quiet = memoryStorage()
|
|
71
|
-
expect((await loadIn(quiet)).muted).toBe(false)
|
|
72
|
-
|
|
73
|
-
await loadIn(memoryStorage())
|
|
74
|
-
useAudio.getState().setMuted(true)
|
|
75
|
-
|
|
76
|
-
expect((await loadIn(quiet)).muted).toBe(false)
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
test("volumes are every tab's and never carry a mute", async () => {
|
|
80
|
-
const muting = memoryStorage()
|
|
81
|
-
await loadIn(muting)
|
|
82
|
-
useAudio.getState().setMuted(true)
|
|
83
|
-
|
|
84
|
-
await loadIn(memoryStorage())
|
|
85
|
-
useAudio.getState().setMuted(false)
|
|
86
|
-
await loadIn(muting)
|
|
87
|
-
useAudio.getState().setSfxVolume(20)
|
|
88
|
-
|
|
89
|
-
expect(JSON.parse(browser.getItem(AUDIO_SETTINGS_STORAGE_KEY)!).state).not.toHaveProperty(
|
|
90
|
-
'muted',
|
|
91
|
-
)
|
|
92
|
-
expect(browser.getItem(AUDIO_MUTED_STORAGE_KEY)).toBe('false')
|
|
93
|
-
const next = await loadIn(memoryStorage())
|
|
94
|
-
expect(next.sfxVolume).toBe(20)
|
|
95
|
-
expect(next.muted).toBe(false)
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
test("a volume another tab sets reaches this one at once, and this tab's saves carry it", async () => {
|
|
99
|
-
await loadIn(memoryStorage())
|
|
100
|
-
useAudio.getState().setSfxVolume(30)
|
|
101
|
-
useAudio.getState().setRadioPlaying(true)
|
|
102
|
-
const stored = JSON.parse(browser.getItem(AUDIO_SETTINGS_STORAGE_KEY)!)
|
|
103
|
-
|
|
104
|
-
savedInAnotherTab({ ...stored.state, isRadioPlaying: false, muted: true, sfxVolume: 80 })
|
|
105
|
-
|
|
106
|
-
expect(useAudio.getState()).toMatchObject({ isRadioPlaying: true, muted: false, sfxVolume: 80 })
|
|
107
|
-
const setElsewhere = browser.getItem(AUDIO_SETTINGS_STORAGE_KEY)
|
|
108
|
-
useAudio.getState().setMuted(true)
|
|
109
|
-
expect(browser.getItem(AUDIO_SETTINGS_STORAGE_KEY)).toBe(setElsewhere)
|
|
110
|
-
// The radio pausing as the tab is hidden saves every setting: the other tab's volume stays.
|
|
111
|
-
useAudio.getState().setRadioPlaying(false)
|
|
112
|
-
useAudio.getState().setMasterVolume(60)
|
|
113
|
-
expect(JSON.parse(browser.getItem(AUDIO_SETTINGS_STORAGE_KEY)!).state).toMatchObject({
|
|
114
|
-
masterVolume: 60,
|
|
115
|
-
sfxVolume: 80,
|
|
116
|
-
})
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
test("a mute saved when it was every tab's still holds, in settings the old build still reads", async () => {
|
|
120
|
-
browser.setItem(
|
|
121
|
-
AUDIO_SETTINGS_STORAGE_KEY,
|
|
122
|
-
JSON.stringify({ state: { masterVolume: 40, muted: true, sfxVolume: 50 }, version: 0 }),
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
const state = await loadIn(memoryStorage())
|
|
126
|
-
|
|
127
|
-
expect(state.muted).toBe(true)
|
|
128
|
-
expect(state.masterVolume).toBe(40)
|
|
129
|
-
expect((await loadIn(memoryStorage())).muted).toBe(true)
|
|
130
|
-
useAudio.getState().setSfxVolume(45)
|
|
131
|
-
const saved = JSON.parse(browser.getItem(AUDIO_SETTINGS_STORAGE_KEY)!)
|
|
132
|
-
expect(saved).toMatchObject({ state: { masterVolume: 40, sfxVolume: 45 }, version: 0 })
|
|
133
|
-
expect(saved.state).not.toHaveProperty('muted')
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
test('a tab still on the old settings does not undo a choice made since', async () => {
|
|
137
|
-
await loadIn(memoryStorage())
|
|
138
|
-
useAudio.getState().setMuted(false)
|
|
139
|
-
browser.setItem(
|
|
140
|
-
AUDIO_SETTINGS_STORAGE_KEY,
|
|
141
|
-
JSON.stringify({ state: { muted: true, sfxVolume: 50 }, version: 0 }),
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
expect((await loadIn(memoryStorage())).muted).toBe(false)
|
|
145
|
-
})
|
|
146
|
-
})
|