/** * AniWorld - Queue Renderer Module * * Handles rendering of queue items and statistics. * * Dependencies: constants.js, ui-utils.js */ var AniWorld = window.AniWorld || {}; AniWorld.QueueRenderer = (function() { 'use strict'; /** * Update full queue display * @param {Object} data - Queue data */ function updateQueueDisplay(data) { // Update statistics updateStatistics(data.statistics, data); // Update active downloads renderActiveDownloads(data.active_downloads || []); // Update pending queue renderPendingQueue(data.pending_queue || []); // Update completed downloads renderCompletedDownloads(data.completed_downloads || []); // Update failed downloads renderFailedDownloads(data.failed_downloads || []); // Update button states updateButtonStates(data); } /** * Update statistics display * @param {Object} stats - Statistics object * @param {Object} data - Full queue data */ function updateStatistics(stats, data) { const statistics = stats || {}; document.getElementById('total-items').textContent = statistics.total_items || 0; document.getElementById('pending-items').textContent = (data.pending_queue || []).length; document.getElementById('completed-items').textContent = statistics.completed_items || 0; document.getElementById('failed-items').textContent = statistics.failed_items || 0; // Update section counts document.getElementById('queue-count').textContent = (data.pending_queue || []).length; document.getElementById('completed-count').textContent = statistics.completed_items || 0; document.getElementById('failed-count').textContent = statistics.failed_items || 0; document.getElementById('current-speed').textContent = statistics.current_speed || '0 MB/s'; document.getElementById('average-speed').textContent = statistics.average_speed || '0 MB/s'; // Format ETA const etaElement = document.getElementById('eta-time'); if (statistics.eta) { const eta = new Date(statistics.eta); const now = new Date(); const diffMs = eta - now; if (diffMs > 0) { const hours = Math.floor(diffMs / (1000 * 60 * 60)); const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60)); etaElement.textContent = hours + 'h ' + minutes + 'm'; } else { etaElement.textContent = 'Calculating...'; } } else { etaElement.textContent = '--:--'; } } /** * Render active downloads * @param {Array} downloads - Active downloads array */ function renderActiveDownloads(downloads) { const container = document.getElementById('active-downloads'); if (downloads.length === 0) { container.innerHTML = '
' + '' + '

No active downloads

' + '
'; return; } container.innerHTML = downloads.map(function(download) { return createActiveDownloadCard(download); }).join(''); } /** * Create active download card HTML * @param {Object} download - Download item * @returns {string} HTML string */ function createActiveDownloadCard(download) { const progress = download.progress || {}; const progressPercent = progress.percent || 0; const speed = progress.speed_mbps ? progress.speed_mbps.toFixed(1) + ' MB/s' : '0 MB/s'; const episodeNum = String(download.episode.episode).padStart(2, '0'); const episodeTitle = download.episode.title || 'Episode ' + download.episode.episode; return '
' + '
' + '
' + '

' + AniWorld.UI.escapeHtml(download.serie_name) + '

' + '

' + AniWorld.UI.escapeHtml(download.episode.season) + 'x' + episodeNum + ' - ' + AniWorld.UI.escapeHtml(episodeTitle) + '

' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '' + (progressPercent > 0 ? progressPercent.toFixed(1) + '%' : 'Starting...') + '' + '' + speed + '' + '
' + '
' + '
'; } /** * Render pending queue * @param {Array} queue - Pending queue array */ function renderPendingQueue(queue) { const container = document.getElementById('pending-queue'); if (queue.length === 0) { container.innerHTML = '
' + '' + '

No items in queue

' + 'Add episodes from the main page to start downloading' + '
'; return; } container.innerHTML = queue.map(function(item, index) { return createPendingQueueCard(item, index); }).join(''); } /** * Create pending queue card HTML * @param {Object} download - Download item * @param {number} index - Queue position * @returns {string} HTML string */ function createPendingQueueCard(download, index) { const addedAt = new Date(download.added_at).toLocaleString(); const episodeNum = String(download.episode.episode).padStart(2, '0'); const episodeTitle = download.episode.title || 'Episode ' + download.episode.episode; return '
' + '
' + (index + 1) + '
' + '
' + '
' + '

' + AniWorld.UI.escapeHtml(download.serie_name) + '

' + '

' + AniWorld.UI.escapeHtml(download.episode.season) + 'x' + episodeNum + ' - ' + AniWorld.UI.escapeHtml(episodeTitle) + '

' + 'Added: ' + addedAt + '' + '
' + '
' + '' + '
' + '
' + '
'; } /** * Render completed downloads * @param {Array} downloads - Completed downloads array */ function renderCompletedDownloads(downloads) { const container = document.getElementById('completed-downloads'); if (downloads.length === 0) { container.innerHTML = '
' + '' + '

No completed downloads

' + '
'; return; } container.innerHTML = downloads.map(function(download) { return createCompletedDownloadCard(download); }).join(''); } /** * Create completed download card HTML * @param {Object} download - Download item * @returns {string} HTML string */ function createCompletedDownloadCard(download) { const completedAt = new Date(download.completed_at).toLocaleString(); const duration = AniWorld.UI.calculateDuration(download.started_at, download.completed_at); const episodeNum = String(download.episode.episode).padStart(2, '0'); const episodeTitle = download.episode.title || 'Episode ' + download.episode.episode; return '
' + '
' + '
' + '

' + AniWorld.UI.escapeHtml(download.serie_name) + '

' + '

' + AniWorld.UI.escapeHtml(download.episode.season) + 'x' + episodeNum + ' - ' + AniWorld.UI.escapeHtml(episodeTitle) + '

' + 'Completed: ' + completedAt + ' (' + duration + ')' + '
' + '
' + '' + '
' + '
' + '
'; } /** * Render failed downloads * @param {Array} downloads - Failed downloads array */ function renderFailedDownloads(downloads) { const container = document.getElementById('failed-downloads'); if (downloads.length === 0) { container.innerHTML = '
' + '' + '

No failed downloads

' + '
'; return; } container.innerHTML = downloads.map(function(download) { return createFailedDownloadCard(download); }).join(''); } /** * Create failed download card HTML * @param {Object} download - Download item * @returns {string} HTML string */ function createFailedDownloadCard(download) { const failedAt = new Date(download.completed_at).toLocaleString(); const retryCount = download.retry_count || 0; const episodeNum = String(download.episode.episode).padStart(2, '0'); const episodeTitle = download.episode.title || 'Episode ' + download.episode.episode; return '
' + '
' + '
' + '

' + AniWorld.UI.escapeHtml(download.serie_name) + '

' + '

' + AniWorld.UI.escapeHtml(download.episode.season) + 'x' + episodeNum + ' - ' + AniWorld.UI.escapeHtml(episodeTitle) + '

' + 'Failed: ' + failedAt + (retryCount > 0 ? ' (Retry ' + retryCount + ')' : '') + '' + (download.error ? '' + AniWorld.UI.escapeHtml(download.error) + '' : '') + '
' + '
' + '' + '' + '
' + '
' + '
'; } /** * Update button states based on queue data * @param {Object} data - Queue data */ function updateButtonStates(data) { const hasActive = (data.active_downloads || []).length > 0; const hasPending = (data.pending_queue || []).length > 0; const hasFailed = (data.failed_downloads || []).length > 0; const hasCompleted = (data.completed_downloads || []).length > 0; console.log('Button states update:', { hasPending: hasPending, pendingCount: (data.pending_queue || []).length, hasActive: hasActive, hasFailed: hasFailed, hasCompleted: hasCompleted }); // Enable start button only if there are pending items and no active downloads document.getElementById('start-queue-btn').disabled = !hasPending || hasActive; // Show/hide start/stop buttons based on whether downloads are active if (hasActive) { document.getElementById('start-queue-btn').style.display = 'none'; document.getElementById('stop-queue-btn').style.display = 'inline-flex'; document.getElementById('stop-queue-btn').disabled = false; } else { document.getElementById('stop-queue-btn').style.display = 'none'; document.getElementById('start-queue-btn').style.display = 'inline-flex'; } document.getElementById('retry-all-btn').disabled = !hasFailed; document.getElementById('clear-completed-btn').disabled = !hasCompleted; document.getElementById('clear-failed-btn').disabled = !hasFailed; // Update clear pending button if it exists const clearPendingBtn = document.getElementById('clear-pending-btn'); if (clearPendingBtn) { clearPendingBtn.disabled = !hasPending; } } // Public API return { updateQueueDisplay: updateQueueDisplay, updateStatistics: updateStatistics, renderActiveDownloads: renderActiveDownloads, renderPendingQueue: renderPendingQueue, renderCompletedDownloads: renderCompletedDownloads, renderFailedDownloads: renderFailedDownloads, updateButtonStates: updateButtonStates }; })();