@preference-sl/pref-viewer 2.13.0-beta.14 → 2.13.0-beta.16

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@preference-sl/pref-viewer",
3
- "version": "2.13.0-beta.14",
3
+ "version": "2.13.0-beta.16",
4
4
  "description": "Web Component to preview GLTF models with Babylon.js",
5
5
  "author": "Alex Moreno Palacio <amoreno@preference.es>",
6
6
  "scripts": {
@@ -1485,10 +1485,10 @@ export default class BabylonJSController {
1485
1485
  * @returns {void}
1486
1486
  */
1487
1487
  #onKeyUp(event) {
1488
- // CTRL + ALT + letter
1489
- if (event.ctrlKey && event.altKey && event.key !== undefined) {
1490
- switch (event.key.toLowerCase()) {
1491
- case "d":
1488
+ // CTRL + ALT + letter (uses event.code for physical key, layout-independent — fixes Mac Option+D producing "∂" instead of "d")
1489
+ if (event.ctrlKey && event.altKey && event.code !== undefined) {
1490
+ switch (event.code) {
1491
+ case "KeyD":
1492
1492
  this.#openDownloadDialog();
1493
1493
  break;
1494
1494
  default:
@@ -2087,7 +2087,7 @@ export default class BabylonJSController {
2087
2087
  * Applies material and camera options, sets wall/floor visibility, and initializes lights and shadows.
2088
2088
  * Returns an object with success status and error details.
2089
2089
  */
2090
- async #loadContainers() {
2090
+ async #loadContainers(force = false) {
2091
2091
  this.#detachAnimationChangedListener();
2092
2092
  await this.#stopRender();
2093
2093
 
@@ -2096,7 +2096,7 @@ export default class BabylonJSController {
2096
2096
 
2097
2097
  const promiseArray = [];
2098
2098
  Object.values(this.#containers).forEach((container) => {
2099
- promiseArray.push(this.#loadAssetContainer(container));
2099
+ promiseArray.push(this.#loadAssetContainer(container, force));
2100
2100
  });
2101
2101
 
2102
2102
  let detail = {
@@ -2594,8 +2594,8 @@ export default class BabylonJSController {
2594
2594
  * @public
2595
2595
  * @returns {Promise<boolean>} Resolves to true if loading succeeds, false otherwise.
2596
2596
  */
2597
- async load() {
2598
- return await this.#loadContainers();
2597
+ async load(force = false) {
2598
+ return await this.#loadContainers(force);
2599
2599
  }
2600
2600
 
2601
2601
  /**
@@ -2630,11 +2630,13 @@ export default class BabylonJSController {
2630
2630
  * @public
2631
2631
  * @returns {boolean} True if IBL options were set successfully, false otherwise.
2632
2632
  */
2633
- setIBLOptions() {
2634
- this.#stopRender();
2635
- const IBLOptionsSetted = this.#setOptions_IBL();
2636
- this.#startRender();
2637
- return IBLOptionsSetted;
2633
+ async setIBLOptions() {
2634
+ await this.#stopRender();
2635
+ try {
2636
+ return await this.#setOptions_IBL();
2637
+ } finally {
2638
+ await this.#startRender();
2639
+ }
2638
2640
  }
2639
2641
 
2640
2642
  /**
@@ -75,6 +75,7 @@ export default class PrefViewer3D extends HTMLElement {
75
75
  #isInitialized = false;
76
76
  #isLoaded = false;
77
77
  #isVisible = false;
78
+ #lastLoadTimestamp = undefined;
78
79
 
79
80
  #wrapper = null;
80
81
  #canvas = null;
@@ -230,6 +231,27 @@ export default class PrefViewer3D extends HTMLElement {
230
231
  this.#babylonJSController.enable();
231
232
  }
232
233
 
234
+ /**
235
+ * Determines whether the next 3D load should bypass cache checks.
236
+ * @private
237
+ * @param {object} config - Incoming config payload.
238
+ * @returns {boolean} True when force reload should be enabled.
239
+ */
240
+ #shouldForceReload(config) {
241
+ if (!config || typeof config !== "object") {
242
+ return false;
243
+ }
244
+ if (config.forceReload === true) {
245
+ return true;
246
+ }
247
+ if (config.timestamp === undefined || config.timestamp === null) {
248
+ return false;
249
+ }
250
+ const changed = config.timestamp !== this.#lastLoadTimestamp;
251
+ this.#lastLoadTimestamp = config.timestamp;
252
+ return changed;
253
+ }
254
+
233
255
  /**
234
256
  * Resets update flags for all containers and material/camera options after loading or setting options.
235
257
  * @private
@@ -558,6 +580,7 @@ export default class PrefViewer3D extends HTMLElement {
558
580
  }
559
581
 
560
582
  this.#onLoading();
583
+ const forceReload = this.#shouldForceReload(config);
561
584
 
562
585
  // Containers
563
586
  this.#checkNeedToUpdateContainers(config);
@@ -566,10 +589,10 @@ export default class PrefViewer3D extends HTMLElement {
566
589
  if (config.options) {
567
590
  this.#checkNeedToUpdateCamera(config.options);
568
591
  this.#checkNeedToUpdateMaterials(config.options);
569
- this.#checkNeedToUpdateIBL(config.options);
592
+ await this.#checkNeedToUpdateIBL(config.options);
570
593
  }
571
594
 
572
- const loadDetail = await this.#babylonJSController.load();
595
+ const loadDetail = await this.#babylonJSController.load(forceReload);
573
596
 
574
597
  return { ...loadDetail, load: this.#onLoaded() };
575
598
  }
@@ -579,9 +602,9 @@ export default class PrefViewer3D extends HTMLElement {
579
602
  * Updates internal states, triggers option setting events, and returns the result.
580
603
  * @public
581
604
  * @param {object} options - Options object containing camera and material settings.
582
- * @returns {object} Object containing success status and details of set options.
605
+ * @returns {Promise<object>} Object containing success status and details of set options.
583
606
  */
584
- setOptions(options) {
607
+ async setOptions(options) {
585
608
  if (!this.#babylonJSController) {
586
609
  return;
587
610
  }
@@ -595,8 +618,9 @@ export default class PrefViewer3D extends HTMLElement {
595
618
  if (this.#checkNeedToUpdateMaterials(options)) {
596
619
  someSetted = someSetted || this.#babylonJSController.setMaterialOptions();
597
620
  }
598
- if (this.#checkNeedToUpdateIBL(options)) {
599
- someSetted = someSetted || this.#babylonJSController.setIBLOptions();
621
+ const needUpdateIBL = await this.#checkNeedToUpdateIBL(options);
622
+ if (needUpdateIBL) {
623
+ someSetted = someSetted || (await this.#babylonJSController.setIBLOptions());
600
624
  }
601
625
  const detail = this.#onSetOptions();
602
626
  return { success: someSetted, detail: detail };
@@ -81,6 +81,7 @@ export default class PrefViewer extends HTMLElement {
81
81
  onViewerHoverStart: null,
82
82
  onViewerHoverEnd: null,
83
83
  on3DSceneLoaded: null,
84
+ on3DSceneError: null,
84
85
  };
85
86
 
86
87
  /**
@@ -136,6 +137,9 @@ export default class PrefViewer extends HTMLElement {
136
137
  this.loadMaterials(value);
137
138
  break;
138
139
  case "mode":
140
+ if (typeof value !== "string") {
141
+ return;
142
+ }
139
143
  if (_old === value || value.toLowerCase() === this.#mode) {
140
144
  return;
141
145
  }
@@ -151,6 +155,9 @@ export default class PrefViewer extends HTMLElement {
151
155
  this.setOptions(value);
152
156
  break;
153
157
  case "show-model":
158
+ if (typeof value !== "string") {
159
+ return;
160
+ }
154
161
  if (_old === value) {
155
162
  return;
156
163
  }
@@ -158,6 +165,9 @@ export default class PrefViewer extends HTMLElement {
158
165
  showModel ? this.showModel() : this.hideModel();
159
166
  break;
160
167
  case "show-scene":
168
+ if (typeof value !== "string") {
169
+ return;
170
+ }
161
171
  if (_old === value) {
162
172
  return;
163
173
  }
@@ -213,6 +223,7 @@ export default class PrefViewer extends HTMLElement {
213
223
  }
214
224
  if (this.#component3D) {
215
225
  this.#component3D.removeEventListener("scene-loaded", this.#handlers.on3DSceneLoaded);
226
+ this.#component3D.removeEventListener("scene-error", this.#handlers.on3DSceneError);
216
227
  this.#component3D.remove();
217
228
  }
218
229
  if (this.#menu3D) {
@@ -249,7 +260,11 @@ export default class PrefViewer extends HTMLElement {
249
260
  this.#menu3DSyncSettings();
250
261
  this.#menu3D?.setApplying(false);
251
262
  };
263
+ this.#handlers.on3DSceneError = () => {
264
+ this.#menu3D?.setApplying(false, true);
265
+ };
252
266
  this.#component3D.addEventListener("scene-loaded", this.#handlers.on3DSceneLoaded);
267
+ this.#component3D.addEventListener("scene-error", this.#handlers.on3DSceneError);
253
268
  this.#wrapper.appendChild(this.#component3D);
254
269
  }
255
270
 
@@ -454,7 +469,14 @@ export default class PrefViewer extends HTMLElement {
454
469
  * @returns {void}
455
470
  */
456
471
  #addTaskToQueue(value, type) {
457
- this.#taskQueue.push(new PrefViewerTask(value, type));
472
+ const task = new PrefViewerTask(value, type);
473
+ if (
474
+ task.type === PrefViewerTask.Types.Options ||
475
+ task.type === PrefViewerTask.Types.Drawing
476
+ ) {
477
+ this.#taskQueue = this.#taskQueue.filter((queuedTask) => queuedTask.type !== task.type);
478
+ }
479
+ this.#taskQueue.push(task);
458
480
  if (this.#isInitialized && !this.#isLoading) {
459
481
  this.#processNextTask();
460
482
  }
@@ -552,6 +574,32 @@ export default class PrefViewer extends HTMLElement {
552
574
  this.dispatchEvent(new CustomEvent("scene-loaded", customEventOptions));
553
575
  }
554
576
 
577
+ /**
578
+ * Handles 3D load errors.
579
+ * Clears loading state and dispatches a "scene-error" event.
580
+ * @private
581
+ * @param {object} [detail] - Optional details about the failure.
582
+ * @returns {void}
583
+ */
584
+ #on3DError(detail) {
585
+ this.#isLoaded = false;
586
+ this.#isLoading = false;
587
+ this.#menu3D?.setApplying(false, true);
588
+
589
+ this.removeAttribute("loading-3d");
590
+ this.removeAttribute("loaded-3d");
591
+
592
+ const customEventOptions = {
593
+ bubbles: true,
594
+ cancelable: true,
595
+ composed: true,
596
+ };
597
+ if (detail) {
598
+ customEventOptions.detail = detail;
599
+ }
600
+ this.dispatchEvent(new CustomEvent("scene-error", customEventOptions));
601
+ }
602
+
555
603
  /**
556
604
  * Handles the start of a 2D loading operation.
557
605
  * Updates loading state, sets attributes, and dispatches a "drawing-loading" event.
@@ -598,6 +646,31 @@ export default class PrefViewer extends HTMLElement {
598
646
  this.dispatchEvent(new CustomEvent("drawing-loaded", customEventOptions));
599
647
  }
600
648
 
649
+ /**
650
+ * Handles 2D load errors.
651
+ * Clears loading state and dispatches a "drawing-error" event.
652
+ * @private
653
+ * @param {object} [detail] - Optional details about the failure.
654
+ * @returns {void}
655
+ */
656
+ #on2DError(detail) {
657
+ this.#isLoaded = false;
658
+ this.#isLoading = false;
659
+
660
+ this.removeAttribute("loading-2d");
661
+ this.removeAttribute("loaded-2d");
662
+
663
+ const customEventOptions = {
664
+ bubbles: true,
665
+ cancelable: true,
666
+ composed: true,
667
+ };
668
+ if (detail) {
669
+ customEventOptions.detail = detail;
670
+ }
671
+ this.dispatchEvent(new CustomEvent("drawing-error", customEventOptions));
672
+ }
673
+
601
674
  /**
602
675
  * Handles the "drawing-zoom-changed" event from the 2D viewer component.
603
676
  * Dispatches a custom "drawing-zoom-changed" event from the PrefViewer element, forwarding the event detail to external listeners.
@@ -640,16 +713,27 @@ export default class PrefViewer extends HTMLElement {
640
713
  * @param {object} config - The configuration object to process.
641
714
  * @returns {void}
642
715
  */
643
- #processConfig(config) {
716
+ async #processConfig(config) {
644
717
  if (!this.#component3D) {
718
+ this.#processNextTask();
645
719
  return;
646
720
  }
647
721
 
648
722
  this.#on3DLoading();
649
- this.#component3D.load(config).then((detail) => {
650
- this.#on3DLoaded(detail);
723
+ try {
724
+ const detail = await this.#component3D.load(config);
725
+ if (detail?.success === false) {
726
+ this.#on3DError(detail);
727
+ } else {
728
+ this.#on3DLoaded(detail);
729
+ }
730
+ } catch (error) {
731
+ this.#on3DError({
732
+ error: error instanceof Error ? error : new Error(String(error)),
733
+ });
734
+ } finally {
651
735
  this.#processNextTask();
652
- });
736
+ }
653
737
  }
654
738
 
655
739
  /**
@@ -670,16 +754,23 @@ export default class PrefViewer extends HTMLElement {
670
754
  * @param {object} drawing - The drawing object to process.
671
755
  * @returns {void}
672
756
  */
673
- #processDrawing(drawing) {
757
+ async #processDrawing(drawing) {
674
758
  if (!this.#component2D) {
759
+ this.#processNextTask();
675
760
  return;
676
761
  }
677
762
 
678
763
  this.#on2DLoading();
679
- this.#component2D.load(drawing).then((detail) => {
764
+ try {
765
+ const detail = await this.#component2D.load(drawing);
680
766
  this.#on2DLoaded(detail);
767
+ } catch (error) {
768
+ this.#on2DError({
769
+ error: error instanceof Error ? error : new Error(String(error)),
770
+ });
771
+ } finally {
681
772
  this.#processNextTask();
682
- });
773
+ }
683
774
  }
684
775
 
685
776
  /**
@@ -725,15 +816,23 @@ export default class PrefViewer extends HTMLElement {
725
816
  * @param {object} options - The options object to process.
726
817
  * @returns {void}
727
818
  */
728
- #processOptions(options) {
819
+ async #processOptions(options) {
729
820
  if (!this.#component3D) {
821
+ this.#processNextTask();
730
822
  return;
731
823
  }
732
824
 
733
825
  this.#on3DLoading();
734
- const detail = this.#component3D.setOptions(options);
735
- this.#on3DLoaded(detail);
736
- this.#processNextTask();
826
+ try {
827
+ const detail = await this.#component3D.setOptions(options);
828
+ this.#on3DLoaded(detail);
829
+ } catch (error) {
830
+ this.#on3DError({
831
+ error: error instanceof Error ? error : new Error(String(error)),
832
+ });
833
+ } finally {
834
+ this.#processNextTask();
835
+ }
737
836
  }
738
837
 
739
838
  /**
@@ -745,6 +844,7 @@ export default class PrefViewer extends HTMLElement {
745
844
  */
746
845
  #processVisibility(config) {
747
846
  if (!this.#component3D) {
847
+ this.#processNextTask();
748
848
  return;
749
849
  }
750
850
  const showModel = config.model?.visible;
@@ -974,7 +1074,7 @@ export default class PrefViewer extends HTMLElement {
974
1074
  * @returns {void}
975
1075
  */
976
1076
  setMode(mode = this.#mode) {
977
- mode = mode.toLowerCase();
1077
+ mode = typeof mode === "string" ? mode.toLowerCase() : this.#mode;
978
1078
  if (mode !== "2d" && mode !== "3d") {
979
1079
  console.warn(`PrefViewer: invalid mode "${mode}". Allowed modes are "2d" and "3d".`);
980
1080
  mode = this.#mode;