iobroker.mywebui 1.37.67 → 1.37.69
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;
|
|
797
|
+
};
|
|
798
|
+
|
|
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;
|
|
850
812
|
};
|
|
851
813
|
|
|
852
|
-
const
|
|
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,111 +857,187 @@ export class IobrokerWebuiAppShell extends BaseCustomWebComponentConstructorAppe
|
|
|
904
857
|
return wrap;
|
|
905
858
|
};
|
|
906
859
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
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
|
+
};
|
|
913
887
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
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
|
+
};
|
|
920
968
|
|
|
921
|
-
|
|
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
|
-
|
|
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
|
+
};
|
|
949
1017
|
|
|
950
|
-
// ──
|
|
1018
|
+
// ── Render ────────────────────────────────────────────────────────────
|
|
951
1019
|
content.innerHTML = '';
|
|
952
1020
|
|
|
953
|
-
const
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
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
|
+
}
|
|
958
1033
|
|
|
959
|
-
|
|
960
|
-
const d = document.createElement('div');
|
|
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;';
|
|
962
|
-
d.textContent = title;
|
|
963
|
-
return d;
|
|
964
|
-
};
|
|
1034
|
+
cfgList.forEach((cfg, i) => content.appendChild(buildAnimBlock(cfg, i)));
|
|
965
1035
|
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
content.appendChild(svgAttrRow);
|
|
972
|
-
effectSel.addEventListener('change', () => { svgAttrRow.style.display = effectSel.value === 'svg' ? '' : 'none'; });
|
|
973
|
-
|
|
974
|
-
content.appendChild(field('Value From', 'valueFrom', valueFromInp));
|
|
975
|
-
content.appendChild(field('Value To', 'valueTo', valueToInp));
|
|
976
|
-
|
|
977
|
-
const colorRows = document.createElement('div');
|
|
978
|
-
colorRows.style.display = (cfg.effect === 'fill' || cfg.effect === 'svg') ? '' : 'none';
|
|
979
|
-
colorRows.appendChild(field('Color From', 'fillColorFrom', fillFromInp));
|
|
980
|
-
colorRows.appendChild(field('Color To', 'fillColorTo', fillToInp));
|
|
981
|
-
content.appendChild(colorRows);
|
|
982
|
-
effectSel.addEventListener('change', () => { colorRows.style.display = (effectSel.value === 'fill' || effectSel.value === 'svg') ? '' : 'none'; });
|
|
983
|
-
|
|
984
|
-
const motionRows = document.createElement('div');
|
|
985
|
-
motionRows.style.display = cfg.effect === 'motionPath' ? '' : 'none';
|
|
986
|
-
motionRows.appendChild(field('Path ID', 'pathId', pathIdInp));
|
|
987
|
-
motionRows.appendChild(alignToPathChk);
|
|
988
|
-
motionRows.appendChild(orientToPathChk);
|
|
989
|
-
content.appendChild(motionRows);
|
|
990
|
-
effectSel.addEventListener('change', () => { motionRows.style.display = effectSel.value === 'motionPath' ? '' : 'none'; });
|
|
991
|
-
|
|
992
|
-
content.appendChild(sec('Timing'));
|
|
993
|
-
content.appendChild(field('Duration (s)', 'duration', durationInp));
|
|
994
|
-
content.appendChild(field('Ease', 'ease', easeSel));
|
|
995
|
-
content.appendChild(field('Repeat', 'repeat', repeatInp));
|
|
996
|
-
content.appendChild(yoyoChk);
|
|
997
|
-
|
|
998
|
-
const originRows = document.createElement('div');
|
|
999
|
-
originRows.style.display = (cfg.effect === 'rotation' || cfg.effect === 'scale') ? '' : 'none';
|
|
1000
|
-
originRows.appendChild(sec('Transform Origin'));
|
|
1001
|
-
originRows.appendChild(field('Origin X (%)', 'transformOriginX', originXInp));
|
|
1002
|
-
originRows.appendChild(field('Origin Y (%)', 'transformOriginY', originYInp));
|
|
1003
|
-
content.appendChild(originRows);
|
|
1004
|
-
effectSel.addEventListener('change', () => { originRows.style.display = (effectSel.value === 'rotation' || effectSel.value === 'scale') ? '' : 'none'; });
|
|
1005
|
-
|
|
1006
|
-
content.appendChild(sec('Controls (OID triggers)'));
|
|
1007
|
-
content.appendChild(playCtrl);
|
|
1008
|
-
content.appendChild(pauseCtrl);
|
|
1009
|
-
content.appendChild(resumeCtrl);
|
|
1010
|
-
content.appendChild(stopCtrl);
|
|
1011
|
-
content.appendChild(reverseCtrl);
|
|
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 = () => { cfgList.push({}); this._updateAnimationsPanel(); };
|
|
1040
|
+
content.appendChild(addBtn);
|
|
1012
1041
|
}
|
|
1013
1042
|
|
|
1014
1043
|
// ─── Effects Panel ───────────────────────────────────────────────────────
|
|
@@ -95,17 +95,39 @@ function buildTweenConfig(cfg, value) {
|
|
|
95
95
|
case 'translateX':
|
|
96
96
|
config.x = cfg.valueTo != null ? parseFloat(cfg.valueTo)
|
|
97
97
|
: (typeof normalVal === 'boolean' ? (normalVal ? 100 : 0) : numVal);
|
|
98
|
+
if (cfg.valueFrom != null) config.startAt = { x: parseFloat(cfg.valueFrom) };
|
|
98
99
|
break;
|
|
99
100
|
case 'translateY':
|
|
100
101
|
config.y = cfg.valueTo != null ? parseFloat(cfg.valueTo)
|
|
101
102
|
: (typeof normalVal === 'boolean' ? (normalVal ? 100 : 0) : numVal);
|
|
103
|
+
if (cfg.valueFrom != null) config.startAt = { y: parseFloat(cfg.valueFrom) };
|
|
102
104
|
break;
|
|
103
105
|
case 'translate': {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
// valueTo / valueFrom: "x,y" or single number (used for x, y=0)
|
|
107
|
+
const parseXY = (v, defVal) => {
|
|
108
|
+
if (v == null) return { x: defVal, y: 0 };
|
|
109
|
+
const s = String(v);
|
|
110
|
+
const parts = s.split(',');
|
|
111
|
+
return { x: parseFloat(parts[0]) || 0, y: parts.length > 1 ? parseFloat(parts[1]) || 0 : 0 };
|
|
112
|
+
};
|
|
113
|
+
const to = parseXY(cfg.valueTo, typeof normalVal === 'boolean' ? (normalVal ? 100 : 0) : numVal);
|
|
114
|
+
config.x = to.x; config.y = to.y;
|
|
115
|
+
if (cfg.valueFrom != null) {
|
|
116
|
+
const from = parseXY(cfg.valueFrom, 0);
|
|
117
|
+
config.startAt = { x: from.x, y: from.y };
|
|
118
|
+
}
|
|
107
119
|
break;
|
|
108
120
|
}
|
|
121
|
+
case 'left':
|
|
122
|
+
config.left = cfg.valueTo != null ? cfg.valueTo
|
|
123
|
+
: (typeof normalVal === 'boolean' ? (normalVal ? '100px' : '0px') : (numVal + 'px'));
|
|
124
|
+
if (cfg.valueFrom != null) config.startAt = { left: isNaN(cfg.valueFrom) ? cfg.valueFrom : cfg.valueFrom + 'px' };
|
|
125
|
+
break;
|
|
126
|
+
case 'top':
|
|
127
|
+
config.top = cfg.valueTo != null ? cfg.valueTo
|
|
128
|
+
: (typeof normalVal === 'boolean' ? (normalVal ? '100px' : '0px') : (numVal + 'px'));
|
|
129
|
+
if (cfg.valueFrom != null) config.startAt = { top: isNaN(cfg.valueFrom) ? cfg.valueFrom : cfg.valueFrom + 'px' };
|
|
130
|
+
break;
|
|
109
131
|
case 'skew': {
|
|
110
132
|
const sv = cfg.valueTo != null ? parseFloat(cfg.valueTo)
|
|
111
133
|
: (typeof normalVal === 'boolean' ? (normalVal ? 45 : 0) : numVal);
|
|
@@ -298,12 +320,19 @@ export async function scanAndApplyAnimations(root) {
|
|
|
298
320
|
const elements = (root || document).querySelectorAll('[data-animation]');
|
|
299
321
|
for (const el of elements) {
|
|
300
322
|
try {
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
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);
|
|
307
336
|
} catch (e) {
|
|
308
337
|
console.warn('[AnimationService] data-animation parse error on element:', el, e);
|
|
309
338
|
}
|
|
@@ -313,8 +342,9 @@ export async function scanAndApplyAnimations(root) {
|
|
|
313
342
|
export function cleanupAnimations(root) {
|
|
314
343
|
const elements = (root || document).querySelectorAll('[data-animation]');
|
|
315
344
|
for (const el of elements) {
|
|
316
|
-
const
|
|
317
|
-
|
|
345
|
+
const instances = _activeAnimations.get(el) || [];
|
|
346
|
+
for (const inst of instances) inst.destroy();
|
|
347
|
+
_activeAnimations.delete(el);
|
|
318
348
|
}
|
|
319
349
|
}
|
|
320
350
|
|