iobroker.mywebui 1.37.68 → 1.37.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/io-package.json
CHANGED
package/package.json
CHANGED
|
@@ -760,85 +760,32 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
|
|
|
760
760
|
const designItem = items[0];
|
|
761
761
|
const element = designItem.element;
|
|
762
762
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
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
|
-
|
|
804
|
-
const save = () => {
|
|
805
|
-
const newCfg = collectAnimCfg();
|
|
806
|
-
if (!newCfg.effect && !newCfg.controls && !Object.keys(newCfg).some(k => k.endsWith('_bind'))) {
|
|
807
|
-
designItem.removeAttribute('data-animation');
|
|
808
|
-
} else {
|
|
809
|
-
designItem.setAttribute('data-animation', JSON.stringify(newCfg));
|
|
810
|
-
}
|
|
763
|
+
// Support both single object (legacy) and array
|
|
764
|
+
let cfgList = [];
|
|
765
|
+
try {
|
|
766
|
+
const raw = JSON.parse(element.getAttribute('data-animation') || '[]');
|
|
767
|
+
cfgList = Array.isArray(raw) ? raw : [raw];
|
|
768
|
+
} catch (e) {}
|
|
769
|
+
|
|
770
|
+
const saveAll = () => {
|
|
771
|
+
const result = cfgList.filter(c => c._collect && (c._collect().effect || Object.keys(c._collect()).some(k => k.endsWith('_bind'))));
|
|
772
|
+
const data = result.map(c => c._collect());
|
|
773
|
+
if (data.length === 0) designItem.removeAttribute('data-animation');
|
|
774
|
+
else if (data.length === 1) designItem.setAttribute('data-animation', JSON.stringify(data[0]));
|
|
775
|
+
else designItem.setAttribute('data-animation', JSON.stringify(data));
|
|
811
776
|
};
|
|
777
|
+
const saveAndRefresh = () => { saveAll(); this._updateAnimationsPanel(); };
|
|
812
778
|
|
|
813
|
-
|
|
779
|
+
// ── Shared helpers ────────────────────────────────────────────────────
|
|
814
780
|
|
|
815
|
-
|
|
816
|
-
const field = (label, propKey, inputEl) => {
|
|
817
|
-
const row = document.createElement('div');
|
|
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
|
-
}
|
|
826
|
-
const lbl = document.createElement('span');
|
|
827
|
-
lbl.textContent = label;
|
|
828
|
-
lbl.style.cssText = 'min-width:84px;font-size:11px;color:#555;';
|
|
829
|
-
row.appendChild(lbl);
|
|
830
|
-
row.appendChild(inputEl);
|
|
831
|
-
return row;
|
|
832
|
-
};
|
|
833
|
-
|
|
834
|
-
const inp = (val, type = 'text') => {
|
|
781
|
+
const mkInp = (val, type, onchange) => {
|
|
835
782
|
const i = document.createElement('input');
|
|
836
783
|
i.type = type; i.value = val ?? '';
|
|
837
784
|
i.style.cssText = 'flex:1;padding:3px 5px;font-size:11px;border:1px solid #ccc;border-radius:3px;';
|
|
838
|
-
i.onchange =
|
|
785
|
+
i.onchange = onchange; return i;
|
|
839
786
|
};
|
|
840
787
|
|
|
841
|
-
const
|
|
788
|
+
const mkSel = (options, val, onchange) => {
|
|
842
789
|
const s = document.createElement('select');
|
|
843
790
|
s.style.cssText = 'flex:1;padding:3px 5px;font-size:11px;border:1px solid #ccc;border-radius:3px;';
|
|
844
791
|
options.forEach(([v, t]) => {
|
|
@@ -846,54 +793,60 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
|
|
|
846
793
|
o.value = v; o.textContent = t; o.selected = v === val;
|
|
847
794
|
s.appendChild(o);
|
|
848
795
|
});
|
|
849
|
-
s.onchange =
|
|
796
|
+
s.onchange = onchange; return s;
|
|
850
797
|
};
|
|
851
798
|
|
|
852
|
-
const
|
|
799
|
+
const mkField = (label, propKey, inputEl, cfg) => {
|
|
800
|
+
const row = document.createElement('div');
|
|
801
|
+
row.style.cssText = 'display:flex;align-items:center;gap:4px;margin-bottom:6px;';
|
|
802
|
+
if (propKey) {
|
|
803
|
+
row.appendChild(this._makeBindSquare(propKey, cfg, designItem, 'data-animation', saveAndRefresh));
|
|
804
|
+
} else {
|
|
805
|
+
const sp = document.createElement('div'); sp.style.cssText = 'width:11px;min-width:11px;flex-shrink:0;';
|
|
806
|
+
row.appendChild(sp);
|
|
807
|
+
}
|
|
808
|
+
const lbl = document.createElement('span');
|
|
809
|
+
lbl.textContent = label; lbl.style.cssText = 'min-width:84px;font-size:11px;color:#555;';
|
|
810
|
+
row.appendChild(lbl); row.appendChild(inputEl);
|
|
811
|
+
return row;
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
const mkChkField = (propKey, label, val, cfg, onchange) => {
|
|
853
815
|
const row = document.createElement('div');
|
|
854
816
|
row.style.cssText = 'display:flex;align-items:center;gap:4px;margin-bottom:6px;';
|
|
855
817
|
row.appendChild(this._makeBindSquare(propKey, cfg, designItem, 'data-animation', saveAndRefresh));
|
|
856
818
|
const lbl = document.createElement('label');
|
|
857
819
|
lbl.style.cssText = 'display:flex;align-items:center;gap:4px;font-size:11px;cursor:pointer;';
|
|
858
820
|
const c = document.createElement('input');
|
|
859
|
-
c.type = 'checkbox'; c.checked = val === true || val === 'true';
|
|
860
|
-
c.
|
|
861
|
-
lbl.appendChild(c);
|
|
862
|
-
lbl.appendChild(document.createTextNode(label));
|
|
821
|
+
c.type = 'checkbox'; c.checked = val === true || val === 'true'; c.onchange = onchange;
|
|
822
|
+
lbl.appendChild(c); lbl.appendChild(document.createTextNode(label));
|
|
863
823
|
row.appendChild(lbl);
|
|
824
|
+
row._checked = () => c.checked;
|
|
864
825
|
return row;
|
|
865
826
|
};
|
|
866
827
|
|
|
867
|
-
const
|
|
828
|
+
const mkOidRow = (label, ctrlCfg, onchange) => {
|
|
868
829
|
const wrap = document.createElement('div');
|
|
869
830
|
wrap.style.cssText = 'border:1px solid #eee;border-radius:3px;padding:6px;margin-bottom:6px;background:#fafafa;';
|
|
870
831
|
const head = document.createElement('div');
|
|
871
832
|
head.style.cssText = 'display:flex;align-items:center;gap:4px;font-size:11px;font-weight:600;color:#444;margin-bottom:5px;';
|
|
872
833
|
head.appendChild(this._makeBindSquare('oid', ctrlCfg, designItem, 'data-animation', saveAndRefresh));
|
|
873
|
-
const headText = document.createElement('span');
|
|
874
|
-
headText.
|
|
875
|
-
|
|
876
|
-
wrap.appendChild(head);
|
|
877
|
-
const condSel = sel([
|
|
834
|
+
const headText = document.createElement('span'); headText.textContent = label;
|
|
835
|
+
head.appendChild(headText); wrap.appendChild(head);
|
|
836
|
+
const condSel = mkSel([
|
|
878
837
|
['equal','='],['not_equal','≠'],['less_than','<'],['less_equal','≤'],
|
|
879
838
|
['greater_than','>'],['greater_equal','≥'],['exists','exists']
|
|
880
|
-
], ctrlCfg.condition || 'equal');
|
|
839
|
+
], ctrlCfg.condition || 'equal', onchange);
|
|
881
840
|
condSel.style.cssText += 'flex:1;';
|
|
882
|
-
condSel.onchange = save;
|
|
883
841
|
const condRow = document.createElement('div');
|
|
884
842
|
condRow.style.cssText = 'display:flex;align-items:center;gap:4px;margin-bottom:4px;';
|
|
885
843
|
condRow.appendChild(this._makeBindSquare('condition', ctrlCfg, designItem, 'data-animation', saveAndRefresh));
|
|
886
|
-
condRow.appendChild(condSel);
|
|
887
|
-
|
|
888
|
-
const valInp = document.createElement('input');
|
|
889
|
-
valInp.type = 'text'; valInp.value = ctrlCfg.value ?? 'true';
|
|
890
|
-
valInp.style.cssText = 'flex:1;padding:3px 5px;font-size:11px;border:1px solid #ccc;border-radius:3px;';
|
|
891
|
-
valInp.onchange = save;
|
|
844
|
+
condRow.appendChild(condSel); wrap.appendChild(condRow);
|
|
845
|
+
const valInp = mkInp(ctrlCfg.value ?? 'true', 'text', onchange);
|
|
892
846
|
const valRow = document.createElement('div');
|
|
893
847
|
valRow.style.cssText = 'display:flex;align-items:center;gap:4px;';
|
|
894
848
|
valRow.appendChild(this._makeBindSquare('value', ctrlCfg, designItem, 'data-animation', saveAndRefresh));
|
|
895
|
-
valRow.appendChild(valInp);
|
|
896
|
-
wrap.appendChild(valRow);
|
|
849
|
+
valRow.appendChild(valInp); wrap.appendChild(valRow);
|
|
897
850
|
wrap._getCtrl = () => {
|
|
898
851
|
const v = { condition: condSel.value, value: valInp.value };
|
|
899
852
|
if (ctrlCfg.oid_bind) v.oid_bind = ctrlCfg.oid_bind;
|
|
@@ -904,112 +857,198 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
|
|
|
904
857
|
return wrap;
|
|
905
858
|
};
|
|
906
859
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
860
|
+
// ── Build one animation block ─────────────────────────────────────────
|
|
861
|
+
|
|
862
|
+
const buildAnimBlock = (cfg, index) => {
|
|
863
|
+
const save = saveAll;
|
|
864
|
+
const controls = cfg.controls || {};
|
|
865
|
+
|
|
866
|
+
const block = document.createElement('div');
|
|
867
|
+
block.style.cssText = 'border:2px solid #c8d6e0;border-radius:5px;padding:8px;margin-bottom:10px;background:#f7fafc;';
|
|
868
|
+
|
|
869
|
+
// Header: "Animation #N" + delete button
|
|
870
|
+
const hdr = document.createElement('div');
|
|
871
|
+
hdr.style.cssText = 'display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;';
|
|
872
|
+
const hdrTitle = document.createElement('span');
|
|
873
|
+
hdrTitle.textContent = `Animation #${index + 1}`;
|
|
874
|
+
hdrTitle.style.cssText = 'font-size:12px;font-weight:700;color:#3a6a8a;';
|
|
875
|
+
const delBtn = document.createElement('button');
|
|
876
|
+
delBtn.textContent = '✕ Remove';
|
|
877
|
+
delBtn.style.cssText = 'font-size:10px;padding:2px 6px;cursor:pointer;border:1px solid #c66;border-radius:3px;background:#fff;color:#c33;';
|
|
878
|
+
delBtn.onclick = () => { cfgList.splice(index, 1); saveAndRefresh(); };
|
|
879
|
+
hdr.appendChild(hdrTitle); hdr.appendChild(delBtn);
|
|
880
|
+
block.appendChild(hdr);
|
|
881
|
+
|
|
882
|
+
const sec = (title) => {
|
|
883
|
+
const d = document.createElement('div');
|
|
884
|
+
d.style.cssText = 'font-size:11px;font-weight:700;color:#555;margin:8px 0 4px;padding-top:6px;border-top:1px solid #dde;clear:both;';
|
|
885
|
+
d.textContent = title; return d;
|
|
886
|
+
};
|
|
921
887
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
888
|
+
const effectSel = mkSel([
|
|
889
|
+
['','— none —'],['opacity','Opacity'],['rotation','Rotation'],['scale','Scale'],
|
|
890
|
+
['translateX','Move X (relative)'],['translateY','Move Y (relative)'],['translate','Move XY (relative)'],
|
|
891
|
+
['left','Move Left (absolute)'],['top','Move Top (absolute)'],
|
|
892
|
+
['skew','Skew'],['fill','Fill Color'],['transform','Transform (CSS)'],
|
|
893
|
+
['svg','SVG Attribute'],['morphSVG','MorphSVG'],['motionPath','Motion Path']
|
|
894
|
+
], cfg.effect || '', save);
|
|
895
|
+
|
|
896
|
+
const svgAttrSel = mkSel([
|
|
897
|
+
['fill','fill'],['color','color'],['fill-opacity','fill-opacity'],
|
|
898
|
+
['stroke-opacity','stroke-opacity'],['stroke-width','stroke-width'],
|
|
899
|
+
['stroke-dasharray','stroke-dasharray'],['stroke-dashoffset','stroke-dashoffset'],
|
|
900
|
+
['x','x'],['y','y'],['cx','cx'],['cy','cy'],['r','r'],['rx','rx'],['ry','ry']
|
|
901
|
+
], cfg.svgAttr || 'fill', save);
|
|
902
|
+
|
|
903
|
+
const easeSel = mkSel([
|
|
904
|
+
['none','none'],['power1.in','power1.in'],['power1.out','power1.out'],
|
|
905
|
+
['power1.inOut','power1.inOut'],['power2.in','power2.in'],['power2.out','power2.out'],
|
|
906
|
+
['power2.inOut','power2.inOut'],['power3.inOut','power3.inOut'],
|
|
907
|
+
['bounce.out','bounce.out'],['elastic.out(1,0.3)','elastic.out'],
|
|
908
|
+
['back.inOut(1.7)','back.inOut'],['circ.inOut','circ.inOut'],
|
|
909
|
+
['expo.inOut','expo.inOut'],['sine.inOut','sine.inOut']
|
|
910
|
+
], cfg.ease || 'power1.inOut', save);
|
|
911
|
+
|
|
912
|
+
const valueFromInp = mkInp(cfg.valueFrom, 'text', save);
|
|
913
|
+
const valueToInp = mkInp(cfg.valueTo, 'text', save);
|
|
914
|
+
const durationInp = mkInp(cfg.duration ?? 1, 'number', save);
|
|
915
|
+
const repeatInp = mkInp(cfg.repeat ?? 0, 'number', save);
|
|
916
|
+
const originXInp = mkInp(cfg.transformOriginX ?? '50', 'number', save);
|
|
917
|
+
const originYInp = mkInp(cfg.transformOriginY ?? '50', 'number', save);
|
|
918
|
+
const fillFromInp = mkInp(cfg.fillColorFrom || '#000000', 'color', save);
|
|
919
|
+
const fillToInp = mkInp(cfg.fillColorTo || '#ff0000', 'color', save);
|
|
920
|
+
const pathIdInp = mkInp(cfg.pathId, 'text', save);
|
|
921
|
+
|
|
922
|
+
const yoyoChk = mkChkField('yoyo', 'Yoyo', cfg.yoyo, cfg, save);
|
|
923
|
+
const alignToPathChk = mkChkField('alignToPath', 'Align to path', cfg.alignToPath, cfg, save);
|
|
924
|
+
const orientToPathChk= mkChkField('orientToPath', 'Auto-rotate', cfg.orientToPath, cfg, save);
|
|
925
|
+
|
|
926
|
+
const playCtrl = mkOidRow('▶ Play', controls.play || {}, save);
|
|
927
|
+
const pauseCtrl = mkOidRow('⏸ Pause', controls.pause || {}, save);
|
|
928
|
+
const resumeCtrl = mkOidRow('▶ Resume', controls.resume || {}, save);
|
|
929
|
+
const stopCtrl = mkOidRow('⏹ Stop', controls.stop || {}, save);
|
|
930
|
+
const reverseCtrl = mkOidRow('◀ Reverse', controls.reverse || {}, save);
|
|
931
|
+
|
|
932
|
+
// _collect: reads all UI values back into a config object
|
|
933
|
+
cfg._collect = () => {
|
|
934
|
+
const e = effectSel.value;
|
|
935
|
+
const c = {};
|
|
936
|
+
const addCtrl = (key, row) => { const v = row._getCtrl(); if (v.oid || v.oid_bind) c[key] = v; };
|
|
937
|
+
addCtrl('play', playCtrl); addCtrl('pause', pauseCtrl);
|
|
938
|
+
addCtrl('resume', resumeCtrl); addCtrl('stop', stopCtrl);
|
|
939
|
+
addCtrl('reverse', reverseCtrl);
|
|
940
|
+
const out = {};
|
|
941
|
+
if (e) out.effect = e;
|
|
942
|
+
if (e === 'svg' && svgAttrSel.value) out.svgAttr = svgAttrSel.value;
|
|
943
|
+
if (valueFromInp.value) out.valueFrom = valueFromInp.value;
|
|
944
|
+
if (valueToInp.value) out.valueTo = valueToInp.value;
|
|
945
|
+
out.duration = parseFloat(durationInp.value) || 1;
|
|
946
|
+
out.ease = easeSel.value || 'power1.inOut';
|
|
947
|
+
out.repeat = parseInt(repeatInp.value) || 0;
|
|
948
|
+
if (yoyoChk._checked()) out.yoyo = true;
|
|
949
|
+
if (['rotation','scale'].includes(e)) {
|
|
950
|
+
out.transformOriginX = originXInp.value;
|
|
951
|
+
out.transformOriginY = originYInp.value;
|
|
952
|
+
}
|
|
953
|
+
if (e === 'fill' || e === 'svg') {
|
|
954
|
+
if (fillFromInp.value) out.fillColorFrom = fillFromInp.value;
|
|
955
|
+
if (fillToInp.value) out.fillColorTo = fillToInp.value;
|
|
956
|
+
}
|
|
957
|
+
if (e === 'motionPath') {
|
|
958
|
+
if (pathIdInp.value) out.pathId = pathIdInp.value;
|
|
959
|
+
if (alignToPathChk._checked()) out.alignToPath = true;
|
|
960
|
+
if (orientToPathChk._checked()) out.orientToPath = true;
|
|
961
|
+
}
|
|
962
|
+
if (Object.keys(c).length) out.controls = c;
|
|
963
|
+
for (const [k, v] of Object.entries(cfg)) {
|
|
964
|
+
if (k.endsWith('_bind') && v) out[k] = v;
|
|
965
|
+
}
|
|
966
|
+
return out;
|
|
967
|
+
};
|
|
950
968
|
|
|
951
|
-
|
|
969
|
+
// Build block DOM
|
|
970
|
+
block.appendChild(sec('Effect'));
|
|
971
|
+
block.appendChild(mkField('Type', 'effect', effectSel, cfg));
|
|
972
|
+
|
|
973
|
+
const svgAttrRow = mkField('SVG Attr', 'svgAttr', svgAttrSel, cfg);
|
|
974
|
+
svgAttrRow.style.display = cfg.effect === 'svg' ? '' : 'none';
|
|
975
|
+
block.appendChild(svgAttrRow);
|
|
976
|
+
effectSel.addEventListener('change', () => { svgAttrRow.style.display = effectSel.value === 'svg' ? '' : 'none'; });
|
|
977
|
+
|
|
978
|
+
block.appendChild(mkField('Value From', 'valueFrom', valueFromInp, cfg));
|
|
979
|
+
block.appendChild(mkField('Value To', 'valueTo', valueToInp, cfg));
|
|
980
|
+
|
|
981
|
+
const colorRows = document.createElement('div');
|
|
982
|
+
colorRows.style.display = (cfg.effect === 'fill' || cfg.effect === 'svg') ? '' : 'none';
|
|
983
|
+
colorRows.appendChild(mkField('Color From', 'fillColorFrom', fillFromInp, cfg));
|
|
984
|
+
colorRows.appendChild(mkField('Color To', 'fillColorTo', fillToInp, cfg));
|
|
985
|
+
block.appendChild(colorRows);
|
|
986
|
+
effectSel.addEventListener('change', () => { colorRows.style.display = (effectSel.value === 'fill' || effectSel.value === 'svg') ? '' : 'none'; });
|
|
987
|
+
|
|
988
|
+
const motionRows = document.createElement('div');
|
|
989
|
+
motionRows.style.display = cfg.effect === 'motionPath' ? '' : 'none';
|
|
990
|
+
motionRows.appendChild(mkField('Path ID', 'pathId', pathIdInp, cfg));
|
|
991
|
+
motionRows.appendChild(alignToPathChk);
|
|
992
|
+
motionRows.appendChild(orientToPathChk);
|
|
993
|
+
block.appendChild(motionRows);
|
|
994
|
+
effectSel.addEventListener('change', () => { motionRows.style.display = effectSel.value === 'motionPath' ? '' : 'none'; });
|
|
995
|
+
|
|
996
|
+
block.appendChild(sec('Timing'));
|
|
997
|
+
block.appendChild(mkField('Duration (s)', 'duration', durationInp, cfg));
|
|
998
|
+
block.appendChild(mkField('Ease', 'ease', easeSel, cfg));
|
|
999
|
+
block.appendChild(mkField('Repeat', 'repeat', repeatInp, cfg));
|
|
1000
|
+
block.appendChild(yoyoChk);
|
|
1001
|
+
|
|
1002
|
+
const originRows = document.createElement('div');
|
|
1003
|
+
originRows.style.display = (cfg.effect === 'rotation' || cfg.effect === 'scale') ? '' : 'none';
|
|
1004
|
+
originRows.appendChild(sec('Transform Origin'));
|
|
1005
|
+
originRows.appendChild(mkField('Origin X (%)', 'transformOriginX', originXInp, cfg));
|
|
1006
|
+
originRows.appendChild(mkField('Origin Y (%)', 'transformOriginY', originYInp, cfg));
|
|
1007
|
+
block.appendChild(originRows);
|
|
1008
|
+
effectSel.addEventListener('change', () => { originRows.style.display = (effectSel.value === 'rotation' || effectSel.value === 'scale') ? '' : 'none'; });
|
|
1009
|
+
|
|
1010
|
+
block.appendChild(sec('Controls (OID triggers)'));
|
|
1011
|
+
block.appendChild(playCtrl); block.appendChild(pauseCtrl);
|
|
1012
|
+
block.appendChild(resumeCtrl); block.appendChild(stopCtrl);
|
|
1013
|
+
block.appendChild(reverseCtrl);
|
|
1014
|
+
|
|
1015
|
+
return block;
|
|
1016
|
+
};
|
|
1017
|
+
|
|
1018
|
+
// ── Render ────────────────────────────────────────────────────────────
|
|
952
1019
|
content.innerHTML = '';
|
|
953
1020
|
|
|
954
|
-
const
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
content.appendChild(
|
|
1021
|
+
const clearAllBtn = document.createElement('button');
|
|
1022
|
+
clearAllBtn.textContent = 'Clear All';
|
|
1023
|
+
clearAllBtn.style.cssText = 'float:right;font-size:10px;padding:2px 6px;cursor:pointer;border:1px solid #ccc;border-radius:3px;background:#fff;margin-bottom:8px;';
|
|
1024
|
+
clearAllBtn.onclick = () => { designItem.removeAttribute('data-animation'); this._updateAnimationsPanel(); };
|
|
1025
|
+
content.appendChild(clearAllBtn);
|
|
1026
|
+
|
|
1027
|
+
if (cfgList.length === 0) {
|
|
1028
|
+
const hint = document.createElement('p');
|
|
1029
|
+
hint.style.cssText = 'color:#999;font-style:italic;font-size:11px;';
|
|
1030
|
+
hint.textContent = 'No animations. Click "+ Add Animation" to start.';
|
|
1031
|
+
content.appendChild(hint);
|
|
1032
|
+
}
|
|
959
1033
|
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1034
|
+
cfgList.forEach((cfg, i) => content.appendChild(buildAnimBlock(cfg, i)));
|
|
1035
|
+
|
|
1036
|
+
const addBtn = document.createElement('button');
|
|
1037
|
+
addBtn.textContent = '+ Add Animation';
|
|
1038
|
+
addBtn.style.cssText = 'width:100%;margin-top:6px;padding:5px;font-size:12px;cursor:pointer;border:1px solid #5a9;border-radius:4px;background:#e8f8f0;color:#2a7a5a;font-weight:600;';
|
|
1039
|
+
addBtn.onclick = () => {
|
|
1040
|
+
// Collect currently valid animations from UI
|
|
1041
|
+
const existing = cfgList
|
|
1042
|
+
.filter(c => c._collect)
|
|
1043
|
+
.map(c => c._collect())
|
|
1044
|
+
.filter(c => c.effect || Object.keys(c).some(k => k.endsWith('_bind')));
|
|
1045
|
+
// Add new empty animation with a default effect so it survives save/reload
|
|
1046
|
+
existing.push({ effect: 'opacity', duration: 1, ease: 'power1.inOut', repeat: 0 });
|
|
1047
|
+
const val = existing.length === 1 ? existing[0] : existing;
|
|
1048
|
+
designItem.setAttribute('data-animation', JSON.stringify(val));
|
|
1049
|
+
this._updateAnimationsPanel();
|
|
965
1050
|
};
|
|
966
|
-
|
|
967
|
-
content.appendChild(sec('Effect'));
|
|
968
|
-
content.appendChild(field('Type', 'effect', effectSel));
|
|
969
|
-
|
|
970
|
-
const svgAttrRow = field('SVG Attr', 'svgAttr', svgAttrSel);
|
|
971
|
-
svgAttrRow.style.display = cfg.effect === 'svg' ? '' : 'none';
|
|
972
|
-
content.appendChild(svgAttrRow);
|
|
973
|
-
effectSel.addEventListener('change', () => { svgAttrRow.style.display = effectSel.value === 'svg' ? '' : 'none'; });
|
|
974
|
-
|
|
975
|
-
content.appendChild(field('Value From', 'valueFrom', valueFromInp));
|
|
976
|
-
content.appendChild(field('Value To', 'valueTo', valueToInp));
|
|
977
|
-
|
|
978
|
-
const colorRows = document.createElement('div');
|
|
979
|
-
colorRows.style.display = (cfg.effect === 'fill' || cfg.effect === 'svg') ? '' : 'none';
|
|
980
|
-
colorRows.appendChild(field('Color From', 'fillColorFrom', fillFromInp));
|
|
981
|
-
colorRows.appendChild(field('Color To', 'fillColorTo', fillToInp));
|
|
982
|
-
content.appendChild(colorRows);
|
|
983
|
-
effectSel.addEventListener('change', () => { colorRows.style.display = (effectSel.value === 'fill' || effectSel.value === 'svg') ? '' : 'none'; });
|
|
984
|
-
|
|
985
|
-
const motionRows = document.createElement('div');
|
|
986
|
-
motionRows.style.display = cfg.effect === 'motionPath' ? '' : 'none';
|
|
987
|
-
motionRows.appendChild(field('Path ID', 'pathId', pathIdInp));
|
|
988
|
-
motionRows.appendChild(alignToPathChk);
|
|
989
|
-
motionRows.appendChild(orientToPathChk);
|
|
990
|
-
content.appendChild(motionRows);
|
|
991
|
-
effectSel.addEventListener('change', () => { motionRows.style.display = effectSel.value === 'motionPath' ? '' : 'none'; });
|
|
992
|
-
|
|
993
|
-
content.appendChild(sec('Timing'));
|
|
994
|
-
content.appendChild(field('Duration (s)', 'duration', durationInp));
|
|
995
|
-
content.appendChild(field('Ease', 'ease', easeSel));
|
|
996
|
-
content.appendChild(field('Repeat', 'repeat', repeatInp));
|
|
997
|
-
content.appendChild(yoyoChk);
|
|
998
|
-
|
|
999
|
-
const originRows = document.createElement('div');
|
|
1000
|
-
originRows.style.display = (cfg.effect === 'rotation' || cfg.effect === 'scale') ? '' : 'none';
|
|
1001
|
-
originRows.appendChild(sec('Transform Origin'));
|
|
1002
|
-
originRows.appendChild(field('Origin X (%)', 'transformOriginX', originXInp));
|
|
1003
|
-
originRows.appendChild(field('Origin Y (%)', 'transformOriginY', originYInp));
|
|
1004
|
-
content.appendChild(originRows);
|
|
1005
|
-
effectSel.addEventListener('change', () => { originRows.style.display = (effectSel.value === 'rotation' || effectSel.value === 'scale') ? '' : 'none'; });
|
|
1006
|
-
|
|
1007
|
-
content.appendChild(sec('Controls (OID triggers)'));
|
|
1008
|
-
content.appendChild(playCtrl);
|
|
1009
|
-
content.appendChild(pauseCtrl);
|
|
1010
|
-
content.appendChild(resumeCtrl);
|
|
1011
|
-
content.appendChild(stopCtrl);
|
|
1012
|
-
content.appendChild(reverseCtrl);
|
|
1051
|
+
content.appendChild(addBtn);
|
|
1013
1052
|
}
|
|
1014
1053
|
|
|
1015
1054
|
// ─── Effects Panel ───────────────────────────────────────────────────────
|
|
@@ -320,12 +320,19 @@ export async function scanAndApplyAnimations(root) {
|
|
|
320
320
|
const elements = (root || document).querySelectorAll('[data-animation]');
|
|
321
321
|
for (const el of elements) {
|
|
322
322
|
try {
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
323
|
+
const raw = JSON.parse(el.getAttribute('data-animation'));
|
|
324
|
+
// Support both single object and array of animations
|
|
325
|
+
const cfgList = Array.isArray(raw) ? raw : [raw];
|
|
326
|
+
const existing = _activeAnimations.get(el) || [];
|
|
327
|
+
for (const inst of existing) inst.destroy();
|
|
328
|
+
const instances = [];
|
|
329
|
+
for (const cfg of cfgList) {
|
|
330
|
+
if (!cfg || typeof cfg !== 'object') continue;
|
|
331
|
+
const inst = new AnimationInstance(el, cfg);
|
|
332
|
+
await inst.init();
|
|
333
|
+
instances.push(inst);
|
|
334
|
+
}
|
|
335
|
+
_activeAnimations.set(el, instances);
|
|
329
336
|
} catch (e) {
|
|
330
337
|
console.warn('[AnimationService] data-animation parse error on element:', el, e);
|
|
331
338
|
}
|
|
@@ -335,8 +342,9 @@ export async function scanAndApplyAnimations(root) {
|
|
|
335
342
|
export function cleanupAnimations(root) {
|
|
336
343
|
const elements = (root || document).querySelectorAll('[data-animation]');
|
|
337
344
|
for (const el of elements) {
|
|
338
|
-
const
|
|
339
|
-
|
|
345
|
+
const instances = _activeAnimations.get(el) || [];
|
|
346
|
+
for (const inst of instances) inst.destroy();
|
|
347
|
+
_activeAnimations.delete(el);
|
|
340
348
|
}
|
|
341
349
|
}
|
|
342
350
|
|