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/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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ebta-workflow-prod",
3
3
  "private": false,
4
- "version": "4.1.9",
4
+ "version": "4.1.11",
5
5
  "description": "",
6
6
  "license": "UNLICENSED",
7
7
  "author": "",
@@ -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: "//win11assets.blob.core.windows.net/playbookicon/260917_BR120.mp4" },
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
- logPanovideo("applyZoomLimit: complete", {
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
- logPanovideo("fullscreen effect: start");
310
+
350
311
  const onFullscreenChange = () => {
351
312
  const fullscreen = document.fullscreenElement === playerRef.current;
352
- logPanovideo("fullscreen event: changed", { fullscreen });
313
+
353
314
  setIsFullscreen(fullscreen);
354
315
  };
355
316
  document.addEventListener("fullscreenchange", onFullscreenChange);
356
- logPanovideo("fullscreen effect: complete");
317
+
357
318
  return () => {
358
- logPanovideo("fullscreen effect cleanup: start");
319
+
359
320
  document.removeEventListener("fullscreenchange", onFullscreenChange);
360
- logPanovideo("fullscreen effect cleanup: complete");
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
- logPanovideo("resize: updating Marzipano size", {
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
- logPanovideo("wheel effect: start");
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
- logPanovideo("wheel effect: complete");
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
- logPanovideo("viewer effect: start");
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
- logPanovideo("video effect: creating video", { sourceUrl });
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
- logPanovideo("video event: error", { src: video.currentSrc || video.src, error: video.error });
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
- logPanovideo("video event: loadedmetadata", {
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
- logPanovideo("video event: raiseEvent(loaded) failed", raiseError);
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
- logPanovideo("video effect: attempting autoplay");
562
- void video.play().then(
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
- logPanovideo("video effect cleanup: destroyScene failed", sceneError);
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
- logPanovideo("togglePlay: requesting playback", { src: v.currentSrc || v.src });
623
- void v.play().then(
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
- logPanovideo("toggleFullscreen: request failed", fullscreenError);
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
- logPanovideo("component render: JSX tree created", { playing, muted, isFullscreen, error });
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",
@@ -35,6 +35,4 @@ const sampleInputDEVElementRegistration: FormElementRegistration<sampleInputDEVP
35
35
  id: "sampleInputDEV",
36
36
  };
37
37
 
38
-
39
- export default sampleInputDEVElementRegistration;
40
-
38
+ export default sampleInputDEVElementRegistration;