@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.cjs
CHANGED
|
@@ -2254,6 +2254,7 @@ __export(index_exports, {
|
|
|
2254
2254
|
DEFAULT_HOTSPOT_TOOLTIP_APPEARANCE: () => DEFAULT_HOTSPOT_TOOLTIP_APPEARANCE,
|
|
2255
2255
|
DEFAULT_HOTSPOT_TOOLTIP_OFFSET: () => DEFAULT_HOTSPOT_TOOLTIP_OFFSET,
|
|
2256
2256
|
DEFAULT_PANO_CURSORS: () => DEFAULT_PANO_CURSORS,
|
|
2257
|
+
DEFAULT_PANO_SCENE_MENU_APPEARANCE: () => DEFAULT_PANO_SCENE_MENU_APPEARANCE,
|
|
2257
2258
|
DEFAULT_PANO_VIDEO_CAPTION_APPEARANCE: () => DEFAULT_PANO_VIDEO_CAPTION_APPEARANCE,
|
|
2258
2259
|
DEFAULT_PANO_VIDEO_CONTROLS_APPEARANCE: () => DEFAULT_PANO_VIDEO_CONTROLS_APPEARANCE,
|
|
2259
2260
|
DEFAULT_PANO_VIDEO_PLAYBACK_RATES: () => DEFAULT_PANO_VIDEO_PLAYBACK_RATES,
|
|
@@ -2271,7 +2272,10 @@ __export(index_exports, {
|
|
|
2271
2272
|
PanoContextMenu: () => PanoContextMenu,
|
|
2272
2273
|
PanoEvents: () => PanoEvents,
|
|
2273
2274
|
PanoFilter: () => PanoFilter,
|
|
2275
|
+
PanoMap: () => PanoMap,
|
|
2276
|
+
PanoMapOverlay: () => PanoMapOverlay,
|
|
2274
2277
|
PanoOverlayPortalProvider: () => PanoOverlayPortalProvider,
|
|
2278
|
+
PanoSceneMenu: () => PanoSceneMenu,
|
|
2275
2279
|
PanoVideo: () => PanoVideo,
|
|
2276
2280
|
PanoVideoControls: () => PanoVideoControls,
|
|
2277
2281
|
PanoViewer: () => PanoViewer,
|
|
@@ -2291,13 +2295,19 @@ __export(index_exports, {
|
|
|
2291
2295
|
WebVR: () => WebVR,
|
|
2292
2296
|
clampPanoPitch: () => clampPanoPitch,
|
|
2293
2297
|
composePanoContextMenuItems: () => composePanoContextMenuItems,
|
|
2298
|
+
containRect: () => containRect,
|
|
2299
|
+
containerPointToImage: () => containerPointToImage,
|
|
2294
2300
|
createDefaultPanoContextMenuItems: () => createDefaultPanoContextMenuItems,
|
|
2295
2301
|
createPanoContextMenuPresets: () => createPanoContextMenuPresets,
|
|
2296
2302
|
cycleSceneId: () => cycleSceneId,
|
|
2303
|
+
headingFromVector: () => headingFromVector,
|
|
2304
|
+
horizontalFov: () => horizontalFov,
|
|
2305
|
+
imagePointToContainer: () => imagePointToContainer,
|
|
2297
2306
|
normalizePanoPosition: () => normalizePanoPosition,
|
|
2298
2307
|
normalizePanoYaw: () => normalizePanoYaw,
|
|
2299
2308
|
panoPositionToVector3: () => panoPositionToVector3,
|
|
2300
2309
|
resolvePanoContextMenuEntries: () => resolvePanoContextMenuEntries,
|
|
2310
|
+
sectorRotation: () => sectorRotation,
|
|
2301
2311
|
unwrapPolygonVertices: () => unwrapPolygonVertices,
|
|
2302
2312
|
usePanoEvents: () => usePanoEvents,
|
|
2303
2313
|
usePanoModal: () => usePanoModal,
|
|
@@ -9025,7 +9035,7 @@ var PanoViewer = (0, import_react29.forwardRef)(
|
|
|
9025
9035
|
contextMenuOverlay,
|
|
9026
9036
|
hotspotAccessibilityControls
|
|
9027
9037
|
] }),
|
|
9028
|
-
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PanoModalHostContext.Provider, { value: modalHost, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PanoOverlayContext.Provider, { value: overlayContext, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
9038
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PanoEventBusContext.Provider, { value: eventBus, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PanoModalHostContext.Provider, { value: modalHost, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PanoOverlayContext.Provider, { value: overlayContext, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
9029
9039
|
"div",
|
|
9030
9040
|
{
|
|
9031
9041
|
"data-pano-overlay": "",
|
|
@@ -9033,7 +9043,7 @@ var PanoViewer = (0, import_react29.forwardRef)(
|
|
|
9033
9043
|
style: { position: "absolute", inset: 0, zIndex: 30, pointerEvents: "none" },
|
|
9034
9044
|
children: renderOverlay?.(overlayContext)
|
|
9035
9045
|
}
|
|
9036
|
-
) }) })
|
|
9046
|
+
) }) }) })
|
|
9037
9047
|
]
|
|
9038
9048
|
}
|
|
9039
9049
|
);
|
|
@@ -18232,197 +18242,1097 @@ var WebVR = (0, import_react61.forwardRef)(function WebVR2({
|
|
|
18232
18242
|
] });
|
|
18233
18243
|
});
|
|
18234
18244
|
|
|
18235
|
-
// src/
|
|
18245
|
+
// src/pano-scene-menu.tsx
|
|
18236
18246
|
var import_react62 = require("react");
|
|
18237
|
-
|
|
18247
|
+
|
|
18248
|
+
// src/drag-motion.ts
|
|
18249
|
+
var DRAG_INERTIA_DAMPING = 8;
|
|
18250
|
+
var DRAG_FRICTION_STOP = 24;
|
|
18251
|
+
var DRAG_SPRING_STRENGTH = 28;
|
|
18252
|
+
var DRAG_RUBBER_CONSTANT = 0.55;
|
|
18253
|
+
var DRAG_SPRING_SETTLE_DISTANCE = 0.5;
|
|
18254
|
+
var DRAG_SPRING_SETTLE_VELOCITY = 12;
|
|
18255
|
+
function resolveDragMotion(motion) {
|
|
18256
|
+
if (motion === false) {
|
|
18257
|
+
return { inertia: false, bounce: false };
|
|
18258
|
+
}
|
|
18238
18259
|
return {
|
|
18239
|
-
|
|
18240
|
-
|
|
18241
|
-
ref.current?.setView(view, options);
|
|
18242
|
-
},
|
|
18243
|
-
reset: () => {
|
|
18244
|
-
ref.current?.reset();
|
|
18245
|
-
},
|
|
18246
|
-
lookTo: (position, options) => ref.current?.lookTo(position, options) ?? Promise.resolve({ status: "cancelled" }),
|
|
18247
|
-
moveTo: (position, options) => ref.current?.moveTo(position, options) ?? Promise.resolve({ status: "cancelled" }),
|
|
18248
|
-
zoomTo: (fov, options) => ref.current?.zoomTo(fov, options) ?? Promise.resolve({ status: "cancelled" }),
|
|
18249
|
-
lookToHotspot: (id, options) => ref.current?.lookToHotspot(id, options) ?? Promise.resolve({ status: "not-found" }),
|
|
18250
|
-
activateMedia: () => ref.current?.activateMedia() ?? Promise.resolve(),
|
|
18251
|
-
enterFullscreen: () => ref.current?.enterFullscreen() ?? Promise.resolve(),
|
|
18252
|
-
exitFullscreen: () => ref.current?.exitFullscreen() ?? Promise.resolve(),
|
|
18253
|
-
toggleFullscreen: () => ref.current?.toggleFullscreen() ?? Promise.resolve(),
|
|
18254
|
-
isFullscreen: () => ref.current?.isFullscreen() ?? false,
|
|
18255
|
-
setScene: (id) => ref.current?.setScene(id) ?? false,
|
|
18256
|
-
nextScene: () => ref.current?.nextScene() ?? false,
|
|
18257
|
-
previousScene: () => ref.current?.previousScene() ?? false,
|
|
18258
|
-
getActiveSceneId: () => ref.current?.getActiveSceneId() ?? null,
|
|
18259
|
-
getSceneIds: () => ref.current?.getSceneIds() ?? [],
|
|
18260
|
-
isSceneTransitioning: () => ref.current?.isSceneTransitioning() ?? false,
|
|
18261
|
-
enterVR: () => ref.current?.enterVR() ?? Promise.resolve(false),
|
|
18262
|
-
exitVR: () => ref.current?.exitVR() ?? Promise.resolve(),
|
|
18263
|
-
toggleVR: () => ref.current?.toggleVR() ?? Promise.resolve(false),
|
|
18264
|
-
isVRAvailable: () => ref.current?.isVRAvailable() ?? false,
|
|
18265
|
-
isVREnabled: () => ref.current?.isVREnabled() ?? false,
|
|
18266
|
-
getVRMode: () => ref.current?.getVRMode() ?? null,
|
|
18267
|
-
requestVRPermission: () => ref.current?.requestVRPermission() ?? Promise.resolve(false),
|
|
18268
|
-
getVideo: () => ref.current?.getVideo() ?? null,
|
|
18269
|
-
subscribeVideo: (onStoreChange) => ref.current?.subscribeVideo(onStoreChange) ?? (() => {
|
|
18270
|
-
}),
|
|
18271
|
-
playVideo: () => ref.current?.playVideo() ?? Promise.resolve(),
|
|
18272
|
-
pauseVideo: () => {
|
|
18273
|
-
ref.current?.pauseVideo();
|
|
18274
|
-
},
|
|
18275
|
-
toggleVideo: () => {
|
|
18276
|
-
ref.current?.toggleVideo();
|
|
18277
|
-
},
|
|
18278
|
-
seekVideo: (time) => {
|
|
18279
|
-
ref.current?.seekVideo(time);
|
|
18280
|
-
},
|
|
18281
|
-
setVideoVolume: (volume) => {
|
|
18282
|
-
ref.current?.setVideoVolume(volume);
|
|
18283
|
-
},
|
|
18284
|
-
setVideoMuted: (muted) => {
|
|
18285
|
-
ref.current?.setVideoMuted(muted);
|
|
18286
|
-
},
|
|
18287
|
-
toggleVideoMuted: () => {
|
|
18288
|
-
ref.current?.toggleVideoMuted();
|
|
18289
|
-
},
|
|
18290
|
-
getBackgroundAudio: () => ref.current?.getBackgroundAudio() ?? null,
|
|
18291
|
-
subscribeBackgroundAudio: (onStoreChange) => ref.current?.subscribeBackgroundAudio(onStoreChange) ?? (() => {
|
|
18292
|
-
}),
|
|
18293
|
-
playBackgroundAudio: () => ref.current?.playBackgroundAudio() ?? Promise.resolve(),
|
|
18294
|
-
pauseBackgroundAudio: () => {
|
|
18295
|
-
ref.current?.pauseBackgroundAudio();
|
|
18296
|
-
},
|
|
18297
|
-
toggleBackgroundAudio: () => {
|
|
18298
|
-
ref.current?.toggleBackgroundAudio();
|
|
18299
|
-
},
|
|
18300
|
-
setBackgroundAudioVolume: (volume) => {
|
|
18301
|
-
ref.current?.setBackgroundAudioVolume(volume);
|
|
18302
|
-
},
|
|
18303
|
-
setBackgroundAudioMuted: (muted) => {
|
|
18304
|
-
ref.current?.setBackgroundAudioMuted(muted);
|
|
18305
|
-
},
|
|
18306
|
-
toggleBackgroundAudioMuted: () => {
|
|
18307
|
-
ref.current?.toggleBackgroundAudioMuted();
|
|
18308
|
-
},
|
|
18309
|
-
startAutoRotate: () => {
|
|
18310
|
-
ref.current?.startAutoRotate();
|
|
18311
|
-
},
|
|
18312
|
-
stopAutoRotate: () => {
|
|
18313
|
-
ref.current?.stopAutoRotate();
|
|
18314
|
-
}
|
|
18260
|
+
inertia: motion?.inertia !== false,
|
|
18261
|
+
bounce: motion?.bounce !== false
|
|
18315
18262
|
};
|
|
18316
18263
|
}
|
|
18317
|
-
function
|
|
18318
|
-
|
|
18319
|
-
|
|
18320
|
-
|
|
18264
|
+
function clampAxis(value, min, max) {
|
|
18265
|
+
if (max < min) return min;
|
|
18266
|
+
return Math.min(max, Math.max(min, value));
|
|
18267
|
+
}
|
|
18268
|
+
function constrainDragPosition(raw, min, max, dimension, bounce) {
|
|
18269
|
+
if (!(Number.isFinite(raw) && Number.isFinite(min) && Number.isFinite(max))) {
|
|
18270
|
+
return 0;
|
|
18271
|
+
}
|
|
18272
|
+
if (raw >= min && raw <= max) {
|
|
18273
|
+
return raw;
|
|
18274
|
+
}
|
|
18275
|
+
if (!bounce) {
|
|
18276
|
+
return clampAxis(raw, min, max);
|
|
18277
|
+
}
|
|
18278
|
+
const size = Math.max(1, Math.abs(dimension) || 1);
|
|
18279
|
+
if (raw < min) {
|
|
18280
|
+
const overshoot2 = min - raw;
|
|
18281
|
+
return min - rubberBandDistance(overshoot2, size);
|
|
18282
|
+
}
|
|
18283
|
+
const overshoot = raw - max;
|
|
18284
|
+
return max + rubberBandDistance(overshoot, size);
|
|
18285
|
+
}
|
|
18286
|
+
function rubberBandDistance(overshoot, dimension, constant = DRAG_RUBBER_CONSTANT) {
|
|
18287
|
+
if (!(overshoot > 0)) return 0;
|
|
18288
|
+
const size = Math.max(1, Math.abs(dimension) || 1);
|
|
18289
|
+
return size * overshoot / (size + overshoot * constant);
|
|
18290
|
+
}
|
|
18291
|
+
function velocityFromSamples(samples, now) {
|
|
18292
|
+
if (samples.length < 2) return 0;
|
|
18293
|
+
const windowMs = 100;
|
|
18294
|
+
let oldest = samples[0];
|
|
18295
|
+
const newest = samples[samples.length - 1];
|
|
18296
|
+
const referenceTime = now ?? newest.t;
|
|
18297
|
+
for (let i = samples.length - 2; i >= 0; i -= 1) {
|
|
18298
|
+
const sample = samples[i];
|
|
18299
|
+
if (referenceTime - sample.t > windowMs) break;
|
|
18300
|
+
oldest = sample;
|
|
18301
|
+
}
|
|
18302
|
+
const dt = (newest.t - oldest.t) / 1e3;
|
|
18303
|
+
if (!(dt >= 1e-3)) return 0;
|
|
18304
|
+
return (newest.position - oldest.position) / dt;
|
|
18305
|
+
}
|
|
18306
|
+
function integrateAxis(state, deltaSeconds, options) {
|
|
18307
|
+
const dt = Math.max(0, deltaSeconds);
|
|
18308
|
+
if (!(dt > 0)) {
|
|
18309
|
+
return { ...state, settled: isAxisSettled(state, options) };
|
|
18310
|
+
}
|
|
18311
|
+
const damping = options.damping ?? DRAG_INERTIA_DAMPING;
|
|
18312
|
+
const frictionStop = options.frictionStop ?? DRAG_FRICTION_STOP;
|
|
18313
|
+
const spring = options.spring ?? DRAG_SPRING_STRENGTH;
|
|
18314
|
+
const { min, max } = options;
|
|
18315
|
+
let { position, velocity } = state;
|
|
18316
|
+
const pastMin = position < min;
|
|
18317
|
+
const pastMax = position > max;
|
|
18318
|
+
if (pastMin || pastMax) {
|
|
18319
|
+
if (!options.bounce) {
|
|
18320
|
+
position = clampAxis(position, min, max);
|
|
18321
|
+
return { position, velocity: 0, settled: true };
|
|
18322
|
+
}
|
|
18323
|
+
const edge = pastMin ? min : max;
|
|
18324
|
+
if (pastMin && velocity < 0) velocity = 0;
|
|
18325
|
+
if (pastMax && velocity > 0) velocity = 0;
|
|
18326
|
+
const displacement = edge - position;
|
|
18327
|
+
velocity += displacement * spring * dt;
|
|
18328
|
+
const decay = Math.exp(-damping * dt);
|
|
18329
|
+
velocity *= decay;
|
|
18330
|
+
position += velocity * dt;
|
|
18331
|
+
if (pastMin && position > min) {
|
|
18332
|
+
position = min;
|
|
18333
|
+
velocity = 0;
|
|
18334
|
+
}
|
|
18335
|
+
if (pastMax && position < max) {
|
|
18336
|
+
position = max;
|
|
18337
|
+
velocity = 0;
|
|
18338
|
+
}
|
|
18339
|
+
const settled = isAxisSettled({ position, velocity }, options);
|
|
18340
|
+
if (settled) {
|
|
18341
|
+
return { position: clampAxis(position, min, max), velocity: 0, settled: true };
|
|
18342
|
+
}
|
|
18343
|
+
return { position, velocity, settled: false };
|
|
18344
|
+
}
|
|
18345
|
+
if (!options.inertia || Math.abs(velocity) < frictionStop) {
|
|
18346
|
+
return { position, velocity: 0, settled: true };
|
|
18347
|
+
}
|
|
18348
|
+
position += velocity * dt;
|
|
18349
|
+
velocity *= Math.exp(-damping * dt);
|
|
18350
|
+
if (position < min || position > max) {
|
|
18351
|
+
if (!options.bounce) {
|
|
18352
|
+
position = clampAxis(position, min, max);
|
|
18353
|
+
return { position, velocity: 0, settled: true };
|
|
18354
|
+
}
|
|
18355
|
+
if (Math.abs(velocity) < frictionStop) {
|
|
18356
|
+
velocity = 0;
|
|
18357
|
+
}
|
|
18358
|
+
return { position, velocity, settled: false };
|
|
18359
|
+
}
|
|
18360
|
+
if (Math.abs(velocity) < frictionStop) {
|
|
18361
|
+
return { position, velocity: 0, settled: true };
|
|
18362
|
+
}
|
|
18363
|
+
return { position, velocity, settled: false };
|
|
18364
|
+
}
|
|
18365
|
+
function isAxisSettled(state, options) {
|
|
18366
|
+
const frictionStop = options.frictionStop ?? DRAG_FRICTION_STOP;
|
|
18367
|
+
const inside = state.position >= options.min - DRAG_SPRING_SETTLE_DISTANCE && state.position <= options.max + DRAG_SPRING_SETTLE_DISTANCE;
|
|
18368
|
+
const slow = Math.abs(state.velocity) < (options.frictionStop ?? DRAG_SPRING_SETTLE_VELOCITY);
|
|
18369
|
+
if (!inside) return false;
|
|
18370
|
+
return slow || Math.abs(state.velocity) < frictionStop;
|
|
18371
|
+
}
|
|
18372
|
+
function pushSample(samples, sample, maxCount = 8) {
|
|
18373
|
+
const next = samples.length >= maxCount ? samples.slice(samples.length - maxCount + 1) : samples.slice();
|
|
18374
|
+
next.push(sample);
|
|
18375
|
+
return next;
|
|
18321
18376
|
}
|
|
18322
18377
|
|
|
18323
|
-
// src/
|
|
18324
|
-
var import_fiber31 = require("@react-three/fiber");
|
|
18325
|
-
var import_react63 = require("react");
|
|
18326
|
-
var import_three40 = require("three");
|
|
18378
|
+
// src/pano-scene-menu.tsx
|
|
18327
18379
|
var import_jsx_runtime41 = require("react/jsx-runtime");
|
|
18328
|
-
var
|
|
18329
|
-
var
|
|
18330
|
-
|
|
18380
|
+
var SCENE_MENU_DRAG_THRESHOLD_PX = 6;
|
|
18381
|
+
var DEFAULT_PANO_SCENE_MENU_APPEARANCE = {
|
|
18382
|
+
background: "rgba(16, 16, 16, 0.72)",
|
|
18383
|
+
padding: "8px 10px",
|
|
18384
|
+
itemWidth: 128,
|
|
18385
|
+
itemHeight: 80,
|
|
18386
|
+
gap: 8,
|
|
18387
|
+
borderRadius: 8,
|
|
18388
|
+
selectionColor: "#ffffff",
|
|
18389
|
+
selectionWidth: 2,
|
|
18390
|
+
labelBackground: "rgba(0, 0, 0, 0.55)",
|
|
18391
|
+
labelColor: "#ffffff",
|
|
18392
|
+
labelAlign: "start",
|
|
18393
|
+
fontSize: 12,
|
|
18394
|
+
controlColor: "#ffffff",
|
|
18395
|
+
controlBackground: "rgba(255, 255, 255, 0.12)"
|
|
18396
|
+
};
|
|
18397
|
+
var DEFAULT_INSET = 12;
|
|
18398
|
+
function cssLength(value) {
|
|
18399
|
+
return typeof value === "number" ? `${value}px` : value;
|
|
18400
|
+
}
|
|
18401
|
+
function resolveAppearance2(appearance) {
|
|
18331
18402
|
return {
|
|
18332
|
-
|
|
18333
|
-
|
|
18334
|
-
blocked: false,
|
|
18335
|
-
ended: false,
|
|
18336
|
-
currentTime: 0,
|
|
18337
|
-
duration: 0,
|
|
18338
|
-
volume: 1,
|
|
18339
|
-
volumeAdjustable: true,
|
|
18340
|
-
muted: true,
|
|
18341
|
-
playbackRate: 1,
|
|
18342
|
-
variantId: "default",
|
|
18343
|
-
variants: [],
|
|
18344
|
-
trackId: null,
|
|
18345
|
-
tracks: [],
|
|
18346
|
-
captionText: "",
|
|
18347
|
-
captionAppearance: DEFAULT_PANO_VIDEO_CAPTION_APPEARANCE,
|
|
18348
|
-
captionsEnabled: true,
|
|
18349
|
-
playbackRates: EMPTY_RATES,
|
|
18350
|
-
playbackState: "paused"
|
|
18403
|
+
...DEFAULT_PANO_SCENE_MENU_APPEARANCE,
|
|
18404
|
+
...appearance
|
|
18351
18405
|
};
|
|
18352
18406
|
}
|
|
18353
|
-
function
|
|
18354
|
-
if (
|
|
18355
|
-
|
|
18356
|
-
|
|
18357
|
-
if (video.ended) {
|
|
18358
|
-
return "ended";
|
|
18407
|
+
function resolveInset(inset) {
|
|
18408
|
+
if (inset === void 0) {
|
|
18409
|
+
const value = cssLength(DEFAULT_INSET);
|
|
18410
|
+
return { top: value, right: value, bottom: value, left: value };
|
|
18359
18411
|
}
|
|
18360
|
-
if (
|
|
18361
|
-
|
|
18412
|
+
if (typeof inset === "number" || typeof inset === "string") {
|
|
18413
|
+
const value = cssLength(inset);
|
|
18414
|
+
return { top: value, right: value, bottom: value, left: value };
|
|
18362
18415
|
}
|
|
18363
|
-
return
|
|
18416
|
+
return {
|
|
18417
|
+
top: cssLength(inset.top ?? DEFAULT_INSET),
|
|
18418
|
+
right: cssLength(inset.right ?? DEFAULT_INSET),
|
|
18419
|
+
bottom: cssLength(inset.bottom ?? DEFAULT_INSET),
|
|
18420
|
+
left: cssLength(inset.left ?? DEFAULT_INSET)
|
|
18421
|
+
};
|
|
18364
18422
|
}
|
|
18365
|
-
function
|
|
18366
|
-
|
|
18367
|
-
|
|
18423
|
+
function itemLabel(item) {
|
|
18424
|
+
return item.title?.trim() || item.id;
|
|
18425
|
+
}
|
|
18426
|
+
function isHorizontal(placement) {
|
|
18427
|
+
return placement === "top" || placement === "bottom";
|
|
18428
|
+
}
|
|
18429
|
+
var CHEVRON_PATHS = {
|
|
18430
|
+
// Keep glyphs optically centered in the 16×16 viewBox.
|
|
18431
|
+
up: "M3 11l5-6 5 6",
|
|
18432
|
+
down: "M3 5l5 6 5-6",
|
|
18433
|
+
left: "M11 3 5 8l6 5",
|
|
18434
|
+
right: "M5 3l6 5-6 5"
|
|
18435
|
+
};
|
|
18436
|
+
function arrowChevron(direction, orientation2) {
|
|
18437
|
+
if (direction === "prev") {
|
|
18438
|
+
return orientation2 === "horizontal" ? "left" : "up";
|
|
18368
18439
|
}
|
|
18369
|
-
|
|
18370
|
-
|
|
18371
|
-
|
|
18372
|
-
|
|
18373
|
-
|
|
18374
|
-
|
|
18375
|
-
|
|
18376
|
-
|
|
18377
|
-
|
|
18440
|
+
return orientation2 === "horizontal" ? "right" : "down";
|
|
18441
|
+
}
|
|
18442
|
+
function toggleChevron(placement, expanded) {
|
|
18443
|
+
switch (placement) {
|
|
18444
|
+
case "bottom":
|
|
18445
|
+
return expanded ? "down" : "up";
|
|
18446
|
+
case "top":
|
|
18447
|
+
return expanded ? "up" : "down";
|
|
18448
|
+
case "left":
|
|
18449
|
+
return expanded ? "left" : "right";
|
|
18450
|
+
case "right":
|
|
18451
|
+
return expanded ? "right" : "left";
|
|
18452
|
+
default: {
|
|
18453
|
+
const exhaustive = placement;
|
|
18454
|
+
void exhaustive;
|
|
18455
|
+
return "down";
|
|
18378
18456
|
}
|
|
18379
18457
|
}
|
|
18380
|
-
return parts.join("\n");
|
|
18381
18458
|
}
|
|
18382
|
-
function
|
|
18383
|
-
|
|
18384
|
-
|
|
18385
|
-
|
|
18386
|
-
|
|
18387
|
-
|
|
18388
|
-
|
|
18459
|
+
function dockInset(placement, inset) {
|
|
18460
|
+
switch (placement) {
|
|
18461
|
+
case "top":
|
|
18462
|
+
return inset.top;
|
|
18463
|
+
case "bottom":
|
|
18464
|
+
return inset.bottom;
|
|
18465
|
+
case "left":
|
|
18466
|
+
return inset.left;
|
|
18467
|
+
case "right":
|
|
18468
|
+
return inset.right;
|
|
18469
|
+
default: {
|
|
18470
|
+
const exhaustive = placement;
|
|
18471
|
+
void exhaustive;
|
|
18472
|
+
return inset.bottom;
|
|
18473
|
+
}
|
|
18389
18474
|
}
|
|
18390
|
-
|
|
18391
|
-
|
|
18392
|
-
|
|
18393
|
-
|
|
18394
|
-
|
|
18475
|
+
}
|
|
18476
|
+
function Chevron({ arrow }) {
|
|
18477
|
+
return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
18478
|
+
"svg",
|
|
18479
|
+
{
|
|
18480
|
+
"aria-hidden": true,
|
|
18481
|
+
fill: "none",
|
|
18482
|
+
height: "16",
|
|
18483
|
+
stroke: "currentColor",
|
|
18484
|
+
strokeLinecap: "round",
|
|
18485
|
+
strokeLinejoin: "round",
|
|
18486
|
+
strokeWidth: "1.75",
|
|
18487
|
+
viewBox: "0 0 16 16",
|
|
18488
|
+
width: "16",
|
|
18489
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("path", { d: CHEVRON_PATHS[arrow] })
|
|
18395
18490
|
}
|
|
18396
|
-
|
|
18397
|
-
|
|
18491
|
+
);
|
|
18492
|
+
}
|
|
18493
|
+
function ControlButton({
|
|
18494
|
+
className,
|
|
18495
|
+
disabled,
|
|
18496
|
+
label,
|
|
18497
|
+
onClick,
|
|
18498
|
+
children
|
|
18499
|
+
}) {
|
|
18500
|
+
return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
18501
|
+
"button",
|
|
18502
|
+
{
|
|
18503
|
+
"aria-label": label,
|
|
18504
|
+
className: cn("pano-scene-menu-control", className),
|
|
18505
|
+
disabled,
|
|
18506
|
+
onClick,
|
|
18507
|
+
onPointerDown: (event) => {
|
|
18508
|
+
event.stopPropagation();
|
|
18509
|
+
},
|
|
18510
|
+
title: label,
|
|
18511
|
+
type: "button",
|
|
18512
|
+
children
|
|
18513
|
+
}
|
|
18514
|
+
);
|
|
18515
|
+
}
|
|
18516
|
+
function horizontalAlignStyle(align, inset, widthValue) {
|
|
18517
|
+
const left = `calc(${inset.left} + var(--pano-safe-left))`;
|
|
18518
|
+
const right = `calc(${inset.right} + var(--pano-safe-right))`;
|
|
18519
|
+
if (!widthValue) {
|
|
18520
|
+
return { left, right, width: void 0, marginInline: void 0 };
|
|
18521
|
+
}
|
|
18522
|
+
switch (align) {
|
|
18523
|
+
case "start":
|
|
18524
|
+
return { left, right: "auto", width: widthValue, marginInline: void 0 };
|
|
18525
|
+
case "end":
|
|
18526
|
+
return { left: "auto", right, width: widthValue, marginInline: void 0 };
|
|
18527
|
+
case "center":
|
|
18528
|
+
return { left, right, width: widthValue, marginInline: "auto" };
|
|
18529
|
+
default: {
|
|
18530
|
+
const exhaustive = align;
|
|
18531
|
+
void exhaustive;
|
|
18532
|
+
return { left, right, width: widthValue, marginInline: "auto" };
|
|
18398
18533
|
}
|
|
18399
18534
|
}
|
|
18400
|
-
return list.length > 0 ? list[0] : null;
|
|
18401
18535
|
}
|
|
18402
|
-
function
|
|
18403
|
-
|
|
18404
|
-
|
|
18405
|
-
|
|
18406
|
-
|
|
18407
|
-
|
|
18408
|
-
|
|
18536
|
+
function verticalAlignStyle(align, inset, heightValue) {
|
|
18537
|
+
const top = `calc(${inset.top} + var(--pano-safe-top))`;
|
|
18538
|
+
const bottom = `calc(${inset.bottom} + var(--pano-safe-bottom))`;
|
|
18539
|
+
if (!heightValue) {
|
|
18540
|
+
return { top, bottom, height: void 0, marginBlock: void 0 };
|
|
18541
|
+
}
|
|
18542
|
+
switch (align) {
|
|
18543
|
+
case "start":
|
|
18544
|
+
return { top, bottom: "auto", height: heightValue, marginBlock: void 0 };
|
|
18545
|
+
case "end":
|
|
18546
|
+
return { top: "auto", bottom, height: heightValue, marginBlock: void 0 };
|
|
18547
|
+
case "center":
|
|
18548
|
+
return { top, bottom, height: heightValue, marginBlock: "auto" };
|
|
18549
|
+
default: {
|
|
18550
|
+
const exhaustive = align;
|
|
18551
|
+
void exhaustive;
|
|
18552
|
+
return { top, bottom, height: heightValue, marginBlock: "auto" };
|
|
18553
|
+
}
|
|
18554
|
+
}
|
|
18409
18555
|
}
|
|
18410
|
-
function
|
|
18411
|
-
const
|
|
18412
|
-
const
|
|
18413
|
-
const
|
|
18414
|
-
|
|
18415
|
-
|
|
18416
|
-
|
|
18417
|
-
|
|
18418
|
-
|
|
18419
|
-
|
|
18420
|
-
|
|
18556
|
+
function placementBoxStyle(placement, inset, width, height, align) {
|
|
18557
|
+
const widthValue = width === void 0 ? void 0 : cssLength(width);
|
|
18558
|
+
const heightValue = height === void 0 ? void 0 : cssLength(height);
|
|
18559
|
+
const maxWidth = `calc(100% - ${inset.left} - ${inset.right} - var(--pano-safe-left) - var(--pano-safe-right))`;
|
|
18560
|
+
const maxHeight = `calc(100% - ${inset.top} - ${inset.bottom} - var(--pano-safe-top) - var(--pano-safe-bottom))`;
|
|
18561
|
+
switch (placement) {
|
|
18562
|
+
case "top":
|
|
18563
|
+
return {
|
|
18564
|
+
...horizontalAlignStyle(align, inset, widthValue),
|
|
18565
|
+
top: `calc(${inset.top} + var(--pano-safe-top))`,
|
|
18566
|
+
bottom: "auto",
|
|
18567
|
+
maxWidth,
|
|
18568
|
+
height: heightValue,
|
|
18569
|
+
maxHeight
|
|
18570
|
+
};
|
|
18571
|
+
case "bottom":
|
|
18572
|
+
return {
|
|
18573
|
+
...horizontalAlignStyle(align, inset, widthValue),
|
|
18574
|
+
top: "auto",
|
|
18575
|
+
bottom: `calc(${inset.bottom} + var(--pano-safe-bottom))`,
|
|
18576
|
+
maxWidth,
|
|
18577
|
+
height: heightValue,
|
|
18578
|
+
maxHeight
|
|
18579
|
+
};
|
|
18580
|
+
case "left":
|
|
18581
|
+
return {
|
|
18582
|
+
...verticalAlignStyle(align, inset, heightValue),
|
|
18583
|
+
right: "auto",
|
|
18584
|
+
left: `calc(${inset.left} + var(--pano-safe-left))`,
|
|
18585
|
+
width: widthValue,
|
|
18586
|
+
maxWidth,
|
|
18587
|
+
maxHeight
|
|
18588
|
+
};
|
|
18589
|
+
case "right":
|
|
18590
|
+
return {
|
|
18591
|
+
...verticalAlignStyle(align, inset, heightValue),
|
|
18592
|
+
right: `calc(${inset.right} + var(--pano-safe-right))`,
|
|
18593
|
+
left: "auto",
|
|
18594
|
+
width: widthValue,
|
|
18595
|
+
maxWidth,
|
|
18596
|
+
maxHeight
|
|
18421
18597
|
};
|
|
18598
|
+
default: {
|
|
18599
|
+
const exhaustive = placement;
|
|
18600
|
+
void exhaustive;
|
|
18601
|
+
return {};
|
|
18422
18602
|
}
|
|
18423
|
-
|
|
18424
|
-
|
|
18425
|
-
|
|
18603
|
+
}
|
|
18604
|
+
}
|
|
18605
|
+
function PanoSceneMenu({
|
|
18606
|
+
items,
|
|
18607
|
+
activeId = null,
|
|
18608
|
+
onSelect,
|
|
18609
|
+
placement = "bottom",
|
|
18610
|
+
align = "center",
|
|
18611
|
+
inset,
|
|
18612
|
+
width,
|
|
18613
|
+
height,
|
|
18614
|
+
collapsible = false,
|
|
18615
|
+
defaultExpanded = true,
|
|
18616
|
+
expanded: expandedProp,
|
|
18617
|
+
onExpandedChange,
|
|
18618
|
+
showArrows = false,
|
|
18619
|
+
loop = false,
|
|
18620
|
+
dragMotion: dragMotionProp,
|
|
18621
|
+
appearance,
|
|
18622
|
+
className,
|
|
18623
|
+
style,
|
|
18624
|
+
label = "Scenes",
|
|
18625
|
+
previousLabel = "Previous scene",
|
|
18626
|
+
nextLabel = "Next scene",
|
|
18627
|
+
expandLabel = "Show scenes",
|
|
18628
|
+
collapseLabel = "Hide scenes"
|
|
18629
|
+
}) {
|
|
18630
|
+
const listId = (0, import_react62.useId)();
|
|
18631
|
+
const listRef = (0, import_react62.useRef)(null);
|
|
18632
|
+
const trackRef = (0, import_react62.useRef)(null);
|
|
18633
|
+
const itemRefs = (0, import_react62.useRef)(/* @__PURE__ */ new Map());
|
|
18634
|
+
const resolved = resolveAppearance2(appearance);
|
|
18635
|
+
const resolvedInset = resolveInset(inset);
|
|
18636
|
+
const horizontal = isHorizontal(placement);
|
|
18637
|
+
const customDrag = dragMotionProp !== false;
|
|
18638
|
+
const dragMotion = resolveDragMotion(
|
|
18639
|
+
dragMotionProp === false ? false : dragMotionProp
|
|
18640
|
+
);
|
|
18641
|
+
const isExpandedControlled = expandedProp !== void 0;
|
|
18642
|
+
const [internalExpanded, setInternalExpanded] = (0, import_react62.useState)(defaultExpanded);
|
|
18643
|
+
const expanded = collapsible ? isExpandedControlled ? expandedProp : internalExpanded : true;
|
|
18644
|
+
const [offset, setOffset] = (0, import_react62.useState)(0);
|
|
18645
|
+
const [viewportSize, setViewportSize] = (0, import_react62.useState)(0);
|
|
18646
|
+
const [contentSize, setContentSize] = (0, import_react62.useState)(0);
|
|
18647
|
+
const offsetRef = (0, import_react62.useRef)(0);
|
|
18648
|
+
const dragRef = (0, import_react62.useRef)(null);
|
|
18649
|
+
const animRef = (0, import_react62.useRef)(null);
|
|
18650
|
+
const maxOffset = Math.max(0, contentSize - viewportSize);
|
|
18651
|
+
offsetRef.current = offset;
|
|
18652
|
+
const activeIndex = (0, import_react62.useMemo)(() => {
|
|
18653
|
+
if (activeId == null) {
|
|
18654
|
+
return -1;
|
|
18655
|
+
}
|
|
18656
|
+
return items.findIndex((item) => item.id === activeId);
|
|
18657
|
+
}, [activeId, items]);
|
|
18658
|
+
const cancelScrollAnimation = (0, import_react62.useCallback)(() => {
|
|
18659
|
+
const anim = animRef.current;
|
|
18660
|
+
if (anim?.raf != null) {
|
|
18661
|
+
cancelAnimationFrame(anim.raf);
|
|
18662
|
+
}
|
|
18663
|
+
animRef.current = null;
|
|
18664
|
+
}, []);
|
|
18665
|
+
const clampOffset = (0, import_react62.useCallback)(
|
|
18666
|
+
(value) => Math.min(maxOffset, Math.max(0, value)),
|
|
18667
|
+
[maxOffset]
|
|
18668
|
+
);
|
|
18669
|
+
const startScrollAnimation = (0, import_react62.useCallback)(
|
|
18670
|
+
(velocity) => {
|
|
18671
|
+
cancelScrollAnimation();
|
|
18672
|
+
const current = offsetRef.current;
|
|
18673
|
+
const needsSpring = dragMotion.bounce && (current < 0 || current > maxOffset);
|
|
18674
|
+
const nextVelocity = dragMotion.inertia ? velocity : 0;
|
|
18675
|
+
if (!needsSpring && Math.abs(nextVelocity) < 1) {
|
|
18676
|
+
setOffset(clampOffset(current));
|
|
18677
|
+
return;
|
|
18678
|
+
}
|
|
18679
|
+
if (!dragMotion.inertia && !dragMotion.bounce) {
|
|
18680
|
+
setOffset(clampOffset(current));
|
|
18681
|
+
return;
|
|
18682
|
+
}
|
|
18683
|
+
const anim = {
|
|
18684
|
+
raf: null,
|
|
18685
|
+
lastTime: performance.now(),
|
|
18686
|
+
position: current,
|
|
18687
|
+
velocity: nextVelocity
|
|
18688
|
+
};
|
|
18689
|
+
animRef.current = anim;
|
|
18690
|
+
const tick = (now) => {
|
|
18691
|
+
if (animRef.current !== anim) return;
|
|
18692
|
+
const dt = Math.min(0.064, Math.max(0, (now - anim.lastTime) / 1e3));
|
|
18693
|
+
anim.lastTime = now;
|
|
18694
|
+
const next = integrateAxis(
|
|
18695
|
+
{ position: anim.position, velocity: anim.velocity },
|
|
18696
|
+
dt,
|
|
18697
|
+
{
|
|
18698
|
+
min: 0,
|
|
18699
|
+
max: maxOffset,
|
|
18700
|
+
inertia: dragMotion.inertia,
|
|
18701
|
+
bounce: dragMotion.bounce
|
|
18702
|
+
}
|
|
18703
|
+
);
|
|
18704
|
+
anim.position = next.position;
|
|
18705
|
+
anim.velocity = next.velocity;
|
|
18706
|
+
setOffset(next.position);
|
|
18707
|
+
if (next.settled) {
|
|
18708
|
+
setOffset(clampOffset(next.position));
|
|
18709
|
+
animRef.current = null;
|
|
18710
|
+
return;
|
|
18711
|
+
}
|
|
18712
|
+
anim.raf = requestAnimationFrame(tick);
|
|
18713
|
+
};
|
|
18714
|
+
anim.raf = requestAnimationFrame(tick);
|
|
18715
|
+
},
|
|
18716
|
+
[
|
|
18717
|
+
cancelScrollAnimation,
|
|
18718
|
+
clampOffset,
|
|
18719
|
+
dragMotion.bounce,
|
|
18720
|
+
dragMotion.inertia,
|
|
18721
|
+
maxOffset
|
|
18722
|
+
]
|
|
18723
|
+
);
|
|
18724
|
+
(0, import_react62.useEffect)(() => () => cancelScrollAnimation(), [cancelScrollAnimation]);
|
|
18725
|
+
(0, import_react62.useLayoutEffect)(() => {
|
|
18726
|
+
if (!customDrag || !expanded) return;
|
|
18727
|
+
const list = listRef.current;
|
|
18728
|
+
const track = trackRef.current;
|
|
18729
|
+
if (!list || !track) return;
|
|
18730
|
+
const measure = () => {
|
|
18731
|
+
const nextViewport = horizontal ? list.clientWidth : list.clientHeight;
|
|
18732
|
+
const nextContent = horizontal ? track.scrollWidth : track.scrollHeight;
|
|
18733
|
+
setViewportSize(
|
|
18734
|
+
(previous) => previous === nextViewport ? previous : nextViewport
|
|
18735
|
+
);
|
|
18736
|
+
setContentSize(
|
|
18737
|
+
(previous) => previous === nextContent ? previous : nextContent
|
|
18738
|
+
);
|
|
18739
|
+
};
|
|
18740
|
+
measure();
|
|
18741
|
+
const observer = new ResizeObserver(measure);
|
|
18742
|
+
observer.observe(list);
|
|
18743
|
+
observer.observe(track);
|
|
18744
|
+
return () => observer.disconnect();
|
|
18745
|
+
}, [customDrag, expanded, horizontal, items]);
|
|
18746
|
+
(0, import_react62.useEffect)(() => {
|
|
18747
|
+
if (!customDrag) return;
|
|
18748
|
+
if (dragRef.current || animRef.current) return;
|
|
18749
|
+
setOffset((previous) => Math.min(maxOffset, Math.max(0, previous)));
|
|
18750
|
+
}, [customDrag, maxOffset]);
|
|
18751
|
+
(0, import_react62.useEffect)(() => {
|
|
18752
|
+
if (!expanded || activeId == null) {
|
|
18753
|
+
return;
|
|
18754
|
+
}
|
|
18755
|
+
const node = itemRefs.current.get(activeId);
|
|
18756
|
+
if (!node) return;
|
|
18757
|
+
if (!customDrag) {
|
|
18758
|
+
node.scrollIntoView({
|
|
18759
|
+
behavior: "smooth",
|
|
18760
|
+
block: "nearest",
|
|
18761
|
+
inline: "nearest"
|
|
18762
|
+
});
|
|
18763
|
+
return;
|
|
18764
|
+
}
|
|
18765
|
+
const list = listRef.current;
|
|
18766
|
+
if (!list) return;
|
|
18767
|
+
const listRect = list.getBoundingClientRect();
|
|
18768
|
+
const itemRect = node.getBoundingClientRect();
|
|
18769
|
+
if (horizontal) {
|
|
18770
|
+
const itemStart2 = itemRect.left - listRect.left + offsetRef.current;
|
|
18771
|
+
const itemEnd2 = itemStart2 + itemRect.width;
|
|
18772
|
+
let next2 = offsetRef.current;
|
|
18773
|
+
if (itemStart2 < next2) next2 = itemStart2;
|
|
18774
|
+
else if (itemEnd2 > next2 + listRect.width) next2 = itemEnd2 - listRect.width;
|
|
18775
|
+
cancelScrollAnimation();
|
|
18776
|
+
setOffset(clampOffset(next2));
|
|
18777
|
+
return;
|
|
18778
|
+
}
|
|
18779
|
+
const itemStart = itemRect.top - listRect.top + offsetRef.current;
|
|
18780
|
+
const itemEnd = itemStart + itemRect.height;
|
|
18781
|
+
let next = offsetRef.current;
|
|
18782
|
+
if (itemStart < next) next = itemStart;
|
|
18783
|
+
else if (itemEnd > next + listRect.height) next = itemEnd - listRect.height;
|
|
18784
|
+
cancelScrollAnimation();
|
|
18785
|
+
setOffset(clampOffset(next));
|
|
18786
|
+
}, [
|
|
18787
|
+
activeId,
|
|
18788
|
+
cancelScrollAnimation,
|
|
18789
|
+
clampOffset,
|
|
18790
|
+
customDrag,
|
|
18791
|
+
expanded,
|
|
18792
|
+
horizontal
|
|
18793
|
+
]);
|
|
18794
|
+
const rootStyle = (0, import_react62.useMemo)(() => {
|
|
18795
|
+
const vars = {
|
|
18796
|
+
"--pano-scene-menu-background": resolved.background,
|
|
18797
|
+
"--pano-scene-menu-padding": cssLength(resolved.padding),
|
|
18798
|
+
"--pano-scene-menu-item-width": cssLength(resolved.itemWidth),
|
|
18799
|
+
"--pano-scene-menu-item-height": cssLength(resolved.itemHeight),
|
|
18800
|
+
"--pano-scene-menu-gap": cssLength(resolved.gap),
|
|
18801
|
+
"--pano-scene-menu-radius": cssLength(resolved.borderRadius),
|
|
18802
|
+
"--pano-scene-menu-selection-color": resolved.selectionColor,
|
|
18803
|
+
"--pano-scene-menu-selection-width": cssLength(resolved.selectionWidth),
|
|
18804
|
+
"--pano-scene-menu-label-background": resolved.labelBackground,
|
|
18805
|
+
"--pano-scene-menu-label-color": resolved.labelColor,
|
|
18806
|
+
"--pano-scene-menu-label-align": resolved.labelAlign,
|
|
18807
|
+
"--pano-scene-menu-font-size": cssLength(resolved.fontSize),
|
|
18808
|
+
"--pano-scene-menu-control-color": resolved.controlColor,
|
|
18809
|
+
"--pano-scene-menu-control-background": resolved.controlBackground,
|
|
18810
|
+
"--pano-scene-menu-dock-inset": dockInset(placement, resolvedInset)
|
|
18811
|
+
};
|
|
18812
|
+
return {
|
|
18813
|
+
...vars,
|
|
18814
|
+
...placementBoxStyle(placement, resolvedInset, width, height, align),
|
|
18815
|
+
...style
|
|
18816
|
+
};
|
|
18817
|
+
}, [align, height, placement, resolved, resolvedInset, style, width]);
|
|
18818
|
+
const setExpanded = (next) => {
|
|
18819
|
+
if (!collapsible) {
|
|
18820
|
+
return;
|
|
18821
|
+
}
|
|
18822
|
+
if (!isExpandedControlled) {
|
|
18823
|
+
setInternalExpanded(next);
|
|
18824
|
+
}
|
|
18825
|
+
onExpandedChange?.(next);
|
|
18826
|
+
};
|
|
18827
|
+
const selectAt = (index) => {
|
|
18828
|
+
const item = items[index];
|
|
18829
|
+
if (!item) {
|
|
18830
|
+
return;
|
|
18831
|
+
}
|
|
18832
|
+
onSelect?.(item.id);
|
|
18833
|
+
itemRefs.current.get(item.id)?.focus({ preventScroll: true });
|
|
18834
|
+
};
|
|
18835
|
+
const selectRelative = (delta) => {
|
|
18836
|
+
if (items.length === 0) {
|
|
18837
|
+
return;
|
|
18838
|
+
}
|
|
18839
|
+
const current = activeIndex >= 0 ? activeIndex : 0;
|
|
18840
|
+
if (loop) {
|
|
18841
|
+
const next = ((current + delta) % items.length + items.length) % items.length;
|
|
18842
|
+
selectAt(next);
|
|
18843
|
+
return;
|
|
18844
|
+
}
|
|
18845
|
+
selectAt(Math.max(0, Math.min(items.length - 1, current + delta)));
|
|
18846
|
+
};
|
|
18847
|
+
const onKeyDown = (event) => {
|
|
18848
|
+
if (items.length === 0 || !expanded) {
|
|
18849
|
+
return;
|
|
18850
|
+
}
|
|
18851
|
+
const current = activeIndex >= 0 ? activeIndex : Math.max(
|
|
18852
|
+
0,
|
|
18853
|
+
items.findIndex(
|
|
18854
|
+
(item) => itemRefs.current.get(item.id) === event.target
|
|
18855
|
+
)
|
|
18856
|
+
);
|
|
18857
|
+
const backward = horizontal ? event.key === "ArrowLeft" : event.key === "ArrowUp";
|
|
18858
|
+
const forward = horizontal ? event.key === "ArrowRight" : event.key === "ArrowDown";
|
|
18859
|
+
if (backward) {
|
|
18860
|
+
event.preventDefault();
|
|
18861
|
+
if (loop) {
|
|
18862
|
+
selectAt((current - 1 + items.length) % items.length);
|
|
18863
|
+
} else {
|
|
18864
|
+
selectAt(Math.max(0, current - 1));
|
|
18865
|
+
}
|
|
18866
|
+
return;
|
|
18867
|
+
}
|
|
18868
|
+
if (forward) {
|
|
18869
|
+
event.preventDefault();
|
|
18870
|
+
if (loop) {
|
|
18871
|
+
selectAt((current + 1) % items.length);
|
|
18872
|
+
} else {
|
|
18873
|
+
selectAt(Math.min(items.length - 1, current + 1));
|
|
18874
|
+
}
|
|
18875
|
+
return;
|
|
18876
|
+
}
|
|
18877
|
+
switch (event.key) {
|
|
18878
|
+
case "Home": {
|
|
18879
|
+
event.preventDefault();
|
|
18880
|
+
selectAt(0);
|
|
18881
|
+
break;
|
|
18882
|
+
}
|
|
18883
|
+
case "End": {
|
|
18884
|
+
event.preventDefault();
|
|
18885
|
+
selectAt(items.length - 1);
|
|
18886
|
+
break;
|
|
18887
|
+
}
|
|
18888
|
+
default:
|
|
18889
|
+
break;
|
|
18890
|
+
}
|
|
18891
|
+
};
|
|
18892
|
+
const onWheel = (event) => {
|
|
18893
|
+
const list = listRef.current;
|
|
18894
|
+
if (!list) {
|
|
18895
|
+
return;
|
|
18896
|
+
}
|
|
18897
|
+
if (!customDrag) {
|
|
18898
|
+
if (horizontal) {
|
|
18899
|
+
const canScroll2 = list.scrollWidth > list.clientWidth + 1;
|
|
18900
|
+
if (!canScroll2 || Math.abs(event.deltaY) <= Math.abs(event.deltaX)) {
|
|
18901
|
+
return;
|
|
18902
|
+
}
|
|
18903
|
+
event.preventDefault();
|
|
18904
|
+
event.stopPropagation();
|
|
18905
|
+
list.scrollLeft += event.deltaY;
|
|
18906
|
+
return;
|
|
18907
|
+
}
|
|
18908
|
+
const canScroll = list.scrollHeight > list.clientHeight + 1;
|
|
18909
|
+
if (!canScroll || Math.abs(event.deltaX) <= Math.abs(event.deltaY)) {
|
|
18910
|
+
return;
|
|
18911
|
+
}
|
|
18912
|
+
event.preventDefault();
|
|
18913
|
+
event.stopPropagation();
|
|
18914
|
+
list.scrollTop += event.deltaX;
|
|
18915
|
+
return;
|
|
18916
|
+
}
|
|
18917
|
+
if (horizontal) {
|
|
18918
|
+
if (maxOffset <= 0 || Math.abs(event.deltaY) <= Math.abs(event.deltaX)) {
|
|
18919
|
+
return;
|
|
18920
|
+
}
|
|
18921
|
+
event.preventDefault();
|
|
18922
|
+
event.stopPropagation();
|
|
18923
|
+
cancelScrollAnimation();
|
|
18924
|
+
setOffset((previous) => clampOffset(previous + event.deltaY));
|
|
18925
|
+
return;
|
|
18926
|
+
}
|
|
18927
|
+
if (maxOffset <= 0 || Math.abs(event.deltaX) <= Math.abs(event.deltaY)) {
|
|
18928
|
+
return;
|
|
18929
|
+
}
|
|
18930
|
+
event.preventDefault();
|
|
18931
|
+
event.stopPropagation();
|
|
18932
|
+
cancelScrollAnimation();
|
|
18933
|
+
setOffset((previous) => clampOffset(previous + event.deltaX));
|
|
18934
|
+
};
|
|
18935
|
+
const axisPoint = (event) => horizontal ? event.clientX : event.clientY;
|
|
18936
|
+
const endSuppressClickRef = (0, import_react62.useRef)(false);
|
|
18937
|
+
const beginListDrag = (event, captureTarget) => {
|
|
18938
|
+
if (!customDrag || event.button !== 0) return;
|
|
18939
|
+
cancelScrollAnimation();
|
|
18940
|
+
const now = performance.now();
|
|
18941
|
+
const origin = axisPoint(event);
|
|
18942
|
+
dragRef.current = {
|
|
18943
|
+
pointerId: event.pointerId,
|
|
18944
|
+
origin,
|
|
18945
|
+
startOffset: offsetRef.current,
|
|
18946
|
+
samples: [{ t: now, position: offsetRef.current }],
|
|
18947
|
+
moved: false,
|
|
18948
|
+
suppressClick: false
|
|
18949
|
+
};
|
|
18950
|
+
try {
|
|
18951
|
+
captureTarget.setPointerCapture(event.pointerId);
|
|
18952
|
+
} catch {
|
|
18953
|
+
}
|
|
18954
|
+
};
|
|
18955
|
+
const onListPointerMove = (event) => {
|
|
18956
|
+
const drag = dragRef.current;
|
|
18957
|
+
if (!drag || drag.pointerId !== event.pointerId) return;
|
|
18958
|
+
const point = axisPoint(event);
|
|
18959
|
+
const delta = point - drag.origin;
|
|
18960
|
+
if (!drag.moved && Math.abs(delta) >= SCENE_MENU_DRAG_THRESHOLD_PX) {
|
|
18961
|
+
drag.moved = true;
|
|
18962
|
+
drag.suppressClick = true;
|
|
18963
|
+
}
|
|
18964
|
+
if (!drag.moved) return;
|
|
18965
|
+
const raw = drag.startOffset - delta;
|
|
18966
|
+
const now = performance.now();
|
|
18967
|
+
drag.samples = pushSample(drag.samples, { t: now, position: raw });
|
|
18968
|
+
setOffset(
|
|
18969
|
+
constrainDragPosition(
|
|
18970
|
+
raw,
|
|
18971
|
+
0,
|
|
18972
|
+
maxOffset,
|
|
18973
|
+
Math.max(1, viewportSize),
|
|
18974
|
+
dragMotion.bounce
|
|
18975
|
+
)
|
|
18976
|
+
);
|
|
18977
|
+
event.preventDefault();
|
|
18978
|
+
};
|
|
18979
|
+
const finishPointerWithClickGuard = (event) => {
|
|
18980
|
+
const drag = dragRef.current;
|
|
18981
|
+
if (!drag || drag.pointerId !== event.pointerId) return;
|
|
18982
|
+
const suppress = drag.suppressClick;
|
|
18983
|
+
dragRef.current = null;
|
|
18984
|
+
try {
|
|
18985
|
+
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
|
|
18986
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
18987
|
+
}
|
|
18988
|
+
} catch {
|
|
18989
|
+
}
|
|
18990
|
+
if (suppress) {
|
|
18991
|
+
endSuppressClickRef.current = true;
|
|
18992
|
+
queueMicrotask(() => {
|
|
18993
|
+
endSuppressClickRef.current = false;
|
|
18994
|
+
});
|
|
18995
|
+
}
|
|
18996
|
+
if (!drag.moved) return;
|
|
18997
|
+
if (event.type === "pointercancel") {
|
|
18998
|
+
setOffset(clampOffset(offsetRef.current));
|
|
18999
|
+
return;
|
|
19000
|
+
}
|
|
19001
|
+
startScrollAnimation(velocityFromSamples(drag.samples));
|
|
19002
|
+
};
|
|
19003
|
+
const onItemClick = (event, id) => {
|
|
19004
|
+
if (endSuppressClickRef.current) {
|
|
19005
|
+
event.preventDefault();
|
|
19006
|
+
event.stopPropagation();
|
|
19007
|
+
return;
|
|
19008
|
+
}
|
|
19009
|
+
onSelect?.(id);
|
|
19010
|
+
};
|
|
19011
|
+
const canStep = items.length > 1;
|
|
19012
|
+
const canGoPrevious = canStep && (loop || activeIndex !== 0);
|
|
19013
|
+
const canGoNext = canStep && (loop || activeIndex !== items.length - 1);
|
|
19014
|
+
const orientation2 = horizontal ? "horizontal" : "vertical";
|
|
19015
|
+
const trackStyle = customDrag ? {
|
|
19016
|
+
transform: horizontal ? `translate3d(${-offset}px, 0, 0)` : `translate3d(0, ${-offset}px, 0)`,
|
|
19017
|
+
willChange: "transform"
|
|
19018
|
+
} : void 0;
|
|
19019
|
+
return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
|
|
19020
|
+
"div",
|
|
19021
|
+
{
|
|
19022
|
+
"aria-label": label,
|
|
19023
|
+
className: cn("pano-scene-menu", className),
|
|
19024
|
+
"data-align": align,
|
|
19025
|
+
"data-collapsible": collapsible ? "true" : void 0,
|
|
19026
|
+
"data-drag-motion": customDrag ? "custom" : "native",
|
|
19027
|
+
"data-expanded": expanded ? "true" : "false",
|
|
19028
|
+
"data-orientation": orientation2,
|
|
19029
|
+
"data-placement": placement,
|
|
19030
|
+
role: "group",
|
|
19031
|
+
style: rootStyle,
|
|
19032
|
+
children: [
|
|
19033
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
|
|
19034
|
+
"div",
|
|
19035
|
+
{
|
|
19036
|
+
"aria-hidden": expanded ? void 0 : true,
|
|
19037
|
+
className: "pano-scene-menu-body",
|
|
19038
|
+
...!expanded ? { inert: true } : null,
|
|
19039
|
+
children: [
|
|
19040
|
+
showArrows ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
19041
|
+
ControlButton,
|
|
19042
|
+
{
|
|
19043
|
+
className: "pano-scene-menu-arrow",
|
|
19044
|
+
disabled: !canGoPrevious || !expanded,
|
|
19045
|
+
label: previousLabel,
|
|
19046
|
+
onClick: () => selectRelative(-1),
|
|
19047
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(Chevron, { arrow: arrowChevron("prev", orientation2) })
|
|
19048
|
+
}
|
|
19049
|
+
) : null,
|
|
19050
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
19051
|
+
"div",
|
|
19052
|
+
{
|
|
19053
|
+
"aria-label": label,
|
|
19054
|
+
className: "pano-scene-menu-list",
|
|
19055
|
+
id: listId,
|
|
19056
|
+
onKeyDown,
|
|
19057
|
+
onPointerCancel: customDrag ? finishPointerWithClickGuard : void 0,
|
|
19058
|
+
onPointerDown: customDrag ? (event) => beginListDrag(event, event.currentTarget) : void 0,
|
|
19059
|
+
onPointerMove: customDrag ? onListPointerMove : void 0,
|
|
19060
|
+
onPointerUp: customDrag ? finishPointerWithClickGuard : void 0,
|
|
19061
|
+
onWheel,
|
|
19062
|
+
ref: listRef,
|
|
19063
|
+
role: "radiogroup",
|
|
19064
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
19065
|
+
"div",
|
|
19066
|
+
{
|
|
19067
|
+
className: "pano-scene-menu-track",
|
|
19068
|
+
ref: trackRef,
|
|
19069
|
+
style: trackStyle,
|
|
19070
|
+
children: items.map((item) => {
|
|
19071
|
+
const name = itemLabel(item);
|
|
19072
|
+
const selected = item.id === activeId;
|
|
19073
|
+
return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
19074
|
+
"button",
|
|
19075
|
+
{
|
|
19076
|
+
"aria-checked": selected,
|
|
19077
|
+
className: "pano-scene-menu-item",
|
|
19078
|
+
"data-active": selected ? "true" : void 0,
|
|
19079
|
+
onClick: (event) => onItemClick(event, item.id),
|
|
19080
|
+
onPointerDown: (event) => {
|
|
19081
|
+
event.stopPropagation();
|
|
19082
|
+
const list = listRef.current;
|
|
19083
|
+
if (customDrag && list) {
|
|
19084
|
+
beginListDrag(event, list);
|
|
19085
|
+
}
|
|
19086
|
+
},
|
|
19087
|
+
ref: (node) => {
|
|
19088
|
+
if (node) {
|
|
19089
|
+
itemRefs.current.set(item.id, node);
|
|
19090
|
+
} else {
|
|
19091
|
+
itemRefs.current.delete(item.id);
|
|
19092
|
+
}
|
|
19093
|
+
},
|
|
19094
|
+
role: "radio",
|
|
19095
|
+
tabIndex: selected || activeId == null && item === items[0] ? 0 : -1,
|
|
19096
|
+
title: name,
|
|
19097
|
+
type: "button",
|
|
19098
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { className: "pano-scene-menu-thumb", children: [
|
|
19099
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
19100
|
+
"img",
|
|
19101
|
+
{
|
|
19102
|
+
alt: "",
|
|
19103
|
+
decoding: "async",
|
|
19104
|
+
draggable: false,
|
|
19105
|
+
src: item.thumbnailUrl
|
|
19106
|
+
}
|
|
19107
|
+
),
|
|
19108
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "pano-scene-menu-label", children: name })
|
|
19109
|
+
] })
|
|
19110
|
+
},
|
|
19111
|
+
item.id
|
|
19112
|
+
);
|
|
19113
|
+
})
|
|
19114
|
+
}
|
|
19115
|
+
)
|
|
19116
|
+
}
|
|
19117
|
+
),
|
|
19118
|
+
showArrows ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
19119
|
+
ControlButton,
|
|
19120
|
+
{
|
|
19121
|
+
className: "pano-scene-menu-arrow",
|
|
19122
|
+
disabled: !canGoNext || !expanded,
|
|
19123
|
+
label: nextLabel,
|
|
19124
|
+
onClick: () => selectRelative(1),
|
|
19125
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(Chevron, { arrow: arrowChevron("next", orientation2) })
|
|
19126
|
+
}
|
|
19127
|
+
) : null
|
|
19128
|
+
]
|
|
19129
|
+
}
|
|
19130
|
+
),
|
|
19131
|
+
collapsible ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
19132
|
+
ControlButton,
|
|
19133
|
+
{
|
|
19134
|
+
className: "pano-scene-menu-toggle",
|
|
19135
|
+
label: expanded ? collapseLabel : expandLabel,
|
|
19136
|
+
onClick: () => setExpanded(!expanded),
|
|
19137
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(Chevron, { arrow: toggleChevron(placement, expanded) })
|
|
19138
|
+
}
|
|
19139
|
+
) : null
|
|
19140
|
+
]
|
|
19141
|
+
}
|
|
19142
|
+
);
|
|
19143
|
+
}
|
|
19144
|
+
|
|
19145
|
+
// src/use-pano-viewer.ts
|
|
19146
|
+
var import_react63 = require("react");
|
|
19147
|
+
function createPanoViewerHandle(ref) {
|
|
19148
|
+
return {
|
|
19149
|
+
getView: () => ref.current?.getView() ?? { yaw: 0, pitch: 0, fov: 75 },
|
|
19150
|
+
setView: (view, options) => {
|
|
19151
|
+
ref.current?.setView(view, options);
|
|
19152
|
+
},
|
|
19153
|
+
reset: () => {
|
|
19154
|
+
ref.current?.reset();
|
|
19155
|
+
},
|
|
19156
|
+
lookTo: (position, options) => ref.current?.lookTo(position, options) ?? Promise.resolve({ status: "cancelled" }),
|
|
19157
|
+
moveTo: (position, options) => ref.current?.moveTo(position, options) ?? Promise.resolve({ status: "cancelled" }),
|
|
19158
|
+
zoomTo: (fov, options) => ref.current?.zoomTo(fov, options) ?? Promise.resolve({ status: "cancelled" }),
|
|
19159
|
+
lookToHotspot: (id, options) => ref.current?.lookToHotspot(id, options) ?? Promise.resolve({ status: "not-found" }),
|
|
19160
|
+
activateMedia: () => ref.current?.activateMedia() ?? Promise.resolve(),
|
|
19161
|
+
enterFullscreen: () => ref.current?.enterFullscreen() ?? Promise.resolve(),
|
|
19162
|
+
exitFullscreen: () => ref.current?.exitFullscreen() ?? Promise.resolve(),
|
|
19163
|
+
toggleFullscreen: () => ref.current?.toggleFullscreen() ?? Promise.resolve(),
|
|
19164
|
+
isFullscreen: () => ref.current?.isFullscreen() ?? false,
|
|
19165
|
+
setScene: (id) => ref.current?.setScene(id) ?? false,
|
|
19166
|
+
nextScene: () => ref.current?.nextScene() ?? false,
|
|
19167
|
+
previousScene: () => ref.current?.previousScene() ?? false,
|
|
19168
|
+
getActiveSceneId: () => ref.current?.getActiveSceneId() ?? null,
|
|
19169
|
+
getSceneIds: () => ref.current?.getSceneIds() ?? [],
|
|
19170
|
+
isSceneTransitioning: () => ref.current?.isSceneTransitioning() ?? false,
|
|
19171
|
+
enterVR: () => ref.current?.enterVR() ?? Promise.resolve(false),
|
|
19172
|
+
exitVR: () => ref.current?.exitVR() ?? Promise.resolve(),
|
|
19173
|
+
toggleVR: () => ref.current?.toggleVR() ?? Promise.resolve(false),
|
|
19174
|
+
isVRAvailable: () => ref.current?.isVRAvailable() ?? false,
|
|
19175
|
+
isVREnabled: () => ref.current?.isVREnabled() ?? false,
|
|
19176
|
+
getVRMode: () => ref.current?.getVRMode() ?? null,
|
|
19177
|
+
requestVRPermission: () => ref.current?.requestVRPermission() ?? Promise.resolve(false),
|
|
19178
|
+
getVideo: () => ref.current?.getVideo() ?? null,
|
|
19179
|
+
subscribeVideo: (onStoreChange) => ref.current?.subscribeVideo(onStoreChange) ?? (() => {
|
|
19180
|
+
}),
|
|
19181
|
+
playVideo: () => ref.current?.playVideo() ?? Promise.resolve(),
|
|
19182
|
+
pauseVideo: () => {
|
|
19183
|
+
ref.current?.pauseVideo();
|
|
19184
|
+
},
|
|
19185
|
+
toggleVideo: () => {
|
|
19186
|
+
ref.current?.toggleVideo();
|
|
19187
|
+
},
|
|
19188
|
+
seekVideo: (time) => {
|
|
19189
|
+
ref.current?.seekVideo(time);
|
|
19190
|
+
},
|
|
19191
|
+
setVideoVolume: (volume) => {
|
|
19192
|
+
ref.current?.setVideoVolume(volume);
|
|
19193
|
+
},
|
|
19194
|
+
setVideoMuted: (muted) => {
|
|
19195
|
+
ref.current?.setVideoMuted(muted);
|
|
19196
|
+
},
|
|
19197
|
+
toggleVideoMuted: () => {
|
|
19198
|
+
ref.current?.toggleVideoMuted();
|
|
19199
|
+
},
|
|
19200
|
+
getBackgroundAudio: () => ref.current?.getBackgroundAudio() ?? null,
|
|
19201
|
+
subscribeBackgroundAudio: (onStoreChange) => ref.current?.subscribeBackgroundAudio(onStoreChange) ?? (() => {
|
|
19202
|
+
}),
|
|
19203
|
+
playBackgroundAudio: () => ref.current?.playBackgroundAudio() ?? Promise.resolve(),
|
|
19204
|
+
pauseBackgroundAudio: () => {
|
|
19205
|
+
ref.current?.pauseBackgroundAudio();
|
|
19206
|
+
},
|
|
19207
|
+
toggleBackgroundAudio: () => {
|
|
19208
|
+
ref.current?.toggleBackgroundAudio();
|
|
19209
|
+
},
|
|
19210
|
+
setBackgroundAudioVolume: (volume) => {
|
|
19211
|
+
ref.current?.setBackgroundAudioVolume(volume);
|
|
19212
|
+
},
|
|
19213
|
+
setBackgroundAudioMuted: (muted) => {
|
|
19214
|
+
ref.current?.setBackgroundAudioMuted(muted);
|
|
19215
|
+
},
|
|
19216
|
+
toggleBackgroundAudioMuted: () => {
|
|
19217
|
+
ref.current?.toggleBackgroundAudioMuted();
|
|
19218
|
+
},
|
|
19219
|
+
startAutoRotate: () => {
|
|
19220
|
+
ref.current?.startAutoRotate();
|
|
19221
|
+
},
|
|
19222
|
+
stopAutoRotate: () => {
|
|
19223
|
+
ref.current?.stopAutoRotate();
|
|
19224
|
+
}
|
|
19225
|
+
};
|
|
19226
|
+
}
|
|
19227
|
+
function usePanoViewer() {
|
|
19228
|
+
const ref = (0, import_react63.useRef)(null);
|
|
19229
|
+
const handle = (0, import_react63.useMemo)(() => createPanoViewerHandle(ref), []);
|
|
19230
|
+
return (0, import_react63.useMemo)(() => ({ ref, ...handle }), [handle]);
|
|
19231
|
+
}
|
|
19232
|
+
|
|
19233
|
+
// src/video/pano-video.tsx
|
|
19234
|
+
var import_fiber31 = require("@react-three/fiber");
|
|
19235
|
+
var import_react64 = require("react");
|
|
19236
|
+
var import_three40 = require("three");
|
|
19237
|
+
var import_jsx_runtime42 = require("react/jsx-runtime");
|
|
19238
|
+
var EMPTY_TRACKS = [];
|
|
19239
|
+
var EMPTY_RATES = DEFAULT_PANO_VIDEO_PLAYBACK_RATES;
|
|
19240
|
+
function createEmptySnapshot() {
|
|
19241
|
+
return {
|
|
19242
|
+
ready: false,
|
|
19243
|
+
playing: false,
|
|
19244
|
+
blocked: false,
|
|
19245
|
+
ended: false,
|
|
19246
|
+
currentTime: 0,
|
|
19247
|
+
duration: 0,
|
|
19248
|
+
volume: 1,
|
|
19249
|
+
volumeAdjustable: true,
|
|
19250
|
+
muted: true,
|
|
19251
|
+
playbackRate: 1,
|
|
19252
|
+
variantId: "default",
|
|
19253
|
+
variants: [],
|
|
19254
|
+
trackId: null,
|
|
19255
|
+
tracks: [],
|
|
19256
|
+
captionText: "",
|
|
19257
|
+
captionAppearance: DEFAULT_PANO_VIDEO_CAPTION_APPEARANCE,
|
|
19258
|
+
captionsEnabled: true,
|
|
19259
|
+
playbackRates: EMPTY_RATES,
|
|
19260
|
+
playbackState: "paused"
|
|
19261
|
+
};
|
|
19262
|
+
}
|
|
19263
|
+
function playbackStateFromVideo(video, blocked) {
|
|
19264
|
+
if (blocked) {
|
|
19265
|
+
return "blocked";
|
|
19266
|
+
}
|
|
19267
|
+
if (video.ended) {
|
|
19268
|
+
return "ended";
|
|
19269
|
+
}
|
|
19270
|
+
if (!video.paused) {
|
|
19271
|
+
return "playing";
|
|
19272
|
+
}
|
|
19273
|
+
return "paused";
|
|
19274
|
+
}
|
|
19275
|
+
function collectCaptionText(track) {
|
|
19276
|
+
if (!track?.activeCues || track.activeCues.length === 0) {
|
|
19277
|
+
return "";
|
|
19278
|
+
}
|
|
19279
|
+
const parts = [];
|
|
19280
|
+
for (let index = 0; index < track.activeCues.length; index += 1) {
|
|
19281
|
+
const cue = track.activeCues[index];
|
|
19282
|
+
if (!cue) {
|
|
19283
|
+
continue;
|
|
19284
|
+
}
|
|
19285
|
+
const text = cueText(cue);
|
|
19286
|
+
if (text) {
|
|
19287
|
+
parts.push(text);
|
|
19288
|
+
}
|
|
19289
|
+
}
|
|
19290
|
+
return parts.join("\n");
|
|
19291
|
+
}
|
|
19292
|
+
function findTextTrack(video, tracks, trackId) {
|
|
19293
|
+
if (!trackId) {
|
|
19294
|
+
return null;
|
|
19295
|
+
}
|
|
19296
|
+
const configured = tracks.find((track) => panoVideoTrackId(track) === trackId);
|
|
19297
|
+
if (!configured) {
|
|
19298
|
+
return null;
|
|
19299
|
+
}
|
|
19300
|
+
const list = video.textTracks;
|
|
19301
|
+
for (let index = 0; index < list.length; index += 1) {
|
|
19302
|
+
const textTrack = list[index];
|
|
19303
|
+
if (!textTrack) {
|
|
19304
|
+
continue;
|
|
19305
|
+
}
|
|
19306
|
+
if (textTrack.language === configured.srcLang || textTrack.label === configured.label) {
|
|
19307
|
+
return textTrack;
|
|
19308
|
+
}
|
|
19309
|
+
}
|
|
19310
|
+
return list.length > 0 ? list[0] : null;
|
|
19311
|
+
}
|
|
19312
|
+
function applyEquirectangularLayout(texture, anisotropy) {
|
|
19313
|
+
texture.colorSpace = import_three40.SRGBColorSpace;
|
|
19314
|
+
texture.wrapS = import_three40.RepeatWrapping;
|
|
19315
|
+
texture.repeat.x = -1;
|
|
19316
|
+
texture.offset.x = 1;
|
|
19317
|
+
texture.anisotropy = anisotropy;
|
|
19318
|
+
texture.needsUpdate = true;
|
|
19319
|
+
}
|
|
19320
|
+
function usePosterTexture2(poster, onError) {
|
|
19321
|
+
const gl = (0, import_fiber31.useThree)((state) => state.gl);
|
|
19322
|
+
const [texture, setTexture] = (0, import_react64.useState)(null);
|
|
19323
|
+
const onErrorRef = (0, import_react64.useRef)(onError);
|
|
19324
|
+
onErrorRef.current = onError;
|
|
19325
|
+
(0, import_react64.useEffect)(() => {
|
|
19326
|
+
let active = true;
|
|
19327
|
+
setTexture(null);
|
|
19328
|
+
if (!poster) {
|
|
19329
|
+
return () => {
|
|
19330
|
+
active = false;
|
|
19331
|
+
};
|
|
19332
|
+
}
|
|
19333
|
+
const loader = new import_three40.TextureLoader();
|
|
19334
|
+
loader.load(
|
|
19335
|
+
poster,
|
|
18426
19336
|
(loadedTexture) => {
|
|
18427
19337
|
if (!active) {
|
|
18428
19338
|
loadedTexture.dispose();
|
|
@@ -18449,7 +19359,7 @@ function usePosterTexture2(poster, onError) {
|
|
|
18449
19359
|
active = false;
|
|
18450
19360
|
};
|
|
18451
19361
|
}, [gl, poster]);
|
|
18452
|
-
(0,
|
|
19362
|
+
(0, import_react64.useEffect)(
|
|
18453
19363
|
() => () => {
|
|
18454
19364
|
texture?.dispose();
|
|
18455
19365
|
},
|
|
@@ -18508,37 +19418,37 @@ function PanoVideo({
|
|
|
18508
19418
|
}) {
|
|
18509
19419
|
usePanoMediaActivationIntent(autoPlay);
|
|
18510
19420
|
const gl = (0, import_fiber31.useThree)((state) => state.gl);
|
|
18511
|
-
const host = (0,
|
|
18512
|
-
const variants = (0,
|
|
19421
|
+
const host = (0, import_react64.useContext)(PanoVideoHostContext);
|
|
19422
|
+
const variants = (0, import_react64.useMemo)(
|
|
18513
19423
|
() => resolvePanoVideoVariants({ poster, src, variants: variantsProp }),
|
|
18514
19424
|
[poster, src, variantsProp]
|
|
18515
19425
|
);
|
|
18516
19426
|
const tracks = tracksProp ?? EMPTY_TRACKS;
|
|
18517
19427
|
const captionsEnabled = captions !== false;
|
|
18518
|
-
const captionAppearance = (0,
|
|
19428
|
+
const captionAppearance = (0, import_react64.useMemo)(
|
|
18519
19429
|
() => resolvePanoVideoCaptionAppearance(
|
|
18520
19430
|
typeof captions === "object" ? captions : void 0
|
|
18521
19431
|
),
|
|
18522
19432
|
[captions]
|
|
18523
19433
|
);
|
|
18524
|
-
const [variantId, setVariantId] = (0,
|
|
19434
|
+
const [variantId, setVariantId] = (0, import_react64.useState)(
|
|
18525
19435
|
() => resolvePanoVideoVariantId(variants, defaultVariantId)
|
|
18526
19436
|
);
|
|
18527
|
-
const [trackId, setTrackId] = (0,
|
|
19437
|
+
const [trackId, setTrackId] = (0, import_react64.useState)(
|
|
18528
19438
|
() => captionsEnabled ? defaultPanoVideoTrackId(tracks) : null
|
|
18529
19439
|
);
|
|
18530
|
-
const [ready, setReady] = (0,
|
|
18531
|
-
const autoPlayRef = (0,
|
|
19440
|
+
const [ready, setReady] = (0, import_react64.useState)(false);
|
|
19441
|
+
const autoPlayRef = (0, import_react64.useRef)(autoPlay);
|
|
18532
19442
|
autoPlayRef.current = autoPlay;
|
|
18533
|
-
const blockedRef = (0,
|
|
18534
|
-
const captionAppearanceOverrideRef = (0,
|
|
19443
|
+
const blockedRef = (0, import_react64.useRef)(false);
|
|
19444
|
+
const captionAppearanceOverrideRef = (0, import_react64.useRef)(
|
|
18535
19445
|
{}
|
|
18536
19446
|
);
|
|
18537
|
-
const mediaCapabilitiesRef = (0,
|
|
19447
|
+
const mediaCapabilitiesRef = (0, import_react64.useRef)({
|
|
18538
19448
|
volumeAdjustable: true,
|
|
18539
19449
|
playbackRates: [...DEFAULT_PANO_VIDEO_PLAYBACK_RATES]
|
|
18540
19450
|
});
|
|
18541
|
-
const storeRef = (0,
|
|
19451
|
+
const storeRef = (0, import_react64.useRef)(null);
|
|
18542
19452
|
if (!storeRef.current) {
|
|
18543
19453
|
storeRef.current = {
|
|
18544
19454
|
snapshot: {
|
|
@@ -18548,7 +19458,7 @@ function PanoVideo({
|
|
|
18548
19458
|
listeners: /* @__PURE__ */ new Set()
|
|
18549
19459
|
};
|
|
18550
19460
|
}
|
|
18551
|
-
const actionsRef = (0,
|
|
19461
|
+
const actionsRef = (0, import_react64.useRef)({
|
|
18552
19462
|
play: async () => {
|
|
18553
19463
|
},
|
|
18554
19464
|
pause: () => {
|
|
@@ -18568,19 +19478,19 @@ function PanoVideo({
|
|
|
18568
19478
|
setCaptionAppearance: (_appearance) => {
|
|
18569
19479
|
}
|
|
18570
19480
|
});
|
|
18571
|
-
const onLoadRef = (0,
|
|
18572
|
-
const onErrorRef = (0,
|
|
18573
|
-
const onPlaybackStateChangeRef = (0,
|
|
18574
|
-
const onEndedRef = (0,
|
|
18575
|
-
const onVariantChangeRef = (0,
|
|
18576
|
-
const onTrackChangeRef = (0,
|
|
19481
|
+
const onLoadRef = (0, import_react64.useRef)(onLoad);
|
|
19482
|
+
const onErrorRef = (0, import_react64.useRef)(onError);
|
|
19483
|
+
const onPlaybackStateChangeRef = (0, import_react64.useRef)(onPlaybackStateChange);
|
|
19484
|
+
const onEndedRef = (0, import_react64.useRef)(onEnded);
|
|
19485
|
+
const onVariantChangeRef = (0, import_react64.useRef)(onVariantChange);
|
|
19486
|
+
const onTrackChangeRef = (0, import_react64.useRef)(onTrackChange);
|
|
18577
19487
|
onLoadRef.current = onLoad;
|
|
18578
19488
|
onErrorRef.current = onError;
|
|
18579
19489
|
onPlaybackStateChangeRef.current = onPlaybackStateChange;
|
|
18580
19490
|
onEndedRef.current = onEnded;
|
|
18581
19491
|
onVariantChangeRef.current = onVariantChange;
|
|
18582
19492
|
onTrackChangeRef.current = onTrackChange;
|
|
18583
|
-
const resource = (0,
|
|
19493
|
+
const resource = (0, import_react64.useMemo)(() => {
|
|
18584
19494
|
if (typeof document === "undefined") {
|
|
18585
19495
|
return null;
|
|
18586
19496
|
}
|
|
@@ -18595,7 +19505,7 @@ function PanoVideo({
|
|
|
18595
19505
|
texture.generateMipmaps = false;
|
|
18596
19506
|
return { video, texture };
|
|
18597
19507
|
}, []);
|
|
18598
|
-
(0,
|
|
19508
|
+
(0, import_react64.useEffect)(
|
|
18599
19509
|
() => () => {
|
|
18600
19510
|
if (!resource) {
|
|
18601
19511
|
return;
|
|
@@ -18610,13 +19520,13 @@ function PanoVideo({
|
|
|
18610
19520
|
},
|
|
18611
19521
|
[resource]
|
|
18612
19522
|
);
|
|
18613
|
-
(0,
|
|
19523
|
+
(0, import_react64.useEffect)(() => {
|
|
18614
19524
|
if (!resource) {
|
|
18615
19525
|
return;
|
|
18616
19526
|
}
|
|
18617
19527
|
resource.texture.anisotropy = gl.capabilities.getMaxAnisotropy();
|
|
18618
19528
|
}, [gl, resource]);
|
|
18619
|
-
(0,
|
|
19529
|
+
(0, import_react64.useLayoutEffect)(() => {
|
|
18620
19530
|
if (!resource) {
|
|
18621
19531
|
return;
|
|
18622
19532
|
}
|
|
@@ -18632,7 +19542,7 @@ function PanoVideo({
|
|
|
18632
19542
|
activeVariant?.poster ?? poster,
|
|
18633
19543
|
onError
|
|
18634
19544
|
);
|
|
18635
|
-
const emitSnapshot = (0,
|
|
19545
|
+
const emitSnapshot = (0, import_react64.useCallback)(
|
|
18636
19546
|
(patch) => {
|
|
18637
19547
|
const store = storeRef.current;
|
|
18638
19548
|
const video = resource?.video;
|
|
@@ -18684,7 +19594,7 @@ function PanoVideo({
|
|
|
18684
19594
|
captionAppearance
|
|
18685
19595
|
]
|
|
18686
19596
|
);
|
|
18687
|
-
const controller = (0,
|
|
19597
|
+
const controller = (0, import_react64.useMemo)(
|
|
18688
19598
|
() => ({
|
|
18689
19599
|
subscribe: (onStoreChange) => {
|
|
18690
19600
|
const store = storeRef.current;
|
|
@@ -18736,7 +19646,7 @@ function PanoVideo({
|
|
|
18736
19646
|
}),
|
|
18737
19647
|
[]
|
|
18738
19648
|
);
|
|
18739
|
-
(0,
|
|
19649
|
+
(0, import_react64.useLayoutEffect)(() => {
|
|
18740
19650
|
if (!host || variants.length === 0) {
|
|
18741
19651
|
return;
|
|
18742
19652
|
}
|
|
@@ -18749,7 +19659,7 @@ function PanoVideo({
|
|
|
18749
19659
|
notifyPanoVideoHost(host);
|
|
18750
19660
|
};
|
|
18751
19661
|
}, [captionsEnabled, controller, controls, host, variants.length]);
|
|
18752
|
-
(0,
|
|
19662
|
+
(0, import_react64.useLayoutEffect)(() => {
|
|
18753
19663
|
if (!resource) {
|
|
18754
19664
|
return;
|
|
18755
19665
|
}
|
|
@@ -18762,10 +19672,10 @@ function PanoVideo({
|
|
|
18762
19672
|
};
|
|
18763
19673
|
emitSnapshot();
|
|
18764
19674
|
}, [emitSnapshot, playbackRates, resource]);
|
|
18765
|
-
(0,
|
|
19675
|
+
(0, import_react64.useEffect)(() => {
|
|
18766
19676
|
emitSnapshot();
|
|
18767
19677
|
}, [emitSnapshot]);
|
|
18768
|
-
(0,
|
|
19678
|
+
(0, import_react64.useEffect)(() => {
|
|
18769
19679
|
if (!resource || !activeVariant || !mediaKey) {
|
|
18770
19680
|
return;
|
|
18771
19681
|
}
|
|
@@ -18822,7 +19732,7 @@ function PanoVideo({
|
|
|
18822
19732
|
video.removeEventListener("error", handleError);
|
|
18823
19733
|
};
|
|
18824
19734
|
}, [mediaKey, resource]);
|
|
18825
|
-
(0,
|
|
19735
|
+
(0, import_react64.useEffect)(() => {
|
|
18826
19736
|
if (!resource) {
|
|
18827
19737
|
return;
|
|
18828
19738
|
}
|
|
@@ -18914,137 +19824,1292 @@ function PanoVideo({
|
|
|
18914
19824
|
}
|
|
18915
19825
|
};
|
|
18916
19826
|
}, [emitSnapshot, resource]);
|
|
18917
|
-
(0,
|
|
19827
|
+
(0, import_react64.useLayoutEffect)(() => {
|
|
19828
|
+
if (!resource) {
|
|
19829
|
+
return;
|
|
19830
|
+
}
|
|
19831
|
+
const { video } = resource;
|
|
19832
|
+
video.loop = loop;
|
|
19833
|
+
video.muted = muted;
|
|
19834
|
+
if (mediaCapabilitiesRef.current.volumeAdjustable) {
|
|
19835
|
+
video.volume = clampVolume(volume);
|
|
19836
|
+
}
|
|
19837
|
+
applyInlineVideoPlayback(video, playsInline);
|
|
19838
|
+
video.preload = preload;
|
|
19839
|
+
}, [loop, muted, playsInline, preload, resource, volume]);
|
|
19840
|
+
(0, import_react64.useEffect)(() => {
|
|
19841
|
+
if (!resource) {
|
|
19842
|
+
return;
|
|
19843
|
+
}
|
|
19844
|
+
const { video } = resource;
|
|
19845
|
+
let active = true;
|
|
19846
|
+
const handleTimeUpdate = () => {
|
|
19847
|
+
if (active) {
|
|
19848
|
+
emitSnapshot();
|
|
19849
|
+
}
|
|
19850
|
+
};
|
|
19851
|
+
const handlePlay = () => {
|
|
19852
|
+
blockedRef.current = false;
|
|
19853
|
+
onPlaybackStateChangeRef.current?.("playing");
|
|
19854
|
+
emitSnapshot();
|
|
19855
|
+
};
|
|
19856
|
+
const handlePause = () => {
|
|
19857
|
+
onPlaybackStateChangeRef.current?.(
|
|
19858
|
+
video.ended ? "ended" : "paused"
|
|
19859
|
+
);
|
|
19860
|
+
emitSnapshot();
|
|
19861
|
+
};
|
|
19862
|
+
const handleEnded = () => {
|
|
19863
|
+
onPlaybackStateChangeRef.current?.("ended");
|
|
19864
|
+
onEndedRef.current?.();
|
|
19865
|
+
emitSnapshot();
|
|
19866
|
+
};
|
|
19867
|
+
const handleVolume = () => {
|
|
19868
|
+
emitSnapshot();
|
|
19869
|
+
};
|
|
19870
|
+
video.addEventListener("timeupdate", handleTimeUpdate);
|
|
19871
|
+
video.addEventListener("durationchange", handleTimeUpdate);
|
|
19872
|
+
video.addEventListener("progress", handleTimeUpdate);
|
|
19873
|
+
video.addEventListener("play", handlePlay);
|
|
19874
|
+
video.addEventListener("pause", handlePause);
|
|
19875
|
+
video.addEventListener("ended", handleEnded);
|
|
19876
|
+
video.addEventListener("volumechange", handleVolume);
|
|
19877
|
+
video.addEventListener("ratechange", handleVolume);
|
|
19878
|
+
return () => {
|
|
19879
|
+
active = false;
|
|
19880
|
+
video.removeEventListener("timeupdate", handleTimeUpdate);
|
|
19881
|
+
video.removeEventListener("durationchange", handleTimeUpdate);
|
|
19882
|
+
video.removeEventListener("progress", handleTimeUpdate);
|
|
19883
|
+
video.removeEventListener("play", handlePlay);
|
|
19884
|
+
video.removeEventListener("pause", handlePause);
|
|
19885
|
+
video.removeEventListener("ended", handleEnded);
|
|
19886
|
+
video.removeEventListener("volumechange", handleVolume);
|
|
19887
|
+
video.removeEventListener("ratechange", handleVolume);
|
|
19888
|
+
};
|
|
19889
|
+
}, [emitSnapshot, resource]);
|
|
19890
|
+
(0, import_react64.useEffect)(() => {
|
|
18918
19891
|
if (!resource) {
|
|
18919
19892
|
return;
|
|
18920
19893
|
}
|
|
18921
|
-
const { video } = resource;
|
|
18922
|
-
|
|
18923
|
-
|
|
18924
|
-
|
|
18925
|
-
|
|
18926
|
-
|
|
18927
|
-
|
|
18928
|
-
|
|
18929
|
-
|
|
18930
|
-
|
|
18931
|
-
|
|
19894
|
+
const { video } = resource;
|
|
19895
|
+
const applyModes = () => {
|
|
19896
|
+
const selected2 = findTextTrack(video, tracks, trackId);
|
|
19897
|
+
const list = video.textTracks;
|
|
19898
|
+
for (let index = 0; index < list.length; index += 1) {
|
|
19899
|
+
const textTrack = list[index];
|
|
19900
|
+
if (!textTrack) {
|
|
19901
|
+
continue;
|
|
19902
|
+
}
|
|
19903
|
+
textTrack.mode = textTrack === selected2 ? "hidden" : "disabled";
|
|
19904
|
+
}
|
|
19905
|
+
emitSnapshot({ captionText: collectCaptionText(selected2) });
|
|
19906
|
+
};
|
|
19907
|
+
applyModes();
|
|
19908
|
+
const selected = findTextTrack(video, tracks, trackId);
|
|
19909
|
+
const handleCueChange = () => {
|
|
19910
|
+
emitSnapshot({
|
|
19911
|
+
captionText: collectCaptionText(
|
|
19912
|
+
findTextTrack(video, tracks, trackId)
|
|
19913
|
+
)
|
|
19914
|
+
});
|
|
19915
|
+
};
|
|
19916
|
+
selected?.addEventListener("cuechange", handleCueChange);
|
|
19917
|
+
video.textTracks.addEventListener("addtrack", applyModes);
|
|
19918
|
+
video.textTracks.addEventListener("change", applyModes);
|
|
19919
|
+
return () => {
|
|
19920
|
+
selected?.removeEventListener("cuechange", handleCueChange);
|
|
19921
|
+
video.textTracks.removeEventListener("addtrack", applyModes);
|
|
19922
|
+
video.textTracks.removeEventListener("change", applyModes);
|
|
19923
|
+
};
|
|
19924
|
+
}, [emitSnapshot, resource, trackId, tracks]);
|
|
19925
|
+
(0, import_react64.useEffect)(() => {
|
|
19926
|
+
if (!captionsEnabled) {
|
|
19927
|
+
setTrackId(null);
|
|
19928
|
+
return;
|
|
19929
|
+
}
|
|
19930
|
+
setTrackId(defaultPanoVideoTrackId(tracks));
|
|
19931
|
+
}, [captionsEnabled, tracks]);
|
|
19932
|
+
const activeTexture = ready && resource ? resource.texture : posterTexture;
|
|
19933
|
+
if (variants.length === 0) {
|
|
19934
|
+
return null;
|
|
19935
|
+
}
|
|
19936
|
+
return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_jsx_runtime42.Fragment, { children: [
|
|
19937
|
+
activeTexture ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
|
|
19938
|
+
"mesh",
|
|
19939
|
+
{
|
|
19940
|
+
rotation: [0, import_three40.MathUtils.degToRad(90 + yawOffset2), 0],
|
|
19941
|
+
visible,
|
|
19942
|
+
children: [
|
|
19943
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)("sphereGeometry", { args: [DEFAULT_PANORAMA_RADIUS, 128, 64] }),
|
|
19944
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
19945
|
+
PanoBasicMaterial,
|
|
19946
|
+
{
|
|
19947
|
+
map: activeTexture,
|
|
19948
|
+
side: import_three40.BackSide,
|
|
19949
|
+
toneMapped: false
|
|
19950
|
+
}
|
|
19951
|
+
)
|
|
19952
|
+
]
|
|
19953
|
+
}
|
|
19954
|
+
) : null,
|
|
19955
|
+
captionsEnabled ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(PanoVideoCaptions, { appearance: typeof captions === "object" ? captions : void 0 }) : null
|
|
19956
|
+
] });
|
|
19957
|
+
}
|
|
19958
|
+
|
|
19959
|
+
// src/map/pano-map.tsx
|
|
19960
|
+
var import_react66 = require("react");
|
|
19961
|
+
|
|
19962
|
+
// src/map/geometry.ts
|
|
19963
|
+
function sectorRotation(heading, yaw) {
|
|
19964
|
+
return heading + yaw;
|
|
19965
|
+
}
|
|
19966
|
+
function horizontalFov(verticalFov, aspect) {
|
|
19967
|
+
if (!(aspect > 0) || !(verticalFov > 0) || verticalFov >= 180) return verticalFov;
|
|
19968
|
+
const verticalRad = verticalFov * Math.PI / 180;
|
|
19969
|
+
const horizontalRad = 2 * Math.atan(Math.tan(verticalRad / 2) * aspect);
|
|
19970
|
+
return horizontalRad * 180 / Math.PI;
|
|
19971
|
+
}
|
|
19972
|
+
function containRect(containerWidth, containerHeight, imageWidth, imageHeight) {
|
|
19973
|
+
if (!(containerWidth > 0) || !(containerHeight > 0) || !(imageWidth > 0) || !(imageHeight > 0)) {
|
|
19974
|
+
return { left: 0, top: 0, width: 0, height: 0 };
|
|
19975
|
+
}
|
|
19976
|
+
const scale = Math.min(
|
|
19977
|
+
containerWidth / imageWidth,
|
|
19978
|
+
containerHeight / imageHeight
|
|
19979
|
+
);
|
|
19980
|
+
const width = imageWidth * scale;
|
|
19981
|
+
const height = imageHeight * scale;
|
|
19982
|
+
return {
|
|
19983
|
+
left: (containerWidth - width) / 2,
|
|
19984
|
+
top: (containerHeight - height) / 2,
|
|
19985
|
+
width,
|
|
19986
|
+
height
|
|
19987
|
+
};
|
|
19988
|
+
}
|
|
19989
|
+
function imagePointToContainer(point, viewport) {
|
|
19990
|
+
const { fit, scale, panX, panY } = viewport;
|
|
19991
|
+
return {
|
|
19992
|
+
x: fit.left + panX + point.x * fit.width * scale,
|
|
19993
|
+
y: fit.top + panY + point.y * fit.height * scale
|
|
19994
|
+
};
|
|
19995
|
+
}
|
|
19996
|
+
function containerPointToImage(point, viewport) {
|
|
19997
|
+
const { fit, scale, panX, panY } = viewport;
|
|
19998
|
+
const width = fit.width * scale;
|
|
19999
|
+
const height = fit.height * scale;
|
|
20000
|
+
if (!(width > 0) || !(height > 0)) return null;
|
|
20001
|
+
return {
|
|
20002
|
+
x: (point.x - fit.left - panX) / width,
|
|
20003
|
+
y: (point.y - fit.top - panY) / height
|
|
20004
|
+
};
|
|
20005
|
+
}
|
|
20006
|
+
function clamp012(value) {
|
|
20007
|
+
return Math.min(1, Math.max(0, value));
|
|
20008
|
+
}
|
|
20009
|
+
function headingFromVector(dx, dy) {
|
|
20010
|
+
if (dx === 0 && dy === 0) return 0;
|
|
20011
|
+
const radians = Math.atan2(dx, -dy);
|
|
20012
|
+
return radians * 180 / Math.PI;
|
|
20013
|
+
}
|
|
20014
|
+
function normalizeDegrees(value) {
|
|
20015
|
+
const wrapped = (value % 360 + 360) % 360;
|
|
20016
|
+
return wrapped > 180 ? wrapped - 360 : wrapped;
|
|
20017
|
+
}
|
|
20018
|
+
|
|
20019
|
+
// src/map/use-map-view.ts
|
|
20020
|
+
var import_react65 = require("react");
|
|
20021
|
+
var DEFAULT_VIEW2 = { yaw: 0, pitch: 0, fov: 75 };
|
|
20022
|
+
function useMapView(controlled) {
|
|
20023
|
+
const eventBus = (0, import_react65.useContext)(PanoEventBusContext);
|
|
20024
|
+
const [view, setView] = (0, import_react65.useState)(controlled ?? DEFAULT_VIEW2);
|
|
20025
|
+
const controlledRef = (0, import_react65.useRef)(controlled);
|
|
20026
|
+
controlledRef.current = controlled;
|
|
20027
|
+
(0, import_react65.useEffect)(() => {
|
|
20028
|
+
if (controlled !== void 0) {
|
|
20029
|
+
setView(controlled);
|
|
20030
|
+
return;
|
|
20031
|
+
}
|
|
20032
|
+
if (!eventBus) return;
|
|
20033
|
+
return eventBus.subscribe("viewchange", (next) => {
|
|
20034
|
+
if (controlledRef.current !== void 0) return;
|
|
20035
|
+
setView(next);
|
|
20036
|
+
});
|
|
20037
|
+
}, [controlled, eventBus]);
|
|
20038
|
+
return controlled ?? view;
|
|
20039
|
+
}
|
|
20040
|
+
|
|
20041
|
+
// src/map/pano-map.tsx
|
|
20042
|
+
var import_jsx_runtime43 = require("react/jsx-runtime");
|
|
20043
|
+
var DEFAULT_ICON_SIZE2 = 28;
|
|
20044
|
+
var DEFAULT_SECTOR_COLOR = "#ffffff";
|
|
20045
|
+
var DEFAULT_SECTOR_OPACITY = 0.55;
|
|
20046
|
+
var MIN_SCALE = 1;
|
|
20047
|
+
var DEFAULT_MAX_SCALE = 8;
|
|
20048
|
+
var ZOOM_STEP = 1.25;
|
|
20049
|
+
function defaultGradientId(prefix) {
|
|
20050
|
+
return `${prefix}-sector-gradient`;
|
|
20051
|
+
}
|
|
20052
|
+
function SectorShape({
|
|
20053
|
+
sector,
|
|
20054
|
+
angle,
|
|
20055
|
+
rotation,
|
|
20056
|
+
gradientId,
|
|
20057
|
+
interactive,
|
|
20058
|
+
onHeadingPointerDown,
|
|
20059
|
+
transformRef
|
|
20060
|
+
}) {
|
|
20061
|
+
const half = angle / 2;
|
|
20062
|
+
const start = (-half - 90) * Math.PI / 180;
|
|
20063
|
+
const end = (half - 90) * Math.PI / 180;
|
|
20064
|
+
const radius = sector.radius;
|
|
20065
|
+
const largeArc = angle > 180 ? 1 : 0;
|
|
20066
|
+
const x1 = Math.cos(start) * radius;
|
|
20067
|
+
const y1 = Math.sin(start) * radius;
|
|
20068
|
+
const x2 = Math.cos(end) * radius;
|
|
20069
|
+
const y2 = Math.sin(end) * radius;
|
|
20070
|
+
const path = `M 0 0 L ${x1} ${y1} A ${radius} ${radius} 0 ${largeArc} 1 ${x2} ${y2} Z`;
|
|
20071
|
+
const opacity = sector.opacity ?? DEFAULT_SECTOR_OPACITY;
|
|
20072
|
+
const color = sector.color ?? DEFAULT_SECTOR_COLOR;
|
|
20073
|
+
const gradient = sector.gradient;
|
|
20074
|
+
const strokeWidth = sector.strokeWidth ?? 0;
|
|
20075
|
+
const strokeColor = sector.strokeColor ?? color;
|
|
20076
|
+
const strokeOpacity = sector.strokeOpacity ?? 1;
|
|
20077
|
+
const gradientStrength = gradient && gradient.stops.length > 0 ? Math.max(
|
|
20078
|
+
...gradient.stops.map((stop) => stop.opacity ?? 1),
|
|
20079
|
+
Number.EPSILON
|
|
20080
|
+
) : 1;
|
|
20081
|
+
return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("g", { ref: transformRef, transform: `rotate(${rotation})`, style: { pointerEvents: "none" }, children: [
|
|
20082
|
+
gradient ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("defs", { children: gradient.type === "radial" ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("radialGradient", { id: gradientId, cx: "0", cy: "0", r: radius, gradientUnits: "userSpaceOnUse", children: gradient.stops.map((stop, index) => /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
20083
|
+
"stop",
|
|
20084
|
+
{
|
|
20085
|
+
offset: `${stop.offset * 100}%`,
|
|
20086
|
+
stopColor: stop.color,
|
|
20087
|
+
stopOpacity: (stop.opacity ?? 1) / gradientStrength
|
|
20088
|
+
},
|
|
20089
|
+
`${stop.offset}:${index}`
|
|
20090
|
+
)) }) : /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
20091
|
+
"linearGradient",
|
|
20092
|
+
{
|
|
20093
|
+
id: gradientId,
|
|
20094
|
+
x1: "0",
|
|
20095
|
+
y1: "0",
|
|
20096
|
+
x2: "0",
|
|
20097
|
+
y2: -radius,
|
|
20098
|
+
gradientUnits: "userSpaceOnUse",
|
|
20099
|
+
children: gradient.stops.map((stop, index) => /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
20100
|
+
"stop",
|
|
20101
|
+
{
|
|
20102
|
+
offset: `${stop.offset * 100}%`,
|
|
20103
|
+
stopColor: stop.color,
|
|
20104
|
+
stopOpacity: (stop.opacity ?? 1) / gradientStrength
|
|
20105
|
+
},
|
|
20106
|
+
`${stop.offset}:${index}`
|
|
20107
|
+
))
|
|
20108
|
+
}
|
|
20109
|
+
) }) : null,
|
|
20110
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
20111
|
+
"path",
|
|
20112
|
+
{
|
|
20113
|
+
d: path,
|
|
20114
|
+
fill: gradient ? `url(#${gradientId})` : color,
|
|
20115
|
+
fillOpacity: opacity,
|
|
20116
|
+
stroke: strokeWidth > 0 ? strokeColor : "none",
|
|
20117
|
+
strokeWidth: strokeWidth > 0 ? strokeWidth : void 0,
|
|
20118
|
+
strokeOpacity: strokeWidth > 0 ? strokeOpacity : void 0,
|
|
20119
|
+
vectorEffect: "non-scaling-stroke",
|
|
20120
|
+
style: {
|
|
20121
|
+
pointerEvents: interactive ? "auto" : "none",
|
|
20122
|
+
cursor: interactive ? "grab" : void 0
|
|
20123
|
+
},
|
|
20124
|
+
onPointerDown: interactive ? onHeadingPointerDown : void 0
|
|
20125
|
+
}
|
|
20126
|
+
)
|
|
20127
|
+
] });
|
|
20128
|
+
}
|
|
20129
|
+
function DefaultSpotIcon({ active, size }) {
|
|
20130
|
+
return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(
|
|
20131
|
+
"svg",
|
|
20132
|
+
{
|
|
20133
|
+
width: size,
|
|
20134
|
+
height: size,
|
|
20135
|
+
viewBox: "0 0 32 32",
|
|
20136
|
+
"aria-hidden": "true",
|
|
20137
|
+
style: { display: "block" },
|
|
20138
|
+
children: [
|
|
20139
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
20140
|
+
"rect",
|
|
20141
|
+
{
|
|
20142
|
+
x: "3",
|
|
20143
|
+
y: "7",
|
|
20144
|
+
width: "26",
|
|
20145
|
+
height: "18",
|
|
20146
|
+
rx: "5",
|
|
20147
|
+
fill: active ? "rgba(15,23,42,0.92)" : "rgba(15,23,42,0.72)",
|
|
20148
|
+
stroke: "rgba(255,255,255,0.92)",
|
|
20149
|
+
strokeWidth: "2"
|
|
20150
|
+
}
|
|
20151
|
+
),
|
|
20152
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)("circle", { cx: "16", cy: "16", r: "5", fill: "none", stroke: "rgba(255,255,255,0.92)", strokeWidth: "2" }),
|
|
20153
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)("circle", { cx: "16", cy: "16", r: "2", fill: active ? "#ef4444" : "rgba(255,255,255,0.85)" })
|
|
20154
|
+
]
|
|
20155
|
+
}
|
|
20156
|
+
);
|
|
20157
|
+
}
|
|
20158
|
+
function PanoMap({
|
|
20159
|
+
map,
|
|
20160
|
+
activeSceneId,
|
|
20161
|
+
onSelectScene,
|
|
20162
|
+
view: controlledView,
|
|
20163
|
+
mode = "view",
|
|
20164
|
+
selectedSpotId = null,
|
|
20165
|
+
onSelectedSpotIdChange,
|
|
20166
|
+
onEditChange,
|
|
20167
|
+
placementSceneId,
|
|
20168
|
+
className,
|
|
20169
|
+
style,
|
|
20170
|
+
showZoomControls,
|
|
20171
|
+
showCollapseControl,
|
|
20172
|
+
collapsed: controlledCollapsed,
|
|
20173
|
+
onCollapsedChange,
|
|
20174
|
+
renderChrome,
|
|
20175
|
+
initialScale
|
|
20176
|
+
}) {
|
|
20177
|
+
const reactId = (0, import_react66.useId)().replace(/:/g, "");
|
|
20178
|
+
const view = useMapView(controlledView);
|
|
20179
|
+
const rootRef = (0, import_react66.useRef)(null);
|
|
20180
|
+
const containerRef = (0, import_react66.useRef)(null);
|
|
20181
|
+
const sectorNodeRef = (0, import_react66.useRef)(null);
|
|
20182
|
+
const yawRef = (0, import_react66.useRef)(view.yaw);
|
|
20183
|
+
const [size, setSize] = (0, import_react66.useState)({ width: 0, height: 0 });
|
|
20184
|
+
const [imageSize2, setImageSize] = (0, import_react66.useState)({ width: 0, height: 0 });
|
|
20185
|
+
const [scale, setScale] = (0, import_react66.useState)(1);
|
|
20186
|
+
const [pan, setPan] = (0, import_react66.useState)({ x: 0, y: 0 });
|
|
20187
|
+
const [uncontrolledCollapsed, setUncontrolledCollapsed] = (0, import_react66.useState)(false);
|
|
20188
|
+
const dragRef = (0, import_react66.useRef)(null);
|
|
20189
|
+
const panAnimRef = (0, import_react66.useRef)(null);
|
|
20190
|
+
const panRef = (0, import_react66.useRef)(pan);
|
|
20191
|
+
const scaleRef = (0, import_react66.useRef)(scale);
|
|
20192
|
+
const initialViewKeyRef = (0, import_react66.useRef)(null);
|
|
20193
|
+
const spots = map.spots;
|
|
20194
|
+
const iconSize = map.iconSize ?? DEFAULT_ICON_SIZE2;
|
|
20195
|
+
const interaction = map.interaction ?? "fixed";
|
|
20196
|
+
const gestureZoom = interaction === "zoom";
|
|
20197
|
+
const dragMotion = resolveDragMotion(map.dragMotion);
|
|
20198
|
+
const maxScale = Math.max(MIN_SCALE, map.maxScale ?? DEFAULT_MAX_SCALE);
|
|
20199
|
+
const resolvedInitialScale = Math.min(
|
|
20200
|
+
maxScale,
|
|
20201
|
+
Math.max(
|
|
20202
|
+
MIN_SCALE,
|
|
20203
|
+
Number.isFinite(initialScale) ? initialScale : MIN_SCALE
|
|
20204
|
+
)
|
|
20205
|
+
);
|
|
20206
|
+
const zoomControls = gestureZoom && (showZoomControls ?? map.controls?.zoom === true);
|
|
20207
|
+
const collapseControl = showCollapseControl ?? map.controls?.collapse === true;
|
|
20208
|
+
const collapsed = controlledCollapsed ?? uncontrolledCollapsed;
|
|
20209
|
+
const editing = mode === "edit";
|
|
20210
|
+
const fit = (0, import_react66.useMemo)(
|
|
20211
|
+
() => containRect(size.width, size.height, imageSize2.width, imageSize2.height),
|
|
20212
|
+
[size, imageSize2]
|
|
20213
|
+
);
|
|
20214
|
+
const viewport = (0, import_react66.useMemo)(
|
|
20215
|
+
() => ({ fit, scale, panX: pan.x, panY: pan.y }),
|
|
20216
|
+
[fit, scale, pan]
|
|
20217
|
+
);
|
|
20218
|
+
(0, import_react66.useLayoutEffect)(() => {
|
|
20219
|
+
if (collapsed) return;
|
|
20220
|
+
const node = containerRef.current;
|
|
20221
|
+
if (!node) return;
|
|
20222
|
+
const measure = () => {
|
|
20223
|
+
const width = node.clientWidth;
|
|
20224
|
+
const height = node.clientHeight;
|
|
20225
|
+
if (!(width > 0) || !(height > 0)) return;
|
|
20226
|
+
setSize(
|
|
20227
|
+
(previous) => previous.width === width && previous.height === height ? previous : { width, height }
|
|
20228
|
+
);
|
|
20229
|
+
};
|
|
20230
|
+
measure();
|
|
20231
|
+
const observer = new ResizeObserver(measure);
|
|
20232
|
+
observer.observe(node);
|
|
20233
|
+
return () => observer.disconnect();
|
|
20234
|
+
}, [collapsed]);
|
|
20235
|
+
(0, import_react66.useEffect)(() => {
|
|
20236
|
+
yawRef.current = view.yaw;
|
|
20237
|
+
const node = sectorNodeRef.current;
|
|
20238
|
+
if (!node) return;
|
|
20239
|
+
const active = spots.find((spot) => spot.sceneId === activeSceneId);
|
|
20240
|
+
if (!active || !map.sector) return;
|
|
20241
|
+
node.setAttribute(
|
|
20242
|
+
"transform",
|
|
20243
|
+
`rotate(${sectorRotation(active.heading, view.yaw)})`
|
|
20244
|
+
);
|
|
20245
|
+
}, [view.yaw, activeSceneId, spots, map.sector]);
|
|
20246
|
+
const setCollapsed = (next) => {
|
|
20247
|
+
onCollapsedChange?.(next);
|
|
20248
|
+
if (controlledCollapsed === void 0) setUncontrolledCollapsed(next);
|
|
20249
|
+
};
|
|
20250
|
+
panRef.current = pan;
|
|
20251
|
+
scaleRef.current = scale;
|
|
20252
|
+
const panBoundsFor = (0, import_react66.useCallback)(
|
|
20253
|
+
(nextScale) => {
|
|
20254
|
+
const width = fit.width * nextScale;
|
|
20255
|
+
const height = fit.height * nextScale;
|
|
20256
|
+
if (!(fit.width > 0) || !(fit.height > 0)) {
|
|
20257
|
+
return { minX: 0, maxX: 0, minY: 0, maxY: 0 };
|
|
20258
|
+
}
|
|
20259
|
+
return {
|
|
20260
|
+
minX: Math.min(0, size.width - (fit.left + width)),
|
|
20261
|
+
maxX: Math.max(0, -fit.left),
|
|
20262
|
+
minY: Math.min(0, size.height - (fit.top + height)),
|
|
20263
|
+
maxY: Math.max(0, -fit.top)
|
|
20264
|
+
};
|
|
20265
|
+
},
|
|
20266
|
+
[fit, size]
|
|
20267
|
+
);
|
|
20268
|
+
const clampPan = (0, import_react66.useCallback)(
|
|
20269
|
+
(nextScale, nextPan) => {
|
|
20270
|
+
const bounds = panBoundsFor(nextScale);
|
|
20271
|
+
return {
|
|
20272
|
+
x: Math.min(bounds.maxX, Math.max(bounds.minX, nextPan.x)),
|
|
20273
|
+
y: Math.min(bounds.maxY, Math.max(bounds.minY, nextPan.y))
|
|
20274
|
+
};
|
|
20275
|
+
},
|
|
20276
|
+
[panBoundsFor]
|
|
20277
|
+
);
|
|
20278
|
+
const cancelPanAnimation = (0, import_react66.useCallback)(() => {
|
|
20279
|
+
const anim = panAnimRef.current;
|
|
20280
|
+
if (anim?.raf != null) {
|
|
20281
|
+
cancelAnimationFrame(anim.raf);
|
|
20282
|
+
}
|
|
20283
|
+
panAnimRef.current = null;
|
|
20284
|
+
}, []);
|
|
20285
|
+
const startPanAnimation = (0, import_react66.useCallback)(
|
|
20286
|
+
(velocity) => {
|
|
20287
|
+
cancelPanAnimation();
|
|
20288
|
+
const current = panRef.current;
|
|
20289
|
+
const currentScale = scaleRef.current;
|
|
20290
|
+
const bounds = panBoundsFor(currentScale);
|
|
20291
|
+
const needsSpring = dragMotion.bounce && (current.x < bounds.minX || current.x > bounds.maxX || current.y < bounds.minY || current.y > bounds.maxY);
|
|
20292
|
+
const vx = dragMotion.inertia ? velocity.x : 0;
|
|
20293
|
+
const vy = dragMotion.inertia ? velocity.y : 0;
|
|
20294
|
+
if (!needsSpring && Math.abs(vx) < 1 && Math.abs(vy) < 1) {
|
|
20295
|
+
setPan(clampPan(currentScale, current));
|
|
20296
|
+
return;
|
|
20297
|
+
}
|
|
20298
|
+
if (!dragMotion.inertia && !dragMotion.bounce) {
|
|
20299
|
+
setPan(clampPan(currentScale, current));
|
|
20300
|
+
return;
|
|
20301
|
+
}
|
|
20302
|
+
const anim = {
|
|
20303
|
+
raf: null,
|
|
20304
|
+
lastTime: performance.now(),
|
|
20305
|
+
x: { position: current.x, velocity: vx },
|
|
20306
|
+
y: { position: current.y, velocity: vy }
|
|
20307
|
+
};
|
|
20308
|
+
panAnimRef.current = anim;
|
|
20309
|
+
const tick = (now) => {
|
|
20310
|
+
if (panAnimRef.current !== anim) return;
|
|
20311
|
+
const dt = Math.min(0.064, Math.max(0, (now - anim.lastTime) / 1e3));
|
|
20312
|
+
anim.lastTime = now;
|
|
20313
|
+
const liveBounds = panBoundsFor(scaleRef.current);
|
|
20314
|
+
const nextX = integrateAxis(anim.x, dt, {
|
|
20315
|
+
min: liveBounds.minX,
|
|
20316
|
+
max: liveBounds.maxX,
|
|
20317
|
+
inertia: dragMotion.inertia,
|
|
20318
|
+
bounce: dragMotion.bounce
|
|
20319
|
+
});
|
|
20320
|
+
const nextY = integrateAxis(anim.y, dt, {
|
|
20321
|
+
min: liveBounds.minY,
|
|
20322
|
+
max: liveBounds.maxY,
|
|
20323
|
+
inertia: dragMotion.inertia,
|
|
20324
|
+
bounce: dragMotion.bounce
|
|
20325
|
+
});
|
|
20326
|
+
anim.x = { position: nextX.position, velocity: nextX.velocity };
|
|
20327
|
+
anim.y = { position: nextY.position, velocity: nextY.velocity };
|
|
20328
|
+
setPan({ x: nextX.position, y: nextY.position });
|
|
20329
|
+
if (nextX.settled && nextY.settled) {
|
|
20330
|
+
setPan(
|
|
20331
|
+
clampPan(scaleRef.current, {
|
|
20332
|
+
x: nextX.position,
|
|
20333
|
+
y: nextY.position
|
|
20334
|
+
})
|
|
20335
|
+
);
|
|
20336
|
+
panAnimRef.current = null;
|
|
20337
|
+
return;
|
|
20338
|
+
}
|
|
20339
|
+
anim.raf = requestAnimationFrame(tick);
|
|
20340
|
+
};
|
|
20341
|
+
anim.raf = requestAnimationFrame(tick);
|
|
20342
|
+
},
|
|
20343
|
+
[cancelPanAnimation, clampPan, dragMotion.bounce, dragMotion.inertia, panBoundsFor]
|
|
20344
|
+
);
|
|
20345
|
+
(0, import_react66.useEffect)(() => () => cancelPanAnimation(), [cancelPanAnimation]);
|
|
20346
|
+
(0, import_react66.useEffect)(() => {
|
|
20347
|
+
if (collapsed) cancelPanAnimation();
|
|
20348
|
+
}, [cancelPanAnimation, collapsed]);
|
|
20349
|
+
(0, import_react66.useEffect)(() => {
|
|
20350
|
+
if (!gestureZoom) {
|
|
20351
|
+
initialViewKeyRef.current = null;
|
|
20352
|
+
setScale(1);
|
|
20353
|
+
setPan({ x: 0, y: 0 });
|
|
20354
|
+
return;
|
|
20355
|
+
}
|
|
20356
|
+
setScale((previous) => {
|
|
20357
|
+
const next = Math.min(previous, maxScale);
|
|
20358
|
+
if (next !== previous) {
|
|
20359
|
+
setPan((currentPan) => clampPan(next, currentPan));
|
|
20360
|
+
}
|
|
20361
|
+
return next;
|
|
20362
|
+
});
|
|
20363
|
+
}, [clampPan, gestureZoom, maxScale]);
|
|
20364
|
+
(0, import_react66.useEffect)(() => {
|
|
20365
|
+
if (!gestureZoom) return;
|
|
20366
|
+
if (!(fit.width > 0) || !(fit.height > 0)) return;
|
|
20367
|
+
if (!(size.width > 0) || !(size.height > 0)) return;
|
|
20368
|
+
const key = `${interaction}:${initialScale ?? MIN_SCALE}`;
|
|
20369
|
+
if (initialViewKeyRef.current === key) return;
|
|
20370
|
+
initialViewKeyRef.current = key;
|
|
20371
|
+
const pivot = { x: size.width / 2, y: size.height / 2 };
|
|
20372
|
+
const nextPan = {
|
|
20373
|
+
x: pivot.x - (pivot.x - fit.left) * resolvedInitialScale - fit.left,
|
|
20374
|
+
y: pivot.y - (pivot.y - fit.top) * resolvedInitialScale - fit.top
|
|
20375
|
+
};
|
|
20376
|
+
setScale(resolvedInitialScale);
|
|
20377
|
+
setPan(clampPan(resolvedInitialScale, nextPan));
|
|
20378
|
+
}, [
|
|
20379
|
+
clampPan,
|
|
20380
|
+
fit.height,
|
|
20381
|
+
fit.left,
|
|
20382
|
+
fit.top,
|
|
20383
|
+
fit.width,
|
|
20384
|
+
gestureZoom,
|
|
20385
|
+
initialScale,
|
|
20386
|
+
interaction,
|
|
20387
|
+
resolvedInitialScale,
|
|
20388
|
+
size.height,
|
|
20389
|
+
size.width
|
|
20390
|
+
]);
|
|
20391
|
+
const applyZoom = (0, import_react66.useCallback)(
|
|
20392
|
+
(nextScale, origin) => {
|
|
20393
|
+
if (!gestureZoom) return;
|
|
20394
|
+
cancelPanAnimation();
|
|
20395
|
+
const clamped = Math.min(maxScale, Math.max(MIN_SCALE, nextScale));
|
|
20396
|
+
const pivot = origin ?? { x: size.width / 2, y: size.height / 2 };
|
|
20397
|
+
const ratio = clamped / scale;
|
|
20398
|
+
const nextPan = {
|
|
20399
|
+
x: pivot.x - (pivot.x - (fit.left + pan.x)) * ratio - fit.left,
|
|
20400
|
+
y: pivot.y - (pivot.y - (fit.top + pan.y)) * ratio - fit.top
|
|
20401
|
+
};
|
|
20402
|
+
setScale(clamped);
|
|
20403
|
+
setPan(clampPan(clamped, nextPan));
|
|
20404
|
+
},
|
|
20405
|
+
[
|
|
20406
|
+
cancelPanAnimation,
|
|
20407
|
+
clampPan,
|
|
20408
|
+
fit.left,
|
|
20409
|
+
fit.top,
|
|
20410
|
+
gestureZoom,
|
|
20411
|
+
maxScale,
|
|
20412
|
+
pan.x,
|
|
20413
|
+
pan.y,
|
|
20414
|
+
scale,
|
|
20415
|
+
size.height,
|
|
20416
|
+
size.width
|
|
20417
|
+
]
|
|
20418
|
+
);
|
|
20419
|
+
(0, import_react66.useEffect)(() => {
|
|
20420
|
+
const root = rootRef.current;
|
|
20421
|
+
if (!root) return;
|
|
20422
|
+
const onWheel = (event) => {
|
|
20423
|
+
event.preventDefault();
|
|
20424
|
+
event.stopPropagation();
|
|
20425
|
+
if (!gestureZoom || collapsed) return;
|
|
20426
|
+
const viewport2 = containerRef.current;
|
|
20427
|
+
if (!viewport2) return;
|
|
20428
|
+
const rect = viewport2.getBoundingClientRect();
|
|
20429
|
+
const factor = event.deltaY < 0 ? ZOOM_STEP : 1 / ZOOM_STEP;
|
|
20430
|
+
applyZoom(scale * factor, {
|
|
20431
|
+
x: event.clientX - rect.left,
|
|
20432
|
+
y: event.clientY - rect.top
|
|
20433
|
+
});
|
|
20434
|
+
};
|
|
20435
|
+
root.addEventListener("wheel", onWheel, { passive: false });
|
|
20436
|
+
return () => root.removeEventListener("wheel", onWheel);
|
|
20437
|
+
}, [applyZoom, collapsed, gestureZoom, scale]);
|
|
20438
|
+
const updateSpots = (next, selected) => {
|
|
20439
|
+
onEditChange?.({
|
|
20440
|
+
spots: next,
|
|
20441
|
+
...selected === void 0 ? {} : { selectedSpotId: selected }
|
|
20442
|
+
});
|
|
20443
|
+
};
|
|
20444
|
+
const localPoint = (event) => {
|
|
20445
|
+
const rect = containerRef.current?.getBoundingClientRect();
|
|
20446
|
+
if (!rect) return null;
|
|
20447
|
+
return { x: event.clientX - rect.left, y: event.clientY - rect.top };
|
|
20448
|
+
};
|
|
20449
|
+
const onPointerDownSurface = (event) => {
|
|
20450
|
+
if (collapsed) return;
|
|
20451
|
+
if (event.button !== 0) return;
|
|
20452
|
+
const point = localPoint(event);
|
|
20453
|
+
if (!point) return;
|
|
20454
|
+
if (editing && placementSceneId) {
|
|
20455
|
+
const image = containerPointToImage(point, viewport);
|
|
20456
|
+
if (!image) return;
|
|
20457
|
+
const spot = {
|
|
20458
|
+
id: crypto.randomUUID(),
|
|
20459
|
+
sceneId: placementSceneId,
|
|
20460
|
+
x: clamp012(image.x),
|
|
20461
|
+
y: clamp012(image.y),
|
|
20462
|
+
heading: normalizeDegrees(-view.yaw)
|
|
20463
|
+
};
|
|
20464
|
+
updateSpots([...spots, spot], spot.id);
|
|
20465
|
+
onSelectedSpotIdChange?.(spot.id);
|
|
20466
|
+
event.preventDefault();
|
|
20467
|
+
event.stopPropagation();
|
|
20468
|
+
return;
|
|
20469
|
+
}
|
|
20470
|
+
if (gestureZoom && scale > 1) {
|
|
20471
|
+
cancelPanAnimation();
|
|
20472
|
+
const now = performance.now();
|
|
20473
|
+
dragRef.current = {
|
|
20474
|
+
kind: "pan",
|
|
20475
|
+
pointerId: event.pointerId,
|
|
20476
|
+
originX: point.x,
|
|
20477
|
+
originY: point.y,
|
|
20478
|
+
panX: pan.x,
|
|
20479
|
+
panY: pan.y,
|
|
20480
|
+
samplesX: [{ t: now, position: pan.x }],
|
|
20481
|
+
samplesY: [{ t: now, position: pan.y }]
|
|
20482
|
+
};
|
|
20483
|
+
try {
|
|
20484
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
20485
|
+
} catch {
|
|
20486
|
+
}
|
|
20487
|
+
event.preventDefault();
|
|
20488
|
+
event.stopPropagation();
|
|
20489
|
+
}
|
|
20490
|
+
};
|
|
20491
|
+
const onPointerMoveSurface = (event) => {
|
|
20492
|
+
const drag = dragRef.current;
|
|
20493
|
+
if (!drag || drag.pointerId !== event.pointerId) return;
|
|
20494
|
+
const point = localPoint(event);
|
|
20495
|
+
if (!point) return;
|
|
20496
|
+
if (drag.kind === "pan") {
|
|
20497
|
+
const rawX = drag.panX + (point.x - drag.originX);
|
|
20498
|
+
const rawY = drag.panY + (point.y - drag.originY);
|
|
20499
|
+
const bounds = panBoundsFor(scale);
|
|
20500
|
+
const now = performance.now();
|
|
20501
|
+
drag.samplesX = pushSample(drag.samplesX, { t: now, position: rawX });
|
|
20502
|
+
drag.samplesY = pushSample(drag.samplesY, { t: now, position: rawY });
|
|
20503
|
+
setPan({
|
|
20504
|
+
x: constrainDragPosition(
|
|
20505
|
+
rawX,
|
|
20506
|
+
bounds.minX,
|
|
20507
|
+
bounds.maxX,
|
|
20508
|
+
size.width,
|
|
20509
|
+
dragMotion.bounce
|
|
20510
|
+
),
|
|
20511
|
+
y: constrainDragPosition(
|
|
20512
|
+
rawY,
|
|
20513
|
+
bounds.minY,
|
|
20514
|
+
bounds.maxY,
|
|
20515
|
+
size.height,
|
|
20516
|
+
dragMotion.bounce
|
|
20517
|
+
)
|
|
20518
|
+
});
|
|
20519
|
+
event.preventDefault();
|
|
20520
|
+
event.stopPropagation();
|
|
20521
|
+
return;
|
|
20522
|
+
}
|
|
20523
|
+
if (drag.kind === "spot") {
|
|
20524
|
+
const image = containerPointToImage(
|
|
20525
|
+
{ x: point.x - drag.offsetX, y: point.y - drag.offsetY },
|
|
20526
|
+
viewport
|
|
20527
|
+
);
|
|
20528
|
+
if (!image) return;
|
|
20529
|
+
updateSpots(
|
|
20530
|
+
spots.map(
|
|
20531
|
+
(spot2) => spot2.id === drag.spotId ? { ...spot2, x: clamp012(image.x), y: clamp012(image.y) } : spot2
|
|
20532
|
+
)
|
|
20533
|
+
);
|
|
20534
|
+
event.preventDefault();
|
|
20535
|
+
event.stopPropagation();
|
|
18932
20536
|
return;
|
|
18933
20537
|
}
|
|
18934
|
-
const
|
|
18935
|
-
|
|
18936
|
-
const
|
|
18937
|
-
|
|
18938
|
-
|
|
18939
|
-
|
|
18940
|
-
|
|
18941
|
-
|
|
18942
|
-
|
|
18943
|
-
|
|
18944
|
-
|
|
18945
|
-
|
|
18946
|
-
|
|
18947
|
-
|
|
18948
|
-
|
|
20538
|
+
const spot = spots.find((entry) => entry.id === drag.spotId);
|
|
20539
|
+
if (!spot) return;
|
|
20540
|
+
const center = imagePointToContainer(spot, viewport);
|
|
20541
|
+
const heading = normalizeDegrees(
|
|
20542
|
+
headingFromVector(point.x - center.x, point.y - center.y) - view.yaw
|
|
20543
|
+
);
|
|
20544
|
+
updateSpots(
|
|
20545
|
+
spots.map(
|
|
20546
|
+
(entry) => entry.id === drag.spotId ? { ...entry, heading } : entry
|
|
20547
|
+
)
|
|
20548
|
+
);
|
|
20549
|
+
if (sectorNodeRef.current && spot.sceneId === activeSceneId) {
|
|
20550
|
+
sectorNodeRef.current.setAttribute(
|
|
20551
|
+
"transform",
|
|
20552
|
+
`rotate(${sectorRotation(heading, view.yaw)})`
|
|
18949
20553
|
);
|
|
18950
|
-
|
|
18951
|
-
|
|
18952
|
-
|
|
18953
|
-
|
|
18954
|
-
|
|
18955
|
-
|
|
18956
|
-
|
|
18957
|
-
|
|
18958
|
-
|
|
18959
|
-
|
|
18960
|
-
|
|
18961
|
-
|
|
18962
|
-
|
|
18963
|
-
|
|
18964
|
-
|
|
18965
|
-
|
|
18966
|
-
|
|
18967
|
-
video.addEventListener("ratechange", handleVolume);
|
|
18968
|
-
return () => {
|
|
18969
|
-
active = false;
|
|
18970
|
-
video.removeEventListener("timeupdate", handleTimeUpdate);
|
|
18971
|
-
video.removeEventListener("durationchange", handleTimeUpdate);
|
|
18972
|
-
video.removeEventListener("progress", handleTimeUpdate);
|
|
18973
|
-
video.removeEventListener("play", handlePlay);
|
|
18974
|
-
video.removeEventListener("pause", handlePause);
|
|
18975
|
-
video.removeEventListener("ended", handleEnded);
|
|
18976
|
-
video.removeEventListener("volumechange", handleVolume);
|
|
18977
|
-
video.removeEventListener("ratechange", handleVolume);
|
|
18978
|
-
};
|
|
18979
|
-
}, [emitSnapshot, resource]);
|
|
18980
|
-
(0, import_react63.useEffect)(() => {
|
|
18981
|
-
if (!resource) {
|
|
20554
|
+
}
|
|
20555
|
+
event.preventDefault();
|
|
20556
|
+
event.stopPropagation();
|
|
20557
|
+
};
|
|
20558
|
+
const endDrag = (event) => {
|
|
20559
|
+
const drag = dragRef.current;
|
|
20560
|
+
if (drag?.pointerId !== event.pointerId) return;
|
|
20561
|
+
dragRef.current = null;
|
|
20562
|
+
try {
|
|
20563
|
+
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
|
|
20564
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
20565
|
+
}
|
|
20566
|
+
} catch {
|
|
20567
|
+
}
|
|
20568
|
+
if (drag.kind !== "pan") return;
|
|
20569
|
+
if (event.type === "pointercancel") {
|
|
20570
|
+
setPan(clampPan(scale, panRef.current));
|
|
18982
20571
|
return;
|
|
18983
20572
|
}
|
|
18984
|
-
|
|
18985
|
-
|
|
18986
|
-
|
|
18987
|
-
|
|
18988
|
-
|
|
18989
|
-
|
|
18990
|
-
|
|
18991
|
-
|
|
18992
|
-
|
|
18993
|
-
|
|
20573
|
+
startPanAnimation({
|
|
20574
|
+
x: velocityFromSamples(drag.samplesX),
|
|
20575
|
+
y: velocityFromSamples(drag.samplesY)
|
|
20576
|
+
});
|
|
20577
|
+
};
|
|
20578
|
+
const sectorAngle = (() => {
|
|
20579
|
+
if (!map.sector) return 0;
|
|
20580
|
+
if (!map.sector.followFov) return map.sector.angle;
|
|
20581
|
+
const aspect = size.width > 0 && size.height > 0 ? size.width / size.height : 1;
|
|
20582
|
+
return horizontalFov(view.fov, aspect);
|
|
20583
|
+
})();
|
|
20584
|
+
const activeSpot = spots.find((spot) => spot.sceneId === activeSceneId);
|
|
20585
|
+
const selectedSpot = spots.find((spot) => spot.id === selectedSpotId);
|
|
20586
|
+
const controls = /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "pano-map-controls", style: { pointerEvents: "auto" }, children: [
|
|
20587
|
+
collapseControl ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
20588
|
+
"button",
|
|
20589
|
+
{
|
|
20590
|
+
type: "button",
|
|
20591
|
+
className: "pano-map-control",
|
|
20592
|
+
"aria-label": collapsed ? "Expand map" : "Collapse map",
|
|
20593
|
+
"aria-pressed": !collapsed,
|
|
20594
|
+
style: {
|
|
20595
|
+
background: "rgba(15, 23, 42, 0.92)",
|
|
20596
|
+
border: "1px solid rgba(255, 255, 255, 0.28)",
|
|
20597
|
+
color: "#f8fafc"
|
|
20598
|
+
},
|
|
20599
|
+
onClick: () => setCollapsed(!collapsed),
|
|
20600
|
+
children: collapsed ? "\u25A3" : "\u25A2"
|
|
18994
20601
|
}
|
|
18995
|
-
|
|
18996
|
-
|
|
18997
|
-
|
|
18998
|
-
|
|
18999
|
-
|
|
19000
|
-
|
|
19001
|
-
|
|
19002
|
-
|
|
20602
|
+
) : null,
|
|
20603
|
+
zoomControls && !collapsed ? /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(import_jsx_runtime43.Fragment, { children: [
|
|
20604
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
20605
|
+
"button",
|
|
20606
|
+
{
|
|
20607
|
+
type: "button",
|
|
20608
|
+
className: "pano-map-control",
|
|
20609
|
+
"aria-label": "Zoom in",
|
|
20610
|
+
style: {
|
|
20611
|
+
background: "rgba(15, 23, 42, 0.92)",
|
|
20612
|
+
border: "1px solid rgba(255, 255, 255, 0.28)",
|
|
20613
|
+
color: "#f8fafc"
|
|
20614
|
+
},
|
|
20615
|
+
onClick: () => applyZoom(scale * ZOOM_STEP),
|
|
20616
|
+
children: "+"
|
|
20617
|
+
}
|
|
20618
|
+
),
|
|
20619
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
20620
|
+
"button",
|
|
20621
|
+
{
|
|
20622
|
+
type: "button",
|
|
20623
|
+
className: "pano-map-control",
|
|
20624
|
+
"aria-label": "Zoom out",
|
|
20625
|
+
style: {
|
|
20626
|
+
background: "rgba(15, 23, 42, 0.92)",
|
|
20627
|
+
border: "1px solid rgba(255, 255, 255, 0.28)",
|
|
20628
|
+
color: "#f8fafc"
|
|
20629
|
+
},
|
|
20630
|
+
onClick: () => applyZoom(scale / ZOOM_STEP),
|
|
20631
|
+
children: "\u2212"
|
|
20632
|
+
}
|
|
20633
|
+
)
|
|
20634
|
+
] }) : null
|
|
20635
|
+
] });
|
|
20636
|
+
const syncImageSize = (image) => {
|
|
20637
|
+
if (!(image.naturalWidth > 0) || !(image.naturalHeight > 0)) return;
|
|
20638
|
+
setImageSize(
|
|
20639
|
+
(previous) => previous.width === image.naturalWidth && previous.height === image.naturalHeight ? previous : { width: image.naturalWidth, height: image.naturalHeight }
|
|
20640
|
+
);
|
|
20641
|
+
};
|
|
20642
|
+
return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(
|
|
20643
|
+
"div",
|
|
20644
|
+
{
|
|
20645
|
+
ref: rootRef,
|
|
20646
|
+
className: ["pano-map", collapsed ? "pano-map-collapsed" : "", className].filter(Boolean).join(" "),
|
|
20647
|
+
style: {
|
|
20648
|
+
position: "relative",
|
|
20649
|
+
overflow: "hidden",
|
|
20650
|
+
width: "100%",
|
|
20651
|
+
height: "100%",
|
|
20652
|
+
pointerEvents: "auto",
|
|
20653
|
+
touchAction: gestureZoom && !collapsed ? "none" : "auto",
|
|
20654
|
+
...style
|
|
20655
|
+
},
|
|
20656
|
+
"data-pano-map": "",
|
|
20657
|
+
"data-mode": mode,
|
|
20658
|
+
"data-collapsed": collapsed ? "true" : void 0,
|
|
20659
|
+
children: [
|
|
20660
|
+
renderChrome ? renderChrome(controls) : /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: "pano-map-chrome", children: controls }),
|
|
20661
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(
|
|
20662
|
+
"div",
|
|
20663
|
+
{
|
|
20664
|
+
ref: containerRef,
|
|
20665
|
+
className: "pano-map-viewport",
|
|
20666
|
+
hidden: collapsed,
|
|
20667
|
+
style: {
|
|
20668
|
+
position: "absolute",
|
|
20669
|
+
inset: 0,
|
|
20670
|
+
overflow: "hidden",
|
|
20671
|
+
visibility: collapsed ? "hidden" : "visible"
|
|
20672
|
+
},
|
|
20673
|
+
onPointerDown: onPointerDownSurface,
|
|
20674
|
+
onPointerMove: onPointerMoveSurface,
|
|
20675
|
+
onPointerUp: endDrag,
|
|
20676
|
+
onPointerCancel: endDrag,
|
|
20677
|
+
children: [
|
|
20678
|
+
fit.width > 0 && fit.height > 0 ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
20679
|
+
"div",
|
|
20680
|
+
{
|
|
20681
|
+
className: "pano-map-image-layer",
|
|
20682
|
+
style: {
|
|
20683
|
+
position: "absolute",
|
|
20684
|
+
left: fit.left + pan.x,
|
|
20685
|
+
top: fit.top + pan.y,
|
|
20686
|
+
width: fit.width * scale,
|
|
20687
|
+
height: fit.height * scale,
|
|
20688
|
+
transformOrigin: "0 0"
|
|
20689
|
+
},
|
|
20690
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
20691
|
+
"img",
|
|
20692
|
+
{
|
|
20693
|
+
src: map.image,
|
|
20694
|
+
alt: "",
|
|
20695
|
+
draggable: false,
|
|
20696
|
+
ref: (node) => {
|
|
20697
|
+
if (node) syncImageSize(node);
|
|
20698
|
+
},
|
|
20699
|
+
onLoad: (event) => {
|
|
20700
|
+
syncImageSize(event.currentTarget);
|
|
20701
|
+
},
|
|
20702
|
+
style: {
|
|
20703
|
+
width: "100%",
|
|
20704
|
+
height: "100%",
|
|
20705
|
+
display: "block",
|
|
20706
|
+
userSelect: "none",
|
|
20707
|
+
pointerEvents: "none"
|
|
20708
|
+
}
|
|
20709
|
+
}
|
|
20710
|
+
)
|
|
20711
|
+
}
|
|
20712
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
20713
|
+
"img",
|
|
20714
|
+
{
|
|
20715
|
+
src: map.image,
|
|
20716
|
+
alt: "",
|
|
20717
|
+
hidden: true,
|
|
20718
|
+
ref: (node) => {
|
|
20719
|
+
if (node) syncImageSize(node);
|
|
20720
|
+
},
|
|
20721
|
+
onLoad: (event) => {
|
|
20722
|
+
syncImageSize(event.currentTarget);
|
|
20723
|
+
}
|
|
20724
|
+
}
|
|
20725
|
+
),
|
|
20726
|
+
activeSpot && map.sector ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
20727
|
+
"svg",
|
|
20728
|
+
{
|
|
20729
|
+
className: "pano-map-sector-layer",
|
|
20730
|
+
width: size.width,
|
|
20731
|
+
height: size.height,
|
|
20732
|
+
style: {
|
|
20733
|
+
position: "absolute",
|
|
20734
|
+
inset: 0,
|
|
20735
|
+
overflow: "visible",
|
|
20736
|
+
pointerEvents: "none"
|
|
20737
|
+
},
|
|
20738
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
20739
|
+
"g",
|
|
20740
|
+
{
|
|
20741
|
+
transform: `translate(${imagePointToContainer(activeSpot, viewport).x} ${imagePointToContainer(activeSpot, viewport).y})`,
|
|
20742
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
20743
|
+
SectorShape,
|
|
20744
|
+
{
|
|
20745
|
+
sector: map.sector,
|
|
20746
|
+
angle: sectorAngle,
|
|
20747
|
+
rotation: sectorRotation(activeSpot.heading, view.yaw),
|
|
20748
|
+
gradientId: defaultGradientId(reactId),
|
|
20749
|
+
interactive: editing,
|
|
20750
|
+
onHeadingPointerDown: (event) => {
|
|
20751
|
+
event.stopPropagation();
|
|
20752
|
+
if (event.button !== 0) return;
|
|
20753
|
+
onSelectedSpotIdChange?.(activeSpot.id);
|
|
20754
|
+
dragRef.current = {
|
|
20755
|
+
kind: "heading",
|
|
20756
|
+
pointerId: event.pointerId,
|
|
20757
|
+
spotId: activeSpot.id
|
|
20758
|
+
};
|
|
20759
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
20760
|
+
},
|
|
20761
|
+
transformRef: (node) => {
|
|
20762
|
+
sectorNodeRef.current = node;
|
|
20763
|
+
}
|
|
20764
|
+
}
|
|
20765
|
+
)
|
|
20766
|
+
}
|
|
20767
|
+
)
|
|
20768
|
+
}
|
|
20769
|
+
) : null,
|
|
20770
|
+
spots.map((spot) => {
|
|
20771
|
+
const point = imagePointToContainer(spot, viewport);
|
|
20772
|
+
const active = spot.sceneId === activeSceneId;
|
|
20773
|
+
const selected = spot.id === selectedSpotId;
|
|
20774
|
+
return /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
20775
|
+
"button",
|
|
20776
|
+
{
|
|
20777
|
+
type: "button",
|
|
20778
|
+
className: "pano-map-spot",
|
|
20779
|
+
"aria-label": spot.sceneId,
|
|
20780
|
+
"aria-current": active ? "true" : void 0,
|
|
20781
|
+
"data-active": active ? "true" : void 0,
|
|
20782
|
+
"data-selected": selected ? "true" : void 0,
|
|
20783
|
+
style: {
|
|
20784
|
+
position: "absolute",
|
|
20785
|
+
left: point.x,
|
|
20786
|
+
top: point.y,
|
|
20787
|
+
width: iconSize,
|
|
20788
|
+
height: iconSize,
|
|
20789
|
+
transform: "translate(-50%, -50%)",
|
|
20790
|
+
padding: 0,
|
|
20791
|
+
border: selected ? "2px solid #38bdf8" : "0",
|
|
20792
|
+
borderRadius: 8,
|
|
20793
|
+
background: "transparent",
|
|
20794
|
+
cursor: editing ? "grab" : "pointer",
|
|
20795
|
+
pointerEvents: "auto"
|
|
20796
|
+
},
|
|
20797
|
+
onPointerDown: (event) => {
|
|
20798
|
+
event.stopPropagation();
|
|
20799
|
+
if (event.button !== 0) return;
|
|
20800
|
+
onSelectedSpotIdChange?.(spot.id);
|
|
20801
|
+
if (!editing) return;
|
|
20802
|
+
const pointLocal = localPoint(event);
|
|
20803
|
+
if (!pointLocal) return;
|
|
20804
|
+
const center = imagePointToContainer(spot, viewport);
|
|
20805
|
+
dragRef.current = {
|
|
20806
|
+
kind: "spot",
|
|
20807
|
+
pointerId: event.pointerId,
|
|
20808
|
+
spotId: spot.id,
|
|
20809
|
+
offsetX: pointLocal.x - center.x,
|
|
20810
|
+
offsetY: pointLocal.y - center.y
|
|
20811
|
+
};
|
|
20812
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
20813
|
+
},
|
|
20814
|
+
onClick: (event) => {
|
|
20815
|
+
event.stopPropagation();
|
|
20816
|
+
if (editing) return;
|
|
20817
|
+
onSelectScene?.(spot.sceneId, spot);
|
|
20818
|
+
},
|
|
20819
|
+
children: spot.icon ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
20820
|
+
"img",
|
|
20821
|
+
{
|
|
20822
|
+
src: spot.icon,
|
|
20823
|
+
alt: "",
|
|
20824
|
+
draggable: false,
|
|
20825
|
+
style: { width: "100%", height: "100%", display: "block", pointerEvents: "none" }
|
|
20826
|
+
}
|
|
20827
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(DefaultSpotIcon, { active, size: iconSize })
|
|
20828
|
+
},
|
|
20829
|
+
spot.id
|
|
20830
|
+
);
|
|
20831
|
+
})
|
|
20832
|
+
]
|
|
20833
|
+
}
|
|
19003
20834
|
)
|
|
19004
|
-
|
|
20835
|
+
]
|
|
20836
|
+
}
|
|
20837
|
+
);
|
|
20838
|
+
}
|
|
20839
|
+
|
|
20840
|
+
// src/map/pano-map-overlay.tsx
|
|
20841
|
+
var import_react67 = require("react");
|
|
20842
|
+
var import_jsx_runtime44 = require("react/jsx-runtime");
|
|
20843
|
+
var DEFAULT_DIALOG_WIDTH = 720;
|
|
20844
|
+
var DEFAULT_DIALOG_HEIGHT = 520;
|
|
20845
|
+
function dockedBox(corner, offsetX, offsetY, width, height) {
|
|
20846
|
+
const horizontal = corner === "top-right" || corner === "bottom-right" ? { left: "auto", right: offsetX } : { left: offsetX, right: "auto" };
|
|
20847
|
+
const vertical = corner === "bottom-left" || corner === "bottom-right" ? { top: "auto", bottom: offsetY } : { top: offsetY, bottom: "auto" };
|
|
20848
|
+
return { ...horizontal, ...vertical, width, height };
|
|
20849
|
+
}
|
|
20850
|
+
function PanoMapDialogShell({
|
|
20851
|
+
open,
|
|
20852
|
+
onOpenChange,
|
|
20853
|
+
title,
|
|
20854
|
+
closeLabel,
|
|
20855
|
+
dialogWidth,
|
|
20856
|
+
dialogHeight,
|
|
20857
|
+
children
|
|
20858
|
+
}) {
|
|
20859
|
+
usePanoModal(open);
|
|
20860
|
+
const { size } = usePanoOverlay();
|
|
20861
|
+
const titleId = (0, import_react67.useId)();
|
|
20862
|
+
const panelRef = (0, import_react67.useRef)(null);
|
|
20863
|
+
const previouslyFocused = (0, import_react67.useRef)(null);
|
|
20864
|
+
(0, import_react67.useEffect)(() => {
|
|
20865
|
+
if (!open) return;
|
|
20866
|
+
const active = panelRef.current?.ownerDocument.activeElement;
|
|
20867
|
+
previouslyFocused.current = active instanceof HTMLElement ? active : null;
|
|
20868
|
+
const close = panelRef.current?.querySelector("[data-pano-map-dialog-close]");
|
|
20869
|
+
close?.focus({ preventScroll: true });
|
|
20870
|
+
const onKeyDown = (event) => {
|
|
20871
|
+
if (event.key === "Escape") {
|
|
20872
|
+
event.preventDefault();
|
|
20873
|
+
event.stopPropagation();
|
|
20874
|
+
onOpenChange(false);
|
|
20875
|
+
}
|
|
19005
20876
|
};
|
|
19006
|
-
|
|
19007
|
-
|
|
19008
|
-
video.textTracks.addEventListener("change", applyModes);
|
|
20877
|
+
const doc = panelRef.current?.ownerDocument;
|
|
20878
|
+
doc?.addEventListener("keydown", onKeyDown, true);
|
|
19009
20879
|
return () => {
|
|
19010
|
-
|
|
19011
|
-
|
|
19012
|
-
|
|
20880
|
+
doc?.removeEventListener("keydown", onKeyDown, true);
|
|
20881
|
+
const previous = previouslyFocused.current;
|
|
20882
|
+
if (previous?.isConnected) previous.focus({ preventScroll: true });
|
|
19013
20883
|
};
|
|
19014
|
-
}, [
|
|
19015
|
-
|
|
19016
|
-
|
|
19017
|
-
|
|
19018
|
-
|
|
19019
|
-
|
|
19020
|
-
|
|
19021
|
-
|
|
19022
|
-
|
|
19023
|
-
|
|
19024
|
-
|
|
19025
|
-
}
|
|
19026
|
-
return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(import_jsx_runtime41.Fragment, { children: [
|
|
19027
|
-
activeTexture ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
|
|
19028
|
-
"mesh",
|
|
20884
|
+
}, [open, onOpenChange]);
|
|
20885
|
+
if (!open) return null;
|
|
20886
|
+
const preferredWidth = Number.isFinite(dialogWidth) && dialogWidth > 0 ? dialogWidth : DEFAULT_DIALOG_WIDTH;
|
|
20887
|
+
const preferredHeight = Number.isFinite(dialogHeight) && dialogHeight > 0 ? dialogHeight : DEFAULT_DIALOG_HEIGHT;
|
|
20888
|
+
const width = Math.min(preferredWidth, Math.max(0, size.width - 32));
|
|
20889
|
+
const height = Math.min(preferredHeight, Math.max(0, size.height - 32));
|
|
20890
|
+
const left = Math.max(0, (size.width - width) / 2);
|
|
20891
|
+
const top = Math.max(0, (size.height - height) / 2);
|
|
20892
|
+
return /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(import_jsx_runtime44.Fragment, { children: [
|
|
20893
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
20894
|
+
"div",
|
|
19029
20895
|
{
|
|
19030
|
-
|
|
19031
|
-
|
|
20896
|
+
className: "pano-map-dialog-backdrop",
|
|
20897
|
+
"aria-hidden": "true",
|
|
20898
|
+
style: {
|
|
20899
|
+
position: "absolute",
|
|
20900
|
+
inset: 0,
|
|
20901
|
+
pointerEvents: "auto"
|
|
20902
|
+
},
|
|
20903
|
+
onClick: () => onOpenChange(false)
|
|
20904
|
+
}
|
|
20905
|
+
),
|
|
20906
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(
|
|
20907
|
+
"div",
|
|
20908
|
+
{
|
|
20909
|
+
ref: panelRef,
|
|
20910
|
+
className: "pano-map-dialog",
|
|
20911
|
+
role: "dialog",
|
|
20912
|
+
"aria-modal": "true",
|
|
20913
|
+
"aria-labelledby": titleId,
|
|
20914
|
+
"data-pano-map-dialog": "",
|
|
20915
|
+
style: {
|
|
20916
|
+
position: "absolute",
|
|
20917
|
+
left,
|
|
20918
|
+
top,
|
|
20919
|
+
width,
|
|
20920
|
+
height,
|
|
20921
|
+
boxSizing: "border-box",
|
|
20922
|
+
display: "flex",
|
|
20923
|
+
flexDirection: "column",
|
|
20924
|
+
overflow: "hidden",
|
|
20925
|
+
pointerEvents: "auto",
|
|
20926
|
+
zIndex: 1
|
|
20927
|
+
},
|
|
19032
20928
|
children: [
|
|
19033
|
-
/* @__PURE__ */ (0,
|
|
19034
|
-
|
|
19035
|
-
|
|
19036
|
-
|
|
19037
|
-
|
|
19038
|
-
|
|
19039
|
-
|
|
19040
|
-
|
|
19041
|
-
|
|
20929
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("header", { className: "pano-map-dialog-header", style: { flexShrink: 0 }, children: [
|
|
20930
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)("h2", { id: titleId, className: "pano-map-dialog-title", children: title }),
|
|
20931
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
20932
|
+
"button",
|
|
20933
|
+
{
|
|
20934
|
+
type: "button",
|
|
20935
|
+
className: "pano-map-dialog-close",
|
|
20936
|
+
"data-pano-map-dialog-close": "",
|
|
20937
|
+
"aria-label": closeLabel,
|
|
20938
|
+
onClick: () => onOpenChange(false),
|
|
20939
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("svg", { "aria-hidden": "true", width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("path", { d: "m4 4 8 8M12 4l-8 8" }) })
|
|
20940
|
+
}
|
|
20941
|
+
)
|
|
20942
|
+
] }),
|
|
20943
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "pano-map-dialog-body", style: { minHeight: 0, flex: 1 }, children })
|
|
19042
20944
|
]
|
|
19043
20945
|
}
|
|
19044
|
-
)
|
|
19045
|
-
captionsEnabled ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(PanoVideoCaptions, { appearance: typeof captions === "object" ? captions : void 0 }) : null
|
|
20946
|
+
)
|
|
19046
20947
|
] });
|
|
19047
20948
|
}
|
|
20949
|
+
function PanoMapOverlay({
|
|
20950
|
+
map,
|
|
20951
|
+
activeSceneId,
|
|
20952
|
+
onSelectScene,
|
|
20953
|
+
view,
|
|
20954
|
+
mode = "view",
|
|
20955
|
+
selectedSpotId,
|
|
20956
|
+
onSelectedSpotIdChange,
|
|
20957
|
+
onEditChange,
|
|
20958
|
+
placementSceneId,
|
|
20959
|
+
presentationOverride,
|
|
20960
|
+
presentation: presentationModeProp,
|
|
20961
|
+
corner: cornerProp,
|
|
20962
|
+
width: widthProp,
|
|
20963
|
+
height: heightProp,
|
|
20964
|
+
offsetX: offsetXProp,
|
|
20965
|
+
offsetY: offsetYProp,
|
|
20966
|
+
dialogWidth = DEFAULT_DIALOG_WIDTH,
|
|
20967
|
+
dialogHeight = DEFAULT_DIALOG_HEIGHT,
|
|
20968
|
+
interaction,
|
|
20969
|
+
maxScale,
|
|
20970
|
+
initialScale,
|
|
20971
|
+
showZoomControls,
|
|
20972
|
+
showCollapseControl,
|
|
20973
|
+
iconSize,
|
|
20974
|
+
openLabel = "Map",
|
|
20975
|
+
closeLabel = "Close",
|
|
20976
|
+
title = "Map",
|
|
20977
|
+
className,
|
|
20978
|
+
style,
|
|
20979
|
+
editorSurface = false
|
|
20980
|
+
}) {
|
|
20981
|
+
const { size } = usePanoOverlay();
|
|
20982
|
+
const [open, setOpen] = (0, import_react67.useState)(false);
|
|
20983
|
+
const [collapsed, setCollapsed] = (0, import_react67.useState)(false);
|
|
20984
|
+
const basePresentation = presentationOverride ?? map.presentation ?? {
|
|
20985
|
+
mode: "docked",
|
|
20986
|
+
corner: "bottom-left",
|
|
20987
|
+
width: 220,
|
|
20988
|
+
height: 160,
|
|
20989
|
+
offsetX: 16,
|
|
20990
|
+
offsetY: 16
|
|
20991
|
+
};
|
|
20992
|
+
const presentationMode = presentationModeProp ?? basePresentation.mode;
|
|
20993
|
+
const corner = cornerProp ?? basePresentation.corner ?? "bottom-left";
|
|
20994
|
+
const offsetX = offsetXProp ?? basePresentation.offsetX ?? 16;
|
|
20995
|
+
const offsetY = offsetYProp ?? basePresentation.offsetY ?? 16;
|
|
20996
|
+
const width = widthProp ?? basePresentation.width ?? (presentationMode === "dialog" ? 48 : 220);
|
|
20997
|
+
const height = heightProp ?? basePresentation.height ?? (presentationMode === "dialog" ? 48 : 160);
|
|
20998
|
+
const resolvedMap = (0, import_react67.useMemo)(() => {
|
|
20999
|
+
if (interaction === void 0 && maxScale === void 0 && iconSize === void 0) {
|
|
21000
|
+
return map;
|
|
21001
|
+
}
|
|
21002
|
+
return {
|
|
21003
|
+
...map,
|
|
21004
|
+
...interaction === void 0 ? {} : { interaction },
|
|
21005
|
+
...maxScale === void 0 ? {} : { maxScale },
|
|
21006
|
+
...iconSize === void 0 ? {} : { iconSize }
|
|
21007
|
+
};
|
|
21008
|
+
}, [iconSize, interaction, map, maxScale]);
|
|
21009
|
+
const mapProps = {
|
|
21010
|
+
map: resolvedMap,
|
|
21011
|
+
activeSceneId,
|
|
21012
|
+
view,
|
|
21013
|
+
mode,
|
|
21014
|
+
selectedSpotId,
|
|
21015
|
+
onSelectedSpotIdChange,
|
|
21016
|
+
onEditChange,
|
|
21017
|
+
placementSceneId,
|
|
21018
|
+
initialScale,
|
|
21019
|
+
showZoomControls,
|
|
21020
|
+
showCollapseControl,
|
|
21021
|
+
onSelectScene: (sceneId, spot) => {
|
|
21022
|
+
onSelectScene?.(sceneId, spot);
|
|
21023
|
+
if (presentationMode === "dialog") setOpen(false);
|
|
21024
|
+
}
|
|
21025
|
+
};
|
|
21026
|
+
const editorBox = (0, import_react67.useMemo)(() => {
|
|
21027
|
+
const panelWidth2 = widthProp !== void 0 && Number.isFinite(widthProp) && widthProp > 0 ? widthProp : Math.min(420, Math.max(240, size.width * 0.45));
|
|
21028
|
+
const panelHeight2 = heightProp !== void 0 && Number.isFinite(heightProp) && heightProp > 0 ? heightProp : Math.min(320, Math.max(180, size.height * 0.45));
|
|
21029
|
+
return {
|
|
21030
|
+
left: 16,
|
|
21031
|
+
right: "auto",
|
|
21032
|
+
top: "auto",
|
|
21033
|
+
bottom: 16,
|
|
21034
|
+
width: panelWidth2,
|
|
21035
|
+
height: panelHeight2
|
|
21036
|
+
};
|
|
21037
|
+
}, [heightProp, size.height, size.width, widthProp]);
|
|
21038
|
+
if (editorSurface) {
|
|
21039
|
+
return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
21040
|
+
"div",
|
|
21041
|
+
{
|
|
21042
|
+
className: ["pano-map-overlay", "pano-map-overlay-editor", className].filter(Boolean).join(" "),
|
|
21043
|
+
style: {
|
|
21044
|
+
position: "absolute",
|
|
21045
|
+
pointerEvents: "auto",
|
|
21046
|
+
...editorBox,
|
|
21047
|
+
...style
|
|
21048
|
+
},
|
|
21049
|
+
"data-pano-map-overlay": "",
|
|
21050
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(PanoMap, { ...mapProps, showCollapseControl: false })
|
|
21051
|
+
}
|
|
21052
|
+
);
|
|
21053
|
+
}
|
|
21054
|
+
if (presentationMode === "dialog") {
|
|
21055
|
+
const launcherBox = dockedBox(corner, offsetX, offsetY, width, height);
|
|
21056
|
+
return /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: ["pano-map-overlay", className].filter(Boolean).join(" "), "data-pano-map-overlay": "", children: [
|
|
21057
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
21058
|
+
"button",
|
|
21059
|
+
{
|
|
21060
|
+
type: "button",
|
|
21061
|
+
className: "pano-map-launcher",
|
|
21062
|
+
"aria-label": openLabel,
|
|
21063
|
+
style: {
|
|
21064
|
+
position: "absolute",
|
|
21065
|
+
pointerEvents: "auto",
|
|
21066
|
+
...launcherBox,
|
|
21067
|
+
...style
|
|
21068
|
+
},
|
|
21069
|
+
onClick: () => setOpen(true),
|
|
21070
|
+
children: openLabel
|
|
21071
|
+
}
|
|
21072
|
+
),
|
|
21073
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
21074
|
+
PanoMapDialogShell,
|
|
21075
|
+
{
|
|
21076
|
+
open,
|
|
21077
|
+
onOpenChange: setOpen,
|
|
21078
|
+
title,
|
|
21079
|
+
closeLabel,
|
|
21080
|
+
dialogWidth,
|
|
21081
|
+
dialogHeight,
|
|
21082
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { style: { height: "100%" }, children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(PanoMap, { ...mapProps, showCollapseControl: false }) })
|
|
21083
|
+
}
|
|
21084
|
+
)
|
|
21085
|
+
] });
|
|
21086
|
+
}
|
|
21087
|
+
const panelWidth = collapsed ? Math.max(44, Math.min(width, 56)) : width;
|
|
21088
|
+
const panelHeight = collapsed ? Math.max(44, Math.min(height, 56)) : height;
|
|
21089
|
+
const box = dockedBox(corner, offsetX, offsetY, panelWidth, panelHeight);
|
|
21090
|
+
return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
21091
|
+
"div",
|
|
21092
|
+
{
|
|
21093
|
+
className: ["pano-map-overlay", className].filter(Boolean).join(" "),
|
|
21094
|
+
style: {
|
|
21095
|
+
position: "absolute",
|
|
21096
|
+
pointerEvents: "auto",
|
|
21097
|
+
...box,
|
|
21098
|
+
...style
|
|
21099
|
+
},
|
|
21100
|
+
"data-pano-map-overlay": "",
|
|
21101
|
+
"data-presentation": "docked",
|
|
21102
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
21103
|
+
PanoMap,
|
|
21104
|
+
{
|
|
21105
|
+
...mapProps,
|
|
21106
|
+
collapsed,
|
|
21107
|
+
onCollapsedChange: setCollapsed
|
|
21108
|
+
}
|
|
21109
|
+
)
|
|
21110
|
+
}
|
|
21111
|
+
);
|
|
21112
|
+
}
|
|
19048
21113
|
// Annotate the CommonJS export names for ESM import in node:
|
|
19049
21114
|
0 && (module.exports = {
|
|
19050
21115
|
AudioHotspot,
|
|
@@ -19053,6 +21118,7 @@ function PanoVideo({
|
|
|
19053
21118
|
DEFAULT_HOTSPOT_TOOLTIP_APPEARANCE,
|
|
19054
21119
|
DEFAULT_HOTSPOT_TOOLTIP_OFFSET,
|
|
19055
21120
|
DEFAULT_PANO_CURSORS,
|
|
21121
|
+
DEFAULT_PANO_SCENE_MENU_APPEARANCE,
|
|
19056
21122
|
DEFAULT_PANO_VIDEO_CAPTION_APPEARANCE,
|
|
19057
21123
|
DEFAULT_PANO_VIDEO_CONTROLS_APPEARANCE,
|
|
19058
21124
|
DEFAULT_PANO_VIDEO_PLAYBACK_RATES,
|
|
@@ -19070,7 +21136,10 @@ function PanoVideo({
|
|
|
19070
21136
|
PanoContextMenu,
|
|
19071
21137
|
PanoEvents,
|
|
19072
21138
|
PanoFilter,
|
|
21139
|
+
PanoMap,
|
|
21140
|
+
PanoMapOverlay,
|
|
19073
21141
|
PanoOverlayPortalProvider,
|
|
21142
|
+
PanoSceneMenu,
|
|
19074
21143
|
PanoVideo,
|
|
19075
21144
|
PanoVideoControls,
|
|
19076
21145
|
PanoViewer,
|
|
@@ -19090,13 +21159,19 @@ function PanoVideo({
|
|
|
19090
21159
|
WebVR,
|
|
19091
21160
|
clampPanoPitch,
|
|
19092
21161
|
composePanoContextMenuItems,
|
|
21162
|
+
containRect,
|
|
21163
|
+
containerPointToImage,
|
|
19093
21164
|
createDefaultPanoContextMenuItems,
|
|
19094
21165
|
createPanoContextMenuPresets,
|
|
19095
21166
|
cycleSceneId,
|
|
21167
|
+
headingFromVector,
|
|
21168
|
+
horizontalFov,
|
|
21169
|
+
imagePointToContainer,
|
|
19096
21170
|
normalizePanoPosition,
|
|
19097
21171
|
normalizePanoYaw,
|
|
19098
21172
|
panoPositionToVector3,
|
|
19099
21173
|
resolvePanoContextMenuEntries,
|
|
21174
|
+
sectorRotation,
|
|
19100
21175
|
unwrapPolygonVertices,
|
|
19101
21176
|
usePanoEvents,
|
|
19102
21177
|
usePanoModal,
|