iobroker.mywebui 1.37.36 → 1.37.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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "mywebui",
4
- "version": "1.37.36",
4
+ "version": "1.37.38",
5
5
  "titleLang": {
6
6
  "en": "mywebui",
7
7
  "de": "mywebui",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.mywebui",
3
- "version": "1.37.36",
3
+ "version": "1.37.38",
4
4
  "description": "ioBroker mywebui - Custom edited mywebui by gokturk413",
5
5
  "type": "module",
6
6
  "main": "dist/backend/main.js",
@@ -27,6 +27,7 @@ import { IobrokerWebuiScreenEditor } from './IobrokerWebuiScreenEditor.js';
27
27
  import { IobrokerWebuiConfirmationWrapper } from './IobrokerWebuiConfirmationWrapper.js';
28
28
  import { getPanelContainerForElement } from './DockHelper.js';
29
29
  import { IobrokerWebuiPropertyGrid } from './IobrokerWebuiPropertyGrid.js';
30
+ import { IobrokerWebuiBindingsEditor } from './IobrokerWebuiBindingsEditor.js';
30
31
  import { typeInfoFromJsonSchema } from '@gokturk413/propertygrid.webcomponent';
31
32
  import { openSelectIdDialog } from "@iobroker/webcomponent-selectid-dialog/dist/selectIdHelper.js";
32
33
  import "./IobrokerWebuiTranslationEditor.js";
@@ -403,12 +404,22 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
403
404
  const designItem = selectedItems[0];
404
405
  const element = designItem.element;
405
406
 
406
- // Read existing binding from bind-prop:hidden attribute
407
- const bindAttr = element.getAttribute('bind-prop:hidden');
407
+ // Read existing signal from data-visibility-signal (may be plain signal string or JSON with signal+expression)
408
+ const existingSignalAttr = element.getAttribute('data-visibility-signal') || null;
409
+ let existingSignal = existingSignalAttr;
408
410
  let existingBinding = null;
409
- if (bindAttr != null) {
410
- const parsed = bindingsHelper.parseBinding(element, 'bind-prop:hidden', bindAttr, 'property', 'bind-prop:');
411
- if (parsed) existingBinding = parsed[1];
411
+ if (existingSignalAttr) {
412
+ if (existingSignalAttr.startsWith('{')) {
413
+ try {
414
+ const parsed = JSON.parse(existingSignalAttr);
415
+ existingSignal = parsed.signal || existingSignalAttr;
416
+ existingBinding = { ...parsed, target: 'property' };
417
+ } catch (e) {
418
+ existingBinding = { signal: existingSignalAttr, target: 'property' };
419
+ }
420
+ } else {
421
+ existingBinding = { signal: existingSignalAttr, target: 'property' };
422
+ }
412
423
  }
413
424
 
414
425
  // Read group access control config from data attributes
@@ -420,25 +431,54 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
420
431
 
421
432
  content.innerHTML = '';
422
433
 
434
+ // Helper: open IobrokerWebuiBindingsEditor directly (pre-populates with existingBinding, saves to data-visibility-signal)
435
+ const openVisibilityBindingEditor = () => {
436
+ const dynEdt = new IobrokerWebuiBindingsEditor(
437
+ { name: 'visibility', propertyType: 'propertyAndAttribute' },
438
+ existingBinding,
439
+ 'property',
440
+ serviceContainer,
441
+ designItem.instanceServiceContainer,
442
+ this
443
+ );
444
+ const cw = new IobrokerWebuiConfirmationWrapper();
445
+ cw.title = 'Edit Visibility Signal Binding';
446
+ cw.appendChild(dynEdt);
447
+ const dlg = this.openDialog(cw, { x: 200, y: 200, width: 700, height: 460 });
448
+ cw.cancelClicked.on(() => dlg.close());
449
+ cw.okClicked.on(() => {
450
+ dlg.close();
451
+ const signal = dynEdt.objectNames;
452
+ if (signal) {
453
+ const bnd = { signal };
454
+ if (dynEdt.expression) bnd.expression = dynEdt.expression;
455
+ if (dynEdt.inverted) bnd.inverted = true;
456
+ if (dynEdt.twoWay) bnd.twoWay = true;
457
+ const attrValue = (bnd.expression || bnd.inverted || bnd.twoWay)
458
+ ? JSON.stringify(bnd)
459
+ : signal;
460
+ designItem.setAttribute('data-visibility-signal', attrValue);
461
+ } else {
462
+ designItem.removeAttribute('data-visibility-signal');
463
+ }
464
+ designItem.removeAttribute('bind-prop:hidden');
465
+ this._updateVisibilityPanel();
466
+ });
467
+ };
468
+
423
469
  // --- BINDING ROW ---
424
470
  const bindRow = document.createElement('div');
425
471
  bindRow.style.cssText = 'display:flex;align-items:center;gap:6px;padding:4px 0;margin-bottom:10px;padding-bottom:10px;border-bottom:1px solid #ddd;';
426
472
 
427
- // button on the LEFT (like Properties panel style)
428
- const bindBtn = document.createElement('button');
429
- bindBtn.textContent = '';
430
- bindBtn.title = 'Right-click for binding options';
431
- bindBtn.style.cssText = existingBinding
432
- ? 'width:16px;height:16px;padding:0;font-size:11px;line-height:1;border:1px solid #888;background:#ffd700;cursor:pointer;flex-shrink:0;'
433
- : 'width:16px;height:16px;padding:0;font-size:11px;line-height:1;border:1px solid #888;background:#f0f0f0;cursor:pointer;flex-shrink:0;';
434
-
435
- // Left-click: open binding editor (pass null so openBindingsEditor doesn't reject on service check)
436
- bindBtn.onclick = () => {
437
- const property = { name: 'hidden', propertyType: 'propertyAndAttribute' };
438
- serviceContainer.config.openBindingsEditor(property, [designItem], null, 'property');
439
- };
473
+ // Bind indicator styled exactly like PropertyGridPropertyList's isSetElement
474
+ const bindBtn = document.createElement('div');
475
+ bindBtn.title = existingSignal ? 'Visibility: ' + existingSignal + ' (click / right-click)' : 'Click to bind visibility signal';
476
+ bindBtn.style.cssText = 'width:7px;height:7px;border:1px solid white;cursor:pointer;flex-shrink:0;box-sizing:border-box;'
477
+ + (existingSignal ? 'background:orange;' : 'background:transparent;');
478
+
479
+ bindBtn.onclick = openVisibilityBindingEditor;
440
480
 
441
- // Right-click: context menu (like Properties panel)
481
+ // Right-click: context menu
442
482
  bindBtn.oncontextmenu = (e) => {
443
483
  e.preventDefault();
444
484
  const existing = document.getElementById('__vis-ctx-menu');
@@ -461,14 +501,12 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
461
501
  menu.appendChild(item);
462
502
  };
463
503
 
464
- addItem('edit binding', () => {
465
- const property = { name: 'hidden', propertyType: 'propertyAndAttribute' };
466
- serviceContainer.config.openBindingsEditor(property, [designItem], null, 'property');
467
- });
504
+ addItem('edit binding', openVisibilityBindingEditor);
468
505
  addItem('clear binding', () => {
506
+ designItem.removeAttribute('data-visibility-signal');
469
507
  designItem.removeAttribute('bind-prop:hidden');
470
508
  this._updateVisibilityPanel();
471
- }, !existingBinding);
509
+ }, !existingSignal);
472
510
 
