diff --git a/web/sections/GalleriesPanel.tsx b/web/sections/GalleriesPanel.tsx index 2d089f4..8d5ea3a 100644 --- a/web/sections/GalleriesPanel.tsx +++ b/web/sections/GalleriesPanel.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { useSearchParams } from 'react-router'; -import { Images, Loader2, Plus, Trash2 } from 'lucide-react'; +import { Check, Images, Loader2, Pencil, Plus, Trash2, X } from 'lucide-react'; import { useClient } from 'hooks/useClient'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; @@ -38,6 +38,8 @@ export const GalleriesPanel = () => { const [name, setName] = useState(''); /** Paths handed over by the context menu, waiting for a name. */ const [pending, setPending] = useState([]); + /** The gallery being renamed, and the name being typed into it. Null when nobody is renaming. */ + const [renaming, setRenaming] = useState<{ id: number; name: string } | null>(null); const [busy, setBusy] = useState(null); const [error, setError] = useState(null); const [params, setParams] = useSearchParams(); @@ -119,6 +121,25 @@ export const GalleriesPanel = () => { } }; + /* + Renaming changes the NAME and leaves the slug exactly where it is. + + Everything that points at a gallery points at its id — `collections.gallery_id`, and a gallery + block's `content.galleryId`. Nothing renders through the slug, so a rename cannot break a reference + today. It is left alone anyway, because a slug is an ADDRESS: it can end up in a link somebody + sent, and the day something does resolve by it, a label having quietly moved it would be a very + hard bug to find. + + So the two are separable and this only writes one of them. A gallery whose slug is genuinely wrong + is a different act with different consequences, and it should look like one. + */ + const rename = () => + send('rename', async () => { + if (!renaming?.name.trim()) return; + await clientRef.current.patch(`/fofos-admin/galleries/${renaming.id}`, { name: renaming.name.trim() }); + setRenaming(null); + }); + const create = () => send('create', async () => { // `urls` in the same request, so creating from a selection is one action — splitting it would @@ -187,6 +208,47 @@ export const GalleriesPanel = () => { open ? 'border-primary/60 bg-muted/40' : 'border-border' }`} > + {renaming?.id === gallery.id ? ( +
+ +
+ setRenaming({ id: gallery.id, name: event.target.value })} + onKeyDown={(event) => { + if (event.key === 'Enter' && renaming.name.trim()) void rename(); + if (event.key === 'Escape') setRenaming(null); + }} + /> + {/* Said while renaming, not afterwards: somebody about to change a name is exactly + who wonders whether the address moves with it. */} + + {gallery.slug} · the address does not change + +
+ + +
+ ) : ( + )} + + {renaming?.id !== gallery.id && ( + + )}