import { listImages, countImages, libraryStats, libraryLetterCounts } from "@/lib/db/queries"; import { LibraryGrid } from "@/components/grid/LibraryGrid"; import { getAppSetting } from "@/lib/db/appSettings"; import { UploadCard } from "@/components/ingest/UploadCard"; import { RegisterVisible } from "@/components/select/RegisterVisible"; import { FilterBar } from "@/components/grid/FilterBar"; import { LetterBar } from "@/components/grid/LetterBar"; import type { LibraryView } from "@/components/grid/ViewToggle"; import { resolveSort } from "@/lib/sortServer"; import { parseFilterCriteria, anyActive as hasAnyCriteria, statusToFlags } from "@/lib/filters"; import { Disc3 } from "lucide-react"; export const dynamic = "force-dynamic"; export default async function Home({ searchParams, }: { searchParams: Promise>; }) { const sp = await searchParams; const criteria = parseFilterCriteria(sp); const sort = await resolveSort(typeof sp.sort === "string" ? sp.sort : undefined); const rawLetter = (typeof sp.letter === "string" ? sp.letter : "").toUpperCase(); const letter = rawLetter === "#" ? "#" : (/^[A-Z]$/.test(rawLetter) ? rawLetter : null); const search = (typeof sp.q === "string" ? sp.q.trim() : "") || undefined; const view: LibraryView = sp.view === "portrait" ? "portrait" : "landscape"; const anyActive = hasAnyCriteria(criteria) || letter != null || !!search; // URL pagination — `page` is the anchor (1-based). Page size from // user settings (Settings → Appearance → Items Per Page). Negative // / non-numeric params clamp to 1. const PAGE_SIZE = Math.max(25, Math.min(500, getAppSetting("coverPageSize") ?? 100)); const rawPage = typeof sp.page === "string" ? Number(sp.page) : NaN; const page = Number.isFinite(rawPage) && rawPage >= 1 ? Math.floor(rawPage) : 1; const offset = (page - 1) * PAGE_SIZE; const filterOpts = { sort, letter: letter ?? undefined, search, ...statusToFlags(criteria.status), marks: criteria.marks, actressIds: criteria.ids.actresses, actressMode: criteria.mode.actresses, studioIds: criteria.ids.studios, seriesIds: criteria.ids.series, genreIds: criteria.ids.genres, genreMode: criteria.mode.genres, collectionIds: criteria.ids.collections, collectionMode: criteria.mode.collections, tagIds: criteria.ids.tags, tagMode: criteria.mode.tags, categoryIds: criteria.ids.categories, categoryMode: criteria.mode.categories, }; const totalCount = countImages(filterOpts); const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE)); // Clamp out-of-range page back to last; cheap re-fetch since we only // care about offset and we already have the count. const effectivePage = Math.min(page, totalPages); const effectiveOffset = (effectivePage - 1) * PAGE_SIZE; const items = listImages({ ...filterOpts, limit: PAGE_SIZE, offset: effectiveOffset }); const stats = libraryStats(); const letterCounts = libraryLetterCounts({ ...statusToFlags(criteria.status), marks: criteria.marks, actressIds: criteria.ids.actresses, actressMode: criteria.mode.actresses, studioIds: criteria.ids.studios, seriesIds: criteria.ids.series, genreIds: criteria.ids.genres, genreMode: criteria.mode.genres, collectionIds: criteria.ids.collections, collectionMode: criteria.mode.collections, tagIds: criteria.ids.tags, tagMode: criteria.mode.tags, categoryIds: criteria.ids.categories, categoryMode: criteria.mode.categories, search, }); return (

Your Cover Library

Drop cover images to import. Codes are parsed from filenames; metadata can be filled in manually or seeded from a sibling .nfo file. Tag, collect, rate, and search.

i.id)} /> {items.length === 0 ? (

{anyActive ? "Nothing Matches" : "Nothing Here Yet"}

{anyActive ? "All filtered out — switch back to All." : "Drag a few covers above to get started."}

) : (
)}
); } function Stat({ label, value }: { label: string; value: number }) { return (
{value}
{label}
); }