@preference-sl/pref-viewer 2.16.0 → 2.16.2-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +129 -102
- package/index.d.ts +373 -12
- package/package.json +2 -1
- package/src/babylonjs-animation-opening.js +11 -5
- package/src/babylonjs-controller.js +442 -65
- package/src/panzoom-controller.js +36 -17
- package/src/pref-viewer-2d.js +395 -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 +615 -185
- package/src/styles.js +111 -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,50 @@ 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
|
+
* - appendSvgNode / removeAppendedSvgNodes: Manage host-provided SVG nodes through the root facade.
|
|
49
|
+
* - startTurnable / stopTurnable / toggleTurnable / getTurnableState: Control and inspect the auto-rotate state.
|
|
50
|
+
* - getAnimations / playAnimation / setAnimationProgress / setAnimationLoop: Inspect and control named glTF animations.
|
|
51
|
+
* - getDebugInfo: Return a diagnostic snapshot of the active 3D runtime.
|
|
40
52
|
*
|
|
41
53
|
* Public Properties:
|
|
42
|
-
* - culture, isInitialized, isLoaded, isLoading, isMode2D, isMode3D expose runtime state for host inspection.
|
|
54
|
+
* - Getters: culture, isInitialized, isLoaded, isLoading, isMode2D, isMode3D expose runtime state for host inspection.
|
|
55
|
+
* - Setters: config, culture, drawing, materials, mode, model, options, scene, and styles mirror the corresponding load/set methods for reactive bindings.
|
|
43
56
|
*
|
|
44
57
|
* Events:
|
|
45
|
-
* - `
|
|
58
|
+
* - `pref-viewer-initialized`: emitted after the host and internal components are mounted and public methods/properties are available.
|
|
59
|
+
* - `pref-viewer-loading` / `pref-viewer-loaded` / `pref-viewer-error`: generic lifecycle events that fire for both 2D and 3D pipelines.
|
|
60
|
+
* - `pref-viewer-3d-loading` / `pref-viewer-3d-loaded` / `pref-viewer-3d-error`: announce Babylon.js work start, completion, and failure.
|
|
61
|
+
* - `pref-viewer-2d-loading` / `pref-viewer-2d-loaded` / `pref-viewer-2d-error`: announce SVG pipeline start, completion, and failure.
|
|
62
|
+
* - `pref-viewer-2d-viewport-changed`: proxies pan/zoom delta from the 2D controller.
|
|
63
|
+
* - `pref-viewer-selection-state-changed`: emitted whenever the selectable or selected element sets change.
|
|
64
|
+
* - `pref-viewer-3d-turnable-changed`: emitted after the auto-rotate state starts, stops, or is toggled.
|
|
65
|
+
* - `pref-viewer-element-click`: emitted with `{ ids: string[] }` for 2D or host-forwarded semantic clicks.
|
|
66
|
+
* - Legacy `scene-*` and `drawing-*` aliases are still emitted alongside their modern equivalents for backward compatibility.
|
|
46
67
|
*
|
|
47
68
|
* Notes:
|
|
48
69
|
* - Internally wires PrefViewerMenu3D hover/apply events to PrefViewer3D render settings, ensuring persisted toggles stay in sync with BabylonJSController.
|
|
@@ -56,6 +77,16 @@ export default class PrefViewer extends HTMLElement {
|
|
|
56
77
|
#isLoaded = false;
|
|
57
78
|
#isLoading = false;
|
|
58
79
|
#mode = "3d";
|
|
80
|
+
#selectionState = {
|
|
81
|
+
selectableIds: [],
|
|
82
|
+
selectedIds: [],
|
|
83
|
+
selectedItems: [],
|
|
84
|
+
};
|
|
85
|
+
#turnableState = {
|
|
86
|
+
running: false,
|
|
87
|
+
speed: 1,
|
|
88
|
+
direction: "clockwise",
|
|
89
|
+
};
|
|
59
90
|
|
|
60
91
|
// Task queue for sequential processing
|
|
61
92
|
#taskQueue = [];
|
|
@@ -67,6 +98,12 @@ export default class PrefViewer extends HTMLElement {
|
|
|
67
98
|
#culture = "";
|
|
68
99
|
#cultureSuppressAttribute = false;
|
|
69
100
|
|
|
101
|
+
// Runtime style management
|
|
102
|
+
#STYLES_STORAGE_KEY = PREF_VIEWER_STYLES_STORAGE_KEY;
|
|
103
|
+
#styles = {
|
|
104
|
+
colorPrimary: PrefViewerStyleVariables.colorPrimary,
|
|
105
|
+
};
|
|
106
|
+
|
|
70
107
|
// References to internal components
|
|
71
108
|
#wrapper = null;
|
|
72
109
|
#component2D = null;
|
|
@@ -77,6 +114,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
77
114
|
// Event handlers
|
|
78
115
|
#handlers = {
|
|
79
116
|
on2DZoomChanged: null,
|
|
117
|
+
on2DElementClick: null,
|
|
80
118
|
onMenuApply: null,
|
|
81
119
|
onViewerHoverStart: null,
|
|
82
120
|
onViewerHoverEnd: null,
|
|
@@ -98,6 +136,8 @@ export default class PrefViewer extends HTMLElement {
|
|
|
98
136
|
} else {
|
|
99
137
|
this.#culture = setLocale(this.#cultureInitial);
|
|
100
138
|
}
|
|
139
|
+
|
|
140
|
+
this.#applyStyles(this.#stylesReadPersisted(), { persist: false, applyCssVariables: false });
|
|
101
141
|
}
|
|
102
142
|
|
|
103
143
|
/**
|
|
@@ -106,13 +146,13 @@ export default class PrefViewer extends HTMLElement {
|
|
|
106
146
|
* @returns {string[]} Array of attribute names to observe.
|
|
107
147
|
*/
|
|
108
148
|
static get observedAttributes() {
|
|
109
|
-
return ["config", "culture", "drawing", "materials", "mode", "model", "scene", "options", "show-model", "show-scene", "show-
|
|
149
|
+
return ["config", "culture", "drawing", "materials", "mode", "model", "scene", "options", "show-model", "show-scene", "show-render-menu", "styles"];
|
|
110
150
|
}
|
|
111
151
|
|
|
112
152
|
/**
|
|
113
153
|
* Observes changes to specific attributes and triggers corresponding actions.
|
|
114
154
|
* 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.
|
|
155
|
+
* 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
156
|
* @public
|
|
117
157
|
* @param {string} name - The name of the changed attribute.
|
|
118
158
|
* @param {*} _old - The previous value of the attribute (unused).
|
|
@@ -174,16 +214,12 @@ export default class PrefViewer extends HTMLElement {
|
|
|
174
214
|
const showScene = value.toLowerCase() === "true";
|
|
175
215
|
showScene ? this.showScene() : this.hideScene();
|
|
176
216
|
break;
|
|
177
|
-
case "show-menu":
|
|
217
|
+
case "show-render-menu":
|
|
178
218
|
// Recreate menu to apply show/hide setting
|
|
179
219
|
this.#createMenu3D();
|
|
180
220
|
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);
|
|
221
|
+
case "styles":
|
|
222
|
+
this.setStyles(value);
|
|
187
223
|
break;
|
|
188
224
|
}
|
|
189
225
|
}
|
|
@@ -195,24 +231,24 @@ export default class PrefViewer extends HTMLElement {
|
|
|
195
231
|
* @returns {void|boolean} Returns false if initialization fails; otherwise void.
|
|
196
232
|
*/
|
|
197
233
|
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
|
-
|
|
234
|
+
this.#createShadowDOM();
|
|
235
|
+
this.#stylesApplyCssVariables();
|
|
206
236
|
this.#createComponent3D();
|
|
207
237
|
this.#createComponent2D();
|
|
208
238
|
this.#createMenu3D();
|
|
209
239
|
|
|
210
|
-
|
|
211
|
-
this.setMode();
|
|
212
|
-
}
|
|
240
|
+
this.setMode(this.getAttribute("mode") ?? this.#mode);
|
|
213
241
|
|
|
214
242
|
this.#isInitialized = true;
|
|
215
|
-
|
|
243
|
+
Promise.resolve().then(() => {
|
|
244
|
+
if (!this.isConnected || !this.#isInitialized) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
this.#dispatchPrefViewerEvent("pref-viewer-initialized", {
|
|
248
|
+
isInitialized: this.#isInitialized,
|
|
249
|
+
});
|
|
250
|
+
this.#processNextTask();
|
|
251
|
+
});
|
|
216
252
|
}
|
|
217
253
|
|
|
218
254
|
/**
|
|
@@ -229,21 +265,42 @@ export default class PrefViewer extends HTMLElement {
|
|
|
229
265
|
this.#dialog?.remove();
|
|
230
266
|
}
|
|
231
267
|
if (this.#component2D) {
|
|
232
|
-
this.#component2D.removeEventListener("
|
|
268
|
+
this.#component2D.removeEventListener("pref-viewer-2d-viewport-changed", this.#handlers.on2DZoomChanged);
|
|
269
|
+
this.#component2D.removeEventListener("pref-viewer-2d-element-click", this.#handlers.on2DElementClick);
|
|
233
270
|
this.#component2D.remove();
|
|
234
271
|
}
|
|
235
272
|
if (this.#component3D) {
|
|
236
|
-
this.#component3D.removeEventListener("
|
|
237
|
-
this.#component3D.removeEventListener("
|
|
273
|
+
this.#component3D.removeEventListener("pref-viewer-3d-loaded", this.#handlers.on3DSceneLoaded);
|
|
274
|
+
this.#component3D.removeEventListener("pref-viewer-3d-error", this.#handlers.on3DSceneError);
|
|
238
275
|
this.#component3D.remove();
|
|
239
276
|
}
|
|
240
277
|
if (this.#menu3D) {
|
|
241
|
-
this.#menu3D.removeEventListener("pref-viewer-
|
|
278
|
+
this.#menu3D.removeEventListener("pref-viewer-3d-render-settings-change-requested", this.#handlers.onMenuApply);
|
|
242
279
|
this.#menu3D.remove();
|
|
243
280
|
this.#menu3D = null;
|
|
244
281
|
}
|
|
245
282
|
}
|
|
246
283
|
|
|
284
|
+
/**
|
|
285
|
+
* Creates the root shadow DOM structure and host styles.
|
|
286
|
+
* @private
|
|
287
|
+
* @returns {void}
|
|
288
|
+
*/
|
|
289
|
+
#createShadowDOM() {
|
|
290
|
+
this.#wrapper = document.createElement("div");
|
|
291
|
+
this.#wrapper.classList.add("pref-viewer-wrapper");
|
|
292
|
+
this.shadowRoot.append(this.#wrapper);
|
|
293
|
+
|
|
294
|
+
const link = document.createElement("link");
|
|
295
|
+
link.setAttribute("rel", "stylesheet");
|
|
296
|
+
link.setAttribute("href", "https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900;1,100..900&display=swap");
|
|
297
|
+
this.shadowRoot.append(link);
|
|
298
|
+
|
|
299
|
+
const style = document.createElement("style");
|
|
300
|
+
style.textContent = PrefViewerStyles;
|
|
301
|
+
this.shadowRoot.append(style);
|
|
302
|
+
}
|
|
303
|
+
|
|
247
304
|
/**
|
|
248
305
|
* Creates and appends the 2D viewer component to the shadow DOM.
|
|
249
306
|
* Sets the "visible" attribute to true by default.
|
|
@@ -252,9 +309,11 @@ export default class PrefViewer extends HTMLElement {
|
|
|
252
309
|
*/
|
|
253
310
|
#createComponent2D() {
|
|
254
311
|
this.#handlers.on2DZoomChanged = this.#on2DZoomChanged.bind(this);
|
|
312
|
+
this.#handlers.on2DElementClick = this.#on2DElementClick.bind(this);
|
|
255
313
|
this.#component2D = document.createElement("pref-viewer-2d");
|
|
256
314
|
this.#component2D.setAttribute("visible", "false");
|
|
257
|
-
this.#component2D.addEventListener("
|
|
315
|
+
this.#component2D.addEventListener("pref-viewer-2d-viewport-changed", this.#handlers.on2DZoomChanged);
|
|
316
|
+
this.#component2D.addEventListener("pref-viewer-2d-element-click", this.#handlers.on2DElementClick);
|
|
258
317
|
this.#wrapper.appendChild(this.#component2D);
|
|
259
318
|
}
|
|
260
319
|
|
|
@@ -267,15 +326,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
267
326
|
#createComponent3D() {
|
|
268
327
|
this.#component3D = document.createElement("pref-viewer-3d");
|
|
269
328
|
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
|
-
|
|
329
|
+
|
|
279
330
|
this.#handlers.on3DSceneLoaded = () => {
|
|
280
331
|
this.#menu3DSyncSettings();
|
|
281
332
|
this.#menu3D?.setApplying(false);
|
|
@@ -283,15 +334,15 @@ export default class PrefViewer extends HTMLElement {
|
|
|
283
334
|
this.#handlers.on3DSceneError = () => {
|
|
284
335
|
this.#menu3D?.setApplying(false, true);
|
|
285
336
|
};
|
|
286
|
-
this.#component3D.addEventListener("
|
|
287
|
-
this.#component3D.addEventListener("
|
|
337
|
+
this.#component3D.addEventListener("pref-viewer-3d-loaded", this.#handlers.on3DSceneLoaded);
|
|
338
|
+
this.#component3D.addEventListener("pref-viewer-3d-error", this.#handlers.on3DSceneError);
|
|
288
339
|
this.#wrapper.appendChild(this.#component3D);
|
|
289
340
|
}
|
|
290
341
|
|
|
291
342
|
/**
|
|
292
343
|
* Creates (or recreates) the PrefViewerMenu3D element, wires hover/apply handlers, and syncs it with the 3D component.
|
|
293
344
|
* 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.
|
|
345
|
+
* Respects the "show-render-menu" attribute to determine if the menu should be visible.
|
|
295
346
|
* @private
|
|
296
347
|
* @returns {void}
|
|
297
348
|
*/
|
|
@@ -300,7 +351,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
300
351
|
return;
|
|
301
352
|
}
|
|
302
353
|
if (this.#menu3D) {
|
|
303
|
-
this.#menu3D.removeEventListener("pref-viewer-
|
|
354
|
+
this.#menu3D.removeEventListener("pref-viewer-3d-render-settings-change-requested", this.#handlers.onMenuApply);
|
|
304
355
|
this.#menu3D.remove();
|
|
305
356
|
this.#menu3D = null;
|
|
306
357
|
}
|
|
@@ -314,13 +365,13 @@ export default class PrefViewer extends HTMLElement {
|
|
|
314
365
|
}
|
|
315
366
|
|
|
316
367
|
// Check if menu should be shown (default: true if not specified)
|
|
317
|
-
const
|
|
318
|
-
const
|
|
368
|
+
const show3DMenuAttr = this.getAttribute("show-render-menu");
|
|
369
|
+
const show3DMenu = show3DMenuAttr === null || show3DMenuAttr === "true";
|
|
319
370
|
|
|
320
|
-
if (!
|
|
371
|
+
if (!show3DMenu) {
|
|
321
372
|
return;
|
|
322
373
|
}
|
|
323
|
-
|
|
374
|
+
|
|
324
375
|
this.#menu3D = document.createElement("pref-viewer-menu-3d");
|
|
325
376
|
if (typeof this.#menu3D.setCulture === "function") {
|
|
326
377
|
this.#menu3D.setCulture(this.#culture);
|
|
@@ -328,57 +379,22 @@ export default class PrefViewer extends HTMLElement {
|
|
|
328
379
|
this.#menu3D.setAttribute("culture", this.#culture);
|
|
329
380
|
}
|
|
330
381
|
|
|
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
382
|
if (!this.#handlers.onMenuApply) {
|
|
340
383
|
this.#handlers.onMenuApply = this.#onMenuApply.bind(this);
|
|
341
384
|
}
|
|
342
385
|
this.#handlers.onViewerHoverStart = () => this.#menu3D.setViewerHover(true);
|
|
343
386
|
this.#handlers.onViewerHoverEnd = () => this.#menu3D.setViewerHover(false);
|
|
344
387
|
|
|
345
|
-
this.#menu3D.addEventListener("pref-viewer-
|
|
388
|
+
this.#menu3D.addEventListener("pref-viewer-3d-render-settings-change-requested", this.#handlers.onMenuApply);
|
|
346
389
|
this.#wrapper.addEventListener("mouseenter", this.#handlers.onViewerHoverStart);
|
|
347
390
|
this.#wrapper.addEventListener("mouseleave", this.#handlers.onViewerHoverEnd);
|
|
348
391
|
|
|
349
392
|
this.#wrapper.appendChild(this.#menu3D);
|
|
350
393
|
this.#menu3DSyncSettings();
|
|
351
|
-
this.#
|
|
394
|
+
this.#menu3DUpdateElementAvailability();
|
|
352
395
|
this.#menu3DUpdateAvailability();
|
|
353
396
|
}
|
|
354
397
|
|
|
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
398
|
/**
|
|
383
399
|
* Reads the last persisted locale from localStorage so the viewer restores user preference.
|
|
384
400
|
* @private
|
|
@@ -428,6 +444,84 @@ export default class PrefViewer extends HTMLElement {
|
|
|
428
444
|
this.#cultureSuppressAttribute = false;
|
|
429
445
|
}
|
|
430
446
|
|
|
447
|
+
/**
|
|
448
|
+
* Reads persisted style overrides from localStorage.
|
|
449
|
+
* @private
|
|
450
|
+
* @returns {object} Persisted style overrides, or an empty object when unavailable.
|
|
451
|
+
*/
|
|
452
|
+
#stylesReadPersisted() {
|
|
453
|
+
if (typeof window === "undefined") {
|
|
454
|
+
return {};
|
|
455
|
+
}
|
|
456
|
+
try {
|
|
457
|
+
const serialized = window.localStorage?.getItem(this.#STYLES_STORAGE_KEY);
|
|
458
|
+
return serialized ? JSON.parse(serialized) : {};
|
|
459
|
+
} catch (_error) {
|
|
460
|
+
return {};
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Persists current style overrides in localStorage.
|
|
466
|
+
* @private
|
|
467
|
+
* @returns {void}
|
|
468
|
+
*/
|
|
469
|
+
#stylesPersist() {
|
|
470
|
+
if (typeof window === "undefined") {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
try {
|
|
474
|
+
window.localStorage?.setItem(this.#STYLES_STORAGE_KEY, JSON.stringify(this.#styles));
|
|
475
|
+
} catch (_error) {}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Keeps the host CSS custom properties in sync with the style variables.
|
|
480
|
+
* @private
|
|
481
|
+
* @returns {void}
|
|
482
|
+
*/
|
|
483
|
+
#stylesApplyCssVariables() {
|
|
484
|
+
this.style.setProperty("--pref-viewer-color-primary", this.#styles.colorPrimary);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Applies supported style overrides to PrefViewerStyleVariables and the host CSS variables.
|
|
489
|
+
* @private
|
|
490
|
+
* @param {object} styles - Style overrides.
|
|
491
|
+
* @param {object} [options] - Apply options.
|
|
492
|
+
* @param {boolean} [options.persist=true] - Whether to persist the resolved styles.
|
|
493
|
+
* @param {boolean} [options.applyCssVariables=true] - Whether to update host CSS custom properties immediately.
|
|
494
|
+
* @returns {boolean} True when at least one supported style was applied.
|
|
495
|
+
*/
|
|
496
|
+
#applyStyles(styles, options = {}) {
|
|
497
|
+
const { persist = true, applyCssVariables = true } = options;
|
|
498
|
+
if (!styles || typeof styles !== "object") {
|
|
499
|
+
return false;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const nextStyles = {};
|
|
503
|
+
if (typeof styles.colorPrimary === "string" && styles.colorPrimary.trim().length > 0) {
|
|
504
|
+
nextStyles.colorPrimary = styles.colorPrimary.trim();
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (!Object.keys(nextStyles).length) {
|
|
508
|
+
return false;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
this.#styles = {
|
|
512
|
+
...this.#styles,
|
|
513
|
+
...nextStyles,
|
|
514
|
+
};
|
|
515
|
+
PrefViewerStyleVariables.colorPrimary = this.#styles.colorPrimary;
|
|
516
|
+
if (applyCssVariables) {
|
|
517
|
+
this.#stylesApplyCssVariables();
|
|
518
|
+
}
|
|
519
|
+
if (persist) {
|
|
520
|
+
this.#stylesPersist();
|
|
521
|
+
}
|
|
522
|
+
return true;
|
|
523
|
+
}
|
|
524
|
+
|
|
431
525
|
/**
|
|
432
526
|
* Synchronizes the menu switches with the current render settings reported by PrefViewer3D.
|
|
433
527
|
* Skips the update when either the menu or the getter API is unavailable.
|
|
@@ -467,12 +561,19 @@ export default class PrefViewer extends HTMLElement {
|
|
|
467
561
|
* @private
|
|
468
562
|
* @returns {void}
|
|
469
563
|
*/
|
|
470
|
-
#
|
|
564
|
+
#menu3DUpdateElementAvailability() {
|
|
471
565
|
if (!this.#menu3D) {
|
|
472
566
|
return;
|
|
473
567
|
}
|
|
474
568
|
const iblAvailable = this.#component3D?.isIBLAvailable?.() === true;
|
|
475
|
-
this.#
|
|
569
|
+
const animationMenuEnabled = this.#component3D?.isAnimationMenuEnabled?.() === true;
|
|
570
|
+
const lightingTimeOfDayEnabled = this.#component3D?.isLightingTimeOfDayAvailable?.() === true;
|
|
571
|
+
this.#menu3D.setElementEnabled?.("iblEnabled", iblAvailable);
|
|
572
|
+
this.#menu3D.setElementVisible?.("showAnimationMenu", false);
|
|
573
|
+
this.#menu3D.setElementVisible?.("highlightEnabled", animationMenuEnabled);
|
|
574
|
+
this.#menu3D.setElementVisible?.("highlightColor", animationMenuEnabled);
|
|
575
|
+
this.#menu3D.setElementVisible?.("showLightingTimeOfDay", false);
|
|
576
|
+
this.#menu3D.setElementVisible?.("lightingTimeOfDay", lightingTimeOfDayEnabled);
|
|
476
577
|
}
|
|
477
578
|
|
|
478
579
|
/**
|
|
@@ -486,18 +587,19 @@ export default class PrefViewer extends HTMLElement {
|
|
|
486
587
|
if (!this.#component3D) {
|
|
487
588
|
return;
|
|
488
589
|
}
|
|
489
|
-
const { highlightEnabled: _he, highlightColor: _hc,
|
|
590
|
+
const { showAnimationMenu: _sam, highlightEnabled: _he, highlightColor: _hc, showLightingTimeOfDay: _slt, lightingTimeOfDay: _lt, persist: _persist, ...reloadSettings } = settings ?? {};
|
|
490
591
|
const hasReloadSettings = Object.keys(reloadSettings).length > 0;
|
|
491
592
|
if (hasReloadSettings) {
|
|
492
593
|
this.#menu3D?.setApplying(true);
|
|
493
594
|
}
|
|
494
595
|
try {
|
|
495
596
|
const result = await this.#component3D.applyRenderSettings(settings);
|
|
597
|
+
const controllerSettings = this.#component3D.getRenderSettings();
|
|
598
|
+
if (controllerSettings) {
|
|
599
|
+
this.#menu3D?.setSettings(controllerSettings);
|
|
600
|
+
}
|
|
601
|
+
this.#menu3DUpdateElementAvailability();
|
|
496
602
|
if (!result?.changed) {
|
|
497
|
-
const controllerSettings = this.#component3D.getRenderSettings();
|
|
498
|
-
if (controllerSettings) {
|
|
499
|
-
this.#menu3D?.setSettings(controllerSettings);
|
|
500
|
-
}
|
|
501
603
|
this.#menu3D?.setApplying(false);
|
|
502
604
|
}
|
|
503
605
|
} catch (error) {
|
|
@@ -548,10 +650,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
548
650
|
*/
|
|
549
651
|
#addTaskToQueue(value, type) {
|
|
550
652
|
const task = new PrefViewerTask(value, type);
|
|
551
|
-
if (
|
|
552
|
-
task.type === PrefViewerTask.Types.Options ||
|
|
553
|
-
task.type === PrefViewerTask.Types.Drawing
|
|
554
|
-
) {
|
|
653
|
+
if (task.type === PrefViewerTask.Types.Options || task.type === PrefViewerTask.Types.Drawing || task.type === PrefViewerTask.Types.Styles) {
|
|
555
654
|
this.#taskQueue = this.#taskQueue.filter((queuedTask) => queuedTask.type !== task.type);
|
|
556
655
|
}
|
|
557
656
|
this.#taskQueue.push(task);
|
|
@@ -597,6 +696,9 @@ export default class PrefViewer extends HTMLElement {
|
|
|
597
696
|
case PrefViewerTask.Types.Options:
|
|
598
697
|
this.#processOptions(task.value);
|
|
599
698
|
break;
|
|
699
|
+
case PrefViewerTask.Types.Styles:
|
|
700
|
+
this.#processStyles(task.value);
|
|
701
|
+
break;
|
|
600
702
|
case PrefViewerTask.Types.Visibility:
|
|
601
703
|
this.#processVisibility(task.value);
|
|
602
704
|
break;
|
|
@@ -616,12 +718,9 @@ export default class PrefViewer extends HTMLElement {
|
|
|
616
718
|
this.removeAttribute("loaded-3d");
|
|
617
719
|
this.setAttribute("loading-3d", "");
|
|
618
720
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
composed: true,
|
|
623
|
-
};
|
|
624
|
-
this.dispatchEvent(new CustomEvent("scene-loading", customEventOptions));
|
|
721
|
+
this.#dispatchPrefViewerEvent("pref-viewer-3d-loading");
|
|
722
|
+
this.#dispatchPrefViewerEvent("scene-loading"); // Legacy event for backward compatibility
|
|
723
|
+
this.#dispatchPrefViewerEvent("pref-viewer-loading");
|
|
625
724
|
}
|
|
626
725
|
|
|
627
726
|
/**
|
|
@@ -634,22 +733,18 @@ export default class PrefViewer extends HTMLElement {
|
|
|
634
733
|
#on3DLoaded(detail) {
|
|
635
734
|
this.#isLoaded = true;
|
|
636
735
|
this.#isLoading = false;
|
|
637
|
-
|
|
736
|
+
|
|
638
737
|
this.#menu3DSyncSettings();
|
|
639
|
-
this.#
|
|
738
|
+
this.#menu3DUpdateElementAvailability();
|
|
640
739
|
|
|
641
740
|
this.removeAttribute("loading-3d");
|
|
642
741
|
this.setAttribute("loaded-3d", "");
|
|
643
742
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
};
|
|
649
|
-
if (detail) {
|
|
650
|
-
customEventOptions.detail = detail;
|
|
651
|
-
}
|
|
652
|
-
this.dispatchEvent(new CustomEvent("scene-loaded", customEventOptions));
|
|
743
|
+
this.#dispatchPrefViewerEvent("pref-viewer-3d-loaded", detail ?? {});
|
|
744
|
+
this.#dispatchPrefViewerEvent("scene-loaded", detail ?? {}); // Legacy event for backward compatibility
|
|
745
|
+
this.#dispatchPrefViewerEvent("pref-viewer-loaded", {
|
|
746
|
+
result: detail ?? null,
|
|
747
|
+
});
|
|
653
748
|
}
|
|
654
749
|
|
|
655
750
|
/**
|
|
@@ -667,15 +762,9 @@ export default class PrefViewer extends HTMLElement {
|
|
|
667
762
|
this.removeAttribute("loading-3d");
|
|
668
763
|
this.removeAttribute("loaded-3d");
|
|
669
764
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
composed: true,
|
|
674
|
-
};
|
|
675
|
-
if (detail) {
|
|
676
|
-
customEventOptions.detail = detail;
|
|
677
|
-
}
|
|
678
|
-
this.dispatchEvent(new CustomEvent("scene-error", customEventOptions));
|
|
765
|
+
this.#dispatchPrefViewerEvent("pref-viewer-3d-error", detail ?? {});
|
|
766
|
+
this.#dispatchPrefViewerEvent("scene-error", detail ?? {}); // Legacy event for backward compatibility
|
|
767
|
+
this.#dispatchPrefViewerEvent("pref-viewer-error", { error: detail?.error ?? detail ?? null });
|
|
679
768
|
}
|
|
680
769
|
|
|
681
770
|
/**
|
|
@@ -691,12 +780,9 @@ export default class PrefViewer extends HTMLElement {
|
|
|
691
780
|
this.removeAttribute("loaded-2d");
|
|
692
781
|
this.setAttribute("loading-2d", "");
|
|
693
782
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
composed: true,
|
|
698
|
-
};
|
|
699
|
-
this.dispatchEvent(new CustomEvent("drawing-loading", customEventOptions));
|
|
783
|
+
this.#dispatchPrefViewerEvent("pref-viewer-2d-loading");
|
|
784
|
+
this.#dispatchPrefViewerEvent("drawing-loading"); // Legacy event for backward compatibility
|
|
785
|
+
this.#dispatchPrefViewerEvent("pref-viewer-loading");
|
|
700
786
|
}
|
|
701
787
|
|
|
702
788
|
/**
|
|
@@ -713,15 +799,11 @@ export default class PrefViewer extends HTMLElement {
|
|
|
713
799
|
this.removeAttribute("loading-2d");
|
|
714
800
|
this.setAttribute("loaded-2d", "");
|
|
715
801
|
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
};
|
|
721
|
-
if (detail) {
|
|
722
|
-
customEventOptions.detail = detail;
|
|
723
|
-
}
|
|
724
|
-
this.dispatchEvent(new CustomEvent("drawing-loaded", customEventOptions));
|
|
802
|
+
this.#applySelectionStateTo2D();
|
|
803
|
+
|
|
804
|
+
this.#dispatchPrefViewerEvent("pref-viewer-2d-loaded", detail ?? {});
|
|
805
|
+
this.#dispatchPrefViewerEvent("drawing-loaded", detail ?? {}); // Legacy event for backward compatibility
|
|
806
|
+
this.#dispatchPrefViewerEvent("pref-viewer-loaded", { result: detail ?? null });
|
|
725
807
|
}
|
|
726
808
|
|
|
727
809
|
/**
|
|
@@ -738,20 +820,14 @@ export default class PrefViewer extends HTMLElement {
|
|
|
738
820
|
this.removeAttribute("loading-2d");
|
|
739
821
|
this.removeAttribute("loaded-2d");
|
|
740
822
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
composed: true,
|
|
745
|
-
};
|
|
746
|
-
if (detail) {
|
|
747
|
-
customEventOptions.detail = detail;
|
|
748
|
-
}
|
|
749
|
-
this.dispatchEvent(new CustomEvent("drawing-error", customEventOptions));
|
|
823
|
+
this.#dispatchPrefViewerEvent("pref-viewer-2d-error", detail ?? {});
|
|
824
|
+
this.#dispatchPrefViewerEvent("drawing-error", detail ?? {}); // Legacy event for backward compatibility
|
|
825
|
+
this.#dispatchPrefViewerEvent("pref-viewer-error", { error: detail?.error ?? detail ?? null });
|
|
750
826
|
}
|
|
751
827
|
|
|
752
828
|
/**
|
|
753
|
-
* Handles the "
|
|
754
|
-
* Dispatches
|
|
829
|
+
* Handles the "pref-viewer-2d-viewport-changed" event from the 2D viewer component.
|
|
830
|
+
* Dispatches the public 2D viewport event, forwarding the event detail to external listeners.
|
|
755
831
|
* @private
|
|
756
832
|
* @param {CustomEvent} event - The original zoom change event from the 2D viewer.
|
|
757
833
|
* @returns {void}
|
|
@@ -759,29 +835,35 @@ export default class PrefViewer extends HTMLElement {
|
|
|
759
835
|
#on2DZoomChanged(event) {
|
|
760
836
|
event.stopPropagation();
|
|
761
837
|
event.preventDefault();
|
|
762
|
-
const
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
838
|
+
const detail = { ...(event.detail ?? {}) };
|
|
839
|
+
this.#dispatchPrefViewerEvent("pref-viewer-2d-viewport-changed", detail);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/**
|
|
843
|
+
* Proxy semantic click ids from the encapsulated 2D component.
|
|
844
|
+
* @private
|
|
845
|
+
* @param {CustomEvent} event - Internal component event.
|
|
846
|
+
* @returns {void}
|
|
847
|
+
*/
|
|
848
|
+
#on2DElementClick(event) {
|
|
849
|
+
event.stopPropagation();
|
|
850
|
+
this.notifyElementClick(event.detail?.ids ?? []);
|
|
769
851
|
}
|
|
770
852
|
|
|
771
853
|
/**
|
|
772
|
-
* Handles the custom "pref-viewer-
|
|
854
|
+
* Handles the custom "pref-viewer-3d-render-settings-change-requested" event emitted by the menu component.
|
|
773
855
|
* Forwards the requested settings to the render-application pipeline.
|
|
774
856
|
* @private
|
|
775
857
|
* @param {CustomEvent} event - Menu event containing the `settings` payload.
|
|
776
858
|
* @returns {void}
|
|
777
859
|
*/
|
|
778
|
-
#onMenuApply(event) {
|
|
860
|
+
async #onMenuApply(event) {
|
|
779
861
|
event.stopPropagation();
|
|
780
862
|
const settings = event.detail?.settings;
|
|
781
863
|
if (!settings) {
|
|
782
864
|
return;
|
|
783
865
|
}
|
|
784
|
-
this.#applyRenderSettings(settings);
|
|
866
|
+
await this.#applyRenderSettings(settings);
|
|
785
867
|
}
|
|
786
868
|
|
|
787
869
|
/**
|
|
@@ -913,6 +995,17 @@ export default class PrefViewer extends HTMLElement {
|
|
|
913
995
|
}
|
|
914
996
|
}
|
|
915
997
|
|
|
998
|
+
/**
|
|
999
|
+
* Processes queued style overrides and advances the task queue.
|
|
1000
|
+
* @private
|
|
1001
|
+
* @param {object} styles - Style overrides to apply.
|
|
1002
|
+
* @returns {void}
|
|
1003
|
+
*/
|
|
1004
|
+
#processStyles(styles) {
|
|
1005
|
+
this.#applyStyles(styles);
|
|
1006
|
+
this.#processNextTask();
|
|
1007
|
+
}
|
|
1008
|
+
|
|
916
1009
|
/**
|
|
917
1010
|
* Processes visibility configuration for the model and scene.
|
|
918
1011
|
* Shows or hides the model and/or scene based on the config, then processes the next task.
|
|
@@ -936,6 +1029,112 @@ export default class PrefViewer extends HTMLElement {
|
|
|
936
1029
|
this.#processNextTask();
|
|
937
1030
|
}
|
|
938
1031
|
|
|
1032
|
+
/**
|
|
1033
|
+
* Constructs and dispatches a CustomEvent from the host element.
|
|
1034
|
+
* Strips any incoming `mode` field from the detail to avoid leaking stale values,
|
|
1035
|
+
* then re-attaches `mode` only for generic (non-scoped) event types.
|
|
1036
|
+
* Always appends a `timestamp` (Unix ms) so consumers can correlate events.
|
|
1037
|
+
* @private
|
|
1038
|
+
* @param {string} type - The event type string, e.g. "pref-viewer-loaded".
|
|
1039
|
+
* @param {object} [detail={}] - Arbitrary key/value pairs to include in `event.detail`.
|
|
1040
|
+
* @returns {void}
|
|
1041
|
+
*/
|
|
1042
|
+
#dispatchPrefViewerEvent(type, detail = {}) {
|
|
1043
|
+
const eventDetail = { ...(detail ?? {}) };
|
|
1044
|
+
delete eventDetail.mode;
|
|
1045
|
+
|
|
1046
|
+
const hasExplicitModeScope = type.startsWith("pref-viewer-2d-")
|
|
1047
|
+
|| type.startsWith("pref-viewer-3d-")
|
|
1048
|
+
|| type.startsWith("drawing-")
|
|
1049
|
+
|| type.startsWith("scene-");
|
|
1050
|
+
|
|
1051
|
+
if (!hasExplicitModeScope) {
|
|
1052
|
+
eventDetail.mode = this.#mode;
|
|
1053
|
+
}
|
|
1054
|
+
eventDetail.timestamp = Date.now();
|
|
1055
|
+
|
|
1056
|
+
this.dispatchEvent(
|
|
1057
|
+
new CustomEvent(type, {
|
|
1058
|
+
bubbles: true,
|
|
1059
|
+
cancelable: false,
|
|
1060
|
+
composed: true,
|
|
1061
|
+
detail: eventDetail,
|
|
1062
|
+
}),
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
/**
|
|
1067
|
+
* Normalizes a mixed array of element descriptors into a plain string-id list.
|
|
1068
|
+
* Accepts raw strings, objects with an `id` property, or objects with an `Id` property.
|
|
1069
|
+
* Non-string and empty-string results are filtered out.
|
|
1070
|
+
* @private
|
|
1071
|
+
* @param {Array<string|{id?: string, Id?: string}>} value - Array of id-like items to normalize.
|
|
1072
|
+
* @returns {string[]} Deduplicated, non-empty string ids.
|
|
1073
|
+
*/
|
|
1074
|
+
#normalizeIdList(value) {
|
|
1075
|
+
if (!Array.isArray(value)) {
|
|
1076
|
+
return [];
|
|
1077
|
+
}
|
|
1078
|
+
return value.map((item) => (typeof item === "string" ? item : (item?.id ?? item?.Id))).filter((id) => typeof id === "string" && id.length > 0);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
/**
|
|
1082
|
+
* Normalizes a mixed array of selection descriptors into a canonical list of selection items.
|
|
1083
|
+
* Each output item contains an `id` string and a `highlights` string array.
|
|
1084
|
+
* Accepts raw id strings (expanded to `{ id, highlights: [id] }`), or objects with
|
|
1085
|
+
* `id`/`Id` and optional `highlights`/`Highlights` fields.
|
|
1086
|
+
* Items that cannot be resolved to a non-empty string id are discarded.
|
|
1087
|
+
* @private
|
|
1088
|
+
* @param {Array<string|{id?: string, Id?: string, highlights?: string[], Highlights?: string[]}>} value - Raw selection descriptors.
|
|
1089
|
+
* @returns {Array<{id: string, highlights: string[]}>} Normalized, non-null selection items.
|
|
1090
|
+
*/
|
|
1091
|
+
#normalizeSelectionItems(value) {
|
|
1092
|
+
if (!Array.isArray(value)) {
|
|
1093
|
+
return [];
|
|
1094
|
+
}
|
|
1095
|
+
return value
|
|
1096
|
+
.map((item) => {
|
|
1097
|
+
if (typeof item === "string") {
|
|
1098
|
+
return { id: item, highlights: [item] };
|
|
1099
|
+
}
|
|
1100
|
+
const id = item?.id ?? item?.Id;
|
|
1101
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
1102
|
+
return null;
|
|
1103
|
+
}
|
|
1104
|
+
const highlights = item?.highlights ?? item?.Highlights ?? [id];
|
|
1105
|
+
return {
|
|
1106
|
+
id,
|
|
1107
|
+
highlights: this.#normalizeIdList(highlights),
|
|
1108
|
+
};
|
|
1109
|
+
})
|
|
1110
|
+
.filter(Boolean);
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
/**
|
|
1114
|
+
* Dispatches a `pref-viewer-selection-state-changed` event carrying the current selection snapshot.
|
|
1115
|
+
* Called whenever `selectableIds`, `selectedIds`, or `selectedItems` change.
|
|
1116
|
+
* @private
|
|
1117
|
+
* @returns {void}
|
|
1118
|
+
*/
|
|
1119
|
+
#emitSelectionStateChanged() {
|
|
1120
|
+
this.#dispatchPrefViewerEvent("pref-viewer-selection-state-changed", this.getSelectionState());
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
/** Apply the current semantic snapshot to the 2D renderer when available. */
|
|
1124
|
+
#applySelectionStateTo2D() {
|
|
1125
|
+
this.#component2D?.applySelectionState?.(this.getSelectionState());
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
/**
|
|
1129
|
+
* Dispatches a `pref-viewer-3d-turnable-changed` event carrying the current turnable snapshot.
|
|
1130
|
+
* Called after `startTurnable`, `stopTurnable`, or `toggleTurnable` mutates the internal state.
|
|
1131
|
+
* @private
|
|
1132
|
+
* @returns {void}
|
|
1133
|
+
*/
|
|
1134
|
+
#emitTurnableChanged() {
|
|
1135
|
+
this.#dispatchPrefViewerEvent("pref-viewer-3d-turnable-changed", { state: this.getTurnableState() });
|
|
1136
|
+
}
|
|
1137
|
+
|
|
939
1138
|
/**
|
|
940
1139
|
* ---------------------------
|
|
941
1140
|
* Public methods
|
|
@@ -1070,13 +1269,25 @@ export default class PrefViewer extends HTMLElement {
|
|
|
1070
1269
|
if (!config) {
|
|
1071
1270
|
return false;
|
|
1072
1271
|
}
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
this.#addTaskToQueue(config.drawing, "drawing");
|
|
1272
|
+
if (config.styles) {
|
|
1273
|
+
this.#addTaskToQueue(config.styles, "styles");
|
|
1076
1274
|
}
|
|
1077
1275
|
if (config.culture) {
|
|
1078
1276
|
this.#addTaskToQueue(config.culture, "culture");
|
|
1079
1277
|
}
|
|
1278
|
+
this.#addTaskToQueue(config, "config");
|
|
1279
|
+
if (config.drawing) {
|
|
1280
|
+
this.#addTaskToQueue(config.drawing, "drawing");
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
/**
|
|
1285
|
+
* Returns a diagnostic snapshot for the active 3D runtime.
|
|
1286
|
+
* @public
|
|
1287
|
+
* @returns {object|null} Debug information, or null when the 3D component is not initialized.
|
|
1288
|
+
*/
|
|
1289
|
+
getDebugInfo() {
|
|
1290
|
+
return this.#component3D?.getDebugInfo?.() ?? null;
|
|
1080
1291
|
}
|
|
1081
1292
|
|
|
1082
1293
|
/**
|
|
@@ -1188,6 +1399,236 @@ export default class PrefViewer extends HTMLElement {
|
|
|
1188
1399
|
this.#addTaskToQueue(options, "options");
|
|
1189
1400
|
}
|
|
1190
1401
|
|
|
1402
|
+
/**
|
|
1403
|
+
* Sets supported PrefViewer style variables from an object or JSON string and adds them to the task queue.
|
|
1404
|
+
* Currently supports `colorPrimary`, which updates `PrefViewerStyleVariables.colorPrimary`, applies the host CSS custom property, and persists the value in localStorage.
|
|
1405
|
+
* @public
|
|
1406
|
+
* @param {object|string} styles - Style overrides object or JSON string.
|
|
1407
|
+
* @returns {boolean|void} Returns false if styles are invalid; otherwise void.
|
|
1408
|
+
*/
|
|
1409
|
+
setStyles(styles) {
|
|
1410
|
+
styles = typeof styles === "string" ? JSON.parse(styles) : styles;
|
|
1411
|
+
if (!styles || typeof styles !== "object") {
|
|
1412
|
+
return false;
|
|
1413
|
+
}
|
|
1414
|
+
this.#addTaskToQueue(styles, "styles");
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
/**
|
|
1418
|
+
* Sets image-based lighting options using the stable contract entry point.
|
|
1419
|
+
* @public
|
|
1420
|
+
* @param {object|string} ibl - IBL options payload.
|
|
1421
|
+
* @returns {boolean|void} Returns false if options are invalid; otherwise void.
|
|
1422
|
+
*/
|
|
1423
|
+
setIBL(ibl) {
|
|
1424
|
+
ibl = typeof ibl === "string" ? JSON.parse(ibl) : ibl;
|
|
1425
|
+
if (!ibl) {
|
|
1426
|
+
return false;
|
|
1427
|
+
}
|
|
1428
|
+
return this.setOptions({ ibl });
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
/**
|
|
1432
|
+
* Defines the elements that the host application considers selectable.
|
|
1433
|
+
* The normalized state is rendered by the 2D component as non-destructive yellow SVG clones.
|
|
1434
|
+
* @public
|
|
1435
|
+
* @param {Array<string|{id?: string, Id?: string}>} elements - Element ids or objects containing id/Id.
|
|
1436
|
+
* @returns {object} Current selection state.
|
|
1437
|
+
*/
|
|
1438
|
+
setSelectableElements(elements = []) {
|
|
1439
|
+
this.#selectionState.selectableIds = this.#normalizeIdList(elements);
|
|
1440
|
+
this.#applySelectionStateTo2D();
|
|
1441
|
+
this.#emitSelectionStateChanged();
|
|
1442
|
+
return this.getSelectionState();
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
/**
|
|
1446
|
+
* Defines the currently selected elements using the semantic PrefViewer contract.
|
|
1447
|
+
* @public
|
|
1448
|
+
* @param {Array<string|{id?: string, Id?: string, highlights?: string[], Highlights?: string[]}>} elements - Element ids or objects containing id/Id and optional highlights/Highlights.
|
|
1449
|
+
* @returns {object} Current selection state.
|
|
1450
|
+
*/
|
|
1451
|
+
setSelectedElements(elements = []) {
|
|
1452
|
+
const selectedItems = this.#normalizeSelectionItems(elements);
|
|
1453
|
+
this.#selectionState.selectedItems = selectedItems;
|
|
1454
|
+
this.#selectionState.selectedIds = selectedItems.map((item) => item.id);
|
|
1455
|
+
this.#applySelectionStateTo2D();
|
|
1456
|
+
this.#emitSelectionStateChanged();
|
|
1457
|
+
return this.getSelectionState();
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
/**
|
|
1461
|
+
* Applies a complete host selection state in one call.
|
|
1462
|
+
* @public
|
|
1463
|
+
* @param {{selectableIds?: Array<string|object>, selectables?: Array<string|object>, selectedItems?: Array<string|object>, selectedIds?: Array<string|object>, selected?: Array<string|object>}} state - Selection state with selectableIds/selectables and selectedItems/selectedIds/selected.
|
|
1464
|
+
* @returns {object} Current selection state.
|
|
1465
|
+
*/
|
|
1466
|
+
applySelectionState(state = {}) {
|
|
1467
|
+
this.#selectionState.selectableIds = this.#normalizeIdList(state.selectableIds ?? state.selectables ?? []);
|
|
1468
|
+
const selectedItems = this.#normalizeSelectionItems(state.selectedItems ?? state.selectedIds ?? state.selected ?? []);
|
|
1469
|
+
this.#selectionState.selectedItems = selectedItems;
|
|
1470
|
+
this.#selectionState.selectedIds = selectedItems.map((item) => item.id);
|
|
1471
|
+
this.#applySelectionStateTo2D();
|
|
1472
|
+
this.#emitSelectionStateChanged();
|
|
1473
|
+
return this.getSelectionState();
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
/**
|
|
1477
|
+
* Clears selected elements while keeping the selectable set.
|
|
1478
|
+
* @public
|
|
1479
|
+
* @returns {object} Current selection state.
|
|
1480
|
+
*/
|
|
1481
|
+
clearSelectedElements() {
|
|
1482
|
+
this.#selectionState.selectedIds = [];
|
|
1483
|
+
this.#selectionState.selectedItems = [];
|
|
1484
|
+
this.#applySelectionStateTo2D();
|
|
1485
|
+
this.#emitSelectionStateChanged();
|
|
1486
|
+
return this.getSelectionState();
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
/**
|
|
1490
|
+
* Clears selectable elements while keeping the current selected state.
|
|
1491
|
+
* @public
|
|
1492
|
+
* @returns {object} Current selection state.
|
|
1493
|
+
*/
|
|
1494
|
+
clearSelectableElements() {
|
|
1495
|
+
this.#selectionState.selectableIds = [];
|
|
1496
|
+
this.#applySelectionStateTo2D();
|
|
1497
|
+
this.#emitSelectionStateChanged();
|
|
1498
|
+
return this.getSelectionState();
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
/**
|
|
1502
|
+
* Clears all semantic selection state.
|
|
1503
|
+
* @public
|
|
1504
|
+
* @returns {object} Current selection state.
|
|
1505
|
+
*/
|
|
1506
|
+
clearSelectionState() {
|
|
1507
|
+
this.#selectionState = {
|
|
1508
|
+
selectableIds: [],
|
|
1509
|
+
selectedIds: [],
|
|
1510
|
+
selectedItems: [],
|
|
1511
|
+
};
|
|
1512
|
+
this.#applySelectionStateTo2D();
|
|
1513
|
+
this.#emitSelectionStateChanged();
|
|
1514
|
+
return this.getSelectionState();
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
/**
|
|
1518
|
+
* Returns the current semantic selection state.
|
|
1519
|
+
* @public
|
|
1520
|
+
* @returns {{selectableIds: string[], selectedIds: string[], selectedItems: object[]}}
|
|
1521
|
+
*/
|
|
1522
|
+
getSelectionState() {
|
|
1523
|
+
return {
|
|
1524
|
+
selectableIds: [...this.#selectionState.selectableIds],
|
|
1525
|
+
selectedIds: [...this.#selectionState.selectedIds],
|
|
1526
|
+
selectedItems: this.#selectionState.selectedItems.map((item) => ({
|
|
1527
|
+
id: item.id,
|
|
1528
|
+
highlights: [...item.highlights],
|
|
1529
|
+
})),
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
/**
|
|
1534
|
+
* Publishes host-forwarded semantic ids through the same exact event contract as 2D hit-testing.
|
|
1535
|
+
* @public
|
|
1536
|
+
* @param {object|string|Array<object|string>} element - Element id, descriptor, collection, or `{ ids }` detail.
|
|
1537
|
+
* @returns {void}
|
|
1538
|
+
*/
|
|
1539
|
+
notifyElementClick(element) {
|
|
1540
|
+
let candidates;
|
|
1541
|
+
if (Array.isArray(element)) {
|
|
1542
|
+
candidates = element;
|
|
1543
|
+
} else if (Array.isArray(element?.ids)) {
|
|
1544
|
+
candidates = element.ids;
|
|
1545
|
+
} else {
|
|
1546
|
+
candidates = [element?.element ?? element];
|
|
1547
|
+
}
|
|
1548
|
+
const ids = candidates
|
|
1549
|
+
.map((candidate) => typeof candidate === "string" ? candidate : (candidate?.id ?? candidate?.Id))
|
|
1550
|
+
.filter((id) => typeof id === "string" && id.length > 0);
|
|
1551
|
+
if (ids.length === 0) {
|
|
1552
|
+
return;
|
|
1553
|
+
}
|
|
1554
|
+
this.dispatchEvent(new CustomEvent("pref-viewer-element-click", {
|
|
1555
|
+
bubbles: true,
|
|
1556
|
+
cancelable: true,
|
|
1557
|
+
composed: true,
|
|
1558
|
+
detail: { ids },
|
|
1559
|
+
}));
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
/**
|
|
1563
|
+
* Add a host-owned SVG node through the public root facade.
|
|
1564
|
+
* @public
|
|
1565
|
+
* @param {string} id - Owner id.
|
|
1566
|
+
* @param {string} svgId - Stable selector/id.
|
|
1567
|
+
* @param {SVGElement} svg - SVG node.
|
|
1568
|
+
* @returns {boolean} True when appended.
|
|
1569
|
+
*/
|
|
1570
|
+
appendSvgNode(id, svgId, svg) {
|
|
1571
|
+
return this.#component2D?.appendSvgNode?.(id, svgId, svg) ?? false;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
/**
|
|
1575
|
+
* Remove every SVG node previously added through appendSvgNode.
|
|
1576
|
+
* @public
|
|
1577
|
+
* @returns {void}
|
|
1578
|
+
* */
|
|
1579
|
+
removeAppendedSvgNodes() {
|
|
1580
|
+
this.#component2D?.removeAppendedSvgNodes?.();
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
/**
|
|
1584
|
+
* Starts the semantic turnable state.
|
|
1585
|
+
* @public
|
|
1586
|
+
* @param {{speed?: number, direction?: string}} [options] - Optional speed/direction values.
|
|
1587
|
+
* @returns {object} Current turnable state.
|
|
1588
|
+
*/
|
|
1589
|
+
startTurnable(options = {}) {
|
|
1590
|
+
this.#turnableState = {
|
|
1591
|
+
...this.#turnableState,
|
|
1592
|
+
...options,
|
|
1593
|
+
running: true,
|
|
1594
|
+
};
|
|
1595
|
+
this.#emitTurnableChanged();
|
|
1596
|
+
return this.getTurnableState();
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
/**
|
|
1600
|
+
* Stops the semantic turnable state.
|
|
1601
|
+
* @public
|
|
1602
|
+
* @returns {object} Current turnable state.
|
|
1603
|
+
*/
|
|
1604
|
+
stopTurnable() {
|
|
1605
|
+
this.#turnableState = {
|
|
1606
|
+
...this.#turnableState,
|
|
1607
|
+
running: false,
|
|
1608
|
+
};
|
|
1609
|
+
this.#emitTurnableChanged();
|
|
1610
|
+
return this.getTurnableState();
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
/**
|
|
1614
|
+
* Toggles the semantic turnable state.
|
|
1615
|
+
* @public
|
|
1616
|
+
* @param {object} [options] - Optional speed/direction values used when starting.
|
|
1617
|
+
* @returns {object} Current turnable state.
|
|
1618
|
+
*/
|
|
1619
|
+
toggleTurnable(options = {}) {
|
|
1620
|
+
return this.#turnableState.running ? this.stopTurnable() : this.startTurnable(options);
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
/**
|
|
1624
|
+
* Returns the current semantic turnable state.
|
|
1625
|
+
* @public
|
|
1626
|
+
* @returns {{running: boolean, speed: number, direction: string}}
|
|
1627
|
+
*/
|
|
1628
|
+
getTurnableState() {
|
|
1629
|
+
return { ...this.#turnableState };
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1191
1632
|
/**
|
|
1192
1633
|
* Opens a modal dialog with the specified title, content, and footer.
|
|
1193
1634
|
* @public
|
|
@@ -1433,31 +1874,20 @@ export default class PrefViewer extends HTMLElement {
|
|
|
1433
1874
|
}
|
|
1434
1875
|
|
|
1435
1876
|
/**
|
|
1436
|
-
*
|
|
1877
|
+
* Sends style updates to `setStyles()` enabling reactive bindings.
|
|
1437
1878
|
* @public
|
|
1438
|
-
* @param {object|string} value -
|
|
1879
|
+
* @param {object|string} value - Style overrides object or JSON string.
|
|
1439
1880
|
*/
|
|
1440
|
-
set
|
|
1441
|
-
this.
|
|
1881
|
+
set styles(value) {
|
|
1882
|
+
this.setStyles(value);
|
|
1442
1883
|
}
|
|
1443
|
-
|
|
1444
1884
|
/**
|
|
1445
|
-
*
|
|
1446
|
-
* Accepts boolean or string "true"/"false".
|
|
1447
|
-
* @public
|
|
1448
|
-
* @param {boolean|string} value
|
|
1449
|
-
*/
|
|
1450
|
-
set highlightEnabled(value) {
|
|
1451
|
-
this.setAttribute("highlight-enabled", String(value));
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
/**
|
|
1455
|
-
* Sets the hover highlight color at runtime.
|
|
1885
|
+
* Redirects scene assignments to `loadScene()` for normalized handling.
|
|
1456
1886
|
* @public
|
|
1457
|
-
* @param {string} value -
|
|
1887
|
+
* @param {object|string} value - Scene payload or serialized JSON.
|
|
1458
1888
|
*/
|
|
1459
|
-
set
|
|
1460
|
-
this.
|
|
1889
|
+
set scene(value) {
|
|
1890
|
+
this.loadScene(value);
|
|
1461
1891
|
}
|
|
1462
1892
|
|
|
1463
1893
|
/**
|