正在做etani,ejtile的動畫套件

分享和讨论Javascript相关的话题
头像
ejsoon
圈圈精英
圈圈精英
帖子: 3970
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 159 次
被圈友点赞: 190 次
联系:

Re: 正在做etani,ejtile的動畫套件

帖子 ejsoon »

grok expert

代码: 全选

### 新增函式位置
在程式碼的 `// Initialize the animation control panel on window load` 註釋之前(即 window.addEventListener('load', ... ) 之前),新增以下全局獨立函式:

```javascript
// Function to append animation tag based on id, type, and attr
function etaniAppendAnimate(id, type, attr) {
    if (!etani_clone) return;

    const isRepeat = document.querySelector('.etaniModeRepeat.active');

    // Determine UI animateType based on type and attr
    let animateTypeUI = attr;
    if (type === 'animateTransform' && attr === 'transform') {
        animateTypeUI = 'transform';
    } else if (type === 'animate' && attr === 'opacity') {
        animateTypeUI = 'opacity';
    }

    const defs = etani_clone.querySelector('defs');
    const targetG = defs ? defs.querySelector(`g#${id}`) : null;
    let targetElement;

    if (targetG) {
        targetElement = etani_clone.querySelector(`.etdrop use[href="#${id}"]`);
        if (!targetElement) return;
    } else {
        targetElement = etani_clone;
    }

    const ns = 'http://www.w3.org/2000/svg';

    if (animateTypeUI === 'transform') {
        // Get original transform values
        const originalUseElement = document.querySelector(`#etmain .etdrop use[href="#${id}"]`);
        const originalTransformString = originalUseElement ? originalUseElement.getAttribute('transform') || '' : '';
        const originalTransforms = parseTransform(originalTransformString);

        // Base function to create animateTransform
        const baseAnimate = (subType, initialValue) => {
            const anim = document.createElementNS(ns, 'animateTransform');
            anim.setAttribute('attributeName', 'transform');
            anim.setAttribute('attributeType', 'XML');
            anim.setAttribute('type', subType);
            anim.setAttribute('values', initialValue);
            anim.setAttribute('fill', isRepeat ? 'remove' : 'freeze');
            if (isRepeat) anim.setAttribute('repeatCount', 'indefinite');
            if (subType === 'scale' || subType === 'rotate') {
                anim.setAttribute('additive', 'sum');
            }
            return anim;
        };

        // Create and append animations
        const translateAnim = baseAnimate('translate', originalTransforms.translate);
        const scaleAnim = baseAnimate('scale', originalTransforms.scale);
        const rotateAnim = baseAnimate('rotate', originalTransforms.rotate);

        if (!targetG) {
            translateAnim.setAttribute('href', `#${id}`);
            scaleAnim.setAttribute('href', `#${id}`);
            rotateAnim.setAttribute('href', `#${id}`);
        }

        targetElement.appendChild(translateAnim);
        targetElement.appendChild(scaleAnim);
        targetElement.appendChild(rotateAnim);
    } else {
        // Create general animation tag
        const animate = document.createElementNS(ns, type);
        if (attr) animate.setAttribute('attributeName', attr);
        animate.setAttribute('values', '1');
        animate.setAttribute('fill', isRepeat ? 'remove' : 'freeze');
        if (isRepeat) animate.setAttribute('repeatCount', 'indefinite');

        if (!targetG) {
            animate.setAttribute('href', `#${id}`);
        }

        targetElement.appendChild(animate);
    }

    // Update UI for the specific id
    const itemRight = document.querySelector(`.etaniItem[data-use-id="${id}"] .etaniItemRight`);
    if (!itemRight || itemRight.querySelector(`.etaniAnimate[data-type="${animateTypeUI}"]`)) return;

    // --- Start of HTML control creation (copied and adapted from handleAllAppendAnimationClick) ---
    const etaniAnimate = document.createElement('div');
    etaniAnimate.className = 'etaniAnimate';
    etaniAnimate.setAttribute('data-type', animateTypeUI);

    const nameSpan = document.createElement('span');
    nameSpan.className = 'etaniAnimateName';
    nameSpan.textContent = animateTypeUI;

    const durSpan = document.createElement('span');
    durSpan.className = 'etaniAnimateDur';
    durSpan.textContent = 'dur: 0s';
    durSpan.addEventListener('click', () => {
        const currentDur = parseFloat(durSpan.textContent.replace('dur: ', '').replace('s', ''));
        const newDur = prompt('Enter duration in seconds:', currentDur);
        if (newDur !== null && !isNaN(newDur) && newDur >= 0) {
            let animates;
            if (animateTypeUI === 'transform') {
                animates = etani_clone.querySelectorAll(`.etdrop use[href="#${id}"] animateTransform`);
            } else {
                animates = [etani_clone.querySelector(`.etdrop use[href="#${id}"] animate[attributeName="${attr}"]`)];
            }
            const currentValues = animates[0]?.getAttribute('values')?.split(';') || [];
            const currentValuesCount = currentValues.length;
            const isIntegerDur = Number.isInteger(currentDur);
            const isIntegerNewDur = Number.isInteger(newDur);
            if (isIntegerDur && isIntegerNewDur && newDur > currentDur) {
                const valuesToAdd = newDur - currentDur;
                const originalUseElement = document.querySelector(`#etmain .etdrop use[href="#${id}"]`);
                if (originalUseElement) {
                    let originalTransforms;
                    let valueToAdd;
                    if (animateTypeUI === 'transform') {
                        const originalTransformString = originalUseElement.getAttribute('transform') || '';
                        originalTransforms = parseTransform(originalTransformString);
                    } else {
                        valueToAdd = '1';
                    }
                    animates.forEach(animate => {
                        const currentValueString = animate.getAttribute('values') || '';
                        if (animateTypeUI === 'transform') {
                            const subType = animate.getAttribute('type').toLowerCase();
                            valueToAdd = subType === 'translate' ? originalTransforms.translate :
                                         subType === 'scale' ? originalTransforms.scale :
                                         subType === 'rotate' ? originalTransforms.rotate : '';
                        }
                        let newValues = currentValueString ? currentValueString.split(';') : [];
                        for (let i = 0; i < valuesToAdd; i++) {
                            newValues.push(valueToAdd);
                        }
                        animate.setAttribute('values', newValues.join(';'));
                    });
                    // Update UI to add new etaniAVItems
                    const etaniAV = itemRight.querySelector(`.etaniAnimate[data-type="${animateTypeUI}"] .etaniAV`);
                    if (etaniAV) {
                        const existingItems = etaniAV.querySelectorAll('.etaniAVItem');
                        let existingLetters = Array.from(existingItems).map(item => item.textContent);
                        for (let i = 0; i < valuesToAdd; i++) {
                            const newAVItem = document.createElement('span');
                            newAVItem.className = 'etaniAVItem';
                            newAVItem.textContent = animateTypeUI === 'transform' ? findFirstMissingLetter(existingLetters) : '1';
                            if (animateTypeUI === 'transform') existingLetters.push(newAVItem.textContent);
                            newAVItem.addEventListener('click', (e) => handleAVItemClick(e, animateTypeUI));
                            etaniAV.appendChild(newAVItem);
                        }
                    }
                }
            }
            animates.forEach(animate => {
                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');
                }
            });
            durSpan.textContent = `dur: ${newDur}s`;
            updateEtaniResult();
        }
    });

    const etaniAnimateAttr = document.createElement('div');
    etaniAnimateAttr.className = 'etaniAnimateAttr';
    etaniAnimateAttr.appendChild(nameSpan);
    etaniAnimateAttr.appendChild(durSpan);
    etaniAnimate.appendChild(etaniAnimateAttr);

    const frSpan = document.createElement('span');
    frSpan.className = 'etaniAnimateFR';
    frSpan.textContent = document.querySelector('.etaniModeMixed.active') ? 'freeze' : 'repeat';
    frSpan.style.display = document.querySelector('.etaniModeMixed.active') ? 'inline-block' : 'none';
    frSpan.addEventListener('click', () => {
        const currentValue = frSpan.textContent;
        const newValue = currentValue === 'freeze' ? 'repeat' : 'freeze';
        frSpan.textContent = newValue;

        let animateElem;
        if (animateTypeUI === 'transform') {
            animateElem = etani_clone.querySelectorAll(`.etdrop use[href="#${id}"] animateTransform`);
        } else {
            animateElem = [etani_clone.querySelector(`.etdrop use[href="#${id}"] animate[attributeName="${attr}"]`)];
        }
        animateElem.forEach(animate => {
            if (animate.hasAttribute('fill') || animate.hasAttribute('repeatCount')) {
                if (newValue === 'repeat') {
                    animate.removeAttribute('fill');
                    animate.setAttribute('repeatCount', 'indefinite');
                } else {
                    animate.removeAttribute('repeatCount');
                    animate.setAttribute('fill', 'freeze');
                }
            }
        });
        updateEtaniResult();
    });
    etaniAnimateAttr.appendChild(frSpan);

    // Add the attribute add button
    const attrAddSpan = document.createElement('span');
    attrAddSpan.className = 'etaniAnimateAttrAdd';
    attrAddSpan.textContent = '+';
    attrAddSpan.addEventListener('click', (e) => showDropdown(e, animateTypeUI, id));
    etaniAnimateAttr.appendChild(attrAddSpan);

    // Handle clicks on existing attribute spans
    etaniAnimateAttr.addEventListener('click', (e) => {
        if (e.target.classList.contains('etaniAnimateId')) {
            editAttribute(e.target, animateTypeUI, id, 'id');
        } else if (e.target.classList.contains('etaniAnimateBegin')) {
            editAttribute(e.target, animateTypeUI, id, 'begin');
        } else if (e.target.classList.contains('etaniAnimateOther')) {
            editAttribute(e.target, animateTypeUI, id, 'other', e.target.textContent.split('=')[0]);
        }
    });

    const valueDiv = document.createElement('div');
    valueDiv.className = 'etaniAnimateValue';

    const avCtrlDiv = createControlButtons(animateTypeUI, id);

    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 = animateTypeUI === 'transform' ? 'a' : '1';
    avItemSpan.addEventListener('click', (e) => handleAVItemClick(e, animateTypeUI));

    avDiv.appendChild(avItemSpan);

    valueDiv.appendChild(avCtrlDiv);
    valueDiv.appendChild(avLabelSpan);
    valueDiv.appendChild(avDiv);

    etaniAnimate.appendChild(valueDiv);
    itemRight.appendChild(etaniAnimate);
    // --- End of HTML control creation ---

    updateEtaniResult();
}
```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
圈圈精英
圈圈精英
帖子: 3970
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 159 次
被圈友点赞: 190 次
联系:

Re: 正在做etani,ejtile的動畫套件

帖子 ejsoon »

改寫handleAllAppendAnimationCkick()函式,循環調用etaniAppendAnimate()。

用繁體中文回答。只需告知所要更改的代碼和位置,以及要替換的內容。代碼的注釋都要是英文。

grok expert

代码: 全选

### 修改 handleAllAppendAnimationClick 函式
將整個 `function handleAllAppendAnimationClick(animateType) { ... }` 替換為:
```javascript
// Handle click event for adding animation (transform or opacity)
function handleAllAppendAnimationClick(animateType) {
    if (!etani_clone) return;

    // Only work in tiles mode
    if (currentSelectMode !== 'tiles') {
        alert(`${animateType.charAt(0).toUpperCase() + animateType.slice(1)} can only be added in tiles mode`);
        return;
    }

    const etdropUses = document.querySelectorAll('#etmain .etdrop use');
    const etaniItemRights = document.querySelectorAll('.etaniItemRight');

    etdropUses.forEach((originalUseElement, i) => {
        const useId = originalUseElement.getAttribute('href').substring(1);
        const itemRight = etaniItemRights[i];

        if (!itemRight) return;

        // Check if animation already exists
        if (itemRight.querySelector(`.etaniAnimate[data-type="${animateType}"]`)) return;

        // Map animateType to type and attr for etaniAppendAnimate
        let type, attr;
        if (animateType === 'transform') {
            type = 'animateTransform';
            attr = 'transform';
        } else if (animateType === 'opacity') {
            type = 'animate';
            attr = 'opacity';
        } else {
            return; // Unsupported type
        }

        // Call etaniAppendAnimate for each useId
        etaniAppendAnimate(useId, type, attr);
    });

    updateEtaniResult();
}
```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
圈圈精英
圈圈精英
帖子: 3970
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 159 次
被圈友点赞: 190 次
联系:

Re: 正在做etani,ejtile的動畫套件

帖子 ejsoon »

接著上次的改動,在你提供的handleAllAppendAnimationCkick()中,有一個check if animation already exists的檢查。現在去掉這個檢查,也就是說可以有多組相同animateType存在,按存在次序對應etaniItemRight。

用繁體中文回答。只需告知所要更改的代碼和位置,以及要替換的內容。代碼的注釋都要是英文。

代码: 全选

### 修改 handleAllAppendAnimationClick 函式
將整個 `function handleAllAppendAnimationClick(animateType) { ... }` 替換為:
```javascript
// Handle click event for adding animation (transform or opacity)
function handleAllAppendAnimationClick(animateType) {
    if (!etani_clone) return;

    // Only work in tiles mode
    if (currentSelectMode !== 'tiles') {
        alert(`${animateType.charAt(0).toUpperCase() + animateType.slice(1)} can only be added in tiles mode`);
        return;
    }

    const etdropUses = document.querySelectorAll('#etmain .etdrop use');
    const etaniItemRights = document.querySelectorAll('.etaniItemRight');

    etdropUses.forEach((originalUseElement, i) => {
        const useId = originalUseElement.getAttribute('href').substring(1);
        const itemRight = etaniItemRights[i];

        if (!itemRight) return;

        // Map animateType to type and attr for etaniAppendAnimate
        let type, attr;
        if (animateType === 'transform') {
            type = 'animateTransform';
            attr = 'transform';
        } else if (animateType === 'opacity') {
            type = 'animate';
            attr = 'opacity';
        } else {
            return; // Unsupported type
        }

        // Call etaniAppendAnimate for each useId
        etaniAppendAnimate(useId, type, attr);
    });

    updateEtaniResult();
}
```

### 修改 etaniAppendAnimate 函式
在 `function etaniAppendAnimate(id, type, attr) { ... }` 中,刪除以下行:
```javascript
if (!itemRight || itemRight.querySelector(`.etaniAnimate[data-type="${animateTypeUI}"]`)) return;
```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
圈圈精英
圈圈精英
帖子: 3970
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 159 次
被圈友点赞: 190 次
联系:

Re: 正在做etani,ejtile的動畫套件

帖子 ejsoon »

在etnaiDropdown點擊時,如果有多個相同類型的動畫標籤,請準確對應。

用繁體中文回答。只需告知所要更改的代碼和位置,以及要替換的內容。代碼的注釋都要是英文。

grok expert

代码: 全选

### 修改 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();
        }
    }
}
```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
圈圈精英
圈圈精英
帖子: 3970
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 159 次
被圈友点赞: 190 次
联系:

