@preference-sl/pref-viewer 2.10.0-beta.15 → 2.10.0-beta.17

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.15",
3
+ "version": "2.10.0-beta.17",
4
4
  "description": "Web Component to preview GLTF models with Babylon.js",
5
5
  "author": "Alex Moreno Palacio <amoreno@preference.es>",
6
6
  "scripts": {
@@ -1,137 +1,208 @@
1
- // Inicializar IndexedDB
2
- export async function initDb(dbName, storeName) {
3
- return new Promise((resolve, reject) => {
4
- const request = indexedDB.open(dbName, 1);
1
+ // wwwroot/js/gltf-storage.js
5
2
 
6
- request.onerror = () => reject(request.error);
7
- request.onsuccess = () => {
8
- window.gltfDB = request.result;
9
- resolve();
10
- };
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;
11
7
 
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 });
18
- }
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) {
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
+ const store = upgradeDb.createObjectStore(storeName, { keyPath: 'id' });
27
+ store.createIndex('expirationTimeStamp', 'expirationTimeStamp', { unique: false });
19
28
  };
20
29
  });
21
30
  }
22
31
 
32
+ // --- public API -------------------------------------------------------------
33
+
34
+ // Inicializar IndexedDB y dejar el handle en PrefConfigurator.db (público)
35
+ export async function initDb(dbName, storeName) {
36
+ const db = await _openEnsuringStore(dbName, storeName);
37
+ // Close any previous handle to avoid versionchange blocking
38
+ try { PC.db?.close?.(); } catch { }
39
+ PC.db = db;
40
+
41
+ // If another tab upgrades, close gracefully so next call can re-init
42
+ db.onversionchange = () => {
43
+ try { db.close(); } catch { }
44
+ if (PC.db === db) PC.db = null;
45
+ console.warn("[PrefConfigurator] DB connection closed due to versionchange. Re-run initDb().");
46
+ };
47
+ }
48
+
23
49
  // Guardar modelo
24
50
  export async function saveModel(modelDataStr, storeName) {
25
51
  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);
52
+ let db;
53
+ try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
54
+
55
+ let modelData;
56
+ try { modelData = JSON.parse(modelDataStr); }
57
+ catch (e) { reject(new Error("saveModel: modelDataStr is not valid JSON")); return; }
31
58
 
32
59
  const dataToStore = {
33
60
  ...modelData,
34
61
  data: modelData.data,
35
- size: modelData.data.length,
36
- timestamp: new Date().toISOString(),
62
+ size: (modelData?.data?.length ?? 0)
37
63
  };
38
64
 
39
- const transaction = window.gltfDB.transaction([storeName], "readwrite");
40
- const store = transaction.objectStore(storeName);
41
- const request = store.put(dataToStore);
65
+ const tx = db.transaction([storeName], "readwrite");
66
+ const store = tx.objectStore(storeName);
67
+ const req = store.put(dataToStore);
42
68
 
43
- request.onerror = () => reject(request.error);
44
- request.onsuccess = () => resolve();
69
+ req.onerror = () => reject(req.error);
70
+ req.onsuccess = () => resolve();
45
71
  });
46
72
  }
47
73
 
48
74
  // Cargar modelo
49
75
  export function loadModel(modelId, storeName) {
50
76
  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");
77
+ let db;
78
+ try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
79
+
80
+ const tx = db.transaction([storeName], "readonly");
56
81
  const store = tx.objectStore(storeName);
57
82
  const req = store.get(modelId);
83
+
58
84
  req.onerror = () => reject(req.error);
59
85
  req.onsuccess = () => resolve(req.result ?? null);
60
86
  });
61
87
  }
62
88
 
89
+ // Descargar archivo desde base64
63
90
  export function downloadFileFromBytes(fileName, bytesBase64, mimeType) {
64
91
  const link = document.createElement("a");
65
92
  link.download = fileName;
66
93
  link.href = `data:${mimeType};base64,${bytesBase64}`;
67
94
  document.body.appendChild(link);
68
95
  link.click();
69
- document.body.removeChild(link);
96
+ link.remove();
70
97
  }
71
98
 
72
99
  // Obtener todos los modelos (solo metadata)
