iobroker.mywebui 1.37.75 → 1.37.76
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 +1 -1
- package/package.json +9 -7
- package/www/dist/frontend/config/IobrokerWebuiAppShell.js +177 -139
- package/www/dist/frontend/runtime/AnimationService.js +14 -7
- package/www/node_modules/@gokturk413/web-component-designer-visualization-addons/dist/helpers/BindingsHelper.js +1 -0
package/io-package.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.mywebui",
|
|
3
|
-
"version": "1.37.
|
|
3
|
+
"version": "1.37.76",
|
|
4
4
|
"description": "ioBroker mywebui - Custom edited mywebui by gokturk413",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/backend/main.js",
|
|
@@ -74,20 +74,20 @@
|
|
|
74
74
|
"@alcalzone/release-script-plugin-iobroker": "^4.0.0",
|
|
75
75
|
"@alcalzone/release-script-plugin-license": "^4.0.0",
|
|
76
76
|
"@blockly/zoom-to-fit": "^7.0.3",
|
|
77
|
-
"@iobroker/socket-client": "^5.0.2",
|
|
78
|
-
"@iobroker/testing": "^5.1.1",
|
|
79
|
-
"@iobroker/webcomponent-selectid-dialog": "^1.0.12",
|
|
80
77
|
"@gokturk413/base-custom-webcomponent": "*",
|
|
81
|
-
"@node-projects/lean-he-esm": "^3.4.1",
|
|
82
|
-
"@node-projects/node-html-parser-esm": "^6.4.1",
|
|
83
78
|
"@gokturk413/propertygrid.webcomponent": "*",
|
|
84
79
|
"@gokturk413/splitview.webcomponent": "^1.0.1",
|
|
85
80
|
"@gokturk413/web-component-designer": "*",
|
|
86
81
|
"@gokturk413/web-component-designer-codeview-monaco": "*",
|
|
87
82
|
"@gokturk413/web-component-designer-htmlparserservice-nodehtmlparser": "*",
|
|
88
|
-
"@node-projects/web-component-designer-stylesheetservice-css-tools": "^0.1.11",
|
|
89
83
|
"@gokturk413/web-component-designer-visualization-addons": "*",
|
|
90
84
|
"@gokturk413/web-component-designer-widgets-wunderbaum": "*",
|
|
85
|
+
"@iobroker/socket-client": "^5.0.2",
|
|
86
|
+
"@iobroker/testing": "^5.1.1",
|
|
87
|
+
"@iobroker/webcomponent-selectid-dialog": "^1.0.12",
|
|
88
|
+
"@node-projects/lean-he-esm": "^3.4.1",
|
|
89
|
+
"@node-projects/node-html-parser-esm": "^6.4.1",
|
|
90
|
+
"@node-projects/web-component-designer-stylesheetservice-css-tools": "^0.1.11",
|
|
91
91
|
"@types/json-schema": "^7.0.15",
|
|
92
92
|
"@web/dev-server": "^0.4.6",
|
|
93
93
|
"blockly": "^12.3.1",
|
|
@@ -107,10 +107,12 @@
|
|
|
107
107
|
"gulp-replace": "^1.1.4",
|
|
108
108
|
"javascript-obfuscator": "^5.3.0",
|
|
109
109
|
"long": "^5.3.2",
|
|
110
|
+
"marked": "^18.0.3",
|
|
110
111
|
"mobile-drag-drop": "^3.0.0-rc.0",
|
|
111
112
|
"mocha": "^11.7.4",
|
|
112
113
|
"monaco-editor": "^0.50.0",
|
|
113
114
|
"nyc": "^17.1.0",
|
|
115
|
+
"puppeteer": "^24.42.0",
|
|
114
116
|
"sinon-chai": "^4.0.1",
|
|
115
117
|
"toastify-js": "^1.12.0",
|
|
116
118
|
"ts-node": "^10.9.2",
|
|
@@ -1115,154 +1115,192 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
|
|
|
1115
1115
|
const designItem = items[0];
|
|
1116
1116
|
const element = designItem.element;
|
|
1117
1117
|
|
|
1118
|
-
let
|
|
1119
|
-
try {
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
if (
|
|
1127
|
-
if (
|
|
1128
|
-
|
|
1129
|
-
if (triggerSel.value === 'oid') {
|
|
1130
|
-
out.condition = condSel.value;
|
|
1131
|
-
out.conditionValue = condValInp.value;
|
|
1132
|
-
}
|
|
1133
|
-
if (typeSel.value === 'glow') {
|
|
1134
|
-
out.glowColor = glowColorInp.value;
|
|
1135
|
-
out.glowSize = parseInt(glowSizeInp.value) || 10;
|
|
1136
|
-
}
|
|
1137
|
-
if (typeSel.value === 'blur') out.blurAmount = parseInt(blurInp.value) || 5;
|
|
1138
|
-
// Preserve all _bind properties
|
|
1139
|
-
for (const [k, v] of Object.entries(cfg)) {
|
|
1140
|
-
if (k.endsWith('_bind') && v) out[k] = v;
|
|
1141
|
-
}
|
|
1142
|
-
return out;
|
|
1118
|
+
let raw = null;
|
|
1119
|
+
try { raw = JSON.parse(element.getAttribute('data-effects') || 'null'); } catch (e) {}
|
|
1120
|
+
let cfgList = Array.isArray(raw) ? raw : (raw ? [raw] : []);
|
|
1121
|
+
|
|
1122
|
+
content.innerHTML = '';
|
|
1123
|
+
|
|
1124
|
+
const saveAll = () => {
|
|
1125
|
+
const data = cfgList.filter(c => c._collect).map(c => c._collect()).filter(c => c.type);
|
|
1126
|
+
if (data.length === 0) designItem.removeAttribute('data-effects');
|
|
1127
|
+
else if (data.length === 1) designItem.setAttribute('data-effects', JSON.stringify(data[0]));
|
|
1128
|
+
else designItem.setAttribute('data-effects', JSON.stringify(data));
|
|
1143
1129
|
};
|
|
1130
|
+
const saveAndRefresh = () => { saveAll(); this._updateEffectsPanel(); };
|
|
1144
1131
|
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1132
|
+
// ── Add Effect button ─────────────────────────────────────────────────
|
|
1133
|
+
const addBtn = document.createElement('button');
|
|
1134
|
+
addBtn.textContent = '+ Add Effect';
|
|
1135
|
+
addBtn.style.cssText = 'width:100%;font-size:11px;padding:4px;cursor:pointer;border:1px solid #4caf50;border-radius:3px;background:#e8f5e9;color:#2e7d32;margin-bottom:8px;';
|
|
1136
|
+
addBtn.onclick = () => {
|
|
1137
|
+
const existing = cfgList.filter(c => c._collect).map(c => c._collect()).filter(c => c.type);
|
|
1138
|
+
existing.push({ type: 'fadeIn', trigger: 'load', duration: 0.5, ease: 'power2.out' });
|
|
1139
|
+
const val = existing.length === 1 ? existing[0] : existing;
|
|
1140
|
+
designItem.setAttribute('data-effects', JSON.stringify(val));
|
|
1141
|
+
this._updateEffectsPanel();
|
|
1149
1142
|
};
|
|
1143
|
+
content.appendChild(addBtn);
|
|
1150
1144
|
|
|
1151
|
-
|
|
1145
|
+
if (cfgList.length === 0) {
|
|
1146
|
+
const ph = document.createElement('p');
|
|
1147
|
+
ph.style.cssText = 'color:#999;font-style:italic;font-size:11px;text-align:center;margin-top:12px;';
|
|
1148
|
+
ph.textContent = 'No effects. Click "+ Add Effect".';
|
|
1149
|
+
content.appendChild(ph);
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
1152
|
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1153
|
+
// ── Build one collapsible block per effect ────────────────────────────
|
|
1154
|
+
const buildEffectBlock = (cfg, index) => {
|
|
1155
|
+
const wrapper = document.createElement('div');
|
|
1156
|
+
wrapper.style.cssText = 'border:1px solid #ddd;border-radius:4px;margin-bottom:6px;overflow:hidden;';
|
|
1157
|
+
|
|
1158
|
+
// Header
|
|
1159
|
+
const header = document.createElement('div');
|
|
1160
|
+
header.style.cssText = 'display:flex;align-items:center;justify-content:space-between;background:#f0f4ff;padding:4px 6px;cursor:pointer;user-select:none;';
|
|
1161
|
+
const headerLeft = document.createElement('span');
|
|
1162
|
+
headerLeft.style.cssText = 'font-size:11px;font-weight:600;color:#1a237e;';
|
|
1163
|
+
headerLeft.textContent = `▶ #${index + 1} — ${cfg.type || 'none'} (${cfg.trigger || 'load'})`;
|
|
1164
|
+
const delBtn = document.createElement('button');
|
|
1165
|
+
delBtn.textContent = '✕';
|
|
1166
|
+
delBtn.title = 'Remove this effect';
|
|
1167
|
+
delBtn.style.cssText = 'font-size:10px;padding:1px 5px;cursor:pointer;border:1px solid #e57373;border-radius:3px;background:#ffebee;color:#c62828;';
|
|
1168
|
+
delBtn.onclick = (e) => {
|
|
1169
|
+
e.stopPropagation();
|
|
1170
|
+
const data = cfgList.filter(c => c._collect).map(c => c._collect()).filter(c => c.type);
|
|
1171
|
+
data.splice(index, 1);
|
|
1172
|
+
if (data.length === 0) designItem.removeAttribute('data-effects');
|
|
1173
|
+
else if (data.length === 1) designItem.setAttribute('data-effects', JSON.stringify(data[0]));
|
|
1174
|
+
else designItem.setAttribute('data-effects', JSON.stringify(data));
|
|
1175
|
+
this._updateEffectsPanel();
|
|
1176
|
+
};
|
|
1177
|
+
header.appendChild(headerLeft);
|
|
1178
|
+
header.appendChild(delBtn);
|
|
1169
1179
|
|
|
1170
|
-
|
|
1171
|
-
const
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1180
|
+
// Body (collapsed by default)
|
|
1181
|
+
const body = document.createElement('div');
|
|
1182
|
+
body.style.cssText = 'padding:8px;display:none;';
|
|
1183
|
+
header.onclick = () => {
|
|
1184
|
+
body.style.display = body.style.display === 'none' ? '' : 'none';
|
|
1185
|
+
headerLeft.textContent = (body.style.display === 'none' ? '▶' : '▼') + ` #${index + 1} — ${cfg.type || 'none'} (${cfg.trigger || 'load'})`;
|
|
1186
|
+
};
|
|
1176
1187
|
|
|
1177
|
-
|
|
1178
|
-
const
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
const
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1188
|
+
// ── helpers scoped to this block ──────────────────────────────────
|
|
1189
|
+
const makeBindSq = (propKey) => this._makeBindSquare(propKey, cfg, designItem, 'data-effects', saveAndRefresh);
|
|
1190
|
+
|
|
1191
|
+
const field = (label, propKey, inputEl) => {
|
|
1192
|
+
const row = document.createElement('div');
|
|
1193
|
+
row.style.cssText = 'display:flex;align-items:center;gap:4px;margin-bottom:6px;';
|
|
1194
|
+
row.appendChild(propKey ? makeBindSq(propKey) : (() => { const sp = document.createElement('div'); sp.style.cssText = 'width:11px;min-width:11px;flex-shrink:0;'; return sp; })());
|
|
1195
|
+
const lbl = document.createElement('span');
|
|
1196
|
+
lbl.textContent = label;
|
|
1197
|
+
lbl.style.cssText = 'min-width:78px;font-size:11px;color:#555;';
|
|
1198
|
+
row.appendChild(lbl); row.appendChild(inputEl);
|
|
1199
|
+
return row;
|
|
1200
|
+
};
|
|
1187
1201
|
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1202
|
+
const inp = (val, type = 'text') => {
|
|
1203
|
+
const i = document.createElement('input');
|
|
1204
|
+
i.type = type; i.value = val ?? '';
|
|
1205
|
+
i.style.cssText = 'flex:1;padding:3px 5px;font-size:11px;border:1px solid #ccc;border-radius:3px;';
|
|
1206
|
+
i.onchange = saveAll; return i;
|
|
1207
|
+
};
|
|
1208
|
+
|
|
1209
|
+
const sel = (options, val) => {
|
|
1210
|
+
const s = document.createElement('select');
|
|
1211
|
+
s.style.cssText = 'flex:1;padding:3px 5px;font-size:11px;border:1px solid #ccc;border-radius:3px;';
|
|
1212
|
+
options.forEach(([v, t]) => { const o = document.createElement('option'); o.value = v; o.textContent = t; o.selected = v === val; s.appendChild(o); });
|
|
1213
|
+
s.onchange = saveAll; return s;
|
|
1214
|
+
};
|
|
1215
|
+
|
|
1216
|
+
const typeSel = sel([
|
|
1217
|
+
['','— none —'],['fadeIn','Fade In'],['fadeOut','Fade Out'],
|
|
1218
|
+
['slideInLeft','Slide In Left'],['slideInRight','Slide In Right'],
|
|
1219
|
+
['slideInTop','Slide In Top'],['slideInBottom','Slide In Bottom'],
|
|
1220
|
+
['bounce','Bounce'],['pulse','Pulse'],['shake','Shake'],
|
|
1221
|
+
['glow','Glow'],['blur','Blur'],['spin','Spin'],['flip','Flip 3D']
|
|
1222
|
+
], cfg.type || '');
|
|
1223
|
+
|
|
1224
|
+
const triggerSel = sel([
|
|
1225
|
+
['load','On Load'],['hover','On Hover'],['click','On Click'],['oid','OID State']
|
|
1226
|
+
], cfg.trigger || 'load');
|
|
1227
|
+
|
|
1228
|
+
const durationInp = inp(cfg.duration ?? 0.5, 'number');
|
|
1229
|
+
const delayInp = inp(cfg.delay ?? 0, 'number');
|
|
1230
|
+
const repeatInp = inp(cfg.repeat ?? 0, 'number');
|
|
1231
|
+
const easeSel = sel([
|
|
1232
|
+
['power2.out','power2.out'],['power2.in','power2.in'],['power2.inOut','power2.inOut'],
|
|
1233
|
+
['power1.inOut','power1.inOut'],['bounce.out','bounce.out'],
|
|
1234
|
+
['elastic.out(1,0.3)','elastic.out'],['back.inOut(1.7)','back.inOut'],['none','none']
|
|
1235
|
+
], cfg.ease || 'power2.out');
|
|
1236
|
+
|
|
1237
|
+
const condSel = sel([['equal','='],['not_equal','≠'],['less_than','<'],['greater_than','>'],['exists','exists']], cfg.condition || 'equal');
|
|
1238
|
+
const condValInp = inp(cfg.conditionValue ?? 'true');
|
|
1239
|
+
const glowColorInp = inp(cfg.glowColor || 'yellow', 'color');
|
|
1240
|
+
const glowSizeInp = inp(cfg.glowSize || 10, 'number');
|
|
1241
|
+
const blurInp = inp(cfg.blurAmount || 5, 'number');
|
|
1242
|
+
|
|
1243
|
+
body.appendChild(field('Type', 'type', typeSel));
|
|
1244
|
+
body.appendChild(field('Trigger', null, triggerSel));
|
|
1245
|
+
body.appendChild(field('Duration (s)', 'duration', durationInp));
|
|
1246
|
+
body.appendChild(field('Delay (s)', 'delay', delayInp));
|
|
1247
|
+
body.appendChild(field('Repeat', 'repeat', repeatInp));
|
|
1248
|
+
body.appendChild(field('Ease', 'ease', easeSel));
|
|
1249
|
+
|
|
1250
|
+
// OID section
|
|
1251
|
+
const oidSection = document.createElement('div');
|
|
1252
|
+
oidSection.style.display = cfg.trigger === 'oid' ? '' : 'none';
|
|
1253
|
+
const oidLabel = document.createElement('div');
|
|
1254
|
+
oidLabel.style.cssText = 'display:flex;align-items:center;gap:4px;font-size:11px;font-weight:600;color:#555;margin-bottom:3px;padding-left:4px;';
|
|
1255
|
+
oidLabel.appendChild(makeBindSq('oid'));
|
|
1256
|
+
const oidLabelText = document.createElement('span'); oidLabelText.textContent = 'OID'; oidLabel.appendChild(oidLabelText);
|
|
1257
|
+
const condRowDiv = document.createElement('div');
|
|
1258
|
+
condRowDiv.style.cssText = 'display:flex;align-items:center;gap:4px;margin-bottom:4px;padding-left:4px;';
|
|
1259
|
+
condRowDiv.appendChild(makeBindSq('condition')); condRowDiv.appendChild(condSel);
|
|
1260
|
+
const condValRowDiv = document.createElement('div');
|
|
1261
|
+
condValRowDiv.style.cssText = 'display:flex;align-items:center;gap:4px;margin-bottom:6px;padding-left:4px;';
|
|
1262
|
+
condValRowDiv.appendChild(makeBindSq('conditionValue')); condValRowDiv.appendChild(condValInp);
|
|
1263
|
+
oidSection.appendChild(oidLabel); oidSection.appendChild(condRowDiv); oidSection.appendChild(condValRowDiv);
|
|
1264
|
+
body.appendChild(oidSection);
|
|
1265
|
+
triggerSel.addEventListener('change', () => { oidSection.style.display = triggerSel.value === 'oid' ? '' : 'none'; });
|
|
1266
|
+
|
|
1267
|
+
// Glow section
|
|
1268
|
+
const glowSection = document.createElement('div');
|
|
1269
|
+
glowSection.style.display = cfg.type === 'glow' ? '' : 'none';
|
|
1270
|
+
glowSection.appendChild(field('Glow Color', 'glowColor', glowColorInp));
|
|
1271
|
+
glowSection.appendChild(field('Glow Size', 'glowSize', glowSizeInp));
|
|
1272
|
+
body.appendChild(glowSection);
|
|
1273
|
+
typeSel.addEventListener('change', () => { glowSection.style.display = typeSel.value === 'glow' ? '' : 'none'; });
|
|
1274
|
+
|
|
1275
|
+
// Blur section
|
|
1276
|
+
const blurSection = document.createElement('div');
|
|
1277
|
+
blurSection.style.display = cfg.type === 'blur' ? '' : 'none';
|
|
1278
|
+
blurSection.appendChild(field('Blur (px)', 'blurAmount', blurInp));
|
|
1279
|
+
body.appendChild(blurSection);
|
|
1280
|
+
typeSel.addEventListener('change', () => { blurSection.style.display = typeSel.value === 'blur' ? '' : 'none'; });
|
|
1281
|
+
|
|
1282
|
+
// _collect: read current UI values back into a plain object
|
|
1283
|
+
cfg._collect = () => {
|
|
1284
|
+
const out = {};
|
|
1285
|
+
if (typeSel.value) out.type = typeSel.value;
|
|
1286
|
+
out.trigger = triggerSel.value || 'load';
|
|
1287
|
+
out.duration = parseFloat(durationInp.value) || 0.5;
|
|
1288
|
+
if (parseFloat(delayInp.value)) out.delay = parseFloat(delayInp.value);
|
|
1289
|
+
if (parseInt(repeatInp.value)) out.repeat = parseInt(repeatInp.value);
|
|
1290
|
+
out.ease = easeSel.value || 'power2.out';
|
|
1291
|
+
if (triggerSel.value === 'oid') { out.condition = condSel.value; out.conditionValue = condValInp.value; }
|
|
1292
|
+
if (typeSel.value === 'glow') { out.glowColor = glowColorInp.value; out.glowSize = parseInt(glowSizeInp.value) || 10; }
|
|
1293
|
+
if (typeSel.value === 'blur') out.blurAmount = parseInt(blurInp.value) || 5;
|
|
1294
|
+
for (const [k, v] of Object.entries(cfg)) { if (k.endsWith('_bind') && v) out[k] = v; }
|
|
1295
|
+
return out;
|
|
1296
|
+
};
|
|
1297
|
+
|
|
1298
|
+
wrapper.appendChild(header);
|
|
1299
|
+
wrapper.appendChild(body);
|
|
1300
|
+
return wrapper;
|
|
1301
|
+
};
|
|
1218
1302
|
|
|
1219
|
-
|
|
1220
|
-
clearBtn.textContent = 'Clear';
|
|
1221
|
-
clearBtn.style.cssText = 'float:right;font-size:10px;padding:2px 6px;cursor:pointer;border:1px solid #ccc;border-radius:3px;background:#fff;margin-bottom:8px;';
|
|
1222
|
-
clearBtn.onclick = () => { designItem.removeAttribute('data-effects'); this._updateEffectsPanel(); };
|
|
1223
|
-
content.appendChild(clearBtn);
|
|
1224
|
-
|
|
1225
|
-
content.appendChild(field('Type', 'type', typeSel));
|
|
1226
|
-
content.appendChild(field('Trigger', null, triggerSel));
|
|
1227
|
-
content.appendChild(field('Duration (s)', 'duration', durationInp));
|
|
1228
|
-
content.appendChild(field('Delay (s)', 'delay', delayInp));
|
|
1229
|
-
content.appendChild(field('Repeat', 'repeat', repeatInp));
|
|
1230
|
-
content.appendChild(field('Ease', 'ease', easeSel));
|
|
1231
|
-
|
|
1232
|
-
const oidSection = document.createElement('div');
|
|
1233
|
-
oidSection.style.display = cfg.trigger === 'oid' ? '' : 'none';
|
|
1234
|
-
const oidLabel = document.createElement('div');
|
|
1235
|
-
oidLabel.style.cssText = 'display:flex;align-items:center;gap:4px;font-size:11px;font-weight:600;color:#555;margin-bottom:3px;padding-left:4px;';
|
|
1236
|
-
oidLabel.appendChild(this._makeBindSquare('oid', cfg, designItem, 'data-effects', saveAndRefresh));
|
|
1237
|
-
const oidLabelText = document.createElement('span');
|
|
1238
|
-
oidLabelText.textContent = 'OID';
|
|
1239
|
-
oidLabel.appendChild(oidLabelText);
|
|
1240
|
-
const condRowDiv = document.createElement('div');
|
|
1241
|
-
condRowDiv.style.cssText = 'display:flex;align-items:center;gap:4px;margin-bottom:4px;padding-left:4px;';
|
|
1242
|
-
condRowDiv.appendChild(this._makeBindSquare('condition', cfg, designItem, 'data-effects', saveAndRefresh));
|
|
1243
|
-
condRowDiv.appendChild(condSel);
|
|
1244
|
-
const condValRowDiv = document.createElement('div');
|
|
1245
|
-
condValRowDiv.style.cssText = 'display:flex;align-items:center;gap:4px;margin-bottom:6px;padding-left:4px;';
|
|
1246
|
-
condValRowDiv.appendChild(this._makeBindSquare('conditionValue', cfg, designItem, 'data-effects', saveAndRefresh));
|
|
1247
|
-
condValRowDiv.appendChild(condValInp);
|
|
1248
|
-
oidSection.appendChild(oidLabel);
|
|
1249
|
-
oidSection.appendChild(condRowDiv);
|
|
1250
|
-
oidSection.appendChild(condValRowDiv);
|
|
1251
|
-
content.appendChild(oidSection);
|
|
1252
|
-
triggerSel.addEventListener('change', () => { oidSection.style.display = triggerSel.value === 'oid' ? '' : 'none'; });
|
|
1253
|
-
|
|
1254
|
-
const glowSection = document.createElement('div');
|
|
1255
|
-
glowSection.style.display = cfg.type === 'glow' ? '' : 'none';
|
|
1256
|
-
glowSection.appendChild(field('Glow Color', 'glowColor', glowColorInp));
|
|
1257
|
-
glowSection.appendChild(field('Glow Size', 'glowSize', glowSizeInp));
|
|
1258
|
-
content.appendChild(glowSection);
|
|
1259
|
-
typeSel.addEventListener('change', () => { glowSection.style.display = typeSel.value === 'glow' ? '' : 'none'; });
|
|
1260
|
-
|
|
1261
|
-
const blurSection = document.createElement('div');
|
|
1262
|
-
blurSection.style.display = cfg.type === 'blur' ? '' : 'none';
|
|
1263
|
-
blurSection.appendChild(field('Blur (px)', 'blurAmount', blurInp));
|
|
1264
|
-
content.appendChild(blurSection);
|
|
1265
|
-
typeSel.addEventListener('change', () => { blurSection.style.display = typeSel.value === 'blur' ? '' : 'none'; });
|
|
1303
|
+
cfgList.forEach((cfg, i) => content.appendChild(buildEffectBlock(cfg, i)));
|
|
1266
1304
|
}
|
|
1267
1305
|
|
|
1268
1306
|
/* Move to a Dock Spawn Helper */
|
|
@@ -489,11 +489,17 @@ export async function scanAndApplyEffects(root) {
|
|
|
489
489
|
const elements = (root || document).querySelectorAll('[data-effects]');
|
|
490
490
|
for (const el of elements) {
|
|
491
491
|
try {
|
|
492
|
-
const
|
|
493
|
-
const
|
|
494
|
-
|
|
495
|
-
const
|
|
496
|
-
|
|
492
|
+
const raw = JSON.parse(el.getAttribute('data-effects'));
|
|
493
|
+
const cfgList = Array.isArray(raw) ? raw : [raw];
|
|
494
|
+
const existing = _activeEffects.get(el) || [];
|
|
495
|
+
for (const inst of existing) if (inst?.cleanup) inst.cleanup();
|
|
496
|
+
const instances = [];
|
|
497
|
+
for (const cfg of cfgList) {
|
|
498
|
+
if (!cfg || typeof cfg !== 'object') continue;
|
|
499
|
+
const cleanup = await _applyEffect(el, cfg);
|
|
500
|
+
instances.push({ cleanup });
|
|
501
|
+
}
|
|
502
|
+
_activeEffects.set(el, instances);
|
|
497
503
|
} catch (e) {
|
|
498
504
|
console.warn('[AnimationService] data-effects parse error on element:', el, e);
|
|
499
505
|
}
|
|
@@ -503,8 +509,9 @@ export async function scanAndApplyEffects(root) {
|
|
|
503
509
|
export function cleanupEffects(root) {
|
|
504
510
|
const elements = (root || document).querySelectorAll('[data-effects]');
|
|
505
511
|
for (const el of elements) {
|
|
506
|
-
const
|
|
507
|
-
if (inst?.cleanup)
|
|
512
|
+
const instances = _activeEffects.get(el) || [];
|
|
513
|
+
for (const inst of instances) if (inst?.cleanup) inst.cleanup();
|
|
514
|
+
_activeEffects.delete(el);
|
|
508
515
|
}
|
|
509
516
|
}
|
|
510
517
|
|
|
@@ -698,6 +698,7 @@ export class BindingsHelper {
|
|
|
698
698
|
let valuesObject = new Array(signals.length);
|
|
699
699
|
for (let i = 0; i < signals.length; i++) {
|
|
700
700
|
const s = signals[i];
|
|
701
|
+
if (s == null) continue; // ?prop resolved to undefined — skip, re-apply fires when property changes
|
|
701
702
|
if (s[0] === '?') {
|
|
702
703
|
if (root) {
|
|
703
704
|
const nm = s.substring(1);
|