@preference-sl/pref-viewer 2.16.0-beta.10 → 2.16.0-beta.11
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 +125 -102
- package/package.json +1 -1
- package/src/babylonjs-animation-opening.js +11 -5
- package/src/babylonjs-controller.js +337 -60
- package/src/pref-viewer-2d.js +14 -21
- package/src/pref-viewer-3d.js +140 -85
- package/src/pref-viewer-menu-3d.js +84 -40
- package/src/pref-viewer-task.js +7 -2
- package/src/pref-viewer.js +547 -185
- package/src/styles.js +105 -29
package/src/pref-viewer.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import PrefViewerTask from "./pref-viewer-task.js";
|
|
2
|
-
import { PrefViewerStyles } from "./styles.js";
|
|
2
|
+
import { PREF_VIEWER_STYLES_STORAGE_KEY, PrefViewerStyles, PrefViewerStyleVariables } from "./styles.js";
|
|
3
3
|
import { DEFAULT_LOCALE, resolveLocale, setLocale, SUPPORTED_LOCALES } from "./localization/i18n.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -9,10 +9,10 @@ import { DEFAULT_LOCALE, resolveLocale, setLocale, SUPPORTED_LOCALES } from "./l
|
|
|
9
9
|
* - Hosts synchronized 2D (SVG) and 3D (Babylon.js) canvases plus a render-settings menu inside a single shadow DOM wrapper.
|
|
10
10
|
* - Queues mutations (config/model/scene/materials/drawing/options/culture) to guarantee deterministic execution and avoid race conditions.
|
|
11
11
|
* - Normalizes remote URLs, Base64 payloads, and IndexedDB lookups before forwarding jobs to the BabylonJSController stack.
|
|
12
|
-
* - Persists the active locale to localStorage, mirrors
|
|
12
|
+
* - Persists the active locale and supported style variables to localStorage, mirrors locale to PrefViewerMenu3D, and keeps the shared i18n module in sync with the chosen culture.
|
|
13
13
|
* - Surfaces a dialog API that powers built-in download flows (GLB, glTF+ZIP, USDZ) for model-only, scene-only, or combined asset exports.
|
|
14
|
-
* - Emits granular lifecycle custom events so host applications can react to loading progress, drawing zoom changes, and menu interactions.
|
|
15
|
-
* - Exposes imperative methods for toggling visibility, updating options, switching modes, and requesting downloads without attribute juggling.
|
|
14
|
+
* - Emits granular lifecycle custom events so host applications can react to loading progress, drawing zoom changes, selection changes, and menu interactions.
|
|
15
|
+
* - Exposes imperative methods for toggling visibility, updating options, switching modes, controlling animations, managing selection state, and requesting downloads without attribute juggling.
|
|
16
16
|
* - Automatically reflects supported attributes back to the DOM so templates, frameworks, and manual scripting stay aligned with viewer state.
|
|
17
17
|
*
|
|
18
18
|
* Usage:
|
|
@@ -20,29 +20,49 @@ import { DEFAULT_LOCALE, resolveLocale, setLocale, SUPPORTED_LOCALES } from "./l
|
|
|
20
20
|
* - Feed serialized JSON strings or plain objects to `loadConfig`, `loadModel`, `loadScene`, `loadMaterials`, `loadDrawing`, or `setOptions`.
|
|
21
21
|
* - Drive localization by setting the `culture` attribute/property or invoking `setCulture("es-ES")`, which propagates to the menu and dialog copy.
|
|
22
22
|
* - Trigger downloads through the provided public methods or open the interactive dialog with `openDialog()` for user-driven selection.
|
|
23
|
-
* - Listen for `
|
|
23
|
+
* - Listen for `pref-viewer-initialized`, `pref-viewer-loading`, `pref-viewer-loaded`, `pref-viewer-error`, `pref-viewer-3d-loaded`, `pref-viewer-2d-loaded`, and `pref-viewer-2d-viewport-changed` to integrate with surrounding UX.
|
|
24
24
|
*
|
|
25
25
|
* Reactive Attributes:
|
|
26
26
|
* - config: Configuration descriptor that can bundle model/scene/materials/drawing/culture hints.
|
|
27
27
|
* - model / scene / materials / drawing: Independent asset entry points (URLs, Base64, indexed data) for each subsystem.
|
|
28
|
-
* - options: JSON describing render toggles (AA, SSAO, IBL, shadows)
|
|
28
|
+
* - options: JSON describing render toggles (AA, SSAO, IBL, shadows), animation menu, highlight, lighting, and misc viewer knobs.
|
|
29
29
|
* - culture: Locale identifier (RFC-5646 style) that resolves against `SUPPORTED_LOCALES` before applying.
|
|
30
30
|
* - mode: Switches between `2d` and `3d`, automatically hiding/showing the relevant subcomponents and menu states.
|
|
31
31
|
* - show-model / show-scene: Boolean flags to toggle visibility without reloading the underlying resources.
|
|
32
|
+
* - show-render-menu: Boolean flag to show or hide the render-settings menu (default: true).
|
|
33
|
+
* - styles: JSON style overrides. Currently supports `colorPrimary`, persisted in localStorage and exposed as `--pref-viewer-color-primary`.
|
|
32
34
|
*
|
|
33
35
|
* Public Methods (selection):
|
|
34
36
|
* - loadConfig / loadModel / loadScene / loadMaterials / loadDrawing: Queue asset ingestion jobs.
|
|
35
|
-
* - setOptions / setMode / setCulture: Update runtime behavior, render mode, or
|
|
37
|
+
* - setOptions / setIBL / setMode / setCulture / setStyles: Update runtime behavior, IBL lighting, render mode, locale, or supported visual variables.
|
|
36
38
|
* - showModel / hideModel / showScene / hideScene: Toggle visibility using the internal task queue for consistent sequencing.
|
|
37
39
|
* - zoomCenter / zoomExtentsAll / zoomIn / zoomOut: Forward zoom commands to the 2D controller when active.
|
|
38
|
-
* -
|
|
40
|
+
* - downloadModelGLB / downloadModelGLTF / downloadModelUSDZ: Export the loaded model in the requested format.
|
|
41
|
+
* - downloadSceneGLB / downloadSceneGLTF / downloadSceneUSDZ: Export the loaded environment in the requested format.
|
|
42
|
+
* - downloadModelAndSceneGLB / downloadModelAndSceneGLTF / downloadModelAndSceneUSDZ: Export the combined scene in the requested format.
|
|
39
43
|
* - openDialog / closeDialog: Manage PrefViewerDialog instances rendered inside the shadow tree.
|
|
44
|
+
* - setSelectableElements / setSelectedElements / applySelectionState: Drive the semantic selection contract.
|
|
45
|
+
* - clearSelectedElements / clearSelectableElements / clearSelectionState: Reset selection state fully or partially.
|
|
46
|
+
* - getSelectionState: Return a snapshot of the current selection.
|
|
47
|
+
* - notifyElementClick: Forward a host-side element click into the viewer event stream.
|
|
48
|
+
* - startTurnable / stopTurnable / toggleTurnable / getTurnableState: Control and inspect the auto-rotate state.
|
|
49
|
+
* - getAnimations / playAnimation / setAnimationProgress / setAnimationLoop: Inspect and control named glTF animations.
|
|
50
|
+
* - getDebugInfo: Return a diagnostic snapshot of the active 3D runtime.
|
|
40
51
|
*
|
|
41
52
|
* Public Properties:
|
|
42
|
-
* - culture, isInitialized, isLoaded, isLoading, isMode2D, isMode3D expose runtime state for host inspection.
|
|
53
|
+
* - Getters: culture, isInitialized, isLoaded, isLoading, isMode2D, isMode3D expose runtime state for host inspection.
|
|
54
|
+
* - Setters: config, culture, drawing, materials, mode, model, options, scene, and styles mirror the corresponding load/set methods for reactive bindings.
|
|
43
55
|
*
|
|
44
56
|
* Events:
|
|
45
|
-
* - `
|
|
57
|
+
* - `pref-viewer-initialized`: emitted after the host and internal components are mounted and public methods/properties are available.
|
|
58
|
+
* - `pref-viewer-loading` / `pref-viewer-loaded` / `pref-viewer-error`: generic lifecycle events that fire for both 2D and 3D pipelines.
|
|
59
|
+
* - `pref-viewer-3d-loading` / `pref-viewer-3d-loaded` / `pref-viewer-3d-error`: announce Babylon.js work start, completion, and failure.
|
|
60
|
+
* - `pref-viewer-2d-loading` / `pref-viewer-2d-loaded` / `pref-viewer-2d-error`: announce SVG pipeline start, completion, and failure.
|
|
61
|
+
* - `pref-viewer-2d-viewport-changed`: proxies pan/zoom delta from the 2D controller.
|
|
62
|
+
* - `pref-viewer-selection-state-changed`: emitted whenever the selectable or selected element sets change.
|
|
63
|
+
* - `pref-viewer-3d-turnable-changed`: emitted after the auto-rotate state starts, stops, or is toggled.
|
|
64
|
+
* - `pref-viewer-element-click`: emitted when the host forwards an element click via `notifyElementClick`.
|
|
65
|
+
* - Legacy `scene-*` and `drawing-*` aliases are still emitted alongside their modern equivalents for backward compatibility.
|
|
46
66
|
*
|
|
47
67
|
* Notes:
|
|
48
68
|
* - Internally wires PrefViewerMenu3D hover/apply events to PrefViewer3D render settings, ensuring persisted toggles stay in sync with BabylonJSController.
|
|
@@ -56,6 +76,16 @@ export default class PrefViewer extends HTMLElement {
|
|
|
56
76
|
#isLoaded = false;
|
|
57
77
|
#isLoading = false;
|
|
58
78
|
#mode = "3d";
|
|
79
|
+
#selectionState = {
|
|
80
|
+
selectableIds: [],
|
|
81
|
+
selectedIds: [],
|
|
82
|
+
selectedItems: [],
|
|
83
|
+
};
|
|
84
|
+
#turnableState = {
|
|
85
|
+
running: false,
|
|
86
|
+
speed: 1,
|
|
87
|
+
direction: "clockwise",
|
|
88
|
+
};
|
|
59
89
|
|
|
60
90
|
// Task queue for sequential processing
|
|
61
91
|
#taskQueue = [];
|
|
@@ -67,6 +97,12 @@ export default class PrefViewer extends HTMLElement {
|
|
|
67
97
|
#culture = "";
|
|
68
98
|
#cultureSuppressAttribute = false;
|
|
69
99
|
|
|
100
|
+
// Runtime style management
|
|
101
|
+
#STYLES_STORAGE_KEY = PREF_VIEWER_STYLES_STORAGE_KEY;
|
|
102
|
+
#styles = {
|
|
103
|
+
colorPrimary: PrefViewerStyleVariables.colorPrimary,
|
|
104
|
+
};
|
|
105
|
+
|
|
70
106
|
// References to internal components
|
|
71
107
|
#wrapper = null;
|
|
72
108
|
#component2D = null;
|
|
@@ -98,6 +134,8 @@ export default class PrefViewer extends HTMLElement {
|
|
|
98
134
|
} else {
|
|
99
135
|
this.#culture = setLocale(this.#cultureInitial);
|
|
100
136
|
}
|
|
137
|
+
|
|
138
|
+
this.#applyStyles(this.#stylesReadPersisted(), { persist: false, applyCssVariables: false });
|
|
101
139
|
}
|
|
102
140
|
|
|
103
141
|
/**
|
|
@@ -106,13 +144,13 @@ export default class PrefViewer extends HTMLElement {
|
|
|
106
144
|
* @returns {string[]} Array of attribute names to observe.
|
|
107
145
|
*/
|
|
108
146
|
static get observedAttributes() {
|
|
109
|
-
return ["config", "culture", "drawing", "materials", "mode", "model", "scene", "options", "show-model", "show-scene", "show-
|
|
147
|
+
return ["config", "culture", "drawing", "materials", "mode", "model", "scene", "options", "show-model", "show-scene", "show-render-menu", "styles"];
|
|
110
148
|
}
|
|
111
149
|
|
|
112
150
|
/**
|
|
113
151
|
* Observes changes to specific attributes and triggers corresponding actions.
|
|
114
152
|
* Loads configuration, drawing, model, scene, materials, or options when their attributes change.
|
|
115
|
-
* Toggles model or scene visibility when "show-model" or "show-scene" attributes change.
|
|
153
|
+
* Toggles model or scene visibility when "show-model" or "show-scene" attributes change, recreates the 3D menu when "show-render-menu" changes, and queues style overrides when "styles" changes.
|
|
116
154
|
* @public
|
|
117
155
|
* @param {string} name - The name of the changed attribute.
|
|
118
156
|
* @param {*} _old - The previous value of the attribute (unused).
|
|
@@ -174,16 +212,12 @@ export default class PrefViewer extends HTMLElement {
|
|
|
174
212
|
const showScene = value.toLowerCase() === "true";
|
|
175
213
|
showScene ? this.showScene() : this.hideScene();
|
|
176
214
|
break;
|
|
177
|
-
case "show-menu":
|
|
215
|
+
case "show-render-menu":
|
|
178
216
|
// Recreate menu to apply show/hide setting
|
|
179
217
|
this.#createMenu3D();
|
|
180
218
|
break;
|
|
181
|
-
case "
|
|
182
|
-
|
|
183
|
-
case "highlight-color":
|
|
184
|
-
case "highlight-enabled":
|
|
185
|
-
// Pass these attributes to the 3D component and menu
|
|
186
|
-
this.#updateComponentAttributes(name, value);
|
|
219
|
+
case "styles":
|
|
220
|
+
this.setStyles(value);
|
|
187
221
|
break;
|
|
188
222
|
}
|
|
189
223
|
}
|
|
@@ -195,24 +229,24 @@ export default class PrefViewer extends HTMLElement {
|
|
|
195
229
|
* @returns {void|boolean} Returns false if initialization fails; otherwise void.
|
|
196
230
|
*/
|
|
197
231
|
connectedCallback() {
|
|
198
|
-
this.#
|
|
199
|
-
this.#
|
|
200
|
-
this.shadowRoot.append(this.#wrapper);
|
|
201
|
-
|
|
202
|
-
const style = document.createElement("style");
|
|
203
|
-
style.textContent = PrefViewerStyles;
|
|
204
|
-
this.shadowRoot.append(style);
|
|
205
|
-
|
|
232
|
+
this.#createShadowDOM();
|
|
233
|
+
this.#stylesApplyCssVariables();
|
|
206
234
|
this.#createComponent3D();
|
|
207
235
|
this.#createComponent2D();
|
|
208
236
|
this.#createMenu3D();
|
|
209
237
|
|
|
210
|
-
|
|
211
|
-
this.setMode();
|
|
212
|
-
}
|
|
238
|
+
this.setMode(this.getAttribute("mode") ?? this.#mode);
|
|
213
239
|
|
|
214
240
|
this.#isInitialized = true;
|
|
215
|
-
|
|
241
|
+
Promise.resolve().then(() => {
|
|
242
|
+
if (!this.isConnected || !this.#isInitialized) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
this.#dispatchPrefViewerEvent("pref-viewer-initialized", {
|
|
246
|
+
isInitialized: this.#isInitialized,
|
|
247
|
+
});
|
|
248
|
+
this.#processNextTask();
|
|
249
|
+
});
|
|
216
250
|
}
|
|
217
251
|
|
|
218
252
|
/**
|
|
@@ -229,21 +263,41 @@ export default class PrefViewer extends HTMLElement {
|
|
|
229
263
|
this.#dialog?.remove();
|
|
230
264
|
}
|
|
231
265
|
if (this.#component2D) {
|
|
232
|
-
this.#component2D.removeEventListener("
|
|
266
|
+
this.#component2D.removeEventListener("pref-viewer-2d-viewport-changed", this.#handlers.on2DZoomChanged);
|
|
233
267
|
this.#component2D.remove();
|
|
234
268
|
}
|
|
235
269
|
if (this.#component3D) {
|
|
236
|
-
this.#component3D.removeEventListener("
|
|
237
|
-
this.#component3D.removeEventListener("
|
|
270
|
+
this.#component3D.removeEventListener("pref-viewer-3d-loaded", this.#handlers.on3DSceneLoaded);
|
|
271
|
+
this.#component3D.removeEventListener("pref-viewer-3d-error", this.#handlers.on3DSceneError);
|
|
238
272
|
this.#component3D.remove();
|
|
239
273
|
}
|
|
240
274
|
if (this.#menu3D) {
|
|
241
|
-
this.#menu3D.removeEventListener("pref-viewer-
|
|
275
|
+
this.#menu3D.removeEventListener("pref-viewer-3d-render-settings-change-requested", this.#handlers.onMenuApply);
|
|
242
276
|
this.#menu3D.remove();
|
|
243
277
|
this.#menu3D = null;
|
|
244
278
|
}
|
|
245
279
|
}
|
|
246
280
|
|
|
281
|
+
/**
|
|
282
|
+
* Creates the root shadow DOM structure and host styles.
|
|
283
|
+
* @private
|
|
284
|
+
* @returns {void}
|
|
285
|
+
*/
|
|
286
|
+
#createShadowDOM() {
|
|
287
|
+
this.#wrapper = document.createElement("div");
|
|
288
|
+
this.#wrapper.classList.add("pref-viewer-wrapper");
|
|
289
|
+
this.shadowRoot.append(this.#wrapper);
|
|
290
|
+
|
|
291
|
+
const link = document.createElement("link");
|
|
292
|
+
link.setAttribute("rel", "stylesheet");
|
|
293
|
+
link.setAttribute("href", "https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900;1,100..900&display=swap");
|
|
294
|
+
this.shadowRoot.append(link);
|
|
295
|
+
|
|
296
|
+
const style = document.createElement("style");
|
|
297
|
+
style.textContent = PrefViewerStyles;
|
|
298
|
+
this.shadowRoot.append(style);
|
|
299
|
+
}
|
|
300
|
+
|
|
247
301
|
/**
|
|
248
302
|
* Creates and appends the 2D viewer component to the shadow DOM.
|
|
249
303
|
* Sets the "visible" attribute to true by default.
|
|
@@ -254,7 +308,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
254
308
|
this.#handlers.on2DZoomChanged = this.#on2DZoomChanged.bind(this);
|
|
255
309
|
this.#component2D = document.createElement("pref-viewer-2d");
|
|
256
310
|
this.#component2D.setAttribute("visible", "false");
|
|
257
|
-
this.#component2D.addEventListener("
|
|
311
|
+
this.#component2D.addEventListener("pref-viewer-2d-viewport-changed", this.#handlers.on2DZoomChanged);
|
|
258
312
|
this.#wrapper.appendChild(this.#component2D);
|
|
259
313
|
}
|
|
260
314
|
|
|
@@ -267,15 +321,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
267
321
|
#createComponent3D() {
|
|
268
322
|
this.#component3D = document.createElement("pref-viewer-3d");
|
|
269
323
|
this.#component3D.setAttribute("visible", "false");
|
|
270
|
-
|
|
271
|
-
// Pass new configuration attributes to 3D component
|
|
272
|
-
const newAttrs = ["show-animation-menu", "highlight-color", "highlight-enabled"];
|
|
273
|
-
for (const attr of newAttrs) {
|
|
274
|
-
if (this.hasAttribute(attr)) {
|
|
275
|
-
this.#component3D.setAttribute(attr, this.getAttribute(attr));
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
324
|
+
|
|
279
325
|
this.#handlers.on3DSceneLoaded = () => {
|
|
280
326
|
this.#menu3DSyncSettings();
|
|
281
327
|
this.#menu3D?.setApplying(false);
|
|
@@ -283,15 +329,15 @@ export default class PrefViewer extends HTMLElement {
|
|
|
283
329
|
this.#handlers.on3DSceneError = () => {
|
|
284
330
|
this.#menu3D?.setApplying(false, true);
|
|
285
331
|
};
|
|
286
|
-
this.#component3D.addEventListener("
|
|
287
|
-
this.#component3D.addEventListener("
|
|
332
|
+
this.#component3D.addEventListener("pref-viewer-3d-loaded", this.#handlers.on3DSceneLoaded);
|
|
333
|
+
this.#component3D.addEventListener("pref-viewer-3d-error", this.#handlers.on3DSceneError);
|
|
288
334
|
this.#wrapper.appendChild(this.#component3D);
|
|
289
335
|
}
|
|
290
336
|
|
|
291
337
|
/**
|
|
292
338
|
* Creates (or recreates) the PrefViewerMenu3D element, wires hover/apply handlers, and syncs it with the 3D component.
|
|
293
339
|
* When a menu already exists it is removed so the DOM stays clean.
|
|
294
|
-
* Respects the "show-menu" attribute to determine if menu should be visible.
|
|
340
|
+
* Respects the "show-render-menu" attribute to determine if the menu should be visible.
|
|
295
341
|
* @private
|
|
296
342
|
* @returns {void}
|
|
297
343
|
*/
|
|
@@ -300,7 +346,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
300
346
|
return;
|
|
301
347
|
}
|
|
302
348
|
if (this.#menu3D) {
|
|
303
|
-
this.#menu3D.removeEventListener("pref-viewer-
|
|
349
|
+
this.#menu3D.removeEventListener("pref-viewer-3d-render-settings-change-requested", this.#handlers.onMenuApply);
|
|
304
350
|
this.#menu3D.remove();
|
|
305
351
|
this.#menu3D = null;
|
|
306
352
|
}
|
|
@@ -314,13 +360,13 @@ export default class PrefViewer extends HTMLElement {
|
|
|
314
360
|
}
|
|
315
361
|
|
|
316
362
|
// Check if menu should be shown (default: true if not specified)
|
|
317
|
-
const
|
|
318
|
-
const
|
|
363
|
+
const show3DMenuAttr = this.getAttribute("show-render-menu");
|
|
364
|
+
const show3DMenu = show3DMenuAttr === null || show3DMenuAttr === "true";
|
|
319
365
|
|
|
320
|
-
if (!
|
|
366
|
+
if (!show3DMenu) {
|
|
321
367
|
return;
|
|
322
368
|
}
|
|
323
|
-
|
|
369
|
+
|
|
324
370
|
this.#menu3D = document.createElement("pref-viewer-menu-3d");
|
|
325
371
|
if (typeof this.#menu3D.setCulture === "function") {
|
|
326
372
|
this.#menu3D.setCulture(this.#culture);
|
|
@@ -328,57 +374,22 @@ export default class PrefViewer extends HTMLElement {
|
|
|
328
374
|
this.#menu3D.setAttribute("culture", this.#culture);
|
|
329
375
|
}
|
|
330
376
|
|
|
331
|
-
// Pass illumination-related attributes to menu
|
|
332
|
-
const illumAttrs = ["show-illumination", "illumination-color"];
|
|
333
|
-
for (const attr of illumAttrs) {
|
|
334
|
-
if (this.hasAttribute(attr)) {
|
|
335
|
-
this.#menu3D.setAttribute(attr, this.getAttribute(attr));
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
377
|
if (!this.#handlers.onMenuApply) {
|
|
340
378
|
this.#handlers.onMenuApply = this.#onMenuApply.bind(this);
|
|
341
379
|
}
|
|
342
380
|
this.#handlers.onViewerHoverStart = () => this.#menu3D.setViewerHover(true);
|
|
343
381
|
this.#handlers.onViewerHoverEnd = () => this.#menu3D.setViewerHover(false);
|
|
344
382
|
|
|
345
|
-
this.#menu3D.addEventListener("pref-viewer-
|
|
383
|
+
this.#menu3D.addEventListener("pref-viewer-3d-render-settings-change-requested", this.#handlers.onMenuApply);
|
|
346
384
|
this.#wrapper.addEventListener("mouseenter", this.#handlers.onViewerHoverStart);
|
|
347
385
|
this.#wrapper.addEventListener("mouseleave", this.#handlers.onViewerHoverEnd);
|
|
348
386
|
|
|
349
387
|
this.#wrapper.appendChild(this.#menu3D);
|
|
350
388
|
this.#menu3DSyncSettings();
|
|
351
|
-
this.#
|
|
389
|
+
this.#menu3DUpdateElementAvailability();
|
|
352
390
|
this.#menu3DUpdateAvailability();
|
|
353
391
|
}
|
|
354
392
|
|
|
355
|
-
/**
|
|
356
|
-
* Updates attributes on child components (3D viewer and menu) based on parent attributes.
|
|
357
|
-
* @private
|
|
358
|
-
* @param {string} name - Attribute name.
|
|
359
|
-
* @param {*} value - Attribute value.
|
|
360
|
-
* @returns {void}
|
|
361
|
-
*/
|
|
362
|
-
#updateComponentAttributes(name, value) {
|
|
363
|
-
// Pass to 3D component
|
|
364
|
-
if (this.#component3D) {
|
|
365
|
-
if (value === null) {
|
|
366
|
-
this.#component3D.removeAttribute(name);
|
|
367
|
-
} else {
|
|
368
|
-
this.#component3D.setAttribute(name, value);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// Pass to menu
|
|
373
|
-
if (this.#menu3D) {
|
|
374
|
-
if (value === null) {
|
|
375
|
-
this.#menu3D.removeAttribute(name);
|
|
376
|
-
} else {
|
|
377
|
-
this.#menu3D.setAttribute(name, value);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
393
|
/**
|
|
383
394
|
* Reads the last persisted locale from localStorage so the viewer restores user preference.
|
|
384
395
|
* @private
|
|
@@ -428,6 +439,84 @@ export default class PrefViewer extends HTMLElement {
|
|
|
428
439
|
this.#cultureSuppressAttribute = false;
|
|
429
440
|
}
|
|
430
441
|
|
|
442
|
+
/**
|
|
443
|
+
* Reads persisted style overrides from localStorage.
|
|
444
|
+
* @private
|
|
445
|
+
* @returns {object} Persisted style overrides, or an empty object when unavailable.
|
|
446
|
+
*/
|
|
447
|
+
#stylesReadPersisted() {
|
|
448
|
+
if (typeof window === "undefined") {
|
|
449
|
+
return {};
|
|
450
|
+
}
|
|
451
|
+
try {
|
|
452
|
+
const serialized = window.localStorage?.getItem(this.#STYLES_STORAGE_KEY);
|
|
453
|
+
return serialized ? JSON.parse(serialized) : {};
|
|
454
|
+
} catch (_error) {
|
|
455
|
+
return {};
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Persists current style overrides in localStorage.
|
|
461
|
+
* @private
|
|
462
|
+
* @returns {void}
|
|
463
|
+
*/
|
|
464
|
+
#stylesPersist() {
|
|
465
|
+
if (typeof window === "undefined") {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
try {
|
|
469
|
+
window.localStorage?.setItem(this.#STYLES_STORAGE_KEY, JSON.stringify(this.#styles));
|
|
470
|
+
} catch (_error) {}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Keeps the host CSS custom properties in sync with the style variables.
|
|
475
|
+
* @private
|
|
476
|
+
* @returns {void}
|
|
477
|
+
*/
|
|
478
|
+
#stylesApplyCssVariables() {
|
|
479
|
+
this.style.setProperty("--pref-viewer-color-primary", this.#styles.colorPrimary);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Applies supported style overrides to PrefViewerStyleVariables and the host CSS variables.
|
|
484
|
+
* @private
|
|
485
|
+
* @param {object} styles - Style overrides.
|
|
486
|
+
* @param {object} [options] - Apply options.
|
|
487
|
+
* @param {boolean} [options.persist=true] - Whether to persist the resolved styles.
|
|
488
|
+
* @param {boolean} [options.applyCssVariables=true] - Whether to update host CSS custom properties immediately.
|
|
489
|
+
* @returns {boolean} True when at least one supported style was applied.
|
|
490
|
+
*/
|
|
491
|
+
#applyStyles(styles, options = {}) {
|
|
492
|
+
const { persist = true, applyCssVariables = true } = options;
|
|
493
|
+
if (!styles || typeof styles !== "object") {
|
|
494
|
+
return false;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const nextStyles = {};
|
|
498
|
+
if (typeof styles.colorPrimary === "string" && styles.colorPrimary.trim().length > 0) {
|
|
499
|
+
nextStyles.colorPrimary = styles.colorPrimary.trim();
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (!Object.keys(nextStyles).length) {
|
|
503
|
+
return false;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
this.#styles = {
|
|
507
|
+
...this.#styles,
|
|
508
|
+
...nextStyles,
|
|
509
|
+
};
|
|
510
|
+
PrefViewerStyleVariables.colorPrimary = this.#styles.colorPrimary;
|
|
511
|
+
if (applyCssVariables) {
|
|
512
|
+
this.#stylesApplyCssVariables();
|
|
513
|
+
}
|
|
514
|
+
if (persist) {
|
|
515
|
+
this.#stylesPersist();
|
|
516
|
+
}
|
|
517
|
+
return true;
|
|
518
|
+
}
|
|
519
|
+
|
|
431
520
|
/**
|
|
432
521
|
* Synchronizes the menu switches with the current render settings reported by PrefViewer3D.
|
|
433
522
|
* Skips the update when either the menu or the getter API is unavailable.
|
|
@@ -467,12 +556,19 @@ export default class PrefViewer extends HTMLElement {
|
|
|
467
556
|
* @private
|
|
468
557
|
* @returns {void}
|
|
469
558
|
*/
|
|
470
|
-
#
|
|
559
|
+
#menu3DUpdateElementAvailability() {
|
|
471
560
|
if (!this.#menu3D) {
|
|
472
561
|
return;
|
|
473
562
|
}
|
|
474
563
|
const iblAvailable = this.#component3D?.isIBLAvailable?.() === true;
|
|
475
|
-
this.#
|
|
564
|
+
const animationMenuEnabled = this.#component3D?.isAnimationMenuEnabled?.() === true;
|
|
565
|
+
const lightingTimeOfDayEnabled = this.#component3D?.isLightingTimeOfDayAvailable?.() === true;
|
|
566
|
+
this.#menu3D.setElementEnabled?.("iblEnabled", iblAvailable);
|
|
567
|
+
this.#menu3D.setElementVisible?.("showAnimationMenu", false);
|
|
568
|
+
this.#menu3D.setElementVisible?.("highlightEnabled", animationMenuEnabled);
|
|
569
|
+
this.#menu3D.setElementVisible?.("highlightColor", animationMenuEnabled);
|
|
570
|
+
this.#menu3D.setElementVisible?.("showLightingTimeOfDay", false);
|
|
571
|
+
this.#menu3D.setElementVisible?.("lightingTimeOfDay", lightingTimeOfDayEnabled);
|
|
476
572
|
}
|
|
477
573
|
|
|
478
574
|
/**
|
|
@@ -486,18 +582,19 @@ export default class PrefViewer extends HTMLElement {
|
|
|
486
582
|
if (!this.#component3D) {
|
|
487
583
|
return;
|
|
488
584
|
}
|
|
489
|
-
const { highlightEnabled: _he, highlightColor: _hc,
|
|
585
|
+
const { showAnimationMenu: _sam, highlightEnabled: _he, highlightColor: _hc, showLightingTimeOfDay: _slt, lightingTimeOfDay: _lt, persist: _persist, ...reloadSettings } = settings ?? {};
|
|
490
586
|
const hasReloadSettings = Object.keys(reloadSettings).length > 0;
|
|
491
587
|
if (hasReloadSettings) {
|
|
492
588
|
this.#menu3D?.setApplying(true);
|
|
493
589
|
}
|
|
494
590
|
try {
|
|
495
591
|
const result = await this.#component3D.applyRenderSettings(settings);
|
|
592
|
+
const controllerSettings = this.#component3D.getRenderSettings();
|
|
593
|
+
if (controllerSettings) {
|
|
594
|
+
this.#menu3D?.setSettings(controllerSettings);
|
|
595
|
+
}
|
|
596
|
+
this.#menu3DUpdateElementAvailability();
|
|
496
597
|
if (!result?.changed) {
|
|
497
|
-
const controllerSettings = this.#component3D.getRenderSettings();
|
|
498
|
-
if (controllerSettings) {
|
|
499
|
-
this.#menu3D?.setSettings(controllerSettings);
|
|
500
|
-
}
|
|
501
598
|
this.#menu3D?.setApplying(false);
|
|
502
599
|
}
|
|
503
600
|
} catch (error) {
|
|
@@ -548,10 +645,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
548
645
|
*/
|
|
549
646
|
#addTaskToQueue(value, type) {
|
|
550
647
|
const task = new PrefViewerTask(value, type);
|
|
551
|
-
if (
|
|
552
|
-
task.type === PrefViewerTask.Types.Options ||
|
|
553
|
-
task.type === PrefViewerTask.Types.Drawing
|
|
554
|
-
) {
|
|
648
|
+
if (task.type === PrefViewerTask.Types.Options || task.type === PrefViewerTask.Types.Drawing || task.type === PrefViewerTask.Types.Styles) {
|
|
555
649
|
this.#taskQueue = this.#taskQueue.filter((queuedTask) => queuedTask.type !== task.type);
|
|
556
650
|
}
|
|
557
651
|
this.#taskQueue.push(task);
|
|
@@ -597,6 +691,9 @@ export default class PrefViewer extends HTMLElement {
|
|
|
597
691
|
case PrefViewerTask.Types.Options:
|
|
598
692
|
this.#processOptions(task.value);
|
|
599
693
|
break;
|
|
694
|
+
case PrefViewerTask.Types.Styles:
|
|
695
|
+
this.#processStyles(task.value);
|
|
696
|
+
break;
|
|
600
697
|
case PrefViewerTask.Types.Visibility:
|
|
601
698
|
this.#processVisibility(task.value);
|
|
602
699
|
break;
|
|
@@ -616,12 +713,9 @@ export default class PrefViewer extends HTMLElement {
|
|
|
616
713
|
this.removeAttribute("loaded-3d");
|
|
617
714
|
this.setAttribute("loading-3d", "");
|
|
618
715
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
composed: true,
|
|
623
|
-
};
|
|
624
|
-
this.dispatchEvent(new CustomEvent("scene-loading", customEventOptions));
|
|
716
|
+
this.#dispatchPrefViewerEvent("pref-viewer-3d-loading");
|
|
717
|
+
this.#dispatchPrefViewerEvent("scene-loading"); // Legacy event for backward compatibility
|
|
718
|
+
this.#dispatchPrefViewerEvent("pref-viewer-loading");
|
|
625
719
|
}
|
|
626
720
|
|
|
627
721
|
/**
|
|
@@ -634,22 +728,18 @@ export default class PrefViewer extends HTMLElement {
|
|
|
634
728
|
#on3DLoaded(detail) {
|
|
635
729
|
this.#isLoaded = true;
|
|
636
730
|
this.#isLoading = false;
|
|
637
|
-
|
|
731
|
+
|
|
638
732
|
this.#menu3DSyncSettings();
|
|
639
|
-
this.#
|
|
733
|
+
this.#menu3DUpdateElementAvailability();
|
|
640
734
|
|
|
641
735
|
this.removeAttribute("loading-3d");
|
|
642
736
|
this.setAttribute("loaded-3d", "");
|
|
643
737
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
};
|
|
649
|
-
if (detail) {
|
|
650
|
-
customEventOptions.detail = detail;
|
|
651
|
-
}
|
|
652
|
-
this.dispatchEvent(new CustomEvent("scene-loaded", customEventOptions));
|
|
738
|
+
this.#dispatchPrefViewerEvent("pref-viewer-3d-loaded", detail ?? {});
|
|
739
|
+
this.#dispatchPrefViewerEvent("scene-loaded", detail ?? {}); // Legacy event for backward compatibility
|
|
740
|
+
this.#dispatchPrefViewerEvent("pref-viewer-loaded", {
|
|
741
|
+
result: detail ?? null,
|
|
742
|
+
});
|
|
653
743
|
}
|
|
654
744
|
|
|
655
745
|
/**
|
|
@@ -667,15 +757,9 @@ export default class PrefViewer extends HTMLElement {
|
|
|
667
757
|
this.removeAttribute("loading-3d");
|
|
668
758
|
this.removeAttribute("loaded-3d");
|
|
669
759
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
composed: true,
|
|
674
|
-
};
|
|
675
|
-
if (detail) {
|
|
676
|
-
customEventOptions.detail = detail;
|
|
677
|
-
}
|
|
678
|
-
this.dispatchEvent(new CustomEvent("scene-error", customEventOptions));
|
|
760
|
+
this.#dispatchPrefViewerEvent("pref-viewer-3d-error", detail ?? {});
|
|
761
|
+
this.#dispatchPrefViewerEvent("scene-error", detail ?? {}); // Legacy event for backward compatibility
|
|
762
|
+
this.#dispatchPrefViewerEvent("pref-viewer-error", { error: detail?.error ?? detail ?? null });
|
|
679
763
|
}
|
|
680
764
|
|
|
681
765
|
/**
|
|
@@ -691,12 +775,9 @@ export default class PrefViewer extends HTMLElement {
|
|
|
691
775
|
this.removeAttribute("loaded-2d");
|
|
692
776
|
this.setAttribute("loading-2d", "");
|
|
693
777
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
composed: true,
|
|
698
|
-
};
|
|
699
|
-
this.dispatchEvent(new CustomEvent("drawing-loading", customEventOptions));
|
|
778
|
+
this.#dispatchPrefViewerEvent("pref-viewer-2d-loading");
|
|
779
|
+
this.#dispatchPrefViewerEvent("drawing-loading"); // Legacy event for backward compatibility
|
|
780
|
+
this.#dispatchPrefViewerEvent("pref-viewer-loading");
|
|
700
781
|
}
|
|
701
782
|
|
|
702
783
|
/**
|
|
@@ -713,15 +794,9 @@ export default class PrefViewer extends HTMLElement {
|
|
|
713
794
|
this.removeAttribute("loading-2d");
|
|
714
795
|
this.setAttribute("loaded-2d", "");
|
|
715
796
|
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
composed: true,
|
|
720
|
-
};
|
|
721
|
-
if (detail) {
|
|
722
|
-
customEventOptions.detail = detail;
|
|
723
|
-
}
|
|
724
|
-
this.dispatchEvent(new CustomEvent("drawing-loaded", customEventOptions));
|
|
797
|
+
this.#dispatchPrefViewerEvent("pref-viewer-2d-loaded", detail ?? {});
|
|
798
|
+
this.#dispatchPrefViewerEvent("drawing-loaded", detail ?? {}); // Legacy event for backward compatibility
|
|
799
|
+
this.#dispatchPrefViewerEvent("pref-viewer-loaded", { result: detail ?? null });
|
|
725
800
|
}
|
|
726
801
|
|
|
727
802
|
/**
|
|
@@ -738,20 +813,14 @@ export default class PrefViewer extends HTMLElement {
|
|
|
738
813
|
this.removeAttribute("loading-2d");
|
|
739
814
|
this.removeAttribute("loaded-2d");
|
|
740
815
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
composed: true,
|
|
745
|
-
};
|
|
746
|
-
if (detail) {
|
|
747
|
-
customEventOptions.detail = detail;
|
|
748
|
-
}
|
|
749
|
-
this.dispatchEvent(new CustomEvent("drawing-error", customEventOptions));
|
|
816
|
+
this.#dispatchPrefViewerEvent("pref-viewer-2d-error", detail ?? {});
|
|
817
|
+
this.#dispatchPrefViewerEvent("drawing-error", detail ?? {}); // Legacy event for backward compatibility
|
|
818
|
+
this.#dispatchPrefViewerEvent("pref-viewer-error", { error: detail?.error ?? detail ?? null });
|
|
750
819
|
}
|
|
751
820
|
|
|
752
821
|
/**
|
|
753
|
-
* Handles the "
|
|
754
|
-
* Dispatches
|
|
822
|
+
* Handles the "pref-viewer-2d-viewport-changed" event from the 2D viewer component.
|
|
823
|
+
* Dispatches the public 2D viewport event, forwarding the event detail to external listeners.
|
|
755
824
|
* @private
|
|
756
825
|
* @param {CustomEvent} event - The original zoom change event from the 2D viewer.
|
|
757
826
|
* @returns {void}
|
|
@@ -759,29 +828,24 @@ export default class PrefViewer extends HTMLElement {
|
|
|
759
828
|
#on2DZoomChanged(event) {
|
|
760
829
|
event.stopPropagation();
|
|
761
830
|
event.preventDefault();
|
|
762
|
-
const
|
|
763
|
-
|
|
764
|
-
cancelable: true,
|
|
765
|
-
composed: true,
|
|
766
|
-
detail: event.detail,
|
|
767
|
-
};
|
|
768
|
-
this.dispatchEvent(new CustomEvent("drawing-zoom-changed", customEventOptions));
|
|
831
|
+
const detail = { ...(event.detail ?? {}) };
|
|
832
|
+
this.#dispatchPrefViewerEvent("pref-viewer-2d-viewport-changed", detail);
|
|
769
833
|
}
|
|
770
834
|
|
|
771
835
|
/**
|
|
772
|
-
* Handles the custom "pref-viewer-
|
|
836
|
+
* Handles the custom "pref-viewer-3d-render-settings-change-requested" event emitted by the menu component.
|
|
773
837
|
* Forwards the requested settings to the render-application pipeline.
|
|
774
838
|
* @private
|
|
775
839
|
* @param {CustomEvent} event - Menu event containing the `settings` payload.
|
|
776
840
|
* @returns {void}
|
|
777
841
|
*/
|
|
778
|
-
#onMenuApply(event) {
|
|
842
|
+
async #onMenuApply(event) {
|
|
779
843
|
event.stopPropagation();
|
|
780
844
|
const settings = event.detail?.settings;
|
|
781
845
|
if (!settings) {
|
|
782
846
|
return;
|
|
783
847
|
}
|
|
784
|
-
this.#applyRenderSettings(settings);
|
|
848
|
+
await this.#applyRenderSettings(settings);
|
|
785
849
|
}
|
|
786
850
|
|
|
787
851
|
/**
|
|
@@ -913,6 +977,17 @@ export default class PrefViewer extends HTMLElement {
|
|
|
913
977
|
}
|
|
914
978
|
}
|
|
915
979
|
|
|
980
|
+
/**
|
|
981
|
+
* Processes queued style overrides and advances the task queue.
|
|
982
|
+
* @private
|
|
983
|
+
* @param {object} styles - Style overrides to apply.
|
|
984
|
+
* @returns {void}
|
|
985
|
+
*/
|
|
986
|
+
#processStyles(styles) {
|
|
987
|
+
this.#applyStyles(styles);
|
|
988
|
+
this.#processNextTask();
|
|
989
|
+
}
|
|
990
|
+
|
|
916
991
|
/**
|
|
917
992
|
* Processes visibility configuration for the model and scene.
|
|
918
993
|
* Shows or hides the model and/or scene based on the config, then processes the next task.
|
|
@@ -936,6 +1011,107 @@ export default class PrefViewer extends HTMLElement {
|
|
|
936
1011
|
this.#processNextTask();
|
|
937
1012
|
}
|
|
938
1013
|
|
|
1014
|
+
/**
|
|
1015
|
+
* Constructs and dispatches a CustomEvent from the host element.
|
|
1016
|
+
* Strips any incoming `mode` field from the detail to avoid leaking stale values,
|
|
1017
|
+
* then re-attaches `mode` only for generic (non-scoped) event types.
|
|
1018
|
+
* Always appends a `timestamp` (Unix ms) so consumers can correlate events.
|
|
1019
|
+
* @private
|
|
1020
|
+
* @param {string} type - The event type string, e.g. "pref-viewer-loaded".
|
|
1021
|
+
* @param {object} [detail={}] - Arbitrary key/value pairs to include in `event.detail`.
|
|
1022
|
+
* @returns {void}
|
|
1023
|
+
*/
|
|
1024
|
+
#dispatchPrefViewerEvent(type, detail = {}) {
|
|
1025
|
+
const eventDetail = { ...(detail ?? {}) };
|
|
1026
|
+
delete eventDetail.mode;
|
|
1027
|
+
|
|
1028
|
+
const hasExplicitModeScope = type.startsWith("pref-viewer-2d-")
|
|
1029
|
+
|| type.startsWith("pref-viewer-3d-")
|
|
1030
|
+
|| type.startsWith("drawing-")
|
|
1031
|
+
|| type.startsWith("scene-");
|
|
1032
|
+
|
|
1033
|
+
if (!hasExplicitModeScope) {
|
|
1034
|
+
eventDetail.mode = this.#mode;
|
|
1035
|
+
}
|
|
1036
|
+
eventDetail.timestamp = Date.now();
|
|
1037
|
+
|
|
1038
|
+
this.dispatchEvent(
|
|
1039
|
+
new CustomEvent(type, {
|
|
1040
|
+
bubbles: true,
|
|
1041
|
+
cancelable: false,
|
|
1042
|
+
composed: true,
|
|
1043
|
+
detail: eventDetail,
|
|
1044
|
+
}),
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
/**
|
|
1049
|
+
* Normalizes a mixed array of element descriptors into a plain string-id list.
|
|
1050
|
+
* Accepts raw strings, objects with an `id` property, or objects with an `Id` property.
|
|
1051
|
+
* Non-string and empty-string results are filtered out.
|
|
1052
|
+
* @private
|
|
1053
|
+
* @param {Array<string|{id?: string, Id?: string}>} value - Array of id-like items to normalize.
|
|
1054
|
+
* @returns {string[]} Deduplicated, non-empty string ids.
|
|
1055
|
+
*/
|
|
1056
|
+
#normalizeIdList(value) {
|
|
1057
|
+
if (!Array.isArray(value)) {
|
|
1058
|
+
return [];
|
|
1059
|
+
}
|
|
1060
|
+
return value.map((item) => (typeof item === "string" ? item : (item?.id ?? item?.Id))).filter((id) => typeof id === "string" && id.length > 0);
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
/**
|
|
1064
|
+
* Normalizes a mixed array of selection descriptors into a canonical list of selection items.
|
|
1065
|
+
* Each output item contains an `id` string and a `highlights` string array.
|
|
1066
|
+
* Accepts raw id strings (expanded to `{ id, highlights: [id] }`), or objects with
|
|
1067
|
+
* `id`/`Id` and optional `highlights`/`Highlights` fields.
|
|
1068
|
+
* Items that cannot be resolved to a non-empty string id are discarded.
|
|
1069
|
+
* @private
|
|
1070
|
+
* @param {Array<string|{id?: string, Id?: string, highlights?: string[], Highlights?: string[]}>} value - Raw selection descriptors.
|
|
1071
|
+
* @returns {Array<{id: string, highlights: string[]}>} Normalized, non-null selection items.
|
|
1072
|
+
*/
|
|
1073
|
+
#normalizeSelectionItems(value) {
|
|
1074
|
+
if (!Array.isArray(value)) {
|
|
1075
|
+
return [];
|
|
1076
|
+
}
|
|
1077
|
+
return value
|
|
1078
|
+
.map((item) => {
|
|
1079
|
+
if (typeof item === "string") {
|
|
1080
|
+
return { id: item, highlights: [item] };
|
|
1081
|
+
}
|
|
1082
|
+
const id = item?.id ?? item?.Id;
|
|
1083
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
1084
|
+
return null;
|
|
1085
|
+
}
|
|
1086
|
+
const highlights = item?.highlights ?? item?.Highlights ?? [id];
|
|
1087
|
+
return {
|
|
1088
|
+
id,
|
|
1089
|
+
highlights: this.#normalizeIdList(highlights),
|
|
1090
|
+
};
|
|
1091
|
+
})
|
|
1092
|
+
.filter(Boolean);
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
/**
|
|
1096
|
+
* Dispatches a `pref-viewer-selection-state-changed` event carrying the current selection snapshot.
|
|
1097
|
+
* Called whenever `selectableIds`, `selectedIds`, or `selectedItems` change.
|
|
1098
|
+
* @private
|
|
1099
|
+
* @returns {void}
|
|
1100
|
+
*/
|
|
1101
|
+
#emitSelectionStateChanged() {
|
|
1102
|
+
this.#dispatchPrefViewerEvent("pref-viewer-selection-state-changed", this.getSelectionState());
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
/**
|
|
1106
|
+
* Dispatches a `pref-viewer-3d-turnable-changed` event carrying the current turnable snapshot.
|
|
1107
|
+
* Called after `startTurnable`, `stopTurnable`, or `toggleTurnable` mutates the internal state.
|
|
1108
|
+
* @private
|
|
1109
|
+
* @returns {void}
|
|
1110
|
+
*/
|
|
1111
|
+
#emitTurnableChanged() {
|
|
1112
|
+
this.#dispatchPrefViewerEvent("pref-viewer-3d-turnable-changed", { state: this.getTurnableState() });
|
|
1113
|
+
}
|
|
1114
|
+
|
|
939
1115
|
/**
|
|
940
1116
|
* ---------------------------
|
|
941
1117
|
* Public methods
|
|
@@ -1070,13 +1246,25 @@ export default class PrefViewer extends HTMLElement {
|
|
|
1070
1246
|
if (!config) {
|
|
1071
1247
|
return false;
|
|
1072
1248
|
}
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
this.#addTaskToQueue(config.drawing, "drawing");
|
|
1249
|
+
if (config.styles) {
|
|
1250
|
+
this.#addTaskToQueue(config.styles, "styles");
|
|
1076
1251
|
}
|
|
1077
1252
|
if (config.culture) {
|
|
1078
1253
|
this.#addTaskToQueue(config.culture, "culture");
|
|
1079
1254
|
}
|
|
1255
|
+
this.#addTaskToQueue(config, "config");
|
|
1256
|
+
if (config.drawing) {
|
|
1257
|
+
this.#addTaskToQueue(config.drawing, "drawing");
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
/**
|
|
1262
|
+
* Returns a diagnostic snapshot for the active 3D runtime.
|
|
1263
|
+
* @public
|
|
1264
|
+
* @returns {object|null} Debug information, or null when the 3D component is not initialized.
|
|
1265
|
+
*/
|
|
1266
|
+
getDebugInfo() {
|
|
1267
|
+
return this.#component3D?.getDebugInfo?.() ?? null;
|
|
1080
1268
|
}
|
|
1081
1269
|
|
|
1082
1270
|
/**
|
|
@@ -1188,6 +1376,191 @@ export default class PrefViewer extends HTMLElement {
|
|
|
1188
1376
|
this.#addTaskToQueue(options, "options");
|
|
1189
1377
|
}
|
|
1190
1378
|
|
|
1379
|
+
/**
|
|
1380
|
+
* Sets supported PrefViewer style variables from an object or JSON string and adds them to the task queue.
|
|
1381
|
+
* Currently supports `colorPrimary`, which updates `PrefViewerStyleVariables.colorPrimary`, applies the host CSS custom property, and persists the value in localStorage.
|
|
1382
|
+
* @public
|
|
1383
|
+
* @param {object|string} styles - Style overrides object or JSON string.
|
|
1384
|
+
* @returns {boolean|void} Returns false if styles are invalid; otherwise void.
|
|
1385
|
+
*/
|
|
1386
|
+
setStyles(styles) {
|
|
1387
|
+
styles = typeof styles === "string" ? JSON.parse(styles) : styles;
|
|
1388
|
+
if (!styles || typeof styles !== "object") {
|
|
1389
|
+
return false;
|
|
1390
|
+
}
|
|
1391
|
+
this.#addTaskToQueue(styles, "styles");
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
/**
|
|
1395
|
+
* Sets image-based lighting options using the stable contract entry point.
|
|
1396
|
+
* @public
|
|
1397
|
+
* @param {object|string} ibl - IBL options payload.
|
|
1398
|
+
* @returns {boolean|void} Returns false if options are invalid; otherwise void.
|
|
1399
|
+
*/
|
|
1400
|
+
setIBL(ibl) {
|
|
1401
|
+
ibl = typeof ibl === "string" ? JSON.parse(ibl) : ibl;
|
|
1402
|
+
if (!ibl) {
|
|
1403
|
+
return false;
|
|
1404
|
+
}
|
|
1405
|
+
return this.setOptions({ ibl });
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
/**
|
|
1409
|
+
* Defines the elements that the host application considers selectable.
|
|
1410
|
+
* This method stores the contract state; visual selection rendering is handled by host-specific follow-up work.
|
|
1411
|
+
* @public
|
|
1412
|
+
* @param {Array<string|{id?: string, Id?: string}>} elements - Element ids or objects containing id/Id.
|
|
1413
|
+
* @returns {object} Current selection state.
|
|
1414
|
+
*/
|
|
1415
|
+
setSelectableElements(elements = []) {
|
|
1416
|
+
this.#selectionState.selectableIds = this.#normalizeIdList(elements);
|
|
1417
|
+
this.#emitSelectionStateChanged();
|
|
1418
|
+
return this.getSelectionState();
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
/**
|
|
1422
|
+
* Defines the currently selected elements using the semantic PrefViewer contract.
|
|
1423
|
+
* @public
|
|
1424
|
+
* @param {Array<string|{id?: string, Id?: string, highlights?: string[], Highlights?: string[]}>} elements - Element ids or objects containing id/Id and optional highlights/Highlights.
|
|
1425
|
+
* @returns {object} Current selection state.
|
|
1426
|
+
*/
|
|
1427
|
+
setSelectedElements(elements = []) {
|
|
1428
|
+
const selectedItems = this.#normalizeSelectionItems(elements);
|
|
1429
|
+
this.#selectionState.selectedItems = selectedItems;
|
|
1430
|
+
this.#selectionState.selectedIds = selectedItems.map((item) => item.id);
|
|
1431
|
+
this.#emitSelectionStateChanged();
|
|
1432
|
+
return this.getSelectionState();
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
/**
|
|
1436
|
+
* Applies a complete host selection state in one call.
|
|
1437
|
+
* @public
|
|
1438
|
+
* @param {{selectableIds?: string[], selectables?: string[], selectedItems?: object[], selectedIds?: string[], selected?: string[]}} state - Selection state with selectableIds/selectables and selectedItems/selectedIds/selected.
|
|
1439
|
+
* @returns {object} Current selection state.
|
|
1440
|
+
*/
|
|
1441
|
+
applySelectionState(state = {}) {
|
|
1442
|
+
this.#selectionState.selectableIds = this.#normalizeIdList(state.selectableIds ?? state.selectables ?? []);
|
|
1443
|
+
this.setSelectedElements(state.selectedItems ?? state.selectedIds ?? state.selected ?? []);
|
|
1444
|
+
return this.getSelectionState();
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
/**
|
|
1448
|
+
* Clears selected elements while keeping the selectable set.
|
|
1449
|
+
* @public
|
|
1450
|
+
* @returns {object} Current selection state.
|
|
1451
|
+
*/
|
|
1452
|
+
clearSelectedElements() {
|
|
1453
|
+
this.#selectionState.selectedIds = [];
|
|
1454
|
+
this.#selectionState.selectedItems = [];
|
|
1455
|
+
this.#emitSelectionStateChanged();
|
|
1456
|
+
return this.getSelectionState();
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
/**
|
|
1460
|
+
* Clears selectable elements while keeping the current selected state.
|
|
1461
|
+
* @public
|
|
1462
|
+
* @returns {object} Current selection state.
|
|
1463
|
+
*/
|
|
1464
|
+
clearSelectableElements() {
|
|
1465
|
+
this.#selectionState.selectableIds = [];
|
|
1466
|
+
this.#emitSelectionStateChanged();
|
|
1467
|
+
return this.getSelectionState();
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
/**
|
|
1471
|
+
* Clears all semantic selection state.
|
|
1472
|
+
* @public
|
|
1473
|
+
* @returns {object} Current selection state.
|
|
1474
|
+
*/
|
|
1475
|
+
clearSelectionState() {
|
|
1476
|
+
this.#selectionState = {
|
|
1477
|
+
selectableIds: [],
|
|
1478
|
+
selectedIds: [],
|
|
1479
|
+
selectedItems: [],
|
|
1480
|
+
};
|
|
1481
|
+
this.#emitSelectionStateChanged();
|
|
1482
|
+
return this.getSelectionState();
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
/**
|
|
1486
|
+
* Returns the current semantic selection state.
|
|
1487
|
+
* @public
|
|
1488
|
+
* @returns {{selectableIds: string[], selectedIds: string[], selectedItems: object[]}}
|
|
1489
|
+
*/
|
|
1490
|
+
getSelectionState() {
|
|
1491
|
+
return {
|
|
1492
|
+
selectableIds: [...this.#selectionState.selectableIds],
|
|
1493
|
+
selectedIds: [...this.#selectionState.selectedIds],
|
|
1494
|
+
selectedItems: this.#selectionState.selectedItems.map((item) => ({
|
|
1495
|
+
id: item.id,
|
|
1496
|
+
highlights: [...item.highlights],
|
|
1497
|
+
})),
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
/**
|
|
1502
|
+
* Contract hook for host-driven element click forwarding.
|
|
1503
|
+
* Internal 2D/3D hit-testing can call this once it exposes stable element ids.
|
|
1504
|
+
* @public
|
|
1505
|
+
* @param {object|string} element - Element id or detail object.
|
|
1506
|
+
* @returns {void}
|
|
1507
|
+
*/
|
|
1508
|
+
notifyElementClick(element) {
|
|
1509
|
+
const detail = typeof element === "string" ? { id: element } : { ...(element ?? {}) };
|
|
1510
|
+
this.#dispatchPrefViewerEvent("pref-viewer-element-click", {
|
|
1511
|
+
element: detail,
|
|
1512
|
+
});
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
/**
|
|
1516
|
+
* Starts the semantic turnable state.
|
|
1517
|
+
* @public
|
|
1518
|
+
* @param {{speed?: number, direction?: string}} [options] - Optional speed/direction values.
|
|
1519
|
+
* @returns {object} Current turnable state.
|
|
1520
|
+
*/
|
|
1521
|
+
startTurnable(options = {}) {
|
|
1522
|
+
this.#turnableState = {
|
|
1523
|
+
...this.#turnableState,
|
|
1524
|
+
...options,
|
|
1525
|
+
running: true,
|
|
1526
|
+
};
|
|
1527
|
+
this.#emitTurnableChanged();
|
|
1528
|
+
return this.getTurnableState();
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
/**
|
|
1532
|
+
* Stops the semantic turnable state.
|
|
1533
|
+
* @public
|
|
1534
|
+
* @returns {object} Current turnable state.
|
|
1535
|
+
*/
|
|
1536
|
+
stopTurnable() {
|
|
1537
|
+
this.#turnableState = {
|
|
1538
|
+
...this.#turnableState,
|
|
1539
|
+
running: false,
|
|
1540
|
+
};
|
|
1541
|
+
this.#emitTurnableChanged();
|
|
1542
|
+
return this.getTurnableState();
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
/**
|
|
1546
|
+
* Toggles the semantic turnable state.
|
|
1547
|
+
* @public
|
|
1548
|
+
* @param {object} [options] - Optional speed/direction values used when starting.
|
|
1549
|
+
* @returns {object} Current turnable state.
|
|
1550
|
+
*/
|
|
1551
|
+
toggleTurnable(options = {}) {
|
|
1552
|
+
return this.#turnableState.running ? this.stopTurnable() : this.startTurnable(options);
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
/**
|
|
1556
|
+
* Returns the current semantic turnable state.
|
|
1557
|
+
* @public
|
|
1558
|
+
* @returns {{running: boolean, speed: number, direction: string}}
|
|
1559
|
+
*/
|
|
1560
|
+
getTurnableState() {
|
|
1561
|
+
return { ...this.#turnableState };
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1191
1564
|
/**
|
|
1192
1565
|
* Opens a modal dialog with the specified title, content, and footer.
|
|
1193
1566
|
* @public
|
|
@@ -1433,31 +1806,20 @@ export default class PrefViewer extends HTMLElement {
|
|
|
1433
1806
|
}
|
|
1434
1807
|
|
|
1435
1808
|
/**
|
|
1436
|
-
*
|
|
1437
|
-
* @public
|
|
1438
|
-
* @param {object|string} value - Scene payload or serialized JSON.
|
|
1439
|
-
*/
|
|
1440
|
-
set scene(value) {
|
|
1441
|
-
this.loadScene(value);
|
|
1442
|
-
}
|
|
1443
|
-
|
|
1444
|
-
/**
|
|
1445
|
-
* Enables or disables the hover highlight effect at runtime.
|
|
1446
|
-
* Accepts boolean or string "true"/"false".
|
|
1809
|
+
* Sends style updates to `setStyles()` enabling reactive bindings.
|
|
1447
1810
|
* @public
|
|
1448
|
-
* @param {
|
|
1811
|
+
* @param {object|string} value - Style overrides object or JSON string.
|
|
1449
1812
|
*/
|
|
1450
|
-
set
|
|
1451
|
-
this.
|
|
1813
|
+
set styles(value) {
|
|
1814
|
+
this.setStyles(value);
|
|
1452
1815
|
}
|
|
1453
|
-
|
|
1454
1816
|
/**
|
|
1455
|
-
*
|
|
1817
|
+
* Redirects scene assignments to `loadScene()` for normalized handling.
|
|
1456
1818
|
* @public
|
|
1457
|
-
* @param {string} value -
|
|
1819
|
+
* @param {object|string} value - Scene payload or serialized JSON.
|
|
1458
1820
|
*/
|
|
1459
|
-
set
|
|
1460
|
-
this.
|
|
1821
|
+
set scene(value) {
|
|
1822
|
+
this.loadScene(value);
|
|
1461
1823
|
}
|
|
1462
1824
|
|
|
1463
1825
|
/**
|