@ericchen1990/pano-view 3.7.0 → 3.8.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/dist/data.cjs +220 -7
- package/dist/data.cjs.map +1 -1
- package/dist/data.d.mts +104 -2
- package/dist/data.d.ts +104 -2
- package/dist/data.mjs +220 -7
- package/dist/data.mjs.map +1 -1
- package/dist/index.cjs +974 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +194 -2
- package/dist/index.d.ts +194 -2
- package/dist/index.mjs +980 -2
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +125 -0
- package/dist/ui.cjs +1 -1
- package/dist/ui.cjs.map +1 -1
- package/dist/ui.mjs +1 -1
- package/dist/ui.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -9011,7 +9011,7 @@ var PanoViewer = forwardRef2(
|
|
|
9011
9011
|
contextMenuOverlay,
|
|
9012
9012
|
hotspotAccessibilityControls
|
|
9013
9013
|
] }),
|
|
9014
|
-
/* @__PURE__ */ jsx13(PanoModalHostContext.Provider, { value: modalHost, children: /* @__PURE__ */ jsx13(PanoOverlayContext.Provider, { value: overlayContext, children: /* @__PURE__ */ jsx13(
|
|
9014
|
+
/* @__PURE__ */ jsx13(PanoEventBusContext.Provider, { value: eventBus, children: /* @__PURE__ */ jsx13(PanoModalHostContext.Provider, { value: modalHost, children: /* @__PURE__ */ jsx13(PanoOverlayContext.Provider, { value: overlayContext, children: /* @__PURE__ */ jsx13(
|
|
9015
9015
|
"div",
|
|
9016
9016
|
{
|
|
9017
9017
|
"data-pano-overlay": "",
|
|
@@ -9019,7 +9019,7 @@ var PanoViewer = forwardRef2(
|
|
|
9019
9019
|
style: { position: "absolute", inset: 0, zIndex: 30, pointerEvents: "none" },
|
|
9020
9020
|
children: renderOverlay?.(overlayContext)
|
|
9021
9021
|
}
|
|
9022
|
-
) }) })
|
|
9022
|
+
) }) }) })
|
|
9023
9023
|
]
|
|
9024
9024
|
}
|
|
9025
9025
|
);
|
|
@@ -19240,6 +19240,976 @@ function PanoVideo({
|
|
|
19240
19240
|
captionsEnabled ? /* @__PURE__ */ jsx41(PanoVideoCaptions, { appearance: typeof captions === "object" ? captions : void 0 }) : null
|
|
19241
19241
|
] });
|
|
19242
19242
|
}
|
|
19243
|
+
|
|
19244
|
+
// src/map/pano-map.tsx
|
|
19245
|
+
import {
|
|
19246
|
+
useCallback as useCallback12,
|
|
19247
|
+
useEffect as useEffect43,
|
|
19248
|
+
useId as useId3,
|
|
19249
|
+
useLayoutEffect as useLayoutEffect10,
|
|
19250
|
+
useMemo as useMemo29,
|
|
19251
|
+
useRef as useRef43,
|
|
19252
|
+
useState as useState27
|
|
19253
|
+
} from "react";
|
|
19254
|
+
|
|
19255
|
+
// src/map/geometry.ts
|
|
19256
|
+
function sectorRotation(heading, yaw) {
|
|
19257
|
+
return heading + yaw;
|
|
19258
|
+
}
|
|
19259
|
+
function horizontalFov(verticalFov, aspect) {
|
|
19260
|
+
if (!(aspect > 0) || !(verticalFov > 0) || verticalFov >= 180) return verticalFov;
|
|
19261
|
+
const verticalRad = verticalFov * Math.PI / 180;
|
|
19262
|
+
const horizontalRad = 2 * Math.atan(Math.tan(verticalRad / 2) * aspect);
|
|
19263
|
+
return horizontalRad * 180 / Math.PI;
|
|
19264
|
+
}
|
|
19265
|
+
function containRect(containerWidth, containerHeight, imageWidth, imageHeight) {
|
|
19266
|
+
if (!(containerWidth > 0) || !(containerHeight > 0) || !(imageWidth > 0) || !(imageHeight > 0)) {
|
|
19267
|
+
return { left: 0, top: 0, width: 0, height: 0 };
|
|
19268
|
+
}
|
|
19269
|
+
const scale = Math.min(
|
|
19270
|
+
containerWidth / imageWidth,
|
|
19271
|
+
containerHeight / imageHeight
|
|
19272
|
+
);
|
|
19273
|
+
const width = imageWidth * scale;
|
|
19274
|
+
const height = imageHeight * scale;
|
|
19275
|
+
return {
|
|
19276
|
+
left: (containerWidth - width) / 2,
|
|
19277
|
+
top: (containerHeight - height) / 2,
|
|
19278
|
+
width,
|
|
19279
|
+
height
|
|
19280
|
+
};
|
|
19281
|
+
}
|
|
19282
|
+
function imagePointToContainer(point, viewport) {
|
|
19283
|
+
const { fit, scale, panX, panY } = viewport;
|
|
19284
|
+
return {
|
|
19285
|
+
x: fit.left + panX + point.x * fit.width * scale,
|
|
19286
|
+
y: fit.top + panY + point.y * fit.height * scale
|
|
19287
|
+
};
|
|
19288
|
+
}
|
|
19289
|
+
function containerPointToImage(point, viewport) {
|
|
19290
|
+
const { fit, scale, panX, panY } = viewport;
|
|
19291
|
+
const width = fit.width * scale;
|
|
19292
|
+
const height = fit.height * scale;
|
|
19293
|
+
if (!(width > 0) || !(height > 0)) return null;
|
|
19294
|
+
return {
|
|
19295
|
+
x: (point.x - fit.left - panX) / width,
|
|
19296
|
+
y: (point.y - fit.top - panY) / height
|
|
19297
|
+
};
|
|
19298
|
+
}
|
|
19299
|
+
function clamp012(value) {
|
|
19300
|
+
return Math.min(1, Math.max(0, value));
|
|
19301
|
+
}
|
|
19302
|
+
function headingFromVector(dx, dy) {
|
|
19303
|
+
if (dx === 0 && dy === 0) return 0;
|
|
19304
|
+
const radians = Math.atan2(dx, -dy);
|
|
19305
|
+
return radians * 180 / Math.PI;
|
|
19306
|
+
}
|
|
19307
|
+
function normalizeDegrees(value) {
|
|
19308
|
+
const wrapped = (value % 360 + 360) % 360;
|
|
19309
|
+
return wrapped > 180 ? wrapped - 360 : wrapped;
|
|
19310
|
+
}
|
|
19311
|
+
|
|
19312
|
+
// src/map/use-map-view.ts
|
|
19313
|
+
import { useContext as useContext29, useEffect as useEffect42, useRef as useRef42, useState as useState26 } from "react";
|
|
19314
|
+
var DEFAULT_VIEW2 = { yaw: 0, pitch: 0, fov: 75 };
|
|
19315
|
+
function useMapView(controlled) {
|
|
19316
|
+
const eventBus = useContext29(PanoEventBusContext);
|
|
19317
|
+
const [view, setView] = useState26(controlled ?? DEFAULT_VIEW2);
|
|
19318
|
+
const controlledRef = useRef42(controlled);
|
|
19319
|
+
controlledRef.current = controlled;
|
|
19320
|
+
useEffect42(() => {
|
|
19321
|
+
if (controlled !== void 0) {
|
|
19322
|
+
setView(controlled);
|
|
19323
|
+
return;
|
|
19324
|
+
}
|
|
19325
|
+
if (!eventBus) return;
|
|
19326
|
+
return eventBus.subscribe("viewchange", (next) => {
|
|
19327
|
+
if (controlledRef.current !== void 0) return;
|
|
19328
|
+
setView(next);
|
|
19329
|
+
});
|
|
19330
|
+
}, [controlled, eventBus]);
|
|
19331
|
+
return controlled ?? view;
|
|
19332
|
+
}
|
|
19333
|
+
|
|
19334
|
+
// src/map/pano-map.tsx
|
|
19335
|
+
import { Fragment as Fragment8, jsx as jsx42, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
19336
|
+
var DEFAULT_ICON_SIZE2 = 28;
|
|
19337
|
+
var DEFAULT_SECTOR_COLOR = "#ffffff";
|
|
19338
|
+
var DEFAULT_SECTOR_OPACITY = 0.55;
|
|
19339
|
+
var MIN_SCALE = 1;
|
|
19340
|
+
var DEFAULT_MAX_SCALE = 8;
|
|
19341
|
+
var ZOOM_STEP = 1.25;
|
|
19342
|
+
function defaultGradientId(prefix) {
|
|
19343
|
+
return `${prefix}-sector-gradient`;
|
|
19344
|
+
}
|
|
19345
|
+
function SectorShape({
|
|
19346
|
+
sector,
|
|
19347
|
+
angle,
|
|
19348
|
+
rotation,
|
|
19349
|
+
gradientId,
|
|
19350
|
+
interactive,
|
|
19351
|
+
onHeadingPointerDown,
|
|
19352
|
+
transformRef
|
|
19353
|
+
}) {
|
|
19354
|
+
const half = angle / 2;
|
|
19355
|
+
const start = (-half - 90) * Math.PI / 180;
|
|
19356
|
+
const end = (half - 90) * Math.PI / 180;
|
|
19357
|
+
const radius = sector.radius;
|
|
19358
|
+
const largeArc = angle > 180 ? 1 : 0;
|
|
19359
|
+
const x1 = Math.cos(start) * radius;
|
|
19360
|
+
const y1 = Math.sin(start) * radius;
|
|
19361
|
+
const x2 = Math.cos(end) * radius;
|
|
19362
|
+
const y2 = Math.sin(end) * radius;
|
|
19363
|
+
const path = `M 0 0 L ${x1} ${y1} A ${radius} ${radius} 0 ${largeArc} 1 ${x2} ${y2} Z`;
|
|
19364
|
+
const opacity = sector.opacity ?? DEFAULT_SECTOR_OPACITY;
|
|
19365
|
+
const color = sector.color ?? DEFAULT_SECTOR_COLOR;
|
|
19366
|
+
const gradient = sector.gradient;
|
|
19367
|
+
const strokeWidth = sector.strokeWidth ?? 0;
|
|
19368
|
+
const strokeColor = sector.strokeColor ?? color;
|
|
19369
|
+
const strokeOpacity = sector.strokeOpacity ?? 1;
|
|
19370
|
+
const gradientStrength = gradient && gradient.stops.length > 0 ? Math.max(
|
|
19371
|
+
...gradient.stops.map((stop) => stop.opacity ?? 1),
|
|
19372
|
+
Number.EPSILON
|
|
19373
|
+
) : 1;
|
|
19374
|
+
return /* @__PURE__ */ jsxs23("g", { ref: transformRef, transform: `rotate(${rotation})`, style: { pointerEvents: "none" }, children: [
|
|
19375
|
+
gradient ? /* @__PURE__ */ jsx42("defs", { children: gradient.type === "radial" ? /* @__PURE__ */ jsx42("radialGradient", { id: gradientId, cx: "0", cy: "0", r: radius, gradientUnits: "userSpaceOnUse", children: gradient.stops.map((stop, index) => /* @__PURE__ */ jsx42(
|
|
19376
|
+
"stop",
|
|
19377
|
+
{
|
|
19378
|
+
offset: `${stop.offset * 100}%`,
|
|
19379
|
+
stopColor: stop.color,
|
|
19380
|
+
stopOpacity: (stop.opacity ?? 1) / gradientStrength
|
|
19381
|
+
},
|
|
19382
|
+
`${stop.offset}:${index}`
|
|
19383
|
+
)) }) : /* @__PURE__ */ jsx42(
|
|
19384
|
+
"linearGradient",
|
|
19385
|
+
{
|
|
19386
|
+
id: gradientId,
|
|
19387
|
+
x1: "0",
|
|
19388
|
+
y1: "0",
|
|
19389
|
+
x2: "0",
|
|
19390
|
+
y2: -radius,
|
|
19391
|
+
gradientUnits: "userSpaceOnUse",
|
|
19392
|
+
children: gradient.stops.map((stop, index) => /* @__PURE__ */ jsx42(
|
|
19393
|
+
"stop",
|
|
19394
|
+
{
|
|
19395
|
+
offset: `${stop.offset * 100}%`,
|
|
19396
|
+
stopColor: stop.color,
|
|
19397
|
+
stopOpacity: (stop.opacity ?? 1) / gradientStrength
|
|
19398
|
+
},
|
|
19399
|
+
`${stop.offset}:${index}`
|
|
19400
|
+
))
|
|
19401
|
+
}
|
|
19402
|
+
) }) : null,
|
|
19403
|
+
/* @__PURE__ */ jsx42(
|
|
19404
|
+
"path",
|
|
19405
|
+
{
|
|
19406
|
+
d: path,
|
|
19407
|
+
fill: gradient ? `url(#${gradientId})` : color,
|
|
19408
|
+
fillOpacity: opacity,
|
|
19409
|
+
stroke: strokeWidth > 0 ? strokeColor : "none",
|
|
19410
|
+
strokeWidth: strokeWidth > 0 ? strokeWidth : void 0,
|
|
19411
|
+
strokeOpacity: strokeWidth > 0 ? strokeOpacity : void 0,
|
|
19412
|
+
vectorEffect: "non-scaling-stroke",
|
|
19413
|
+
style: {
|
|
19414
|
+
pointerEvents: interactive ? "auto" : "none",
|
|
19415
|
+
cursor: interactive ? "grab" : void 0
|
|
19416
|
+
},
|
|
19417
|
+
onPointerDown: interactive ? onHeadingPointerDown : void 0
|
|
19418
|
+
}
|
|
19419
|
+
)
|
|
19420
|
+
] });
|
|
19421
|
+
}
|
|
19422
|
+
function DefaultSpotIcon({ active, size }) {
|
|
19423
|
+
return /* @__PURE__ */ jsxs23(
|
|
19424
|
+
"svg",
|
|
19425
|
+
{
|
|
19426
|
+
width: size,
|
|
19427
|
+
height: size,
|
|
19428
|
+
viewBox: "0 0 32 32",
|
|
19429
|
+
"aria-hidden": "true",
|
|
19430
|
+
style: { display: "block" },
|
|
19431
|
+
children: [
|
|
19432
|
+
/* @__PURE__ */ jsx42(
|
|
19433
|
+
"rect",
|
|
19434
|
+
{
|
|
19435
|
+
x: "3",
|
|
19436
|
+
y: "7",
|
|
19437
|
+
width: "26",
|
|
19438
|
+
height: "18",
|
|
19439
|
+
rx: "5",
|
|
19440
|
+
fill: active ? "rgba(15,23,42,0.92)" : "rgba(15,23,42,0.72)",
|
|
19441
|
+
stroke: "rgba(255,255,255,0.92)",
|
|
19442
|
+
strokeWidth: "2"
|
|
19443
|
+
}
|
|
19444
|
+
),
|
|
19445
|
+
/* @__PURE__ */ jsx42("circle", { cx: "16", cy: "16", r: "5", fill: "none", stroke: "rgba(255,255,255,0.92)", strokeWidth: "2" }),
|
|
19446
|
+
/* @__PURE__ */ jsx42("circle", { cx: "16", cy: "16", r: "2", fill: active ? "#ef4444" : "rgba(255,255,255,0.85)" })
|
|
19447
|
+
]
|
|
19448
|
+
}
|
|
19449
|
+
);
|
|
19450
|
+
}
|
|
19451
|
+
function PanoMap({
|
|
19452
|
+
map,
|
|
19453
|
+
activeSceneId,
|
|
19454
|
+
onSelectScene,
|
|
19455
|
+
view: controlledView,
|
|
19456
|
+
mode = "view",
|
|
19457
|
+
selectedSpotId = null,
|
|
19458
|
+
onSelectedSpotIdChange,
|
|
19459
|
+
onEditChange,
|
|
19460
|
+
placementSceneId,
|
|
19461
|
+
className,
|
|
19462
|
+
style,
|
|
19463
|
+
showZoomControls,
|
|
19464
|
+
showCollapseControl,
|
|
19465
|
+
collapsed: controlledCollapsed,
|
|
19466
|
+
onCollapsedChange,
|
|
19467
|
+
renderChrome
|
|
19468
|
+
}) {
|
|
19469
|
+
const reactId = useId3().replace(/:/g, "");
|
|
19470
|
+
const view = useMapView(controlledView);
|
|
19471
|
+
const rootRef = useRef43(null);
|
|
19472
|
+
const containerRef = useRef43(null);
|
|
19473
|
+
const sectorNodeRef = useRef43(null);
|
|
19474
|
+
const yawRef = useRef43(view.yaw);
|
|
19475
|
+
const [size, setSize] = useState27({ width: 0, height: 0 });
|
|
19476
|
+
const [imageSize2, setImageSize] = useState27({ width: 0, height: 0 });
|
|
19477
|
+
const [scale, setScale] = useState27(1);
|
|
19478
|
+
const [pan, setPan] = useState27({ x: 0, y: 0 });
|
|
19479
|
+
const [uncontrolledCollapsed, setUncontrolledCollapsed] = useState27(false);
|
|
19480
|
+
const dragRef = useRef43(null);
|
|
19481
|
+
const spots = map.spots;
|
|
19482
|
+
const iconSize = map.iconSize ?? DEFAULT_ICON_SIZE2;
|
|
19483
|
+
const interaction = map.interaction ?? "fixed";
|
|
19484
|
+
const gestureZoom = interaction === "zoom";
|
|
19485
|
+
const maxScale = Math.max(MIN_SCALE, map.maxScale ?? DEFAULT_MAX_SCALE);
|
|
19486
|
+
const zoomControls = gestureZoom && (showZoomControls ?? map.controls?.zoom === true);
|
|
19487
|
+
const collapseControl = showCollapseControl ?? map.controls?.collapse === true;
|
|
19488
|
+
const collapsed = controlledCollapsed ?? uncontrolledCollapsed;
|
|
19489
|
+
const editing = mode === "edit";
|
|
19490
|
+
const fit = useMemo29(
|
|
19491
|
+
() => containRect(size.width, size.height, imageSize2.width, imageSize2.height),
|
|
19492
|
+
[size, imageSize2]
|
|
19493
|
+
);
|
|
19494
|
+
const viewport = useMemo29(
|
|
19495
|
+
() => ({ fit, scale, panX: pan.x, panY: pan.y }),
|
|
19496
|
+
[fit, scale, pan]
|
|
19497
|
+
);
|
|
19498
|
+
useLayoutEffect10(() => {
|
|
19499
|
+
if (collapsed) return;
|
|
19500
|
+
const node = containerRef.current;
|
|
19501
|
+
if (!node) return;
|
|
19502
|
+
const measure = () => {
|
|
19503
|
+
const width = node.clientWidth;
|
|
19504
|
+
const height = node.clientHeight;
|
|
19505
|
+
if (!(width > 0) || !(height > 0)) return;
|
|
19506
|
+
setSize(
|
|
19507
|
+
(previous) => previous.width === width && previous.height === height ? previous : { width, height }
|
|
19508
|
+
);
|
|
19509
|
+
};
|
|
19510
|
+
measure();
|
|
19511
|
+
const observer = new ResizeObserver(measure);
|
|
19512
|
+
observer.observe(node);
|
|
19513
|
+
return () => observer.disconnect();
|
|
19514
|
+
}, [collapsed]);
|
|
19515
|
+
useEffect43(() => {
|
|
19516
|
+
yawRef.current = view.yaw;
|
|
19517
|
+
const node = sectorNodeRef.current;
|
|
19518
|
+
if (!node) return;
|
|
19519
|
+
const active = spots.find((spot) => spot.sceneId === activeSceneId);
|
|
19520
|
+
if (!active || !map.sector) return;
|
|
19521
|
+
node.setAttribute(
|
|
19522
|
+
"transform",
|
|
19523
|
+
`rotate(${sectorRotation(active.heading, view.yaw)})`
|
|
19524
|
+
);
|
|
19525
|
+
}, [view.yaw, activeSceneId, spots, map.sector]);
|
|
19526
|
+
const setCollapsed = (next) => {
|
|
19527
|
+
onCollapsedChange?.(next);
|
|
19528
|
+
if (controlledCollapsed === void 0) setUncontrolledCollapsed(next);
|
|
19529
|
+
};
|
|
19530
|
+
const clampPan = useCallback12(
|
|
19531
|
+
(nextScale, nextPan) => {
|
|
19532
|
+
const width = fit.width * nextScale;
|
|
19533
|
+
const height = fit.height * nextScale;
|
|
19534
|
+
if (!(fit.width > 0) || !(fit.height > 0)) return nextPan;
|
|
19535
|
+
const minX = Math.min(0, size.width - (fit.left + width));
|
|
19536
|
+
const maxX = Math.max(0, -fit.left);
|
|
19537
|
+
const minY = Math.min(0, size.height - (fit.top + height));
|
|
19538
|
+
const maxY = Math.max(0, -fit.top);
|
|
19539
|
+
return {
|
|
19540
|
+
x: Math.min(maxX, Math.max(minX, nextPan.x)),
|
|
19541
|
+
y: Math.min(maxY, Math.max(minY, nextPan.y))
|
|
19542
|
+
};
|
|
19543
|
+
},
|
|
19544
|
+
[fit, size]
|
|
19545
|
+
);
|
|
19546
|
+
useEffect43(() => {
|
|
19547
|
+
if (!gestureZoom) {
|
|
19548
|
+
setScale(1);
|
|
19549
|
+
setPan({ x: 0, y: 0 });
|
|
19550
|
+
return;
|
|
19551
|
+
}
|
|
19552
|
+
setScale((previous) => {
|
|
19553
|
+
const next = Math.min(previous, maxScale);
|
|
19554
|
+
if (next !== previous) {
|
|
19555
|
+
setPan((currentPan) => clampPan(next, currentPan));
|
|
19556
|
+
}
|
|
19557
|
+
return next;
|
|
19558
|
+
});
|
|
19559
|
+
}, [clampPan, gestureZoom, maxScale]);
|
|
19560
|
+
const applyZoom = useCallback12(
|
|
19561
|
+
(nextScale, origin) => {
|
|
19562
|
+
if (!gestureZoom) return;
|
|
19563
|
+
const clamped = Math.min(maxScale, Math.max(MIN_SCALE, nextScale));
|
|
19564
|
+
const pivot = origin ?? { x: size.width / 2, y: size.height / 2 };
|
|
19565
|
+
const ratio = clamped / scale;
|
|
19566
|
+
const nextPan = {
|
|
19567
|
+
x: pivot.x - (pivot.x - (fit.left + pan.x)) * ratio - fit.left,
|
|
19568
|
+
y: pivot.y - (pivot.y - (fit.top + pan.y)) * ratio - fit.top
|
|
19569
|
+
};
|
|
19570
|
+
setScale(clamped);
|
|
19571
|
+
setPan(clampPan(clamped, nextPan));
|
|
19572
|
+
},
|
|
19573
|
+
[
|
|
19574
|
+
clampPan,
|
|
19575
|
+
fit.left,
|
|
19576
|
+
fit.top,
|
|
19577
|
+
gestureZoom,
|
|
19578
|
+
maxScale,
|
|
19579
|
+
pan.x,
|
|
19580
|
+
pan.y,
|
|
19581
|
+
scale,
|
|
19582
|
+
size.height,
|
|
19583
|
+
size.width
|
|
19584
|
+
]
|
|
19585
|
+
);
|
|
19586
|
+
useEffect43(() => {
|
|
19587
|
+
const root = rootRef.current;
|
|
19588
|
+
if (!root) return;
|
|
19589
|
+
const onWheel = (event) => {
|
|
19590
|
+
event.preventDefault();
|
|
19591
|
+
event.stopPropagation();
|
|
19592
|
+
if (!gestureZoom || collapsed) return;
|
|
19593
|
+
const viewport2 = containerRef.current;
|
|
19594
|
+
if (!viewport2) return;
|
|
19595
|
+
const rect = viewport2.getBoundingClientRect();
|
|
19596
|
+
const factor = event.deltaY < 0 ? ZOOM_STEP : 1 / ZOOM_STEP;
|
|
19597
|
+
applyZoom(scale * factor, {
|
|
19598
|
+
x: event.clientX - rect.left,
|
|
19599
|
+
y: event.clientY - rect.top
|
|
19600
|
+
});
|
|
19601
|
+
};
|
|
19602
|
+
root.addEventListener("wheel", onWheel, { passive: false });
|
|
19603
|
+
return () => root.removeEventListener("wheel", onWheel);
|
|
19604
|
+
}, [applyZoom, collapsed, gestureZoom, scale]);
|
|
19605
|
+
const updateSpots = (next, selected) => {
|
|
19606
|
+
onEditChange?.({
|
|
19607
|
+
spots: next,
|
|
19608
|
+
...selected === void 0 ? {} : { selectedSpotId: selected }
|
|
19609
|
+
});
|
|
19610
|
+
};
|
|
19611
|
+
const localPoint = (event) => {
|
|
19612
|
+
const rect = containerRef.current?.getBoundingClientRect();
|
|
19613
|
+
if (!rect) return null;
|
|
19614
|
+
return { x: event.clientX - rect.left, y: event.clientY - rect.top };
|
|
19615
|
+
};
|
|
19616
|
+
const onPointerDownSurface = (event) => {
|
|
19617
|
+
if (collapsed) return;
|
|
19618
|
+
if (event.button !== 0) return;
|
|
19619
|
+
const point = localPoint(event);
|
|
19620
|
+
if (!point) return;
|
|
19621
|
+
if (editing && placementSceneId) {
|
|
19622
|
+
const image = containerPointToImage(point, viewport);
|
|
19623
|
+
if (!image) return;
|
|
19624
|
+
const spot = {
|
|
19625
|
+
id: crypto.randomUUID(),
|
|
19626
|
+
sceneId: placementSceneId,
|
|
19627
|
+
x: clamp012(image.x),
|
|
19628
|
+
y: clamp012(image.y),
|
|
19629
|
+
heading: normalizeDegrees(-view.yaw)
|
|
19630
|
+
};
|
|
19631
|
+
updateSpots([...spots, spot], spot.id);
|
|
19632
|
+
onSelectedSpotIdChange?.(spot.id);
|
|
19633
|
+
event.preventDefault();
|
|
19634
|
+
event.stopPropagation();
|
|
19635
|
+
return;
|
|
19636
|
+
}
|
|
19637
|
+
if (gestureZoom && scale > 1) {
|
|
19638
|
+
dragRef.current = {
|
|
19639
|
+
kind: "pan",
|
|
19640
|
+
pointerId: event.pointerId,
|
|
19641
|
+
originX: point.x,
|
|
19642
|
+
originY: point.y,
|
|
19643
|
+
panX: pan.x,
|
|
19644
|
+
panY: pan.y
|
|
19645
|
+
};
|
|
19646
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
19647
|
+
event.preventDefault();
|
|
19648
|
+
event.stopPropagation();
|
|
19649
|
+
}
|
|
19650
|
+
};
|
|
19651
|
+
const onPointerMoveSurface = (event) => {
|
|
19652
|
+
const drag = dragRef.current;
|
|
19653
|
+
if (!drag || drag.pointerId !== event.pointerId) return;
|
|
19654
|
+
const point = localPoint(event);
|
|
19655
|
+
if (!point) return;
|
|
19656
|
+
if (drag.kind === "pan") {
|
|
19657
|
+
setPan(
|
|
19658
|
+
clampPan(scale, {
|
|
19659
|
+
x: drag.panX + (point.x - drag.originX),
|
|
19660
|
+
y: drag.panY + (point.y - drag.originY)
|
|
19661
|
+
})
|
|
19662
|
+
);
|
|
19663
|
+
event.preventDefault();
|
|
19664
|
+
event.stopPropagation();
|
|
19665
|
+
return;
|
|
19666
|
+
}
|
|
19667
|
+
if (drag.kind === "spot") {
|
|
19668
|
+
const image = containerPointToImage(
|
|
19669
|
+
{ x: point.x - drag.offsetX, y: point.y - drag.offsetY },
|
|
19670
|
+
viewport
|
|
19671
|
+
);
|
|
19672
|
+
if (!image) return;
|
|
19673
|
+
updateSpots(
|
|
19674
|
+
spots.map(
|
|
19675
|
+
(spot2) => spot2.id === drag.spotId ? { ...spot2, x: clamp012(image.x), y: clamp012(image.y) } : spot2
|
|
19676
|
+
)
|
|
19677
|
+
);
|
|
19678
|
+
event.preventDefault();
|
|
19679
|
+
event.stopPropagation();
|
|
19680
|
+
return;
|
|
19681
|
+
}
|
|
19682
|
+
const spot = spots.find((entry) => entry.id === drag.spotId);
|
|
19683
|
+
if (!spot) return;
|
|
19684
|
+
const center = imagePointToContainer(spot, viewport);
|
|
19685
|
+
const heading = normalizeDegrees(
|
|
19686
|
+
headingFromVector(point.x - center.x, point.y - center.y) - view.yaw
|
|
19687
|
+
);
|
|
19688
|
+
updateSpots(
|
|
19689
|
+
spots.map(
|
|
19690
|
+
(entry) => entry.id === drag.spotId ? { ...entry, heading } : entry
|
|
19691
|
+
)
|
|
19692
|
+
);
|
|
19693
|
+
if (sectorNodeRef.current && spot.sceneId === activeSceneId) {
|
|
19694
|
+
sectorNodeRef.current.setAttribute(
|
|
19695
|
+
"transform",
|
|
19696
|
+
`rotate(${sectorRotation(heading, view.yaw)})`
|
|
19697
|
+
);
|
|
19698
|
+
}
|
|
19699
|
+
event.preventDefault();
|
|
19700
|
+
event.stopPropagation();
|
|
19701
|
+
};
|
|
19702
|
+
const endDrag = (event) => {
|
|
19703
|
+
if (dragRef.current?.pointerId !== event.pointerId) return;
|
|
19704
|
+
dragRef.current = null;
|
|
19705
|
+
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
|
|
19706
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
19707
|
+
}
|
|
19708
|
+
};
|
|
19709
|
+
const sectorAngle = (() => {
|
|
19710
|
+
if (!map.sector) return 0;
|
|
19711
|
+
if (!map.sector.followFov) return map.sector.angle;
|
|
19712
|
+
const aspect = size.width > 0 && size.height > 0 ? size.width / size.height : 1;
|
|
19713
|
+
return horizontalFov(view.fov, aspect);
|
|
19714
|
+
})();
|
|
19715
|
+
const activeSpot = spots.find((spot) => spot.sceneId === activeSceneId);
|
|
19716
|
+
const selectedSpot = spots.find((spot) => spot.id === selectedSpotId);
|
|
19717
|
+
const controls = /* @__PURE__ */ jsxs23("div", { className: "pano-map-controls", style: { pointerEvents: "auto" }, children: [
|
|
19718
|
+
collapseControl ? /* @__PURE__ */ jsx42(
|
|
19719
|
+
"button",
|
|
19720
|
+
{
|
|
19721
|
+
type: "button",
|
|
19722
|
+
className: "pano-map-control",
|
|
19723
|
+
"aria-label": collapsed ? "Expand map" : "Collapse map",
|
|
19724
|
+
"aria-pressed": !collapsed,
|
|
19725
|
+
style: {
|
|
19726
|
+
background: "rgba(15, 23, 42, 0.92)",
|
|
19727
|
+
border: "1px solid rgba(255, 255, 255, 0.28)",
|
|
19728
|
+
color: "#f8fafc"
|
|
19729
|
+
},
|
|
19730
|
+
onClick: () => setCollapsed(!collapsed),
|
|
19731
|
+
children: collapsed ? "\u25A3" : "\u25A2"
|
|
19732
|
+
}
|
|
19733
|
+
) : null,
|
|
19734
|
+
zoomControls && !collapsed ? /* @__PURE__ */ jsxs23(Fragment8, { children: [
|
|
19735
|
+
/* @__PURE__ */ jsx42(
|
|
19736
|
+
"button",
|
|
19737
|
+
{
|
|
19738
|
+
type: "button",
|
|
19739
|
+
className: "pano-map-control",
|
|
19740
|
+
"aria-label": "Zoom in",
|
|
19741
|
+
style: {
|
|
19742
|
+
background: "rgba(15, 23, 42, 0.92)",
|
|
19743
|
+
border: "1px solid rgba(255, 255, 255, 0.28)",
|
|
19744
|
+
color: "#f8fafc"
|
|
19745
|
+
},
|
|
19746
|
+
onClick: () => applyZoom(scale * ZOOM_STEP),
|
|
19747
|
+
children: "+"
|
|
19748
|
+
}
|
|
19749
|
+
),
|
|
19750
|
+
/* @__PURE__ */ jsx42(
|
|
19751
|
+
"button",
|
|
19752
|
+
{
|
|
19753
|
+
type: "button",
|
|
19754
|
+
className: "pano-map-control",
|
|
19755
|
+
"aria-label": "Zoom out",
|
|
19756
|
+
style: {
|
|
19757
|
+
background: "rgba(15, 23, 42, 0.92)",
|
|
19758
|
+
border: "1px solid rgba(255, 255, 255, 0.28)",
|
|
19759
|
+
color: "#f8fafc"
|
|
19760
|
+
},
|
|
19761
|
+
onClick: () => applyZoom(scale / ZOOM_STEP),
|
|
19762
|
+
children: "\u2212"
|
|
19763
|
+
}
|
|
19764
|
+
)
|
|
19765
|
+
] }) : null
|
|
19766
|
+
] });
|
|
19767
|
+
const syncImageSize = (image) => {
|
|
19768
|
+
if (!(image.naturalWidth > 0) || !(image.naturalHeight > 0)) return;
|
|
19769
|
+
setImageSize(
|
|
19770
|
+
(previous) => previous.width === image.naturalWidth && previous.height === image.naturalHeight ? previous : { width: image.naturalWidth, height: image.naturalHeight }
|
|
19771
|
+
);
|
|
19772
|
+
};
|
|
19773
|
+
return /* @__PURE__ */ jsxs23(
|
|
19774
|
+
"div",
|
|
19775
|
+
{
|
|
19776
|
+
ref: rootRef,
|
|
19777
|
+
className: ["pano-map", collapsed ? "pano-map-collapsed" : "", className].filter(Boolean).join(" "),
|
|
19778
|
+
style: {
|
|
19779
|
+
position: "relative",
|
|
19780
|
+
overflow: "hidden",
|
|
19781
|
+
width: "100%",
|
|
19782
|
+
height: "100%",
|
|
19783
|
+
pointerEvents: "auto",
|
|
19784
|
+
touchAction: gestureZoom && !collapsed ? "none" : "auto",
|
|
19785
|
+
...style
|
|
19786
|
+
},
|
|
19787
|
+
"data-pano-map": "",
|
|
19788
|
+
"data-mode": mode,
|
|
19789
|
+
"data-collapsed": collapsed ? "true" : void 0,
|
|
19790
|
+
children: [
|
|
19791
|
+
renderChrome ? renderChrome(controls) : /* @__PURE__ */ jsx42("div", { className: "pano-map-chrome", children: controls }),
|
|
19792
|
+
/* @__PURE__ */ jsxs23(
|
|
19793
|
+
"div",
|
|
19794
|
+
{
|
|
19795
|
+
ref: containerRef,
|
|
19796
|
+
className: "pano-map-viewport",
|
|
19797
|
+
hidden: collapsed,
|
|
19798
|
+
style: {
|
|
19799
|
+
position: "absolute",
|
|
19800
|
+
inset: 0,
|
|
19801
|
+
overflow: "hidden",
|
|
19802
|
+
visibility: collapsed ? "hidden" : "visible"
|
|
19803
|
+
},
|
|
19804
|
+
onPointerDown: onPointerDownSurface,
|
|
19805
|
+
onPointerMove: onPointerMoveSurface,
|
|
19806
|
+
onPointerUp: endDrag,
|
|
19807
|
+
onPointerCancel: endDrag,
|
|
19808
|
+
children: [
|
|
19809
|
+
fit.width > 0 && fit.height > 0 ? /* @__PURE__ */ jsx42(
|
|
19810
|
+
"div",
|
|
19811
|
+
{
|
|
19812
|
+
className: "pano-map-image-layer",
|
|
19813
|
+
style: {
|
|
19814
|
+
position: "absolute",
|
|
19815
|
+
left: fit.left + pan.x,
|
|
19816
|
+
top: fit.top + pan.y,
|
|
19817
|
+
width: fit.width * scale,
|
|
19818
|
+
height: fit.height * scale,
|
|
19819
|
+
transformOrigin: "0 0"
|
|
19820
|
+
},
|
|
19821
|
+
children: /* @__PURE__ */ jsx42(
|
|
19822
|
+
"img",
|
|
19823
|
+
{
|
|
19824
|
+
src: map.image,
|
|
19825
|
+
alt: "",
|
|
19826
|
+
draggable: false,
|
|
19827
|
+
ref: (node) => {
|
|
19828
|
+
if (node) syncImageSize(node);
|
|
19829
|
+
},
|
|
19830
|
+
onLoad: (event) => {
|
|
19831
|
+
syncImageSize(event.currentTarget);
|
|
19832
|
+
},
|
|
19833
|
+
style: {
|
|
19834
|
+
width: "100%",
|
|
19835
|
+
height: "100%",
|
|
19836
|
+
display: "block",
|
|
19837
|
+
userSelect: "none",
|
|
19838
|
+
pointerEvents: "none"
|
|
19839
|
+
}
|
|
19840
|
+
}
|
|
19841
|
+
)
|
|
19842
|
+
}
|
|
19843
|
+
) : /* @__PURE__ */ jsx42(
|
|
19844
|
+
"img",
|
|
19845
|
+
{
|
|
19846
|
+
src: map.image,
|
|
19847
|
+
alt: "",
|
|
19848
|
+
hidden: true,
|
|
19849
|
+
ref: (node) => {
|
|
19850
|
+
if (node) syncImageSize(node);
|
|
19851
|
+
},
|
|
19852
|
+
onLoad: (event) => {
|
|
19853
|
+
syncImageSize(event.currentTarget);
|
|
19854
|
+
}
|
|
19855
|
+
}
|
|
19856
|
+
),
|
|
19857
|
+
activeSpot && map.sector ? /* @__PURE__ */ jsx42(
|
|
19858
|
+
"svg",
|
|
19859
|
+
{
|
|
19860
|
+
className: "pano-map-sector-layer",
|
|
19861
|
+
width: size.width,
|
|
19862
|
+
height: size.height,
|
|
19863
|
+
style: {
|
|
19864
|
+
position: "absolute",
|
|
19865
|
+
inset: 0,
|
|
19866
|
+
overflow: "visible",
|
|
19867
|
+
pointerEvents: "none"
|
|
19868
|
+
},
|
|
19869
|
+
children: /* @__PURE__ */ jsx42(
|
|
19870
|
+
"g",
|
|
19871
|
+
{
|
|
19872
|
+
transform: `translate(${imagePointToContainer(activeSpot, viewport).x} ${imagePointToContainer(activeSpot, viewport).y})`,
|
|
19873
|
+
children: /* @__PURE__ */ jsx42(
|
|
19874
|
+
SectorShape,
|
|
19875
|
+
{
|
|
19876
|
+
sector: map.sector,
|
|
19877
|
+
angle: sectorAngle,
|
|
19878
|
+
rotation: sectorRotation(activeSpot.heading, view.yaw),
|
|
19879
|
+
gradientId: defaultGradientId(reactId),
|
|
19880
|
+
interactive: editing,
|
|
19881
|
+
onHeadingPointerDown: (event) => {
|
|
19882
|
+
event.stopPropagation();
|
|
19883
|
+
if (event.button !== 0) return;
|
|
19884
|
+
onSelectedSpotIdChange?.(activeSpot.id);
|
|
19885
|
+
dragRef.current = {
|
|
19886
|
+
kind: "heading",
|
|
19887
|
+
pointerId: event.pointerId,
|
|
19888
|
+
spotId: activeSpot.id
|
|
19889
|
+
};
|
|
19890
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
19891
|
+
},
|
|
19892
|
+
transformRef: (node) => {
|
|
19893
|
+
sectorNodeRef.current = node;
|
|
19894
|
+
}
|
|
19895
|
+
}
|
|
19896
|
+
)
|
|
19897
|
+
}
|
|
19898
|
+
)
|
|
19899
|
+
}
|
|
19900
|
+
) : null,
|
|
19901
|
+
spots.map((spot) => {
|
|
19902
|
+
const point = imagePointToContainer(spot, viewport);
|
|
19903
|
+
const active = spot.sceneId === activeSceneId;
|
|
19904
|
+
const selected = spot.id === selectedSpotId;
|
|
19905
|
+
return /* @__PURE__ */ jsx42(
|
|
19906
|
+
"button",
|
|
19907
|
+
{
|
|
19908
|
+
type: "button",
|
|
19909
|
+
className: "pano-map-spot",
|
|
19910
|
+
"aria-label": spot.sceneId,
|
|
19911
|
+
"aria-current": active ? "true" : void 0,
|
|
19912
|
+
"data-active": active ? "true" : void 0,
|
|
19913
|
+
"data-selected": selected ? "true" : void 0,
|
|
19914
|
+
style: {
|
|
19915
|
+
position: "absolute",
|
|
19916
|
+
left: point.x,
|
|
19917
|
+
top: point.y,
|
|
19918
|
+
width: iconSize,
|
|
19919
|
+
height: iconSize,
|
|
19920
|
+
transform: "translate(-50%, -50%)",
|
|
19921
|
+
padding: 0,
|
|
19922
|
+
border: selected ? "2px solid #38bdf8" : "0",
|
|
19923
|
+
borderRadius: 8,
|
|
19924
|
+
background: "transparent",
|
|
19925
|
+
cursor: editing ? "grab" : "pointer",
|
|
19926
|
+
pointerEvents: "auto"
|
|
19927
|
+
},
|
|
19928
|
+
onPointerDown: (event) => {
|
|
19929
|
+
event.stopPropagation();
|
|
19930
|
+
if (event.button !== 0) return;
|
|
19931
|
+
onSelectedSpotIdChange?.(spot.id);
|
|
19932
|
+
if (!editing) return;
|
|
19933
|
+
const pointLocal = localPoint(event);
|
|
19934
|
+
if (!pointLocal) return;
|
|
19935
|
+
const center = imagePointToContainer(spot, viewport);
|
|
19936
|
+
dragRef.current = {
|
|
19937
|
+
kind: "spot",
|
|
19938
|
+
pointerId: event.pointerId,
|
|
19939
|
+
spotId: spot.id,
|
|
19940
|
+
offsetX: pointLocal.x - center.x,
|
|
19941
|
+
offsetY: pointLocal.y - center.y
|
|
19942
|
+
};
|
|
19943
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
19944
|
+
},
|
|
19945
|
+
onClick: (event) => {
|
|
19946
|
+
event.stopPropagation();
|
|
19947
|
+
if (editing) return;
|
|
19948
|
+
onSelectScene?.(spot.sceneId, spot);
|
|
19949
|
+
},
|
|
19950
|
+
children: spot.icon ? /* @__PURE__ */ jsx42(
|
|
19951
|
+
"img",
|
|
19952
|
+
{
|
|
19953
|
+
src: spot.icon,
|
|
19954
|
+
alt: "",
|
|
19955
|
+
draggable: false,
|
|
19956
|
+
style: { width: "100%", height: "100%", display: "block", pointerEvents: "none" }
|
|
19957
|
+
}
|
|
19958
|
+
) : /* @__PURE__ */ jsx42(DefaultSpotIcon, { active, size: iconSize })
|
|
19959
|
+
},
|
|
19960
|
+
spot.id
|
|
19961
|
+
);
|
|
19962
|
+
})
|
|
19963
|
+
]
|
|
19964
|
+
}
|
|
19965
|
+
)
|
|
19966
|
+
]
|
|
19967
|
+
}
|
|
19968
|
+
);
|
|
19969
|
+
}
|
|
19970
|
+
|
|
19971
|
+
// src/map/pano-map-overlay.tsx
|
|
19972
|
+
import {
|
|
19973
|
+
useEffect as useEffect44,
|
|
19974
|
+
useId as useId4,
|
|
19975
|
+
useMemo as useMemo30,
|
|
19976
|
+
useRef as useRef44,
|
|
19977
|
+
useState as useState28
|
|
19978
|
+
} from "react";
|
|
19979
|
+
import { Fragment as Fragment9, jsx as jsx43, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
19980
|
+
function dockedBox(corner, offsetX, offsetY, width, height) {
|
|
19981
|
+
const horizontal = corner === "top-right" || corner === "bottom-right" ? { left: "auto", right: offsetX } : { left: offsetX, right: "auto" };
|
|
19982
|
+
const vertical = corner === "bottom-left" || corner === "bottom-right" ? { top: "auto", bottom: offsetY } : { top: offsetY, bottom: "auto" };
|
|
19983
|
+
return { ...horizontal, ...vertical, width, height };
|
|
19984
|
+
}
|
|
19985
|
+
function PanoMapDialogShell({
|
|
19986
|
+
open,
|
|
19987
|
+
onOpenChange,
|
|
19988
|
+
title,
|
|
19989
|
+
closeLabel,
|
|
19990
|
+
children
|
|
19991
|
+
}) {
|
|
19992
|
+
usePanoModal(open);
|
|
19993
|
+
const { size } = usePanoOverlay();
|
|
19994
|
+
const titleId = useId4();
|
|
19995
|
+
const panelRef = useRef44(null);
|
|
19996
|
+
const previouslyFocused = useRef44(null);
|
|
19997
|
+
useEffect44(() => {
|
|
19998
|
+
if (!open) return;
|
|
19999
|
+
const active = panelRef.current?.ownerDocument.activeElement;
|
|
20000
|
+
previouslyFocused.current = active instanceof HTMLElement ? active : null;
|
|
20001
|
+
const close = panelRef.current?.querySelector("[data-pano-map-dialog-close]");
|
|
20002
|
+
close?.focus({ preventScroll: true });
|
|
20003
|
+
const onKeyDown = (event) => {
|
|
20004
|
+
if (event.key === "Escape") {
|
|
20005
|
+
event.preventDefault();
|
|
20006
|
+
event.stopPropagation();
|
|
20007
|
+
onOpenChange(false);
|
|
20008
|
+
}
|
|
20009
|
+
};
|
|
20010
|
+
const doc = panelRef.current?.ownerDocument;
|
|
20011
|
+
doc?.addEventListener("keydown", onKeyDown, true);
|
|
20012
|
+
return () => {
|
|
20013
|
+
doc?.removeEventListener("keydown", onKeyDown, true);
|
|
20014
|
+
const previous = previouslyFocused.current;
|
|
20015
|
+
if (previous?.isConnected) previous.focus({ preventScroll: true });
|
|
20016
|
+
};
|
|
20017
|
+
}, [open, onOpenChange]);
|
|
20018
|
+
if (!open) return null;
|
|
20019
|
+
const width = Math.min(720, Math.max(0, size.width - 32));
|
|
20020
|
+
const height = Math.min(520, Math.max(0, size.height - 32));
|
|
20021
|
+
const left = Math.max(0, (size.width - width) / 2);
|
|
20022
|
+
const top = Math.max(0, (size.height - height) / 2);
|
|
20023
|
+
return /* @__PURE__ */ jsxs24(Fragment9, { children: [
|
|
20024
|
+
/* @__PURE__ */ jsx43(
|
|
20025
|
+
"div",
|
|
20026
|
+
{
|
|
20027
|
+
className: "pano-map-dialog-backdrop",
|
|
20028
|
+
"aria-hidden": "true",
|
|
20029
|
+
style: {
|
|
20030
|
+
position: "absolute",
|
|
20031
|
+
inset: 0,
|
|
20032
|
+
pointerEvents: "auto"
|
|
20033
|
+
},
|
|
20034
|
+
onClick: () => onOpenChange(false)
|
|
20035
|
+
}
|
|
20036
|
+
),
|
|
20037
|
+
/* @__PURE__ */ jsxs24(
|
|
20038
|
+
"div",
|
|
20039
|
+
{
|
|
20040
|
+
ref: panelRef,
|
|
20041
|
+
className: "pano-map-dialog",
|
|
20042
|
+
role: "dialog",
|
|
20043
|
+
"aria-modal": "true",
|
|
20044
|
+
"aria-labelledby": titleId,
|
|
20045
|
+
"data-pano-map-dialog": "",
|
|
20046
|
+
style: {
|
|
20047
|
+
position: "absolute",
|
|
20048
|
+
left,
|
|
20049
|
+
top,
|
|
20050
|
+
width,
|
|
20051
|
+
height,
|
|
20052
|
+
boxSizing: "border-box",
|
|
20053
|
+
display: "flex",
|
|
20054
|
+
flexDirection: "column",
|
|
20055
|
+
overflow: "hidden",
|
|
20056
|
+
pointerEvents: "auto",
|
|
20057
|
+
zIndex: 1
|
|
20058
|
+
},
|
|
20059
|
+
children: [
|
|
20060
|
+
/* @__PURE__ */ jsxs24("header", { className: "pano-map-dialog-header", style: { flexShrink: 0 }, children: [
|
|
20061
|
+
/* @__PURE__ */ jsx43("h2", { id: titleId, className: "pano-map-dialog-title", children: title }),
|
|
20062
|
+
/* @__PURE__ */ jsx43(
|
|
20063
|
+
"button",
|
|
20064
|
+
{
|
|
20065
|
+
type: "button",
|
|
20066
|
+
className: "pano-map-dialog-close",
|
|
20067
|
+
"data-pano-map-dialog-close": "",
|
|
20068
|
+
"aria-label": closeLabel,
|
|
20069
|
+
onClick: () => onOpenChange(false),
|
|
20070
|
+
children: /* @__PURE__ */ jsx43("svg", { "aria-hidden": "true", width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: /* @__PURE__ */ jsx43("path", { d: "m4 4 8 8M12 4l-8 8" }) })
|
|
20071
|
+
}
|
|
20072
|
+
)
|
|
20073
|
+
] }),
|
|
20074
|
+
/* @__PURE__ */ jsx43("div", { className: "pano-map-dialog-body", style: { minHeight: 0, flex: 1 }, children })
|
|
20075
|
+
]
|
|
20076
|
+
}
|
|
20077
|
+
)
|
|
20078
|
+
] });
|
|
20079
|
+
}
|
|
20080
|
+
function PanoMapOverlay({
|
|
20081
|
+
map,
|
|
20082
|
+
activeSceneId,
|
|
20083
|
+
onSelectScene,
|
|
20084
|
+
view,
|
|
20085
|
+
mode = "view",
|
|
20086
|
+
selectedSpotId,
|
|
20087
|
+
onSelectedSpotIdChange,
|
|
20088
|
+
onEditChange,
|
|
20089
|
+
placementSceneId,
|
|
20090
|
+
presentationOverride,
|
|
20091
|
+
openLabel = "Map",
|
|
20092
|
+
closeLabel = "Close",
|
|
20093
|
+
title = "Map",
|
|
20094
|
+
className,
|
|
20095
|
+
style,
|
|
20096
|
+
editorSurface = false
|
|
20097
|
+
}) {
|
|
20098
|
+
const { size } = usePanoOverlay();
|
|
20099
|
+
const [open, setOpen] = useState28(false);
|
|
20100
|
+
const [collapsed, setCollapsed] = useState28(false);
|
|
20101
|
+
const presentation = presentationOverride ?? map.presentation ?? {
|
|
20102
|
+
mode: "docked",
|
|
20103
|
+
corner: "bottom-left",
|
|
20104
|
+
width: 220,
|
|
20105
|
+
height: 160,
|
|
20106
|
+
offsetX: 16,
|
|
20107
|
+
offsetY: 16
|
|
20108
|
+
};
|
|
20109
|
+
const corner = presentation.corner ?? "bottom-left";
|
|
20110
|
+
const offsetX = presentation.offsetX ?? 16;
|
|
20111
|
+
const offsetY = presentation.offsetY ?? 16;
|
|
20112
|
+
const width = presentation.width ?? (presentation.mode === "dialog" ? 48 : 220);
|
|
20113
|
+
const height = presentation.height ?? (presentation.mode === "dialog" ? 48 : 160);
|
|
20114
|
+
const mapProps = {
|
|
20115
|
+
map,
|
|
20116
|
+
activeSceneId,
|
|
20117
|
+
view,
|
|
20118
|
+
mode,
|
|
20119
|
+
selectedSpotId,
|
|
20120
|
+
onSelectedSpotIdChange,
|
|
20121
|
+
onEditChange,
|
|
20122
|
+
placementSceneId,
|
|
20123
|
+
onSelectScene: (sceneId, spot) => {
|
|
20124
|
+
onSelectScene?.(sceneId, spot);
|
|
20125
|
+
if (presentation.mode === "dialog") setOpen(false);
|
|
20126
|
+
}
|
|
20127
|
+
};
|
|
20128
|
+
const editorBox = useMemo30(() => {
|
|
20129
|
+
const panelWidth2 = Math.min(420, Math.max(240, size.width * 0.45));
|
|
20130
|
+
const panelHeight2 = Math.min(320, Math.max(180, size.height * 0.45));
|
|
20131
|
+
return {
|
|
20132
|
+
left: 16,
|
|
20133
|
+
right: "auto",
|
|
20134
|
+
top: "auto",
|
|
20135
|
+
bottom: 16,
|
|
20136
|
+
width: panelWidth2,
|
|
20137
|
+
height: panelHeight2
|
|
20138
|
+
};
|
|
20139
|
+
}, [size.height, size.width]);
|
|
20140
|
+
if (editorSurface) {
|
|
20141
|
+
return /* @__PURE__ */ jsx43(
|
|
20142
|
+
"div",
|
|
20143
|
+
{
|
|
20144
|
+
className: ["pano-map-overlay", "pano-map-overlay-editor", className].filter(Boolean).join(" "),
|
|
20145
|
+
style: {
|
|
20146
|
+
position: "absolute",
|
|
20147
|
+
pointerEvents: "auto",
|
|
20148
|
+
...editorBox,
|
|
20149
|
+
...style
|
|
20150
|
+
},
|
|
20151
|
+
"data-pano-map-overlay": "",
|
|
20152
|
+
children: /* @__PURE__ */ jsx43(PanoMap, { ...mapProps, showCollapseControl: false })
|
|
20153
|
+
}
|
|
20154
|
+
);
|
|
20155
|
+
}
|
|
20156
|
+
if (presentation.mode === "dialog") {
|
|
20157
|
+
const launcherBox = dockedBox(corner, offsetX, offsetY, width, height);
|
|
20158
|
+
return /* @__PURE__ */ jsxs24("div", { className: ["pano-map-overlay", className].filter(Boolean).join(" "), "data-pano-map-overlay": "", children: [
|
|
20159
|
+
/* @__PURE__ */ jsx43(
|
|
20160
|
+
"button",
|
|
20161
|
+
{
|
|
20162
|
+
type: "button",
|
|
20163
|
+
className: "pano-map-launcher",
|
|
20164
|
+
"aria-label": openLabel,
|
|
20165
|
+
style: {
|
|
20166
|
+
position: "absolute",
|
|
20167
|
+
pointerEvents: "auto",
|
|
20168
|
+
...launcherBox,
|
|
20169
|
+
...style
|
|
20170
|
+
},
|
|
20171
|
+
onClick: () => setOpen(true),
|
|
20172
|
+
children: openLabel
|
|
20173
|
+
}
|
|
20174
|
+
),
|
|
20175
|
+
/* @__PURE__ */ jsx43(
|
|
20176
|
+
PanoMapDialogShell,
|
|
20177
|
+
{
|
|
20178
|
+
open,
|
|
20179
|
+
onOpenChange: setOpen,
|
|
20180
|
+
title,
|
|
20181
|
+
closeLabel,
|
|
20182
|
+
children: /* @__PURE__ */ jsx43("div", { style: { height: "100%" }, children: /* @__PURE__ */ jsx43(PanoMap, { ...mapProps, showCollapseControl: false }) })
|
|
20183
|
+
}
|
|
20184
|
+
)
|
|
20185
|
+
] });
|
|
20186
|
+
}
|
|
20187
|
+
const panelWidth = collapsed ? Math.max(44, Math.min(width, 56)) : width;
|
|
20188
|
+
const panelHeight = collapsed ? Math.max(44, Math.min(height, 56)) : height;
|
|
20189
|
+
const box = dockedBox(corner, offsetX, offsetY, panelWidth, panelHeight);
|
|
20190
|
+
return /* @__PURE__ */ jsx43(
|
|
20191
|
+
"div",
|
|
20192
|
+
{
|
|
20193
|
+
className: ["pano-map-overlay", className].filter(Boolean).join(" "),
|
|
20194
|
+
style: {
|
|
20195
|
+
position: "absolute",
|
|
20196
|
+
pointerEvents: "auto",
|
|
20197
|
+
...box,
|
|
20198
|
+
...style
|
|
20199
|
+
},
|
|
20200
|
+
"data-pano-map-overlay": "",
|
|
20201
|
+
"data-presentation": "docked",
|
|
20202
|
+
children: /* @__PURE__ */ jsx43(
|
|
20203
|
+
PanoMap,
|
|
20204
|
+
{
|
|
20205
|
+
...mapProps,
|
|
20206
|
+
collapsed,
|
|
20207
|
+
onCollapsedChange: setCollapsed
|
|
20208
|
+
}
|
|
20209
|
+
)
|
|
20210
|
+
}
|
|
20211
|
+
);
|
|
20212
|
+
}
|
|
19243
20213
|
export {
|
|
19244
20214
|
AudioHotspot,
|
|
19245
20215
|
AutoRotate,
|
|
@@ -19264,6 +20234,8 @@ export {
|
|
|
19264
20234
|
PanoContextMenu,
|
|
19265
20235
|
PanoEvents,
|
|
19266
20236
|
PanoFilter,
|
|
20237
|
+
PanoMap,
|
|
20238
|
+
PanoMapOverlay,
|
|
19267
20239
|
PanoOverlayPortalProvider,
|
|
19268
20240
|
PanoVideo,
|
|
19269
20241
|
PanoVideoControls,
|
|
@@ -19284,13 +20256,19 @@ export {
|
|
|
19284
20256
|
WebVR,
|
|
19285
20257
|
clampPanoPitch,
|
|
19286
20258
|
composePanoContextMenuItems,
|
|
20259
|
+
containRect,
|
|
20260
|
+
containerPointToImage,
|
|
19287
20261
|
createDefaultPanoContextMenuItems,
|
|
19288
20262
|
createPanoContextMenuPresets,
|
|
19289
20263
|
cycleSceneId,
|
|
20264
|
+
headingFromVector,
|
|
20265
|
+
horizontalFov,
|
|
20266
|
+
imagePointToContainer,
|
|
19290
20267
|
normalizePanoPosition,
|
|
19291
20268
|
normalizePanoYaw,
|
|
19292
20269
|
panoPositionToVector3,
|
|
19293
20270
|
resolvePanoContextMenuEntries,
|
|
20271
|
+
sectorRotation,
|
|
19294
20272
|
unwrapPolygonVertices,
|
|
19295
20273
|
usePanoEvents,
|
|
19296
20274
|
usePanoModal,
|