iobroker.mywebui 1.42.38 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.mywebui",
3
- "version": "1.42.38",
3
+ "version": "1.42.39",
4
4
  "description": "ioBroker mywebui - Custom edited mywebui by gokturk413 with 3D Editor",
5
5
  "type": "module",
6
6
  "main": "dist/backend/main.js",
@@ -1,19 +1,12 @@
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
- /**
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
- */
12
5
  export class IobrokerWebui3DScreenPropertiesPanel extends BaseCustomWebComponentConstructorAppend {
13
6
 
14
7
  static template = html`
15
8
  <div id="root">
16
- <!-- Sub-tab bar: Scene / Object / Geom / Mater -->
9
+ <!-- Sub-tab bar -->
17
10
  <div id="subBar">
18
11
  <span class="stab active" data-sub="scene">Scene</span>
19
12
  <span class="stab" data-sub="object">Object</span>
@@ -21,45 +14,96 @@ export class IobrokerWebui3DScreenPropertiesPanel extends BaseCustomWebComponent
21
14
  <span class="stab" data-sub="mater">Mater</span>
22
15
  </div>
23
16
 
24
- <!-- Sub-tab panels -->
17
+ <!-- Scene tab -->
25
18
  <div id="subScene" class="spanel active"></div>
26
19
 
20
+ <!-- Object tab -->
27
21
  <div id="subObject" class="spanel" style="display:none;">
28
- <!-- Three.js SidebarObject embedded here -->
29
22
  <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>
34
- </div>
23
+ <div class="sechdr">BINDINGS (IOBROKER STATES)</div>
24
+ <div id="bindRows"></div>
35
25
  </div>
36
26
 
27
+ <!-- Geom tab -->
37
28
  <div id="subGeom" class="spanel" style="display:none;"></div>
29
+
30
+ <!-- Mater tab -->
38
31
  <div id="subMater" class="spanel" style="display:none;"></div>
39
32
 
40
- <!-- Binding dialog (inline overlay) -->
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>
39
+
40
+ <!-- Binding dialog -->
41
41
  <div id="bdOverlay">
42
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>
46
- <div style="display:flex;gap:4px;">
47
- <input id="bdSig" type="text" placeholder="mywebui.0.data.value" autocomplete="off" />
48
- <button id="bdBrowse">...</button>
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>
49
52
  </div>
50
53
  </div>
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" />
54
+
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>
54
63
  </div>
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>
64
+
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>
73
+
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>
81
+
82
+ <div class="bdRow">
83
+ <span class="bdLbl">write back signal :</span>
84
+ <input id="bdWriteBack" type="text" class="bdInput" placeholder=""/>
85
+ </div>
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>
90
+ </div>
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>
58
102
  </div>
103
+
59
104
  <div id="bdFoot">
60
- <button id="bdClear">Clear</button>
61
- <button id="bdCancel">Cancel</button>
62
- <button id="bdOk" class="primary">OK</button>
105
+ <button class="bdBtn accent-ok" id="bdOk">Ok</button>
106
+ <button class="bdBtn" id="bdCancel">Cancel</button>
63
107
  </div>
64
108
  </div>
65
109
  </div>
@@ -72,52 +116,69 @@ export class IobrokerWebui3DScreenPropertiesPanel extends BaseCustomWebComponent
72
116
 
73
117
  /* Sub-tab bar */
74
118
  #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; }
119
+ .stab { padding:5px 10px;cursor:pointer;font-size:11px;color:#888;border-right:1px solid #3c3c3c;white-space:nowrap;user-select:none; }
76
120
  .stab:hover { color:#ddd;background:#3e3e42; }
77
121
  .stab.active { color:#fff;background:#1e1e1e;border-top:2px solid #4ec9b0; }
78
122
 
79
123
  /* Sub-tab panels */
80
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; }
81
128
 
82
129
  /* Section header */
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; }
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; }
84
131
 
85
- /* Binding rows */
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; }
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; }
88
135
  .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; }
100
-
101
- /* Binding dialog */
102
- #bdOverlay { display:none;position:absolute;inset:0;background:rgba(0,0,0,0.78);z-index:300;align-items:center;justify-content:center; }
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 */
153
+ #bdOverlay { display:none;position:absolute;inset:0;background:rgba(0,0,0,0.75);z-index:500;align-items:center;justify-content:center; }
103
154
  #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; }
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; }
114
175
  `;
115
176
 
116
177
  _editor = null;
117
178
  _editorHost = null;
118
179
  _bindings = {};
119
180
  _selectedObj = null;
120
- _bindTarget = null;
181
+ _ctxTarget = null; // { uuid, key }
121
182
 
122
183
  // ── Public API ─────────────────────────────────────────────────────────────
123
184
 
@@ -129,7 +190,8 @@ export class IobrokerWebui3DScreenPropertiesPanel extends BaseCustomWebComponent
129
190
  await this._injectThreeCSS();
130
191
  await this._mountThreePanels();
131
192
  this._setupSubTabs();
132
- this._setupBindingRows();
193
+ this._buildBindingRows();
194
+ this._setupContextMenu();
133
195
  this._setupBindingDialog();
134
196
 
135
197
  editor.signals.objectSelected.add(obj => {
@@ -161,7 +223,7 @@ export class IobrokerWebui3DScreenPropertiesPanel extends BaseCustomWebComponent
161
223
  sr.appendChild(l);
162
224
  }
163
225
 
164
- // ── Mount Three.js sidebar sub-panels ──────────────────────────────────────
226
+ // ── Mount Three.js sidebar panels ──────────────────────────────────────────
165
227
 
166
228
  async _mountThreePanels() {
167
229
  const base = EDITOR_BASE + 'js/';
@@ -200,12 +262,12 @@ export class IobrokerWebui3DScreenPropertiesPanel extends BaseCustomWebComponent
200
262
  btn.classList.add('active');
201
263
  Object.values(panels).forEach(p => { if (p) p.style.display = 'none'; });
202
264
  const p = panels[btn.dataset.sub];
203
- if (p) p.style.display = 'block';
265
+ if (p) p.style.display = btn.dataset.sub === 'object' ? 'flex' : 'block';
204
266
  });
205
267
  });
