Files
PVideoDl/frontend/src/routes/+layout.svelte
T

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>