feat(stls): enlarge candidate images in preview picker
All checks were successful
continuous-integration/drone/push Build is passing

Each loaded candidate gets an enlarge button that opens the ImageLightbox
without changing selection, so the right preview is easier to choose.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 04:54:03 +02:00
parent f0a9d3b4da
commit 8b500a1610

View File

@@ -7,6 +7,7 @@ import {
Check, Check,
AlertCircle, AlertCircle,
ImageOff, ImageOff,
Maximize2,
} from "lucide-react"; } from "lucide-react";
import { import {
Dialog, Dialog,
@@ -20,6 +21,7 @@ import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { toast } from "sonner"; import { toast } from "sonner";
import { setPreviewFromExtract } from "../actions"; import { setPreviewFromExtract } from "../actions";
import { ImageLightbox } from "./image-lightbox";
interface ArchiveImage { interface ArchiveImage {
id: string; id: string;
@@ -65,6 +67,7 @@ export function ArchivePreviewPicker({
const [thumbnails, setThumbnails] = useState<Map<string, ThumbnailState>>(new Map()); const [thumbnails, setThumbnails] = useState<Map<string, ThumbnailState>>(new Map());
const [selectedPath, setSelectedPath] = useState<string | null>(null); const [selectedPath, setSelectedPath] = useState<string | null>(null);
const [isPending, startTransition] = useTransition(); const [isPending, startTransition] = useTransition();
const [lightboxSrc, setLightboxSrc] = useState<string | null>(null);
const pollTimers = useRef<Map<string, ReturnType<typeof setInterval>>>(new Map()); const pollTimers = useRef<Map<string, ReturnType<typeof setInterval>>>(new Map());
// Track which paths have already been requested to avoid re-requesting // Track which paths have already been requested to avoid re-requesting
const requestedPaths = useRef<Set<string>>(new Set()); const requestedPaths = useRef<Set<string>>(new Set());
@@ -290,12 +293,25 @@ export function ArchivePreviewPicker({
const isFailed = thumbState?.status === "failed"; const isFailed = thumbState?.status === "failed";
return ( return (
<div key={img.id} className="group relative">
{isLoaded && thumbState?.imageUrl && (
<button
type="button"
className="absolute top-1.5 left-1.5 z-10 flex h-6 w-6 items-center justify-center rounded-md bg-black/60 text-white opacity-0 transition-opacity hover:bg-black/80 group-hover:opacity-100"
onClick={(e) => {
e.stopPropagation();
setLightboxSrc(thumbState.imageUrl!);
}}
title="Enlarge"
>
<Maximize2 className="h-3.5 w-3.5" />
</button>
)}
<button <button
key={img.id}
type="button" type="button"
className={cn( className={cn(
"relative aspect-square rounded-lg overflow-hidden border-2 transition-all", "relative aspect-square w-full rounded-lg overflow-hidden border-2 transition-all",
"hover:border-primary/50 cursor-pointer group", "hover:border-primary/50 cursor-pointer",
isSelected isSelected
? "border-primary ring-2 ring-primary/30" ? "border-primary ring-2 ring-primary/30"
: "border-border", : "border-border",
@@ -355,6 +371,7 @@ export function ArchivePreviewPicker({
</p> </p>
</div> </div>
</button> </button>
</div>
); );
})} })}
</div> </div>
@@ -394,6 +411,13 @@ export function ArchivePreviewPicker({
</div> </div>
)} )}
</DialogContent> </DialogContent>
<ImageLightbox
src={lightboxSrc}
open={!!lightboxSrc}
onOpenChange={(open) => {
if (!open) setLightboxSrc(null);
}}
/>
</Dialog> </Dialog>
); );
} }