代码: 全选
// Global variable to hold the SVG clone and ensure state is maintained
let etani = null;
let intervalMoving = null;
// Add dynamic CSS styles to the document
function addEtaniStyles() {
if (document.getElementById('dynamic-et-styles')) {
return;
}
const styleSheet = createEl('style');
styleSheet.id = 'dynamic-et-styles';
styleSheet.textContent = `
.etaniinner {
margin-top: 10px;
}
.etaniCtrl {
margin-bottom: 10px;
clear: both;
padding: 5px;
border: 1px solid #c0c0c0;
text-align: center;
}
.etaniCtrl > div {
display: inline-block;
vertical-align: top;
padding: 5px;
border: 1px solid #ccc;
margin: 0 5px 5px 5px;
text-align: left;
}
.etaniCtrl > div > a, .etaniCtrl > div > span {
display: inline-block;
text-decoration: none;
padding: 2px 8px;
font-size: 14px;
margin: 0 2px;
cursor: pointer;
user-select: none;
}
.etaniCtrl > div > span {
border: 1px solid #888;
background-color: #eee;
color: #333;
}
.etaniCtrl > div > span.active {
background-color: #008CBA;
color: white;
border-color: #008CBA;
}
.etaniContentHTML {
border: 1px solid #db3a32;
color: #db3a32;
}
.etaniUpdateTiles {
border: 1px solid #008CBA;
color: #008CBA;
}
.etaniCenter {
border: 1px solid green;
color: green;
}
.etaniAllAppendTransform {
border: 1px solid #2e36b9;
color: #2e36b9;
}
.etaniAllAppendOpacity {
border: 1px solid #b68942;
color: #b68942;
}
.etaniValueIncrease {
border: 1px solid purple;
color: purple;
}
.etaniCol {
border: 1px solid #aaa;
padding: 5px;
margin-bottom: 10px;
clear: both;
}
.etaniItem {
min-height: 48px;
border: 1px solid #ccc;
box-sizing: border-box;
width: 100%;
margin-bottom: -1px;
background-color: lightyellow;
display: inline-block;
}
.etaniItemLeft {
float: left;
width: 60px;
min-height: 48px;
padding: 2px 0;
text-align: center;
}
.etaniItemImageOuter {
width: 40px;
height: 40px;
margin: 0 auto;
}
.etaniItemImage {
width: 100%;
height: 100%;
display: block;
}
.etaniItemId {
text-align: center;
font-size: 12px;
word-break: break-all;
margin-top: 2px;
cursor: pointer;
}
.etaniItemPlus {
width: 12px;
height: 12px;
display: inline-block;
margin-left: 2px;
}
.etaniItemRight {
margin-left: 60px;
padding: 7px;
min-height: 64px;
background-color: #fff;
}
.etaniWindow {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 24px;
border: 1px solid #ccc;
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
z-index: 777;
width: auto;
display: inline-block;
}
.etaniAppendRow {
margin-bottom: 8px;
}
.etaniAppendRow a, .etaniAppendRow span {
display: inline-block;
padding: 4px 8px;
margin-right: 5px;
border: 1px solid #888;
background-color: #eee;
color: #333;
cursor: pointer;
text-decoration: none;
font-size: 12px;
}
.etaniAppendSet.active {
background-color: #008CBA;
color: white;
border-color: #008CBA;
}
.etaniWindow label {
display: inline;
margin-bottom: 10px;
}
.etaniAppendSpecify {
display: inline;
width: 120px;
box-sizing: border-box;
padding: 4px;
font-size: 12px;
}
.etaniWindow button {
margin-right: 10px;
padding: 5px 10px;
}
.etaniResult {
text-align: center;
margin-bottom: 10px;
padding: 10px;
border: 1px solid #bbb;
box-sizing: border-box;
}
.etaniResultImage {
display: block;
max-width: 480px;
width: 100%;
height: auto;
margin: 0 auto 10px auto;
border: 1px solid #000;
box-sizing: border-box;
}
.etaniResultDR {
text-align: center;
margin-bottom: 10px;
}
.etaniResultDownload, .etaniResultRename {
display: inline-block;
margin-right: 15px;
text-decoration: none;
padding: 5px 10px;
font-size: 16px;
}
.etaniResultDownload {
border: 1px solid blue;
color: blue;
}
.etaniResultRename {
border: 1px solid brown;
color: brown;
}
.etaniResultSize {
display: inline-block;
margin-left: 10px;
font-size: 12px;
color: #555;
}
.etaniAnimate {
border: 1px solid #999;
padding: 5px;
margin-bottom: 5px;
}
.etaniAnimateAttr {
margin-bottom: 5px;
}
.etaniAnimateAttr > span {
cursor: pointer;
display: inline-block;
padding: 2px 5px;
font-size: 12px;
box-sizing: border-box;
border-width: 1px;
border-style: solid;
}
.etaniAnimateName {
background-color: #555;
border-color: #555;
color: white;
margin-right: 10px;
}
.etaniAnimateAttr > span:not(.etaniAnimateName) {
margin-right: 7px;
}
.etaniAnimateDur {
border-color: blue;
color: blue;
}
.etaniAnimateFR {
border-color: #78229f;
color: #78229f;
}
.etaniAnimateAttrAdd {
border-color: #2c8c12;
color: #2c8c12;
}
.etaniAVCtrl {
display: inline-block;
vertical-align: top;
margin-right: 5px;
margin-bottom: 3px;
}
.etaniAVCtrl > span {
display: inline-block;
width: 24px;
height: 24px;
cursor: pointer;
vertical-align: top;
margin-right: 3px;
box-sizing: border-box;
}
.etaniAVLabel {
font-size: 14px;
margin-right: 5px;
}
.etaniAV {
display: inline-block;
vertical-align: top;
}
.etaniAVItem {
display: inline-block;
height: 24px;
background-color: #ff9933;
border: 1px dashed #00bfff;
margin: 0 5px 3px;
padding: 0 5px;
box-sizing: border-box;
cursor: pointer;
position: relative;
text-align: center;
line-height: 24px;
font-size: 12px;
color: #333;
}
.etaniAVAdd {
background-color: #a7fca7;
border: 1px solid #71c371;
}
.etaniAVDelete {
background-color: #ffcccc;
border: 1px solid #cc3333;
}
.etaniAVCopy {
background-color: #ccccff;
border: 1px solid #6666cc;
}
.etaniAVMove {
background-color: #ffcc99;
border: 1px solid #cc9966;
}
.etaniAVDelete.active {
background-color: #cc3333;
color: white;
}
.etaniAVCopy.active {
background-color: #6666cc;
color: white;
}
.etaniAVMove.active {
background-color: #cc9966;
color: white;
}
.etaniAVCtrl > span > svg {
margin-left: -1px;
margin-top: -1px;
}
textarea.etaniHTMLTextarea {
width: calc(100vw - 72px);
height: calc(50vh - 24px);
resize: none;
border: 1px solid #ccc;
font-size: 12px;
box-sizing: border-box;
}
.etaniWindowRow {
padding-top: 12px;
}
`;
document.head.appendChild(styleSheet);
}
// Append ctrl elements to etaniCtrl
function addEtaniCtrlElements(etaniinner) {
// Create the etaniCtrl element
const etaniCtrl = createEl('div', 'etaniCtrl');
etaniinner.appendChild(etaniCtrl);
// Create etaniContent div
const etaniContent = createEl('div', 'etaniContent');
// Create etaniContentHTML a
const etaniContentHTML = createEl('a', 'etaniContentHTML');
etaniContentHTML.href = 'javascript:;';
etaniContentHTML.textContent = 'HTML';
etaniContentHTML.addEventListener('click', handleContentHTMLClick);
etaniContent.appendChild(etaniContentHTML);
// Append to etaniCtrl
etaniCtrl.appendChild(etaniContent);
// Create etaniUpdate div
const etaniUpdate = createEl('div', 'etaniUpdate');
// Create etaniUpdateTiles a
const etaniUpdateTiles = createEl('a', 'etaniUpdateTiles');
etaniUpdateTiles.href = 'javascript:;';
etaniUpdateTiles.textContent = 'update';
etaniUpdate.appendChild(etaniUpdateTiles);
// Create etaniCenter a
const etaniCenter = createEl('a', 'etaniCenter');
etaniCenter.href = 'javascript:;';
etaniCenter.textContent = 'Center';
etaniUpdate.appendChild(etaniCenter);
// Append to etaniCtrl
etaniCtrl.appendChild(etaniUpdate);
// Create etaniFilter div
const etaniFilter = createEl('div', 'etaniFilter');
// Create etaniFilterTiles span
const etaniFilterTiles = createEl('span', 'etaniFilterTiles active', 'tiles');
etaniFilter.appendChild(etaniFilterTiles);
// Create etaniFilterMoving span
const etaniFilterMoving = createEl('span', 'etaniFilterMoving', 'moving');
etaniFilter.appendChild(etaniFilterMoving);
// Create etaniFilterBoard span
const etaniFilterBoard = createEl('span', 'etaniFilterBoard', 'board');
etaniFilter.appendChild(etaniFilterBoard);
// Append to etaniCtrl
etaniCtrl.appendChild(etaniFilter);
// Add click event listeners for etaniFilter spans
const filterSpans = etaniFilter.querySelectorAll('span');
filterSpans.forEach(function(span) {
span.addEventListener('click', function() {
// Remove active from all spans in etaniFilter
filterSpans.forEach(function(s) { s.classList.remove('active'); });
// Add active to clicked span
this.classList.add('active');
// Update visibility after filter change
updateVisibility();
const active = document.querySelector('.etaniFilter .active');
if (active && active.classList[0] === 'etaniFilterMoving') {
if (intervalMoving) clearInterval(intervalMoving);
intervalMoving = setInterval(updateMovingTiles, 1000);
// Call immediately
updateMovingTiles();
} else {
if (intervalMoving) clearInterval(intervalMoving);
intervalMoving = null;
}
});
});
// Create etaniMode div
const etaniMode = createEl('div', 'etaniMode');
// Create etaniModeRepeat span
const etaniModeRepeat = createEl('span', 'etaniModeRepeat active', 'repeat');
etaniMode.appendChild(etaniModeRepeat);
// Create etaniModeFreeze span
const etaniModeFreeze = createEl('span', 'etaniModeFreeze', 'freeze');
etaniMode.appendChild(etaniModeFreeze);
// Create etaniModeMixed span
const etaniModeMixed = createEl('span', 'etaniModeMixed', 'mixed');
etaniMode.appendChild(etaniModeMixed);
// Append to etaniCtrl
etaniCtrl.appendChild(etaniMode);
// Add click event listeners for etaniMode spans
const modeSpans = etaniMode.querySelectorAll('span');
modeSpans.forEach(function(span) {
span.addEventListener('click', function() {
// Remove active from all spans in etaniMode
modeSpans.forEach(function(s) { s.classList.remove('active'); });
// Add active to clicked span
this.classList.add('active');
});
});
// Create etaniAllAppend div
const etaniAllAppend = createEl('div', 'etaniAllAppend');
// Create etaniAllAppendTransform a
const etaniAllAppendTransform = createEl('a', 'etaniAllAppendTransform');
etaniAllAppendTransform.href = 'javascript:;';
etaniAllAppendTransform.textContent = 'transform';
etaniAllAppend.appendChild(etaniAllAppendTransform);
// Create etaniAllAppendOpacity a
const etaniAllAppendOpacity = createEl('a', 'etaniAllAppendOpacity');
etaniAllAppendOpacity.href = 'javascript:;';
etaniAllAppendOpacity.textContent = 'opacity';
etaniAllAppend.appendChild(etaniAllAppendOpacity);
// Append to etaniCtrl
etaniCtrl.appendChild(etaniAllAppend);
// Create etaniValue div
const etaniValue = createEl('div', 'etaniValue');
// Create etaniValueIncrease a
const etaniValueIncrease = createEl('a', 'etaniValueIncrease');
etaniValueIncrease.href = 'javascript:;';
etaniValueIncrease.textContent = 'increase';
etaniValue.appendChild(etaniValueIncrease);
// Append to etaniCtrl
etaniCtrl.appendChild(etaniValue);
}
// Define updateMovingTiles
function updateMovingTiles() {
const movingUse = document.querySelector('#etmain > .etdrop > use.tilemoving');
const items = document.querySelectorAll('.etaniItem');
items.forEach(function(item) {
item.style.display = 'none';
});
if (movingUse) {
const id = movingUse.getAttribute('href').slice(1);
const item = document.querySelector('.etaniItem[data-id="' + id + '"]');
if (item) {
item.style.display = 'block';
}
}
}
// list Etani elements
function listEtaniItems() {
// Get the etaniCol element
let etaniCol = document.querySelector('.etaniCol');
if (!etaniCol) return; // Exit if etaniCol not found
// Clear existing content in etaniCol
etaniCol.innerHTML = '';
// Collect elements into etaniElementArray
let uses = etani.querySelectorAll('.etdrop > use');
let etanidrop = etani.getElementById('etanidrop');
let boardElements = etani.querySelectorAll('.etdrop > .etboard [id]');
let etaniElementArray = [...uses, etanidrop, ...boardElements];
// Loop through etaniElementArray to create etaniItem elements
etaniElementArray.forEach(function(element) {
if (!element) return; // Skip if element is null
// Determine itemId and dataType
let itemId;
let dataType;
if (element.tagName === 'use') {
itemId = element.getAttribute('href').slice(1);
dataType = 'tile';
} else if (element.id === 'etanidrop') {
itemId = 'board';
dataType = 'board';
} else {
itemId = element.id;
dataType = 'board';
}
// Create etaniItem div
const etaniItem = createEl('div', 'etaniItem');
etaniItem.dataset.id = itemId;
etaniItem.dataset.type = dataType;
// Create etaniItemLeft div
const etaniItemLeft = createEl('div', 'etaniItemLeft');
etaniItem.appendChild(etaniItemLeft);
// Create etaniItemImageOuter div
const etaniItemImageOuter = createEl('div', 'etaniItemImageOuter');
etaniItemLeft.appendChild(etaniItemImageOuter);
if (dataType === 'tile') {
// Create etaniItemImage img
const etaniItemImage = createEl('img', 'etaniItemImage');
etaniItemImage.src = generateTileImage(itemId);
etaniItemImageOuter.appendChild(etaniItemImage);
} else {
// Generate background color
etaniItemImageOuter.style.background = generateHexColor(itemId);
}
// Create etaniItemId div
const etaniItemId = createEl('div', 'etaniItemId');
etaniItemId.addEventListener('click', function() {
etaniAppendAnimateWindow(itemId, dataType);
});
etaniItemLeft.appendChild(etaniItemId);
// Create etaniItemName span
const etaniItemName = createEl('span', 'etaniItemName', itemId);
etaniItemId.appendChild(etaniItemName);
// Create etaniItemPlus img
const etaniItemPlus = createEl('img', 'etaniItemPlus');
etaniItemPlus.width = 12;
etaniItemPlus.height = 12;
etaniItemPlus.src = 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEyIiBoZWlnaHQ9IjEyIiBmaWxsPSJub25lIiBzdHJva2Utd2lkdGg9IjEiPjxsaW5lIHN0cm9rZT0iZGFya2dyZWVuIiB4MT0iNiIgeTE9IjAiIHgyPSI2IiB5Mj0iMTIiPjwvbGluZT48bGluZSBzdHJva2U9ImRhcmtncmVlbiIgeDE9IjAiIHkxPSI2IiB4Mj0iMTIiIHkyPSI2Ij48L2xpbmU+PC9zdmc+';
etaniItemId.appendChild(etaniItemPlus);
// Create etaniItemRight div
const etaniItemRight = createEl('div', 'etaniItemRight');
etaniItem.appendChild(etaniItemRight);
// Append etaniItem to etaniCol
etaniCol.appendChild(etaniItem);
});
}
// Define updateVisibility
function updateVisibility() {
const active = document.querySelector('.etaniFilter .active');
if (!active) return;
const activeClass = active.classList[0]; // e.g., 'etaniFilterTiles'
const items = document.querySelectorAll('.etaniItem');
items.forEach(function(item) {
item.style.display = 'none';
});
if (activeClass === 'etaniFilterTiles') {
document.querySelectorAll('.etaniItem[data-type="tile"]').forEach(function(item) {
item.style.display = 'block';
});
} else if (activeClass === 'etaniFilterBoard') {
document.querySelectorAll('.etaniItem[data-type="board"]').forEach(function(item) {
item.style.display = 'block';
});
} else if (activeClass === 'etaniFilterMoving') {
document.querySelectorAll('.etaniItem[data-type="board"]').forEach(function(item) {
item.style.display = 'block';
});
// Tiles visibility handled by interval
}
}
// Convert SVG string to a Base64 data URL
function svgToBase64(svgString) {
const base64 = btoa(unescape(encodeURIComponent(svgString)));
return `data:image/svg+xml;base64,${base64}`;
}
// Define start button click
function etaniStartClick() {
const etaniouter = document.querySelector('.etaniouter');
const originalSvg = document.getElementById('etmain');
if (!originalSvg) {
console.error('SVG with ID "etmain" not found.');
return;
}
etani = originalSvg.cloneNode(true);
const etwaitElement = etani.querySelector('.etwait');
if (etwaitElement) {
etwaitElement.remove();
}
etani.id = 'etani';
const etdropClone = etani.querySelector('.etdrop');
if (etdropClone) {
etdropClone.id = 'etanidrop';
}
// Check if etaniinner already exists
let etaniinner = etaniouter.querySelector('.etaniinner');
if (etaniinner) {
// Remove etaniinner
etaniouter.removeChild(etaniinner);
// Restore button text
etaniStart.textContent = 'start ejtile animate';
} else {
// Create etaniinner div
etaniinner = createEl('div', 'etaniinner');
// Create etaniCtrl div
addEtaniCtrlElements(etaniinner);
// Create etaniCol div
const etaniCol = createEl('div', 'etaniCol');
etaniinner.appendChild(etaniCol);
// Create etaniResult div
const etaniResult = createEl('div', 'etaniResult');
etaniinner.appendChild(etaniResult);
// Append etaniinner to etaniouter
etaniouter.appendChild(etaniinner);
// Change button text
etaniStart.textContent = 'close ejtile animate';
// list etani elements
listEtaniItems();
// update etani elements display by etaniFilter
updateVisibility();
// update result
updateEtaniResult();
}
}
// Generate base64 image for a tile
function generateTileImage(tileid) {
const originalTile = document.querySelector(`#etmain > defs > g#${tileid}`);
if (!originalTile) return null;
const etdropUses = document.querySelectorAll('#etmain > .etdrop > use');
const etwaitGroups = document.querySelectorAll('#etmain > .etwait g');
let targetUse = null;
for (const group of etwaitGroups) {
const useInGroup = group.querySelector(`use[href="#${tileid}"]`);
if (useInGroup) {
targetUse = useInGroup;
break;
}
}
let etwaittransform = '', etwaitfill = '', etwaitstroke = '', etwaitstrokeWidth = '';
if (targetUse) {
etwaittransform = targetUse.getAttribute('transform') || '';
etwaitfill = targetUse.getAttribute('fill') || '';
etwaitstroke = targetUse.getAttribute('stroke') || '';
etwaitstrokeWidth = targetUse.getAttribute('stroke-width') || '';
const scaleMatch = etwaittransform.match(/scale\(([^)]+)\)/);
const rotateMatch = etwaittransform.match(/rotate\(([^)]+)\)/);
const scalePart = scaleMatch ? scaleMatch[0] : '';
const rotatePart = rotateMatch ? rotateMatch[0] : '';
etwaittransform = `translate(20,20) ${scalePart} ${rotatePart}`.trim().replace(/\s+/g, ' ');
}
const tileclone = originalTile.cloneNode(true);
tileclone.removeAttribute('id');
if (etwaittransform) tileclone.setAttribute('transform', etwaittransform);
if (etwaitfill) tileclone.setAttribute('fill', etwaitfill);
if (etwaitstroke) tileclone.setAttribute('stroke', etwaitstroke);
if (etwaitstrokeWidth) tileclone.setAttribute('stroke-width', etwaitstrokeWidth);
const svgWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgWrapper.setAttribute('width', '40');
svgWrapper.setAttribute('height', '40');
svgWrapper.setAttribute('version', '1.1');
svgWrapper.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
svgWrapper.appendChild(tileclone);
const svgString = new XMLSerializer().serializeToString(svgWrapper);
return svgToBase64(svgString);
}
// generate hex color
function generateHexColor(seed) {
let hash = 0;
for (let i = 0; i < seed.length; i++) {
hash = seed.charCodeAt(i) + ((hash << 5) - hash);
}
let color = (hash & 0x00FFFFFF).toString(16).toUpperCase();
while (color.length < 6) {
color = '0' + color;
}
return '#' + color;
}
// Handle the HTML popup window
function handleContentHTMLClick() {
if (!etani) return;
const textarea = createEl('textarea', 'etaniHTMLTextarea');
textarea.value = new XMLSerializer().serializeToString(etani);
etaniWindow(textarea);
}
// TODO
// Handle click event for the update button
function handleUpdateTilesClick(e) {
e.preventDefault();
if (!etani) return;
const originalSvg = document.getElementById('etmain');
if (!originalSvg) return;
const originalDefs = originalSvg.querySelector('defs');
const cloneDefs = etani_clone.querySelector('defs');
const originalDrop = originalSvg.querySelector('.etdrop');
const cloneDrop = etani_clone.querySelector('.etdrop');
if (!originalDefs || !cloneDefs || !originalDrop || !cloneDrop) return;
const originalTiles = originalDefs.querySelectorAll('g[id]');
const cloneTilesMap = new Map(Array.from(cloneDefs.querySelectorAll('g[id]')).map(g => [g.id, g]));
const originalUsesMap = new Map(Array.from(originalDrop.querySelectorAll('use[href]')).map(u => [u.getAttribute('href').substring(1), u]));
const cloneUsesMap = new Map(Array.from(cloneDrop.querySelectorAll('use[href]')).map(u => [u.getAttribute('href').substring(1), u]));
originalTiles.forEach(originalTileG => {
const tileId = originalTileG.id;
const cloneTileG = cloneTilesMap.get(tileId);
const originalUse = originalUsesMap.get(tileId);
const cloneUse = cloneUsesMap.get(tileId);
if (cloneTileG) {
// Update existing tile definition
cloneTileG.replaceWith(originalTileG.cloneNode(true));
// Update corresponding <use> transform
if (originalUse && cloneUse) {
const transform = originalUse.getAttribute('transform');
if (transform) {
cloneUse.setAttribute('transform', transform);
} else {
cloneUse.removeAttribute('transform');
}
}
} else {
// Add new tile definition
cloneDefs.appendChild(originalTileG.cloneNode(true));
// Add new <use> element
if (originalUse) {
cloneDrop.appendChild(originalUse.cloneNode(true));
}
// Add new etaniItem to UI
createEtaniItem(tileId);
}
});
// Logic from handleUpdateBoardClick (merging board update) (Inst 3)
const originalSvg_board = document.getElementById('etmain');
if (originalSvg_board) {
const originalBoard = originalSvg_board.querySelector('.etdrop .etboard');
const cloneBoard = etani_clone.querySelector('.etdrop .etboard');
if (originalBoard && cloneBoard) {
cloneBoard.replaceWith(originalBoard.cloneNode(true));
}
}
// End of merged logic
// Refresh all tile images as defs might have changed
updateAllTileImages();
updateEtaniResult();
}
// Open a generic window
function etaniWindow(content, confirm_function = null) {
// Get the .etaniinner element
const inner = document.querySelector('.etaniinner');
if (!inner) {
console.error('Element with class .etaniinner not found.');
return;
}
let windowDiv = inner.querySelector('.etaniWindow');
if (windowDiv) return;
// Create the main div.etaniWindow
windowDiv = createEl('div', 'etaniWindow');
// Append custom content to the window
if (typeof content === 'string') {
windowDiv.innerHTML = content;
} else if (content instanceof Element) {
windowDiv.appendChild(content);
} else {
console.error('Invalid content type provided.');
return;
}
// Add confirm and cancel buttons if not already in content
let btnConfirm = windowDiv.querySelector('button.confirm'); // Assume class or id for identification
let btnCancel = windowDiv.querySelector('button.cancel');
if (!btnConfirm || !btnCancel) {
const rowButtons = createEl('div', 'etaniWindowRow');
btnConfirm = createEl('button', 'confirm', 'Confirm');
btnCancel = createEl('button', 'cancel', 'Cancel');
rowButtons.appendChild(btnConfirm);
rowButtons.appendChild(btnCancel);
windowDiv.appendChild(rowButtons);
}
// Append the windowDiv to .etaniinner
inner.appendChild(windowDiv);
// Add event listeners
if (btnConfirm) {
btnConfirm.addEventListener('click', function() {
if (typeof confirm_function === 'function') {
confirm_function();
}
windowDiv.remove();
});
}
if (btnCancel) {
btnCancel.addEventListener('click', function() {
windowDiv.remove();
});
}
}
// Open append animate window
function etaniAppendAnimateWindow(id, dataType) {
const appendAnimateWindow = createEl('div', 'appendAnimateWindow');
// First row
const row1 = createEl('div', 'etaniAppendRow');
const strong = createEl('strong', null, 'id: ' + id);
row1.appendChild(strong);
appendAnimateWindow.appendChild(row1);
// Second row
const row2 = createEl('div', 'etaniAppendRow');
const aTransform = createEl('a', 'etaniAppendTransform', 'Transform');
aTransform.href = 'javascript:;';
const aMotion = createEl('a', 'etaniAppendMotion', 'Motion');
aMotion.href = 'javascript:;';
const spanSet = createEl('span', 'etaniAppendSet', 'Set');
row2.appendChild(aTransform);
row2.appendChild(aMotion);
row2.appendChild(spanSet);
appendAnimateWindow.appendChild(row2);
// Third row
const row3 = createEl('div', 'etaniAppendRow');
const aOpacity = createEl('a', 'etaniAppendOpacity', 'Opacity');
aOpacity.href = 'javascript:;';
const aWriting = createEl('a', 'etaniAppendWriting', 'Writing');
aWriting.href = 'javascript:;';
row3.appendChild(aOpacity);
row3.appendChild(aWriting);
appendAnimateWindow.appendChild(row3);
// Fourth row
const row4 = createEl('div', 'etaniAppendRow');
const aFill = createEl('a', 'etaniAppendFill', 'Fill');
aFill.href = 'javascript:;';
const aStroke = createEl('a', 'etaniAppendStroke', 'Stroke');
aStroke.href = 'javascript:;';
const aWidth = createEl('a', 'etaniAppendWidth', 'Width');
aWidth.href = 'javascript:;';
row4.appendChild(aFill);
row4.appendChild(aStroke);
row4.appendChild(aWidth);
appendAnimateWindow.appendChild(row4);
// Fifth row
const row5 = createEl('div', 'etaniAppendRow');
const label = createEl('label', null, 'specify: ');
const input = createEl('input', 'etaniAppendSpecify');
input.type = 'text';
row5.appendChild(label);
row5.appendChild(input);
appendAnimateWindow.appendChild(row5);
etaniWindow(appendAnimateWindow);
// Add click events to all specified elements
// For a elements with href="javascript:;"
aTransform.addEventListener('click', function() {
etaniAppendAnimate(id, dataType, "animateTransform", "transform");
document.querySelector('.etaniWindow').remove();
});
aMotion.addEventListener('click', function() {
// TODO: Implement Motion click functionality
});
aOpacity.addEventListener('click', function() {
// TODO: Implement Opacity click functionality
});
aWriting.addEventListener('click', function() {
// TODO: Implement Writing click functionality
});
aFill.addEventListener('click', function() {
// TODO: Implement Fill click functionality
});
aStroke.addEventListener('click', function() {
// TODO: Implement Stroke click functionality
});
aWidth.addEventListener('click', function() {
// TODO: Implement Width click functionality
});
// For span.etaniAppendSet
spanSet.addEventListener('click', function() {
if (spanSet.classList.contains('active')) {
this.classList.remove('active');
aTransform.style.pointerEvents = 'auto';
aTransform.style.opacity = '1'; // Add disabled CSS state
aMotion.style.pointerEvents = 'auto';
aMotion.style.opacity = '1'; // Add disabled CSS state
} else {
this.classList.add('active');
aTransform.style.pointerEvents = 'none';
aTransform.style.opacity = '0.5'; // Add disabled CSS state
aMotion.style.pointerEvents = 'none';
aMotion.style.opacity = '0.5'; // Add disabled CSS state
}
});
}
// Update the result section with the current state of etani
function updateEtaniResult() {
if (!etani) return;
let etaniResult = document.querySelector('.etaniResult');
if (!etaniResult) return;
const svgString = new XMLSerializer().serializeToString(etani);
const sizeInBytes = new Blob([svgString]).size;
const base64Url = svgToBase64(svgString);
// Generate default filename with current date and time
const now = new Date();
const defaultFilename = `ejtileAnimation_${now.toISOString().replace(/[-:T]/g, '').slice(0, 15)}.svg`;
let imgElement = document.querySelector('.etaniResultImage');
let downloadElementOuter = document.querySelector('.etaniResultDR');
let downloadElement = document.querySelector('.etaniResultDownload');
let renameElement = document.querySelector('.etaniResultRename');
let sizeElement = document.querySelector('.etaniResultSize');
if (!imgElement) {
imgElement = createEl('img', 'etaniResultImage');
imgElement.alt = 'Rendered Ejtile Animation SVG';
etaniResult.appendChild(imgElement);
}
if (!downloadElementOuter) {
downloadElementOuter = createEl('div', 'etaniResultDR');
}
if (!downloadElement) {
downloadElement = createEl('a', 'etaniResultDownload', 'Download SVG');
downloadElement.href = 'javascript:;';
downloadElementOuter.appendChild(downloadElement);
etaniResult.appendChild(downloadElementOuter);
}
if (!renameElement) {
renameElement = createEl('a', 'etaniResultRename', 'Rename File');
renameElement.href = 'javascript:;';
downloadElementOuter.appendChild(renameElement);
etaniResult.appendChild(downloadElementOuter);
}
if (!sizeElement) {
sizeElement = createEl('span', 'etaniResultSize');
etaniResult.appendChild(sizeElement);
}
if (imgElement && downloadElement && renameElement && sizeElement) {
imgElement.src = base64Url;
sizeElement.textContent = `Size: ${sizeInBytes} byte`;
downloadElement.href = base64Url;
downloadElement.download = defaultFilename;
renameElement.onclick = (e) => {
e.preventDefault();
let currentDownloadName = downloadElement.download;
let newFilename = prompt("Enter new filename:", currentDownloadName);
if (newFilename) {
if (!newFilename.toLowerCase().endsWith('.svg')) {
newFilename += '.svg';
}
downloadElement.download = newFilename;
alert(`Filename changed to: ${newFilename}`);
}
};
}
}
// create element and set className
function createEl(tag, className, textContent) {
const el = document.createElement(tag);
if (className) {
el.className = className;
}
if (textContent) {
el.textContent = textContent;
}
return el;
}
// parse transform values
function parseTransformValues(transformStr) {
const transforms = {
translate: '0,0',
scale: '1,1',
rotate: '0'
};
if (!transformStr) {
return transforms;
}
// Extract translate values
const translateMatch = /translate\(([^)]+)\)/.exec(transformStr);
if (translateMatch) {
transforms.translate = translateMatch[1].trim();
}
// Extract scale values
const scaleMatch = /scale\(([^)]+)\)/.exec(transformStr);
if (scaleMatch) {
transforms.scale = scaleMatch[1].trim();
}
// Extract rotate values
const rotateMatch = /rotate\(([^)]+)\)/.exec(transformStr);
if (rotateMatch) {
transforms.rotate = rotateMatch[1].trim();
}
return transforms;
}
// append etaniAnimate to etaniItemRight
function appendAnimateToResult(id, elementtype, tagname, animatetype, defaultvalue = null) {
// 1. Find the SVG insertion target using the 'etani' global variable
let targetSVGParent;
if (elementtype === 'tile') {
// For 'tile', the target is the <use> element in .etdrop
targetSVGParent = etani.querySelector(`.etdrop > use[href="#${id}"]`);
} else if (elementtype === 'board') {
// For 'board', the target is the element (e.g., <g>) with the matching id
targetSVGParent = etani.querySelector(`#${id}`);
}
if (!targetSVGParent) {
console.error(`Error: SVG target parent (href="#${id}" or "#${id}") not found in 'etani' variable.`);
return;
}
// 2. Determine repeat or fill attributes from the UI state
const repeatModeActive = document.querySelector('.etaniModeRepeat.active');
let repeatOrFillAttrs = {};
if (tagname === 'set') {
// 'set' never has repeatCount
if (!repeatModeActive) {
repeatOrFillAttrs = { fill: "freeze" };
}
} else {
// Other animation types
if (repeatModeActive) {
repeatOrFillAttrs = { repeatCount: "indefinite" };
} else {
repeatOrFillAttrs = { fill: "freeze" };
}
}
// 3. Create and append SVG elements
switch (tagname) {
case 'animateTransform':
// Find the source <use> element in #etmain to read the transform from
const sourceElement = document.querySelector(`#etmain .etdrop > use[href="#${id}"]`);
const transformString = sourceElement ? sourceElement.getAttribute('transform') : '';
// Parse the existing transform values
const transformValues = parseTransformValues(transformString);
// Define base attributes for all 3 transform animations
const baseAttrs = {
attributeName: "transform",
attributeType: "XML",
...repeatOrFillAttrs // Add the repeat/fill logic
};
// Create and append <animateTransform> for translate
const elTranslate = createSVGElement('animateTransform', {
...baseAttrs,
type: "translate",
values: transformValues.translate
});
targetSVGParent.appendChild(elTranslate);
// Create and append <animateTransform> for scale
const elScale = createSVGElement('animateTransform', {
...baseAttrs,
type: "scale",
values: transformValues.scale,
additive: "sum"
});
targetSVGParent.appendChild(elScale);
// Create and append <animateTransform> for rotate
const elRotate = createSVGElement('animateTransform', {
...baseAttrs,
type: "rotate",
values: transformValues.rotate,
additive: "sum"
});
targetSVGParent.appendChild(elRotate);
break;
case 'animate':
const elAnimateSVG = createSVGElement('animate', {
attributeName: animatetype,
values: defaultvalue,
...repeatOrFillAttrs
});
targetSVGParent.appendChild(elAnimateSVG);
break;
case 'animateMotion':
const elMotion = createSVGElement('animateMotion', {
dur: "1s",
path: defaultvalue,
...repeatOrFillAttrs
});
targetSVGParent.appendChild(elMotion);
break;
case 'set':
const elSet = createSVGElement('set', {
attributeName: animatetype,
to: defaultvalue,
dur: "1s",
...repeatOrFillAttrs // Will be {fill: "freeze"} or {}
});
targetSVGParent.appendChild(elSet);
break;
}
}
// append etaniAnimate to etaniItemRight
function appendAnimateToItemRight(id, elementtype, tagname, animatetype, defaultvalue = null) {
// Find the UI target parent element (in the .etaniinner UI panel)
const targetUIParent = document.querySelector(`.etaniinner .etaniItem[data-id="${id}"] > .etaniItemRight`);
if (!targetUIParent) {
console.error(`Error: UI target (.etaniinner .etaniItem[data-id="${id}"] > .etaniItemRight) not found.`);
return;
}
// Build the UI structure
const elAnimate = createEl('div', 'etaniAnimate');
elAnimate.dataset.tagname = tagname;
elAnimate.dataset.animatetype = animatetype;
// 1. Build .etaniAnimateAttr
const elAnimateAttr = createEl('div', 'etaniAnimateAttr');
const elAnimateName = createEl('span', 'etaniAnimateName');
const elAnimateDur = createEl('span', 'etaniAnimateDur');
const elAnimateAttrAdd = createEl('span', 'etaniAnimateAttrAdd', '+');
elAnimateAttr.appendChild(elAnimateName);
elAnimateAttr.appendChild(elAnimateDur);
elAnimateAttr.appendChild(elAnimateAttrAdd);
// 2. Build .etaniAnimateValue
const elAnimateValue = createEl('div', 'etaniAnimateValue');
const elAVLabel = createEl('span', 'etaniAVLabel');
const elAV = createEl('div', 'etaniAV');
const elAVItem = createEl('span', 'etaniAVItem');
elAV.appendChild(elAVItem);
// create controls for this type
const elAVCtrl = createEl('div', 'etaniAVCtrl');
const etaniAVAdd = createEl('span', 'etaniAVAdd');
etaniAVAdd.innerHTML = '<svg width="24" height="24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>';
const etaniAVDelete = createEl('span', 'etaniAVDelete');
etaniAVDelete.innerHTML = '<svg width="24" height="24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line></svg>';
const etaniAVCopy = createEl('span', 'etaniAVCopy');
etaniAVCopy.innerHTML = '<svg width="24" height="24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>';
const etaniAVMove = createEl('span', 'etaniAVMove');
etaniAVMove.innerHTML = '<svg width="24" height="24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><path d="M 7,4 L 3,8 L 7,12 M 3,8 L 18,8 M 17,12 L 21,16 L 17,20 M 21,16 L 6,16"></path></svg>';
elAVCtrl.appendChild(etaniAVAdd);
elAVCtrl.appendChild(etaniAVDelete);
elAVCtrl.appendChild(etaniAVCopy);
elAVCtrl.appendChild(etaniAVMove);
elAVCtrl.dataset.mode = 'edit';
etaniAVAdd.addEventListener('click', () => {
const active = elAVCtrl.querySelector('.active');
if (active) active.classList.remove('active');
elAnimateValue.dataset.mode = 'edit';
});
const toggleButtons = {
delete: { element: etaniAVDelete, mode: 'delete' },
copy: { element: etaniAVCopy, mode: 'copy' },
move: { element: etaniAVMove, mode: 'move' }
};
Object.values(toggleButtons).forEach(({ element, mode }) => {
element.addEventListener('click', function () {
if (elAnimateValue.dataset.mode === mode) {
this.classList.remove('active');
elAnimateValue.dataset.mode = 'edit';
} else {
const active = elAnimateValue.querySelector('.active');
if (active) active.classList.remove('active');
this.classList.add('active');
elAnimateValue.dataset.mode = mode;
}
});
});
// 3. Apply logic based on tagname
switch (tagname) {
case 'animateTransform':
elAnimateName.textContent = 'transform';
elAnimateDur.textContent = 'dur: 0s';
elAVLabel.textContent = 'values : ';
elAVItem.textContent = 'a'; // Default placeholder
elAnimateValue.appendChild(elAVCtrl);
break;
case 'animate':
elAnimateName.textContent = animatetype;
elAnimateDur.textContent = 'dur: 0s';
elAVLabel.textContent = 'values : ';
elAVItem.textContent = defaultvalue;
elAnimateValue.appendChild(elAVCtrl);
break;
case 'animateMotion':
elAnimateName.textContent = 'motion';
elAnimateDur.textContent = 'dur: 1s';
elAVLabel.textContent = 'move to : ';
elAVItem.textContent = defaultvalue;
// No controls for this type
break;
case 'set':
elAnimateName.textContent = animatetype;
elAnimateDur.textContent = 'dur: 1s';
elAVLabel.textContent = 'set to : ';
elAVItem.textContent = defaultvalue;
// No controls for this type
break;
default:
console.error(`Error: Unknown tagname "${tagname}" for UI.`);
return;
}
// 4. Assemble the .etaniAnimateValue children
elAnimateValue.appendChild(elAVLabel);
elAnimateValue.appendChild(elAV);
// 5. Assemble the final element
elAnimate.appendChild(elAnimateAttr);
elAnimate.appendChild(elAnimateValue);
// 6. Append the fully constructed UI element to the DOM
targetUIParent.appendChild(elAnimate);
}
// create SVG element
function createSVGElement(name, attrs) {
const el = document.createElementNS('http://www.w3.org/2000/svg', name);
for (const key in attrs) {
el.setAttribute(key, attrs[key]);
}
return el;
}
// Appends animation UI controls and the corresponding SVG animation element.
function etaniAppendAnimate(id, elementtype, tagname, animatetype, defaultvalue = null) {
appendAnimateToItemRight(id, elementtype, tagname, animatetype, defaultvalue);
appendAnimateToResult(id, elementtype, tagname, animatetype, defaultvalue);
}
// Initialize the animation control panel on window load
window.addEventListener('load', function() {
// Get the etmainouter element
const etmainouter = document.getElementById('etmainouter');
if (!etmainouter) return; // Exit if etmainouter not found
// insert dynamic style
addEtaniStyles();
// Create etaniouter div
const etaniouter = createEl('div', 'etaniouter');
// Create etaniStart button
const etaniStart = createEl('button', null, 'start ejtile animate');
etaniStart.id = 'etaniStart';
// Append button to etaniouter
etaniouter.appendChild(etaniStart);
// Insert etaniouter after etmainouter
etmainouter.parentNode.insertBefore(etaniouter, etmainouter.nextSibling);
// Add click event listener to etaniStart
etaniStart.addEventListener('click', etaniStartClick);
});