// Download state
let allDownloads = [];
let collapsedGroups = new Set();
let isClearing = false;
/**
* Load all downloads
*/
async function loadDownloads() {
// Skip refresh if currently clearing downloads to avoid conflicts
if (isClearing) {
return;
}
try {
const data = await getDownloads();
allDownloads = data.downloads;
updateStats();
filterDownloads();
} catch (error) {
console.error('Failed to load downloads:', error);
}
}
/**
* Update download statistics display
*/
function updateStats() {
const stats = {
total: allDownloads.length,
downloading: allDownloads.filter(d => d.status === 'downloading').length,
paused: allDownloads.filter(d => d.status === 'paused').length,
completed: allDownloads.filter(d => d.status === 'completed').length,
cancelled: allDownloads.filter(d => d.status === 'cancelled').length,
failed: allDownloads.filter(d => d.status === 'failed').length
};
const statsHtml = `
Total: ${stats.total}
${stats.downloading > 0 ? `En cours: ${stats.downloading}
` : ''}
${stats.paused > 0 ? `En pause: ${stats.paused}
` : ''}
${stats.completed > 0 ? `Terminés: ${stats.completed}
` : ''}
${stats.cancelled > 0 ? `Annulés: ${stats.cancelled}
` : ''}
${stats.failed > 0 ? `Échoués: ${stats.failed}
` : ''}
`;
document.getElementById('downloadsStats').innerHTML = statsHtml;
}
/**
* Filter and sort downloads
*/
function filterDownloads() {
const statusFilter = document.getElementById('statusFilter').value;
const sortBy = document.getElementById('sortBy').value;
const groupBy = document.getElementById('groupBy').value;
const searchTerm = document.getElementById('searchDownloads').value.toLowerCase();
// Filter by status and search
let filtered = allDownloads.filter(dl => {
const matchesStatus = statusFilter === 'all' || dl.status === statusFilter;
const matchesSearch = !searchTerm ||
dl.filename.toLowerCase().includes(searchTerm) ||
(dl.url && dl.url.toLowerCase().includes(searchTerm));
return matchesStatus && matchesSearch;
});
// Sort
filtered.sort((a, b) => {
switch (sortBy) {
case 'date_asc':
return new Date(a.created_at) - new Date(b.created_at);
case 'name':
return a.filename.localeCompare(b.filename);
case 'name_desc':
return b.filename.localeCompare(a.filename);
case 'size':
return (b.total_bytes || 0) - (a.total_bytes || 0);
case 'date':
default:
return new Date(b.created_at) - new Date(a.created_at);
}
});
// Apply grouping
displayDownloads(filtered, groupBy);
}
/**
* Group downloads by criteria
*/
function groupDownloads(downloads, groupBy) {
const groups = {};
downloads.forEach(dl => {
let key = 'Ungrouped';
switch (groupBy) {
case 'series':
key = extractSeriesName(dl.filename);
break;
case 'status':
key = translateStatus(dl.status);
break;
case 'day':
key = getDayString(dl.created_at);
break;
default:
key = 'Tous';
}
if (!groups[key]) {
groups[key] = [];
}
groups[key].push(dl);
});
return groups;
}
/**
* Display downloads (flat or grouped)
*/
function displayDownloads(downloads, groupBy = 'none') {
const container = document.getElementById('downloadsList');
if (downloads.length === 0) {
container.innerHTML = `
Aucun téléchargement trouvé
`;
return;
}
// Group downloads if needed
if (groupBy && groupBy !== 'none') {
const groups = groupDownloads(downloads, groupBy);
const groupNames = Object.keys(groups);
// Sort group names
groupNames.sort((a, b) => a.localeCompare(b));
// Display grouped downloads
let html = '';
groupNames.forEach((groupName, index) => {
const groupDownloads = groups[groupName];
const groupId = `group-${index}`;
const isCollapsed = collapsedGroups.has(groupId);
const collapsedClass = isCollapsed ? 'collapsed' : '';
const displayStyle = isCollapsed ? 'display: none;' : '';
html += `
${groupDownloads.map(dl => renderDownloadItem(dl)).join('')}
`;
});
container.innerHTML = html;
} else {
// Display flat list
container.innerHTML = downloads.map(dl => renderDownloadItem(dl)).join('');
}
}
/**
* Render a single download item
*/
function renderDownloadItem(dl) {
return `
${formatBytes(dl.downloaded_bytes)}${dl.total_bytes ? ' / ' + formatBytes(dl.total_bytes) : ''}
${dl.speed > 0 ? formatSpeed(dl.speed) : ''}
${renderDownloadActions(dl)}
${dl.error ? `
${escapeHtml(dl.error)}
` : ''}
`;
}
/**
* Render download action buttons based on status
*/
function renderDownloadActions(dl) {
switch (dl.status) {
case 'downloading':
return `
`;
case 'paused':
return `
`;
case 'completed':
return `
`;
case 'failed':
default:
return `
`;
}
}
/**
* Toggle group collapse/expand
*/
function toggleGroup(groupId) {
const items = document.getElementById(groupId);
const header = items.previousElementSibling;
if (!items || !header) {
console.error('Could not find group elements');
return;
}
const isCollapsed = collapsedGroups.has(groupId);
if (isCollapsed) {
items.style.display = 'flex';
header.classList.remove('collapsed');
collapsedGroups.delete(groupId);
} else {
items.style.display = 'none';
header.classList.add('collapsed');
collapsedGroups.add(groupId);
}
}
/**
* Handle pause button click
*/
async function handlePause(id) {
try {
await pauseDownload(id);
loadDownloads();
} catch (error) {
console.error('Pause error:', error);
alert('Erreur lors de la mise en pause');
}
}
/**
* Handle resume button click
*/
async function handleResume(id) {
try {
await resumeDownload(id);
loadDownloads();
} catch (error) {
console.error('Resume error:', error);
alert('Erreur lors de la reprise');
}
}
/**
* Handle cancel/delete button click
*/
async function handleCancel(id) {
if (!confirm('Êtes-vous sûr ?')) {
return;
}
try {
await cancelDownload(id);
loadDownloads();
} catch (error) {
console.error('Cancel error:', error);
alert('Erreur lors de la suppression');
}
}
/**
* Clear unwanted downloads
*/
async function clearCompleted() {
const unwanted = allDownloads.filter(dl =>
dl.status === 'cancelled' ||
dl.status === 'failed' ||
dl.status === 'deleted'
);
if (unwanted.length === 0) {
alert('Aucun téléchargement à supprimer');
return;
}
// Count by status
const byStatus = unwanted.reduce((acc, dl) => {
acc[dl.status] = (acc[dl.status] || 0) + 1;
return acc;
}, {});
let message = 'Supprimer ';
if (byStatus.cancelled) message += `${byStatus.cancelled} annulé(s) `;
if (byStatus.failed) message += `${byStatus.failed} échoué(s) `;
if (byStatus.deleted) message += `${byStatus.deleted} supprimé(s) `;
message += '?';
if (!confirm(message)) {
return;
}
// Set flag to prevent auto-refresh conflicts
isClearing = true;
try {
// Delete all in parallel (much faster)
await Promise.all(unwanted.map(dl => cancelDownload(dl.id)));
} catch (error) {
console.error('Error deleting downloads:', error);
alert('Erreur lors de la suppression');
} finally {
// Clear flag and refresh
isClearing = false;
loadDownloads();
}
}
/**
* Download file to user's computer
*/
function downloadFile(id) {
window.open(`${API_BASE}/download/${id}/file`, '_blank');
}
/**
* Watch video in player
*/
function watchVideo(id) {
window.open(`/player/${id}`, '_blank');
}