"use client"; import { useEffect, useRef, useState } from "react"; import { Settings as SettingsIcon, X, Palette, Trash2, Wrench, Film, FolderTree, Captions, } from "lucide-react"; import { useSettingsPanel } from "./SettingsPanelProvider"; import { DefaultSortSelect } from "./DefaultSortSelect"; import { AccentColorPickers } from "./AccentColorPickers"; import { DisplayGroup, TrashGroup, MaintenanceGroup, BackupGroup } from "./SettingsToggles"; import { VideoLibrarySettings } from "./VideoLibrarySettings"; import { WhisperJavSettings } from "./WhisperJavSettings"; import { SubtitleLibraryPaths } from "./SubtitleLibraryPaths"; import { useClickOutside } from "@/lib/hooks/useClickOutside"; import { cn } from "@/lib/utils"; import type { SortKey } from "@/lib/sort"; import type { LibraryStats } from "@/lib/db/queries"; interface PanelData { defaultSort: SortKey; stats: LibraryStats; libraryRoot: string; dbPath: string; } function fmtBytes(n: number): string { if (n < 1024) return `${n} B`; if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`; if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)} MB`; return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`; } function fmtDate(ms: number | null): string { if (!ms) return "—"; const d = new Date(ms); return d.toISOString().slice(0, 10); } export function SettingsPanel({ data }: { data: PanelData }) { const { open, close } = useSettingsPanel(); const panelRef = useRef(null); useClickOutside(panelRef, close, open); useEffect(() => { if (!open) return; const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") close(); }; window.addEventListener("keydown", onKey); document.body.style.overflow = "hidden"; return () => { window.removeEventListener("keydown", onKey); document.body.style.overflow = ""; }; }, [open, close]); if (!open) return null; return (

Settings

); } /* ===================================================================== Section content blocks. Five top-level sections: Appearance · Library · Video · Tools · Info ===================================================================== */ function AppearanceSection({ data }: { data: PanelData }) { return ( ); } function LibrarySection() { return ( ); } function VideoSection() { return ; } function SubtitlesSection() { return ( ); } function ToolsSection() { return ( ); } function InfoSection({ data }: { data: PanelData }) { const s = data.stats; const watchedPct = s.images > 0 ? Math.round((s.watched / s.images) * 100) : 0; return (
{s.trashed > 0 && ( )} {s.labels > 0 && }
); } /* ===================================================================== Sidebar layout — single layout, no toggle. ===================================================================== */ const SIDEBAR_NAV = [ { id: "appearance", label: "Appearance", Icon: Palette }, { id: "library", label: "Library", Icon: Trash2 }, { id: "video", label: "Video", Icon: Film }, { id: "subtitles", label: "Subtitles", Icon: Captions }, { id: "tools", label: "Tools", Icon: Wrench }, { id: "info", label: "Info", Icon: FolderTree }, ] as const; type SidebarSection = typeof SIDEBAR_NAV[number]["id"]; function SidebarLayout({ data }: { data: PanelData }) { const [active, setActive] = useState("appearance"); const content: Record = { appearance: , library: , video: , subtitles: , tools: , info: , }; return (
{content[active]}
); } /* ===================================================================== Tiny primitives. ===================================================================== */ function Card({ title, children }: { title: string; children: React.ReactNode }) { return (

{title}

{children}
); } /** Sub-group label inside a Card — small cyan caps header. */ function SubGroup({ label, children }: { label: string; children: React.ReactNode }) { return (
{label}
{children}
); } /** Horizontal rule between sub-groups inside a Card. */ function Divider() { return
; } function StatGroup({ label, children }: { label: string; children: React.ReactNode }) { return (
{label}
{children}
); } function Row({ label, value, mono }: { label: string; value: string; mono?: boolean }) { return (
{label}
{value}
); }