ebta-workflow-prod 4.1.9 → 4.1.11
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/build/main.js +3 -3
- package/changelog.md +7 -0
- package/package.json +1 -1
- package/src/elements/CsColorPicker.tsx +9 -0
- package/src/elements/PanovideoPlayer.tsx +30 -121
- package/src/elements/SampleInput.tsx +1 -3
package/changelog.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
4.1.0: Urgent & Major: Update to follow up with Vertigis foundation update React 19 and TypeScript 6.
|
|
2
|
+
4.1.3 Fixed some minor issues in AttributeFilter /DrapGeometry activity that was ignored in urgent fix 4.1.0. Work very stable for long time.
|
|
3
|
+
4.1.4 Attempted to fix css pollution by mantine that caused strange shrink, and new acitity simplify Geometry which is used in Permit Workflow. Turned out to be a major failure that stopped whole app.
|
|
4
|
+
4.1.5-4.1.7 Attempts to revert back but not working, changed VSWFA DEV version to 4.1.3
|
|
5
|
+
4.1.8 New feature: Panoramic video player utilitzing Marzipano player. 14/10/2026
|
|
6
|
+
4.1.9 Minor improvement on panoplayer and totally fixed mantine pollution css issue. 14/10/2026
|
|
7
|
+
4.1.10 Minor improvement on panoplayer for too much log info. 14/10/2026
|
package/package.json
CHANGED
|
@@ -7,6 +7,15 @@ type ColorFormat = "hex" | "rgba" | "rgb" | "hsl" | "hsla";
|
|
|
7
7
|
//import "@mantine/core/styles.css";
|
|
8
8
|
//Causing shrink
|
|
9
9
|
|
|
10
|
+
import "@mantine/core/styles/Input.css";
|
|
11
|
+
import "@mantine/core/styles/ColorInput.css";
|
|
12
|
+
import "@mantine/core/styles/ColorPicker.css";
|
|
13
|
+
import "@mantine/core/styles/ColorSwatch.css";
|
|
14
|
+
import "@mantine/core/styles/Popover.css";
|
|
15
|
+
import "@mantine/core/styles/Paper.css";
|
|
16
|
+
import "@mantine/core/styles/ActionIcon.css";
|
|
17
|
+
import "@mantine/core/styles/UnstyledButton.css";
|
|
18
|
+
|
|
10
19
|
import Color from "@arcgis/core/Color";
|
|
11
20
|
|
|
12
21
|
|
|
@@ -4,16 +4,6 @@ import { FormElementProps, FormElementRegistration } from "@vertigis/workflow";
|
|
|
4
4
|
import Marzipano from "marzipano";
|
|
5
5
|
const M: any = Marzipano;
|
|
6
6
|
|
|
7
|
-
function logPanovideo(message: string, details?: unknown): void {
|
|
8
|
-
if (details === undefined) {
|
|
9
|
-
console.log(`[PanovideoPlayer] ${message}`);
|
|
10
|
-
} else {
|
|
11
|
-
console.log(`[PanovideoPlayer] ${message}`, details);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
logPanovideo("module loaded", { marzipanoAvailable: Boolean(M) });
|
|
16
|
-
|
|
17
7
|
export interface VideoResolution {
|
|
18
8
|
/** Display label for this source. */
|
|
19
9
|
label: string;
|
|
@@ -64,13 +54,11 @@ class VideoAsset {
|
|
|
64
54
|
private readonly emptyCanvas: HTMLCanvasElement;
|
|
65
55
|
|
|
66
56
|
constructor(el: HTMLVideoElement) {
|
|
67
|
-
logPanovideo("VideoAsset.constructor: start", { src: el.currentSrc || el.src });
|
|
68
57
|
this.el = el;
|
|
69
58
|
this.emptyCanvas = document.createElement("canvas");
|
|
70
59
|
this.emptyCanvas.width = 1;
|
|
71
60
|
this.emptyCanvas.height = 1;
|
|
72
61
|
this.loop();
|
|
73
|
-
logPanovideo("VideoAsset.constructor: complete");
|
|
74
62
|
}
|
|
75
63
|
|
|
76
64
|
width(): number {
|
|
@@ -95,15 +83,12 @@ class VideoAsset {
|
|
|
95
83
|
}
|
|
96
84
|
|
|
97
85
|
destroy(): void {
|
|
98
|
-
logPanovideo("VideoAsset.destroy: start");
|
|
99
86
|
this.destroyed = true;
|
|
100
87
|
cancelAnimationFrame(this.raf);
|
|
101
|
-
logPanovideo("VideoAsset.destroy: complete");
|
|
102
88
|
}
|
|
103
89
|
|
|
104
90
|
private loop = (): void => {
|
|
105
91
|
if (this.destroyed) {
|
|
106
|
-
logPanovideo("VideoAsset.loop: stopped; asset destroyed");
|
|
107
92
|
return;
|
|
108
93
|
}
|
|
109
94
|
const t = this.el.currentTime;
|
|
@@ -122,7 +107,6 @@ M.dependencies.eventEmitter(VideoAsset);
|
|
|
122
107
|
/* -------------------------------------------------------------------------- */
|
|
123
108
|
|
|
124
109
|
function createVideo(url: string, muted: boolean): HTMLVideoElement {
|
|
125
|
-
logPanovideo("createVideo: start", { url, muted });
|
|
126
110
|
const v = document.createElement("video");
|
|
127
111
|
v.crossOrigin = "anonymous"; // required to use the video as a WebGL texture
|
|
128
112
|
v.loop = true;
|
|
@@ -134,16 +118,13 @@ function createVideo(url: string, muted: boolean): HTMLVideoElement {
|
|
|
134
118
|
v.setAttribute("webkit-playsinline", "");
|
|
135
119
|
v.src = url;
|
|
136
120
|
v.load();
|
|
137
|
-
logPanovideo("createVideo: complete", { src: v.src, readyState: v.readyState });
|
|
138
121
|
return v;
|
|
139
122
|
}
|
|
140
123
|
|
|
141
124
|
function disposeVideo(v: HTMLVideoElement): void {
|
|
142
|
-
logPanovideo("disposeVideo: start", { src: v.currentSrc || v.src, readyState: v.readyState });
|
|
143
125
|
v.pause();
|
|
144
126
|
v.removeAttribute("src");
|
|
145
127
|
v.load();
|
|
146
|
-
logPanovideo("disposeVideo: complete");
|
|
147
128
|
}
|
|
148
129
|
|
|
149
130
|
function formatTime(seconds: number): string {
|
|
@@ -159,7 +140,7 @@ function formatTime(seconds: number): string {
|
|
|
159
140
|
|
|
160
141
|
const DEFAULT_RESOLUTIONS: VideoResolution[] = [
|
|
161
142
|
// The first source is used by the player when `value` is empty.
|
|
162
|
-
{ label: "1280x640", url: "//
|
|
143
|
+
{ label: "1280x640", url: "//www.marzipano.net/media/video/mercedes-f1-2880x1440.mp4" },
|
|
163
144
|
];
|
|
164
145
|
|
|
165
146
|
/** Widest vertical field of view the user can zoom out to. */
|
|
@@ -283,13 +264,6 @@ function PanovideoPlayer(props: PanovideoPlayerProps): React.ReactElement {
|
|
|
283
264
|
const { value, resolutions = DEFAULT_RESOLUTIONS, autoplay = true, maxZoom = 3, raiseEvent } = props;
|
|
284
265
|
const sourceUrl = value || resolutions[0]?.url;
|
|
285
266
|
|
|
286
|
-
logPanovideo("component render: start", {
|
|
287
|
-
sourceUrl,
|
|
288
|
-
resolutionsCount: resolutions.length,
|
|
289
|
-
autoplay,
|
|
290
|
-
maxZoom,
|
|
291
|
-
hasSetValue: typeof props.setValue === "function",
|
|
292
|
-
});
|
|
293
267
|
|
|
294
268
|
const playerRef = React.useRef<HTMLDivElement>(null);
|
|
295
269
|
const containerRef = React.useRef<HTMLDivElement>(null);
|
|
@@ -323,41 +297,28 @@ function PanovideoPlayer(props: PanovideoPlayerProps): React.ReactElement {
|
|
|
323
297
|
const view = viewRef.current;
|
|
324
298
|
const video = videoRef.current;
|
|
325
299
|
if (!view || !video || !video.videoWidth) {
|
|
326
|
-
logPanovideo("applyZoomLimit: skipped", {
|
|
327
|
-
reason,
|
|
328
|
-
hasView: Boolean(view),
|
|
329
|
-
hasVideo: Boolean(video),
|
|
330
|
-
videoWidth: video?.videoWidth,
|
|
331
|
-
});
|
|
332
300
|
return;
|
|
333
301
|
}
|
|
334
302
|
const effectiveWidth = video.videoWidth * Math.max(1, maxZoomRef.current);
|
|
335
303
|
view.setLimiter(M.RectilinearView.limit.traditional(effectiveWidth, MAX_VFOV));
|
|
336
304
|
// Re-clamp the current FOV under the new limiter.
|
|
337
305
|
view.setFov(Math.min(INITIAL_FOV, view.fov()));
|
|
338
|
-
|
|
339
|
-
reason,
|
|
340
|
-
videoWidth: video.videoWidth,
|
|
341
|
-
videoHeight: video.videoHeight,
|
|
342
|
-
maxZoom: maxZoomRef.current,
|
|
343
|
-
effectiveWidth,
|
|
344
|
-
fovDeg: (view.fov() * 180) / Math.PI,
|
|
345
|
-
});
|
|
306
|
+
|
|
346
307
|
};
|
|
347
308
|
|
|
348
309
|
React.useEffect(() => {
|
|
349
|
-
|
|
310
|
+
|
|
350
311
|
const onFullscreenChange = () => {
|
|
351
312
|
const fullscreen = document.fullscreenElement === playerRef.current;
|
|
352
|
-
|
|
313
|
+
|
|
353
314
|
setIsFullscreen(fullscreen);
|
|
354
315
|
};
|
|
355
316
|
document.addEventListener("fullscreenchange", onFullscreenChange);
|
|
356
|
-
|
|
317
|
+
|
|
357
318
|
return () => {
|
|
358
|
-
|
|
319
|
+
|
|
359
320
|
document.removeEventListener("fullscreenchange", onFullscreenChange);
|
|
360
|
-
|
|
321
|
+
|
|
361
322
|
};
|
|
362
323
|
}, []);
|
|
363
324
|
|
|
@@ -372,12 +333,7 @@ function PanovideoPlayer(props: PanovideoPlayerProps): React.ReactElement {
|
|
|
372
333
|
if (!viewer) {
|
|
373
334
|
return;
|
|
374
335
|
}
|
|
375
|
-
|
|
376
|
-
reason,
|
|
377
|
-
fullscreen: isFullscreen,
|
|
378
|
-
width: container.clientWidth,
|
|
379
|
-
height: container.clientHeight,
|
|
380
|
-
});
|
|
336
|
+
|
|
381
337
|
viewer.updateSize();
|
|
382
338
|
};
|
|
383
339
|
|
|
@@ -400,7 +356,7 @@ function PanovideoPlayer(props: PanovideoPlayerProps): React.ReactElement {
|
|
|
400
356
|
}, [isFullscreen]);
|
|
401
357
|
|
|
402
358
|
React.useEffect(() => {
|
|
403
|
-
|
|
359
|
+
|
|
404
360
|
const player = playerRef.current;
|
|
405
361
|
if (!player) {
|
|
406
362
|
return undefined;
|
|
@@ -415,59 +371,44 @@ function PanovideoPlayer(props: PanovideoPlayerProps): React.ReactElement {
|
|
|
415
371
|
const previousFov = view.fov();
|
|
416
372
|
// Proportional zoom: the same wheel travel feels the same at any FOV.
|
|
417
373
|
view.setFov(previousFov * Math.exp(event.deltaY * deltaMultiplier * WHEEL_SENSITIVITY));
|
|
418
|
-
logPanovideo("wheel: zoom", { deltaY: event.deltaY, previousFov, nextFov: view.fov() });
|
|
419
374
|
};
|
|
420
375
|
player.addEventListener("wheel", onWheel, { passive: false });
|
|
421
|
-
|
|
376
|
+
|
|
422
377
|
return () => {
|
|
423
|
-
logPanovideo("wheel effect cleanup");
|
|
424
378
|
player.removeEventListener("wheel", onWheel);
|
|
425
379
|
};
|
|
426
380
|
}, []);
|
|
427
381
|
|
|
428
382
|
/* ---- 1. create the Marzipano viewer and view once ---- */
|
|
429
383
|
React.useEffect(() => {
|
|
430
|
-
|
|
384
|
+
|
|
431
385
|
const container = containerRef.current;
|
|
432
386
|
if (!container) {
|
|
433
|
-
logPanovideo("viewer effect: skipped; no container");
|
|
434
387
|
return undefined;
|
|
435
388
|
}
|
|
436
389
|
|
|
437
|
-
logPanovideo("viewer effect: creating Marzipano viewer");
|
|
438
390
|
const viewer = new M.Viewer(container, { controls: { scrollZoom: false } });
|
|
439
|
-
logPanovideo("viewer effect: viewer created");
|
|
440
391
|
|
|
441
392
|
// Permissive limiter for now; the real one is applied once the video
|
|
442
393
|
// reports its dimensions (see applyZoomLimit).
|
|
443
394
|
const limiter = M.RectilinearView.limit.traditional(PLACEHOLDER_MAX_RESOLUTION, MAX_VFOV);
|
|
444
395
|
const view = new M.RectilinearView({ yaw: Math.PI, fov: INITIAL_FOV }, limiter);
|
|
445
|
-
logPanovideo("viewer effect: view created", { fovDeg: (view.fov() * 180) / Math.PI });
|
|
446
396
|
|
|
447
397
|
viewerRef.current = viewer;
|
|
448
398
|
viewRef.current = view;
|
|
449
|
-
logPanovideo("viewer effect: complete");
|
|
450
399
|
|
|
451
400
|
return () => {
|
|
452
|
-
logPanovideo("viewer effect cleanup: start");
|
|
453
401
|
viewer.destroy();
|
|
454
402
|
viewerRef.current = null;
|
|
455
403
|
viewRef.current = null;
|
|
456
|
-
logPanovideo("viewer effect cleanup: complete");
|
|
457
404
|
};
|
|
458
405
|
}, []);
|
|
459
406
|
|
|
460
407
|
/* ---- 2. create the video and scene; rebuilt whenever the source URL changes ---- */
|
|
461
408
|
React.useEffect(() => {
|
|
462
|
-
logPanovideo("video effect: start", { sourceUrl });
|
|
463
409
|
const viewer = viewerRef.current;
|
|
464
410
|
const view = viewRef.current;
|
|
465
411
|
if (!viewer || !view || !sourceUrl) {
|
|
466
|
-
logPanovideo("video effect: skipped", {
|
|
467
|
-
hasViewer: Boolean(viewer),
|
|
468
|
-
hasView: Boolean(view),
|
|
469
|
-
sourceUrl,
|
|
470
|
-
});
|
|
471
412
|
return undefined;
|
|
472
413
|
}
|
|
473
414
|
|
|
@@ -477,7 +418,7 @@ function PanovideoPlayer(props: PanovideoPlayerProps): React.ReactElement {
|
|
|
477
418
|
setLoading(true);
|
|
478
419
|
setLoadPercent(0);
|
|
479
420
|
|
|
480
|
-
|
|
421
|
+
console.info("[PanovideoPlayer] video effect: creating video", { sourceUrl });
|
|
481
422
|
const video = createVideo(sourceUrl, videoRef.current?.muted ?? true);
|
|
482
423
|
videoRef.current = video;
|
|
483
424
|
setMuted(video.muted);
|
|
@@ -485,25 +426,21 @@ setLoadPercent(0);
|
|
|
485
426
|
video.playbackRate = playbackRateRef.current;
|
|
486
427
|
|
|
487
428
|
const onPlay = () => {
|
|
488
|
-
logPanovideo("video event: play", { src: video.currentSrc || video.src });
|
|
489
429
|
setPlaying(true);
|
|
490
430
|
};
|
|
491
431
|
const onPause = () => {
|
|
492
|
-
logPanovideo("video event: pause", { src: video.currentSrc || video.src });
|
|
493
432
|
setPlaying(false);
|
|
494
433
|
};
|
|
495
434
|
const onError = () => {
|
|
496
435
|
setLoading(false);
|
|
497
|
-
|
|
436
|
+
console.error("[PanovideoPlayer] video event: error", { src: video.currentSrc || video.src, error: video.error });
|
|
498
437
|
setError(`Unable to load ${video.src}`);
|
|
499
438
|
};
|
|
500
439
|
|
|
501
440
|
const onWaiting = () => {
|
|
502
|
-
logPanovideo("video event: waiting (buffering)");
|
|
503
441
|
setLoading(true);
|
|
504
442
|
};
|
|
505
443
|
const onReady = () => {
|
|
506
|
-
logPanovideo("video event: ready to play", { readyState: video.readyState });
|
|
507
444
|
setLoading(false);
|
|
508
445
|
};
|
|
509
446
|
const onProgress = () => {
|
|
@@ -518,7 +455,7 @@ const onProgress = () => {
|
|
|
518
455
|
setDuration(Number.isFinite(video.duration) ? video.duration : 0);
|
|
519
456
|
};
|
|
520
457
|
const onLoadedMetadata = () => {
|
|
521
|
-
|
|
458
|
+
console.info("[PanovideoPlayer] video event: loadedmetadata", {
|
|
522
459
|
videoWidth: video.videoWidth,
|
|
523
460
|
videoHeight: video.videoHeight,
|
|
524
461
|
duration: video.duration,
|
|
@@ -528,7 +465,7 @@ const onProgress = () => {
|
|
|
528
465
|
try {
|
|
529
466
|
raiseEventRef.current("loaded" as any, sourceUrl);
|
|
530
467
|
} catch (raiseError) {
|
|
531
|
-
|
|
468
|
+
console.error("[PanovideoPlayer] video event: raiseEvent(loaded) failed", raiseError);
|
|
532
469
|
}
|
|
533
470
|
};
|
|
534
471
|
video.addEventListener("play", onPlay);
|
|
@@ -544,32 +481,21 @@ video.addEventListener("playing", onReady);
|
|
|
544
481
|
video.addEventListener("progress", onProgress);
|
|
545
482
|
|
|
546
483
|
const asset = new VideoAsset(video);
|
|
547
|
-
logPanovideo("video effect: asset created");
|
|
548
484
|
const source = new M.SingleAssetSource(asset);
|
|
549
|
-
logPanovideo("video effect: source created");
|
|
550
485
|
const geometry = new M.EquirectGeometry([{ width: 1 }]);
|
|
551
|
-
logPanovideo("video effect: geometry created");
|
|
552
486
|
|
|
553
|
-
logPanovideo("video effect: creating scene");
|
|
554
487
|
// The view is reused so camera direction and zoom survive a source swap.
|
|
555
488
|
const scene = viewer.createScene({ source, geometry, view, pinFirstLevel: true });
|
|
556
|
-
logPanovideo("video effect: switching to scene");
|
|
557
489
|
scene.switchTo();
|
|
558
490
|
assetRef.current = asset;
|
|
559
491
|
|
|
560
492
|
if (autoplayRef.current) {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
() => logPanovideo("video effect: autoplay started"),
|
|
564
|
-
(playError: unknown) => logPanovideo("video effect: autoplay rejected", playError),
|
|
493
|
+
void video.play().catch((playError: unknown) =>
|
|
494
|
+
console.error("[PanovideoPlayer] autoplay rejected", playError),
|
|
565
495
|
);
|
|
566
|
-
} else {
|
|
567
|
-
logPanovideo("video effect: autoplay disabled");
|
|
568
496
|
}
|
|
569
|
-
logPanovideo("video effect: complete");
|
|
570
497
|
|
|
571
498
|
return () => {
|
|
572
|
-
logPanovideo("video effect cleanup: start");
|
|
573
499
|
video.removeEventListener("play", onPlay);
|
|
574
500
|
video.removeEventListener("pause", onPause);
|
|
575
501
|
video.removeEventListener("error", onError);
|
|
@@ -587,7 +513,7 @@ video.removeEventListener("progress", onProgress);
|
|
|
587
513
|
try {
|
|
588
514
|
viewer.destroyScene(scene);
|
|
589
515
|
} catch (sceneError) {
|
|
590
|
-
|
|
516
|
+
console.error("[PanovideoPlayer] video effect cleanup: destroyScene failed", sceneError);
|
|
591
517
|
}
|
|
592
518
|
}
|
|
593
519
|
disposeVideo(video);
|
|
@@ -597,7 +523,6 @@ video.removeEventListener("progress", onProgress);
|
|
|
597
523
|
if (assetRef.current === asset) {
|
|
598
524
|
assetRef.current = null;
|
|
599
525
|
}
|
|
600
|
-
logPanovideo("video effect cleanup: complete");
|
|
601
526
|
};
|
|
602
527
|
// Rebuilt only when the source URL changes. Other props are read via refs.
|
|
603
528
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -605,46 +530,35 @@ video.removeEventListener("progress", onProgress);
|
|
|
605
530
|
|
|
606
531
|
/* ---- re-apply the zoom limit when `maxZoom` changes at runtime ---- */
|
|
607
532
|
React.useEffect(() => {
|
|
608
|
-
logPanovideo("maxZoom effect", { maxZoom });
|
|
609
533
|
applyZoomLimit("maxZoom changed");
|
|
610
534
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
611
535
|
}, [maxZoom]);
|
|
612
536
|
|
|
613
537
|
/* ---- controls ---- */
|
|
614
538
|
const togglePlay = () => {
|
|
615
|
-
logPanovideo("togglePlay: start");
|
|
616
539
|
const v = videoRef.current;
|
|
617
540
|
if (!v) {
|
|
618
|
-
logPanovideo("togglePlay: skipped; no video");
|
|
619
541
|
return;
|
|
620
542
|
}
|
|
621
543
|
if (v.paused) {
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
() => logPanovideo("togglePlay: playback started"),
|
|
625
|
-
(playError: unknown) => logPanovideo("togglePlay: playback rejected", playError),
|
|
544
|
+
void v.play().catch((playError: unknown) =>
|
|
545
|
+
console.error("[PanovideoPlayer] playback rejected", playError),
|
|
626
546
|
);
|
|
627
547
|
} else {
|
|
628
|
-
logPanovideo("togglePlay: pausing playback", { currentTime: v.currentTime });
|
|
629
548
|
v.pause();
|
|
630
549
|
}
|
|
631
|
-
logPanovideo("togglePlay: complete");
|
|
632
550
|
};
|
|
633
551
|
|
|
634
552
|
const toggleMute = () => {
|
|
635
|
-
logPanovideo("toggleMute: start");
|
|
636
553
|
const v = videoRef.current;
|
|
637
554
|
if (!v) {
|
|
638
|
-
logPanovideo("toggleMute: skipped; no video");
|
|
639
555
|
return;
|
|
640
556
|
}
|
|
641
557
|
v.muted = !v.muted;
|
|
642
558
|
setMuted(v.muted);
|
|
643
|
-
logPanovideo("toggleMute: complete", { muted: v.muted });
|
|
644
559
|
};
|
|
645
560
|
|
|
646
561
|
const changeSpeed = (rate: number) => {
|
|
647
|
-
logPanovideo("changeSpeed", { rate });
|
|
648
562
|
playbackRateRef.current = rate;
|
|
649
563
|
setPlaybackRate(rate);
|
|
650
564
|
const v = videoRef.current;
|
|
@@ -657,18 +571,11 @@ video.removeEventListener("progress", onProgress);
|
|
|
657
571
|
const adjustZoom = (direction: "in" | "out") => {
|
|
658
572
|
const view = viewRef.current;
|
|
659
573
|
if (!view) {
|
|
660
|
-
logPanovideo("adjustZoom: skipped; no view", { direction });
|
|
661
574
|
return;
|
|
662
575
|
}
|
|
663
576
|
const previousFov = view.fov();
|
|
664
577
|
// Proportional step; setFov passes through the limiter, so min/max are enforced.
|
|
665
578
|
view.setFov(previousFov * (direction === "in" ? ZOOM_IN_FACTOR : ZOOM_OUT_FACTOR));
|
|
666
|
-
logPanovideo("adjustZoom: complete", {
|
|
667
|
-
direction,
|
|
668
|
-
previousFov,
|
|
669
|
-
nextFov: view.fov(),
|
|
670
|
-
nextFovDeg: (view.fov() * 180) / Math.PI,
|
|
671
|
-
});
|
|
672
579
|
};
|
|
673
580
|
|
|
674
581
|
const seekTo = (time: number) => {
|
|
@@ -682,10 +589,8 @@ video.removeEventListener("progress", onProgress);
|
|
|
682
589
|
};
|
|
683
590
|
|
|
684
591
|
const toggleFullscreen = async () => {
|
|
685
|
-
logPanovideo("toggleFullscreen: start", { isFullscreen });
|
|
686
592
|
const player = playerRef.current;
|
|
687
593
|
if (!player) {
|
|
688
|
-
logPanovideo("toggleFullscreen: skipped; no player element");
|
|
689
594
|
return;
|
|
690
595
|
}
|
|
691
596
|
try {
|
|
@@ -694,9 +599,8 @@ video.removeEventListener("progress", onProgress);
|
|
|
694
599
|
} else {
|
|
695
600
|
await player.requestFullscreen();
|
|
696
601
|
}
|
|
697
|
-
logPanovideo("toggleFullscreen: request complete");
|
|
698
602
|
} catch (fullscreenError) {
|
|
699
|
-
|
|
603
|
+
console.error("[PanovideoPlayer] toggleFullscreen: request failed", fullscreenError);
|
|
700
604
|
setError("Fullscreen is unavailable in this browser or context.");
|
|
701
605
|
}
|
|
702
606
|
};
|
|
@@ -729,7 +633,6 @@ video.removeEventListener("progress", onProgress);
|
|
|
729
633
|
return;
|
|
730
634
|
}
|
|
731
635
|
event.preventDefault();
|
|
732
|
-
logPanovideo("keyboard: Space pressed; toggling playback");
|
|
733
636
|
togglePlay();
|
|
734
637
|
};
|
|
735
638
|
|
|
@@ -807,6 +710,14 @@ video.removeEventListener("progress", onProgress);
|
|
|
807
710
|
value={Math.min(currentTime, duration)}
|
|
808
711
|
disabled={duration <= 0}
|
|
809
712
|
onChange={(event) => seekTo(Number(event.currentTarget.value))}
|
|
713
|
+
onClick={(event) => {
|
|
714
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
715
|
+
if (rect.width === 0 || duration <= 0) {
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
const percent = Math.max(0, Math.min(1, (event.clientX - rect.left) / rect.width));
|
|
719
|
+
seekTo(percent * duration);
|
|
720
|
+
}}
|
|
810
721
|
style={timelineStyle}
|
|
811
722
|
aria-label="Video progress"
|
|
812
723
|
aria-valuetext={`${formatTime(currentTime)} of ${formatTime(duration)}`}
|
|
@@ -829,21 +740,19 @@ video.removeEventListener("progress", onProgress);
|
|
|
829
740
|
)}
|
|
830
741
|
</div>
|
|
831
742
|
);
|
|
832
|
-
|
|
743
|
+
|
|
833
744
|
return element;
|
|
834
745
|
}
|
|
835
746
|
|
|
836
747
|
const PanovideoPlayerElementRegistration: FormElementRegistration<PanovideoPlayerProps> = {
|
|
837
748
|
component: PanovideoPlayer,
|
|
838
749
|
getInitialProperties: () => {
|
|
839
|
-
logPanovideo("getInitialProperties: start");
|
|
840
750
|
const properties = {
|
|
841
751
|
value: "",
|
|
842
752
|
resolutions: DEFAULT_RESOLUTIONS,
|
|
843
753
|
autoplay: true,
|
|
844
754
|
maxZoom: 3,
|
|
845
755
|
};
|
|
846
|
-
logPanovideo("getInitialProperties: complete", properties);
|
|
847
756
|
return properties;
|
|
848
757
|
},
|
|
849
758
|
id: "PanovideoPlayer",
|