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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "mywebui",
4
- "version": "1.37.40",
4
+ "version": "1.37.41",
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.40",
3
+ "version": "1.37.41",
4
4
  "description": "ioBroker mywebui - Custom edited mywebui by gokturk413",
5
5
  "type": "module",
6
6
  "main": "dist/backend/main.js",
@@ -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 #checkAndApply(element, visibilityConfig, currentStateVal) {
28
- try {
29
- const action = visibilityConfig.action || 'hide';
27
+ async applyVisibility(element, visibilityConfig, relativeSignalPath, root) {
28
+ if (!visibilityConfig || !visibilityConfig.enabled) return;
30
29
 
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
- }
43
- }
30
+ if (!this.#originalDisplayStyles.has(element)) {
31
+ this.#originalDisplayStyles.set(element, element.style.display || '');
32
+ }
44
33
 
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":"...","converter":{...},"historic":{...}}
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
- let stateVal = currentStateVal;
61
- if (stateVal === undefined) {
62
- const state = await iobrokerHandler.connection.getState(signalId);
63
- stateVal = state?.val;
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
- // Evaluate expression if present
67
- if (expression) {
68
- try { stateVal = new Function('__0', expression)(stateVal); } catch (e) { console.warn('[Visibility] Expression error:', e); }
69
- }
51
+ // Step 2: Signal binding
52
+ if (visibilityConfig.objectId) {
53
+ const action = visibilityConfig.action || 'hide';
70
54
 
71
- // Apply converter if present (object map like {"true":"1","false":"0"} or inverse)
72
- if (converter && typeof converter === 'object') {
73
- const key = String(stateVal);
74
- if (key in converter) stateVal = converter[key];
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
- // Truthy state value apply action
78
- const isActive = stateVal !== null && stateVal !== undefined && stateVal !== false && stateVal !== 0 && stateVal !== '';
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
- return;
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
- // No restrictions active → show/enable
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 applyVisibility(element, visibilityConfig) {
94
- if (!visibilityConfig || !visibilityConfig.enabled) return;
95
-
96
- if (!this.#originalDisplayStyles.has(element)) {
97
- this.#originalDisplayStyles.set(element, element.style.display || '');
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
- await iobrokerHandler.subscribeState(signalId, (_id, state) => {
108
- this.#checkAndApply(element, visibilityConfig, state?.val);
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
- // Collect all elements needing visibility handling (group control OR signal binding)
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
  }