import { useCallback, useEffect, useRef, useState } from 'react' import { Check, ChevronLeft, ChevronRight, Copy, ExternalLink, FileQuestion, ImageOff, Loader2, Search, Trash2, X, } from 'lucide-react' import { api, vorschauUrl, type MediaItem, type MediaPage } from '../api' import { run, useStore } from '../store' import { bytes, istBild, zeit } from '../format' import { Knopf, Leer, Nachfrage, useKopieren } from '../components/Bausteine' const PRO_SEITE = 60 export function Galerie() { const note = useStore((s) => s.note) const [suche, setSuche] = useState('') const [seite, setSeite] = useState(0) const [daten, setDaten] = useState(null) const [laedt, setLaedt] = useState(true) const [gewaehlt, setGewaehlt] = useState>(new Set()) const [loeschen, setLoeschen] = useState(null) const [loescht, setLoescht] = useState(false) const { kopiert, kopieren } = useKopieren() // Die Suche wartet einen Moment ab. Ohne das schickt jeder Tastendruck // eine Anfrage, und bei "adder" sind das fünf, von denen vier veraltet // ankommen können. const [sucheAktiv, setSucheAktiv] = useState('') useEffect(() => { const t = setTimeout(() => { setSucheAktiv(suche) setSeite(0) }, 250) return () => clearTimeout(t) }, [suche]) // Welche Anfrage die neueste ist. Eine ältere, die später ankommt, darf // die neuere nicht überschreiben — sonst steht nach schnellem Tippen das // Ergebnis von vorletzter Eingabe auf dem Schirm. const lauf = useRef(0) const laden = useCallback(async () => { const meine = ++lauf.current setLaedt(true) try { const seiteDaten = await api.media(sucheAktiv, PRO_SEITE, seite * PRO_SEITE) if (meine !== lauf.current) return setDaten(seiteDaten) } catch (err) { if (meine !== lauf.current) return note('fehler', err instanceof Error ? err.message : 'Unbekannter Fehler') } finally { if (meine === lauf.current) setLaedt(false) } }, [sucheAktiv, seite, note]) useEffect(() => { void laden() }, [laden]) const items = daten?.items ?? [] const gesamt = daten?.total ?? 0 const seiten = Math.max(1, Math.ceil(gesamt / PRO_SEITE)) function umschalten(id: number) { setGewaehlt((alt) => { const neu = new Set(alt) if (neu.has(id)) neu.delete(id) else neu.add(id) return neu }) } const alleGewaehlt = items.length > 0 && items.every((i) => gewaehlt.has(i.id)) function alleUmschalten() { setGewaehlt((alt) => { const neu = new Set(alt) if (alleGewaehlt) items.forEach((i) => neu.delete(i.id)) else items.forEach((i) => neu.add(i.id)) return neu }) } async function loeschenAusfuehren() { if (!loeschen) return setLoescht(true) const ergebnis = loeschen.length === 1 ? await run( () => api.deleteMedia(loeschen[0]!.id), (r) => `${r.deleted} gelöscht.`, ) : await run( () => api.deleteMediaBulk(loeschen.map((i) => i.id)), (r) => r.count === loeschen.length ? `${r.count} Dateien gelöscht.` : // Der Dienst überspringt, was er nicht findet. // Diese Lücke zu verschweigen wäre genau die // Sorte Rückmeldung, die hier nicht vorkommt. `${r.count} von ${loeschen.length} gelöscht — der Rest war schon weg.`, ) setLoescht(false) setLoeschen(null) if (ergebnis) { setGewaehlt(new Set()) void laden() } } async function adresseKopieren(item: MediaItem) { const ok = await kopieren(item.url, String(item.id)) if (!ok) note('fehler', 'Die Zwischenablage ist gesperrt — nur über https erreichbar.') } return (
{/* Kopfzeile: Suche, Auswahl, Löschen */}
setSuche(e.target.value)} placeholder="Pfad durchsuchen, z. B. vehicles/" className="w-full rounded-md border border-rand bg-flaeche py-1.5 pr-8 pl-9 text-sm placeholder:text-schrift-leise/60 focus:border-akzent focus:outline-none" /> {suche && ( )}
{laedt ? ( ) : ( <> {gesamt} {gesamt === 1 ? 'Datei' : 'Dateien'} {sucheAktiv && ' gefunden'} )} {items.length > 0 && ( )} {gewaehlt.size > 0 && ( setLoeschen(items.filter((i) => gewaehlt.has(i.id)))} kind={ <> {gewaehlt.size} löschen } /> )}
{/* Das Raster */} {items.length === 0 && !laedt ? ( } text={ sucheAktiv ? `Nichts gefunden für "${sucheAktiv}".` : 'Noch keine Datei hochgeladen.' } /> ) : (
{items.map((item) => { const an = gewaehlt.has(item.id) return (
{an && ( )}

{item.path}

{bytes(item.size)} · {zeit(item.updated_at)}

) })}
)} {/* Blättern */} {seiten > 1 && (
setSeite((s) => s - 1)} kind={} /> Seite {seite + 1} von {seiten} = seiten} onClick={() => setSeite((s) => s + 1)} kind={} />
)} setLoeschen(null)} onJa={() => void loeschenAusfuehren()} text={ <>

Die Datei wird von der Platte entfernt. Es gibt keine Sicherung, aus der sie zurückkommt.

    {loeschen?.slice(0, 12).map((i) =>
  • {i.path}
  • )} {loeschen && loeschen.length > 12 && (
  • … und {loeschen.length - 12} weitere
  • )}
} />
) }