iobroker.mywebui 1.42.37 → 1.42.39
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();
|
|
@@ -1,108 +1,109 @@
|
|
|
1
1
|
import { BaseCustomWebComponentConstructorAppend, css, html } from "@gokturk413/base-custom-webcomponent";
|
|
2
|
-
import { iobrokerHandler } from "../common/IobrokerHandler.js";
|
|
3
2
|
|
|
4
3
|
const EDITOR_BASE = new URL('../../../3d-editor/', import.meta.url).href;
|
|
5
4
|
|
|
6
|
-
const RAD2DEG = 180 / Math.PI;
|
|
7
|
-
const DEG2RAD = Math.PI / 180;
|
|
8
|
-
|
|
9
5
|
export class IobrokerWebui3DScreenPropertiesPanel extends BaseCustomWebComponentConstructorAppend {
|
|
10
6
|
|
|
11
7
|
static template = html`
|
|
12
8
|
<div id="root">
|
|
13
|
-
<!--
|
|
14
|
-
<div id="
|
|
15
|
-
<span class="
|
|
16
|
-
<span
|
|
9
|
+
<!-- Sub-tab bar -->
|
|
10
|
+
<div id="subBar">
|
|
11
|
+
<span class="stab active" data-sub="scene">Scene</span>
|
|
12
|
+
<span class="stab" data-sub="object">Object</span>
|
|
13
|
+
<span class="stab" data-sub="geom">Geom</span>
|
|
14
|
+
<span class="stab" data-sub="mater">Mater</span>
|
|
17
15
|
</div>
|
|
18
16
|
|
|
19
|
-
<!--
|
|
20
|
-
<div id="
|
|
17
|
+
<!-- Scene tab -->
|
|
18
|
+
<div id="subScene" class="spanel active"></div>
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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>
|
|
20
|
+
<!-- Object tab -->
|
|
21
|
+
<div id="subObject" class="spanel" style="display:none;">
|
|
22
|
+
<div id="objThreeWrap"></div>
|
|
23
|
+
<div class="sechdr">BINDINGS (IOBROKER STATES)</div>
|
|
24
|
+
<div id="bindRows"></div>
|
|
25
|
+
</div>
|
|
41
26
|
|
|
42
|
-
|
|
43
|
-
|
|
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>
|
|
27
|
+
<!-- Geom tab -->
|
|
28
|
+
<div id="subGeom" class="spanel" style="display:none;"></div>
|
|
50
29
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
<div id="projWrap"></div>
|
|
54
|
-
</div>
|
|
30
|
+
<!-- Mater tab -->
|
|
31
|
+
<div id="subMater" class="spanel" style="display:none;"></div>
|
|
55
32
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
</div>
|
|
33
|
+
<!-- Context menu -->
|
|
34
|
+
<div id="ctxMenu">
|
|
35
|
+
<div class="cmItem" data-action="clear">clear</div>
|
|
36
|
+
<div class="cmItem" data-action="edittext">edit as text</div>
|
|
37
|
+
<div class="cmItem" data-action="editbind">edit binding</div>
|
|
38
|
+
</div>
|
|
60
39
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
40
|
+
<!-- Binding dialog -->
|
|
41
|
+
<div id="bdOverlay">
|
|
42
|
+
<div id="bdBox">
|
|
43
|
+
<div id="bdTitle">Edit Binding of '<span id="bdPropLabel"></span>' - 3DScreen</div>
|
|
44
|
+
|
|
45
|
+
<div class="bdRow">
|
|
46
|
+
<div class="bdLbl">objects</div>
|
|
47
|
+
<div style="display:flex;gap:3px;align-items:center;">
|
|
48
|
+
<input id="bdObj" type="text" class="bdInput" style="flex:1;" placeholder="state id" autocomplete="off"/>
|
|
49
|
+
<button class="bdBtn sm" id="bdObjX">X</button>
|
|
50
|
+
<button class="bdBtn sm" id="bdObjBrowse">...</button>
|
|
51
|
+
<button class="bdBtn sm accent" id="bdObjIOB">IOB</button>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
65
54
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
55
|
+
<div class="bdRow" style="display:flex;align-items:center;gap:8px;">
|
|
56
|
+
<span class="bdLbl" style="margin-bottom:0;">type :</span>
|
|
57
|
+
<select id="bdType" class="bdSel">
|
|
58
|
+
<option value="signal">signal</option>
|
|
59
|
+
<option value="ignore" selected>ignore</option>
|
|
60
|
+
<option value="css">css</option>
|
|
61
|
+
<option value="attribute">attribute</option>
|
|
62
|
+
</select>
|
|
63
|
+
</div>
|
|
70
64
|
|
|
71
|
-
|
|
65
|
+
<div class="bdRow" style="display:flex;align-items:center;gap:8px;">
|
|
66
|
+
<input id="bdTwoWay" type="checkbox"/>
|
|
67
|
+
<span class="bdChkLbl">two way binding</span>
|
|
68
|
+
</div>
|
|
69
|
+
<div class="bdRow" style="display:flex;align-items:center;gap:8px;">
|
|
70
|
+
<input id="bdInvert" type="checkbox"/>
|
|
71
|
+
<span class="bdChkLbl">invert logic</span>
|
|
72
|
+
</div>
|
|
72
73
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
<span class="btab" data-t="Transl">Transl...</span>
|
|
81
|
-
</div>
|
|
74
|
+
<div class="bdRow" style="display:flex;align-items:center;justify-content:space-between;">
|
|
75
|
+
<span class="bdLbl" style="margin-bottom:0;">formula</span>
|
|
76
|
+
<button class="bdBtn sm" id="bdHistoric">historic</button>
|
|
77
|
+
</div>
|
|
78
|
+
<div class="bdRow">
|
|
79
|
+
<textarea id="bdFormula" class="bdTa" rows="2" placeholder="val"></textarea>
|
|
80
|
+
</div>
|
|
82
81
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
<div id="bdHead">Edit Binding — <span id="bdPropName"></span></div>
|
|
87
|
-
<div class="bd-row">
|
|
88
|
-
<div class="bd-lbl">State ID</div>
|
|
89
|
-
<div style="display:flex;gap:4px;">
|
|
90
|
-
<input id="bdSignal" type="text" placeholder="mywebui.0.0_data.value" />
|
|
91
|
-
<button id="bdBrowse" title="Browse">...</button>
|
|
92
|
-
</div>
|
|
82
|
+
<div class="bdRow">
|
|
83
|
+
<span class="bdLbl">write back signal :</span>
|
|
84
|
+
<input id="bdWriteBack" type="text" class="bdInput" placeholder=""/>
|
|
93
85
|
</div>
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
<
|
|
86
|
+
|
|
87
|
+
<div class="bdRow">
|
|
88
|
+
<span class="bdLbl">formula write back (two way)</span>
|
|
89
|
+
<textarea id="bdFormulaWB" class="bdTa" rows="2" placeholder="val"></textarea>
|
|
97
90
|
</div>
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
<
|
|
91
|
+
|
|
92
|
+
<div class="bdRow">
|
|
93
|
+
<span class="bdLbl">converter:</span>
|
|
94
|
+
<table id="bdConvTable" class="bdTable">
|
|
95
|
+
<thead><tr><th>condition</th><th>value</th></tr></thead>
|
|
96
|
+
<tbody id="bdConvBody"></tbody>
|
|
97
|
+
</table>
|
|
98
|
+
<div style="display:flex;gap:4px;margin-top:4px;justify-content:flex-end;">
|
|
99
|
+
<button class="bdBtn" id="bdConvAdd">add</button>
|
|
100
|
+
<button class="bdBtn" id="bdConvRemove">remove</button>
|
|
101
|
+
</div>
|
|
101
102
|
</div>
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
<button id="
|
|
105
|
-
<button
|
|
103
|
+
|
|
104
|
+
<div id="bdFoot">
|
|
105
|
+
<button class="bdBtn accent-ok" id="bdOk">Ok</button>
|
|
106
|
+
<button class="bdBtn" id="bdCancel">Cancel</button>
|
|
106
107
|
</div>
|
|
107
108
|
</div>
|
|
108
109
|
</div>
|
|
@@ -113,487 +114,410 @@ export class IobrokerWebui3DScreenPropertiesPanel extends BaseCustomWebComponent
|
|
|
113
114
|
:host { display:block;width:100%;height:100%;overflow:hidden; }
|
|
114
115
|
#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
116
|
|
|
116
|
-
/*
|
|
117
|
-
#
|
|
118
|
-
.lbl { color:#888; }
|
|
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; }
|
|
117
|
+
/* Sub-tab bar */
|
|
118
|
+
#subBar { display:flex;background:#2d2d30;border-bottom:1px solid #3c3c3c;flex-shrink:0; }
|
|
127
119
|
.stab { padding:5px 10px;cursor:pointer;font-size:11px;color:#888;border-right:1px solid #3c3c3c;white-space:nowrap;user-select:none; }
|
|
128
120
|
.stab:hover { color:#ddd;background:#3e3e42; }
|
|
129
121
|
.stab.active { color:#fff;background:#1e1e1e;border-top:2px solid #4ec9b0; }
|
|
130
122
|
|
|
131
|
-
|
|
123
|
+
/* Sub-tab panels */
|
|
124
|
+
.spanel { flex:1;overflow:auto;min-height:0; }
|
|
125
|
+
#subScene,#subGeom,#subMater { overflow:auto; }
|
|
126
|
+
#subObject { display:flex;flex-direction:column; }
|
|
127
|
+
#objThreeWrap { overflow:auto; }
|
|
132
128
|
|
|
133
129
|
/* Section header */
|
|
134
|
-
.
|
|
135
|
-
|
|
136
|
-
/*
|
|
137
|
-
#
|
|
138
|
-
.
|
|
139
|
-
.
|
|
140
|
-
|
|
141
|
-
.
|
|
142
|
-
.
|
|
143
|
-
.
|
|
144
|
-
|
|
145
|
-
.
|
|
146
|
-
|
|
147
|
-
.
|
|
148
|
-
.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
.brow:hover { background:#2a2a2a; }
|
|
158
|
-
.b-lbl { width:90px;color:#888;font-size:10px;flex-shrink:0; }
|
|
159
|
-
.b-sig { flex:1;color:#4ec9b0;font-size:10px;font-family:monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap; }
|
|
160
|
-
.b-sig.none { color:#555; }
|
|
161
|
-
.b-edit { font-size:10px;padding:1px 5px;background:#3c3c3c;border:1px solid #555;color:#ccc;cursor:pointer; }
|
|
162
|
-
|
|
163
|
-
/* Bottom tab bar */
|
|
164
|
-
#botBar { display:flex;border-top:2px solid #3c3c3c;background:#2d2d30;flex-shrink:0;overflow-x:auto; }
|
|
165
|
-
.btab { padding:5px 8px;cursor:pointer;font-size:10px;color:#888;border-right:1px solid #3c3c3c;white-space:nowrap;flex:1;text-align:center;user-select:none; }
|
|
166
|
-
.btab:hover { background:#3e3e42;color:#ccc; }
|
|
167
|
-
.btab.active { background:#1e1e1e;color:#fff;border-top:2px solid #4ec9b0; }
|
|
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; }
|
|
176
|
-
|
|
177
|
-
/* Binding dialog */
|
|
130
|
+
.sechdr { background:#252526;color:#9cdcfe;padding:4px 8px;font-size:10px;font-weight:bold;text-transform:uppercase;border-top:1px solid #3c3c3c;border-bottom:1px solid #3c3c3c;letter-spacing:.5px;flex-shrink:0; }
|
|
131
|
+
|
|
132
|
+
/* Binding rows — match 2D property grid style */
|
|
133
|
+
#bindRows { flex:1;overflow:auto; }
|
|
134
|
+
.brow { display:flex;align-items:center;padding:2px 4px;gap:0;border-bottom:1px solid #2a2a2a;min-height:20px; }
|
|
135
|
+
.brow:hover { background:#252526; }
|
|
136
|
+
/* □ square button */
|
|
137
|
+
.bsq { width:14px;height:14px;min-width:14px;border:1px solid #555;background:#2d2d30;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:8px;color:#888;flex-shrink:0;margin-right:4px;padding:0; }
|
|
138
|
+
.bsq:hover { border-color:#4ec9b0; }
|
|
139
|
+
.bsq.bound { background:#0e639c;border-color:#1177bb;color:#fff; }
|
|
140
|
+
/* property name */
|
|
141
|
+
.blbl { width:90px;color:#9cdcfe;font-size:11px;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap; }
|
|
142
|
+
/* signal display */
|
|
143
|
+
.bval { flex:1;color:#4ec9b0;font-size:10px;font-family:monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:0 2px; }
|
|
144
|
+
.bval.none { color:#555; }
|
|
145
|
+
|
|
146
|
+
/* Context menu */
|
|
147
|
+
#ctxMenu { display:none;position:fixed;z-index:9999;background:#2d2d30;border:1px solid #555;min-width:120px;box-shadow:2px 2px 8px rgba(0,0,0,0.6); }
|
|
148
|
+
#ctxMenu.open { display:block; }
|
|
149
|
+
.cmItem { padding:5px 12px;cursor:pointer;font-size:11px;color:#ccc; }
|
|
150
|
+
.cmItem:hover { background:#3e3e42;color:#fff; }
|
|
151
|
+
|
|
152
|
+
/* Binding dialog overlay */
|
|
178
153
|
#bdOverlay { display:none;position:absolute;inset:0;background:rgba(0,0,0,0.75);z-index:500;align-items:center;justify-content:center; }
|
|
179
|
-
#bdOverlay.
|
|
180
|
-
#
|
|
181
|
-
#
|
|
182
|
-
.
|
|
183
|
-
.
|
|
184
|
-
.
|
|
185
|
-
.
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
154
|
+
#bdOverlay.open { display:flex; }
|
|
155
|
+
#bdBox { background:#2d2d30;border:1px solid #555;padding:0;width:340px;max-height:90%;overflow-y:auto;box-shadow:4px 4px 16px rgba(0,0,0,0.7); }
|
|
156
|
+
#bdTitle { background:#3e3e42;padding:7px 10px;font-size:12px;font-weight:bold;color:#9cdcfe;border-bottom:1px solid #555;margin-bottom:0; }
|
|
157
|
+
.bdRow { padding:4px 10px 2px; }
|
|
158
|
+
.bdLbl { color:#888;font-size:10px;margin-bottom:2px;display:block; }
|
|
159
|
+
.bdInput { width:100%;background:#3c3c3c;border:1px solid #555;color:#ccc;padding:3px 5px;font-size:11px;box-sizing:border-box; }
|
|
160
|
+
.bdInput:focus { border-color:#4ec9b0;outline:none; }
|
|
161
|
+
.bdSel { background:#3c3c3c;border:1px solid #555;color:#ccc;padding:2px 4px;font-size:11px; }
|
|
162
|
+
.bdTa { width:100%;background:#3c3c3c;border:1px solid #555;color:#ccc;padding:3px 5px;font-size:11px;box-sizing:border-box;resize:vertical;font-family:monospace; }
|
|
163
|
+
.bdTa:focus { border-color:#4ec9b0;outline:none; }
|
|
164
|
+
.bdChkLbl { color:#ccc;font-size:11px; }
|
|
165
|
+
.bdTable { width:100%;border-collapse:collapse;font-size:10px;color:#ccc; }
|
|
166
|
+
.bdTable th { background:#252526;padding:2px 6px;text-align:left;color:#888;border:1px solid #3c3c3c; }
|
|
167
|
+
.bdTable td { padding:2px 4px;border:1px solid #3c3c3c; }
|
|
168
|
+
.bdTable td input { width:100%;background:#3c3c3c;border:none;color:#ccc;padding:1px 3px;font-size:10px; }
|
|
169
|
+
.bdBtn { padding:3px 10px;background:#3c3c3c;border:1px solid #555;color:#ccc;cursor:pointer;font-size:11px; }
|
|
170
|
+
.bdBtn:hover { border-color:#999; }
|
|
171
|
+
.bdBtn.sm { padding:2px 6px;font-size:10px; }
|
|
172
|
+
.bdBtn.accent { background:#0e639c;border-color:#1177bb;color:#fff; }
|
|
173
|
+
.bdBtn.accent-ok { background:#0e639c;border-color:#1177bb;color:#fff; }
|
|
174
|
+
#bdFoot { display:flex;justify-content:flex-end;gap:6px;padding:8px 10px;border-top:1px solid #3c3c3c;margin-top:4px; }
|
|
190
175
|
`;
|
|
191
176
|
|
|
192
177
|
_editor = null;
|
|
193
178
|
_editorHost = null;
|
|
194
|
-
_bindings = {};
|
|
179
|
+
_bindings = {};
|
|
195
180
|
_selectedObj = null;
|
|
196
|
-
|
|
181
|
+
_ctxTarget = null; // { uuid, key }
|
|
197
182
|
|
|
198
|
-
// ──
|
|
183
|
+
// ── Public API ─────────────────────────────────────────────────────────────
|
|
199
184
|
|
|
200
185
|
async connect(editor, editorHost) {
|
|
201
|
-
this._editor
|
|
186
|
+
this._editor = editor;
|
|
202
187
|
this._editorHost = editorHost;
|
|
203
|
-
this._bindings
|
|
188
|
+
this._bindings = editorHost?.sceneData?.bindings ?? {};
|
|
204
189
|
|
|
205
190
|
await this._injectThreeCSS();
|
|
206
|
-
await this.
|
|
207
|
-
this.
|
|
208
|
-
this.
|
|
191
|
+
await this._mountThreePanels();
|
|
192
|
+
this._setupSubTabs();
|
|
193
|
+
this._buildBindingRows();
|
|
194
|
+
this._setupContextMenu();
|
|
209
195
|
this._setupBindingDialog();
|
|
210
|
-
this._setupVisibilityUI();
|
|
211
196
|
|
|
212
|
-
editor.signals.objectSelected.add(
|
|
197
|
+
editor.signals.objectSelected.add(obj => {
|
|
213
198
|
this._selectedObj = obj;
|
|
214
|
-
this.
|
|
215
|
-
this._updateBindRows(obj);
|
|
216
|
-
const typeEl = this._getDomElement('typeVal');
|
|
217
|
-
if (typeEl) typeEl.textContent = obj ? (obj.type ?? '-') : '-';
|
|
199
|
+
this._refreshBindRows();
|
|
218
200
|
});
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if (obj && obj === this._selectedObj) this._updateObjectPanel(obj);
|
|
201
|
+
editor.signals.objectChanged.add(obj => {
|
|
202
|
+
if (obj && obj === this._selectedObj) this._refreshBindRows();
|
|
222
203
|
});
|
|
223
204
|
|
|
224
|
-
this.
|
|
225
|
-
this._updateBindRows(null);
|
|
205
|
+
this._refreshBindRows();
|
|
226
206
|
}
|
|
227
207
|
|
|
228
|
-
/** Call this before saving scene data */
|
|
229
208
|
getBindings() { return this._bindings; }
|
|
230
209
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
this.
|
|
234
|
-
if (this._selectedObj) this._updateBindRows(this._selectedObj);
|
|
210
|
+
setBindings(b) {
|
|
211
|
+
this._bindings = b || {};
|
|
212
|
+
this._refreshBindRows();
|
|
235
213
|
}
|
|
236
214
|
|
|
237
|
-
// ── CSS
|
|
215
|
+
// ── Three.js CSS ────────────────────────────────────────────────────────────
|
|
238
216
|
|
|
239
217
|
async _injectThreeCSS() {
|
|
240
218
|
const sr = this.shadowRoot;
|
|
241
|
-
if (!sr || sr.querySelector('#
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
sr.appendChild(lnk);
|
|
219
|
+
if (!sr || sr.querySelector('#tcss')) return;
|
|
220
|
+
const l = document.createElement('link');
|
|
221
|
+
l.id = 'tcss'; l.rel = 'stylesheet';
|
|
222
|
+
l.href = EDITOR_BASE + 'css/main.css';
|
|
223
|
+
sr.appendChild(l);
|
|
247
224
|
}
|
|
248
225
|
|
|
249
|
-
// ──
|
|
226
|
+
// ── Mount Three.js sidebar panels ──────────────────────────────────────────
|
|
250
227
|
|
|
251
|
-
async
|
|
228
|
+
async _mountThreePanels() {
|
|
252
229
|
const base = EDITOR_BASE + 'js/';
|
|
230
|
+
const ed = this._editor;
|
|
253
231
|
const [
|
|
254
232
|
{ SidebarScene },
|
|
255
233
|
{ SidebarObject },
|
|
256
234
|
{ SidebarGeometry },
|
|
257
235
|
{ SidebarMaterial },
|
|
258
|
-
{ SidebarProject },
|
|
259
|
-
{ SidebarSettings },
|
|
260
236
|
] = await Promise.all([
|
|
261
237
|
import(/* @vite-ignore */ base + 'Sidebar.Scene.js'),
|
|
262
238
|
import(/* @vite-ignore */ base + 'Sidebar.Object.js'),
|
|
263
239
|
import(/* @vite-ignore */ base + 'Sidebar.Geometry.js'),
|
|
264
240
|
import(/* @vite-ignore */ base + 'Sidebar.Material.js'),
|
|
265
|
-
import(/* @vite-ignore */ base + 'Sidebar.Project.js'),
|
|
266
|
-
import(/* @vite-ignore */ base + 'Sidebar.Settings.js'),
|
|
267
241
|
]);
|
|
268
242
|
|
|
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);
|
|
243
|
+
this._getDomElement('subScene').appendChild(new SidebarScene(ed).dom);
|
|
244
|
+
this._getDomElement('objThreeWrap').appendChild(new SidebarObject(ed).dom);
|
|
245
|
+
this._getDomElement('subGeom').appendChild(new SidebarGeometry(ed).dom);
|
|
246
|
+
this._getDomElement('subMater').appendChild(new SidebarMaterial(ed).dom);
|
|
295
247
|
}
|
|
296
248
|
|
|
297
|
-
// ──
|
|
249
|
+
// ── Sub-tabs ────────────────────────────────────────────────────────────────
|
|
298
250
|
|
|
299
|
-
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
Transl: 'tabTransl',
|
|
251
|
+
_setupSubTabs() {
|
|
252
|
+
const bar = this._getDomElement('subBar');
|
|
253
|
+
const panels = {
|
|
254
|
+
scene: this._getDomElement('subScene'),
|
|
255
|
+
object: this._getDomElement('subObject'),
|
|
256
|
+
geom: this._getDomElement('subGeom'),
|
|
257
|
+
mater: this._getDomElement('subMater'),
|
|
307
258
|
};
|
|
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
259
|
bar?.querySelectorAll('.stab').forEach(btn => {
|
|
337
260
|
btn.addEventListener('click', () => {
|
|
338
261
|
bar.querySelectorAll('.stab').forEach(b => b.classList.remove('active'));
|
|
339
262
|
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
|
-
}
|
|
263
|
+
Object.values(panels).forEach(p => { if (p) p.style.display = 'none'; });
|
|
264
|
+
const p = panels[btn.dataset.sub];
|
|
265
|
+
if (p) p.style.display = btn.dataset.sub === 'object' ? 'flex' : 'block';
|
|
349
266
|
});
|
|
350
267
|
});
|
|
351
268
|
}
|
|
352
269
|
|
|
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' },
|
|
270
|
+
// ── Bindable properties ─────────────────────────────────────────────────────
|
|
271
|
+
|
|
272
|
+
_BINDABLE = [
|
|
273
|
+
{ key: 'position.x', label: 'Position X' },
|
|
274
|
+
{ key: 'position.y', label: 'Position Y' },
|
|
275
|
+
{ key: 'position.z', label: 'Position Z' },
|
|
276
|
+
{ key: 'rotation.x', label: 'Rotation X (°)' },
|
|
277
|
+
{ key: 'rotation.y', label: 'Rotation Y (°)' },
|
|
278
|
+
{ key: 'rotation.z', label: 'Rotation Z (°)' },
|
|
279
|
+
{ key: 'scale.x', label: 'Scale X' },
|
|
280
|
+
{ key: 'scale.y', label: 'Scale Y' },
|
|
281
|
+
{ key: 'scale.z', label: 'Scale Z' },
|
|
282
|
+
{ key: 'visible', label: 'Visible' },
|
|
283
|
+
{ key: 'material.opacity', label: 'Opacity' },
|
|
284
|
+
{ key: 'material.color', label: 'Color (hex)' },
|
|
377
285
|
];
|
|
378
286
|
|
|
379
|
-
|
|
380
|
-
const
|
|
381
|
-
if (!
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
this._BINDABLE_PROPS.forEach(({ key, label }) => {
|
|
287
|
+
_buildBindingRows() {
|
|
288
|
+
const cont = this._getDomElement('bindRows');
|
|
289
|
+
if (!cont) return;
|
|
290
|
+
cont.innerHTML = '';
|
|
291
|
+
this._BINDABLE.forEach(({ key, label }) => {
|
|
385
292
|
const row = document.createElement('div');
|
|
386
293
|
row.className = 'brow';
|
|
387
294
|
row.dataset.key = key;
|
|
388
295
|
|
|
296
|
+
// □ square button (left of label)
|
|
297
|
+
const sq = document.createElement('button');
|
|
298
|
+
sq.className = 'bsq';
|
|
299
|
+
sq.title = 'right-click to bind / clear';
|
|
300
|
+
sq.textContent = '■';
|
|
301
|
+
sq.addEventListener('contextmenu', e => {
|
|
302
|
+
e.preventDefault();
|
|
303
|
+
this._showCtxMenu(e, key);
|
|
304
|
+
});
|
|
305
|
+
sq.addEventListener('click', e => {
|
|
306
|
+
e.preventDefault();
|
|
307
|
+
this._showCtxMenu(e, key);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// property name
|
|
389
311
|
const lbl = document.createElement('span');
|
|
390
|
-
lbl.className = '
|
|
312
|
+
lbl.className = 'blbl';
|
|
391
313
|
lbl.textContent = label;
|
|
392
314
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
const editBtn = document.createElement('button');
|
|
398
|
-
editBtn.className = 'b-edit';
|
|
399
|
-
editBtn.textContent = '□';
|
|
400
|
-
editBtn.title = 'Edit binding';
|
|
401
|
-
editBtn.addEventListener('click', () => this._openBindingDialog(key, label, sig, editBtn));
|
|
315
|
+
// current signal display
|
|
316
|
+
const val = document.createElement('span');
|
|
317
|
+
val.className = 'bval none';
|
|
318
|
+
val.textContent = '';
|
|
402
319
|
|
|
320
|
+
row.appendChild(sq);
|
|
403
321
|
row.appendChild(lbl);
|
|
404
|
-
row.appendChild(
|
|
405
|
-
|
|
406
|
-
container.appendChild(row);
|
|
322
|
+
row.appendChild(val);
|
|
323
|
+
cont.appendChild(row);
|
|
407
324
|
});
|
|
408
325
|
}
|
|
409
326
|
|
|
410
|
-
|
|
411
|
-
const
|
|
412
|
-
if (!
|
|
413
|
-
const uuid =
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
const
|
|
417
|
-
const
|
|
418
|
-
const
|
|
419
|
-
|
|
420
|
-
if (
|
|
421
|
-
|
|
422
|
-
|
|
327
|
+
_refreshBindRows() {
|
|
328
|
+
const cont = this._getDomElement('bindRows');
|
|
329
|
+
if (!cont) return;
|
|
330
|
+
const uuid = this._selectedObj?.uuid;
|
|
331
|
+
cont.querySelectorAll('.brow').forEach(row => {
|
|
332
|
+
const key = row.dataset.key;
|
|
333
|
+
const sq = row.querySelector('.bsq');
|
|
334
|
+
const val = row.querySelector('.bval');
|
|
335
|
+
const bind = uuid ? this._bindings[uuid]?.[key] : null;
|
|
336
|
+
|
|
337
|
+
if (val) {
|
|
338
|
+
val.textContent = bind?.signal || '';
|
|
339
|
+
val.className = 'bval' + (bind?.signal ? '' : ' none');
|
|
423
340
|
}
|
|
424
|
-
if (
|
|
425
|
-
|
|
426
|
-
btn.title = binding?.signal ? `Bound: ${binding.signal}` : 'Edit binding';
|
|
427
|
-
btn.style.color = binding?.signal ? '#4ec9b0' : '';
|
|
341
|
+
if (sq) {
|
|
342
|
+
sq.className = 'bsq' + (bind?.signal ? ' bound' : '');
|
|
428
343
|
}
|
|
429
344
|
});
|
|
430
345
|
}
|
|
431
346
|
|
|
432
|
-
// ──
|
|
347
|
+
// ── Context menu ────────────────────────────────────────────────────────────
|
|
433
348
|
|
|
434
|
-
|
|
435
|
-
const
|
|
349
|
+
_setupContextMenu() {
|
|
350
|
+
const menu = this._getDomElement('ctxMenu');
|
|
351
|
+
if (!menu) return;
|
|
436
352
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
353
|
+
menu.addEventListener('click', e => {
|
|
354
|
+
const item = e.target.closest('.cmItem');
|
|
355
|
+
if (!item) return;
|
|
356
|
+
const action = item.dataset.action;
|
|
357
|
+
menu.classList.remove('open');
|
|
441
358
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
if (
|
|
445
|
-
if (this._bindings[uuid]) {
|
|
446
|
-
delete this._bindings[uuid][key];
|
|
447
|
-
if (!Object.keys(this._bindings[uuid]).length) delete this._bindings[uuid];
|
|
448
|
-
}
|
|
449
|
-
this._syncBindingsToHost();
|
|
450
|
-
this._updateBindRows(this._selectedObj);
|
|
451
|
-
}
|
|
452
|
-
overlay?.classList.remove('show');
|
|
453
|
-
this._bindingTarget = null;
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
this._getDomElement('bdOk')?.addEventListener('click', () => {
|
|
457
|
-
const { uuid, key } = this._bindingTarget || {};
|
|
458
|
-
const signal = this._getDomElement('bdSignal')?.value?.trim();
|
|
459
|
-
const formula = this._getDomElement('bdFormula')?.value?.trim() || 'val';
|
|
460
|
-
const twoWay = this._getDomElement('bdTwoWay')?.checked ?? false;
|
|
461
|
-
|
|
462
|
-
if (uuid && key && signal) {
|
|
463
|
-
if (!this._bindings[uuid]) this._bindings[uuid] = {};
|
|
464
|
-
this._bindings[uuid][key] = { signal, formula, twoWay };
|
|
465
|
-
this._syncBindingsToHost();
|
|
466
|
-
this._updateBindRows(this._selectedObj);
|
|
467
|
-
}
|
|
468
|
-
overlay?.classList.remove('show');
|
|
469
|
-
this._bindingTarget = null;
|
|
359
|
+
if (action === 'clear') this._clearBinding(this._ctxTarget);
|
|
360
|
+
if (action === 'editbind') this._openBindDialog(this._ctxTarget);
|
|
361
|
+
if (action === 'edittext') this._openTextEditor(this._ctxTarget);
|
|
470
362
|
});
|
|
471
363
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
if (id) {
|
|
476
|
-
const inp = this._getDomElement('bdSignal');
|
|
477
|
-
if (inp) inp.value = id;
|
|
478
|
-
}
|
|
479
|
-
} catch (_) {}
|
|
364
|
+
// close on outside click
|
|
365
|
+
document.addEventListener('click', e => {
|
|
366
|
+
if (!menu.contains(e.target)) menu.classList.remove('open');
|
|
480
367
|
});
|
|
481
368
|
}
|
|
482
369
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
this.
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
if (sigInp) sigInp.value = existing?.signal || '';
|
|
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)) + ']';
|
|
370
|
+
_showCtxMenu(e, key) {
|
|
371
|
+
if (!this._selectedObj) {
|
|
372
|
+
alert('Select a 3D object first.');
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
this._ctxTarget = { uuid: this._selectedObj.uuid, key };
|
|
376
|
+
const menu = this._getDomElement('ctxMenu');
|
|
377
|
+
if (!menu) return;
|
|
378
|
+
// position near click
|
|
379
|
+
menu.style.left = e.clientX + 'px';
|
|
380
|
+
menu.style.top = e.clientY + 'px';
|
|
381
|
+
menu.classList.add('open');
|
|
382
|
+
}
|
|
500
383
|
|
|
501
|
-
|
|
502
|
-
|
|
384
|
+
_clearBinding({ uuid, key } = {}) {
|
|
385
|
+
if (!uuid || !key) return;
|
|
386
|
+
delete this._bindings[uuid]?.[key];
|
|
387
|
+
if (this._bindings[uuid] && !Object.keys(this._bindings[uuid]).length)
|
|
388
|
+
delete this._bindings[uuid];
|
|
389
|
+
this._syncHost();
|
|
390
|
+
this._refreshBindRows();
|
|
503
391
|
}
|
|
504
392
|
|
|
505
|
-
|
|
506
|
-
if (
|
|
507
|
-
|
|
508
|
-
|
|
393
|
+
_openTextEditor({ uuid, key } = {}) {
|
|
394
|
+
if (!uuid || !key) return;
|
|
395
|
+
const ex = this._bindings[uuid]?.[key];
|
|
396
|
+
const val = ex?.signal || '';
|
|
397
|
+
const nv = window.prompt(`State ID for "${key}":`, val);
|
|
398
|
+
if (nv === null) return;
|
|
399
|
+
if (!nv.trim()) { this._clearBinding({ uuid, key }); return; }
|
|
400
|
+
if (!this._bindings[uuid]) this._bindings[uuid] = {};
|
|
401
|
+
this._bindings[uuid][key] = { signal: nv.trim(), formula: ex?.formula || 'val', twoWay: ex?.twoWay || false };
|
|
402
|
+
this._syncHost();
|
|
403
|
+
this._refreshBindRows();
|
|
509
404
|
}
|
|
510
405
|
|
|
511
|
-
// ──
|
|
406
|
+
// ── Binding dialog ──────────────────────────────────────────────────────────
|
|
512
407
|
|
|
513
|
-
|
|
514
|
-
const
|
|
515
|
-
const settings = host?.sceneData?.settings ?? {};
|
|
408
|
+
_setupBindingDialog() {
|
|
409
|
+
const ov = this._getDomElement('bdOverlay');
|
|
516
410
|
|
|
517
|
-
|
|
518
|
-
|
|
411
|
+
this._getDomElement('bdCancel')?.addEventListener('click', () => {
|
|
412
|
+
ov?.classList.remove('open');
|
|
413
|
+
});
|
|
519
414
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
ensure('visibilityRedirectScreen', '');
|
|
415
|
+
this._getDomElement('bdObjX')?.addEventListener('click', () => {
|
|
416
|
+
const inp = this._getDomElement('bdObj');
|
|
417
|
+
if (inp) inp.value = '';
|
|
418
|
+
});
|
|
525
419
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
420
|
+
this._getDomElement('bdObjBrowse')?.addEventListener('click', async () => {
|
|
421
|
+
try {
|
|
422
|
+
const { openSelectIdDialog } = await import('@iobroker/webcomponent-selectid-dialog/dist/selectIdHelper.js');
|
|
423
|
+
const connection = (await import('../common/IobrokerHandler.js')).iobrokerHandler.connection;
|
|
424
|
+
const id = await openSelectIdDialog(connection, null, false);
|
|
425
|
+
if (id) { const inp = this._getDomElement('bdObj'); if (inp) inp.value = id; }
|
|
426
|
+
} catch (_) {}
|
|
427
|
+
});
|
|
529
428
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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
|
-
`;
|
|
429
|
+
this._getDomElement('bdObjIOB')?.addEventListener('click', async () => {
|
|
430
|
+
try {
|
|
431
|
+
const { openSelectIdDialog } = await import('@iobroker/webcomponent-selectid-dialog/dist/selectIdHelper.js');
|
|
432
|
+
const connection = (await import('../common/IobrokerHandler.js')).iobrokerHandler.connection;
|
|
433
|
+
const id = await openSelectIdDialog(connection, null, false);
|
|
434
|
+
if (id) { const inp = this._getDomElement('bdObj'); if (inp) inp.value = id; }
|
|
435
|
+
} catch (_) {}
|
|
436
|
+
});
|
|
554
437
|
|
|
555
|
-
|
|
556
|
-
|
|
438
|
+
this._getDomElement('bdHistoric')?.addEventListener('click', () => {
|
|
439
|
+
// historic signals - show simple list if available
|
|
557
440
|
});
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
const
|
|
561
|
-
if (
|
|
562
|
-
|
|
441
|
+
|
|
442
|
+
this._getDomElement('bdConvAdd')?.addEventListener('click', () => {
|
|
443
|
+
const tbody = this._getDomElement('bdConvBody');
|
|
444
|
+
if (!tbody) return;
|
|
445
|
+
const tr = document.createElement('tr');
|
|
446
|
+
tr.innerHTML = '<td><input type="text" placeholder="condition"/></td><td><input type="text" placeholder="value"/></td>';
|
|
447
|
+
tbody.appendChild(tr);
|
|
563
448
|
});
|
|
564
|
-
|
|
565
|
-
|
|
449
|
+
|
|
450
|
+
this._getDomElement('bdConvRemove')?.addEventListener('click', () => {
|
|
451
|
+
const tbody = this._getDomElement('bdConvBody');
|
|
452
|
+
if (!tbody) return;
|
|
453
|
+
const last = tbody.querySelector('tr:last-child');
|
|
454
|
+
if (last) last.remove();
|
|
566
455
|
});
|
|
567
456
|
|
|
568
|
-
|
|
569
|
-
|
|
457
|
+
this._getDomElement('bdOk')?.addEventListener('click', () => {
|
|
458
|
+
const { uuid, key } = this._ctxTarget || {};
|
|
459
|
+
const signal = this._getDomElement('bdObj')?.value?.trim();
|
|
460
|
+
const formula = this._getDomElement('bdFormula')?.value?.trim() || 'val';
|
|
461
|
+
const formulaWB = this._getDomElement('bdFormulaWB')?.value?.trim() || '';
|
|
462
|
+
const writeBack = this._getDomElement('bdWriteBack')?.value?.trim() || '';
|
|
463
|
+
const twoWay = this._getDomElement('bdTwoWay')?.checked ?? false;
|
|
464
|
+
const invert = this._getDomElement('bdInvert')?.checked ?? false;
|
|
465
|
+
const type = this._getDomElement('bdType')?.value || 'signal';
|
|
466
|
+
|
|
467
|
+
// collect converter rows
|
|
468
|
+
const converter = [];
|
|
469
|
+
this._getDomElement('bdConvBody')?.querySelectorAll('tr').forEach(tr => {
|
|
470
|
+
const inputs = tr.querySelectorAll('input');
|
|
471
|
+
if (inputs[0]?.value || inputs[1]?.value)
|
|
472
|
+
converter.push({ condition: inputs[0]?.value || '', value: inputs[1]?.value || '' });
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
if (uuid && key && signal) {
|
|
476
|
+
if (!this._bindings[uuid]) this._bindings[uuid] = {};
|
|
477
|
+
this._bindings[uuid][key] = { signal, formula, twoWay, invert, type, writeBack, formulaWB, converter };
|
|
478
|
+
this._syncHost();
|
|
479
|
+
this._refreshBindRows();
|
|
480
|
+
} else if (uuid && key && !signal) {
|
|
481
|
+
this._clearBinding({ uuid, key });
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
ov?.classList.remove('open');
|
|
485
|
+
});
|
|
570
486
|
}
|
|
571
487
|
|
|
572
|
-
|
|
573
|
-
if (!
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
488
|
+
_openBindDialog({ uuid, key } = {}) {
|
|
489
|
+
if (!uuid || !key) return;
|
|
490
|
+
const ex = this._bindings[uuid]?.[key];
|
|
491
|
+
|
|
492
|
+
const getEl = id => this._getDomElement(id);
|
|
493
|
+
const label = this._BINDABLE.find(b => b.key === key)?.label || key;
|
|
494
|
+
|
|
495
|
+
getEl('bdPropLabel').textContent = label;
|
|
496
|
+
getEl('bdObj').value = ex?.signal || '';
|
|
497
|
+
getEl('bdFormula').value = ex?.formula || 'val';
|
|
498
|
+
getEl('bdFormulaWB').value = ex?.formulaWB || '';
|
|
499
|
+
getEl('bdWriteBack').value = ex?.writeBack || '';
|
|
500
|
+
getEl('bdTwoWay').checked = ex?.twoWay || false;
|
|
501
|
+
getEl('bdInvert').checked = ex?.invert || false;
|
|
502
|
+
if (getEl('bdType')) getEl('bdType').value = ex?.type || 'signal';
|
|
503
|
+
|
|
504
|
+
// fill converter
|
|
505
|
+
const tbody = getEl('bdConvBody');
|
|
506
|
+
if (tbody) {
|
|
507
|
+
tbody.innerHTML = '';
|
|
508
|
+
(ex?.converter || []).forEach(row => {
|
|
509
|
+
const tr = document.createElement('tr');
|
|
510
|
+
tr.innerHTML = `<td><input type="text" value="${row.condition || ''}"/></td><td><input type="text" value="${row.value || ''}"/></td>`;
|
|
511
|
+
tbody.appendChild(tr);
|
|
593
512
|
});
|
|
594
|
-
} catch (_) {
|
|
595
|
-
container.innerHTML = '<span style="color:#666;font-size:11px;">Could not load groups</span>';
|
|
596
513
|
}
|
|
514
|
+
|
|
515
|
+
this._ctxTarget = { uuid, key };
|
|
516
|
+
getEl('bdOverlay')?.classList.add('open');
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
_syncHost() {
|
|
520
|
+
if (this._editorHost?.sceneData) this._editorHost.sceneData.bindings = this._bindings;
|
|
597
521
|
}
|
|
598
522
|
}
|
|
599
523
|
|
|
@@ -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="
|
|
111
|
+
<div id="effectsDock" title="Projects" 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) => {
|
|
@@ -309,7 +313,7 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
|
|
|
309
313
|
|
|
310
314
|
setTimeout(makeSetup('visibilityDock', () => this._setupVisibilityPanel(), 'Visibility'), 1000);
|
|
311
315
|
setTimeout(makeSetup('animationsDock', () => this._setupAnimationsPanel(), 'Animations'), 1200);
|
|
312
|
-
setTimeout(makeSetup('effectsDock', () => this._setupEffectsPanel(), '
|
|
316
|
+
setTimeout(makeSetup('effectsDock', () => this._setupEffectsPanel(), 'Projects'), 1400);
|
|
313
317
|
}
|
|
314
318
|
|
|
315
319
|
_setupVisibilityPanel() {
|
|
@@ -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;
|