iobroker.mywebui 1.42.39 → 1.42.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 +15 -1
- package/package.json +1 -1
- package/www/dist/frontend/config/ConfigureWebcomponentDesigner.js +2 -0
- package/www/dist/frontend/config/IobrokerWebuiDynamicPropsEditor.js +159 -0
- package/www/dist/frontend/runtime/CustomControls.js +10 -0
- package/www/dist/frontend/runtime/DynamicElementProperties.js +97 -0
- package/www/dist/frontend/runtime/ScreenViewer.js +3 -0
- package/www/dist/frontend/services/DynamicPropertiesHelper.js +75 -0
- package/www/dist/frontend/services/IobrokerWebuiCustomControlEventsService.js +8 -3
- package/www/dist/frontend/services/IobrokerWebuiLitPropertiesService.js +53 -0
- package/www/dist/frontend/services/IobrokerWebuiPropertiesService.js +7 -1
package/io-package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "mywebui",
|
|
4
|
-
"version": "1.42.
|
|
4
|
+
"version": "1.42.41",
|
|
5
5
|
"titleLang": {
|
|
6
6
|
"en": "mywebui",
|
|
7
7
|
"de": "mywebui",
|
|
@@ -29,6 +29,20 @@
|
|
|
29
29
|
"zh-cn": "使用万维网传送器的高锰用户接口"
|
|
30
30
|
},
|
|
31
31
|
"news": {
|
|
32
|
+
"1.42.41": {
|
|
33
|
+
"en": "improvement: dynamic properties now work on Lit/package widgets at runtime too (accessors + -changed events applied by ScreenViewer); events tab lists -changed events of dynamic properties; added webui-demo-widget sample package",
|
|
34
|
+
"az": "təkmilləşdirmə: dinamik property-lər artıq runtime-da Lit/package widget-lərdə də işləyir (accessor-lar + -changed event-ləri ScreenViewer tərəfindən tətbiq olunur); events tabı dinamik property-lərin -changed event-lərini göstərir; webui-demo-widget nümunə paketi əlavə olundu",
|
|
35
|
+
"tr": "iyileştirme: dinamik property'ler artık runtime'da Lit/package widget'larda da çalışıyor (accessor'lar + -changed event'leri ScreenViewer tarafından uygulanıyor); events sekmesi dinamik property'lerin -changed event'lerini listeliyor; webui-demo-widget örnek paketi eklendi",
|
|
36
|
+
"ru": "улучшение: динамические свойства теперь работают и на Lit/package виджетах во время выполнения (аксессоры + события -changed применяются ScreenViewer); вкладка events показывает события -changed динамических свойств; добавлен пример пакета webui-demo-widget",
|
|
37
|
+
"de": "Verbesserung: dynamische Properties funktionieren zur Laufzeit jetzt auch bei Lit/Package-Widgets (Accessoren + -changed-Events durch ScreenViewer); Events-Tab listet -changed-Events dynamischer Properties; webui-demo-widget Beispielpaket hinzugefügt"
|
|
38
|
+
},
|
|
39
|
+
"1.42.40": {
|
|
40
|
+
"en": "feature: per-instance dynamic properties for custom controls (add/edit name, type, default via properties tab) and extended Lit widget property support (Array/Object/converter types, webuiProperties metadata for signal/color/screen/enum editors)",
|
|
41
|
+
"az": "yenilik: custom control-lar üçün instance-əsaslı dinamik property-lər (properties tabından ad, tip, default əlavə/redaktə) və Lit widget-lər üçün genişləndirilmiş property dəstəyi (Array/Object/converter tipləri, signal/color/screen/enum editorları üçün webuiProperties metadata)",
|
|
42
|
+
"tr": "yenilik: custom control'ler için instance bazlı dinamik property'ler (properties sekmesinden ad, tip, varsayılan ekleme/düzenleme) ve Lit widget'lar için genişletilmiş property desteği (Array/Object/converter tipleri, signal/color/screen/enum editörleri için webuiProperties metadata)",
|
|
43
|
+
"ru": "новое: динамические свойства экземпляров для custom control (добавление/редактирование имени, типа, значения по умолчанию во вкладке properties) и расширенная поддержка свойств Lit-виджетов (типы Array/Object/converter, метаданные webuiProperties для редакторов signal/color/screen/enum)",
|
|
44
|
+
"de": "Neu: instanzbasierte dynamische Properties für Custom Controls (Name, Typ, Standardwert über den Properties-Tab hinzufügen/bearbeiten) und erweiterte Lit-Widget-Property-Unterstützung (Array/Object/Converter-Typen, webuiProperties-Metadaten für Signal/Color/Screen/Enum-Editoren)"
|
|
45
|
+
},
|
|
32
46
|
"1.37.30": {
|
|
33
47
|
"en": "cleanup: remove unused grafanaLangSync onMessage handler and helper functions from main.js",
|
|
34
48
|
"az": "təmizlik: istifadəsiz grafanaLangSync onMessage handler və köməkçi funksiyalar main.js-dən silindi",
|
package/package.json
CHANGED
|
@@ -14,6 +14,7 @@ import { IobrokerWebuiConfigButtonProvider } from "../services/IobrokerWebuiConf
|
|
|
14
14
|
import { IobrokerWebuiCustomElementContextMenu } from "../services/IobrokerWebuiCustomElementContextMenu.js";
|
|
15
15
|
import { IobrokerWebuiRefactorService } from "../services/IobrokerWebuiRefactorService.js";
|
|
16
16
|
import { IobrokerWebuiSpecialPropertiesService } from "../services/IobrokerWebuiSpecialPropertiesService.js";
|
|
17
|
+
import { IobrokerWebuiLitPropertiesService } from "../services/IobrokerWebuiLitPropertiesService.js";
|
|
17
18
|
// import { IobrokerWebuiVisibilityPropertiesService } from "../services/IobrokerWebuiVisibilityPropertiesService.js";
|
|
18
19
|
import { iobrokerHandler } from "../common/IobrokerHandler.js";
|
|
19
20
|
import { ExpandCollapseContextMenu } from "@gokturk413/web-component-designer-widgets-wunderbaum";
|
|
@@ -49,6 +50,7 @@ export function configureDesigner(bindingsHelper) {
|
|
|
49
50
|
serviceContainer.register('elementsService', new JsonFileElementsService('native', './node_modules/@gokturk413/web-component-designer/config/elements-native.json'));
|
|
50
51
|
serviceContainer.register('propertyService', new IobrokerWebuiPropertiesService());
|
|
51
52
|
serviceContainer.register('propertyService', new IobrokerWebuiSpecialPropertiesService());
|
|
53
|
+
serviceContainer.register('propertyService', new IobrokerWebuiLitPropertiesService());
|
|
52
54
|
// Visibility is now handled directly in PropertyGrid
|
|
53
55
|
// serviceContainer.register('propertyService', new IobrokerWebuiVisibilityPropertiesService());
|
|
54
56
|
serviceContainer.designViewConfigButtons.push(new IobrokerWebuiConfigButtonProvider());
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { BasePropertyEditor } from "@gokturk413/web-component-designer";
|
|
2
|
+
import { readDynamicDefs, dynamicPropertyTypes } from "../services/DynamicPropertiesHelper.js";
|
|
3
|
+
|
|
4
|
+
const notAllowedChars = '!"§$%&/()=?`´-:.,;<>|\\\'#+*°^';
|
|
5
|
+
|
|
6
|
+
// property grid editor for the 'dynamic-property-defs' attribute.
|
|
7
|
+
// shows a button which opens a dialog to add/remove/edit dynamic properties (name, type, default)
|
|
8
|
+
export class IobrokerWebuiDynamicPropsEditor extends BasePropertyEditor {
|
|
9
|
+
constructor(property) {
|
|
10
|
+
super(property);
|
|
11
|
+
let btn = document.createElement('button');
|
|
12
|
+
btn.textContent = 'properties...';
|
|
13
|
+
btn.title = 'add/edit dynamic properties of this instance';
|
|
14
|
+
btn.style.width = '100%';
|
|
15
|
+
btn.onclick = () => this._openDialog();
|
|
16
|
+
this.element = btn;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
refreshValue(valueType, value) { }
|
|
20
|
+
|
|
21
|
+
_openDialog() {
|
|
22
|
+
const designItem = this.designItems?.[0];
|
|
23
|
+
if (!designItem)
|
|
24
|
+
return;
|
|
25
|
+
const defs = readDynamicDefs(designItem.element).map(x => ({ ...x }));
|
|
26
|
+
|
|
27
|
+
const overlay = document.createElement('div');
|
|
28
|
+
overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:100000;display:flex;align-items:center;justify-content:center;';
|
|
29
|
+
const panel = document.createElement('div');
|
|
30
|
+
panel.style.cssText = 'background:#1e2d3d;color:#d0d8e0;border:1px solid #3e6db4;min-width:430px;max-width:600px;max-height:80vh;display:flex;flex-direction:column;font-size:12px;font-family:inherit;box-shadow:0 4px 24px rgba(0,0,0,0.6);';
|
|
31
|
+
|
|
32
|
+
const header = document.createElement('div');
|
|
33
|
+
header.textContent = 'dynamic properties';
|
|
34
|
+
header.style.cssText = 'padding:6px 10px;font-weight:bold;border-bottom:1px solid #3e6db4;color:#7c9cbf;';
|
|
35
|
+
panel.appendChild(header);
|
|
36
|
+
|
|
37
|
+
const headRow = document.createElement('div');
|
|
38
|
+
headRow.style.cssText = 'display:grid;grid-template-columns:1fr 90px 1fr 1fr 28px;gap:3px;padding:4px 10px 0 10px;color:#7c9cbf;';
|
|
39
|
+
for (const t of ['name', 'type', 'default', 'enum values', '']) {
|
|
40
|
+
const d = document.createElement('div');
|
|
41
|
+
d.textContent = t;
|
|
42
|
+
headRow.appendChild(d);
|
|
43
|
+
}
|
|
44
|
+
panel.appendChild(headRow);
|
|
45
|
+
|
|
46
|
+
const list = document.createElement('div');
|
|
47
|
+
list.style.cssText = 'display:flex;flex-direction:column;gap:3px;padding:4px 10px;overflow-y:auto;flex:1;';
|
|
48
|
+
panel.appendChild(list);
|
|
49
|
+
|
|
50
|
+
const renderRows = () => {
|
|
51
|
+
list.innerHTML = '';
|
|
52
|
+
defs.forEach((def, idx) => {
|
|
53
|
+
const row = document.createElement('div');
|
|
54
|
+
row.style.cssText = 'display:grid;grid-template-columns:1fr 90px 1fr 1fr 28px;gap:3px;';
|
|
55
|
+
|
|
56
|
+
const nameInput = document.createElement('input');
|
|
57
|
+
nameInput.value = def.name ?? '';
|
|
58
|
+
nameInput.placeholder = 'propertyName';
|
|
59
|
+
nameInput.oninput = () => def.name = nameInput.value;
|
|
60
|
+
row.appendChild(nameInput);
|
|
61
|
+
|
|
62
|
+
const typeSelect = document.createElement('select');
|
|
63
|
+
for (const t of dynamicPropertyTypes) {
|
|
64
|
+
const opt = document.createElement('option');
|
|
65
|
+
opt.value = t;
|
|
66
|
+
opt.textContent = t;
|
|
67
|
+
if (def.type === t)
|
|
68
|
+
opt.selected = true;
|
|
69
|
+
typeSelect.appendChild(opt);
|
|
70
|
+
}
|
|
71
|
+
typeSelect.onchange = () => {
|
|
72
|
+
def.type = typeSelect.value;
|
|
73
|
+
valuesInput.style.visibility = def.type === 'enum' ? 'visible' : 'hidden';
|
|
74
|
+
};
|
|
75
|
+
row.appendChild(typeSelect);
|
|
76
|
+
|
|
77
|
+
const defInput = document.createElement('input');
|
|
78
|
+
defInput.value = def.default ?? '';
|
|
79
|
+
defInput.placeholder = 'default';
|
|
80
|
+
defInput.oninput = () => def.default = defInput.value;
|
|
81
|
+
row.appendChild(defInput);
|
|
82
|
+
|
|
83
|
+
const valuesInput = document.createElement('input');
|
|
84
|
+
valuesInput.value = def.values ? JSON.stringify(def.values) : '';
|
|
85
|
+
valuesInput.placeholder = '["a","b"]';
|
|
86
|
+
valuesInput.style.visibility = def.type === 'enum' ? 'visible' : 'hidden';
|
|
87
|
+
valuesInput.oninput = () => {
|
|
88
|
+
try { def.values = JSON.parse(valuesInput.value); } catch { }
|
|
89
|
+
};
|
|
90
|
+
row.appendChild(valuesInput);
|
|
91
|
+
|
|
92
|
+
const delBtn = document.createElement('button');
|
|
93
|
+
delBtn.textContent = '✕';
|
|
94
|
+
delBtn.title = 'remove';
|
|
95
|
+
delBtn.onclick = () => {
|
|
96
|
+
defs.splice(idx, 1);
|
|
97
|
+
renderRows();
|
|
98
|
+
};
|
|
99
|
+
row.appendChild(delBtn);
|
|
100
|
+
list.appendChild(row);
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
renderRows();
|
|
104
|
+
|
|
105
|
+
const footer = document.createElement('div');
|
|
106
|
+
footer.style.cssText = 'display:flex;gap:4px;padding:8px 10px;border-top:1px solid #3e6db4;';
|
|
107
|
+
const addBtn = document.createElement('button');
|
|
108
|
+
addBtn.textContent = 'add...';
|
|
109
|
+
addBtn.onclick = () => {
|
|
110
|
+
defs.push({ name: '', type: 'string' });
|
|
111
|
+
renderRows();
|
|
112
|
+
};
|
|
113
|
+
const spacer = document.createElement('div');
|
|
114
|
+
spacer.style.flex = '1';
|
|
115
|
+
const okBtn = document.createElement('button');
|
|
116
|
+
okBtn.textContent = 'ok';
|
|
117
|
+
okBtn.style.minWidth = '60px';
|
|
118
|
+
okBtn.onclick = async () => {
|
|
119
|
+
const cleaned = [];
|
|
120
|
+
const usedNames = new Set();
|
|
121
|
+
for (const def of defs) {
|
|
122
|
+
let name = def.name ?? '';
|
|
123
|
+
for (const c of notAllowedChars)
|
|
124
|
+
name = name.replaceAll(c, '');
|
|
125
|
+
name = name.replaceAll(' ', '');
|
|
126
|
+
if (!name)
|
|
127
|
+
continue;
|
|
128
|
+
name = name[0].toLowerCase() + name.substring(1);
|
|
129
|
+
if (usedNames.has(name))
|
|
130
|
+
continue;
|
|
131
|
+
usedNames.add(name);
|
|
132
|
+
const entry = { name, type: def.type ?? 'string' };
|
|
133
|
+
if (def.default != null && def.default !== '')
|
|
134
|
+
entry.default = def.default;
|
|
135
|
+
if (def.type === 'enum' && def.values)
|
|
136
|
+
entry.values = def.values;
|
|
137
|
+
cleaned.push(entry);
|
|
138
|
+
}
|
|
139
|
+
overlay.remove();
|
|
140
|
+
await this._valueChanged(cleaned.length ? JSON.stringify(cleaned) : null);
|
|
141
|
+
};
|
|
142
|
+
const cancelBtn = document.createElement('button');
|
|
143
|
+
cancelBtn.textContent = 'cancel';
|
|
144
|
+
cancelBtn.style.minWidth = '60px';
|
|
145
|
+
cancelBtn.onclick = () => overlay.remove();
|
|
146
|
+
footer.appendChild(addBtn);
|
|
147
|
+
footer.appendChild(spacer);
|
|
148
|
+
footer.appendChild(okBtn);
|
|
149
|
+
footer.appendChild(cancelBtn);
|
|
150
|
+
panel.appendChild(footer);
|
|
151
|
+
|
|
152
|
+
overlay.appendChild(panel);
|
|
153
|
+
overlay.onclick = e => {
|
|
154
|
+
if (e.target === overlay)
|
|
155
|
+
overlay.remove();
|
|
156
|
+
};
|
|
157
|
+
document.body.appendChild(overlay);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -3,6 +3,7 @@ import { iobrokerHandler } from "../common/IobrokerHandler.js";
|
|
|
3
3
|
import { PropertiesHelper } from "@gokturk413/web-component-designer/dist/elements/services/propertiesService/services/PropertiesHelper.js";
|
|
4
4
|
import { visibilityService } from "./VisibilityService.js";
|
|
5
5
|
import { scanAndApplyAnimations, scanAndApplyEffects, cleanupAnimations, cleanupEffects } from "./AnimationService.js";
|
|
6
|
+
import { applyDynamicProperties, applyDynamicPropertiesToTree } from "./DynamicElementProperties.js";
|
|
6
7
|
export const webuiCustomControlPrefix = 'webui-';
|
|
7
8
|
export const webuiCustomControlSymbol = Symbol('webuiCustomControlSymbol');
|
|
8
9
|
export class BaseCustomControl extends BaseCustomWebComponentConstructorAppend {
|
|
@@ -35,6 +36,7 @@ export class BaseCustomControl extends BaseCustomWebComponentConstructorAppend {
|
|
|
35
36
|
}
|
|
36
37
|
async connectedCallback() {
|
|
37
38
|
this._parseAttributesToProperties();
|
|
39
|
+
this._setupDynamicProperties();
|
|
38
40
|
this._bindingsRefresh();
|
|
39
41
|
this.#specialValueHandler = {
|
|
40
42
|
valueProvider: (specialValueName) => {
|
|
@@ -106,6 +108,14 @@ export class BaseCustomControl extends BaseCustomWebComponentConstructorAppend {
|
|
|
106
108
|
cleanupAnimations(this.shadowRoot);
|
|
107
109
|
cleanupEffects(this.shadowRoot);
|
|
108
110
|
}
|
|
111
|
+
// defines accessors for the per-instance dynamic properties declared in the
|
|
112
|
+
// 'dynamic-property-defs' attribute: [ { "name": "kamran", "type": "string", "default": "x" }, ... ]
|
|
113
|
+
// so they behave like normal control properties (bindings refresh + changed events).
|
|
114
|
+
// also applies dynamic properties to elements inside the control template (e.g. Lit widgets)
|
|
115
|
+
_setupDynamicProperties() {
|
|
116
|
+
applyDynamicProperties(this);
|
|
117
|
+
applyDynamicPropertiesToTree(this.shadowRoot);
|
|
118
|
+
}
|
|
109
119
|
_assignEvent(event, callback) {
|
|
110
120
|
const arrayEl = [event, callback];
|
|
111
121
|
this.#eventListeners.push(arrayEl);
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { PropertiesHelper } from "@gokturk413/web-component-designer/dist/elements/services/propertiesService/services/PropertiesHelper.js";
|
|
2
|
+
|
|
3
|
+
// runtime support for per-instance dynamic properties (any element, incl. Lit/package widgets):
|
|
4
|
+
// reads the 'dynamic-property-defs' attribute and defines accessors on the instance so the
|
|
5
|
+
// properties dispatch '<name>-changed' events and refresh webui bindings on change.
|
|
6
|
+
export const dynamicDefsAttributeName = 'dynamic-property-defs';
|
|
7
|
+
|
|
8
|
+
export function parseDynamicDefs(element) {
|
|
9
|
+
try {
|
|
10
|
+
const v = element.getAttribute(dynamicDefsAttributeName);
|
|
11
|
+
if (v) {
|
|
12
|
+
const parsed = JSON.parse(v);
|
|
13
|
+
if (Array.isArray(parsed))
|
|
14
|
+
return parsed.filter(x => x && x.name);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch (e) {
|
|
18
|
+
console.warn('invalid ' + dynamicDefsAttributeName + ' attribute', e, element);
|
|
19
|
+
}
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function findPrototypeAccessor(element, name) {
|
|
24
|
+
let proto = Object.getPrototypeOf(element);
|
|
25
|
+
while (proto && proto !== HTMLElement.prototype) {
|
|
26
|
+
const desc = Object.getOwnPropertyDescriptor(proto, name);
|
|
27
|
+
if (desc)
|
|
28
|
+
return desc;
|
|
29
|
+
proto = Object.getPrototypeOf(proto);
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function applyDynamicProperties(element) {
|
|
35
|
+
const defs = parseDynamicDefs(element);
|
|
36
|
+
for (const def of defs) {
|
|
37
|
+
const name = def.name;
|
|
38
|
+
if (Object.getOwnPropertyDescriptor(element, name))
|
|
39
|
+
continue;
|
|
40
|
+
// if the element already has an accessor for this name (e.g. a Lit reactive
|
|
41
|
+
// property), route values through it so the widget keeps reacting, but still
|
|
42
|
+
// dispatch the '-changed' event webui bindings listen to
|
|
43
|
+
const protoDesc = findPrototypeAccessor(element, name);
|
|
44
|
+
if (protoDesc && (protoDesc.get || protoDesc.set)) {
|
|
45
|
+
Object.defineProperty(element, name, {
|
|
46
|
+
get() {
|
|
47
|
+
return protoDesc.get ? protoDesc.get.call(this) : undefined;
|
|
48
|
+
},
|
|
49
|
+
set(newValue) {
|
|
50
|
+
const oldValue = protoDesc.get ? protoDesc.get.call(this) : undefined;
|
|
51
|
+
if (oldValue !== newValue) {
|
|
52
|
+
protoDesc.set?.call(this, newValue);
|
|
53
|
+
this._bindingsRefresh?.(name);
|
|
54
|
+
this.dispatchEvent(new CustomEvent(PropertiesHelper.camelToDashCase(name) + '-changed', { detail: { newValue } }));
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
enumerable: true,
|
|
58
|
+
configurable: true,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
Object.defineProperty(element, name, {
|
|
63
|
+
get() {
|
|
64
|
+
return this['_' + name];
|
|
65
|
+
},
|
|
66
|
+
set(newValue) {
|
|
67
|
+
if (this['_' + name] !== newValue && (!Number.isNaN(this['_' + name]) || !Number.isNaN(newValue))) {
|
|
68
|
+
this['_' + name] = newValue;
|
|
69
|
+
this._bindingsRefresh?.(name);
|
|
70
|
+
this.dispatchEvent(new CustomEvent(PropertiesHelper.camelToDashCase(name) + '-changed', { detail: { newValue } }));
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
enumerable: true,
|
|
74
|
+
configurable: true,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
const attr = element.getAttribute(PropertiesHelper.camelToDashCase(name));
|
|
78
|
+
let value = attr ?? def.default;
|
|
79
|
+
if (value != null) {
|
|
80
|
+
if (def.type === 'number')
|
|
81
|
+
value = parseFloat(value);
|
|
82
|
+
else if (def.type === 'boolean')
|
|
83
|
+
value = value === '' || value === true || value === 'true';
|
|
84
|
+
if (protoDesc)
|
|
85
|
+
element[name] = value;
|
|
86
|
+
else
|
|
87
|
+
element['_' + name] = value;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function applyDynamicPropertiesToTree(root) {
|
|
93
|
+
if (root instanceof Element && root.hasAttribute(dynamicDefsAttributeName))
|
|
94
|
+
applyDynamicProperties(root);
|
|
95
|
+
for (const el of root.querySelectorAll('[' + dynamicDefsAttributeName + ']'))
|
|
96
|
+
applyDynamicProperties(el);
|
|
97
|
+
}
|
|
@@ -7,6 +7,7 @@ import { isFirefox } from "@gokturk413/web-component-designer/dist/elements/help
|
|
|
7
7
|
import { PropertiesHelper } from "@gokturk413/web-component-designer/dist/elements/services/propertiesService/services/PropertiesHelper.js";
|
|
8
8
|
import { visibilityService } from "./VisibilityService.js";
|
|
9
9
|
import { scanAndApplyAnimations, scanAndApplyEffects, cleanupAnimations, cleanupEffects, ensureGSAP } from "./AnimationService.js";
|
|
10
|
+
import { applyDynamicPropertiesToTree } from "./DynamicElementProperties.js";
|
|
10
11
|
window.ensureGSAP = ensureGSAP;
|
|
11
12
|
let ScreenViewer = class ScreenViewer extends BaseCustomWebComponentConstructorAppend {
|
|
12
13
|
static { ScreenViewer_1 = this; }
|
|
@@ -313,6 +314,8 @@ let ScreenViewer = class ScreenViewer extends BaseCustomWebComponentConstructorA
|
|
|
313
314
|
get() { return el.style.pointerEvents === 'none'; }
|
|
314
315
|
});
|
|
315
316
|
}
|
|
317
|
+
// dynamic per-instance properties (incl. Lit/package widgets) must exist before bindings
|
|
318
|
+
applyDynamicPropertiesToTree(this._rootShadow);
|
|
316
319
|
const res = window.appShell.bindingsHelper.applyAllBindings(this._rootShadow, this.relativeSignalsPath, this);
|
|
317
320
|
if (this._iobBindings)
|
|
318
321
|
this._iobBindings.push(...res);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { PropertyType } from "@gokturk413/web-component-designer";
|
|
2
|
+
import { iobrokerHandler } from "../common/IobrokerHandler.js";
|
|
3
|
+
import { IobrokerWebuiSignalPropertyEditor } from "../config/IobrokerWebuiSignalPropertyEditor.js";
|
|
4
|
+
import { IobrokerWebuiDynamicPropsEditor } from "../config/IobrokerWebuiDynamicPropsEditor.js";
|
|
5
|
+
|
|
6
|
+
// attribute on the element instance holding the per-instance dynamic property definitions:
|
|
7
|
+
// [ { "name": "kamran", "type": "string", "default": "abc", "values": [...] }, ... ]
|
|
8
|
+
export const dynamicDefsAttributeName = 'dynamic-property-defs';
|
|
9
|
+
|
|
10
|
+
export const dynamicPropertyTypes = ['string', 'number', 'boolean', 'color', 'date', 'signal', 'screen', 'enum'];
|
|
11
|
+
|
|
12
|
+
export function readDynamicDefs(element) {
|
|
13
|
+
try {
|
|
14
|
+
const v = element.getAttribute(dynamicDefsAttributeName);
|
|
15
|
+
if (v) {
|
|
16
|
+
const parsed = JSON.parse(v);
|
|
17
|
+
if (Array.isArray(parsed))
|
|
18
|
+
return parsed.filter(x => x && x.name);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
console.warn('invalid ' + dynamicDefsAttributeName + ' attribute', e);
|
|
23
|
+
}
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function convertDynamicDefault(def) {
|
|
28
|
+
if (def.default == null || def.default === '')
|
|
29
|
+
return undefined;
|
|
30
|
+
if (def.type === 'number') {
|
|
31
|
+
const n = parseFloat(def.default);
|
|
32
|
+
return Number.isNaN(n) ? undefined : n;
|
|
33
|
+
}
|
|
34
|
+
if (def.type === 'boolean')
|
|
35
|
+
return def.default === true || def.default === 'true';
|
|
36
|
+
return def.default;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function createWebuiTypedProperty(name, prp, service) {
|
|
40
|
+
if (prp.type === 'color')
|
|
41
|
+
return { name, type: "color", service, propertyType: PropertyType.propertyAndAttribute };
|
|
42
|
+
if (prp.type === 'number')
|
|
43
|
+
return { name, type: "number", service, propertyType: PropertyType.propertyAndAttribute };
|
|
44
|
+
if (prp.type === 'boolean')
|
|
45
|
+
return { name, type: "boolean", service, propertyType: PropertyType.propertyAndAttribute };
|
|
46
|
+
if (prp.type === 'date')
|
|
47
|
+
return { name, type: "date", service, propertyType: PropertyType.propertyAndAttribute };
|
|
48
|
+
if (prp.type === 'enum')
|
|
49
|
+
return { name, type: "list", values: prp.values, service, propertyType: PropertyType.propertyAndAttribute };
|
|
50
|
+
if (prp.type === 'signal')
|
|
51
|
+
return { name, type: "signal", service, propertyType: PropertyType.propertyAndAttribute, createEditor: p => new IobrokerWebuiSignalPropertyEditor(p, window.appShell) };
|
|
52
|
+
if (prp.type === 'screen') {
|
|
53
|
+
const screens = await iobrokerHandler.getAllNames('screen');
|
|
54
|
+
return { name, type: "list", values: screens, service, propertyType: PropertyType.propertyAndAttribute };
|
|
55
|
+
}
|
|
56
|
+
return { name, type: "string", service, propertyType: PropertyType.propertyAndAttribute };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// builds the property grid entries for the dynamic properties of a design item:
|
|
60
|
+
// one group 'dynamic' containing the defined properties + the defs editor button
|
|
61
|
+
export async function buildDynamicPropertiesGroup(designItem, service) {
|
|
62
|
+
const defs = readDynamicDefs(designItem.element);
|
|
63
|
+
const properties = [];
|
|
64
|
+
for (const d of defs)
|
|
65
|
+
properties.push(await createWebuiTypedProperty(d.name, d, service));
|
|
66
|
+
properties.push({
|
|
67
|
+
name: 'dynamicPropertyDefs',
|
|
68
|
+
type: 'string',
|
|
69
|
+
attributeName: dynamicDefsAttributeName,
|
|
70
|
+
service,
|
|
71
|
+
propertyType: PropertyType.attribute,
|
|
72
|
+
createEditor: p => new IobrokerWebuiDynamicPropsEditor(p)
|
|
73
|
+
});
|
|
74
|
+
return { name: 'dynamic', description: 'instance dynamic properties', properties };
|
|
75
|
+
}
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import { EventsService, PropertiesHelper } from "@gokturk413/web-component-designer";
|
|
2
2
|
import { BaseCustomControl, webuiCustomControlSymbol } from "../runtime/CustomControls.js";
|
|
3
|
+
import { readDynamicDefs } from "./DynamicPropertiesHelper.js";
|
|
3
4
|
export class IobrokerWebuiCustomControlEventsService {
|
|
4
5
|
isHandledElementFromEventsService(designItem) {
|
|
5
|
-
return designItem.element instanceof BaseCustomControl;
|
|
6
|
+
return designItem.element instanceof BaseCustomControl || designItem.element.hasAttribute?.('dynamic-property-defs');
|
|
6
7
|
}
|
|
7
8
|
getPossibleEvents(designItem) {
|
|
8
9
|
const evt = [];
|
|
9
|
-
let control = designItem.element.constructor[webuiCustomControlSymbol]
|
|
10
|
-
for (const pname in control
|
|
10
|
+
let control = designItem.element.constructor[webuiCustomControlSymbol]?.control;
|
|
11
|
+
for (const pname in control?.properties) {
|
|
11
12
|
if (control.properties[pname].internal)
|
|
12
13
|
continue;
|
|
13
14
|
evt.push({ name: PropertiesHelper.camelToDashCase(pname) + '-changed' });
|
|
14
15
|
}
|
|
16
|
+
// changed events of the per-instance dynamic properties (dynamic-property-defs attribute)
|
|
17
|
+
for (const def of readDynamicDefs(designItem.element)) {
|
|
18
|
+
evt.push({ name: PropertiesHelper.camelToDashCase(def.name) + '-changed' });
|
|
19
|
+
}
|
|
15
20
|
return [...evt, ...EventsService._simpleMouseEvents, ...EventsService._pointerEvents, ...EventsService._allElements, ...EventsService._focusableEvents];
|
|
16
21
|
}
|
|
17
22
|
getEvent(designItem, name) {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Lit2PropertiesService, PropertiesHelper, PropertyType, RefreshMode } from "@gokturk413/web-component-designer";
|
|
2
|
+
import { buildDynamicPropertiesGroup, createWebuiTypedProperty } from "./DynamicPropertiesHelper.js";
|
|
3
|
+
|
|
4
|
+
// extended properties service for Lit (2/3) widgets:
|
|
5
|
+
// - supports Array & custom converter properties (edited as string/JSON) which Lit2PropertiesService skips
|
|
6
|
+
// - supports webui specific types via 'static webuiProperties' metadata on the widget class
|
|
7
|
+
// (e.g. static webuiProperties = { mySignal: { type: 'signal' }, myColor: { type: 'color' } })
|
|
8
|
+
// - supports per-instance dynamic properties via the 'dynamic-property-defs' attribute
|
|
9
|
+
export class IobrokerWebuiLitPropertiesService extends Lit2PropertiesService {
|
|
10
|
+
name = "webuiLit";
|
|
11
|
+
|
|
12
|
+
getRefreshMode(designItem) {
|
|
13
|
+
return RefreshMode.fullOnValueChange;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async getProperties(designItem) {
|
|
17
|
+
if (!this.isHandledElement(designItem))
|
|
18
|
+
return null;
|
|
19
|
+
const ctor = designItem.element.constructor;
|
|
20
|
+
const meta = ctor.webuiProperties ?? {};
|
|
21
|
+
const properties = [];
|
|
22
|
+
const handled = new Set();
|
|
23
|
+
for (const [name, litProperty] of ctor.elementProperties.entries()) {
|
|
24
|
+
handled.add(name);
|
|
25
|
+
const m = meta[name];
|
|
26
|
+
if (m) {
|
|
27
|
+
properties.push(await createWebuiTypedProperty(name, m, this));
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
let type = litProperty?.type ?? litProperty;
|
|
31
|
+
if (type === String)
|
|
32
|
+
properties.push({ name, type: "string", service: this, propertyType: PropertyType.propertyAndAttribute });
|
|
33
|
+
else if (type === Number)
|
|
34
|
+
properties.push({ name, type: "number", service: this, propertyType: PropertyType.propertyAndAttribute });
|
|
35
|
+
else if (type === Boolean)
|
|
36
|
+
properties.push({ name, type: "boolean", service: this, propertyType: PropertyType.propertyAndAttribute });
|
|
37
|
+
else if (type === Date)
|
|
38
|
+
properties.push({ name, type: "date", service: this, propertyType: PropertyType.propertyAndAttribute });
|
|
39
|
+
else if (PropertiesHelper.isTypescriptEnum(type))
|
|
40
|
+
properties.push({ name, type: "enum", enumValues: PropertiesHelper.getTypescriptEnumEntries(type), service: this, propertyType: PropertyType.propertyAndAttribute });
|
|
41
|
+
else
|
|
42
|
+
// Array, Object and custom converters are edited as string (JSON)
|
|
43
|
+
properties.push({ name, type: "string", service: this, propertyType: PropertyType.propertyAndAttribute });
|
|
44
|
+
}
|
|
45
|
+
// webuiProperties entries which are no lit reactive properties
|
|
46
|
+
for (const name in meta) {
|
|
47
|
+
if (!handled.has(name))
|
|
48
|
+
properties.push(await createWebuiTypedProperty(name, meta[name], this));
|
|
49
|
+
}
|
|
50
|
+
properties.push(await buildDynamicPropertiesGroup(designItem, this));
|
|
51
|
+
return properties;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
import { BaseCustomWebComponentPropertiesService, PropertyType } from "@gokturk413/web-component-designer";
|
|
1
|
+
import { BaseCustomWebComponentPropertiesService, PropertyType, RefreshMode } from "@gokturk413/web-component-designer";
|
|
2
2
|
import { BaseCustomControl, webuiCustomControlSymbol } from "../runtime/CustomControls.js";
|
|
3
3
|
import { iobrokerHandler } from "../common/IobrokerHandler.js";
|
|
4
4
|
import { ScreenViewer } from "../runtime/ScreenViewer.js";
|
|
5
5
|
import { IobrokerWebuiSignalPropertyEditor } from "../config/IobrokerWebuiSignalPropertyEditor.js";
|
|
6
|
+
import { buildDynamicPropertiesGroup } from "./DynamicPropertiesHelper.js";
|
|
6
7
|
export class IobrokerWebuiPropertiesService extends BaseCustomWebComponentPropertiesService {
|
|
7
8
|
isHandledElement(designItem) {
|
|
8
9
|
return designItem.element instanceof BaseCustomControl || designItem.element instanceof ScreenViewer;
|
|
9
10
|
;
|
|
10
11
|
}
|
|
12
|
+
getRefreshMode(designItem) {
|
|
13
|
+
// dynamic properties can change with every value change (dynamic-property-defs attribute)
|
|
14
|
+
return RefreshMode.fullOnValueChange;
|
|
15
|
+
}
|
|
11
16
|
async getProperties(designItem) {
|
|
12
17
|
if (!this.isHandledElement(designItem))
|
|
13
18
|
return null;
|
|
@@ -67,6 +72,7 @@ export class IobrokerWebuiPropertiesService extends BaseCustomWebComponentProper
|
|
|
67
72
|
for (const [groupName, groupProps] of groupMap) {
|
|
68
73
|
result.push({ name: groupName, description: '', properties: groupProps });
|
|
69
74
|
}
|
|
75
|
+
result.push(await buildDynamicPropertiesGroup(designItem, this));
|
|
70
76
|
return result;
|
|
71
77
|
}
|
|
72
78
|
else {
|