73
100
  export async function getAllModels(storeName) {
74
101
  return new Promise((resolve, reject) => {
75
- if (!window.gltfDB) {
76
- reject(new Error("Database not initialized"));
77
- return;
78
- }
102
+ let db;
103
+ try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
79
104
 
80
- const transaction = window.gltfDB.transaction([storeName], "readonly");
81
- const store = transaction.objectStore(storeName);
82
- const request = store.getAll();
105
+ const tx = db.transaction([storeName], "readonly");
106
+ const store = tx.objectStore(storeName);
107
+ const req = store.getAll();
83
108
 
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) => ({
109
+ req.onerror = () => reject(req.error);
110
+ req.onsuccess = () => {
111
+ const items = Array.isArray(req.result) ? req.result : [];
112
+ const results = items.map(item => ({
88
113
  id: item.id,
89
114
  metadata: item.metadata,
90
- timestamp: item.timestamp,
91
- size: item.size,
92
- type: item.type,
115
+ timeStamp: item.timeStamp,
116
+ size: item.size
93
117
  }));
118
+ // keep old behavior: return JSON string
94
119
  resolve(JSON.stringify(results));
95
120
  };
96
121
  });
97
122
  }
98
123
 
99
- // Eliminar modelo
124
+ // Eliminar modelo por id
100
125
  export async function deleteModel(modelId, storeName) {
101
126
  return new Promise((resolve, reject) => {
102
- if (!window.gltfDB) {
103
- reject(new Error("Database not initialized"));
104
- return;
105
- }
127
+ let db;
128
+ try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
106
129
 
107
- const transaction = window.gltfDB.transaction([storeName], "readwrite");
108
- const store = transaction.objectStore(storeName);
109
- const request = store.delete(modelId);
130
+ const tx = db.transaction([storeName], "readwrite");
131
+ const store = tx.objectStore(storeName);
132
+ const req = store.delete(modelId);
110
133
 
111
- request.onerror = () => reject(request.error);
112
- request.onsuccess = () => resolve();
134
+ req.onerror = () => reject(req.error);
135
+ req.onsuccess = () => resolve();
113
136
  });
114
137
  }
115
138
 
116
- // Limpiar toda la base de datos
139
+ // Limpiar toda la store
117
140
  export async function clearAll(storeName) {
118
141
  return new Promise((resolve, reject) => {
119
- if (!window.gltfDB) {
120
- reject(new Error("Database not initialized"));
121
- return;
122
- }
142
+ let db;
143
+ try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
144
+
145
+ const tx = db.transaction([storeName], "readwrite");
146
+ const store = tx.objectStore(storeName);
147
+ const req = store.clear();
148
+
149
+ req.onerror = () => reject(req.error);
150
+ req.onsuccess = () => resolve();
151
+ });
152
+ }
153
+
154
+ // Borrar modelos expirados usando el índice "expirationTimeStamp"
155
+ export async function cleanExpiredModels(storeName) {
156
+ return new Promise((resolve, reject) => {
157
+ let db;
158
+ try { db = _getDbOrThrow(); } catch (e) { reject(e); return; }
159
+
160
+ const tx = db.transaction([storeName], "readwrite");
161
+ const store = tx.objectStore(storeName);
162
+
163
+ const index = store.index("expirationTimeStamp");
164
+ const now = Date.now();
165
+ const range = IDBKeyRange.upperBound(now);
166
+ const cursorRequest = index.openCursor(range);
167
+
168
+ cursorRequest.onerror = () => reject(cursorRequest.error);
169
+ cursorRequest.onsuccess = (event) => {
170
+ const cursor = event.target.result;
171
+ if (cursor) {
172
+ cursor.delete();
173
+ cursor.continue();
174
+ }
175
+ };
176
+
177
+ tx.oncomplete = () => resolve();
178
+ tx.onerror = () => reject(tx.error);
179
+ });
180
+ }
123
181
 
124
- const transaction = window.gltfDB.transaction([storeName], "readwrite");
125
- const store = transaction.objectStore(storeName);
126
- const request = store.clear();
182
+ // Utilidades opcionales y visibles (por si las quieres usar en consola)
183
+ export function closeDb() {
184
+ if (PC.db) {
185
+ try { PC.db.close(); } catch { }
186
+ PC.db = null;
187
+ }
188
+ }
127
189
 
128
- request.onerror = () => reject(request.error);
129
- request.onsuccess = () => resolve();
190
+ export function deleteDatabase(dbName) {
191
+ return new Promise((resolve, reject) => {
192
+ // Close current handle if points to this DB
193
+ if (PC.db && PC.db.name === dbName) {
194
+ try { PC.db.close(); } catch { }
195
+ PC.db = null;
196
+ }
197
+ const req = indexedDB.deleteDatabase(dbName);
198
+ req.onblocked = () => reject(new Error("Delete blocked by another tab or worker"));
199
+ req.onerror = () => reject(req.error);
200
+ req.onsuccess = () => resolve();
130
201
  });
131
202
  }
132
203
 
204
+ // Attach a frozen, public API (no private state)
133
205
  (function attachPublicAPI(global) {
134
- const root = (global.PrefConfigurator ??= {});
135
206
  const storage = {
136
207
  initDb,
137
208
  saveModel,
@@ -139,10 +210,12 @@ export async function clearAll(storeName) {
139
210
  getAllModels,
140
211
  deleteModel,
141
212
  clearAll,
213
+ cleanExpiredModels,
142
214
  downloadFileFromBytes,
215
+ // extras
216
+ closeDb,
217
+ deleteDatabase,
143
218
  };
144
-
145
- // versionado del módulo público
146
- root.version = root.version ?? "1.0.0";
147
- root.storage = Object.freeze(storage);
148
- })(globalThis);
219
+ // free to inspect PC.db in devtools
220
+ global.PrefConfigurator.storage = Object.freeze(storage);
221
+ })(globalThis);
package/src/index.js CHANGED
@@ -60,7 +60,7 @@ class PrefViewer extends HTMLElement {
60
60
  storage: null,
61
61
  visible: false,
62
62
  size: null,
63
- timestamp: null,
63
+ timeStamp: null,
64
64
  changed: false,
65
65
  },
