Files
pinkudex/components/settings/UndersizedCoversButton.tsx
2026-05-26 22:46:00 +02:00

133 lines
5.6 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState, useTransition } from "react";
import Link from "next/link";
import { Ruler, Loader2, AlertTriangle, ExternalLink } from "lucide-react";
import { scanUndersizedCovers, type UndersizedCover } from "@/app/actions/maintenance";
import { thumbUrl } from "@/lib/assetUrls";
import { useSettingsPanel } from "./SettingsPanelProvider";
type State =
| { kind: "idle" }
| { kind: "scanning" }
| { kind: "result"; rows: UndersizedCover[] };
function fmtBytes(n: number): string {
if (n < 1024) return `${n} B`;
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
return `${(n / 1024 / 1024).toFixed(2)} MB`;
}
export function UndersizedCoversButton() {
const [state, setState] = useState<State>({ kind: "idle" });
const [pending, start] = useTransition();
const { close: closeSettings } = useSettingsPanel();
const scan = () => {
setState({ kind: "scanning" });
start(async () => {
const rows = await scanUndersizedCovers();
setState({ kind: "result", rows });
});
};
return (
<div className="py-2">
<div className="flex items-start justify-between gap-4">
<div className="min-w-0">
<div className="text-sm font-medium">Find Undersized Covers</div>
<div className="text-xs text-[var(--color-fg-muted)] mt-0.5">
Scan top-level covers smaller than standard JAV size (default
floor is <code className="font-mono">750×500</code>; real covers are
usually <code className="font-mono">800×538</code>). Catches
thumbnails or web previews accidentally imported as covers.
</div>
{state.kind === "result" && state.rows.length === 0 && (
<div className="text-xs text-[var(--color-mint)] mt-2">
No undersized covers all top-level covers meet the size threshold.
</div>
)}
{state.kind === "result" && state.rows.length > 0 && (
<div className="text-xs text-[var(--color-coral)] mt-2 flex items-center gap-1.5">
<AlertTriangle className="w-3.5 h-3.5" />
{state.rows.length} undersized cover{state.rows.length === 1 ? "" : "s"} found.
</div>
)}
</div>
<div className="flex-shrink-0 flex items-center gap-2">
{state.kind === "idle" && (
<button
onClick={scan}
disabled={pending}
className="inline-flex items-center justify-center gap-1.5 min-w-[100px] text-xs px-3 py-1.5 rounded-lg glass glass-hover text-[var(--color-fg-dim)] hover:text-[var(--color-fg)] whitespace-nowrap"
>
<Ruler className="w-3.5 h-3.5" /> Scan
</button>
)}
{state.kind === "scanning" && (
<span className="flex items-center gap-1.5 text-xs text-[var(--color-fg-dim)]">
<Loader2 className="w-3.5 h-3.5 animate-spin" /> Scanning
</span>
)}
{state.kind === "result" && (
<>
<button
onClick={scan}
disabled={pending}
className="inline-flex items-center justify-center gap-1.5 min-w-[100px] text-xs px-3 py-1.5 rounded-lg glass glass-hover text-[var(--color-fg-dim)] hover:text-[var(--color-fg)] whitespace-nowrap"
>
Re-scan
</button>
<button
onClick={() => setState({ kind: "idle" })}
className="inline-flex items-center justify-center min-w-[80px] text-xs px-3 py-1.5 rounded-lg text-[var(--color-fg-muted)] hover:text-[var(--color-fg)] hover:bg-[var(--color-glass)]"
>
Dismiss
</button>
</>
)}
</div>
</div>
{state.kind === "result" && state.rows.length > 0 && (
<div className="mt-3 max-h-72 overflow-y-auto rounded-md border border-[var(--color-glass-border)] bg-[var(--color-bg-1)]/40">
{state.rows.map((r) => (
<Link
key={r.id}
href={`/image/${r.id}`}
onClick={closeSettings}
className="flex items-center gap-3 p-2 border-b border-[var(--color-glass-border)] last:border-b-0 hover:bg-[var(--color-glass)] transition-colors"
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={thumbUrl({ thumbPath: r.thumbPath, code: r.code, id: r.id })}
alt=""
className="w-12 h-12 object-contain bg-black/40 rounded shrink-0"
loading="lazy"
/>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2 text-xs">
{r.code ? (
<span className="font-mono font-bold text-[var(--color-cyan)]">{r.code}</span>
) : (
<span className="font-mono text-[var(--color-fg-muted)] italic">no code</span>
)}
<span className="font-mono text-[var(--color-coral)] tabular-nums">
{r.width}×{r.height}
</span>
<span className="font-mono text-[var(--color-fg-muted)] tabular-nums">
{fmtBytes(r.bytes)}
</span>
</div>
<div className="text-[11px] text-[var(--color-fg-dim)] truncate font-mono mt-0.5">
{r.filename}
</div>
</div>
<ExternalLink className="w-3.5 h-3.5 text-[var(--color-fg-muted)] shrink-0" />
</Link>
))}
</div>
)}
</div>
);
}