@preference-sl/pref-viewer 2.11.0-beta.12 → 2.11.0-beta.14
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 +61 -50
- package/src/babylonjs-animation-controller.js +57 -74
- package/src/babylonjs-animation-opening-menu.js +111 -90
- package/src/babylonjs-animation-opening.js +25 -27
- package/src/babylonjs-controller.js +110 -37
- package/src/index.js +15 -911
- package/src/pref-viewer.js +911 -0
- package/src/styles.js +141 -3
package/package.json
CHANGED
|
@@ -1,53 +1,64 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
2
|
+
"name": "@preference-sl/pref-viewer",
|
|
3
|
+
"version": "2.11.0-beta.14",
|
|
4
|
+
"description": "Web Component to preview GLTF models with Babylon.js",
|
|
5
|
+
"author": "Alex Moreno Palacio <amoreno@preference.es>",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build:prefweb": "webpack --config webpack.config.cjs --mode production",
|
|
8
|
+
"build:prefweb:dev": "webpack --config webpack.config.cjs --mode development",
|
|
9
|
+
"release": "standard-version --releaseCommitMessageFormat \"chore(release): {{currentTag}} [skip ci]\"",
|
|
10
|
+
"release:beta": "standard-version --prerelease beta --releaseCommitMessageFormat \"chore(release): {{currentTag}} [skip ci]\"",
|
|
11
|
+
"build": "esbuild src/index.js --bundle --platform=node --format=esm --outfile=dist/bundle.js --sourcemap",
|
|
12
|
+
"start": "npm run build && http-server -c-1 . -p 8080",
|
|
13
|
+
"test": "vitest"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://bitbucket.org/preferencesl/pref-viewer.git"
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"type": "module",
|
|
24
|
+
"main": "src/index.js",
|
|
25
|
+
"types": "index.d.ts",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"import": "./src/index.js",
|
|
29
|
+
"require": "./src/index.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"sideEffects": false,
|
|
33
|
+
"files": [
|
|
34
|
+
"src",
|
|
35
|
+
"index.d.ts"
|
|
36
|
+
],
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@babylonjs/core": "^8.39.2",
|
|
39
|
+
"@babylonjs/loaders": "^8.39.2",
|
|
40
|
+
"@babylonjs/serializers": "^8.39.2",
|
|
41
|
+
"@panzoom/panzoom": "^4.6.0",
|
|
42
|
+
"babylonjs-gltf2interface": "^8.39.2",
|
|
43
|
+
"buffer": "^6.0.3",
|
|
44
|
+
"idb": "^8.0.3",
|
|
45
|
+
"is-svg": "^6.1.0",
|
|
46
|
+
"jszip": "^3.10.1",
|
|
47
|
+
"stream": "^0.0.3",
|
|
48
|
+
"string_decoder": "^1.3.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@babel/core": "^7.22.0",
|
|
52
|
+
"@babel/preset-env": "^7.22.0",
|
|
53
|
+
"babel-loader": "^9.2.1",
|
|
54
|
+
"clean-webpack-plugin": "^4.0.0",
|
|
55
|
+
"esbuild": "^0.25.10",
|
|
56
|
+
"http-server": "^14.1.1",
|
|
57
|
+
"jsdom": "^26.1.0",
|
|
58
|
+
"standard-version": "^9.5.0",
|
|
59
|
+
"terser-webpack-plugin": "^5.3.6",
|
|
60
|
+
"vitest": "^3.2.3",
|
|
61
|
+
"webpack": "^5.88.2",
|
|
62
|
+
"webpack-cli": "^5.1.4"
|
|
28
63
|
}
|
|
29
|
-
},
|
|
30
|
-
"sideEffects": false,
|
|
31
|
-
"files": [
|
|
32
|
-
"src",
|
|
33
|
-
"index.d.ts"
|
|
34
|
-
],
|
|
35
|
-
"dependencies": {
|
|
36
|
-
"@babylonjs/core": "^8.36.1",
|
|
37
|
-
"@babylonjs/gui": "^8.36.1",
|
|
38
|
-
"@babylonjs/loaders": "^8.36.1",
|
|
39
|
-
"@babylonjs/serializers": "^8.36.1",
|
|
40
|
-
"@panzoom/panzoom": "^4.6.0",
|
|
41
|
-
"babylonjs-gltf2interface": "^8.36.1",
|
|
42
|
-
"idb": "^8.0.3",
|
|
43
|
-
"is-svg": "^6.1.0",
|
|
44
|
-
"jszip": "^3.10.1"
|
|
45
|
-
},
|
|
46
|
-
"devDependencies": {
|
|
47
|
-
"esbuild": "^0.25.10",
|
|
48
|
-
"http-server": "^14.1.1",
|
|
49
|
-
"jsdom": "^26.1.0",
|
|
50
|
-
"standard-version": "^9.5.0",
|
|
51
|
-
"vitest": "^3.2.3"
|
|
52
|
-
}
|
|
53
64
|
}
|
|
@@ -1,29 +1,35 @@
|
|
|
1
|
-
import { Color3, HighlightLayer, Mesh, PickingInfo,
|
|
2
|
-
import { AdvancedDynamicTexture } from "@babylonjs/gui";
|
|
1
|
+
import { Color3, HighlightLayer, Mesh, PickingInfo, Scene } from "@babylonjs/core";
|
|
3
2
|
import { PrefViewerColors } from "./styles.js";
|
|
4
3
|
import OpeningAnimation from "./babylonjs-animation-opening.js";
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
|
-
* BabylonJSAnimationController - Manages animation playback and interactive
|
|
6
|
+
* BabylonJSAnimationController - Manages animation playback, highlighting, and interactive controls for animated nodes in Babylon.js scenes.
|
|
8
7
|
*
|
|
9
|
-
*
|
|
8
|
+
* Summary:
|
|
9
|
+
* This class detects, groups, and manages opening/closing animations for scene nodes, provides interactive highlighting of animated nodes and their meshes, and displays a menu for animation control. It is designed for integration with product configurators and interactive 3D applications using Babylon.js.
|
|
10
|
+
*
|
|
11
|
+
* Key features:
|
|
10
12
|
* - Detects and groups opening/closing animations in the scene.
|
|
11
13
|
* - Tracks animated transformation nodes and their relationships to meshes.
|
|
12
|
-
* - Highlights animated nodes and their child meshes
|
|
13
|
-
* - Displays and disposes the animation control menu
|
|
14
|
-
* - Provides API for
|
|
14
|
+
* - Highlights animated nodes and their child meshes on pointer hover.
|
|
15
|
+
* - Displays and disposes the animation control menu for animated nodes.
|
|
16
|
+
* - Provides public API for highlighting, showing the animation menu, and disposing resources.
|
|
17
|
+
* - Cleans up all resources and observers to prevent memory leaks.
|
|
15
18
|
*
|
|
16
19
|
* Public Methods:
|
|
17
20
|
* - dispose(): Disposes all resources managed by the animation controller.
|
|
21
|
+
* - hightlightMeshes(pickingInfo): Highlights meshes that are children of an animated node when hovered.
|
|
22
|
+
* - hideMenu(): Hides and disposes the animation control menu if it exists.
|
|
23
|
+
* - showMenu(pickingInfo): Displays the animation control menu for the animated node under the pointer.
|
|
18
24
|
*
|
|
19
25
|
* @class
|
|
20
26
|
*/
|
|
21
27
|
export default class BabylonJSAnimationController {
|
|
22
28
|
#scene = null;
|
|
29
|
+
#canvas = null;
|
|
23
30
|
#animatedNodes = [];
|
|
24
31
|
#highlightLayer = null;
|
|
25
32
|
#highlightColor = Color3.FromHexString(PrefViewerColors.primary);
|
|
26
|
-
#advancedDynamicTexture = null;
|
|
27
33
|
#openingAnimations = [];
|
|
28
34
|
|
|
29
35
|
/**
|
|
@@ -32,8 +38,8 @@ export default class BabylonJSAnimationController {
|
|
|
32
38
|
*/
|
|
33
39
|
constructor(scene) {
|
|
34
40
|
this.#scene = scene;
|
|
41
|
+
this.#canvas = this.#scene._engine._renderingCanvas;
|
|
35
42
|
this.#initializeAnimations();
|
|
36
|
-
this.#setupPointerObservers();
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
/**
|
|
@@ -41,11 +47,12 @@ export default class BabylonJSAnimationController {
|
|
|
41
47
|
* @private
|
|
42
48
|
*/
|
|
43
49
|
#initializeAnimations() {
|
|
50
|
+
this.hideMenu(); // Clean up any existing menus
|
|
44
51
|
if (!this.#scene.animationGroups.length) {
|
|
45
52
|
return;
|
|
46
53
|
}
|
|
47
54
|
this.#getAnimatedNodes();
|
|
48
|
-
this.#
|
|
55
|
+
this.#getOpeningAnimations();
|
|
49
56
|
}
|
|
50
57
|
|
|
51
58
|
/**
|
|
@@ -72,7 +79,7 @@ export default class BabylonJSAnimationController {
|
|
|
72
79
|
* @description
|
|
73
80
|
* Uses animation group names with the pattern "animation_open_<name>" and "animation_close_<name>".
|
|
74
81
|
*/
|
|
75
|
-
#
|
|
82
|
+
#getOpeningAnimations() {
|
|
76
83
|
const openings = {};
|
|
77
84
|
this.#scene.animationGroups.forEach((animationGroup) => {
|
|
78
85
|
const match = animationGroup.name.match(/^animation_(open|close)_(.+)$/);
|
|
@@ -123,12 +130,38 @@ export default class BabylonJSAnimationController {
|
|
|
123
130
|
return nodeId;
|
|
124
131
|
};
|
|
125
132
|
|
|
133
|
+
/**
|
|
134
|
+
* ---------------------------
|
|
135
|
+
* Public methods
|
|
136
|
+
* ---------------------------
|
|
137
|
+
*/
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Disposes all resources managed by the animation controller.
|
|
141
|
+
* Cleans up the highlight layer, animation menu, and internal animation/node lists.
|
|
142
|
+
* Should be called when the controller is no longer needed to prevent memory leaks.
|
|
143
|
+
* @public
|
|
144
|
+
*/
|
|
145
|
+
dispose() {
|
|
146
|
+
if (this.#highlightLayer) {
|
|
147
|
+
this.#highlightLayer.dispose();
|
|
148
|
+
this.#highlightLayer = null;
|
|
149
|
+
}
|
|
150
|
+
this.hideMenu();
|
|
151
|
+
this.#animatedNodes = [];
|
|
152
|
+
this.#openingAnimations = [];
|
|
153
|
+
const observer = this.#scene.onPointerObservable._observers.find((observer) => observer.callback.name.includes("#onAnimationPointerObservable"));
|
|
154
|
+
if (observer) {
|
|
155
|
+
this.#scene.onPointerObservable.remove(observer);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
126
159
|
/**
|
|
127
160
|
* Highlights meshes that are children of an animated node when hovered.
|
|
128
|
-
* @
|
|
161
|
+
* @public
|
|
129
162
|
* @param {PickingInfo} pickingInfo - Raycast info from pointer position.
|
|
130
163
|
*/
|
|
131
|
-
|
|
164
|
+
hightlightMeshes(pickingInfo) {
|
|
132
165
|
if (!this.#highlightLayer) {
|
|
133
166
|
this.#highlightLayer = new HighlightLayer("hl_animations", this.#scene);
|
|
134
167
|
}
|
|
@@ -151,52 +184,27 @@ export default class BabylonJSAnimationController {
|
|
|
151
184
|
}
|
|
152
185
|
|
|
153
186
|
/**
|
|
154
|
-
*
|
|
155
|
-
* @
|
|
156
|
-
|
|
157
|
-
#setupPointerObservers() {
|
|
158
|
-
if (this.#openingAnimations.length === 0) {
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
this.#scene.onPointerObservable.add(this.#onAnimationPointerObservable.bind(this));
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Handles pointer events in the Babylon.js scene for animation interaction.
|
|
166
|
-
* On pointer move, highlights meshes belonging to animated nodes under the cursor.
|
|
167
|
-
* On pointer up (click), disposes any existing GUI and displays the animation control menu for the selected node.
|
|
168
|
-
*
|
|
169
|
-
* @private
|
|
170
|
-
* @param {PointerInfo} pointerInfo - The pointer event information from Babylon.js.
|
|
187
|
+
* Hides and disposes the animation control menu if it exists.
|
|
188
|
+
* @public
|
|
189
|
+
* @returns {void}
|
|
171
190
|
*/
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
this.#hightlightMeshesForAnimation(pickingInfo);
|
|
176
|
-
}
|
|
177
|
-
if (pointerInfo.type === PointerEventTypes.POINTERUP) {
|
|
178
|
-
// Remove any previously created Babylon GUI
|
|
179
|
-
if (this.#advancedDynamicTexture) {
|
|
180
|
-
this.#advancedDynamicTexture.dispose();
|
|
181
|
-
this.#advancedDynamicTexture = null;
|
|
182
|
-
}
|
|
183
|
-
const pickingInfo = this.#scene.pick(pointerInfo.event.clientX, pointerInfo.event.clientY);
|
|
184
|
-
this.#showMenu(pickingInfo);
|
|
185
|
-
}
|
|
191
|
+
hideMenu() {
|
|
192
|
+
this.#openingAnimations.forEach((openingAnimation) => openingAnimation.hideControls());
|
|
193
|
+
this.#canvas.parentElement.querySelectorAll("div.pref-viewer-3d.animation-menu").forEach((menu) => menu.remove());
|
|
186
194
|
}
|
|
187
195
|
|
|
188
196
|
/**
|
|
189
197
|
* Displays the animation control menu for the animated node under the pointer.
|
|
190
|
-
* @
|
|
198
|
+
* @public
|
|
191
199
|
* @param {PickingInfo} pickingInfo - Raycast info from pointer position.
|
|
192
|
-
* @description
|
|
193
|
-
* Creates the GUI if needed and invokes OpeningAnimation.showControls.
|
|
194
200
|
*/
|
|
195
|
-
|
|
201
|
+
showMenu(pickingInfo) {
|
|
196
202
|
if (!pickingInfo?.hit && !pickingInfo?.pickedMesh) {
|
|
197
203
|
return;
|
|
198
204
|
}
|
|
199
205
|
|
|
206
|
+
this.hideMenu();
|
|
207
|
+
|
|
200
208
|
const nodeId = this.#getNodeAnimatedByMesh(pickingInfo.pickedMesh);
|
|
201
209
|
if (!nodeId) {
|
|
202
210
|
return;
|
|
@@ -205,32 +213,7 @@ export default class BabylonJSAnimationController {
|
|
|
205
213
|
if (!openingAnimation) {
|
|
206
214
|
return;
|
|
207
215
|
}
|
|
208
|
-
if (!this.#advancedDynamicTexture) {
|
|
209
|
-
this.#advancedDynamicTexture = AdvancedDynamicTexture.CreateFullscreenUI("UI_Animation");
|
|
210
|
-
}
|
|
211
|
-
openingAnimation.showControls(this.#advancedDynamicTexture);
|
|
212
|
-
}
|
|
213
216
|
|
|
214
|
-
|
|
215
|
-
* Disposes all resources managed by the animation controller.
|
|
216
|
-
* Cleans up the highlight layer, GUI texture, and internal animation/node lists.
|
|
217
|
-
* Should be called when the controller is no longer needed to prevent memory leaks.
|
|
218
|
-
* @public
|
|
219
|
-
*/
|
|
220
|
-
dispose() {
|
|
221
|
-
if (this.#highlightLayer) {
|
|
222
|
-
this.#highlightLayer.dispose();
|
|
223
|
-
this.#highlightLayer = null;
|
|
224
|
-
}
|
|
225
|
-
if (this.#advancedDynamicTexture) {
|
|
226
|
-
this.#advancedDynamicTexture.dispose();
|
|
227
|
-
this.#advancedDynamicTexture = null;
|
|
228
|
-
}
|
|
229
|
-
this.#animatedNodes = [];
|
|
230
|
-
this.#openingAnimations = [];
|
|
231
|
-
const observer = this.#scene.onPointerObservable._observers.find((observer) => observer.callback.name.includes("#onAnimationPointerObservable"));
|
|
232
|
-
if (observer) {
|
|
233
|
-
this.#scene.onPointerObservable.remove(observer);
|
|
234
|
-
}
|
|
217
|
+
openingAnimation.showControls(this.#canvas);
|
|
235
218
|
}
|
|
236
219
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { AdvancedDynamicTexture, Button, Control, Image, Slider, StackPanel } from "@babylonjs/gui";
|
|
2
1
|
import OpeningAnimation from "./babylonjs-animation-opening.js";
|
|
3
|
-
import {
|
|
2
|
+
import { PrefViewer3DAnimationMenuStyles } from "./styles.js";
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* OpeningAnimationMenu - Manages and renders the animation control menu for opening/closing animations in a Babylon.js scene.
|
|
@@ -10,15 +9,23 @@ import { PrefViewerColors } from "./styles.js";
|
|
|
10
9
|
* - Updates button states and slider position based on animation state, progress, and loop mode.
|
|
11
10
|
* - Handles user interactions and invokes provided callbacks for animation actions.
|
|
12
11
|
* - Synchronizes the slider value with animation progress, avoiding callback loops.
|
|
12
|
+
* - Provides setters to update animation state, progress, and loop mode from external controllers.
|
|
13
|
+
* - Disposes and cleans up all DOM resources when no longer needed.
|
|
13
14
|
*
|
|
14
15
|
* Public Setters:
|
|
15
16
|
* - set animationState(state): Updates the animation state and button states.
|
|
16
17
|
* - set animationProgress(progress): Updates the animation progress and slider value.
|
|
17
18
|
* - set animationLoop(loop): Updates the loop mode and loop button states.
|
|
18
19
|
*
|
|
20
|
+
* Public Methods:
|
|
21
|
+
* - dispose(): Disposes the animation menu and releases all associated DOM resources.
|
|
22
|
+
*
|
|
23
|
+
* Public Getters:
|
|
24
|
+
* - isVisible: Returns whether the animation menu is currently visible in the DOM.
|
|
25
|
+
*
|
|
19
26
|
* Private Methods:
|
|
20
27
|
* - #createMenu(): Initializes and renders the menu UI.
|
|
21
|
-
* - #addButton(name,
|
|
28
|
+
* - #addButton(name, imageSVG, enabled, active, visible, callback): Adds a button to the menu with specified properties.
|
|
22
29
|
* - #createButtons(): Creates all control buttons and sets their initial states.
|
|
23
30
|
* - #getButtonByName(name): Retrieves a button control by its name.
|
|
24
31
|
* - #setButtonState(name, enabled, active, visible): Updates the visual state of a button.
|
|
@@ -30,7 +37,7 @@ import { PrefViewerColors } from "./styles.js";
|
|
|
30
37
|
* - #createSlider(): Creates and configures the animation progress slider.
|
|
31
38
|
*
|
|
32
39
|
* Usage Example:
|
|
33
|
-
* const menu = new OpeningAnimationMenu(
|
|
40
|
+
* const menu = new OpeningAnimationMenu(name, canvas, state, progress, loop, {
|
|
34
41
|
* onOpen: () => { ... },
|
|
35
42
|
* onClose: () => { ... },
|
|
36
43
|
* onPause: () => { ... },
|
|
@@ -49,45 +56,37 @@ export default class OpeningAnimationMenu {
|
|
|
49
56
|
#animationLoop = false;
|
|
50
57
|
#callbacks = null;
|
|
51
58
|
|
|
52
|
-
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
59
|
+
#name = "";
|
|
60
|
+
#canvas = null;
|
|
61
|
+
#containerMain = null;
|
|
62
|
+
#containerButtons = null;
|
|
56
63
|
#slider = null;
|
|
57
64
|
|
|
58
|
-
// Style properties
|
|
59
|
-
#buttonSize = 28;
|
|
60
|
-
#buttonLoopPaddingLeft = 3;
|
|
61
|
-
#colorActive = PrefViewerColors.primary;
|
|
62
|
-
#colorEnabled = "#333333";
|
|
63
|
-
#colorDisabled = "#777777";
|
|
64
|
-
#colorIcon = "#FFFFFF";
|
|
65
|
-
#colorBorder = "#FFFFFF";
|
|
66
|
-
#sliderThumbWidth = 20;
|
|
67
|
-
#sliderBarOffset = 10;
|
|
68
65
|
#icon = {
|
|
69
|
-
close: `<svg id="play-backwards" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path
|
|
70
|
-
closed: `<svg id="skip-backward" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path
|
|
71
|
-
open: `<svg id="play" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path
|
|
72
|
-
opened: `<svg id="skip-forward" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path
|
|
73
|
-
pause: `<svg id="pause" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path
|
|
74
|
-
repeat: `<svg id="repeat" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path
|
|
75
|
-
repeatOff: `<svg id="repeat-off" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path
|
|
66
|
+
close: `<svg id="play-backwards" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M16,18.86V4.86l-11,7,11,7Z"/></svg>`,
|
|
67
|
+
closed: `<svg id="skip-backward" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M20,5V19L13,12M6,5V19H4V5M13,5V19L6,12"/></svg>`,
|
|
68
|
+
open: `<svg id="play" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M8,5.14v14l11-7-11-7Z"/></svg>`,
|
|
69
|
+
opened: `<svg id="skip-forward" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M4,5V19L11,12M18,5V19H20V5M11,5V19L18,12"/></svg>`,
|
|
70
|
+
pause: `<svg id="pause" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M14,19H18V5H14M6,19H10V5H6V19Z"/></svg>`,
|
|
71
|
+
repeat: `<svg id="repeat" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M17,17H7V14L3,18L7,22V19H19V13H17M7,7H17V10L21,6L17,2V5H5V11H7V7Z"/></svg>`,
|
|
72
|
+
repeatOff: `<svg id="repeat-off" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M2,5.27L3.28,4L20,20.72L18.73,22L15.73,19H7V22L3,18L7,14V17H13.73L7,10.27V11H5V8.27L2,5.27M17,13H19V17.18L17,15.18V13M17,5V2L21,6L17,10V7H8.82L6.82,5H17Z"/></svg>`,
|
|
76
73
|
};
|
|
77
74
|
|
|
78
75
|
#isSettingSliderValue = false;
|
|
79
76
|
|
|
80
77
|
/**
|
|
81
78
|
* Constructs the OpeningAnimationMenu and initializes the menu UI.
|
|
82
|
-
* @param {
|
|
79
|
+
* @param {string} name - The name of the animation.
|
|
80
|
+
* @param {HTMLCanvasElement} canvas - The canvas element for rendering.
|
|
83
81
|
* @param {number} animationState - Current animation state (enum).
|
|
84
82
|
* @param {number} animationProgress - Current animation progress (0 to 1).
|
|
85
83
|
* @param {boolean} animationLoop - Whether the animation is set to loop.
|
|
86
84
|
* @param {object} callbacks - Callback functions for menu actions (play, pause, open, close, etc.).
|
|
87
85
|
* @public
|
|
88
86
|
*/
|
|
89
|
-
constructor(
|
|
90
|
-
this.#
|
|
87
|
+
constructor(name, canvas, animationState, animationProgress, animationLoop, callbacks) {
|
|
88
|
+
this.#name = name;
|
|
89
|
+
this.#canvas = canvas;
|
|
91
90
|
this.#animationState = animationState;
|
|
92
91
|
this.#animationProgress = animationProgress;
|
|
93
92
|
this.#animationLoop = animationLoop;
|
|
@@ -101,19 +100,19 @@ export default class OpeningAnimationMenu {
|
|
|
101
100
|
* @private
|
|
102
101
|
*/
|
|
103
102
|
#createMenu() {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
this.#
|
|
108
|
-
this.#
|
|
109
|
-
this.#secondaryPanel = new StackPanel();
|
|
110
|
-
this.#secondaryPanel.isVertical = false;
|
|
111
|
-
this.#secondaryPanel.height = `${this.#buttonSize}px`;
|
|
112
|
-
this.#mainPanel.addControl(this.#secondaryPanel);
|
|
103
|
+
this.#containerMain = document.createElement("div");
|
|
104
|
+
this.#containerMain.classList.add("pref-viewer-3d");
|
|
105
|
+
this.#containerMain.classList.add("animation-menu");
|
|
106
|
+
this.#containerMain.setAttribute("data-animation-name", this.#name);
|
|
107
|
+
this.#canvas.parentElement.prepend(this.#containerMain);
|
|
113
108
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
this.#
|
|
109
|
+
const style = document.createElement("style");
|
|
110
|
+
style.textContent = PrefViewer3DAnimationMenuStyles;
|
|
111
|
+
this.#containerMain.appendChild(style);
|
|
112
|
+
|
|
113
|
+
this.#containerButtons = document.createElement("div");
|
|
114
|
+
this.#containerButtons.classList.add("animation-menu-buttons");
|
|
115
|
+
this.#containerMain.appendChild(this.#containerButtons);
|
|
117
116
|
|
|
118
117
|
this.#createButtons();
|
|
119
118
|
this.#createSlider();
|
|
@@ -130,25 +129,21 @@ export default class OpeningAnimationMenu {
|
|
|
130
129
|
* @param {boolean} visible - Whether the button is visible.
|
|
131
130
|
* @param {function} callback - Callback to invoke on button click.
|
|
132
131
|
*/
|
|
133
|
-
#addButton(name,
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
};
|
|
143
|
-
const button = Button.CreateImageOnlyButton(`button_animation_${name}`, imageURL);
|
|
144
|
-
Object.assign(button, buttonProps);
|
|
145
|
-
button.image.stretch = Image.STRETCH_UNIFORM;
|
|
146
|
-
button.onPointerUpObservable.add(() => {
|
|
132
|
+
#addButton(name, imageSVG, enabled = true, active = false, visible = true, callback) {
|
|
133
|
+
const stateClass = active ? "active" : enabled ? "enabled" : "disabled";
|
|
134
|
+
const visibilityClass = visible ? "visible" : "hidden";
|
|
135
|
+
|
|
136
|
+
const button = document.createElement("button");
|
|
137
|
+
button.classList.add(stateClass, visibilityClass);
|
|
138
|
+
button.setAttribute("name", `button_animation_${name}`);
|
|
139
|
+
button.innerHTML = imageSVG;
|
|
140
|
+
button.addEventListener("click", (event) => {
|
|
147
141
|
if (callback && typeof callback === "function") {
|
|
148
142
|
callback();
|
|
149
143
|
}
|
|
150
144
|
});
|
|
151
|
-
|
|
145
|
+
|
|
146
|
+
this.#containerButtons.appendChild(button);
|
|
152
147
|
}
|
|
153
148
|
|
|
154
149
|
/**
|
|
@@ -157,19 +152,13 @@ export default class OpeningAnimationMenu {
|
|
|
157
152
|
*/
|
|
158
153
|
#createButtons() {
|
|
159
154
|
const buttonsState = this.#getButtonsState();
|
|
160
|
-
this.#addButton("closed",
|
|
161
|
-
this.#addButton("close",
|
|
162
|
-
this.#addButton("pause",
|
|
163
|
-
this.#addButton("open",
|
|
164
|
-
this.#addButton("opened",
|
|
165
|
-
this.#addButton("repeat",
|
|
166
|
-
this.#addButton("repeatOff",
|
|
167
|
-
|
|
168
|
-
// Adjust padding for loop buttons
|
|
169
|
-
this.#getButtonByName("repeat").paddingLeft = `${this.#buttonLoopPaddingLeft}px`;
|
|
170
|
-
this.#getButtonByName("repeat").width = `${this.#buttonSize + this.#buttonLoopPaddingLeft}px`;
|
|
171
|
-
this.#getButtonByName("repeatOff").paddingLeft = `${this.#buttonLoopPaddingLeft}px`;
|
|
172
|
-
this.#getButtonByName("repeatOff").width = `${this.#buttonSize + this.#buttonLoopPaddingLeft}px`;
|
|
155
|
+
this.#addButton("closed", this.#icon.closed, buttonsState.closed.enabled, buttonsState.closed.active, buttonsState.closed.visible, this.#callbacks.onGoToClosed);
|
|
156
|
+
this.#addButton("close", this.#icon.close, buttonsState.close.enabled, buttonsState.close.active, buttonsState.close.visible, this.#callbacks.onClose);
|
|
157
|
+
this.#addButton("pause", this.#icon.pause, buttonsState.pause.enabled, buttonsState.pause.active, buttonsState.pause.visible, this.#callbacks.onPause);
|
|
158
|
+
this.#addButton("open", this.#icon.open, buttonsState.open.enabled, buttonsState.open.active, buttonsState.open.visible, this.#callbacks.onOpen);
|
|
159
|
+
this.#addButton("opened", this.#icon.opened, buttonsState.opened.enabled, buttonsState.opened.active, buttonsState.opened.visible, this.#callbacks.onGoToOpened);
|
|
160
|
+
this.#addButton("repeat", this.#icon.repeat, buttonsState.repeat.enabled, buttonsState.repeat.active, buttonsState.repeat.visible, this.#callbacks.onToggleLoop);
|
|
161
|
+
this.#addButton("repeatOff", this.#icon.repeatOff, buttonsState.repeatOff.enabled, buttonsState.repeatOff.active, buttonsState.repeatOff.visible, this.#callbacks.onToggleLoop);
|
|
173
162
|
}
|
|
174
163
|
|
|
175
164
|
/**
|
|
@@ -179,7 +168,7 @@ export default class OpeningAnimationMenu {
|
|
|
179
168
|
* @returns {Button|null} The button control or null if not found.
|
|
180
169
|
*/
|
|
181
170
|
#getButtonByName(name) {
|
|
182
|
-
return this.#
|
|
171
|
+
return this.#containerButtons.querySelector(`button[name="button_animation_${name}"]`);
|
|
183
172
|
}
|
|
184
173
|
|
|
185
174
|
/**
|
|
@@ -195,8 +184,11 @@ export default class OpeningAnimationMenu {
|
|
|
195
184
|
if (!button) {
|
|
196
185
|
return;
|
|
197
186
|
}
|
|
198
|
-
|
|
199
|
-
button.
|
|
187
|
+
const classToRemove = ["active", "enabled", "disabled", "hidden", "visible"];
|
|
188
|
+
button.classList.remove(...classToRemove);
|
|
189
|
+
const stateClass = active ? "active" : enabled ? "enabled" : "disabled";
|
|
190
|
+
const visibilityClass = visible ? "visible" : "hidden";
|
|
191
|
+
button.classList.add(stateClass, visibilityClass);
|
|
200
192
|
}
|
|
201
193
|
|
|
202
194
|
/**
|
|
@@ -293,32 +285,45 @@ export default class OpeningAnimationMenu {
|
|
|
293
285
|
* @private
|
|
294
286
|
*/
|
|
295
287
|
#createSlider() {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
background: this.#colorDisabled,
|
|
306
|
-
color: this.#colorEnabled,
|
|
307
|
-
borderColor: this.#colorBorder,
|
|
308
|
-
thumbColor: this.#colorEnabled,
|
|
309
|
-
};
|
|
310
|
-
this.#slider = new Slider("slider_animation_progress");
|
|
311
|
-
Object.assign(this.#slider, sliderProps);
|
|
312
|
-
this.#slider.onValueChangedObservable.add((value) => {
|
|
288
|
+
this.#slider = document.createElement("input");
|
|
289
|
+
this.#slider.setAttribute("type", "range");
|
|
290
|
+
this.#slider.setAttribute("min", "0");
|
|
291
|
+
this.#slider.setAttribute("max", "1");
|
|
292
|
+
this.#slider.setAttribute("step", "0.01");
|
|
293
|
+
this.#slider.setAttribute("value", this.#animationProgress.toString());
|
|
294
|
+
this.#slider.classList.add("animation-menu-slider");
|
|
295
|
+
|
|
296
|
+
this.#slider.addEventListener("input", (event) => {
|
|
313
297
|
if (this.#isSettingSliderValue) {
|
|
314
298
|
this.#isSettingSliderValue = false;
|
|
315
299
|
return;
|
|
316
300
|
}
|
|
317
301
|
if (this.#callbacks && typeof this.#callbacks.onSetAnimationProgress === "function") {
|
|
318
|
-
this.#callbacks.onSetAnimationProgress(value);
|
|
302
|
+
this.#callbacks.onSetAnimationProgress(event.target.value);
|
|
319
303
|
}
|
|
320
304
|
});
|
|
321
|
-
|
|
305
|
+
|
|
306
|
+
this.#containerMain.appendChild(this.#slider);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* ---------------------------
|
|
311
|
+
* Public methods
|
|
312
|
+
* ---------------------------
|
|
313
|
+
*/
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Disposes the animation menu and releases all associated DOM resources.
|
|
317
|
+
* @public
|
|
318
|
+
* @returns {void}
|
|
319
|
+
*/
|
|
320
|
+
dispose() {
|
|
321
|
+
if (this.isVisible) {
|
|
322
|
+
this.#containerMain.remove();
|
|
323
|
+
}
|
|
324
|
+
this.#containerMain = null;
|
|
325
|
+
this.#containerButtons = null;
|
|
326
|
+
this.#slider = null;
|
|
322
327
|
}
|
|
323
328
|
|
|
324
329
|
/**
|
|
@@ -358,4 +363,20 @@ export default class OpeningAnimationMenu {
|
|
|
358
363
|
this.#animationState = state;
|
|
359
364
|
this.#setPlayerButtonsState();
|
|
360
365
|
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* ---------------------------
|
|
369
|
+
* Public getters
|
|
370
|
+
* ---------------------------
|
|
371
|
+
*/
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Returns whether the animation menu is currently visible in the DOM.
|
|
375
|
+
* @public
|
|
376
|
+
* @returns {boolean} True if the menu is visible, false otherwise.
|
|
377
|
+
*/
|
|
378
|
+
get isVisible() {
|
|
379
|
+
const menuElement = this.#canvas.parentElement.querySelector(`div.animation-menu[data-animation-name="${this.#name}"]`);
|
|
380
|
+
return !!menuElement;
|
|
381
|
+
}
|
|
361
382
|
}
|