@preference-sl/pref-viewer 2.10.0-beta.3 → 2.10.0-beta.30

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@preference-sl/pref-viewer",
3
- "version": "2.10.0-beta.3",
3
+ "version": "2.10.0-beta.30",
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,137 +1,214 @@
1
- // Inicializar IndexedDB
2
- export async function initDb(dbName, storeName) {
1
+ // wwwroot/js/gltf-storage.js
2
+
3
+ // Public, inspectable namespace
4
+ const PC = (globalThis.PrefConfigurator ??= {});
5
+ PC.version = PC.version ?? "1.0.1"; // bump if your schema changes!!
6
+ PC.db = PC.db ?? null;
7
+
8
+ function _getDbOrThrow() {
9
+ const db = PC.db;
10
+ if (!db) throw new Error("Database not initialized. Call initDb(...) first.");
11
+ return db;
12
+ }
13
+
14
+ // --- internal helpers -------------------------------------------------------
15
+ function _openEnsuringStore(dbName, storeName) {
3
16
  return new Promise((resolve, reject) => {
4
- const request = indexedDB.open(dbName, 1);
17
+ const open = indexedDB.open(dbName, 5);
5
18
 
6
- request.onerror = () => reject(request.error);
7
- request.onsuccess = () => {
8
- window.gltfDB = request.result;
9
- resolve();
10
- };
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;
11
26
 
12
- request.onupgradeneeded = (event) => {
13
- const db = event.target.result;
14
- if (!db.objectStoreNames.contains(storeName)) {
15
- const store = db.createObjectStore(storeName, { keyPath: "id" });
16
- store.createIndex("type", "type", { unique: false });
17
- store.createIndex("timestamp", "timestamp", { unique: false });
27
+ // Eliminar la object store si ya existe
28
+ if (upgradeDb.objectStoreNames.contains(storeName)) {
29
+ upgradeDb.deleteObjectStore(storeName);
18
30
  }
31
+
32
+ const store = upgradeDb.createObjectStore(storeName, { keyPath: 'id' });
33
+ store.createIndex('expirationTimeStamp', 'expirationTimeStamp', { unique: false });
19
34
  };
20
35
  });
21
36
  }
22
37
 
38
+ // --- public API -------------------------------------------------------------
39
+
40
+ // Inicializar IndexedDB y dejar el handle en PrefConfigurator.db (público)
41
+ export async function initDb(dbName, storeName) {
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
+ };
53
+ }
54
+
23
55
  // Guardar modelo
24
56
  export async function saveModel(modelDataStr, storeName) {
25
57
  return new Promise((resolve, reject) => {
26
- if (!window.gltfDB) {
27
- reject(new Error("Database not initialized"));
28
- return;
29
- }
30
- let modelData = JSON.parse(modelDataStr);
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; }
31
64
 
32
65
  const dataToStore = {
33
66
  ...modelData,
34
67
  data: modelData.data,
35
- size: modelData.data.length,
36
- timestamp: new Date().toISOString(),
68
+ size: (modelData?.data?.length ?? 0)
37
69
  };
38
70
 
39
- const transaction = window.gltfDB.transaction([storeName], "readwrite");
40
- const store = transaction.objectStore(storeName);
41
- const request = store.put(dataToStore);
71
+ const tx = db.transaction([storeName], "readwrite");
72
+ const store = tx.objectStore(storeName);
73
+ const req = store.put(dataToStore);
42
74
 
43
- request.onerror = () => reject(request.error);
44
- request.onsuccess = () => resolve();
75
+ req.onerror = () => reject(req.error);
76
+ req.onsuccess = () => resolve();
45
77
  });
46
78
  }
47
79
 
48
80
  // Cargar modelo
49
81
  export function loadModel(modelId, storeName) {
50
82
  return new Promise((resolve, reject) => {
51
- if (!globalThis.gltfDB) {
52
- reject(new Error("Database not initialized"));
53
- return;
54
- }
55
- const tx = globalThis.gltfDB.transaction([storeName], "readonly");
83
+ let db;
84
+ try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
85
+
86
+ const tx = db.transaction([storeName], "readonly");
56
87
  const store = tx.objectStore(storeName);
57
88
  const req = store.get(modelId);
89
+
58
90
  req.onerror = () => reject(req.error);
59
91
  req.onsuccess = () => resolve(req.result ?? null);
60
92
  });
61
93
  }
62
94
 
95
+ // Descargar archivo desde base64
63
96
  export function downloadFileFromBytes(fileName, bytesBase64, mimeType) {
64
97
  const link = document.createElement("a");
65
98
  link.download = fileName;
66
99
  link.href = `data:${mimeType};base64,${bytesBase64}`;
67
100
  document.body.appendChild(link);
68
101
  link.click();
69
- document.body.removeChild(link);
102
+ link.remove();
70
103
  }
