@preference-sl/pref-viewer 2.1.0 → 2.1.2

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 +54 -62
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@preference-sl/pref-viewer",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "description": "Web Component to preview GLTF models with Babylon.js",
5
5
  "author": "Alex Moreno Palacio <amoreno@preference.es>",
6
6
  "license": "MIT",
package/src/index.js CHANGED
@@ -1,45 +1,3 @@
1
- /**
2
- * =============================================================================
3
- * PrefViewer Web Component (JavaScript)
4
- * =============================================================================
5
- *
6
- * Overview
7
- * --------
8
- * `PrefViewer` is a self-contained Web Component built with Babylon.js that:
9
- * • Inserts a <canvas> into its shadow DOM to render a glTF model.
10
- * • Creates and manages a Babylon Engine, Scene, ArcRotateCamera, and basic lighting.
11
- * • Listens for a `model` attribute to load different glTF files (defaults to "./models/patata.gltf").
12
- * • Automatically disposes previous meshes when switching models.
13
- * • Dispatches “model-loaded” and “model-error” CustomEvents so host pages can react.
14
- *
15
- * Usage
16
- * -----
17
- * 1. **Import the script (module)**
18
- * <script type="module" src="path/to/pref-viewer.js"></script>
19
- *
20
- * 2. **Place the custom element in your HTML**
21
- * <pref-viewer
22
- * model="https://example.com/models/myModel.gltf"
23
- * style="width:800px; height:600px;">
24
- * </pref-viewer>
25
- *
26
- * 3. **Listen for loading events (optional)**
27
- * const viewer = document.querySelector("pref-viewer");
28
- * viewer.addEventListener("model-loaded", (evt) => {
29
- * console.log("Loaded meshes:", evt.detail.meshes);
30
- * });
31
- * viewer.addEventListener("model-error", (evt) => {
32
- * console.error("Failed to load model:", evt.detail.error);
33
- * });
34
- *
35
- * 4. **Change models at runtime**
36
- * viewer.setAttribute("model", "https://example.com/models/anotherModel.glb");
37
- *
38
- * -----------------------------------------------------------------------------
39
- * Implementation code below
40
- * -----------------------------------------------------------------------------
41
- */
42
-
43
1
  import {
44
2
  Engine,
45
3
  Scene,
@@ -55,16 +13,22 @@ import "@babylonjs/loaders";
55
13
  class PrefViewer extends HTMLElement {
56
14
  constructor() {
57
15
  super();
16
+ console.log("PrefViewer: constructor");
58
17
  this.attachShadow({ mode: "open" });
59
18
  this._createCanvas();
60
19
  this._wrapCanvas();
20
+
21
+ // These will be assigned in initBabylon():
61
22
  this.engine = null;
62
23
  this.scene = null;
63
24
  this.camera = null;
64
25
  this.hemiLight = null;
65
26
  this.dirLight = null;
66
- this.modelUrl = null;
67
27
  this._onWindowResize = null;
28
+
29
+ // modelUrl may be set via attribute before connectedCallback
30
+ this.modelUrl = null;
31
+ this._hasInitialized = false;
68
32
  }
69
33
 
70
34
  static get observedAttributes() {
@@ -72,27 +36,45 @@ class PrefViewer extends HTMLElement {
72
36
  }
73
37
 
74
38
  attributeChangedCallback(name, _oldValue, newValue) {
39
+ console.log(`PrefViewer: attributeChangedCallback - ${name} -> ${newValue}`);
75
40
  if (name === "model" && newValue) {
76
41
  this.modelUrl = newValue;
77
- this._reloadModel();
42
+ console.log(`PrefViewer: modelUrl set to ${this.modelUrl}`);
43
+ // Only reload if we've already initialized Babylon
44
+ if (this._hasInitialized) {
45
+ this._reloadModel();
46
+ }
78
47
  }
79
48
  }
80
49
 
81
50
  connectedCallback() {
82
- // If no `model` attribute is present, use default bundled GLTF
51
+ console.log("PrefViewer: connectedCallback");
52
+ // 1) Determine modelUrl (either from attribute or default)
83
53
  if (!this.hasAttribute("model")) {
84
54
  this.modelUrl = new URL("./models/patata.gltf", import.meta.url).href;
55
+ console.log(`PrefViewer: no model attribute, defaulting to ${this.modelUrl}`);
56
+ } else {
57
+ this.modelUrl = this.getAttribute("model");
58
+ console.log(`PrefViewer: model attribute present, using ${this.modelUrl}`);
85
59
  }
60
+
61
+ // 2) Now initialize Babylon
86
62
  this._initializeBabylon();
63
+ // 3) Mark that init is done
64
+ this._hasInitialized = true;
65
+ // 4) Finally, load whatever modelUrl we have
66
+ this._reloadModel();
87
67
  }
88
68
 
89
69
  disconnectedCallback() {
70
+ console.log("PrefViewer: disconnectedCallback - disposing engine");
90
71
  this._disposeEngine();
91
72
  window.removeEventListener("resize", this._onWindowResize);
92
73
  }
93
74
 
94
75
  // ====== Private setup methods ======
95
76
  _createCanvas() {
77
+ console.log("PrefViewer: _createCanvas");
96
78
  this.canvas = document.createElement("canvas");
97
79
  Object.assign(this.canvas.style, {
98
80
  width: "100%",
@@ -102,6 +84,7 @@ class PrefViewer extends HTMLElement {
102
84
  }
103
85
 
104
86
  _wrapCanvas() {
87
+ console.log("PrefViewer: _wrapCanvas");
105
88
  const wrapper = document.createElement("div");
106
89
  Object.assign(wrapper.style, {
107
90
  width: "100%",
@@ -113,49 +96,55 @@ class PrefViewer extends HTMLElement {
113
96
  }
114
97
 
115
98
  _initializeBabylon() {
99
+ console.log("PrefViewer: _initializeBabylon - creating engine and scene");
116
100
  // 1) Create engine and scene
117
101
  this.engine = new Engine(this.canvas, true, { alpha: true });
118
102
  this.scene = new Scene(this.engine);
119
103
  this.scene.clearColor = new Color4(1, 1, 1, 1);
120
104
 
121
105
  // 2) Hook into Babylon’s GLTF loader so "https://..." URIs aren't prefixed with blob:
106
+ console.log("PrefViewer: Adding preprocessUrl hook");
122
107
  SceneLoader.OnPluginActivatedObservable.add((plugin) => {
108
+ console.log(`PrefViewer: Plugin activated - ${plugin.name}`);
123
109
  if (plugin.name === "gltf" || plugin.name === "gltf2") {
124
110
  plugin.preprocessUrl = (url) => {
125
- // Normalize backslashes to forward slashes
126
111
  const fixed = url.replace(/\\/g, "/");
127
- // If it starts with http:// or https://, return as-is:
112
+ console.log(`PrefViewer: preprocessUrl received "${url}", normalized to "${fixed}"`);
128
113
  if (/^https?:\/\//i.test(fixed)) {
114
+ console.log(`PrefViewer: preprocessUrl returning absolute URL "${fixed}"`);
129
115
  return fixed;
130
116
  }
131
- // Otherwise, leave it (Babylon will treat it relative to the blob)
117
+ console.log(`PrefViewer: preprocessUrl returning relative URL "${fixed}"`);
132
118
  return fixed;
133
119
  };
134
120
  }
135
121
  });
136
122
 
137
123
  // 3) Create camera and lights
124
+ console.log("PrefViewer: _createCamera and _createLights");
138
125
  this._createCamera();
139
126
  this._createLights();
140
127
 
141
128
  // 4) Hook up input/event handlers
129
+ console.log("PrefViewer: _setupEventListeners");
142
130
  this._setupEventListeners();
143
131
 
144
132
  // 5) Start render loop
133
+ console.log("PrefViewer: Starting render loop");
145
134
  this.engine.runRenderLoop(() => {
146
135
  if (this.scene) {
147
136
  this.scene.render();
148
137
  }
149
138
  });
150
- this._onWindowResize = () => this.engine.resize();
139
+ this._onWindowResize = () => {
140
+ console.log("PrefViewer: Window resized - calling engine.resize()");
141
+ this.engine.resize();
142
+ };
151
143
  window.addEventListener("resize", this._onWindowResize);
152
-
153
- // 6) Load the initial model (if modelUrl is already set)
154
- this._reloadModel();
155
144
  }
156
145
 
157
146
  _createCamera() {
158
- // ArcRotateCamera that orbits around origin
147
+ console.log("PrefViewer: _createCamera");
159
148
  this.camera = new ArcRotateCamera(
160
149
  "camera",
161
150
  Math.PI / 2,
@@ -168,7 +157,7 @@ class PrefViewer extends HTMLElement {
168
157
  }
169
158
 
170
159
  _createLights() {
171
- // Simple hemispheric + directional as a starting point
160
+ console.log("PrefViewer: _createLights");
172
161
  this.hemiLight = new HemisphericLight(
173
162
  "hemiLight",
174
163
  new Vector3(0, 1, 0),
@@ -186,10 +175,9 @@ class PrefViewer extends HTMLElement {
186
175
  }
187
176
 
188
177
  _setupEventListeners() {
189
- // Zoom toward point-of-interest on wheel scroll
178
+ console.log("PrefViewer: _setupEventListeners");
190
179
  this.canvas.addEventListener("wheel", (evt) => {
191
180
  if (!this.scene || !this.camera) return;
192
-
193
181
  const pickResult = this.scene.pick(
194
182
  this.scene.pointerX,
195
183
  this.scene.pointerY
@@ -197,7 +185,6 @@ class PrefViewer extends HTMLElement {
197
185
  const pivotPoint = pickResult.hit
198
186
  ? pickResult.pickedPoint.clone()
199
187
  : this.camera.target.clone();
200
-
201
188
  this.camera.target = pivotPoint;
202
189
  this.camera.inertialRadiusOffset +=
203
190
  evt.deltaY * this.camera.wheelPrecision * 0.01;
@@ -207,7 +194,9 @@ class PrefViewer extends HTMLElement {
207
194
 
208
195
  // ====== Model loading / management ======
209
196
  async _reloadModel() {
197
+ console.log(`PrefViewer: _reloadModel - loading ${this.modelUrl}`);
210
198
  if (!this.scene || !this.modelUrl) {
199
+ console.warn("PrefViewer: _reloadModel aborted (scene or modelUrl missing)");
211
200
  return;
212
201
  }
213
202
 
@@ -215,6 +204,7 @@ class PrefViewer extends HTMLElement {
215
204
  this._disposePreviousMeshes();
216
205
 
217
206
  try {
207
+ console.log(`PrefViewer: ImportMeshAsync("${this.modelUrl}")`);
218
208
  const result = await SceneLoader.ImportMeshAsync(
219
209
  null,
220
210
  "",
@@ -223,10 +213,10 @@ class PrefViewer extends HTMLElement {
223
213
  undefined,
224
214
  ".gltf"
225
215
  );
226
- // After loading, create defaults if none exist
216
+ console.log("PrefViewer: Model loaded, creating default camera/light if needed");
227
217
  this.scene.createDefaultCameraOrLight(true, true, true);
228
218
 
229
- // Dispatch "model-loaded" event
219
+ console.log("PrefViewer: Dispatching model-loaded event");
230
220
  this.dispatchEvent(
231
221
  new CustomEvent("model-loaded", {
232
222
  detail: {
@@ -238,8 +228,8 @@ class PrefViewer extends HTMLElement {
238
228
  })
239
229
  );
240
230
  } catch (err) {
241
- console.error("Error loading model:", err);
242
- // Dispatch "model-error" event
231
+ console.error("PrefViewer: Error loading model:", err);
232
+ console.log("PrefViewer: Dispatching model-error event");
243
233
  this.dispatchEvent(
244
234
  new CustomEvent("model-error", {
245
235
  detail: { error: err },
@@ -251,16 +241,18 @@ class PrefViewer extends HTMLElement {
251
241
  }
252
242
 
253
243
  _disposePreviousMeshes() {
244
+ console.log("PrefViewer: _disposePreviousMeshes");
254
245
  if (!this.scene) return;
255
246
  this.scene.meshes.slice().forEach((mesh) => {
256
247
  if (mesh.getClassName() === "Mesh") {
248
+ console.log(`PrefViewer: Disposing mesh ${mesh.name}`);
257
249
  mesh.dispose();
258
250
  }
259
251
  });
260
252
  }
261
253
 
262
- // ====== Cleanup ======
263
254
  _disposeEngine() {
255
+ console.log("PrefViewer: _disposeEngine");
264
256
  if (this.engine) {
265
257
  this.engine.dispose();
266
258
  this.engine = null;