Verschieben hatte denselben Fehler -- und Ordner entfernen gab es nur im Dienst

VERSCHIEBEN: derselbe Griff wie beim Loeschen, dieselbe Wirkung.

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

`items` ist die offene Seite; von 3565 gewaehlten Bildern wanderten 60. Ich
hatte gestern nur das Loeschen repariert und diese Stelle nicht gesucht --
zwei Knoepfe mit demselben Muster, und ich habe einen davon uebersehen.

Jetzt haelt der Auftrag auch hier `ids` (alle) und `namen` (die benennbaren)
getrennt, und verschoben wird in Haeppchen zu 200 mit Fortschritt im Knopf.
Was nicht ging, wird benannt statt verschwiegen.

ORDNER ENTFERNEN: die Route gab es (`DELETE /folders/:pfad`), die
Schnittstelle auch (`ordnerEntfernen` in api.ts) -- nur wurde sie NIRGENDS
aufgerufen. Eine Funktion ohne Knopf ist fuer den, der sie sucht, nicht
vorhanden; dieselbe Lehre steht schon ueber "Neuer Ordner" in derselben Datei.

Der Knopf sitzt jetzt neben dem Umbenennen, erst beim Darueberfahren sichtbar.
Die REGEL bleibt beim Dienst: er lehnt einen Ordner mit Inhalt ab und sagt,
wieviele Dateien darin liegen. Sie in der Oberflaeche zu wiederholen hiesse,
sie zweimal zu pflegen -- und die zweite Fassung ist irgendwann die falsche.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-26 13:44:38 +02:00
co-authored by Claude Opus 5
parent 8a17b8b974
commit c9e5bb3c31
3 changed files with 116 additions and 25 deletions
+61 -21
View File
@@ -1,7 +1,7 @@
import { useState } from 'react'
import { AlertTriangle, FolderInput, Plus } from 'lucide-react'
import { api, type Folders, type MediaItem } from '../api'
import { run } from '../store'
import { run, useStore } from '../store'
import { Feld, Knopf } from './Bausteine'
/** Dateien in einen anderen Ordner legen.
@@ -12,12 +12,17 @@ import { Feld, Knopf } from './Bausteine'
* Zwischenspeicher. Das ist kein Fehler, sondern der Preis vorhersagbarer
* Adressen; nur muss man es vorher wissen und nicht hinterher suchen. */
export function Verschieben({
dateien,
ids,
namen,
ordner,
onFertig,
onAbbruch,
}: {
dateien: MediaItem[]
/** ALLE Gewaehlten. */
ids: number[]
/** Nur die, deren Pfad bekannt ist -- die der offenen Seite. Fuer die
* Anzeige, nicht fuer die Handlung. */
namen: MediaItem[]
ordner: Folders | null
onFertig: () => void
onAbbruch: () => void
@@ -25,27 +30,56 @@ export function Verschieben({
const [ziel, setZiel] = useState('')
const [neuerOrdner, setNeuerOrdner] = useState('')
const [laeuft, setLaeuft] = useState(false)
const [fortschritt, setFortschritt] = useState(0)
const beispiel = dateien[0]
const beispiel = namen[0]
const name = beispiel?.path.split('/').pop() ?? ''
const neuerPfad = ziel ? `${ziel}/${name}` : name
async function verschieben() {
setLaeuft(true)
const r = await run(
() => api.verschieben(dateien.map((d) => d.id), ziel),
(x) =>
x.fehlgeschlagen === 0
? `${x.verschoben} verschoben.`
: `${x.verschoben} verschoben, ${x.fehlgeschlagen} nicht: ` +
x.ergebnisse
.filter((e) => !e.ok)
.slice(0, 3)
.map((e) => `${e.path} (${e.error})`)
.join('; '),
)
const { note } = useStore.getState()
// IN HAEPPCHEN, wie beim Loeschen: der Dienst verschiebt jede Datei
// einzeln, und tausende in einer Anfrage laufen in die Zeitgrenze des
// Proxys. 200 je Runde, und der Knopf sagt, wie weit es ist.
const PORTION = 200
let bewegt = 0
let daneben: string[] = []
let abbruch: string | null = null
for (let i = 0; i < ids.length; i += PORTION) {
try {
const x = await api.verschieben(ids.slice(i, i + PORTION), ziel)
bewegt += x.verschoben
daneben.push(
...x.ergebnisse.filter((e) => !e.ok).map((e) => `${e.path} (${e.error})`),
)
} catch (e) {
abbruch = e instanceof Error ? e.message : 'Unbekannt'
break
}
setFortschritt(bewegt)
}
setLaeuft(false)
if (r) onFertig()
if (abbruch) {
note('fehler', `${bewegt} von ${ids.length} verschoben, dann brach es ab: ${abbruch}`)
return
}
if (daneben.length === 0) {
note('ok', `${bewegt} verschoben.`)
} else {
// Was NICHT ging, wird benannt -- ein "verschoben" ueber einer
// halben Bewegung waere die Sorte Rueckmeldung, die hier nicht
// vorkommt.
note(
'hinweis',
`${bewegt} verschoben, ${daneben.length} nicht: ${daneben.slice(0, 3).join('; ')}` +
(daneben.length > 3 ? ` … und ${daneben.length - 3} weitere` : ''),
)
}
onFertig()
}
async function ordnerAnlegen() {
@@ -69,7 +103,7 @@ export function Verschieben({
>
<h3 className="flex items-center gap-2 border-b border-rand px-4 py-3 text-sm font-semibold">
<FolderInput size={16} className="text-akzent" />
{dateien.length === 1 ? 'Datei verschieben' : `${dateien.length} Dateien verschieben`}
{ids.length === 1 ? 'Datei verschieben' : `${ids.length} Dateien verschieben`}
</h3>
<div className="flex flex-col gap-3 px-4 py-4">
@@ -126,9 +160,9 @@ export function Verschieben({
<div className="rounded border border-rand bg-grund p-2 font-mono text-xs">
<div className="text-schrift-leise line-through">{beispiel.path}</div>
<div className="text-gut">{neuerPfad}</div>
{dateien.length > 1 && (
{ids.length > 1 && (
<div className="mt-1 text-schrift-leise">
und {dateien.length - 1} weitere
und {ids.length - 1} weitere
</div>
)}
</div>
@@ -141,7 +175,13 @@ export function Verschieben({
art="haupt"
wartet={laeuft}
onClick={() => void verschieben()}
kind="Verschieben"
kind={
// Bei tausenden Dateien dauert es; Stillstand ist
// sonst von Haengen nicht zu unterscheiden.
laeuft && fortschritt > 0
? `${fortschritt} von ${ids.length}`
: 'Verschieben'
}
/>
</div>
</div>