Re: 正在做etani,ejtile的動畫套件

帖子 ejsoon »

前一次的修改中,`${mode}ing`造成了錯誤,因為原來的mode是「deleting、copying、moving」,而`${mode}ing`會變成「deleteing、copying、moveing」。請更正。

代码: 全选

### 修改 handleAVModeToggle 函式
將整個 `function handleAVModeToggle(e, animateType, mode, group) { ... }` 替換為:
```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;
    const modeIng = mode === 'delete' ? 'deleting' : `${mode}ing`;
    button.classList.toggle(modeIng, 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(`${modeIng}-target`, isMode);
    });

    etaniCol.classList.toggle(`${modeIng}-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(`${modeIng}-target`);
        });
        if (mode === 'move') selectedMoveItem = null;
    }
}
```

### 修改 handleAVItemClick 函式
在 `function handleAVItemClick(e, animateType = 'transform', group = 0) { ... }` 中的所有 `${mode}ing` 相關邏輯,替換為使用正確的 modeIng:
- 將 `if (etaniCol.classList.contains(`moving-mode-${animateType}`)) {` 替換為:
```javascript
const modeIngMove = 'moving';
if (etaniCol.classList.contains(`${modeIngMove}-mode-${animateType}`)) {
```
- 將 `item.classList.toggle(`${mode}ing-target`, isMode);` 在相關地方調整,但由於函式中是特定模式,替換所有相關的 classList.contains 和 classList.add/remove:
  - `etaniCol.classList.contains(`copying-mode-${animateType}`)` 替換為 `etaniCol.classList.contains(`copying-mode-${animateType}`)` (copying 無誤)。
  - `etaniCol.classList.contains(`deleting-mode-${animateType}`)` 替換為 `etaniCol.classList.contains(`deleting-mode-${animateType}`)` (deleting 無誤)。
  - 在 deleting 區塊中,`remainingItems.forEach(item => { item.classList.add('deleting-target'); });` 保持不變(已正確)。

### 修改 resetModes 函式
在 `function resetModes(animateType, excludeMode = null) { ... }` 中的模式移除邏輯,確保使用正確的 'deleting':
- 將 `etaniCol.classList.remove(`deleting-mode-${animateType}`);` 替換為:
```javascript
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';
    });
}
```
- 類似地,對於 copy 和 move,保持 'copying' 和 'moving' 不變。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
圈圈精英
圈圈精英
帖子: 3970
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 159 次
被圈友点赞: 190 次
联系:

Re: 正在做etani,ejtile的動畫套件

帖子 ejsoon »

https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
圈圈精英
圈圈精英
帖子: 3970
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 159 次
被圈友点赞: 190 次
联系:

Re: 正在做etani,ejtile的動畫套件

帖子 ejsoon »

把往.etaniCol添加.etaniItem,往.etaniItem添加.etaniItemRight,分別整合成一個函式,之後換掉所有可以用到這兩個函式的地方。

用繁體中文回答。只需告知所要更改的代碼和位置,以及要替換的內容。代碼的注釋都要是英文。

gemini 2.5pro

代码: 全选

好的,我將按照您的要求,建立兩個新的輔助函式,一個用於將 `.etaniItem` 附加到 `.etaniCol`,另一個用於將 `.etaniItemRight` 附加到 `.etaniItem`。然後,我將修改 `createEtaniItem` 函式來使用這兩個新函式。

這兩個動作目前只在 `createEtaniItem` 函式中使用,所以我們將在那裡進行替換。

-----

### 1\. 添加新的輔助函式

請在 `function createEtaniItem(tileid)` 函式 *之前*(大約在第 1415 行)添加以下兩個新函式:

```javascript
// Helper function to append etaniItem to etaniCol
function appendEtaniItemToCol(etaniCol, etaniItem) {
    if (etaniCol) {
        etaniCol.appendChild(etaniItem);
    }
}

