@preference-sl/pref-viewer 2.1.2 → 2.1.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +79 -18
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@preference-sl/pref-viewer",
3
- "version": "2.1.2",
3
+ "version": "2.1.3",
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,3 +1,45 @@
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 with updated preprocessUrl
40
+ * -----------------------------------------------------------------------------
41
+ */
42
+
1
43
  import {
2
44
  Engine,
3
45
  Scene,
@@ -18,7 +60,7 @@ class PrefViewer extends HTMLElement {
18
60
  this._createCanvas();
19
61
  this._wrapCanvas();
20
62
 
21
- // These will be assigned in initBabylon():
63
+ // These will be set in _initializeBabylon()
22
64
  this.engine = null;
23
65
  this.scene = null;
24
66
  this.camera = null;
@@ -26,7 +68,7 @@ class PrefViewer extends HTMLElement {
26
68
  this.dirLight = null;
27
69
  this._onWindowResize = null;
28
70
 
29
- // modelUrl may be set via attribute before connectedCallback
71
+ // modelUrl might be provided via attribute before connectedCallback
30
72
  this.modelUrl = null;
31
73
  this._hasInitialized = false;
32
74
  }
@@ -40,7 +82,7 @@ class PrefViewer extends HTMLElement {
40
82
  if (name === "model" && newValue) {
41
83
  this.modelUrl = newValue;
42
84
  console.log(`PrefViewer: modelUrl set to ${this.modelUrl}`);
43
- // Only reload if we've already initialized Babylon
85
+ // Only reload if initialization has already happened
44
86
  if (this._hasInitialized) {
45
87
  this._reloadModel();
46
88
  }
@@ -49,7 +91,7 @@ class PrefViewer extends HTMLElement {
49
91
 
50
92
  connectedCallback() {
51
93
  console.log("PrefViewer: connectedCallback");
52
- // 1) Determine modelUrl (either from attribute or default)
94
+ // 1) Determine modelUrl now that element is connected
53
95
  if (!this.hasAttribute("model")) {
54
96
  this.modelUrl = new URL("./models/patata.gltf", import.meta.url).href;
55
97
  console.log(`PrefViewer: no model attribute, defaulting to ${this.modelUrl}`);
@@ -58,11 +100,13 @@ class PrefViewer extends HTMLElement {
58
100
  console.log(`PrefViewer: model attribute present, using ${this.modelUrl}`);
59
101
  }
60
102
 
61
- // 2) Now initialize Babylon
103
+ // 2) Initialize Babylon (engine + scene + camera + lights + hooks)
62
104
  this._initializeBabylon();
63
- // 3) Mark that init is done
105
+
106
+ // 3) Mark that initialization is done
64
107
  this._hasInitialized = true;
65
- // 4) Finally, load whatever modelUrl we have
108
+
109
+ // 4) Load whatever modelUrl we have
66
110
  this._reloadModel();
67
111
  }
68
112
 
@@ -97,25 +141,39 @@ class PrefViewer extends HTMLElement {
97
141
 
98
142
  _initializeBabylon() {
99
143
  console.log("PrefViewer: _initializeBabylon - creating engine and scene");
100
- // 1) Create engine and scene
144
+
145
+ // 1) Create the Babylon engine & scene
101
146
  this.engine = new Engine(this.canvas, true, { alpha: true });
102
147
  this.scene = new Scene(this.engine);
103
148
  this.scene.clearColor = new Color4(1, 1, 1, 1);
104
149
 
105
- // 2) Hook into Babylon’s GLTF loader so "https://..." URIs aren't prefixed with blob:
150
+ // 2) Hook into Babylon’s GLTF loader so that any URIs starting with "blob:…"
151
+ // get stripped off before we check for an absolute "https://".
106
152
  console.log("PrefViewer: Adding preprocessUrl hook");
107
153
  SceneLoader.OnPluginActivatedObservable.add((plugin) => {
108
154
  console.log(`PrefViewer: Plugin activated - ${plugin.name}`);
109
155
  if (plugin.name === "gltf" || plugin.name === "gltf2") {
110
156
  plugin.preprocessUrl = (url) => {
111
- const fixed = url.replace(/\\/g, "/");
112
- console.log(`PrefViewer: preprocessUrl received "${url}", normalized to "${fixed}"`);
113
- if (/^https?:\/\//i.test(fixed)) {
114
- console.log(`PrefViewer: preprocessUrl returning absolute URL "${fixed}"`);
115
- return fixed;
157
+ // a) If the loader already prepended "blob:…", strip it out.
158
+ // Regex explanation: ^blob:(?:file|https?|ftp):\/\/[^\/]+\/(.*)
159
+ // basically removes the entire "blob:http://localhost:3000/" prefix.
160
+ const stripped = url.replace(
161
+ /^blob:(?:http|https|file):\/\/[^\/]+\/(.+)/i,
162
+ "$1"
163
+ );
164
+ // b) Normalize backslashes "\" → forward slashes "/"
165
+ const fixedSlashes = stripped.replace(/\\/g, "/");
166
+ console.log(
167
+ `PrefViewer: preprocessUrl received "${url}", stripped to "${stripped}", normalized to "${fixedSlashes}"`
168
+ );
169
+ // c) If it now starts with "http://" or "https://", return it as an absolute URL:
170
+ if (/^https?:\/\//i.test(fixedSlashes)) {
171
+ console.log(`PrefViewer: preprocessUrl returning absolute URL "${fixedSlashes}"`);
172
+ return fixedSlashes;
116
173
  }
117
- console.log(`PrefViewer: preprocessUrl returning relative URL "${fixed}"`);
118
- return fixed;
174
+ // d) Otherwise, return the relative path (Babylon will resolve it relative to the blob if needed)
175
+ console.log(`PrefViewer: preprocessUrl returning relative URL "${fixedSlashes}"`);
176
+ return fixedSlashes;
119
177
  };
120
178
  }
121
179
  });
@@ -125,11 +183,11 @@ class PrefViewer extends HTMLElement {
125
183
  this._createCamera();
126
184
  this._createLights();
127
185
 
128
- // 4) Hook up input/event handlers
186
+ // 4) Hook up input/event handlers (e.g. wheel-to-zoom)
129
187
  console.log("PrefViewer: _setupEventListeners");
130
188
  this._setupEventListeners();
131
189
 
132
- // 5) Start render loop
190
+ // 5) Start Babylon’s render loop
133
191
  console.log("PrefViewer: Starting render loop");
134
192
  this.engine.runRenderLoop(() => {
135
193
  if (this.scene) {
@@ -145,6 +203,7 @@ class PrefViewer extends HTMLElement {
145
203
 
146
204
  _createCamera() {
147
205
  console.log("PrefViewer: _createCamera");
206
+ // ArcRotateCamera that orbits around origin
148
207
  this.camera = new ArcRotateCamera(
149
208
  "camera",
150
209
  Math.PI / 2,
@@ -176,6 +235,7 @@ class PrefViewer extends HTMLElement {
176
235
 
177
236
  _setupEventListeners() {
178
237
  console.log("PrefViewer: _setupEventListeners");
238
+ // Zoom toward point-of-interest on wheel scroll
179
239
  this.canvas.addEventListener("wheel", (evt) => {
180
240
  if (!this.scene || !this.camera) return;
181
241
  const pickResult = this.scene.pick(
@@ -251,6 +311,7 @@ class PrefViewer extends HTMLElement {
251
311
  });
252
312
  }
253
313
 
314
+ // ====== Cleanup ======
254
315
  _disposeEngine() {
255
316
  console.log("PrefViewer: _disposeEngine");
256
317
  if (this.engine) {