iobroker.mywebui 1.37.63 → 1.37.65
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.65",
|
|
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 || v.oid_bind) 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,39 @@ 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
|
-
head.style.cssText = 'font-size:11px;font-weight:600;color:#444;margin-bottom:5px;';
|
|
758
|
-
head.
|
|
871
|
+
head.style.cssText = 'display:flex;align-items:center;gap:4px;font-size:11px;font-weight:600;color:#444;margin-bottom:5px;';
|
|
872
|
+
head.appendChild(this._makeBindSquare('oid', ctrlCfg, designItem, 'data-animation', saveAndRefresh));
|
|
873
|
+
const headText = document.createElement('span');
|
|
874
|
+
headText.textContent = label;
|
|
875
|
+
head.appendChild(headText);
|
|
759
876
|
wrap.appendChild(head);
|
|
760
|
-
|
|
761
877
|
const oidInp = document.createElement('input');
|
|
762
|
-
oidInp.type = 'text';
|
|
763
|
-
oidInp.value = ctrlCfg.oid || '';
|
|
878
|
+
oidInp.type = 'text'; oidInp.value = ctrlCfg.oid || '';
|
|
764
879
|
oidInp.placeholder = 'OID…';
|
|
765
880
|
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
881
|
oidInp.onchange = save;
|
|
767
|
-
|
|
768
882
|
const pickBtn = document.createElement('button');
|
|
769
883
|
pickBtn.textContent = '…';
|
|
770
884
|
pickBtn.style.cssText = 'padding:2px 6px;font-size:11px;cursor:pointer;border:1px solid #aaa;border-radius:3px;margin-left:3px;';
|
|
@@ -772,51 +886,43 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
|
|
|
772
886
|
const picked = await openSelectIdDialog(document.body, { id: oidInp.value });
|
|
773
887
|
if (picked) { oidInp.value = picked; save(); }
|
|
774
888
|
};
|
|
775
|
-
|
|
776
889
|
const oidRow2 = document.createElement('div');
|
|
777
890
|
oidRow2.style.cssText = 'display:flex;margin-bottom:3px;';
|
|
778
|
-
oidRow2.appendChild(oidInp);
|
|
779
|
-
oidRow2.appendChild(pickBtn);
|
|
891
|
+
oidRow2.appendChild(oidInp); oidRow2.appendChild(pickBtn);
|
|
780
892
|
wrap.appendChild(oidRow2);
|
|
781
|
-
|
|
782
893
|
const condSel = sel([
|
|
783
|
-
['equal','='],
|
|
784
|
-
['greater_than','>'],
|
|
894
|
+
['equal','='],['not_equal','≠'],['less_than','<'],['less_equal','≤'],
|
|
895
|
+
['greater_than','>'],['greater_equal','≥'],['exists','exists']
|
|
785
896
|
], ctrlCfg.condition || 'equal');
|
|
786
897
|
condSel.style.cssText += 'width:70px;flex:none;margin-right:4px;';
|
|
787
898
|
const valInp = document.createElement('input');
|
|
788
899
|
valInp.type = 'text'; valInp.value = ctrlCfg.value ?? 'true';
|
|
789
|
-
valInp.placeholder = 'value';
|
|
790
900
|
valInp.style.cssText = 'flex:1;padding:3px 5px;font-size:11px;border:1px solid #ccc;border-radius:3px;';
|
|
791
901
|
condSel.onchange = save; valInp.onchange = save;
|
|
792
|
-
|
|
793
902
|
const condRow = document.createElement('div');
|
|
794
903
|
condRow.style.cssText = 'display:flex;gap:3px;';
|
|
795
904
|
condRow.appendChild(condSel); condRow.appendChild(valInp);
|
|
796
905
|
wrap.appendChild(condRow);
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
});
|
|
906
|
+
wrap._getCtrl = () => {
|
|
907
|
+
const v = { oid: oidInp.value || undefined, condition: condSel.value, value: valInp.value };
|
|
908
|
+
if (ctrlCfg.oid_bind) v.oid_bind = ctrlCfg.oid_bind;
|
|
909
|
+
return v;
|
|
910
|
+
};
|
|
803
911
|
return wrap;
|
|
804
912
|
};
|
|
805
913
|
|
|
806
914
|
const effectSel = sel([
|
|
807
|
-
['',
|
|
808
|
-
['
|
|
809
|
-
['
|
|
810
|
-
['
|
|
811
|
-
['svg','SVG Attribute'], ['morphSVG','MorphSVG'], ['motionPath','Motion Path']
|
|
915
|
+
['','— none —'],['opacity','Opacity'],['rotation','Rotation'],['scale','Scale'],
|
|
916
|
+
['translate','Translate XY'],['translateX','Translate X'],['translateY','Translate Y'],
|
|
917
|
+
['skew','Skew'],['fill','Fill Color'],['transform','Transform (CSS)'],
|
|
918
|
+
['svg','SVG Attribute'],['morphSVG','MorphSVG'],['motionPath','Motion Path']
|
|
812
919
|
], cfg.effect || '');
|
|
813
920
|
|
|
814
921
|
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']
|
|
922
|
+
['fill','fill'],['color','color'],['fill-opacity','fill-opacity'],
|
|
923
|
+
['stroke-opacity','stroke-opacity'],['stroke-width','stroke-width'],
|
|
924
|
+
['stroke-dasharray','stroke-dasharray'],['stroke-dashoffset','stroke-dashoffset'],
|
|
925
|
+
['x','x'],['y','y'],['cx','cx'],['cy','cy'],['r','r'],['rx','rx'],['ry','ry']
|
|
820
926
|
], cfg.svgAttr || 'fill');
|
|
821
927
|
|
|
822
928
|
const easeSel = sel([
|
|
@@ -829,127 +935,82 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
|
|
|
829
935
|
], cfg.ease || 'power1.inOut');
|
|
830
936
|
|
|
831
937
|
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
|
|
938
|
+
const valueToInp = inp(cfg.valueTo, 'text');
|
|
939
|
+
const durationInp = inp(cfg.duration ?? 1, 'number');
|
|
940
|
+
const repeatInp = inp(cfg.repeat ?? 0, 'number');
|
|
941
|
+
const originXInp = inp(cfg.transformOriginX ?? '50', 'number');
|
|
942
|
+
const originYInp = inp(cfg.transformOriginY ?? '50', 'number');
|
|
943
|
+
const fillFromInp = inp(cfg.fillColorFrom || '#000000', 'color');
|
|
944
|
+
const fillToInp = inp(cfg.fillColorTo || '#ff0000', 'color');
|
|
945
|
+
const pathIdInp = inp(cfg.pathId, 'text');
|
|
946
|
+
|
|
947
|
+
const yoyoChk = chkField('yoyo', 'Yoyo', cfg.yoyo);
|
|
948
|
+
const alignToPathChk = chkField('alignToPath', 'Align to path', cfg.alignToPath);
|
|
949
|
+
const orientToPathChk= chkField('orientToPath', 'Auto-rotate', cfg.orientToPath);
|
|
950
|
+
|
|
951
|
+
const playCtrl = oidRow('▶ Play', controls.play || {});
|
|
952
|
+
const pauseCtrl = oidRow('⏸ Pause', controls.pause || {});
|
|
953
|
+
const resumeCtrl = oidRow('▶ Resume', controls.resume || {});
|
|
954
|
+
const stopCtrl = oidRow('⏹ Stop', controls.stop || {});
|
|
955
|
+
const reverseCtrl = oidRow('◀ Reverse', controls.reverse || {});
|
|
956
|
+
|
|
957
|
+
// ── Build UI ──────────────────────────────────────────────────────────
|
|
888
958
|
content.innerHTML = '';
|
|
889
959
|
|
|
890
|
-
// Clear button
|
|
891
960
|
const clearBtn = document.createElement('button');
|
|
892
|
-
clearBtn.textContent = 'Clear
|
|
961
|
+
clearBtn.textContent = 'Clear';
|
|
893
962
|
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
963
|
clearBtn.onclick = () => { designItem.removeAttribute('data-animation'); this._updateAnimationsPanel(); };
|
|
895
964
|
content.appendChild(clearBtn);
|
|
896
965
|
|
|
897
966
|
const sec = (title) => {
|
|
898
967
|
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;';
|
|
968
|
+
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
969
|
d.textContent = title;
|
|
901
970
|
return d;
|
|
902
971
|
};
|
|
903
972
|
|
|
904
973
|
content.appendChild(sec('Effect'));
|
|
905
|
-
content.appendChild(field('Type', effectSel));
|
|
974
|
+
content.appendChild(field('Type', 'effect', effectSel));
|
|
906
975
|
|
|
907
|
-
const svgAttrRow = field('SVG Attr', svgAttrSel);
|
|
976
|
+
const svgAttrRow = field('SVG Attr', 'svgAttr', svgAttrSel);
|
|
908
977
|
svgAttrRow.style.display = cfg.effect === 'svg' ? '' : 'none';
|
|
909
978
|
content.appendChild(svgAttrRow);
|
|
910
|
-
effectSel.addEventListener('change', () => {
|
|
911
|
-
svgAttrRow.style.display = effectSel.value === 'svg' ? '' : 'none';
|
|
912
|
-
});
|
|
979
|
+
effectSel.addEventListener('change', () => { svgAttrRow.style.display = effectSel.value === 'svg' ? '' : 'none'; });
|
|
913
980
|
|
|
914
|
-
content.appendChild(field('Value From', valueFromInp));
|
|
915
|
-
content.appendChild(field('Value To', valueToInp));
|
|
981
|
+
content.appendChild(field('Value From', 'valueFrom', valueFromInp));
|
|
982
|
+
content.appendChild(field('Value To', 'valueTo', valueToInp));
|
|
916
983
|
|
|
917
984
|
const colorRows = document.createElement('div');
|
|
918
985
|
colorRows.style.display = (cfg.effect === 'fill' || cfg.effect === 'svg') ? '' : 'none';
|
|
919
|
-
colorRows.appendChild(field('Color From', fillFromInp));
|
|
920
|
-
colorRows.appendChild(field('Color To', fillToInp));
|
|
986
|
+
colorRows.appendChild(field('Color From', 'fillColorFrom', fillFromInp));
|
|
987
|
+
colorRows.appendChild(field('Color To', 'fillColorTo', fillToInp));
|
|
921
988
|
content.appendChild(colorRows);
|
|
922
|
-
effectSel.addEventListener('change', () => {
|
|
923
|
-
colorRows.style.display = (effectSel.value === 'fill' || effectSel.value === 'svg') ? '' : 'none';
|
|
924
|
-
});
|
|
989
|
+
effectSel.addEventListener('change', () => { colorRows.style.display = (effectSel.value === 'fill' || effectSel.value === 'svg') ? '' : 'none'; });
|
|
925
990
|
|
|
926
991
|
const motionRows = document.createElement('div');
|
|
927
992
|
motionRows.style.display = cfg.effect === 'motionPath' ? '' : 'none';
|
|
928
|
-
motionRows.appendChild(field('Path ID', pathIdInp));
|
|
993
|
+
motionRows.appendChild(field('Path ID', 'pathId', pathIdInp));
|
|
929
994
|
motionRows.appendChild(alignToPathChk);
|
|
930
995
|
motionRows.appendChild(orientToPathChk);
|
|
931
996
|
content.appendChild(motionRows);
|
|
932
|
-
effectSel.addEventListener('change', () => {
|
|
933
|
-
motionRows.style.display = effectSel.value === 'motionPath' ? '' : 'none';
|
|
934
|
-
});
|
|
997
|
+
effectSel.addEventListener('change', () => { motionRows.style.display = effectSel.value === 'motionPath' ? '' : 'none'; });
|
|
935
998
|
|
|
936
999
|
content.appendChild(sec('Timing'));
|
|
937
|
-
content.appendChild(field('Duration (s)', durationInp));
|
|
938
|
-
content.appendChild(field('Ease', easeSel));
|
|
939
|
-
content.appendChild(field('Repeat', repeatInp));
|
|
1000
|
+
content.appendChild(field('Duration (s)', 'duration', durationInp));
|
|
1001
|
+
content.appendChild(field('Ease', 'ease', easeSel));
|
|
1002
|
+
content.appendChild(field('Repeat', 'repeat', repeatInp));
|
|
940
1003
|
content.appendChild(yoyoChk);
|
|
941
1004
|
|
|
942
1005
|
const originRows = document.createElement('div');
|
|
943
1006
|
originRows.style.display = (cfg.effect === 'rotation' || cfg.effect === 'scale') ? '' : 'none';
|
|
944
1007
|
originRows.appendChild(sec('Transform Origin'));
|
|
945
|
-
originRows.appendChild(field('Origin X (%)', originXInp));
|
|
946
|
-
originRows.appendChild(field('Origin Y (%)', originYInp));
|
|
1008
|
+
originRows.appendChild(field('Origin X (%)', 'transformOriginX', originXInp));
|
|
1009
|
+
originRows.appendChild(field('Origin Y (%)', 'transformOriginY', originYInp));
|
|
947
1010
|
content.appendChild(originRows);
|
|
948
|
-
effectSel.addEventListener('change', () => {
|
|
949
|
-
originRows.style.display = (effectSel.value === 'rotation' || effectSel.value === 'scale') ? '' : 'none';
|
|
950
|
-
});
|
|
1011
|
+
effectSel.addEventListener('change', () => { originRows.style.display = (effectSel.value === 'rotation' || effectSel.value === 'scale') ? '' : 'none'; });
|
|
951
1012
|
|
|
952
|
-
content.appendChild(sec('Controls (OID
|
|
1013
|
+
content.appendChild(sec('Controls (OID triggers)'));
|
|
953
1014
|
content.appendChild(playCtrl);
|
|
954
1015
|
content.appendChild(pauseCtrl);
|
|
955
1016
|
content.appendChild(resumeCtrl);
|
|
@@ -1002,18 +1063,52 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
|
|
|
1002
1063
|
let cfg = {};
|
|
1003
1064
|
try { cfg = JSON.parse(element.getAttribute('data-effects') || '{}'); } catch (e) {}
|
|
1004
1065
|
|
|
1066
|
+
const collectEffectsCfg = () => {
|
|
1067
|
+
const out = {};
|
|
1068
|
+
if (typeSel.value) out.type = typeSel.value;
|
|
1069
|
+
out.trigger = triggerSel.value || 'load';
|
|
1070
|
+
out.duration = parseFloat(durationInp.value) || 0.5;
|
|
1071
|
+
if (parseFloat(delayInp.value)) out.delay = parseFloat(delayInp.value);
|
|
1072
|
+
if (parseInt(repeatInp.value)) out.repeat = parseInt(repeatInp.value);
|
|
1073
|
+
out.ease = easeSel.value || 'power2.out';
|
|
1074
|
+
if (triggerSel.value === 'oid' && (oidInp.value || cfg.oid_bind)) {
|
|
1075
|
+
if (oidInp.value) out.oid = oidInp.value;
|
|
1076
|
+
out.condition = condSel.value;
|
|
1077
|
+
out.conditionValue = condValInp.value;
|
|
1078
|
+
}
|
|
1079
|
+
if (typeSel.value === 'glow') {
|
|
1080
|
+
out.glowColor = glowColorInp.value;
|
|
1081
|
+
out.glowSize = parseInt(glowSizeInp.value) || 10;
|
|
1082
|
+
}
|
|
1083
|
+
if (typeSel.value === 'blur') out.blurAmount = parseInt(blurInp.value) || 5;
|
|
1084
|
+
// Preserve all _bind properties
|
|
1085
|
+
for (const [k, v] of Object.entries(cfg)) {
|
|
1086
|
+
if (k.endsWith('_bind') && v) out[k] = v;
|
|
1087
|
+
}
|
|
1088
|
+
return out;
|
|
1089
|
+
};
|
|
1090
|
+
|
|
1005
1091
|
const save = () => {
|
|
1006
1092
|
const c = collectEffectsCfg();
|
|
1007
1093
|
if (!c.type) designItem.removeAttribute('data-effects');
|
|
1008
1094
|
else designItem.setAttribute('data-effects', JSON.stringify(c));
|
|
1009
1095
|
};
|
|
1010
1096
|
|
|
1011
|
-
const
|
|
1097
|
+
const saveAndRefresh = () => { save(); this._updateEffectsPanel(); };
|
|
1098
|
+
|
|
1099
|
+
const field = (label, propKey, inputEl) => {
|
|
1012
1100
|
const row = document.createElement('div');
|
|
1013
|
-
row.style.cssText = 'display:flex;align-items:center;gap:
|
|
1101
|
+
row.style.cssText = 'display:flex;align-items:center;gap:4px;margin-bottom:6px;';
|
|
1102
|
+
if (propKey) {
|
|
1103
|
+
row.appendChild(this._makeBindSquare(propKey, cfg, designItem, 'data-effects', saveAndRefresh));
|
|
1104
|
+
} else {
|
|
1105
|
+
const sp = document.createElement('div');
|
|
1106
|
+
sp.style.cssText = 'width:11px;min-width:11px;flex-shrink:0;';
|
|
1107
|
+
row.appendChild(sp);
|
|
1108
|
+
}
|
|
1014
1109
|
const lbl = document.createElement('span');
|
|
1015
1110
|
lbl.textContent = label;
|
|
1016
|
-
lbl.style.cssText = 'min-width:
|
|
1111
|
+
lbl.style.cssText = 'min-width:78px;font-size:11px;color:#555;';
|
|
1017
1112
|
row.appendChild(lbl); row.appendChild(inputEl);
|
|
1018
1113
|
return row;
|
|
1019
1114
|
};
|
|
@@ -1049,107 +1144,78 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
|
|
|
1049
1144
|
], cfg.trigger || 'load');
|
|
1050
1145
|
|
|
1051
1146
|
const durationInp = inp(cfg.duration ?? 0.5, 'number');
|
|
1052
|
-
const delayInp
|
|
1053
|
-
const repeatInp
|
|
1147
|
+
const delayInp = inp(cfg.delay ?? 0, 'number');
|
|
1148
|
+
const repeatInp = inp(cfg.repeat ?? 0, 'number');
|
|
1054
1149
|
|
|
1055
1150
|
const easeSel = sel([
|
|
1056
1151
|
['power2.out','power2.out'],['power2.in','power2.in'],['power2.inOut','power2.inOut'],
|
|
1057
1152
|
['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']
|
|
1153
|
+
['elastic.out(1,0.3)','elastic.out'],['back.inOut(1.7)','back.inOut'],['none','none']
|
|
1060
1154
|
], cfg.ease || 'power2.out');
|
|
1061
1155
|
|
|
1062
|
-
const oidInp
|
|
1063
|
-
const condSel
|
|
1064
|
-
['equal','='],['not_equal','≠'],['less_than','<'],['greater_than','>'],['exists','exists']
|
|
1065
|
-
], cfg.condition || 'equal');
|
|
1156
|
+
const oidInp = inp(cfg.oid || '');
|
|
1157
|
+
const condSel = sel([['equal','='],['not_equal','≠'],['less_than','<'],['greater_than','>'],['exists','exists']], cfg.condition || 'equal');
|
|
1066
1158
|
const condValInp = inp(cfg.conditionValue ?? 'true');
|
|
1067
|
-
|
|
1068
1159
|
const glowColorInp = inp(cfg.glowColor || 'yellow', 'color');
|
|
1069
|
-
const glowSizeInp
|
|
1070
|
-
const blurInp
|
|
1160
|
+
const glowSizeInp = inp(cfg.glowSize || 10, 'number');
|
|
1161
|
+
const blurInp = inp(cfg.blurAmount || 5, 'number');
|
|
1071
1162
|
|
|
1072
1163
|
const oidPickBtn = document.createElement('button');
|
|
1073
1164
|
oidPickBtn.textContent = '…';
|
|
1074
|
-
oidPickBtn.style.cssText = 'padding:2px 6px;font-size:11px;cursor:pointer;border:1px solid #aaa;border-radius:3px;';
|
|
1165
|
+
oidPickBtn.style.cssText = 'padding:2px 6px;font-size:11px;cursor:pointer;border:1px solid #aaa;border-radius:3px;flex-shrink:0;';
|
|
1075
1166
|
oidPickBtn.onclick = async () => {
|
|
1076
1167
|
const picked = await openSelectIdDialog(document.body, { id: oidInp.value });
|
|
1077
1168
|
if (picked) { oidInp.value = picked; save(); }
|
|
1078
1169
|
};
|
|
1079
1170
|
|
|
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
|
-
|
|
1171
|
+
// ── Build UI ──────────────────────────────────────────────────────────
|
|
1101
1172
|
content.innerHTML = '';
|
|
1102
1173
|
|
|
1103
1174
|
const clearBtn = document.createElement('button');
|
|
1104
|
-
clearBtn.textContent = 'Clear
|
|
1175
|
+
clearBtn.textContent = 'Clear';
|
|
1105
1176
|
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
1177
|
clearBtn.onclick = () => { designItem.removeAttribute('data-effects'); this._updateEffectsPanel(); };
|
|
1107
1178
|
content.appendChild(clearBtn);
|
|
1108
1179
|
|
|
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));
|
|
1180
|
+
content.appendChild(field('Type', 'type', typeSel));
|
|
1181
|
+
content.appendChild(field('Trigger', null, triggerSel));
|
|
1182
|
+
content.appendChild(field('Duration (s)', 'duration', durationInp));
|
|
1183
|
+
content.appendChild(field('Delay (s)', 'delay', delayInp));
|
|
1184
|
+
content.appendChild(field('Repeat', 'repeat', repeatInp));
|
|
1185
|
+
content.appendChild(field('Ease', 'ease', easeSel));
|
|
1115
1186
|
|
|
1116
1187
|
const oidSection = document.createElement('div');
|
|
1117
1188
|
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
1189
|
const oidLabel = document.createElement('div');
|
|
1124
|
-
oidLabel.style.cssText = 'font-size:11px;font-weight:600;color:#555;margin-bottom:3px;';
|
|
1125
|
-
oidLabel.
|
|
1190
|
+
oidLabel.style.cssText = 'display:flex;align-items:center;gap:4px;font-size:11px;font-weight:600;color:#555;margin-bottom:3px;padding-left:4px;';
|
|
1191
|
+
oidLabel.appendChild(this._makeBindSquare('oid', cfg, designItem, 'data-effects', saveAndRefresh));
|
|
1192
|
+
const oidLabelText = document.createElement('span');
|
|
1193
|
+
oidLabelText.textContent = 'OID';
|
|
1194
|
+
oidLabel.appendChild(oidLabelText);
|
|
1195
|
+
const oidRowDiv = document.createElement('div');
|
|
1196
|
+
oidRowDiv.style.cssText = 'display:flex;gap:3px;margin-bottom:4px;padding-left:15px;';
|
|
1197
|
+
oidRowDiv.appendChild(oidInp); oidRowDiv.appendChild(oidPickBtn);
|
|
1198
|
+
const condRowDiv = document.createElement('div');
|
|
1199
|
+
condRowDiv.style.cssText = 'display:flex;gap:3px;margin-bottom:6px;padding-left:15px;';
|
|
1200
|
+
condRowDiv.appendChild(condSel); condRowDiv.appendChild(condValInp);
|
|
1126
1201
|
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);
|
|
1202
|
+
oidSection.appendChild(oidRowDiv);
|
|
1203
|
+
oidSection.appendChild(condRowDiv);
|
|
1132
1204
|
content.appendChild(oidSection);
|
|
1133
|
-
triggerSel.addEventListener('change', () => {
|
|
1134
|
-
oidSection.style.display = triggerSel.value === 'oid' ? '' : 'none';
|
|
1135
|
-
});
|
|
1205
|
+
triggerSel.addEventListener('change', () => { oidSection.style.display = triggerSel.value === 'oid' ? '' : 'none'; });
|
|
1136
1206
|
|
|
1137
1207
|
const glowSection = document.createElement('div');
|
|
1138
1208
|
glowSection.style.display = cfg.type === 'glow' ? '' : 'none';
|
|
1139
|
-
glowSection.appendChild(field('Glow Color', glowColorInp));
|
|
1140
|
-
glowSection.appendChild(field('Glow Size
|
|
1209
|
+
glowSection.appendChild(field('Glow Color', 'glowColor', glowColorInp));
|
|
1210
|
+
glowSection.appendChild(field('Glow Size', 'glowSize', glowSizeInp));
|
|
1141
1211
|
content.appendChild(glowSection);
|
|
1142
|
-
typeSel.addEventListener('change', () => {
|
|
1143
|
-
glowSection.style.display = typeSel.value === 'glow' ? '' : 'none';
|
|
1144
|
-
});
|
|
1212
|
+
typeSel.addEventListener('change', () => { glowSection.style.display = typeSel.value === 'glow' ? '' : 'none'; });
|
|
1145
1213
|
|
|
1146
1214
|
const blurSection = document.createElement('div');
|
|
1147
1215
|
blurSection.style.display = cfg.type === 'blur' ? '' : 'none';
|
|
1148
|
-
blurSection.appendChild(field('Blur (px)', blurInp));
|
|
1216
|
+
blurSection.appendChild(field('Blur (px)', 'blurAmount', blurInp));
|
|
1149
1217
|
content.appendChild(blurSection);
|
|
1150
|
-
typeSel.addEventListener('change', () => {
|
|
1151
|
-
blurSection.style.display = typeSel.value === 'blur' ? '' : 'none';
|
|
1152
|
-
});
|
|
1218
|
+
typeSel.addEventListener('change', () => { blurSection.style.display = typeSel.value === 'blur' ? '' : 'none'; });
|
|
1153
1219
|
}
|
|
1154
1220
|
|
|
1155
1221
|
/* Move to a Dock Spawn Helper */
|
|
@@ -156,9 +156,26 @@ class AnimationInstance {
|
|
|
156
156
|
if (!window.gsap) return;
|
|
157
157
|
const controls = this.cfg.controls || {};
|
|
158
158
|
|
|
159
|
-
const bindControl = (key, action) => {
|
|
159
|
+
const bindControl = async (key, action) => {
|
|
160
160
|
const ctrl = controls[key];
|
|
161
|
-
if (!ctrl
|
|
161
|
+
if (!ctrl) return;
|
|
162
|
+
|
|
163
|
+
// If OID itself is bound via binding square, resolve it first
|
|
164
|
+
if (ctrl.oid_bind?.signal) {
|
|
165
|
+
try {
|
|
166
|
+
const s = await iobrokerHandler.connection.getState(ctrl.oid_bind.signal);
|
|
167
|
+
if (s?.val != null) ctrl.oid = String(s.val);
|
|
168
|
+
} catch (e) {}
|
|
169
|
+
const oidBindHandler = (id, state) => {
|
|
170
|
+
if (state?.val != null) ctrl.oid = String(state.val);
|
|
171
|
+
};
|
|
172
|
+
try {
|
|
173
|
+
iobrokerHandler.connection.subscribeState(ctrl.oid_bind.signal, oidBindHandler);
|
|
174
|
+
this._subs.push({ oid: ctrl.oid_bind.signal, handler: oidBindHandler });
|
|
175
|
+
} catch (e) {}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (!ctrl.oid) return;
|
|
162
179
|
const handler = (id, state) => {
|
|
163
180
|
if (checkCond(state?.val, ctrl.condition || 'equal', ctrl.value ?? 'true')) action();
|
|
164
181
|
};
|
|
@@ -171,11 +188,38 @@ class AnimationInstance {
|
|
|
171
188
|
}).catch(() => {});
|
|
172
189
|
};
|
|
173
190
|
|
|
174
|
-
bindControl('play', () => this._play());
|
|
175
|
-
bindControl('pause', () => this.tween?.pause());
|
|
176
|
-
bindControl('resume', () => this.tween?.play());
|
|
177
|
-
bindControl('stop', () => this.tween?.pause(0));
|
|
178
|
-
bindControl('reverse', () => this.tween?.reverse());
|
|
191
|
+
await bindControl('play', () => this._play());
|
|
192
|
+
await bindControl('pause', () => this.tween?.pause());
|
|
193
|
+
await bindControl('resume', () => this.tween?.play());
|
|
194
|
+
await bindControl('stop', () => this.tween?.pause(0));
|
|
195
|
+
await bindControl('reverse', () => this.tween?.reverse());
|
|
196
|
+
|
|
197
|
+
// Dynamic property bindings — each prop can have a propKey_bind: {signal, expression}
|
|
198
|
+
const dynamicProps = [
|
|
199
|
+
'effect', 'valueFrom', 'valueTo', 'duration', 'ease', 'repeat', 'yoyo',
|
|
200
|
+
'fillColorFrom', 'fillColorTo', 'transformOriginX', 'transformOriginY',
|
|
201
|
+
'svgAttr', 'pathId', 'alignToPath', 'orientToPath'
|
|
202
|
+
];
|
|
203
|
+
for (const prop of dynamicProps) {
|
|
204
|
+
const bindCfg = this.cfg[prop + '_bind'];
|
|
205
|
+
if (!bindCfg?.signal) continue;
|
|
206
|
+
const propCapture = prop;
|
|
207
|
+
const handler = (id, state) => {
|
|
208
|
+
if (state?.val != null) {
|
|
209
|
+
this.cfg[propCapture] = state.val;
|
|
210
|
+
// Rebuild tween if currently playing
|
|
211
|
+
if (this.tween && !this.tween.paused()) this._play();
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
try {
|
|
215
|
+
iobrokerHandler.connection.subscribeState(bindCfg.signal, handler);
|
|
216
|
+
this._subs.push({ oid: bindCfg.signal, handler });
|
|
217
|
+
} catch (e) {}
|
|
218
|
+
try {
|
|
219
|
+
const state = await iobrokerHandler.connection.getState(bindCfg.signal);
|
|
220
|
+
if (state?.val != null) this.cfg[prop] = state.val;
|
|
221
|
+
} catch (e) {}
|
|
222
|
+
}
|
|
179
223
|
}
|
|
180
224
|
|
|
181
225
|
_play() {
|
|
@@ -333,15 +377,28 @@ async function _applyEffect(el, cfg) {
|
|
|
333
377
|
} else if (cfg.trigger === 'click') {
|
|
334
378
|
_clickFn = applyTween;
|
|
335
379
|
el.addEventListener('click', _clickFn);
|
|
336
|
-
} else if (cfg.trigger === 'oid'
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
380
|
+
} else if (cfg.trigger === 'oid') {
|
|
381
|
+
// Resolve OID — may come from a binding square binding
|
|
382
|
+
if (cfg.oid_bind?.signal) {
|
|
383
|
+
try {
|
|
384
|
+
const s = await iobrokerHandler.connection.getState(cfg.oid_bind.signal);
|
|
385
|
+
if (s?.val != null) cfg.oid = String(s.val);
|
|
386
|
+
} catch (e) {}
|
|
387
|
+
const oidBindHandler = (id, state) => { if (state?.val != null) cfg.oid = String(state.val); };
|
|
388
|
+
try { iobrokerHandler.connection.subscribeState(cfg.oid_bind.signal, oidBindHandler); } catch (e) {}
|
|
389
|
+
_oidId = cfg.oid_bind.signal;
|
|
390
|
+
_oidHandler = oidBindHandler;
|
|
391
|
+
}
|
|
392
|
+
if (cfg.oid) {
|
|
393
|
+
_oidId = cfg.oid;
|
|
394
|
+
_oidHandler = (id, state) => {
|
|
395
|
+
if (checkCond(state?.val, cfg.condition || 'equal', cfg.conditionValue ?? 'true')) applyTween();
|
|
396
|
+
};
|
|
397
|
+
try { iobrokerHandler.connection.subscribeState(cfg.oid, _oidHandler); } catch (e) {}
|
|
398
|
+
iobrokerHandler.connection.getState(cfg.oid).then(state => {
|
|
399
|
+
if (state && checkCond(state.val, cfg.condition || 'equal', cfg.conditionValue ?? 'true')) applyTween();
|
|
400
|
+
}).catch(() => {});
|
|
401
|
+
}
|
|
345
402
|
}
|
|
346
403
|
|
|
347
404
|
return () => {
|