@preference-sl/pref-viewer 2.11.0-beta.8 → 2.11.0-beta.9
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-controller.js +44 -53
- package/src/css/pref-viewer-2d.css +39 -0
- package/src/css/pref-viewer-3d.css +28 -0
- package/src/css/pref-viewer-dialog.css +105 -0
- package/src/css/pref-viewer.css +11 -0
- package/src/index.js +51 -5
- package/src/pref-viewer-2d.js +1 -1
- package/src/pref-viewer-3d.js +1 -1
- package/src/pref-viewer-dialog.js +60 -42
package/package.json
CHANGED
|
@@ -72,7 +72,6 @@ export default class BabylonJSController {
|
|
|
72
72
|
// References to parent custom elements
|
|
73
73
|
#prefViewer3D = undefined;
|
|
74
74
|
#prefViewer = undefined;
|
|
75
|
-
#prefViewerDialog = null;
|
|
76
75
|
|
|
77
76
|
// Babylon.js core objects
|
|
78
77
|
#engine = null;
|
|
@@ -862,6 +861,24 @@ export default class BabylonJSController {
|
|
|
862
861
|
return detail;
|
|
863
862
|
}
|
|
864
863
|
|
|
864
|
+
/**
|
|
865
|
+
* Appends the current date and time to the provided name string in the format: name_YYYY-MM-DD_HH.mm.ss
|
|
866
|
+
* @private
|
|
867
|
+
* @param {string} name - The base name to which the date and time will be appended.
|
|
868
|
+
* @returns {string} The name with the appended date and time.
|
|
869
|
+
*/
|
|
870
|
+
#addDateToName(name) {
|
|
871
|
+
const now = new Date();
|
|
872
|
+
const year = now.getFullYear();
|
|
873
|
+
const month = (now.getMonth() + 1).toString().padStart(2, "0");
|
|
874
|
+
const day = now.getDate().toString().padStart(2, "0");
|
|
875
|
+
const hours = now.getHours().toString().padStart(2, "0");
|
|
876
|
+
const minutes = now.getMinutes().toString().padStart(2, "0");
|
|
877
|
+
const seconds = now.getSeconds().toString().padStart(2, "0");
|
|
878
|
+
name = `${name}_${year}-${month}-${day}_${hours}.${minutes}.${seconds}`;
|
|
879
|
+
return name;
|
|
880
|
+
}
|
|
881
|
+
|
|
865
882
|
/**
|
|
866
883
|
* Generates and downloads a ZIP file with the specified folder name and files.
|
|
867
884
|
* @private
|
|
@@ -874,16 +891,7 @@ export default class BabylonJSController {
|
|
|
874
891
|
* Uses JSZip to create the ZIP and triggers a browser download.
|
|
875
892
|
*/
|
|
876
893
|
#downloadZip(files, name = "files", comment = "", addDateInName = false) {
|
|
877
|
-
|
|
878
|
-
const now = new Date();
|
|
879
|
-
const year = now.getFullYear();
|
|
880
|
-
const month = (now.getMonth() + 1).toString().padStart(2, "0");
|
|
881
|
-
const day = now.getDate().toString().padStart(2, "0");
|
|
882
|
-
const hours = now.getHours().toString().padStart(2, "0");
|
|
883
|
-
const minutes = now.getMinutes().toString().padStart(2, "0");
|
|
884
|
-
const seconds = now.getSeconds().toString().padStart(2, "0");
|
|
885
|
-
name = `${name}_${year}-${month}-${day}_${hours}.${minutes}.${seconds}`;
|
|
886
|
-
}
|
|
894
|
+
name = addDateInName ? this.#addDateToName(name) : name;
|
|
887
895
|
|
|
888
896
|
const JSZip = require("jszip");
|
|
889
897
|
const zip = new JSZip();
|
|
@@ -915,56 +923,39 @@ export default class BabylonJSController {
|
|
|
915
923
|
if (!this.#prefViewer) {
|
|
916
924
|
return;
|
|
917
925
|
}
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
<h3 style="margin: 0 0 5px; 0">Download 3D Scene</h3>
|
|
925
|
-
<h4 style="margin:0;">Content</h4>
|
|
926
|
-
<div style="display:flex;flex-direction:row;gap:15px;margin:0 10px 0 10px;">
|
|
926
|
+
|
|
927
|
+
const header = "Download 3D Scene";
|
|
928
|
+
const content = `
|
|
929
|
+
<form slot="content" id="download-dialog-form" style="display:flex;flex-direction:column;gap:16px;">
|
|
930
|
+
<h4>Content</h4>
|
|
931
|
+
<div style="display:flex;flex-direction:row;gap:16px;margin:0 8px 0 8px;">
|
|
927
932
|
<label><input type="radio" name="content" value="1"> Model</label>
|
|
928
933
|
<label><input type="radio" name="content" value="2"> Scene</label>
|
|
929
934
|
<label><input type="radio" name="content" value="0" checked> Both</label>
|
|
930
935
|
</div>
|
|
931
|
-
<h4
|
|
932
|
-
<div style="display:flex;flex-direction:row;gap:
|
|
936
|
+
<h4>Format</h4>
|
|
937
|
+
<div style="display:flex;flex-direction:row;gap:16px;margin:0 8px 0 8px;">
|
|
933
938
|
<label><input type="radio" name="format" value="gltf" checked> glTF (ZIP)</label>
|
|
934
939
|
<label><input type="radio" name="format" value="glb"> GLB</label>
|
|
935
940
|
<label><input type="radio" name="format" value="usdz"> USDZ</label>
|
|
936
941
|
</div>
|
|
937
|
-
<div style="display:flex;gap:15px;justify-content:stretch;margin-top:10px;">
|
|
938
|
-
<button type="button" id="download-dialog-download">Download</button>
|
|
939
|
-
<button type="button" id="download-dialog-cancel">Cancel</button>
|
|
940
|
-
</div>
|
|
941
|
-
<style>
|
|
942
|
-
#download-dialog-download,
|
|
943
|
-
#download-dialog-cancel {
|
|
944
|
-
width: 100%;
|
|
945
|
-
padding: 8px 20px;
|
|
946
|
-
font-size: 1.1rem;
|
|
947
|
-
border-radius: 6px;
|
|
948
|
-
border: none;
|
|
949
|
-
background: #eee;
|
|
950
|
-
cursor: pointer;
|
|
951
|
-
transition: background 0.2s;
|
|
952
|
-
}
|
|
953
|
-
#download-dialog-download:hover,
|
|
954
|
-
#download-dialog-cancel:hover {
|
|
955
|
-
background: #e0e0e0;
|
|
956
|
-
}
|
|
957
|
-
</style>
|
|
958
942
|
</form>`;
|
|
943
|
+
const footer = `
|
|
944
|
+
<button type="button" class="primary" id="download-dialog-download">Download</button>
|
|
945
|
+
<button type="button" id="download-dialog-cancel">Cancel</button>`;
|
|
959
946
|
|
|
960
|
-
this.#
|
|
947
|
+
const dialog = this.#prefViewer.openDialog(header, content, footer);
|
|
948
|
+
|
|
949
|
+
if (!dialog) {
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
961
952
|
|
|
962
953
|
// Button event handlers
|
|
963
|
-
const form =
|
|
964
|
-
const
|
|
965
|
-
const
|
|
954
|
+
const form = dialog.querySelector("#download-dialog-form");
|
|
955
|
+
const downloadButton = dialog.querySelector("#download-dialog-download");
|
|
956
|
+
const cancelButton = dialog.querySelector("#download-dialog-cancel");
|
|
966
957
|
|
|
967
|
-
|
|
958
|
+
downloadButton.onclick = () => {
|
|
968
959
|
const contentValue = form.content.value;
|
|
969
960
|
const formatValue = form.format.value;
|
|
970
961
|
switch (formatValue) {
|
|
@@ -978,14 +969,12 @@ export default class BabylonJSController {
|
|
|
978
969
|
this.downloadUSDZ(Number(contentValue));
|
|
979
970
|
break;
|
|
980
971
|
}
|
|
981
|
-
this.#
|
|
972
|
+
this.#prefViewer.closeDialog();
|
|
982
973
|
};
|
|
983
974
|
|
|
984
|
-
|
|
985
|
-
this.#
|
|
975
|
+
cancelButton.onclick = () => {
|
|
976
|
+
this.#prefViewer.closeDialog();
|
|
986
977
|
};
|
|
987
|
-
|
|
988
|
-
this.#prefViewerDialog.open(content);
|
|
989
978
|
}
|
|
990
979
|
|
|
991
980
|
/**
|
|
@@ -1117,6 +1106,7 @@ export default class BabylonJSController {
|
|
|
1117
1106
|
default:
|
|
1118
1107
|
break;
|
|
1119
1108
|
}
|
|
1109
|
+
fileName = this.#addDateToName(fileName);
|
|
1120
1110
|
GLTF2Export.GLBAsync(container, fileName, { exportWithoutWaitingForScene: true }).then((glb) => {
|
|
1121
1111
|
if (glb) {
|
|
1122
1112
|
glb.downloadFiles();
|
|
@@ -1186,6 +1176,7 @@ export default class BabylonJSController {
|
|
|
1186
1176
|
default:
|
|
1187
1177
|
break;
|
|
1188
1178
|
}
|
|
1179
|
+
fileName = this.#addDateToName(fileName);
|
|
1189
1180
|
USDZExportAsync(container).then((response) => {
|
|
1190
1181
|
if (response) {
|
|
1191
1182
|
Tools.Download(new Blob([response], { type: "model/vnd.usdz+zip" }), `${fileName}.usdz`);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/* Variables */
|
|
2
|
+
pref-viewer-2d {
|
|
3
|
+
--pref-viewer-2d-bg-color: #ffffff;
|
|
4
|
+
--pref-viewer-2d-svg-padding: 10px;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
pref-viewer-2d[visible="true"] {
|
|
8
|
+
display: block;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
pref-viewer-2d[visible="false"] {
|
|
12
|
+
display: none;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
pref-viewer-2d {
|
|
16
|
+
grid-column: 1;
|
|
17
|
+
grid-row: 1;
|
|
18
|
+
overflow: hidden;
|
|
19
|
+
min-width: 0;
|
|
20
|
+
min-height: 0;
|
|
21
|
+
align-self: stretch;
|
|
22
|
+
justify-self: stretch;
|
|
23
|
+
background: var(--pref-viewer-2d-bg-color);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
pref-viewer-2d,
|
|
27
|
+
pref-viewer-2d>div,
|
|
28
|
+
pref-viewer-2d>div>svg {
|
|
29
|
+
width: 100%;
|
|
30
|
+
height: 100%;
|
|
31
|
+
display: block;
|
|
32
|
+
position: relative;
|
|
33
|
+
outline: none;
|
|
34
|
+
box-sizing: border-box;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
pref-viewer-2d>div>svg {
|
|
38
|
+
padding: var(--pref-viewer-2d-svg-padding);
|
|
39
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
pref-viewer-3d[visible="true"] {
|
|
2
|
+
display: block;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
pref-viewer-3d[visible="false"] {
|
|
6
|
+
display: none;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
pref-viewer-3d {
|
|
10
|
+
grid-column: 1;
|
|
11
|
+
grid-row: 1;
|
|
12
|
+
overflow: hidden;
|
|
13
|
+
min-width: 0;
|
|
14
|
+
min-height: 0;
|
|
15
|
+
align-self: stretch;
|
|
16
|
+
justify-self: stretch;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
pref-viewer-3d,
|
|
20
|
+
pref-viewer-3d>div,
|
|
21
|
+
pref-viewer-3d>div>canvas {
|
|
22
|
+
width: 100%;
|
|
23
|
+
height: 100%;
|
|
24
|
+
display: block;
|
|
25
|
+
position: relative;
|
|
26
|
+
outline: none;
|
|
27
|
+
box-sizing: border-box;
|
|
28
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/* Variables */
|
|
2
|
+
pref-viewer-dialog {
|
|
3
|
+
--brand-color: #ff6700;
|
|
4
|
+
--dialog-general-space: 16px;
|
|
5
|
+
--dialog-bg-color: #ffffff;
|
|
6
|
+
--dialog-backdrop-color: rgba(0, 0, 0, 0.25);
|
|
7
|
+
--dialog-border-color: #e7e7e7;
|
|
8
|
+
--dialog-border-radius: 8px;
|
|
9
|
+
--dialog-box-shadow: 0 4px 24px rgba(0, 0, 0, 0.25);
|
|
10
|
+
--button-default-bg-color: #bbbbbb;
|
|
11
|
+
--button-default-bg-color-hover: #a1a1a1;
|
|
12
|
+
--button-primary-bg-color: color-mix(in oklab, var(--brand-color), white 25%);
|
|
13
|
+
--button-primary-bg-color-hover: var(--brand-color);
|
|
14
|
+
--button-border-radius: 4px;
|
|
15
|
+
--button-padding-horizontal: 16px;
|
|
16
|
+
--button-padding-vertical: 8px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
pref-viewer-dialog:not {
|
|
20
|
+
display: none;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
pref-viewer-dialog[open] {
|
|
24
|
+
font-family: 'Roboto', ui-sans-serif, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
|
25
|
+
grid-row: 1;
|
|
26
|
+
grid-column: 1;
|
|
27
|
+
overflow: hidden;
|
|
28
|
+
min-width: 0;
|
|
29
|
+
min-height: 0;
|
|
30
|
+
align-self: stretch;
|
|
31
|
+
justify-self: stretch;
|
|
32
|
+
display: flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
justify-content: center;
|
|
35
|
+
background-color: var(--dialog-backdrop-color);
|
|
36
|
+
position: relative;
|
|
37
|
+
z-index: 1000;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
pref-viewer-dialog>.dialog-wrapper {
|
|
41
|
+
display: flex;
|
|
42
|
+
flex-direction: column;
|
|
43
|
+
align-items: stretch;
|
|
44
|
+
background: var(--dialog-bg-color);
|
|
45
|
+
border: 1px solid var(--dialog-border-color);
|
|
46
|
+
border-radius: var(--dialog-border-radius);
|
|
47
|
+
box-shadow: var(--dialog-box-shadow);
|
|
48
|
+
padding: 0;
|
|
49
|
+
min-width: 320px;
|
|
50
|
+
max-width: 90%;
|
|
51
|
+
max-height: 90%;
|
|
52
|
+
overflow: auto;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
pref-viewer-dialog .dialog-header {
|
|
56
|
+
padding: var(--dialog-general-space);
|
|
57
|
+
border-bottom: 1px solid var(--dialog-border-color);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
pref-viewer-dialog .dialog-header h3 {
|
|
61
|
+
margin: 0;
|
|
62
|
+
font-weight: 500;
|
|
63
|
+
font-size: 1.1em;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
pref-viewer-dialog .dialog-content {
|
|
67
|
+
padding: var(--dialog-general-space);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
pref-viewer-dialog .dialog-content h4 {
|
|
71
|
+
margin: 0;
|
|
72
|
+
font-weight: 500;
|
|
73
|
+
font-size: 1.05em;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
pref-viewer-dialog .dialog-footer {
|
|
77
|
+
border-top: 1px solid var(--dialog-border-color);
|
|
78
|
+
padding: var(--dialog-general-space);
|
|
79
|
+
display: flex;
|
|
80
|
+
gap: var(--dialog-general-space);
|
|
81
|
+
justify-content: stretch;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
pref-viewer-dialog .dialog-footer button {
|
|
85
|
+
width: 100%;
|
|
86
|
+
font-size: 1em;
|
|
87
|
+
padding: var(--button-padding-vertical) var(--button-padding-horizontal);
|
|
88
|
+
border-radius: var(--button-border-radius);
|
|
89
|
+
border: none;
|
|
90
|
+
background: var(--button-default-bg-color);
|
|
91
|
+
cursor: pointer;
|
|
92
|
+
transition: background 0.2s;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
pref-viewer-dialog .dialog-footer button.primary {
|
|
96
|
+
background: var(--button-primary-bg-color);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
pref-viewer-dialog .dialog-footer button:hover {
|
|
100
|
+
background: var(--button-default-bg-color-hover);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
pref-viewer-dialog .dialog-footer button.primary:hover {
|
|
104
|
+
background: var(--button-primary-bg-color-hover);
|
|
105
|
+
}
|
package/src/index.js
CHANGED
|
@@ -54,6 +54,8 @@ import { PrefViewerTask } from "./pref-viewer-task.js";
|
|
|
54
54
|
* - downloadSceneGLB(): Downloads the environment as a GLB file.
|
|
55
55
|
* - downloadSceneGLTF(): Downloads the environment as a glTF ZIP file.
|
|
56
56
|
* - downloadSceneUSDZ(): Downloads the environment as a USDZ file.
|
|
57
|
+
* - openDialog(title, content, footer): Opens a modal dialog with the specified title, content, and footer.
|
|
58
|
+
* - closeDialog(): Closes the currently open dialog, if any, and removes it from the DOM.
|
|
57
59
|
*
|
|
58
60
|
* Public Properties:
|
|
59
61
|
* - isInitialized: Indicates if the viewer is initialized.
|
|
@@ -82,6 +84,7 @@ class PrefViewer extends HTMLElement {
|
|
|
82
84
|
|
|
83
85
|
#taskQueue = [];
|
|
84
86
|
|
|
87
|
+
#wrapper = null;
|
|
85
88
|
#component2D = null;
|
|
86
89
|
#component3D = null;
|
|
87
90
|
#dialog = null;
|
|
@@ -165,6 +168,14 @@ class PrefViewer extends HTMLElement {
|
|
|
165
168
|
* @returns {void|boolean} Returns false if initialization fails; otherwise void.
|
|
166
169
|
*/
|
|
167
170
|
connectedCallback() {
|
|
171
|
+
this.#wrapper = document.createElement("div");
|
|
172
|
+
this.#wrapper.classList.add("pref-viewer-wrapper");
|
|
173
|
+
this.shadowRoot.append(this.#wrapper);
|
|
174
|
+
|
|
175
|
+
const style = document.createElement("style");
|
|
176
|
+
style.textContent = `@import "../src/css/pref-viewer.css";`;
|
|
177
|
+
this.shadowRoot.append(style);
|
|
178
|
+
|
|
168
179
|
this.#createComponent3D();
|
|
169
180
|
this.#createComponent2D();
|
|
170
181
|
|
|
@@ -186,7 +197,7 @@ class PrefViewer extends HTMLElement {
|
|
|
186
197
|
this.#component2D = document.createElement("pref-viewer-2d");
|
|
187
198
|
this.#component2D.setAttribute("visible", "false");
|
|
188
199
|
this.#component2D.addEventListener("drawing-zoom-changed", this.#on2DZoomChanged.bind(this));
|
|
189
|
-
this.
|
|
200
|
+
this.#wrapper.appendChild(this.#component2D);
|
|
190
201
|
}
|
|
191
202
|
|
|
192
203
|
/**
|
|
@@ -198,7 +209,7 @@ class PrefViewer extends HTMLElement {
|
|
|
198
209
|
#createComponent3D() {
|
|
199
210
|
this.#component3D = document.createElement("pref-viewer-3d");
|
|
200
211
|
this.#component3D.setAttribute("visible", "false");
|
|
201
|
-
this.
|
|
212
|
+
this.#wrapper.appendChild(this.#component3D);
|
|
202
213
|
}
|
|
203
214
|
|
|
204
215
|
/**
|
|
@@ -500,11 +511,14 @@ class PrefViewer extends HTMLElement {
|
|
|
500
511
|
this.#component3D?.hide();
|
|
501
512
|
this.#component2D?.show();
|
|
502
513
|
} else {
|
|
503
|
-
this.#component3D?.show();
|
|
504
514
|
this.#component2D?.hide();
|
|
515
|
+
this.#component3D?.show();
|
|
505
516
|
}
|
|
506
517
|
if (this.getAttribute("mode") !== mode) {
|
|
507
518
|
this.setAttribute("mode", mode);
|
|
519
|
+
if (this.#dialog) {
|
|
520
|
+
this.closeDialog();
|
|
521
|
+
}
|
|
508
522
|
}
|
|
509
523
|
}
|
|
510
524
|
|
|
@@ -734,7 +748,7 @@ class PrefViewer extends HTMLElement {
|
|
|
734
748
|
|
|
735
749
|
this.#component3D.downloadModelUSDZ();
|
|
736
750
|
}
|
|
737
|
-
|
|
751
|
+
|
|
738
752
|
/**
|
|
739
753
|
* Initiates download of the current complete scene (3D model and environment) in GLB format.
|
|
740
754
|
* @public
|
|
@@ -809,10 +823,42 @@ class PrefViewer extends HTMLElement {
|
|
|
809
823
|
if (!this.#component3D) {
|
|
810
824
|
return;
|
|
811
825
|
}
|
|
812
|
-
|
|
813
826
|
this.#component3D.downloadSceneUSDZ();
|
|
814
827
|
}
|
|
815
828
|
|
|
829
|
+
/**
|
|
830
|
+
* Opens a modal dialog with the specified title, content, and footer.
|
|
831
|
+
* @public
|
|
832
|
+
* @param {string} title - The dialog title to display in the header.
|
|
833
|
+
* @param {string} content - The HTML content to display in the dialog body.
|
|
834
|
+
* @param {string} footer - The HTML content to display in the dialog footer (e.g., action buttons).
|
|
835
|
+
* @returns {HTMLElement} The created dialog element.
|
|
836
|
+
* @description
|
|
837
|
+
* If a dialog is already open, it is closed before opening the new one.
|
|
838
|
+
* The dialog is appended to the viewer's shadow DOM and returned for further manipulation.
|
|
839
|
+
*/
|
|
840
|
+
openDialog(title, content, footer) {
|
|
841
|
+
if (this.#dialog && this.#dialog.hasAttribute("open")) {
|
|
842
|
+
this.#dialog.close();
|
|
843
|
+
}
|
|
844
|
+
this.#dialog = document.createElement("pref-viewer-dialog");
|
|
845
|
+
this.shadowRoot.querySelector(".pref-viewer-wrapper").appendChild(this.#dialog);
|
|
846
|
+
const opened = this.#dialog.open(title, content, footer);
|
|
847
|
+
return opened ? this.#dialog : null;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* Closes the currently open dialog, if any, and removes it from the DOM.
|
|
852
|
+
* @public
|
|
853
|
+
* @returns {void}
|
|
854
|
+
*/
|
|
855
|
+
closeDialog() {
|
|
856
|
+
if (this.#dialog) {
|
|
857
|
+
this.#dialog.close();
|
|
858
|
+
this.#dialog = null;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
816
862
|
/**
|
|
817
863
|
* ---------------------------
|
|
818
864
|
* Public properties
|
package/src/pref-viewer-2d.js
CHANGED
|
@@ -144,7 +144,7 @@ export class PrefViewer2D extends HTMLElement {
|
|
|
144
144
|
this.#wrapper = document.createElement("div");
|
|
145
145
|
this.append(this.#wrapper);
|
|
146
146
|
const style = document.createElement("style");
|
|
147
|
-
style.textContent =
|
|
147
|
+
style.textContent = `@import "../src/css/pref-viewer-2d.css";`;
|
|
148
148
|
this.append(style);
|
|
149
149
|
}
|
|
150
150
|
|
package/src/pref-viewer-3d.js
CHANGED
|
@@ -160,7 +160,7 @@ export class PrefViewer3D extends HTMLElement {
|
|
|
160
160
|
this.#canvas = document.createElement("canvas");
|
|
161
161
|
this.#wrapper.appendChild(this.#canvas);
|
|
162
162
|
const style = document.createElement("style");
|
|
163
|
-
style.textContent =
|
|
163
|
+
style.textContent = `@import "../src/css/pref-viewer-3d.css";`;
|
|
164
164
|
this.append(style);
|
|
165
165
|
}
|
|
166
166
|
|
|
@@ -9,19 +9,22 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Usage:
|
|
11
11
|
* - Use as a custom HTML element: <pref-viewer-dialog></pref-viewer-dialog>
|
|
12
|
-
* - Call open(content) to display the dialog with
|
|
12
|
+
* - Call open(title, content, footer) to display the dialog with a header, content, and footer.
|
|
13
13
|
* - Call close() to hide and remove the dialog.
|
|
14
14
|
*
|
|
15
15
|
* Methods:
|
|
16
16
|
* - constructor(): Initializes the custom element.
|
|
17
17
|
* - connectedCallback(): Called when the element is added to the DOM; sets up DOM and styles.
|
|
18
18
|
* - disconnectedCallback(): Called when the element is removed from the DOM; cleans up resources.
|
|
19
|
-
* - open(content): Displays the dialog and
|
|
19
|
+
* - open(title, content, footer): Displays the dialog and sets its header, content, and footer.
|
|
20
20
|
* - close(): Hides and removes the dialog, refocuses the canvas if available.
|
|
21
21
|
* - #createDOMElements(): Creates the dialog structure and applies styles.
|
|
22
22
|
*/
|
|
23
23
|
export class PrefViewerDialog extends HTMLElement {
|
|
24
24
|
#wrapper = null;
|
|
25
|
+
#header = null;
|
|
26
|
+
#content = null;
|
|
27
|
+
#footer = null;
|
|
25
28
|
|
|
26
29
|
/**
|
|
27
30
|
* Initializes the custom dialog element.
|
|
@@ -45,7 +48,9 @@ export class PrefViewerDialog extends HTMLElement {
|
|
|
45
48
|
* Cleans up resources and event listeners.
|
|
46
49
|
* @returns {void}
|
|
47
50
|
*/
|
|
48
|
-
disconnectedCallback() {
|
|
51
|
+
disconnectedCallback() {
|
|
52
|
+
this.removeEventListener("click", this.#closeOnBackdropClick.bind(this));
|
|
53
|
+
}
|
|
49
54
|
|
|
50
55
|
/**
|
|
51
56
|
* Creates the dialog's DOM structure and applies CSS styles.
|
|
@@ -55,51 +60,57 @@ export class PrefViewerDialog extends HTMLElement {
|
|
|
55
60
|
*/
|
|
56
61
|
#createDOMElements() {
|
|
57
62
|
this.#wrapper = document.createElement("div");
|
|
63
|
+
this.#wrapper.classList.add("dialog-wrapper");
|
|
64
|
+
this.#wrapper.innerHTML = `
|
|
65
|
+
<div class="dialog-header"><h3 class="dialog-header-title"></h3></div>
|
|
66
|
+
<div class="dialog-content"></div>
|
|
67
|
+
<div class="dialog-footer"></div>`;
|
|
58
68
|
this.append(this.#wrapper);
|
|
69
|
+
|
|
59
70
|
const style = document.createElement("style");
|
|
60
|
-
style.textContent =
|
|
61
|
-
pref-viewer-dialog {
|
|
62
|
-
display: none;
|
|
63
|
-
}
|
|
64
|
-
pref-viewer-dialog[open] {
|
|
65
|
-
font-family: ui-sans-serif, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
|
66
|
-
width: 100%;
|
|
67
|
-
height: 100%;
|
|
68
|
-
display: flex;
|
|
69
|
-
align-items: center;
|
|
70
|
-
justify-content: center;
|
|
71
|
-
background-color: rgba(0, 0, 0, 0.25);
|
|
72
|
-
position: fixed;
|
|
73
|
-
top: 0;
|
|
74
|
-
left: 0;
|
|
75
|
-
z-index: 1000;
|
|
76
|
-
}
|
|
77
|
-
pref-viewer-dialog > div:first-child {
|
|
78
|
-
display: inline-block;
|
|
79
|
-
background: #fff;
|
|
80
|
-
border-radius: 10px;
|
|
81
|
-
box-shadow: 0 4px 24px rgba(0,0,0,0.25);
|
|
82
|
-
padding: 15px;
|
|
83
|
-
max-width: 90%;
|
|
84
|
-
max-height: 90%;
|
|
85
|
-
overflow: auto;
|
|
86
|
-
}`;
|
|
71
|
+
style.textContent = `@import "../src/css/pref-viewer-dialog.css";`;
|
|
87
72
|
this.append(style);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
73
|
+
|
|
74
|
+
this.addEventListener("click", this.#closeOnBackdropClick.bind(this));
|
|
75
|
+
|
|
76
|
+
this.#header = this.#wrapper.querySelector(".dialog-header");
|
|
77
|
+
this.#content = this.#wrapper.querySelector(".dialog-content");
|
|
78
|
+
this.#footer = this.#wrapper.querySelector(".dialog-footer");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
#closeOnBackdropClick(event) {
|
|
82
|
+
if (event.target === this) {
|
|
83
|
+
this.close();
|
|
84
|
+
}
|
|
93
85
|
}
|
|
94
86
|
|
|
95
87
|
/**
|
|
96
|
-
* Opens the dialog and
|
|
97
|
-
* @param {
|
|
98
|
-
* @
|
|
88
|
+
* Opens the dialog and sets its header, content, and footer.
|
|
89
|
+
* @param {string} title - The dialog title to display in the header.
|
|
90
|
+
* @param {string} content - The HTML content to display in the dialog body.
|
|
91
|
+
* @param {string} footer - The HTML content to display in the dialog footer (e.g., action buttons).
|
|
92
|
+
* @returns {boolean} True if the dialog was opened, false if no content was provided.
|
|
99
93
|
*/
|
|
100
|
-
open(content) {
|
|
94
|
+
open(title = "", content = "", footer = "") {
|
|
95
|
+
if (this.#wrapper === null || this.#header === null || this.#content === null || this.#footer === null) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
if (title === "" && content === "" && footer === "") {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (title === "") {
|
|
103
|
+
this.#header.style.display = "none";
|
|
104
|
+
}
|
|
105
|
+
if (footer === "") {
|
|
106
|
+
this.#footer.style.display = "none";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
this.#header.querySelector(".dialog-header-title").innerHTML = title;
|
|
110
|
+
this.#content.innerHTML = content;
|
|
111
|
+
this.#footer.innerHTML = footer;
|
|
101
112
|
this.setAttribute("open", "");
|
|
102
|
-
|
|
113
|
+
return true;
|
|
103
114
|
}
|
|
104
115
|
|
|
105
116
|
/**
|
|
@@ -110,12 +121,19 @@ export class PrefViewerDialog extends HTMLElement {
|
|
|
110
121
|
this.removeAttribute("open");
|
|
111
122
|
const parent = this.getRootNode().host;
|
|
112
123
|
if (parent) {
|
|
113
|
-
// Refocus canvas for accessibility
|
|
114
|
-
const canvas = parent.shadowRoot.querySelector("canvas");
|
|
124
|
+
// Refocus 3D canvas or 2D component for accessibility
|
|
125
|
+
const canvas = parent.shadowRoot.querySelector("pref-viewer-3d[visible='true'] canvas");
|
|
115
126
|
if (canvas) {
|
|
116
127
|
canvas.focus();
|
|
128
|
+
} else {
|
|
129
|
+
const component2D = parent.shadowRoot.querySelector("pref-viewer-2d[visible='true']");
|
|
130
|
+
if (component2D) {
|
|
131
|
+
component2D.focus();
|
|
132
|
+
}
|
|
117
133
|
}
|
|
134
|
+
|
|
118
135
|
}
|
|
136
|
+
this.#wrapper = this.#header = this.#content = this.#footer = null;
|
|
119
137
|
this.remove();
|
|
120
138
|
}
|
|
121
139
|
}
|