// Helper function to append etaniItemRight to etaniItem
function appendEtaniItemRightToItem(etaniItem, etaniItemRight) {
    etaniItem.appendChild(etaniItemRight);
}
```

-----

### 2\. 修改 `createEtaniItem` 函式

在 `function createEtaniItem(tileid)` 函式內部,靠近結尾的地方(大約在第 1515 - 1518 行)。

**請將以下代碼:**

```javascript
// ... (inside createEtaniItem, after etaniItemLeft.appendChild(tileidDiv);)
    etaniItem.appendChild(etaniItemLeft);
    etaniItem.appendChild(etaniItemRight);
    etaniCol.appendChild(etaniItem);
}
```

**替換為:**

```javascript
// ... (inside createEtaniItem, after etaniItemLeft.appendChild(tileidDiv);)
    etaniItem.appendChild(etaniItemLeft);
    // Use the new helper function to append etaniItemRight
    appendEtaniItemRightToItem(etaniItem, etaniItemRight);
    
    // Use the new helper function to append etaniItem to etaniCol
    appendEtaniItemToCol(etaniCol, etaniItem);
}
```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
圈圈精英
圈圈精英
帖子: 3970
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 159 次
被圈友点赞: 190 次
联系:

Re: 正在做etani,ejtile的動畫套件

帖子 ejsoon »

改動:

當點擊etaniAppendTransform時,調用etaniAppendAnimate(id, "animateTransform","transform"),並關閉當前的.etaniWindow。

用繁體中文回答。只需告知所要更改的代碼和位置,以及要替換的內容。代碼的注釋都要是英文。

代码: 全选

好的,這是在 `ejtileani.js` 檔案中需要修改的部分。

您需要修改 `showAppendWindow` 函數中,為 `transformLink` 添加事件監聽器的程式碼。

**位置:** `showAppendWindow` 函數內部(約在第 1878 行)。

**原始代碼:**

```javascript
    // Only allow etaniAppendSet to be toggled
    transformLink.addEventListener('click', function(e) {
        e.preventDefault();
    });
