87 lines
2.6 KiB
Svelte
87 lines
2.6 KiB
Svelte
<script lang="ts">
|
|
import '../app.css';
|
|
import { onMount } from 'svelte';
|
|
import { page } from '$app/stores';
|
|
import { DownloadCloud, History, Puzzle, 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 },
|
|
{ href: '/downloaders', label: 'Загрузчики', icon: Puzzle }
|
|
];
|
|
const path = $derived($page.url.pathname);
|
|
</script>
|
|
|
|
<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 : tab.href === '/history' ? $historyCount : null}
|
|
<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 !== null && 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>
|