// 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 += `
${escapeHtml(groupName)}
${groupDownloads.length}
${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 `
${escapeHtml(dl.filename)}
${translateStatus(dl.status)}
${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'); }