@ericchen1990/pano-view 3.7.0 → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +675 -71
- package/README.zh-CN.md +558 -42
- package/dist/data.cjs +239 -7
- package/dist/data.cjs.map +1 -1
- package/dist/data.d.mts +120 -2
- package/dist/data.d.ts +120 -2
- package/dist/data.mjs +239 -7
- package/dist/data.mjs.map +1 -1
- package/dist/index.cjs +2385 -310
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +335 -2
- package/dist/index.d.ts +335 -2
- package/dist/index.mjs +2393 -306
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +399 -1
- package/dist/ui.cjs +1 -1
- package/dist/ui.cjs.map +1 -1
- package/dist/ui.mjs +1 -1
- package/dist/ui.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -9011,7 +9011,7 @@ var PanoViewer = forwardRef2(
|
|
|
9011
9011
|
contextMenuOverlay,
|
|
9012
9012
|
hotspotAccessibilityControls
|
|
9013
9013
|
] }),
|
|
9014
|
-
/* @__PURE__ */ jsx13(PanoModalHostContext.Provider, { value: modalHost, children: /* @__PURE__ */ jsx13(PanoOverlayContext.Provider, { value: overlayContext, children: /* @__PURE__ */ jsx13(
|
|
9014
|
+
/* @__PURE__ */ jsx13(PanoEventBusContext.Provider, { value: eventBus, children: /* @__PURE__ */ jsx13(PanoModalHostContext.Provider, { value: modalHost, children: /* @__PURE__ */ jsx13(PanoOverlayContext.Provider, { value: overlayContext, children: /* @__PURE__ */ jsx13(
|
|
9015
9015
|
"div",
|
|
9016
9016
|
{
|
|
9017
9017
|
"data-pano-overlay": "",
|
|
@@ -9019,7 +9019,7 @@ var PanoViewer = forwardRef2(
|
|
|
9019
9019
|
style: { position: "absolute", inset: 0, zIndex: 30, pointerEvents: "none" },
|
|
9020
9020
|
children: renderOverlay?.(overlayContext)
|
|
9021
9021
|
}
|
|
9022
|
-
) }) })
|
|
9022
|
+
) }) }) })
|
|
9023
9023
|
]
|
|
9024
9024
|
}
|
|
9025
9025
|
);
|
|
@@ -18410,189 +18410,1097 @@ var WebVR = forwardRef4(function WebVR2({
|
|
|
18410
18410
|
] });
|
|
18411
18411
|
});
|
|
18412
18412
|
|
|
18413
|
-
// src/
|
|
18414
|
-
import { useMemo as useMemo27, useRef as useRef40 } from "react";
|
|
18415
|
-
function createPanoViewerHandle(ref) {
|
|
18416
|
-
return {
|
|
18417
|
-
getView: () => ref.current?.getView() ?? { yaw: 0, pitch: 0, fov: 75 },
|
|
18418
|
-
setView: (view, options) => {
|
|
18419
|
-
ref.current?.setView(view, options);
|
|
18420
|
-
},
|
|
18421
|
-
reset: () => {
|
|
18422
|
-
ref.current?.reset();
|
|
18423
|
-
},
|
|
18424
|
-
lookTo: (position, options) => ref.current?.lookTo(position, options) ?? Promise.resolve({ status: "cancelled" }),
|
|
18425
|
-
moveTo: (position, options) => ref.current?.moveTo(position, options) ?? Promise.resolve({ status: "cancelled" }),
|
|
18426
|
-
zoomTo: (fov, options) => ref.current?.zoomTo(fov, options) ?? Promise.resolve({ status: "cancelled" }),
|
|
18427
|
-
lookToHotspot: (id, options) => ref.current?.lookToHotspot(id, options) ?? Promise.resolve({ status: "not-found" }),
|
|
18428
|
-
activateMedia: () => ref.current?.activateMedia() ?? Promise.resolve(),
|
|
18429
|
-
enterFullscreen: () => ref.current?.enterFullscreen() ?? Promise.resolve(),
|
|
18430
|
-
exitFullscreen: () => ref.current?.exitFullscreen() ?? Promise.resolve(),
|
|
18431
|
-
toggleFullscreen: () => ref.current?.toggleFullscreen() ?? Promise.resolve(),
|
|
18432
|
-
isFullscreen: () => ref.current?.isFullscreen() ?? false,
|
|
18433
|
-
setScene: (id) => ref.current?.setScene(id) ?? false,
|
|
18434
|
-
nextScene: () => ref.current?.nextScene() ?? false,
|
|
18435
|
-
previousScene: () => ref.current?.previousScene() ?? false,
|
|
18436
|
-
getActiveSceneId: () => ref.current?.getActiveSceneId() ?? null,
|
|
18437
|
-
getSceneIds: () => ref.current?.getSceneIds() ?? [],
|
|
18438
|
-
isSceneTransitioning: () => ref.current?.isSceneTransitioning() ?? false,
|
|
18439
|
-
enterVR: () => ref.current?.enterVR() ?? Promise.resolve(false),
|
|
18440
|
-
exitVR: () => ref.current?.exitVR() ?? Promise.resolve(),
|
|
18441
|
-
toggleVR: () => ref.current?.toggleVR() ?? Promise.resolve(false),
|
|
18442
|
-
isVRAvailable: () => ref.current?.isVRAvailable() ?? false,
|
|
18443
|
-
isVREnabled: () => ref.current?.isVREnabled() ?? false,
|
|
18444
|
-
getVRMode: () => ref.current?.getVRMode() ?? null,
|
|
18445
|
-
requestVRPermission: () => ref.current?.requestVRPermission() ?? Promise.resolve(false),
|
|
18446
|
-
getVideo: () => ref.current?.getVideo() ?? null,
|
|
18447
|
-
subscribeVideo: (onStoreChange) => ref.current?.subscribeVideo(onStoreChange) ?? (() => {
|
|
18448
|
-
}),
|
|
18449
|
-
playVideo: () => ref.current?.playVideo() ?? Promise.resolve(),
|
|
18450
|
-
pauseVideo: () => {
|
|
18451
|
-
ref.current?.pauseVideo();
|
|
18452
|
-
},
|
|
18453
|
-
toggleVideo: () => {
|
|
18454
|
-
ref.current?.toggleVideo();
|
|
18455
|
-
},
|
|
18456
|
-
seekVideo: (time) => {
|
|
18457
|
-
ref.current?.seekVideo(time);
|
|
18458
|
-
},
|
|
18459
|
-
setVideoVolume: (volume) => {
|
|
18460
|
-
ref.current?.setVideoVolume(volume);
|
|
18461
|
-
},
|
|
18462
|
-
setVideoMuted: (muted) => {
|
|
18463
|
-
ref.current?.setVideoMuted(muted);
|
|
18464
|
-
},
|
|
18465
|
-
toggleVideoMuted: () => {
|
|
18466
|
-
ref.current?.toggleVideoMuted();
|
|
18467
|
-
},
|
|
18468
|
-
getBackgroundAudio: () => ref.current?.getBackgroundAudio() ?? null,
|
|
18469
|
-
subscribeBackgroundAudio: (onStoreChange) => ref.current?.subscribeBackgroundAudio(onStoreChange) ?? (() => {
|
|
18470
|
-
}),
|
|
18471
|
-
playBackgroundAudio: () => ref.current?.playBackgroundAudio() ?? Promise.resolve(),
|
|
18472
|
-
pauseBackgroundAudio: () => {
|
|
18473
|
-
ref.current?.pauseBackgroundAudio();
|
|
18474
|
-
},
|
|
18475
|
-
toggleBackgroundAudio: () => {
|
|
18476
|
-
ref.current?.toggleBackgroundAudio();
|
|
18477
|
-
},
|
|
18478
|
-
setBackgroundAudioVolume: (volume) => {
|
|
18479
|
-
ref.current?.setBackgroundAudioVolume(volume);
|
|
18480
|
-
},
|
|
18481
|
-
setBackgroundAudioMuted: (muted) => {
|
|
18482
|
-
ref.current?.setBackgroundAudioMuted(muted);
|
|
18483
|
-
},
|
|
18484
|
-
toggleBackgroundAudioMuted: () => {
|
|
18485
|
-
ref.current?.toggleBackgroundAudioMuted();
|
|
18486
|
-
},
|
|
18487
|
-
startAutoRotate: () => {
|
|
18488
|
-
ref.current?.startAutoRotate();
|
|
18489
|
-
},
|
|
18490
|
-
stopAutoRotate: () => {
|
|
18491
|
-
ref.current?.stopAutoRotate();
|
|
18492
|
-
}
|
|
18493
|
-
};
|
|
18494
|
-
}
|
|
18495
|
-
function usePanoViewer() {
|
|
18496
|
-
const ref = useRef40(null);
|
|
18497
|
-
const handle = useMemo27(() => createPanoViewerHandle(ref), []);
|
|
18498
|
-
return useMemo27(() => ({ ref, ...handle }), [handle]);
|
|
18499
|
-
}
|
|
18500
|
-
|
|
18501
|
-
// src/video/pano-video.tsx
|
|
18502
|
-
import { useThree as useThree29 } from "@react-three/fiber";
|
|
18413
|
+
// src/pano-scene-menu.tsx
|
|
18503
18414
|
import {
|
|
18504
18415
|
useCallback as useCallback11,
|
|
18505
|
-
useContext as useContext28,
|
|
18506
18416
|
useEffect as useEffect41,
|
|
18417
|
+
useId as useId3,
|
|
18507
18418
|
useLayoutEffect as useLayoutEffect9,
|
|
18508
|
-
useMemo as
|
|
18509
|
-
useRef as
|
|
18419
|
+
useMemo as useMemo27,
|
|
18420
|
+
useRef as useRef40,
|
|
18510
18421
|
useState as useState25
|
|
18511
18422
|
} from "react";
|
|
18512
|
-
|
|
18513
|
-
|
|
18514
|
-
|
|
18515
|
-
|
|
18516
|
-
|
|
18517
|
-
|
|
18518
|
-
|
|
18519
|
-
|
|
18520
|
-
|
|
18521
|
-
|
|
18522
|
-
|
|
18523
|
-
|
|
18524
|
-
var EMPTY_RATES = DEFAULT_PANO_VIDEO_PLAYBACK_RATES;
|
|
18525
|
-
function createEmptySnapshot() {
|
|
18423
|
+
|
|
18424
|
+
// src/drag-motion.ts
|
|
18425
|
+
var DRAG_INERTIA_DAMPING = 8;
|
|
18426
|
+
var DRAG_FRICTION_STOP = 24;
|
|
18427
|
+
var DRAG_SPRING_STRENGTH = 28;
|
|
18428
|
+
var DRAG_RUBBER_CONSTANT = 0.55;
|
|
18429
|
+
var DRAG_SPRING_SETTLE_DISTANCE = 0.5;
|
|
18430
|
+
var DRAG_SPRING_SETTLE_VELOCITY = 12;
|
|
18431
|
+
function resolveDragMotion(motion) {
|
|
18432
|
+
if (motion === false) {
|
|
18433
|
+
return { inertia: false, bounce: false };
|
|
18434
|
+
}
|
|
18526
18435
|
return {
|
|
18527
|
-
|
|
18528
|
-
|
|
18529
|
-
blocked: false,
|
|
18530
|
-
ended: false,
|
|
18531
|
-
currentTime: 0,
|
|
18532
|
-
duration: 0,
|
|
18533
|
-
volume: 1,
|
|
18534
|
-
volumeAdjustable: true,
|
|
18535
|
-
muted: true,
|
|
18536
|
-
playbackRate: 1,
|
|
18537
|
-
variantId: "default",
|
|
18538
|
-
variants: [],
|
|
18539
|
-
trackId: null,
|
|
18540
|
-
tracks: [],
|
|
18541
|
-
captionText: "",
|
|
18542
|
-
captionAppearance: DEFAULT_PANO_VIDEO_CAPTION_APPEARANCE,
|
|
18543
|
-
captionsEnabled: true,
|
|
18544
|
-
playbackRates: EMPTY_RATES,
|
|
18545
|
-
playbackState: "paused"
|
|
18436
|
+
inertia: motion?.inertia !== false,
|
|
18437
|
+
bounce: motion?.bounce !== false
|
|
18546
18438
|
};
|
|
18547
18439
|
}
|
|
18548
|
-
function
|
|
18549
|
-
if (
|
|
18550
|
-
|
|
18440
|
+
function clampAxis(value, min, max) {
|
|
18441
|
+
if (max < min) return min;
|
|
18442
|
+
return Math.min(max, Math.max(min, value));
|
|
18443
|
+
}
|
|
18444
|
+
function constrainDragPosition(raw, min, max, dimension, bounce) {
|
|
18445
|
+
if (!(Number.isFinite(raw) && Number.isFinite(min) && Number.isFinite(max))) {
|
|
18446
|
+
return 0;
|
|
18551
18447
|
}
|
|
18552
|
-
if (
|
|
18553
|
-
return
|
|
18448
|
+
if (raw >= min && raw <= max) {
|
|
18449
|
+
return raw;
|
|
18554
18450
|
}
|
|
18555
|
-
if (!
|
|
18556
|
-
return
|
|
18451
|
+
if (!bounce) {
|
|
18452
|
+
return clampAxis(raw, min, max);
|
|
18557
18453
|
}
|
|
18558
|
-
|
|
18454
|
+
const size = Math.max(1, Math.abs(dimension) || 1);
|
|
18455
|
+
if (raw < min) {
|
|
18456
|
+
const overshoot2 = min - raw;
|
|
18457
|
+
return min - rubberBandDistance(overshoot2, size);
|
|
18458
|
+
}
|
|
18459
|
+
const overshoot = raw - max;
|
|
18460
|
+
return max + rubberBandDistance(overshoot, size);
|
|
18559
18461
|
}
|
|
18560
|
-
function
|
|
18561
|
-
if (!
|
|
18562
|
-
|
|
18462
|
+
function rubberBandDistance(overshoot, dimension, constant = DRAG_RUBBER_CONSTANT) {
|
|
18463
|
+
if (!(overshoot > 0)) return 0;
|
|
18464
|
+
const size = Math.max(1, Math.abs(dimension) || 1);
|
|
18465
|
+
return size * overshoot / (size + overshoot * constant);
|
|
18466
|
+
}
|
|
18467
|
+
function velocityFromSamples(samples, now) {
|
|
18468
|
+
if (samples.length < 2) return 0;
|
|
18469
|
+
const windowMs = 100;
|
|
18470
|
+
let oldest = samples[0];
|
|
18471
|
+
const newest = samples[samples.length - 1];
|
|
18472
|
+
const referenceTime = now ?? newest.t;
|
|
18473
|
+
for (let i = samples.length - 2; i >= 0; i -= 1) {
|
|
18474
|
+
const sample = samples[i];
|
|
18475
|
+
if (referenceTime - sample.t > windowMs) break;
|
|
18476
|
+
oldest = sample;
|
|
18477
|
+
}
|
|
18478
|
+
const dt = (newest.t - oldest.t) / 1e3;
|
|
18479
|
+
if (!(dt >= 1e-3)) return 0;
|
|
18480
|
+
return (newest.position - oldest.position) / dt;
|
|
18481
|
+
}
|
|
18482
|
+
function integrateAxis(state, deltaSeconds, options) {
|
|
18483
|
+
const dt = Math.max(0, deltaSeconds);
|
|
18484
|
+
if (!(dt > 0)) {
|
|
18485
|
+
return { ...state, settled: isAxisSettled(state, options) };
|
|
18486
|
+
}
|
|
18487
|
+
const damping = options.damping ?? DRAG_INERTIA_DAMPING;
|
|
18488
|
+
const frictionStop = options.frictionStop ?? DRAG_FRICTION_STOP;
|
|
18489
|
+
const spring = options.spring ?? DRAG_SPRING_STRENGTH;
|
|
18490
|
+
const { min, max } = options;
|
|
18491
|
+
let { position, velocity } = state;
|
|
18492
|
+
const pastMin = position < min;
|
|
18493
|
+
const pastMax = position > max;
|
|
18494
|
+
if (pastMin || pastMax) {
|
|
18495
|
+
if (!options.bounce) {
|
|
18496
|
+
position = clampAxis(position, min, max);
|
|
18497
|
+
return { position, velocity: 0, settled: true };
|
|
18498
|
+
}
|
|
18499
|
+
const edge = pastMin ? min : max;
|
|
18500
|
+
if (pastMin && velocity < 0) velocity = 0;
|
|
18501
|
+
if (pastMax && velocity > 0) velocity = 0;
|
|
18502
|
+
const displacement = edge - position;
|
|
18503
|
+
velocity += displacement * spring * dt;
|
|
18504
|
+
const decay = Math.exp(-damping * dt);
|
|
18505
|
+
velocity *= decay;
|
|
18506
|
+
position += velocity * dt;
|
|
18507
|
+
if (pastMin && position > min) {
|
|
18508
|
+
position = min;
|
|
18509
|
+
velocity = 0;
|
|
18510
|
+
}
|
|
18511
|
+
if (pastMax && position < max) {
|
|
18512
|
+
position = max;
|
|
18513
|
+
velocity = 0;
|
|
18514
|
+
}
|
|
18515
|
+
const settled = isAxisSettled({ position, velocity }, options);
|
|
18516
|
+
if (settled) {
|
|
18517
|
+
return { position: clampAxis(position, min, max), velocity: 0, settled: true };
|
|
18518
|
+
}
|
|
18519
|
+
return { position, velocity, settled: false };
|
|
18520
|
+
}
|
|
18521
|
+
if (!options.inertia || Math.abs(velocity) < frictionStop) {
|
|
18522
|
+
return { position, velocity: 0, settled: true };
|
|
18523
|
+
}
|
|
18524
|
+
position += velocity * dt;
|
|
18525
|
+
velocity *= Math.exp(-damping * dt);
|
|
18526
|
+
if (position < min || position > max) {
|
|
18527
|
+
if (!options.bounce) {
|
|
18528
|
+
position = clampAxis(position, min, max);
|
|
18529
|
+
return { position, velocity: 0, settled: true };
|
|
18530
|
+
}
|
|
18531
|
+
if (Math.abs(velocity) < frictionStop) {
|
|
18532
|
+
velocity = 0;
|
|
18533
|
+
}
|
|
18534
|
+
return { position, velocity, settled: false };
|
|
18535
|
+
}
|
|
18536
|
+
if (Math.abs(velocity) < frictionStop) {
|
|
18537
|
+
return { position, velocity: 0, settled: true };
|
|
18538
|
+
}
|
|
18539
|
+
return { position, velocity, settled: false };
|
|
18540
|
+
}
|
|
18541
|
+
function isAxisSettled(state, options) {
|
|
18542
|
+
const frictionStop = options.frictionStop ?? DRAG_FRICTION_STOP;
|
|
18543
|
+
const inside = state.position >= options.min - DRAG_SPRING_SETTLE_DISTANCE && state.position <= options.max + DRAG_SPRING_SETTLE_DISTANCE;
|
|
18544
|
+
const slow = Math.abs(state.velocity) < (options.frictionStop ?? DRAG_SPRING_SETTLE_VELOCITY);
|
|
18545
|
+
if (!inside) return false;
|
|
18546
|
+
return slow || Math.abs(state.velocity) < frictionStop;
|
|
18547
|
+
}
|
|
18548
|
+
function pushSample(samples, sample, maxCount = 8) {
|
|
18549
|
+
const next = samples.length >= maxCount ? samples.slice(samples.length - maxCount + 1) : samples.slice();
|
|
18550
|
+
next.push(sample);
|
|
18551
|
+
return next;
|
|
18552
|
+
}
|
|
18553
|
+
|
|
18554
|
+
// src/pano-scene-menu.tsx
|
|
18555
|
+
import { jsx as jsx41, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
18556
|
+
var SCENE_MENU_DRAG_THRESHOLD_PX = 6;
|
|
18557
|
+
var DEFAULT_PANO_SCENE_MENU_APPEARANCE = {
|
|
18558
|
+
background: "rgba(16, 16, 16, 0.72)",
|
|
18559
|
+
padding: "8px 10px",
|
|
18560
|
+
itemWidth: 128,
|
|
18561
|
+
itemHeight: 80,
|
|
18562
|
+
gap: 8,
|
|
18563
|
+
borderRadius: 8,
|
|
18564
|
+
selectionColor: "#ffffff",
|
|
18565
|
+
selectionWidth: 2,
|
|
18566
|
+
labelBackground: "rgba(0, 0, 0, 0.55)",
|
|
18567
|
+
labelColor: "#ffffff",
|
|
18568
|
+
labelAlign: "start",
|
|
18569
|
+
fontSize: 12,
|
|
18570
|
+
controlColor: "#ffffff",
|
|
18571
|
+
controlBackground: "rgba(255, 255, 255, 0.12)"
|
|
18572
|
+
};
|
|
18573
|
+
var DEFAULT_INSET = 12;
|
|
18574
|
+
function cssLength(value) {
|
|
18575
|
+
return typeof value === "number" ? `${value}px` : value;
|
|
18576
|
+
}
|
|
18577
|
+
function resolveAppearance2(appearance) {
|
|
18578
|
+
return {
|
|
18579
|
+
...DEFAULT_PANO_SCENE_MENU_APPEARANCE,
|
|
18580
|
+
...appearance
|
|
18581
|
+
};
|
|
18582
|
+
}
|
|
18583
|
+
function resolveInset(inset) {
|
|
18584
|
+
if (inset === void 0) {
|
|
18585
|
+
const value = cssLength(DEFAULT_INSET);
|
|
18586
|
+
return { top: value, right: value, bottom: value, left: value };
|
|
18563
18587
|
}
|
|
18564
|
-
|
|
18565
|
-
|
|
18566
|
-
|
|
18567
|
-
|
|
18568
|
-
|
|
18588
|
+
if (typeof inset === "number" || typeof inset === "string") {
|
|
18589
|
+
const value = cssLength(inset);
|
|
18590
|
+
return { top: value, right: value, bottom: value, left: value };
|
|
18591
|
+
}
|
|
18592
|
+
return {
|
|
18593
|
+
top: cssLength(inset.top ?? DEFAULT_INSET),
|
|
18594
|
+
right: cssLength(inset.right ?? DEFAULT_INSET),
|
|
18595
|
+
bottom: cssLength(inset.bottom ?? DEFAULT_INSET),
|
|
18596
|
+
left: cssLength(inset.left ?? DEFAULT_INSET)
|
|
18597
|
+
};
|
|
18598
|
+
}
|
|
18599
|
+
function itemLabel(item) {
|
|
18600
|
+
return item.title?.trim() || item.id;
|
|
18601
|
+
}
|
|
18602
|
+
function isHorizontal(placement) {
|
|
18603
|
+
return placement === "top" || placement === "bottom";
|
|
18604
|
+
}
|
|
18605
|
+
var CHEVRON_PATHS = {
|
|
18606
|
+
// Keep glyphs optically centered in the 16×16 viewBox.
|
|
18607
|
+
up: "M3 11l5-6 5 6",
|
|
18608
|
+
down: "M3 5l5 6 5-6",
|
|
18609
|
+
left: "M11 3 5 8l6 5",
|
|
18610
|
+
right: "M5 3l6 5-6 5"
|
|
18611
|
+
};
|
|
18612
|
+
function arrowChevron(direction, orientation2) {
|
|
18613
|
+
if (direction === "prev") {
|
|
18614
|
+
return orientation2 === "horizontal" ? "left" : "up";
|
|
18615
|
+
}
|
|
18616
|
+
return orientation2 === "horizontal" ? "right" : "down";
|
|
18617
|
+
}
|
|
18618
|
+
function toggleChevron(placement, expanded) {
|
|
18619
|
+
switch (placement) {
|
|
18620
|
+
case "bottom":
|
|
18621
|
+
return expanded ? "down" : "up";
|
|
18622
|
+
case "top":
|
|
18623
|
+
return expanded ? "up" : "down";
|
|
18624
|
+
case "left":
|
|
18625
|
+
return expanded ? "left" : "right";
|
|
18626
|
+
case "right":
|
|
18627
|
+
return expanded ? "right" : "left";
|
|
18628
|
+
default: {
|
|
18629
|
+
const exhaustive = placement;
|
|
18630
|
+
void exhaustive;
|
|
18631
|
+
return "down";
|
|
18569
18632
|
}
|
|
18570
|
-
|
|
18571
|
-
|
|
18572
|
-
|
|
18633
|
+
}
|
|
18634
|
+
}
|
|
18635
|
+
function dockInset(placement, inset) {
|
|
18636
|
+
switch (placement) {
|
|
18637
|
+
case "top":
|
|
18638
|
+
return inset.top;
|
|
18639
|
+
case "bottom":
|
|
18640
|
+
return inset.bottom;
|
|
18641
|
+
case "left":
|
|
18642
|
+
return inset.left;
|
|
18643
|
+
case "right":
|
|
18644
|
+
return inset.right;
|
|
18645
|
+
default: {
|
|
18646
|
+
const exhaustive = placement;
|
|
18647
|
+
void exhaustive;
|
|
18648
|
+
return inset.bottom;
|
|
18573
18649
|
}
|
|
18574
18650
|
}
|
|
18575
|
-
return parts.join("\n");
|
|
18576
18651
|
}
|
|
18577
|
-
function
|
|
18578
|
-
|
|
18579
|
-
|
|
18652
|
+
function Chevron({ arrow }) {
|
|
18653
|
+
return /* @__PURE__ */ jsx41(
|
|
18654
|
+
"svg",
|
|
18655
|
+
{
|
|
18656
|
+
"aria-hidden": true,
|
|
18657
|
+
fill: "none",
|
|
18658
|
+
height: "16",
|
|
18659
|
+
stroke: "currentColor",
|
|
18660
|
+
strokeLinecap: "round",
|
|
18661
|
+
strokeLinejoin: "round",
|
|
18662
|
+
strokeWidth: "1.75",
|
|
18663
|
+
viewBox: "0 0 16 16",
|
|
18664
|
+
width: "16",
|
|
18665
|
+
children: /* @__PURE__ */ jsx41("path", { d: CHEVRON_PATHS[arrow] })
|
|
18666
|
+
}
|
|
18667
|
+
);
|
|
18668
|
+
}
|
|
18669
|
+
function ControlButton({
|
|
18670
|
+
className,
|
|
18671
|
+
disabled,
|
|
18672
|
+
label,
|
|
18673
|
+
onClick,
|
|
18674
|
+
children
|
|
18675
|
+
}) {
|
|
18676
|
+
return /* @__PURE__ */ jsx41(
|
|
18677
|
+
"button",
|
|
18678
|
+
{
|
|
18679
|
+
"aria-label": label,
|
|
18680
|
+
className: cn("pano-scene-menu-control", className),
|
|
18681
|
+
disabled,
|
|
18682
|
+
onClick,
|
|
18683
|
+
onPointerDown: (event) => {
|
|
18684
|
+
event.stopPropagation();
|
|
18685
|
+
},
|
|
18686
|
+
title: label,
|
|
18687
|
+
type: "button",
|
|
18688
|
+
children
|
|
18689
|
+
}
|
|
18690
|
+
);
|
|
18691
|
+
}
|
|
18692
|
+
function horizontalAlignStyle(align, inset, widthValue) {
|
|
18693
|
+
const left = `calc(${inset.left} + var(--pano-safe-left))`;
|
|
18694
|
+
const right = `calc(${inset.right} + var(--pano-safe-right))`;
|
|
18695
|
+
if (!widthValue) {
|
|
18696
|
+
return { left, right, width: void 0, marginInline: void 0 };
|
|
18697
|
+
}
|
|
18698
|
+
switch (align) {
|
|
18699
|
+
case "start":
|
|
18700
|
+
return { left, right: "auto", width: widthValue, marginInline: void 0 };
|
|
18701
|
+
case "end":
|
|
18702
|
+
return { left: "auto", right, width: widthValue, marginInline: void 0 };
|
|
18703
|
+
case "center":
|
|
18704
|
+
return { left, right, width: widthValue, marginInline: "auto" };
|
|
18705
|
+
default: {
|
|
18706
|
+
const exhaustive = align;
|
|
18707
|
+
void exhaustive;
|
|
18708
|
+
return { left, right, width: widthValue, marginInline: "auto" };
|
|
18709
|
+
}
|
|
18580
18710
|
}
|
|
18581
|
-
|
|
18582
|
-
|
|
18583
|
-
|
|
18711
|
+
}
|
|
18712
|
+
function verticalAlignStyle(align, inset, heightValue) {
|
|
18713
|
+
const top = `calc(${inset.top} + var(--pano-safe-top))`;
|
|
18714
|
+
const bottom = `calc(${inset.bottom} + var(--pano-safe-bottom))`;
|
|
18715
|
+
if (!heightValue) {
|
|
18716
|
+
return { top, bottom, height: void 0, marginBlock: void 0 };
|
|
18584
18717
|
}
|
|
18585
|
-
|
|
18586
|
-
|
|
18587
|
-
|
|
18588
|
-
|
|
18589
|
-
|
|
18718
|
+
switch (align) {
|
|
18719
|
+
case "start":
|
|
18720
|
+
return { top, bottom: "auto", height: heightValue, marginBlock: void 0 };
|
|
18721
|
+
case "end":
|
|
18722
|
+
return { top: "auto", bottom, height: heightValue, marginBlock: void 0 };
|
|
18723
|
+
case "center":
|
|
18724
|
+
return { top, bottom, height: heightValue, marginBlock: "auto" };
|
|
18725
|
+
default: {
|
|
18726
|
+
const exhaustive = align;
|
|
18727
|
+
void exhaustive;
|
|
18728
|
+
return { top, bottom, height: heightValue, marginBlock: "auto" };
|
|
18590
18729
|
}
|
|
18591
|
-
|
|
18592
|
-
|
|
18730
|
+
}
|
|
18731
|
+
}
|
|
18732
|
+
function placementBoxStyle(placement, inset, width, height, align) {
|
|
18733
|
+
const widthValue = width === void 0 ? void 0 : cssLength(width);
|
|
18734
|
+
const heightValue = height === void 0 ? void 0 : cssLength(height);
|
|
18735
|
+
const maxWidth = `calc(100% - ${inset.left} - ${inset.right} - var(--pano-safe-left) - var(--pano-safe-right))`;
|
|
18736
|
+
const maxHeight = `calc(100% - ${inset.top} - ${inset.bottom} - var(--pano-safe-top) - var(--pano-safe-bottom))`;
|
|
18737
|
+
switch (placement) {
|
|
18738
|
+
case "top":
|
|
18739
|
+
return {
|
|
18740
|
+
...horizontalAlignStyle(align, inset, widthValue),
|
|
18741
|
+
top: `calc(${inset.top} + var(--pano-safe-top))`,
|
|
18742
|
+
bottom: "auto",
|
|
18743
|
+
maxWidth,
|
|
18744
|
+
height: heightValue,
|
|
18745
|
+
maxHeight
|
|
18746
|
+
};
|
|
18747
|
+
case "bottom":
|
|
18748
|
+
return {
|
|
18749
|
+
...horizontalAlignStyle(align, inset, widthValue),
|
|
18750
|
+
top: "auto",
|
|
18751
|
+
bottom: `calc(${inset.bottom} + var(--pano-safe-bottom))`,
|
|
18752
|
+
maxWidth,
|
|
18753
|
+
height: heightValue,
|
|
18754
|
+
maxHeight
|
|
18755
|
+
};
|
|
18756
|
+
case "left":
|
|
18757
|
+
return {
|
|
18758
|
+
...verticalAlignStyle(align, inset, heightValue),
|
|
18759
|
+
right: "auto",
|
|
18760
|
+
left: `calc(${inset.left} + var(--pano-safe-left))`,
|
|
18761
|
+
width: widthValue,
|
|
18762
|
+
maxWidth,
|
|
18763
|
+
maxHeight
|
|
18764
|
+
};
|
|
18765
|
+
case "right":
|
|
18766
|
+
return {
|
|
18767
|
+
...verticalAlignStyle(align, inset, heightValue),
|
|
18768
|
+
right: `calc(${inset.right} + var(--pano-safe-right))`,
|
|
18769
|
+
left: "auto",
|
|
18770
|
+
width: widthValue,
|
|
18771
|
+
maxWidth,
|
|
18772
|
+
maxHeight
|
|
18773
|
+
};
|
|
18774
|
+
default: {
|
|
18775
|
+
const exhaustive = placement;
|
|
18776
|
+
void exhaustive;
|
|
18777
|
+
return {};
|
|
18593
18778
|
}
|
|
18594
18779
|
}
|
|
18595
|
-
|
|
18780
|
+
}
|
|
18781
|
+
function PanoSceneMenu({
|
|
18782
|
+
items,
|
|
18783
|
+
activeId = null,
|
|
18784
|
+
onSelect,
|
|
18785
|
+
placement = "bottom",
|
|
18786
|
+
align = "center",
|
|
18787
|
+
inset,
|
|
18788
|
+
width,
|
|
18789
|
+
height,
|
|
18790
|
+
collapsible = false,
|
|
18791
|
+
defaultExpanded = true,
|
|
18792
|
+
expanded: expandedProp,
|
|
18793
|
+
onExpandedChange,
|
|
18794
|
+
showArrows = false,
|
|
18795
|
+
loop = false,
|
|
18796
|
+
dragMotion: dragMotionProp,
|
|
18797
|
+
appearance,
|
|
18798
|
+
className,
|
|
18799
|
+
style,
|
|
18800
|
+
label = "Scenes",
|
|
18801
|
+
previousLabel = "Previous scene",
|
|
18802
|
+
nextLabel = "Next scene",
|
|
18803
|
+
expandLabel = "Show scenes",
|
|
18804
|
+
collapseLabel = "Hide scenes"
|
|
18805
|
+
}) {
|
|
18806
|
+
const listId = useId3();
|
|
18807
|
+
const listRef = useRef40(null);
|
|
18808
|
+
const trackRef = useRef40(null);
|
|
18809
|
+
const itemRefs = useRef40(/* @__PURE__ */ new Map());
|
|
18810
|
+
const resolved = resolveAppearance2(appearance);
|
|
18811
|
+
const resolvedInset = resolveInset(inset);
|
|
18812
|
+
const horizontal = isHorizontal(placement);
|
|
18813
|
+
const customDrag = dragMotionProp !== false;
|
|
18814
|
+
const dragMotion = resolveDragMotion(
|
|
18815
|
+
dragMotionProp === false ? false : dragMotionProp
|
|
18816
|
+
);
|
|
18817
|
+
const isExpandedControlled = expandedProp !== void 0;
|
|
18818
|
+
const [internalExpanded, setInternalExpanded] = useState25(defaultExpanded);
|
|
18819
|
+
const expanded = collapsible ? isExpandedControlled ? expandedProp : internalExpanded : true;
|
|
18820
|
+
const [offset, setOffset] = useState25(0);
|
|
18821
|
+
const [viewportSize, setViewportSize] = useState25(0);
|
|
18822
|
+
const [contentSize, setContentSize] = useState25(0);
|
|
18823
|
+
const offsetRef = useRef40(0);
|
|
18824
|
+
const dragRef = useRef40(null);
|
|
18825
|
+
const animRef = useRef40(null);
|
|
18826
|
+
const maxOffset = Math.max(0, contentSize - viewportSize);
|
|
18827
|
+
offsetRef.current = offset;
|
|
18828
|
+
const activeIndex = useMemo27(() => {
|
|
18829
|
+
if (activeId == null) {
|
|
18830
|
+
return -1;
|
|
18831
|
+
}
|
|
18832
|
+
return items.findIndex((item) => item.id === activeId);
|
|
18833
|
+
}, [activeId, items]);
|
|
18834
|
+
const cancelScrollAnimation = useCallback11(() => {
|
|
18835
|
+
const anim = animRef.current;
|
|
18836
|
+
if (anim?.raf != null) {
|
|
18837
|
+
cancelAnimationFrame(anim.raf);
|
|
18838
|
+
}
|
|
18839
|
+
animRef.current = null;
|
|
18840
|
+
}, []);
|
|
18841
|
+
const clampOffset = useCallback11(
|
|
18842
|
+
(value) => Math.min(maxOffset, Math.max(0, value)),
|
|
18843
|
+
[maxOffset]
|
|
18844
|
+
);
|
|
18845
|
+
const startScrollAnimation = useCallback11(
|
|
18846
|
+
(velocity) => {
|
|
18847
|
+
cancelScrollAnimation();
|
|
18848
|
+
const current = offsetRef.current;
|
|
18849
|
+
const needsSpring = dragMotion.bounce && (current < 0 || current > maxOffset);
|
|
18850
|
+
const nextVelocity = dragMotion.inertia ? velocity : 0;
|
|
18851
|
+
if (!needsSpring && Math.abs(nextVelocity) < 1) {
|
|
18852
|
+
setOffset(clampOffset(current));
|
|
18853
|
+
return;
|
|
18854
|
+
}
|
|
18855
|
+
if (!dragMotion.inertia && !dragMotion.bounce) {
|
|
18856
|
+
setOffset(clampOffset(current));
|
|
18857
|
+
return;
|
|
18858
|
+
}
|
|
18859
|
+
const anim = {
|
|
18860
|
+
raf: null,
|
|
18861
|
+
lastTime: performance.now(),
|
|
18862
|
+
position: current,
|
|
18863
|
+
velocity: nextVelocity
|
|
18864
|
+
};
|
|
18865
|
+
animRef.current = anim;
|
|
18866
|
+
const tick = (now) => {
|
|
18867
|
+
if (animRef.current !== anim) return;
|
|
18868
|
+
const dt = Math.min(0.064, Math.max(0, (now - anim.lastTime) / 1e3));
|
|
18869
|
+
anim.lastTime = now;
|
|
18870
|
+
const next = integrateAxis(
|
|
18871
|
+
{ position: anim.position, velocity: anim.velocity },
|
|
18872
|
+
dt,
|
|
18873
|
+
{
|
|
18874
|
+
min: 0,
|
|
18875
|
+
max: maxOffset,
|
|
18876
|
+
inertia: dragMotion.inertia,
|
|
18877
|
+
bounce: dragMotion.bounce
|
|
18878
|
+
}
|
|
18879
|
+
);
|
|
18880
|
+
anim.position = next.position;
|
|
18881
|
+
anim.velocity = next.velocity;
|
|
18882
|
+
setOffset(next.position);
|
|
18883
|
+
if (next.settled) {
|
|
18884
|
+
setOffset(clampOffset(next.position));
|
|
18885
|
+
animRef.current = null;
|
|
18886
|
+
return;
|
|
18887
|
+
}
|
|
18888
|
+
anim.raf = requestAnimationFrame(tick);
|
|
18889
|
+
};
|
|
18890
|
+
anim.raf = requestAnimationFrame(tick);
|
|
18891
|
+
},
|
|
18892
|
+
[
|
|
18893
|
+
cancelScrollAnimation,
|
|
18894
|
+
clampOffset,
|
|
18895
|
+
dragMotion.bounce,
|
|
18896
|
+
dragMotion.inertia,
|
|
18897
|
+
maxOffset
|
|
18898
|
+
]
|
|
18899
|
+
);
|
|
18900
|
+
useEffect41(() => () => cancelScrollAnimation(), [cancelScrollAnimation]);
|
|
18901
|
+
useLayoutEffect9(() => {
|
|
18902
|
+
if (!customDrag || !expanded) return;
|
|
18903
|
+
const list = listRef.current;
|
|
18904
|
+
const track = trackRef.current;
|
|
18905
|
+
if (!list || !track) return;
|
|
18906
|
+
const measure = () => {
|
|
18907
|
+
const nextViewport = horizontal ? list.clientWidth : list.clientHeight;
|
|
18908
|
+
const nextContent = horizontal ? track.scrollWidth : track.scrollHeight;
|
|
18909
|
+
setViewportSize(
|
|
18910
|
+
(previous) => previous === nextViewport ? previous : nextViewport
|
|
18911
|
+
);
|
|
18912
|
+
setContentSize(
|
|
18913
|
+
(previous) => previous === nextContent ? previous : nextContent
|
|
18914
|
+
);
|
|
18915
|
+
};
|
|
18916
|
+
measure();
|
|
18917
|
+
const observer = new ResizeObserver(measure);
|
|
18918
|
+
observer.observe(list);
|
|
18919
|
+
observer.observe(track);
|
|
18920
|
+
return () => observer.disconnect();
|
|
18921
|
+
}, [customDrag, expanded, horizontal, items]);
|
|
18922
|
+
useEffect41(() => {
|
|
18923
|
+
if (!customDrag) return;
|
|
18924
|
+
if (dragRef.current || animRef.current) return;
|
|
18925
|
+
setOffset((previous) => Math.min(maxOffset, Math.max(0, previous)));
|
|
18926
|
+
}, [customDrag, maxOffset]);
|
|
18927
|
+
useEffect41(() => {
|
|
18928
|
+
if (!expanded || activeId == null) {
|
|
18929
|
+
return;
|
|
18930
|
+
}
|
|
18931
|
+
const node = itemRefs.current.get(activeId);
|
|
18932
|
+
if (!node) return;
|
|
18933
|
+
if (!customDrag) {
|
|
18934
|
+
node.scrollIntoView({
|
|
18935
|
+
behavior: "smooth",
|
|
18936
|
+
block: "nearest",
|
|
18937
|
+
inline: "nearest"
|
|
18938
|
+
});
|
|
18939
|
+
return;
|
|
18940
|
+
}
|
|
18941
|
+
const list = listRef.current;
|
|
18942
|
+
if (!list) return;
|
|
18943
|
+
const listRect = list.getBoundingClientRect();
|
|
18944
|
+
const itemRect = node.getBoundingClientRect();
|
|
18945
|
+
if (horizontal) {
|
|
18946
|
+
const itemStart2 = itemRect.left - listRect.left + offsetRef.current;
|
|
18947
|
+
const itemEnd2 = itemStart2 + itemRect.width;
|
|
18948
|
+
let next2 = offsetRef.current;
|
|
18949
|
+
if (itemStart2 < next2) next2 = itemStart2;
|
|
18950
|
+
else if (itemEnd2 > next2 + listRect.width) next2 = itemEnd2 - listRect.width;
|
|
18951
|
+
cancelScrollAnimation();
|
|
18952
|
+
setOffset(clampOffset(next2));
|
|
18953
|
+
return;
|
|
18954
|
+
}
|
|
18955
|
+
const itemStart = itemRect.top - listRect.top + offsetRef.current;
|
|
18956
|
+
const itemEnd = itemStart + itemRect.height;
|
|
18957
|
+
let next = offsetRef.current;
|
|
18958
|
+
if (itemStart < next) next = itemStart;
|
|
18959
|
+
else if (itemEnd > next + listRect.height) next = itemEnd - listRect.height;
|
|
18960
|
+
cancelScrollAnimation();
|
|
18961
|
+
setOffset(clampOffset(next));
|
|
18962
|
+
}, [
|
|
18963
|
+
activeId,
|
|
18964
|
+
cancelScrollAnimation,
|
|
18965
|
+
clampOffset,
|
|
18966
|
+
customDrag,
|
|
18967
|
+
expanded,
|
|
18968
|
+
horizontal
|
|
18969
|
+
]);
|
|
18970
|
+
const rootStyle = useMemo27(() => {
|
|
18971
|
+
const vars = {
|
|
18972
|
+
"--pano-scene-menu-background": resolved.background,
|
|
18973
|
+
"--pano-scene-menu-padding": cssLength(resolved.padding),
|
|
18974
|
+
"--pano-scene-menu-item-width": cssLength(resolved.itemWidth),
|
|
18975
|
+
"--pano-scene-menu-item-height": cssLength(resolved.itemHeight),
|
|
18976
|
+
"--pano-scene-menu-gap": cssLength(resolved.gap),
|
|
18977
|
+
"--pano-scene-menu-radius": cssLength(resolved.borderRadius),
|
|
18978
|
+
"--pano-scene-menu-selection-color": resolved.selectionColor,
|
|
18979
|
+
"--pano-scene-menu-selection-width": cssLength(resolved.selectionWidth),
|
|
18980
|
+
"--pano-scene-menu-label-background": resolved.labelBackground,
|
|
18981
|
+
"--pano-scene-menu-label-color": resolved.labelColor,
|
|
18982
|
+
"--pano-scene-menu-label-align": resolved.labelAlign,
|
|
18983
|
+
"--pano-scene-menu-font-size": cssLength(resolved.fontSize),
|
|
18984
|
+
"--pano-scene-menu-control-color": resolved.controlColor,
|
|
18985
|
+
"--pano-scene-menu-control-background": resolved.controlBackground,
|
|
18986
|
+
"--pano-scene-menu-dock-inset": dockInset(placement, resolvedInset)
|
|
18987
|
+
};
|
|
18988
|
+
return {
|
|
18989
|
+
...vars,
|
|
18990
|
+
...placementBoxStyle(placement, resolvedInset, width, height, align),
|
|
18991
|
+
...style
|
|
18992
|
+
};
|
|
18993
|
+
}, [align, height, placement, resolved, resolvedInset, style, width]);
|
|
18994
|
+
const setExpanded = (next) => {
|
|
18995
|
+
if (!collapsible) {
|
|
18996
|
+
return;
|
|
18997
|
+
}
|
|
18998
|
+
if (!isExpandedControlled) {
|
|
18999
|
+
setInternalExpanded(next);
|
|
19000
|
+
}
|
|
19001
|
+
onExpandedChange?.(next);
|
|
19002
|
+
};
|
|
19003
|
+
const selectAt = (index) => {
|
|
19004
|
+
const item = items[index];
|
|
19005
|
+
if (!item) {
|
|
19006
|
+
return;
|
|
19007
|
+
}
|
|
19008
|
+
onSelect?.(item.id);
|
|
19009
|
+
itemRefs.current.get(item.id)?.focus({ preventScroll: true });
|
|
19010
|
+
};
|
|
19011
|
+
const selectRelative = (delta) => {
|
|
19012
|
+
if (items.length === 0) {
|
|
19013
|
+
return;
|
|
19014
|
+
}
|
|
19015
|
+
const current = activeIndex >= 0 ? activeIndex : 0;
|
|
19016
|
+
if (loop) {
|
|
19017
|
+
const next = ((current + delta) % items.length + items.length) % items.length;
|
|
19018
|
+
selectAt(next);
|
|
19019
|
+
return;
|
|
19020
|
+
}
|
|
19021
|
+
selectAt(Math.max(0, Math.min(items.length - 1, current + delta)));
|
|
19022
|
+
};
|
|
19023
|
+
const onKeyDown = (event) => {
|
|
19024
|
+
if (items.length === 0 || !expanded) {
|
|
19025
|
+
return;
|
|
19026
|
+
}
|
|
19027
|
+
const current = activeIndex >= 0 ? activeIndex : Math.max(
|
|
19028
|
+
0,
|
|
19029
|
+
items.findIndex(
|
|
19030
|
+
(item) => itemRefs.current.get(item.id) === event.target
|
|
19031
|
+
)
|
|
19032
|
+
);
|
|
19033
|
+
const backward = horizontal ? event.key === "ArrowLeft" : event.key === "ArrowUp";
|
|
19034
|
+
const forward = horizontal ? event.key === "ArrowRight" : event.key === "ArrowDown";
|
|
19035
|
+
if (backward) {
|
|
19036
|
+
event.preventDefault();
|
|
19037
|
+
if (loop) {
|
|
19038
|
+
selectAt((current - 1 + items.length) % items.length);
|
|
19039
|
+
} else {
|
|
19040
|
+
selectAt(Math.max(0, current - 1));
|
|
19041
|
+
}
|
|
19042
|
+
return;
|
|
19043
|
+
}
|
|
19044
|
+
if (forward) {
|
|
19045
|
+
event.preventDefault();
|
|
19046
|
+
if (loop) {
|
|
19047
|
+
selectAt((current + 1) % items.length);
|
|
19048
|
+
} else {
|
|
19049
|
+
selectAt(Math.min(items.length - 1, current + 1));
|
|
19050
|
+
}
|
|
19051
|
+
return;
|
|
19052
|
+
}
|
|
19053
|
+
switch (event.key) {
|
|
19054
|
+
case "Home": {
|
|
19055
|
+
event.preventDefault();
|
|
19056
|
+
selectAt(0);
|
|
19057
|
+
break;
|
|
19058
|
+
}
|
|
19059
|
+
case "End": {
|
|
19060
|
+
event.preventDefault();
|
|
19061
|
+
selectAt(items.length - 1);
|
|
19062
|
+
break;
|
|
19063
|
+
}
|
|
19064
|
+
default:
|
|
19065
|
+
break;
|
|
19066
|
+
}
|
|
19067
|
+
};
|
|
19068
|
+
const onWheel = (event) => {
|
|
19069
|
+
const list = listRef.current;
|
|
19070
|
+
if (!list) {
|
|
19071
|
+
return;
|
|
19072
|
+
}
|
|
19073
|
+
if (!customDrag) {
|
|
19074
|
+
if (horizontal) {
|
|
19075
|
+
const canScroll2 = list.scrollWidth > list.clientWidth + 1;
|
|
19076
|
+
if (!canScroll2 || Math.abs(event.deltaY) <= Math.abs(event.deltaX)) {
|
|
19077
|
+
return;
|
|
19078
|
+
}
|
|
19079
|
+
event.preventDefault();
|
|
19080
|
+
event.stopPropagation();
|
|
19081
|
+
list.scrollLeft += event.deltaY;
|
|
19082
|
+
return;
|
|
19083
|
+
}
|
|
19084
|
+
const canScroll = list.scrollHeight > list.clientHeight + 1;
|
|
19085
|
+
if (!canScroll || Math.abs(event.deltaX) <= Math.abs(event.deltaY)) {
|
|
19086
|
+
return;
|
|
19087
|
+
}
|
|
19088
|
+
event.preventDefault();
|
|
19089
|
+
event.stopPropagation();
|
|
19090
|
+
list.scrollTop += event.deltaX;
|
|
19091
|
+
return;
|
|
19092
|
+
}
|
|
19093
|
+
if (horizontal) {
|
|
19094
|
+
if (maxOffset <= 0 || Math.abs(event.deltaY) <= Math.abs(event.deltaX)) {
|
|
19095
|
+
return;
|
|
19096
|
+
}
|
|
19097
|
+
event.preventDefault();
|
|
19098
|
+
event.stopPropagation();
|
|
19099
|
+
cancelScrollAnimation();
|
|
19100
|
+
setOffset((previous) => clampOffset(previous + event.deltaY));
|
|
19101
|
+
return;
|
|
19102
|
+
}
|
|
19103
|
+
if (maxOffset <= 0 || Math.abs(event.deltaX) <= Math.abs(event.deltaY)) {
|
|
19104
|
+
return;
|
|
19105
|
+
}
|
|
19106
|
+
event.preventDefault();
|
|
19107
|
+
event.stopPropagation();
|
|
19108
|
+
cancelScrollAnimation();
|
|
19109
|
+
setOffset((previous) => clampOffset(previous + event.deltaX));
|
|
19110
|
+
};
|
|
19111
|
+
const axisPoint = (event) => horizontal ? event.clientX : event.clientY;
|
|
19112
|
+
const endSuppressClickRef = useRef40(false);
|
|
19113
|
+
const beginListDrag = (event, captureTarget) => {
|
|
19114
|
+
if (!customDrag || event.button !== 0) return;
|
|
19115
|
+
cancelScrollAnimation();
|
|
19116
|
+
const now = performance.now();
|
|
19117
|
+
const origin = axisPoint(event);
|
|
19118
|
+
dragRef.current = {
|
|
19119
|
+
pointerId: event.pointerId,
|
|
19120
|
+
origin,
|
|
19121
|
+
startOffset: offsetRef.current,
|
|
19122
|
+
samples: [{ t: now, position: offsetRef.current }],
|
|
19123
|
+
moved: false,
|
|
19124
|
+
suppressClick: false
|
|
19125
|
+
};
|
|
19126
|
+
try {
|
|
19127
|
+
captureTarget.setPointerCapture(event.pointerId);
|
|
19128
|
+
} catch {
|
|
19129
|
+
}
|
|
19130
|
+
};
|
|
19131
|
+
const onListPointerMove = (event) => {
|
|
19132
|
+
const drag = dragRef.current;
|
|
19133
|
+
if (!drag || drag.pointerId !== event.pointerId) return;
|
|
19134
|
+
const point = axisPoint(event);
|
|
19135
|
+
const delta = point - drag.origin;
|
|
19136
|
+
if (!drag.moved && Math.abs(delta) >= SCENE_MENU_DRAG_THRESHOLD_PX) {
|
|
19137
|
+
drag.moved = true;
|
|
19138
|
+
drag.suppressClick = true;
|
|
19139
|
+
}
|
|
19140
|
+
if (!drag.moved) return;
|
|
19141
|
+
const raw = drag.startOffset - delta;
|
|
19142
|
+
const now = performance.now();
|
|
19143
|
+
drag.samples = pushSample(drag.samples, { t: now, position: raw });
|
|
19144
|
+
setOffset(
|
|
19145
|
+
constrainDragPosition(
|
|
19146
|
+
raw,
|
|
19147
|
+
0,
|
|
19148
|
+
maxOffset,
|
|
19149
|
+
Math.max(1, viewportSize),
|
|
19150
|
+
dragMotion.bounce
|
|
19151
|
+
)
|
|
19152
|
+
);
|
|
19153
|
+
event.preventDefault();
|
|
19154
|
+
};
|
|
19155
|
+
const finishPointerWithClickGuard = (event) => {
|
|
19156
|
+
const drag = dragRef.current;
|
|
19157
|
+
if (!drag || drag.pointerId !== event.pointerId) return;
|
|
19158
|
+
const suppress = drag.suppressClick;
|
|
19159
|
+
dragRef.current = null;
|
|
19160
|
+
try {
|
|
19161
|
+
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
|
|
19162
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
19163
|
+
}
|
|
19164
|
+
} catch {
|
|
19165
|
+
}
|
|
19166
|
+
if (suppress) {
|
|
19167
|
+
endSuppressClickRef.current = true;
|
|
19168
|
+
queueMicrotask(() => {
|
|
19169
|
+
endSuppressClickRef.current = false;
|
|
19170
|
+
});
|
|
19171
|
+
}
|
|
19172
|
+
if (!drag.moved) return;
|
|
19173
|
+
if (event.type === "pointercancel") {
|
|
19174
|
+
setOffset(clampOffset(offsetRef.current));
|
|
19175
|
+
return;
|
|
19176
|
+
}
|
|
19177
|
+
startScrollAnimation(velocityFromSamples(drag.samples));
|
|
19178
|
+
};
|
|
19179
|
+
const onItemClick = (event, id) => {
|
|
19180
|
+
if (endSuppressClickRef.current) {
|
|
19181
|
+
event.preventDefault();
|
|
19182
|
+
event.stopPropagation();
|
|
19183
|
+
return;
|
|
19184
|
+
}
|
|
19185
|
+
onSelect?.(id);
|
|
19186
|
+
};
|
|
19187
|
+
const canStep = items.length > 1;
|
|
19188
|
+
const canGoPrevious = canStep && (loop || activeIndex !== 0);
|
|
19189
|
+
const canGoNext = canStep && (loop || activeIndex !== items.length - 1);
|
|
19190
|
+
const orientation2 = horizontal ? "horizontal" : "vertical";
|
|
19191
|
+
const trackStyle = customDrag ? {
|
|
19192
|
+
transform: horizontal ? `translate3d(${-offset}px, 0, 0)` : `translate3d(0, ${-offset}px, 0)`,
|
|
19193
|
+
willChange: "transform"
|
|
19194
|
+
} : void 0;
|
|
19195
|
+
return /* @__PURE__ */ jsxs22(
|
|
19196
|
+
"div",
|
|
19197
|
+
{
|
|
19198
|
+
"aria-label": label,
|
|
19199
|
+
className: cn("pano-scene-menu", className),
|
|
19200
|
+
"data-align": align,
|
|
19201
|
+
"data-collapsible": collapsible ? "true" : void 0,
|
|
19202
|
+
"data-drag-motion": customDrag ? "custom" : "native",
|
|
19203
|
+
"data-expanded": expanded ? "true" : "false",
|
|
19204
|
+
"data-orientation": orientation2,
|
|
19205
|
+
"data-placement": placement,
|
|
19206
|
+
role: "group",
|
|
19207
|
+
style: rootStyle,
|
|
19208
|
+
children: [
|
|
19209
|
+
/* @__PURE__ */ jsxs22(
|
|
19210
|
+
"div",
|
|
19211
|
+
{
|
|
19212
|
+
"aria-hidden": expanded ? void 0 : true,
|
|
19213
|
+
className: "pano-scene-menu-body",
|
|
19214
|
+
...!expanded ? { inert: true } : null,
|
|
19215
|
+
children: [
|
|
19216
|
+
showArrows ? /* @__PURE__ */ jsx41(
|
|
19217
|
+
ControlButton,
|
|
19218
|
+
{
|
|
19219
|
+
className: "pano-scene-menu-arrow",
|
|
19220
|
+
disabled: !canGoPrevious || !expanded,
|
|
19221
|
+
label: previousLabel,
|
|
19222
|
+
onClick: () => selectRelative(-1),
|
|
19223
|
+
children: /* @__PURE__ */ jsx41(Chevron, { arrow: arrowChevron("prev", orientation2) })
|
|
19224
|
+
}
|
|
19225
|
+
) : null,
|
|
19226
|
+
/* @__PURE__ */ jsx41(
|
|
19227
|
+
"div",
|
|
19228
|
+
{
|
|
19229
|
+
"aria-label": label,
|
|
19230
|
+
className: "pano-scene-menu-list",
|
|
19231
|
+
id: listId,
|
|
19232
|
+
onKeyDown,
|
|
19233
|
+
onPointerCancel: customDrag ? finishPointerWithClickGuard : void 0,
|
|
19234
|
+
onPointerDown: customDrag ? (event) => beginListDrag(event, event.currentTarget) : void 0,
|
|
19235
|
+
onPointerMove: customDrag ? onListPointerMove : void 0,
|
|
19236
|
+
onPointerUp: customDrag ? finishPointerWithClickGuard : void 0,
|
|
19237
|
+
onWheel,
|
|
19238
|
+
ref: listRef,
|
|
19239
|
+
role: "radiogroup",
|
|
19240
|
+
children: /* @__PURE__ */ jsx41(
|
|
19241
|
+
"div",
|
|
19242
|
+
{
|
|
19243
|
+
className: "pano-scene-menu-track",
|
|
19244
|
+
ref: trackRef,
|
|
19245
|
+
style: trackStyle,
|
|
19246
|
+
children: items.map((item) => {
|
|
19247
|
+
const name = itemLabel(item);
|
|
19248
|
+
const selected = item.id === activeId;
|
|
19249
|
+
return /* @__PURE__ */ jsx41(
|
|
19250
|
+
"button",
|
|
19251
|
+
{
|
|
19252
|
+
"aria-checked": selected,
|
|
19253
|
+
className: "pano-scene-menu-item",
|
|
19254
|
+
"data-active": selected ? "true" : void 0,
|
|
19255
|
+
onClick: (event) => onItemClick(event, item.id),
|
|
19256
|
+
onPointerDown: (event) => {
|
|
19257
|
+
event.stopPropagation();
|
|
19258
|
+
const list = listRef.current;
|
|
19259
|
+
if (customDrag && list) {
|
|
19260
|
+
beginListDrag(event, list);
|
|
19261
|
+
}
|
|
19262
|
+
},
|
|
19263
|
+
ref: (node) => {
|
|
19264
|
+
if (node) {
|
|
19265
|
+
itemRefs.current.set(item.id, node);
|
|
19266
|
+
} else {
|
|
19267
|
+
itemRefs.current.delete(item.id);
|
|
19268
|
+
}
|
|
19269
|
+
},
|
|
19270
|
+
role: "radio",
|
|
19271
|
+
tabIndex: selected || activeId == null && item === items[0] ? 0 : -1,
|
|
19272
|
+
title: name,
|
|
19273
|
+
type: "button",
|
|
19274
|
+
children: /* @__PURE__ */ jsxs22("span", { className: "pano-scene-menu-thumb", children: [
|
|
19275
|
+
/* @__PURE__ */ jsx41(
|
|
19276
|
+
"img",
|
|
19277
|
+
{
|
|
19278
|
+
alt: "",
|
|
19279
|
+
decoding: "async",
|
|
19280
|
+
draggable: false,
|
|
19281
|
+
src: item.thumbnailUrl
|
|
19282
|
+
}
|
|
19283
|
+
),
|
|
19284
|
+
/* @__PURE__ */ jsx41("span", { className: "pano-scene-menu-label", children: name })
|
|
19285
|
+
] })
|
|
19286
|
+
},
|
|
19287
|
+
item.id
|
|
19288
|
+
);
|
|
19289
|
+
})
|
|
19290
|
+
}
|
|
19291
|
+
)
|
|
19292
|
+
}
|
|
19293
|
+
),
|
|
19294
|
+
showArrows ? /* @__PURE__ */ jsx41(
|
|
19295
|
+
ControlButton,
|
|
19296
|
+
{
|
|
19297
|
+
className: "pano-scene-menu-arrow",
|
|
19298
|
+
disabled: !canGoNext || !expanded,
|
|
19299
|
+
label: nextLabel,
|
|
19300
|
+
onClick: () => selectRelative(1),
|
|
19301
|
+
children: /* @__PURE__ */ jsx41(Chevron, { arrow: arrowChevron("next", orientation2) })
|
|
19302
|
+
}
|
|
19303
|
+
) : null
|
|
19304
|
+
]
|
|
19305
|
+
}
|
|
19306
|
+
),
|
|
19307
|
+
collapsible ? /* @__PURE__ */ jsx41(
|
|
19308
|
+
ControlButton,
|
|
19309
|
+
{
|
|
19310
|
+
className: "pano-scene-menu-toggle",
|
|
19311
|
+
label: expanded ? collapseLabel : expandLabel,
|
|
19312
|
+
onClick: () => setExpanded(!expanded),
|
|
19313
|
+
children: /* @__PURE__ */ jsx41(Chevron, { arrow: toggleChevron(placement, expanded) })
|
|
19314
|
+
}
|
|
19315
|
+
) : null
|
|
19316
|
+
]
|
|
19317
|
+
}
|
|
19318
|
+
);
|
|
19319
|
+
}
|
|
19320
|
+
|
|
19321
|
+
// src/use-pano-viewer.ts
|
|
19322
|
+
import { useMemo as useMemo28, useRef as useRef41 } from "react";
|
|
19323
|
+
function createPanoViewerHandle(ref) {
|
|
19324
|
+
return {
|
|
19325
|
+
getView: () => ref.current?.getView() ?? { yaw: 0, pitch: 0, fov: 75 },
|
|
19326
|
+
setView: (view, options) => {
|
|
19327
|
+
ref.current?.setView(view, options);
|
|
19328
|
+
},
|
|
19329
|
+
reset: () => {
|
|
19330
|
+
ref.current?.reset();
|
|
19331
|
+
},
|
|
19332
|
+
lookTo: (position, options) => ref.current?.lookTo(position, options) ?? Promise.resolve({ status: "cancelled" }),
|
|
19333
|
+
moveTo: (position, options) => ref.current?.moveTo(position, options) ?? Promise.resolve({ status: "cancelled" }),
|
|
19334
|
+
zoomTo: (fov, options) => ref.current?.zoomTo(fov, options) ?? Promise.resolve({ status: "cancelled" }),
|
|
19335
|
+
lookToHotspot: (id, options) => ref.current?.lookToHotspot(id, options) ?? Promise.resolve({ status: "not-found" }),
|
|
19336
|
+
activateMedia: () => ref.current?.activateMedia() ?? Promise.resolve(),
|
|
19337
|
+
enterFullscreen: () => ref.current?.enterFullscreen() ?? Promise.resolve(),
|
|
19338
|
+
exitFullscreen: () => ref.current?.exitFullscreen() ?? Promise.resolve(),
|
|
19339
|
+
toggleFullscreen: () => ref.current?.toggleFullscreen() ?? Promise.resolve(),
|
|
19340
|
+
isFullscreen: () => ref.current?.isFullscreen() ?? false,
|
|
19341
|
+
setScene: (id) => ref.current?.setScene(id) ?? false,
|
|
19342
|
+
nextScene: () => ref.current?.nextScene() ?? false,
|
|
19343
|
+
previousScene: () => ref.current?.previousScene() ?? false,
|
|
19344
|
+
getActiveSceneId: () => ref.current?.getActiveSceneId() ?? null,
|
|
19345
|
+
getSceneIds: () => ref.current?.getSceneIds() ?? [],
|
|
19346
|
+
isSceneTransitioning: () => ref.current?.isSceneTransitioning() ?? false,
|
|
19347
|
+
enterVR: () => ref.current?.enterVR() ?? Promise.resolve(false),
|
|
19348
|
+
exitVR: () => ref.current?.exitVR() ?? Promise.resolve(),
|
|
19349
|
+
toggleVR: () => ref.current?.toggleVR() ?? Promise.resolve(false),
|
|
19350
|
+
isVRAvailable: () => ref.current?.isVRAvailable() ?? false,
|
|
19351
|
+
isVREnabled: () => ref.current?.isVREnabled() ?? false,
|
|
19352
|
+
getVRMode: () => ref.current?.getVRMode() ?? null,
|
|
19353
|
+
requestVRPermission: () => ref.current?.requestVRPermission() ?? Promise.resolve(false),
|
|
19354
|
+
getVideo: () => ref.current?.getVideo() ?? null,
|
|
19355
|
+
subscribeVideo: (onStoreChange) => ref.current?.subscribeVideo(onStoreChange) ?? (() => {
|
|
19356
|
+
}),
|
|
19357
|
+
playVideo: () => ref.current?.playVideo() ?? Promise.resolve(),
|
|
19358
|
+
pauseVideo: () => {
|
|
19359
|
+
ref.current?.pauseVideo();
|
|
19360
|
+
},
|
|
19361
|
+
toggleVideo: () => {
|
|
19362
|
+
ref.current?.toggleVideo();
|
|
19363
|
+
},
|
|
19364
|
+
seekVideo: (time) => {
|
|
19365
|
+
ref.current?.seekVideo(time);
|
|
19366
|
+
},
|
|
19367
|
+
setVideoVolume: (volume) => {
|
|
19368
|
+
ref.current?.setVideoVolume(volume);
|
|
19369
|
+
},
|
|
19370
|
+
setVideoMuted: (muted) => {
|
|
19371
|
+
ref.current?.setVideoMuted(muted);
|
|
19372
|
+
},
|
|
19373
|
+
toggleVideoMuted: () => {
|
|
19374
|
+
ref.current?.toggleVideoMuted();
|
|
19375
|
+
},
|
|
19376
|
+
getBackgroundAudio: () => ref.current?.getBackgroundAudio() ?? null,
|
|
19377
|
+
subscribeBackgroundAudio: (onStoreChange) => ref.current?.subscribeBackgroundAudio(onStoreChange) ?? (() => {
|
|
19378
|
+
}),
|
|
19379
|
+
playBackgroundAudio: () => ref.current?.playBackgroundAudio() ?? Promise.resolve(),
|
|
19380
|
+
pauseBackgroundAudio: () => {
|
|
19381
|
+
ref.current?.pauseBackgroundAudio();
|
|
19382
|
+
},
|
|
19383
|
+
toggleBackgroundAudio: () => {
|
|
19384
|
+
ref.current?.toggleBackgroundAudio();
|
|
19385
|
+
},
|
|
19386
|
+
setBackgroundAudioVolume: (volume) => {
|
|
19387
|
+
ref.current?.setBackgroundAudioVolume(volume);
|
|
19388
|
+
},
|
|
19389
|
+
setBackgroundAudioMuted: (muted) => {
|
|
19390
|
+
ref.current?.setBackgroundAudioMuted(muted);
|
|
19391
|
+
},
|
|
19392
|
+
toggleBackgroundAudioMuted: () => {
|
|
19393
|
+
ref.current?.toggleBackgroundAudioMuted();
|
|
19394
|
+
},
|
|
19395
|
+
startAutoRotate: () => {
|
|
19396
|
+
ref.current?.startAutoRotate();
|
|
19397
|
+
},
|
|
19398
|
+
stopAutoRotate: () => {
|
|
19399
|
+
ref.current?.stopAutoRotate();
|
|
19400
|
+
}
|
|
19401
|
+
};
|
|
19402
|
+
}
|
|
19403
|
+
function usePanoViewer() {
|
|
19404
|
+
const ref = useRef41(null);
|
|
19405
|
+
const handle = useMemo28(() => createPanoViewerHandle(ref), []);
|
|
19406
|
+
return useMemo28(() => ({ ref, ...handle }), [handle]);
|
|
19407
|
+
}
|
|
19408
|
+
|
|
19409
|
+
// src/video/pano-video.tsx
|
|
19410
|
+
import { useThree as useThree29 } from "@react-three/fiber";
|
|
19411
|
+
import {
|
|
19412
|
+
useCallback as useCallback12,
|
|
19413
|
+
useContext as useContext28,
|
|
19414
|
+
useEffect as useEffect42,
|
|
19415
|
+
useLayoutEffect as useLayoutEffect10,
|
|
19416
|
+
useMemo as useMemo29,
|
|
19417
|
+
useRef as useRef42,
|
|
19418
|
+
useState as useState26
|
|
19419
|
+
} from "react";
|
|
19420
|
+
import {
|
|
19421
|
+
BackSide as BackSide4,
|
|
19422
|
+
LinearFilter as LinearFilter11,
|
|
19423
|
+
LinearMipmapLinearFilter as LinearMipmapLinearFilter7,
|
|
19424
|
+
MathUtils as MathUtils13,
|
|
19425
|
+
RepeatWrapping as RepeatWrapping2,
|
|
19426
|
+
SRGBColorSpace as SRGBColorSpace10,
|
|
19427
|
+
TextureLoader as TextureLoader8,
|
|
19428
|
+
VideoTexture as VideoTexture2
|
|
19429
|
+
} from "three";
|
|
19430
|
+
import { Fragment as Fragment7, jsx as jsx42, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
19431
|
+
var EMPTY_TRACKS = [];
|
|
19432
|
+
var EMPTY_RATES = DEFAULT_PANO_VIDEO_PLAYBACK_RATES;
|
|
19433
|
+
function createEmptySnapshot() {
|
|
19434
|
+
return {
|
|
19435
|
+
ready: false,
|
|
19436
|
+
playing: false,
|
|
19437
|
+
blocked: false,
|
|
19438
|
+
ended: false,
|
|
19439
|
+
currentTime: 0,
|
|
19440
|
+
duration: 0,
|
|
19441
|
+
volume: 1,
|
|
19442
|
+
volumeAdjustable: true,
|
|
19443
|
+
muted: true,
|
|
19444
|
+
playbackRate: 1,
|
|
19445
|
+
variantId: "default",
|
|
19446
|
+
variants: [],
|
|
19447
|
+
trackId: null,
|
|
19448
|
+
tracks: [],
|
|
19449
|
+
captionText: "",
|
|
19450
|
+
captionAppearance: DEFAULT_PANO_VIDEO_CAPTION_APPEARANCE,
|
|
19451
|
+
captionsEnabled: true,
|
|
19452
|
+
playbackRates: EMPTY_RATES,
|
|
19453
|
+
playbackState: "paused"
|
|
19454
|
+
};
|
|
19455
|
+
}
|
|
19456
|
+
function playbackStateFromVideo(video, blocked) {
|
|
19457
|
+
if (blocked) {
|
|
19458
|
+
return "blocked";
|
|
19459
|
+
}
|
|
19460
|
+
if (video.ended) {
|
|
19461
|
+
return "ended";
|
|
19462
|
+
}
|
|
19463
|
+
if (!video.paused) {
|
|
19464
|
+
return "playing";
|
|
19465
|
+
}
|
|
19466
|
+
return "paused";
|
|
19467
|
+
}
|
|
19468
|
+
function collectCaptionText(track) {
|
|
19469
|
+
if (!track?.activeCues || track.activeCues.length === 0) {
|
|
19470
|
+
return "";
|
|
19471
|
+
}
|
|
19472
|
+
const parts = [];
|
|
19473
|
+
for (let index = 0; index < track.activeCues.length; index += 1) {
|
|
19474
|
+
const cue = track.activeCues[index];
|
|
19475
|
+
if (!cue) {
|
|
19476
|
+
continue;
|
|
19477
|
+
}
|
|
19478
|
+
const text = cueText(cue);
|
|
19479
|
+
if (text) {
|
|
19480
|
+
parts.push(text);
|
|
19481
|
+
}
|
|
19482
|
+
}
|
|
19483
|
+
return parts.join("\n");
|
|
19484
|
+
}
|
|
19485
|
+
function findTextTrack(video, tracks, trackId) {
|
|
19486
|
+
if (!trackId) {
|
|
19487
|
+
return null;
|
|
19488
|
+
}
|
|
19489
|
+
const configured = tracks.find((track) => panoVideoTrackId(track) === trackId);
|
|
19490
|
+
if (!configured) {
|
|
19491
|
+
return null;
|
|
19492
|
+
}
|
|
19493
|
+
const list = video.textTracks;
|
|
19494
|
+
for (let index = 0; index < list.length; index += 1) {
|
|
19495
|
+
const textTrack = list[index];
|
|
19496
|
+
if (!textTrack) {
|
|
19497
|
+
continue;
|
|
19498
|
+
}
|
|
19499
|
+
if (textTrack.language === configured.srcLang || textTrack.label === configured.label) {
|
|
19500
|
+
return textTrack;
|
|
19501
|
+
}
|
|
19502
|
+
}
|
|
19503
|
+
return list.length > 0 ? list[0] : null;
|
|
18596
19504
|
}
|
|
18597
19505
|
function applyEquirectangularLayout(texture, anisotropy) {
|
|
18598
19506
|
texture.colorSpace = SRGBColorSpace10;
|
|
@@ -18604,10 +19512,10 @@ function applyEquirectangularLayout(texture, anisotropy) {
|
|
|
18604
19512
|
}
|
|
18605
19513
|
function usePosterTexture2(poster, onError) {
|
|
18606
19514
|
const gl = useThree29((state) => state.gl);
|
|
18607
|
-
const [texture, setTexture] =
|
|
18608
|
-
const onErrorRef =
|
|
19515
|
+
const [texture, setTexture] = useState26(null);
|
|
19516
|
+
const onErrorRef = useRef42(onError);
|
|
18609
19517
|
onErrorRef.current = onError;
|
|
18610
|
-
|
|
19518
|
+
useEffect42(() => {
|
|
18611
19519
|
let active = true;
|
|
18612
19520
|
setTexture(null);
|
|
18613
19521
|
if (!poster) {
|
|
@@ -18644,7 +19552,7 @@ function usePosterTexture2(poster, onError) {
|
|
|
18644
19552
|
active = false;
|
|
18645
19553
|
};
|
|
18646
19554
|
}, [gl, poster]);
|
|
18647
|
-
|
|
19555
|
+
useEffect42(
|
|
18648
19556
|
() => () => {
|
|
18649
19557
|
texture?.dispose();
|
|
18650
19558
|
},
|
|
@@ -18704,36 +19612,36 @@ function PanoVideo({
|
|
|
18704
19612
|
usePanoMediaActivationIntent(autoPlay);
|
|
18705
19613
|
const gl = useThree29((state) => state.gl);
|
|
18706
19614
|
const host = useContext28(PanoVideoHostContext);
|
|
18707
|
-
const variants =
|
|
19615
|
+
const variants = useMemo29(
|
|
18708
19616
|
() => resolvePanoVideoVariants({ poster, src, variants: variantsProp }),
|
|
18709
19617
|
[poster, src, variantsProp]
|
|
18710
19618
|
);
|
|
18711
19619
|
const tracks = tracksProp ?? EMPTY_TRACKS;
|
|
18712
19620
|
const captionsEnabled = captions !== false;
|
|
18713
|
-
const captionAppearance =
|
|
19621
|
+
const captionAppearance = useMemo29(
|
|
18714
19622
|
() => resolvePanoVideoCaptionAppearance(
|
|
18715
19623
|
typeof captions === "object" ? captions : void 0
|
|
18716
19624
|
),
|
|
18717
19625
|
[captions]
|
|
18718
19626
|
);
|
|
18719
|
-
const [variantId, setVariantId] =
|
|
19627
|
+
const [variantId, setVariantId] = useState26(
|
|
18720
19628
|
() => resolvePanoVideoVariantId(variants, defaultVariantId)
|
|
18721
19629
|
);
|
|
18722
|
-
const [trackId, setTrackId] =
|
|
19630
|
+
const [trackId, setTrackId] = useState26(
|
|
18723
19631
|
() => captionsEnabled ? defaultPanoVideoTrackId(tracks) : null
|
|
18724
19632
|
);
|
|
18725
|
-
const [ready, setReady] =
|
|
18726
|
-
const autoPlayRef =
|
|
19633
|
+
const [ready, setReady] = useState26(false);
|
|
19634
|
+
const autoPlayRef = useRef42(autoPlay);
|
|
18727
19635
|
autoPlayRef.current = autoPlay;
|
|
18728
|
-
const blockedRef =
|
|
18729
|
-
const captionAppearanceOverrideRef =
|
|
19636
|
+
const blockedRef = useRef42(false);
|
|
19637
|
+
const captionAppearanceOverrideRef = useRef42(
|
|
18730
19638
|
{}
|
|
18731
19639
|
);
|
|
18732
|
-
const mediaCapabilitiesRef =
|
|
19640
|
+
const mediaCapabilitiesRef = useRef42({
|
|
18733
19641
|
volumeAdjustable: true,
|
|
18734
19642
|
playbackRates: [...DEFAULT_PANO_VIDEO_PLAYBACK_RATES]
|
|
18735
19643
|
});
|
|
18736
|
-
const storeRef =
|
|
19644
|
+
const storeRef = useRef42(null);
|
|
18737
19645
|
if (!storeRef.current) {
|
|
18738
19646
|
storeRef.current = {
|
|
18739
19647
|
snapshot: {
|
|
@@ -18743,7 +19651,7 @@ function PanoVideo({
|
|
|
18743
19651
|
listeners: /* @__PURE__ */ new Set()
|
|
18744
19652
|
};
|
|
18745
19653
|
}
|
|
18746
|
-
const actionsRef =
|
|
19654
|
+
const actionsRef = useRef42({
|
|
18747
19655
|
play: async () => {
|
|
18748
19656
|
},
|
|
18749
19657
|
pause: () => {
|
|
@@ -18763,19 +19671,19 @@ function PanoVideo({
|
|
|
18763
19671
|
setCaptionAppearance: (_appearance) => {
|
|
18764
19672
|
}
|
|
18765
19673
|
});
|
|
18766
|
-
const onLoadRef =
|
|
18767
|
-
const onErrorRef =
|
|
18768
|
-
const onPlaybackStateChangeRef =
|
|
18769
|
-
const onEndedRef =
|
|
18770
|
-
const onVariantChangeRef =
|
|
18771
|
-
const onTrackChangeRef =
|
|
19674
|
+
const onLoadRef = useRef42(onLoad);
|
|
19675
|
+
const onErrorRef = useRef42(onError);
|
|
19676
|
+
const onPlaybackStateChangeRef = useRef42(onPlaybackStateChange);
|
|
19677
|
+
const onEndedRef = useRef42(onEnded);
|
|
19678
|
+
const onVariantChangeRef = useRef42(onVariantChange);
|
|
19679
|
+
const onTrackChangeRef = useRef42(onTrackChange);
|
|
18772
19680
|
onLoadRef.current = onLoad;
|
|
18773
19681
|
onErrorRef.current = onError;
|
|
18774
19682
|
onPlaybackStateChangeRef.current = onPlaybackStateChange;
|
|
18775
19683
|
onEndedRef.current = onEnded;
|
|
18776
19684
|
onVariantChangeRef.current = onVariantChange;
|
|
18777
19685
|
onTrackChangeRef.current = onTrackChange;
|
|
18778
|
-
const resource =
|
|
19686
|
+
const resource = useMemo29(() => {
|
|
18779
19687
|
if (typeof document === "undefined") {
|
|
18780
19688
|
return null;
|
|
18781
19689
|
}
|
|
@@ -18790,7 +19698,7 @@ function PanoVideo({
|
|
|
18790
19698
|
texture.generateMipmaps = false;
|
|
18791
19699
|
return { video, texture };
|
|
18792
19700
|
}, []);
|
|
18793
|
-
|
|
19701
|
+
useEffect42(
|
|
18794
19702
|
() => () => {
|
|
18795
19703
|
if (!resource) {
|
|
18796
19704
|
return;
|
|
@@ -18805,13 +19713,13 @@ function PanoVideo({
|
|
|
18805
19713
|
},
|
|
18806
19714
|
[resource]
|
|
18807
19715
|
);
|
|
18808
|
-
|
|
19716
|
+
useEffect42(() => {
|
|
18809
19717
|
if (!resource) {
|
|
18810
19718
|
return;
|
|
18811
19719
|
}
|
|
18812
19720
|
resource.texture.anisotropy = gl.capabilities.getMaxAnisotropy();
|
|
18813
19721
|
}, [gl, resource]);
|
|
18814
|
-
|
|
19722
|
+
useLayoutEffect10(() => {
|
|
18815
19723
|
if (!resource) {
|
|
18816
19724
|
return;
|
|
18817
19725
|
}
|
|
@@ -18827,7 +19735,7 @@ function PanoVideo({
|
|
|
18827
19735
|
activeVariant?.poster ?? poster,
|
|
18828
19736
|
onError
|
|
18829
19737
|
);
|
|
18830
|
-
const emitSnapshot =
|
|
19738
|
+
const emitSnapshot = useCallback12(
|
|
18831
19739
|
(patch) => {
|
|
18832
19740
|
const store = storeRef.current;
|
|
18833
19741
|
const video = resource?.video;
|
|
@@ -18879,7 +19787,7 @@ function PanoVideo({
|
|
|
18879
19787
|
captionAppearance
|
|
18880
19788
|
]
|
|
18881
19789
|
);
|
|
18882
|
-
const controller =
|
|
19790
|
+
const controller = useMemo29(
|
|
18883
19791
|
() => ({
|
|
18884
19792
|
subscribe: (onStoreChange) => {
|
|
18885
19793
|
const store = storeRef.current;
|
|
@@ -18931,7 +19839,7 @@ function PanoVideo({
|
|
|
18931
19839
|
}),
|
|
18932
19840
|
[]
|
|
18933
19841
|
);
|
|
18934
|
-
|
|
19842
|
+
useLayoutEffect10(() => {
|
|
18935
19843
|
if (!host || variants.length === 0) {
|
|
18936
19844
|
return;
|
|
18937
19845
|
}
|
|
@@ -18944,7 +19852,7 @@ function PanoVideo({
|
|
|
18944
19852
|
notifyPanoVideoHost(host);
|
|
18945
19853
|
};
|
|
18946
19854
|
}, [captionsEnabled, controller, controls, host, variants.length]);
|
|
18947
|
-
|
|
19855
|
+
useLayoutEffect10(() => {
|
|
18948
19856
|
if (!resource) {
|
|
18949
19857
|
return;
|
|
18950
19858
|
}
|
|
@@ -18957,10 +19865,10 @@ function PanoVideo({
|
|
|
18957
19865
|
};
|
|
18958
19866
|
emitSnapshot();
|
|
18959
19867
|
}, [emitSnapshot, playbackRates, resource]);
|
|
18960
|
-
|
|
19868
|
+
useEffect42(() => {
|
|
18961
19869
|
emitSnapshot();
|
|
18962
19870
|
}, [emitSnapshot]);
|
|
18963
|
-
|
|
19871
|
+
useEffect42(() => {
|
|
18964
19872
|
if (!resource || !activeVariant || !mediaKey) {
|
|
18965
19873
|
return;
|
|
18966
19874
|
}
|
|
@@ -19017,7 +19925,7 @@ function PanoVideo({
|
|
|
19017
19925
|
video.removeEventListener("error", handleError);
|
|
19018
19926
|
};
|
|
19019
19927
|
}, [mediaKey, resource]);
|
|
19020
|
-
|
|
19928
|
+
useEffect42(() => {
|
|
19021
19929
|
if (!resource) {
|
|
19022
19930
|
return;
|
|
19023
19931
|
}
|
|
@@ -19109,137 +20017,1306 @@ function PanoVideo({
|
|
|
19109
20017
|
}
|
|
19110
20018
|
};
|
|
19111
20019
|
}, [emitSnapshot, resource]);
|
|
19112
|
-
|
|
20020
|
+
useLayoutEffect10(() => {
|
|
20021
|
+
if (!resource) {
|
|
20022
|
+
return;
|
|
20023
|
+
}
|
|
20024
|
+
const { video } = resource;
|
|
20025
|
+
video.loop = loop;
|
|
20026
|
+
video.muted = muted;
|
|
20027
|
+
if (mediaCapabilitiesRef.current.volumeAdjustable) {
|
|
20028
|
+
video.volume = clampVolume(volume);
|
|
20029
|
+
}
|
|
20030
|
+
applyInlineVideoPlayback(video, playsInline);
|
|
20031
|
+
video.preload = preload;
|
|
20032
|
+
}, [loop, muted, playsInline, preload, resource, volume]);
|
|
20033
|
+
useEffect42(() => {
|
|
20034
|
+
if (!resource) {
|
|
20035
|
+
return;
|
|
20036
|
+
}
|
|
20037
|
+
const { video } = resource;
|
|
20038
|
+
let active = true;
|
|
20039
|
+
const handleTimeUpdate = () => {
|
|
20040
|
+
if (active) {
|
|
20041
|
+
emitSnapshot();
|
|
20042
|
+
}
|
|
20043
|
+
};
|
|
20044
|
+
const handlePlay = () => {
|
|
20045
|
+
blockedRef.current = false;
|
|
20046
|
+
onPlaybackStateChangeRef.current?.("playing");
|
|
20047
|
+
emitSnapshot();
|
|
20048
|
+
};
|
|
20049
|
+
const handlePause = () => {
|
|
20050
|
+
onPlaybackStateChangeRef.current?.(
|
|
20051
|
+
video.ended ? "ended" : "paused"
|
|
20052
|
+
);
|
|
20053
|
+
emitSnapshot();
|
|
20054
|
+
};
|
|
20055
|
+
const handleEnded = () => {
|
|
20056
|
+
onPlaybackStateChangeRef.current?.("ended");
|
|
20057
|
+
onEndedRef.current?.();
|
|
20058
|
+
emitSnapshot();
|
|
20059
|
+
};
|
|
20060
|
+
const handleVolume = () => {
|
|
20061
|
+
emitSnapshot();
|
|
20062
|
+
};
|
|
20063
|
+
video.addEventListener("timeupdate", handleTimeUpdate);
|
|
20064
|
+
video.addEventListener("durationchange", handleTimeUpdate);
|
|
20065
|
+
video.addEventListener("progress", handleTimeUpdate);
|
|
20066
|
+
video.addEventListener("play", handlePlay);
|
|
20067
|
+
video.addEventListener("pause", handlePause);
|
|
20068
|
+
video.addEventListener("ended", handleEnded);
|
|
20069
|
+
video.addEventListener("volumechange", handleVolume);
|
|
20070
|
+
video.addEventListener("ratechange", handleVolume);
|
|
20071
|
+
return () => {
|
|
20072
|
+
active = false;
|
|
20073
|
+
video.removeEventListener("timeupdate", handleTimeUpdate);
|
|
20074
|
+
video.removeEventListener("durationchange", handleTimeUpdate);
|
|
20075
|
+
video.removeEventListener("progress", handleTimeUpdate);
|
|
20076
|
+
video.removeEventListener("play", handlePlay);
|
|
20077
|
+
video.removeEventListener("pause", handlePause);
|
|
20078
|
+
video.removeEventListener("ended", handleEnded);
|
|
20079
|
+
video.removeEventListener("volumechange", handleVolume);
|
|
20080
|
+
video.removeEventListener("ratechange", handleVolume);
|
|
20081
|
+
};
|
|
20082
|
+
}, [emitSnapshot, resource]);
|
|
20083
|
+
useEffect42(() => {
|
|
19113
20084
|
if (!resource) {
|
|
19114
20085
|
return;
|
|
19115
20086
|
}
|
|
19116
|
-
const { video } = resource;
|
|
19117
|
-
|
|
19118
|
-
|
|
19119
|
-
|
|
19120
|
-
|
|
19121
|
-
|
|
19122
|
-
|
|
19123
|
-
|
|
19124
|
-
|
|
19125
|
-
|
|
19126
|
-
|
|
20087
|
+
const { video } = resource;
|
|
20088
|
+
const applyModes = () => {
|
|
20089
|
+
const selected2 = findTextTrack(video, tracks, trackId);
|
|
20090
|
+
const list = video.textTracks;
|
|
20091
|
+
for (let index = 0; index < list.length; index += 1) {
|
|
20092
|
+
const textTrack = list[index];
|
|
20093
|
+
if (!textTrack) {
|
|
20094
|
+
continue;
|
|
20095
|
+
}
|
|
20096
|
+
textTrack.mode = textTrack === selected2 ? "hidden" : "disabled";
|
|
20097
|
+
}
|
|
20098
|
+
emitSnapshot({ captionText: collectCaptionText(selected2) });
|
|
20099
|
+
};
|
|
20100
|
+
applyModes();
|
|
20101
|
+
const selected = findTextTrack(video, tracks, trackId);
|
|
20102
|
+
const handleCueChange = () => {
|
|
20103
|
+
emitSnapshot({
|
|
20104
|
+
captionText: collectCaptionText(
|
|
20105
|
+
findTextTrack(video, tracks, trackId)
|
|
20106
|
+
)
|
|
20107
|
+
});
|
|
20108
|
+
};
|
|
20109
|
+
selected?.addEventListener("cuechange", handleCueChange);
|
|
20110
|
+
video.textTracks.addEventListener("addtrack", applyModes);
|
|
20111
|
+
video.textTracks.addEventListener("change", applyModes);
|
|
20112
|
+
return () => {
|
|
20113
|
+
selected?.removeEventListener("cuechange", handleCueChange);
|
|
20114
|
+
video.textTracks.removeEventListener("addtrack", applyModes);
|
|
20115
|
+
video.textTracks.removeEventListener("change", applyModes);
|
|
20116
|
+
};
|
|
20117
|
+
}, [emitSnapshot, resource, trackId, tracks]);
|
|
20118
|
+
useEffect42(() => {
|
|
20119
|
+
if (!captionsEnabled) {
|
|
20120
|
+
setTrackId(null);
|
|
20121
|
+
return;
|
|
20122
|
+
}
|
|
20123
|
+
setTrackId(defaultPanoVideoTrackId(tracks));
|
|
20124
|
+
}, [captionsEnabled, tracks]);
|
|
20125
|
+
const activeTexture = ready && resource ? resource.texture : posterTexture;
|
|
20126
|
+
if (variants.length === 0) {
|
|
20127
|
+
return null;
|
|
20128
|
+
}
|
|
20129
|
+
return /* @__PURE__ */ jsxs23(Fragment7, { children: [
|
|
20130
|
+
activeTexture ? /* @__PURE__ */ jsxs23(
|
|
20131
|
+
"mesh",
|
|
20132
|
+
{
|
|
20133
|
+
rotation: [0, MathUtils13.degToRad(90 + yawOffset2), 0],
|
|
20134
|
+
visible,
|
|
20135
|
+
children: [
|
|
20136
|
+
/* @__PURE__ */ jsx42("sphereGeometry", { args: [DEFAULT_PANORAMA_RADIUS, 128, 64] }),
|
|
20137
|
+
/* @__PURE__ */ jsx42(
|
|
20138
|
+
PanoBasicMaterial,
|
|
20139
|
+
{
|
|
20140
|
+
map: activeTexture,
|
|
20141
|
+
side: BackSide4,
|
|
20142
|
+
toneMapped: false
|
|
20143
|
+
}
|
|
20144
|
+
)
|
|
20145
|
+
]
|
|
20146
|
+
}
|
|
20147
|
+
) : null,
|
|
20148
|
+
captionsEnabled ? /* @__PURE__ */ jsx42(PanoVideoCaptions, { appearance: typeof captions === "object" ? captions : void 0 }) : null
|
|
20149
|
+
] });
|
|
20150
|
+
}
|
|
20151
|
+
|
|
20152
|
+
// src/map/pano-map.tsx
|
|
20153
|
+
import {
|
|
20154
|
+
useCallback as useCallback13,
|
|
20155
|
+
useEffect as useEffect44,
|
|
20156
|
+
useId as useId4,
|
|
20157
|
+
useLayoutEffect as useLayoutEffect11,
|
|
20158
|
+
useMemo as useMemo30,
|
|
20159
|
+
useRef as useRef44,
|
|
20160
|
+
useState as useState28
|
|
20161
|
+
} from "react";
|
|
20162
|
+
|
|
20163
|
+
// src/map/geometry.ts
|
|
20164
|
+
function sectorRotation(heading, yaw) {
|
|
20165
|
+
return heading + yaw;
|
|
20166
|
+
}
|
|
20167
|
+
function horizontalFov(verticalFov, aspect) {
|
|
20168
|
+
if (!(aspect > 0) || !(verticalFov > 0) || verticalFov >= 180) return verticalFov;
|
|
20169
|
+
const verticalRad = verticalFov * Math.PI / 180;
|
|
20170
|
+
const horizontalRad = 2 * Math.atan(Math.tan(verticalRad / 2) * aspect);
|
|
20171
|
+
return horizontalRad * 180 / Math.PI;
|
|
20172
|
+
}
|
|
20173
|
+
function containRect(containerWidth, containerHeight, imageWidth, imageHeight) {
|
|
20174
|
+
if (!(containerWidth > 0) || !(containerHeight > 0) || !(imageWidth > 0) || !(imageHeight > 0)) {
|
|
20175
|
+
return { left: 0, top: 0, width: 0, height: 0 };
|
|
20176
|
+
}
|
|
20177
|
+
const scale = Math.min(
|
|
20178
|
+
containerWidth / imageWidth,
|
|
20179
|
+
containerHeight / imageHeight
|
|
20180
|
+
);
|
|
20181
|
+
const width = imageWidth * scale;
|
|
20182
|
+
const height = imageHeight * scale;
|
|
20183
|
+
return {
|
|
20184
|
+
left: (containerWidth - width) / 2,
|
|
20185
|
+
top: (containerHeight - height) / 2,
|
|
20186
|
+
width,
|
|
20187
|
+
height
|
|
20188
|
+
};
|
|
20189
|
+
}
|
|
20190
|
+
function imagePointToContainer(point, viewport) {
|
|
20191
|
+
const { fit, scale, panX, panY } = viewport;
|
|
20192
|
+
return {
|
|
20193
|
+
x: fit.left + panX + point.x * fit.width * scale,
|
|
20194
|
+
y: fit.top + panY + point.y * fit.height * scale
|
|
20195
|
+
};
|
|
20196
|
+
}
|
|
20197
|
+
function containerPointToImage(point, viewport) {
|
|
20198
|
+
const { fit, scale, panX, panY } = viewport;
|
|
20199
|
+
const width = fit.width * scale;
|
|
20200
|
+
const height = fit.height * scale;
|
|
20201
|
+
if (!(width > 0) || !(height > 0)) return null;
|
|
20202
|
+
return {
|
|
20203
|
+
x: (point.x - fit.left - panX) / width,
|
|
20204
|
+
y: (point.y - fit.top - panY) / height
|
|
20205
|
+
};
|
|
20206
|
+
}
|
|
20207
|
+
function clamp012(value) {
|
|
20208
|
+
return Math.min(1, Math.max(0, value));
|
|
20209
|
+
}
|
|
20210
|
+
function headingFromVector(dx, dy) {
|
|
20211
|
+
if (dx === 0 && dy === 0) return 0;
|
|
20212
|
+
const radians = Math.atan2(dx, -dy);
|
|
20213
|
+
return radians * 180 / Math.PI;
|
|
20214
|
+
}
|
|
20215
|
+
function normalizeDegrees(value) {
|
|
20216
|
+
const wrapped = (value % 360 + 360) % 360;
|
|
20217
|
+
return wrapped > 180 ? wrapped - 360 : wrapped;
|
|
20218
|
+
}
|
|
20219
|
+
|
|
20220
|
+
// src/map/use-map-view.ts
|
|
20221
|
+
import { useContext as useContext29, useEffect as useEffect43, useRef as useRef43, useState as useState27 } from "react";
|
|
20222
|
+
var DEFAULT_VIEW2 = { yaw: 0, pitch: 0, fov: 75 };
|
|
20223
|
+
function useMapView(controlled) {
|
|
20224
|
+
const eventBus = useContext29(PanoEventBusContext);
|
|
20225
|
+
const [view, setView] = useState27(controlled ?? DEFAULT_VIEW2);
|
|
20226
|
+
const controlledRef = useRef43(controlled);
|
|
20227
|
+
controlledRef.current = controlled;
|
|
20228
|
+
useEffect43(() => {
|
|
20229
|
+
if (controlled !== void 0) {
|
|
20230
|
+
setView(controlled);
|
|
20231
|
+
return;
|
|
20232
|
+
}
|
|
20233
|
+
if (!eventBus) return;
|
|
20234
|
+
return eventBus.subscribe("viewchange", (next) => {
|
|
20235
|
+
if (controlledRef.current !== void 0) return;
|
|
20236
|
+
setView(next);
|
|
20237
|
+
});
|
|
20238
|
+
}, [controlled, eventBus]);
|
|
20239
|
+
return controlled ?? view;
|
|
20240
|
+
}
|
|
20241
|
+
|
|
20242
|
+
// src/map/pano-map.tsx
|
|
20243
|
+
import { Fragment as Fragment8, jsx as jsx43, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
20244
|
+
var DEFAULT_ICON_SIZE2 = 28;
|
|
20245
|
+
var DEFAULT_SECTOR_COLOR = "#ffffff";
|
|
20246
|
+
var DEFAULT_SECTOR_OPACITY = 0.55;
|
|
20247
|
+
var MIN_SCALE = 1;
|
|
20248
|
+
var DEFAULT_MAX_SCALE = 8;
|
|
20249
|
+
var ZOOM_STEP = 1.25;
|
|
20250
|
+
function defaultGradientId(prefix) {
|
|
20251
|
+
return `${prefix}-sector-gradient`;
|
|
20252
|
+
}
|
|
20253
|
+
function SectorShape({
|
|
20254
|
+
sector,
|
|
20255
|
+
angle,
|
|
20256
|
+
rotation,
|
|
20257
|
+
gradientId,
|
|
20258
|
+
interactive,
|
|
20259
|
+
onHeadingPointerDown,
|
|
20260
|
+
transformRef
|
|
20261
|
+
}) {
|
|
20262
|
+
const half = angle / 2;
|
|
20263
|
+
const start = (-half - 90) * Math.PI / 180;
|
|
20264
|
+
const end = (half - 90) * Math.PI / 180;
|
|
20265
|
+
const radius = sector.radius;
|
|
20266
|
+
const largeArc = angle > 180 ? 1 : 0;
|
|
20267
|
+
const x1 = Math.cos(start) * radius;
|
|
20268
|
+
const y1 = Math.sin(start) * radius;
|
|
20269
|
+
const x2 = Math.cos(end) * radius;
|
|
20270
|
+
const y2 = Math.sin(end) * radius;
|
|
20271
|
+
const path = `M 0 0 L ${x1} ${y1} A ${radius} ${radius} 0 ${largeArc} 1 ${x2} ${y2} Z`;
|
|
20272
|
+
const opacity = sector.opacity ?? DEFAULT_SECTOR_OPACITY;
|
|
20273
|
+
const color = sector.color ?? DEFAULT_SECTOR_COLOR;
|
|
20274
|
+
const gradient = sector.gradient;
|
|
20275
|
+
const strokeWidth = sector.strokeWidth ?? 0;
|
|
20276
|
+
const strokeColor = sector.strokeColor ?? color;
|
|
20277
|
+
const strokeOpacity = sector.strokeOpacity ?? 1;
|
|
20278
|
+
const gradientStrength = gradient && gradient.stops.length > 0 ? Math.max(
|
|
20279
|
+
...gradient.stops.map((stop) => stop.opacity ?? 1),
|
|
20280
|
+
Number.EPSILON
|
|
20281
|
+
) : 1;
|
|
20282
|
+
return /* @__PURE__ */ jsxs24("g", { ref: transformRef, transform: `rotate(${rotation})`, style: { pointerEvents: "none" }, children: [
|
|
20283
|
+
gradient ? /* @__PURE__ */ jsx43("defs", { children: gradient.type === "radial" ? /* @__PURE__ */ jsx43("radialGradient", { id: gradientId, cx: "0", cy: "0", r: radius, gradientUnits: "userSpaceOnUse", children: gradient.stops.map((stop, index) => /* @__PURE__ */ jsx43(
|
|
20284
|
+
"stop",
|
|
20285
|
+
{
|
|
20286
|
+
offset: `${stop.offset * 100}%`,
|
|
20287
|
+
stopColor: stop.color,
|
|
20288
|
+
stopOpacity: (stop.opacity ?? 1) / gradientStrength
|
|
20289
|
+
},
|
|
20290
|
+
`${stop.offset}:${index}`
|
|
20291
|
+
)) }) : /* @__PURE__ */ jsx43(
|
|
20292
|
+
"linearGradient",
|
|
20293
|
+
{
|
|
20294
|
+
id: gradientId,
|
|
20295
|
+
x1: "0",
|
|
20296
|
+
y1: "0",
|
|
20297
|
+
x2: "0",
|
|
20298
|
+
y2: -radius,
|
|
20299
|
+
gradientUnits: "userSpaceOnUse",
|
|
20300
|
+
children: gradient.stops.map((stop, index) => /* @__PURE__ */ jsx43(
|
|
20301
|
+
"stop",
|
|
20302
|
+
{
|
|
20303
|
+
offset: `${stop.offset * 100}%`,
|
|
20304
|
+
stopColor: stop.color,
|
|
20305
|
+
stopOpacity: (stop.opacity ?? 1) / gradientStrength
|
|
20306
|
+
},
|
|
20307
|
+
`${stop.offset}:${index}`
|
|
20308
|
+
))
|
|
20309
|
+
}
|
|
20310
|
+
) }) : null,
|
|
20311
|
+
/* @__PURE__ */ jsx43(
|
|
20312
|
+
"path",
|
|
20313
|
+
{
|
|
20314
|
+
d: path,
|
|
20315
|
+
fill: gradient ? `url(#${gradientId})` : color,
|
|
20316
|
+
fillOpacity: opacity,
|
|
20317
|
+
stroke: strokeWidth > 0 ? strokeColor : "none",
|
|
20318
|
+
strokeWidth: strokeWidth > 0 ? strokeWidth : void 0,
|
|
20319
|
+
strokeOpacity: strokeWidth > 0 ? strokeOpacity : void 0,
|
|
20320
|
+
vectorEffect: "non-scaling-stroke",
|
|
20321
|
+
style: {
|
|
20322
|
+
pointerEvents: interactive ? "auto" : "none",
|
|
20323
|
+
cursor: interactive ? "grab" : void 0
|
|
20324
|
+
},
|
|
20325
|
+
onPointerDown: interactive ? onHeadingPointerDown : void 0
|
|
20326
|
+
}
|
|
20327
|
+
)
|
|
20328
|
+
] });
|
|
20329
|
+
}
|
|
20330
|
+
function DefaultSpotIcon({ active, size }) {
|
|
20331
|
+
return /* @__PURE__ */ jsxs24(
|
|
20332
|
+
"svg",
|
|
20333
|
+
{
|
|
20334
|
+
width: size,
|
|
20335
|
+
height: size,
|
|
20336
|
+
viewBox: "0 0 32 32",
|
|
20337
|
+
"aria-hidden": "true",
|
|
20338
|
+
style: { display: "block" },
|
|
20339
|
+
children: [
|
|
20340
|
+
/* @__PURE__ */ jsx43(
|
|
20341
|
+
"rect",
|
|
20342
|
+
{
|
|
20343
|
+
x: "3",
|
|
20344
|
+
y: "7",
|
|
20345
|
+
width: "26",
|
|
20346
|
+
height: "18",
|
|
20347
|
+
rx: "5",
|
|
20348
|
+
fill: active ? "rgba(15,23,42,0.92)" : "rgba(15,23,42,0.72)",
|
|
20349
|
+
stroke: "rgba(255,255,255,0.92)",
|
|
20350
|
+
strokeWidth: "2"
|
|
20351
|
+
}
|
|
20352
|
+
),
|
|
20353
|
+
/* @__PURE__ */ jsx43("circle", { cx: "16", cy: "16", r: "5", fill: "none", stroke: "rgba(255,255,255,0.92)", strokeWidth: "2" }),
|
|
20354
|
+
/* @__PURE__ */ jsx43("circle", { cx: "16", cy: "16", r: "2", fill: active ? "#ef4444" : "rgba(255,255,255,0.85)" })
|
|
20355
|
+
]
|
|
20356
|
+
}
|
|
20357
|
+
);
|
|
20358
|
+
}
|
|
20359
|
+
function PanoMap({
|
|
20360
|
+
map,
|
|
20361
|
+
activeSceneId,
|
|
20362
|
+
onSelectScene,
|
|
20363
|
+
view: controlledView,
|
|
20364
|
+
mode = "view",
|
|
20365
|
+
selectedSpotId = null,
|
|
20366
|
+
onSelectedSpotIdChange,
|
|
20367
|
+
onEditChange,
|
|
20368
|
+
placementSceneId,
|
|
20369
|
+
className,
|
|
20370
|
+
style,
|
|
20371
|
+
showZoomControls,
|
|
20372
|
+
showCollapseControl,
|
|
20373
|
+
collapsed: controlledCollapsed,
|
|
20374
|
+
onCollapsedChange,
|
|
20375
|
+
renderChrome,
|
|
20376
|
+
initialScale
|
|
20377
|
+
}) {
|
|
20378
|
+
const reactId = useId4().replace(/:/g, "");
|
|
20379
|
+
const view = useMapView(controlledView);
|
|
20380
|
+
const rootRef = useRef44(null);
|
|
20381
|
+
const containerRef = useRef44(null);
|
|
20382
|
+
const sectorNodeRef = useRef44(null);
|
|
20383
|
+
const yawRef = useRef44(view.yaw);
|
|
20384
|
+
const [size, setSize] = useState28({ width: 0, height: 0 });
|
|
20385
|
+
const [imageSize2, setImageSize] = useState28({ width: 0, height: 0 });
|
|
20386
|
+
const [scale, setScale] = useState28(1);
|
|
20387
|
+
const [pan, setPan] = useState28({ x: 0, y: 0 });
|
|
20388
|
+
const [uncontrolledCollapsed, setUncontrolledCollapsed] = useState28(false);
|
|
20389
|
+
const dragRef = useRef44(null);
|
|
20390
|
+
const panAnimRef = useRef44(null);
|
|
20391
|
+
const panRef = useRef44(pan);
|
|
20392
|
+
const scaleRef = useRef44(scale);
|
|
20393
|
+
const initialViewKeyRef = useRef44(null);
|
|
20394
|
+
const spots = map.spots;
|
|
20395
|
+
const iconSize = map.iconSize ?? DEFAULT_ICON_SIZE2;
|
|
20396
|
+
const interaction = map.interaction ?? "fixed";
|
|
20397
|
+
const gestureZoom = interaction === "zoom";
|
|
20398
|
+
const dragMotion = resolveDragMotion(map.dragMotion);
|
|
20399
|
+
const maxScale = Math.max(MIN_SCALE, map.maxScale ?? DEFAULT_MAX_SCALE);
|
|
20400
|
+
const resolvedInitialScale = Math.min(
|
|
20401
|
+
maxScale,
|
|
20402
|
+
Math.max(
|
|
20403
|
+
MIN_SCALE,
|
|
20404
|
+
Number.isFinite(initialScale) ? initialScale : MIN_SCALE
|
|
20405
|
+
)
|
|
20406
|
+
);
|
|
20407
|
+
const zoomControls = gestureZoom && (showZoomControls ?? map.controls?.zoom === true);
|
|
20408
|
+
const collapseControl = showCollapseControl ?? map.controls?.collapse === true;
|
|
20409
|
+
const collapsed = controlledCollapsed ?? uncontrolledCollapsed;
|
|
20410
|
+
const editing = mode === "edit";
|
|
20411
|
+
const fit = useMemo30(
|
|
20412
|
+
() => containRect(size.width, size.height, imageSize2.width, imageSize2.height),
|
|
20413
|
+
[size, imageSize2]
|
|
20414
|
+
);
|
|
20415
|
+
const viewport = useMemo30(
|
|
20416
|
+
() => ({ fit, scale, panX: pan.x, panY: pan.y }),
|
|
20417
|
+
[fit, scale, pan]
|
|
20418
|
+
);
|
|
20419
|
+
useLayoutEffect11(() => {
|
|
20420
|
+
if (collapsed) return;
|
|
20421
|
+
const node = containerRef.current;
|
|
20422
|
+
if (!node) return;
|
|
20423
|
+
const measure = () => {
|
|
20424
|
+
const width = node.clientWidth;
|
|
20425
|
+
const height = node.clientHeight;
|
|
20426
|
+
if (!(width > 0) || !(height > 0)) return;
|
|
20427
|
+
setSize(
|
|
20428
|
+
(previous) => previous.width === width && previous.height === height ? previous : { width, height }
|
|
20429
|
+
);
|
|
20430
|
+
};
|
|
20431
|
+
measure();
|
|
20432
|
+
const observer = new ResizeObserver(measure);
|
|
20433
|
+
observer.observe(node);
|
|
20434
|
+
return () => observer.disconnect();
|
|
20435
|
+
}, [collapsed]);
|
|
20436
|
+
useEffect44(() => {
|
|
20437
|
+
yawRef.current = view.yaw;
|
|
20438
|
+
const node = sectorNodeRef.current;
|
|
20439
|
+
if (!node) return;
|
|
20440
|
+
const active = spots.find((spot) => spot.sceneId === activeSceneId);
|
|
20441
|
+
if (!active || !map.sector) return;
|
|
20442
|
+
node.setAttribute(
|
|
20443
|
+
"transform",
|
|
20444
|
+
`rotate(${sectorRotation(active.heading, view.yaw)})`
|
|
20445
|
+
);
|
|
20446
|
+
}, [view.yaw, activeSceneId, spots, map.sector]);
|
|
20447
|
+
const setCollapsed = (next) => {
|
|
20448
|
+
onCollapsedChange?.(next);
|
|
20449
|
+
if (controlledCollapsed === void 0) setUncontrolledCollapsed(next);
|
|
20450
|
+
};
|
|
20451
|
+
panRef.current = pan;
|
|
20452
|
+
scaleRef.current = scale;
|
|
20453
|
+
const panBoundsFor = useCallback13(
|
|
20454
|
+
(nextScale) => {
|
|
20455
|
+
const width = fit.width * nextScale;
|
|
20456
|
+
const height = fit.height * nextScale;
|
|
20457
|
+
if (!(fit.width > 0) || !(fit.height > 0)) {
|
|
20458
|
+
return { minX: 0, maxX: 0, minY: 0, maxY: 0 };
|
|
20459
|
+
}
|
|
20460
|
+
return {
|
|
20461
|
+
minX: Math.min(0, size.width - (fit.left + width)),
|
|
20462
|
+
maxX: Math.max(0, -fit.left),
|
|
20463
|
+
minY: Math.min(0, size.height - (fit.top + height)),
|
|
20464
|
+
maxY: Math.max(0, -fit.top)
|
|
20465
|
+
};
|
|
20466
|
+
},
|
|
20467
|
+
[fit, size]
|
|
20468
|
+
);
|
|
20469
|
+
const clampPan = useCallback13(
|
|
20470
|
+
(nextScale, nextPan) => {
|
|
20471
|
+
const bounds = panBoundsFor(nextScale);
|
|
20472
|
+
return {
|
|
20473
|
+
x: Math.min(bounds.maxX, Math.max(bounds.minX, nextPan.x)),
|
|
20474
|
+
y: Math.min(bounds.maxY, Math.max(bounds.minY, nextPan.y))
|
|
20475
|
+
};
|
|
20476
|
+
},
|
|
20477
|
+
[panBoundsFor]
|
|
20478
|
+
);
|
|
20479
|
+
const cancelPanAnimation = useCallback13(() => {
|
|
20480
|
+
const anim = panAnimRef.current;
|
|
20481
|
+
if (anim?.raf != null) {
|
|
20482
|
+
cancelAnimationFrame(anim.raf);
|
|
20483
|
+
}
|
|
20484
|
+
panAnimRef.current = null;
|
|
20485
|
+
}, []);
|
|
20486
|
+
const startPanAnimation = useCallback13(
|
|
20487
|
+
(velocity) => {
|
|
20488
|
+
cancelPanAnimation();
|
|
20489
|
+
const current = panRef.current;
|
|
20490
|
+
const currentScale = scaleRef.current;
|
|
20491
|
+
const bounds = panBoundsFor(currentScale);
|
|
20492
|
+
const needsSpring = dragMotion.bounce && (current.x < bounds.minX || current.x > bounds.maxX || current.y < bounds.minY || current.y > bounds.maxY);
|
|
20493
|
+
const vx = dragMotion.inertia ? velocity.x : 0;
|
|
20494
|
+
const vy = dragMotion.inertia ? velocity.y : 0;
|
|
20495
|
+
if (!needsSpring && Math.abs(vx) < 1 && Math.abs(vy) < 1) {
|
|
20496
|
+
setPan(clampPan(currentScale, current));
|
|
20497
|
+
return;
|
|
20498
|
+
}
|
|
20499
|
+
if (!dragMotion.inertia && !dragMotion.bounce) {
|
|
20500
|
+
setPan(clampPan(currentScale, current));
|
|
20501
|
+
return;
|
|
20502
|
+
}
|
|
20503
|
+
const anim = {
|
|
20504
|
+
raf: null,
|
|
20505
|
+
lastTime: performance.now(),
|
|
20506
|
+
x: { position: current.x, velocity: vx },
|
|
20507
|
+
y: { position: current.y, velocity: vy }
|
|
20508
|
+
};
|
|
20509
|
+
panAnimRef.current = anim;
|
|
20510
|
+
const tick = (now) => {
|
|
20511
|
+
if (panAnimRef.current !== anim) return;
|
|
20512
|
+
const dt = Math.min(0.064, Math.max(0, (now - anim.lastTime) / 1e3));
|
|
20513
|
+
anim.lastTime = now;
|
|
20514
|
+
const liveBounds = panBoundsFor(scaleRef.current);
|
|
20515
|
+
const nextX = integrateAxis(anim.x, dt, {
|
|
20516
|
+
min: liveBounds.minX,
|
|
20517
|
+
max: liveBounds.maxX,
|
|
20518
|
+
inertia: dragMotion.inertia,
|
|
20519
|
+
bounce: dragMotion.bounce
|
|
20520
|
+
});
|
|
20521
|
+
const nextY = integrateAxis(anim.y, dt, {
|
|
20522
|
+
min: liveBounds.minY,
|
|
20523
|
+
max: liveBounds.maxY,
|
|
20524
|
+
inertia: dragMotion.inertia,
|
|
20525
|
+
bounce: dragMotion.bounce
|
|
20526
|
+
});
|
|
20527
|
+
anim.x = { position: nextX.position, velocity: nextX.velocity };
|
|
20528
|
+
anim.y = { position: nextY.position, velocity: nextY.velocity };
|
|
20529
|
+
setPan({ x: nextX.position, y: nextY.position });
|
|
20530
|
+
if (nextX.settled && nextY.settled) {
|
|
20531
|
+
setPan(
|
|
20532
|
+
clampPan(scaleRef.current, {
|
|
20533
|
+
x: nextX.position,
|
|
20534
|
+
y: nextY.position
|
|
20535
|
+
})
|
|
20536
|
+
);
|
|
20537
|
+
panAnimRef.current = null;
|
|
20538
|
+
return;
|
|
20539
|
+
}
|
|
20540
|
+
anim.raf = requestAnimationFrame(tick);
|
|
20541
|
+
};
|
|
20542
|
+
anim.raf = requestAnimationFrame(tick);
|
|
20543
|
+
},
|
|
20544
|
+
[cancelPanAnimation, clampPan, dragMotion.bounce, dragMotion.inertia, panBoundsFor]
|
|
20545
|
+
);
|
|
20546
|
+
useEffect44(() => () => cancelPanAnimation(), [cancelPanAnimation]);
|
|
20547
|
+
useEffect44(() => {
|
|
20548
|
+
if (collapsed) cancelPanAnimation();
|
|
20549
|
+
}, [cancelPanAnimation, collapsed]);
|
|
20550
|
+
useEffect44(() => {
|
|
20551
|
+
if (!gestureZoom) {
|
|
20552
|
+
initialViewKeyRef.current = null;
|
|
20553
|
+
setScale(1);
|
|
20554
|
+
setPan({ x: 0, y: 0 });
|
|
20555
|
+
return;
|
|
20556
|
+
}
|
|
20557
|
+
setScale((previous) => {
|
|
20558
|
+
const next = Math.min(previous, maxScale);
|
|
20559
|
+
if (next !== previous) {
|
|
20560
|
+
setPan((currentPan) => clampPan(next, currentPan));
|
|
20561
|
+
}
|
|
20562
|
+
return next;
|
|
20563
|
+
});
|
|
20564
|
+
}, [clampPan, gestureZoom, maxScale]);
|
|
20565
|
+
useEffect44(() => {
|
|
20566
|
+
if (!gestureZoom) return;
|
|
20567
|
+
if (!(fit.width > 0) || !(fit.height > 0)) return;
|
|
20568
|
+
if (!(size.width > 0) || !(size.height > 0)) return;
|
|
20569
|
+
const key = `${interaction}:${initialScale ?? MIN_SCALE}`;
|
|
20570
|
+
if (initialViewKeyRef.current === key) return;
|
|
20571
|
+
initialViewKeyRef.current = key;
|
|
20572
|
+
const pivot = { x: size.width / 2, y: size.height / 2 };
|
|
20573
|
+
const nextPan = {
|
|
20574
|
+
x: pivot.x - (pivot.x - fit.left) * resolvedInitialScale - fit.left,
|
|
20575
|
+
y: pivot.y - (pivot.y - fit.top) * resolvedInitialScale - fit.top
|
|
20576
|
+
};
|
|
20577
|
+
setScale(resolvedInitialScale);
|
|
20578
|
+
setPan(clampPan(resolvedInitialScale, nextPan));
|
|
20579
|
+
}, [
|
|
20580
|
+
clampPan,
|
|
20581
|
+
fit.height,
|
|
20582
|
+
fit.left,
|
|
20583
|
+
fit.top,
|
|
20584
|
+
fit.width,
|
|
20585
|
+
gestureZoom,
|
|
20586
|
+
initialScale,
|
|
20587
|
+
interaction,
|
|
20588
|
+
resolvedInitialScale,
|
|
20589
|
+
size.height,
|
|
20590
|
+
size.width
|
|
20591
|
+
]);
|
|
20592
|
+
const applyZoom = useCallback13(
|
|
20593
|
+
(nextScale, origin) => {
|
|
20594
|
+
if (!gestureZoom) return;
|
|
20595
|
+
cancelPanAnimation();
|
|
20596
|
+
const clamped = Math.min(maxScale, Math.max(MIN_SCALE, nextScale));
|
|
20597
|
+
const pivot = origin ?? { x: size.width / 2, y: size.height / 2 };
|
|
20598
|
+
const ratio = clamped / scale;
|
|
20599
|
+
const nextPan = {
|
|
20600
|
+
x: pivot.x - (pivot.x - (fit.left + pan.x)) * ratio - fit.left,
|
|
20601
|
+
y: pivot.y - (pivot.y - (fit.top + pan.y)) * ratio - fit.top
|
|
20602
|
+
};
|
|
20603
|
+
setScale(clamped);
|
|
20604
|
+
setPan(clampPan(clamped, nextPan));
|
|
20605
|
+
},
|
|
20606
|
+
[
|
|
20607
|
+
cancelPanAnimation,
|
|
20608
|
+
clampPan,
|
|
20609
|
+
fit.left,
|
|
20610
|
+
fit.top,
|
|
20611
|
+
gestureZoom,
|
|
20612
|
+
maxScale,
|
|
20613
|
+
pan.x,
|
|
20614
|
+
pan.y,
|
|
20615
|
+
scale,
|
|
20616
|
+
size.height,
|
|
20617
|
+
size.width
|
|
20618
|
+
]
|
|
20619
|
+
);
|
|
20620
|
+
useEffect44(() => {
|
|
20621
|
+
const root = rootRef.current;
|
|
20622
|
+
if (!root) return;
|
|
20623
|
+
const onWheel = (event) => {
|
|
20624
|
+
event.preventDefault();
|
|
20625
|
+
event.stopPropagation();
|
|
20626
|
+
if (!gestureZoom || collapsed) return;
|
|
20627
|
+
const viewport2 = containerRef.current;
|
|
20628
|
+
if (!viewport2) return;
|
|
20629
|
+
const rect = viewport2.getBoundingClientRect();
|
|
20630
|
+
const factor = event.deltaY < 0 ? ZOOM_STEP : 1 / ZOOM_STEP;
|
|
20631
|
+
applyZoom(scale * factor, {
|
|
20632
|
+
x: event.clientX - rect.left,
|
|
20633
|
+
y: event.clientY - rect.top
|
|
20634
|
+
});
|
|
20635
|
+
};
|
|
20636
|
+
root.addEventListener("wheel", onWheel, { passive: false });
|
|
20637
|
+
return () => root.removeEventListener("wheel", onWheel);
|
|
20638
|
+
}, [applyZoom, collapsed, gestureZoom, scale]);
|
|
20639
|
+
const updateSpots = (next, selected) => {
|
|
20640
|
+
onEditChange?.({
|
|
20641
|
+
spots: next,
|
|
20642
|
+
...selected === void 0 ? {} : { selectedSpotId: selected }
|
|
20643
|
+
});
|
|
20644
|
+
};
|
|
20645
|
+
const localPoint = (event) => {
|
|
20646
|
+
const rect = containerRef.current?.getBoundingClientRect();
|
|
20647
|
+
if (!rect) return null;
|
|
20648
|
+
return { x: event.clientX - rect.left, y: event.clientY - rect.top };
|
|
20649
|
+
};
|
|
20650
|
+
const onPointerDownSurface = (event) => {
|
|
20651
|
+
if (collapsed) return;
|
|
20652
|
+
if (event.button !== 0) return;
|
|
20653
|
+
const point = localPoint(event);
|
|
20654
|
+
if (!point) return;
|
|
20655
|
+
if (editing && placementSceneId) {
|
|
20656
|
+
const image = containerPointToImage(point, viewport);
|
|
20657
|
+
if (!image) return;
|
|
20658
|
+
const spot = {
|
|
20659
|
+
id: crypto.randomUUID(),
|
|
20660
|
+
sceneId: placementSceneId,
|
|
20661
|
+
x: clamp012(image.x),
|
|
20662
|
+
y: clamp012(image.y),
|
|
20663
|
+
heading: normalizeDegrees(-view.yaw)
|
|
20664
|
+
};
|
|
20665
|
+
updateSpots([...spots, spot], spot.id);
|
|
20666
|
+
onSelectedSpotIdChange?.(spot.id);
|
|
20667
|
+
event.preventDefault();
|
|
20668
|
+
event.stopPropagation();
|
|
20669
|
+
return;
|
|
20670
|
+
}
|
|
20671
|
+
if (gestureZoom && scale > 1) {
|
|
20672
|
+
cancelPanAnimation();
|
|
20673
|
+
const now = performance.now();
|
|
20674
|
+
dragRef.current = {
|
|
20675
|
+
kind: "pan",
|
|
20676
|
+
pointerId: event.pointerId,
|
|
20677
|
+
originX: point.x,
|
|
20678
|
+
originY: point.y,
|
|
20679
|
+
panX: pan.x,
|
|
20680
|
+
panY: pan.y,
|
|
20681
|
+
samplesX: [{ t: now, position: pan.x }],
|
|
20682
|
+
samplesY: [{ t: now, position: pan.y }]
|
|
20683
|
+
};
|
|
20684
|
+
try {
|
|
20685
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
20686
|
+
} catch {
|
|
20687
|
+
}
|
|
20688
|
+
event.preventDefault();
|
|
20689
|
+
event.stopPropagation();
|
|
20690
|
+
}
|
|
20691
|
+
};
|
|
20692
|
+
const onPointerMoveSurface = (event) => {
|
|
20693
|
+
const drag = dragRef.current;
|
|
20694
|
+
if (!drag || drag.pointerId !== event.pointerId) return;
|
|
20695
|
+
const point = localPoint(event);
|
|
20696
|
+
if (!point) return;
|
|
20697
|
+
if (drag.kind === "pan") {
|
|
20698
|
+
const rawX = drag.panX + (point.x - drag.originX);
|
|
20699
|
+
const rawY = drag.panY + (point.y - drag.originY);
|
|
20700
|
+
const bounds = panBoundsFor(scale);
|
|
20701
|
+
const now = performance.now();
|
|
20702
|
+
drag.samplesX = pushSample(drag.samplesX, { t: now, position: rawX });
|
|
20703
|
+
drag.samplesY = pushSample(drag.samplesY, { t: now, position: rawY });
|
|
20704
|
+
setPan({
|
|
20705
|
+
x: constrainDragPosition(
|
|
20706
|
+
rawX,
|
|
20707
|
+
bounds.minX,
|
|
20708
|
+
bounds.maxX,
|
|
20709
|
+
size.width,
|
|
20710
|
+
dragMotion.bounce
|
|
20711
|
+
),
|
|
20712
|
+
y: constrainDragPosition(
|
|
20713
|
+
rawY,
|
|
20714
|
+
bounds.minY,
|
|
20715
|
+
bounds.maxY,
|
|
20716
|
+
size.height,
|
|
20717
|
+
dragMotion.bounce
|
|
20718
|
+
)
|
|
20719
|
+
});
|
|
20720
|
+
event.preventDefault();
|
|
20721
|
+
event.stopPropagation();
|
|
20722
|
+
return;
|
|
20723
|
+
}
|
|
20724
|
+
if (drag.kind === "spot") {
|
|
20725
|
+
const image = containerPointToImage(
|
|
20726
|
+
{ x: point.x - drag.offsetX, y: point.y - drag.offsetY },
|
|
20727
|
+
viewport
|
|
20728
|
+
);
|
|
20729
|
+
if (!image) return;
|
|
20730
|
+
updateSpots(
|
|
20731
|
+
spots.map(
|
|
20732
|
+
(spot2) => spot2.id === drag.spotId ? { ...spot2, x: clamp012(image.x), y: clamp012(image.y) } : spot2
|
|
20733
|
+
)
|
|
20734
|
+
);
|
|
20735
|
+
event.preventDefault();
|
|
20736
|
+
event.stopPropagation();
|
|
19127
20737
|
return;
|
|
19128
20738
|
}
|
|
19129
|
-
const
|
|
19130
|
-
|
|
19131
|
-
const
|
|
19132
|
-
|
|
19133
|
-
|
|
19134
|
-
|
|
19135
|
-
|
|
19136
|
-
|
|
19137
|
-
|
|
19138
|
-
|
|
19139
|
-
|
|
19140
|
-
|
|
19141
|
-
|
|
19142
|
-
|
|
19143
|
-
|
|
20739
|
+
const spot = spots.find((entry) => entry.id === drag.spotId);
|
|
20740
|
+
if (!spot) return;
|
|
20741
|
+
const center = imagePointToContainer(spot, viewport);
|
|
20742
|
+
const heading = normalizeDegrees(
|
|
20743
|
+
headingFromVector(point.x - center.x, point.y - center.y) - view.yaw
|
|
20744
|
+
);
|
|
20745
|
+
updateSpots(
|
|
20746
|
+
spots.map(
|
|
20747
|
+
(entry) => entry.id === drag.spotId ? { ...entry, heading } : entry
|
|
20748
|
+
)
|
|
20749
|
+
);
|
|
20750
|
+
if (sectorNodeRef.current && spot.sceneId === activeSceneId) {
|
|
20751
|
+
sectorNodeRef.current.setAttribute(
|
|
20752
|
+
"transform",
|
|
20753
|
+
`rotate(${sectorRotation(heading, view.yaw)})`
|
|
19144
20754
|
);
|
|
19145
|
-
|
|
19146
|
-
|
|
19147
|
-
|
|
19148
|
-
|
|
19149
|
-
|
|
19150
|
-
|
|
19151
|
-
|
|
19152
|
-
|
|
19153
|
-
|
|
19154
|
-
|
|
19155
|
-
|
|
19156
|
-
|
|
19157
|
-
|
|
19158
|
-
|
|
19159
|
-
|
|
19160
|
-
|
|
19161
|
-
|
|
19162
|
-
video.addEventListener("ratechange", handleVolume);
|
|
19163
|
-
return () => {
|
|
19164
|
-
active = false;
|
|
19165
|
-
video.removeEventListener("timeupdate", handleTimeUpdate);
|
|
19166
|
-
video.removeEventListener("durationchange", handleTimeUpdate);
|
|
19167
|
-
video.removeEventListener("progress", handleTimeUpdate);
|
|
19168
|
-
video.removeEventListener("play", handlePlay);
|
|
19169
|
-
video.removeEventListener("pause", handlePause);
|
|
19170
|
-
video.removeEventListener("ended", handleEnded);
|
|
19171
|
-
video.removeEventListener("volumechange", handleVolume);
|
|
19172
|
-
video.removeEventListener("ratechange", handleVolume);
|
|
19173
|
-
};
|
|
19174
|
-
}, [emitSnapshot, resource]);
|
|
19175
|
-
useEffect41(() => {
|
|
19176
|
-
if (!resource) {
|
|
20755
|
+
}
|
|
20756
|
+
event.preventDefault();
|
|
20757
|
+
event.stopPropagation();
|
|
20758
|
+
};
|
|
20759
|
+
const endDrag = (event) => {
|
|
20760
|
+
const drag = dragRef.current;
|
|
20761
|
+
if (drag?.pointerId !== event.pointerId) return;
|
|
20762
|
+
dragRef.current = null;
|
|
20763
|
+
try {
|
|
20764
|
+
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
|
|
20765
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
20766
|
+
}
|
|
20767
|
+
} catch {
|
|
20768
|
+
}
|
|
20769
|
+
if (drag.kind !== "pan") return;
|
|
20770
|
+
if (event.type === "pointercancel") {
|
|
20771
|
+
setPan(clampPan(scale, panRef.current));
|
|
19177
20772
|
return;
|
|
19178
20773
|
}
|
|
19179
|
-
|
|
19180
|
-
|
|
19181
|
-
|
|
19182
|
-
|
|
19183
|
-
|
|
19184
|
-
|
|
19185
|
-
|
|
19186
|
-
|
|
19187
|
-
|
|
19188
|
-
|
|
20774
|
+
startPanAnimation({
|
|
20775
|
+
x: velocityFromSamples(drag.samplesX),
|
|
20776
|
+
y: velocityFromSamples(drag.samplesY)
|
|
20777
|
+
});
|
|
20778
|
+
};
|
|
20779
|
+
const sectorAngle = (() => {
|
|
20780
|
+
if (!map.sector) return 0;
|
|
20781
|
+
if (!map.sector.followFov) return map.sector.angle;
|
|
20782
|
+
const aspect = size.width > 0 && size.height > 0 ? size.width / size.height : 1;
|
|
20783
|
+
return horizontalFov(view.fov, aspect);
|
|
20784
|
+
})();
|
|
20785
|
+
const activeSpot = spots.find((spot) => spot.sceneId === activeSceneId);
|
|
20786
|
+
const selectedSpot = spots.find((spot) => spot.id === selectedSpotId);
|
|
20787
|
+
const controls = /* @__PURE__ */ jsxs24("div", { className: "pano-map-controls", style: { pointerEvents: "auto" }, children: [
|
|
20788
|
+
collapseControl ? /* @__PURE__ */ jsx43(
|
|
20789
|
+
"button",
|
|
20790
|
+
{
|
|
20791
|
+
type: "button",
|
|
20792
|
+
className: "pano-map-control",
|
|
20793
|
+
"aria-label": collapsed ? "Expand map" : "Collapse map",
|
|
20794
|
+
"aria-pressed": !collapsed,
|
|
20795
|
+
style: {
|
|
20796
|
+
background: "rgba(15, 23, 42, 0.92)",
|
|
20797
|
+
border: "1px solid rgba(255, 255, 255, 0.28)",
|
|
20798
|
+
color: "#f8fafc"
|
|
20799
|
+
},
|
|
20800
|
+
onClick: () => setCollapsed(!collapsed),
|
|
20801
|
+
children: collapsed ? "\u25A3" : "\u25A2"
|
|
19189
20802
|
}
|
|
19190
|
-
|
|
19191
|
-
|
|
19192
|
-
|
|
19193
|
-
|
|
19194
|
-
|
|
19195
|
-
|
|
19196
|
-
|
|
19197
|
-
|
|
20803
|
+
) : null,
|
|
20804
|
+
zoomControls && !collapsed ? /* @__PURE__ */ jsxs24(Fragment8, { children: [
|
|
20805
|
+
/* @__PURE__ */ jsx43(
|
|
20806
|
+
"button",
|
|
20807
|
+
{
|
|
20808
|
+
type: "button",
|
|
20809
|
+
className: "pano-map-control",
|
|
20810
|
+
"aria-label": "Zoom in",
|
|
20811
|
+
style: {
|
|
20812
|
+
background: "rgba(15, 23, 42, 0.92)",
|
|
20813
|
+
border: "1px solid rgba(255, 255, 255, 0.28)",
|
|
20814
|
+
color: "#f8fafc"
|
|
20815
|
+
},
|
|
20816
|
+
onClick: () => applyZoom(scale * ZOOM_STEP),
|
|
20817
|
+
children: "+"
|
|
20818
|
+
}
|
|
20819
|
+
),
|
|
20820
|
+
/* @__PURE__ */ jsx43(
|
|
20821
|
+
"button",
|
|
20822
|
+
{
|
|
20823
|
+
type: "button",
|
|
20824
|
+
className: "pano-map-control",
|
|
20825
|
+
"aria-label": "Zoom out",
|
|
20826
|
+
style: {
|
|
20827
|
+
background: "rgba(15, 23, 42, 0.92)",
|
|
20828
|
+
border: "1px solid rgba(255, 255, 255, 0.28)",
|
|
20829
|
+
color: "#f8fafc"
|
|
20830
|
+
},
|
|
20831
|
+
onClick: () => applyZoom(scale / ZOOM_STEP),
|
|
20832
|
+
children: "\u2212"
|
|
20833
|
+
}
|
|
20834
|
+
)
|
|
20835
|
+
] }) : null
|
|
20836
|
+
] });
|
|
20837
|
+
const syncImageSize = (image) => {
|
|
20838
|
+
if (!(image.naturalWidth > 0) || !(image.naturalHeight > 0)) return;
|
|
20839
|
+
setImageSize(
|
|
20840
|
+
(previous) => previous.width === image.naturalWidth && previous.height === image.naturalHeight ? previous : { width: image.naturalWidth, height: image.naturalHeight }
|
|
20841
|
+
);
|
|
20842
|
+
};
|
|
20843
|
+
return /* @__PURE__ */ jsxs24(
|
|
20844
|
+
"div",
|
|
20845
|
+
{
|
|
20846
|
+
ref: rootRef,
|
|
20847
|
+
className: ["pano-map", collapsed ? "pano-map-collapsed" : "", className].filter(Boolean).join(" "),
|
|
20848
|
+
style: {
|
|
20849
|
+
position: "relative",
|
|
20850
|
+
overflow: "hidden",
|
|
20851
|
+
width: "100%",
|
|
20852
|
+
height: "100%",
|
|
20853
|
+
pointerEvents: "auto",
|
|
20854
|
+
touchAction: gestureZoom && !collapsed ? "none" : "auto",
|
|
20855
|
+
...style
|
|
20856
|
+
},
|
|
20857
|
+
"data-pano-map": "",
|
|
20858
|
+
"data-mode": mode,
|
|
20859
|
+
"data-collapsed": collapsed ? "true" : void 0,
|
|
20860
|
+
children: [
|
|
20861
|
+
renderChrome ? renderChrome(controls) : /* @__PURE__ */ jsx43("div", { className: "pano-map-chrome", children: controls }),
|
|
20862
|
+
/* @__PURE__ */ jsxs24(
|
|
20863
|
+
"div",
|
|
20864
|
+
{
|
|
20865
|
+
ref: containerRef,
|
|
20866
|
+
className: "pano-map-viewport",
|
|
20867
|
+
hidden: collapsed,
|
|
20868
|
+
style: {
|
|
20869
|
+
position: "absolute",
|
|
20870
|
+
inset: 0,
|
|
20871
|
+
overflow: "hidden",
|
|
20872
|
+
visibility: collapsed ? "hidden" : "visible"
|
|
20873
|
+
},
|
|
20874
|
+
onPointerDown: onPointerDownSurface,
|
|
20875
|
+
onPointerMove: onPointerMoveSurface,
|
|
20876
|
+
onPointerUp: endDrag,
|
|
20877
|
+
onPointerCancel: endDrag,
|
|
20878
|
+
children: [
|
|
20879
|
+
fit.width > 0 && fit.height > 0 ? /* @__PURE__ */ jsx43(
|
|
20880
|
+
"div",
|
|
20881
|
+
{
|
|
20882
|
+
className: "pano-map-image-layer",
|
|
20883
|
+
style: {
|
|
20884
|
+
position: "absolute",
|
|
20885
|
+
left: fit.left + pan.x,
|
|
20886
|
+
top: fit.top + pan.y,
|
|
20887
|
+
width: fit.width * scale,
|
|
20888
|
+
height: fit.height * scale,
|
|
20889
|
+
transformOrigin: "0 0"
|
|
20890
|
+
},
|
|
20891
|
+
children: /* @__PURE__ */ jsx43(
|
|
20892
|
+
"img",
|
|
20893
|
+
{
|
|
20894
|
+
src: map.image,
|
|
20895
|
+
alt: "",
|
|
20896
|
+
draggable: false,
|
|
20897
|
+
ref: (node) => {
|
|
20898
|
+
if (node) syncImageSize(node);
|
|
20899
|
+
},
|
|
20900
|
+
onLoad: (event) => {
|
|
20901
|
+
syncImageSize(event.currentTarget);
|
|
20902
|
+
},
|
|
20903
|
+
style: {
|
|
20904
|
+
width: "100%",
|
|
20905
|
+
height: "100%",
|
|
20906
|
+
display: "block",
|
|
20907
|
+
userSelect: "none",
|
|
20908
|
+
pointerEvents: "none"
|
|
20909
|
+
}
|
|
20910
|
+
}
|
|
20911
|
+
)
|
|
20912
|
+
}
|
|
20913
|
+
) : /* @__PURE__ */ jsx43(
|
|
20914
|
+
"img",
|
|
20915
|
+
{
|
|
20916
|
+
src: map.image,
|
|
20917
|
+
alt: "",
|
|
20918
|
+
hidden: true,
|
|
20919
|
+
ref: (node) => {
|
|
20920
|
+
if (node) syncImageSize(node);
|
|
20921
|
+
},
|
|
20922
|
+
onLoad: (event) => {
|
|
20923
|
+
syncImageSize(event.currentTarget);
|
|
20924
|
+
}
|
|
20925
|
+
}
|
|
20926
|
+
),
|
|
20927
|
+
activeSpot && map.sector ? /* @__PURE__ */ jsx43(
|
|
20928
|
+
"svg",
|
|
20929
|
+
{
|
|
20930
|
+
className: "pano-map-sector-layer",
|
|
20931
|
+
width: size.width,
|
|
20932
|
+
height: size.height,
|
|
20933
|
+
style: {
|
|
20934
|
+
position: "absolute",
|
|
20935
|
+
inset: 0,
|
|
20936
|
+
overflow: "visible",
|
|
20937
|
+
pointerEvents: "none"
|
|
20938
|
+
},
|
|
20939
|
+
children: /* @__PURE__ */ jsx43(
|
|
20940
|
+
"g",
|
|
20941
|
+
{
|
|
20942
|
+
transform: `translate(${imagePointToContainer(activeSpot, viewport).x} ${imagePointToContainer(activeSpot, viewport).y})`,
|
|
20943
|
+
children: /* @__PURE__ */ jsx43(
|
|
20944
|
+
SectorShape,
|
|
20945
|
+
{
|
|
20946
|
+
sector: map.sector,
|
|
20947
|
+
angle: sectorAngle,
|
|
20948
|
+
rotation: sectorRotation(activeSpot.heading, view.yaw),
|
|
20949
|
+
gradientId: defaultGradientId(reactId),
|
|
20950
|
+
interactive: editing,
|
|
20951
|
+
onHeadingPointerDown: (event) => {
|
|
20952
|
+
event.stopPropagation();
|
|
20953
|
+
if (event.button !== 0) return;
|
|
20954
|
+
onSelectedSpotIdChange?.(activeSpot.id);
|
|
20955
|
+
dragRef.current = {
|
|
20956
|
+
kind: "heading",
|
|
20957
|
+
pointerId: event.pointerId,
|
|
20958
|
+
spotId: activeSpot.id
|
|
20959
|
+
};
|
|
20960
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
20961
|
+
},
|
|
20962
|
+
transformRef: (node) => {
|
|
20963
|
+
sectorNodeRef.current = node;
|
|
20964
|
+
}
|
|
20965
|
+
}
|
|
20966
|
+
)
|
|
20967
|
+
}
|
|
20968
|
+
)
|
|
20969
|
+
}
|
|
20970
|
+
) : null,
|
|
20971
|
+
spots.map((spot) => {
|
|
20972
|
+
const point = imagePointToContainer(spot, viewport);
|
|
20973
|
+
const active = spot.sceneId === activeSceneId;
|
|
20974
|
+
const selected = spot.id === selectedSpotId;
|
|
20975
|
+
return /* @__PURE__ */ jsx43(
|
|
20976
|
+
"button",
|
|
20977
|
+
{
|
|
20978
|
+
type: "button",
|
|
20979
|
+
className: "pano-map-spot",
|
|
20980
|
+
"aria-label": spot.sceneId,
|
|
20981
|
+
"aria-current": active ? "true" : void 0,
|
|
20982
|
+
"data-active": active ? "true" : void 0,
|
|
20983
|
+
"data-selected": selected ? "true" : void 0,
|
|
20984
|
+
style: {
|
|
20985
|
+
position: "absolute",
|
|
20986
|
+
left: point.x,
|
|
20987
|
+
top: point.y,
|
|
20988
|
+
width: iconSize,
|
|
20989
|
+
height: iconSize,
|
|
20990
|
+
transform: "translate(-50%, -50%)",
|
|
20991
|
+
padding: 0,
|
|
20992
|
+
border: selected ? "2px solid #38bdf8" : "0",
|
|
20993
|
+
borderRadius: 8,
|
|
20994
|
+
background: "transparent",
|
|
20995
|
+
cursor: editing ? "grab" : "pointer",
|
|
20996
|
+
pointerEvents: "auto"
|
|
20997
|
+
},
|
|
20998
|
+
onPointerDown: (event) => {
|
|
20999
|
+
event.stopPropagation();
|
|
21000
|
+
if (event.button !== 0) return;
|
|
21001
|
+
onSelectedSpotIdChange?.(spot.id);
|
|
21002
|
+
if (!editing) return;
|
|
21003
|
+
const pointLocal = localPoint(event);
|
|
21004
|
+
if (!pointLocal) return;
|
|
21005
|
+
const center = imagePointToContainer(spot, viewport);
|
|
21006
|
+
dragRef.current = {
|
|
21007
|
+
kind: "spot",
|
|
21008
|
+
pointerId: event.pointerId,
|
|
21009
|
+
spotId: spot.id,
|
|
21010
|
+
offsetX: pointLocal.x - center.x,
|
|
21011
|
+
offsetY: pointLocal.y - center.y
|
|
21012
|
+
};
|
|
21013
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
21014
|
+
},
|
|
21015
|
+
onClick: (event) => {
|
|
21016
|
+
event.stopPropagation();
|
|
21017
|
+
if (editing) return;
|
|
21018
|
+
onSelectScene?.(spot.sceneId, spot);
|
|
21019
|
+
},
|
|
21020
|
+
children: spot.icon ? /* @__PURE__ */ jsx43(
|
|
21021
|
+
"img",
|
|
21022
|
+
{
|
|
21023
|
+
src: spot.icon,
|
|
21024
|
+
alt: "",
|
|
21025
|
+
draggable: false,
|
|
21026
|
+
style: { width: "100%", height: "100%", display: "block", pointerEvents: "none" }
|
|
21027
|
+
}
|
|
21028
|
+
) : /* @__PURE__ */ jsx43(DefaultSpotIcon, { active, size: iconSize })
|
|
21029
|
+
},
|
|
21030
|
+
spot.id
|
|
21031
|
+
);
|
|
21032
|
+
})
|
|
21033
|
+
]
|
|
21034
|
+
}
|
|
19198
21035
|
)
|
|
19199
|
-
|
|
21036
|
+
]
|
|
21037
|
+
}
|
|
21038
|
+
);
|
|
21039
|
+
}
|
|
21040
|
+
|
|
21041
|
+
// src/map/pano-map-overlay.tsx
|
|
21042
|
+
import {
|
|
21043
|
+
useEffect as useEffect45,
|
|
21044
|
+
useId as useId5,
|
|
21045
|
+
useMemo as useMemo31,
|
|
21046
|
+
useRef as useRef45,
|
|
21047
|
+
useState as useState29
|
|
21048
|
+
} from "react";
|
|
21049
|
+
import { Fragment as Fragment9, jsx as jsx44, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
21050
|
+
var DEFAULT_DIALOG_WIDTH = 720;
|
|
21051
|
+
var DEFAULT_DIALOG_HEIGHT = 520;
|
|
21052
|
+
function dockedBox(corner, offsetX, offsetY, width, height) {
|
|
21053
|
+
const horizontal = corner === "top-right" || corner === "bottom-right" ? { left: "auto", right: offsetX } : { left: offsetX, right: "auto" };
|
|
21054
|
+
const vertical = corner === "bottom-left" || corner === "bottom-right" ? { top: "auto", bottom: offsetY } : { top: offsetY, bottom: "auto" };
|
|
21055
|
+
return { ...horizontal, ...vertical, width, height };
|
|
21056
|
+
}
|
|
21057
|
+
function PanoMapDialogShell({
|
|
21058
|
+
open,
|
|
21059
|
+
onOpenChange,
|
|
21060
|
+
title,
|
|
21061
|
+
closeLabel,
|
|
21062
|
+
dialogWidth,
|
|
21063
|
+
dialogHeight,
|
|
21064
|
+
children
|
|
21065
|
+
}) {
|
|
21066
|
+
usePanoModal(open);
|
|
21067
|
+
const { size } = usePanoOverlay();
|
|
21068
|
+
const titleId = useId5();
|
|
21069
|
+
const panelRef = useRef45(null);
|
|
21070
|
+
const previouslyFocused = useRef45(null);
|
|
21071
|
+
useEffect45(() => {
|
|
21072
|
+
if (!open) return;
|
|
21073
|
+
const active = panelRef.current?.ownerDocument.activeElement;
|
|
21074
|
+
previouslyFocused.current = active instanceof HTMLElement ? active : null;
|
|
21075
|
+
const close = panelRef.current?.querySelector("[data-pano-map-dialog-close]");
|
|
21076
|
+
close?.focus({ preventScroll: true });
|
|
21077
|
+
const onKeyDown = (event) => {
|
|
21078
|
+
if (event.key === "Escape") {
|
|
21079
|
+
event.preventDefault();
|
|
21080
|
+
event.stopPropagation();
|
|
21081
|
+
onOpenChange(false);
|
|
21082
|
+
}
|
|
19200
21083
|
};
|
|
19201
|
-
|
|
19202
|
-
|
|
19203
|
-
video.textTracks.addEventListener("change", applyModes);
|
|
21084
|
+
const doc = panelRef.current?.ownerDocument;
|
|
21085
|
+
doc?.addEventListener("keydown", onKeyDown, true);
|
|
19204
21086
|
return () => {
|
|
19205
|
-
|
|
19206
|
-
|
|
19207
|
-
|
|
21087
|
+
doc?.removeEventListener("keydown", onKeyDown, true);
|
|
21088
|
+
const previous = previouslyFocused.current;
|
|
21089
|
+
if (previous?.isConnected) previous.focus({ preventScroll: true });
|
|
19208
21090
|
};
|
|
19209
|
-
}, [
|
|
19210
|
-
|
|
19211
|
-
|
|
19212
|
-
|
|
19213
|
-
|
|
19214
|
-
|
|
19215
|
-
|
|
19216
|
-
|
|
19217
|
-
|
|
19218
|
-
|
|
19219
|
-
|
|
19220
|
-
}
|
|
19221
|
-
return /* @__PURE__ */ jsxs22(Fragment7, { children: [
|
|
19222
|
-
activeTexture ? /* @__PURE__ */ jsxs22(
|
|
19223
|
-
"mesh",
|
|
21091
|
+
}, [open, onOpenChange]);
|
|
21092
|
+
if (!open) return null;
|
|
21093
|
+
const preferredWidth = Number.isFinite(dialogWidth) && dialogWidth > 0 ? dialogWidth : DEFAULT_DIALOG_WIDTH;
|
|
21094
|
+
const preferredHeight = Number.isFinite(dialogHeight) && dialogHeight > 0 ? dialogHeight : DEFAULT_DIALOG_HEIGHT;
|
|
21095
|
+
const width = Math.min(preferredWidth, Math.max(0, size.width - 32));
|
|
21096
|
+
const height = Math.min(preferredHeight, Math.max(0, size.height - 32));
|
|
21097
|
+
const left = Math.max(0, (size.width - width) / 2);
|
|
21098
|
+
const top = Math.max(0, (size.height - height) / 2);
|
|
21099
|
+
return /* @__PURE__ */ jsxs25(Fragment9, { children: [
|
|
21100
|
+
/* @__PURE__ */ jsx44(
|
|
21101
|
+
"div",
|
|
19224
21102
|
{
|
|
19225
|
-
|
|
19226
|
-
|
|
21103
|
+
className: "pano-map-dialog-backdrop",
|
|
21104
|
+
"aria-hidden": "true",
|
|
21105
|
+
style: {
|
|
21106
|
+
position: "absolute",
|
|
21107
|
+
inset: 0,
|
|
21108
|
+
pointerEvents: "auto"
|
|
21109
|
+
},
|
|
21110
|
+
onClick: () => onOpenChange(false)
|
|
21111
|
+
}
|
|
21112
|
+
),
|
|
21113
|
+
/* @__PURE__ */ jsxs25(
|
|
21114
|
+
"div",
|
|
21115
|
+
{
|
|
21116
|
+
ref: panelRef,
|
|
21117
|
+
className: "pano-map-dialog",
|
|
21118
|
+
role: "dialog",
|
|
21119
|
+
"aria-modal": "true",
|
|
21120
|
+
"aria-labelledby": titleId,
|
|
21121
|
+
"data-pano-map-dialog": "",
|
|
21122
|
+
style: {
|
|
21123
|
+
position: "absolute",
|
|
21124
|
+
left,
|
|
21125
|
+
top,
|
|
21126
|
+
width,
|
|
21127
|
+
height,
|
|
21128
|
+
boxSizing: "border-box",
|
|
21129
|
+
display: "flex",
|
|
21130
|
+
flexDirection: "column",
|
|
21131
|
+
overflow: "hidden",
|
|
21132
|
+
pointerEvents: "auto",
|
|
21133
|
+
zIndex: 1
|
|
21134
|
+
},
|
|
19227
21135
|
children: [
|
|
19228
|
-
/* @__PURE__ */
|
|
19229
|
-
|
|
19230
|
-
|
|
19231
|
-
|
|
19232
|
-
|
|
19233
|
-
|
|
19234
|
-
|
|
19235
|
-
|
|
19236
|
-
|
|
21136
|
+
/* @__PURE__ */ jsxs25("header", { className: "pano-map-dialog-header", style: { flexShrink: 0 }, children: [
|
|
21137
|
+
/* @__PURE__ */ jsx44("h2", { id: titleId, className: "pano-map-dialog-title", children: title }),
|
|
21138
|
+
/* @__PURE__ */ jsx44(
|
|
21139
|
+
"button",
|
|
21140
|
+
{
|
|
21141
|
+
type: "button",
|
|
21142
|
+
className: "pano-map-dialog-close",
|
|
21143
|
+
"data-pano-map-dialog-close": "",
|
|
21144
|
+
"aria-label": closeLabel,
|
|
21145
|
+
onClick: () => onOpenChange(false),
|
|
21146
|
+
children: /* @__PURE__ */ jsx44("svg", { "aria-hidden": "true", width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: /* @__PURE__ */ jsx44("path", { d: "m4 4 8 8M12 4l-8 8" }) })
|
|
21147
|
+
}
|
|
21148
|
+
)
|
|
21149
|
+
] }),
|
|
21150
|
+
/* @__PURE__ */ jsx44("div", { className: "pano-map-dialog-body", style: { minHeight: 0, flex: 1 }, children })
|
|
19237
21151
|
]
|
|
19238
21152
|
}
|
|
19239
|
-
)
|
|
19240
|
-
captionsEnabled ? /* @__PURE__ */ jsx41(PanoVideoCaptions, { appearance: typeof captions === "object" ? captions : void 0 }) : null
|
|
21153
|
+
)
|
|
19241
21154
|
] });
|
|
19242
21155
|
}
|
|
21156
|
+
function PanoMapOverlay({
|
|
21157
|
+
map,
|
|
21158
|
+
activeSceneId,
|
|
21159
|
+
onSelectScene,
|
|
21160
|
+
view,
|
|
21161
|
+
mode = "view",
|
|
21162
|
+
selectedSpotId,
|
|
21163
|
+
onSelectedSpotIdChange,
|
|
21164
|
+
onEditChange,
|
|
21165
|
+
placementSceneId,
|
|
21166
|
+
presentationOverride,
|
|
21167
|
+
presentation: presentationModeProp,
|
|
21168
|
+
corner: cornerProp,
|
|
21169
|
+
width: widthProp,
|
|
21170
|
+
height: heightProp,
|
|
21171
|
+
offsetX: offsetXProp,
|
|
21172
|
+
offsetY: offsetYProp,
|
|
21173
|
+
dialogWidth = DEFAULT_DIALOG_WIDTH,
|
|
21174
|
+
dialogHeight = DEFAULT_DIALOG_HEIGHT,
|
|
21175
|
+
interaction,
|
|
21176
|
+
maxScale,
|
|
21177
|
+
initialScale,
|
|
21178
|
+
showZoomControls,
|
|
21179
|
+
showCollapseControl,
|
|
21180
|
+
iconSize,
|
|
21181
|
+
openLabel = "Map",
|
|
21182
|
+
closeLabel = "Close",
|
|
21183
|
+
title = "Map",
|
|
21184
|
+
className,
|
|
21185
|
+
style,
|
|
21186
|
+
editorSurface = false
|
|
21187
|
+
}) {
|
|
21188
|
+
const { size } = usePanoOverlay();
|
|
21189
|
+
const [open, setOpen] = useState29(false);
|
|
21190
|
+
const [collapsed, setCollapsed] = useState29(false);
|
|
21191
|
+
const basePresentation = presentationOverride ?? map.presentation ?? {
|
|
21192
|
+
mode: "docked",
|
|
21193
|
+
corner: "bottom-left",
|
|
21194
|
+
width: 220,
|
|
21195
|
+
height: 160,
|
|
21196
|
+
offsetX: 16,
|
|
21197
|
+
offsetY: 16
|
|
21198
|
+
};
|
|
21199
|
+
const presentationMode = presentationModeProp ?? basePresentation.mode;
|
|
21200
|
+
const corner = cornerProp ?? basePresentation.corner ?? "bottom-left";
|
|
21201
|
+
const offsetX = offsetXProp ?? basePresentation.offsetX ?? 16;
|
|
21202
|
+
const offsetY = offsetYProp ?? basePresentation.offsetY ?? 16;
|
|
21203
|
+
const width = widthProp ?? basePresentation.width ?? (presentationMode === "dialog" ? 48 : 220);
|
|
21204
|
+
const height = heightProp ?? basePresentation.height ?? (presentationMode === "dialog" ? 48 : 160);
|
|
21205
|
+
const resolvedMap = useMemo31(() => {
|
|
21206
|
+
if (interaction === void 0 && maxScale === void 0 && iconSize === void 0) {
|
|
21207
|
+
return map;
|
|
21208
|
+
}
|
|
21209
|
+
return {
|
|
21210
|
+
...map,
|
|
21211
|
+
...interaction === void 0 ? {} : { interaction },
|
|
21212
|
+
...maxScale === void 0 ? {} : { maxScale },
|
|
21213
|
+
...iconSize === void 0 ? {} : { iconSize }
|
|
21214
|
+
};
|
|
21215
|
+
}, [iconSize, interaction, map, maxScale]);
|
|
21216
|
+
const mapProps = {
|
|
21217
|
+
map: resolvedMap,
|
|
21218
|
+
activeSceneId,
|
|
21219
|
+
view,
|
|
21220
|
+
mode,
|
|
21221
|
+
selectedSpotId,
|
|
21222
|
+
onSelectedSpotIdChange,
|
|
21223
|
+
onEditChange,
|
|
21224
|
+
placementSceneId,
|
|
21225
|
+
initialScale,
|
|
21226
|
+
showZoomControls,
|
|
21227
|
+
showCollapseControl,
|
|
21228
|
+
onSelectScene: (sceneId, spot) => {
|
|
21229
|
+
onSelectScene?.(sceneId, spot);
|
|
21230
|
+
if (presentationMode === "dialog") setOpen(false);
|
|
21231
|
+
}
|
|
21232
|
+
};
|
|
21233
|
+
const editorBox = useMemo31(() => {
|
|
21234
|
+
const panelWidth2 = widthProp !== void 0 && Number.isFinite(widthProp) && widthProp > 0 ? widthProp : Math.min(420, Math.max(240, size.width * 0.45));
|
|
21235
|
+
const panelHeight2 = heightProp !== void 0 && Number.isFinite(heightProp) && heightProp > 0 ? heightProp : Math.min(320, Math.max(180, size.height * 0.45));
|
|
21236
|
+
return {
|
|
21237
|
+
left: 16,
|
|
21238
|
+
right: "auto",
|
|
21239
|
+
top: "auto",
|
|
21240
|
+
bottom: 16,
|
|
21241
|
+
width: panelWidth2,
|
|
21242
|
+
height: panelHeight2
|
|
21243
|
+
};
|
|
21244
|
+
}, [heightProp, size.height, size.width, widthProp]);
|
|
21245
|
+
if (editorSurface) {
|
|
21246
|
+
return /* @__PURE__ */ jsx44(
|
|
21247
|
+
"div",
|
|
21248
|
+
{
|
|
21249
|
+
className: ["pano-map-overlay", "pano-map-overlay-editor", className].filter(Boolean).join(" "),
|
|
21250
|
+
style: {
|
|
21251
|
+
position: "absolute",
|
|
21252
|
+
pointerEvents: "auto",
|
|
21253
|
+
...editorBox,
|
|
21254
|
+
...style
|
|
21255
|
+
},
|
|
21256
|
+
"data-pano-map-overlay": "",
|
|
21257
|
+
children: /* @__PURE__ */ jsx44(PanoMap, { ...mapProps, showCollapseControl: false })
|
|
21258
|
+
}
|
|
21259
|
+
);
|
|
21260
|
+
}
|
|
21261
|
+
if (presentationMode === "dialog") {
|
|
21262
|
+
const launcherBox = dockedBox(corner, offsetX, offsetY, width, height);
|
|
21263
|
+
return /* @__PURE__ */ jsxs25("div", { className: ["pano-map-overlay", className].filter(Boolean).join(" "), "data-pano-map-overlay": "", children: [
|
|
21264
|
+
/* @__PURE__ */ jsx44(
|
|
21265
|
+
"button",
|
|
21266
|
+
{
|
|
21267
|
+
type: "button",
|
|
21268
|
+
className: "pano-map-launcher",
|
|
21269
|
+
"aria-label": openLabel,
|
|
21270
|
+
style: {
|
|
21271
|
+
position: "absolute",
|
|
21272
|
+
pointerEvents: "auto",
|
|
21273
|
+
...launcherBox,
|
|
21274
|
+
...style
|
|
21275
|
+
},
|
|
21276
|
+
onClick: () => setOpen(true),
|
|
21277
|
+
children: openLabel
|
|
21278
|
+
}
|
|
21279
|
+
),
|
|
21280
|
+
/* @__PURE__ */ jsx44(
|
|
21281
|
+
PanoMapDialogShell,
|
|
21282
|
+
{
|
|
21283
|
+
open,
|
|
21284
|
+
onOpenChange: setOpen,
|
|
21285
|
+
title,
|
|
21286
|
+
closeLabel,
|
|
21287
|
+
dialogWidth,
|
|
21288
|
+
dialogHeight,
|
|
21289
|
+
children: /* @__PURE__ */ jsx44("div", { style: { height: "100%" }, children: /* @__PURE__ */ jsx44(PanoMap, { ...mapProps, showCollapseControl: false }) })
|
|
21290
|
+
}
|
|
21291
|
+
)
|
|
21292
|
+
] });
|
|
21293
|
+
}
|
|
21294
|
+
const panelWidth = collapsed ? Math.max(44, Math.min(width, 56)) : width;
|
|
21295
|
+
const panelHeight = collapsed ? Math.max(44, Math.min(height, 56)) : height;
|
|
21296
|
+
const box = dockedBox(corner, offsetX, offsetY, panelWidth, panelHeight);
|
|
21297
|
+
return /* @__PURE__ */ jsx44(
|
|
21298
|
+
"div",
|
|
21299
|
+
{
|
|
21300
|
+
className: ["pano-map-overlay", className].filter(Boolean).join(" "),
|
|
21301
|
+
style: {
|
|
21302
|
+
position: "absolute",
|
|
21303
|
+
pointerEvents: "auto",
|
|
21304
|
+
...box,
|
|
21305
|
+
...style
|
|
21306
|
+
},
|
|
21307
|
+
"data-pano-map-overlay": "",
|
|
21308
|
+
"data-presentation": "docked",
|
|
21309
|
+
children: /* @__PURE__ */ jsx44(
|
|
21310
|
+
PanoMap,
|
|
21311
|
+
{
|
|
21312
|
+
...mapProps,
|
|
21313
|
+
collapsed,
|
|
21314
|
+
onCollapsedChange: setCollapsed
|
|
21315
|
+
}
|
|
21316
|
+
)
|
|
21317
|
+
}
|
|
21318
|
+
);
|
|
21319
|
+
}
|
|
19243
21320
|
export {
|
|
19244
21321
|
AudioHotspot,
|
|
19245
21322
|
AutoRotate,
|
|
@@ -19247,6 +21324,7 @@ export {
|
|
|
19247
21324
|
DEFAULT_HOTSPOT_TOOLTIP_APPEARANCE,
|
|
19248
21325
|
DEFAULT_HOTSPOT_TOOLTIP_OFFSET,
|
|
19249
21326
|
DEFAULT_PANO_CURSORS,
|
|
21327
|
+
DEFAULT_PANO_SCENE_MENU_APPEARANCE,
|
|
19250
21328
|
DEFAULT_PANO_VIDEO_CAPTION_APPEARANCE,
|
|
19251
21329
|
DEFAULT_PANO_VIDEO_CONTROLS_APPEARANCE,
|
|
19252
21330
|
DEFAULT_PANO_VIDEO_PLAYBACK_RATES,
|
|
@@ -19264,7 +21342,10 @@ export {
|
|
|
19264
21342
|
PanoContextMenu,
|
|
19265
21343
|
PanoEvents,
|
|
19266
21344
|
PanoFilter,
|
|
21345
|
+
PanoMap,
|
|
21346
|
+
PanoMapOverlay,
|
|
19267
21347
|
PanoOverlayPortalProvider,
|
|
21348
|
+
PanoSceneMenu,
|
|
19268
21349
|
PanoVideo,
|
|
19269
21350
|
PanoVideoControls,
|
|
19270
21351
|
PanoViewer,
|
|
@@ -19284,13 +21365,19 @@ export {
|
|
|
19284
21365
|
WebVR,
|
|
19285
21366
|
clampPanoPitch,
|
|
19286
21367
|
composePanoContextMenuItems,
|
|
21368
|
+
containRect,
|
|
21369
|
+
containerPointToImage,
|
|
19287
21370
|
createDefaultPanoContextMenuItems,
|
|
19288
21371
|
createPanoContextMenuPresets,
|
|
19289
21372
|
cycleSceneId,
|
|
21373
|
+
headingFromVector,
|
|
21374
|
+
horizontalFov,
|
|
21375
|
+
imagePointToContainer,
|
|
19290
21376
|
normalizePanoPosition,
|
|
19291
21377
|
normalizePanoYaw,
|
|
19292
21378
|
panoPositionToVector3,
|
|
19293
21379
|
resolvePanoContextMenuEntries,
|
|
21380
|
+
sectorRotation,
|
|
19294
21381
|
unwrapPolygonVertices,
|
|
19295
21382
|
usePanoEvents,
|
|
19296
21383
|
usePanoModal,
|