@preference-sl/pref-viewer 2.13.0-beta.13 → 2.13.0-beta.14
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/gltf-storage.js +48 -55
package/package.json
CHANGED
package/src/gltf-storage.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
const PC = (globalThis.PrefConfigurator ??= {});
|
|
5
5
|
PC.version = PC.version ?? "1.0.1"; // bump if your schema changes!!
|
|
6
6
|
PC.db = PC.db ?? null;
|
|
7
|
+
PC.dbName = PC.dbName ?? null;
|
|
8
|
+
PC.storeName = PC.storeName ?? null;
|
|
7
9
|
|
|
8
10
|
function _getDbOrThrow() {
|
|
9
11
|
const db = PC.db;
|
|
@@ -23,22 +25,50 @@ function _openEnsuringStore(dbName, storeName) {
|
|
|
23
25
|
}
|
|
24
26
|
open.onupgradeneeded = (ev) => {
|
|
25
27
|
const upgradeDb = ev.target.result;
|
|
28
|
+
const tx = ev.target.transaction;
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
if (upgradeDb.objectStoreNames.contains(storeName)) {
|
|
29
|
-
upgradeDb.
|
|
30
|
+
let store;
|
|
31
|
+
if (!upgradeDb.objectStoreNames.contains(storeName)) {
|
|
32
|
+
store = upgradeDb.createObjectStore(storeName, { keyPath: 'id' });
|
|
33
|
+
} else if (tx) {
|
|
34
|
+
store = tx.objectStore(storeName);
|
|
30
35
|
}
|
|
31
36
|
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
if (store && !store.indexNames.contains('expirationTimeStamp')) {
|
|
38
|
+
store.createIndex('expirationTimeStamp', 'expirationTimeStamp', { unique: false });
|
|
39
|
+
}
|
|
34
40
|
};
|
|
35
41
|
});
|
|
36
42
|
}
|
|
37
43
|
|
|
44
|
+
function _isRecoverableDbError(err) {
|
|
45
|
+
const msg = String(err?.message ?? err ?? "");
|
|
46
|
+
if (!msg) return false;
|
|
47
|
+
return msg.includes("Database not initialized") ||
|
|
48
|
+
msg.includes("The database connection is closing") ||
|
|
49
|
+
msg.includes("InvalidStateError") ||
|
|
50
|
+
msg.includes("NotFoundError");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function _withDbRetry(work) {
|
|
54
|
+
try {
|
|
55
|
+
return await work(_getDbOrThrow());
|
|
56
|
+
} catch (e) {
|
|
57
|
+
if (!_isRecoverableDbError(e) || !PC.dbName || !PC.storeName) {
|
|
58
|
+
throw e;
|
|
59
|
+
}
|
|
60
|
+
await initDb(PC.dbName, PC.storeName);
|
|
61
|
+
return await work(_getDbOrThrow());
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
38
65
|
// --- public API -------------------------------------------------------------
|
|
39
66
|
|
|
40
67
|
// Inicializar IndexedDB y dejar el handle en PrefConfigurator.db (público)
|
|
41
68
|
export async function initDb(dbName, storeName) {
|
|
69
|
+
PC.dbName = dbName;
|
|
70
|
+
PC.storeName = storeName;
|
|
71
|
+
|
|
42
72
|
const db = await _openEnsuringStore(dbName, storeName);
|
|
43
73
|
// Close any previous handle to avoid versionchange blocking
|
|
44
74
|
try { PC.db?.close?.(); } catch { }
|
|
@@ -54,10 +84,7 @@ export async function initDb(dbName, storeName) {
|
|
|
54
84
|
|
|
55
85
|
// Guardar modelo
|
|
56
86
|
export async function saveModel(modelDataStr, storeName) {
|
|
57
|
-
return new Promise((resolve, reject) => {
|
|
58
|
-
let db;
|
|
59
|
-
try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
|
|
60
|
-
|
|
87
|
+
return _withDbRetry((db) => new Promise((resolve, reject) => {
|
|
61
88
|
let modelData;
|
|
62
89
|
try { modelData = JSON.parse(modelDataStr); }
|
|
63
90
|
catch (e) { reject(new Error("saveModel: modelDataStr is not valid JSON")); return; }
|
|
@@ -74,59 +101,34 @@ export async function saveModel(modelDataStr, storeName) {
|
|
|
74
101
|
|
|
75
102
|
req.onerror = () => reject(req.error);
|
|
76
103
|
req.onsuccess = () => resolve();
|
|
77
|
-
});
|
|
104
|
+
}));
|
|
78
105
|
}
|
|
79
106
|
|
|
80
107
|
// Cargar modelo
|
|
81
108
|
export function loadModel(modelId, storeName) {
|
|
82
|
-
return new Promise((resolve, reject) => {
|
|
83
|
-
let db;
|
|
84
|
-
try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
|
|
85
|
-
|
|
109
|
+
return _withDbRetry((db) => new Promise((resolve, reject) => {
|
|
86
110
|
const tx = db.transaction([storeName], "readonly");
|
|
87
111
|
const store = tx.objectStore(storeName);
|
|
88
112
|
const req = store.get(modelId);
|
|
89
113
|
|
|
90
114
|
req.onerror = () => reject(req.error);
|
|
91
115
|
req.onsuccess = () => resolve(req.result ?? null);
|
|
92
|
-
});
|
|
116
|
+
}));
|
|
93
117
|
}
|
|
94
118
|
|
|
95
119
|
// Descargar archivo desde base64
|
|
96
120
|
export function downloadFileFromBytes(fileName, bytesBase64, mimeType) {
|
|
97
|
-
let objectURL = null;
|
|
98
121
|
const link = document.createElement("a");
|
|
99
122
|
link.download = fileName;
|
|
100
|
-
|
|
101
|
-
const decoded = atob(bytesBase64);
|
|
102
|
-
const bytes = new Uint8Array(decoded.length);
|
|
103
|
-
for (let i = 0; i < decoded.length; i++) {
|
|
104
|
-
bytes[i] = decoded.charCodeAt(i);
|
|
105
|
-
}
|
|
106
|
-
const blob = new Blob([bytes], { type: mimeType || "application/octet-stream" });
|
|
107
|
-
objectURL = URL.createObjectURL(blob);
|
|
108
|
-
link.href = objectURL;
|
|
109
|
-
} catch {
|
|
110
|
-
// Fallback if base64 decoding fails for any reason.
|
|
111
|
-
link.href = `data:${mimeType};base64,${bytesBase64}`;
|
|
112
|
-
}
|
|
123
|
+
link.href = `data:${mimeType};base64,${bytesBase64}`;
|
|
113
124
|
document.body.appendChild(link);
|
|
114
125
|
link.click();
|
|
115
|
-
link.href = "";
|
|
116
126
|
link.remove();
|
|
117
|
-
if (objectURL) {
|
|
118
|
-
setTimeout(() => {
|
|
119
|
-
URL.revokeObjectURL(objectURL);
|
|
120
|
-
}, 0);
|
|
121
|
-
}
|
|
122
127
|
}
|
|
123
128
|
|
|
124
129
|
// Obtener todos los modelos (solo metadata)
|
|
125
130
|
export async function getAllModels(storeName) {
|
|
126
|
-
return new Promise((resolve, reject) => {
|
|
127
|
-
let db;
|
|
128
|
-
try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
|
|
129
|
-
|
|
131
|
+
return _withDbRetry((db) => new Promise((resolve, reject) => {
|
|
130
132
|
const tx = db.transaction([storeName], "readonly");
|
|
131
133
|
const store = tx.objectStore(storeName);
|
|
132
134
|
const req = store.getAll();
|
|
@@ -143,45 +145,36 @@ export async function getAllModels(storeName) {
|
|
|
143
145
|
// keep old behavior: return JSON string
|
|
144
146
|
resolve(JSON.stringify(results));
|
|
145
147
|
};
|
|
146
|
-
});
|
|
148
|
+
}));
|
|
147
149
|
}
|
|
148
150
|
|
|
149
151
|
// Eliminar modelo por id
|
|
150
152
|
export async function deleteModel(modelId, storeName) {
|
|
151
|
-
return new Promise((resolve, reject) => {
|
|
152
|
-
let db;
|
|
153
|
-
try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
|
|
154
|
-
|
|
153
|
+
return _withDbRetry((db) => new Promise((resolve, reject) => {
|
|
155
154
|
const tx = db.transaction([storeName], "readwrite");
|
|
156
155
|
const store = tx.objectStore(storeName);
|
|
157
156
|
const req = store.delete(modelId);
|
|
158
157
|
|
|
159
158
|
req.onerror = () => reject(req.error);
|
|
160
159
|
req.onsuccess = () => resolve();
|
|
161
|
-
});
|
|
160
|
+
}));
|
|
162
161
|
}
|
|
163
162
|
|
|
164
163
|
// Limpiar toda la store
|
|
165
164
|
export async function clearAll(storeName) {
|
|
166
|
-
return new Promise((resolve, reject) => {
|
|
167
|
-
let db;
|
|
168
|
-
try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
|
|
169
|
-
|
|
165
|
+
return _withDbRetry((db) => new Promise((resolve, reject) => {
|
|
170
166
|
const tx = db.transaction([storeName], "readwrite");
|
|
171
167
|
const store = tx.objectStore(storeName);
|
|
172
168
|
const req = store.clear();
|
|
173
169
|
|
|
174
170
|
req.onerror = () => reject(req.error);
|
|
175
171
|
req.onsuccess = () => resolve();
|
|
176
|
-
});
|
|
172
|
+
}));
|
|
177
173
|
}
|
|
178
174
|
|
|
179
175
|
// Borrar modelos expirados usando el índice "expirationTimeStamp"
|
|
180
176
|
export async function cleanExpiredModels(storeName) {
|
|
181
|
-
return new Promise((resolve, reject) => {
|
|
182
|
-
let db;
|
|
183
|
-
try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
|
|
184
|
-
|
|
177
|
+
return _withDbRetry((db) => new Promise((resolve, reject) => {
|
|
185
178
|
const tx = db.transaction([storeName], "readwrite");
|
|
186
179
|
const store = tx.objectStore(storeName);
|
|
187
180
|
|
|
@@ -201,7 +194,7 @@ export async function cleanExpiredModels(storeName) {
|
|
|
201
194
|
|
|
202
195
|
tx.oncomplete = () => resolve();
|
|
203
196
|
tx.onerror = () => reject(tx.error);
|
|
204
|
-
});
|
|
197
|
+
}));
|
|
205
198
|
}
|
|
206
199
|
|
|
207
200
|
// Utilidades opcionales y visibles (por si las quieres usar en consola)
|