0d7a65cffb
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
105 lines
3.1 KiB
Svelte
105 lines
3.1 KiB
Svelte
<script lang="ts">
|
|
import { page } from '$app/state';
|
|
import { goto } from '$app/navigation';
|
|
import { user, logout } from '$lib/auth/store.svelte';
|
|
import { player } from '$lib/player/store.svelte';
|
|
import PlayerBar from './PlayerBar.svelte';
|
|
import SearchInput from './SearchInput.svelte';
|
|
|
|
let { children } = $props<{ children: import('svelte').Snippet }>();
|
|
|
|
let menuOpen = $state(false);
|
|
|
|
async function handleLogout() {
|
|
menuOpen = false;
|
|
await logout();
|
|
goto('/login', { replaceState: true });
|
|
}
|
|
|
|
function isActive(href: string): boolean {
|
|
if (href === '/') return page.url.pathname === '/';
|
|
return page.url.pathname.startsWith(href);
|
|
}
|
|
|
|
const navItems = [
|
|
{ href: '/', label: 'Library' },
|
|
{ href: '/library/liked', label: 'Liked' },
|
|
{ href: '/library/hidden', label: 'Hidden' },
|
|
{ href: '/search', label: 'Search' },
|
|
{ href: '/discover', label: 'Discover' },
|
|
{ href: '/requests', label: 'Requests' },
|
|
{ href: '/playlists', label: 'Playlists' },
|
|
{ href: '/settings', label: 'Settings' }
|
|
];
|
|
|
|
// Admin link sits between Playlists and Settings, only visible to admins.
|
|
const visibleNavItems = $derived(
|
|
user.value?.is_admin
|
|
? [...navItems.slice(0, -1), { href: '/admin', label: 'Admin' }, navItems[navItems.length - 1]]
|
|
: navItems
|
|
);
|
|
</script>
|
|
|
|
<svelte:window onclick={() => (menuOpen = false)} onkeydown={(e) => e.key === 'Escape' && (menuOpen = false)} />
|
|
|
|
<div class="grid h-screen grid-cols-[auto_1fr] grid-rows-[auto_1fr_auto] bg-background text-text-primary">
|
|
<header class="col-span-2 flex items-center gap-4 border-b border-border bg-surface px-4 py-3">
|
|
<div class="font-semibold">Minstrel</div>
|
|
<div class="flex-1">
|
|
<SearchInput />
|
|
</div>
|
|
<div class="relative">
|
|
<button
|
|
type="button"
|
|
class="rounded px-3 py-1 hover:bg-surface-hover"
|
|
onclick={(e) => { e.stopPropagation(); menuOpen = !menuOpen; }}
|
|
>
|
|
{user.value?.username ?? ''}
|
|
</button>
|
|
{#if menuOpen}
|
|
<div
|
|
class="absolute right-0 mt-1 w-32 rounded border border-border bg-surface shadow"
|
|
onclick={(e) => e.stopPropagation()}
|
|
onkeydown={(e) => e.stopPropagation()}
|
|
role="menu"
|
|
tabindex="-1"
|
|
>
|
|
<button
|
|
type="button"
|
|
class="block w-full px-3 py-2 text-left hover:bg-surface-hover"
|
|
onclick={handleLogout}
|
|
>
|
|
Log out
|
|
</button>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</header>
|
|
|
|
<nav class="hidden w-48 border-r border-border bg-surface md:block">
|
|
<ul class="p-2">
|
|
{#each visibleNavItems as item}
|
|
<li>
|
|
<a
|
|
href={item.href}
|
|
class="block rounded px-3 py-2 hover:bg-surface-hover"
|
|
aria-current={isActive(item.href) ? 'page' : undefined}
|
|
>
|
|
{item.label}
|
|
</a>
|
|
</li>
|
|
{/each}
|
|
</ul>
|
|
</nav>
|
|
|
|
<main class="overflow-y-auto p-4 md:col-start-2">
|
|
{@render children?.()}
|
|
</main>
|
|
|
|
{#if player.current}
|
|
<div class="col-span-2">
|
|
<PlayerBar />
|
|
</div>
|
|
{/if}
|
|
</div>
|