```

**請將其替換為:**

```javascript
    // Call etaniAppendAnimate for transform and close the window
    transformLink.addEventListener('click', function(e) {
        e.preventDefault(); // Prevent default link behavior
        etaniAppendAnimate(useId, 'animateTransform', 'transform'); // Call the append function
        document.body.removeChild(windowDiv); // Close the current window
        isAppendWindowOpen = false; // Reset the flag
    });
```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
圈圈精英
圈圈精英
帖子: 3970
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 159 次
被圈友点赞: 190 次
联系:

Re: 正在做etani,ejtile的動畫套件

帖子 ejsoon »

https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
圈圈精英
圈圈精英
帖子: 3970
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 159 次
被圈友点赞: 190 次
联系:

Re: 正在做etani,ejtile的動畫套件

帖子 ejsoon »

改動:

當點擊etaniAppendOpacity時,如果etaniAppendSet沒有選中,則調用etaniAppendAnimate(id, "animate","opacity"),並關閉當前的.etaniWindow。
當點擊etaniAppendOpacity時,如果etaniAppendSet選中了,則調用etaniAppendAnimate(id, "set","opacity"),並關閉當前的.etaniWindow,並且在所添加的.etaniAnimate中,沒有etaniAVCtrl,「etaniAVLabel」的innerHTML也改為「set to :」,在etani_clone內的<use>中所增加的標籤為:「<set attributeName="opacity" to="0" dur="1s"/>」。

用繁體中文回答。只需告知所要更改的代碼和位置,以及要替換的內容。代碼的注釋都要是英文。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
圈圈精英
圈圈精英
帖子: 3970
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 159 次
被圈友点赞: 190 次
联系:

Re: 正在做etani,ejtile的動畫套件

帖子 ejsoon »

gemini 2.5pro

代码: 全选

好的,這個改動需要修改兩個地方:

1.  `showAppendWindow` 函數中 `opacityLink` 的點擊事件。
2.  `etaniAppendAnimate` 函數,使其能夠處理 `type="set"` 的情況,並相應地修改 `etani_clone` 和使用者介面 (UI)。

-----

### 改動一: `showAppendWindow` 函數

**位置:** `showAppendWindow` 函數內部(約在第 1913 行)。

**原始代碼:**

```javascript
    const opacityLink = document.createElement('a');
    opacityLink.className = 'etaniAppendOpacity';
    opacityLink.textContent = 'Opacity';
    opacityLink.href = 'javascript:;';
    opacityLink.addEventListener('click', function(e) {
        e.preventDefault();
    });
