@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.
- package/package.json +1 -1
- package/src/index.js +79 -18
package/package.json
CHANGED
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
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
103
|
+
// 2) Initialize Babylon (engine + scene + camera + lights + hooks)
|
|
62
104
|
this._initializeBabylon();
|
|
63
|
-
|
|
105
|
+
|
|
106
|
+
// 3) Mark that initialization is done
|
|
64
107
|
this._hasInitialized = true;
|
|
65
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
118
|
-
|
|
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) {
|