iobroker.mywebui 1.37.44 → 1.37.47

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.44",
4
+ "version": "1.37.47",
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.44",
3
+ "version": "1.37.47",
4
4
  "description": "ioBroker mywebui - Custom edited mywebui by gokturk413",
5
5
  "type": "module",
6
6
  "main": "dist/backend/main.js",
@@ -404,29 +404,28 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
404
404
  const designItem = selectedItems[0];
405
405
  const element = designItem.element;
406
406
 
407
- // Read existing signal from data-visibility-signal (may be plain signal string or JSON with signal+expression+historic+converter)
408
- const existingSignalAttr = element.getAttribute('data-visibility-signal') || null;
409
- let existingSignal = existingSignalAttr;
407
+ // Read existing signal binding — bind-prop:hidden (hide) or bind-prop:disable (disable)
408
+ const hiddenAttr = element.getAttribute('bind-prop:hidden') || null;
409
+ const disableAttr = element.getAttribute('bind-prop:disable') || null;
410
+ const existingBindPropAttr = hiddenAttr || disableAttr;
411
+ let existingSignalAction = hiddenAttr ? 'hide' : (disableAttr ? 'disable' : 'hide');
412
+ let existingSignal = null;
410
413
  let existingBinding = null;
411
- if (existingSignalAttr) {
412
- if (existingSignalAttr.startsWith('{')) {
413
- try {
414
- const parsed = JSON.parse(existingSignalAttr);
415
- existingSignal = parsed.signal || existingSignalAttr;
416
- // BindingsEditor expects bindableObjectNames array, not signal string
417
- existingBinding = {
418
- bindableObjectNames: parsed.signal ? [parsed.signal] : [],
419
- expression: parsed.expression || '',
420
- historic: parsed.historic || null,
421
- converter: parsed.converter || null,
422
- invert: parsed.invert || false,
423
- target: 'property'
424
- };
425
- } catch (e) {
426
- existingBinding = { bindableObjectNames: [existingSignalAttr], target: 'property' };
427
- }
428
- } else {
429
- existingBinding = { bindableObjectNames: [existingSignalAttr], target: 'property' };
414
+ if (existingBindPropAttr) {
415
+ try {
416
+ const parsed = JSON.parse(existingBindPropAttr);
417
+ existingSignal = parsed.signal || null;
418
+ existingBinding = {
419
+ bindableObjectNames: parsed.signal ? [parsed.signal] : [],
420
+ expression: parsed.expression || '',
421
+ historic: parsed.historic || null,
422
+ converter: parsed.converter || null,
423
+ invert: parsed.inverted || false,
424
+ target: 'property'
425
+ };
426
+ } catch (e) {
427
+ existingSignal = existingBindPropAttr;
428
+ existingBinding = { bindableObjectNames: [existingBindPropAttr], target: 'property' };
430
429
  }
431
430
  }
432
431
 
@@ -439,7 +438,18 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
439
438
 
440
439
  content.innerHTML = '';
441
440
 
442
- // Helper: open IobrokerWebuiBindingsEditor directly (pre-populates with existingBinding, saves to data-visibility-signal)
441
+ // Signal action selector (hide / disable) referenced by openVisibilityBindingEditor
442
+ const signalActionSelect = document.createElement('select');
443
+ signalActionSelect.style.cssText = 'font-size:11px;padding:2px 4px;border:1px solid #ccc;border-radius:3px;background:#fff;';
444
+ ['hide', 'disable'].forEach(a => {
445
+ const opt = document.createElement('option');
446
+ opt.value = a;
447
+ opt.textContent = a;
448
+ opt.selected = a === existingSignalAction;
449
+ signalActionSelect.appendChild(opt);
450
+ });
451
+
452
+ // Helper: open IobrokerWebuiBindingsEditor
443
453
  const openVisibilityBindingEditor = () => {
444
454
  const dynEdt = new IobrokerWebuiBindingsEditor(
445
455
  { name: 'visibility', propertyType: 'propertyAndAttribute' },
@@ -457,24 +467,32 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
457
467
  cw.okClicked.on(() => {
458
468
  dlg.close();
459
469
  const signal = dynEdt.objectNames;
470
+ const action = signalActionSelect.value;
460
471
  if (signal) {
461
472
  const bnd = { signal };
462
473
  if (dynEdt.expression) bnd.expression = dynEdt.expression;
463
474
  if (dynEdt.historic) bnd.historic = dynEdt.historic;
464
- if (dynEdt.invert) bnd.invert = true;
465
- // converters: array of {key,value} objects → plain object, or named converter string
475
+ if (dynEdt.invert) bnd.inverted = true;
466
476
  if (dynEdt.converters && dynEdt.converters.length > 0) {
467
477
  bnd.converter = {};
468
478
  for (const c of dynEdt.converters) bnd.converter[c.key] = c.value;
469
479
  } else if (dynEdt.convertersString) {
470
480
  bnd.converter = dynEdt.convertersString;
471
481
  }
472
- const needsJson = bnd.expression || bnd.historic || bnd.converter || bnd.invert;
473
- designItem.setAttribute('data-visibility-signal', needsJson ? JSON.stringify(bnd) : signal);
482
+ if (action === 'disable') {
483
+ designItem.setAttribute('bind-prop:disable', JSON.stringify(bnd));
484
+ designItem.removeAttribute('bind-prop:hidden');
485
+ } else {
486
+ designItem.setAttribute('bind-prop:hidden', JSON.stringify(bnd));
487
+ designItem.removeAttribute('bind-prop:disable');
488
+ }
489
+ designItem.setAttribute('data-visibility-action', action);
474
490
  } else {
475
- designItem.removeAttribute('data-visibility-signal');
491
+ designItem.removeAttribute('bind-prop:hidden');
492
+ designItem.removeAttribute('bind-prop:disable');
493
+ designItem.removeAttribute('data-visibility-action');
476
494
  }
477
- designItem.removeAttribute('bind-prop:hidden');
495
+ designItem.removeAttribute('data-visibility-signal');
478
496
  this._updateVisibilityPanel();
479
497
  });
480
498
  };
@@ -516,8 +534,9 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
516
534
 
517
535
  addItem('edit binding', openVisibilityBindingEditor);
518
536
  addItem('clear binding', () => {
519
- designItem.removeAttribute('data-visibility-signal');
520
537
  designItem.removeAttribute('bind-prop:hidden');
538
+ designItem.removeAttribute('bind-prop:disable');
539
+ designItem.removeAttribute('data-visibility-signal');
521
540
  this._updateVisibilityPanel();
522
541
  }, !existingSignal);
523
542
 
@@ -529,15 +548,17 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
529
548
  bindRow.appendChild(bindBtn);
530
549
 
531
550
  const bindLabel = document.createElement('span');
532
- bindLabel.textContent = 'Visibility';
533
- bindLabel.style.cssText = 'font-size:12px;font-weight:600;flex:1;';
551
+ bindLabel.textContent = 'Signal';
552
+ bindLabel.style.cssText = 'font-size:12px;font-weight:600;';
534
553
  bindRow.appendChild(bindLabel);
535
554
 
555
+ bindRow.appendChild(signalActionSelect);
556
+
536
557
  if (existingSignal) {
537
558
  const statusSpan = document.createElement('span');
538
559
  statusSpan.textContent = existingSignal;
539
560
  statusSpan.title = existingSignal;
540
- statusSpan.style.cssText = 'font-size:10px;color:#aaa;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:right;';
561
+ statusSpan.style.cssText = 'font-size:10px;color:#aaa;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:right;';
541
562
  bindRow.appendChild(statusSpan);
542
563
  }
543
564
 
@@ -566,7 +587,10 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
566
587
  const enableCheck = document.createElement('input');
567
588
  enableCheck.type = 'checkbox';
568
589
  enableCheck.checked = dataConfig.enabled || false;
569
- enableCheck.onchange = () => updateGroupConfig('enabled', enableCheck.checked);
590
+ enableCheck.onchange = () => {
591
+ updateGroupConfig('enabled', enableCheck.checked);
592
+ if (enableCheck.checked) updateGroupConfig('action', actionSelect.value);
593
+ };
570
594
  enableLabel.appendChild(enableCheck);
571
595
  enableLabel.appendChild(document.createTextNode('Enable Group Visibility Control'));
572
596
  enableDiv.appendChild(enableLabel);
@@ -345,6 +345,17 @@ let ScreenViewer = class ScreenViewer extends BaseCustomWebComponentConstructorA
345
345
  fragment.appendChild(n);
346
346
  this._rootShadow.appendChild(fragment);
347
347
  }
348
+ // Custom bind-prop:disable handler — sets pointer-events+opacity when value is truthy
349
+ for (const el of this._rootShadow.querySelectorAll('[bind-prop\\:disable]')) {
350
+ Object.defineProperty(el, 'disable', {
351
+ configurable: true,
352
+ set(val) {
353
+ el.style.pointerEvents = val ? 'none' : '';
354
+ el.style.opacity = val ? '0.5' : '';
355
+ },
356
+ get() { return el.style.pointerEvents === 'none'; }
357
+ });
358
+ }
348
359
  const res = window.appShell.bindingsHelper.applyAllBindings(this._rootShadow, this.relativeSignalsPath, this);
349
360
  if (this._iobBindings)
350
361
  this._iobBindings.push(...res);
@@ -4,7 +4,6 @@ class VisibilityService {
4
4
  static instance = new VisibilityService();
5
5
 
6
6
  #originalDisplayStyles = new WeakMap();
7
- #cleanupFns = new WeakMap();
8
7
 
9
8
  #applyResult(element, result) {
10
9
  if (!result.visible) {
@@ -24,118 +23,27 @@ class VisibilityService {
24
23
  }
25
24
  }
26
25
 
27
- async applyVisibility(element, visibilityConfig, relativeSignalPath, root) {
28
- if (!visibilityConfig || !visibilityConfig.enabled) return;
26
+ // Signal-based visibility is handled by applyAllBindings via bind-prop:hidden.
27
+ // VisibilityService only handles group access control.
28
+ async applyGroupVisibility(element, groups, action) {
29
+ if (!groups || groups.length === 0) return;
29
30
 
30
31
  if (!this.#originalDisplayStyles.has(element)) {
31
32
  this.#originalDisplayStyles.set(element, element.style.display || '');
32
33
  }
33
34
 
34
- // Clean up any previous binding subscription
35
- const prevCleanup = this.#cleanupFns.get(element);
36
- if (prevCleanup) { prevCleanup(); this.#cleanupFns.delete(element); }
35
+ const result = await iobrokerHandler.checkVisibility({
36
+ enabled: true,
37
+ groups,
38
+ action: action || 'hide'
39
+ });
37
40
 
38
- // Step 1: Group access check (if configured)
39
- if (visibilityConfig.groups && visibilityConfig.groups.length > 0) {
40
- const groupResult = await iobrokerHandler.checkVisibility({
41
- enabled: true,
42
- groups: visibilityConfig.groups,
43
- action: visibilityConfig.action || 'hide'
44
- });
45
- if (!groupResult.visible || !groupResult.enabled) {
46
- this.#applyResult(element, groupResult);
47
- return;
48
- }
49
- }
50
-
51
- // Step 2: Signal binding
52
- if (visibilityConfig.objectId) {
53
- const action = visibilityConfig.action || 'hide';
54
-
55
- let signalStr, bindingConfig;
56
- const raw = visibilityConfig.objectId;
57
- if (typeof raw === 'string' && raw.startsWith('{')) {
58
- try {
59
- const parsed = JSON.parse(raw);
60
- signalStr = parsed.signal;
61
- bindingConfig = parsed;
62
- } catch (e) {
63
- signalStr = raw;
64
- bindingConfig = {};
65
- }
66
- } else {
67
- signalStr = raw;
68
- bindingConfig = {};
69
- }
70
-
71
- const onValue = (v) => {
72
- const isActive = v !== null && v !== undefined && v !== false && v !== 0 && v !== '';
73
- this.#applyResult(element, isActive
74
- ? { visible: action !== 'hide', enabled: action !== 'disable' }
75
- : { visible: true, enabled: true }
76
- );
77
- };
78
-
79
- const bindingsHelper = window.appShell?.bindingsHelper;
80
- if (bindingsHelper && root) {
81
- // Use BindingsHelper.applyBinding — supports ?, ??, {combined}, expression, converter, historic
82
- // A Proxy intercepts the final evaluated value written to element.__vis
83
- const proxy = new Proxy(element, {
84
- set(target, prop, value) {
85
- if (prop === '__vis') {
86
- onValue(value);
87
- } else {
88
- target[prop] = value;
89
- }
90
- return true;
91
- },
92
- get(target, prop) { return target[prop]; }
93
- });
94
-
95
- const bindingObj = {
96
- signal: signalStr,
97
- target: 'property'
98
- };
99
- if (bindingConfig.expression) bindingObj.expression = bindingConfig.expression;
100
- if (bindingConfig.converter) bindingObj.converter = bindingConfig.converter;
101
- if (bindingConfig.historic) bindingObj.historic = bindingConfig.historic;
102
- if (bindingConfig.invert) bindingObj.inverted = bindingConfig.invert;
103
-
104
- // Format: [propertyName, bindingOptions]
105
- const binding = ['__vis', bindingObj];
106
- const cleanup = bindingsHelper.applyBinding(proxy, binding, relativeSignalPath || '', root);
107
- if (cleanup) this.#cleanupFns.set(element, cleanup);
108
- } else {
109
- // Fallback: direct subscription (no ?, ?? or {combined} support)
110
- await this.#applyDirect(signalStr, bindingConfig, onValue);
111
- }
112
- } else {
113
- this.#applyResult(element, { visible: true, enabled: true });
41
+ if (!result.visible || !result.enabled) {
42
+ this.#applyResult(element, result);
114
43
  }
115
44
  }
116
45
 
117
- async #applyDirect(signalId, bindingConfig, onValue) {
118
- const evaluate = (raw) => {
119
- let v = raw;
120
- if (bindingConfig.expression) {
121
- try { v = new Function('__0', bindingConfig.expression)(v); } catch (e) { console.warn('[Visibility] Expression error:', e); }
122
- }
123
- if (bindingConfig.converter && typeof bindingConfig.converter === 'object') {
124
- const k = String(v);
125
- if (k in bindingConfig.converter) v = bindingConfig.converter[k];
126
- }
127
- onValue(v);
128
- };
129
-
130
- const state = await iobrokerHandler.connection.getState(signalId);
131
- evaluate(state?.val);
132
- await iobrokerHandler.subscribeState(signalId, (_id, state) => evaluate(state?.val));
133
- }
134
-
135
46
  removeVisibility(element) {
136
- const prevCleanup = this.#cleanupFns.get(element);
137
- if (prevCleanup) { prevCleanup(); this.#cleanupFns.delete(element); }
138
-
139
47
  const originalDisplay = this.#originalDisplayStyles.get(element);
140
48
  if (originalDisplay !== undefined) {
141
49
  element.style.display = originalDisplay;
@@ -147,28 +55,17 @@ class VisibilityService {
147
55
  }
148
56
 
149
57
  async scanAndApply(container = document.body) {
150
- // Get ScreenViewer context from shadow root host — needed for ?, ??, {} pattern resolution
151
- const screenViewer = container.host || null;
152
- const relativeSignalPath = screenViewer?.relativeSignalsPath || '';
153
- const root = screenViewer || null;
154
-
155
- const groupElements = container.querySelectorAll('[data-visibility-enabled="true"]');
156
- const signalElements = container.querySelectorAll('[data-visibility-signal]');
157
- const seen = new Set();
58
+ // Signal-based visibility: handled automatically by applyAllBindings (bind-prop:hidden).
59
+ // Here we only apply group access control.
60
+ const groupElements = container.querySelectorAll('[data-visibility-groups]');
158
61
 
159
- for (const element of [...groupElements, ...signalElements]) {
160
- if (seen.has(element)) continue;
161
- seen.add(element);
62
+ for (const element of groupElements) {
162
63
  try {
163
- const visibilityConfig = {
164
- enabled: true,
165
- objectId: element.getAttribute('data-visibility-signal') || null,
166
- action: element.getAttribute('data-visibility-action') || 'hide',
167
- groups: element.getAttribute('data-visibility-groups')?.split(',').filter(g => g) || []
168
- };
169
- await this.applyVisibility(element, visibilityConfig, relativeSignalPath, root);
64
+ const groups = element.getAttribute('data-visibility-groups')?.split(',').filter(g => g) || [];
65
+ const action = element.getAttribute('data-visibility-action') || 'hide';
66
+ await this.applyGroupVisibility(element, groups, action);
170
67
  } catch (err) {
171
- console.error('[Visibility] Error:', err);
68
+ console.error('[Visibility] Group check error:', err);
172
69
  }
173
70
  }
174
71
  }