iobroker.mywebui 1.37.63 → 1.37.64
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.
|
|
4
|
+
"version": "1.37.64",
|
|
5
5
|
"titleLang": {
|
|
6
6
|
"en": "mywebui",
|
|
7
7
|
"de": "mywebui",
|
|
@@ -337,7 +337,7 @@
|
|
|
337
337
|
"color": "#c8ffe1",
|
|
338
338
|
"order": 1
|
|
339
339
|
},
|
|
340
|
-
"installedFrom": "iobroker.mywebui
|
|
340
|
+
"installedFrom": "iobroker.mywebui"
|
|
341
341
|
},
|
|
342
342
|
"native": {
|
|
343
343
|
"licenseKey": "",
|
package/package.json
CHANGED
|
@@ -651,6 +651,73 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
|
|
|
651
651
|
content.appendChild(actionDiv);
|
|
652
652
|
}
|
|
653
653
|
|
|
654
|
+
// ─── Shared binding square helper ────────────────────────────────────────
|
|
655
|
+
|
|
656
|
+
_makeBindSquare(propKey, cfg, designItem, attrName, saveAndRefresh) {
|
|
657
|
+
const bindCfg = cfg[propKey + '_bind'] || null;
|
|
658
|
+
const signal = bindCfg?.signal || null;
|
|
659
|
+
|
|
660
|
+
const btn = document.createElement('div');
|
|
661
|
+
btn.title = signal ? `Bound: ${signal}\n(right-click: edit/clear)` : `Click to bind to ioBroker state`;
|
|
662
|
+
btn.style.cssText = 'width:11px;height:11px;min-width:11px;border:1px solid #596c7a;cursor:pointer;flex-shrink:0;box-sizing:border-box;'
|
|
663
|
+
+ (signal ? 'background:orange;' : 'background:transparent;');
|
|
664
|
+
|
|
665
|
+
const openEditor = () => {
|
|
666
|
+
const existingBinding = signal ? { bindableObjectNames: [signal], expression: bindCfg.expression || '', target: 'property' } : null;
|
|
667
|
+
const dynEdt = new IobrokerWebuiBindingsEditor(
|
|
668
|
+
{ name: propKey, propertyType: 'propertyAndAttribute' },
|
|
669
|
+
existingBinding, 'property', serviceContainer,
|
|
670
|
+
designItem.instanceServiceContainer, this
|
|
671
|
+
);
|
|
672
|
+
const cw = new IobrokerWebuiConfirmationWrapper();
|
|
673
|
+
cw.title = `Bind "${propKey}"`;
|
|
674
|
+
cw.appendChild(dynEdt);
|
|
675
|
+
const dlg = this.openDialog(cw, { x: 200, y: 200, width: 700, height: 460 });
|
|
676
|
+
cw.cancelClicked.on(() => dlg.close());
|
|
677
|
+
cw.okClicked.on(() => {
|
|
678
|
+
dlg.close();
|
|
679
|
+
const newSignal = dynEdt.objectNames;
|
|
680
|
+
if (newSignal) {
|
|
681
|
+
const bnd = { signal: newSignal };
|
|
682
|
+
if (dynEdt.expression) bnd.expression = dynEdt.expression;
|
|
683
|
+
cfg[propKey + '_bind'] = bnd;
|
|
684
|
+
} else {
|
|
685
|
+
delete cfg[propKey + '_bind'];
|
|
686
|
+
}
|
|
687
|
+
saveAndRefresh();
|
|
688
|
+
});
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
btn.onclick = openEditor;
|
|
692
|
+
|
|
693
|
+
btn.oncontextmenu = (e) => {
|
|
694
|
+
e.preventDefault();
|
|
695
|
+
const existing = document.getElementById('__animbind-ctx');
|
|
696
|
+
if (existing) existing.remove();
|
|
697
|
+
const menu = document.createElement('div');
|
|
698
|
+
menu.id = '__animbind-ctx';
|
|
699
|
+
menu.style.cssText = `position:fixed;left:${e.clientX}px;top:${e.clientY}px;background:#2d2d2d;color:#ddd;font-size:12px;border:1px solid #555;border-radius:3px;z-index:99999;min-width:120px;box-shadow:2px 2px 6px rgba(0,0,0,0.5);`;
|
|
700
|
+
const addItem = (text, cb, disabled = false) => {
|
|
701
|
+
const item = document.createElement('div');
|
|
702
|
+
item.textContent = text;
|
|
703
|
+
item.style.cssText = `padding:6px 12px;cursor:${disabled ? 'default' : 'pointer'};color:${disabled ? '#666' : '#ddd'};`;
|
|
704
|
+
if (!disabled) {
|
|
705
|
+
item.onmouseenter = () => item.style.background = '#3e6db4';
|
|
706
|
+
item.onmouseleave = () => item.style.background = '';
|
|
707
|
+
item.onclick = () => { menu.remove(); cb(); };
|
|
708
|
+
}
|
|
709
|
+
menu.appendChild(item);
|
|
710
|
+
};
|
|
711
|
+
addItem('edit binding', openEditor);
|
|
712
|
+
addItem('clear binding', () => { delete cfg[propKey + '_bind']; saveAndRefresh(); }, !signal);
|
|
713
|
+
document.body.appendChild(menu);
|
|
714
|
+
const close = (ev) => { if (!menu.contains(ev.target)) { menu.remove(); document.removeEventListener('mousedown', close); } };
|
|
715
|
+
setTimeout(() => document.addEventListener('mousedown', close), 0);
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
return btn;
|
|
719
|
+
}
|
|
720
|
+
|
|
654
721
|
// ─── Animations Panel ────────────────────────────────────────────────────
|
|
655
722
|
|
|
656
723
|
_setupAnimationsPanel() {
|
|
@@ -697,33 +764,78 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
|
|
|
697
764
|
try { cfg = JSON.parse(element.getAttribute('data-animation') || '{}'); } catch (e) {}
|
|
698
765
|
const controls = cfg.controls || {};
|
|
699
766
|
|
|
767
|
+
const collectAnimCfg = () => {
|
|
768
|
+
const e = effectSel.value;
|
|
769
|
+
const c = {};
|
|
770
|
+
const addCtrl = (key, row) => { const v = row._getCtrl(); if (v.oid) c[key] = v; };
|
|
771
|
+
addCtrl('play', playCtrl); addCtrl('pause', pauseCtrl);
|
|
772
|
+
addCtrl('resume', resumeCtrl); addCtrl('stop', stopCtrl);
|
|
773
|
+
addCtrl('reverse', reverseCtrl);
|
|
774
|
+
const out = {};
|
|
775
|
+
if (e) out.effect = e;
|
|
776
|
+
if (e === 'svg' && svgAttrSel.value) out.svgAttr = svgAttrSel.value;
|
|
777
|
+
if (valueFromInp.value) out.valueFrom = valueFromInp.value;
|
|
778
|
+
if (valueToInp.value) out.valueTo = valueToInp.value;
|
|
779
|
+
out.duration = parseFloat(durationInp.value) || 1;
|
|
780
|
+
out.ease = easeSel.value || 'power1.inOut';
|
|
781
|
+
out.repeat = parseInt(repeatInp.value) || 0;
|
|
782
|
+
if (yoyoChk.querySelector('input').checked) out.yoyo = true;
|
|
783
|
+
if (['rotation','scale'].includes(e)) {
|
|
784
|
+
out.transformOriginX = originXInp.value;
|
|
785
|
+
out.transformOriginY = originYInp.value;
|
|
786
|
+
}
|
|
787
|
+
if (e === 'fill' || e === 'svg') {
|
|
788
|
+
if (fillFromInp.value) out.fillColorFrom = fillFromInp.value;
|
|
789
|
+
if (fillToInp.value) out.fillColorTo = fillToInp.value;
|
|
790
|
+
}
|
|
791
|
+
if (e === 'motionPath') {
|
|
792
|
+
if (pathIdInp.value) out.pathId = pathIdInp.value;
|
|
793
|
+
if (alignToPathChk.querySelector('input').checked) out.alignToPath = true;
|
|
794
|
+
if (orientToPathChk.querySelector('input').checked) out.orientToPath = true;
|
|
795
|
+
}
|
|
796
|
+
if (Object.keys(c).length) out.controls = c;
|
|
797
|
+
// Preserve all _bind properties from cfg
|
|
798
|
+
for (const [k, v] of Object.entries(cfg)) {
|
|
799
|
+
if (k.endsWith('_bind') && v) out[k] = v;
|
|
800
|
+
}
|
|
801
|
+
return out;
|
|
802
|
+
};
|
|
803
|
+
|
|
700
804
|
const save = () => {
|
|
701
805
|
const newCfg = collectAnimCfg();
|
|
702
|
-
if (
|
|
806
|
+
if (!newCfg.effect && !newCfg.controls && !Object.keys(newCfg).some(k => k.endsWith('_bind'))) {
|
|
703
807
|
designItem.removeAttribute('data-animation');
|
|
704
808
|
} else {
|
|
705
809
|
designItem.setAttribute('data-animation', JSON.stringify(newCfg));
|
|
706
810
|
}
|
|
707
811
|
};
|
|
708
812
|
|
|
709
|
-
const
|
|
813
|
+
const saveAndRefresh = () => { save(); this._updateAnimationsPanel(); };
|
|
814
|
+
|
|
815
|
+
// field(label, propKey, inputEl) — propKey=null → no binding square
|
|
816
|
+
const field = (label, propKey, inputEl) => {
|
|
710
817
|
const row = document.createElement('div');
|
|
711
|
-
row.style.cssText = 'display:flex;align-items:center;gap:
|
|
818
|
+
row.style.cssText = 'display:flex;align-items:center;gap:4px;margin-bottom:6px;';
|
|
819
|
+
if (propKey) {
|
|
820
|
+
row.appendChild(this._makeBindSquare(propKey, cfg, designItem, 'data-animation', saveAndRefresh));
|
|
821
|
+
} else {
|
|
822
|
+
const sp = document.createElement('div');
|
|
823
|
+
sp.style.cssText = 'width:11px;min-width:11px;flex-shrink:0;';
|
|
824
|
+
row.appendChild(sp);
|
|
825
|
+
}
|
|
712
826
|
const lbl = document.createElement('span');
|
|
713
827
|
lbl.textContent = label;
|
|
714
|
-
lbl.style.cssText = 'min-width:
|
|
828
|
+
lbl.style.cssText = 'min-width:84px;font-size:11px;color:#555;';
|
|
715
829
|
row.appendChild(lbl);
|
|
716
830
|
row.appendChild(inputEl);
|
|
717
831
|
return row;
|
|
718
832
|
};
|
|
719
833
|
|
|
720
|
-
const inp = (val, type = 'text'
|
|
834
|
+
const inp = (val, type = 'text') => {
|
|
721
835
|
const i = document.createElement('input');
|
|
722
|
-
i.type = type;
|
|
723
|
-
i.
|
|
724
|
-
i.
|
|
725
|
-
i.onchange = save;
|
|
726
|
-
return i;
|
|
836
|
+
i.type = type; i.value = val ?? '';
|
|
837
|
+
i.style.cssText = 'flex:1;padding:3px 5px;font-size:11px;border:1px solid #ccc;border-radius:3px;';
|
|
838
|
+
i.onchange = save; return i;
|
|
727
839
|
};
|
|
728
840
|
|
|
729
841
|
const sel = (options, val) => {
|
|
@@ -734,37 +846,36 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
|
|
|
734
846
|
o.value = v; o.textContent = t; o.selected = v === val;
|
|
735
847
|
s.appendChild(o);
|
|
736
848
|
});
|
|
737
|
-
s.onchange = save;
|
|
738
|
-
return s;
|
|
849
|
+
s.onchange = save; return s;
|
|
739
850
|
};
|
|
740
851
|
|
|
741
|
-
const
|
|
742
|
-
const
|
|
743
|
-
|
|
852
|
+
const chkField = (propKey, label, val) => {
|
|
853
|
+
const row = document.createElement('div');
|
|
854
|
+
row.style.cssText = 'display:flex;align-items:center;gap:4px;margin-bottom:6px;';
|
|
855
|
+
row.appendChild(this._makeBindSquare(propKey, cfg, designItem, 'data-animation', saveAndRefresh));
|
|
856
|
+
const lbl = document.createElement('label');
|
|
857
|
+
lbl.style.cssText = 'display:flex;align-items:center;gap:4px;font-size:11px;cursor:pointer;';
|
|
744
858
|
const c = document.createElement('input');
|
|
745
859
|
c.type = 'checkbox'; c.checked = val === true || val === 'true';
|
|
746
860
|
c.onchange = save;
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
861
|
+
lbl.appendChild(c);
|
|
862
|
+
lbl.appendChild(document.createTextNode(label));
|
|
863
|
+
row.appendChild(lbl);
|
|
864
|
+
return row;
|
|
750
865
|
};
|
|
751
866
|
|
|
752
|
-
const oidRow = (label,
|
|
867
|
+
const oidRow = (label, ctrlCfg) => {
|
|
753
868
|
const wrap = document.createElement('div');
|
|
754
869
|
wrap.style.cssText = 'border:1px solid #eee;border-radius:3px;padding:6px;margin-bottom:6px;background:#fafafa;';
|
|
755
|
-
|
|
756
870
|
const head = document.createElement('div');
|
|
757
871
|
head.style.cssText = 'font-size:11px;font-weight:600;color:#444;margin-bottom:5px;';
|
|
758
872
|
head.textContent = label;
|
|
759
873
|
wrap.appendChild(head);
|
|
760
|
-
|
|
761
874
|
const oidInp = document.createElement('input');
|
|
762
|
-
oidInp.type = 'text';
|
|
763
|
-
oidInp.value = ctrlCfg.oid || '';
|
|
875
|
+
oidInp.type = 'text'; oidInp.value = ctrlCfg.oid || '';
|
|
764
876
|
oidInp.placeholder = 'OID…';
|
|
765
877
|
oidInp.style.cssText = 'width:100%;box-sizing:border-box;padding:3px 5px;font-size:11px;border:1px solid #ccc;border-radius:3px;margin-bottom:3px;';
|
|
766
878
|
oidInp.onchange = save;
|
|
767
|
-
|
|
768
879
|
const pickBtn = document.createElement('button');
|
|
769
880
|
pickBtn.textContent = '…';
|
|
770
881
|
pickBtn.style.cssText = 'padding:2px 6px;font-size:11px;cursor:pointer;border:1px solid #aaa;border-radius:3px;margin-left:3px;';
|
|
@@ -772,51 +883,39 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
|
|
|
772
883
|
const picked = await openSelectIdDialog(document.body, { id: oidInp.value });
|
|
773
884
|
if (picked) { oidInp.value = picked; save(); }
|
|
774
885
|
};
|
|
775
|
-
|
|
776
886
|
const oidRow2 = document.createElement('div');
|
|
777
887
|
oidRow2.style.cssText = 'display:flex;margin-bottom:3px;';
|
|
778
|
-
oidRow2.appendChild(oidInp);
|
|
779
|
-
oidRow2.appendChild(pickBtn);
|
|
888
|
+
oidRow2.appendChild(oidInp); oidRow2.appendChild(pickBtn);
|
|
780
889
|
wrap.appendChild(oidRow2);
|
|
781
|
-
|
|
782
890
|
const condSel = sel([
|
|
783
|
-
['equal','='],
|
|
784
|
-
['greater_than','>'],
|
|
891
|
+
['equal','='],['not_equal','≠'],['less_than','<'],['less_equal','≤'],
|
|
892
|
+
['greater_than','>'],['greater_equal','≥'],['exists','exists']
|
|
785
893
|
], ctrlCfg.condition || 'equal');
|
|
786
894
|
condSel.style.cssText += 'width:70px;flex:none;margin-right:4px;';
|
|
787
895
|
const valInp = document.createElement('input');
|
|
788
896
|
valInp.type = 'text'; valInp.value = ctrlCfg.value ?? 'true';
|
|
789
|
-
valInp.placeholder = 'value';
|
|
790
897
|
valInp.style.cssText = 'flex:1;padding:3px 5px;font-size:11px;border:1px solid #ccc;border-radius:3px;';
|
|
791
898
|
condSel.onchange = save; valInp.onchange = save;
|
|
792
|
-
|
|
793
899
|
const condRow = document.createElement('div');
|
|
794
900
|
condRow.style.cssText = 'display:flex;gap:3px;';
|
|
795
901
|
condRow.appendChild(condSel); condRow.appendChild(valInp);
|
|
796
902
|
wrap.appendChild(condRow);
|
|
797
|
-
|
|
798
|
-
wrap._getCtrl = () => ({
|
|
799
|
-
oid: oidInp.value || undefined,
|
|
800
|
-
condition: condSel.value,
|
|
801
|
-
value: valInp.value
|
|
802
|
-
});
|
|
903
|
+
wrap._getCtrl = () => ({ oid: oidInp.value || undefined, condition: condSel.value, value: valInp.value });
|
|
803
904
|
return wrap;
|
|
804
905
|
};
|
|
805
906
|
|
|
806
907
|
const effectSel = sel([
|
|
807
|
-
['',
|
|
808
|
-
['
|
|
809
|
-
['
|
|
810
|
-
['
|
|
811
|
-
['svg','SVG Attribute'], ['morphSVG','MorphSVG'], ['motionPath','Motion Path']
|
|
908
|
+
['','— none —'],['opacity','Opacity'],['rotation','Rotation'],['scale','Scale'],
|
|
909
|
+
['translate','Translate XY'],['translateX','Translate X'],['translateY','Translate Y'],
|
|
910
|
+
['skew','Skew'],['fill','Fill Color'],['transform','Transform (CSS)'],
|
|
911
|
+
['svg','SVG Attribute'],['morphSVG','MorphSVG'],['motionPath','Motion Path']
|
|
812
912
|
], cfg.effect || '');
|
|
813
913
|
|
|
814
914
|
const svgAttrSel = sel([
|
|
815
|
-
['fill','fill'],
|
|
816
|
-
['stroke-opacity','stroke-opacity'],
|
|
817
|
-
['stroke-dasharray','stroke-dasharray'],
|
|
818
|
-
['x','x'],
|
|
819
|
-
['r','r'], ['rx','rx'], ['ry','ry']
|
|
915
|
+
['fill','fill'],['color','color'],['fill-opacity','fill-opacity'],
|
|
916
|
+
['stroke-opacity','stroke-opacity'],['stroke-width','stroke-width'],
|
|
917
|
+
['stroke-dasharray','stroke-dasharray'],['stroke-dashoffset','stroke-dashoffset'],
|
|
918
|
+
['x','x'],['y','y'],['cx','cx'],['cy','cy'],['r','r'],['rx','rx'],['ry','ry']
|
|
820
919
|
], cfg.svgAttr || 'fill');
|
|
821
920
|
|
|
822
921
|
const easeSel = sel([
|
|
@@ -829,127 +928,82 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
|
|
|
829
928
|
], cfg.ease || 'power1.inOut');
|
|
830
929
|
|
|
831
930
|
const valueFromInp = inp(cfg.valueFrom, 'text');
|
|
832
|
-
const valueToInp
|
|
833
|
-
const durationInp
|
|
834
|
-
const repeatInp
|
|
835
|
-
const
|
|
836
|
-
const
|
|
837
|
-
const
|
|
838
|
-
const
|
|
839
|
-
const
|
|
840
|
-
|
|
841
|
-
const
|
|
842
|
-
const
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
const
|
|
846
|
-
const
|
|
847
|
-
const
|
|
848
|
-
const
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
const c = {};
|
|
853
|
-
const addCtrl = (key, row) => {
|
|
854
|
-
const v = row._getCtrl();
|
|
855
|
-
if (v.oid) c[key] = v;
|
|
856
|
-
};
|
|
857
|
-
addCtrl('play', playCtrl); addCtrl('pause', pauseCtrl);
|
|
858
|
-
addCtrl('resume', resumeCtrl); addCtrl('stop', stopCtrl);
|
|
859
|
-
addCtrl('reverse', reverseCtrl);
|
|
860
|
-
|
|
861
|
-
const out = {};
|
|
862
|
-
if (e) out.effect = e;
|
|
863
|
-
if (e === 'svg' && svgAttrSel.value) out.svgAttr = svgAttrSel.value;
|
|
864
|
-
if (valueFromInp.value) out.valueFrom = valueFromInp.value;
|
|
865
|
-
if (valueToInp.value) out.valueTo = valueToInp.value;
|
|
866
|
-
out.duration = parseFloat(durationInp.value) || 1;
|
|
867
|
-
out.ease = easeSel.value || 'power1.inOut';
|
|
868
|
-
out.repeat = parseInt(repeatInp.value) || 0;
|
|
869
|
-
if (yoyoChk.querySelector('input').checked) out.yoyo = true;
|
|
870
|
-
if (['rotation','scale'].includes(e)) {
|
|
871
|
-
out.transformOriginX = originXInp.value;
|
|
872
|
-
out.transformOriginY = originYInp.value;
|
|
873
|
-
}
|
|
874
|
-
if (e === 'fill' || e === 'svg') {
|
|
875
|
-
if (fillFromInp.value) out.fillColorFrom = fillFromInp.value;
|
|
876
|
-
if (fillToInp.value) out.fillColorTo = fillToInp.value;
|
|
877
|
-
}
|
|
878
|
-
if (e === 'motionPath') {
|
|
879
|
-
if (pathIdInp.value) out.pathId = pathIdInp.value;
|
|
880
|
-
if (alignToPathChk.querySelector('input').checked) out.alignToPath = true;
|
|
881
|
-
if (orientToPathChk.querySelector('input').checked) out.orientToPath = true;
|
|
882
|
-
}
|
|
883
|
-
if (Object.keys(c).length) out.controls = c;
|
|
884
|
-
return out;
|
|
885
|
-
};
|
|
886
|
-
|
|
887
|
-
// Build UI
|
|
931
|
+
const valueToInp = inp(cfg.valueTo, 'text');
|
|
932
|
+
const durationInp = inp(cfg.duration ?? 1, 'number');
|
|
933
|
+
const repeatInp = inp(cfg.repeat ?? 0, 'number');
|
|
934
|
+
const originXInp = inp(cfg.transformOriginX ?? '50', 'number');
|
|
935
|
+
const originYInp = inp(cfg.transformOriginY ?? '50', 'number');
|
|
936
|
+
const fillFromInp = inp(cfg.fillColorFrom || '#000000', 'color');
|
|
937
|
+
const fillToInp = inp(cfg.fillColorTo || '#ff0000', 'color');
|
|
938
|
+
const pathIdInp = inp(cfg.pathId, 'text');
|
|
939
|
+
|
|
940
|
+
const yoyoChk = chkField('yoyo', 'Yoyo', cfg.yoyo);
|
|
941
|
+
const alignToPathChk = chkField('alignToPath', 'Align to path', cfg.alignToPath);
|
|
942
|
+
const orientToPathChk= chkField('orientToPath', 'Auto-rotate', cfg.orientToPath);
|
|
943
|
+
|
|
944
|
+
const playCtrl = oidRow('▶ Play', controls.play || {});
|
|
945
|
+
const pauseCtrl = oidRow('⏸ Pause', controls.pause || {});
|
|
946
|
+
const resumeCtrl = oidRow('▶ Resume', controls.resume || {});
|
|
947
|
+
const stopCtrl = oidRow('⏹ Stop', controls.stop || {});
|
|
948
|
+
const reverseCtrl = oidRow('◀ Reverse', controls.reverse || {});
|
|
949
|
+
|
|
950
|
+
// ── Build UI ──────────────────────────────────────────────────────────
|
|
888
951
|
content.innerHTML = '';
|
|
889
952
|
|
|
890
|
-
// Clear button
|
|
891
953
|
const clearBtn = document.createElement('button');
|
|
892
|
-
clearBtn.textContent = 'Clear
|
|
954
|
+
clearBtn.textContent = 'Clear';
|
|
893
955
|
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;';
|
|
894
956
|
clearBtn.onclick = () => { designItem.removeAttribute('data-animation'); this._updateAnimationsPanel(); };
|
|
895
957
|
content.appendChild(clearBtn);
|
|
896
958
|
|
|
897
959
|
const sec = (title) => {
|
|
898
960
|
const d = document.createElement('div');
|
|
899
|
-
d.style.cssText = 'font-size:11px;font-weight:700;color:#555;margin:10px 0 5px;padding-top:8px;border-top:1px solid #eee;';
|
|
961
|
+
d.style.cssText = 'font-size:11px;font-weight:700;color:#555;margin:10px 0 5px;padding-top:8px;border-top:1px solid #eee;clear:both;';
|
|
900
962
|
d.textContent = title;
|
|
901
963
|
return d;
|
|
902
964
|
};
|
|
903
965
|
|
|
904
966
|
content.appendChild(sec('Effect'));
|
|
905
|
-
content.appendChild(field('Type', effectSel));
|
|
967
|
+
content.appendChild(field('Type', 'effect', effectSel));
|
|
906
968
|
|
|
907
|
-
const svgAttrRow = field('SVG Attr', svgAttrSel);
|
|
969
|
+
const svgAttrRow = field('SVG Attr', 'svgAttr', svgAttrSel);
|
|
908
970
|
svgAttrRow.style.display = cfg.effect === 'svg' ? '' : 'none';
|
|
909
971
|
content.appendChild(svgAttrRow);
|
|
910
|
-
effectSel.addEventListener('change', () => {
|
|
911
|
-
svgAttrRow.style.display = effectSel.value === 'svg' ? '' : 'none';
|
|
912
|
-
});
|
|
972
|
+
effectSel.addEventListener('change', () => { svgAttrRow.style.display = effectSel.value === 'svg' ? '' : 'none'; });
|
|
913
973
|
|
|
914
|
-
content.appendChild(field('Value From', valueFromInp));
|
|
915
|
-
content.appendChild(field('Value To', valueToInp));
|
|
974
|
+
content.appendChild(field('Value From', 'valueFrom', valueFromInp));
|
|
975
|
+
content.appendChild(field('Value To', 'valueTo', valueToInp));
|
|
916
976
|
|
|
917
977
|
const colorRows = document.createElement('div');
|
|
918
978
|
colorRows.style.display = (cfg.effect === 'fill' || cfg.effect === 'svg') ? '' : 'none';
|
|
919
|
-
colorRows.appendChild(field('Color From', fillFromInp));
|
|
920
|
-
colorRows.appendChild(field('Color To', fillToInp));
|
|
979
|
+
colorRows.appendChild(field('Color From', 'fillColorFrom', fillFromInp));
|
|
980
|
+
colorRows.appendChild(field('Color To', 'fillColorTo', fillToInp));
|
|
921
981
|
content.appendChild(colorRows);
|
|
922
|
-
effectSel.addEventListener('change', () => {
|
|
923
|
-
colorRows.style.display = (effectSel.value === 'fill' || effectSel.value === 'svg') ? '' : 'none';
|
|
924
|
-
});
|
|
982
|
+
effectSel.addEventListener('change', () => { colorRows.style.display = (effectSel.value === 'fill' || effectSel.value === 'svg') ? '' : 'none'; });
|
|
925
983
|
|
|
926
984
|
const motionRows = document.createElement('div');
|
|
927
985
|
motionRows.style.display = cfg.effect === 'motionPath' ? '' : 'none';
|
|
928
|
-
motionRows.appendChild(field('Path ID', pathIdInp));
|
|
986
|
+
motionRows.appendChild(field('Path ID', 'pathId', pathIdInp));
|
|
929
987
|
motionRows.appendChild(alignToPathChk);
|
|
930
988
|
motionRows.appendChild(orientToPathChk);
|
|
931
989
|
content.appendChild(motionRows);
|
|
932
|
-
effectSel.addEventListener('change', () => {
|
|
933
|
-
motionRows.style.display = effectSel.value === 'motionPath' ? '' : 'none';
|
|
934
|
-
});
|
|
990
|
+
effectSel.addEventListener('change', () => { motionRows.style.display = effectSel.value === 'motionPath' ? '' : 'none'; });
|
|
935
991
|
|
|
936
992
|
content.appendChild(sec('Timing'));
|
|
937
|
-
content.appendChild(field('Duration (s)', durationInp));
|
|
938
|
-
content.appendChild(field('Ease', easeSel));
|
|
939
|
-
content.appendChild(field('Repeat', repeatInp));
|
|
993
|
+
content.appendChild(field('Duration (s)', 'duration', durationInp));
|
|
994
|
+
content.appendChild(field('Ease', 'ease', easeSel));
|
|
995
|
+
content.appendChild(field('Repeat', 'repeat', repeatInp));
|
|
940
996
|
content.appendChild(yoyoChk);
|
|
941
997
|
|
|
942
998
|
const originRows = document.createElement('div');
|
|
943
999
|
originRows.style.display = (cfg.effect === 'rotation' || cfg.effect === 'scale') ? '' : 'none';
|
|
944
1000
|
originRows.appendChild(sec('Transform Origin'));
|
|
945
|
-
originRows.appendChild(field('Origin X (%)', originXInp));
|
|
946
|
-
originRows.appendChild(field('Origin Y (%)', originYInp));
|
|
1001
|
+
originRows.appendChild(field('Origin X (%)', 'transformOriginX', originXInp));
|
|
1002
|
+
originRows.appendChild(field('Origin Y (%)', 'transformOriginY', originYInp));
|
|
947
1003
|
content.appendChild(originRows);
|
|
948
|
-
effectSel.addEventListener('change', () => {
|
|
949
|
-
originRows.style.display = (effectSel.value === 'rotation' || effectSel.value === 'scale') ? '' : 'none';
|
|
950
|
-
});
|
|
1004
|
+
effectSel.addEventListener('change', () => { originRows.style.display = (effectSel.value === 'rotation' || effectSel.value === 'scale') ? '' : 'none'; });
|
|
951
1005
|
|
|
952
|
-
content.appendChild(sec('Controls (OID
|
|
1006
|
+
content.appendChild(sec('Controls (OID triggers)'));
|
|
953
1007
|
content.appendChild(playCtrl);
|
|
954
1008
|
content.appendChild(pauseCtrl);
|
|
955
1009
|
content.appendChild(resumeCtrl);
|
|
@@ -1002,18 +1056,52 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
|
|
|
1002
1056
|
let cfg = {};
|
|
1003
1057
|
try { cfg = JSON.parse(element.getAttribute('data-effects') || '{}'); } catch (e) {}
|
|
1004
1058
|
|
|
1059
|
+
const collectEffectsCfg = () => {
|
|
1060
|
+
const out = {};
|
|
1061
|
+
if (typeSel.value) out.type = typeSel.value;
|
|
1062
|
+
out.trigger = triggerSel.value || 'load';
|
|
1063
|
+
out.duration = parseFloat(durationInp.value) || 0.5;
|
|
1064
|
+
if (parseFloat(delayInp.value)) out.delay = parseFloat(delayInp.value);
|
|
1065
|
+
if (parseInt(repeatInp.value)) out.repeat = parseInt(repeatInp.value);
|
|
1066
|
+
out.ease = easeSel.value || 'power2.out';
|
|
1067
|
+
if (triggerSel.value === 'oid' && oidInp.value) {
|
|
1068
|
+
out.oid = oidInp.value;
|
|
1069
|
+
out.condition = condSel.value;
|
|
1070
|
+
out.conditionValue = condValInp.value;
|
|
1071
|
+
}
|
|
1072
|
+
if (typeSel.value === 'glow') {
|
|
1073
|
+
out.glowColor = glowColorInp.value;
|
|
1074
|
+
out.glowSize = parseInt(glowSizeInp.value) || 10;
|
|
1075
|
+
}
|
|
1076
|
+
if (typeSel.value === 'blur') out.blurAmount = parseInt(blurInp.value) || 5;
|
|
1077
|
+
// Preserve all _bind properties
|
|
1078
|
+
for (const [k, v] of Object.entries(cfg)) {
|
|
1079
|
+
if (k.endsWith('_bind') && v) out[k] = v;
|
|
1080
|
+
}
|
|
1081
|
+
return out;
|
|
1082
|
+
};
|
|
1083
|
+
|
|
1005
1084
|
const save = () => {
|
|
1006
1085
|
const c = collectEffectsCfg();
|
|
1007
1086
|
if (!c.type) designItem.removeAttribute('data-effects');
|
|
1008
1087
|
else designItem.setAttribute('data-effects', JSON.stringify(c));
|
|
1009
1088
|
};
|
|
1010
1089
|
|
|
1011
|
-
const
|
|
1090
|
+
const saveAndRefresh = () => { save(); this._updateEffectsPanel(); };
|
|
1091
|
+
|
|
1092
|
+
const field = (label, propKey, inputEl) => {
|
|
1012
1093
|
const row = document.createElement('div');
|
|
1013
|
-
row.style.cssText = 'display:flex;align-items:center;gap:
|
|
1094
|
+
row.style.cssText = 'display:flex;align-items:center;gap:4px;margin-bottom:6px;';
|
|
1095
|
+
if (propKey) {
|
|
1096
|
+
row.appendChild(this._makeBindSquare(propKey, cfg, designItem, 'data-effects', saveAndRefresh));
|
|
1097
|
+
} else {
|
|
1098
|
+
const sp = document.createElement('div');
|
|
1099
|
+
sp.style.cssText = 'width:11px;min-width:11px;flex-shrink:0;';
|
|
1100
|
+
row.appendChild(sp);
|
|
1101
|
+
}
|
|
1014
1102
|
const lbl = document.createElement('span');
|
|
1015
1103
|
lbl.textContent = label;
|
|
1016
|
-
lbl.style.cssText = 'min-width:
|
|
1104
|
+
lbl.style.cssText = 'min-width:78px;font-size:11px;color:#555;';
|
|
1017
1105
|
row.appendChild(lbl); row.appendChild(inputEl);
|
|
1018
1106
|
return row;
|
|
1019
1107
|
};
|
|
@@ -1049,107 +1137,75 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
|
|
|
1049
1137
|
], cfg.trigger || 'load');
|
|
1050
1138
|
|
|
1051
1139
|
const durationInp = inp(cfg.duration ?? 0.5, 'number');
|
|
1052
|
-
const delayInp
|
|
1053
|
-
const repeatInp
|
|
1140
|
+
const delayInp = inp(cfg.delay ?? 0, 'number');
|
|
1141
|
+
const repeatInp = inp(cfg.repeat ?? 0, 'number');
|
|
1054
1142
|
|
|
1055
1143
|
const easeSel = sel([
|
|
1056
1144
|
['power2.out','power2.out'],['power2.in','power2.in'],['power2.inOut','power2.inOut'],
|
|
1057
1145
|
['power1.inOut','power1.inOut'],['bounce.out','bounce.out'],
|
|
1058
|
-
['elastic.out(1,0.3)','elastic.out'],['back.inOut(1.7)','back.inOut'],
|
|
1059
|
-
['none','none']
|
|
1146
|
+
['elastic.out(1,0.3)','elastic.out'],['back.inOut(1.7)','back.inOut'],['none','none']
|
|
1060
1147
|
], cfg.ease || 'power2.out');
|
|
1061
1148
|
|
|
1062
|
-
const oidInp
|
|
1063
|
-
const condSel
|
|
1064
|
-
['equal','='],['not_equal','≠'],['less_than','<'],['greater_than','>'],['exists','exists']
|
|
1065
|
-
], cfg.condition || 'equal');
|
|
1149
|
+
const oidInp = inp(cfg.oid || '');
|
|
1150
|
+
const condSel = sel([['equal','='],['not_equal','≠'],['less_than','<'],['greater_than','>'],['exists','exists']], cfg.condition || 'equal');
|
|
1066
1151
|
const condValInp = inp(cfg.conditionValue ?? 'true');
|
|
1067
|
-
|
|
1068
1152
|
const glowColorInp = inp(cfg.glowColor || 'yellow', 'color');
|
|
1069
|
-
const glowSizeInp
|
|
1070
|
-
const blurInp
|
|
1153
|
+
const glowSizeInp = inp(cfg.glowSize || 10, 'number');
|
|
1154
|
+
const blurInp = inp(cfg.blurAmount || 5, 'number');
|
|
1071
1155
|
|
|
1072
1156
|
const oidPickBtn = document.createElement('button');
|
|
1073
1157
|
oidPickBtn.textContent = '…';
|
|
1074
|
-
oidPickBtn.style.cssText = 'padding:2px 6px;font-size:11px;cursor:pointer;border:1px solid #aaa;border-radius:3px;';
|
|
1158
|
+
oidPickBtn.style.cssText = 'padding:2px 6px;font-size:11px;cursor:pointer;border:1px solid #aaa;border-radius:3px;flex-shrink:0;';
|
|
1075
1159
|
oidPickBtn.onclick = async () => {
|
|
1076
1160
|
const picked = await openSelectIdDialog(document.body, { id: oidInp.value });
|
|
1077
1161
|
if (picked) { oidInp.value = picked; save(); }
|
|
1078
1162
|
};
|
|
1079
1163
|
|
|
1080
|
-
|
|
1081
|
-
const out = {};
|
|
1082
|
-
if (typeSel.value) out.type = typeSel.value;
|
|
1083
|
-
out.trigger = triggerSel.value || 'load';
|
|
1084
|
-
out.duration = parseFloat(durationInp.value) || 0.5;
|
|
1085
|
-
if (parseFloat(delayInp.value)) out.delay = parseFloat(delayInp.value);
|
|
1086
|
-
if (parseInt(repeatInp.value)) out.repeat = parseInt(repeatInp.value);
|
|
1087
|
-
out.ease = easeSel.value || 'power2.out';
|
|
1088
|
-
if (triggerSel.value === 'oid' && oidInp.value) {
|
|
1089
|
-
out.oid = oidInp.value;
|
|
1090
|
-
out.condition = condSel.value;
|
|
1091
|
-
out.conditionValue = condValInp.value;
|
|
1092
|
-
}
|
|
1093
|
-
if (typeSel.value === 'glow') {
|
|
1094
|
-
out.glowColor = glowColorInp.value;
|
|
1095
|
-
out.glowSize = parseInt(glowSizeInp.value) || 10;
|
|
1096
|
-
}
|
|
1097
|
-
if (typeSel.value === 'blur') out.blurAmount = parseInt(blurInp.value) || 5;
|
|
1098
|
-
return out;
|
|
1099
|
-
};
|
|
1100
|
-
|
|
1164
|
+
// ── Build UI ──────────────────────────────────────────────────────────
|
|
1101
1165
|
content.innerHTML = '';
|
|
1102
1166
|
|
|
1103
1167
|
const clearBtn = document.createElement('button');
|
|
1104
|
-
clearBtn.textContent = 'Clear
|
|
1168
|
+
clearBtn.textContent = 'Clear';
|
|
1105
1169
|
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;';
|
|
1106
1170
|
clearBtn.onclick = () => { designItem.removeAttribute('data-effects'); this._updateEffectsPanel(); };
|
|
1107
1171
|
content.appendChild(clearBtn);
|
|
1108
1172
|
|
|
1109
|
-
content.appendChild(field('Type', typeSel));
|
|
1110
|
-
content.appendChild(field('Trigger', triggerSel));
|
|
1111
|
-
content.appendChild(field('Duration (s)', durationInp));
|
|
1112
|
-
content.appendChild(field('Delay (s)', delayInp));
|
|
1113
|
-
content.appendChild(field('Repeat', repeatInp));
|
|
1114
|
-
content.appendChild(field('Ease', easeSel));
|
|
1173
|
+
content.appendChild(field('Type', 'type', typeSel));
|
|
1174
|
+
content.appendChild(field('Trigger', null, triggerSel));
|
|
1175
|
+
content.appendChild(field('Duration (s)', 'duration', durationInp));
|
|
1176
|
+
content.appendChild(field('Delay (s)', 'delay', delayInp));
|
|
1177
|
+
content.appendChild(field('Repeat', 'repeat', repeatInp));
|
|
1178
|
+
content.appendChild(field('Ease', 'ease', easeSel));
|
|
1115
1179
|
|
|
1116
1180
|
const oidSection = document.createElement('div');
|
|
1117
1181
|
oidSection.style.display = cfg.trigger === 'oid' ? '' : 'none';
|
|
1118
|
-
const oidRow = document.createElement('div');
|
|
1119
|
-
oidRow.style.cssText = 'display:flex;gap:3px;margin-bottom:6px;';
|
|
1120
|
-
oidRow.appendChild(oidInp); oidRow.appendChild(oidPickBtn);
|
|
1121
|
-
oidSection.appendChild(field('OID', document.createElement('span')));
|
|
1122
|
-
oidSection.lastChild.remove();
|
|
1123
1182
|
const oidLabel = document.createElement('div');
|
|
1124
|
-
oidLabel.style.cssText = 'font-size:11px;font-weight:600;color:#555;margin-bottom:3px;';
|
|
1183
|
+
oidLabel.style.cssText = 'font-size:11px;font-weight:600;color:#555;margin-bottom:3px;padding-left:15px;';
|
|
1125
1184
|
oidLabel.textContent = 'OID';
|
|
1185
|
+
const oidRowDiv = document.createElement('div');
|
|
1186
|
+
oidRowDiv.style.cssText = 'display:flex;gap:3px;margin-bottom:4px;padding-left:15px;';
|
|
1187
|
+
oidRowDiv.appendChild(oidInp); oidRowDiv.appendChild(oidPickBtn);
|
|
1188
|
+
const condRowDiv = document.createElement('div');
|
|
1189
|
+
condRowDiv.style.cssText = 'display:flex;gap:3px;margin-bottom:6px;padding-left:15px;';
|
|
1190
|
+
condRowDiv.appendChild(condSel); condRowDiv.appendChild(condValInp);
|
|
1126
1191
|
oidSection.appendChild(oidLabel);
|
|
1127
|
-
oidSection.appendChild(
|
|
1128
|
-
|
|
1129
|
-
condRow.style.cssText = 'display:flex;gap:3px;margin-bottom:6px;';
|
|
1130
|
-
condRow.appendChild(condSel); condRow.appendChild(condValInp);
|
|
1131
|
-
oidSection.appendChild(condRow);
|
|
1192
|
+
oidSection.appendChild(oidRowDiv);
|
|
1193
|
+
oidSection.appendChild(condRowDiv);
|
|
1132
1194
|
content.appendChild(oidSection);
|
|
1133
|
-
triggerSel.addEventListener('change', () => {
|
|
1134
|
-
oidSection.style.display = triggerSel.value === 'oid' ? '' : 'none';
|
|
1135
|
-
});
|
|
1195
|
+
triggerSel.addEventListener('change', () => { oidSection.style.display = triggerSel.value === 'oid' ? '' : 'none'; });
|
|
1136
1196
|
|
|
1137
1197
|
const glowSection = document.createElement('div');
|
|
1138
1198
|
glowSection.style.display = cfg.type === 'glow' ? '' : 'none';
|
|
1139
|
-
glowSection.appendChild(field('Glow Color', glowColorInp));
|
|
1140
|
-
glowSection.appendChild(field('Glow Size
|
|
1199
|
+
glowSection.appendChild(field('Glow Color', 'glowColor', glowColorInp));
|
|
1200
|
+
glowSection.appendChild(field('Glow Size', 'glowSize', glowSizeInp));
|
|
1141
1201
|
content.appendChild(glowSection);
|
|
1142
|
-
typeSel.addEventListener('change', () => {
|
|
1143
|
-
glowSection.style.display = typeSel.value === 'glow' ? '' : 'none';
|
|
1144
|
-
});
|
|
1202
|
+
typeSel.addEventListener('change', () => { glowSection.style.display = typeSel.value === 'glow' ? '' : 'none'; });
|
|
1145
1203
|
|
|
1146
1204
|
const blurSection = document.createElement('div');
|
|
1147
1205
|
blurSection.style.display = cfg.type === 'blur' ? '' : 'none';
|
|
1148
|
-
blurSection.appendChild(field('Blur (px)', blurInp));
|
|
1206
|
+
blurSection.appendChild(field('Blur (px)', 'blurAmount', blurInp));
|
|
1149
1207
|
content.appendChild(blurSection);
|
|
1150
|
-
typeSel.addEventListener('change', () => {
|
|
1151
|
-
blurSection.style.display = typeSel.value === 'blur' ? '' : 'none';
|
|
1152
|
-
});
|
|
1208
|
+
typeSel.addEventListener('change', () => { blurSection.style.display = typeSel.value === 'blur' ? '' : 'none'; });
|
|
1153
1209
|
}
|
|
1154
1210
|
|
|
1155
1211
|
/* Move to a Dock Spawn Helper */
|
|
@@ -176,6 +176,33 @@ class AnimationInstance {
|
|
|
176
176
|
bindControl('resume', () => this.tween?.play());
|
|
177
177
|
bindControl('stop', () => this.tween?.pause(0));
|
|
178
178
|
bindControl('reverse', () => this.tween?.reverse());
|
|
179
|
+
|
|
180
|
+
// Dynamic property bindings — each prop can have a propKey_bind: {signal, expression}
|
|
181
|
+
const dynamicProps = [
|
|
182
|
+
'effect', 'valueFrom', 'valueTo', 'duration', 'ease', 'repeat', 'yoyo',
|
|
183
|
+
'fillColorFrom', 'fillColorTo', 'transformOriginX', 'transformOriginY',
|
|
184
|
+
'svgAttr', 'pathId', 'alignToPath', 'orientToPath'
|
|
185
|
+
];
|
|
186
|
+
for (const prop of dynamicProps) {
|
|
187
|
+
const bindCfg = this.cfg[prop + '_bind'];
|
|
188
|
+
if (!bindCfg?.signal) continue;
|
|
189
|
+
const propCapture = prop;
|
|
190
|
+
const handler = (id, state) => {
|
|
191
|
+
if (state?.val != null) {
|
|
192
|
+
this.cfg[propCapture] = state.val;
|
|
193
|
+
// Rebuild tween if currently playing
|
|
194
|
+
if (this.tween && !this.tween.paused()) this._play();
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
try {
|
|
198
|
+
iobrokerHandler.connection.subscribeState(bindCfg.signal, handler);
|
|
199
|
+
this._subs.push({ oid: bindCfg.signal, handler });
|
|
200
|
+
} catch (e) {}
|
|
201
|
+
try {
|
|
202
|
+
const state = await iobrokerHandler.connection.getState(bindCfg.signal);
|
|
203
|
+
if (state?.val != null) this.cfg[prop] = state.val;
|
|
204
|
+
} catch (e) {}
|
|
205
|
+
}
|
|
179
206
|
}
|
|
180
207
|
|
|
181
208
|
_play() {
|