iobroker.mywebui 1.37.40 → 1.37.41
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
|
@@ -4,6 +4,7 @@ class VisibilityService {
|
|
|
4
4
|
static instance = new VisibilityService();
|
|
5
5
|
|
|
6
6
|
#originalDisplayStyles = new WeakMap();
|
|
7
|
+
#cleanupFns = new WeakMap();
|
|
7
8
|
|
|
8
9
|
#applyResult(element, result) {
|
|
9
10
|
if (!result.visible) {
|
|
@@ -14,7 +15,6 @@ class VisibilityService {
|
|
|
14
15
|
element.style.display = orig;
|
|
15
16
|
element.style.visibility = '';
|
|
16
17
|
}
|
|
17
|
-
|
|
18
18
|
if (!result.enabled) {
|
|
19
19
|
element.style.pointerEvents = 'none';
|
|
20
20
|
element.style.opacity = '0.5';
|
|
@@ -24,93 +24,118 @@ class VisibilityService {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
async
|
|
28
|
-
|
|
29
|
-
const action = visibilityConfig.action || 'hide';
|
|
27
|
+
async applyVisibility(element, visibilityConfig, relativeSignalPath, root) {
|
|
28
|
+
if (!visibilityConfig || !visibilityConfig.enabled) return;
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
}
|
|
43
|
-
}
|
|
30
|
+
if (!this.#originalDisplayStyles.has(element)) {
|
|
31
|
+
this.#originalDisplayStyles.set(element, element.style.display || '');
|
|
32
|
+
}
|
|
44
33
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
let signalId = visibilityConfig.objectId;
|
|
49
|
-
let expression = null;
|
|
50
|
-
let converter = null;
|
|
51
|
-
if (typeof signalId === 'string' && signalId.startsWith('{')) {
|
|
52
|
-
try {
|
|
53
|
-
const parsed = JSON.parse(signalId);
|
|
54
|
-
signalId = parsed.signal || signalId;
|
|
55
|
-
expression = parsed.expression || null;
|
|
56
|
-
converter = parsed.converter || null;
|
|
57
|
-
} catch (e) { /* keep raw */ }
|
|
58
|
-
}
|
|
34
|
+
// Clean up any previous binding subscription
|
|
35
|
+
const prevCleanup = this.#cleanupFns.get(element);
|
|
36
|
+
if (prevCleanup) { prevCleanup(); this.#cleanupFns.delete(element); }
|
|
59
37
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
+
}
|
|
65
50
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
51
|
+
// Step 2: Signal binding
|
|
52
|
+
if (visibilityConfig.objectId) {
|
|
53
|
+
const action = visibilityConfig.action || 'hide';
|
|
70
54
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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 = {};
|
|
75
65
|
}
|
|
66
|
+
} else {
|
|
67
|
+
signalStr = raw;
|
|
68
|
+
bindingConfig = {};
|
|
69
|
+
}
|
|
76
70
|
|
|
77
|
-
|
|
78
|
-
const isActive =
|
|
71
|
+
const onValue = (v) => {
|
|
72
|
+
const isActive = v !== null && v !== undefined && v !== false && v !== 0 && v !== '';
|
|
79
73
|
this.#applyResult(element, isActive
|
|
80
74
|
? { visible: action !== 'hide', enabled: action !== 'disable' }
|
|
81
75
|
: { visible: true, enabled: true }
|
|
82
76
|
);
|
|
83
|
-
|
|
84
|
-
|
|
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
|
+
});
|
|
85
94
|
|
|
86
|
-
|
|
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 {
|
|
87
113
|
this.#applyResult(element, { visible: true, enabled: true });
|
|
88
|
-
} catch (err) {
|
|
89
|
-
console.error('[Visibility] Check failed:', err);
|
|
90
114
|
}
|
|
91
115
|
}
|
|
92
116
|
|
|
93
|
-
async
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
await this.#checkAndApply(element, visibilityConfig, undefined);
|
|
101
|
-
|
|
102
|
-
if (visibilityConfig.objectId) {
|
|
103
|
-
let signalId = visibilityConfig.objectId;
|
|
104
|
-
if (typeof signalId === 'string' && signalId.startsWith('{')) {
|
|
105
|
-
try { signalId = JSON.parse(signalId).signal || signalId; } catch (e) { /* keep raw */ }
|
|
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); }
|
|
106
122
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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));
|
|
111
133
|
}
|
|
112
134
|
|
|
113
135
|
removeVisibility(element) {
|
|
136
|
+
const prevCleanup = this.#cleanupFns.get(element);
|
|
137
|
+
if (prevCleanup) { prevCleanup(); this.#cleanupFns.delete(element); }
|
|
138
|
+
|
|
114
139
|
const originalDisplay = this.#originalDisplayStyles.get(element);
|
|
115
140
|
if (originalDisplay !== undefined) {
|
|
116
141
|
element.style.display = originalDisplay;
|
|
@@ -122,7 +147,11 @@ class VisibilityService {
|
|
|
122
147
|
}
|
|
123
148
|
|
|
124
149
|
async scanAndApply(container = document.body) {
|
|
125
|
-
//
|
|
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
|
+
|
|
126
155
|
const groupElements = container.querySelectorAll('[data-visibility-enabled="true"]');
|
|
127
156
|
const signalElements = container.querySelectorAll('[data-visibility-signal]');
|
|
128
157
|
const seen = new Set();
|
|
@@ -137,7 +166,7 @@ class VisibilityService {
|
|
|
137
166
|
action: element.getAttribute('data-visibility-action') || 'hide',
|
|
138
167
|
groups: element.getAttribute('data-visibility-groups')?.split(',').filter(g => g) || []
|
|
139
168
|
};
|
|
140
|
-
await this.applyVisibility(element, visibilityConfig);
|
|
169
|
+
await this.applyVisibility(element, visibilityConfig, relativeSignalPath, root);
|
|
141
170
|
} catch (err) {
|
|
142
171
|
console.error('[Visibility] Error:', err);
|
|
143
172
|
}
|