From 34057638ca2c389439a52777bb67f15aad524d18 Mon Sep 17 00:00:00 2001 From: MasutaYunikon Date: Sat, 11 Apr 2026 12:56:19 +0200 Subject: [PATCH] fix: formatage --- src/views/DownloadsView.vue | 330 ++++++++++++++++++------------------ 1 file changed, 167 insertions(+), 163 deletions(-) diff --git a/src/views/DownloadsView.vue b/src/views/DownloadsView.vue index dd526d9..ff0288b 100644 --- a/src/views/DownloadsView.vue +++ b/src/views/DownloadsView.vue @@ -133,7 +133,6 @@
- -
@@ -225,29 +223,24 @@
- ↓ {{ formatSpeed(t.speed) }} ETA {{ formatEta(t.eta) }} - État non remonté par le client
+
@@ -267,11 +260,11 @@ - {{ t.organizeState === 'done' ? `Importé (${t.organizeProgress?.done}/${t.organizeProgress?.total})` : + {{ t.organizeState === 'done' ? `Importé (${t.organizeProgress?.done}/${t.organizeProgress?.total})` : t.organizeState === 'partial' ? `En cours (${t.organizeProgress?.done}/${t.organizeProgress?.total})` : 'Non importé' }} @@ -286,18 +279,20 @@ -
+
- +
- -
@@ -337,24 +339,27 @@ import { useToast } from '@/composables/useToast' const { add: toast } = useToast() -const torrents = ref([]) -const loading = ref(true) -const polling = ref(true) -const noClients = ref(false) -const importing = ref>({}) -const importingAll = ref(false) -const activeTab = ref<'active' | 'done'>('active') -const hideImported = ref(false) -const seedingOnly = ref(false) -const search = ref('') -const sortOpen = ref(false) -const colOpen = ref(false) -const activeSort = ref<'none' | 'name' | 'size' | 'progress' | 'state' | 'ratio'>('none') -const sortDir = ref<'asc' | 'desc'>('asc') -const sortRef = ref(null) -const colRef = ref(null) +// ─── State ──────────────────────────────────────────────────── +const torrents = ref([]) +const loading = ref(true) +const polling = ref(true) +const noClients = ref(false) +const importing = ref>({}) +const importingAll = ref(false) +const confirmDelete = ref(null) +const deleting = ref>({}) +const deleteWithFiles = ref>({}) +const activeTab = ref<'active' | 'done'>('active') +const hideImported = ref(false) +const seedingOnly = ref(false) +const search = ref('') +const sortOpen = ref(false) +const colOpen = ref(false) +const activeSort = ref<'none' | 'name' | 'size' | 'progress' | 'state' | 'ratio'>('none') +const sortDir = ref<'asc' | 'desc'>('asc') +const sortRef = ref(null) +const colRef = ref(null) -// Colonnes visibles const columns = ref({ client : true, size : true, @@ -371,29 +376,20 @@ const columnOptions = [ { key: 'upspeed', label: 'Vitesse upload' }, ] +const sortOptions = [ + { label: 'Nom', value: 'name' }, + { label: 'Taille', value: 'size' }, + { label: 'Progression', value: 'progress' }, + { label: 'État', value: 'state' }, + { label: 'Ratio', value: 'ratio' }, +] + onClickOutside(sortRef, () => { sortOpen.value = false }) onClickOutside(colRef, () => { colOpen.value = false }) -const sortOptions = [ - { label: 'Nom', value: 'name' }, - { label: 'Taille', value: 'size' }, - { label: 'Progression', value: 'progress' }, - { label: 'État', value: 'state' }, - { label: 'Ratio', value: 'ratio' }, -] - -function setSort(val: typeof activeSort.value) { - if (activeSort.value === val) { - sortDir.value = sortDir.value === 'asc' ? 'desc' : 'asc' - } else { - activeSort.value = val - sortDir.value = 'asc' - } - sortOpen.value = false -} - let pollInterval: ReturnType | null = null +// ─── Computed ───────────────────────────────────────────────── const activeTorrents = computed(() => torrents.value.filter(t => ['downloading', 'paused', 'checking', 'error'].includes(t.state)) ) @@ -403,9 +399,7 @@ const doneTorrents = computed(() => ) const visibleTorrents = computed(() => { - let list = activeTab.value === 'active' - ? activeTorrents.value - : doneTorrents.value + let list = activeTab.value === 'active' ? activeTorrents.value : doneTorrents.value if (activeTab.value === 'done') { if (seedingOnly.value) list = list.filter(t => t.state === 'seeding') @@ -441,113 +435,21 @@ const stats = computed(() => [ { label: 'Erreurs', value: torrents.value.filter(t => t.state === 'error').length }, ]) +// ─── Helpers ────────────────────────────────────────────────── +function setSort(val: typeof activeSort.value) { + if (activeSort.value === val) { + sortDir.value = sortDir.value === 'asc' ? 'desc' : 'asc' + } else { + activeSort.value = val + sortDir.value = 'asc' + } + sortOpen.value = false +} + function clampedProgress(t: any): number { return Math.min(100, Math.max(0, t.progress ?? 0)) } -async function fetchTorrents() { - try { - const res = await fetch('/api/downloads', { credentials: 'include' }) - if (res.status === 503) { noClients.value = true; return } - if (!res.ok) return - torrents.value = await res.json() - noClients.value = false - } catch { - } finally { - loading.value = false - } -} - -function startPolling() { - fetchTorrents() - pollInterval = setInterval(fetchTorrents, 60_000) -} - -function stopPolling() { - if (pollInterval) { clearInterval(pollInterval); pollInterval = null } -} - -function togglePolling() { - polling.value = !polling.value - polling.value ? startPolling() : stopPolling() -} - -onMounted(startPolling) -onUnmounted(stopPolling) - -const confirmDelete = ref(null) -const deleting = ref>({}) -const deleteWithFiles = ref>({}) - -async function deleteTorrent(torrent: any) { - deleting.value[torrent.hash] = true - try { - const withFiles = deleteWithFiles.value[torrent.hash] ?? false - const res = await fetch(`/api/torrent/${torrent.hash}?deleteFiles=${withFiles}`, { - method : 'DELETE', - credentials: 'include', - }) - if (res.ok) { - toast(`"${torrent.name}" supprimé ✓`, 'success') - confirmDelete.value = null - await fetchTorrents() - } else { - toast('Erreur lors de la suppression', 'error') - } - } catch { - toast('Impossible de contacter le serveur', 'error') - } finally { - deleting.value[torrent.hash] = false - } -} -importing.value[torrent.hash] = true -try { - const res = await fetch('/api/organize', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - credentials: 'include', - body: JSON.stringify({ hash: torrent.hash, save_path: torrent.save_path, name: torrent.name }), - }) - if (res.ok) { - toast(`${torrent.name} importé ✓`, 'success') - await fetchTorrents() - } else { - const { error } = await res.json() - toast(error ?? "Erreur lors de l'import", 'error') - } -} catch { - toast('Impossible de contacter le serveur', 'error') -} finally { - importing.value[torrent.hash] = false -} -} - -async function importAll() { - const toImport = doneTorrents.value.filter(t => t.organizeState !== 'done') - if (toImport.length === 0) { toast('Tous les torrents sont déjà importés', 'success'); return } - - importingAll.value = true - let done = 0, errors = 0 - - for (const t of toImport) { - try { - const res = await fetch('/api/organize', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - credentials: 'include', - body: JSON.stringify({ hash: t.hash, save_path: t.save_path, name: t.name }), - }) - res.ok ? done++ : errors++ - } catch { errors++ } - } - - importingAll.value = false - await fetchTorrents() - errors === 0 - ? toast(`${done} torrent(s) importé(s) ✓`, 'success') - : toast(`${done} OK, ${errors} erreur(s)`, 'error') -} - function stateBadge(state: string) { const map: Record = { downloading: { label: 'Téléchargement', class: 'border-accent/40 text-accent' }, @@ -578,4 +480,106 @@ function formatEta(seconds: number): string { if (m > 0) return `${m}m${s.toString().padStart(2, '0')}s` return `${s}s` } + +// ─── Actions ────────────────────────────────────────────────── +async function fetchTorrents() { + try { + const res = await fetch('/api/downloads', { credentials: 'include' }) + if (res.status === 503) { noClients.value = true; return } + if (!res.ok) return + torrents.value = await res.json() + noClients.value = false + } catch { + } finally { + loading.value = false + } +} + +function startPolling() { + fetchTorrents() + pollInterval = setInterval(fetchTorrents, 60_000) +} + +function stopPolling() { + if (pollInterval) { clearInterval(pollInterval); pollInterval = null } +} + +function togglePolling() { + polling.value = !polling.value + polling.value ? startPolling() : stopPolling() +} + +async function importTorrent(torrent: any) { + importing.value[torrent.hash] = true + try { + const res = await fetch('/api/organize', { + method : 'POST', + headers : { 'Content-Type': 'application/json' }, + credentials: 'include', + body : JSON.stringify({ hash: torrent.hash, save_path: torrent.save_path, name: torrent.name }), + }) + if (res.ok) { + toast(`${torrent.name} importé ✓`, 'success') + await fetchTorrents() + } else { + const { error } = await res.json() + toast(error ?? "Erreur lors de l'import", 'error') + } + } catch { + toast('Impossible de contacter le serveur', 'error') + } finally { + importing.value[torrent.hash] = false + } +} + +async function importAll() { + const toImport = doneTorrents.value.filter(t => t.organizeState !== 'done') + if (toImport.length === 0) { toast('Tous les torrents sont déjà importés', 'success'); return } + + importingAll.value = true + let done = 0, errors = 0 + + for (const t of toImport) { + try { + const res = await fetch('/api/organize', { + method : 'POST', + headers : { 'Content-Type': 'application/json' }, + credentials: 'include', + body : JSON.stringify({ hash: t.hash, save_path: t.save_path, name: t.name }), + }) + res.ok ? done++ : errors++ + } catch { errors++ } + } + + importingAll.value = false + await fetchTorrents() + errors === 0 + ? toast(`${done} torrent(s) importé(s) ✓`, 'success') + : toast(`${done} OK, ${errors} erreur(s)`, 'error') +} + +async function deleteTorrent(torrent: any) { + deleting.value[torrent.hash] = true + try { + const withFiles = deleteWithFiles.value[torrent.hash] ?? false + const res = await fetch(`/api/torrent/${torrent.hash}?deleteFiles=${withFiles}`, { + method : 'DELETE', + credentials: 'include', + }) + if (res.ok) { + toast(`"${torrent.name}" supprimé ✓`, 'success') + confirmDelete.value = null + await fetchTorrents() + } else { + toast('Erreur lors de la suppression', 'error') + } + } catch { + toast('Impossible de contacter le serveur', 'error') + } finally { + deleting.value[torrent.hash] = false + } +} + +onMounted(startPolling) +onUnmounted(stopPolling) \ No newline at end of file