@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.
- package/package.json +5 -5
- package/src/gltf-storage.js +167 -193
- 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
|
|
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.
|
|
38
|
-
"@babylonjs/loaders": "^8.
|
|
39
|
-
"@babylonjs/serializers": "^8.
|
|
40
|
-
"babylonjs-gltf2interface": "^8.
|
|
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",
|
package/src/gltf-storage.js
CHANGED
|
@@ -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
|
|
6
|
-
PC.db = PC.db ?? null;
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
let db;
|
|
84
|
+
try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
|
|
113
85
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
86
|
+
const tx = db.transaction([storeName], "readonly");
|
|
87
|
+
const store = tx.objectStore(storeName);
|
|
88
|
+
const req = store.get(modelId);
|
|
117
89
|
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
132
|
+
return new Promise((resolve, reject) => {
|
|
133
|
+
let db;
|
|
134
|
+
try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
|
|
163
135
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
136
|
+
const tx = db.transaction([storeName], "readwrite");
|
|
137
|
+
const store = tx.objectStore(storeName);
|
|
138
|
+
const req = store.delete(modelId);
|
|
167
139
|
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
147
|
+
return new Promise((resolve, reject) => {
|
|
148
|
+
let db;
|
|
149
|
+
try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
|
|
178
150
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
151
|
+
const tx = db.transaction([storeName], "readwrite");
|
|
152
|
+
const store = tx.objectStore(storeName);
|
|
153
|
+
const req = store.clear();
|
|
182
154
|
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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 (
|
|
188
|
+
// Utilidades opcionales y visibles (por si las quieres usar en consola)
|
|
217
189
|
export function closeDb() {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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);
|