```

**請將其替換為:**

```javascript
    const opacityLink = document.createElement('a');
    opacityLink.className = 'etaniAppendOpacity';
    opacityLink.textContent = 'Opacity';
    opacityLink.href = 'javascript:;';
    opacityLink.addEventListener('click', function(e) {
        e.preventDefault();
        
        // Find the 'setSpan' within this window's scope
        const setSpan = windowDiv.querySelector('.etaniAppendSet');
        
        if (setSpan && setSpan.classList.contains('selected')) {
            // Case 2: 'Set' is selected
            // Call etaniAppendAnimate with type 'set'
            etaniAppendAnimate(useId, 'set', 'opacity');
        } else {
            // Case 1: 'Set' is not selected
            // Call etaniAppendAnimate with type 'animate'
            etaniAppendAnimate(useId, 'animate', 'opacity');
        }
        
        // Close the window and reset the flag in both cases
        document.body.removeChild(windowDiv);
        isAppendWindowOpen = false;
    });
```

-----

### 改動二: `etaniAppendAnimate` 函數 (SVG 標籤創建)

**位置:** `etaniAppendAnimate` 函數內部(約在第 2118 行)。

**原始代碼:**

```javascript
    if (animateTypeUI === 'transform') {
        // ... (code for animateTransform) ...
        targetElement.appendChild(rotateAnim);
    } else {
        // Create general animation tag
        const animate = document.createElementNS(ns, type);
        if (attr) animate.setAttribute('attributeName', attr);
        animate.setAttribute('values', '1');
        animate.setAttribute('fill', isRepeat ? 'remove' : 'freeze');
        if (isRepeat) animate.setAttribute('repeatCount', 'indefinite');

        if (!targetG) {
            animate.setAttribute('href', `#${id}`);
        }

        targetElement.appendChild(animate);
    }