66
66
  environment: {
@@ -70,7 +70,7 @@ class PrefViewer extends HTMLElement {
70
70
  storage: null,
71
71
  visible: false,
72
72
  size: null,
73
- timestamp: null,
73
+ timeStamp: null,
74
74
  changed: false,
75
75
  },
76
76
  materials: {
@@ -80,7 +80,7 @@ class PrefViewer extends HTMLElement {
80
80
  show: true,
81
81
  visible: false,
82
82
  size: null,
83
- timestamp: null,
83
+ timeStamp: null,
84
84
  changed: false,
85
85
  },
86
86
  },
@@ -228,7 +228,7 @@ class PrefViewer extends HTMLElement {
228
228
  this.shadowRoot.append(this.#wrapper);
229
229
  }
230
230
 
231
- #setStatusSceneLoading(detail) {
231
+ #setStatusSceneLoading() {
232
232
  this.loaded = false;
233
233
  this.loading = true;
234
234
  if (this.hasAttribute("loaded")) {
@@ -240,14 +240,40 @@ class PrefViewer extends HTMLElement {
240
240
  bubbles: true,
241
241
  cancelable: false,
242
242
  composed: true,
243
- detail: detail,
244
243
  })
245
244
  );
246
245
  }
247
246
 
248
- #setStatusSceneLoaded(detail) {
247
+ #setStatusSceneLoaded() {
249
248
  this.loaded = true;
250
249
  this.loading = false;
