@preference-sl/pref-viewer 2.14.0-beta.2 → 2.14.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/babylonjs-animation-controller.js +119 -3
- package/src/babylonjs-animation-opening.js +59 -0
- package/src/babylonjs-controller.js +113 -0
- package/src/localization/translations.js +16 -0
- package/src/pref-viewer-3d.js +81 -2
- package/src/pref-viewer-menu-3d.js +125 -6
- package/src/pref-viewer.js +113 -1
- package/src/styles.js +97 -1
package/package.json
CHANGED
|
@@ -45,15 +45,22 @@ export default class BabylonJSAnimationController {
|
|
|
45
45
|
#highlightColor = Color3.FromHexString(PrefViewerStyleVariables.colorPrimary);
|
|
46
46
|
#highlightLayer = null;
|
|
47
47
|
#overlayLayer = null;
|
|
48
|
-
#useHighlightLayer = true;
|
|
49
|
-
#lastHighlightedMeshId = null;
|
|
50
|
-
#lastHighlightedNodeIds = [];
|
|
48
|
+
#useHighlightLayer = true;
|
|
49
|
+
#lastHighlightedMeshId = null;
|
|
50
|
+
#lastHighlightedNodeIds = [];
|
|
51
|
+
|
|
52
|
+
// Static property to store global highlight enabled state
|
|
53
|
+
static #globalHighlightEnabled = true;
|
|
54
|
+
static #instanceId = 0;
|
|
55
|
+
#thisInstanceId = 0;
|
|
51
56
|
|
|
52
57
|
/**
|
|
53
58
|
* Creates a new BabylonJSAnimationController for a Babylon.js scene.
|
|
54
59
|
* @param {AssetContainer|Scene} assetContainer - The Babylon.js asset container or scene instance.
|
|
55
60
|
*/
|
|
56
61
|
constructor(assetContainer) {
|
|
62
|
+
BabylonJSAnimationController.#instanceId++;
|
|
63
|
+
this.#thisInstanceId = BabylonJSAnimationController.#instanceId;
|
|
57
64
|
this.#scene = assetContainer.scene ? assetContainer.scene : assetContainer;
|
|
58
65
|
this.#assetContainer = assetContainer;
|
|
59
66
|
this.#canvas = this.#scene._engine._renderingCanvas;
|
|
@@ -459,6 +466,17 @@ export default class BabylonJSAnimationController {
|
|
|
459
466
|
* Returns whether highlight visuals changed in this call and whether any mesh remains highlighted.
|
|
460
467
|
*/
|
|
461
468
|
highlightMeshes(pickingInfo) {
|
|
469
|
+
// If highlight is disabled globally, just remove any existing highlight and return
|
|
470
|
+
if (!BabylonJSAnimationController.#globalHighlightEnabled) {
|
|
471
|
+
if (this.#lastHighlightedMeshId !== null) {
|
|
472
|
+
this.#removeHighlight();
|
|
473
|
+
this.#lastHighlightedMeshId = null;
|
|
474
|
+
this.#lastHighlightedNodeIds = [];
|
|
475
|
+
return { changed: true, highlighted: false };
|
|
476
|
+
}
|
|
477
|
+
return { changed: false, highlighted: false };
|
|
478
|
+
}
|
|
479
|
+
|
|
462
480
|
let changed = false;
|
|
463
481
|
let highlighted = false;
|
|
464
482
|
|
|
@@ -504,6 +522,40 @@ export default class BabylonJSAnimationController {
|
|
|
504
522
|
return { changed: changed, highlighted: highlighted };
|
|
505
523
|
}
|
|
506
524
|
|
|
525
|
+
/**
|
|
526
|
+
* Configures the highlight settings for animated nodes on hover.
|
|
527
|
+
* @public
|
|
528
|
+
* @param {{enabled?: boolean, color?: string}} config - Configuration object.
|
|
529
|
+
* @param {boolean} [config.enabled] - Whether to enable highlight on hover. If false, removes highlight.
|
|
530
|
+
* @param {string} [config.color] - Hex color string for the highlight (e.g., "#FF0000"). Only used if enabled is not false.
|
|
531
|
+
* @returns {void}
|
|
532
|
+
*/
|
|
533
|
+
setHighlightConfig(config = {}) {
|
|
534
|
+
// Handle enabled state
|
|
535
|
+
if (config.enabled !== undefined) {
|
|
536
|
+
BabylonJSAnimationController.#globalHighlightEnabled = config.enabled;
|
|
537
|
+
if (config.enabled === false) {
|
|
538
|
+
this.#removeHighlight();
|
|
539
|
+
this.#lastHighlightedMeshId = null;
|
|
540
|
+
this.#lastHighlightedNodeIds = [];
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Update color regardless of enabled state so it's ready when re-enabled
|
|
545
|
+
if (config.color) {
|
|
546
|
+
try {
|
|
547
|
+
this.#highlightColor = Color3.FromHexString(config.color);
|
|
548
|
+
} catch (e) {
|
|
549
|
+
console.warn("Invalid highlight color:", config.color);
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
// Re-apply highlight with new color if meshes are currently highlighted
|
|
553
|
+
if (BabylonJSAnimationController.#globalHighlightEnabled && this.#lastHighlightedNodeIds.length > 0) {
|
|
554
|
+
this.#addHighlight(this.#lastHighlightedNodeIds);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
507
559
|
/**
|
|
508
560
|
* Hides and disposes the animation control menu if it exists.
|
|
509
561
|
* @public
|
|
@@ -514,6 +566,70 @@ export default class BabylonJSAnimationController {
|
|
|
514
566
|
this.#canvas?.parentElement?.querySelectorAll("div.pref-viewer-3d.animation-menu").forEach((menu) => menu.remove());
|
|
515
567
|
}
|
|
516
568
|
|
|
569
|
+
/**
|
|
570
|
+
* Returns a snapshot of all available animations and their current state.
|
|
571
|
+
* @public
|
|
572
|
+
* @returns {Array<{name: string, state: number, progress: number, loop: boolean, nodes: string[]}>}
|
|
573
|
+
*/
|
|
574
|
+
getAnimations() {
|
|
575
|
+
return this.#openingAnimations.map((anim) => ({
|
|
576
|
+
name: anim.name,
|
|
577
|
+
state: anim.state,
|
|
578
|
+
progress: anim.progress,
|
|
579
|
+
loop: anim.loop,
|
|
580
|
+
nodes: anim.nodes ?? [],
|
|
581
|
+
}));
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Controls a named animation by applying the specified action.
|
|
586
|
+
* @public
|
|
587
|
+
* @param {string} name - The name of the animation to control.
|
|
588
|
+
* @param {"open"|"close"|"pause"|"goToOpened"|"goToClosed"} action - The action to apply.
|
|
589
|
+
* @returns {boolean} True if the animation was found and the action was applied; otherwise false.
|
|
590
|
+
*/
|
|
591
|
+
playAnimation(name, action) {
|
|
592
|
+
const anim = this.#openingAnimations.find((a) => a.name === name);
|
|
593
|
+
if (!anim) return false;
|
|
594
|
+
switch (action) {
|
|
595
|
+
case "open": anim.playOpen(); break;
|
|
596
|
+
case "close": anim.playClose(); break;
|
|
597
|
+
case "pause": anim.pause(); break;
|
|
598
|
+
case "goToOpened": anim.goToOpened(); break;
|
|
599
|
+
case "goToClosed": anim.goToClosed(); break;
|
|
600
|
+
default: return false;
|
|
601
|
+
}
|
|
602
|
+
return true;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Seeks a named animation to the given normalized progress position (0–1).
|
|
607
|
+
* @public
|
|
608
|
+
* @param {string} name - The name of the animation to seek.
|
|
609
|
+
* @param {number} progress - Progress value between 0 and 1.
|
|
610
|
+
* @returns {boolean} True if the animation was found and seeked; otherwise false.
|
|
611
|
+
*/
|
|
612
|
+
setAnimationProgress(name, progress) {
|
|
613
|
+
const anim = this.#openingAnimations.find((a) => a.name === name);
|
|
614
|
+
if (!anim) return false;
|
|
615
|
+
anim.setProgress(progress);
|
|
616
|
+
return true;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Enables or disables loop mode for a named animation.
|
|
621
|
+
* @public
|
|
622
|
+
* @param {string} name - The name of the animation to configure.
|
|
623
|
+
* @param {boolean} loop - True to enable looping; false to disable.
|
|
624
|
+
* @returns {boolean} True if the animation was found and loop mode was set; otherwise false.
|
|
625
|
+
*/
|
|
626
|
+
setAnimationLoop(name, loop) {
|
|
627
|
+
const anim = this.#openingAnimations.find((a) => a.name === name);
|
|
628
|
+
if (!anim) return false;
|
|
629
|
+
anim.setLoop(loop);
|
|
630
|
+
return true;
|
|
631
|
+
}
|
|
632
|
+
|
|
517
633
|
/**
|
|
518
634
|
* Displays the animation control menu for the animated node under the pointer.
|
|
519
635
|
* Hides the current menu when picking info is invalid or when no animated node is found.
|
|
@@ -589,4 +589,63 @@ export default class OpeningAnimation {
|
|
|
589
589
|
get state() {
|
|
590
590
|
return this.#state;
|
|
591
591
|
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Returns the normalized progress (0–1) of the current animation.
|
|
595
|
+
* @public
|
|
596
|
+
* @returns {number}
|
|
597
|
+
*/
|
|
598
|
+
get progress() {
|
|
599
|
+
return this.#getProgress();
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Returns whether loop mode is currently enabled for this animation.
|
|
604
|
+
* @public
|
|
605
|
+
* @returns {boolean}
|
|
606
|
+
*/
|
|
607
|
+
get loop() {
|
|
608
|
+
return this.#loop;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Returns the node IDs affected by this animation.
|
|
613
|
+
* @public
|
|
614
|
+
* @returns {string[]}
|
|
615
|
+
*/
|
|
616
|
+
get nodes() {
|
|
617
|
+
return [...this.#nodes];
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Seeks the animation to the given normalized progress position (0–1).
|
|
622
|
+
* Mirrors the same threshold/snap logic used by the animation control menu.
|
|
623
|
+
* @public
|
|
624
|
+
* @param {number} progress - Progress value between 0 and 1.
|
|
625
|
+
* @returns {void}
|
|
626
|
+
*/
|
|
627
|
+
setProgress(progress) {
|
|
628
|
+
progress = this.#checkProgress(progress);
|
|
629
|
+
if (progress === 0) {
|
|
630
|
+
this.goToClosed();
|
|
631
|
+
} else if (progress === 1) {
|
|
632
|
+
this.goToOpened();
|
|
633
|
+
} else {
|
|
634
|
+
const frame = this.#getFrameFromProgress(progress);
|
|
635
|
+
this.#goToFrameAndPause(frame);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Enables or disables loop mode for this animation.
|
|
641
|
+
* @public
|
|
642
|
+
* @param {boolean} loop - True to enable looping; false to disable.
|
|
643
|
+
* @returns {void}
|
|
644
|
+
*/
|
|
645
|
+
setLoop(loop) {
|
|
646
|
+
this.#loop = Boolean(loop);
|
|
647
|
+
if (this.#menu) {
|
|
648
|
+
this.#menu.animationLoop = this.#loop;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
592
651
|
}
|
|
@@ -85,6 +85,9 @@ export default class BabylonJSController {
|
|
|
85
85
|
ambientOcclusionEnabled: true,
|
|
86
86
|
iblEnabled: true,
|
|
87
87
|
shadowsEnabled: false,
|
|
88
|
+
// Highlight settings
|
|
89
|
+
highlightEnabled: true,
|
|
90
|
+
highlightColor: "#ff6700",
|
|
88
91
|
};
|
|
89
92
|
|
|
90
93
|
// Canvas HTML element
|
|
@@ -496,10 +499,16 @@ export default class BabylonJSController {
|
|
|
496
499
|
|
|
497
500
|
let changed = false;
|
|
498
501
|
Object.keys(settings).forEach((key) => {
|
|
502
|
+
// Handle boolean settings
|
|
499
503
|
if (typeof settings[key] === "boolean" && this.#settings[key] !== settings[key]) {
|
|
500
504
|
this.#settings[key] = settings[key];
|
|
501
505
|
changed = true;
|
|
502
506
|
}
|
|
507
|
+
// Handle string settings (like illuminationColor)
|
|
508
|
+
if (typeof settings[key] === "string" && this.#settings[key] !== settings[key]) {
|
|
509
|
+
this.#settings[key] = settings[key];
|
|
510
|
+
changed = true;
|
|
511
|
+
}
|
|
503
512
|
});
|
|
504
513
|
|
|
505
514
|
if (changed) {
|
|
@@ -526,9 +535,14 @@ export default class BabylonJSController {
|
|
|
526
535
|
}
|
|
527
536
|
const parsed = JSON.parse(serialized);
|
|
528
537
|
Object.keys(BabylonJSController.DEFAULT_RENDER_SETTINGS).forEach((key) => {
|
|
538
|
+
// Handle boolean settings
|
|
529
539
|
if (typeof parsed?.[key] === "boolean") {
|
|
530
540
|
this.#settings[key] = parsed[key];
|
|
531
541
|
}
|
|
542
|
+
// Handle string settings (like illuminationColor)
|
|
543
|
+
if (typeof parsed?.[key] === "string") {
|
|
544
|
+
this.#settings[key] = parsed[key];
|
|
545
|
+
}
|
|
532
546
|
});
|
|
533
547
|
} catch (error) {
|
|
534
548
|
console.warn("PrefViewer: unable to load render settings", error);
|
|
@@ -2458,6 +2472,11 @@ export default class BabylonJSController {
|
|
|
2458
2472
|
this.#checkModelMetadata(oldModelMetadata, newModelMetadata);
|
|
2459
2473
|
this.#setMaxSimultaneousLights();
|
|
2460
2474
|
this.#babylonJSAnimationController = new BabylonJSAnimationController(this.#containers.model.assetContainer);
|
|
2475
|
+
// Apply stored highlight settings so fresh page loads respect persisted state
|
|
2476
|
+
this.#setHighlightConfig({
|
|
2477
|
+
enabled: this.#settings.highlightEnabled,
|
|
2478
|
+
color: this.#settings.highlightColor,
|
|
2479
|
+
});
|
|
2461
2480
|
if (this.#babylonJSAnimationController?.hasAnimations?.()) {
|
|
2462
2481
|
this.#attachAnimationChangedListener();
|
|
2463
2482
|
}
|
|
@@ -3019,4 +3038,98 @@ export default class BabylonJSController {
|
|
|
3019
3038
|
async reloadWithCurrentSettings() {
|
|
3020
3039
|
return await this.#loadContainers();
|
|
3021
3040
|
}
|
|
3041
|
+
|
|
3042
|
+
/**
|
|
3043
|
+
* Sets highlight configuration for animation hover effects.
|
|
3044
|
+
* @public
|
|
3045
|
+
* @param {{showAnimationMenu?:boolean, highlightColor?:string, highlightEnabled?:boolean}} config - Highlight configuration.
|
|
3046
|
+
* @returns {void}
|
|
3047
|
+
*/
|
|
3048
|
+
setIlluminationConfig(config = {}) {
|
|
3049
|
+
|
|
3050
|
+
if (config.showAnimationMenu !== undefined) {
|
|
3051
|
+
// Handle show-animation-menu via the 3D component's parent
|
|
3052
|
+
// This is handled in pref-viewer-3d.js attributeChangedCallback
|
|
3053
|
+
}
|
|
3054
|
+
|
|
3055
|
+
// Handle highlight color for animation hover
|
|
3056
|
+
if (config.highlightColor !== undefined || config.highlightEnabled !== undefined) {
|
|
3057
|
+
// Normalize highlightEnabled: accept boolean, string "true"/"false", or null
|
|
3058
|
+
let enabled = config.highlightEnabled;
|
|
3059
|
+
if (typeof enabled === "string") {
|
|
3060
|
+
enabled = enabled.toLowerCase() !== "false";
|
|
3061
|
+
} else if (enabled === null) {
|
|
3062
|
+
enabled = undefined;
|
|
3063
|
+
}
|
|
3064
|
+
// Keep #settings in sync so getRenderSettings() always reflects the real state
|
|
3065
|
+
if (enabled !== undefined) {
|
|
3066
|
+
this.#settings.highlightEnabled = enabled;
|
|
3067
|
+
}
|
|
3068
|
+
if (config.highlightColor !== undefined && config.highlightColor !== null) {
|
|
3069
|
+
this.#settings.highlightColor = config.highlightColor;
|
|
3070
|
+
}
|
|
3071
|
+
this.#setHighlightConfig({
|
|
3072
|
+
color: config.highlightColor ?? undefined,
|
|
3073
|
+
enabled,
|
|
3074
|
+
});
|
|
3075
|
+
}
|
|
3076
|
+
|
|
3077
|
+
// Persist settings
|
|
3078
|
+
this.#storeRenderSettings();
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
/**
|
|
3082
|
+
* Returns a snapshot of all available animations and their current state.
|
|
3083
|
+
* @public
|
|
3084
|
+
* @returns {Array<{name: string, state: number, progress: number, loop: boolean, nodes: string[]}>}
|
|
3085
|
+
*/
|
|
3086
|
+
getAnimations() {
|
|
3087
|
+
return this.#babylonJSAnimationController?.getAnimations() ?? [];
|
|
3088
|
+
}
|
|
3089
|
+
|
|
3090
|
+
/**
|
|
3091
|
+
* Controls a named animation by applying the specified action.
|
|
3092
|
+
* @public
|
|
3093
|
+
* @param {string} name - The name of the animation to control.
|
|
3094
|
+
* @param {"open"|"close"|"pause"|"goToOpened"|"goToClosed"} action - The action to apply.
|
|
3095
|
+
* @returns {boolean} True if the animation was found and the action was applied; otherwise false.
|
|
3096
|
+
*/
|
|
3097
|
+
playAnimation(name, action) {
|
|
3098
|
+
return this.#babylonJSAnimationController?.playAnimation(name, action) ?? false;
|
|
3099
|
+
}
|
|
3100
|
+
|
|
3101
|
+
/**
|
|
3102
|
+
* Seeks a named animation to the given normalized progress position (0–1).
|
|
3103
|
+
* @public
|
|
3104
|
+
* @param {string} name - The name of the animation to seek.
|
|
3105
|
+
* @param {number} progress - Progress value between 0 and 1.
|
|
3106
|
+
* @returns {boolean} True if the animation was found and seeked; otherwise false.
|
|
3107
|
+
*/
|
|
3108
|
+
setAnimationProgress(name, progress) {
|
|
3109
|
+
return this.#babylonJSAnimationController?.setAnimationProgress(name, progress) ?? false;
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
/**
|
|
3113
|
+
* Enables or disables loop mode for a named animation.
|
|
3114
|
+
* @public
|
|
3115
|
+
* @param {string} name - The name of the animation to configure.
|
|
3116
|
+
* @param {boolean} loop - True to enable looping; false to disable.
|
|
3117
|
+
* @returns {boolean} True if the animation was found and loop mode was set; otherwise false.
|
|
3118
|
+
*/
|
|
3119
|
+
setAnimationLoop(name, loop) {
|
|
3120
|
+
return this.#babylonJSAnimationController?.setAnimationLoop(name, loop) ?? false;
|
|
3121
|
+
}
|
|
3122
|
+
|
|
3123
|
+
/**
|
|
3124
|
+
* Configures the highlight settings for animated nodes on hover.
|
|
3125
|
+
* @private
|
|
3126
|
+
* @param {{enabled?: boolean, color?: string}} config - Configuration object.
|
|
3127
|
+
* @returns {void}
|
|
3128
|
+
*/
|
|
3129
|
+
#setHighlightConfig(config = {}) {
|
|
3130
|
+
if (this.#babylonJSAnimationController && typeof this.#babylonJSAnimationController.setHighlightConfig === "function") {
|
|
3131
|
+
this.#babylonJSAnimationController.setHighlightConfig(config);
|
|
3132
|
+
this.#requestRender({ frames: 2 });
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3022
3135
|
}
|
|
@@ -29,6 +29,14 @@ export const translations = {
|
|
|
29
29
|
label: "Shadows",
|
|
30
30
|
helper: "Enables shadows.",
|
|
31
31
|
},
|
|
32
|
+
highlightEnabled: {
|
|
33
|
+
label: "Highlight",
|
|
34
|
+
helper: "Highlight animated elements on hover.",
|
|
35
|
+
},
|
|
36
|
+
highlightColor: {
|
|
37
|
+
label: "Highlight Color",
|
|
38
|
+
helper: "Color for hover highlight on animations.",
|
|
39
|
+
},
|
|
32
40
|
},
|
|
33
41
|
},
|
|
34
42
|
downloadDialog: {
|
|
@@ -79,6 +87,14 @@ export const translations = {
|
|
|
79
87
|
label: "Sombras",
|
|
80
88
|
helper: "Activa sombras.",
|
|
81
89
|
},
|
|
90
|
+
highlightEnabled: {
|
|
91
|
+
label: "Resaltado",
|
|
92
|
+
helper: "Resaltar elementos animados al pasar el ratón.",
|
|
93
|
+
},
|
|
94
|
+
highlightColor: {
|
|
95
|
+
label: "Color de resaltado",
|
|
96
|
+
helper: "Color del resaltado al pasar sobre animaciones.",
|
|
97
|
+
},
|
|
82
98
|
},
|
|
83
99
|
},
|
|
84
100
|
downloadDialog: {
|
package/src/pref-viewer-3d.js
CHANGED
|
@@ -97,7 +97,7 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
97
97
|
* @returns {string[]} Array of attribute names that trigger attributeChangedCallback.
|
|
98
98
|
*/
|
|
99
99
|
static get observedAttributes() {
|
|
100
|
-
return ["show-model", "show-scene", "visible"];
|
|
100
|
+
return ["show-model", "show-scene", "visible", "show-animation-menu", "highlight-color", "highlight-enabled"];
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
/**
|
|
@@ -137,6 +137,18 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
137
137
|
this.hide();
|
|
138
138
|
}
|
|
139
139
|
break;
|
|
140
|
+
case "show-animation-menu":
|
|
141
|
+
case "highlight-color":
|
|
142
|
+
case "highlight-enabled":
|
|
143
|
+
// Pass these to BabylonJSController if initialized
|
|
144
|
+
if (this.#babylonJSController && typeof this.#babylonJSController.setIlluminationConfig === "function") {
|
|
145
|
+
this.#babylonJSController.setIlluminationConfig({
|
|
146
|
+
showAnimationMenu: this.getAttribute("show-animation-menu"),
|
|
147
|
+
highlightColor: this.getAttribute("highlight-color"),
|
|
148
|
+
highlightEnabled: this.getAttribute("highlight-enabled"),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
140
152
|
}
|
|
141
153
|
}
|
|
142
154
|
|
|
@@ -229,6 +241,16 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
229
241
|
#initializeBabylonJS() {
|
|
230
242
|
this.#babylonJSController = new BabylonJSController(this.#canvas, this.#data.containers, this.#data.options);
|
|
231
243
|
this.#babylonJSController.enable();
|
|
244
|
+
|
|
245
|
+
// Apply highlight attributes that were set before the controller was ready
|
|
246
|
+
const highlightColor = this.getAttribute("highlight-color");
|
|
247
|
+
const highlightEnabled = this.getAttribute("highlight-enabled");
|
|
248
|
+
if (highlightColor !== null || highlightEnabled !== null) {
|
|
249
|
+
this.#babylonJSController.setIlluminationConfig({
|
|
250
|
+
highlightColor: highlightColor ?? undefined,
|
|
251
|
+
highlightEnabled: highlightEnabled ?? undefined,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
232
254
|
}
|
|
233
255
|
|
|
234
256
|
/**
|
|
@@ -805,6 +827,48 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
805
827
|
return this.#babylonJSController.getRenderSettings();
|
|
806
828
|
}
|
|
807
829
|
|
|
830
|
+
/**
|
|
831
|
+
* Returns a snapshot of all available animations and their current state.
|
|
832
|
+
* @public
|
|
833
|
+
* @returns {Array<{name: string, state: number, progress: number, loop: boolean, nodes: string[]}>}
|
|
834
|
+
*/
|
|
835
|
+
getAnimations() {
|
|
836
|
+
return this.#babylonJSController?.getAnimations() ?? [];
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* Controls a named animation by applying the specified action.
|
|
841
|
+
* @public
|
|
842
|
+
* @param {string} name - The name of the animation to control.
|
|
843
|
+
* @param {"open"|"close"|"pause"|"goToOpened"|"goToClosed"} action - The action to apply.
|
|
844
|
+
* @returns {boolean} True if the animation was found and the action was applied; otherwise false.
|
|
845
|
+
*/
|
|
846
|
+
playAnimation(name, action) {
|
|
847
|
+
return this.#babylonJSController?.playAnimation(name, action) ?? false;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* Seeks a named animation to the given normalized progress position (0–1).
|
|
852
|
+
* @public
|
|
853
|
+
* @param {string} name - The name of the animation to seek.
|
|
854
|
+
* @param {number} progress - Progress value between 0 and 1.
|
|
855
|
+
* @returns {boolean} True if the animation was found and seeked; otherwise false.
|
|
856
|
+
*/
|
|
857
|
+
setAnimationProgress(name, progress) {
|
|
858
|
+
return this.#babylonJSController?.setAnimationProgress(name, progress) ?? false;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* Enables or disables loop mode for a named animation.
|
|
863
|
+
* @public
|
|
864
|
+
* @param {string} name - The name of the animation to configure.
|
|
865
|
+
* @param {boolean} loop - True to enable looping; false to disable.
|
|
866
|
+
* @returns {boolean} True if the animation was found and loop mode was set; otherwise false.
|
|
867
|
+
*/
|
|
868
|
+
setAnimationLoop(name, loop) {
|
|
869
|
+
return this.#babylonJSController?.setAnimationLoop(name, loop) ?? false;
|
|
870
|
+
}
|
|
871
|
+
|
|
808
872
|
/**
|
|
809
873
|
* Reports whether an IBL environment map is currently available.
|
|
810
874
|
* @public
|
|
@@ -832,7 +896,22 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
832
896
|
if (!this.#babylonJSController) {
|
|
833
897
|
return { changed: false, success: false };
|
|
834
898
|
}
|
|
835
|
-
|
|
899
|
+
|
|
900
|
+
// Handle highlight settings separately for immediate effect without reload
|
|
901
|
+
if (settings.highlightEnabled !== undefined || settings.highlightColor !== undefined) {
|
|
902
|
+
if (typeof this.#babylonJSController.setIlluminationConfig === "function") {
|
|
903
|
+
// Convert string "false" to boolean false
|
|
904
|
+
const highlightEnabled = settings.highlightEnabled === "false" ? false : settings.highlightEnabled;
|
|
905
|
+
this.#babylonJSController.setIlluminationConfig({
|
|
906
|
+
highlightEnabled: highlightEnabled,
|
|
907
|
+
highlightColor: settings.highlightColor,
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// Exclude highlight settings from the reload — they are handled above without requiring a scene reload
|
|
913
|
+
const { highlightEnabled: _he, highlightColor: _hc, ...reloadSettings } = settings;
|
|
914
|
+
const scheduled = this.#babylonJSController.scheduleRenderSettingsReload(reloadSettings);
|
|
836
915
|
if (!scheduled.changed) {
|
|
837
916
|
return { changed: false, success: true };
|
|
838
917
|
}
|
|
@@ -31,6 +31,9 @@ import { DEFAULT_LOCALE, resolveLocale, translate } from "./localization/i18n.js
|
|
|
31
31
|
export default class PrefViewerMenu3D extends HTMLElement {
|
|
32
32
|
#elements = {
|
|
33
33
|
applyButton: null,
|
|
34
|
+
colorPicker: null,
|
|
35
|
+
colorSlider: null,
|
|
36
|
+
colorPickerPreview: null,
|
|
34
37
|
panel: null,
|
|
35
38
|
pendingLabel: null,
|
|
36
39
|
status: null,
|
|
@@ -44,6 +47,7 @@ export default class PrefViewerMenu3D extends HTMLElement {
|
|
|
44
47
|
|
|
45
48
|
#handles = {
|
|
46
49
|
onApplyClick: null,
|
|
50
|
+
onColorSliderInput: null,
|
|
47
51
|
onHostLeave: null,
|
|
48
52
|
onPanelEnter: null,
|
|
49
53
|
onSwitchChange: null,
|
|
@@ -51,7 +55,10 @@ export default class PrefViewerMenu3D extends HTMLElement {
|
|
|
51
55
|
};
|
|
52
56
|
|
|
53
57
|
#DEFAULT_RENDER_SETTINGS = { ...BabylonJSController.DEFAULT_RENDER_SETTINGS };
|
|
54
|
-
|
|
58
|
+
// Only include boolean settings as switches (exclude illuminationColor which is handled by color picker)
|
|
59
|
+
#MENU_SWITCHES = Object.keys(this.#DEFAULT_RENDER_SETTINGS).filter(key =>
|
|
60
|
+
typeof this.#DEFAULT_RENDER_SETTINGS[key] === "boolean"
|
|
61
|
+
);
|
|
55
62
|
#appliedSettings = { ...this.#DEFAULT_RENDER_SETTINGS };
|
|
56
63
|
#draftSettings = { ...this.#DEFAULT_RENDER_SETTINGS };
|
|
57
64
|
#switchAvailability = {};
|
|
@@ -72,7 +79,7 @@ export default class PrefViewerMenu3D extends HTMLElement {
|
|
|
72
79
|
* @returns {string[]} Array of attribute names to observe.
|
|
73
80
|
*/
|
|
74
81
|
static get observedAttributes() {
|
|
75
|
-
return ["culture"];
|
|
82
|
+
return ["culture", "show-illumination", "illumination-color"];
|
|
76
83
|
}
|
|
77
84
|
|
|
78
85
|
/**
|
|
@@ -86,6 +93,20 @@ export default class PrefViewerMenu3D extends HTMLElement {
|
|
|
86
93
|
attributeChangedCallback(name, _old, value) {
|
|
87
94
|
if (name === "culture") {
|
|
88
95
|
this.#setCultureInternal(value || DEFAULT_LOCALE);
|
|
96
|
+
} else if (name === "show-illumination" || name === "illumination-color") {
|
|
97
|
+
// Update draft settings when attributes change
|
|
98
|
+
if (name === "show-illumination") {
|
|
99
|
+
this.#draftSettings.illuminationEnabled = value === "true" || value === true;
|
|
100
|
+
// Update the corresponding switch if it exists
|
|
101
|
+
const switchEl = this.#elements.switches?.illuminationEnabled;
|
|
102
|
+
if (switchEl) {
|
|
103
|
+
switchEl.checked = this.#draftSettings.illuminationEnabled;
|
|
104
|
+
}
|
|
105
|
+
} else if (name === "illumination-color") {
|
|
106
|
+
this.#draftSettings.illuminationColor = value;
|
|
107
|
+
// Update color picker preview if exists
|
|
108
|
+
this.#updateColorPickerPreview(value);
|
|
109
|
+
}
|
|
89
110
|
}
|
|
90
111
|
}
|
|
91
112
|
|
|
@@ -139,10 +160,16 @@ export default class PrefViewerMenu3D extends HTMLElement {
|
|
|
139
160
|
<div class="menu-switches">
|
|
140
161
|
${this.#MENU_SWITCHES.map((key) => this.#renderSwitchRow(key, this.#texts)).join("")}
|
|
141
162
|
</div>
|
|
163
|
+
<div class="menu-color-picker" data-setting="highlightColor">
|
|
164
|
+
<div class="color-slider-row">
|
|
165
|
+
<span class="color-picker-label">${this.#texts.switches?.highlightColor?.label || "Highlight Color"}</span>
|
|
166
|
+
<span class="color-preview" style="background-color: ${this.#draftSettings.highlightColor || '#ff6700'}"></span>
|
|
167
|
+
</div>
|
|
168
|
+
<input type="range" class="color-slider" min="0" max="359" value="${PrefViewerMenu3D.#hexToHue(this.#draftSettings.highlightColor || '#ff6700')}">
|
|
169
|
+
</div>
|
|
142
170
|
<div class="menu-footer">
|
|
143
171
|
<span class="menu-pending" aria-live="polite">${this.#texts.pendingLabel}</span>
|
|
144
172
|
<span class="menu-status" aria-live="assertive"></span>
|
|
145
|
-
<button class="menu-apply" type="button" disabled>${this.#texts.actions.apply}</button>
|
|
146
173
|
</div>
|
|
147
174
|
</div>
|
|
148
175
|
`;
|
|
@@ -271,6 +298,12 @@ export default class PrefViewerMenu3D extends HTMLElement {
|
|
|
271
298
|
this.#elements.applyButton = this.querySelector(".menu-apply");
|
|
272
299
|
this.#elements.pendingLabel = this.querySelector(".menu-pending");
|
|
273
300
|
this.#elements.status = this.querySelector(".menu-status");
|
|
301
|
+
|
|
302
|
+
// Cache color picker elements
|
|
303
|
+
this.#elements.colorPicker = this.querySelector(".menu-color-picker");
|
|
304
|
+
this.#elements.colorSlider = this.querySelector(".color-slider");
|
|
305
|
+
this.#elements.colorPickerPreview = this.querySelector(".color-preview");
|
|
306
|
+
|
|
274
307
|
this.#MENU_SWITCHES.forEach((key) => {
|
|
275
308
|
this.#elements.switches[key] = this.querySelector(`input[data-setting="${key}"]`);
|
|
276
309
|
const wrapper = this.querySelector(`.menu-switch[data-setting="${key}"]`);
|
|
@@ -301,13 +334,15 @@ export default class PrefViewerMenu3D extends HTMLElement {
|
|
|
301
334
|
};
|
|
302
335
|
this.#handles.onSwitchChange = (event) => this.#handleSwitchChange(event);
|
|
303
336
|
this.#handles.onApplyClick = () => this.#emitApply();
|
|
337
|
+
this.#handles.onColorSliderInput = (event) => this.#handleColorSliderInput(event);
|
|
304
338
|
|
|
305
339
|
this.#elements.toggleButton.addEventListener("mouseenter", this.#handles.onToggleEnter);
|
|
306
340
|
this.#elements.toggleButton.addEventListener("focus", this.#handles.onToggleEnter);
|
|
307
341
|
this.#elements.panel.addEventListener("mouseenter", this.#handles.onPanelEnter);
|
|
308
342
|
this.addEventListener("mouseleave", this.#handles.onHostLeave);
|
|
309
343
|
Object.values(this.#elements.switches).forEach((input) => input?.addEventListener("change", this.#handles.onSwitchChange));
|
|
310
|
-
this.#elements.applyButton
|
|
344
|
+
this.#elements.applyButton?.addEventListener("click", this.#handles.onApplyClick);
|
|
345
|
+
this.#elements.colorSlider?.addEventListener("input", this.#handles.onColorSliderInput);
|
|
311
346
|
}
|
|
312
347
|
|
|
313
348
|
/**
|
|
@@ -325,6 +360,8 @@ export default class PrefViewerMenu3D extends HTMLElement {
|
|
|
325
360
|
this.removeEventListener("mouseleave", this.#handles.onHostLeave);
|
|
326
361
|
Object.values(this.#elements.switches).forEach((input) => input?.removeEventListener("change", this.#handles.onSwitchChange));
|
|
327
362
|
this.#elements.applyButton?.removeEventListener("click", this.#handles.onApplyClick);
|
|
363
|
+
|
|
364
|
+
this.#elements.colorSlider?.removeEventListener("input", this.#handles.onColorSliderInput);
|
|
328
365
|
}
|
|
329
366
|
|
|
330
367
|
/**
|
|
@@ -364,6 +401,77 @@ export default class PrefViewerMenu3D extends HTMLElement {
|
|
|
364
401
|
}
|
|
365
402
|
this.#draftSettings[key] = event.currentTarget.checked;
|
|
366
403
|
this.#updatePendingState();
|
|
404
|
+
|
|
405
|
+
// For highlight toggle, apply immediately without waiting for user to click Apply
|
|
406
|
+
if (key === "highlightEnabled") {
|
|
407
|
+
this.#emitApply(true);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Handles clicks on the color picker buttons.
|
|
413
|
+
* @private
|
|
414
|
+
* @param {Event} event - Click event.
|
|
415
|
+
* @returns {void}
|
|
416
|
+
*/
|
|
417
|
+
#handleColorSliderInput(event) {
|
|
418
|
+
const hue = parseInt(event.target.value, 10);
|
|
419
|
+
const color = PrefViewerMenu3D.#hueToHex(hue);
|
|
420
|
+
this.#draftSettings.highlightColor = color;
|
|
421
|
+
this.#updateColorPickerPreview(color);
|
|
422
|
+
this.#updatePendingState();
|
|
423
|
+
this.#emitApply(true);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
static #hueToHex(hue) {
|
|
427
|
+
const h = ((hue % 360) + 360) % 360;
|
|
428
|
+
const a = Math.min(0.5, 1 - 0.5);
|
|
429
|
+
const f = (n) => {
|
|
430
|
+
const k = (n + h / 30) % 12;
|
|
431
|
+
return 0.5 - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
|
432
|
+
};
|
|
433
|
+
return '#' + [f(0), f(8), f(4)].map((v) => Math.round(v * 255).toString(16).padStart(2, '0')).join('');
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
static #hexToHue(hex) {
|
|
437
|
+
if (!hex || hex.length < 7) return 24;
|
|
438
|
+
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
|
439
|
+
const g = parseInt(hex.slice(3, 5), 16) / 255;
|
|
440
|
+
const b = parseInt(hex.slice(5, 7), 16) / 255;
|
|
441
|
+
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
442
|
+
if (max === min) return 0;
|
|
443
|
+
const d = max - min;
|
|
444
|
+
const h = max === r ? (g - b) / d + (g < b ? 6 : 0) : max === g ? (b - r) / d + 2 : (r - g) / d + 4;
|
|
445
|
+
return Math.round(h * 60);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Updates the color picker UI (input value, preview, selected button).
|
|
450
|
+
* @private
|
|
451
|
+
* @param {string} color - Selected color.
|
|
452
|
+
* @returns {void}
|
|
453
|
+
*/
|
|
454
|
+
#updateColorPickerUI(color) {
|
|
455
|
+
this.#updateColorPickerPreview(color);
|
|
456
|
+
if (this.#elements.colorSlider) {
|
|
457
|
+
const hue = PrefViewerMenu3D.#hexToHue(color);
|
|
458
|
+
// Only update slider position if not actively focused (user dragging)
|
|
459
|
+
if (document.activeElement !== this.#elements.colorSlider) {
|
|
460
|
+
this.#elements.colorSlider.value = hue;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Updates the color preview element.
|
|
467
|
+
* @private
|
|
468
|
+
* @param {string} color - Color to preview.
|
|
469
|
+
* @returns {void}
|
|
470
|
+
*/
|
|
471
|
+
#updateColorPickerPreview(color) {
|
|
472
|
+
if (this.#elements.colorPickerPreview) {
|
|
473
|
+
this.#elements.colorPickerPreview.style.backgroundColor = color;
|
|
474
|
+
}
|
|
367
475
|
}
|
|
368
476
|
|
|
369
477
|
/**
|
|
@@ -371,8 +479,9 @@ export default class PrefViewerMenu3D extends HTMLElement {
|
|
|
371
479
|
* @private
|
|
372
480
|
* @returns {void}
|
|
373
481
|
*/
|
|
374
|
-
#emitApply() {
|
|
375
|
-
|
|
482
|
+
#emitApply(force = false) {
|
|
483
|
+
// For illumination/color changes, we apply immediately regardless of pending state
|
|
484
|
+
if (!force && this.#isApplying) {
|
|
376
485
|
return;
|
|
377
486
|
}
|
|
378
487
|
|
|
@@ -554,6 +663,16 @@ export default class PrefViewerMenu3D extends HTMLElement {
|
|
|
554
663
|
this.#draftSettings = { ...this.#appliedSettings };
|
|
555
664
|
this.#updateSwitches();
|
|
556
665
|
this.#updatePendingState();
|
|
666
|
+
|
|
667
|
+
// Sync color slider with current highlightColor
|
|
668
|
+
if (this.#draftSettings.highlightColor) {
|
|
669
|
+
this.#updateColorPickerUI(this.#draftSettings.highlightColor);
|
|
670
|
+
}
|
|
671
|
+
// Update illumination color picker if present
|
|
672
|
+
if (this.#draftSettings.illuminationColor) {
|
|
673
|
+
this.#updateColorPickerUI(this.#draftSettings.illuminationColor);
|
|
674
|
+
}
|
|
675
|
+
|
|
557
676
|
this.setApplying(false);
|
|
558
677
|
this.#setStatusMessage("");
|
|
559
678
|
}
|
package/src/pref-viewer.js
CHANGED
|
@@ -106,7 +106,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
106
106
|
* @returns {string[]} Array of attribute names to observe.
|
|
107
107
|
*/
|
|
108
108
|
static get observedAttributes() {
|
|
109
|
-
return ["config", "culture", "drawing", "materials", "mode", "model", "scene", "options", "show-model", "show-scene", "show-menu"];
|
|
109
|
+
return ["config", "culture", "drawing", "materials", "mode", "model", "scene", "options", "show-model", "show-scene", "show-menu", "accent-color", "show-animation-menu", "highlight-color", "highlight-enabled"];
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
/**
|
|
@@ -178,6 +178,13 @@ export default class PrefViewer extends HTMLElement {
|
|
|
178
178
|
// Recreate menu to apply show/hide setting
|
|
179
179
|
this.#createMenu3D();
|
|
180
180
|
break;
|
|
181
|
+
case "accent-color":
|
|
182
|
+
case "show-animation-menu":
|
|
183
|
+
case "highlight-color":
|
|
184
|
+
case "highlight-enabled":
|
|
185
|
+
// Pass these attributes to the 3D component and menu
|
|
186
|
+
this.#updateComponentAttributes(name, value);
|
|
187
|
+
break;
|
|
181
188
|
}
|
|
182
189
|
}
|
|
183
190
|
|
|
@@ -260,6 +267,15 @@ export default class PrefViewer extends HTMLElement {
|
|
|
260
267
|
#createComponent3D() {
|
|
261
268
|
this.#component3D = document.createElement("pref-viewer-3d");
|
|
262
269
|
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
|
+
|
|
263
279
|
this.#handlers.on3DSceneLoaded = () => {
|
|
264
280
|
this.#menu3DSyncSettings();
|
|
265
281
|
this.#menu3D?.setApplying(false);
|
|
@@ -304,6 +320,14 @@ export default class PrefViewer extends HTMLElement {
|
|
|
304
320
|
this.#menu3D.setAttribute("culture", this.#culture);
|
|
305
321
|
}
|
|
306
322
|
|
|
323
|
+
// Pass illumination-related attributes to menu
|
|
324
|
+
const illumAttrs = ["show-illumination", "illumination-color"];
|
|
325
|
+
for (const attr of illumAttrs) {
|
|
326
|
+
if (this.hasAttribute(attr)) {
|
|
327
|
+
this.#menu3D.setAttribute(attr, this.getAttribute(attr));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
307
331
|
if (!this.#handlers.onMenuApply) {
|
|
308
332
|
this.#handlers.onMenuApply = this.#onMenuApply.bind(this);
|
|
309
333
|
}
|
|
@@ -320,6 +344,33 @@ export default class PrefViewer extends HTMLElement {
|
|
|
320
344
|
this.#menu3DUpdateAvailability();
|
|
321
345
|
}
|
|
322
346
|
|
|
347
|
+
/**
|
|
348
|
+
* Updates attributes on child components (3D viewer and menu) based on parent attributes.
|
|
349
|
+
* @private
|
|
350
|
+
* @param {string} name - Attribute name.
|
|
351
|
+
* @param {*} value - Attribute value.
|
|
352
|
+
* @returns {void}
|
|
353
|
+
*/
|
|
354
|
+
#updateComponentAttributes(name, value) {
|
|
355
|
+
// Pass to 3D component
|
|
356
|
+
if (this.#component3D) {
|
|
357
|
+
if (value === null) {
|
|
358
|
+
this.#component3D.removeAttribute(name);
|
|
359
|
+
} else {
|
|
360
|
+
this.#component3D.setAttribute(name, value);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Pass to menu
|
|
365
|
+
if (this.#menu3D) {
|
|
366
|
+
if (value === null) {
|
|
367
|
+
this.#menu3D.removeAttribute(name);
|
|
368
|
+
} else {
|
|
369
|
+
this.#menu3D.setAttribute(name, value);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
323
374
|
/**
|
|
324
375
|
* Reads the last persisted locale from localStorage so the viewer restores user preference.
|
|
325
376
|
* @private
|
|
@@ -1258,6 +1309,48 @@ export default class PrefViewer extends HTMLElement {
|
|
|
1258
1309
|
this.#component2D.zoomOut();
|
|
1259
1310
|
}
|
|
1260
1311
|
|
|
1312
|
+
/**
|
|
1313
|
+
* Returns a snapshot of all available animations and their current state.
|
|
1314
|
+
* @public
|
|
1315
|
+
* @returns {Array<{name: string, state: number, progress: number, loop: boolean, nodes: string[]}>}
|
|
1316
|
+
*/
|
|
1317
|
+
getAnimations() {
|
|
1318
|
+
return this.#component3D?.getAnimations() ?? [];
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
/**
|
|
1322
|
+
* Controls a named animation by applying the specified action.
|
|
1323
|
+
* @public
|
|
1324
|
+
* @param {string} name - The name of the animation to control.
|
|
1325
|
+
* @param {"open"|"close"|"pause"|"goToOpened"|"goToClosed"} action - The action to apply.
|
|
1326
|
+
* @returns {boolean} True if the animation was found and the action was applied; otherwise false.
|
|
1327
|
+
*/
|
|
1328
|
+
playAnimation(name, action) {
|
|
1329
|
+
return this.#component3D?.playAnimation(name, action) ?? false;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
/**
|
|
1333
|
+
* Seeks a named animation to the given normalized progress position (0–1).
|
|
1334
|
+
* @public
|
|
1335
|
+
* @param {string} name - The name of the animation to seek.
|
|
1336
|
+
* @param {number} progress - Progress value between 0 and 1.
|
|
1337
|
+
* @returns {boolean} True if the animation was found and seeked; otherwise false.
|
|
1338
|
+
*/
|
|
1339
|
+
setAnimationProgress(name, progress) {
|
|
1340
|
+
return this.#component3D?.setAnimationProgress(name, progress) ?? false;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
/**
|
|
1344
|
+
* Enables or disables loop mode for a named animation.
|
|
1345
|
+
* @public
|
|
1346
|
+
* @param {string} name - The name of the animation to configure.
|
|
1347
|
+
* @param {boolean} loop - True to enable looping; false to disable.
|
|
1348
|
+
* @returns {boolean} True if the animation was found and loop mode was set; otherwise false.
|
|
1349
|
+
*/
|
|
1350
|
+
setAnimationLoop(name, loop) {
|
|
1351
|
+
return this.#component3D?.setAnimationLoop(name, loop) ?? false;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1261
1354
|
/**
|
|
1262
1355
|
* ---------------------------
|
|
1263
1356
|
* Public properties (setters)
|
|
@@ -1336,6 +1429,25 @@ export default class PrefViewer extends HTMLElement {
|
|
|
1336
1429
|
this.loadScene(value);
|
|
1337
1430
|
}
|
|
1338
1431
|
|
|
1432
|
+
/**
|
|
1433
|
+
* Enables or disables the hover highlight effect at runtime.
|
|
1434
|
+
* Accepts boolean or string "true"/"false".
|
|
1435
|
+
* @public
|
|
1436
|
+
* @param {boolean|string} value
|
|
1437
|
+
*/
|
|
1438
|
+
set highlightEnabled(value) {
|
|
1439
|
+
this.setAttribute("highlight-enabled", String(value));
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
/**
|
|
1443
|
+
* Sets the hover highlight color at runtime.
|
|
1444
|
+
* @public
|
|
1445
|
+
* @param {string} value - Hex color string, e.g. "#ff6700".
|
|
1446
|
+
*/
|
|
1447
|
+
set highlightColor(value) {
|
|
1448
|
+
this.setAttribute("highlight-color", value);
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1339
1451
|
/**
|
|
1340
1452
|
* ---------------------------
|
|
1341
1453
|
* Public properties (getters)
|
package/src/styles.js
CHANGED
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
export const PrefViewerStyleVariables = {
|
|
2
|
-
colorPrimary: "#ff6700", //
|
|
2
|
+
colorPrimary: "#ff6700", // Default accent color - can be overridden via attribute
|
|
3
3
|
fontFamily: "'Roboto', ui-sans-serif, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji",
|
|
4
4
|
};
|
|
5
5
|
|
|
6
|
+
// Default palette colors for illumination color picker
|
|
7
|
+
export const DEFAULT_ILLUMINATION_PALETTE = [
|
|
8
|
+
"#ffffff", // White
|
|
9
|
+
"#ff6700", // Orange (default)
|
|
10
|
+
"#ff0000", // Red
|
|
11
|
+
"#00ff00", // Green
|
|
12
|
+
"#0000ff", // Blue
|
|
13
|
+
"#ffff00", // Yellow
|
|
14
|
+
"#ff00ff", // Magenta
|
|
15
|
+
"#00ffff", // Cyan
|
|
16
|
+
"#ffb6c1", // Light Pink
|
|
17
|
+
"#98fb98", // Light Green
|
|
18
|
+
"#87ceeb", // Light Blue
|
|
19
|
+
"#dda0dd", // Plum
|
|
20
|
+
];
|
|
21
|
+
|
|
6
22
|
export const PrefViewerStyles = `
|
|
7
23
|
:host .pref-viewer-wrapper {
|
|
8
24
|
display: grid !important;
|
|
@@ -67,6 +83,10 @@ export const PrefViewer3DStyles = `
|
|
|
67
83
|
display: none;
|
|
68
84
|
}
|
|
69
85
|
|
|
86
|
+
pref-viewer-3d[show-animation-menu="false"] > div.animation-menu {
|
|
87
|
+
display: none;
|
|
88
|
+
}
|
|
89
|
+
|
|
70
90
|
pref-viewer-3d {
|
|
71
91
|
grid-column: 1;
|
|
72
92
|
grid-row: 1;
|
|
@@ -181,6 +201,8 @@ export const PrefViewerMenu3DStyles = `
|
|
|
181
201
|
|
|
182
202
|
pref-viewer-menu-3d>.menu-wrapper>.menu-panel {
|
|
183
203
|
width: var(--panel-width);
|
|
204
|
+
max-height: 70vh;
|
|
205
|
+
overflow-y: auto;
|
|
184
206
|
border-radius: var(--panel-radius);
|
|
185
207
|
background: var(--panel-bg-color);
|
|
186
208
|
border: 1px solid var(--panel-border-color);
|
|
@@ -386,6 +408,80 @@ export const PrefViewerMenu3DStyles = `
|
|
|
386
408
|
pref-viewer-menu-3d[data-applying]>.menu-wrapper>.menu-panel>.menu-footer>.menu-apply {
|
|
387
409
|
opacity: 0.75;
|
|
388
410
|
}
|
|
411
|
+
|
|
412
|
+
/* Color Slider Styles */
|
|
413
|
+
pref-viewer-menu-3d>.menu-wrapper>.menu-panel>.menu-color-picker {
|
|
414
|
+
display: flex;
|
|
415
|
+
flex-direction: column;
|
|
416
|
+
gap: 8px;
|
|
417
|
+
padding: var(--panel-spacing) 0;
|
|
418
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
pref-viewer-menu-3d>.menu-wrapper>.menu-panel>.menu-color-picker:last-child {
|
|
422
|
+
border-bottom: none;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
pref-viewer-menu-3d>.menu-wrapper>.menu-panel>.menu-color-picker>.color-slider-row {
|
|
426
|
+
display: flex;
|
|
427
|
+
align-items: center;
|
|
428
|
+
justify-content: space-between;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
pref-viewer-menu-3d>.menu-wrapper>.menu-panel>.menu-color-picker>.color-slider-row>.color-picker-label {
|
|
432
|
+
font-size: var(--font-size-medium);
|
|
433
|
+
font-weight: var(--font-weight-medium);
|
|
434
|
+
color: var(--text-color);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
pref-viewer-menu-3d>.menu-wrapper>.menu-panel>.menu-color-picker>.color-slider-row>.color-preview {
|
|
438
|
+
width: 20px;
|
|
439
|
+
height: 20px;
|
|
440
|
+
border-radius: 50%;
|
|
441
|
+
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
442
|
+
flex-shrink: 0;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
pref-viewer-menu-3d>.menu-wrapper>.menu-panel>.menu-color-picker>.color-slider {
|
|
446
|
+
-webkit-appearance: none;
|
|
447
|
+
appearance: none;
|
|
448
|
+
width: 100%;
|
|
449
|
+
height: 10px;
|
|
450
|
+
border-radius: 5px;
|
|
451
|
+
cursor: pointer;
|
|
452
|
+
background: linear-gradient(to right,
|
|
453
|
+
hsl(0,100%,50%), hsl(30,100%,50%), hsl(60,100%,50%), hsl(90,100%,50%),
|
|
454
|
+
hsl(120,100%,50%), hsl(150,100%,50%), hsl(180,100%,50%), hsl(210,100%,50%),
|
|
455
|
+
hsl(240,100%,50%), hsl(270,100%,50%), hsl(300,100%,50%), hsl(330,100%,50%),
|
|
456
|
+
hsl(360,100%,50%));
|
|
457
|
+
outline: none;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
pref-viewer-menu-3d>.menu-wrapper>.menu-panel>.menu-color-picker>.color-slider::-webkit-slider-thumb {
|
|
461
|
+
-webkit-appearance: none;
|
|
462
|
+
width: 16px;
|
|
463
|
+
height: 16px;
|
|
464
|
+
border-radius: 50%;
|
|
465
|
+
background: white;
|
|
466
|
+
border: 2px solid rgba(0, 0, 0, 0.25);
|
|
467
|
+
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
|
|
468
|
+
cursor: pointer;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
pref-viewer-menu-3d>.menu-wrapper>.menu-panel>.menu-color-picker>.color-slider::-moz-range-thumb {
|
|
472
|
+
width: 16px;
|
|
473
|
+
height: 16px;
|
|
474
|
+
border-radius: 50%;
|
|
475
|
+
background: white;
|
|
476
|
+
border: 2px solid rgba(0, 0, 0, 0.25);
|
|
477
|
+
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
|
|
478
|
+
cursor: pointer;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
pref-viewer-menu-3d>.menu-wrapper>.menu-panel>.menu-color-picker[data-disabled] {
|
|
482
|
+
opacity: 0.5;
|
|
483
|
+
pointer-events: none;
|
|
484
|
+
}
|
|
389
485
|
`;
|
|
390
486
|
|
|
391
487
|
export const PrefViewer3DAnimationMenuStyles = `
|