118 lines
4.5 KiB
TypeScript
118 lines
4.5 KiB
TypeScript
import { Component, For, onMount, Show } from 'solid-js'
|
|
import { Link, Outlet, useLocation } from '@tanstack/solid-router'
|
|
import { RadioTower } from 'lucide-solid'
|
|
import { refreshMe } from '../../lib/auth'
|
|
import { permissions } from '../../lib/permissions'
|
|
import { ThemeToggle } from '../../components/ui'
|
|
|
|
interface NavItem {
|
|
to: string
|
|
idx: string
|
|
label: string
|
|
activePaths?: string[]
|
|
adminOnly?: boolean
|
|
}
|
|
|
|
const NAV: NavItem[] = [
|
|
{ to: '/', idx: '01', label: '概览' },
|
|
{ to: '/rooms', idx: '02', label: '房间' },
|
|
{ to: '/watch', idx: '03', label: '观看', activePaths: ['/watch', '/room'] },
|
|
{ to: '/publish', idx: '04', label: '推流' },
|
|
{ to: '/users', idx: '05', label: '用户', adminOnly: true },
|
|
]
|
|
|
|
export const Layout: Component = () => {
|
|
const location = useLocation()
|
|
|
|
onMount(async () => {
|
|
await refreshMe()
|
|
await permissions.refresh()
|
|
})
|
|
|
|
const isActive = (item: NavItem) => (item.activePaths ?? [item.to]).includes(location().pathname)
|
|
|
|
const navLinks = (h: string) => (
|
|
<>
|
|
<For each={NAV}>
|
|
{(item) => (
|
|
<Show when={!item.adminOnly || permissions.can('user:manage')}>
|
|
<Link
|
|
to={item.to}
|
|
class={`nav-link flex ${h} shrink-0 items-center gap-1.5 font-mono text-[11px] tracking-[0.16em] uppercase whitespace-nowrap transition-colors ${
|
|
isActive(item)
|
|
? 'is-active text-foreground dark:text-primary'
|
|
: 'text-muted-foreground hover:text-foreground'
|
|
}`}
|
|
>
|
|
<span class="text-[9px] opacity-50">{item.idx}</span>
|
|
{item.label}
|
|
</Link>
|
|
</Show>
|
|
)}
|
|
</For>
|
|
</>
|
|
)
|
|
|
|
return (
|
|
<div class="relative min-h-screen">
|
|
<div class="atmo-grid" aria-hidden="true" />
|
|
<div class="atmo-glow atmo-glow-a" aria-hidden="true" />
|
|
<div class="atmo-glow atmo-glow-b" aria-hidden="true" />
|
|
<div class="scanline-sweep" aria-hidden="true" />
|
|
<div class="noise" aria-hidden="true" />
|
|
|
|
<header class="sticky top-0 z-40 border-b border-line/80 bg-background/85 backdrop-blur-md">
|
|
<div
|
|
class="absolute inset-x-0 top-0 h-[3px]"
|
|
style="background: linear-gradient(90deg, hsl(var(--acid-lime)), hsl(var(--acid-cyan)) 38%, hsl(var(--acid-violet)) 70%, hsl(var(--acid-magenta)))"
|
|
/>
|
|
<div class="mx-auto flex h-12 max-w-[1400px] items-center gap-4 px-4 sm:px-6">
|
|
<Link to="/" class="flex min-w-0 items-center gap-2.5" aria-label="首页">
|
|
<span class="size-2 shrink-0 bg-acid-lime pulse-acid" />
|
|
<span class="display text-base whitespace-nowrap">LIVE·SFU</span>
|
|
<span class="micro-label hidden text-muted-foreground md:inline">fan-out console</span>
|
|
</Link>
|
|
|
|
<nav class="ml-auto hidden items-center gap-5 md:flex" aria-label="主导航">
|
|
{navLinks('h-6')}
|
|
</nav>
|
|
|
|
<div class="ml-auto flex shrink-0 items-center gap-2 md:ml-0">
|
|
<Show when={permissions.isAuthed()}>
|
|
<span class="hidden items-center gap-1.5 border border-line bg-card/60 px-2 py-1 sm:flex">
|
|
<span class="size-1.5 bg-acid-cyan" />
|
|
<span class="font-mono text-[10px] tracking-[0.14em] uppercase text-muted-foreground">
|
|
{permissions.role()}
|
|
</span>
|
|
</span>
|
|
</Show>
|
|
<ThemeToggle />
|
|
<Show when={!permissions.isAuthed()}>
|
|
<Link to="/login" class="btn btn-primary btn-sm">
|
|
登录
|
|
</Link>
|
|
</Show>
|
|
</div>
|
|
</div>
|
|
|
|
<nav class="overflow-x-auto border-t border-line/60 [scrollbar-width:none] md:hidden [&::-webkit-scrollbar]:hidden" aria-label="主导航(移动端)">
|
|
<div class="mx-auto flex max-w-[1400px] items-center gap-4 px-4">
|
|
{navLinks('h-10')}
|
|
</div>
|
|
</nav>
|
|
</header>
|
|
|
|
<main class="relative z-10">
|
|
<Outlet />
|
|
</main>
|
|
|
|
<footer class="relative z-10 mt-16 border-t border-line/80 bg-background/70 backdrop-blur-sm">
|
|
<div class="mx-auto flex max-w-[1400px] flex-col gap-1.5 px-4 py-4 font-mono text-[10px] tracking-[0.18em] uppercase text-muted-foreground sm:flex-row sm:items-center sm:justify-between sm:px-6">
|
|
<span>Cloudflare Realtime · SRS · WHIP / WHEP / HLS / FLV</span>
|
|
<span>live-sfu fan-out console — 一路推流 多端扇出</span>
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
)
|
|
}
|