Loeschen loeschte nur die sichtbare Seite

Gemeldet: alle 3565 Bilder eines Ordners gewaehlt, auf "3565 loeschen"
gedrueckt -- weg waren 60. Sechzig ist die Seitengroesse, und darin steckt der
ganze Fehler:

    onClick={() => setLoeschen(items.filter((i) => gewaehlt.has(i.id)))}

`items` ist die OFFENE SEITE. Der Knopf zaehlte die Auswahl, geloescht wurde
die Schnittmenge mit dem gerade Sichtbaren. Die Absicht dahinter war gut --
der Dialog will die Pfade zeigen, und die kennt er nur fuer geladene Eintraege
-- aber das Ergebnis war eine Zahl, die etwas anderes versprach als sie tat.

Jetzt haelt der Auftrag beides getrennt: `ids` sind ALLE Gewaehlten, `namen`
nur die benennbaren. Der Dialog zeigt bis zu zwoelf Pfade und sagt "und N
weitere" -- ohne eine Vollstaendigkeit vorzutaeuschen, die er nicht hat.

IN HAEPPCHEN ZU 200. Der Dienst verschiebt jede Datei einzeln; 3565 in einer
Anfrage laufen minutenlang und fallen beim Proxy in die Zeitgrenze -- dann
waere die Haelfte weg und die Oberflaeche meldete einen Fehler. Nach jeder
Runde steht im Knopf, wie weit es ist: bei etwas, das eine Minute dauert, ist
Stillstand von Haengen nicht zu unterscheiden. Bricht eine Runde ab, wird
abgebrochen und die Zahl genannt, die schon durch ist.

