iobroker.mywebui 1.42.37 → 1.42.38
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/io-package.json
CHANGED
package/package.json
CHANGED
|
@@ -19,15 +19,10 @@ export class IobrokerWebui3DScreenEditor extends BaseCustomWebComponentConstruct
|
|
|
19
19
|
<button id="scriptToggleBtn" class="tb-btn" title="Toggle scene script panel">📄 Script</button>
|
|
20
20
|
</div>
|
|
21
21
|
|
|
22
|
-
<!--
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
<!-- ioBroker 3D properties panel -->
|
|
27
|
-
<iobroker-webui-3dscreen-properties id="iobPropPanel"
|
|
28
|
-
style="width:300px;flex-shrink:0;border-left:1px solid #3c3c3c;overflow:hidden;">
|
|
29
|
-
</iobroker-webui-3dscreen-properties>
|
|
30
|
-
</div>
|
|
22
|
+
<!-- three.js editor mount point
|
|
23
|
+
position:relative → contains position:absolute children (viewport, sidebar, etc.)
|
|
24
|
+
overflow:visible → allow position:fixed dropdowns to escape clip boundary -->
|
|
25
|
+
<div id="editorContainer" style="flex:1;position:relative;overflow:hidden;min-height:0;"></div>
|
|
31
26
|
|
|
32
27
|
<!-- Bottom Monaco panel for iobroker scene script -->
|
|
33
28
|
<div id="scriptPanel" style="height:0;flex-shrink:0;border-top:2px solid #3c3c3c;display:flex;flex-direction:column;overflow:hidden;background:#1e1e1e;transition:height 0.15s;">
|
|
@@ -282,12 +277,6 @@ export class IobrokerWebui3DScreenEditor extends BaseCustomWebComponentConstruct
|
|
|
282
277
|
this._resizeObserver = new ResizeObserver(() => editor.signals.windowResize.dispatch());
|
|
283
278
|
this._resizeObserver.observe(container);
|
|
284
279
|
|
|
285
|
-
// Connect ioBroker properties panel
|
|
286
|
-
const iobPropPanel = this._getDomElement('iobPropPanel');
|
|
287
|
-
if (iobPropPanel?.connect) {
|
|
288
|
-
iobPropPanel.connect(editor, this).catch(e => console.warn('3D prop panel connect error:', e));
|
|
289
|
-
}
|
|
290
|
-
|
|
291
280
|
// Dispatch resize after layout is complete (container may have 0 size on first tick)
|
|
292
281
|
requestAnimationFrame(() => {
|
|
293
282
|
editor.signals.windowResize.dispatch();
|
|
@@ -3,103 +3,60 @@ import { iobrokerHandler } from "../common/IobrokerHandler.js";
|
|
|
3
3
|
|
|
4
4
|
const EDITOR_BASE = new URL('../../../3d-editor/', import.meta.url).href;
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
/**
|
|
7
|
+
* IobrokerWebui3DScreenPropertiesPanel
|
|
8
|
+
* Injected into the webui's "Properties" (attributeDock) when a 3D screen editor is active.
|
|
9
|
+
* Has sub-tabs: Scene | Object | Geom | Mater — no own bottom tab bar.
|
|
10
|
+
* connect(editor, editorHost) must be called after Three.js editor is ready.
|
|
11
|
+
*/
|
|
9
12
|
export class IobrokerWebui3DScreenPropertiesPanel extends BaseCustomWebComponentConstructorAppend {
|
|
10
13
|
|
|
11
14
|
static template = html`
|
|
12
15
|
<div id="root">
|
|
13
|
-
<!--
|
|
14
|
-
<div id="
|
|
15
|
-
<span class="
|
|
16
|
-
<span
|
|
16
|
+
<!-- Sub-tab bar: Scene / Object / Geom / Mater -->
|
|
17
|
+
<div id="subBar">
|
|
18
|
+
<span class="stab active" data-sub="scene">Scene</span>
|
|
19
|
+
<span class="stab" data-sub="object">Object</span>
|
|
20
|
+
<span class="stab" data-sub="geom">Geom</span>
|
|
21
|
+
<span class="stab" data-sub="mater">Mater</span>
|
|
17
22
|
</div>
|
|
18
23
|
|
|
19
|
-
<!--
|
|
20
|
-
<div id="
|
|
21
|
-
|
|
22
|
-
<!-- PROP tab: Scene / Object / Geom / Mater sub-tabs -->
|
|
23
|
-
<div id="tabProp" class="mpanel active">
|
|
24
|
-
<div id="propSubBar">
|
|
25
|
-
<span class="stab active" data-sub="scene">Scene</span>
|
|
26
|
-
<span class="stab" data-sub="object">Object</span>
|
|
27
|
-
<span class="stab" data-sub="geom">Geom</span>
|
|
28
|
-
<span class="stab" data-sub="mater">Mater</span>
|
|
29
|
-
</div>
|
|
30
|
-
<div id="subScene" class="scontent active"></div>
|
|
31
|
-
<div id="subObject" class="scontent" style="display:none;">
|
|
32
|
-
<div id="objProps"></div>
|
|
33
|
-
<div id="bindSection">
|
|
34
|
-
<div class="sec-hdr">Bindings (ioBroker)</div>
|
|
35
|
-
<div id="bindRows"></div>
|
|
36
|
-
</div>
|
|
37
|
-
</div>
|
|
38
|
-
<div id="subGeom" class="scontent" style="display:none;"></div>
|
|
39
|
-
<div id="subMater" class="scontent" style="display:none;"></div>
|
|
40
|
-
</div>
|
|
41
|
-
|
|
42
|
-
<!-- SETTI tab -->
|
|
43
|
-
<div id="tabSetti" class="mpanel" style="display:none;">
|
|
44
|
-
<div id="settingsWrap"></div>
|
|
45
|
-
<div id="visSection">
|
|
46
|
-
<div class="sec-hdr">Screen Visibility Control</div>
|
|
47
|
-
<div id="visContent"></div>
|
|
48
|
-
</div>
|
|
49
|
-
</div>
|
|
24
|
+
<!-- Sub-tab panels -->
|
|
25
|
+
<div id="subScene" class="spanel active"></div>
|
|
50
26
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
27
|
+
<div id="subObject" class="spanel" style="display:none;">
|
|
28
|
+
<!-- Three.js SidebarObject embedded here -->
|
|
29
|
+
<div id="objThreeWrap"></div>
|
|
30
|
+
<!-- ioBroker bindings section -->
|
|
31
|
+
<div id="bindSection">
|
|
32
|
+
<div class="sechdr">Bindings (ioBroker states)</div>
|
|
33
|
+
<div id="bindRows"></div>
|
|
54
34
|
</div>
|
|
55
|
-
|
|
56
|
-
<!-- VISI tab -->
|
|
57
|
-
<div id="tabVisi" class="mpanel" style="display:none;">
|
|
58
|
-
<div id="visiContent2" style="padding:10px;"></div>
|
|
59
|
-
</div>
|
|
60
|
-
|
|
61
|
-
<!-- REFAC tab -->
|
|
62
|
-
<div id="tabRefac" class="mpanel" style="display:none;">
|
|
63
|
-
<div style="padding:12px;color:#888;font-size:11px;">Refactor options for 3D screen.</div>
|
|
64
|
-
</div>
|
|
65
|
-
|
|
66
|
-
<!-- TRANSL tab -->
|
|
67
|
-
<div id="tabTransl" class="mpanel" style="display:none;">
|
|
68
|
-
<div id="translWrap" style="padding:10px;"></div>
|
|
69
|
-
</div>
|
|
70
|
-
|
|
71
35
|
</div>
|
|
72
36
|
|
|
73
|
-
|
|
74
|
-
<div id="
|
|
75
|
-
<span class="btab active" data-t="Prop">Prop...</span>
|
|
76
|
-
<span class="btab" data-t="Setti">Setti...</span>
|
|
77
|
-
<span class="btab" data-t="Proj">Proj...</span>
|
|
78
|
-
<span class="btab" data-t="Visi">Visi...</span>
|
|
79
|
-
<span class="btab" data-t="Refac">Refac...</span>
|
|
80
|
-
<span class="btab" data-t="Transl">Transl...</span>
|
|
81
|
-
</div>
|
|
37
|
+
<div id="subGeom" class="spanel" style="display:none;"></div>
|
|
38
|
+
<div id="subMater" class="spanel" style="display:none;"></div>
|
|
82
39
|
|
|
83
|
-
<!-- Binding dialog overlay -->
|
|
40
|
+
<!-- Binding dialog (inline overlay) -->
|
|
84
41
|
<div id="bdOverlay">
|
|
85
|
-
<div id="
|
|
86
|
-
<div id="
|
|
87
|
-
<div class="
|
|
88
|
-
<div class="
|
|
42
|
+
<div id="bdBox">
|
|
43
|
+
<div id="bdTitle">Binding — <span id="bdPropName"></span></div>
|
|
44
|
+
<div class="bdr">
|
|
45
|
+
<div class="bdl">ioBroker State ID</div>
|
|
89
46
|
<div style="display:flex;gap:4px;">
|
|
90
|
-
<input id="
|
|
91
|
-
<button id="bdBrowse"
|
|
47
|
+
<input id="bdSig" type="text" placeholder="mywebui.0.data.value" autocomplete="off" />
|
|
48
|
+
<button id="bdBrowse">...</button>
|
|
92
49
|
</div>
|
|
93
50
|
</div>
|
|
94
|
-
<div class="
|
|
95
|
-
<div class="
|
|
96
|
-
<input id="
|
|
51
|
+
<div class="bdr">
|
|
52
|
+
<div class="bdl">Formula <small>(use <b>val</b> for state value)</small></div>
|
|
53
|
+
<input id="bdForm" type="text" placeholder="val" />
|
|
97
54
|
</div>
|
|
98
|
-
<div class="
|
|
99
|
-
<input id="
|
|
100
|
-
<
|
|
55
|
+
<div class="bdr" style="display:flex;align-items:center;gap:8px;">
|
|
56
|
+
<input id="bdTw" type="checkbox" />
|
|
57
|
+
<span style="color:#ccc;font-size:11px;">Two-way (write back)</span>
|
|
101
58
|
</div>
|
|
102
|
-
<div id="
|
|
59
|
+
<div id="bdFoot">
|
|
103
60
|
<button id="bdClear">Clear</button>
|
|
104
61
|
<button id="bdCancel">Cancel</button>
|
|
105
62
|
<button id="bdOk" class="primary">OK</button>
|
|
@@ -113,487 +70,259 @@ export class IobrokerWebui3DScreenPropertiesPanel extends BaseCustomWebComponent
|
|
|
113
70
|
:host { display:block;width:100%;height:100%;overflow:hidden; }
|
|
114
71
|
#root { width:100%;height:100%;display:flex;flex-direction:column;overflow:hidden;background:#1a1a1a;color:#ccc;font-family:Helvetica,Arial,sans-serif;font-size:12px;position:relative; }
|
|
115
72
|
|
|
116
|
-
/*
|
|
117
|
-
#
|
|
118
|
-
.
|
|
119
|
-
#typeVal { color:#9cdcfe; }
|
|
120
|
-
|
|
121
|
-
/* Main area */
|
|
122
|
-
#mainArea { flex:1;min-height:0;overflow:hidden;position:relative; }
|
|
123
|
-
.mpanel { width:100%;height:100%;overflow:auto;display:flex;flex-direction:column; }
|
|
124
|
-
|
|
125
|
-
/* Props sub-tab bar */
|
|
126
|
-
#propSubBar { display:flex;background:#2d2d30;border-bottom:1px solid #3c3c3c;flex-shrink:0;overflow-x:auto; }
|
|
127
|
-
.stab { padding:5px 10px;cursor:pointer;font-size:11px;color:#888;border-right:1px solid #3c3c3c;white-space:nowrap;user-select:none; }
|
|
73
|
+
/* Sub-tab bar */
|
|
74
|
+
#subBar { display:flex;background:#2d2d30;border-bottom:1px solid #3c3c3c;flex-shrink:0; }
|
|
75
|
+
.stab { padding:6px 12px;cursor:pointer;font-size:11px;color:#888;border-right:1px solid #3c3c3c;white-space:nowrap;user-select:none; }
|
|
128
76
|
.stab:hover { color:#ddd;background:#3e3e42; }
|
|
129
77
|
.stab.active { color:#fff;background:#1e1e1e;border-top:2px solid #4ec9b0; }
|
|
130
78
|
|
|
131
|
-
|
|
79
|
+
/* Sub-tab panels */
|
|
80
|
+
.spanel { flex:1;overflow:auto;min-height:0; }
|
|
132
81
|
|
|
133
82
|
/* Section header */
|
|
134
|
-
.
|
|
135
|
-
|
|
136
|
-
/* Object property rows */
|
|
137
|
-
#objProps { padding:4px 0; }
|
|
138
|
-
.prop-row { display:flex;align-items:center;padding:2px 6px;min-height:22px;border-bottom:1px solid #252526; }
|
|
139
|
-
.prop-row:hover { background:#2a2a2a; }
|
|
140
|
-
.bind-btn { width:13px;height:13px;border:1px solid #555;background:#1a1a1a;cursor:pointer;margin-right:5px;flex-shrink:0;font-size:8px;display:flex;align-items:center;justify-content:center;border-radius:1px; }
|
|
141
|
-
.bind-btn.bound { background:#0d7a5a;border-color:#4ec9b0; }
|
|
142
|
-
.bind-btn:hover { border-color:#9cdcfe; }
|
|
143
|
-
.prop-label { width:72px;flex-shrink:0;color:#aaa;font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap; }
|
|
144
|
-
.prop-inputs { flex:1;display:flex;align-items:center;gap:2px; }
|
|
145
|
-
.pnum { width:52px;background:#3c3c3c;border:1px solid #555;color:#ccc;padding:2px 4px;font-size:11px;text-align:right; }
|
|
146
|
-
.pnum:focus { border-color:#4ec9b0;outline:none; }
|
|
147
|
-
.ptxt { flex:1;background:#3c3c3c;border:1px solid #555;color:#ccc;padding:2px 4px;font-size:11px; }
|
|
148
|
-
.ptxt:focus { border-color:#4ec9b0;outline:none; }
|
|
149
|
-
.ax { color:#666;font-size:9px;margin-right:1px; }
|
|
150
|
-
.pchk { cursor:pointer; }
|
|
151
|
-
.pval-ro { color:#888;font-size:11px;font-family:monospace; }
|
|
83
|
+
.sechdr { background:#2d2d30;color:#9cdcfe;padding:5px 8px;font-size:10px;font-weight:bold;text-transform:uppercase;border-top:1px solid #3c3c3c;border-bottom:1px solid #3c3c3c;letter-spacing:.5px; }
|
|
152
84
|
|
|
153
85
|
/* Binding rows */
|
|
154
|
-
#
|
|
155
|
-
|
|
156
|
-
.brow {
|
|
157
|
-
.
|
|
158
|
-
.
|
|
159
|
-
.
|
|
160
|
-
.
|
|
161
|
-
.
|
|
162
|
-
|
|
163
|
-
/*
|
|
164
|
-
#
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
/* Visibility UI */
|
|
170
|
-
.vis-row { margin:8px 10px; }
|
|
171
|
-
.vis-lbl { color:#888;font-size:11px;margin-bottom:3px;display:block; }
|
|
172
|
-
.vis-select { width:100%;background:#3c3c3c;border:1px solid #555;color:#ccc;padding:4px;font-size:11px; }
|
|
173
|
-
.vis-group-list { border:1px solid #444;padding:6px;max-height:100px;overflow-y:auto;background:#1e1e1e;font-size:11px; }
|
|
174
|
-
.vis-group-item { display:flex;align-items:center;gap:6px;padding:2px 0; }
|
|
175
|
-
.vis-check { cursor:pointer; }
|
|
86
|
+
#bindRows { padding:2px 0; }
|
|
87
|
+
.brow { display:flex;align-items:center;padding:3px 6px;gap:5px;border-bottom:1px solid #252526;min-height:22px; }
|
|
88
|
+
.brow:hover { background:#252526; }
|
|
89
|
+
.blbl { width:96px;color:#888;font-size:10px;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap; }
|
|
90
|
+
.bsig { flex:1;color:#4ec9b0;font-size:10px;font-family:monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap; }
|
|
91
|
+
.bsig.none { color:#555; }
|
|
92
|
+
.bedit { padding:1px 6px;background:#2d2d30;border:1px solid #555;color:#ccc;cursor:pointer;font-size:10px;flex-shrink:0; }
|
|
93
|
+
.bedit:hover { border-color:#4ec9b0;color:#4ec9b0; }
|
|
94
|
+
|
|
95
|
+
/* Three.js panel host — reset some Three.js CSS for dark bg */
|
|
96
|
+
#objThreeWrap { overflow:auto; }
|
|
97
|
+
#subScene { overflow:auto; }
|
|
98
|
+
#subGeom { overflow:auto; }
|
|
99
|
+
#subMater { overflow:auto; }
|
|
176
100
|
|
|
177
101
|
/* Binding dialog */
|
|
178
|
-
#bdOverlay { display:none;position:absolute;inset:0;background:rgba(0,0,0,0.
|
|
179
|
-
#bdOverlay.
|
|
180
|
-
#
|
|
181
|
-
#
|
|
182
|
-
.
|
|
183
|
-
.
|
|
184
|
-
.
|
|
185
|
-
.
|
|
186
|
-
#bdBrowse { padding:3px
|
|
187
|
-
#
|
|
188
|
-
#
|
|
189
|
-
#
|
|
102
|
+
#bdOverlay { display:none;position:absolute;inset:0;background:rgba(0,0,0,0.78);z-index:300;align-items:center;justify-content:center; }
|
|
103
|
+
#bdOverlay.open { display:flex; }
|
|
104
|
+
#bdBox { background:#252526;border:1px solid #555;border-radius:4px;padding:14px;width:90%;max-width:310px;color:#ccc;font-size:12px; }
|
|
105
|
+
#bdTitle { font-weight:bold;color:#9cdcfe;margin-bottom:10px;font-size:12px; }
|
|
106
|
+
.bdr { margin-bottom:8px; }
|
|
107
|
+
.bdl { color:#888;font-size:10px;margin-bottom:3px; }
|
|
108
|
+
.bdr input[type="text"] { width:100%;background:#3c3c3c;border:1px solid #555;color:#ccc;padding:4px;font-size:11px;box-sizing:border-box; }
|
|
109
|
+
.bdr input[type="text"]:focus { border-color:#4ec9b0;outline:none; }
|
|
110
|
+
#bdBrowse { padding:3px 8px;background:#3c3c3c;border:1px solid #555;color:#ccc;cursor:pointer;font-size:11px; }
|
|
111
|
+
#bdFoot { display:flex;justify-content:flex-end;gap:6px;margin-top:12px; }
|
|
112
|
+
#bdFoot button { padding:4px 12px;border:1px solid #555;background:#3c3c3c;color:#ccc;cursor:pointer;font-size:11px; }
|
|
113
|
+
#bdFoot button.primary { background:#0e639c;border-color:#1177bb;color:#fff; }
|
|
190
114
|
`;
|
|
191
115
|
|
|
192
116
|
_editor = null;
|
|
193
117
|
_editorHost = null;
|
|
194
|
-
_bindings = {};
|
|
118
|
+
_bindings = {};
|
|
195
119
|
_selectedObj = null;
|
|
196
|
-
|
|
120
|
+
_bindTarget = null;
|
|
197
121
|
|
|
198
|
-
// ──
|
|
122
|
+
// ── Public API ─────────────────────────────────────────────────────────────
|
|
199
123
|
|
|
200
124
|
async connect(editor, editorHost) {
|
|
201
|
-
this._editor
|
|
125
|
+
this._editor = editor;
|
|
202
126
|
this._editorHost = editorHost;
|
|
203
|
-
this._bindings
|
|
127
|
+
this._bindings = editorHost?.sceneData?.bindings ?? {};
|
|
204
128
|
|
|
205
129
|
await this._injectThreeCSS();
|
|
206
|
-
await this.
|
|
207
|
-
this.
|
|
208
|
-
this.
|
|
130
|
+
await this._mountThreePanels();
|
|
131
|
+
this._setupSubTabs();
|
|
132
|
+
this._setupBindingRows();
|
|
209
133
|
this._setupBindingDialog();
|
|
210
|
-
this._setupVisibilityUI();
|
|
211
134
|
|
|
212
|
-
editor.signals.objectSelected.add(
|
|
135
|
+
editor.signals.objectSelected.add(obj => {
|
|
213
136
|
this._selectedObj = obj;
|
|
214
|
-
this.
|
|
215
|
-
this._updateBindRows(obj);
|
|
216
|
-
const typeEl = this._getDomElement('typeVal');
|
|
217
|
-
if (typeEl) typeEl.textContent = obj ? (obj.type ?? '-') : '-';
|
|
137
|
+
this._refreshBindRows();
|
|
218
138
|
});
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if (obj && obj === this._selectedObj) this._updateObjectPanel(obj);
|
|
139
|
+
editor.signals.objectChanged.add(obj => {
|
|
140
|
+
if (obj && obj === this._selectedObj) this._refreshBindRows();
|
|
222
141
|
});
|
|
223
142
|
|
|
224
|
-
this.
|
|
225
|
-
this._updateBindRows(null);
|
|
143
|
+
this._refreshBindRows();
|
|
226
144
|
}
|
|
227
145
|
|
|
228
|
-
/** Call this before saving scene data */
|
|
229
146
|
getBindings() { return this._bindings; }
|
|
230
147
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
this.
|
|
234
|
-
if (this._selectedObj) this._updateBindRows(this._selectedObj);
|
|
148
|
+
setBindings(b) {
|
|
149
|
+
this._bindings = b || {};
|
|
150
|
+
this._refreshBindRows();
|
|
235
151
|
}
|
|
236
152
|
|
|
237
|
-
// ── CSS
|
|
153
|
+
// ── Three.js CSS ────────────────────────────────────────────────────────────
|
|
238
154
|
|
|
239
155
|
async _injectThreeCSS() {
|
|
240
156
|
const sr = this.shadowRoot;
|
|
241
|
-
if (!sr || sr.querySelector('#
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
sr.appendChild(lnk);
|
|
157
|
+
if (!sr || sr.querySelector('#tcss')) return;
|
|
158
|
+
const l = document.createElement('link');
|
|
159
|
+
l.id = 'tcss'; l.rel = 'stylesheet';
|
|
160
|
+
l.href = EDITOR_BASE + 'css/main.css';
|
|
161
|
+
sr.appendChild(l);
|
|
247
162
|
}
|
|
248
163
|
|
|
249
|
-
// ──
|
|
164
|
+
// ── Mount Three.js sidebar sub-panels ──────────────────────────────────────
|
|
250
165
|
|
|
251
|
-
async
|
|
166
|
+
async _mountThreePanels() {
|
|
252
167
|
const base = EDITOR_BASE + 'js/';
|
|
168
|
+
const ed = this._editor;
|
|
253
169
|
const [
|
|
254
170
|
{ SidebarScene },
|
|
255
171
|
{ SidebarObject },
|
|
256
172
|
{ SidebarGeometry },
|
|
257
173
|
{ SidebarMaterial },
|
|
258
|
-
{ SidebarProject },
|
|
259
|
-
{ SidebarSettings },
|
|
260
174
|
] = await Promise.all([
|
|
261
175
|
import(/* @vite-ignore */ base + 'Sidebar.Scene.js'),
|
|
262
176
|
import(/* @vite-ignore */ base + 'Sidebar.Object.js'),
|
|
263
177
|
import(/* @vite-ignore */ base + 'Sidebar.Geometry.js'),
|
|
264
178
|
import(/* @vite-ignore */ base + 'Sidebar.Material.js'),
|
|
265
|
-
import(/* @vite-ignore */ base + 'Sidebar.Project.js'),
|
|
266
|
-
import(/* @vite-ignore */ base + 'Sidebar.Settings.js'),
|
|
267
179
|
]);
|
|
268
180
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
this._getDomElement('subScene').appendChild(scenePanel.dom);
|
|
274
|
-
|
|
275
|
-
// Object sub-tab: our own property rows + Three.js Object panel below
|
|
276
|
-
const objPanel = new SidebarObject(ed);
|
|
277
|
-
this._getDomElement('objProps').appendChild(objPanel.dom);
|
|
278
|
-
this._buildBindRows(ed);
|
|
279
|
-
|
|
280
|
-
// Geometry sub-tab
|
|
281
|
-
const geomPanel = new SidebarGeometry(ed);
|
|
282
|
-
this._getDomElement('subGeom').appendChild(geomPanel.dom);
|
|
283
|
-
|
|
284
|
-
// Material sub-tab
|
|
285
|
-
const matPanel = new SidebarMaterial(ed);
|
|
286
|
-
this._getDomElement('subMater').appendChild(matPanel.dom);
|
|
287
|
-
|
|
288
|
-
// Project tab
|
|
289
|
-
const projPanel = new SidebarProject(ed);
|
|
290
|
-
this._getDomElement('projWrap').appendChild(projPanel.dom);
|
|
291
|
-
|
|
292
|
-
// Settings tab
|
|
293
|
-
const settPanel = new SidebarSettings(ed);
|
|
294
|
-
this._getDomElement('settingsWrap').appendChild(settPanel.dom);
|
|
181
|
+
this._getDomElement('subScene').appendChild(new SidebarScene(ed).dom);
|
|
182
|
+
this._getDomElement('objThreeWrap').appendChild(new SidebarObject(ed).dom);
|
|
183
|
+
this._getDomElement('subGeom').appendChild(new SidebarGeometry(ed).dom);
|
|
184
|
+
this._getDomElement('subMater').appendChild(new SidebarMaterial(ed).dom);
|
|
295
185
|
}
|
|
296
186
|
|
|
297
|
-
// ──
|
|
187
|
+
// ── Sub-tabs ────────────────────────────────────────────────────────────────
|
|
298
188
|
|
|
299
|
-
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
Transl: 'tabTransl',
|
|
189
|
+
_setupSubTabs() {
|
|
190
|
+
const bar = this._getDomElement('subBar');
|
|
191
|
+
const panels = {
|
|
192
|
+
scene: this._getDomElement('subScene'),
|
|
193
|
+
object: this._getDomElement('subObject'),
|
|
194
|
+
geom: this._getDomElement('subGeom'),
|
|
195
|
+
mater: this._getDomElement('subMater'),
|
|
307
196
|
};
|
|
308
|
-
const bar = this._getDomElement('botBar');
|
|
309
|
-
bar?.querySelectorAll('.btab').forEach(btn => {
|
|
310
|
-
btn.addEventListener('click', () => {
|
|
311
|
-
bar.querySelectorAll('.btab').forEach(b => b.classList.remove('active'));
|
|
312
|
-
btn.classList.add('active');
|
|
313
|
-
Object.values(tabMap).forEach(id => {
|
|
314
|
-
const el = this._getDomElement(id);
|
|
315
|
-
if (el) el.style.display = 'none';
|
|
316
|
-
});
|
|
317
|
-
const target = tabMap[btn.dataset.t];
|
|
318
|
-
if (target) {
|
|
319
|
-
const el = this._getDomElement(target);
|
|
320
|
-
if (el) el.style.display = 'flex';
|
|
321
|
-
}
|
|
322
|
-
});
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// ── Properties sub-tabs (Scene / Object / Geom / Mater) ──────────────────
|
|
327
|
-
|
|
328
|
-
_setupPropSubTabs() {
|
|
329
|
-
const subMap = {
|
|
330
|
-
scene: 'subScene',
|
|
331
|
-
object: 'subObject',
|
|
332
|
-
geom: 'subGeom',
|
|
333
|
-
mater: 'subMater',
|
|
334
|
-
};
|
|
335
|
-
const bar = this._getDomElement('propSubBar');
|
|
336
197
|
bar?.querySelectorAll('.stab').forEach(btn => {
|
|
337
198
|
btn.addEventListener('click', () => {
|
|
338
199
|
bar.querySelectorAll('.stab').forEach(b => b.classList.remove('active'));
|
|
339
200
|
btn.classList.add('active');
|
|
340
|
-
Object.values(
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
});
|
|
344
|
-
const t = subMap[btn.dataset.sub];
|
|
345
|
-
if (t) {
|
|
346
|
-
const el = this._getDomElement(t);
|
|
347
|
-
if (el) el.style.display = 'block';
|
|
348
|
-
}
|
|
201
|
+
Object.values(panels).forEach(p => { if (p) p.style.display = 'none'; });
|
|
202
|
+
const p = panels[btn.dataset.sub];
|
|
203
|
+
if (p) p.style.display = 'block';
|
|
349
204
|
});
|
|
350
205
|
});
|
|
351
206
|
}
|
|
352
207
|
|
|
353
|
-
// ──
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
{ key: '
|
|
365
|
-
{ key: '
|
|
366
|
-
{ key: '
|
|
367
|
-
{ key: '
|
|
368
|
-
{ key: 'rotation.y', label: 'Rotation Y (°)' },
|
|
369
|
-
{ key: 'rotation.z', label: 'Rotation Z (°)' },
|
|
370
|
-
{ key: 'scale.x', label: 'Scale X' },
|
|
371
|
-
{ key: 'scale.y', label: 'Scale Y' },
|
|
372
|
-
{ key: 'scale.z', label: 'Scale Z' },
|
|
373
|
-
{ key: 'visible', label: 'Visible' },
|
|
374
|
-
{ key: 'material.color', label: 'Mat. Color' },
|
|
375
|
-
{ key: 'material.opacity', label: 'Mat. Opacity' },
|
|
376
|
-
{ key: 'material.emissive', label: 'Emissive' },
|
|
208
|
+
// ── Bindable properties list ────────────────────────────────────────────────
|
|
209
|
+
|
|
210
|
+
_BINDABLE = [
|
|
211
|
+
{ key: 'position.x', label: 'Position X' },
|
|
212
|
+
{ key: 'position.y', label: 'Position Y' },
|
|
213
|
+
{ key: 'position.z', label: 'Position Z' },
|
|
214
|
+
{ key: 'rotation.x', label: 'Rotation X (°)' },
|
|
215
|
+
{ key: 'rotation.y', label: 'Rotation Y (°)' },
|
|
216
|
+
{ key: 'rotation.z', label: 'Rotation Z (°)' },
|
|
217
|
+
{ key: 'scale.x', label: 'Scale X' },
|
|
218
|
+
{ key: 'scale.y', label: 'Scale Y' },
|
|
219
|
+
{ key: 'scale.z', label: 'Scale Z' },
|
|
220
|
+
{ key: 'visible', label: 'Visible' },
|
|
221
|
+
{ key: 'material.opacity', label: 'Opacity' },
|
|
222
|
+
{ key: 'material.color', label: 'Color (hex)' },
|
|
377
223
|
];
|
|
378
224
|
|
|
379
|
-
|
|
380
|
-
const
|
|
381
|
-
if (!
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
this._BINDABLE_PROPS.forEach(({ key, label }) => {
|
|
225
|
+
_setupBindingRows() {
|
|
226
|
+
const cont = this._getDomElement('bindRows');
|
|
227
|
+
if (!cont) return;
|
|
228
|
+
cont.innerHTML = '';
|
|
229
|
+
this._BINDABLE.forEach(({ key, label }) => {
|
|
385
230
|
const row = document.createElement('div');
|
|
386
231
|
row.className = 'brow';
|
|
387
232
|
row.dataset.key = key;
|
|
388
233
|
|
|
389
234
|
const lbl = document.createElement('span');
|
|
390
|
-
lbl.className = '
|
|
235
|
+
lbl.className = 'blbl';
|
|
391
236
|
lbl.textContent = label;
|
|
392
237
|
|
|
393
238
|
const sig = document.createElement('span');
|
|
394
|
-
sig.className = '
|
|
239
|
+
sig.className = 'bsig none';
|
|
395
240
|
sig.textContent = '—';
|
|
396
241
|
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
editBtn.addEventListener('click', () => this._openBindingDialog(key, label, sig, editBtn));
|
|
242
|
+
const btn = document.createElement('button');
|
|
243
|
+
btn.className = 'bedit';
|
|
244
|
+
btn.textContent = '□ bind';
|
|
245
|
+
btn.addEventListener('click', () => this._openBindDlg(key, label));
|
|
402
246
|
|
|
403
247
|
row.appendChild(lbl);
|
|
404
248
|
row.appendChild(sig);
|
|
405
|
-
row.appendChild(
|
|
406
|
-
|
|
249
|
+
row.appendChild(btn);
|
|
250
|
+
cont.appendChild(row);
|
|
407
251
|
});
|
|
408
252
|
}
|
|
409
253
|
|
|
410
|
-
|
|
411
|
-
const
|
|
412
|
-
if (!
|
|
413
|
-
const uuid =
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
const
|
|
417
|
-
const
|
|
418
|
-
const
|
|
419
|
-
|
|
420
|
-
if (
|
|
421
|
-
sig.textContent = binding?.signal || '—';
|
|
422
|
-
sig.className = 'b-sig' + (binding?.signal ? '' : ' none');
|
|
423
|
-
}
|
|
424
|
-
if (btn) {
|
|
425
|
-
btn.textContent = binding?.signal ? '■' : '□';
|
|
426
|
-
btn.title = binding?.signal ? `Bound: ${binding.signal}` : 'Edit binding';
|
|
427
|
-
btn.style.color = binding?.signal ? '#4ec9b0' : '';
|
|
428
|
-
}
|
|
254
|
+
_refreshBindRows() {
|
|
255
|
+
const cont = this._getDomElement('bindRows');
|
|
256
|
+
if (!cont) return;
|
|
257
|
+
const uuid = this._selectedObj?.uuid;
|
|
258
|
+
cont.querySelectorAll('.brow').forEach(row => {
|
|
259
|
+
const key = row.dataset.key;
|
|
260
|
+
const sig = row.querySelector('.bsig');
|
|
261
|
+
const btn = row.querySelector('.bedit');
|
|
262
|
+
const bind = uuid ? this._bindings[uuid]?.[key] : null;
|
|
263
|
+
if (sig) { sig.textContent = bind?.signal || '—'; sig.className = 'bsig' + (bind?.signal ? '' : ' none'); }
|
|
264
|
+
if (btn) { btn.textContent = bind?.signal ? '■ bound' : '□ bind'; btn.style.color = bind?.signal ? '#4ec9b0' : ''; }
|
|
429
265
|
});
|
|
430
266
|
}
|
|
431
267
|
|
|
432
|
-
// ── Binding dialog
|
|
268
|
+
// ── Binding dialog ──────────────────────────────────────────────────────────
|
|
433
269
|
|
|
434
270
|
_setupBindingDialog() {
|
|
435
|
-
const
|
|
271
|
+
const ov = this._getDomElement('bdOverlay');
|
|
436
272
|
|
|
437
273
|
this._getDomElement('bdCancel')?.addEventListener('click', () => {
|
|
438
|
-
|
|
439
|
-
this._bindingTarget = null;
|
|
274
|
+
ov?.classList.remove('open'); this._bindTarget = null;
|
|
440
275
|
});
|
|
441
276
|
|
|
442
277
|
this._getDomElement('bdClear')?.addEventListener('click', () => {
|
|
443
|
-
const { uuid, key } = this.
|
|
278
|
+
const { uuid, key } = this._bindTarget || {};
|
|
444
279
|
if (uuid && key) {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
}
|
|
449
|
-
this._syncBindingsToHost();
|
|
450
|
-
this._updateBindRows(this._selectedObj);
|
|
280
|
+
delete this._bindings[uuid]?.[key];
|
|
281
|
+
if (this._bindings[uuid] && !Object.keys(this._bindings[uuid]).length) delete this._bindings[uuid];
|
|
282
|
+
this._syncHost(); this._refreshBindRows();
|
|
451
283
|
}
|
|
452
|
-
|
|
453
|
-
this._bindingTarget = null;
|
|
284
|
+
ov?.classList.remove('open'); this._bindTarget = null;
|
|
454
285
|
});
|
|
455
286
|
|
|
456
287
|
this._getDomElement('bdOk')?.addEventListener('click', () => {
|
|
457
|
-
const { uuid, key } = this.
|
|
458
|
-
const signal = this._getDomElement('
|
|
459
|
-
const formula = this._getDomElement('
|
|
460
|
-
const twoWay = this._getDomElement('
|
|
461
|
-
|
|
288
|
+
const { uuid, key } = this._bindTarget || {};
|
|
289
|
+
const signal = this._getDomElement('bdSig')?.value?.trim();
|
|
290
|
+
const formula = this._getDomElement('bdForm')?.value?.trim() || 'val';
|
|
291
|
+
const twoWay = this._getDomElement('bdTw')?.checked ?? false;
|
|
462
292
|
if (uuid && key && signal) {
|
|
463
293
|
if (!this._bindings[uuid]) this._bindings[uuid] = {};
|
|
464
294
|
this._bindings[uuid][key] = { signal, formula, twoWay };
|
|
465
|
-
this.
|
|
466
|
-
this._updateBindRows(this._selectedObj);
|
|
295
|
+
this._syncHost(); this._refreshBindRows();
|
|
467
296
|
}
|
|
468
|
-
|
|
469
|
-
this._bindingTarget = null;
|
|
297
|
+
ov?.classList.remove('open'); this._bindTarget = null;
|
|
470
298
|
});
|
|
471
299
|
|
|
472
300
|
this._getDomElement('bdBrowse')?.addEventListener('click', async () => {
|
|
473
301
|
try {
|
|
474
|
-
const id = await iobrokerHandler.showSelectIdDialog();
|
|
475
|
-
if (id) {
|
|
476
|
-
const inp = this._getDomElement('bdSignal');
|
|
477
|
-
if (inp) inp.value = id;
|
|
478
|
-
}
|
|
302
|
+
const id = await iobrokerHandler.showSelectIdDialog?.();
|
|
303
|
+
if (id) { const inp = this._getDomElement('bdSig'); if (inp) inp.value = id; }
|
|
479
304
|
} catch (_) {}
|
|
480
305
|
});
|
|
481
306
|
}
|
|
482
307
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
const
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
const
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
if (frmInp) frmInp.value = existing?.formula || 'val';
|
|
498
|
-
if (twInp) twInp.checked = existing?.twoWay || false;
|
|
499
|
-
if (propNm) propNm.textContent = label + ' [' + (obj.name || obj.uuid.slice(0, 8)) + ']';
|
|
500
|
-
|
|
501
|
-
const overlay = this._getDomElement('bdOverlay');
|
|
502
|
-
overlay?.classList.add('show');
|
|
308
|
+
_openBindDlg(key, label) {
|
|
309
|
+
if (!this._selectedObj) { alert('Select a 3D object first.'); return; }
|
|
310
|
+
const uuid = this._selectedObj.uuid;
|
|
311
|
+
this._bindTarget = { uuid, key };
|
|
312
|
+
const ex = this._bindings[uuid]?.[key];
|
|
313
|
+
const sigEl = this._getDomElement('bdSig');
|
|
314
|
+
const frmEl = this._getDomElement('bdForm');
|
|
315
|
+
const twEl = this._getDomElement('bdTw');
|
|
316
|
+
const nmEl = this._getDomElement('bdPropName');
|
|
317
|
+
if (sigEl) sigEl.value = ex?.signal || '';
|
|
318
|
+
if (frmEl) frmEl.value = ex?.formula || 'val';
|
|
319
|
+
if (twEl) twEl.checked = ex?.twoWay || false;
|
|
320
|
+
if (nmEl) nmEl.textContent = label + ' [' + (this._selectedObj.name || uuid.slice(0,8)) + ']';
|
|
321
|
+
this._getDomElement('bdOverlay')?.classList.add('open');
|
|
503
322
|
}
|
|
504
323
|
|
|
505
|
-
|
|
506
|
-
if (this._editorHost?.sceneData)
|
|
507
|
-
this._editorHost.sceneData.bindings = this._bindings;
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
// ── Visibility UI (Settings tab) ──────────────────────────────────────────
|
|
512
|
-
|
|
513
|
-
_setupVisibilityUI() {
|
|
514
|
-
const host = this._editorHost;
|
|
515
|
-
const settings = host?.sceneData?.settings ?? {};
|
|
516
|
-
|
|
517
|
-
const content = this._getDomElement('visContent');
|
|
518
|
-
if (!content) return;
|
|
519
|
-
|
|
520
|
-
const ensure = (key, def) => { if (settings[key] == null) settings[key] = def; };
|
|
521
|
-
ensure('visibilityEnabled', false);
|
|
522
|
-
ensure('visibilityGroups', []);
|
|
523
|
-
ensure('visibilityAction', 'hide');
|
|
524
|
-
ensure('visibilityRedirectScreen', '');
|
|
525
|
-
|
|
526
|
-
const save = () => {
|
|
527
|
-
if (host?.sceneData) host.sceneData.settings = settings;
|
|
528
|
-
};
|
|
529
|
-
|
|
530
|
-
content.innerHTML = `
|
|
531
|
-
<div class="vis-row">
|
|
532
|
-
<label class="vis-row" style="display:flex;align-items:center;gap:8px;cursor:pointer;">
|
|
533
|
-
<input type="checkbox" id="vis3d-enable" ${settings.visibilityEnabled ? 'checked' : ''} />
|
|
534
|
-
<span style="color:#ccc;font-size:11px;">Enable Visibility Control</span>
|
|
535
|
-
</label>
|
|
536
|
-
</div>
|
|
537
|
-
<div class="vis-row">
|
|
538
|
-
<span class="vis-lbl">Only for user groups:</span>
|
|
539
|
-
<div class="vis-group-list" id="vis3d-groups"><span style="color:#666;font-size:11px;">Loading...</span></div>
|
|
540
|
-
</div>
|
|
541
|
-
<div class="vis-row">
|
|
542
|
-
<span class="vis-lbl">If not in group:</span>
|
|
543
|
-
<select class="vis-select" id="vis3d-action">
|
|
544
|
-
<option value="hide" ${settings.visibilityAction==='hide'?'selected':''}>Show "Access Denied"</option>
|
|
545
|
-
<option value="redirect" ${settings.visibilityAction==='redirect'?'selected':''}>Redirect to screen</option>
|
|
546
|
-
</select>
|
|
547
|
-
</div>
|
|
548
|
-
<div class="vis-row" id="vis3d-redir-row" style="display:${settings.visibilityAction==='redirect'?'block':'none'}">
|
|
549
|
-
<span class="vis-lbl">Redirect screen:</span>
|
|
550
|
-
<input type="text" id="vis3d-redir" value="${settings.visibilityRedirectScreen||''}"
|
|
551
|
-
style="width:100%;background:#3c3c3c;border:1px solid #555;color:#ccc;padding:4px;font-size:11px;box-sizing:border-box;" />
|
|
552
|
-
</div>
|
|
553
|
-
`;
|
|
554
|
-
|
|
555
|
-
content.querySelector('#vis3d-enable')?.addEventListener('change', e => {
|
|
556
|
-
settings.visibilityEnabled = e.target.checked; save();
|
|
557
|
-
});
|
|
558
|
-
content.querySelector('#vis3d-action')?.addEventListener('change', e => {
|
|
559
|
-
settings.visibilityAction = e.target.value;
|
|
560
|
-
const rd = content.querySelector('#vis3d-redir-row');
|
|
561
|
-
if (rd) rd.style.display = e.target.value === 'redirect' ? 'block' : 'none';
|
|
562
|
-
save();
|
|
563
|
-
});
|
|
564
|
-
content.querySelector('#vis3d-redir')?.addEventListener('input', e => {
|
|
565
|
-
settings.visibilityRedirectScreen = e.target.value; save();
|
|
566
|
-
});
|
|
567
|
-
|
|
568
|
-
// Load user groups
|
|
569
|
-
this._loadUserGroups(settings, content.querySelector('#vis3d-groups'), save);
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
async _loadUserGroups(settings, container, save) {
|
|
573
|
-
if (!container) return;
|
|
574
|
-
try {
|
|
575
|
-
const groups = await iobrokerHandler.getUserGroups?.() ?? [];
|
|
576
|
-
if (!groups.length) { container.innerHTML = '<span style="color:#666;font-size:11px;">No groups found</span>'; return; }
|
|
577
|
-
container.innerHTML = '';
|
|
578
|
-
groups.forEach(g => {
|
|
579
|
-
const id = typeof g === 'string' ? g : (g.id ?? g._id ?? g.name);
|
|
580
|
-
const lbl = typeof g === 'string' ? g : (g.name ?? id);
|
|
581
|
-
const checked = (settings.visibilityGroups || []).includes(id);
|
|
582
|
-
const item = document.createElement('label');
|
|
583
|
-
item.className = 'vis-group-item';
|
|
584
|
-
item.innerHTML = `<input type="checkbox" class="vis-check" ${checked?'checked':''} />${lbl}`;
|
|
585
|
-
item.querySelector('input').addEventListener('change', ev => {
|
|
586
|
-
const arr = settings.visibilityGroups || [];
|
|
587
|
-
if (ev.target.checked) { if (!arr.includes(id)) arr.push(id); }
|
|
588
|
-
else { const i = arr.indexOf(id); if (i >= 0) arr.splice(i, 1); }
|
|
589
|
-
settings.visibilityGroups = arr;
|
|
590
|
-
save();
|
|
591
|
-
});
|
|
592
|
-
container.appendChild(item);
|
|
593
|
-
});
|
|
594
|
-
} catch (_) {
|
|
595
|
-
container.innerHTML = '<span style="color:#666;font-size:11px;">Could not load groups</span>';
|
|
596
|
-
}
|
|
324
|
+
_syncHost() {
|
|
325
|
+
if (this._editorHost?.sceneData) this._editorHost.sceneData.bindings = this._bindings;
|
|
597
326
|
}
|
|
598
327
|
}
|
|
599
328
|
|
|
@@ -108,7 +108,7 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
|
|
|
108
108
|
<div id="visibilityDock" title="Visibility" style="overflow: auto; width: 100%;" dock-spawn-dock-to="attributeDock">
|
|
109
109
|
</div>
|
|
110
110
|
|
|
111
|
-
<div id="effectsDock" title="Effects" style="overflow: auto; width: 100%;" dock-spawn-dock-to="attributeDock">
|
|
111
|
+
<div id="effectsDock" title="Effects / Project" style="overflow: auto; width: 100%;" dock-spawn-dock-to="attributeDock">
|
|
112
112
|
</div>
|
|
113
113
|
|
|
114
114
|
<div id="animationsDock" title="Animations" style="overflow: auto; width: 100%;" dock-spawn-dock-to="attributeDock">
|
|
@@ -182,11 +182,15 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
|
|
|
182
182
|
let element = this._dock.getElementInSlot(previousPanel.elementContent);
|
|
183
183
|
if (element?.deactivated)
|
|
184
184
|
element?.deactivated();
|
|
185
|
+
if (element instanceof IobrokerWebui3DScreenEditor)
|
|
186
|
+
this._deactivate3DPropertiesMode();
|
|
185
187
|
}
|
|
186
188
|
if (panel) {
|
|
187
189
|
let element = this._dock.getElementInSlot(panel.elementContent);
|
|
188
190
|
if (element?.activated)
|
|
189
191
|
element?.activated();
|
|
192
|
+
if (element instanceof IobrokerWebui3DScreenEditor)
|
|
193
|
+
this._activate3DPropertiesMode(element);
|
|
190
194
|
}
|
|
191
195
|
},
|
|
192
196
|
onClosePanel: (manager, panel) => {
|
|
@@ -2129,6 +2133,188 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
|
|
|
2129
2133
|
this.openDock(scriptEditor);
|
|
2130
2134
|
}
|
|
2131
2135
|
}
|
|
2136
|
+
|
|
2137
|
+
// ── 3D Screen Properties Mode ──────────────────────────────────────────────
|
|
2138
|
+
|
|
2139
|
+
_3dPropsActive = false;
|
|
2140
|
+
_3dPropsPanel = null;
|
|
2141
|
+
_3dSettPanel = null;
|
|
2142
|
+
_3dProjPanel = null;
|
|
2143
|
+
|
|
2144
|
+
_activate3DPropertiesMode(editor3DEl) {
|
|
2145
|
+
if (this._3dPropsActive) return;
|
|
2146
|
+
this._3dPropsActive = true;
|
|
2147
|
+
|
|
2148
|
+
// Helper: hide existing children and track state
|
|
2149
|
+
const hideChildren = (el) => {
|
|
2150
|
+
for (const c of el.children) {
|
|
2151
|
+
c.__3d_prev_display = c.style.display;
|
|
2152
|
+
c.style.display = 'none';
|
|
2153
|
+
}
|
|
2154
|
+
};
|
|
2155
|
+
// Helper: restore
|
|
2156
|
+
const showChildren = (el) => {
|
|
2157
|
+
for (const c of el.children) {
|
|
2158
|
+
if (c.__3d_prev_display !== undefined) {
|
|
2159
|
+
c.style.display = c.__3d_prev_display;
|
|
2160
|
+
delete c.__3d_prev_display;
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
};
|
|
2164
|
+
|
|
2165
|
+
const attrDock = this._getDomElement('attributeDock');
|
|
2166
|
+
const settiDock = this._getDomElement('settingsDock');
|
|
2167
|
+
const projDock = this._getDomElement('effectsDock'); // reuse effectsDock for Project
|
|
2168
|
+
|
|
2169
|
+
if (attrDock) hideChildren(attrDock);
|
|
2170
|
+
if (settiDock) hideChildren(settiDock);
|
|
2171
|
+
if (projDock) hideChildren(projDock);
|
|
2172
|
+
|
|
2173
|
+
const tryConnect = () => {
|
|
2174
|
+
if (!editor3DEl._editor) { setTimeout(tryConnect, 200); return; }
|
|
2175
|
+
const ed = editor3DEl._editor;
|
|
2176
|
+
const base = new URL('../../../3d-editor/js/', import.meta.url).href;
|
|
2177
|
+
|
|
2178
|
+
// ── attributeDock: Scene / Object / Geom / Mater ───────────────
|
|
2179
|
+
if (attrDock) {
|
|
2180
|
+
const propPanel = document.createElement('iobroker-webui-3dscreen-properties');
|
|
2181
|
+
propPanel.id = '__3d_attr';
|
|
2182
|
+
propPanel.style.cssText = 'display:block;width:100%;height:100%;';
|
|
2183
|
+
attrDock.appendChild(propPanel);
|
|
2184
|
+
propPanel.connect(ed, editor3DEl).catch(e => console.warn('3D props connect:', e));
|
|
2185
|
+
this._3dPropsPanel = propPanel;
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
// ── settingsDock: SidebarSettings + Visibility ─────────────────
|
|
2189
|
+
if (settiDock) {
|
|
2190
|
+
const wrap = document.createElement('div');
|
|
2191
|
+
wrap.id = '__3d_setti';
|
|
2192
|
+
wrap.style.cssText = 'width:100%;height:100%;overflow:auto;background:#1a1a1a;color:#ccc;font-family:Helvetica,Arial,sans-serif;font-size:12px;';
|
|
2193
|
+
|
|
2194
|
+
Promise.all([
|
|
2195
|
+
import(/* @vite-ignore */ base + 'Sidebar.Settings.js'),
|
|
2196
|
+
]).then(([{ SidebarSettings }]) => {
|
|
2197
|
+
const settPanel = new SidebarSettings(ed);
|
|
2198
|
+
settPanel.dom.style.cssText = 'padding:8px;';
|
|
2199
|
+
wrap.appendChild(settPanel.dom);
|
|
2200
|
+
|
|
2201
|
+
// ── Visibility section ─────────────────────────────────
|
|
2202
|
+
const visDiv = document.createElement('div');
|
|
2203
|
+
visDiv.style.cssText = 'border-top:1px solid #3c3c3c;margin-top:8px;padding:10px;';
|
|
2204
|
+
visDiv.innerHTML = `
|
|
2205
|
+
<div style="color:#9cdcfe;font-size:10px;font-weight:bold;text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px;">
|
|
2206
|
+
Screen Visibility Control
|
|
2207
|
+
</div>
|
|
2208
|
+
<label style="display:flex;align-items:center;gap:8px;margin-bottom:8px;cursor:pointer;font-size:11px;">
|
|
2209
|
+
<input type="checkbox" id="__3d_vis_en" />
|
|
2210
|
+
Enable Visibility Control
|
|
2211
|
+
</label>
|
|
2212
|
+
<div style="font-size:11px;color:#888;margin-bottom:4px;">Only for groups:</div>
|
|
2213
|
+
<div id="__3d_vis_groups" style="border:1px solid #444;padding:6px;max-height:100px;overflow-y:auto;background:#111;font-size:11px;margin-bottom:8px;">
|
|
2214
|
+
<span style="color:#555;">Loading...</span>
|
|
2215
|
+
</div>
|
|
2216
|
+
<div style="font-size:11px;color:#888;margin-bottom:4px;">If not in group:</div>
|
|
2217
|
+
<select id="__3d_vis_act" style="width:100%;background:#3c3c3c;border:1px solid #555;color:#ccc;padding:4px;font-size:11px;margin-bottom:8px;">
|
|
2218
|
+
<option value="hide">Show "Access Denied"</option>
|
|
2219
|
+
<option value="redirect">Redirect to screen</option>
|
|
2220
|
+
</select>
|
|
2221
|
+
<div id="__3d_vis_rdiv" style="display:none;">
|
|
2222
|
+
<div style="font-size:11px;color:#888;margin-bottom:4px;">Redirect screen:</div>
|
|
2223
|
+
<input id="__3d_vis_redir" type="text" style="width:100%;background:#3c3c3c;border:1px solid #555;color:#ccc;padding:4px;font-size:11px;box-sizing:border-box;" />
|
|
2224
|
+
</div>
|
|
2225
|
+
`;
|
|
2226
|
+
|
|
2227
|
+
const settings = editor3DEl.sceneData?.settings ?? {};
|
|
2228
|
+
editor3DEl.sceneData = editor3DEl.sceneData ?? {};
|
|
2229
|
+
editor3DEl.sceneData.settings = settings;
|
|
2230
|
+
if (!settings.visibilityEnabled) settings.visibilityEnabled = false;
|
|
2231
|
+
if (!settings.visibilityGroups) settings.visibilityGroups = [];
|
|
2232
|
+
if (!settings.visibilityAction) settings.visibilityAction = 'hide';
|
|
2233
|
+
if (!settings.visibilityRedirectScreen) settings.visibilityRedirectScreen = '';
|
|
2234
|
+
|
|
2235
|
+
const save = () => {};
|
|
2236
|
+
const enEl = visDiv.querySelector('#__3d_vis_en');
|
|
2237
|
+
const actEl = visDiv.querySelector('#__3d_vis_act');
|
|
2238
|
+
const rdDiv = visDiv.querySelector('#__3d_vis_rdiv');
|
|
2239
|
+
const rdEl = visDiv.querySelector('#__3d_vis_redir');
|
|
2240
|
+
|
|
2241
|
+
if (enEl) { enEl.checked = settings.visibilityEnabled; enEl.addEventListener('change', e => { settings.visibilityEnabled = e.target.checked; }); }
|
|
2242
|
+
if (actEl) { actEl.value = settings.visibilityAction; actEl.addEventListener('change', e => { settings.visibilityAction = e.target.value; if (rdDiv) rdDiv.style.display = e.target.value === 'redirect' ? 'block' : 'none'; }); }
|
|
2243
|
+
if (rdDiv) rdDiv.style.display = settings.visibilityAction === 'redirect' ? 'block' : 'none';
|
|
2244
|
+
if (rdEl) { rdEl.value = settings.visibilityRedirectScreen || ''; rdEl.addEventListener('input', e => { settings.visibilityRedirectScreen = e.target.value; }); }
|
|
2245
|
+
|
|
2246
|
+
// Load groups
|
|
2247
|
+
const grpEl = visDiv.querySelector('#__3d_vis_groups');
|
|
2248
|
+
iobrokerHandler.getUserGroups?.().then(groups => {
|
|
2249
|
+
if (!grpEl) return;
|
|
2250
|
+
if (!groups?.length) { grpEl.innerHTML = '<span style="color:#555;">No groups</span>'; return; }
|
|
2251
|
+
grpEl.innerHTML = '';
|
|
2252
|
+
groups.forEach(g => {
|
|
2253
|
+
const id = typeof g === 'string' ? g : (g.id ?? g._id ?? g.name ?? g);
|
|
2254
|
+
const lbl = typeof g === 'string' ? g : (g.name ?? id);
|
|
2255
|
+
const chk = (settings.visibilityGroups || []).includes(id);
|
|
2256
|
+
const row = document.createElement('label');
|
|
2257
|
+
row.style.cssText = 'display:flex;align-items:center;gap:6px;padding:2px 0;cursor:pointer;';
|
|
2258
|
+
row.innerHTML = `<input type="checkbox" ${chk ? 'checked' : ''} />${lbl}`;
|
|
2259
|
+
row.querySelector('input').addEventListener('change', ev => {
|
|
2260
|
+
const arr = settings.visibilityGroups || [];
|
|
2261
|
+
if (ev.target.checked) { if (!arr.includes(id)) arr.push(id); }
|
|
2262
|
+
else { const i = arr.indexOf(id); if (i >= 0) arr.splice(i, 1); }
|
|
2263
|
+
settings.visibilityGroups = arr;
|
|
2264
|
+
});
|
|
2265
|
+
grpEl.appendChild(row);
|
|
2266
|
+
});
|
|
2267
|
+
}).catch(() => { if (grpEl) grpEl.innerHTML = '<span style="color:#555;">—</span>'; });
|
|
2268
|
+
|
|
2269
|
+
wrap.appendChild(visDiv);
|
|
2270
|
+
});
|
|
2271
|
+
|
|
2272
|
+
settiDock.appendChild(wrap);
|
|
2273
|
+
this._3dSettPanel = wrap;
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
// ── effectsDock: SidebarProject (Geometries / Materials / Textures) ──
|
|
2277
|
+
if (projDock) {
|
|
2278
|
+
const wrap = document.createElement('div');
|
|
2279
|
+
wrap.id = '__3d_proj';
|
|
2280
|
+
wrap.style.cssText = 'width:100%;height:100%;overflow:auto;background:#1a1a1a;';
|
|
2281
|
+
import(/* @vite-ignore */ base + 'Sidebar.Project.js').then(({ SidebarProject }) => {
|
|
2282
|
+
const p = new SidebarProject(ed);
|
|
2283
|
+
p.dom.style.cssText = 'padding:8px;';
|
|
2284
|
+
wrap.appendChild(p.dom);
|
|
2285
|
+
});
|
|
2286
|
+
projDock.appendChild(wrap);
|
|
2287
|
+
this._3dProjPanel = wrap;
|
|
2288
|
+
}
|
|
2289
|
+
};
|
|
2290
|
+
|
|
2291
|
+
tryConnect();
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
_deactivate3DPropertiesMode() {
|
|
2295
|
+
if (!this._3dPropsActive) return;
|
|
2296
|
+
this._3dPropsActive = false;
|
|
2297
|
+
|
|
2298
|
+
const restore = (dockId, panelEl) => {
|
|
2299
|
+
const dock = this._getDomElement(dockId);
|
|
2300
|
+
if (!dock) return;
|
|
2301
|
+
panelEl?.remove();
|
|
2302
|
+
for (const c of dock.children) {
|
|
2303
|
+
if (c.__3d_prev_display !== undefined) {
|
|
2304
|
+
c.style.display = c.__3d_prev_display;
|
|
2305
|
+
delete c.__3d_prev_display;
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
};
|
|
2309
|
+
|
|
2310
|
+
restore('attributeDock', this._3dPropsPanel);
|
|
2311
|
+
restore('settingsDock', this._3dSettPanel);
|
|
2312
|
+
restore('effectsDock', this._3dProjPanel);
|
|
2313
|
+
|
|
2314
|
+
this._3dPropsPanel = null;
|
|
2315
|
+
this._3dSettPanel = null;
|
|
2316
|
+
this._3dProjPanel = null;
|
|
2317
|
+
}
|
|
2132
2318
|
}
|
|
2133
2319
|
window.customElements.define('iobroker-webui-app-shell', IobrokerWebuiAppShell);
|
|
2134
2320
|
const err = console.error;
|