All checks were successful
Build and Push Docker Image / build (push) Successful in 1m4s
Per-row audio codec summary (distinct lowercased codecs across an item's audio streams) via scalar subquery on media_streams, rendered as "ac3 · aac" in a new monospace Audio column. v2026.04.15.9 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
87 lines
2.7 KiB
TypeScript
87 lines
2.7 KiB
TypeScript
import { createRootRoute, Link, Outlet } from "@tanstack/react-router";
|
|
import { useEffect, useState } from "react";
|
|
import { api } from "~/shared/lib/api";
|
|
import { cn } from "~/shared/lib/utils";
|
|
|
|
declare const __APP_VERSION__: string;
|
|
|
|
export const Route = createRootRoute({
|
|
component: RootLayout,
|
|
});
|
|
|
|
function NavLink({ to, children, exact = false }: { to: string; children: React.ReactNode; exact?: boolean }) {
|
|
return (
|
|
<Link
|
|
to={to}
|
|
className={cn(
|
|
"px-2.5 py-1 rounded text-[0.85rem] no-underline transition-colors text-gray-500 hover:bg-gray-100 hover:text-gray-900",
|
|
)}
|
|
activeProps={{ className: "bg-gray-100 text-gray-900 font-medium" }}
|
|
activeOptions={{ exact }}
|
|
>
|
|
{children}
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
function VersionBadge() {
|
|
const [serverVersion, setServerVersion] = useState<string | null>(null);
|
|
useEffect(() => {
|
|
api
|
|
.get<{ version: string }>("/api/version")
|
|
.then((d) => setServerVersion(d.version))
|
|
.catch(() => {});
|
|
}, []);
|
|
const buildVersion = typeof __APP_VERSION__ !== "undefined" ? __APP_VERSION__ : null;
|
|
const mismatch = buildVersion && serverVersion && buildVersion !== serverVersion;
|
|
return (
|
|
<span
|
|
className="text-[0.65rem] text-gray-400 font-mono ml-1"
|
|
title={mismatch ? `Frontend: ${buildVersion}, Server: ${serverVersion}` : undefined}
|
|
>
|
|
v{serverVersion ?? buildVersion ?? "?"}
|
|
{mismatch && (
|
|
<span className="text-amber-500 ml-1" title="Frontend and server versions differ — rebuild or refresh">
|
|
⚠
|
|
</span>
|
|
)}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
function RootLayout() {
|
|
const isDev = import.meta.env.DEV;
|
|
return (
|
|
<div className="min-h-screen bg-white text-gray-900 text-sm">
|
|
{isDev && (
|
|
<div className="bg-amber-100 border-b border-amber-300 text-amber-800 text-xs font-semibold text-center py-1 px-4">
|
|
⚠ DEV MODE — fresh test database, 50 movies + 10 series per scan
|
|
</div>
|
|
)}
|
|
<nav className="flex flex-wrap items-center gap-0.5 px-3 sm:px-5 py-2 sm:py-0 sm:h-12 bg-white border-b border-gray-200 sticky top-0 z-50">
|
|
<Link to="/" className="font-bold text-[0.95rem] mr-3 sm:mr-5 no-underline text-gray-900">
|
|
🎬 netfelix
|
|
</Link>
|
|
<VersionBadge />
|
|
<div className="flex flex-wrap items-center gap-0.5">
|
|
<NavLink to="/" exact>
|
|
Library
|
|
</NavLink>
|
|
<NavLink to="/pipeline">Pipeline</NavLink>
|
|
<NavLink to="/review/subtitles">Subtitles</NavLink>
|
|
</div>
|
|
<div className="flex-1" />
|
|
<div className="flex items-center gap-0.5">
|
|
<NavLink to="/paths">Paths</NavLink>
|
|
<NavLink to="/settings">Settings</NavLink>
|
|
</div>
|
|
</nav>
|
|
<main className="max-w-[1600px] mx-auto px-3 sm:px-5 pt-4 pb-12">
|
|
<Outlet />
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
import type React from "react";
|