250
+
251
+ const toLoadDetail = {
252
+ container_model: !!this.#data.containers.model.changed,
253
+ container_environment: !!this.#data.containers.environment.changed,
254
+ container_materials: !!this.#data.containers.materials.changed,
255
+ options_camera: !!this.#data.options.camera.changed,
256
+ options_inneWallMaterial: !!this.#data.options.materials.innerWall.changed,
257
+ options_outerWallMaterial: !!this.#data.options.materials.outerWall.changed,
258
+ options_innerFloorMaterial: !!this.#data.options.materials.innerFloor.changed,
259
+ options_outerFloorMaterial: !!this.#data.options.materials.outerFloor.changed,
260
+ };
261
+ const loadedDetail = {
262
+ container_model: !!this.#data.containers.model.changed?.success,
263
+ container_environment: !!this.#data.containers.environment.changed?.success,
264
+ container_materials: !!this.#data.containers.materials.changed?.success,
265
+ options_camera: !!this.#data.options.camera.changed?.success,
266
+ options_inneWallMaterial: !!this.#data.options.materials.innerWall.changed?.success,
267
+ options_outerWallMaterial: !!this.#data.options.materials.outerWall.changed?.success,
268
+ options_innerFloorMaterial: !!this.#data.options.materials.innerFloor.changed?.success,
269
+ options_outerFloorMaterial: !!this.#data.options.materials.outerFloor.changed?.success,
270
+ };
271
+
272
+ const detail = {
273
+ tried: toLoadDetail,
274
+ success: loadedDetail,
275
+ };
276
+
251
277
  if (this.hasAttribute("loading")) {
252
278
  this.removeAttribute("loading");
253
279
  }
@@ -262,18 +288,36 @@ class PrefViewer extends HTMLElement {
262
288
  );
263
289
  }
264
290
 
265
- #setStatusOptionsLoading(detail) {
291
+ #setStatusOptionsLoading() {
266
292
  this.dispatchEvent(
267
293
  new CustomEvent("options-loading", {
268
294
  bubbles: true,
269
295
  cancelable: false,
270
296
  composed: true,
271
- detail: detail,
272
297
  })
273
298
  );
274
299
  }
275
300
 