```

**請將其替換為:**

```javascript
    if (animateTypeUI === 'transform') {
        // ... (existing code for animateTransform) ...
        targetElement.appendChild(rotateAnim);
    } else if (type === 'set' && attr === 'opacity') {
        // Handle <set> for opacity as requested
        const setElement = document.createElementNS(ns, 'set');
        setElement.setAttribute('attributeName', 'opacity');
        setElement.setAttribute('to', '0'); // Default 'to' value
        setElement.setAttribute('dur', '1s'); // Default 'dur' value
        
        if (!targetG) {
            setElement.setAttribute('href', `#${id}`);
        }
        targetElement.appendChild(setElement);

    } else {
        // Create general animation tag (original 'else' block)
        const animate = document.createElementNS(ns, type);
        if (attr) animate.setAttribute('attributeName', attr);
        animate.setAttribute('values', '1');
        animate.setAttribute('fill', isRepeat ? 'remove' : 'freeze');
        if (isRepeat) animate.setAttribute('repeatCount', 'indefinite');

        if (!targetG) {
            animate.setAttribute('href', `#${id}`);
        }

        targetElement.appendChild(animate);
    }
```

-----

### 改動三: `etaniAppendAnimate` 函數 (UI 創建 - `durSpan`)

**位置:** `etaniAppendAnimate` 函數內部(約在第 2176 行)。

**原始代碼:**

```javascript
    const durSpan = document.createElement('span');
    durSpan.className = 'etaniAnimateDur';
    durSpan.textContent = 'dur: 0s';
    durSpan.addEventListener('click', () => {
        // ... (complex listener code) ...
    });
```

**請將其替換為:**

```javascript
    const durSpan = document.createElement('span');
    durSpan.className = 'etaniAnimateDur';
    // Set initial dur based on type
    durSpan.textContent = (type === 'set' && attr === 'opacity') ? 'dur: 1s' : 'dur: 0s';

    // Only add complex click listener if it's not a 'set' element
    if (type !== 'set') {
        durSpan.addEventListener('click', () => {
            // ... (PASTE original complex listener code here, lines 2179-2268) ...
            const currentDur = parseFloat(durSpan.textContent.replace('dur: ', '').replace('s', ''));
            const newDur = prompt('Enter duration in seconds:', currentDur);
            if (newDur !== null && !isNaN(newDur) && newDur >= 0) {
                let animates;
                if (animateTypeUI === 'transform') {
                    animates = getAnimates(animateTypeUI, id, null, groupIndex);
                } else if (animateTypeUI === 'opacity') {
                    animates = getAnimates(animateTypeUI, id, null, groupIndex);
                }
                const currentValues = animates[0]?.getAttribute('values')?.split(';') || [];
                const currentValuesCount = currentValues.length;
                const isIntegerDur = Number.isInteger(currentDur);
                const isIntegerNewDur = Number.isInteger(newDur);
                if (isIntegerDur && isIntegerNewDur && newDur > currentDur) {
                    const valuesToAdd = newDur - currentDur;
                    const originalUseElement = document.querySelector(`#etmain .etdrop use[href="#${id}"]`);
                    if (originalUseElement) {
                        let originalTransforms;
                        let valueToAdd;
                        if (animateTypeUI === 'transform') {
                            const originalTransformString = originalUseElement.getAttribute('transform') || '';
                            originalTransforms = parseTransform(originalTransformString);
                        } else {
                            valueToAdd = '1';
                        }
                        animates.forEach(animate => {
                            const currentValueString = animate.getAttribute('values') || '';
                            if (animateTypeUI === 'transform') {
                                const subType = animate.getAttribute('type').toLowerCase();
                                valueToAdd = subType === 'translate' ? originalTransforms.translate :
                                             subType === 'scale' ? originalTransforms.scale :
                                             subType === 'rotate' ? originalTransforms.rotate : '';
                            }
                            let newValues = currentValueString ? currentValueString.split(';') : [];
                            for (let i = 0; i < valuesToAdd; i++) {
                                newValues.push(valueToAdd);
                            }
                            animate.setAttribute('values', newValues.join(';'));
                        });
                        // Update UI to add new etaniAVItems
                        const etaniAV = itemRight.querySelector(`.etaniAnimate[data-type="${animateTypeUI}"] .etaniAV`);
                        if (etaniAV) {
                            const existingItems = etaniAV.querySelectorAll('.etaniAVItem');
                            let existingLetters = Array.from(existingItems).map(item => item.textContent);
                            for (let i = 0; i < valuesToAdd; i++) {
                                const newAVItem = document.createElement('span');
                                newAVItem.className = 'etaniAVItem';
                                newAVItem.textContent = animateTypeUI === 'transform' ? findFirstMissingLetter(existingLetters) : '1';
                                if (animateTypeUI === 'transform') existingLetters.push(newAVItem.textContent);
                                newAVItem.addEventListener('click', (e) => handleAVItemClick(e, animateTypeUI));
                                etaniAV.appendChild(newAVItem);
                            }
                        }
                    }
                }
                animates.forEach(animate => {
                    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');
                    }
                });
                durSpan.textContent = `dur: ${newDur}s`;
                updateEtaniResult();
            }
        });
    }
    // If it is a 'set' element, add a simpler listener just for 'dur'
    else if (type === 'set' && attr === 'opacity') {
        durSpan.addEventListener('click', () => {
            const currentDur = parseFloat(durSpan.textContent.replace('dur: ', '').replace('s', ''));
            const newDur = prompt('Enter duration in seconds:', currentDur);
            if (newDur !== null && !isNaN(newDur) && newDur >= 0) {
                // Get the <set> element
                const setElement = getAnimates(animateTypeUI, id, null, groupIndex)[0];
                if (setElement) {
                    setElement.setAttribute('dur', `${newDur}s`);
                    durSpan.textContent = `dur: ${newDur}s`;
                    updateEtaniResult();
                }
            }
        });
    }
