reference support
Deploy / deploy (push) Successful in 2m50s

This commit is contained in:
twotalesanimation
2026-07-12 10:36:29 +02:00
parent c09d06b6c7
commit 99d1c46b7f
+120 -5
View File
@@ -213,7 +213,88 @@ interface LinkedShot {
references: { id: string; fileUrl: string; fileName: string; label: string | null }[];
}
interface LightboxItem {
url: string;
label: string;
}
function ShotLightbox({
items,
index,
onClose,
}: {
items: LightboxItem[];
index: number;
onClose: () => void;
}) {
const [current, setCurrent] = useState(index);
const item = items[current];
// Keyboard navigation
useEffect(() => {
function onKey(e: KeyboardEvent) {
if (e.key === "Escape") onClose();
if (e.key === "ArrowLeft") setCurrent((c) => Math.max(0, c - 1));
if (e.key === "ArrowRight") setCurrent((c) => Math.min(items.length - 1, c + 1));
}
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [items.length, onClose]);
return (
<div
className="fixed inset-0 z-50 bg-black/90 flex flex-col items-center justify-center"
onClick={onClose}
>
<button
className="absolute top-4 right-4 text-white/70 hover:text-white p-2"
onClick={onClose}
>
<X className="h-6 w-6" />
</button>
<div
className="max-w-[90vw] max-h-[80vh] overflow-hidden"
onClick={(e) => e.stopPropagation()}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={item.url}
alt={item.label}
className="max-w-full max-h-[80vh] object-contain rounded-lg"
/>
</div>
<p className="mt-3 text-sm text-zinc-300">{item.label}</p>
{items.length > 1 && (
<div className="flex gap-4 mt-4" onClick={(e) => e.stopPropagation()}>
<button
disabled={current === 0}
onClick={() => setCurrent((c) => c - 1)}
className="px-4 py-2 rounded bg-zinc-800 text-zinc-300 disabled:opacity-30 hover:bg-zinc-700 text-sm"
>
Prev
</button>
<span className="text-zinc-500 text-sm self-center">
{current + 1} / {items.length}
</span>
<button
disabled={current === items.length - 1}
onClick={() => setCurrent((c) => c + 1)}
className="px-4 py-2 rounded bg-zinc-800 text-zinc-300 disabled:opacity-30 hover:bg-zinc-700 text-sm"
>
Next
</button>
</div>
)}
</div>
);
}
function LinkedShotPanel({ shotId }: { shotId: string }) {
const [lightboxIndex, setLightboxIndex] = useState<number | null>(null);
const { data, isLoading } = useQuery<{ shot: LinkedShot }>({
queryKey: ["linked-shot", shotId],
queryFn: async () => {
@@ -239,7 +320,23 @@ function LinkedShotPanel({ shotId }: { shotId: string }) {
const hasContent = shot.thumbnailUrl || shot.references.length > 0;
if (!hasContent) return null;
// Build a flat list of lightbox items: thumbnail first, then references
const lightboxItems: LightboxItem[] = [
...(shot.thumbnailUrl
? [{ url: shot.thumbnailUrl, label: `${shot.shotCode} — Thumbnail` }]
: []),
...shot.references.map((ref) => ({
url: ref.fileUrl,
label: ref.label ?? ref.fileName,
})),
];
// Index offsets: thumbnail is 0 (if present), refs start at offset
const thumbLightboxIndex = shot.thumbnailUrl ? 0 : null;
const refLightboxOffset = shot.thumbnailUrl ? 1 : 0;
return (
<>
<div className="mt-3 rounded-lg border border-zinc-800 bg-zinc-900/50 p-3 space-y-3">
<div className="flex items-center gap-1.5 text-[10px] font-bold uppercase tracking-widest text-zinc-500">
<BookImage className="h-3 w-3" />
@@ -247,28 +344,37 @@ function LinkedShotPanel({ shotId }: { shotId: string }) {
</div>
{shot.thumbnailUrl && (
<div className="relative w-full max-w-xs aspect-[2.39] rounded-md overflow-hidden border border-zinc-700">
<div
className="relative w-full max-w-xs aspect-[2.39] rounded-md overflow-hidden border border-zinc-700 cursor-zoom-in group"
onClick={() => setLightboxIndex(thumbLightboxIndex!)}
>
<Image
src={shot.thumbnailUrl}
alt={shot.shotCode}
fill
className="object-cover"
className="object-cover transition-transform group-hover:scale-105"
sizes="320px"
/>
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-colors" />
</div>
)}
{shot.references.length > 0 && (
<div className="grid grid-cols-4 gap-2">
{shot.references.map((ref) => (
<div key={ref.id} className="relative aspect-square rounded-md overflow-hidden border border-zinc-700 bg-zinc-950 group">
{shot.references.map((ref, i) => (
<div
key={ref.id}
className="relative aspect-square rounded-md overflow-hidden border border-zinc-700 bg-zinc-950 cursor-zoom-in group"
onClick={() => setLightboxIndex(refLightboxOffset + i)}
>
<Image
src={ref.fileUrl}
alt={ref.label ?? ref.fileName}
fill
className="object-cover"
className="object-cover transition-transform group-hover:scale-105"
sizes="100px"
/>
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-colors" />
{ref.label && (
<div className="absolute bottom-0 inset-x-0 bg-black/70 text-[9px] text-zinc-300 truncate px-1 py-0.5">
{ref.label}
@@ -279,6 +385,15 @@ function LinkedShotPanel({ shotId }: { shotId: string }) {
</div>
)}
</div>
{lightboxIndex !== null && (
<ShotLightbox
items={lightboxItems}
index={lightboxIndex}
onClose={() => setLightboxIndex(null)}
/>
)}
</>
);
}