the first photograph leads, and nothing says so twice
A collection named its own main picture. A gallery is already ORDERED — somebody drags it into the sequence they want — so "which one leads" was being answered twice, in two screens, by two mechanisms that could disagree. Reordering a gallery and then wondering why the card had not changed was a bug waiting to be filed. So the grid here decides nothing at all. It is what the collection SHOWS, in the gallery's order, and the pictures are no longer buttons: making them look pressable would promise a control that lives in another screen. Adding, reordering and framing all belong to Galleries, and now so does this. The `cover` column is gone from both databases. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { useSearchParams } from 'react-router';
|
import { useSearchParams } from 'react-router';
|
||||||
import { Boxes, Check, Images, Loader2, Plus, Trash2 } from 'lucide-react';
|
import { Boxes, Images, Loader2, Plus, Trash2 } from 'lucide-react';
|
||||||
import { useClient } from 'hooks/useClient';
|
import { useClient } from 'hooks/useClient';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
@@ -176,14 +176,6 @@ export const CollectionsPanel = () => {
|
|||||||
await clientRef.current.patch(`/fofos-admin/collections/${selected.id}`, { gallery_id: galleryId });
|
await clientRef.current.patch(`/fofos-admin/collections/${selected.id}`, { gallery_id: galleryId });
|
||||||
});
|
});
|
||||||
|
|
||||||
const chooseCover = (path: string) =>
|
|
||||||
send(async () => {
|
|
||||||
if (!selected) return;
|
|
||||||
// Pressing the one already chosen clears it, which is how "no choice, use the first" is reachable
|
|
||||||
// again without a second control that only does that.
|
|
||||||
await clientRef.current.patch(`/fofos-admin/collections/${selected.id}`, {
|
|
||||||
cover: selected.cover === path ? null : path,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -279,36 +271,20 @@ export const CollectionsPanel = () => {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<p className="mb-3 text-xs text-muted-foreground">
|
<p className="mb-3 text-xs text-muted-foreground">
|
||||||
{photos.length} photographs, in the gallery’s order. Click one to make it the main photo.
|
{photos.length} photographs, in the gallery’s order — the first one leads. Reorder
|
||||||
|
them in Galleries.
|
||||||
</p>
|
</p>
|
||||||
|
{/* Not buttons. Nothing here is a decision — the order is the gallery's and so is which one
|
||||||
|
leads. Making them look pressable would promise a control that lives elsewhere. */}
|
||||||
<ul className="grid grid-cols-[repeat(auto-fill,minmax(9rem,1fr))] gap-3">
|
<ul className="grid grid-cols-[repeat(auto-fill,minmax(9rem,1fr))] gap-3">
|
||||||
{photos.map((photo) => {
|
{photos.map((photo) => (
|
||||||
const isCover = selected.cover ? photo.path === selected.cover : photo === photos[0];
|
<li key={photo.path}>
|
||||||
return (
|
<div className="overflow-hidden rounded-md border border-border">
|
||||||
<li key={photo.path}>
|
<img src={thumbUrl(photo.path, 320)} alt="" className="aspect-square w-full object-cover" />
|
||||||
<button
|
</div>
|
||||||
type="button"
|
<p className="mt-1 truncate font-mono text-[10px] text-muted-foreground/70">{photo.name}</p>
|
||||||
disabled={busy}
|
</li>
|
||||||
onClick={() => void chooseCover(photo.path)}
|
))}
|
||||||
className={`relative block w-full overflow-hidden rounded-md border transition-colors ${
|
|
||||||
isCover ? 'border-primary ring-1 ring-primary' : 'border-border hover:border-primary/50'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<img src={thumbUrl(photo.path, 320)} alt="" className="aspect-square w-full object-cover" />
|
|
||||||
{isCover && (
|
|
||||||
<span className="absolute top-1.5 right-1.5 flex items-center gap-1 rounded bg-primary px-1.5 py-0.5 text-[10px] text-primary-foreground shadow">
|
|
||||||
<Check className="size-2.5" />
|
|
||||||
{/* "Main" rather than "Cover": the first one is the main photo by default, so
|
|
||||||
the badge has to be true of a picture nobody chose as well as one somebody
|
|
||||||
did. */}
|
|
||||||
Main
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
<p className="mt-1 truncate font-mono text-[10px] text-muted-foreground/70">{photo.name}</p>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ul>
|
</ul>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ export type Collection = {
|
|||||||
gallery_id: number | null;
|
gallery_id: number | null;
|
||||||
title: string;
|
title: string;
|
||||||
slug: string | null;
|
slug: string | null;
|
||||||
/** A media PATH, the same string a gallery item holds — see the site's schema. */
|
|
||||||
cover: string | null;
|
|
||||||
description: string | null;
|
description: string | null;
|
||||||
descriptive_text: string | null;
|
descriptive_text: string | null;
|
||||||
measurement: string | null;
|
measurement: string | null;
|
||||||
|
|||||||
Reference in New Issue
Block a user