mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-25 23:18:44 -07:00
cc7f77b7de
When the server is pointed at the bucket CI publishes builds to (DorkNet:Downloads:BaseUrl), the site lists them on a new Downloads page and the Quest page offers "Latest build" next to "Choose file". The server reads manifest.json and the files over HTTP on the internal network and streams them through with Range support (DownloadsController); only files the manifest lists are served. The xapk reader now reads from a byte source, either the picked File or the server URL via Range requests, so the browser installer streams the APK and OBB to the headset without downloading the package first. Unset, both features stay hidden.
116 lines
3.8 KiB
TypeScript
116 lines
3.8 KiB
TypeScript
import { NavLink, Link, useLocation, useNavigate } from 'react-router';
|
|
import { useEffect, useRef, useState } from 'react';
|
|
import { BrandMark } from './BrandMark';
|
|
import { getDownloads } from '../lib/downloads';
|
|
|
|
const NAV = [
|
|
{ to: '/', label: 'Home', exact: true },
|
|
{ to: '/feed', label: 'Feed' },
|
|
{ to: '/players', label: 'Players' },
|
|
{ to: '/rooms', label: 'Rooms' },
|
|
{ to: '/join', label: 'Join' },
|
|
{ to: '/quest', label: 'Quest' },
|
|
];
|
|
// Shown only when the server offers downloads (DorkNet:Downloads:BaseUrl).
|
|
const DOWNLOADS = { to: '/downloads', label: 'Downloads', exact: false };
|
|
|
|
export function Header() {
|
|
const [open, setOpen] = useState(false);
|
|
const [q, setQ] = useState('');
|
|
const nav = useNavigate();
|
|
const loc = useLocation();
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
const [hasDownloads, setHasDownloads] = useState(false);
|
|
const navItems = hasDownloads ? [...NAV, DOWNLOADS] : NAV;
|
|
|
|
useEffect(() => {
|
|
getDownloads().then((m) => setHasDownloads(m !== null)).catch(() => {});
|
|
}, []);
|
|
|
|
// Close the mobile drawer on route change so tapping a nav item
|
|
// doesn't leave a half-open overlay covering the page.
|
|
useEffect(() => { setOpen(false); }, [loc.pathname]);
|
|
|
|
const submitSearch = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
const v = q.trim();
|
|
if (v) nav(`/players?q=${encodeURIComponent(v)}`);
|
|
};
|
|
|
|
return (
|
|
<header className="sticky top-0 z-40 border-b border-ink-800 bg-ink-950/85 backdrop-blur">
|
|
<div className="mx-auto max-w-6xl flex items-center gap-4 px-4 sm:px-6 py-3">
|
|
<Link to="/" className="flex items-center gap-2 shrink-0">
|
|
<BrandMark className="size-7" />
|
|
<span className="font-semibold text-ink-50">DorkNet</span>
|
|
</Link>
|
|
|
|
<nav className="hidden md:flex items-center gap-1 ml-2">
|
|
{navItems.map(n => (
|
|
<NavLink
|
|
key={n.to}
|
|
to={n.to}
|
|
end={n.exact}
|
|
className={({ isActive }) =>
|
|
`px-3 py-1.5 rounded-md text-sm transition-colors ${
|
|
isActive ? 'bg-ink-800 text-ink-50' : 'text-ink-300 hover:bg-ink-800/60 hover:text-ink-100'
|
|
}`
|
|
}
|
|
>
|
|
{n.label}
|
|
</NavLink>
|
|
))}
|
|
</nav>
|
|
|
|
<form onSubmit={submitSearch} className="ml-auto hidden sm:flex flex-1 max-w-xs">
|
|
<input
|
|
ref={inputRef}
|
|
value={q}
|
|
onChange={e => setQ(e.target.value)}
|
|
placeholder="Search players…"
|
|
className="input"
|
|
/>
|
|
</form>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={() => setOpen(o => !o)}
|
|
aria-label="Toggle menu"
|
|
className="md:hidden btn-ghost text-sm ml-auto"
|
|
>
|
|
{open ? 'Close' : 'Menu'}
|
|
</button>
|
|
</div>
|
|
|
|
{open && (
|
|
<div className="md:hidden border-t border-ink-800 bg-ink-900">
|
|
<div className="mx-auto max-w-6xl px-4 sm:px-6 py-3 flex flex-col gap-2">
|
|
<form onSubmit={submitSearch} className="flex">
|
|
<input
|
|
value={q}
|
|
onChange={e => setQ(e.target.value)}
|
|
placeholder="Search players…"
|
|
className="input"
|
|
/>
|
|
</form>
|
|
<nav className="flex flex-col gap-1">
|
|
{navItems.map(n => (
|
|
<NavLink
|
|
key={n.to}
|
|
to={n.to}
|
|
end={n.exact}
|
|
className={({ isActive }) =>
|
|
`px-3 py-2 rounded-md text-sm ${isActive ? 'bg-ink-800 text-ink-50' : 'text-ink-200 hover:bg-ink-800/60'}`
|
|
}
|
|
>
|
|
{n.label}
|
|
</NavLink>
|
|
))}
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</header>
|
|
);
|
|
}
|