@preference-sl/pref-viewer 2.10.0-beta.0 → 2.10.0-beta.1

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +68 -44
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@preference-sl/pref-viewer",
3
- "version": "2.10.0-beta.0",
3
+ "version": "2.10.0-beta.1",
4
4
  "description": "Web Component to preview GLTF models with Babylon.js",
5
5
  "author": "Alex Moreno Palacio <amoreno@preference.es>",
6
6
  "scripts": {
package/src/index.js CHANGED
@@ -6,26 +6,36 @@
6
6
  * Overview
7
7
  * --------
8
8
  * `PrefViewer` is a self-contained Web Component built with Babylon.js that:
9
- * • Renders glTF or GLB models in a <canvas> inside its shadow DOM.
10
- * • Supports loading via remote URLs (`model` attribute) or Base64 data URIs (`model-data` attribute).
9
+ * • Renders the glTF or GLB model of the product ('model') and merges it with the glTF or GLB model of environment ('scene') into a <canvas> inside its shadow DOM.
10
+ * • Supports loading via remote URL (storage.url), Base64 data URI (storage.url), or from a Base64 stored entry in IndexedDB (storage.db, storage.table, storage.id).
11
+ * • The data for loading both models is provided via the 'config' (both model and scene), 'model' and 'scene' attributes. The 'config' attribute can carry more initial configurations than just the models.
12
+ * • Exposes methods for making changes to the scene that replicate what the attribute observables do: 'loadConfig', 'loadModel', 'loadScene'.
11
13
  * • Automatically handles scene creation (engine, camera, lighting) and resource cleanup.
12
- * • Emits `model-loaded` and `model-error` events for integration.
14
+ * • Emits 'model-loaded' and 'model-error' events for integration.
13
15
  *
14
16
  * Usage
15
17
  * -----
16
- * Load from a URL:
18
+ * Load model from IndexedDB:
17
19
  * ```html
18
20
  * <pref-viewer
19
- * model="https://example.com/models/myModel.glb"
20
- * style="width:800px; height:600px;">
21
+ * model='{ "storage": { "db": "PrefConfiguratorDB", "table": "gltfModels", "id": "1234-1234-1234-1234-1234" }, "visible": true" }'
22
+ * style="width:800px; height:600px;">
23
+ * </pref-viewer>
24
+ * ```
25
+ *
26
+ * Load scene a URL:
27
+ * ```html
28
+ * <pref-viewer
29
+ * scene='{ "storage": { "url" : "https://example.com/scenes/scene1.gltf" }, "visible": true" }'
30
+ * style="width:800px; height:600px;">
21
31
  * </pref-viewer>
22
32
  * ```
23
33
  *
24
- * Load from Base64 data:
34
+ * Load model from Base64 data:
25
35
  * ```html
26
36
  * <pref-viewer
27
- * model-data="data:application/gltf+json;base64,UEsDB..."
28
- * style="width:800px; height:600px;">
37
+ * model='{ "storage": { "url" : "data:model/gltf+json;base64,UEsDB..." }, "visible": true" }'
38
+ * style="width:800px; height:600px;">
29
39
  * </pref-viewer>
30
40
  * ```
31
41
  */
@@ -41,14 +51,14 @@ class PrefViewer extends HTMLElement {
41
51
 
42
52
  #model = {
43
53
  container: null,
44
- show: true,
54
+ show: true, // Show model by default
45
55
  storage: null,
46
56
  visible: false,
47
57
  };
48
58
 
49
59
  #environment = {
50
60
  container: null,
51
- show: true,
61
+ show: true, // Show environment by default
52
62
  storage: null,
53
63
  visible: false,
54
64
  };