206
268
  }
207
269
 
208
- // ── Bindable properties list ────────────────────────────────────────────────
270
+ // ── Bindable properties ─────────────────────────────────────────────────────
209
271
 
210
272
  _BINDABLE = [
211
273
  { key: 'position.x', label: 'Position X' },
@@ -222,7 +284,7 @@ export class IobrokerWebui3DScreenPropertiesPanel extends BaseCustomWebComponent
222
284
  { key: 'material.color', label: 'Color (hex)' },
223
285
  ];
224
286
 
225
- _setupBindingRows() {
287
+ _buildBindingRows() {
226
288
  const cont = this._getDomElement('bindRows');
227
289
  if (!cont) return;
228
290
  cont.innerHTML = '';
@@ -231,22 +293,33 @@ export class IobrokerWebui3DScreenPropertiesPanel extends BaseCustomWebComponent
231
293
  row.className = 'brow';
232
294
  row.dataset.key = key;
233
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
234
311
  const lbl = document.createElement('span');
235
312
  lbl.className = 'blbl';
236
313
  lbl.textContent = label;
237
314
 
238
- const sig = document.createElement('span');
239
- sig.className = 'bsig none';
240
- sig.textContent = '';
241
-
242
- const btn = document.createElement('button');
243
- btn.className = 'bedit';
244
- btn.textContent = '□ bind';
245
- btn.addEventListener('click', () => this._openBindDlg(key, label));
315
+ // current signal display
316
+ const val = document.createElement('span');
317
+ val.className = 'bval none';
318
+ val.textContent = '';
246
319
 
320
+ row.appendChild(sq);
247
321
  row.appendChild(lbl);
248
- row.appendChild(sig);
249
- row.appendChild(btn);
322
+ row.appendChild(val);
250
323
  cont.appendChild(row);
251
324
  });
252
325
  }
@@ -257,68 +330,190 @@ export class IobrokerWebui3DScreenPropertiesPanel extends BaseCustomWebComponent
257
330
  const uuid = this._selectedObj?.uuid;