```

-----

### 改動四: `etaniAppendAnimate` 函數 (UI 創建 - `valueDiv`)

**位置:** `etaniAppendAnimate` 函數內部(約在第 2288 行)。

**原始代碼:**

```javascript
    const valueDiv = document.createElement('div');
    valueDiv.className = 'etaniAnimateValue';

    const avCtrlDiv = createControlButtons(animateTypeUI, id, groupIndex);

    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 = animateTypeUI === 'transform' ? 'a' : '1';
    avItemSpan.addEventListener('click', (e) => handleAVItemClick(e, animateTypeUI, groupIndex));

    avDiv.appendChild(avItemSpan);

    valueDiv.appendChild(avCtrlDiv);
    valueDiv.appendChild(avLabelSpan);
    valueDiv.appendChild(avDiv);
```

**請將其替換為:**

```javascript
    const valueDiv = document.createElement('div');
    valueDiv.className = 'etaniAnimateValue';
    
    // Conditionally create control buttons, label, and item
    if (type === 'set' && attr === 'opacity') {
        // UI for <set>
        const avLabelSpan = document.createElement('span');
        avLabelSpan.className = 'etaniAVLabel';
        avLabelSpan.textContent = 'set to : '; // Set specific label as requested

        const avDiv = document.createElement('div');
        avDiv.className = 'etaniAV';

        const avItemSpan = document.createElement('span');
        avItemSpan.className = 'etaniAVItem';
        avItemSpan.textContent = '0'; // Default 'to' value from <set> tag
        avItemSpan.style.cursor = 'pointer'; // Allow clicking to edit
        
        // Add click listener to edit the 'to' value for <set>
        avItemSpan.addEventListener('click', (e) => {
            const currentTo = avItemSpan.textContent;
            const newTo = prompt('Enter "to" value (e.g., 0, 1, inherit):', currentTo);
            
            // Check if newTo is not null (i.e., user didn't press cancel)
            if (newTo !== null) { 
                // Get the <set> element
                const setElement = getAnimates(animateTypeUI, id, null, groupIndex)[0];
                if (setElement) {
                    setElement.setAttribute('to', newTo);
                    avItemSpan.textContent = newTo;
                    updateEtaniResult();
                }
            }
        });

        avDiv.appendChild(avItemSpan);
        
        // Do not append etaniAVCtrl as requested
        valueDiv.appendChild(avLabelSpan);
        valueDiv.appendChild(avDiv);
        
    } else {
        // Default UI for <animate> and <animateTransform> (original code)
        const avCtrlDiv = createControlButtons(animateTypeUI, id, groupIndex);

        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 = animateTypeUI === 'transform' ? 'a' : '1';
        avItemSpan.addEventListener('click', (e) => handleAVItemClick(e, animateTypeUI, groupIndex));

        avDiv.appendChild(avItemSpan);

        valueDiv.appendChild(avCtrlDiv);
        valueDiv.appendChild(avLabelSpan);
        valueDiv.appendChild(avDiv);
    }
