/**
* 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 '' +
'' +
'
' +
'
' +
'
' +
'' + (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) + '
' +
'' +
'
';
}
/**
* 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 '' +
'' +
'
';
}
/**
* 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 '' +
'' +
'
';
}
/**
* 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
};
})();