258
331
  cont.querySelectorAll('.brow').forEach(row => {
259
332
  const key = row.dataset.key;
260
- const sig = row.querySelector('.bsig');
261
- const btn = row.querySelector('.bedit');
333
+ const sq = row.querySelector('.bsq');
334
+ const val = row.querySelector('.bval');
262
335
  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' : ''; }
336
+
337
+ if (val) {
338
+ val.textContent = bind?.signal || '';
339
+ val.className = 'bval' + (bind?.signal ? '' : ' none');
340
+ }
341
+ if (sq) {
342
+ sq.className = 'bsq' + (bind?.signal ? ' bound' : '');
343
+ }
344
+ });
345
+ }
346
+
347
+ // ── Context menu ────────────────────────────────────────────────────────────
348
+
349
+ _setupContextMenu() {
350
+ const menu = this._getDomElement('ctxMenu');
351
+ if (!menu) return;
352
+
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');
358
+
359
+ if (action === 'clear') this._clearBinding(this._ctxTarget);
360
+ if (action === 'editbind') this._openBindDialog(this._ctxTarget);
361
+ if (action === 'edittext') this._openTextEditor(this._ctxTarget);
362
+ });
363
+
364
+ // close on outside click
365
+ document.addEventListener('click', e => {
366
+ if (!menu.contains(e.target)) menu.classList.remove('open');
265
367
  });
266
368
  }
267
369
 
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
+ }
383
+
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();
391
+ }
392
+
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();
404
+ }
405
+
268
406
  // ── Binding dialog ──────────────────────────────────────────────────────────
269
407
 
270
408
  _setupBindingDialog() {
271
409
  const ov = this._getDomElement('bdOverlay');
272
410
 
273
411
  this._getDomElement('bdCancel')?.addEventListener('click', () => {
274
- ov?.classList.remove('open'); this._bindTarget = null;
412
+ ov?.classList.remove('open');
275
413
  });
276
414
 
277
- this._getDomElement('bdClear')?.addEventListener('click', () => {
278
- const { uuid, key } = this._bindTarget || {};
279
- if (uuid && key) {
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();
283
- }
284
- ov?.classList.remove('open'); this._bindTarget = null;
415
+ this._getDomElement('bdObjX')?.addEventListener('click', () => {
416
+ const inp = this._getDomElement('bdObj');
417
+ if (inp) inp.value = '';
418
+ });
419
+
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
+ });
428
+
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
+ });
437
+
438
+ this._getDomElement('bdHistoric')?.addEventListener('click', () => {
439
+ // historic signals - show simple list if available
440
+ });
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);
448
+ });
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();
285
455
  });
286
456
 
287
457
  this._getDomElement('bdOk')?.addEventListener('click', () => {
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;
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
+
292
475
  if (uuid && key && signal) {
293
476
  if (!this._bindings[uuid]) this._bindings[uuid] = {};
294
- this._bindings[uuid][key] = { signal, formula, twoWay };
295
- this._syncHost(); this._refreshBindRows();
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 });
296
482
  }
297
- ov?.classList.remove('open'); this._bindTarget = null;
298
- });
299
483
 
300
- this._getDomElement('bdBrowse')?.addEventListener('click', async () => {
301
- try {
302
- const id = await iobrokerHandler.showSelectIdDialog?.();
303
- if (id) { const inp = this._getDomElement('bdSig'); if (inp) inp.value = id; }
304
- } catch (_) {}
484
+ ov?.classList.remove('open');
305
485
  });
306
486
  }
307
487
 
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 };
488
+ _openBindDialog({ uuid, key } = {}) {
489
+ if (!uuid || !key) return;
312
490
  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');
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);
512
+ });
513
+ }
514
+
515
+ this._ctxTarget = { uuid, key };
516
+ getEl('bdOverlay')?.classList.add('open');
322
517
  }
323
518
 
324
519
  _syncHost() {
@@ -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 / Project" style="overflow: auto; width: 100%;" dock-spawn-dock-to="attributeDock">
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">
@@ -313,7 +313,7 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
313
313
 
314
314
  setTimeout(makeSetup('visibilityDock', () => this._setupVisibilityPanel(), 'Visibility'), 1000);
315
315
  setTimeout(makeSetup('animationsDock', () => this._setupAnimationsPanel(), 'Animations'), 1200);
316
- setTimeout(makeSetup('effectsDock', () => this._setupEffectsPanel(), 'Effects'), 1400);
316
+ setTimeout(makeSetup('effectsDock', () => this._setupEffectsPanel(), 'Projects'), 1400);
317
317
  }
318
318
 
319
319
  _setupVisibilityPanel() {