perf: Optimize list rendering with partial DOM updates - Preserve existing card DOM elements (no image flicker) - Update only changed fields (progress, status, speed) - Add/remove cards only when necessary - Reorder cards to match server order

This commit is contained in:
2026-01-08 20:07:19 +09:00
parent 4baf23d8ad
commit cae3c9b269
2 changed files with 87 additions and 32 deletions

View File

@@ -1,6 +1,6 @@
title: "GDM" title: "GDM"
package_name: gommi_downloader_manager package_name: gommi_downloader_manager
version: '0.2.22' version: '0.2.23'
description: FlaskFarm 범용 다운로더 큐 - YouTube, 애니24, 링크애니, Anilife 지원 description: FlaskFarm 범용 다운로더 큐 - YouTube, 애니24, 링크애니, Anilife 지원
developer: projectdx developer: projectdx
home: https://gitea.yommi.duckdns.org/projectdx/gommi_downloader_manager home: https://gitea.yommi.duckdns.org/projectdx/gommi_downloader_manager

View File

@@ -798,18 +798,7 @@
const container = document.getElementById('download_list'); const container = document.getElementById('download_list');
if (!container) return; if (!container) return;
// Save expanded card IDs before re-rendering // Handle empty state
const expandedIds = [];
container.querySelectorAll('.dl-card.expanded').forEach(function(card) {
expandedIds.push(card.id);
});
// Save checked checkbox IDs before re-rendering
const checkedIds = [];
container.querySelectorAll('.dl-select-checkbox:checked').forEach(function(cb) {
checkedIds.push(cb.dataset.id);
});
if (!items || items.length === 0) { if (!items || items.length === 0) {
container.innerHTML = ` container.innerHTML = `
<div class="empty-state"> <div class="empty-state">
@@ -819,30 +808,96 @@
return; return;
} }
let html = ''; // Build a map of incoming items by ID
items.forEach(function(item) { const itemMap = {};
html += createDownloadCard(item); items.forEach(item => itemMap[`card_${item.id}`] = item);
});
container.innerHTML = html;
// Restore expanded state // Get existing cards
expandedIds.forEach(function(cardId) { const existingCards = container.querySelectorAll('.dl-card');
const card = document.getElementById(cardId); const existingIds = new Set();
if (card) card.classList.add('expanded');
});
// Restore checkbox state // Update existing cards or mark for removal
checkedIds.forEach(function(id) { existingCards.forEach(card => {
const cb = container.querySelector('.dl-select-checkbox[data-id="' + id + '"]'); const cardId = card.id;
if (cb) { existingIds.add(cardId);
cb.checked = true;
const card = cb.closest('.dl-card'); if (itemMap[cardId]) {
if (card) card.classList.add('selected'); // Card exists - do partial update (progress, speed, status only)
const item = itemMap[cardId];
updateCardInPlace(card, item);
} else {
// Card no longer in list - remove it
card.remove();
} }
}); });
// Update selected count // Add new cards that don't exist yet
updateSelectedCount(); items.forEach(item => {
const cardId = `card_${item.id}`;
if (!existingIds.has(cardId)) {
const tempDiv = document.createElement('div');
tempDiv.innerHTML = createDownloadCard(item);
const newCard = tempDiv.firstElementChild;
container.appendChild(newCard);
}
});
// Reorder cards to match server order
items.forEach((item, index) => {
const card = document.getElementById(`card_${item.id}`);
if (card && card !== container.children[index]) {
container.insertBefore(card, container.children[index]);
}
});
}
// In-place update without DOM replacement (preserves images)
function updateCardInPlace(card, item) {
const percent = (item.progress && !isNaN(item.progress)) ? item.progress : 0;
const status = item.status || 'pending';
const statusClass = `status-${status}`;
// Update progress bar
const progressFill = card.querySelector('.dl-progress-fill');
if (progressFill) progressFill.style.width = `${percent}%`;
// Update percentage text
const percentText = card.querySelector('.dl-percent-big');
if (percentText) percentText.innerHTML = `${percent}<small>%</small>`;
// Update speed
const speedText = card.querySelector('.dl-speed-text');
if (speedText) speedText.textContent = item.speed || '';
// Update status pill
const statusPill = card.querySelector('.dl-status-pill');
if (statusPill) {
statusPill.className = `dl-status-pill ${statusClass}`;
const statusTextNode = statusPill.childNodes[statusPill.childNodes.length - 1];
if (statusTextNode) {
statusTextNode.textContent = status.charAt(0).toUpperCase() + status.slice(1);
}
}
// Update card background class
card.className = card.className.replace(/status-\w+/g, '').trim();
card.classList.add(statusClass);
if (card.classList.contains('expanded')) card.classList.add('expanded');
if (card.classList.contains('selected')) card.classList.add('selected');
// Update title if changed
const titleEl = card.querySelector('.dl-filename');
const newTitle = item.title || item.filename || item.url || 'No Title';
if (titleEl && titleEl.textContent !== newTitle) {
titleEl.textContent = newTitle;
titleEl.title = newTitle;
}
// Update thumbnail only if src changed
const thumbEl = card.querySelector('.dl-thumb');
if (thumbEl && item.thumbnail && thumbEl.src !== item.thumbnail) {
thumbEl.src = item.thumbnail;
}
} }
function createDownloadCard(item) { function createDownloadCard(item) {