diff --git a/templates/index.html b/templates/index.html index c80bdff..6e4667c 100644 --- a/templates/index.html +++ b/templates/index.html @@ -132,12 +132,144 @@ color: #000; } + .btn-secondary { + background: rgba(255, 255, 255, 0.1); + color: #fff; + } + + .btn-secondary:hover { + background: rgba(255, 255, 255, 0.2); + } + + .section-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + margin-top: 40px; + } + + .section-header h2 { + font-size: 1.8em; + margin: 0; + background: linear-gradient(45deg, #00d9ff, #00ff88); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + } + + .downloads-stats { + display: flex; + gap: 15px; + font-size: 0.85em; + } + + .stat-item { + background: rgba(255, 255, 255, 0.05); + padding: 5px 12px; + border-radius: 15px; + border: 1px solid rgba(255, 255, 255, 0.1); + } + + .stat-count { + font-weight: bold; + color: #00d9ff; + } + + .downloads-controls { + display: flex; + flex-wrap: wrap; + gap: 12px; + margin-bottom: 20px; + background: rgba(255, 255, 255, 0.03); + padding: 15px; + border-radius: 10px; + border: 1px solid rgba(255, 255, 255, 0.08); + } + + .filter-group { + display: flex; + align-items: center; + gap: 8px; + } + + .filter-group label { + font-size: 0.85em; + color: #aaa; + white-space: nowrap; + } + + .filter-group select, + .filter-group input { + padding: 8px 12px; + border: 1px solid rgba(255, 255, 255, 0.15); + border-radius: 6px; + background: rgba(0, 0, 0, 0.3); + color: #fff; + font-size: 13px; + min-width: 120px; + } + + .filter-group select:focus, + .filter-group input:focus { + outline: none; + border-color: #00d9ff; + } + + .search-group input { + min-width: 200px; + } + + .actions-group { + margin-left: auto; + } + .downloads-list { display: flex; flex-direction: column; gap: 15px; } + .downloads-group { + margin-bottom: 20px; + } + + .downloads-group-header { + background: rgba(255, 255, 255, 0.08); + padding: 12px 18px; + border-radius: 8px; + margin-bottom: 12px; + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; + user-select: none; + transition: all 0.3s; + } + + .downloads-group-header:hover { + background: rgba(255, 255, 255, 0.12); + } + + .downloads-group-title { + font-weight: 600; + font-size: 1.05em; + color: #00d9ff; + } + + .downloads-group-count { + background: rgba(0, 217, 255, 0.2); + padding: 4px 10px; + border-radius: 12px; + font-size: 0.85em; + color: #00d9ff; + } + + .downloads-group-items { + display: flex; + flex-direction: column; + gap: 12px; + } + .download-item { background: rgba(255, 255, 255, 0.05); border-radius: 10px; @@ -620,6 +752,61 @@ + +
+

Téléchargements

+
+
+ + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ +
+
+
@@ -635,6 +822,140 @@ let autoRefreshInterval; let currentAnimeUrl = ''; let searchResultsCache = {}; + let allDownloads = []; // Store all downloads for filtering + + // Extract series name from filename (for grouping) + function extractSeriesName(filename) { + // Common patterns: + // - "Naruto Shippuden - Episode 123.mp4" + // - "[One Piece] Ep 456.mkv" + // - "Attack on Titan S03E09.mp4" + // - "Anime Name - S01E05.mp4" + + let name = filename; + + // Remove file extension + name = name.replace(/\.[^/.]+$/, ''); + + // Remove episode numbers and patterns + name = name + .replace(/[-_ ]?(E(?:p)?|Episode|Épisode|Saison|Season)[-_: ]?\d+/gi, '') + .replace(/[-_ ]?S\d{2}E\d{2}/gi, '') + .replace(/\[.*?\]/g, '') + .replace(/\(.*\)/g, '') + .replace(/[-_ ]?\d{3,4}p/gi, '') + .replace(/[-_ ]?(VOSTFR|VF|MULTI)/gi, '') + .trim(); + + // If nothing left, use original filename + if (!name) { + return filename.replace(/\.[^/.]+$/, ''); + } + + return name; + } + + // Get day string for grouping + function getDayString(dateString) { + const date = new Date(dateString); + const today = new Date(); + const yesterday = new Date(today); + yesterday.setDate(yesterday.getDate() - 1); + + if (date.toDateString() === today.toDateString()) { + return "Aujourd'hui"; + } else if (date.toDateString() === yesterday.toDateString()) { + return "Hier"; + } else { + return date.toLocaleDateString('fr-FR', { weekday: 'long', day: 'numeric', month: 'short' }); + } + } + + // 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); + } + }); + + displayDownloads(filtered, groupBy); + } + + // Group downloads + 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; + } + + // Clear completed downloads + async function clearCompleted() { + const completed = allDownloads.filter(dl => dl.status === 'completed'); + + if (completed.length === 0) { + alert('Aucun téléchargement terminé à supprimer'); + return; + } + + if (!confirm(`Supprimer ${completed.length} téléchargement(s) terminé(s) ?`)) { + return; + } + + for (const dl of completed) { + await fetch(`${API_BASE}/download/${dl.id}`, { method: 'DELETE' }); + } + + loadDownloads(); + } // Search Anime across all providers async function searchAnime() { @@ -1149,10 +1470,30 @@ async function loadDownloads() { const response = await fetch(`${API_BASE}/downloads`); const data = await response.json(); - displayDownloads(data.downloads); + allDownloads = data.downloads; // Store all downloads + updateStats(); // Update statistics + filterDownloads(); // Apply current filters } - function displayDownloads(downloads) { + 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, + failed: allDownloads.filter(d => d.status === 'failed').length + }; + + document.getElementById('downloadsStats').innerHTML = ` +
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.failed > 0 ? `
Échoués: ${stats.failed}
` : ''} + `; + } + + function displayDownloads(downloads, groupBy = 'none') { const container = document.getElementById('downloadsList'); if (downloads.length === 0) { @@ -1161,13 +1502,40 @@ -

Aucun téléchargement pour le moment

+

Aucun téléchargement trouvé

`; return; } - container.innerHTML = downloads.map(dl => ` + // Group downloads if needed + const groups = groupBy !== 'none' ? groupDownloads(downloads, groupBy) : null; + + if (groups) { + // Display grouped downloads + let html = ''; + for (const [groupName, groupDownloads] of Object.entries(groups)) { + 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(''); + } + } + + function renderDownloadItem(dl) { + return `
${escapeHtml(dl.filename)}
@@ -1242,7 +1610,13 @@
${dl.error ? `
${escapeHtml(dl.error)}
` : ''}
- `).join(''); + `; + } + + function toggleGroup(header) { + const items = header.nextElementSibling; + items.style.display = items.style.display === 'none' ? 'flex' : 'none'; + header.classList.toggle('collapsed'); } async function pauseDownload(id) {