@@ -89,33 +99,13 @@ class PrefViewer extends HTMLElement {
89
99
  let data = null;
90
100
  switch (name) {
91
101
  case "config":
92
- data = JSON.parse(value);
93
- if (!data) {
94
- return false;
95
- }
96
- this.#model.storage = data.model?.storage || null;
97
- this.#model.show = data.model?.visible || true;
98
- this.#environment.storage = data.scene?.storage || null;
99
- this.#environment.show = data.scene?.visible || true;
100
- this.#initialized && this.#loadContainers(true, true);
102
+ this.loadConfig(value);
101
103
  break;
102
104
  case "model":
103
- data = JSON.parse(value);
104
- if (!data) {
105
- return false;
106
- }
107
- this.#model.storage = data.model?.storage || null;
108
- this.#model.show = data.model?.visible || true;
109
- this.#initialized && this.#loadContainers(true, false);
105
+ this.loadModel(value);
110
106
  break;
111
107
  case "scene":
112
- data = JSON.parse(value);
113
- if (!data) {
114
- return false;
115
- }
116
- this.#environment.storage = data.scene?.storage || null;
117
- this.#environment.show = data.scene?.visible || true;
118
- this.#initialized && this.#loadContainers(false, true);
108
+ this.loadScene(value);
119
109
  break;
120
110
  case "show-model":
121
111
  data = value.toLowerCase?.() === "true";
@@ -157,7 +147,7 @@ class PrefViewer extends HTMLElement {
157
147
 
158
148
  disconnectedCallback() {
159
149
  this.#disposeEngine();
160
- window.removeEventListener("resize", this.#onWindowResize);
150
+ this.#canvasResizeObserver.disconnect();
161
151
  }
162
152
 
163
153
  // Web Component
@@ -182,6 +172,7 @@ class PrefViewer extends HTMLElement {
182
172
  this.shadowRoot.append(wrapper);
183
173
  }
184
174
 
175
+
185
176
  // Bbylon.js
186
177
  #initializeBabylon() {
187
178
  this.#engine = new Engine(this.#canvas, true, { alpha: true });
@@ -190,17 +181,19 @@ class PrefViewer extends HTMLElement {
190
181
  this.#createCamera();
191
182
  this.#createLights();
192
183
  this.#setupInteraction();
193
-
184
+
194
185
  this.#engine.runRenderLoop(() => this.#scene && this.#scene.render());
195
- window.addEventListener("resize", this.#onWindowResize);
186
+ this.#canvasResizeObserver.observe(this.#canvas);
196
187
  }
197
188
 
198
- #onWindowResize() {
199
- this.#engine && this.#engine.resize();
200
- }
189
+ #canvasResizeObserver = new ResizeObserver(() => this.#engine && this.#engine.resize());
201
190
 
202
191
  #createCamera() {
203
- this.#camera = new ArcRotateCamera("camera", Math.PI / 2, Math.PI / 3, 10, Vector3.Zero(), this.#scene);
192
+ this.#camera = new ArcRotateCamera("camera", 3 * Math.PI / 2, Math.PI * 0.47, 10, Vector3.Zero(), this.#scene);
193
+ this.#camera.upperBetaLimit = Math.PI * 0.48;
194
+ this.#camera.lowerBetaLimit = Math.PI * 0.25;
195
+ this.#camera.lowerRadiusLimit = 5;
196
+ this.#camera.upperRadiusLimit = 20;
204
197
  this.#camera.attachControl(this.#canvas, true);
205
198
  }
206
199
 
@@ -230,8 +223,8 @@ class PrefViewer extends HTMLElement {
230
223
  this.#canvas.addEventListener("wheel", (event) => {
231
224
  if (!this.#scene || !this.#camera) return;
232
225
  const pick = this.#scene.pick(this.#scene.pointerX, this.#scene.pointerY);
233
- this.#camera.target = pick.hit ? pick.pickedPoint.clone() : this.#camera.target;
234
- this.#camera.inertialRadiusOffset += event.deltaY * this.#camera.wheelPrecision * 0.01;
226
+ //this.#camera.target = pick.hit ? pick.pickedPoint.clone() : this.#camera.target;
227
+ this.#camera.inertialRadiusOffset -= event.deltaY * this.#camera.wheelPrecision * 0.001;
235
228
  event.preventDefault();
236
229
  });
237
230
  }
@@ -377,7 +370,6 @@ class PrefViewer extends HTMLElement {
377
370
  this.#replaceContainer(this.#environment, environmentContainer.value);
378
371
  }
379
372
 
380
- this.#scene.createDefaultCamera(true, true, true);
381
373
  this.#setVisibilityOfWallAndFloorInModel();
382
374
 
383
375
  this.dispatchEvent(
@@ -401,6 +393,38 @@ class PrefViewer extends HTMLElement {
401
393
  }
402
394
 
403
395
  // Public Methods
396
+ loadConfig(config) {
397
+ config = typeof config === "string" ? JSON.parse(config) : config;
398
+ if (!config) {
399
+ return false;
400
+ }
401
+ this.#model.storage = config.model?.storage || null;
402
+ this.#model.show = config.model?.visible !== undefined ? config.model.visible : this.#model.show;
403
+ this.#environment.storage = config.scene?.storage || null;
404
+ this.#environment.show = config.scene?.visible !== undefined ? config.scene.visible : this.#environment.show;
405
+ this.#initialized && this.#loadContainers(true, true);
406
+ }
407
+
408
+ loadModel(model) {
409
+ model = typeof model === "string" ? JSON.parse(model) : model;
410
+ if (!model) {
411
+ return false;
412
+ }
413
+ this.#model.storage = model.storage || null;
414
+ this.#model.show = model.visible !== undefined ? model.visible : this.#model.show;
415
+ this.#initialized && this.#loadContainers(true, false);
416
+ }
417
+
418
+ loadScene(scene) {
419
+ scene = typeof scene === "string" ? JSON.parse(scene) : scene;
420
+ if (!scene) {
421
+ return false;
422
+ }
423
+ this.#environment.storage = scene.storage || null;
424
+ this.#environment.show = scene.visible !== undefined ? scene.visible : this.#environment.show;
425
+ this.#initialized && this.#loadContainers(false, true);
426
+ }
427
+
404
428
  showModel() {
405
429
  this.#model.show = true;
406
430
  this.#addContainer(this.#model);