@preference-sl/pref-viewer 2.10.0-beta.9 → 2.10.0

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 (3) hide show
  1. package/package.json +5 -5
  2. package/src/gltf-storage.js +167 -193
  3. package/src/index.js +501 -425
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@preference-sl/pref-viewer",
3
- "version": "2.10.0-beta.9",
3
+ "version": "2.10.0",
4
4
  "description": "Web Component to preview GLTF models with Babylon.js",
5
5
  "author": "Alex Moreno Palacio <amoreno@preference.es>",
6
6
  "scripts": {
@@ -34,10 +34,10 @@
34
34
  "index.d.ts"
35
35
  ],
36
36
  "dependencies": {
37
- "@babylonjs/core": "^8.28.2",
38
- "@babylonjs/loaders": "^8.28.2",
39
- "@babylonjs/serializers": "^8.28.2",
40
- "babylonjs-gltf2interface": "^8.28.2"
37
+ "@babylonjs/core": "^8.31.3",
38
+ "@babylonjs/loaders": "^8.31.3",
39
+ "@babylonjs/serializers": "^8.31.3",
40
+ "babylonjs-gltf2interface": "^8.31.3"
41
41
  },
42
42
  "devDependencies": {
43
43
  "esbuild": "^0.25.10",
@@ -1,253 +1,227 @@
1
- // wwwroot/js/gltf-storage.js
1
+ // wwwroot/js/gltf-storage.js
2
2
 
3
3
  // Public, inspectable namespace
4
4
  const PC = (globalThis.PrefConfigurator ??= {});
5
- PC.version = PC.version ?? "1.0.1"; // bump if you change schema
6
- PC.db = PC.db ?? null; // public handle (no private state)
5
+ PC.version = PC.version ?? "1.0.1"; // bump if your schema changes!!
6
+ PC.db = PC.db ?? null;
7
7
 
8
8
  function _getDbOrThrow() {
9
- const db = PC.db;
10
- if (!db) throw new Error("Database not initialized. Call initDb(...) first.");
11
- return db;
9
+ const db = PC.db;
10
+ if (!db) throw new Error("Database not initialized. Call initDb(...) first.");
11
+ return db;
12
12
  }
13
13
 
14
14
  // --- internal helpers -------------------------------------------------------
15
-
16
- function _createStoreIfMissing(db, storeName) {
17
- if (!db.objectStoreNames.contains(storeName)) {
18
- const store = db.createObjectStore(storeName, { keyPath: "id" });
19
- store.createIndex("expirationTimeStamp", "expirationTimeStamp", { unique: false });
20
- }
21
- }
22
-
23
- /**
24
- * Opens a DB without specifying a version. If the required object store doesn't
25
- * exist, it re-opens with (currentVersion + 1) to create it.
26
- */
27
15
  function _openEnsuringStore(dbName, storeName) {
28
- return new Promise((resolve, reject) => {
29
- const open = indexedDB.open(dbName);
30
-
31
- open.onblocked = () => reject(new Error("Open blocked by another tab or worker"));
32
- open.onerror = () => reject(open.error);
33
-
34
- open.onupgradeneeded = (e) => {
35
- // First creation path (brand-new DB)
36
- const db = e.target.result;
37
- _createStoreIfMissing(db, storeName);
38
- };
39
-
40
- open.onsuccess = (e) => {
41
- const db = e.target.result;
42
-
43
- // If the store exists, we're done.
44
- if (db.objectStoreNames.contains(storeName)) {
45
- resolve(db);
46
- return;
47
- }
48
-
49
- // Otherwise bump version just enough to add the store.
50
- const nextVersion = db.version + 1;
51
- db.close();
52
-
53
- const upgrade = indexedDB.open(dbName, nextVersion);
54
- upgrade.onblocked = () => reject(new Error("Upgrade blocked by another tab or worker"));
55
- upgrade.onerror = () => reject(upgrade.error);
56
- upgrade.onupgradeneeded = (ev) => {
57
- const upDb = ev.target.result;
58
- _createStoreIfMissing(upDb, storeName);
59
- };
60
- upgrade.onsuccess = (ev) => resolve(ev.target.result);
61
- };
62
- });
16
+ return new Promise((resolve, reject) => {
17
+ const open = indexedDB.open(dbName, 5);
18
+
19
+ open.onblocked = () => reject(new Error("Open blocked by another tab or worker"));
20
+ open.onerror = () => reject(open.error);
21
+ open.onsuccess = (e) => {
22
+ resolve(e.target.result);
23
+ }
24
+ open.onupgradeneeded = (ev) => {
25
+ const upgradeDb = ev.target.result;
26
+
27
+ // Eliminar la object store si ya existe
28
+ if (upgradeDb.objectStoreNames.contains(storeName)) {
29
+ upgradeDb.deleteObjectStore(storeName);
30
+ }
31
+
32
+ const store = upgradeDb.createObjectStore(storeName, { keyPath: 'id' });
33
+ store.createIndex('expirationTimeStamp', 'expirationTimeStamp', { unique: false });
34
+ };
35
+ });
63
36
  }
64
37
 
65
38
  // --- public API -------------------------------------------------------------
66
39
 
67
40
  // Inicializar IndexedDB y dejar el handle en PrefConfigurator.db (público)
68
41
  export async function initDb(dbName, storeName) {
69
- const db = await _openEnsuringStore(dbName, storeName);
70
-
71
- // Close any previous handle to avoid versionchange blocking
72
- try { PC.db?.close?.(); } catch {}
73
- PC.db = db;
74
-
75
- // If another tab upgrades, close gracefully so next call can re-init
76
- db.onversionchange = () => {
77
- try { db.close(); } catch {}
78
- if (PC.db === db) PC.db = null;
79
- console.warn("[PrefConfigurator] DB connection closed due to versionchange. Re-run initDb().");
80
- };
42
+ const db = await _openEnsuringStore(dbName, storeName);
43
+ // Close any previous handle to avoid versionchange blocking
44
+ try { PC.db?.close?.(); } catch { }
45
+ PC.db = db;
46
+
47
+ // If another tab upgrades, close gracefully so next call can re-init
48
+ db.onversionchange = () => {
49
+ try { db.close(); } catch { }
50
+ if (PC.db === db) PC.db = null;
51
+ console.warn("[PrefConfigurator] DB connection closed due to versionchange. Re-run initDb().");
52
+ };
81
53
  }
82
54
 
83
55
  // Guardar modelo
84
56
  export async function saveModel(modelDataStr, storeName) {
85
- return new Promise((resolve, reject) => {
86
- let db;
87
- try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
88
-
89
- let modelData;
90
- try { modelData = JSON.parse(modelDataStr); }
91
- catch (e) { reject(new Error("saveModel: modelDataStr is not valid JSON")); return; }
92
-
93
- const dataToStore = {
94
- ...modelData,
95
- data: modelData.data,
96
- size: (modelData?.data?.length ?? 0)
97
- };
98
-
99
- const tx = db.transaction([storeName], "readwrite");
100
- const store = tx.objectStore(storeName);
101
- const req = store.put(dataToStore);
102
-
103
- req.onerror = () => reject(req.error);
104
- req.onsuccess = () => resolve();
105
- });
57
+ return new Promise((resolve, reject) => {
58
+ let db;
59
+ try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
60
+
61
+ let modelData;
62
+ try { modelData = JSON.parse(modelDataStr); }
63
+ catch (e) { reject(new Error("saveModel: modelDataStr is not valid JSON")); return; }
64
+
65
+ const dataToStore = {
66
+ ...modelData,
67
+ data: modelData.data,
68
+ size: (modelData?.data?.length ?? 0)
69
+ };
70
+
71
+ const tx = db.transaction([storeName], "readwrite");
72
+ const store = tx.objectStore(storeName);
73
+ const req = store.put(dataToStore);
74
+
75
+ req.onerror = () => reject(req.error);
76
+ req.onsuccess = () => resolve();
77
+ });
106
78
  }
107
79
 
108
80
  // Cargar modelo
109
81
  export function loadModel(modelId, storeName) {
110
- return new Promise((resolve, reject) => {
111
- let db;
112
- try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
82
+ return new Promise((resolve, reject) => {
83
+ let db;
84
+ try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
113
85
 
114
- const tx = db.transaction([storeName], "readonly");
115
- const store = tx.objectStore(storeName);
116
- const req = store.get(modelId);
86
+ const tx = db.transaction([storeName], "readonly");
87
+ const store = tx.objectStore(storeName);
88
+ const req = store.get(modelId);
117
89
 
118
- req.onerror = () => reject(req.error);
119
- req.onsuccess = () => resolve(req.result ?? null);
120
- });
90
+ req.onerror = () => reject(req.error);
91
+ req.onsuccess = () => resolve(req.result ?? null);
92
+ });
121
93
  }
122
94
 
123
95
  // Descargar archivo desde base64
124
96
  export function downloadFileFromBytes(fileName, bytesBase64, mimeType) {
125
- const link = document.createElement("a");
126
- link.download = fileName;
127
- link.href = `data:${mimeType};base64,${bytesBase64}`;
128
- document.body.appendChild(link);
129
- link.click();
130
- link.remove();
97
+ const link = document.createElement("a");
98
+ link.download = fileName;
99
+ link.href = `data:${mimeType};base64,${bytesBase64}`;
100
+ document.body.appendChild(link);
101
+ link.click();
102
+ link.remove();
131
103
  }
132
104
 
133
105
  // Obtener todos los modelos (solo metadata)
134
106
  export async function getAllModels(storeName) {
135
- return new Promise((resolve, reject) => {
136
- let db;
137
- try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
138
-
139
- const tx = db.transaction([storeName], "readonly");
140
- const store = tx.objectStore(storeName);
141
- const req = store.getAll();
142
-
143
- req.onerror = () => reject(req.error);
144
- req.onsuccess = () => {
145
- const items = Array.isArray(req.result) ? req.result : [];
146
- const results = items.map(item => ({
147
- id: item.id,
148
- metadata: item.metadata,
149
- timeStamp: item.timeStamp,
150
- size: item.size
151
- }));
152
- // keep old behavior: return JSON string
153
- resolve(JSON.stringify(results));
154
- };
155
- });
107
+ return new Promise((resolve, reject) => {
108
+ let db;
109
+ try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
110
+
111
+ const tx = db.transaction([storeName], "readonly");
112
+ const store = tx.objectStore(storeName);
113
+ const req = store.getAll();
114
+
115
+ req.onerror = () => reject(req.error);
116
+ req.onsuccess = () => {
117
+ const items = Array.isArray(req.result) ? req.result : [];
118
+ const results = items.map(item => ({
119
+ id: item.id,
120
+ metadata: item.metadata,
121
+ timeStamp: item.timeStamp,
122
+ size: item.size
123
+ }));
124
+ // keep old behavior: return JSON string
125
+ resolve(JSON.stringify(results));
126
+ };
127
+ });
156
128
  }
157
129
 
158
130
  // Eliminar modelo por id
159
131
  export async function deleteModel(modelId, storeName) {
160
- return new Promise((resolve, reject) => {
161
- let db;
162
- try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
132
+ return new Promise((resolve, reject) => {
133
+ let db;
134
+ try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
163
135
 
164
- const tx = db.transaction([storeName], "readwrite");
165
- const store = tx.objectStore(storeName);
166
- const req = store.delete(modelId);
136
+ const tx = db.transaction([storeName], "readwrite");
137
+ const store = tx.objectStore(storeName);
138
+ const req = store.delete(modelId);
167
139
 
168
- req.onerror = () => reject(req.error);
169
- req.onsuccess = () => resolve();
170
- });
140
+ req.onerror = () => reject(req.error);
141
+ req.onsuccess = () => resolve();
142
+ });
171
143
  }
172
144
 
173
145
  // Limpiar toda la store
174
146
  export async function clearAll(storeName) {
175
- return new Promise((resolve, reject) => {
176
- let db;
177
- try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
147
+ return new Promise((resolve, reject) => {
148
+ let db;
149
+ try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
178
150
 
179
- const tx = db.transaction([storeName], "readwrite");
180
- const store = tx.objectStore(storeName);
181
- const req = store.clear();
151
+ const tx = db.transaction([storeName], "readwrite");
152
+ const store = tx.objectStore(storeName);
153
+ const req = store.clear();
182
154
 
183
- req.onerror = () => reject(req.error);
184
- req.onsuccess = () => resolve();
185
- });
155
+ req.onerror = () => reject(req.error);
156
+ req.onsuccess = () => resolve();
157
+ });
186
158
  }
187
159
 
188
160
  // Borrar modelos expirados usando el índice "expirationTimeStamp"
189
161
  export async function cleanExpiredModels(storeName) {
190
- return new Promise((resolve, reject) => {
191
- let db;
192
- try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
193
-
194
- const tx = db.transaction([storeName], "readwrite");
195
- const store = tx.objectStore(storeName);
196
-
197
- const index = store.index("expirationTimeStamp");
198
- const now = Date.now();
199
- const range = IDBKeyRange.upperBound(now);
200
- const cursorRequest = index.openCursor(range);
201
-
202
- cursorRequest.onerror = () => reject(cursorRequest.error);
203
- cursorRequest.onsuccess = (event) => {
204
- const cursor = event.target.result;
205
- if (cursor) {
206
- cursor.delete();
207
- cursor.continue();
208
- }
209
- };
210
-
211
- tx.oncomplete = () => resolve();
212
- tx.onerror = () => reject(tx.error);
213
- });
162
+ return new Promise((resolve, reject) => {
163
+ let db;
164
+ try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
165
+
166
+ const tx = db.transaction([storeName], "readwrite");
167
+ const store = tx.objectStore(storeName);
168
+
169
+ const index = store.index("expirationTimeStamp");
170
+ const now = Date.now();
171
+ const range = IDBKeyRange.upperBound(now);
172
+ const cursorRequest = index.openCursor(range);
173
+
174
+ cursorRequest.onerror = () => reject(cursorRequest.error);
175
+ cursorRequest.onsuccess = (event) => {
176
+ const cursor = event.target.result;
177
+ if (cursor) {
178
+ cursor.delete();
179
+ cursor.continue();
180
+ }
181
+ };
182
+
183
+ tx.oncomplete = () => resolve();
184
+ tx.onerror = () => reject(tx.error);
185
+ });
214
186
  }
215
187
 
216
- // Utilidades opcionales y visibles (para usar en consola)
188
+ // Utilidades opcionales y visibles (por si las quieres usar en consola)
217
189
  export function closeDb() {
218
- if (PC.db) {
219
- try { PC.db.close(); } catch {}
220
- PC.db = null;
221
- }
190
+ if (PC.db) {
191
+ try { PC.db.close(); } catch { }
192
+ PC.db = null;
193
+ }
222
194
  }
223
195
 
224
196
  export function deleteDatabase(dbName) {
225
- return new Promise((resolve, reject) => {
226
- if (PC.db && PC.db.name === dbName) {
227
- try { PC.db.close(); } catch {}
228
- PC.db = null;
229
- }
230
- const req = indexedDB.deleteDatabase(dbName);
231
- req.onblocked = () => reject(new Error("Delete blocked by another tab or worker"));
232
- req.onerror = () => reject(req.error);
233
- req.onsuccess = () => resolve();
234
- });
197
+ return new Promise((resolve, reject) => {
198
+ // Close current handle if points to this DB
199
+ if (PC.db && PC.db.name === dbName) {
200
+ try { PC.db.close(); } catch { }
201
+ PC.db = null;
202
+ }
203
+ const req = indexedDB.deleteDatabase(dbName);
204
+ req.onblocked = () => reject(new Error("Delete blocked by another tab or worker"));
205
+ req.onerror = () => reject(req.error);
206
+ req.onsuccess = () => resolve();
207
+ });
235
208
  }
236
209
 
237
210
  // Attach a frozen, public API (no private state)
238
211
  (function attachPublicAPI(global) {
239
- const storage = {
240
- initDb,
241
- saveModel,
242
- loadModel,
243
- getAllModels,
244
- deleteModel,
245
- clearAll,
246
- cleanExpiredModels,
247
- downloadFileFromBytes,
248
- // extras
249
- closeDb,
250
- deleteDatabase,
251
- };
252
- global.PrefConfigurator.storage = Object.freeze(storage);
212
+ const storage = {
213
+ initDb,
214
+ saveModel,
215
+ loadModel,
216
+ getAllModels,
217
+ deleteModel,
218
+ clearAll,
219
+ cleanExpiredModels,
220
+ downloadFileFromBytes,
221
+ // extras
222
+ closeDb,
223
+ deleteDatabase,
224
+ };
225
+ // free to inspect PC.db in devtools
226
+ global.PrefConfigurator.storage = Object.freeze(storage);
253
227
  })(globalThis);