@preference-sl/pref-viewer 2.11.0-beta.1 → 2.11.0-beta.3

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.
@@ -1,6 +1,56 @@
1
1
  import { ContainerData, MaterialData, CameraData } from "./pref-viewer-3d-data.js";
2
2
  import BabylonJSController from "./babylonjs-controller.js";
3
3
 
4
+ /**
5
+ * PrefViewer3D - Custom Web Component for interactive 3D visualization and configuration.
6
+ *
7
+ * Overview:
8
+ * - Encapsulates a Babylon.js-powered 3D viewer for displaying models, environments, and materials.
9
+ * - Manages internal state for containers (model, environment, materials) and options (camera, materials).
10
+ * - Handles asset loading, configuration, and option updates through attributes and public methods.
11
+ * - Provides API for showing/hiding the viewer, model, and environment, and for downloading assets.
12
+ * - Emits custom events for loading, loaded, and option-setting states.
13
+ *
14
+ * Usage:
15
+ * - Use as a custom HTML element: <pref-viewer-3d ...>
16
+ * - Configure via attributes (config, options, show-model, show-scene, visible).
17
+ * - Control viewer state and assets via public methods.
18
+ *
19
+ * Observed Attributes:
20
+ * - show-model: Show or hide the 3D model ("true"/"false").
21
+ * - show-scene: Show or hide the 3D environment ("true"/"false").
22
+ * - visible: Show or hide the entire viewer ("true"/"false").
23
+ *
24
+ * Public Methods:
25
+ * - show(): Shows the 3D viewer component.
26
+ * - hide(): Hides the 3D viewer component.
27
+ * - load(config): Loads the provided configuration into the viewer.
28
+ * - setOptions(options): Sets viewer options such as camera and materials.
29
+ * - showModel(): Shows the 3D model.
30
+ * - hideModel(): Hides the 3D model.
31
+ * - showEnvironment(): Shows the 3D environment/scene.
32
+ * - hideEnvironment(): Hides the 3D environment/scene.
33
+ * - downloadModelGLB(): Downloads the current 3D model as a GLB file.
34
+ * - downloadModelUSDZ(): Downloads the current 3D model as a USDZ file.
35
+ * - downloadModelAndSceneGLB(): Downloads both the model and scene as a GLB file.
36
+ * - downloadModelAndSceneUSDZ(): Downloads both the model and scene as a USDZ file.
37
+ *
38
+ * Public Properties:
39
+ * - isInitialized: Indicates whether the component has completed initialization.
40
+ * - isLoaded: Indicates whether the GLTF/GLB content is loaded and ready.
41
+ * - isVisible: Indicates whether the component is currently visible.
42
+ *
43
+ * Events:
44
+ * - "scene-loading": Dispatched when a loading operation starts.
45
+ * - "scene-loaded": Dispatched when a loading operation completes.
46
+ * - "scene-setting-options": Dispatched when viewer options are being set.
47
+ * - "scene-set-options": Dispatched when viewer options have been set.
48
+ *
49
+ * Notes:
50
+ * - Internal state and heavy initialization are handled in connectedCallback.
51
+ * - Designed for extensibility and integration in product configurators and visualization tools.
52
+ * - All resource management and rendering operations are performed asynchronously for performance.
53
+ */
4
54
  export class PrefViewer3D extends HTMLElement {
5
55
  // State flags
6
56
  #isInitialized = false;
@@ -15,7 +65,7 @@ export class PrefViewer3D extends HTMLElement {
15
65
  #babylonJSController = null; // BabylonJSController instance
16
66
 
17
67
  /**
18
- * Create a new instance of the 2D viewer component.
68
+ * Create a new instance of the 3D viewer component.
19
69
  * The constructor only calls super(); heavy initialization happens in connectedCallback.
20
70
  */
21
71
  constructor() {
@@ -27,31 +77,22 @@ export class PrefViewer3D extends HTMLElement {
27
77
  * @returns {string[]} Array of attribute names that trigger attributeChangedCallback.
28
78
  */
29
79
  static get observedAttributes() {
30
- return ["config", "options", "show-model", "show-scene", "visible"];
80
+ return ["show-model", "show-scene", "visible"];
31
81
  }
32
82
 
33
83
  /**
34
- * Handle attribute changes.
35
- * @param {string} name - Attribute name.
36
- * @param {string|null} _old - Previous attribute value (unused).
37
- * @param {string|null} value -New attribute value.
84
+ * Handles changes to observed attributes and updates the component state accordingly.
85
+ * @param {string} name - The name of the changed attribute.
86
+ * @param {string|null} _old - The previous value of the attribute.
87
+ * @param {string|null} value - The new value of the attribute.
38
88
  * @returns {void}
39
89
  * @description
40
- * - "svg": triggers load of new SVG content.
90
+ * - "show-model": shows or hides the model according to the attribute value ("true"/"false").
91
+ * - "show-scene": shows or hides the scene according to the attribute value ("true"/"false").
41
92
  * - "visible": shows or hides the component according to the attribute value ("true"/"false").
42
93
  */
43
94
  attributeChangedCallback(name, _old, value) {
44
95
  switch (name) {
45
- case "visible":
46
- if (_old === value) {
47
- return;
48
- }
49
- if (value === "true" && this.#isVisible === false) {
50
- this.show();
51
- } else if (value === "false" && this.#isVisible === true) {
52
- this.hide();
53
- }
54
- break;
55
96
  case "show-model":
56
97
  if (_old === value) {
57
98
  return;
@@ -66,12 +107,23 @@ export class PrefViewer3D extends HTMLElement {
66
107
  const showScene = value.toLowerCase?.() === "true";
67
108
  showScene ? this.showEnvironment() : this.hideEnvironment();
68
109
  break;
110
+ case "visible":
111
+ if (_old === value) {
112
+ return;
113
+ }
114
+ if (value === "true" && this.#isVisible === false) {
115
+ this.show();
116
+ } else if (value === "false" && this.#isVisible === true) {
117
+ this.hide();
118
+ }
119
+ break;
69
120
  }
70
121
  }
71
122
 
72
123
  /**
73
- * Called when the element is inserted into the DOM.
74
- * Sets up DOM nodes, initial visibility and starts loading the SVG if provided.
124
+ * Called when the element is added to the DOM.
125
+ * Creates the DOM structure, sets initial visibility, initializes component data, and sets up the BabylonJSController.
126
+ * Performs heavy initialization tasks required for the 3D viewer.
75
127
  * @returns {void}
76
128
  */
77
129
  connectedCallback() {
@@ -81,6 +133,11 @@ export class PrefViewer3D extends HTMLElement {
81
133
  this.#initializeBabylonJS();
82
134
  }
83
135
 
136
+ /**
137
+ * Called when the element is removed from the DOM.
138
+ * Disables the BabylonJSController to clean up resources and event listeners associated with the 3D viewer.
139
+ * @returns {void}
140
+ */
84
141
  disconnectedCallback() {
85
142
  if (this.#babylonJSController) {
86
143
  this.#babylonJSController.disable();
@@ -113,6 +170,12 @@ export class PrefViewer3D extends HTMLElement {
113
170
  visible ? this.show() : this.hide();
114
171
  }
115
172
 
173
+ /**
174
+ * Initializes the internal data structure for containers and options used by the 3D viewer.
175
+ * Sets up default states for model, environment, materials, camera, and material options.
176
+ * @private
177
+ * @returns {void}
178
+ */
116
179
  #initializeData() {
117
180
  this.#data = {
118
181
  containers: {
@@ -132,17 +195,34 @@ export class PrefViewer3D extends HTMLElement {
132
195
  };
133
196
  }
134
197
 
198
+ /**
199
+ * Initializes the BabylonJSController and enables the Babylon.js engine for rendering.
200
+ * @private
201
+ * @returns {void}
202
+ */
135
203
  #initializeBabylonJS() {
136
204
  this.#babylonJSController = new BabylonJSController(this.#canvas, this.#data.containers, this.#data.options);
137
205
  this.#babylonJSController.enable();
138
206
  }
139
207
 
208
+ /**
209
+ * Resets update flags for all containers and material/camera options after loading or setting options.
210
+ * @private
211
+ * @returns {void}
212
+ */
140
213
  #resetUpdateFlags() {
141
- Object.values(this.#data.containers).forEach(container => container.reset());
142
- Object.values(this.#data.options.materials).forEach(material => material.reset());
214
+ Object.values(this.#data.containers).forEach((container) => container.reset());
215
+ Object.values(this.#data.options.materials).forEach((material) => material.reset());
143
216
  this.#data.options.camera.reset();
144
217
  }
145
218
 
219
+ /**
220
+ * Checks if any container (model, environment, materials) needs to be updated based on the provided config.
221
+ * Sets pending state for containers if storage or visibility changes are detected.
222
+ * @private
223
+ * @param {object} config - Configuration object containing model, scene, and materials info.
224
+ * @returns {void}
225
+ */
146
226
  #checkNeedToUpdateContainers(config) {
147
227
  const modelState = this.#data.containers.model;
148
228
  const modelHasStorage = !!config.model?.storage;
@@ -170,6 +250,13 @@ export class PrefViewer3D extends HTMLElement {
170
250
  }
171
251
  }
172
252
 
253
+ /**
254
+ * Checks if the camera option needs to be updated based on the provided options.
255
+ * Sets pending state for the camera if a change is detected.
256
+ * @private
257
+ * @param {object} options - Options object containing camera info.
258
+ * @returns {boolean} True if camera needs update, false otherwise.
259
+ */
173
260
  #checkNeedToUpdateCamera(options) {
174
261
  if (!options || options.camera === undefined || options.camera === "") {
175
262
  return false;
@@ -182,6 +269,13 @@ export class PrefViewer3D extends HTMLElement {
182
269
  return needUpdate;
183
270
  }
184
271
 
272
+ /**
273
+ * Checks if any material option needs to be updated based on the provided options.
274
+ * Sets pending state for materials if changes are detected.
275
+ * @private
276
+ * @param {object} options - Options object containing material info.
277
+ * @returns {boolean} True if any material needs update, false otherwise.
278
+ */
185
279
  #checkNeedToUpdateMaterials(options) {
186
280
  if (!options) {
187
281
  return false;
@@ -201,24 +295,34 @@ export class PrefViewer3D extends HTMLElement {
201
295
  return someNeedUpdate;
202
296
  }
203
297
 
204
-
298
+ /**
299
+ * Dispatches a "prefviewer3d-loading" event and updates loading state attributes.
300
+ * Used internally when a loading operation starts.
301
+ * @private
302
+ * @returns {void}
303
+ */
205
304
  #onLoading() {
206
- this.dispatchEvent(
207
- new CustomEvent("prefviewer3d-loading", {
208
- bubbles: true,
209
- cancelable: false,
210
- composed: true,
211
- })
212
- );
213
-
305
+ const customEventOptions = {
306
+ bubbles: true, // indicates whether the event bubbles up through the DOM tree or not
307
+ cancelable: true, // indicates whether the event can be canceled, and therefore prevented as if the event never happened
308
+ composed: false, // indicates whether or not the event will propagate across the shadow DOM boundary into the standard DOM
309
+ };
310
+ this.dispatchEvent(new CustomEvent("scene-loading", customEventOptions));
311
+
214
312
  this.removeAttribute("loaded");
215
313
  this.setAttribute("loading", "");
216
-
314
+
217
315
  if (this.#isLoaded === true) {
218
316
  this.#isLoaded = false;
219
317
  }
220
318
  }
221
-
319
+
320
+ /**
321
+ * Dispatches a "prefviewer3d-loaded" event with details about what was loaded and updates state attributes.
322
+ * Used internally when a loading operation completes.
323
+ * @private
324
+ * @returns {object} Detail object describing what was tried and what succeeded.
325
+ */
222
326
  #onLoaded() {
223
327
  const toLoadDetail = {
224
328
  container_model: this.#data.containers.model.isPending,
@@ -245,42 +349,52 @@ export class PrefViewer3D extends HTMLElement {
245
349
  success: loadedDetail,
246
350
  };
247
351
 
248
- this.dispatchEvent(
249
- new CustomEvent("prefviewer3d-loaded", {
250
- bubbles: true,
251
- cancelable: false,
252
- composed: true,
253
- detail: detail,
254
- })
255
- );
256
-
352
+ const customEventOptions = {
353
+ bubbles: true,
354
+ cancelable: true,
355
+ composed: false,
356
+ detail: detail,
357
+ };
358
+ this.dispatchEvent(new CustomEvent("scene-loaded", customEventOptions));
359
+
257
360
  this.removeAttribute("loading");
258
361
  this.setAttribute("loaded", "");
259
-
362
+
260
363
  this.#resetUpdateFlags();
261
364
  this.#isLoaded = true;
262
365
 
263
366
  return detail;
264
367
  }
265
368
 
369
+ /**
370
+ * Dispatches a "prefviewer3d-setting-options" event and updates loading state attributes.
371
+ * Used internally when options are being set.
372
+ * @private
373
+ * @returns {void}
374
+ */
266
375
  #onSettingOptions() {
267
- this.dispatchEvent(
268
- new CustomEvent("prefviewer3d-setting-options", {
269
- bubbles: true,
270
- cancelable: false,
271
- composed: true,
272
- })
273
- );
274
-
376
+ const customEventOptions = {
377
+ bubbles: true,
378
+ cancelable: true,
379
+ composed: false,
380
+ };
381
+ this.dispatchEvent(new CustomEvent("scene-setting-options", customEventOptions));
382
+
275
383
  this.removeAttribute("loaded");
276
384
  this.setAttribute("loading", "");
277
-
385
+
278
386
  if (this.#isLoaded === true) {
279
387
  this.#isLoaded = false;
280
388
  }
281
389
  }
282
-
283
- #onSettedOptions() {
390
+
391
+ /**
392
+ * Dispatches a "prefviewer3d-loaded" event with details about what options were set and updates state attributes.
393
+ * Used internally when options have been set.
394
+ * @private
395
+ * @returns {object} Detail object describing what was tried and what succeeded.
396
+ */
397
+ #onSetOptions() {
284
398
  const toSetDetail = {
285
399
  camera: this.#data.options.camera.isPending,
286
400
  innerWallMaterial: this.#data.options.materials.innerWall.isPending,
@@ -299,19 +413,18 @@ export class PrefViewer3D extends HTMLElement {
299
413
  tried: toSetDetail,
300
414
  success: settedDetail,
301
415
  };
302
-
303
- this.dispatchEvent(
304
- new CustomEvent("prefviewer3d-loaded", {
305
- bubbles: true,
306
- cancelable: false,
307
- composed: true,
308
- detail: detail,
309
- })
310
- );
311
416
 
417
+ const customEventOptions = {
418
+ bubbles: true,
419
+ cancelable: true,
420
+ composed: false,
421
+ detail: detail,
422
+ };
423
+ this.dispatchEvent(new CustomEvent("scene-set-options", customEventOptions));
424
+
312
425
  this.removeAttribute("loading");
313
426
  this.setAttribute("loaded", "");
314
-
427
+
315
428
  this.#resetUpdateFlags();
316
429
  this.#isLoaded = true;
317
430
 
@@ -322,22 +435,40 @@ export class PrefViewer3D extends HTMLElement {
322
435
  * ---------------------------
323
436
  * Public methods
324
437
  * ---------------------------
325
- */
438
+ */
439
+
440
+ /**
441
+ * Hides the 3D viewer component by updating its visibility state and DOM attribute.
442
+ * @public
443
+ * @returns {void}
444
+ */
326
445
  hide() {
327
- this.#isVisible = false;
328
- this.setAttribute("visible", "false");
446
+ this.#isVisible = false;
447
+ this.setAttribute("visible", "false");
329
448
  }
330
-
449
+
450
+ /**
451
+ * Shows the 3D viewer component by updating its visibility state and DOM attribute.
452
+ * @public
453
+ * @returns {void}
454
+ */
331
455
  show() {
332
456
  this.#isVisible = true;
333
457
  this.setAttribute("visible", "true");
334
458
  }
335
459
 
460
+ /**
461
+ * Loads the provided configuration into the 3D viewer.
462
+ * Updates containers and options, triggers loading events, and returns the loading result.
463
+ * @public
464
+ * @param {object} config - Configuration object containing containers and options.
465
+ * @returns {Promise<object>} Resolves with an object containing success and error status and loading details.
466
+ */
336
467
  async load(config) {
337
468
  if (!this.#babylonJSController) {
338
469
  return;
339
470
  }
340
-
471
+
341
472
  this.#onLoading();
342
473
 
343
474
  // Containers
@@ -349,11 +480,18 @@ export class PrefViewer3D extends HTMLElement {
349
480
  this.#checkNeedToUpdateMaterials(config.options);
350
481
  }
351
482
 
352
- const success = await this.#babylonJSController.load();
353
-
354
- return { success: success, detail: this.#onLoaded() };
483
+ const loadDetail = await this.#babylonJSController.load();
484
+
485
+ return { ...loadDetail, load: this.#onLoaded() };
355
486
  }
356
487
 
488
+ /**
489
+ * Sets viewer options such as camera and materials.
490
+ * Updates internal states, triggers option setting events, and returns the result.
491
+ * @public
492
+ * @param {object} options - Options object containing camera and material settings.
493
+ * @returns {object} Object containing success status and details of set options.
494
+ */
357
495
  setOptions(options) {
358
496
  if (!this.#babylonJSController) {
359
497
  return;
@@ -368,10 +506,15 @@ export class PrefViewer3D extends HTMLElement {
368
506
  if (this.#checkNeedToUpdateMaterials(options)) {
369
507
  someSetted = someSetted || this.#babylonJSController.setMaterialOptions();
370
508
  }
371
- const detail = this.#onSettedOptions();
372
- return {success: someSetted, detail: detail};
509
+ const detail = this.#onSetOptions();
510
+ return { success: someSetted, detail: detail };
373
511
  }
374
512
 
513
+ /**
514
+ * Shows the 3D model within the viewer.
515
+ * @public
516
+ * @returns {void}
517
+ */
375
518
  showModel() {
376
519
  if (!this.#babylonJSController) {
377
520
  return;
@@ -379,6 +522,11 @@ export class PrefViewer3D extends HTMLElement {
379
522
  this.#babylonJSController.setContainerVisibility("model", true);
380
523
  }
381
524
 
525
+ /**
526
+ * Hides the 3D model within the viewer.
527
+ * @public
528
+ * @returns {void}
529
+ */
382
530
  hideModel() {
383
531
  if (!this.#babylonJSController) {
384
532
  return;
@@ -386,6 +534,11 @@ export class PrefViewer3D extends HTMLElement {
386
534
  this.#babylonJSController.setContainerVisibility("model", false);
387
535
  }
388
536
 
537
+ /**
538
+ * Shows the 3D environment/scene within the viewer.
539
+ * @public
540
+ * @returns {void}
541
+ */
389
542
  showEnvironment() {
390
543
  if (!this.#babylonJSController) {
391
544
  return;
@@ -393,6 +546,11 @@ export class PrefViewer3D extends HTMLElement {
393
546
  this.#babylonJSController.setContainerVisibility("environment", true);
394
547
  }
395
548
 
549
+ /**
550
+ * Hides the 3D environment/scene within the viewer.
551
+ * @public
552
+ * @returns {void}
553
+ */
396
554
  hideEnvironment() {
397
555
  if (!this.#babylonJSController) {
398
556
  return;
@@ -400,6 +558,11 @@ export class PrefViewer3D extends HTMLElement {
400
558
  this.#babylonJSController.setContainerVisibility("environment", false);
401
559
  }
402
560
 
561
+ /**
562
+ * Downloads the current 3D model as a GLB file.
563
+ * @public
564
+ * @returns {void}
565
+ */
403
566
  downloadModelGLB() {
404
567
  if (!this.#babylonJSController) {
405
568
  return;
@@ -407,6 +570,11 @@ export class PrefViewer3D extends HTMLElement {
407
570
  this.#babylonJSController.downloadModelGLB();
408
571
  }
409
572
 
573
+ /**
574
+ * Downloads the current 3D model as a USDZ file.
575
+ * @public
576
+ * @returns {void}
577
+ */
410
578
  downloadModelUSDZ() {
411
579
  if (!this.#babylonJSController) {
412
580
  return;
@@ -414,6 +582,11 @@ export class PrefViewer3D extends HTMLElement {
414
582
  this.#babylonJSController.downloadModelUSDZ();
415
583
  }
416
584
 
585
+ /**
586
+ * Downloads both the 3D model and scene as a USDZ file.
587
+ * @public
588
+ * @returns {void}
589
+ */
417
590
  downloadModelAndSceneUSDZ() {
418
591
  if (!this.#babylonJSController) {
419
592
  return;
@@ -421,6 +594,11 @@ export class PrefViewer3D extends HTMLElement {
421
594
  this.#babylonJSController.downloadModelAndSceneUSDZ();
422
595
  }
423
596
 
597
+ /**
598
+ * Downloads both the 3D model and scene as a GLB file.
599
+ * @public
600
+ * @returns {void}
601
+ */
424
602
  downloadModelAndSceneGLB() {
425
603
  if (!this.#babylonJSController) {
426
604
  return;
@@ -433,7 +611,7 @@ export class PrefViewer3D extends HTMLElement {
433
611
  * @public
434
612
  * @returns {boolean} True if the component is initialized; otherwise, false.
435
613
  */
436
- get initialized() {
614
+ get isInitialized() {
437
615
  return this.#isInitialized;
438
616
  }
439
617
 
@@ -442,7 +620,7 @@ export class PrefViewer3D extends HTMLElement {
442
620
  * @public
443
621
  * @returns {boolean} True if the GLTF/GLB is loaded; otherwise, false.
444
622
  */
445
- get loaded() {
623
+ get isLoaded() {
446
624
  return this.#isLoaded;
447
625
  }
448
626
 
@@ -451,7 +629,7 @@ export class PrefViewer3D extends HTMLElement {
451
629
  * @public
452
630
  * @returns {boolean} True if the component is visible; otherwise, false.
453
631
  */
454
- get visible() {
632
+ get isVisible() {
455
633
  return this.#isVisible;
456
634
  }
457
635
  }