iobroker.mywebui 1.37.33 → 1.37.35
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
|
@@ -403,57 +403,176 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
|
|
|
403
403
|
const designItem = selectedItems[0];
|
|
404
404
|
const element = designItem.element;
|
|
405
405
|
|
|
406
|
-
// Read existing
|
|
407
|
-
const
|
|
406
|
+
// Read existing binding from bind-prop:hidden attribute
|
|
407
|
+
const bindAttr = element.getAttribute('bind-prop:hidden');
|
|
408
408
|
let existingBinding = null;
|
|
409
|
-
if (
|
|
410
|
-
const parsed = bindingsHelper.parseBinding(element, 'bind-
|
|
409
|
+
if (bindAttr != null) {
|
|
410
|
+
const parsed = bindingsHelper.parseBinding(element, 'bind-prop:hidden', bindAttr, 'property', 'bind-prop:');
|
|
411
411
|
if (parsed) existingBinding = parsed[1];
|
|
412
412
|
}
|
|
413
413
|
|
|
414
|
+
// Read group access control config from data attributes
|
|
415
|
+
const dataConfig = {
|
|
416
|
+
enabled: element.getAttribute('data-visibility-enabled') === 'true',
|
|
417
|
+
groups: element.getAttribute('data-visibility-groups')?.split(',').filter(g => g) || [],
|
|
418
|
+
action: element.getAttribute('data-visibility-action') || 'hide'
|
|
419
|
+
};
|
|
420
|
+
|
|
414
421
|
content.innerHTML = '';
|
|
415
422
|
|
|
416
|
-
//
|
|
417
|
-
const
|
|
418
|
-
|
|
423
|
+
// --- BINDING ROW ---
|
|
424
|
+
const bindRow = document.createElement('div');
|
|
425
|
+
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
|
+
|
|
427
|
+
// □ button on the LEFT (like Properties panel style)
|
|
428
|
+
const bindBtn = document.createElement('button');
|
|
429
|
+
bindBtn.textContent = '□';
|
|
430
|
+
bindBtn.title = 'Right-click for binding options';
|
|
431
|
+
bindBtn.style.cssText = existingBinding
|
|
432
|
+
? 'width:16px;height:16px;padding:0;font-size:11px;line-height:1;border:1px solid #888;background:#ffd700;cursor:pointer;flex-shrink:0;'
|
|
433
|
+
: 'width:16px;height:16px;padding:0;font-size:11px;line-height:1;border:1px solid #888;background:#f0f0f0;cursor:pointer;flex-shrink:0;';
|
|
434
|
+
|
|
435
|
+
// Left-click: open binding editor
|
|
436
|
+
bindBtn.onclick = () => {
|
|
437
|
+
const property = { name: 'hidden', propertyType: 'propertyAndAttribute' };
|
|
438
|
+
serviceContainer.config.openBindingsEditor(property, [designItem], existingBinding, 'property');
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
// Right-click: context menu (like Properties panel)
|
|
442
|
+
bindBtn.oncontextmenu = (e) => {
|
|
443
|
+
e.preventDefault();
|
|
444
|
+
const existing = document.getElementById('__vis-ctx-menu');
|
|
445
|
+
if (existing) existing.remove();
|
|
446
|
+
|
|
447
|
+
const menu = document.createElement('div');
|
|
448
|
+
menu.id = '__vis-ctx-menu';
|
|
449
|
+
menu.style.cssText = `position:fixed;left:${e.clientX}px;top:${e.clientY}px;background:#2d2d2d;color:#ddd;
|
|
450
|
+
font-size:12px;border:1px solid #555;border-radius:3px;z-index:99999;min-width:130px;box-shadow:2px 2px 6px rgba(0,0,0,0.5);`;
|
|
451
|
+
|
|
452
|
+
const addItem = (text, cb, disabled = false) => {
|
|
453
|
+
const item = document.createElement('div');
|
|
454
|
+
item.textContent = text;
|
|
455
|
+
item.style.cssText = `padding:6px 12px;cursor:${disabled ? 'default' : 'pointer'};color:${disabled ? '#666' : '#ddd'};`;
|
|
456
|
+
if (!disabled) {
|
|
457
|
+
item.onmouseenter = () => item.style.background = '#3e6db4';
|
|
458
|
+
item.onmouseleave = () => item.style.background = '';
|
|
459
|
+
item.onclick = () => { menu.remove(); cb(); };
|
|
460
|
+
}
|
|
461
|
+
menu.appendChild(item);
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
addItem('edit binding', () => {
|
|
465
|
+
const property = { name: 'hidden', propertyType: 'propertyAndAttribute' };
|
|
466
|
+
serviceContainer.config.openBindingsEditor(property, [designItem], existingBinding, 'property');
|
|
467
|
+
});
|
|
468
|
+
addItem('clear binding', () => {
|
|
469
|
+
designItem.removeAttribute('bind-prop:hidden');
|
|
470
|
+
this._updateVisibilityPanel();
|
|
471
|
+
}, !existingBinding);
|
|
472
|
+
|
|
473
|
+
document.body.appendChild(menu);
|
|
474
|
+
const close = (ev) => { if (!menu.contains(ev.target)) { menu.remove(); document.removeEventListener('mousedown', close); } };
|
|
475
|
+
setTimeout(() => document.addEventListener('mousedown', close), 0);
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
bindRow.appendChild(bindBtn);
|
|
419
479
|
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
480
|
+
const bindLabel = document.createElement('span');
|
|
481
|
+
bindLabel.textContent = 'Visibility';
|
|
482
|
+
bindLabel.style.cssText = 'font-size:12px;font-weight:600;flex:1;';
|
|
483
|
+
bindRow.appendChild(bindLabel);
|
|
424
484
|
|
|
425
485
|
if (existingBinding) {
|
|
426
|
-
const statusSpan = document.createElement('span');
|
|
427
486
|
const sig = existingBinding.signal || (existingBinding.expression ? 'expr' : '?');
|
|
487
|
+
const statusSpan = document.createElement('span');
|
|
428
488
|
statusSpan.textContent = sig;
|
|
429
489
|
statusSpan.title = JSON.stringify(existingBinding);
|
|
430
|
-
statusSpan.style.cssText = 'font-size:10px;color:#
|
|
431
|
-
|
|
490
|
+
statusSpan.style.cssText = 'font-size:10px;color:#aaa;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:right;';
|
|
491
|
+
bindRow.appendChild(statusSpan);
|
|
432
492
|
}
|
|
433
493
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
494
|
+
content.appendChild(bindRow);
|
|
495
|
+
|
|
496
|
+
// --- GROUP ACCESS CONTROL ---
|
|
497
|
+
const updateGroupConfig = (key, value) => {
|
|
498
|
+
dataConfig[key] = value;
|
|
499
|
+
if (key === 'enabled') {
|
|
500
|
+
if (value) designItem.setAttribute('data-visibility-enabled', 'true');
|
|
501
|
+
else designItem.removeAttribute('data-visibility-enabled');
|
|
502
|
+
} else if (key === 'groups') {
|
|
503
|
+
if (value && value.length > 0) designItem.setAttribute('data-visibility-groups', value.join(','));
|
|
504
|
+
else designItem.removeAttribute('data-visibility-groups');
|
|
505
|
+
} else if (key === 'action') {
|
|
506
|
+
if (value) designItem.setAttribute('data-visibility-action', value);
|
|
507
|
+
else designItem.removeAttribute('data-visibility-action');
|
|
508
|
+
}
|
|
441
509
|
};
|
|
442
|
-
row.appendChild(bindBtn);
|
|
443
510
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
511
|
+
// Enable checkbox
|
|
512
|
+
const enableDiv = document.createElement('div');
|
|
513
|
+
enableDiv.style.cssText = 'margin-bottom:10px;';
|
|
514
|
+
const enableLabel = document.createElement('label');
|
|
515
|
+
enableLabel.style.cssText = 'display:flex;align-items:center;gap:5px;cursor:pointer;font-size:12px;';
|
|
516
|
+
const enableCheck = document.createElement('input');
|
|
517
|
+
enableCheck.type = 'checkbox';
|
|
518
|
+
enableCheck.checked = dataConfig.enabled || false;
|
|
519
|
+
enableCheck.onchange = () => updateGroupConfig('enabled', enableCheck.checked);
|
|
520
|
+
enableLabel.appendChild(enableCheck);
|
|
521
|
+
enableLabel.appendChild(document.createTextNode('Enable Group Visibility Control'));
|
|
522
|
+
enableDiv.appendChild(enableLabel);
|
|
523
|
+
content.appendChild(enableDiv);
|
|
524
|
+
|
|
525
|
+
// Groups
|
|
526
|
+
const groupsDiv = document.createElement('div');
|
|
527
|
+
groupsDiv.style.cssText = 'margin-bottom:10px;';
|
|
528
|
+
const groupsLabel = document.createElement('label');
|
|
529
|
+
groupsLabel.style.cssText = 'font-size:11px;font-weight:600;display:block;margin-bottom:3px;color:#555;';
|
|
530
|
+
groupsLabel.textContent = 'Only for groups:';
|
|
531
|
+
groupsDiv.appendChild(groupsLabel);
|
|
532
|
+
|
|
533
|
+
const groupsList = document.createElement('div');
|
|
534
|
+
groupsList.style.cssText = 'max-height:100px;overflow-y:auto;border:1px solid #ccc;padding:6px;background:#fff;border-radius:3px;';
|
|
535
|
+
|
|
536
|
+
const userGroups = await iobrokerHandler.getUserGroups();
|
|
537
|
+
const selectedGroups = dataConfig.groups || [];
|
|
538
|
+
|
|
539
|
+
userGroups.forEach(group => {
|
|
540
|
+
const groupLabel = document.createElement('label');
|
|
541
|
+
groupLabel.style.cssText = 'display:flex;align-items:center;gap:5px;font-size:11px;padding:3px;cursor:pointer;';
|
|
542
|
+
const groupCheck = document.createElement('input');
|
|
543
|
+
groupCheck.type = 'checkbox';
|
|
544
|
+
groupCheck.checked = selectedGroups.includes(group.id);
|
|
545
|
+
groupCheck.onchange = () => {
|
|
546
|
+
let groups = [...dataConfig.groups];
|
|
547
|
+
if (groupCheck.checked) {
|
|
548
|
+
if (!groups.includes(group.id)) groups.push(group.id);
|
|
549
|
+
} else {
|
|
550
|
+
groups = groups.filter(g => g !== group.id);
|
|
551
|
+
}
|
|
552
|
+
updateGroupConfig('groups', groups);
|
|
452
553
|
};
|
|
453
|
-
|
|
454
|
-
|
|
554
|
+
groupLabel.appendChild(groupCheck);
|
|
555
|
+
groupLabel.appendChild(document.createTextNode(group.name));
|
|
556
|
+
groupsList.appendChild(groupLabel);
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
groupsDiv.appendChild(groupsList);
|
|
560
|
+
content.appendChild(groupsDiv);
|
|
455
561
|
|
|
456
|
-
|
|
562
|
+
// Action
|
|
563
|
+
const actionDiv = document.createElement('div');
|
|
564
|
+
actionDiv.style.cssText = 'margin-bottom:5px;';
|
|
565
|
+
const actionLabel = document.createElement('label');
|
|
566
|
+
actionLabel.style.cssText = 'font-size:11px;font-weight:600;display:block;margin-bottom:3px;color:#555;';
|
|
567
|
+
actionLabel.textContent = 'If user not in group:';
|
|
568
|
+
const actionSelect = document.createElement('select');
|
|
569
|
+
actionSelect.style.cssText = 'width:100%;padding:6px;font-size:12px;border:1px solid #ccc;border-radius:3px;';
|
|
570
|
+
actionSelect.innerHTML = '<option value="hide">hide</option><option value="disable">disable</option>';
|
|
571
|
+
actionSelect.value = dataConfig.action || 'hide';
|
|
572
|
+
actionSelect.onchange = () => updateGroupConfig('action', actionSelect.value);
|
|
573
|
+
actionDiv.appendChild(actionLabel);
|
|
574
|
+
actionDiv.appendChild(actionSelect);
|
|
575
|
+
content.appendChild(actionDiv);
|
|
457
576
|
}
|
|
458
577
|
/* Move to a Dock Spawn Helper */
|
|
459
578
|
activateDockById(name) {
|
|
@@ -1,271 +1,65 @@
|
|
|
1
1
|
import { iobrokerHandler } from '../common/IobrokerHandler.js';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Visibility Service - Handles element visibility based on user groups and conditions
|
|
5
|
-
* Provides protection against browser manipulation
|
|
6
|
-
*/
|
|
7
3
|
class VisibilityService {
|
|
8
4
|
static instance = new VisibilityService();
|
|
9
|
-
|
|
10
|
-
#intervalChecks = new Map();
|
|
11
|
-
#originalDisplayStyles = new WeakMap();
|
|
12
|
-
#configs = new WeakMap();
|
|
13
|
-
#tampering = false;
|
|
14
|
-
|
|
15
|
-
constructor() {
|
|
16
|
-
this.#setupAntiTampering();
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Setup anti-tampering detection
|
|
21
|
-
*/
|
|
22
|
-
#setupAntiTampering() {
|
|
23
|
-
// Detect DevTools opening
|
|
24
|
-
const detectDevTools = () => {
|
|
25
|
-
const threshold = 160;
|
|
26
|
-
const widthThreshold = window.outerWidth - window.innerWidth > threshold;
|
|
27
|
-
const heightThreshold = window.outerHeight - window.innerHeight > threshold;
|
|
28
|
-
|
|
29
|
-
if (widthThreshold || heightThreshold) {
|
|
30
|
-
if (!this.#tampering) {
|
|
31
|
-
this.#tampering = true;
|
|
32
|
-
console.warn('⚠️ [Visibility] Developer tools detected - visibility checks will be re-validated');
|
|
33
|
-
this.#revalidateAll();
|
|
34
|
-
}
|
|
35
|
-
} else {
|
|
36
|
-
this.#tampering = false;
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
setInterval(detectDevTools, 1000);
|
|
41
|
-
|
|
42
|
-
// Detect element.style modifications
|
|
43
|
-
this.#setupMutationObserver();
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Setup mutation observer to detect tampering
|
|
48
|
-
*/
|
|
49
|
-
#setupMutationObserver() {
|
|
50
|
-
const observer = new MutationObserver((mutations) => {
|
|
51
|
-
for (const mutation of mutations) {
|
|
52
|
-
const element = mutation.target;
|
|
53
|
-
|
|
54
|
-
if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
|
|
55
|
-
if (element.hasAttribute('data-visibility-controlled')) {
|
|
56
|
-
console.warn('🚨 [Visibility] Style tampered:', element);
|
|
57
|
-
this.#revalidateElement(element);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
5
|
|
|
61
|
-
|
|
62
|
-
if (mutation.type === 'attributes' && mutation.attributeName === 'data-visibility-controlled') {
|
|
63
|
-
if (!element.hasAttribute('data-visibility-controlled') && this.#configs.has(element)) {
|
|
64
|
-
console.warn('🚨 [Visibility] Controlled attribute removed — restoring');
|
|
65
|
-
element.setAttribute('data-visibility-controlled', 'true');
|
|
66
|
-
this.#revalidateElement(element);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
observer.observe(document.body, {
|
|
73
|
-
attributes: true,
|
|
74
|
-
subtree: true,
|
|
75
|
-
attributeFilter: ['style', 'data-visibility-controlled']
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Apply visibility rules to an element
|
|
81
|
-
* @param {HTMLElement} element
|
|
82
|
-
* @param {object} visibilityConfig
|
|
83
|
-
*/
|
|
84
|
-
async applyVisibility(element, visibilityConfig) {
|
|
85
|
-
if (!visibilityConfig || !visibilityConfig.enabled) {
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
console.log('🔍 [Visibility] Applying visibility rules to:', element.tagName, visibilityConfig);
|
|
90
|
-
|
|
91
|
-
// Store original display style
|
|
92
|
-
if (!this.#originalDisplayStyles.has(element)) {
|
|
93
|
-
this.#originalDisplayStyles.set(element, element.style.display || '');
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Config-i memory-də saxla — DOM-da sensitiv məlumat saxlama
|
|
97
|
-
this.#configs.set(element, visibilityConfig);
|
|
98
|
-
|
|
99
|
-
// Yalnız "izlənilir" işarəsi
|
|
100
|
-
element.setAttribute('data-visibility-controlled', 'true');
|
|
6
|
+
#originalDisplayStyles = new WeakMap();
|
|
101
7
|
|
|
102
|
-
// Initial check
|
|
103
|
-
await this.#checkAndApply(element, visibilityConfig);
|
|
104
|
-
|
|
105
|
-
// Subscribe to datapoint changes if objectId specified
|
|
106
|
-
if (visibilityConfig.objectId) {
|
|
107
|
-
console.log('📡 [Visibility] Subscribing to state changes:', visibilityConfig.objectId);
|
|
108
|
-
await iobrokerHandler.subscribeState(visibilityConfig.objectId, (id, state) => {
|
|
109
|
-
console.log('🔔 [Visibility] State changed:', id, 'new value:', state?.val);
|
|
110
|
-
this.#checkAndApply(element, visibilityConfig);
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Periodic re-validation (protection against tampering)
|
|
115
|
-
const intervalId = setInterval(() => {
|
|
116
|
-
this.#checkAndApply(element, visibilityConfig);
|
|
117
|
-
}, 30000); // Check every 30 seconds (subscription handles real-time updates)
|
|
118
|
-
|
|
119
|
-
this.#intervalChecks.set(element, intervalId);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Check visibility and apply
|
|
124
|
-
*/
|
|
125
8
|
async #checkAndApply(element, visibilityConfig) {
|
|
126
9
|
try {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (visibilityConfig.groups && visibilityConfig.groups.length > 0) {
|
|
130
|
-
// Qrup yoxlaması → backend-ə sendTo: qrupları backend DB-dən oxuyur, frontend-ə inanmır
|
|
131
|
-
const username = await iobrokerHandler.connection.getCurrentUser();
|
|
132
|
-
if (username) {
|
|
133
|
-
result = await iobrokerHandler.connection.sendTo(
|
|
134
|
-
'mywebui.0',
|
|
135
|
-
'checkVisibility',
|
|
136
|
-
{ username, allowedGroups: visibilityConfig.groups }
|
|
137
|
-
);
|
|
138
|
-
result = result ?? { visible: false };
|
|
139
|
-
} else {
|
|
140
|
-
// User alınmadı → fallback
|
|
141
|
-
result = await iobrokerHandler.checkVisibility(visibilityConfig);
|
|
142
|
-
}
|
|
143
|
-
} else {
|
|
144
|
-
// Qrup yoxlaması yoxdur → datapoint condition yoxlaması (frontend)
|
|
145
|
-
result = await iobrokerHandler.checkVisibility(visibilityConfig);
|
|
146
|
-
}
|
|
10
|
+
const result = await iobrokerHandler.checkVisibility(visibilityConfig);
|
|
147
11
|
|
|
148
|
-
console.log('✅ [Visibility] Check result for', element.tagName, ':', { visible: result.visible, enabled: result.enabled });
|
|
149
|
-
|
|
150
|
-
// Apply visibility
|
|
151
12
|
if (!result.visible) {
|
|
152
13
|
element.style.display = 'none';
|
|
153
14
|
element.style.visibility = 'hidden';
|
|
154
|
-
element.setAttribute('aria-hidden', 'true');
|
|
155
15
|
} else {
|
|
156
|
-
const
|
|
157
|
-
element.style.display =
|
|
158
|
-
element.style.visibility = '
|
|
159
|
-
element.removeAttribute('aria-hidden');
|
|
16
|
+
const orig = this.#originalDisplayStyles.get(element) || '';
|
|
17
|
+
element.style.display = orig;
|
|
18
|
+
element.style.visibility = '';
|
|
160
19
|
}
|
|
161
|
-
|
|
162
|
-
// Apply enabled/disabled
|
|
20
|
+
|
|
163
21
|
if (!result.enabled) {
|
|
164
22
|
element.style.pointerEvents = 'none';
|
|
165
23
|
element.style.opacity = '0.5';
|
|
166
|
-
element.setAttribute('disabled', 'true');
|
|
167
|
-
if (element.setAttribute) {
|
|
168
|
-
element.setAttribute('data-disabled-by-visibility', 'true');
|
|
169
|
-
}
|
|
170
24
|
} else {
|
|
171
25
|
element.style.pointerEvents = '';
|
|
172
26
|
element.style.opacity = '';
|
|
173
|
-
element.removeAttribute('disabled');
|
|
174
|
-
element.removeAttribute('data-disabled-by-visibility');
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Store encrypted hash to detect tampering
|
|
178
|
-
this.#storeElementHash(element, result);
|
|
179
|
-
}
|
|
180
|
-
catch (err) {
|
|
181
|
-
console.error('❌ [Visibility] Failed to check visibility:', err);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Store element hash for tampering detection
|
|
187
|
-
*/
|
|
188
|
-
#storeElementHash(element, result) {
|
|
189
|
-
const hash = btoa(JSON.stringify({
|
|
190
|
-
visible: result.visible,
|
|
191
|
-
enabled: result.enabled,
|
|
192
|
-
timestamp: Date.now()
|
|
193
|
-
}));
|
|
194
|
-
element.setAttribute('data-visibility-hash', hash);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Validate element hasn't been tampered
|
|
199
|
-
*/
|
|
200
|
-
async #revalidateElement(element) {
|
|
201
|
-
try {
|
|
202
|
-
// DOM-dan deyil, WeakMap-dan oxu — attribute dəyişdirilsə belə orijinal config qalır
|
|
203
|
-
const config = this.#configs.get(element);
|
|
204
|
-
if (config?.enabled) {
|
|
205
|
-
await this.#checkAndApply(element, config);
|
|
206
27
|
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
console.error('[Visibility] Revalidation error:', err);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
console.error('[Visibility] Check failed:', err);
|
|
210
30
|
}
|
|
211
31
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
controlled.forEach(element => {
|
|
219
|
-
this.#revalidateElement(element);
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Remove visibility control from element
|
|
225
|
-
*/
|
|
226
|
-
removeVisibility(element) {
|
|
227
|
-
const intervalId = this.#intervalChecks.get(element);
|
|
228
|
-
if (intervalId) {
|
|
229
|
-
clearInterval(intervalId);
|
|
230
|
-
this.#intervalChecks.delete(element);
|
|
32
|
+
|
|
33
|
+
async applyVisibility(element, visibilityConfig) {
|
|
34
|
+
if (!visibilityConfig || !visibilityConfig.enabled) return;
|
|
35
|
+
|
|
36
|
+
if (!this.#originalDisplayStyles.has(element)) {
|
|
37
|
+
this.#originalDisplayStyles.set(element, element.style.display || '');
|
|
231
38
|
}
|
|
232
|
-
|
|
233
|
-
this.#
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if (originalDisplay !== undefined) {
|
|
240
|
-
element.style.display = originalDisplay;
|
|
39
|
+
|
|
40
|
+
await this.#checkAndApply(element, visibilityConfig);
|
|
41
|
+
|
|
42
|
+
if (visibilityConfig.objectId) {
|
|
43
|
+
await iobrokerHandler.subscribeState(visibilityConfig.objectId, () => {
|
|
44
|
+
this.#checkAndApply(element, visibilityConfig);
|
|
45
|
+
});
|
|
241
46
|
}
|
|
242
|
-
element.style.visibility = '';
|
|
243
|
-
element.style.pointerEvents = '';
|
|
244
|
-
element.style.opacity = '';
|
|
245
47
|
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Scan and apply visibility to all elements with visibility config
|
|
249
|
-
*/
|
|
48
|
+
|
|
250
49
|
async scanAndApply(container = document.body) {
|
|
251
50
|
const elements = container.querySelectorAll('[data-visibility-enabled="true"]');
|
|
252
|
-
console.log(`🔍 [Visibility] Scanning ${elements.length} elements with visibility config`);
|
|
253
|
-
|
|
254
51
|
for (const element of elements) {
|
|
255
52
|
try {
|
|
256
|
-
// Read config from separate attributes
|
|
257
53
|
const visibilityConfig = {
|
|
258
|
-
enabled:
|
|
54
|
+
enabled: true,
|
|
259
55
|
objectId: element.getAttribute('data-visibility-signal'),
|
|
260
56
|
condition: element.getAttribute('data-visibility-condition') || '==',
|
|
261
57
|
conditionValue: element.getAttribute('data-visibility-value') || '',
|
|
262
58
|
action: element.getAttribute('data-visibility-action') || 'hide',
|
|
263
59
|
groups: element.getAttribute('data-visibility-groups')?.split(',').filter(g => g) || []
|
|
264
60
|
};
|
|
265
|
-
|
|
266
61
|
await this.applyVisibility(element, visibilityConfig);
|
|
267
|
-
}
|
|
268
|
-
catch (err) {
|
|
62
|
+
} catch (err) {
|
|
269
63
|
console.error('[Visibility] Error parsing visibility config:', err);
|
|
270
64
|
}
|
|
271
65
|
}
|