```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
圈圈精英
圈圈精英
帖子: 3970
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 159 次
被圈友点赞: 190 次
联系:

Re: 正在做etani,ejtile的動畫套件

帖子 ejsoon »

當模式選擇為repeat時,在動畫標籤中不要出現「fill=remove」,fill屬性和repeatCount屬性只需要存在一個。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
圈圈精英
圈圈精英
帖子: 3970
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 159 次
被圈友点赞: 190 次
联系:

Re: 正在做etani,ejtile的動畫套件

帖子 ejsoon »

代码: 全选

好的,這個改動需要修改 `etaniAppendAnimate` 函數中,初始創建動畫標籤時設定 `fill` 和 `repeatCount` 屬性的邏輯。

您需要修改兩個地方,都位於 `etaniAppendAnimate` 函數內部。

-----

### 改動一: `animateTransform` 的 `baseAnimate` 輔助函數

**位置:** `etaniAppendAnimate` 函數內部的 `baseAnimate` 函數(約在第 2108 行)。

**原始代碼:**

```javascript
            anim.setAttribute('fill', isRepeat ? 'remove' : 'freeze');
            if (isRepeat) anim.setAttribute('repeatCount', 'indefinite');
            if (subType === 'scale' || subType === 'rotate') {
```

**請將其替換為:**

```javascript
            // Set repeatCount or fill, but not both
            if (isRepeat) {
                anim.setAttribute('repeatCount', 'indefinite');
            } else {
                anim.setAttribute('fill', 'freeze');
            }
            
            if (subType === 'scale' || subType === 'rotate') {
```

-----

### 改動二: 通用 `animate` 標籤的創建邏輯

**位置:** `etaniAppendAnimate` 函數內部, `else` 區塊中(約在第 2137 行)。

**原始代碼:**

```javascript
        const animate = document.createElementNS(ns, type);
        if (attr) animate.setAttribute('attributeName', attr);
        animate.setAttribute('values', '1');
        animate.setAttribute('fill', isRepeat ? 'remove' : 'freeze');
        if (isRepeat) animate.setAttribute('repeatCount', 'indefinite');

        if (!targetG) {
```

**請將其替換為:**

```javascript
        const animate = document.createElementNS(ns, type);
        if (attr) animate.setAttribute('attributeName', attr);
        animate.setAttribute('values', '1');
        
        // Set repeatCount or fill, but not both
        if (isRepeat) {
            animate.setAttribute('repeatCount', 'indefinite');
        } else {
            animate.setAttribute('fill', 'freeze');
        }

        if (!targetG) {
```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
圈圈精英
圈圈精英
帖子: 3970
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 159 次
被圈友点赞: 190 次
联系:

Re: 正在做etani,ejtile的動畫套件

帖子 ejsoon »

當點擊<set>中的etaniAVItem時,如果attr是opacity,則它仍會像<animate>一樣,在0和1之間切換,只有在2秒之內點擊兩次,才會彈出輸入法框,輸入它的值。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
圈圈精英
圈圈精英
帖子: 3970
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 159 次
被圈友点赞: 190 次
联系:

Re: 正在做etani,ejtile的動畫套件

帖子 ejsoon »

代码: 全选

好的,這個改動需要修改 `etaniAppendAnimate` 函數中,為 `<set>` 標籤的 `.etaniAVItem` 綁定點擊事件的程式碼。

**位置:** `etaniAppendAnimate` 函數內部, `else if (type === 'set' && attr === 'opacity')` 區塊中(在您上次修改後的檔案中,大約在第 2320 行)。

**原始代碼:**

```javascript
        // Add click listener to edit the 'to' value for <set>
        avItemSpan.addEventListener('click', (e) => {
            const currentTo = avItemSpan.textContent;
            const newTo = prompt('Enter "to" value (e.g., 0, 1, inherit):', currentTo);
            
            // Check if newTo is not null (i.e., user didn't press cancel)
            if (newTo !== null) { 
                // Get the <set> element
                const setElement = getAnimates(animateTypeUI, id, null, groupIndex)[0];
                if (setElement) {
                    setElement.setAttribute('to', newTo);
                    avItemSpan.textContent = newTo;
                    updateEtaniResult();
                }
            }
        });
```

**請將其替換為:**

```javascript
        // Add click listener to edit the 'to' value for <set>
        // This logic mimics the double-click behavior of <animate> opacity
        avItemSpan.addEventListener('click', (e) => {
            const item = e.currentTarget; // This is avItemSpan
            
            // Get the <set> element from the clone
            const setElement = getAnimates(animateTypeUI, id, null, groupIndex)[0];
            if (!setElement) return;

            const currentTo = item.textContent;
            
            // Check for double-click
            const currentTime = Date.now();
            const isDoubleClick = (item === lastClickedItem && (currentTime - lastClickTime) < 2000);
            
            lastClickTime = currentTime;
            lastClickedItem = item;

            if (isDoubleClick) {
                // Double-click behavior: show prompt
                const newTo = prompt('Enter "to" value (e.g., 0, 1, inherit):', currentTo);
                
                // Check if newTo is not null (i.e., user didn't press cancel)
                if (newTo !== null) { 
                    setElement.setAttribute('to', newTo);
                    item.textContent = newTo;
                    updateEtaniResult();
                }
            } else {
                // Single-click behavior: toggle between 0 and 1
                const newTo = (currentTo === '0') ? '1' : '0';
                setElement.setAttribute('to', newTo);
                item.textContent = newTo;
                updateEtaniResult();
            }
        });
```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
回复

在线用户

正浏览此版面之用户: 没有注册用户 和 0 访客