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
package/package.json
CHANGED
|
@@ -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
|
|
408
|
-
const
|
|
409
|
-
|
|
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 (
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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
|
-
//
|
|
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.
|
|
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
|
-
|
|
473
|
-
|
|
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('
|
|
491
|
+
designItem.removeAttribute('bind-prop:hidden');
|
|
492
|
+
designItem.removeAttribute('bind-prop:disable');
|
|
493
|
+
designItem.removeAttribute('data-visibility-action');
|
|
476
494
|
}
|
|
477
|
-
designItem.removeAttribute('
|
|
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 = '
|
|
533
|
-
bindLabel.style.cssText = 'font-size:12px;font-weight:600;
|
|
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;
|
|
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 = () =>
|
|
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
|
-
|
|
28
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
const result = await iobrokerHandler.checkVisibility({
|
|
36
|
+
enabled: true,
|
|
37
|
+
groups,
|
|
38
|
+
action: action || 'hide'
|
|
39
|
+
});
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
//
|
|
151
|
-
|
|
152
|
-
const
|
|
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
|
|
160
|
-
if (seen.has(element)) continue;
|
|
161
|
-
seen.add(element);
|
|
62
|
+
for (const element of groupElements) {
|
|
162
63
|
try {
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
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]
|
|
68
|
+
console.error('[Visibility] Group check error:', err);
|
|
172
69
|
}
|
|
173
70
|
}
|
|
174
71
|
}
|