代码: 全选
### 修改 etaniAppendAnimate 函式
在 `function etaniAppendAnimate(id, type, attr) { ... }` 中的 `etaniAnimate.setAttribute('data-type', animateTypeUI);` 之後,新增以下代碼:
```javascript
// Calculate group index for multiple instances of the same type
const existingGroups = itemRight.querySelectorAll(`.etaniAnimate[data-type="${animateTypeUI}"]`);
const groupIndex = existingGroups.length;
etaniAnimate.setAttribute('data-group', groupIndex);
```
在相同函式中的 `const avCtrlDiv = createControlButtons(animateTypeUI, id);` 替換為:
```javascript
const avCtrlDiv = createControlButtons(animateTypeUI, id, groupIndex);
```
在相同函式中的 `attrAddSpan.addEventListener('click', (e) => showDropdown(e, animateTypeUI, id));` 替換為:
```javascript
attrAddSpan.addEventListener('click', (e) => showDropdown(e, animateTypeUI, id, groupIndex));
```
在相同函式中的 `etaniAnimateAttr.addEventListener('click', (e) => { ... });` 替換為:
```javascript
etaniAnimateAttr.addEventListener('click', (e) => {
if (e.target.classList.contains('etaniAnimateId')) {
editAttribute(e.target, animateTypeUI, id, 'id', groupIndex);
} else if (e.target.classList.contains('etaniAnimateBegin')) {
editAttribute(e.target, animateTypeUI, id, 'begin', groupIndex);
} else if (e.target.classList.contains('etaniAnimateOther')) {
editAttribute(e.target, animateTypeUI, id, 'other', e.target.textContent.split('=')[0], groupIndex);
}
});
```
在相同函式中的 `avItemSpan.addEventListener('click', (e) => handleAVItemClick(e, animateTypeUI));` 替換為:
```javascript
avItemSpan.addEventListener('click', (e) => handleAVItemClick(e, animateTypeUI, groupIndex));
```
在相同函式中的 `durSpan.addEventListener('click', () => { ... });` 中的 `let animates;` 後的代碼替換為:
```javascript
let animates;
if (animateTypeUI === 'transform') {
animates = getAnimates(animateTypeUI, id, null, groupIndex);
} else if (animateTypeUI === 'opacity') {
animates = getAnimates(animateTypeUI, id, null, groupIndex);
}
```
在相同函式中的 `frSpan.addEventListener('click', () => { ... });` 中的 `let animateElem;` 後的代碼替換為:
```javascript
let animateElem;
if (animateTypeUI === 'transform') {
animateElem = getAnimates(animateTypeUI, id, null, groupIndex);
} else if (animateTypeUI === 'opacity') {
animateElem = getAnimates(animateTypeUI, id, null, groupIndex);
}
```
### 修改 createControlButtons 函式
將整個 `function createControlButtons(animateType, useId) { ... }` 替換為:
```javascript
// Create control buttons when animate is appending
function createControlButtons(animateType, useId, group) {
const avCtrlDiv = document.createElement('div');
avCtrlDiv.className = 'etaniAVCtrl';
const buttons = [
{
className: 'etaniAVAdd',
title: `Add ${animateType.charAt(0).toUpperCase() + animateType.slice(1)} Value`,
svg: {
lines: [
{ x1: '12', y1: '5', x2: '12', y2: '19' },
{ x1: '5', y1: '12', x2: '19', y2: '12' }
]
},
handler: (e) => handleAVAddClick(e, useId, animateType, group)
},
{
className: 'etaniAVDelete',
title: 'Delete Value',
svg: {
lines: [
{ x1: '5', y1: '12', x2: '19', y2: '12' }
]
},
handler: (e) => handleAVModeToggle(e, animateType, 'delete', group)
},
{
className: 'etaniAVCopy',
title: 'Copy Value',
svg: {
rect: { x: '9', y: '9', width: '13', height: '13', rx: '2', ry: '2' },
path: { d: 'M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1' }
},
handler: (e) => handleAVModeToggle(e, animateType, 'copy', group)
},
{
className: 'etaniAVMove',
title: 'Move Value',
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' }
},
handler: (e) => handleAVModeToggle(e, animateType, 'move', group)
}
];
buttons.forEach(button => {
const span = document.createElement('span');
span.className = button.className;
span.title = button.title;
span.setAttribute('data-type', animateType);
span.addEventListener('click', button.handler);
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('width', '24');
svg.setAttribute('height', '24');
svg.setAttribute('viewBox', '0 0 24 24');
svg.setAttribute('fill', 'none');
svg.setAttribute('stroke', 'currentColor');
svg.setAttribute('stroke-width', '1');
svg.setAttribute('stroke-linecap', 'round');
svg.setAttribute('stroke-linejoin', 'round');
if (button.svg.lines) {
button.svg.lines.forEach(line => {
const lineElement = document.createElementNS('http://www.w3.org/2000/svg', 'line');
Object.entries(line).forEach(([key, value]) => lineElement.setAttribute(key, value));
svg.appendChild(lineElement);
});
}
if (button.svg.rect) {
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
Object.entries(button.svg.rect).forEach(([key, value]) => rect.setAttribute(key, value));
svg.appendChild(rect);
}
if (button.svg.path) {
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
Object.entries(button.svg.path).forEach(([key, value]) => path.setAttribute(key, value));
svg.appendChild(path);
}
span.appendChild(svg);
avCtrlDiv.appendChild(span);
});
return avCtrlDiv;
}
```
### 修改 showDropdown 函式
將整個 `function showDropdown(e, animateType, useId) { ... }` 替換為:
```javascript
// Show dropdown menu for adding attributes
function showDropdown(e, animateType, useId, group) {
const attrAddSpan = e.target;
const existingDropdown = attrAddSpan.parentNode.querySelector('.etaniDropdown');
if (existingDropdown) {
// Toggle: if already open, remove it
existingDropdown.remove();
return;
}
const dropdown = document.createElement('div');
dropdown.className = 'etaniDropdown';
// Dynamically determine items based on existing attributes
const attrParent = attrAddSpan.parentNode;
const hasId = attrParent.querySelector('.etaniAnimateId');
const hasBegin = attrParent.querySelector('.etaniAnimateBegin');
const items = [];
if (!hasId) items.push('id');
if (!hasBegin) items.push('begin');
items.push('other');
items.forEach(item => {
const div = document.createElement('div');
div.className = 'etaniDropdownItem';
div.textContent = item;
div.addEventListener('click', () => {
showWindow(item, animateType, useId, group);
dropdown.remove(); // Remove after selecting item
});
dropdown.appendChild(div);
});
attrAddSpan.parentNode.style.position = 'relative'; // Ensure parent is relative for absolute positioning
attrAddSpan.parentNode.appendChild(dropdown);
dropdown.style.left = `${attrAddSpan.offsetLeft}px`;
dropdown.style.top = `${attrAddSpan.offsetTop + attrAddSpan.offsetHeight}px`;
}
```
### 修改 showWindow 函式
將整個 `function showWindow(type, animateType, useId, existingSpan = null, otherAttrName = null) { ... }` 替換為:
```javascript
// Show window for adding/editing attributes
function showWindow(type, animateType, useId, group, existingSpan = null, otherAttrName = null) {
const windowDiv = document.createElement('div');
windowDiv.className = 'etaniWindow';
const existingIds = getExistingIds();
const isEdit = !!existingSpan;
let input1, input2, existingIdsDiv, historyDiv, confirmBtn, deleteBtn, cancelBtn;
if (type === 'id') {
const label = document.createElement('label');
label.textContent = 'Enter ID:';
input1 = document.createElement('input');
input1.type = 'text';
if (isEdit) input1.value = existingSpan.textContent.replace('id=', '');
windowDiv.appendChild(label);
windowDiv.appendChild(input1);
existingIdsDiv = createIdsDisplay(existingIds, false);
windowDiv.appendChild(existingIdsDiv);
} else if (type === 'begin') {
const parsed = isEdit ? parseBeginValue(existingSpan.textContent) : { selected: [], plus: null, time: null };
existingIdsDiv = createIdsDisplay(existingIds, true, parsed.selected);
windowDiv.appendChild(existingIdsDiv);
// Create a div wrapper for plus input with text
const plusDiv = document.createElement('div');
plusDiv.className = 'etaniAnimateBeginPlusOuter';
plusDiv.textContent = 'plus: ';
const plusInput = document.createElement('input');
plusInput.type = 'number';
plusInput.min = '0';
plusInput.step = '0.1';
plusInput.className = 'etaniAnimateBeginPlus';
plusInput.style.width = '60px';
plusInput.style.display = 'inline';
if (isEdit) {
const parsed = parseBeginValue(existingSpan.textContent);
plusInput.value = parsed.plus || parsed.time || '';
}
plusDiv.appendChild(plusInput);
const sText = document.createTextNode(' s');
plusDiv.appendChild(sText);
windowDiv.appendChild(plusDiv);
} else if (type === 'other') {
const label1 = document.createElement('label');
label1.textContent = 'Attribute Name:';
input1 = document.createElement('input');
input1.type = 'text';
input1.className = 'otherAttribute';
if (isEdit) input1.value = otherAttrName;
const attrbr = document.createElement('br');
const label2 = document.createElement('label');
label2.textContent = 'Attribute Value:';
input2 = document.createElement('input');
input2.type = 'text';
input2.className = 'otherAttribute';
if (isEdit) input2.value = existingSpan.textContent.split('=')[1];
windowDiv.appendChild(label1);
windowDiv.appendChild(input1);
windowDiv.appendChild(attrbr);
windowDiv.appendChild(label2);
windowDiv.appendChild(input2);
}
const buttonsDiv = document.createElement('div');
confirmBtn = document.createElement('button');
confirmBtn.textContent = 'Confirm';
confirmBtn.addEventListener('click', () => handleConfirm(type, animateType, useId, input1, input2, existingIdsDiv, existingSpan, windowDiv, group));
buttonsDiv.appendChild(confirmBtn);
if (isEdit) {
deleteBtn = document.createElement('button');
deleteBtn.textContent = 'Delete';
deleteBtn.addEventListener('click', () => handleDelete(animateType, useId, type, otherAttrName, existingSpan, windowDiv, group));
buttonsDiv.appendChild(deleteBtn);
}
cancelBtn = document.createElement('button');
cancelBtn.textContent = 'Cancel';
cancelBtn.addEventListener('click', () => document.body.removeChild(windowDiv));
buttonsDiv.appendChild(cancelBtn);
windowDiv.appendChild(buttonsDiv);
document.body.appendChild(windowDiv);
}
```
### 修改 editAttribute 函式
將整個 `function editAttribute(span, animateType, useId, type, otherAttrName) { ... }` 替換為:
```javascript
// Edit existing attribute
function editAttribute(span, animateType, useId, type, group, otherAttrName) {
showWindow(type, animateType, useId, group, span, otherAttrName);
}
```
### 修改 handleConfirm 函式
將整個 `function handleConfirm(type, animateType, useId, input1, input2, existingIdsDiv, existingSpan, windowDiv) { ... }` 替換為:
```javascript
// Handle confirm button for adding/editing
function handleConfirm(type, animateType, useId, input1, input2, existingIdsDiv, existingSpan, windowDiv, group) {
let value, attrName;
if (type === 'id') {
value = input1.value.trim();
// Validate: only allow alphanumeric and underscore, no spaces
if (!value || !/^[a-zA-Z0-9_]+$/.test(value) || getExistingIds().includes(value)) {
alert('ID must contain only letters, numbers, and underscores (no spaces)');
return;
}
attrName = 'id';
} else if (type === 'begin') {
const selectedZero = existingIdsDiv.querySelector('.zero-s.selected');
const selectedId = existingIdsDiv.querySelector('.etaniIdItem:not(.zero-s).selected');
const plusInput = windowDiv.querySelector('.etaniAnimateBeginPlus');
const plusValue = plusInput && plusInput.value ? parseFloat(plusInput.value) : null;
let parts = [];
if (selectedZero && selectedId) {
parts.push('0s');
} // Only push '0s' if there is a selectedId
let idPart = selectedId ? `${selectedId.textContent}.end` : null;
if (plusValue > 0) {
if (idPart) {
idPart += `+${plusValue}s`;
} else {
idPart = `${plusValue}s`; // If no id, use Xs directly, ignore 0s
}
}
if (idPart) {
parts.push(idPart);
}
value = parts.join(';');
// Handle case where only '0s' is selected without id or plus
if (!value && selectedZero) {
value = '0s';
}
if (!value) return; // No selection
attrName = 'begin';
} else if (type === 'other') {
attrName = input1.value.trim();
value = input2.value.trim();
if (!attrName || !value) return; // Empty
// Check duplicate attributes (simplified, assume no duplicates for same attrName)
}
const animates = getAnimates(animateType, useId, attrName, group);
animates.forEach(animate => animate.setAttribute(attrName, value));
const attrAddSpan = existingSpan ? existingSpan.parentNode.querySelector('.etaniAnimateAttrAdd') : document.querySelector(`.etaniItem[data-use-id="${useId}"] .etaniAnimate[data-type="${animateType}"][data-group="${group}"] .etaniAnimateAttrAdd`);
const className = `etaniAnimate${type.charAt(0).toUpperCase() + type.slice(1)}`;
const newSpan = existingSpan || document.createElement('span');
newSpan.className = className;
newSpan.textContent = type === 'other' ? `${attrName}=${value}` : `${attrName}=${value}`;
if (!existingSpan) attrAddSpan.parentNode.insertBefore(newSpan, attrAddSpan);
updateEtaniResult();
document.body.removeChild(windowDiv);
}
```
### 修改 handleDelete 函式
將整個 `function handleDelete(animateType, useId, type, otherAttrName, existingSpan, windowDiv) { ... }` 替換為:
```javascript
// Handle delete button
function handleDelete(animateType, useId, type, otherAttrName, existingSpan, windowDiv, group) {
const attrName = type === 'other' ? otherAttrName : type;
const animates = getAnimates(animateType, useId, null, group);
animates.forEach(animate => animate.removeAttribute(attrName));
existingSpan.remove();
updateEtaniResult();
if (document.body.contains(windowDiv)) {
document.body.removeChild(windowDiv);
}
}
```
### 修改 getAnimates 函式
將整個 `function getAnimates(animateType, useId, attrType = null) { ... }` 替換為:
```javascript
// Get animate elements for the specific useId, type, and group
function getAnimates(animateType, useId, attrType = null, group = 0) {
const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
if (!cloneUseElement) return [];
if (animateType === 'transform') {
const allAnimates = cloneUseElement.querySelectorAll('animateTransform');
const groupAnimates = Array.from(allAnimates).slice(group * 3, group * 3 + 3);
if (attrType === 'id') {
return groupAnimates.filter(anim => anim.getAttribute('type').toLowerCase() === 'rotate');
} else {
return groupAnimates;
}
} else {
const allAnimates = cloneUseElement.querySelectorAll(`animate[attributeName="${animateType}"]`);
return [allAnimates[group]] || [];
}
}
```
### 修改 handleAVAddClick 函式
將整個 `function handleAVAddClick(e, useElementId, animateType = 'transform') { ... }` 替換為:
```javascript
// Handle click event for the '+' button for transform or opacity
function handleAVAddClick(e, useElementId, animateType = 'transform', group = 0) {
e.preventDefault();
if (!etani_clone) return;
// Reset all modes before adding a new value
resetModes(animateType);
const etaniItemRight = e.currentTarget.closest('.etaniItemRight');
const etaniAnimate = etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"][data-group="${group}"]`);
const etaniAV = etaniAnimate.querySelector('.etaniAV');
if (animateType === 'transform') {
// Get the original <use> element from #etmain to read its current transform
const originalUseElement = document.querySelector(`#etmain .etdrop use[href="#${useElementId}"]`);
if (!originalUseElement) return;
const originalTransformString = originalUseElement.getAttribute('transform') || '';
const originalTransforms = parseTransform(originalTransformString);
// Get the corresponding <use> element in the clone
const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useElementId}"]`);
if (!cloneUseElement) return;
// Get the animates for this group
const animates = getAnimates(animateType, useElementId, null, group);
animates.forEach(animate => {
const subType = animate.getAttribute('type').toLowerCase();
let currentValueString = animate.getAttribute('values') || '';
let newValue = '';
// Get the new value directly from the parsed original transform
if (subType === 'translate') {
newValue = originalTransforms.translate;
} else if (subType === 'scale') {
newValue = originalTransforms.scale;
} else if (subType === 'rotate') {
newValue = originalTransforms.rotate;
}
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 animates = getAnimates(animateType, useElementId, null, group);
const animateOpacity = animates[0];
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, group));
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();
}
```
### 修改 handleAVModeToggle 函式
將整個 `function handleAVModeToggle(e, animateType, mode) { ... }` 替換為:
```javascript
// Handle toggle for delete, copy, or move mode
function handleAVModeToggle(e, animateType, mode, group) {
e.preventDefault();
const button = e.currentTarget;
resetModes(animateType, mode);
const isMode = (mode === 'delete' ? !isDeleteMode : mode === 'copy' ? !isCopyMode : !isMoveMode);
if (mode === 'delete') isDeleteMode = isMode;
if (mode === 'copy') isCopyMode = isMode;
if (mode === 'move') isMoveMode = isMode;
button.classList.toggle(`${mode}ing`, isMode);
const etaniItemRight = button.closest('.etaniItemRight');
const etaniCol = button.closest('.etaniCol');
const avItems = etaniItemRight.querySelectorAll(`.etaniAnimate[data-type="${animateType}"][data-group="${group}"] .etaniAVItem`);
avItems.forEach(item => {
item.classList.toggle(`${mode}ing-target`, isMode);
});
etaniCol.classList.toggle(`${mode}ing-mode-${animateType}`, isMode);
if (isMode) {
button.title = `Click value item to ${mode} (Click again to cancel)`;
} else {
button.title = `${mode.charAt(0).toUpperCase() + mode.slice(1)} Value`;
avItems.forEach(item => {
item.classList.remove(`${mode}ing-target`);
});
if (mode === 'move') selectedMoveItem = null;
}
}
```
### 修改 handleAVItemClick 函式
將整個 `function handleAVItemClick(e, animateType = 'transform') { ... }` 替換為:
```javascript
// Handle click event for an .etaniAVItem in deletion, copy, or move mode
function handleAVItemClick(e, animateType = 'transform', group = 0) {
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}"][data-group="${group}"] .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 cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
if (!cloneUseElement) return;
const allAnimates = getAnimates(animateType, useId, null, group);
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 animates = getAnimates(animateType, useId, null, group);
const animateOpacity = animates[0];
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, group));
etaniAV.insertBefore(newAVItem, item.nextSibling);
if (animateType === 'transform') {
const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
if (!cloneUseElement) return;
const allAnimates = getAnimates(animateType, useId, null, group);
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(';'));
const etaniAnimate = etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"][data-group="${group}"]`);
updateDuration(animate, etaniAnimate, values.length);
}
});
} else if (animateType === 'opacity') {
const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
if (!cloneUseElement) return;
const animates = getAnimates(animateType, useId, null, group);
const animateOpacity = animates[0];
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(';'));
const etaniAnimate = etaniItemRight.querySelector(`.etaniAnimate[data-type="${animateType}"][data-group="${group}"]`);
updateDuration(animateOpacity, etaniAnimate, 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}"][data-group="${group}"]`);
if (animateType === 'transform') {
const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
if (!cloneUseElement) return;
const allAnimates = getAnimates(animateType, useId, null, group);
let newValuesLength = 0;
allAnimates.forEach((animate) => {
const values = animate.getAttribute('values').split(';');
if (itemIndex < values.length) {
values.splice(itemIndex, 1);
newValuesLength = values.length; // All animations will have the same new length
if (newValuesLength > 0) {
animate.setAttribute('values', values.join(';'));
}
}
});
// Update duration once for all animates after removing values
if (newValuesLength > 0 && allAnimates.length > 0) {
const currentDur = parseFloat(etaniAnimate.querySelector('.etaniAnimateDur').textContent.replace('dur: ', '').replace('s', ''));
const isIntegerDur = Number.isInteger(currentDur);
const newDur = isIntegerDur ? (newValuesLength > 1 ? newValuesLength - 1 : 0) : currentDur;
allAnimates.forEach(animate => {
if (newDur > 0) {
animate.setAttribute('dur', `${newDur}s`);
} else {
animate.removeAttribute('dur');
}
});
etaniAnimate.querySelector('.etaniAnimateDur').textContent = `dur: ${newDur}s`;
}
// If all values were removed, clean up
if (newValuesLength === 0 && allAnimates.length > 0) {
allAnimates.forEach(anim => anim.remove());
etaniAnimate.remove();
}
} else if (animateType === 'opacity') {
const cloneUseElement = etani_clone.querySelector(`.etdrop use[href="#${useId}"]`);
if (!cloneUseElement) return;
const animates = getAnimates(animateType, useId, null, group);
const animateOpacity = animates[0];
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}"][data-group="${group}"]`), values.length);
}
}
}
item.remove();
const remainingItems = etaniItemRight.querySelectorAll(`.etaniAnimate[data-type="${animateType}"][data-group="${group}"] .etaniAVItem`);
if (remainingItems.length === 0) {
// The .etaniAnimate element (containing the deleteButton) was just removed
// because it was the last item.
// We cannot .click() the button, as it's no longer in the DOM.
// Instead, we must manually reset the delete mode globally.
resetModes(animateType);
} else {
// If items remain, just re-apply the 'deleting-target' class to them.
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 animates = getAnimates(animateType, useId, null, group);
const animateOpacity = animates[0];
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();
}
}
}
```