Stop Firefox from squashing upload tiles and show IQDB queue progress

The upload grid was its own scroll container (max-height + overflow on the
same element). Firefox sizes auto grid rows to min-content in that setup,
so every tile collapsed to its footer height and the image was clipped to a
wide strip; Chromium sizes them to max-content, which is why this only
showed up in the user's browser. auto-rows-max pins rows to max-content in
both; verified with headless Firefox screenshots and Chromium measurements
(60 tiles render 152x194 each, 1845px of content in a 639px scroller).
The box is now 70vh so it behaves as a proper fixed gallery area with its
own scrollbar instead of shrinking with the item count.

IQDB progress: the page header now shows a live 'Checking IQDB — x/y'
counter with a bar while background checks run, so the queue is visible
without opening the per-file modal (which keeps its spinner, candidates
and per-file errors).
This commit is contained in:
2026-09-19 00:06:05 -05:00
parent bc7494e7be
commit 2f613b7027
+42 -9
View File
@@ -679,6 +679,10 @@ export default function UploadPage() {
const [processing, setProcessing] = useState(false);
const [checkingIds, setCheckingIds] = useState<string[]>([]);
const [checkErrors, setCheckErrors] = useState<Record<string, string>>({});
const [iqdbProgress, setIqdbProgress] = useState<{
done: number;
total: number;
} | null>(null);
const [modalId, setModalId] = useState<string | null>(null);
const [bulkOpen, setBulkOpen] = useState(false);
@@ -796,6 +800,10 @@ export default function UploadPage() {
for (const temp of targets) {
if (!temp.file_url) continue;
if (/\.(mp4|webm)$/i.test(temp.original_filename)) continue;
setIqdbProgress((current) => ({
done: current?.done ?? 0,
total: (current?.total ?? 0) + 1,
}));
try {
const blob = await fetchTempBlob(temp.file_url);
const results = await iqdbSearch(
@@ -822,6 +830,9 @@ export default function UploadPage() {
[temp.temp_id]: describeError(error),
}));
} finally {
setIqdbProgress((current) =>
current ? { ...current, done: current.done + 1 } : current,
);
setCheckingIds((current) =>
current.filter((value) => value !== temp.temp_id),
);
@@ -830,6 +841,9 @@ export default function UploadPage() {
} finally {
setProcessing(false);
setCheckingIds([]);
setIqdbProgress((current) =>
current === null || current.done >= current.total ? null : current,
);
invalidateUploads();
}
}
@@ -1010,14 +1024,30 @@ export default function UploadPage() {
</p>
</div>
{busy || processing ? (
<p className="flex items-center gap-2 text-xs text-ctp-overlay0">
<Spinner className="h-3.5 w-3.5" />
{busy
? `Uploading ${Math.min(uploadedCount + 1, entries.length)}/${entries.length}…`
: checkingIds.length > 0
? `Checking ${checkingIds.length} file(s) against IQDB…`
: "Processing uploads…"}
</p>
<div className="flex w-56 flex-col gap-1.5">
<p className="flex items-center gap-2 text-xs text-ctp-overlay0">
<Spinner className="h-3.5 w-3.5 shrink-0" />
<span className="truncate">
{busy
? `Uploading ${Math.min(uploadedCount + 1, entries.length)}/${entries.length}…`
: iqdbProgress
? `Checking IQDB — ${iqdbProgress.done}/${iqdbProgress.total}`
: "Processing uploads…"}
</span>
</p>
{!busy && iqdbProgress ? (
<div className="h-1 overflow-hidden rounded-full bg-ctp-surface0">
<div
className="h-full bg-ctp-teal transition-[width]"
style={{
width: `${Math.round(
(iqdbProgress.done / iqdbProgress.total) * 100,
)}%`,
}}
/>
</div>
) : null}
</div>
) : null}
</header>
@@ -1120,7 +1150,10 @@ export default function UploadPage() {
))}
</div>
) : null}
<div className="grid max-h-[60vh] grid-cols-[repeat(auto-fill,minmax(150px,1fr))] content-start gap-3 overflow-y-auto overscroll-contain pr-1">
{/* auto-rows-max: when the grid is its own scroll container Firefox
sizes auto rows to min-content and squashes the tiles; max-content
keeps every tile at its aspect-ratio size and scrolls instead. */}
<div className="grid max-h-[70vh] auto-rows-max grid-cols-[repeat(auto-fill,minmax(150px,1fr))] content-start gap-3 overflow-y-auto overscroll-contain pr-1">
{entries.map((entry) => (
<div
key={entry.id}