473
511
  document.body.appendChild(menu);
474
512
  const close = (ev) => { if (!menu.contains(ev.target)) { menu.remove(); document.removeEventListener('mousedown', close); } };
@@ -482,11 +520,10 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
482
520
  bindLabel.style.cssText = 'font-size:12px;font-weight:600;flex:1;';
483
521
  bindRow.appendChild(bindLabel);
484
522
 
485
- if (existingBinding) {
486
- const sig = existingBinding.signal || (existingBinding.expression ? 'expr' : '?');
523
+ if (existingSignal) {
487
524
  const statusSpan = document.createElement('span');
488
- statusSpan.textContent = sig;
489
- statusSpan.title = JSON.stringify(existingBinding);
525
+ statusSpan.textContent = existingSignal;
526
+ statusSpan.title = existingSignal;
490
527
  statusSpan.style.cssText = 'font-size:10px;color:#aaa;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:right;';
491
528
  bindRow.appendChild(statusSpan);
492
529
  }
@@ -5,26 +5,78 @@ class VisibilityService {
5
5
 
6
6
  #originalDisplayStyles = new WeakMap();
7
7
 
8
- async #checkAndApply(element, visibilityConfig) {
8
+ #applyResult(element, result) {
9
+ if (!result.visible) {
10
+ element.style.display = 'none';
11
+ element.style.visibility = 'hidden';
12
+ } else {
13
+ const orig = this.#originalDisplayStyles.get(element) || '';
14
+ element.style.display = orig;
15
+ element.style.visibility = '';
16
+ }
17
+
18
+ if (!result.enabled) {
19
+ element.style.pointerEvents = 'none';
20
+ element.style.opacity = '0.5';
21
+ } else {
22
+ element.style.pointerEvents = '';
23
+ element.style.opacity = '';
24
+ }
25
+ }
26
+
27
+ async #checkAndApply(element, visibilityConfig, currentStateVal) {
9
28
  try {
10
- const result = await iobrokerHandler.checkVisibility(visibilityConfig);
11
-
12
- if (!result.visible) {
13
- element.style.display = 'none';
14
- element.style.visibility = 'hidden';
15
- } else {
16
- const orig = this.#originalDisplayStyles.get(element) || '';
17
- element.style.display = orig;
18
- element.style.visibility = '';
29
+ const action = visibilityConfig.action || 'hide';
30
+
31
+ // Step 1: Group access check (if groups configured)
32
+ if (visibilityConfig.groups && visibilityConfig.groups.length > 0) {
33
+ const groupResult = await iobrokerHandler.checkVisibility({
34
+ enabled: true,
35
+ groups: visibilityConfig.groups,
36
+ action: action
37
+ });
38
+ // If user is not in allowed groups, apply restriction and stop
39
+ if (!groupResult.visible || !groupResult.enabled) {
40
+ this.#applyResult(element, groupResult);
41
+ return;
42
+ }
19
43
  }
20
44
 
21
- if (!result.enabled) {
22
- element.style.pointerEvents = 'none';
23
- element.style.opacity = '0.5';
24
- } else {
25
- element.style.pointerEvents = '';
26
- element.style.opacity = '';
45
+ // Step 2: Signal/state check (if objectId configured)
46
+ if (visibilityConfig.objectId) {
47
+ // objectId may be a plain signal string or JSON like {"signal":"...","expression":"..."}
48
+ let signalId = visibilityConfig.objectId;
49
+ let expression = null;
50
+ if (typeof signalId === 'string' && signalId.startsWith('{')) {
51
+ try {
52
+ const parsed = JSON.parse(signalId);
53
+ signalId = parsed.signal || signalId;
54
+ expression = parsed.expression || null;
55
+ } catch (e) { /* keep raw */ }
56
+ }
57
+
58
+ let stateVal = currentStateVal;
59
+ if (stateVal === undefined) {
60
+ const state = await iobrokerHandler.connection.getState(signalId);
61
+ stateVal = state?.val;
62
+ }
63
+
64
+ // Evaluate expression if present (same pattern as bind-content:text)
65
+ if (expression) {
66
+ try { stateVal = new Function('__0', expression)(stateVal); } catch (e) { console.warn('[Visibility] Expression error:', e); }
67
+ }
68
+
69
+ // Truthy state value → apply action
70
+ const isActive = stateVal !== null && stateVal !== undefined && stateVal !== false && stateVal !== 0 && stateVal !== '';
71
+ this.#applyResult(element, isActive
72
+ ? { visible: action !== 'hide', enabled: action !== 'disable' }
73
+ : { visible: true, enabled: true }
74
+ );
75
+ return;
27
76
  }
77
+
78
+ // No restrictions active → show/enable
79
+ this.#applyResult(element, { visible: true, enabled: true });
28
80
  } catch (err) {
29
81
  console.error('[Visibility] Check failed:', err);
30
82
  }
@@ -37,11 +89,15 @@ class VisibilityService {
37
89
  this.#originalDisplayStyles.set(element, element.style.display || '');
38
90
  }
39
91
 
40
- await this.#checkAndApply(element, visibilityConfig);
92
+ await this.#checkAndApply(element, visibilityConfig, undefined);
41
93
 
42
94
  if (visibilityConfig.objectId) {
43
- await iobrokerHandler.subscribeState(visibilityConfig.objectId, () => {
44
- this.#checkAndApply(element, visibilityConfig);
95
+ let signalId = visibilityConfig.objectId;
96
+ if (typeof signalId === 'string' && signalId.startsWith('{')) {
97
+ try { signalId = JSON.parse(signalId).signal || signalId; } catch (e) { /* keep raw */ }
98
+ }
99
+ await iobrokerHandler.subscribeState(signalId, (_id, state) => {
100
+ this.#checkAndApply(element, visibilityConfig, state?.val);
45
101
  });
46
102
  }
47
103
  }
@@ -58,20 +114,24 @@ class VisibilityService {
58
114
  }
59
115
 
60
116
  async scanAndApply(container = document.body) {
61
- const elements = container.querySelectorAll('[data-visibility-enabled="true"]');
62
- for (const element of elements) {
117
+ // Collect all elements needing visibility handling (group control OR signal binding)
118
+ const groupElements = container.querySelectorAll('[data-visibility-enabled="true"]');
119
+ const signalElements = container.querySelectorAll('[data-visibility-signal]');
120
+ const seen = new Set();
121
+
122
+ for (const element of [...groupElements, ...signalElements]) {
123
+ if (seen.has(element)) continue;
124
+ seen.add(element);
63
125
  try {
64
126
  const visibilityConfig = {
65
127
  enabled: true,
66
- objectId: element.getAttribute('data-visibility-signal'),
67
- condition: element.getAttribute('data-visibility-condition') || '==',
68
- conditionValue: element.getAttribute('data-visibility-value') || '',
128
+ objectId: element.getAttribute('data-visibility-signal') || null,
69
129
  action: element.getAttribute('data-visibility-action') || 'hide',
70
130
  groups: element.getAttribute('data-visibility-groups')?.split(',').filter(g => g) || []
71
131
  };
72
132
  await this.applyVisibility(element, visibilityConfig);
73
133
  } catch (err) {
74
- console.error('[Visibility] Error parsing visibility config:', err);
134
+ console.error('[Visibility] Error:', err);
75
135
  }
76
136
  }
77
137
  }