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
package/package.json
CHANGED
|
@@ -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
|
|
407
|
-
const
|
|
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 (
|
|
410
|
-
|
|
411
|
-
|
|
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
|
-
//
|
|
428
|
-
const bindBtn = document.createElement('
|
|
429
|
-
bindBtn.
|
|
430
|
-
bindBtn.
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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
|
|
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
|
-
}, !
|
|
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 (
|
|
486
|
-
const sig = existingBinding.signal || (existingBinding.expression ? 'expr' : '?');
|
|
523
|
+
if (existingSignal) {
|
|
487
524
|
const statusSpan = document.createElement('span');
|
|
488
|
-
statusSpan.textContent =
|
|
489
|
-
statusSpan.title =
|
|
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
|
-
|
|
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
|
|
11
|
-
|
|
12
|
-
if
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
62
|
-
|
|
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
|
|
134
|
+
console.error('[Visibility] Error:', err);
|
|
75
135
|
}
|
|
76
136
|
}
|
|
77
137
|
}
|