@preference-sl/pref-viewer 2.13.0-beta.2 → 2.13.0-beta.21
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/Readme.md +1 -1
- package/package.json +5 -5
- package/src/babylonjs-animation-controller.js +187 -76
- package/src/babylonjs-animation-opening.js +58 -2
- package/src/babylonjs-controller.js +1008 -359
- package/src/file-storage.js +405 -24
- package/src/gltf-resolver.js +65 -9
- package/src/gltf-storage.js +47 -35
- package/src/localization/i18n.js +1 -1
- package/src/localization/translations.js +3 -3
- package/src/pref-viewer-3d-data.js +102 -52
- package/src/pref-viewer-3d.js +71 -15
- package/src/pref-viewer-menu-3d.js +44 -3
- package/src/pref-viewer.js +134 -17
- package/src/styles.js +21 -5
package/src/pref-viewer-3d.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CameraData, ContainerData, MaterialData, IBLData } from "./pref-viewer-3d-data.js";
|
|
2
2
|
import BabylonJSController from "./babylonjs-controller.js";
|
|
3
3
|
import { PrefViewer3DStyles } from "./styles.js";
|
|
4
|
-
import
|
|
4
|
+
import FileStorage from "./file-storage.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* PrefViewer3D - Custom Web Component for interactive 3D visualization and configuration.
|
|
@@ -75,6 +75,7 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
75
75
|
#isInitialized = false;
|
|
76
76
|
#isLoaded = false;
|
|
77
77
|
#isVisible = false;
|
|
78
|
+
#lastLoadTimestamp = undefined;
|
|
78
79
|
|
|
79
80
|
#wrapper = null;
|
|
80
81
|
#canvas = null;
|
|
@@ -162,7 +163,7 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
162
163
|
*/
|
|
163
164
|
disconnectedCallback() {
|
|
164
165
|
if (this.#babylonJSController) {
|
|
165
|
-
this.#babylonJSController.disable();
|
|
166
|
+
void this.#babylonJSController.disable();
|
|
166
167
|
}
|
|
167
168
|
}
|
|
168
169
|
|
|
@@ -230,6 +231,27 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
230
231
|
this.#babylonJSController.enable();
|
|
231
232
|
}
|
|
232
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Determines whether the next 3D load should bypass cache checks.
|
|
236
|
+
* @private
|
|
237
|
+
* @param {object} config - Incoming config payload.
|
|
238
|
+
* @returns {boolean} True when force reload should be enabled.
|
|
239
|
+
*/
|
|
240
|
+
#shouldForceReload(config) {
|
|
241
|
+
if (!config || typeof config !== "object") {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
if (config.forceReload === true) {
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
if (config.timestamp === undefined || config.timestamp === null) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
const changed = config.timestamp !== this.#lastLoadTimestamp;
|
|
251
|
+
this.#lastLoadTimestamp = config.timestamp;
|
|
252
|
+
return changed;
|
|
253
|
+
}
|
|
254
|
+
|
|
233
255
|
/**
|
|
234
256
|
* Resets update flags for all containers and material/camera options after loading or setting options.
|
|
235
257
|
* @private
|
|
@@ -239,7 +261,6 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
239
261
|
Object.values(this.#data.containers).forEach((container) => container.reset());
|
|
240
262
|
Object.values(this.#data.options.materials).forEach((material) => material.reset());
|
|
241
263
|
this.#data.options.camera.reset();
|
|
242
|
-
this.#data.options.ibl.reset();
|
|
243
264
|
}
|
|
244
265
|
|
|
245
266
|
/**
|
|
@@ -324,6 +345,10 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
324
345
|
/**
|
|
325
346
|
* Resolves incoming IBL settings (HDR URL, timestamp, intensity, shadows) and marks the option as pending when changed.
|
|
326
347
|
* Fetches signed URLs/time stamps when storage keys are provided so the Babylon controller can reload the environment map.
|
|
348
|
+
* If `options.ibl` exists but `options.ibl.url` is undefined (missing), the current IBL URL is kept and not marked as pending.
|
|
349
|
+
* This allows updating other IBL properties (such as intensity or shadows) without requiring a new URL.
|
|
350
|
+
* If `options.ibl.url` is explicitly provided as `null` (or resolves to an unavailable URL), it is treated as a request to
|
|
351
|
+
* clear the current IBL environment, and the IBL state is reset so Babylon can remove the environment map.
|
|
327
352
|
* @private
|
|
328
353
|
* @param {object} options - Options payload that may contain an `ibl` block with url, intensity, or shadow flags.
|
|
329
354
|
* @returns {Promise<boolean>} Resolves to true when any IBL property differs from the cached state, otherwise false.
|
|
@@ -335,18 +360,25 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
335
360
|
const iblState = this.#data.options.ibl;
|
|
336
361
|
|
|
337
362
|
let url = undefined;
|
|
363
|
+
let cachedUrl = undefined;
|
|
338
364
|
let timeStamp = undefined;
|
|
339
365
|
let shadows = undefined;
|
|
340
366
|
let intensity = undefined;
|
|
341
367
|
|
|
342
|
-
if (options.ibl.url) {
|
|
343
|
-
url = options.ibl.url;
|
|
368
|
+
if (typeof options.ibl.url === "string" && options.ibl.url.length > 0) {
|
|
344
369
|
const fileStorage = new FileStorage("PrefViewer", "Files");
|
|
345
370
|
const newURL = await fileStorage.getURL(options.ibl.url);
|
|
346
371
|
if (newURL) {
|
|
347
|
-
url =
|
|
372
|
+
url = options.ibl.url;
|
|
373
|
+
cachedUrl = newURL;
|
|
348
374
|
timeStamp = await fileStorage.getTimeStamp(options.ibl.url);
|
|
375
|
+
} else {
|
|
376
|
+
url = null;
|
|
377
|
+
cachedUrl = null;
|
|
349
378
|
}
|
|
379
|
+
} else if (options.ibl.url === null) {
|
|
380
|
+
url = null;
|
|
381
|
+
cachedUrl = null;
|
|
350
382
|
}
|
|
351
383
|
if (options.ibl.shadows !== undefined) {
|
|
352
384
|
shadows = options.ibl.shadows;
|
|
@@ -358,10 +390,15 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
358
390
|
const needUpdate = url !== undefined && url !== iblState.url ||
|
|
359
391
|
timeStamp !== undefined && timeStamp !== iblState.timeStamp ||
|
|
360
392
|
shadows !== undefined && shadows !== iblState.shadows ||
|
|
361
|
-
|
|
393
|
+
shadows === undefined && iblState.shadows !== iblState.defaultShadows ||
|
|
394
|
+
intensity !== undefined && intensity !== iblState.intensity ||
|
|
395
|
+
intensity === undefined && iblState.intensity !== iblState.defaultIntensity;
|
|
362
396
|
if (needUpdate) {
|
|
363
|
-
|
|
364
|
-
|
|
397
|
+
if (url === null || cachedUrl === null) {
|
|
398
|
+
iblState.reset();
|
|
399
|
+
} else {
|
|
400
|
+
iblState.setValues(url, cachedUrl, intensity, shadows, timeStamp);
|
|
401
|
+
}
|
|
365
402
|
}
|
|
366
403
|
|
|
367
404
|
return needUpdate;
|
|
@@ -543,6 +580,7 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
543
580
|
}
|
|
544
581
|
|
|
545
582
|
this.#onLoading();
|
|
583
|
+
const forceReload = this.#shouldForceReload(config);
|
|
546
584
|
|
|
547
585
|
// Containers
|
|
548
586
|
this.#checkNeedToUpdateContainers(config);
|
|
@@ -551,10 +589,10 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
551
589
|
if (config.options) {
|
|
552
590
|
this.#checkNeedToUpdateCamera(config.options);
|
|
553
591
|
this.#checkNeedToUpdateMaterials(config.options);
|
|
554
|
-
this.#checkNeedToUpdateIBL(config.options);
|
|
592
|
+
await this.#checkNeedToUpdateIBL(config.options);
|
|
555
593
|
}
|
|
556
594
|
|
|
557
|
-
const loadDetail = await this.#babylonJSController.load();
|
|
595
|
+
const loadDetail = await this.#babylonJSController.load(forceReload);
|
|
558
596
|
|
|
559
597
|
return { ...loadDetail, load: this.#onLoaded() };
|
|
560
598
|
}
|
|
@@ -564,9 +602,9 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
564
602
|
* Updates internal states, triggers option setting events, and returns the result.
|
|
565
603
|
* @public
|
|
566
604
|
* @param {object} options - Options object containing camera and material settings.
|
|
567
|
-
* @returns {object} Object containing success status and details of set options.
|
|
605
|
+
* @returns {Promise<object>} Object containing success status and details of set options.
|
|
568
606
|
*/
|
|
569
|
-
setOptions(options) {
|
|
607
|
+
async setOptions(options) {
|
|
570
608
|
if (!this.#babylonJSController) {
|
|
571
609
|
return;
|
|
572
610
|
}
|
|
@@ -580,8 +618,9 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
580
618
|
if (this.#checkNeedToUpdateMaterials(options)) {
|
|
581
619
|
someSetted = someSetted || this.#babylonJSController.setMaterialOptions();
|
|
582
620
|
}
|
|
583
|
-
|
|
584
|
-
|
|
621
|
+
const needUpdateIBL = await this.#checkNeedToUpdateIBL(options);
|
|
622
|
+
if (needUpdateIBL) {
|
|
623
|
+
someSetted = someSetted || (await this.#babylonJSController.setIBLOptions());
|
|
585
624
|
}
|
|
586
625
|
const detail = this.#onSetOptions();
|
|
587
626
|
return { success: someSetted, detail: detail };
|
|
@@ -755,6 +794,23 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
755
794
|
return this.#babylonJSController.getRenderSettings();
|
|
756
795
|
}
|
|
757
796
|
|
|
797
|
+
/**
|
|
798
|
+
* Reports whether an IBL environment map is currently available.
|
|
799
|
+
* @public
|
|
800
|
+
* @returns {boolean} True when a validated IBL texture exists or a pending cached URL is available.
|
|
801
|
+
*/
|
|
802
|
+
isIBLAvailable() {
|
|
803
|
+
const ibl = this.#data?.options?.ibl;
|
|
804
|
+
if (!ibl) {
|
|
805
|
+
return false;
|
|
806
|
+
}
|
|
807
|
+
if (ibl.valid === true) {
|
|
808
|
+
return true;
|
|
809
|
+
}
|
|
810
|
+
const cachedUrl = ibl.cachedUrl;
|
|
811
|
+
return typeof cachedUrl === "string" ? cachedUrl.length > 0 : Boolean(cachedUrl);
|
|
812
|
+
}
|
|
813
|
+
|
|
758
814
|
/**
|
|
759
815
|
* Applies render settings that require reloading the Babylon.js scene.
|
|
760
816
|
* @public
|
|
@@ -9,7 +9,8 @@ import { DEFAULT_LOCALE, resolveLocale, translate } from "./localization/i18n.js
|
|
|
9
9
|
* - Builds an accessible hover/focus-activated panel with switches for AA, SSAO, IBL, and dynamic shadows.
|
|
10
10
|
* - Caches translated copy in `#texts` and listens for culture changes via the `culture` attribute or `setCulture()`.
|
|
11
11
|
* - Tracks applied vs. draft render settings so pending diffs, button enablement, and status text stay in sync.
|
|
12
|
-
* -
|
|
12
|
+
* - Supports per-switch availability (enabled/disabled), preventing interaction and pending diffs for unavailable toggles.
|
|
13
|
+
* - Emits `pref-viewer-menu-3d-apply` whenever the user confirms toggles, allowing PrefViewer/BabylonJSController to persist.
|
|
13
14
|
* - Shows transient status/error messages and per-switch pending states while operations complete.
|
|
14
15
|
*
|
|
15
16
|
* Public API:
|
|
@@ -17,6 +18,7 @@ import { DEFAULT_LOCALE, resolveLocale, translate } from "./localization/i18n.js
|
|
|
17
18
|
* - `setApplying(isApplying, hasError?)`: locks the UI and optionally displays errors while async updates run.
|
|
18
19
|
* - `setViewerHover(isHovering)`: opens/closes the panel based on viewer hover state.
|
|
19
20
|
* - `setEnabled(isEnabled)`: hides the menu in 2D mode and clears hover data.
|
|
21
|
+
* - `setSwitchEnabled(settingKey, isEnabled)`: enables/disables a concrete toggle (e.g. IBL) based on runtime availability.
|
|
20
22
|
* - `setCulture(cultureId)`: forces a locale change and re-renders copy from the i18n layer.
|
|
21
23
|
*
|
|
22
24
|
* Lifecycle & Integration:
|
|
@@ -52,6 +54,7 @@ export default class PrefViewerMenu3D extends HTMLElement {
|
|
|
52
54
|
#MENU_SWITCHES = Object.keys(this.#DEFAULT_RENDER_SETTINGS);
|
|
53
55
|
#appliedSettings = { ...this.#DEFAULT_RENDER_SETTINGS };
|
|
54
56
|
#draftSettings = { ...this.#DEFAULT_RENDER_SETTINGS };
|
|
57
|
+
#switchAvailability = {};
|
|
55
58
|
#statusTimeout = null;
|
|
56
59
|
#isApplying = false;
|
|
57
60
|
#isEnabled = true;
|
|
@@ -352,6 +355,9 @@ export default class PrefViewerMenu3D extends HTMLElement {
|
|
|
352
355
|
* @returns {void}
|
|
353
356
|
*/
|
|
354
357
|
#handleSwitchChange(event) {
|
|
358
|
+
if (event.currentTarget?.disabled) {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
355
361
|
const key = event.currentTarget?.dataset?.setting;
|
|
356
362
|
if (!key) {
|
|
357
363
|
return;
|
|
@@ -369,8 +375,15 @@ export default class PrefViewerMenu3D extends HTMLElement {
|
|
|
369
375
|
if (this.#isApplying || !this.#hasPendingChanges()) {
|
|
370
376
|
return;
|
|
371
377
|
}
|
|
378
|
+
|
|
372
379
|
const detail = { settings: { ...this.#draftSettings } };
|
|
373
|
-
|
|
380
|
+
const customEventOptions = {
|
|
381
|
+
bubbles: true,
|
|
382
|
+
cancelable: true,
|
|
383
|
+
composed: true,
|
|
384
|
+
detail: detail,
|
|
385
|
+
};
|
|
386
|
+
this.dispatchEvent(new CustomEvent("pref-viewer-menu-3d-apply", customEventOptions));
|
|
374
387
|
}
|
|
375
388
|
|
|
376
389
|
/**
|
|
@@ -381,7 +394,10 @@ export default class PrefViewerMenu3D extends HTMLElement {
|
|
|
381
394
|
#updateSwitches() {
|
|
382
395
|
Object.entries(this.#elements.switches).forEach(([key, input]) => {
|
|
383
396
|
if (input) {
|
|
397
|
+
const isEnabled = this.#switchAvailability[key] !== false;
|
|
384
398
|
input.checked = Boolean(this.#draftSettings[key]);
|
|
399
|
+
input.disabled = !isEnabled;
|
|
400
|
+
this.#elements.switchWrappers[key]?.toggleAttribute("data-disabled", !isEnabled);
|
|
385
401
|
}
|
|
386
402
|
});
|
|
387
403
|
}
|
|
@@ -409,7 +425,12 @@ export default class PrefViewerMenu3D extends HTMLElement {
|
|
|
409
425
|
* @returns {string[]} Keys currently pending application.
|
|
410
426
|
*/
|
|
411
427
|
#getPendingKeys() {
|
|
412
|
-
return Object.keys(this.#DEFAULT_RENDER_SETTINGS).filter((key) =>
|
|
428
|
+
return Object.keys(this.#DEFAULT_RENDER_SETTINGS).filter((key) => {
|
|
429
|
+
if (this.#elements.switches[key]?.disabled) {
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
return this.#draftSettings[key] !== this.#appliedSettings[key];
|
|
433
|
+
});
|
|
413
434
|
}
|
|
414
435
|
|
|
415
436
|
/**
|
|
@@ -554,4 +575,24 @@ export default class PrefViewerMenu3D extends HTMLElement {
|
|
|
554
575
|
this.#closeMenu();
|
|
555
576
|
}
|
|
556
577
|
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Enables or disables a specific render-setting switch in the menu.
|
|
581
|
+
* @public
|
|
582
|
+
* @param {string} settingKey - Switch key (e.g. "iblEnabled").
|
|
583
|
+
* @param {boolean} [isEnabled=true] - True to allow interaction, false to disable it.
|
|
584
|
+
* @returns {void}
|
|
585
|
+
*/
|
|
586
|
+
setSwitchEnabled(settingKey, isEnabled = true) {
|
|
587
|
+
if (!settingKey || !this.#MENU_SWITCHES.includes(settingKey)) {
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
const enabled = Boolean(isEnabled);
|
|
591
|
+
this.#switchAvailability[settingKey] = enabled;
|
|
592
|
+
if (!enabled) {
|
|
593
|
+
this.#draftSettings[settingKey] = this.#appliedSettings[settingKey];
|
|
594
|
+
}
|
|
595
|
+
this.#updateSwitches();
|
|
596
|
+
this.#updatePendingState();
|
|
597
|
+
}
|
|
557
598
|
}
|
package/src/pref-viewer.js
CHANGED
|
@@ -81,6 +81,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
81
81
|
onViewerHoverStart: null,
|
|
82
82
|
onViewerHoverEnd: null,
|
|
83
83
|
on3DSceneLoaded: null,
|
|
84
|
+
on3DSceneError: null,
|
|
84
85
|
};
|
|
85
86
|
|
|
86
87
|
/**
|
|
@@ -136,6 +137,9 @@ export default class PrefViewer extends HTMLElement {
|
|
|
136
137
|
this.loadMaterials(value);
|
|
137
138
|
break;
|
|
138
139
|
case "mode":
|
|
140
|
+
if (typeof value !== "string") {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
139
143
|
if (_old === value || value.toLowerCase() === this.#mode) {
|
|
140
144
|
return;
|
|
141
145
|
}
|
|
@@ -151,6 +155,9 @@ export default class PrefViewer extends HTMLElement {
|
|
|
151
155
|
this.setOptions(value);
|
|
152
156
|
break;
|
|
153
157
|
case "show-model":
|
|
158
|
+
if (typeof value !== "string") {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
154
161
|
if (_old === value) {
|
|
155
162
|
return;
|
|
156
163
|
}
|
|
@@ -158,6 +165,9 @@ export default class PrefViewer extends HTMLElement {
|
|
|
158
165
|
showModel ? this.showModel() : this.hideModel();
|
|
159
166
|
break;
|
|
160
167
|
case "show-scene":
|
|
168
|
+
if (typeof value !== "string") {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
161
171
|
if (_old === value) {
|
|
162
172
|
return;
|
|
163
173
|
}
|
|
@@ -213,10 +223,11 @@ export default class PrefViewer extends HTMLElement {
|
|
|
213
223
|
}
|
|
214
224
|
if (this.#component3D) {
|
|
215
225
|
this.#component3D.removeEventListener("scene-loaded", this.#handlers.on3DSceneLoaded);
|
|
226
|
+
this.#component3D.removeEventListener("scene-error", this.#handlers.on3DSceneError);
|
|
216
227
|
this.#component3D.remove();
|
|
217
228
|
}
|
|
218
229
|
if (this.#menu3D) {
|
|
219
|
-
this.#menu3D.removeEventListener("pref-viewer-menu-apply", this.#handlers.onMenuApply);
|
|
230
|
+
this.#menu3D.removeEventListener("pref-viewer-menu-3d-apply", this.#handlers.onMenuApply);
|
|
220
231
|
this.#menu3D.remove();
|
|
221
232
|
this.#menu3D = null;
|
|
222
233
|
}
|
|
@@ -249,7 +260,11 @@ export default class PrefViewer extends HTMLElement {
|
|
|
249
260
|
this.#menu3DSyncSettings();
|
|
250
261
|
this.#menu3D?.setApplying(false);
|
|
251
262
|
};
|
|
263
|
+
this.#handlers.on3DSceneError = () => {
|
|
264
|
+
this.#menu3D?.setApplying(false, true);
|
|
265
|
+
};
|
|
252
266
|
this.#component3D.addEventListener("scene-loaded", this.#handlers.on3DSceneLoaded);
|
|
267
|
+
this.#component3D.addEventListener("scene-error", this.#handlers.on3DSceneError);
|
|
253
268
|
this.#wrapper.appendChild(this.#component3D);
|
|
254
269
|
}
|
|
255
270
|
|
|
@@ -264,7 +279,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
264
279
|
return;
|
|
265
280
|
}
|
|
266
281
|
if (this.#menu3D) {
|
|
267
|
-
this.#menu3D.removeEventListener("pref-viewer-menu-apply", this.#handlers.onMenuApply);
|
|
282
|
+
this.#menu3D.removeEventListener("pref-viewer-menu-3d-apply", this.#handlers.onMenuApply);
|
|
268
283
|
this.#menu3D.remove();
|
|
269
284
|
}
|
|
270
285
|
this.#menu3D = document.createElement("pref-viewer-menu-3d");
|
|
@@ -280,12 +295,13 @@ export default class PrefViewer extends HTMLElement {
|
|
|
280
295
|
this.#handlers.onViewerHoverStart = () => this.#menu3D.setViewerHover(true);
|
|
281
296
|
this.#handlers.onViewerHoverEnd = () => this.#menu3D.setViewerHover(false);
|
|
282
297
|
|
|
283
|
-
this.#menu3D.addEventListener("pref-viewer-menu-apply", this.#handlers.onMenuApply);
|
|
298
|
+
this.#menu3D.addEventListener("pref-viewer-menu-3d-apply", this.#handlers.onMenuApply);
|
|
284
299
|
this.#wrapper.addEventListener("mouseenter", this.#handlers.onViewerHoverStart);
|
|
285
300
|
this.#wrapper.addEventListener("mouseleave", this.#handlers.onViewerHoverEnd);
|
|
286
301
|
|
|
287
302
|
this.#wrapper.appendChild(this.#menu3D);
|
|
288
303
|
this.#menu3DSyncSettings();
|
|
304
|
+
this.#menu3DUpdateSwitchAvailability();
|
|
289
305
|
this.#menu3DUpdateAvailability();
|
|
290
306
|
}
|
|
291
307
|
|
|
@@ -372,6 +388,19 @@ export default class PrefViewer extends HTMLElement {
|
|
|
372
388
|
this.#menu3D.setEnabled?.(isMode3D);
|
|
373
389
|
}
|
|
374
390
|
|
|
391
|
+
/**
|
|
392
|
+
* Enables/disables menu switches according to current 3D option availability.
|
|
393
|
+
* @private
|
|
394
|
+
* @returns {void}
|
|
395
|
+
*/
|
|
396
|
+
#menu3DUpdateSwitchAvailability() {
|
|
397
|
+
if (!this.#menu3D) {
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
const iblAvailable = this.#component3D?.isIBLAvailable?.() === true;
|
|
401
|
+
this.#menu3D.setSwitchEnabled?.("iblEnabled", iblAvailable);
|
|
402
|
+
}
|
|
403
|
+
|
|
375
404
|
/**
|
|
376
405
|
* Applies render toggles via PrefViewer3D, showing progress in the menu and resyncing on completion.
|
|
377
406
|
* When no changes occur, the menu is simply refreshed to match the controller snapshot.
|
|
@@ -440,7 +469,14 @@ export default class PrefViewer extends HTMLElement {
|
|
|
440
469
|
* @returns {void}
|
|
441
470
|
*/
|
|
442
471
|
#addTaskToQueue(value, type) {
|
|
443
|
-
|
|
472
|
+
const task = new PrefViewerTask(value, type);
|
|
473
|
+
if (
|
|
474
|
+
task.type === PrefViewerTask.Types.Options ||
|
|
475
|
+
task.type === PrefViewerTask.Types.Drawing
|
|
476
|
+
) {
|
|
477
|
+
this.#taskQueue = this.#taskQueue.filter((queuedTask) => queuedTask.type !== task.type);
|
|
478
|
+
}
|
|
479
|
+
this.#taskQueue.push(task);
|
|
444
480
|
if (this.#isInitialized && !this.#isLoading) {
|
|
445
481
|
this.#processNextTask();
|
|
446
482
|
}
|
|
@@ -520,6 +556,9 @@ export default class PrefViewer extends HTMLElement {
|
|
|
520
556
|
#on3DLoaded(detail) {
|
|
521
557
|
this.#isLoaded = true;
|
|
522
558
|
this.#isLoading = false;
|
|
559
|
+
|
|
560
|
+
this.#menu3DSyncSettings();
|
|
561
|
+
this.#menu3DUpdateSwitchAvailability();
|
|
523
562
|
|
|
524
563
|
this.removeAttribute("loading-3d");
|
|
525
564
|
this.setAttribute("loaded-3d", "");
|
|
@@ -535,6 +574,32 @@ export default class PrefViewer extends HTMLElement {
|
|
|
535
574
|
this.dispatchEvent(new CustomEvent("scene-loaded", customEventOptions));
|
|
536
575
|
}
|
|
537
576
|
|
|
577
|
+
/**
|
|
578
|
+
* Handles 3D load errors.
|
|
579
|
+
* Clears loading state and dispatches a "scene-error" event.
|
|
580
|
+
* @private
|
|
581
|
+
* @param {object} [detail] - Optional details about the failure.
|
|
582
|
+
* @returns {void}
|
|
583
|
+
*/
|
|
584
|
+
#on3DError(detail) {
|
|
585
|
+
this.#isLoaded = false;
|
|
586
|
+
this.#isLoading = false;
|
|
587
|
+
this.#menu3D?.setApplying(false, true);
|
|
588
|
+
|
|
589
|
+
this.removeAttribute("loading-3d");
|
|
590
|
+
this.removeAttribute("loaded-3d");
|
|
591
|
+
|
|
592
|
+
const customEventOptions = {
|
|
593
|
+
bubbles: true,
|
|
594
|
+
cancelable: true,
|
|
595
|
+
composed: true,
|
|
596
|
+
};
|
|
597
|
+
if (detail) {
|
|
598
|
+
customEventOptions.detail = detail;
|
|
599
|
+
}
|
|
600
|
+
this.dispatchEvent(new CustomEvent("scene-error", customEventOptions));
|
|
601
|
+
}
|
|
602
|
+
|
|
538
603
|
/**
|
|
539
604
|
* Handles the start of a 2D loading operation.
|
|
540
605
|
* Updates loading state, sets attributes, and dispatches a "drawing-loading" event.
|
|
@@ -581,6 +646,31 @@ export default class PrefViewer extends HTMLElement {
|
|
|
581
646
|
this.dispatchEvent(new CustomEvent("drawing-loaded", customEventOptions));
|
|
582
647
|
}
|
|
583
648
|
|
|
649
|
+
/**
|
|
650
|
+
* Handles 2D load errors.
|
|
651
|
+
* Clears loading state and dispatches a "drawing-error" event.
|
|
652
|
+
* @private
|
|
653
|
+
* @param {object} [detail] - Optional details about the failure.
|
|
654
|
+
* @returns {void}
|
|
655
|
+
*/
|
|
656
|
+
#on2DError(detail) {
|
|
657
|
+
this.#isLoaded = false;
|
|
658
|
+
this.#isLoading = false;
|
|
659
|
+
|
|
660
|
+
this.removeAttribute("loading-2d");
|
|
661
|
+
this.removeAttribute("loaded-2d");
|
|
662
|
+
|
|
663
|
+
const customEventOptions = {
|
|
664
|
+
bubbles: true,
|
|
665
|
+
cancelable: true,
|
|
666
|
+
composed: true,
|
|
667
|
+
};
|
|
668
|
+
if (detail) {
|
|
669
|
+
customEventOptions.detail = detail;
|
|
670
|
+
}
|
|
671
|
+
this.dispatchEvent(new CustomEvent("drawing-error", customEventOptions));
|
|
672
|
+
}
|
|
673
|
+
|
|
584
674
|
/**
|
|
585
675
|
* Handles the "drawing-zoom-changed" event from the 2D viewer component.
|
|
586
676
|
* Dispatches a custom "drawing-zoom-changed" event from the PrefViewer element, forwarding the event detail to external listeners.
|
|
@@ -601,7 +691,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
601
691
|
}
|
|
602
692
|
|
|
603
693
|
/**
|
|
604
|
-
* Handles the custom "pref-viewer-menu-apply" event emitted by the menu component.
|
|
694
|
+
* Handles the custom "pref-viewer-menu-3d-apply" event emitted by the menu component.
|
|
605
695
|
* Forwards the requested settings to the render-application pipeline.
|
|
606
696
|
* @private
|
|
607
697
|
* @param {CustomEvent} event - Menu event containing the `settings` payload.
|
|
@@ -623,16 +713,27 @@ export default class PrefViewer extends HTMLElement {
|
|
|
623
713
|
* @param {object} config - The configuration object to process.
|
|
624
714
|
* @returns {void}
|
|
625
715
|
*/
|
|
626
|
-
#processConfig(config) {
|
|
716
|
+
async #processConfig(config) {
|
|
627
717
|
if (!this.#component3D) {
|
|
718
|
+
this.#processNextTask();
|
|
628
719
|
return;
|
|
629
720
|
}
|
|
630
721
|
|
|
631
722
|
this.#on3DLoading();
|
|
632
|
-
|
|
633
|
-
this.#
|
|
723
|
+
try {
|
|
724
|
+
const detail = await this.#component3D.load(config);
|
|
725
|
+
if (detail?.success === false) {
|
|
726
|
+
this.#on3DError(detail);
|
|
727
|
+
} else {
|
|
728
|
+
this.#on3DLoaded(detail);
|
|
729
|
+
}
|
|
730
|
+
} catch (error) {
|
|
731
|
+
this.#on3DError({
|
|
732
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
733
|
+
});
|
|
734
|
+
} finally {
|
|
634
735
|
this.#processNextTask();
|
|
635
|
-
}
|
|
736
|
+
}
|
|
636
737
|
}
|
|
637
738
|
|
|
638
739
|
/**
|
|
@@ -653,16 +754,23 @@ export default class PrefViewer extends HTMLElement {
|
|
|
653
754
|
* @param {object} drawing - The drawing object to process.
|
|
654
755
|
* @returns {void}
|
|
655
756
|
*/
|
|
656
|
-
#processDrawing(drawing) {
|
|
757
|
+
async #processDrawing(drawing) {
|
|
657
758
|
if (!this.#component2D) {
|
|
759
|
+
this.#processNextTask();
|
|
658
760
|
return;
|
|
659
761
|
}
|
|
660
762
|
|
|
661
763
|
this.#on2DLoading();
|
|
662
|
-
|
|
764
|
+
try {
|
|
765
|
+
const detail = await this.#component2D.load(drawing);
|
|
663
766
|
this.#on2DLoaded(detail);
|
|
767
|
+
} catch (error) {
|
|
768
|
+
this.#on2DError({
|
|
769
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
770
|
+
});
|
|
771
|
+
} finally {
|
|
664
772
|
this.#processNextTask();
|
|
665
|
-
}
|
|
773
|
+
}
|
|
666
774
|
}
|
|
667
775
|
|
|
668
776
|
/**
|
|
@@ -708,15 +816,23 @@ export default class PrefViewer extends HTMLElement {
|
|
|
708
816
|
* @param {object} options - The options object to process.
|
|
709
817
|
* @returns {void}
|
|
710
818
|
*/
|
|
711
|
-
#processOptions(options) {
|
|
819
|
+
async #processOptions(options) {
|
|
712
820
|
if (!this.#component3D) {
|
|
821
|
+
this.#processNextTask();
|
|
713
822
|
return;
|
|
714
823
|
}
|
|
715
824
|
|
|
716
825
|
this.#on3DLoading();
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
826
|
+
try {
|
|
827
|
+
const detail = await this.#component3D.setOptions(options);
|
|
828
|
+
this.#on3DLoaded(detail);
|
|
829
|
+
} catch (error) {
|
|
830
|
+
this.#on3DError({
|
|
831
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
832
|
+
});
|
|
833
|
+
} finally {
|
|
834
|
+
this.#processNextTask();
|
|
835
|
+
}
|
|
720
836
|
}
|
|
721
837
|
|
|
722
838
|
/**
|
|
@@ -728,6 +844,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
728
844
|
*/
|
|
729
845
|
#processVisibility(config) {
|
|
730
846
|
if (!this.#component3D) {
|
|
847
|
+
this.#processNextTask();
|
|
731
848
|
return;
|
|
732
849
|
}
|
|
733
850
|
const showModel = config.model?.visible;
|
|
@@ -957,7 +1074,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
957
1074
|
* @returns {void}
|
|
958
1075
|
*/
|
|
959
1076
|
setMode(mode = this.#mode) {
|
|
960
|
-
mode = mode.toLowerCase();
|
|
1077
|
+
mode = typeof mode === "string" ? mode.toLowerCase() : this.#mode;
|
|
961
1078
|
if (mode !== "2d" && mode !== "3d") {
|
|
962
1079
|
console.warn(`PrefViewer: invalid mode "${mode}". Allowed modes are "2d" and "3d".`);
|
|
963
1080
|
mode = this.#mode;
|
package/src/styles.js
CHANGED
|
@@ -234,7 +234,7 @@ export const PrefViewerMenu3DStyles = `
|
|
|
234
234
|
border-bottom: none;
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
-
pref-viewer-menu-3d>.menu-wrapper>.menu-panel>.menu-switches>.menu-switch
|
|
237
|
+
pref-viewer-menu-3d>.menu-wrapper>.menu-panel>.menu-switches>.menu-switch>.menu-switch-copy {
|
|
238
238
|
display: flex;
|
|
239
239
|
flex-direction: column;
|
|
240
240
|
gap: calc(var(--panel-spacing) / 4);
|
|
@@ -262,14 +262,14 @@ export const PrefViewerMenu3DStyles = `
|
|
|
262
262
|
font-size: var(--font-size-small);
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
-
pref-viewer-menu-3d>.menu-wrapper>.menu-panel>.menu-switches>.menu-switch
|
|
265
|
+
pref-viewer-menu-3d>.menu-wrapper>.menu-panel>.menu-switches>.menu-switch>.menu-switch-control {
|
|
266
266
|
position: relative;
|
|
267
267
|
width: var(--switch-control-width);
|
|
268
268
|
min-width: var(--switch-control-width);
|
|
269
269
|
height: var(--switch-control-height);
|
|
270
270
|
}
|
|
271
271
|
|
|
272
|
-
pref-viewer-menu-3d>.menu-wrapper>.menu-panel>.menu-switches>.menu-switch
|
|
272
|
+
pref-viewer-menu-3d>.menu-wrapper>.menu-panel>.menu-switches>.menu-switch>.menu-switch-control input {
|
|
273
273
|
position: absolute;
|
|
274
274
|
inset: 0;
|
|
275
275
|
margin: 0;
|
|
@@ -277,6 +277,18 @@ export const PrefViewerMenu3DStyles = `
|
|
|
277
277
|
cursor: pointer;
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
+
pref-viewer-menu-3d>.menu-wrapper>.menu-panel>.menu-switches>.menu-switch[data-disabled]>.menu-switch-copy {
|
|
281
|
+
opacity: 0.6;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
pref-viewer-menu-3d>.menu-wrapper>.menu-panel>.menu-switches>.menu-switch[data-disabled]>.menu-switch-control {
|
|
285
|
+
opacity: 0.4;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
pref-viewer-menu-3d>.menu-wrapper>.menu-panel>.menu-switches>.menu-switch>.menu-switch-control input:disabled {
|
|
289
|
+
cursor: not-allowed;
|
|
290
|
+
}
|
|
291
|
+
|
|
280
292
|
pref-viewer-menu-3d>.menu-wrapper>.menu-panel>.menu-switches>.menu-switch .menu-switch-visual {
|
|
281
293
|
position: absolute;
|
|
282
294
|
inset: 0;
|
|
@@ -286,6 +298,10 @@ export const PrefViewerMenu3DStyles = `
|
|
|
286
298
|
cursor: pointer;
|
|
287
299
|
}
|
|
288
300
|
|
|
301
|
+
pref-viewer-menu-3d>.menu-wrapper>.menu-panel>.menu-switches>.menu-switch>.menu-switch-control input:disabled + .menu-switch-visual {
|
|
302
|
+
cursor: not-allowed;
|
|
303
|
+
}
|
|
304
|
+
|
|
289
305
|
pref-viewer-menu-3d>.menu-wrapper>.menu-panel>.menu-switches>.menu-switch .menu-switch-visual::after {
|
|
290
306
|
content: "";
|
|
291
307
|
position: absolute;
|
|
@@ -299,11 +315,11 @@ export const PrefViewerMenu3DStyles = `
|
|
|
299
315
|
transition: transform 0.2s ease;
|
|
300
316
|
}
|
|
301
317
|
|
|
302
|
-
pref-viewer-menu-3d>.menu-wrapper>.menu-panel>.menu-switches>.menu-switch
|
|
318
|
+
pref-viewer-menu-3d>.menu-wrapper>.menu-panel>.menu-switches>.menu-switch>.menu-switch-control input:checked + .menu-switch-visual {
|
|
303
319
|
background: var(--switch-control-bar-checked-color);
|
|
304
320
|
}
|
|
305
321
|
|
|
306
|
-
pref-viewer-menu-3d>.menu-wrapper>.menu-panel>.menu-switches>.menu-switch
|
|
322
|
+
pref-viewer-menu-3d>.menu-wrapper>.menu-panel>.menu-switches>.menu-switch>.menu-switch-control input:checked + .menu-switch-visual::after {
|
|
307
323
|
transform: translateX(var(--switch-control-thumb-size));
|
|
308
324
|
}
|
|
309
325
|
|