71
104
 
72
105
  // Obtener todos los modelos (solo metadata)
73
106
  export async function getAllModels(storeName) {
74
107
  return new Promise((resolve, reject) => {
75
- if (!window.gltfDB) {
76
- reject(new Error("Database not initialized"));
77
- return;
78
- }
108
+ let db;
109
+ try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
79
110
 
80
- const transaction = window.gltfDB.transaction([storeName], "readonly");
81
- const store = transaction.objectStore(storeName);
82
- const request = store.getAll();
111
+ const tx = db.transaction([storeName], "readonly");
112
+ const store = tx.objectStore(storeName);
113
+ const req = store.getAll();
83
114
 
84
- request.onerror = () => reject(request.error);
85
- request.onsuccess = () => {
86
- // Excluir los datos binarios para evitar transferir demasiados datos
87
- const results = request.result.map((item) => ({
115
+ req.onerror = () => reject(req.error);
116
+ req.onsuccess = () => {
117
+ const items = Array.isArray(req.result) ? req.result : [];
118
+ const results = items.map(item => ({
88
119
  id: item.id,
89
120
  metadata: item.metadata,
90
- timestamp: item.timestamp,
91
- size: item.size,
92
- type: item.type,
121
+ timeStamp: item.timeStamp,
122
+ size: item.size
93
123
  }));
124
+ // keep old behavior: return JSON string
94
125
  resolve(JSON.stringify(results));
95
126
  };
96
127
  });
97
128
  }
98
129
 
99
- // Eliminar modelo
130
+ // Eliminar modelo por id
100
131
  export async function deleteModel(modelId, storeName) {
101
132
  return new Promise((resolve, reject) => {
102
- if (!window.gltfDB) {
103
- reject(new Error("Database not initialized"));
104
- return;
105
- }
133
+ let db;
134
+ try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
106
135
 
107
- const transaction = window.gltfDB.transaction([storeName], "readwrite");
108
- const store = transaction.objectStore(storeName);
109
- const request = store.delete(modelId);
136
+ const tx = db.transaction([storeName], "readwrite");
137
+ const store = tx.objectStore(storeName);
138
+ const req = store.delete(modelId);
110
139
 
111
- request.onerror = () => reject(request.error);
112
- request.onsuccess = () => resolve();
140
+ req.onerror = () => reject(req.error);
141
+ req.onsuccess = () => resolve();
113
142
  });
114
143
  }
115
144
 
116
- // Limpiar toda la base de datos
145
+ // Limpiar toda la store
117
146
  export async function clearAll(storeName) {
118
147
  return new Promise((resolve, reject) => {
119
- if (!window.gltfDB) {
120
- reject(new Error("Database not initialized"));
121
- return;
122
- }
148
+ let db;
149
+ try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
150
+
151
+ const tx = db.transaction([storeName], "readwrite");
152
+ const store = tx.objectStore(storeName);
153
+ const req = store.clear();
154
+
155
+ req.onerror = () => reject(req.error);
156
+ req.onsuccess = () => resolve();
157
+ });
158
+ }
159
+
160
+ // Borrar modelos expirados usando el índice "expirationTimeStamp"
161
+ export async function cleanExpiredModels(storeName) {
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
+ });
186
+ }
123
187
 
124
- const transaction = window.gltfDB.transaction([storeName], "readwrite");
125
- const store = transaction.objectStore(storeName);
126
- const request = store.clear();
188
+ // Utilidades opcionales y visibles (por si las quieres usar en consola)
189
+ export function closeDb() {
190
+ if (PC.db) {
191
+ try { PC.db.close(); } catch { }
192
+ PC.db = null;
193
+ }
194
+ }
127
195
 
128
- request.onerror = () => reject(request.error);
129
- request.onsuccess = () => resolve();
196
+ export function deleteDatabase(dbName) {
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();
130
207
  });
131
208
  }
132
209
 
210
+ // Attach a frozen, public API (no private state)
133
211
  (function attachPublicAPI(global) {
134
- const root = (global.PrefConfigurator ??= {});
135
212
  const storage = {
136
213
  initDb,
137
214
  saveModel,
@@ -139,10 +216,12 @@ export async function clearAll(storeName) {
139
216
  getAllModels,
140
217
  deleteModel,
141
218
  clearAll,
219
+ cleanExpiredModels,
142
220
  downloadFileFromBytes,
221
+ // extras
222
+ closeDb,
223
+ deleteDatabase,
143
224
  };
144
-
145
- // versionado del módulo público
146
- root.version = root.version ?? "1.0.0";
147
- root.storage = Object.freeze(storage);
225
+ // free to inspect PC.db in devtools
226
+ global.PrefConfigurator.storage = Object.freeze(storage);
148
227
  })(globalThis);