UND DER WARNTEXT WAR FALSCH. Dort stand "Es gibt keine Sicherung, aus der sie
zurueckkommt" -- aus der Zeit vor dem Papierkorb. Nachgesehen: `inPapierkorb`
VERSCHIEBT die Datei und nimmt den Eintrag zurueck, wenn das Verschieben
scheitert; 30 Tage Aufbewahrung. Eine Warnung, die schlimmer klingt als die
Lage, haelt vom Aufraeumen ab und ist damit so schaedlich wie eine zu harmlose.
Der Knopf heisst jetzt "In den Papierkorb".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-23 11:55:26 +02:00
co-authored by Claude Opus 5
parent a99e792194
commit 8a17b8b974
+94 -28
View File
@@ -60,7 +60,16 @@ export function Galerie() {
const [daten, setDaten] = useState<MediaPage | null>(null)
const [laedt, setLaedt] = useState(true)
const [gewaehlt, setGewaehlt] = useState<Set<number>>(new Set())
const [loeschen, setLoeschen] = useState<MediaItem[] | null>(null)
/** Was geloescht werden soll.
*
* `ids` sind ALLE Gewaehlten, `namen` nur die, deren Pfad wir kennen --
* also die der offenen Seite. Vorher war das eine einzige Liste, und
* genau daran ist es gescheitert: der Knopf zaehlte die Auswahl, geloescht
* wurde die Schnittmenge mit der SICHTBAREN Seite. Bei 3565 gewaehlten
* Bildern verschwanden 60. */
const [loeschen, setLoeschen] = useState<{ ids: number[]; namen: MediaItem[] } | null>(null)
/** Wie weit das Loeschen ist -- bei tausenden Dateien dauert es. */
const [loeschFortschritt, setLoeschFortschritt] = useState(0)
const [loescht, setLoescht] = useState(false)
// Welche Datei gerade gross zu sehen ist -- als ID und nicht als Objekt:
// nach einem Neuladen der Liste zeigt sie sonst auf einen alten Datensatz.
@@ -197,23 +206,53 @@ export function Galerie() {
async function loeschenAusfuehren() {
if (!loeschen) return
setLoescht(true)
setLoeschFortschritt(0)
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.`,
)
// IN HAEPPCHEN, nicht in einem Zug.
//
// Der Dienst verschiebt jede Datei einzeln in den Papierkorb. Bei 3565
// Bildern ist das eine Anfrage, die Minuten laeuft -- und die faellt
// beim Proxy in die Zeitgrenze, lange bevor sie fertig ist. Dann waere
// die Haelfte geloescht und die Oberflaeche meldete einen Fehler.
//
// 200 je Runde: gross genug, dass es nicht an der Anzahl der Anfragen
// haengt, klein genug fuer eine Antwort in Sekunden. Und nach jeder
// Runde steht im Knopf, wie weit es ist -- bei etwas, das eine Minute
// dauert, ist Stillstand nicht von Haengen zu unterscheiden.
const { note } = useStore.getState()
const PORTION = 200
const alle = loeschen.ids
let geloescht = 0
let abbruch: string | null = null
for (let i = 0; i < alle.length; i += PORTION) {
const teil = alle.slice(i, i + PORTION)
try {
const r = teil.length === 1
? await api.deleteMedia(teil[0]!).then(() => ({ count: 1 }))
: await api.deleteMediaBulk(teil)
geloescht += r.count
} catch (e) {
// NICHT WEITERMACHEN. Wenn eine Runde scheitert, scheitern die
// naechsten vermutlich auch, und tausend Fehlermeldungen sagen
// weniger als eine mit der Zahl, die schon durch ist.
abbruch = e instanceof Error ? e.message : 'Unbekannt'
break
}
setLoeschFortschritt(geloescht)
}
if (abbruch) {
note('fehler', `${geloescht} von ${alle.length} gelöscht, dann brach es ab: ${abbruch}`)
} else if (geloescht === alle.length) {
note('ok', `${geloescht} ${geloescht === 1 ? 'Datei' : 'Dateien'} in den Papierkorb verschoben.`)
} else {
// Der Dienst ueberspringt, was er nicht findet. Diese Luecke zu
// verschweigen waere genau die Sorte Rueckmeldung, die hier nicht
// vorkommt.
note('hinweis', `${geloescht} von ${alle.length} gelöscht — der Rest war schon weg.`)
}
const ergebnis = !abbruch
setLoescht(false)
setLoeschen(null)
@@ -441,11 +480,23 @@ export function Galerie() {
{gewaehlt.size > 0 && (
<Knopf
art="gefahr"
onClick={() => setLoeschen(items.filter((i) => gewaehlt.has(i.id)))}
onClick={() =>
setLoeschen({
// ALLE Gewaehlten, nicht nur die sichtbaren.
ids: [...gewaehlt],
namen: items.filter((i) => gewaehlt.has(i.id)),
})
}
kind={
<>
<Trash2 size={15} />
{gewaehlt.size} löschen
{/* WAEHREND ES LAEUFT die Zahl, die schon
durch ist. Bei tausenden Dateien dauert es
eine Minute, und Stillstand ist von Haengen
nicht zu unterscheiden. */}
{loescht && loeschFortschritt > 0
? `${loeschFortschritt} von ${gewaehlt.size}`
: `${gewaehlt.size} löschen`}
</>
}
/>
@@ -611,7 +662,7 @@ export function Galerie() {
<ExternalLink size={11} />
</a>
<button
onClick={() => setLoeschen([item])}
onClick={() => setLoeschen({ ids: [item.id], namen: [item] })}
className="rounded border border-rand px-2 py-1 text-schrift-leise
hover:bg-schlecht/15 hover:text-schlecht"
aria-label={`${item.path} löschen`}
@@ -666,7 +717,7 @@ export function Galerie() {
onSchliessen={() => setAnsehen(null)}
onLoeschen={() => {
setAnsehen(null)
setLoeschen([item])
setLoeschen({ ids: [item.id], namen: [item] })
}}
onUmbenennen={() => {
setAnsehen(null)
@@ -724,22 +775,37 @@ export function Galerie() {
<Nachfrage
offen={loeschen !== null}
titel={loeschen?.length === 1 ? 'Datei löschen?' : `${loeschen?.length} Dateien löschen?`}
bestaetigung="Endgültig löschen"
titel={
loeschen?.ids.length === 1
? 'Datei löschen?'
: `${loeschen?.ids.length ?? 0} Dateien löschen?`
}
bestaetigung="In den Papierkorb"
wartet={loescht}
onNein={() => setLoeschen(null)}
onJa={() => void loeschenAusfuehren()}
text={
<>
{/* DER SATZ HIER WAR FALSCH und stand aus der Zeit
vor dem Papierkorb: "Es gibt keine Sicherung, aus
der sie zurueckkommt." Es gibt eine -- die Datei
wird verschoben, nicht geloescht, und liegt 30 Tage
im Papierkorb. Eine Warnung, die schlimmer klingt
als die Lage, haelt vom Aufraeumen ab und ist damit
genauso schaedlich wie eine, die zu harmlos klingt. */}
<p className="mb-2">
Die Datei wird von der Platte entfernt. Es gibt keine Sicherung, aus
der sie zurückkommt.
Verschiebt in den <b>Papierkorb</b> dort liegt alles noch 30 Tage und
lässt sich zurückholen. Erst danach ist es endgültig weg.
</p>
<ul className="max-h-40 overflow-auto rounded border border-rand bg-grund p-2 font-mono text-xs">
{loeschen?.slice(0, 12).map((i) => <li key={i.id}>{i.path}</li>)}
{loeschen && loeschen.length > 12 && (
{loeschen?.namen.slice(0, 12).map((i) => <li key={i.id}>{i.path}</li>)}
{/* Benennen laesst sich nur, was auf der offenen
Seite liegt -- der Rest ist gewaehlt, aber
nicht geladen. Das steht hier so, statt eine
Vollstaendigkeit vorzutaeuschen. */}
{loeschen && loeschen.ids.length > loeschen.namen.slice(0, 12).length && (
<li className="text-schrift-leise">
und {loeschen.length - 12} weitere
und {loeschen.ids.length - loeschen.namen.slice(0, 12).length} weitere
</li>
)}
</ul>