微件

TierListMaker:修订间差异

来自卡厄思梦境WIKI

律Rhyme留言 | 贡献
无编辑摘要
律Rhyme留言 | 贡献
无编辑摘要
第152行: 第152行:
}
}


/* 导出时隐藏角色池 */
.exporting .tier-dropzone.pool {
#tierlist-maker.exporting .pool-wrapper {
  border-color: transparent;
   display: none !important;
   background: transparent;
}
}
</style>
</style>
第165行: 第165行:
   }
   }
   function contrastColor(hex) {
   function contrastColor(hex) {
     hex = hex.replace('#','');
     hex = (hex || '').replace('#','');
     if (hex.length === 3) hex = hex.split('').map(c => c + c).join('');
     if (hex.length === 3) hex = hex.split('').map(c => c + c).join('');
    if (!/^[0-9a-fA-F]{6}$/.test(hex)) return '#fff';
     var r = parseInt(hex.substr(0,2), 16);
     var r = parseInt(hex.substr(0,2), 16);
     var g = parseInt(hex.substr(2,2), 16);
     var g = parseInt(hex.substr(2,2), 16);
第184行: 第185行:
       }
       }
     }, { offset: Number.NEGATIVE_INFINITY, element: null }).element;
     }, { offset: Number.NEGATIVE_INFINITY, element: null }).element;
  }
  function ensureCrossOrigin(img) {
    try {
      if (!img) return;
      if (!img.crossOrigin) img.crossOrigin = 'anonymous';
      if (!img.referrerPolicy) img.referrerPolicy = 'no-referrer';
    } catch(e) {}
   }
   }
   function makeDraggable(avatar) {
   function makeDraggable(avatar) {
    if (!avatar || avatar._draggableInit) return;
    avatar._draggableInit = true;
     avatar.setAttribute('draggable', 'true');
     avatar.setAttribute('draggable', 'true');
     avatar.classList.add('draggable-avatar');
     avatar.classList.add('draggable-avatar');
    avatar.querySelectorAll('img').forEach(ensureCrossOrigin);
     avatar.addEventListener('dragstart', function(e) {
     avatar.addEventListener('dragstart', function(e) {
       avatar.classList.add('dragging');
       avatar.classList.add('dragging');
      avatar._prevParent = avatar.parentNode;
       e.dataTransfer.setData('text/plain', avatar.dataset.id || avatar.querySelector('.avatar-name')?.textContent || '');
       e.dataTransfer.setData('text/plain', avatar.dataset.id || avatar.querySelector('.avatar-name')?.textContent || '');
       e.dataTransfer.effectAllowed = 'move';
       e.dataTransfer.effectAllowed = 'move';
第195行: 第208行:
     avatar.addEventListener('dragend', function() {
     avatar.addEventListener('dragend', function() {
       avatar.classList.remove('dragging');
       avatar.classList.remove('dragging');
      cleanupEmptyWrapper(avatar._prevParent);
      avatar._prevParent = null;
     });
     });
   }
   }
   function initAvatars(root) {
   function initAvatars(root) {
    if (!root) return;
     const avatars = root.querySelectorAll('.avatar-frame');
     const avatars = root.querySelectorAll('.avatar-frame');
     avatars.forEach(function(av) {
     avatars.forEach(function(av) {
第205行: 第221行:
       }
       }
       makeDraggable(av);
       makeDraggable(av);
    });
  }
  function cleanupEmptyWrapper(node) {
    if (!node || node.nodeType !== 1) return;
    if (node.classList && node.classList.contains('tier-dropzone')) return;
    const hasElementChild = node.querySelector('.avatar-frame');
    const onlyWhitespace = !node.textContent || node.textContent.trim().length === 0;
    if (!hasElementChild && node.childElementCount === 0 && onlyWhitespace) {
      node.parentNode && node.parentNode.removeChild(node);
    }
  }
  function cleanupEmptyPlaceholdersIn(container) {
    if (!container) return;
    Array.from(container.children).forEach(function(child) {
      if (child.nodeType !== 1) return;
      if (child.classList.contains('avatar-frame')) return;
      const hasAvatarInside = !!child.querySelector('.avatar-frame');
      const onlyWhitespace = !child.textContent || child.textContent.trim().length === 0;
      if (!hasAvatarInside && child.childElementCount === 0 && onlyWhitespace) {
        child.remove();
      }
     });
     });
   }
   }
   function initDropzone(zone) {
   function initDropzone(zone) {
    if (!zone || zone._dropzoneInit) return;
    zone._dropzoneInit = true;
     zone.addEventListener('dragover', function(e) {
     zone.addEventListener('dragover', function(e) {
       e.preventDefault();
       e.preventDefault();
       e.dataTransfer.dropEffect = 'move';
       e.dataTransfer.dropEffect = 'move';
        
       const afterElement = getDragAfterElement(zone, e.clientY);
       const dragging = document.querySelector('.avatar-frame.dragging');
       const dragging = document.querySelector('.avatar-frame.dragging');
       if (!dragging) return;
       if (!dragging) return;
        
       if (afterElement == null) {
      const afterElement = getDragAfterElement(zone, e.clientY);
        if (dragging.parentNode !== zone) zone.appendChild(dragging);
     
      } else {
      // 安全地移动元素
        if (afterElement !== dragging) zone.insertBefore(dragging, afterElement);
      try {
        if (afterElement == null) {
          // 如果元素不在当前zone中,则添加
          if (dragging.parentNode !== zone) {
            zone.appendChild(dragging);
          }
        } else {
          // 确保afterElement仍然是zone的子元素
          if (zone.contains(afterElement) && dragging !== afterElement) {
            zone.insertBefore(dragging, afterElement);
          }
        }
      } catch (err) {
        // 如果出错,就简单地添加到末尾
        console.warn('Insert error, appending instead:', err);
        if (dragging.parentNode !== zone) {
          zone.appendChild(dragging);
        }
       }
       }
     });
     });
   
 
     zone.addEventListener('drop', function(e) {
     zone.addEventListener('drop', function(e) {
       e.preventDefault();
       e.preventDefault();
      e.stopPropagation();
       const dragging = document.querySelector('.avatar-frame.dragging');
       const dragging = document.querySelector('.avatar-frame.dragging');
       if (dragging) {
       if (dragging) {
        // 确保元素在正确的位置
         if (dragging.parentNode !== zone) zone.appendChild(dragging);
         if (!zone.contains(dragging)) {
        cleanupEmptyWrapper(dragging._prevParent);
          zone.appendChild(dragging);
        dragging._prevParent = null;
         }
      }
      if (zone.classList.contains('pool')) {
         cleanupEmptyPlaceholdersIn(zone);
       }
       }
     });
     });
第265行: 第290行:
       const color = document.createElement('div');
       const color = document.createElement('div');
       color.className = 'color-toggle';
       color.className = 'color-toggle';
      color.title = '更改颜色';
       const del = document.createElement('div');
       const del = document.createElement('div');
       del.className = 'delete-row';
       del.className = 'delete-row';
       del.textContent = '删除';
       del.textContent = '删除';
      del.title = '删除该行';
       t.appendChild(color); t.appendChild(del);
       t.appendChild(color); t.appendChild(del);
       return t;
       return t;
第273行: 第300行:
     th.appendChild(label);
     th.appendChild(label);
     th.appendChild(toolsWrap);
     th.appendChild(toolsWrap);
     const initialBg = th.getAttribute('data-initial-bg') || 'gray';
     const initialBg = th.getAttribute('data-initial-bg') || '#555';
    const fg = contrastColor(initialBg);
     th.style.background = initialBg;
     th.style.background = initialBg;
     th.style.color = fg;
     th.style.color = contrastColor(initialBg);
     attachColorPalette(toolsWrap.querySelector('.color-toggle'), th);
     attachColorPalette(toolsWrap.querySelector('.color-toggle'), th);
     const delBtn = toolsWrap.querySelector('.delete-row');
     const delBtn = toolsWrap.querySelector('.delete-row');
第283行: 第309行:
       const dropzone = tr.querySelector('.tier-dropzone');
       const dropzone = tr.querySelector('.tier-dropzone');
       const pool = document.getElementById('character-pool');
       const pool = document.getElementById('character-pool');
      // 将角色移回角色池
       Array.from(dropzone.querySelectorAll('.avatar-frame')).forEach(function(av) {
       Array.from(dropzone.querySelectorAll('.avatar-frame')).forEach(function(av) {
         pool.appendChild(av);
         pool.appendChild(av);
第291行: 第316行:
   }
   }
   function attachColorPalette(toggle, th) {
   function attachColorPalette(toggle, th) {
    if (!toggle) return;
     const palette = document.createElement('div');
     const palette = document.createElement('div');
     palette.className = 'color-palette';
     palette.className = 'color-palette';
第302行: 第328行:
       sw.className = 'color-swatch';
       sw.className = 'color-swatch';
       sw.style.background = c;
       sw.style.background = c;
       sw.addEventListener('click', function(e) {
       sw.addEventListener('click', function() {
         th.style.background = c;
         th.style.background = c;
         th.style.color = contrastColor(c);
         th.style.color = contrastColor(c);
第336行: 第362行:
   }
   }
   function addNewRow() {
   function addNewRow() {
     const tbody = document.querySelector('#tierlist-table tbody');
     const tbody = document.querySelector('#tierlist-table tbody') || document.querySelector('#tierlist-table');
     const tr = document.createElement('tr');
     const tr = document.createElement('tr');
     tr.className = 'tier-row';
     tr.className = 'tier-row';
第342行: 第368行:
     th.className = 'tier-head';
     th.className = 'tier-head';
     th.setAttribute('data-initial-bg', '#8888ff');
     th.setAttribute('data-initial-bg', '#8888ff');
    th.setAttribute('data-initial-fg', 'white');
     th.textContent = '新行';
     th.textContent = '新行';
     const td = document.createElement('td');
     const td = document.createElement('td');
第356行: 第381行:
     buildEditableHead(th);
     buildEditableHead(th);
   }
   }
 
  // 重置功能:将所有角色移回角色池
  function resetTierList() {
    const pool = document.getElementById('character-pool');
    const allDropzones = document.querySelectorAll('.tier-dropzone:not(.pool)');
   
    allDropzones.forEach(function(zone) {
      Array.from(zone.querySelectorAll('.avatar-frame')).forEach(function(av) {
        pool.appendChild(av);
      });
    });
  }
 
   function ensureHtml2Canvas(cb) {
   function ensureHtml2Canvas(cb) {
     if (window.html2canvas) { cb(); return; }
     if (window.html2canvas) { cb(); return; }
第379行: 第391行:
     document.body.appendChild(s);
     document.body.appendChild(s);
   }
   }
    
   function downloadCanvas(canvas) {
    function done(blobOrUrl) {
      try {
        const link = document.createElement('a');
        const isBlob = blobOrUrl instanceof Blob;
        const url = isBlob ? (URL.createObjectURL(blobOrUrl)) : blobOrUrl;
        link.href = url;
        link.download = 'tierlist.png';
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        if (isBlob) setTimeout(() => URL.revokeObjectURL(url), 0);
      } catch (e) {
        const dataUrl = canvas.toDataURL('image/png');
        window.open(dataUrl, '_blank');
      }
    }
    if (canvas.toBlob) {
      canvas.toBlob(function(blob) {
        if (blob) done(blob);
        else done(canvas.toDataURL('image/png'));
      }, 'image/png');
    } else {
      done(canvas.toDataURL('image/png'));
    }
  }
  function prepareImagesForExport(container) {
    if (!container) return;
    container.querySelectorAll('img').forEach(ensureCrossOrigin);
  }
   function savePNG() {
   function savePNG() {
     const container = document.getElementById('tierlist-maker');
     const maker = document.getElementById('tierlist-maker');
     const table = document.getElementById('tierlist-table');
     const table = document.getElementById('tierlist-table');
      
     if (!table) {
     // 添加导出样式类
      alert('未找到要导出的表格。');
     container.classList.add('exporting');
      return;
      
     }
     maker.classList.add('exporting');
     prepareImagesForExport(table);
     ensureHtml2Canvas(function() {
     ensureHtml2Canvas(function() {
       // 等待样式应用
       html2canvas(table, {
      setTimeout(function() {
        backgroundColor: null,
        html2canvas(table, {
        scale: 2,
          backgroundColor: '#ffffff',
        useCORS: true,
          scale: 2,
        allowTaint: false,
          logging: false,
        imageTimeout: 15000
          useCORS: true
      }).then(function(canvas) {
        }).then(function(canvas) {
        downloadCanvas(canvas);
          canvas.toBlob(function(blob) {
      }).catch(function(err) {
            const link = document.createElement('a');
        console.error('html2canvas 失败', err);
            link.href = URL.createObjectURL(blob);
        alert('保存PNG失败:' + (err && err.message ? err.message : '未知错误'));
            link.download = 'tierlist.png';
      }).finally(function() {
            document.body.appendChild(link);
        maker.classList.remove('exporting');
            link.click();
      });
            document.body.removeChild(link);
    });
            URL.revokeObjectURL(link.href);
  }
           
  function resetAll() {
            // 移除导出样式类
    const pool = document.getElementById('character-pool');
            container.classList.remove('exporting');
    if (!pool) return;
          }, 'image/png');
    const rows = document.querySelectorAll('#tierlist-table .tier-dropzone');
        }).catch(function(err) {
    rows.forEach(function(zone) {
          console.error('保存PNG失败:', err);
      if (zone.classList.contains('pool')) return;
          container.classList.remove('exporting');
      Array.from(zone.querySelectorAll('.avatar-frame')).forEach(function(av) {
          alert('保存PNG失败,请重试。');
        pool.appendChild(av);
        });
      });
       }, 100);
       cleanupEmptyPlaceholdersIn(zone);
     });
     });
    cleanupEmptyPlaceholdersIn(pool);
   }
   }
 
 
   ready(function() {
   ready(function() {
    // Initialize table heads editable and with tools
     document.querySelectorAll('#tierlist-table .tier-head').forEach(buildEditableHead);
     document.querySelectorAll('#tierlist-table .tier-head').forEach(buildEditableHead);
    // Initialize dropzones
     document.querySelectorAll('.tier-dropzone').forEach(initDropzone);
     document.querySelectorAll('.tier-dropzone').forEach(initDropzone);
    // Initialize avatars in pool
     initAvatars(document.getElementById('character-pool'));
     initAvatars(document.getElementById('character-pool'));
    // Controls
 
     document.getElementById('add-row').addEventListener('click', addNewRow);
     var addBtn = document.getElementById('add-row');
     document.getElementById('save-png').addEventListener('click', savePNG);
    if (addBtn) addBtn.addEventListener('click', addNewRow);
     document.getElementById('reset-btn').addEventListener('click', resetTierList);
     var saveBtn = document.getElementById('save-png');
      
    if (saveBtn) saveBtn.addEventListener('click', savePNG);
    // Observe mutations to init new avatars
 
     (function insertResetButton() {
      const controls = document.querySelector('#tierlist-maker .tierlist-controls');
      if (!controls || document.getElementById('reset-roles')) return;
      const resetBtn = document.createElement('div');
      resetBtn.id = 'reset-roles';
      resetBtn.className = 'btn';
      resetBtn.textContent = '重置';
      controls.insertBefore(resetBtn, controls.querySelector('#save-png') || null);
      resetBtn.addEventListener('click', resetAll);
     })();
 
     const pool = document.getElementById('character-pool');
     const pool = document.getElementById('character-pool');
     const obs = new MutationObserver(function(muts) {
     if (pool) {
      muts.forEach(function(m) {
      const obs = new MutationObserver(function(muts) {
        if (m.addedNodes && m.addedNodes.length) {
        muts.forEach(function(m) {
          m.addedNodes.forEach(function(n) {
          if (m.addedNodes && m.addedNodes.length) {
            if (n.nodeType === 1) {
            m.addedNodes.forEach(function(n) {
              if (n.classList.contains('avatar-frame')) makeDraggable(n);
              if (n.nodeType === 1) {
              n.querySelectorAll && n.querySelectorAll('.avatar-frame').forEach(makeDraggable);
                if (n.classList.contains('avatar-frame')) {
             }
                  makeDraggable(n);
           });
                }
         }
                n.querySelectorAll && n.querySelectorAll('.avatar-frame').forEach(makeDraggable);
                // 去除初始即为空的无用占位
                if (pool.contains(n)) cleanupEmptyPlaceholdersIn(pool);
              }
             });
           }
         });
       });
       });
    });
      obs.observe(pool, { childList: true, subtree: true });
    obs.observe(pool, { childList: true, subtree: true });
    }
   });
   });
})();
})();
</script>
</script>
</includeonly>
</includeonly>

2025年10月21日 (二) 09:09的版本