@pascal-app/viewer 1.1.0 → 1.1.2
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 +157 -0
- package/dist/components/viewer/drone-controller.d.ts.map +1 -1
- package/dist/components/viewer/drone-controller.js +1 -5
- package/dist/components/viewer/first-person/build-collider-world.d.ts +14 -0
- package/dist/components/viewer/first-person/build-collider-world.d.ts.map +1 -0
- package/dist/components/viewer/first-person/build-collider-world.js +426 -0
- package/dist/components/viewer/first-person/ground-holes.d.ts +22 -0
- package/dist/components/viewer/first-person/ground-holes.d.ts.map +1 -0
- package/dist/components/viewer/first-person/ground-holes.js +116 -0
- package/dist/components/viewer/first-person/mechanism-targets.d.ts +27 -0
- package/dist/components/viewer/first-person/mechanism-targets.d.ts.map +1 -0
- package/dist/components/viewer/first-person/mechanism-targets.js +79 -0
- package/dist/components/viewer/first-person/terrain-collider.d.ts +32 -0
- package/dist/components/viewer/first-person/terrain-collider.d.ts.map +1 -0
- package/dist/components/viewer/first-person/terrain-collider.js +175 -0
- package/dist/components/viewer/glb-walkthrough-controller.d.ts +1 -1
- package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -1
- package/dist/components/viewer/glb-walkthrough-controller.js +3 -4
- package/dist/components/viewer/live-walkthrough-controller.d.ts +17 -0
- package/dist/components/viewer/live-walkthrough-controller.d.ts.map +1 -0
- package/dist/components/viewer/live-walkthrough-controller.js +1449 -0
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +22 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/lib/daylight/room-map.d.ts +29 -4
- package/dist/lib/daylight/room-map.d.ts.map +1 -1
- package/dist/lib/daylight/room-map.js +219 -36
- package/dist/lib/daylight/room-scene.d.ts.map +1 -1
- package/dist/lib/daylight/room-scene.js +9 -3
- package/dist/lib/daylight/rooms.d.ts +15 -6
- package/dist/lib/daylight/rooms.d.ts.map +1 -1
- package/dist/lib/daylight/rooms.js +49 -9
- package/dist/lib/measurement-presentation.d.ts +2 -0
- package/dist/lib/measurement-presentation.d.ts.map +1 -1
- package/dist/lib/measurement-presentation.js +6 -0
- package/dist/lib/typing-target.d.ts +2 -0
- package/dist/lib/typing-target.d.ts.map +1 -0
- package/dist/lib/typing-target.js +7 -0
- package/dist/lib/viewer-config.d.ts +6 -0
- package/dist/lib/viewer-config.d.ts.map +1 -1
- package/dist/lib/viewer-config.js +2 -0
- package/dist/sfx/audio-settings.d.ts +81 -0
- package/dist/sfx/audio-settings.d.ts.map +1 -0
- package/dist/sfx/audio-settings.js +166 -0
- package/dist/sfx/index.d.ts +6 -0
- package/dist/sfx/index.d.ts.map +1 -0
- package/dist/sfx/index.js +5 -0
- package/dist/sfx/interaction-sounds.d.ts +7 -0
- package/dist/sfx/interaction-sounds.d.ts.map +1 -0
- package/dist/sfx/interaction-sounds.js +39 -0
- package/dist/sfx/player.d.ts +31 -0
- package/dist/sfx/player.d.ts.map +1 -0
- package/dist/sfx/player.js +255 -0
- package/dist/sfx/sounds.d.ts +60 -0
- package/dist/sfx/sounds.d.ts.map +1 -0
- package/dist/sfx/sounds.js +25 -0
- package/dist/sfx/use-viewer-ui-sounds.d.ts +8 -0
- package/dist/sfx/use-viewer-ui-sounds.d.ts.map +1 -0
- package/dist/sfx/use-viewer-ui-sounds.js +42 -0
- package/dist/store/use-first-person-hud.d.ts +13 -0
- package/dist/store/use-first-person-hud.d.ts.map +1 -0
- package/dist/store/use-first-person-hud.js +21 -0
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +27 -13
- package/dist/ui/components/action-button.d.ts +14 -0
- package/dist/ui/components/action-button.d.ts.map +1 -0
- package/dist/ui/components/action-button.js +21 -0
- package/dist/ui/components/action-menu-placement.d.ts +7 -0
- package/dist/ui/components/action-menu-placement.d.ts.map +1 -0
- package/dist/ui/components/action-menu-placement.js +10 -0
- package/dist/ui/components/floorplan-compass-button.d.ts +10 -0
- package/dist/ui/components/floorplan-compass-button.d.ts.map +1 -0
- package/dist/ui/components/floorplan-compass-button.js +22 -0
- package/dist/ui/components/follow-pascal-toggle.d.ts +6 -0
- package/dist/ui/components/follow-pascal-toggle.d.ts.map +1 -0
- package/dist/ui/components/follow-pascal-toggle.js +21 -0
- package/dist/ui/components/localized-html.d.ts +9 -0
- package/dist/ui/components/localized-html.d.ts.map +1 -0
- package/dist/ui/components/localized-html.js +16 -0
- package/dist/ui/components/render-settings-menu.d.ts +9 -0
- package/dist/ui/components/render-settings-menu.d.ts.map +1 -0
- package/dist/ui/components/render-settings-menu.js +145 -0
- package/dist/ui/components/rooms/room-highlight.d.ts +10 -0
- package/dist/ui/components/rooms/room-highlight.d.ts.map +1 -0
- package/dist/ui/components/rooms/room-highlight.js +56 -0
- package/dist/ui/components/rooms/room-labels.d.ts +278 -0
- package/dist/ui/components/rooms/room-labels.d.ts.map +1 -0
- package/dist/ui/components/rooms/room-labels.js +1295 -0
- package/dist/ui/components/rooms/viewer-rooms.d.ts +14 -0
- package/dist/ui/components/rooms/viewer-rooms.d.ts.map +1 -0
- package/dist/ui/components/rooms/viewer-rooms.js +123 -0
- package/dist/ui/components/scene-loader.d.ts +18 -0
- package/dist/ui/components/scene-loader.d.ts.map +1 -0
- package/dist/ui/components/scene-loader.js +29 -0
- package/dist/ui/components/sky-control.d.ts +37 -0
- package/dist/ui/components/sky-control.d.ts.map +1 -0
- package/dist/ui/components/sky-control.js +382 -0
- package/dist/ui/components/use-viewer-camera-navigation-sync.d.ts +4 -0
- package/dist/ui/components/use-viewer-camera-navigation-sync.d.ts.map +1 -0
- package/dist/ui/components/use-viewer-camera-navigation-sync.js +117 -0
- package/dist/ui/components/viewer-controls-bar.d.ts +20 -0
- package/dist/ui/components/viewer-controls-bar.d.ts.map +1 -0
- package/dist/ui/components/viewer-controls-bar.js +130 -0
- package/dist/ui/components/viewer-hover-label.d.ts +14 -0
- package/dist/ui/components/viewer-hover-label.d.ts.map +1 -0
- package/dist/ui/components/viewer-hover-label.js +28 -0
- package/dist/ui/components/viewer-scene-header.d.ts +34 -0
- package/dist/ui/components/viewer-scene-header.d.ts.map +1 -0
- package/dist/ui/components/viewer-scene-header.js +92 -0
- package/dist/ui/components/viewer-selection-manager.d.ts +7 -0
- package/dist/ui/components/viewer-selection-manager.d.ts.map +1 -0
- package/dist/ui/components/viewer-selection-manager.js +116 -0
- package/dist/ui/components/viewer-stage-modes.d.ts +7 -0
- package/dist/ui/components/viewer-stage-modes.d.ts.map +1 -0
- package/dist/ui/components/viewer-stage-modes.js +42 -0
- package/dist/ui/components/viewer-stage-switcher.d.ts +10 -0
- package/dist/ui/components/viewer-stage-switcher.d.ts.map +1 -0
- package/dist/ui/components/viewer-stage-switcher.js +16 -0
- package/dist/ui/components/viewer-stage.d.ts +25 -0
- package/dist/ui/components/viewer-stage.d.ts.map +1 -0
- package/dist/ui/components/viewer-stage.js +107 -0
- package/dist/ui/components/walkthrough-hud.d.ts +29 -0
- package/dist/ui/components/walkthrough-hud.d.ts.map +1 -0
- package/dist/ui/components/walkthrough-hud.js +55 -0
- package/dist/ui/floorplan/floorplan-camera-sync.d.ts +22 -0
- package/dist/ui/floorplan/floorplan-camera-sync.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-camera-sync.js +157 -0
- package/dist/ui/floorplan/floorplan-dimension-renderer.d.ts +61 -0
- package/dist/ui/floorplan/floorplan-dimension-renderer.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-dimension-renderer.js +280 -0
- package/dist/ui/floorplan/floorplan-geometry-renderer.d.ts +54 -0
- package/dist/ui/floorplan/floorplan-geometry-renderer.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-geometry-renderer.js +317 -0
- package/dist/ui/floorplan/floorplan-label-angle.d.ts +24 -0
- package/dist/ui/floorplan/floorplan-label-angle.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-label-angle.js +60 -0
- package/dist/ui/floorplan/floorplan-navigation-sync.d.ts +18 -0
- package/dist/ui/floorplan/floorplan-navigation-sync.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-navigation-sync.js +41 -0
- package/dist/ui/floorplan/floorplan-preview-geometry.d.ts +26 -0
- package/dist/ui/floorplan/floorplan-preview-geometry.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-preview-geometry.js +173 -0
- package/dist/ui/floorplan/floorplan-preview-navigation.d.ts +22 -0
- package/dist/ui/floorplan/floorplan-preview-navigation.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-preview-navigation.js +41 -0
- package/dist/ui/floorplan/floorplan-preview-visibility.d.ts +8 -0
- package/dist/ui/floorplan/floorplan-preview-visibility.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-preview-visibility.js +21 -0
- package/dist/ui/floorplan/floorplan-preview.d.ts +19 -0
- package/dist/ui/floorplan/floorplan-preview.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-preview.js +700 -0
- package/dist/ui/hooks/use-mobile.d.ts +2 -0
- package/dist/ui/hooks/use-mobile.d.ts.map +1 -0
- package/dist/ui/hooks/use-mobile.js +14 -0
- package/dist/ui/index.d.ts +49 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +48 -0
- package/dist/ui/lib/below-grade.d.ts +38 -0
- package/dist/ui/lib/below-grade.d.ts.map +1 -0
- package/dist/ui/lib/below-grade.js +102 -0
- package/dist/ui/lib/camera-pose.d.ts +19 -0
- package/dist/ui/lib/camera-pose.d.ts.map +1 -0
- package/dist/ui/lib/camera-pose.js +113 -0
- package/dist/ui/lib/cn.d.ts +3 -0
- package/dist/ui/lib/cn.d.ts.map +1 -0
- package/dist/ui/lib/cn.js +5 -0
- package/dist/ui/lib/level-name.d.ts +10 -0
- package/dist/ui/lib/level-name.d.ts.map +1 -0
- package/dist/ui/lib/level-name.js +33 -0
- package/dist/ui/lib/node-display-label.d.ts +8 -0
- package/dist/ui/lib/node-display-label.d.ts.map +1 -0
- package/dist/ui/lib/node-display-label.js +36 -0
- package/dist/ui/lib/room-area.d.ts +3 -0
- package/dist/ui/lib/room-area.d.ts.map +1 -0
- package/dist/ui/lib/room-area.js +8 -0
- package/dist/ui/lib/room-assembly-overlay.d.ts +46 -0
- package/dist/ui/lib/room-assembly-overlay.d.ts.map +1 -0
- package/dist/ui/lib/room-assembly-overlay.js +222 -0
- package/dist/ui/lib/room-name-messages.d.ts +28 -0
- package/dist/ui/lib/room-name-messages.d.ts.map +1 -0
- package/dist/ui/lib/room-name-messages.js +112 -0
- package/dist/ui/lib/room-selection.d.ts +58 -0
- package/dist/ui/lib/room-selection.d.ts.map +1 -0
- package/dist/ui/lib/room-selection.js +297 -0
- package/dist/ui/lib/scene-theme-name.d.ts +4 -0
- package/dist/ui/lib/scene-theme-name.d.ts.map +1 -0
- package/dist/ui/lib/scene-theme-name.js +22 -0
- package/dist/ui/lib/selection-modifiers.d.ts +20 -0
- package/dist/ui/lib/selection-modifiers.d.ts.map +1 -0
- package/dist/ui/lib/selection-modifiers.js +15 -0
- package/dist/ui/lib/viewer-selection.d.ts +132 -0
- package/dist/ui/lib/viewer-selection.d.ts.map +1 -0
- package/dist/ui/lib/viewer-selection.js +462 -0
- package/dist/ui/lib/zone-content.d.ts +6 -0
- package/dist/ui/lib/zone-content.d.ts.map +1 -0
- package/dist/ui/lib/zone-content.js +96 -0
- package/dist/ui/primitives/button.d.ts +11 -0
- package/dist/ui/primitives/button.d.ts.map +1 -0
- package/dist/ui/primitives/button.js +35 -0
- package/dist/ui/primitives/dropdown-menu.d.ts +26 -0
- package/dist/ui/primitives/dropdown-menu.d.ts.map +1 -0
- package/dist/ui/primitives/dropdown-menu.js +53 -0
- package/dist/ui/primitives/popover.d.ts +8 -0
- package/dist/ui/primitives/popover.d.ts.map +1 -0
- package/dist/ui/primitives/popover.js +17 -0
- package/dist/ui/primitives/switch.d.ts +5 -0
- package/dist/ui/primitives/switch.d.ts.map +1 -0
- package/dist/ui/primitives/switch.js +8 -0
- package/dist/ui/primitives/tooltip.d.ts +8 -0
- package/dist/ui/primitives/tooltip.d.ts.map +1 -0
- package/dist/ui/primitives/tooltip.js +17 -0
- package/dist/ui/store/camera-pose-store.d.ts +17 -0
- package/dist/ui/store/camera-pose-store.d.ts.map +1 -0
- package/dist/ui/store/camera-pose-store.js +15 -0
- package/dist/ui/store/navigation-sync-pose-store.d.ts +26 -0
- package/dist/ui/store/navigation-sync-pose-store.d.ts.map +1 -0
- package/dist/ui/store/navigation-sync-pose-store.js +16 -0
- package/dist/ui/store/use-follow-pascal.d.ts +30 -0
- package/dist/ui/store/use-follow-pascal.d.ts.map +1 -0
- package/dist/ui/store/use-follow-pascal.js +28 -0
- package/dist/ui/theme.css +339 -0
- package/package.json +34 -7
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export const VIEWER_STAGE_MODES = ['3d', '2d', 'split'];
|
|
2
|
+
const THREE_D_MODES = ['3d'];
|
|
3
|
+
const TWO_D_MODES = ['2d'];
|
|
4
|
+
const SPLIT_MODES = ['split'];
|
|
5
|
+
const THREE_D_TWO_D_MODES = ['3d', '2d'];
|
|
6
|
+
const THREE_D_SPLIT_MODES = ['3d', 'split'];
|
|
7
|
+
const TWO_D_SPLIT_MODES = ['2d', 'split'];
|
|
8
|
+
export function normalizeViewerStageModes(modes) {
|
|
9
|
+
if (!modes)
|
|
10
|
+
return VIEWER_STAGE_MODES;
|
|
11
|
+
const has3D = modes.includes('3d');
|
|
12
|
+
const has2D = modes.includes('2d');
|
|
13
|
+
const hasSplit = modes.includes('split');
|
|
14
|
+
if (has3D && has2D && hasSplit)
|
|
15
|
+
return VIEWER_STAGE_MODES;
|
|
16
|
+
if (has3D && has2D)
|
|
17
|
+
return THREE_D_TWO_D_MODES;
|
|
18
|
+
if (has3D && hasSplit)
|
|
19
|
+
return THREE_D_SPLIT_MODES;
|
|
20
|
+
if (has2D && hasSplit)
|
|
21
|
+
return TWO_D_SPLIT_MODES;
|
|
22
|
+
if (has2D)
|
|
23
|
+
return TWO_D_MODES;
|
|
24
|
+
if (hasSplit)
|
|
25
|
+
return SPLIT_MODES;
|
|
26
|
+
return THREE_D_MODES;
|
|
27
|
+
}
|
|
28
|
+
export function resolveViewerStageMode(mode, modes) {
|
|
29
|
+
return mode && modes.includes(mode) ? mode : (modes[0] ?? '3d');
|
|
30
|
+
}
|
|
31
|
+
export function resolveMobileViewerStageMode(mode, modes) {
|
|
32
|
+
if (mode !== 'split')
|
|
33
|
+
return mode;
|
|
34
|
+
if (modes.includes('2d'))
|
|
35
|
+
return '2d';
|
|
36
|
+
if (modes.includes('3d'))
|
|
37
|
+
return '3d';
|
|
38
|
+
return 'split';
|
|
39
|
+
}
|
|
40
|
+
export function viewerStageIncludes3D(modes) {
|
|
41
|
+
return modes.includes('3d') || modes.includes('split');
|
|
42
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ViewerStageMode } from './viewer-stage-modes';
|
|
2
|
+
export type ViewerStageSwitcherProps = {
|
|
3
|
+
className?: string;
|
|
4
|
+
hideSplitOnMobile?: boolean;
|
|
5
|
+
mode: ViewerStageMode;
|
|
6
|
+
modes?: readonly ViewerStageMode[];
|
|
7
|
+
onChange: (mode: ViewerStageMode) => void;
|
|
8
|
+
};
|
|
9
|
+
export declare function ViewerStageSwitcher({ className, hideSplitOnMobile, mode, modes, onChange, }: ViewerStageSwitcherProps): import("react").JSX.Element;
|
|
10
|
+
//# sourceMappingURL=viewer-stage-switcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viewer-stage-switcher.d.ts","sourceRoot":"","sources":["../../../src/ui/components/viewer-stage-switcher.tsx"],"names":[],"mappings":"AAMA,OAAO,EAA6B,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAEtF,MAAM,MAAM,wBAAwB,GAAG;IACrC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,IAAI,EAAE,eAAe,CAAA;IACrB,KAAK,CAAC,EAAE,SAAS,eAAe,EAAE,CAAA;IAClC,QAAQ,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,CAAA;CAC1C,CAAA;AAED,wBAAgB,mBAAmB,CAAC,EAClC,SAAS,EACT,iBAAwB,EACxB,IAAI,EACJ,KAAK,EACL,QAAQ,GACT,EAAE,wBAAwB,+BAwC1B"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { usePascalI18n } from '@pascal-app/core';
|
|
4
|
+
import { Box, Columns2, Map as MapIcon } from 'lucide-react';
|
|
5
|
+
import { cn } from '../lib/cn';
|
|
6
|
+
import { normalizeViewerStageModes } from './viewer-stage-modes';
|
|
7
|
+
export function ViewerStageSwitcher({ className, hideSplitOnMobile = true, mode, modes, onChange, }) {
|
|
8
|
+
const i18n = usePascalI18n();
|
|
9
|
+
const enabledModes = normalizeViewerStageModes(modes);
|
|
10
|
+
return (_jsxs("div", { "aria-label": i18n._({ id: 'viewer.stage.layout', message: 'Viewer layout' }), className: cn('dark absolute top-4 left-1/2 z-30 flex -translate-x-1/2 items-center gap-1 rounded-full border border-white/10 bg-neutral-950/82 p-1 text-white shadow-elevation-4 backdrop-blur-xl', className), role: "group", children: [enabledModes.includes('3d') ? (_jsx(StageButton, { active: mode === '3d', icon: _jsx(Box, {}), label: "3D", onClick: () => onChange('3d') })) : null, enabledModes.includes('2d') ? (_jsx(StageButton, { active: mode === '2d', icon: _jsx(MapIcon, {}), label: "2D", onClick: () => onChange('2d') })) : null, enabledModes.includes('split') ? (_jsx(StageButton, { active: mode === 'split', className: hideSplitOnMobile && enabledModes.length > 1 ? 'hidden md:flex' : undefined, icon: _jsx(Columns2, {}), label: i18n._({ id: 'viewer.stage.split', message: 'Split' }), onClick: () => onChange('split') })) : null] }));
|
|
11
|
+
}
|
|
12
|
+
function StageButton({ active, className, icon, label, onClick, }) {
|
|
13
|
+
return (_jsxs("button", { "aria-pressed": active, className: cn('flex h-8 items-center gap-1.5 rounded-full px-3 font-medium text-xs', active
|
|
14
|
+
? 'bg-white text-neutral-950 shadow-sm'
|
|
15
|
+
: 'text-neutral-300 transition-colors hover:bg-white/10 hover:text-white', className), onClick: onClick, type: "button", children: [_jsx("span", { className: "[&>svg]:size-3.5", children: icon }), label] }));
|
|
16
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { type FloorplanPreviewScene } from '../floorplan/floorplan-preview';
|
|
3
|
+
import { type ViewerStageMode } from './viewer-stage-modes';
|
|
4
|
+
export type ViewerStageProps = {
|
|
5
|
+
children?: ReactNode;
|
|
6
|
+
className?: string;
|
|
7
|
+
collapseSplitOnMobile?: boolean;
|
|
8
|
+
compassHost?: Element | null;
|
|
9
|
+
defaultMode?: ViewerStageMode;
|
|
10
|
+
floorplanClassName?: string;
|
|
11
|
+
levelId?: string | null;
|
|
12
|
+
mode?: ViewerStageMode;
|
|
13
|
+
modes?: readonly ViewerStageMode[];
|
|
14
|
+
onLevelChange?: (levelId: string) => void;
|
|
15
|
+
onModeChange?: (mode: ViewerStageMode) => void;
|
|
16
|
+
scene?: FloorplanPreviewScene | null;
|
|
17
|
+
showCompass?: boolean;
|
|
18
|
+
showLevelSelector?: boolean;
|
|
19
|
+
showSwitcher?: boolean;
|
|
20
|
+
switcherClassName?: string;
|
|
21
|
+
synchronizeNavigation?: boolean;
|
|
22
|
+
threeDClassName?: string;
|
|
23
|
+
};
|
|
24
|
+
export declare function ViewerStage({ children, className, collapseSplitOnMobile, compassHost, defaultMode, floorplanClassName, levelId, mode: controlledMode, modes, onLevelChange, onModeChange, scene, showCompass, showLevelSelector, showSwitcher, switcherClassName, synchronizeNavigation, threeDClassName, }: ViewerStageProps): import("react").JSX.Element;
|
|
25
|
+
//# sourceMappingURL=viewer-stage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viewer-stage.d.ts","sourceRoot":"","sources":["../../../src/ui/components/viewer-stage.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKtC,OAAO,EAAoB,KAAK,qBAAqB,EAAE,MAAM,gCAAgC,CAAA;AAE7F,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,sBAAsB,CAAA;AAG7B,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,WAAW,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;IAC5B,WAAW,CAAC,EAAE,eAAe,CAAA;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,IAAI,CAAC,EAAE,eAAe,CAAA;IACtB,KAAK,CAAC,EAAE,SAAS,eAAe,EAAE,CAAA;IAClC,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACzC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,CAAA;IAC9C,KAAK,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAA;IACpC,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB,CAAA;AA2BD,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,SAAS,EACT,qBAA4B,EAC5B,WAAW,EACX,WAAW,EACX,kBAAkB,EAClB,OAAO,EACP,IAAI,EAAE,cAAc,EACpB,KAAK,EACL,aAAa,EACb,YAAY,EACZ,KAAK,EACL,WAAkB,EAClB,iBAAwB,EACxB,YAAmB,EACnB,iBAAiB,EACjB,qBAA4B,EAC5B,eAAe,GAChB,EAAE,gBAAgB,+BAyIlB"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useScene } from '@pascal-app/core';
|
|
4
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
5
|
+
import { useShallow } from 'zustand/react/shallow';
|
|
6
|
+
import { markPerfAction } from '../../lib/perf-actions';
|
|
7
|
+
import useViewer from '../../store/use-viewer';
|
|
8
|
+
import { FloorplanPreview } from '../floorplan/floorplan-preview';
|
|
9
|
+
import { cn } from '../lib/cn';
|
|
10
|
+
import { normalizeViewerStageModes, resolveMobileViewerStageMode, resolveViewerStageMode, viewerStageIncludes3D, } from './viewer-stage-modes';
|
|
11
|
+
import { ViewerStageSwitcher } from './viewer-stage-switcher';
|
|
12
|
+
const EMPTY_LEVEL_IDS = [];
|
|
13
|
+
function levelNodeIds(nodes) {
|
|
14
|
+
return Object.values(nodes)
|
|
15
|
+
.filter((node) => node.type === 'level')
|
|
16
|
+
.sort((left, right) => (left.level ?? 0) - (right.level ?? 0))
|
|
17
|
+
.map((node) => node.id);
|
|
18
|
+
}
|
|
19
|
+
function selectViewerLevel(nodes, levelId) {
|
|
20
|
+
const level = nodes[levelId];
|
|
21
|
+
if (level?.type !== 'level')
|
|
22
|
+
return;
|
|
23
|
+
const building = level.parentId ? nodes[level.parentId] : null;
|
|
24
|
+
const viewer = useViewer.getState();
|
|
25
|
+
viewer.setSelection({
|
|
26
|
+
buildingId: building?.type === 'building' ? building.id : null,
|
|
27
|
+
levelId: level.id,
|
|
28
|
+
selectedIds: [],
|
|
29
|
+
zoneId: null,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export function ViewerStage({ children, className, collapseSplitOnMobile = true, compassHost, defaultMode, floorplanClassName, levelId, mode: controlledMode, modes, onLevelChange, onModeChange, scene, showCompass = true, showLevelSelector = true, showSwitcher = true, switcherClassName, synchronizeNavigation = true, threeDClassName, }) {
|
|
33
|
+
const enabledModes = normalizeViewerStageModes(modes);
|
|
34
|
+
const [internalMode, setInternalMode] = useState(() => resolveViewerStageMode(defaultMode, enabledModes));
|
|
35
|
+
const activeMode = resolveViewerStageMode(controlledMode ?? internalMode, enabledModes);
|
|
36
|
+
const storeLevelIds = useScene(useShallow((state) => (scene ? EMPTY_LEVEL_IDS : levelNodeIds(state.nodes))));
|
|
37
|
+
const externalLevelIds = useMemo(() => (scene ? levelNodeIds(scene.nodes) : null), [scene]);
|
|
38
|
+
const levelIds = externalLevelIds ?? storeLevelIds;
|
|
39
|
+
const selectedLevelId = useViewer((state) => state.selection.levelId);
|
|
40
|
+
const [internalLevelId, setInternalLevelId] = useState(null);
|
|
41
|
+
const [internalCompassHost, setInternalCompassHost] = useState(null);
|
|
42
|
+
const resolvedCompassHost = compassHost ?? internalCompassHost;
|
|
43
|
+
const floorplanEnabled = enabledModes.includes('2d') || enabledModes.includes('split');
|
|
44
|
+
const threeDEnabled = viewerStageIncludes3D(enabledModes);
|
|
45
|
+
const mountFloorplan = floorplanEnabled || showCompass;
|
|
46
|
+
const requestMode = useCallback((nextMode) => {
|
|
47
|
+
if (!enabledModes.includes(nextMode))
|
|
48
|
+
return;
|
|
49
|
+
if (controlledMode === undefined)
|
|
50
|
+
setInternalMode(nextMode);
|
|
51
|
+
onModeChange?.(nextMode);
|
|
52
|
+
}, [controlledMode, enabledModes, onModeChange]);
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (controlledMode !== undefined) {
|
|
55
|
+
if (controlledMode !== activeMode)
|
|
56
|
+
onModeChange?.(activeMode);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (internalMode !== activeMode)
|
|
60
|
+
setInternalMode(activeMode);
|
|
61
|
+
}, [activeMode, controlledMode, internalMode, onModeChange]);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (!collapseSplitOnMobile)
|
|
64
|
+
return;
|
|
65
|
+
const mediaQuery = window.matchMedia('(max-width: 767px)');
|
|
66
|
+
const resolveMode = () => {
|
|
67
|
+
if (!mediaQuery.matches)
|
|
68
|
+
return;
|
|
69
|
+
const nextMode = resolveMobileViewerStageMode(activeMode, enabledModes);
|
|
70
|
+
if (nextMode !== activeMode)
|
|
71
|
+
requestMode(nextMode);
|
|
72
|
+
};
|
|
73
|
+
resolveMode();
|
|
74
|
+
mediaQuery.addEventListener('change', resolveMode);
|
|
75
|
+
return () => mediaQuery.removeEventListener('change', resolveMode);
|
|
76
|
+
}, [activeMode, collapseSplitOnMobile, enabledModes, requestMode]);
|
|
77
|
+
const chooseLevel = useCallback((nextLevelId, notify = true) => {
|
|
78
|
+
setInternalLevelId(nextLevelId);
|
|
79
|
+
if (notify && nextLevelId !== useViewer.getState().selection.levelId) {
|
|
80
|
+
markPerfAction('level-switch', nextLevelId);
|
|
81
|
+
}
|
|
82
|
+
if (nextLevelId !== useViewer.getState().selection.levelId)
|
|
83
|
+
selectViewerLevel(scene?.nodes ?? useScene.getState().nodes, nextLevelId);
|
|
84
|
+
if (notify)
|
|
85
|
+
onLevelChange?.(nextLevelId);
|
|
86
|
+
}, [onLevelChange, scene]);
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (activeMode === '3d' || levelIds.length === 0)
|
|
89
|
+
return;
|
|
90
|
+
const nextLevelId = (levelId && levelIds.includes(levelId) ? levelId : null) ??
|
|
91
|
+
(selectedLevelId && levelIds.includes(selectedLevelId) ? selectedLevelId : null) ??
|
|
92
|
+
(internalLevelId && levelIds.includes(internalLevelId) ? internalLevelId : null) ??
|
|
93
|
+
levelIds[0] ??
|
|
94
|
+
null;
|
|
95
|
+
if (nextLevelId)
|
|
96
|
+
chooseLevel(nextLevelId, false);
|
|
97
|
+
}, [activeMode, chooseLevel, internalLevelId, levelId, levelIds, selectedLevelId]);
|
|
98
|
+
return (_jsxs("div", { className: cn('relative h-full w-full overflow-hidden bg-neutral-100', className), "data-pascal-navigation-sync": synchronizeNavigation ? 'on' : 'off', "data-pascal-viewer-stage": activeMode, children: [showCompass && compassHost === undefined ? (_jsx("div", { className: "pointer-events-none absolute inset-0 z-30", ref: setInternalCompassHost })) : null, showSwitcher && enabledModes.length > 1 ? (_jsx(ViewerStageSwitcher, { className: switcherClassName, hideSplitOnMobile: collapseSplitOnMobile, mode: activeMode, modes: enabledModes, onChange: requestMode })) : null, _jsxs("div", { className: activeMode === 'split'
|
|
99
|
+
? 'absolute inset-0 grid grid-rows-2 md:grid-cols-2 md:grid-rows-1'
|
|
100
|
+
: 'absolute inset-0', children: [threeDEnabled ? (_jsx("div", { className: cn(activeMode === '2d'
|
|
101
|
+
? 'pointer-events-none invisible absolute inset-0 h-full w-full'
|
|
102
|
+
: 'relative h-full min-h-0 w-full min-w-0', threeDClassName), "data-pascal-viewer-3d": true, children: children })) : null, mountFloorplan ? (_jsx(FloorplanPreview, { className: cn(activeMode === '3d'
|
|
103
|
+
? 'hidden'
|
|
104
|
+
: activeMode === 'split'
|
|
105
|
+
? 'min-h-0 min-w-0 border-border border-t md:border-t-0 md:border-l'
|
|
106
|
+
: 'h-full w-full', floorplanClassName), compassHost: resolvedCompassHost, levelId: levelId ?? internalLevelId, navigationVisible: activeMode !== '3d', onLevelChange: chooseLevel, scene: scene, showCompass: showCompass, showLevelSelector: showLevelSelector, synchronizeNavigation: synchronizeNavigation })) : null] })] }));
|
|
107
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { WalkthroughInteract } from '../../store/use-first-person-hud';
|
|
3
|
+
/** A control shown in the HUD's key strip: its keys (as printed on the keycaps) and what they do. */
|
|
4
|
+
export type WalkthroughHint = {
|
|
5
|
+
keys: readonly string[];
|
|
6
|
+
action: string;
|
|
7
|
+
};
|
|
8
|
+
export type WalkthroughHudProps = {
|
|
9
|
+
floorLabel?: string | null;
|
|
10
|
+
zoneLabel?: string | null;
|
|
11
|
+
interact?: WalkthroughInteract;
|
|
12
|
+
/** Movement controls listed before the cursor and exit pills. */
|
|
13
|
+
hints?: readonly WalkthroughHint[];
|
|
14
|
+
/** Pointer lock temporarily released (OS screenshot) — the pill flips to
|
|
15
|
+
* "Click to resume" and lets clicks fall through to the canvas. */
|
|
16
|
+
suspended?: boolean;
|
|
17
|
+
onExit?: () => void;
|
|
18
|
+
children?: ReactNode;
|
|
19
|
+
};
|
|
20
|
+
/** The centered walkthrough pointer: a dot that grows into a green ring over
|
|
21
|
+
* an interactable (door / window / elevator). Also mounted by the snapshot
|
|
22
|
+
* capture overlay so walk / drone framing keeps the same E-to-open pointer. */
|
|
23
|
+
export declare function WalkthroughCrosshair({ interact }: {
|
|
24
|
+
interact: WalkthroughInteract;
|
|
25
|
+
}): import("react").JSX.Element;
|
|
26
|
+
export declare function WalkthroughHud({ floorLabel, zoneLabel, interact, hints, suspended, onExit, children, }: WalkthroughHudProps): import("react").JSX.Element;
|
|
27
|
+
/** The walkthrough HUD for the drone: the plain pointer dot and the flight keys, no room or door prompts. */
|
|
28
|
+
export declare function DroneHud({ suspended, onExit, children, }: Pick<WalkthroughHudProps, 'suspended' | 'onExit' | 'children'>): import("react").JSX.Element;
|
|
29
|
+
//# sourceMappingURL=walkthrough-hud.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"walkthrough-hud.d.ts","sourceRoot":"","sources":["../../../src/ui/components/walkthrough-hud.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,SAAS,EAAW,MAAM,OAAO,CAAA;AAC/C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAA;AAI3E,qGAAqG;AACrG,MAAM,MAAM,eAAe,GAAG;IAAE,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAEzE,MAAM,MAAM,mBAAmB,GAAG;IAChC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,QAAQ,CAAC,EAAE,mBAAmB,CAAA;IAC9B,iEAAiE;IACjE,KAAK,CAAC,EAAE,SAAS,eAAe,EAAE,CAAA;IAClC;wEACoE;IACpE,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB,CAAA;AAED;;gFAEgF;AAChF,wBAAgB,oBAAoB,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,mBAAmB,CAAA;CAAE,+BAgBnF;AAED,wBAAgB,cAAc,CAAC,EAC7B,UAAU,EACV,SAAS,EACT,QAAe,EACf,KAAK,EACL,SAAiB,EACjB,MAAM,EACN,QAAQ,GACT,EAAE,mBAAmB,+BA8FrB;AAgCD,6GAA6G;AAC7G,wBAAgB,QAAQ,CAAC,EACvB,SAAS,EACT,MAAM,EACN,QAAQ,GACT,EAAE,IAAI,CAAC,mBAAmB,EAAE,WAAW,GAAG,QAAQ,GAAG,UAAU,CAAC,+BAMhE"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { usePascalI18n } from '@pascal-app/core';
|
|
4
|
+
import { useMemo } from 'react';
|
|
5
|
+
import { cn } from '../lib/cn';
|
|
6
|
+
import { displayRoomName } from '../lib/room-name-messages';
|
|
7
|
+
/** The centered walkthrough pointer: a dot that grows into a green ring over
|
|
8
|
+
* an interactable (door / window / elevator). Also mounted by the snapshot
|
|
9
|
+
* capture overlay so walk / drone framing keeps the same E-to-open pointer. */
|
|
10
|
+
export function WalkthroughCrosshair({ interact }) {
|
|
11
|
+
return (_jsx("div", { className: "pointer-events-none absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2", "data-walkthrough-crosshair": true, children: _jsx("div", { className: cn('rounded-full transition-all duration-150', interact
|
|
12
|
+
? 'h-4 w-4 border-2 border-emerald-400 bg-emerald-400/10'
|
|
13
|
+
: 'h-1.5 w-1.5 bg-white/80 shadow-[0_0_2px_rgba(0,0,0,0.6)]') }) }));
|
|
14
|
+
}
|
|
15
|
+
export function WalkthroughHud({ floorLabel, zoneLabel, interact = null, hints, suspended = false, onExit, children, }) {
|
|
16
|
+
const kbdClass = 'rounded border border-border/60 bg-white/10 px-1.5 py-0.5 font-mono text-[10px]';
|
|
17
|
+
const pillClass = 'flex items-center gap-1.5 rounded-full border border-border/40 bg-background/70 px-3 py-1 text-muted-foreground text-xs backdrop-blur-xl';
|
|
18
|
+
const exitContent = (_jsxs(_Fragment, { children: [_jsx("kbd", { className: kbdClass, children: "Esc" }), "to exit"] }));
|
|
19
|
+
return (_jsxs("div", { className: "dark pointer-events-none absolute inset-0 z-30 text-foreground", "data-walkthrough-hud": true, children: [_jsxs("div", { className: "absolute top-6 left-1/2 flex -translate-x-1/2 flex-col items-center gap-1.5", children: [floorLabel && (_jsx("div", { className: "font-medium text-[10px] text-muted-foreground uppercase tracking-wider", children: floorLabel })), zoneLabel && (_jsx("div", { className: "corner-smooth rounded-full border border-border/40 bg-background/80 px-3 py-1 font-medium text-sm shadow-elevation-3 backdrop-blur-xl", children: _jsx(WalkthroughZoneName, { name: zoneLabel }) })), children] }), _jsx(WalkthroughCrosshair, { interact: interact }), _jsx("div", { className: "absolute bottom-6 left-1/2 flex -translate-x-1/2 items-center gap-2", children: suspended ? (_jsxs("div", { className: pillClass, children: [_jsx("span", { className: "font-medium text-foreground", children: "Click" }), _jsx("span", { children: "or" }), _jsx("kbd", { className: kbdClass, children: "P" }), _jsx("span", { children: "to resume" }), _jsx("span", { className: "text-muted-foreground/60", children: "\u00B7" }), exitContent] })) : (_jsxs(_Fragment, { children: [hints && hints.length > 0 ? (_jsx("div", { className: pillClass, children: hints.map(({ keys, action }, index) => (_jsxs("span", { className: "flex items-center gap-1.5", children: [index > 0 && _jsx("span", { className: "text-muted-foreground/60", children: "\u00B7" }), keys.map((key, keyIndex) => (_jsxs("span", { className: "flex items-center gap-1", children: [keyIndex > 0 && _jsx("span", { className: "text-muted-foreground/60", children: "/" }), _jsx("kbd", { className: kbdClass, children: key })] }, key))), action] }, action))) })) : null, _jsxs("div", { className: pillClass, children: [_jsx("kbd", { className: kbdClass, children: "P" }), "free cursor"] }), onExit ? (_jsx("button", { className: cn(pillClass, 'pointer-events-auto'), onClick: onExit, type: "button", children: exitContent })) : (_jsx("div", { className: pillClass, children: exitContent }))] })) }), interact && (_jsx("div", { className: "absolute top-1/2 left-1/2 mt-7 -translate-x-1/2 whitespace-nowrap", children: _jsxs("div", { className: "corner-smooth flex items-center gap-1.5 rounded-full border border-border/40 bg-background/80 px-3 py-1 text-xs shadow-elevation-3 backdrop-blur-xl", children: [_jsx("kbd", { className: "rounded border border-border/60 bg-white/10 px-1.5 py-0.5 font-mono text-[10px]", children: "E" }), _jsx("span", { className: "text-muted-foreground", children: "or click to" }), _jsxs("span", { className: "font-medium", children: [interact.verb, " ", interact.label] })] }) }))] }));
|
|
20
|
+
}
|
|
21
|
+
/** The drone's key strip: the movement controls of the viewer's `DroneController`. */
|
|
22
|
+
function useDroneHints() {
|
|
23
|
+
const i18n = usePascalI18n();
|
|
24
|
+
return useMemo(() => [
|
|
25
|
+
{
|
|
26
|
+
keys: ['WASD'],
|
|
27
|
+
action: i18n._({ id: 'editor.walkthroughHud.drone.move', message: 'move' }),
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
keys: ['Space', 'E'],
|
|
31
|
+
action: i18n._({ id: 'editor.walkthroughHud.drone.up', message: 'up' }),
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
keys: ['Q'],
|
|
35
|
+
action: i18n._({ id: 'editor.walkthroughHud.drone.down', message: 'down' }),
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
keys: ['Shift'],
|
|
39
|
+
action: i18n._({ id: 'editor.walkthroughHud.drone.boost', message: 'boost' }),
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
keys: ['Alt'],
|
|
43
|
+
action: i18n._({ id: 'editor.walkthroughHud.drone.slow', message: 'slow' }),
|
|
44
|
+
},
|
|
45
|
+
], [i18n]);
|
|
46
|
+
}
|
|
47
|
+
/** The walkthrough HUD for the drone: the plain pointer dot and the flight keys, no room or door prompts. */
|
|
48
|
+
export function DroneHud({ suspended, onExit, children, }) {
|
|
49
|
+
return (_jsx(WalkthroughHud, { hints: useDroneHints(), onExit: onExit, suspended: suspended, children: children }));
|
|
50
|
+
}
|
|
51
|
+
/** The room the walker is in, in the UI language (the scene keeps its stored name). */
|
|
52
|
+
function WalkthroughZoneName({ name }) {
|
|
53
|
+
const i18n = usePascalI18n();
|
|
54
|
+
return displayRoomName(name, i18n);
|
|
55
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type CameraPose } from '@pascal-app/core';
|
|
2
|
+
import { type NavigationSyncPose, type NavigationSyncPoseInput } from '../store/navigation-sync-pose-store';
|
|
3
|
+
export declare function cameraPoseToFloorplanNavigationPose(pose: CameraPose): NavigationSyncPoseInput | null;
|
|
4
|
+
export declare function floorplanNavigationPoseToCameraPose(navigationPose: NavigationSyncPose, cameraPose: CameraPose): CameraPose;
|
|
5
|
+
export type FloorplanCameraSyncBridge = {
|
|
6
|
+
receiveCameraPose: (pose: CameraPose) => void;
|
|
7
|
+
receiveNavigationPose: (pose: NavigationSyncPose | null) => void;
|
|
8
|
+
setActive: (active: boolean) => void;
|
|
9
|
+
};
|
|
10
|
+
export type FloorplanCameraNavigationChannel = {
|
|
11
|
+
publish: (pose: NavigationSyncPoseInput) => void;
|
|
12
|
+
subscribe: (listener: (pose: NavigationSyncPose) => void) => () => void;
|
|
13
|
+
};
|
|
14
|
+
export declare function createFloorplanCameraNavigationChannel(): FloorplanCameraNavigationChannel;
|
|
15
|
+
export declare function subscribeFloorplanCameraNavigation(listener: (pose: NavigationSyncPose) => void): () => void;
|
|
16
|
+
export declare function createFloorplanCameraSyncBridge({ active: initialActive, applyCameraPose, publishNavigationPose, }: {
|
|
17
|
+
active?: boolean;
|
|
18
|
+
applyCameraPose: (pose: CameraPose) => void;
|
|
19
|
+
publishNavigationPose: (pose: NavigationSyncPoseInput) => void;
|
|
20
|
+
}): FloorplanCameraSyncBridge;
|
|
21
|
+
export declare function useFloorplanCameraSyncBridge(): void;
|
|
22
|
+
//# sourceMappingURL=floorplan-camera-sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"floorplan-camera-sync.d.ts","sourceRoot":"","sources":["../../../src/ui/floorplan/floorplan-camera-sync.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,UAAU,EAAW,MAAM,kBAAkB,CAAA;AAG3D,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAE7B,MAAM,qCAAqC,CAAA;AAqC5C,wBAAgB,mCAAmC,CACjD,IAAI,EAAE,UAAU,GACf,uBAAuB,GAAG,IAAI,CAWhC;AAED,wBAAgB,mCAAmC,CACjD,cAAc,EAAE,kBAAkB,EAClC,UAAU,EAAE,UAAU,GACrB,UAAU,CAgBZ;AAED,MAAM,MAAM,yBAAyB,GAAG;IACtC,iBAAiB,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAA;IAC7C,qBAAqB,EAAE,CAAC,IAAI,EAAE,kBAAkB,GAAG,IAAI,KAAK,IAAI,CAAA;IAChE,SAAS,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAA;CACrC,CAAA;AAED,MAAM,MAAM,gCAAgC,GAAG;IAC7C,OAAO,EAAE,CAAC,IAAI,EAAE,uBAAuB,KAAK,IAAI,CAAA;IAChD,SAAS,EAAE,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,IAAI,KAAK,MAAM,IAAI,CAAA;CACxE,CAAA;AAED,wBAAgB,sCAAsC,IAAI,gCAAgC,CAiBzF;AAID,wBAAgB,kCAAkC,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,IAAI,SAxB1B,IAAI,CA0BxE;AAED,wBAAgB,+BAA+B,CAAC,EAC9C,MAAM,EAAE,aAAoB,EAC5B,eAAe,EACf,qBAAqB,GACtB,EAAE;IACD,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,eAAe,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAA;IAC3C,qBAAqB,EAAE,CAAC,IAAI,EAAE,uBAAuB,KAAK,IAAI,CAAA;CAC/D,GAAG,yBAAyB,CAuE5B;AAED,wBAAgB,4BAA4B,SAgB3C"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { emitter } from '@pascal-app/core';
|
|
3
|
+
import { useEffect, useRef } from 'react';
|
|
4
|
+
import { subscribeCameraPose } from '../store/camera-pose-store';
|
|
5
|
+
import { subscribeNavigationSyncPose, } from '../store/navigation-sync-pose-store';
|
|
6
|
+
const POSITION_EPSILON = 0.001;
|
|
7
|
+
const AZIMUTH_EPSILON = 1e-4;
|
|
8
|
+
const VIEW_WIDTH_EPSILON = 0.001;
|
|
9
|
+
function angleDeltaRadians(a, b) {
|
|
10
|
+
return Math.atan2(Math.sin(a - b), Math.cos(a - b));
|
|
11
|
+
}
|
|
12
|
+
function normalizeNearZero(value) {
|
|
13
|
+
return Math.abs(value) < Number.EPSILON * 10 ? 0 : value;
|
|
14
|
+
}
|
|
15
|
+
function navigationSnapshot(pose) {
|
|
16
|
+
return {
|
|
17
|
+
target: [...pose.target],
|
|
18
|
+
azimuth: pose.azimuth,
|
|
19
|
+
viewWidth: pose.viewWidth,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function navigationSnapshotsEqual(previous, next) {
|
|
23
|
+
return (Math.abs(previous.target[0] - next.target[0]) < POSITION_EPSILON &&
|
|
24
|
+
Math.abs(previous.target[1] - next.target[1]) < POSITION_EPSILON &&
|
|
25
|
+
Math.abs(previous.target[2] - next.target[2]) < POSITION_EPSILON &&
|
|
26
|
+
Math.abs(angleDeltaRadians(previous.azimuth, next.azimuth)) < AZIMUTH_EPSILON &&
|
|
27
|
+
Math.abs(previous.viewWidth - next.viewWidth) < VIEW_WIDTH_EPSILON);
|
|
28
|
+
}
|
|
29
|
+
export function cameraPoseToFloorplanNavigationPose(pose) {
|
|
30
|
+
if (!(pose.viewWidth !== undefined && Number.isFinite(pose.viewWidth) && pose.viewWidth > 0)) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
source: '3d',
|
|
35
|
+
target: [...pose.target],
|
|
36
|
+
azimuth: Math.atan2(pose.position[0] - pose.target[0], pose.position[2] - pose.target[2]),
|
|
37
|
+
viewWidth: pose.viewWidth,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function floorplanNavigationPoseToCameraPose(navigationPose, cameraPose) {
|
|
41
|
+
const offsetX = cameraPose.position[0] - cameraPose.target[0];
|
|
42
|
+
const offsetY = cameraPose.position[1] - cameraPose.target[1];
|
|
43
|
+
const offsetZ = cameraPose.position[2] - cameraPose.target[2];
|
|
44
|
+
const horizontalDistance = Math.hypot(offsetX, offsetZ);
|
|
45
|
+
const horizontalX = normalizeNearZero(Math.sin(navigationPose.azimuth) * horizontalDistance);
|
|
46
|
+
const horizontalZ = normalizeNearZero(Math.cos(navigationPose.azimuth) * horizontalDistance);
|
|
47
|
+
const target = [...navigationPose.target];
|
|
48
|
+
return {
|
|
49
|
+
...(cameraPose.fov === undefined ? {} : { fov: cameraPose.fov }),
|
|
50
|
+
position: [target[0] + horizontalX, target[1] + offsetY, target[2] + horizontalZ],
|
|
51
|
+
projection: cameraPose.projection,
|
|
52
|
+
target,
|
|
53
|
+
viewWidth: navigationPose.viewWidth,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export function createFloorplanCameraNavigationChannel() {
|
|
57
|
+
const listeners = new Set();
|
|
58
|
+
let revision = 0;
|
|
59
|
+
return {
|
|
60
|
+
publish: (pose) => {
|
|
61
|
+
revision += 1;
|
|
62
|
+
const revisedPose = { ...pose, revision };
|
|
63
|
+
for (const listener of listeners) {
|
|
64
|
+
listener(revisedPose);
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
subscribe: (listener) => {
|
|
68
|
+
listeners.add(listener);
|
|
69
|
+
return () => listeners.delete(listener);
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const liveCameraNavigation = createFloorplanCameraNavigationChannel();
|
|
74
|
+
export function subscribeFloorplanCameraNavigation(listener) {
|
|
75
|
+
return liveCameraNavigation.subscribe(listener);
|
|
76
|
+
}
|
|
77
|
+
export function createFloorplanCameraSyncBridge({ active: initialActive = true, applyCameraPose, publishNavigationPose, }) {
|
|
78
|
+
let active = initialActive;
|
|
79
|
+
let latestCameraPose = null;
|
|
80
|
+
let pendingNavigationPose = null;
|
|
81
|
+
let lastAppliedNavigationRevision = 0;
|
|
82
|
+
let lastPublishedNavigation = null;
|
|
83
|
+
const applyPendingNavigationPose = () => {
|
|
84
|
+
if (!(latestCameraPose && pendingNavigationPose))
|
|
85
|
+
return false;
|
|
86
|
+
if (pendingNavigationPose.revision === lastAppliedNavigationRevision) {
|
|
87
|
+
pendingNavigationPose = null;
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
const appliedPose = floorplanNavigationPoseToCameraPose(pendingNavigationPose, latestCameraPose);
|
|
91
|
+
const appliedNavigationPose = cameraPoseToFloorplanNavigationPose(appliedPose);
|
|
92
|
+
latestCameraPose = appliedPose;
|
|
93
|
+
lastAppliedNavigationRevision = pendingNavigationPose.revision;
|
|
94
|
+
pendingNavigationPose = null;
|
|
95
|
+
if (appliedNavigationPose) {
|
|
96
|
+
lastPublishedNavigation = navigationSnapshot(appliedNavigationPose);
|
|
97
|
+
}
|
|
98
|
+
applyCameraPose(appliedPose);
|
|
99
|
+
return true;
|
|
100
|
+
};
|
|
101
|
+
const publishCameraNavigationPose = (pose) => {
|
|
102
|
+
const navigationPose = cameraPoseToFloorplanNavigationPose(pose);
|
|
103
|
+
if (!navigationPose)
|
|
104
|
+
return;
|
|
105
|
+
const nextSnapshot = navigationSnapshot(navigationPose);
|
|
106
|
+
if (lastPublishedNavigation &&
|
|
107
|
+
navigationSnapshotsEqual(lastPublishedNavigation, nextSnapshot)) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
lastPublishedNavigation = nextSnapshot;
|
|
111
|
+
publishNavigationPose(navigationPose);
|
|
112
|
+
};
|
|
113
|
+
return {
|
|
114
|
+
receiveCameraPose: (pose) => {
|
|
115
|
+
latestCameraPose = pose;
|
|
116
|
+
if (active && applyPendingNavigationPose())
|
|
117
|
+
return;
|
|
118
|
+
publishCameraNavigationPose(pose);
|
|
119
|
+
},
|
|
120
|
+
receiveNavigationPose: (pose) => {
|
|
121
|
+
if (!active ||
|
|
122
|
+
pose?.source !== '2d' ||
|
|
123
|
+
pose.revision === lastAppliedNavigationRevision ||
|
|
124
|
+
pose.revision === pendingNavigationPose?.revision) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
pendingNavigationPose = pose;
|
|
128
|
+
applyPendingNavigationPose();
|
|
129
|
+
},
|
|
130
|
+
setActive: (nextActive) => {
|
|
131
|
+
if (active === nextActive)
|
|
132
|
+
return;
|
|
133
|
+
active = nextActive;
|
|
134
|
+
if (!active) {
|
|
135
|
+
pendingNavigationPose = null;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (latestCameraPose)
|
|
139
|
+
publishCameraNavigationPose(latestCameraPose);
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
export function useFloorplanCameraSyncBridge() {
|
|
144
|
+
// The compass is portalled into the always-visible viewer area, so its
|
|
145
|
+
// orientation-only 2D pose must still reach the camera while the floorplan
|
|
146
|
+
// itself is hidden in 3D view.
|
|
147
|
+
const bridgeRef = useRef(null);
|
|
148
|
+
if (!bridgeRef.current) {
|
|
149
|
+
bridgeRef.current = createFloorplanCameraSyncBridge({
|
|
150
|
+
applyCameraPose: (pose) => emitter.emit('camera-controls:apply-pose', pose),
|
|
151
|
+
publishNavigationPose: (pose) => liveCameraNavigation.publish(pose),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
const bridge = bridgeRef.current;
|
|
155
|
+
useEffect(() => subscribeCameraPose(bridge.receiveCameraPose), [bridge]);
|
|
156
|
+
useEffect(() => subscribeNavigationSyncPose(bridge.receiveNavigationPose), [bridge]);
|
|
157
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { FloorplanGeometry, FloorplanPoint } from '@pascal-app/core';
|
|
2
|
+
type FloorplanDimensionRenderMode = 'screen' | 'pdf';
|
|
3
|
+
/**
|
|
4
|
+
* Click-to-type dimensions (WS3).
|
|
5
|
+
*
|
|
6
|
+
* On screen, under an editing host (`FloorplanDimensionHitsProvider` set to
|
|
7
|
+
* true), the label plate carries an invisible hit rect plus the data the
|
|
8
|
+
* editing layer needs to drive geometry from a typed value. The editor's
|
|
9
|
+
* layer picks it up by DOM delegation, so every producer of `dimension` /
|
|
10
|
+
* `dimension-string` geometry — wall automatic dimensions, contextual
|
|
11
|
+
* dimensions, manual construction-dimension nodes — becomes editable without
|
|
12
|
+
* each one wiring up a callback. Read-only plans carry no hit rects.
|
|
13
|
+
*
|
|
14
|
+
* Witness points are the measured feature points in level-plan metres; the
|
|
15
|
+
* value is the length the label actually reads (dimension-line points when
|
|
16
|
+
* the producer supplied them, witness points otherwise).
|
|
17
|
+
*/
|
|
18
|
+
export declare const FLOORPLAN_DIMENSION_HIT_ATTRIBUTE = "data-floorplan-dimension-hit";
|
|
19
|
+
/** Whether the dimension labels below it carry typeable hit rects; off unless a host sets it. */
|
|
20
|
+
export declare const FloorplanDimensionHitsProvider: import("react").Provider<boolean>;
|
|
21
|
+
type DimensionGeometry = Extract<FloorplanGeometry, {
|
|
22
|
+
kind: 'dimension';
|
|
23
|
+
}>;
|
|
24
|
+
type DimensionStringGeometry = Extract<FloorplanGeometry, {
|
|
25
|
+
kind: 'dimension-string';
|
|
26
|
+
}>;
|
|
27
|
+
export type ArchitecturalDimensionLayout = {
|
|
28
|
+
dimensionStart: FloorplanPoint;
|
|
29
|
+
dimensionEnd: FloorplanPoint;
|
|
30
|
+
dimensionLineStart: FloorplanPoint;
|
|
31
|
+
dimensionLineEnd: FloorplanPoint;
|
|
32
|
+
extensionStart: FloorplanPoint;
|
|
33
|
+
extensionEnd: FloorplanPoint;
|
|
34
|
+
extensionStartTip: FloorplanPoint;
|
|
35
|
+
extensionEndTip: FloorplanPoint;
|
|
36
|
+
tickHalfVector: FloorplanPoint;
|
|
37
|
+
labelPoint: FloorplanPoint;
|
|
38
|
+
labelAngleDeg: number;
|
|
39
|
+
labelPlacement: 'inside' | 'outside-end';
|
|
40
|
+
outsideStartLabelPoint?: FloorplanPoint;
|
|
41
|
+
outsideStartDimensionLineStart?: FloorplanPoint;
|
|
42
|
+
};
|
|
43
|
+
export declare function floorplanDimensionAnnotationPriority(offsetDistance: number): number;
|
|
44
|
+
export declare function computeArchitecturalDimensionLayout(geometry: DimensionGeometry, sceneRotationDeg: number, annotationUnitsPerPoint?: number): ArchitecturalDimensionLayout | null;
|
|
45
|
+
export declare function FloorplanDimensionRenderer({ geometry, sceneRotationDeg, stroke, annotationUnitsPerPoint, renderMode, onSelect, }: {
|
|
46
|
+
geometry: DimensionGeometry;
|
|
47
|
+
sceneRotationDeg?: number;
|
|
48
|
+
stroke?: string;
|
|
49
|
+
annotationUnitsPerPoint?: number;
|
|
50
|
+
renderMode?: FloorplanDimensionRenderMode;
|
|
51
|
+
onSelect?: () => void;
|
|
52
|
+
}): React.ReactElement | null;
|
|
53
|
+
export declare function FloorplanDimensionStringRenderer({ geometry, sceneRotationDeg, stroke, annotationUnitsPerPoint, renderMode, }: {
|
|
54
|
+
geometry: DimensionStringGeometry;
|
|
55
|
+
sceneRotationDeg?: number;
|
|
56
|
+
stroke?: string;
|
|
57
|
+
annotationUnitsPerPoint?: number;
|
|
58
|
+
renderMode?: FloorplanDimensionRenderMode;
|
|
59
|
+
}): React.ReactElement | null;
|
|
60
|
+
export {};
|
|
61
|
+
//# sourceMappingURL=floorplan-dimension-renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"floorplan-dimension-renderer.d.ts","sourceRoot":"","sources":["../../../src/ui/floorplan/floorplan-dimension-renderer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAsBzE,KAAK,4BAA4B,GAAG,QAAQ,GAAG,KAAK,CAAA;AAEpD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,iCAAiC,iCAAiC,CAAA;AAI/E,iGAAiG;AACjG,eAAO,MAAM,8BAA8B,mCAAgC,CAAA;AAE3E,KAAK,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,EAAE;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAAA;AAC1E,KAAK,uBAAuB,GAAG,OAAO,CAAC,iBAAiB,EAAE;IAAE,IAAI,EAAE,kBAAkB,CAAA;CAAE,CAAC,CAAA;AAGvF,MAAM,MAAM,4BAA4B,GAAG;IACzC,cAAc,EAAE,cAAc,CAAA;IAC9B,YAAY,EAAE,cAAc,CAAA;IAC5B,kBAAkB,EAAE,cAAc,CAAA;IAClC,gBAAgB,EAAE,cAAc,CAAA;IAChC,cAAc,EAAE,cAAc,CAAA;IAC9B,YAAY,EAAE,cAAc,CAAA;IAC5B,iBAAiB,EAAE,cAAc,CAAA;IACjC,eAAe,EAAE,cAAc,CAAA;IAC/B,cAAc,EAAE,cAAc,CAAA;IAC9B,UAAU,EAAE,cAAc,CAAA;IAC1B,aAAa,EAAE,MAAM,CAAA;IACrB,cAAc,EAAE,QAAQ,GAAG,aAAa,CAAA;IACxC,sBAAsB,CAAC,EAAE,cAAc,CAAA;IACvC,8BAA8B,CAAC,EAAE,cAAc,CAAA;CAChD,CAAA;AAED,wBAAgB,oCAAoC,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAEnF;AAED,wBAAgB,mCAAmC,CACjD,QAAQ,EAAE,iBAAiB,EAC3B,gBAAgB,EAAE,MAAM,EACxB,uBAAuB,CAAC,EAAE,MAAM,GAC/B,4BAA4B,GAAG,IAAI,CAuFrC;AAkBD,wBAAgB,0BAA0B,CAAC,EACzC,QAAQ,EACR,gBAAoB,EACpB,MAAqC,EACrC,uBAAuB,EACvB,UAAqB,EACrB,QAAQ,GACT,EAAE;IACD,QAAQ,EAAE,iBAAiB,CAAA;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAChC,UAAU,CAAC,EAAE,4BAA4B,CAAA;IACzC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB,GAAG,KAAK,CAAC,YAAY,GAAG,IAAI,CA8I5B;AAED,wBAAgB,gCAAgC,CAAC,EAC/C,QAAQ,EACR,gBAAoB,EACpB,MAAqC,EACrC,uBAAuB,EACvB,UAAqB,GACtB,EAAE;IACD,QAAQ,EAAE,uBAAuB,CAAA;IACjC,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAChC,UAAU,CAAC,EAAE,4BAA4B,CAAA;CAC1C,GAAG,KAAK,CAAC,YAAY,GAAG,IAAI,CA0K5B"}
|