276
- #setStatusOptionsLoaded(detail) {
301
+ #setStatusOptionsLoaded() {
302
+
303
+ const toLoadDetail = {
304
+ inneWallMaterial: !!this.#data.options.materials.innerWall.changed,
305
+ outerWallMaterial: !!this.#data.options.materials.outerWall.changed,
306
+ innerFloorMaterial: !!this.#data.options.materials.innerFloor.changed,
307
+ outerFloorMaterial: !!this.#data.options.materials.outerFloor.changed,
308
+ };
309
+ const loadedDetail = {
310
+ inneWallMaterial: !!this.#data.options.materials.innerWall.changed?.success,
311
+ outerWallMaterial: !!this.#data.options.materials.outerWall.changed?.success,
312
+ innerFloorMaterial: !!this.#data.options.materials.innerFloor.changed?.success,
313
+ _outerFloorMaterial: !!this.#data.options.materials.outerFloor.changed?.success,
314
+ };
315
+
316
+ const detail = {
317
+ tried: toLoadDetail,
318
+ success: loadedDetail,
319
+ };
320
+
277
321
  this.dispatchEvent(
278
322
  new CustomEvent("options-loaded", {
279
323
  bubbles: true,
@@ -310,10 +354,14 @@ class PrefViewer extends HTMLElement {
310
354
  return someChanged;
311
355
  }
312
356
 
313
- #storeChangedFlagsForContainer(container) {
314
- container.timestamp = container.changed.timestamp;
315
- container.size = container.changed.size;
316
- container.changed.success = true;
357
+ #storeChangedFlagsForContainer(container, success) {
358
+ if (success) {
359
+ container.timeStamp = container.changed.timeStamp;
360
+ container.size = container.changed.size;
361
+ } else {
362
+ container.source = container.changed.source;
363
+ }
364
+ container.changed.success = success;
317
365
  }
318
366
 
319
367
  #resetChangedFlags() {
@@ -459,8 +507,8 @@ class PrefViewer extends HTMLElement {
459
507
  xhr.onload = () => {
460
508
  if (xhr.status === 200) {
461
509
  const size = parseInt(xhr.getResponseHeader("Content-Length"));
462
- const timestamp = new Date(xhr.getResponseHeader("Last-Modified")).toISOString();
463
- resolve([size, timestamp]);
510
+ const timeStamp = new Date(xhr.getResponseHeader("Last-Modified")).toISOString();
511
+ resolve([size, timeStamp]);
464
512
  } else {
465
513
  resolve([0, null]);
466
514
  }
@@ -530,10 +578,10 @@ class PrefViewer extends HTMLElement {
530
578
  }
531
579
 
532
580
  const containers = [];
533
- if (this.#data.containers.model.assetContainer && (this.#data.containers.model.changed || optionMaterial.changed)) {
581
+ if (this.#data.containers.model.assetContainer && (this.#data.containers.model.changed || this.#data.containers.materials.changed || optionMaterial.changed)) {
534
582
  containers.push(this.#data.containers.model.assetContainer);
535
583
  }
536
- if (this.#data.containers.environment.assetContainer && (this.#data.containers.environment.changed || optionMaterial.changed)) {
584
+ if (this.#data.containers.environment.assetContainer && (this.#data.containers.environment.changed || this.#data.containers.materials.changed || optionMaterial.changed)) {
537
585
  containers.push(this.#data.containers.environment.assetContainer);
538
586
  }
539
587
  if (containers.length === 0) {
@@ -635,10 +683,10 @@ class PrefViewer extends HTMLElement {
635
683
  await this.#initStorage(storage.db, storage.table);
636
684
  const object = await loadModel(storage.id, storage.table);
637
685
  source = object.data;
638
- if (object.timestamp === container.timestamp) {
686
+ if (object.timeStamp === container.timeStamp) {
639
687
  return false;
640
688
  } else {
641
- container.changed = { timestamp: object.timestamp, size: object.size, success: false };
689
+ Object.assign(container.changed, { timeStamp: object.timeStamp, size: object.size, success: false });
642
690
  }
643
691
  }
644
692
 
@@ -654,20 +702,20 @@ class PrefViewer extends HTMLElement {
654
702
  type: blob.type,
655
703
  });
656
704
  if (!container.changed) {
657
- if (container.timestamp === null && container.size === size) {
705
+ if (container.timeStamp === null && container.size === size) {
658
706
  return false;
659
707
  } else {
660
- container.changed = { timestamp: null, size: size, success: false };
708
+ Object.assign(container.changed, { timeStamp: null, size: size, success: false });
661
709
  }
662
710
  }
663
711
  } else {
664
712
  const extMatch = source.match(/\.(gltf|glb)(\?|#|$)/i);
665
713
  extension = extMatch ? `.${extMatch[1].toLowerCase()}` : ".gltf";
666
714
  const [fileSize, fileTimestamp ] = await this.#getServerFileDataHeader(source);
667
- if (container.size === fileSize && container.timestamp === fileTimestamp) {
715
+ if (container.size === fileSize && container.timeStamp === fileTimestamp) {
668
716
  return false;
669
717
  } else {
670
- container.changed = { timestamp: fileTimestamp, size: fileSize, success: false };
718
+ Object.assign(container.changed, { timeStamp: fileTimestamp, size: fileSize, success: false });
671
719
  }
672
720
  }
673
721
 
@@ -712,42 +760,31 @@ class PrefViewer extends HTMLElement {
712
760
 
713
761
  if (modelContainer.status === "fulfilled" && modelContainer.value) {
714
762
  this.#replaceContainer(this.#data.containers.model, modelContainer.value);
715
- this.#storeChangedFlagsForContainer(this.#data.containers.model);
763
+ this.#storeChangedFlagsForContainer(this.#data.containers.model, true);
716
764
  } else {
717
765
  this.#data.containers.model.show ? this.#addContainer(this.#data.containers.model) : this.#removeContainer(this.#data.containers.model);
766
+ this.#storeChangedFlagsForContainer(this.#data.containers.model, false);
718
767
  }
719
768
 
720
769
  if (environmentContainer.status === "fulfilled" && environmentContainer.value) {
721
770
  this.#replaceContainer(this.#data.containers.environment, environmentContainer.value);
722
- this.#storeChangedFlagsForContainer(this.#data.containers.environment);
771
+ this.#storeChangedFlagsForContainer(this.#data.containers.environment, true);
723
772
  } else {
724
773
  this.#data.containers.environment.show ? this.#addContainer(this.#data.containers.environment) : this.#removeContainer(this.#data.containers.environment);
774
+ this.#storeChangedFlagsForContainer(this.#data.containers.environment, false);
725
775
  }
726
776
 
727
777
  if (materialsContainer.status === "fulfilled" && materialsContainer.value) {
728
778
  this.#replaceContainer(this.#data.containers.materials, materialsContainer.value);
729
- this.#storeChangedFlagsForContainer(this.#data.containers.materials);
779
+ this.#storeChangedFlagsForContainer(this.#data.containers.materials, true);
780
+ } else {
781
+ this.#storeChangedFlagsForContainer(this.#data.containers.materials, false);
730
782
  }
731
783
 
732
784
  this.#setOptionsMaterials();
733
785
  this.#setOptionsCamera();
734
786
  this.#setVisibilityOfWallAndFloorInModel();
735
-
736
- const loadedDetail = {
737
- model: !!this.#data.containers.model.changed?.success,
738
- environment: !!this.#data.containers.environment.changed?.success,
739
- materials: !!this.#data.containers.materials.changed?.success,
740
- options: {
741
- camera: !!this.#data.options.camera.changed?.success,
742
- inneWallMaterial: !!this.#data.options.materials.innerWall.changed?.success,
743
- outerWallMaterial: !!this.#data.options.materials.outerWall.changed?.success,
744
- innerFloorMaterial: !!this.#data.options.materials.innerFloor.changed?.success,
745
- outerFloorMaterial: !!this.#data.options.materials.outerFloor.changed?.success,
746
- },
747
- };
748
-
749
- this.#setStatusSceneLoaded(loadedDetail);
750
-
787
+ this.#setStatusSceneLoaded();
751
788
  this.#resetChangedFlags();
752
789
  })
753
790
  .catch((error) => {
@@ -772,10 +809,13 @@ class PrefViewer extends HTMLElement {
772
809
  }
773
810
 
774
811
  // Containers
812
+ this.#data.containers.model.changed = { storage: this.#data.containers.model.storage || null };
775
813
  this.#data.containers.model.storage = config.model?.storage || null;
776
814
  this.#data.containers.model.show = config.model?.visible !== undefined ? config.model.visible : this.#data.containers.model.show;
815
+ this.#data.containers.environment.changed = { storage: this.#data.containers.environment.storage || null };
777
816
  this.#data.containers.environment.storage = config.scene?.storage || null;
778
817
  this.#data.containers.environment.show = config.scene?.visible !== undefined ? config.scene.visible : this.#data.containers.environment.show;
818
+ this.#data.containers.materials.changed = { storage: this.#data.containers.materials.storage || null };
779
819
  this.#data.containers.materials.storage = config.materials?.storage || null;
780
820
 
781
821
  // Options
@@ -792,35 +832,17 @@ class PrefViewer extends HTMLElement {
792
832
  return false;
793
833
  }
794
834
 
795
- const cameraChanged = this.#checkCameraChanged(options);
796
- const materialsChanged = this.#checkMaterialsChanged(options);
797
-
798
- const loadingDetail = {
799
- camera: !!this.#data.options.camera.changed,
800
- inneWallMaterial: !!this.#data.options.materials.innerWall.changed,
801
- outerWallMaterial: !!this.#data.options.materials.outerWall.changed,
802
- innerFloorMaterial: !!this.#data.options.materials.innerFloor.changed,
803
- outerFloorMaterial: !!this.#data.options.materials.outerFloor.changed,
804
- };
805
- this.#setStatusOptionsLoading(loadingDetail);
835
+ this.#setStatusOptionsLoading();
806
836
 
807
837
  let someSetted = false;
808
- if (cameraChanged) {
838
+ if (this.#checkCameraChanged(options)) {
809
839
  someSetted = someSetted || this.#setOptionsCamera();
810
840
  }
811
- if (materialsChanged) {
841
+ if (this.#checkMaterialsChanged(options)) {
812
842
  someSetted = someSetted || this.#setOptionsMaterials();
813
843
  }
814
844
 
815
- const loadedDetail = {
816
- camera: !!this.#data.options.camera.changed?.success,
817
- inneWallMaterial: !!this.#data.options.materials.innerWall.changed?.success,
818
- outerWallMaterial: !!this.#data.options.materials.outerWall.changed?.success,
819
- innerFloorMaterial: !!this.#data.options.materials.innerFloor.changed?.success,
820
- outerFloorMaterial: !!this.#data.options.materials.outerFloor.changed?.success,
821
- };
822
- this.#setStatusOptionsLoaded(loadedDetail);
823
-
845
+ this.#setStatusOptionsLoaded();
824
846
  this.#resetChangedFlags();
825
847
 
826
848
  return someSetted;