Enhance PVideoDl with finished_at tracking for downloads and UI updates. Added finished_at field to Download model, updated storage to handle legacy databases, and improved frontend to display recent downloads. Refactored scripts for better clarity and error handling.
This commit is contained in:
@@ -1,6 +1,85 @@
|
||||
<script lang="ts">
|
||||
import '../app.css';
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { DownloadCloud, History, Wifi, WifiOff } from 'lucide-svelte';
|
||||
import * as api from '$lib/api';
|
||||
import {
|
||||
setDownloads,
|
||||
pushToast,
|
||||
connected,
|
||||
activeCount,
|
||||
historyCount,
|
||||
hydrated,
|
||||
startClock
|
||||
} from '$lib/stores';
|
||||
import { connectSSE, disconnectSSE } from '$lib/sse';
|
||||
import Toaster from '$lib/components/Toaster.svelte';
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
onMount(() => {
|
||||
// Гидрация: текущий список + подписка на живые обновления + часы.
|
||||
api
|
||||
.listDownloads()
|
||||
.then(setDownloads)
|
||||
.catch((e) => pushToast('error', `Не удалось загрузить список: ${e.message}`))
|
||||
.finally(() => hydrated.set(true));
|
||||
connectSSE();
|
||||
const stopClock = startClock();
|
||||
return () => {
|
||||
disconnectSSE();
|
||||
stopClock();
|
||||
};
|
||||
});
|
||||
|
||||
const tabs = [
|
||||
{ href: '/', label: 'Активные', icon: DownloadCloud },
|
||||
{ href: '/history', label: 'История', icon: History }
|
||||
];
|
||||
const path = $derived($page.url.pathname);
|
||||
</script>
|
||||
|
||||
{@render children()}
|
||||
<Toaster />
|
||||
|
||||
<main class="mx-auto max-w-3xl px-4 py-10">
|
||||
<header class="mb-6 flex items-end justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold tracking-tight text-zinc-50">PVideoDl</h1>
|
||||
<p class="mt-1 text-sm text-zinc-500">Локальная скачивалка файлов и видео</p>
|
||||
</div>
|
||||
<span
|
||||
class="inline-flex items-center gap-1.5 text-xs {$connected
|
||||
? 'text-emerald-400'
|
||||
: 'text-zinc-500'}"
|
||||
title={$connected ? 'Поток обновлений активен' : 'Нет соединения с сервером'}
|
||||
>
|
||||
{#if $connected}<Wifi class="size-3.5" />онлайн{:else}<WifiOff
|
||||
class="size-3.5"
|
||||
/>оффлайн{/if}
|
||||
</span>
|
||||
</header>
|
||||
|
||||
<nav class="mb-8 flex gap-1 border-b border-zinc-800">
|
||||
{#each tabs as tab (tab.href)}
|
||||
{@const active = path === tab.href}
|
||||
{@const count = tab.href === '/' ? $activeCount : $historyCount}
|
||||
<a
|
||||
href={tab.href}
|
||||
class="-mb-px inline-flex items-center gap-2 border-b-2 px-3 py-2.5 text-sm font-medium transition-colors {active
|
||||
? 'border-indigo-500 text-zinc-100'
|
||||
: 'border-transparent text-zinc-500 hover:text-zinc-300'}"
|
||||
>
|
||||
<tab.icon class="size-4" />
|
||||
{tab.label}
|
||||
{#if $hydrated && count > 0}
|
||||
<span class="rounded-full bg-zinc-800 px-1.5 py-0.5 text-xs font-normal text-zinc-400"
|
||||
>{count}</span
|
||||
>
|
||||
{/if}
|
||||
</a>
|
||||
{/each}
|
||||
</nav>
|
||||
|
||||
{@render children()}
|
||||
</main>
|
||||
|
||||
Reference in New Issue
Block a user