Re: 正在做etani,ejtile的動畫套件
发表于 : 2025年 10月 18日 10:42
圖標再改,css改,刪最後一值刪動畫。
代码: 全选
改動:
一,etaniAVMove圖標再次更改,其svg的path的d="M 7,4 L 3,8 L 7,12 M 3,8 L 18,8 M 17,12 L 21,16 L 17,20 M 21,16 L 6,16"。
二,css更改:
.etaniItemLeft去掉background-color和border-right
.etaniItem加上background-color:lightyellow
三,當etaniAVDelete刪掉最後一個值時,則這個動畫將被刪除。如果刪的類型是transform,則它的<use>的兩個父元素也要刪除。
代碼中所有的注釋都要使用英文。
代码: 全选
// Global variable to hold the SVG clone and ensure state is maintained
let etani_clone = null;
// Track copy, move, and delete mode states, selected item, and last click time for double-click detection
let isCopyMode = false;
let isMoveMode = false;
let isDeleteMode = false;
let selectedMoveItem = null;
let lastClickTime = 0;
let lastClickedItem = null;
// Add dynamic CSS styles to the document
function addDynamicStyles() {
if (document.getElementById('dynamic-et-styles')) {
return;
}
const styleSheet = document.createElement('style');
styleSheet.id = 'dynamic-et-styles';
styleSheet.textContent = `
/* Styles for etaniouter and button */
.etaniouter {
margin-top: 10px;
padding: 10px;
border: 1px solid #ddd;
background-color: #f5f5f5;
overflow: auto;
}
/* Styles for etaniinner, etaniCtrl, etaniCol, etaniResult */
.etaniinner {
margin-top: 10px;
}
.etaniCtrl {
margin-bottom: 10px;
clear: both;
padding: 5px;
border: 1px solid #c0c0c0;
text-align: center;
}
/* Styles for etaniContent, etaniSetting, etaniAllAppend, etaniValueCtrl */
.etaniContent, .etaniSetting, .etaniAllAppend, .etaniValueCtrl {
display: inline-block;
vertical-align: top;
padding: 5px;
border: 1px solid #ccc;
margin: 0 5px 5px 5px;
text-align: left;
}
.etaniCol {
border: 1px solid #aaa;
padding: 5px;
overflow: auto;
margin-bottom: 10px;
clear: both;
}
/* Styles for etaniResult */
.etaniResult {
text-align: center;
margin-bottom: 10px;
padding: 10px;
border: 1px solid #bbb;
overflow: auto;
}
/* Control and button styles */
.etaniContent a, .etaniValueCtrl a {
display: inline-block;
margin: 0 5px;
text-decoration: none;
padding: 5px 10px;
font-size: 14px;
}
.etaniCenter {
border: 1px solid green;
color: green;
}
.etaniContentHTML {
border: 1px solid #0099ff;
color: #0099ff;
margin-right: 15px;
}
.etaniValueCtrlUp {
border: 1px solid purple;
color: purple;
}
/* Styles for etaniAllAppend buttons */
.etaniAllAppend button {
padding: 5px 10px;
font-size: 16px;
margin: 0 5px;
cursor: pointer;
border: 1px solid #333;
background-color: #fff;
}
/* Custom radio button styles */
.etaniSettingMode {
display: inline-block;
cursor: pointer;
padding: 4px 8px;
margin: 0 3px;
font-size: 14px;
border: 1px solid #888;
background-color: #eee;
color: #333;
user-select: none;
}
.etaniSettingMode.active {
background-color: #008CBA;
color: white;
border-color: #008CBA;
}
/* Result and animation item styles */
.etaniResultDR {
text-align: center;
margin-bottom: 10px;
}
.etaniResultDownload, .etaniResultRename {
display: inline-block;
margin-right: 15px;
text-decoration: none;
padding: 5px 10px;
font-size: 16px;
}
.etaniResultDownload {
border: 1px solid blue;
color: blue;
}
.etaniResultRename {
border: 1px solid brown;
color: brown;
}
.etaniResultImage {
display: block;
max-width: 480px;
width: 100%;
height: auto;
margin: 0 auto 10px auto;
border: 1px solid #000;
}
.etaniResultSize {
display: inline-block;
margin-left: 10px;
font-size: 12px;
color: #555;
}
/* etaniItem structure */
.etaniItem {
min-height: 48px;
border: 1px solid #ccc;
box-sizing: border-box;
width: 100%;
margin-bottom: -1px;
overflow: auto;
background-color: lightyellow;
}
.etaniItemLeft {
float: left;
width: 60px;
min-height: 48px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2px 0;
}
.etaniItemRight {
margin-left: 60px;
padding: 7px;
min-height: 48px;
background-color: #fff;
}
.tileid {
text-align: center;
font-size: 12px;
word-break: break-all;
padding-top: 2px;
}
/* Animation controls */
.etaniAnimate {
border: 1px solid #999;
padding: 5px;
margin-bottom: 5px;
}
.etaniAnimateName {
display: inline-block;
padding: 2px 5px;
background-color: #555;
color: white;
margin-right: 10px;
font-size: 12px;
}
.etaniAnimateDur {
display: inline-block;
margin-right: 10px;
font-size: 14px;
}
.etaniAnimateValue {
margin-top: 5px;
}
.etaniAVCtrl {
display: inline-block;
vertical-align: top;
margin-right: 5px;
margin-bottom: 3px;
}
.etaniAVCtrl svg {
margin-left: -1px;
margin-top: -1px;
}
.etaniAVCtrl span {
display: inline-block;
width: 24px;
height: 24px;
line-height: 24px;
text-align: center;
cursor: pointer;
box-sizing: border-box;
vertical-align: top;
margin-right: 3px;
}
.etaniAVAdd {
background-color: #a7fca7;
border: 1px solid #71c371;
}
.etaniAVDelete {
background-color: #ffcccc;
border: 1px solid #cc3333;
}
.etaniAVDelete.deleting {
background-color: #cc3333;
color: white;
}
.etaniAVCopy {
background-color: #ccccff;
border: 1px solid #6666cc;
}
.etaniAVCopy.copying {
background-color: #6666cc;
color: white;
}
.etaniAVMove {
background-color: #ffcc99;
border: 1px solid #cc9966;
}
.etaniAVMove.moving {
background-color: #cc9966;
color: white;
}
.etaniAVLabel {
font-size: 14px;
margin-right: 5px;
}
.etaniAV {
display: inline-block;
vertical-align: top;
}
.etaniAVItem {
display: inline-block;
height: 24px;
background-color: #ff9933;
border: 1px dashed #00bfff;
margin: 0 5px 3px;
padding: 0 5px;
box-sizing: border-box;
cursor: pointer;
position: relative;
text-align: center;
line-height: 24px;
font-size: 12px;
color: #333;
}
.etaniAVItem.deleting-target, .etaniAVItem.copying-target, .etaniAVItem.moving-target {
background-color: #ff4d4d;
border: 2px solid red;
}
.etaniAVItem.selected-move {
background-color: #66ccff;
border: 2px solid #0066cc;
}
/* Modal/popup styles */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
}
.modal-content {
position: fixed;
width: 98%;
height: 48%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
z-index: 1001;
box-sizing: border-box;
}
.modal-content textarea {
width: 100%;
height: calc(100% - 50px);
resize: none;
border: 1px solid #ccc;
font-size: 12px;
box-sizing: border-box;
}
.modal-close {
position: absolute;
top: 0;
right: 10px;
font-size: 48px;
line-height: 1;
cursor: pointer;
color: #333;
}
`;
document.head.appendChild(styleSheet);
}
// Find the first missing letter in the sequence starting from 'a'
function findFirstMissingLetter(existingLetters) {
let letter = 'a';
while (existingLetters.includes(letter)) {
const code = letter.charCodeAt(0);
if (code >= 97 && code < 122) { // lowercase a-y
letter = String.fromCharCode(code + 1);
} else if (code === 122) { // z -> A
letter = 'A';
} else if (code >= 65 && code < 90) { // uppercase A-Y
letter = String.fromCharCode(code + 1);
} else if (code === 90) { // Z -> a (loop back)
letter = 'a';
}
}
return letter;
}
// Extract a specific transform function and its value from a transform string
function extractTransformPart(transformString, type) {
const regex = new RegExp(`(${type})\\(([^)]+)\\)`, 'i');
const match = transformString.match(regex);
if (match) {
return { func: match[1], value: match[2].trim() };
}
return { func: '', value: '' };
}
// Parse transform string to get individual transform values
function parseTransform(transformString) {
const defaultTransform = { translate: '0,0', scale: '1,1', rotate: '0' };
if (!transformString) return defaultTransform;
const transform = {};
const getMatch = (type) => {
const part = extractTransformPart(transformString, type).value;
if (!part) return null;
return part.split(/[,\s]+/).join(',');
};
transform.translate = getMatch('translate') || defaultTransform.translate;
transform.scale = getMatch('scale') || defaultTransform.scale;
if (transform.scale.split(',').length === 1) {
transform.scale += `,${transform.scale}`;
}
transform.rotate = getMatch('rotate') || defaultTransform.rotate;
return transform;
}
// Convert SVG string to a Base64 data URL
function svgToBase64(svgString) {
const base64 = btoa(unescape(encodeURIComponent(svgString)));
return `data:image/svg+xml;base64,${base64}`;
}
// Format byte size into human-readable string
function formatBytes(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// Update the result section with the current state of etani_clone
function updateEtaniResult() {
if (!etani_clone) return;
const svgString = new XMLSerializer().serializeToString(etani_clone);
const sizeInBytes = new Blob([svgString]).size;
const base64Url = svgToBase64(svgString);
const now = new Date();
const yyyy = now.getFullYear();
const mm = String(now.getMonth() + 1).padStart(2, '0');
const dd = String(now.getDate()).padStart(2, '0');
const hh = String(now.getHours()).padStart(2, '0');
const mi = String(now.getMinutes()).padStart(2, '0');
const ss = String(now.getSeconds()).padStart(2, '0');
const defaultFilename = `ejtileAnimation_${yyyy}${mm}${dd}_${hh}${mi}${ss}.svg`;
const imgElement = document.querySelector('.etaniResultImage');
const downloadElement = document.querySelector('.etaniResultDownload');
const renameElement = document.querySelector('.etaniResultRename');
const sizeElement = document.querySelector('.etaniResultSize');
if (imgElement && downloadElement && renameElement && sizeElement) {
imgElement.src = base64Url;
sizeElement.textContent = `Size: ${formatBytes(sizeInBytes)}`;
downloadElement.href = base64Url;
downloadElement.download = defaultFilename;
renameElement.onclick = (e) => {
e.preventDefault();
let currentDownloadName = downloadElement.download;
let newFilename = prompt("Enter new filename:", currentDownloadName);
if (newFilename) {
if (!newFilename.toLowerCase().endsWith('.svg')) {
newFilename += '.svg';
}
downloadElement.download = newFilename;
alert(`Filename changed to: ${newFilename}`);
}
};
}
}
// Handle the 'Center' button click to reset the .etdrop transform
function handleCenterClick(e) {
e.preventDefault();
if (etani_clone) {
const etdrop = etani_clone.querySelector('.etdrop');
if (etdrop) {
etdrop.setAttribute('transform', 'translate(240,240) scale(1,1)');
updateEtaniResult();
}
}
}
// Handle the HTML popup window
function handleContentHTMLClick(e) {
e.preventDefault();
if (!etani_clone) return;
const overlay = document.createElement('div');
overlay.className = 'modal-overlay';
const content = document.createElement('div');
content.className = 'modal-content';
const close = document.createElement('span');
close.className = 'modal-close';
close.innerHTML = '×';
const textarea = document.createElement('textarea');
textarea.value = new XMLSerializer().serializeToString(etani_clone);
const closeModal = () => {
if (document.body.contains(overlay)) document.body.removeChild(overlay);
if (document.body.contains(content)) document.body.removeChild(content);
};
close.onclick = closeModal;
overlay.onclick = closeModal;
content.appendChild(close);
content.appendChild(textarea);
document.body.appendChild(overlay);
document.body.appendChild(content);
}
// Calculate additive transform or opacity value for a given type
function calculateAdditiveValue(type, originalValue, currentBaseValue, scaleValue = '1,1') {
const roundToThreeDecimals = (value) => {
return Math.round(value * 1000) / 1000;
};
if (type === 'rotate') {
const ovAngle = parseFloat(currentBaseValue.trim());
const cvAngle = parseFloat(originalValue.trim());
return roundToThreeDecimals(cvAngle - ovAngle);
} else if (type === 'scale') {
const ovScales = currentBaseValue.split(',').map(c => parseFloat(c.trim()));
const cvScales = originalValue.split(',').map(c => parseFloat(c.trim()));
const v1 = roundToThreeDecimals(cvScales[0] / ovScales[0]);
const v2 = roundToThreeDecimals(cvScales.length > 1 ? (cvScales[1] / ovScales[1]) : (cvScales[0] / ovScales[0]));
return v1 === v2 ? `${v1}` : `${v1},${v2}`;
} else if (type === 'translate') {
const ovCoords = currentBaseValue.split(',').map(c => parseFloat(c.trim()));
const cvCoords = originalValue.split(',').map(c => parseFloat(c.trim()));
const scaleFactors = scaleValue.split(',').map(c => parseFloat(c.trim()));
const v1 = roundToThreeDecimals(cvCoords[0] - (ovCoords[0] * scaleFactors[0]));
const v2 = roundToThreeDecimals(cvCoords[1] - (ovCoords[1] * (scaleFactors.length > 1 ? scaleFactors[1] : scaleFactors[0])));
return `${v1},${v2}`;
} else if (type === 'opacity') {
return originalValue;
}
return '';
}
// Update duration based on the number of values
function updateDuration(animate, etaniAnimate, newValuesLength) {
const newDur = newValuesLength > 1 ? newValuesLength - 1 : 0;
const isRepeat = document.querySelector('.etaniSettingRepeat.active');
if (newDur > 0) {
animate.setAttribute('dur', `${newDur}s`);
if (isRepeat) {
animate.removeAttribute('fill');
animate.setAttribute('repeatCount', 'indefinite');
} else {
animate.removeAttribute('repeatCount');
animate.setAttribute('fill', 'freeze');
}
} else {
animate.removeAttribute('dur');
animate.removeAttribute('fill');
animate.removeAttribute('repeatCount');
}
etaniAnimate.querySelector('.etaniAnimateDur').textContent = `dur: ${newDur}s`;
}
// Handle click event for the '+' button for transform or opacity
function handleAVAddClick(e, useElementId, animateType = 'transform') {
e.preventDefault();
if (!etani_clone) return;
const etaniItemRight = e.currentTarget.closest('.etaniItemRight');
const etaniAV = etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"] .etaniAV`);
const etaniAnimate = etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`);
if (animateType === 'transform') {
const originalUseElement = document.querySelector(`.etdrop use[href="#${useElementId}"]`);
if (!originalUseElement) return;
const originalTransformString = originalUseElement.getAttribute('transform') || '';
const originalTransforms = parseTransform(originalTransformString);
const translateWrapper = etani_clone.querySelector(`.etdrop > g[data-use-href="#${useElementId}"]`);
if (!translateWrapper) return;
const scaleWrapper = translateWrapper.querySelector(`g[data-use-href-scale="#${useElementId}"]`);
const cloneUseElement = scaleWrapper ? scaleWrapper.querySelector('use') : null;
if (!scaleWrapper || !cloneUseElement) return;
const cloneBaseTransformString = cloneUseElement.getAttribute('transform') || 'translate(0,0) scale(1,1) rotate(0)';
const currentBaseTransforms = parseTransform(cloneBaseTransformString);
const currentBaseTranslate = currentBaseTransforms.translate;
const currentBaseScale = currentBaseTransforms.scale;
const currentBaseRotate = currentBaseTransforms.rotate;
const animatesTranslate = translateWrapper.querySelectorAll('animateTransform[type="translate"]');
const animatesScale = scaleWrapper.querySelectorAll('animateTransform[type="scale"]');
const animatesRotate = cloneUseElement.querySelectorAll('animateTransform[type="rotate"]');
const allAnimates = [...animatesRotate, ...animatesScale, ...animatesTranslate];
const scaleValue = calculateAdditiveValue('scale', originalTransforms.scale, currentBaseScale);
allAnimates.forEach(animate => {
const type = animate.getAttribute('type').toLowerCase();
let currentValueString = animate.getAttribute('values') || '';
let newValue = '';
if (type === 'rotate') {
newValue = calculateAdditiveValue('rotate', originalTransforms.rotate, currentBaseRotate);
} else if (type === 'scale') {
newValue = scaleValue;
} else if (type === 'translate') {
newValue = calculateAdditiveValue('translate', originalTransforms.translate, currentBaseTranslate, scaleValue);
}
const newValueString = (currentValueString ? currentValueString + ';' : '') + newValue;
animate.setAttribute('values', newValueString);
updateDuration(animate, etaniAnimate, newValueString.split(';').length);
});
} else if (animateType === 'opacity') {
const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useElementId}"]`);
if (!cloneUseElement) return;
const animateOpacity = cloneUseElement.querySelector('animate[attributeName="opacity"]');
if (!animateOpacity) return;
const currentValueString = animateOpacity.getAttribute('values') || '';
const values = currentValueString ? currentValueString.split(';') : [];
const newValue = values.length > 0 ? values[values.length - 1] : '1';
const newValueString = (currentValueString ? currentValueString + ';' : '') + newValue;
animateOpacity.setAttribute('values', newValueString);
updateDuration(animateOpacity, etaniAnimate, newValueString.split(';').length);
}
// Add new etaniAVItem with appropriate label
if (etaniAV && etaniAnimate) {
const newAVItem = document.createElement('span');
newAVItem.className = 'etaniAVItem';
newAVItem.addEventListener('click', (e) => handleAVItemClick(e, animateType));
const existingItems = etaniAV.querySelectorAll('.etaniAVItem');
if (animateType === 'transform') {
const existingLetters = Array.from(existingItems).map(item => item.textContent);
newAVItem.textContent = findFirstMissingLetter(existingLetters);
} else if (animateType === 'opacity') {
const values = animateType === 'opacity' ?
etaniAnimate.querySelector('animate[attributeName="opacity"]')?.getAttribute('values')?.split(';') || ['1'] :
['1'];
newAVItem.textContent = values[values.length - 1];
}
etaniAV.appendChild(newAVItem);
}
updateEtaniResult();
}
// Reset all mode states for a specific animateType
function resetModes(animateType, excludeMode = null) {
const etaniCol = document.querySelector('.etaniCol');
const avItems = document.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);
if (excludeMode !== 'delete') {
isDeleteMode = false;
etaniCol.classList.remove(`deleting-mode-${animateType}`);
document.querySelectorAll(`.etaniAVDelete[data-type="${animateType}"]`).forEach(btn => {
btn.classList.remove('deleting');
btn.title = 'Delete Value';
});
}
if (excludeMode !== 'copy') {
isCopyMode = false;
etaniCol.classList.remove(`copying-mode-${animateType}`);
document.querySelectorAll(`.etaniAVCopy[data-type="${animateType}"]`).forEach(btn => {
btn.classList.remove('copying');
btn.title = 'Copy Value';
});
}
if (excludeMode !== 'move') {
isMoveMode = false;
selectedMoveItem = null;
etaniCol.classList.remove(`moving-mode-${animateType}`);
document.querySelectorAll(`.etaniAVMove[data-type="${animateType}"]`).forEach(btn => {
btn.classList.remove('moving');
btn.title = 'Move Value';
});
}
avItems.forEach(item => {
item.classList.remove('deleting-target', 'copying-target', 'moving-target', 'selected-move');
});
}
// Handle click event for the '-' button to toggle deletion mode
function handleAVDeleteToggle(e, animateType = 'transform') {
e.preventDefault();
const deleteButton = e.currentTarget;
resetModes(animateType, 'delete');
isDeleteMode = !isDeleteMode;
deleteButton.classList.toggle('deleting', isDeleteMode);
const etaniItemRight = deleteButton.closest('.etaniItemRight');
const etaniCol = deleteButton.closest('.etaniCol');
const avItems = etaniItemRight.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);
avItems.forEach(item => {
item.classList.toggle('deleting-target', isDeleteMode);
});
etaniCol.classList.toggle(`deleting-mode-${animateType}`, isDeleteMode);
if (isDeleteMode) {
deleteButton.title = "Click value item to delete (Click again to cancel)";
} else {
deleteButton.title = "Delete Value";
avItems.forEach(item => {
item.classList.remove('deleting-target');
});
}
}
// Handle click event for the 'Copy' button to toggle copy mode
function handleAVCopyToggle(e, animateType) {
e.preventDefault();
const copyButton = e.currentTarget;
resetModes(animateType, 'copy');
isCopyMode = !isCopyMode;
copyButton.classList.toggle('copying', isCopyMode);
const etaniItemRight = copyButton.closest('.etaniItemRight');
const etaniCol = copyButton.closest('.etaniCol');
const avItems = etaniItemRight.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);
avItems.forEach(item => {
item.classList.toggle('copying-target', isCopyMode);
});
etaniCol.classList.toggle(`copying-mode-${animateType}`, isCopyMode);
if (isCopyMode) {
copyButton.title = "Click value item to copy (Click again to cancel)";
} else {
copyButton.title = "Copy Value";
avItems.forEach(item => {
item.classList.remove('copying-target');
});
}
}
// Handle click event for the 'Move' button to toggle move mode
function handleAVMoveToggle(e, animateType) {
e.preventDefault();
const moveButton = e.currentTarget;
resetModes(animateType, 'move');
isMoveMode = !isMoveMode;
moveButton.classList.toggle('moving', isMoveMode);
const etaniItemRight = moveButton.closest('.etaniItemRight');
const etaniCol = moveButton.closest('.etaniCol');
const avItems = etaniItemRight.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);
avItems.forEach(item => {
item.classList.toggle('moving-target', isMoveMode);
});
etaniCol.classList.toggle(`moving-mode-${animateType}`, isMoveMode);
if (isMoveMode) {
moveButton.title = "Click value item to select, then click another to move (Click again to cancel)";
} else {
moveButton.title = "Move Value";
selectedMoveItem = null;
avItems.forEach(item => {
item.classList.remove('moving-target');
item.classList.remove('selected-move');
});
}
}
// Handle click event for an .etaniAVItem in deletion, copy, or move mode
function handleAVItemClick(e, animateType = 'transform') {
const item = e.currentTarget;
const itemIndex = Array.from(item.parentNode.children).indexOf(item);
const etaniItemRight = item.closest('.etaniItemRight');
const etaniCol = item.closest('.etaniCol');
const useId = etaniItemRight.closest('.etaniItem').querySelector('.tileid').textContent;
const currentTime = Date.now();
const isDoubleClick = (item === lastClickedItem && (currentTime - lastClickTime) < 2000);
lastClickTime = currentTime;
lastClickedItem = item;
if (etaniCol.classList.contains(`moving-mode-${animateType}`)) {
const avItems = etaniItemRight.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);
if (!selectedMoveItem) {
selectedMoveItem = item;
item.classList.add('selected-move');
return;
} else if (selectedMoveItem === item) {
selectedMoveItem.classList.remove('selected-move');
selectedMoveItem = null;
return;
} else {
const targetIndex = itemIndex;
const sourceIndex = Array.from(item.parentNode.children).indexOf(selectedMoveItem);
const parent = item.parentNode;
if (sourceIndex < targetIndex) {
if (item.nextSibling) {
parent.insertBefore(selectedMoveItem, item.nextSibling);
} else {
parent.appendChild(selectedMoveItem);
}
} else {
parent.insertBefore(selectedMoveItem, item);
}
if (animateType === 'transform') {
const translateWrapper = etani_clone.querySelector(`.etdrop > g[data-use-href="#${useId}"]`);
if (!translateWrapper) return;
const scaleWrapper = translateWrapper.querySelector(`g[data-use-href-scale="#${useId}"]`);
const cloneUseElement = scaleWrapper ? scaleWrapper.querySelector('use') : null;
if (!scaleWrapper || !cloneUseElement) return;
const animatesTranslate = translateWrapper.querySelectorAll('animateTransform[type="translate"]');
const animatesScale = scaleWrapper.querySelectorAll('animateTransform[type="scale"]');
const animatesRotate = cloneUseElement.querySelectorAll('animateTransform[type="rotate"]');
const allAnimates = [...animatesTranslate, ...animatesScale, ...animatesRotate];
allAnimates.forEach(animate => {
const values = animate.getAttribute('values').split(';');
const valueToMove = values[sourceIndex];
values.splice(sourceIndex, 1);
if (sourceIndex < targetIndex) {
values.splice(targetIndex, 0, valueToMove);
} else {
values.splice(targetIndex, 0, valueToMove);
}
animate.setAttribute('values', values.join(';'));
});
} else if (animateType === 'opacity') {
const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
if (!cloneUseElement) return;
const animateOpacity = cloneUseElement.querySelector('animate[attributeName="opacity"]');
if (!animateOpacity) return;
const values = animateOpacity.getAttribute('values').split(';');
const valueToMove = values[sourceIndex];
values.splice(sourceIndex, 1);
if (sourceIndex < targetIndex) {
values.splice(targetIndex, 0, valueToMove);
} else {
values.splice(targetIndex, 0, valueToMove);
}
animateOpacity.setAttribute('values', values.join(';'));
}
selectedMoveItem.classList.remove('selected-move');
selectedMoveItem = null;
updateEtaniResult();
return;
}
}
if (etaniCol.classList.contains(`copying-mode-${animateType}`)) {
const etaniAV = item.parentNode;
const newAVItem = document.createElement('span');
newAVItem.className = 'etaniAVItem';
newAVItem.textContent = item.textContent;
newAVItem.addEventListener('click', (e) => handleAVItemClick(e, animateType));
etaniAV.insertBefore(newAVItem, item.nextSibling);
if (animateType === 'transform') {
const translateWrapper = etani_clone.querySelector(`.etdrop > g[data-use-href="#${useId}"]`);
if (!translateWrapper) return;
const scaleWrapper = translateWrapper.querySelector(`g[data-use-href-scale="#${useId}"]`);
const cloneUseElement = scaleWrapper ? scaleWrapper.querySelector('use') : null;
if (!scaleWrapper || !cloneUseElement) return;
const animatesTranslate = translateWrapper.querySelectorAll('animateTransform[type="translate"]');
const animatesScale = scaleWrapper.querySelectorAll('animateTransform[type="scale"]');
const animatesRotate = cloneUseElement.querySelectorAll('animateTransform[type="rotate"]');
const allAnimates = [...animatesTranslate, ...animatesScale, ...animatesRotate];
allAnimates.forEach(animate => {
const values = animate.getAttribute('values').split(';');
if (itemIndex < values.length) {
values.splice(itemIndex + 1, 0, values[itemIndex]);
animate.setAttribute('values', values.join(';'));
updateDuration(animate, etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`), values.length);
}
});
} else if (animateType === 'opacity') {
const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
if (!cloneUseElement) return;
const animateOpacity = cloneUseElement.querySelector('animate[attributeName="opacity"]');
if (!animateOpacity) return;
const values = animateOpacity.getAttribute('values').split(';');
if (itemIndex < values.length) {
values.splice(itemIndex + 1, 0, values[itemIndex]);
animateOpacity.setAttribute('values', values.join(';'));
updateDuration(animateOpacity, etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`), values.length);
}
}
updateEtaniResult();
return;
}
if (etaniCol.classList.contains(`deleting-mode-${animateType}`)) {
const deleteButton = etaniItemRight.querySelector(`.etaniAVDelete[data-type="${animateType}"]`);
const etaniAnimate = etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`);
if (animateType === 'transform') {
const translateWrapper = etani_clone.querySelector(`.etdrop > g[data-use-href="#${useId}"]`);
if (!translateWrapper) return;
const scaleWrapper = translateWrapper.querySelector(`g[data-use-href-scale="#${useId}"]`);
const cloneUseElement = scaleWrapper ? scaleWrapper.querySelector('use') : null;
if (!scaleWrapper || !cloneUseElement) return;
const animatesTranslate = translateWrapper.querySelectorAll('animateTransform[type="translate"]');
const animatesScale = scaleWrapper.querySelectorAll('animateTransform[type="scale"]');
const animatesRotate = cloneUseElement.querySelectorAll('animateTransform[type="rotate"]');
const allAnimates = [...animatesTranslate, ...animatesScale, ...animatesRotate];
allAnimates.forEach(animate => {
const values = animate.getAttribute('values').split(';');
if (itemIndex < values.length) {
values.splice(itemIndex, 1);
if (values.length === 0) {
// Remove the entire transform animation and its wrappers
translateWrapper.replaceWith(cloneUseElement);
cloneUseElement.setAttribute('transform', 'translate(0,0) scale(1,1) rotate(0)');
etaniAnimate.remove();
} else {
animate.setAttribute('values', values.join(';'));
updateDuration(animate, etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`), values.length);
}
}
});
} else if (animateType === 'opacity') {
const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
if (!cloneUseElement) return;
const animateOpacity = cloneUseElement.querySelector('animate[attributeName="opacity"]');
if (!animateOpacity) return;
const values = animateOpacity.getAttribute('values').split(';');
if (itemIndex < values.length) {
values.splice(itemIndex, 1);
if (values.length === 0) {
// Remove the opacity animation
animateOpacity.remove();
etaniAnimate.remove();
} else {
animateOpacity.setAttribute('values', values.join(';'));
updateDuration(animateOpacity, etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`), values.length);
}
}
}
item.remove();
const remainingItems = etaniItemRight.querySelectorAll(`.etaniAnimate[data-type="${animateType}"] .etaniAVItem`);
if (remainingItems.length === 0) {
deleteButton.click();
} else {
remainingItems.forEach(item => {
item.classList.add('deleting-target');
});
}
updateEtaniResult();
} else if (animateType === 'opacity' && !etaniCol.classList.contains(`deleting-mode-${animateType}`) && !etaniCol.classList.contains(`copying-mode-${animateType}`)) {
const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
if (!cloneUseElement) return;
const animateOpacity = cloneUseElement.querySelector('animate[attributeName="opacity"]');
if (!animateOpacity) return;
const values = animateOpacity.getAttribute('values').split(';');
if (itemIndex >= values.length) return;
if (isDoubleClick) {
const newValue = prompt("Enter opacity value (0 to 1):", values[itemIndex]);
if (newValue !== null && !isNaN(newValue) && newValue >= 0 && newValue <= 1) {
values[itemIndex] = newValue;
item.textContent = newValue;
animateOpacity.setAttribute('values', values.join(';'));
updateEtaniResult();
}
} else {
values[itemIndex] = values[itemIndex] === '0' ? '1' : '0';
item.textContent = values[itemIndex];
animateOpacity.setAttribute('values', values.join(';'));
updateEtaniResult();
}
}
}
// Handle click event for the 'Transform' button
function handleAllAppendTransformClick() {
if (!etani_clone) return;
const etdropUses = document.querySelectorAll('#etmain .etdrop use');
const etaniItemRights = document.querySelectorAll('.etaniItemRight');
const isRepeat = document.querySelector('.etaniSettingRepeat.active');
etdropUses.forEach((originalUseElement, i) => {
const useId = originalUseElement.getAttribute('href').substring(1);
const itemRight = etaniItemRights[i];
if (!itemRight) return;
if (itemRight.querySelector('.etaniAnimate[data-type="transform"]')) return;
let cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
if (!cloneUseElement) return;
const baseAnimate = (type, initialValue) => {
const animate = document.createElementNS('http://www.w3.org/2000/svg', 'animateTransform');
animate.setAttribute('attributeName', 'transform');
animate.setAttribute('attributeType', 'XML');
animate.setAttribute('type', type);
animate.setAttribute('values', initialValue);
animate.setAttribute('fill', isRepeat ? 'remove' : 'freeze');
if (isRepeat) animate.setAttribute('repeatCount', 'indefinite');
animate.setAttribute('additive', 'sum');
return animate;
};
cloneUseElement.appendChild(baseAnimate('rotate', '0'));
const scaleWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'g');
scaleWrapper.setAttribute('data-use-href-scale', `#${useId}`);
scaleWrapper.appendChild(baseAnimate('scale', '1'));
const translateWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'g');
translateWrapper.setAttribute('data-use-href', `#${useId}`);
translateWrapper.appendChild(baseAnimate('translate', '0,0'));
cloneUseElement.parentNode.insertBefore(translateWrapper, cloneUseElement);
cloneUseElement.parentNode.insertBefore(scaleWrapper, cloneUseElement);
scaleWrapper.appendChild(cloneUseElement);
translateWrapper.appendChild(scaleWrapper);
const etaniAnimate = document.createElement('div');
etaniAnimate.className = 'etaniAnimate';
etaniAnimate.setAttribute('data-type', 'transform');
const nameSpan = document.createElement('span');
nameSpan.className = 'etaniAnimateName';
nameSpan.textContent = 'transform';
etaniAnimate.appendChild(nameSpan);
const durSpan = document.createElement('span');
durSpan.className = 'etaniAnimateDur';
durSpan.textContent = 'dur: 0s';
etaniAnimate.appendChild(durSpan);
const valueDiv = document.createElement('div');
valueDiv.className = 'etaniAnimateValue';
const avCtrlDiv = document.createElement('div');
avCtrlDiv.className = 'etaniAVCtrl';
const avAddSpan = document.createElement('span');
avAddSpan.className = 'etaniAVAdd';
avAddSpan.title = 'Add Transform Value';
avAddSpan.setAttribute('data-type', 'transform');
avAddSpan.addEventListener('click', (e) => handleAVAddClick(e, useId, 'transform'));
const plusSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
plusSvg.setAttribute('width', '24');
plusSvg.setAttribute('height', '24');
plusSvg.setAttribute('viewBox', '0 0 24 24');
plusSvg.setAttribute('fill', 'none');
plusSvg.setAttribute('stroke', 'currentColor');
plusSvg.setAttribute('stroke-width', '1');
plusSvg.setAttribute('stroke-linecap', 'round');
plusSvg.setAttribute('stroke-linejoin', 'round');
plusSvg.innerHTML = '<line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line>';
avAddSpan.appendChild(plusSvg);
const avDeleteSpan = document.createElement('span');
avDeleteSpan.className = 'etaniAVDelete';
avDeleteSpan.title = 'Delete Value';
avDeleteSpan.setAttribute('data-type', 'transform');
avDeleteSpan.addEventListener('click', (e) => handleAVDeleteToggle(e, 'transform'));
const minusSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
minusSvg.setAttribute('width', '24');
minusSvg.setAttribute('height', '24');
minusSvg.setAttribute('viewBox', '0 0 24 24');
minusSvg.setAttribute('fill', 'none');
minusSvg.setAttribute('stroke', 'currentColor');
minusSvg.setAttribute('stroke-width', '1');
minusSvg.setAttribute('stroke-linecap', 'round');
minusSvg.setAttribute('stroke-linejoin', 'round');
minusSvg.innerHTML = '<line x1="5" y1="12" x2="19" y2="12"></line>';
avDeleteSpan.appendChild(minusSvg);
const avCopySpan = document.createElement('span');
avCopySpan.className = 'etaniAVCopy';
avCopySpan.title = 'Copy Value';
avCopySpan.setAttribute('data-type', 'transform');
avCopySpan.addEventListener('click', (e) => handleAVCopyToggle(e, 'transform'));
const copySvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
copySvg.setAttribute('width', '24');
copySvg.setAttribute('height', '24');
copySvg.setAttribute('viewBox', '0 0 24 24');
copySvg.setAttribute('fill', 'none');
copySvg.setAttribute('stroke', 'currentColor');
copySvg.setAttribute('stroke-width', '1');
copySvg.setAttribute('stroke-linecap', 'round');
copySvg.setAttribute('stroke-linejoin', 'round');
copySvg.innerHTML = '<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>';
avCopySpan.appendChild(copySvg);
const avMoveSpan = document.createElement('span');
avMoveSpan.className = 'etaniAVMove';
avMoveSpan.title = 'Move Value';
avMoveSpan.setAttribute('data-type', 'transform');
avMoveSpan.addEventListener('click', (e) => handleAVMoveToggle(e, 'transform'));
const moveSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
moveSvg.setAttribute('width', '24');
moveSvg.setAttribute('height', '24');
moveSvg.setAttribute('viewBox', '0 0 24 24');
moveSvg.setAttribute('fill', 'none');
moveSvg.setAttribute('stroke', 'currentColor');
moveSvg.setAttribute('stroke-width', '1');
moveSvg.setAttribute('stroke-linecap', 'round');
moveSvg.setAttribute('stroke-linejoin', 'round');
moveSvg.innerHTML = '<path d="M 7,4 L 3,8 L 7,12 M 3,8 L 18,8 M 17,12 L 21,16 L 17,20 M 21,16 L 6,16"></path>';
avMoveSpan.appendChild(moveSvg);
avCtrlDiv.appendChild(avAddSpan);
avCtrlDiv.appendChild(avDeleteSpan);
avCtrlDiv.appendChild(avCopySpan);
avCtrlDiv.appendChild(avMoveSpan);
const avLabelSpan = document.createElement('span');
avLabelSpan.className = 'etaniAVLabel';
avLabelSpan.textContent = 'values : ';
const avDiv = document.createElement('div');
avDiv.className = 'etaniAV';
const avItemSpan = document.createElement('span');
avItemSpan.className = 'etaniAVItem';
avItemSpan.textContent = 'a';
avItemSpan.addEventListener('click', (e) => handleAVItemClick(e, 'transform'));
avDiv.appendChild(avItemSpan);
valueDiv.appendChild(avCtrlDiv);
valueDiv.appendChild(avLabelSpan);
valueDiv.appendChild(avDiv);
etaniAnimate.appendChild(valueDiv);
itemRight.appendChild(etaniAnimate);
});
updateEtaniResult();
}
// Handle click event for the 'Opacity' button
function handleAllAppendOpacityClick() {
if (!etani_clone) return;
const etdropUses = document.querySelectorAll('#etmain .etdrop use');
const etaniItemRights = document.querySelectorAll('.etaniItemRight');
const isRepeat = document.querySelector('.etaniSettingRepeat.active');
etdropUses.forEach((originalUseElement, i) => {
const useId = originalUseElement.getAttribute('href').substring(1);
const itemRight = etaniItemRights[i];
if (!itemRight) return;
if (itemRight.querySelector('.etaniAnimate[data-type="opacity"]')) return;
let cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
if (!cloneUseElement) return;
const animateOpacity = document.createElementNS('http://www.w3.org/2000/svg', 'animate');
animateOpacity.setAttribute('attributeName', 'opacity');
animateOpacity.setAttribute('values', '1');
animateOpacity.setAttribute('fill', isRepeat ? 'remove' : 'freeze');
if (isRepeat) animateOpacity.setAttribute('repeatCount', 'indefinite');
cloneUseElement.appendChild(animateOpacity);
const etaniAnimate = document.createElement('div');
etaniAnimate.className = 'etaniAnimate';
etaniAnimate.setAttribute('data-type', 'opacity');
const nameSpan = document.createElement('span');
nameSpan.className = 'etaniAnimateName';
nameSpan.textContent = 'opacity';
etaniAnimate.appendChild(nameSpan);
const durSpan = document.createElement('span');
durSpan.className = 'etaniAnimateDur';
durSpan.textContent = 'dur: 0s';
etaniAnimate.appendChild(durSpan);
const valueDiv = document.createElement('div');
valueDiv.className = 'etaniAnimateValue';
const avCtrlDiv = document.createElement('div');
avCtrlDiv.className = 'etaniAVCtrl';
const avAddSpan = document.createElement('span');
avAddSpan.className = 'etaniAVAdd';
avAddSpan.title = 'Add Opacity Value';
avAddSpan.setAttribute('data-type', 'opacity');
avAddSpan.addEventListener('click', (e) => handleAVAddClick(e, useId, 'opacity'));
const plusSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
plusSvg.setAttribute('width', '24');
plusSvg.setAttribute('height', '24');
plusSvg.setAttribute('viewBox', '0 0 24 24');
plusSvg.setAttribute('fill', 'none');
plusSvg.setAttribute('stroke', 'currentColor');
plusSvg.setAttribute('stroke-width', '1');
plusSvg.setAttribute('stroke-linecap', 'round');
plusSvg.setAttribute('stroke-linejoin', 'round');
plusSvg.innerHTML = '<line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line>';
avAddSpan.appendChild(plusSvg);
const avDeleteSpan = document.createElement('span');
avDeleteSpan.className = 'etaniAVDelete';
avDeleteSpan.title = 'Delete Value';
avDeleteSpan.setAttribute('data-type', 'opacity');
avDeleteSpan.addEventListener('click', (e) => handleAVDeleteToggle(e, 'opacity'));
const minusSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
minusSvg.setAttribute('width', '24');
minusSvg.setAttribute('height', '24');
minusSvg.setAttribute('viewBox', '0 0 24 24');
minusSvg.setAttribute('fill', 'none');
minusSvg.setAttribute('stroke', 'currentColor');
minusSvg.setAttribute('stroke-width', '1');
minusSvg.setAttribute('stroke-linecap', 'round');
minusSvg.setAttribute('stroke-linejoin', 'round');
minusSvg.innerHTML = '<line x1="5" y1="12" x2="19" y2="12"></line>';
avDeleteSpan.appendChild(minusSvg);
const avCopySpan = document.createElement('span');
avCopySpan.className = 'etaniAVCopy';
avCopySpan.title = 'Copy Value';
avCopySpan.setAttribute('data-type', 'opacity');
avCopySpan.addEventListener('click', (e) => handleAVCopyToggle(e, 'opacity'));
const copySvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
copySvg.setAttribute('width', '24');
copySvg.setAttribute('height', '24');
copySvg.setAttribute('viewBox', '0 0 24 24');
copySvg.setAttribute('fill', 'none');
copySvg.setAttribute('stroke', 'currentColor');
copySvg.setAttribute('stroke-width', '1');
copySvg.setAttribute('stroke-linecap', 'round');
copySvg.setAttribute('stroke-linejoin', 'round');
copySvg.innerHTML = '<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>';
avCopySpan.appendChild(copySvg);
const avMoveSpan = document.createElement('span');
avMoveSpan.className = 'etaniAVMove';
avMoveSpan.title = 'Move Value';
avMoveSpan.setAttribute('data-type', 'opacity');
avMoveSpan.addEventListener('click', (e) => handleAVMoveToggle(e, 'opacity'));
const moveSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
moveSvg.setAttribute('width', '24');
moveSvg.setAttribute('height', '24');
moveSvg.setAttribute('viewBox', '0 0 24 24');
moveSvg.setAttribute('fill', 'none');
moveSvg.setAttribute('stroke', 'currentColor');
moveSvg.setAttribute('stroke-width', '1');
moveSvg.setAttribute('stroke-linecap', 'round');
moveSvg.setAttribute('stroke-linejoin', 'round');
moveSvg.innerHTML = '<path d="M 7,4 L 3,8 L 7,12 M 3,8 L 18,8 M 17,12 L 21,16 L 17,20 M 21,16 L 6,16"></path>';
avMoveSpan.appendChild(moveSvg);
avCtrlDiv.appendChild(avAddSpan);
avCtrlDiv.appendChild(avDeleteSpan);
avCtrlDiv.appendChild(avCopySpan);
avCtrlDiv.appendChild(avMoveSpan);
const avLabelSpan = document.createElement('span');
avLabelSpan.className = 'etaniAVLabel';
avLabelSpan.textContent = 'values : ';
const avDiv = document.createElement('div');
avDiv.className = 'etaniAV';
const avItemSpan = document.createElement('span');
avItemSpan.className = 'etaniAVItem';
avItemSpan.textContent = '1';
avItemSpan.addEventListener('click', (e) => handleAVItemClick(e, 'opacity'));
avDiv.appendChild(avItemSpan);
valueDiv.appendChild(avCtrlDiv);
valueDiv.appendChild(avLabelSpan);
valueDiv.appendChild(avDiv);
etaniAnimate.appendChild(valueDiv);
itemRight.appendChild(etaniAnimate);
});
updateEtaniResult();
}
// Handle click event for the 'Fill up values' button
function handleValueCtrlUpClick() {
if (!etani_clone) return;
const allAnimates = etani_clone.querySelectorAll('animateTransform, animate[attributeName="opacity"]');
let maxValuesLength = 0;
allAnimates.forEach(animate => {
const values = animate.getAttribute('values')?.split(';') || [];
maxValuesLength = Math.max(maxValuesLength, values.length);
});
allAnimates.forEach(animate => {
const values = animate.getAttribute('values')?.split(';') || [];
if (values.length < maxValuesLength && values.length > 0) {
const lastValue = values[values.length - 1];
while (values.length < maxValuesLength) {
values.push(lastValue);
}
animate.setAttribute('values', values.join(';'));
const etaniAnimate = document.querySelector(`.etaniAnimate:has([data-type="${animate.getAttribute('type') || 'opacity'}"])`);
updateDuration(animate, etaniAnimate, values.length);
}
});
const etaniItemRights = document.querySelectorAll('.etaniItemRight');
etaniItemRights.forEach(itemRight => {
const useId = itemRight.closest('.etaniItem').querySelector('.tileid').textContent;
['transform', 'opacity'].forEach(animateType => {
const etaniAV = itemRight.querySelector(`.etaniAnimate[data-type="${animateType}"] .etaniAV`);
if (!etaniAV) return;
const currentItems = etaniAV.querySelectorAll('.etaniAVItem');
const currentValuesLength = currentItems.length;
if (currentValuesLength >= maxValuesLength) return;
const lastItem = currentItems[currentItems.length - 1];
let lastValue = lastItem.textContent;
for (let i = currentValuesLength; i < maxValuesLength; i++) {
const newAVItem = document.createElement('span');
newAVItem.className = 'etaniAVItem';
newAVItem.addEventListener('click', (e) => handleAVItemClick(e, animateType));
if (animateType === 'transform') {
const existingLetters = Array.from(currentItems).map(item => item.textContent);
newAVItem.textContent = findFirstMissingLetter(existingLetters);
existingLetters.push(newAVItem.textContent);
} else if (animateType === 'opacity') {
newAVItem.textContent = lastValue;
}
etaniAV.appendChild(newAVItem);
}
});
});
updateEtaniResult();
}
// Handle setting mode switch (Repeat/Freeze)
function handleSettingModeChange(mode) {
if (!etani_clone) return;
const animates = etani_clone.querySelectorAll('animateTransform, animate[attributeName="opacity"]');
const isRepeat = mode === 'repeat';
animates.forEach(animate => {
if (animate.hasAttribute('dur')) {
if (isRepeat) {
animate.removeAttribute('fill');
animate.setAttribute('repeatCount', 'indefinite');
} else {
animate.removeAttribute('repeatCount');
animate.setAttribute('fill', 'freeze');
}
}
});
document.querySelector('.etaniSettingFreeze').classList.toggle('active', !isRepeat);
document.querySelector('.etaniSettingRepeat').classList.toggle('active', isRepeat);
updateEtaniResult();
}
// Create the internal animation control structure
function createEtaniInner(etaniouter) {
const originalSvg = document.getElementById('etmain');
if (!originalSvg) {
console.error('SVG with ID "etmain" not found.');
return;
}
etani_clone = originalSvg.cloneNode(true);
const etwaitElement = etani_clone.querySelector('.etwait');
if (etwaitElement) {
etwaitElement.remove();
}
etani_clone.id = 'etmainani';
const etaniinner = document.createElement('div');
etaniinner.className = 'etaniinner';
etaniinner.id = 'etaniinner';
const etaniCtrl = document.createElement('div');
etaniCtrl.className = 'etaniCtrl';
const etaniContent = document.createElement('div');
etaniContent.className = 'etaniContent';
const contentHTMLLink = document.createElement('a');
contentHTMLLink.className = 'etaniContentHTML';
contentHTMLLink.textContent = 'Show HTML';
contentHTMLLink.href = 'javascript:;';
contentHTMLLink.addEventListener('click', handleContentHTMLClick);
etaniContent.appendChild(contentHTMLLink);
const centerLink = document.createElement('a');
centerLink.className = 'etaniCenter';
centerLink.textContent = 'Center';
centerLink.href = 'javascript:;';
centerLink.addEventListener('click', handleCenterClick);
etaniContent.appendChild(centerLink);
etaniCtrl.appendChild(etaniContent);
const etaniSetting = document.createElement('div');
etaniSetting.className = 'etaniSetting';
const repeatRadio = document.createElement('span');
repeatRadio.className = 'etaniSettingMode etaniSettingRepeat active';
repeatRadio.textContent = 'Repeat';
repeatRadio.setAttribute('data-mode', 'repeat');
repeatRadio.addEventListener('click', () => handleSettingModeChange('repeat'));
const freezeRadio = document.createElement('span');
freezeRadio.className = 'etaniSettingMode etaniSettingFreeze';
freezeRadio.textContent = 'Freeze';
freezeRadio.setAttribute('data-mode', 'freeze');
freezeRadio.addEventListener('click', () => handleSettingModeChange('freeze'));
etaniSetting.appendChild(repeatRadio);
etaniSetting.appendChild(freezeRadio);
etaniCtrl.appendChild(etaniSetting);
const etaniAllAppend = document.createElement('div');
etaniAllAppend.className = 'etaniAllAppend';
const transformButton = document.createElement('button');
transformButton.className = 'etaniAllAppendTransform';
transformButton.textContent = 'transform';
transformButton.addEventListener('click', handleAllAppendTransformClick);
const opacityButton = document.createElement('button');
opacityButton.className = 'etaniAllAppendOpacity';
opacityButton.textContent = 'opacity';
opacityButton.addEventListener('click', handleAllAppendOpacityClick);
etaniAllAppend.appendChild(transformButton);
etaniAllAppend.appendChild(opacityButton);
etaniCtrl.appendChild(etaniAllAppend);
const etaniValueCtrl = document.createElement('div');
etaniValueCtrl.className = 'etaniValueCtrl';
const valueCtrlUpLink = document.createElement('a');
valueCtrlUpLink.className = 'etaniValueCtrlUp';
valueCtrlUpLink.textContent = 'fill up values';
valueCtrlUpLink.href = 'javascript:;';
valueCtrlUpLink.addEventListener('click', handleValueCtrlUpClick);
etaniValueCtrl.appendChild(valueCtrlUpLink);
etaniCtrl.appendChild(etaniValueCtrl);
const etaniCol = document.createElement('div');
etaniCol.className = 'etaniCol';
const etaniResult = document.createElement('div');
etaniResult.className = 'etaniResult';
const resultImg = document.createElement('img');
resultImg.className = 'etaniResultImage';
resultImg.alt = 'Rendered Ejtile Animation SVG';
const resultDRDiv = document.createElement('div');
resultDRDiv.className = 'etaniResultDR';
const downloadLink = document.createElement('a');
downloadLink.className = 'etaniResultDownload';
downloadLink.textContent = 'Download SVG';
downloadLink.href = 'javascript:;';
const renameLink = document.createElement('a');
renameLink.className = 'etaniResultRename';
renameLink.textContent = 'Rename File';
renameLink.href = 'javascript:;';
const sizeSpan = document.createElement('span');
sizeSpan.className = 'etaniResultSize';
resultDRDiv.appendChild(downloadLink);
resultDRDiv.appendChild(renameLink);
etaniResult.appendChild(resultImg);
etaniResult.appendChild(resultDRDiv);
etaniResult.appendChild(sizeSpan);
etaniinner.appendChild(etaniCtrl);
etaniinner.appendChild(etaniCol);
etaniinner.appendChild(etaniResult);
etaniouter.appendChild(etaniinner);
const etdropUses = document.querySelectorAll('.etdrop use');
const etwaitGroups = document.querySelectorAll('.etwait g');
etdropUses.forEach((useElement, i) => {
const tileid = useElement.getAttribute('href').substring(1);
let targetUse = null;
for (const group of etwaitGroups) {
const useInGroup = group.querySelector(`use[href="#${tileid}"]`);
if (useInGroup) {
targetUse = useInGroup;
break;
}
}
let etwaittransform = '', etwaitfill = '', etwaitstroke = '', etwaitstrokeWidth = '';
if (targetUse) {
etwaittransform = targetUse.getAttribute('transform') || '';
etwaitfill = targetUse.getAttribute('fill') || '';
etwaitstroke = targetUse.getAttribute('stroke') || '';
etwaitstrokeWidth = targetUse.getAttribute('stroke-width') || '';
const scaleMatch = etwaittransform.match(/scale\(([^)]+)\)/);
const rotateMatch = etwaittransform.match(/rotate\(([^)]+)\)/);
const scalePart = scaleMatch ? scaleMatch[0] : '';
const rotatePart = rotateMatch ? rotateMatch[0] : '';
etwaittransform = `translate(20,20) ${scalePart} ${rotatePart}`.trim().replace(/\s+/g, ' ');
}
const originalTile = document.querySelector(`defs g#${tileid}`);
const etaniItem = document.createElement('div');
etaniItem.className = 'etaniItem';
const etaniItemLeft = document.createElement('div');
etaniItemLeft.className = 'etaniItemLeft';
const etaniItemRight = document.createElement('div');
etaniItemRight.className = 'etaniItemRight';
if (originalTile) {
const tileclone = originalTile.cloneNode(true);
tileclone.removeAttribute('id');
if (etwaittransform) tileclone.setAttribute('transform', etwaittransform);
if (etwaitfill) tileclone.setAttribute('fill', etwaitfill);
if (etwaitstroke) tileclone.setAttribute('stroke', etwaitstroke);
if (etwaitstrokeWidth) tileclone.setAttribute('stroke-width', etwaitstrokeWidth);
const svgWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgWrapper.setAttribute('width', '40');
svgWrapper.setAttribute('height', '40');
svgWrapper.setAttribute('version', '1.1');
svgWrapper.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
svgWrapper.setAttribute('xmlns:svg', 'http://www.w3.org/2000/svg');
svgWrapper.className = 'etanitileimg';
svgWrapper.appendChild(tileclone);
etaniItemLeft.appendChild(svgWrapper);
}
const tileidDiv = document.createElement('div');
tileidDiv.className = 'tileid';
tileidDiv.textContent = tileid;
etaniItemLeft.appendChild(tileidDiv);
etaniItem.appendChild(etaniItemLeft);
etaniItem.appendChild(etaniItemRight);
etaniCol.appendChild(etaniItem);
});
updateEtaniResult();
}
// Toggle the visibility and content of the animation control panel
function toggleAnimation(event) {
const etanibutton = event.currentTarget;
const etaniouter = etanibutton.parentNode;
const etaniinner = document.getElementById('etaniinner');
if (etanibutton.textContent === 'Animate it') {
etanibutton.textContent = 'Close Ejtile Ani';
createEtaniInner(etaniouter);
} else if (etanibutton.textContent === 'Close Ejtile Ani') {
etanibutton.textContent = 'Animate it';
if (etaniinner) {
etaniinner.remove();
}
etani_clone = null;
isCopyMode = false;
isMoveMode = false;
isDeleteMode = false;
selectedMoveItem = null;
lastClickTime = 0;
lastClickedItem = null;
}
}
// Initialize the animation control panel on window load
window.addEventListener('load', () => {
addDynamicStyles();
const etmainouter = document.getElementById('etmainouter');
if (etmainouter) {
const etaniouter = document.createElement('div');
etaniouter.className = 'etaniouter';
const etanibutton = document.createElement('button');
etanibutton.id = 'etanibutton';
etanibutton.textContent = 'Animate it';
etanibutton.addEventListener('click', toggleAnimation);
etaniouter.appendChild(etanibutton);
etmainouter.parentNode.insertBefore(etaniouter, etmainouter.nextSibling);
} else {
console.error('Div with ID "etmainouter" not found.');
}
});