fix(v5): Kleinkram der Durchsicht — Rückfragen vor Beenden und Löschen, Enter speichert, Escape schließt, ein Toast je Disc, NFO mit Kennung, „(2)" statt Kennungsstumpf, Werkzeug-Suche gemerkt, CD-Lesen ohne Blockade

WAS (je Fund der Durchsicht vom 12.09.2026):
- F15 Tray „Beenden" fragt nach, wenn ein Rip oder eine Kompression läuft
  (app.quit reißt makemkvcon/HandBrake über die Leine mit).
- F17 „Roh-Dateien löschen" und „Ordner löschen" fragen im Fenster nach
  (Größe und Pfad stehen dabei) — ein Klick war vorher 30 GB.
- F18 Eingabefelder speichern auch bei Enter und beim Verschwinden des
  Feldes (Bereichswechsel) — Tipparbeit ging vorher verloren.
- F19 Dialoge: Escape schließt, role=dialog/aria-modal, Fokus im Dialog.
- F20 Toast „Disc erkannt" einmal je Disc und Titel, nicht dreimal.
- F21 Was mit dem Poster passiert, steht in den Hinweisen (R4).
- F22 Werkzeug-Funde gelten 60 s; ein neuer Pfad und „werkzeuge-laden"
  suchen frisch — vorher bei fehlendem MakeMKV je Aufruf dreimal
  reg query /s (bis 45 s, synchron).
- F23 movie.nfo/tvshow.nfo tragen <uniqueid type=tmdb|imdb>.
- F24 Doppelter Zielordner heißt „Akira (1988) (2)" statt „[bluray-2]".
- F25 Umlaute in drei Knopftexten.
- F26 Phase „rettet" zählt im Haupt als laufender Vorgang (Update-Sperre,
  Taskleiste).
- F27 „Neu komprimieren" nicht mehr für verschobene, unkomprimierte
  Vorgänge.
- F28 Die Automatik sagt bei Audio-CD und unbekannter Disc, warum sie
  nichts tut.
- F30 Audio-CD-Spuren werden asynchron gelesen (alle acht Blöcke Luft),
  „Abbrechen" wirkt mitten in der Spur.
- A5 Der tote Schlüssel transcodePreset ist aus presetFuer raus.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-09-12 21:40:07 +02:00
co-authored by Claude Opus 5
parent 783b8e4897
commit c1ee339446
17 changed files with 238 additions and 54 deletions
+39 -3
View File
@@ -375,6 +375,23 @@ export function Feld({
}) {
const [entwurf, setEntwurf] = useState(wert)
useEffect(() => setEntwurf(wert), [wert])
// 5.7.0 (Durchsicht F18): Gespeichert wird beim Verlassen, bei Enter UND
// wenn das Feld verschwindet (Bereichswechsel, Dialog zu) — vorher ging
// Tipparbeit verloren, wenn man statt zu klicken Enter drückte oder in
// einen anderen Bereich wechselte. Die Refs tragen den jüngsten Stand in
// die Aufräum-Funktion des Effekts.
const juengster = useRef({ entwurf, wert, aufAendern })
juengster.current = { entwurf, wert, aufAendern }
useEffect(
() => () => {
const j = juengster.current
if (j.entwurf !== j.wert) j.aufAendern(j.entwurf)
},
[],
)
const speichern = (): void => {
if (entwurf !== wert) aufAendern(entwurf)
}
return (
<label className="block">
<span className="text-xs font-semibold text-dunst">{name}</span>
@@ -384,8 +401,9 @@ export function Feld({
value={entwurf}
placeholder={platzhalter}
onChange={(e) => setEntwurf(e.target.value)}
onBlur={() => {
if (entwurf !== wert) aufAendern(entwurf)
onBlur={speichern}
onKeyDown={(e) => {
if (e.key === 'Enter') speichern()
}}
className={FELD_KLASSE}
/>
@@ -465,10 +483,28 @@ export function DialogRahmen({
children: React.ReactNode
}) {
const hintergrund = useHintergrundKlick(schliessen)
// 5.7.0 (Durchsicht F19): Escape schließt, der Dialog ist als solcher
// ausgezeichnet (role/aria-modal) und bekommt den Fokus — ohne das blieb
// die Tastatur auf der Seite dahinter, und Escape tat nichts.
const karte = useRef<HTMLDivElement>(null)
useEffect(() => {
const taste = (e: KeyboardEvent): void => {
if (e.key === 'Escape') schliessen()
}
window.addEventListener('keydown', taste)
// Fokus in den Dialog — aber nicht aus einem Eingabefeld heraus, das
// ein Kind schon per autoFocus genommen hat.
if (document.activeElement === null || document.activeElement === document.body) karte.current?.focus()
return () => window.removeEventListener('keydown', taste)
}, [schliessen])
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 p-6 backdrop-blur-sm" {...hintergrund}>
<div
className={`max-h-[85vh] w-full ${breite} overflow-y-auto rounded-2xl border border-tinte/12 bg-buehne p-5 shadow-2xl shadow-black/60`}
ref={karte}
role="dialog"
aria-modal="true"
tabIndex={-1}
className={`max-h-[85vh] w-full ${breite} overflow-y-auto rounded-2xl border border-tinte/12 bg-buehne p-5 shadow-2xl shadow-black/60 focus:outline-none`}
onClick={(e) => e.stopPropagation()}
>
{children}
@@ -198,6 +198,9 @@ export function RohDateienKarte({ verbindung, aktivFuer }: { verbindung: Verbind
const v = verbindung.vorgaenge
const [presetWahl, setPresetWahl] = useState<Record<string, string>>({})
const [alleZeigen, setAlleZeigen] = useState(aktivFuer === true)
// 5.7.0 (Durchsicht F17): Löschen fragt nach — ein Klick war vorher 30 GB.
// Der erste Klick macht aus dem Knopf die Rückfrage, der zweite löscht.
const [loeschfrage, setLoeschfrage] = useState<string | null>(null)
const presets = verbindung.werkzeuge?.presets ?? []
const regelWert = werte['rohAufbewahrung'] ?? '7'
const phasenText: Record<VorgangEintrag['phase'], string> = {
@@ -312,11 +315,28 @@ export function RohDateienKarte({ verbindung, aktivFuer }: { verbindung: Verbind
</button>
</>
)}
{!e.rohGeloescht && ruhig && (
<button onClick={() => vorgangRohLoeschen(e.id)} className="rounded-lg border border-rot/40 bg-rot/10 px-3 py-1 text-[11px] text-rot hover:bg-rot/20" title="Den Roh-Ordner dieses Vorgangs jetzt löschen">
Roh-Dateien löschen
{!e.rohGeloescht && ruhig && loeschfrage !== e.id && (
<button onClick={() => setLoeschfrage(e.id)} className="rounded-lg border border-rot/40 bg-rot/10 px-3 py-1 text-[11px] text-rot hover:bg-rot/20" title="Den Roh-Ordner dieses Vorgangs löschen — fragt erst nach">
Roh-Dateien löschen
</button>
)}
{!e.rohGeloescht && ruhig && loeschfrage === e.id && (
<span className="inline-flex flex-wrap items-center gap-2 rounded-lg border border-rot/50 bg-rot/10 px-2 py-1 text-[11px] text-rot">
{gb(e.rohBytes)} unter {e.rohOrdner} unwiderruflich löschen?
<button
onClick={() => {
setLoeschfrage(null)
vorgangRohLoeschen(e.id)
}}
className="rounded-md bg-rot px-2 py-0.5 font-semibold text-buehne hover:brightness-110"
>
Ja, löschen
</button>
<button onClick={() => setLoeschfrage(null)} className="rounded-md border border-tinte/20 px-2 py-0.5 text-dunst hover:text-schnee">
Nein
</button>
</span>
)}
{e.phase === 'fertig' && e.zielOrdner.length > 0 && (
<button onClick={() => void window.rippy.ordnerOeffnen(e.zielOrdner)} className="rounded-lg border border-tinte/15 px-3 py-1 text-[11px] text-dunst hover:text-schnee">
Ordner öffnen
@@ -348,9 +368,27 @@ export function RohDateienKarte({ verbindung, aktivFuer }: { verbindung: Verbind
<span className="min-w-0 break-all font-mono text-[11px] text-dunst">
{f.ordner} · {gb(f.bytes)}
</span>
<button onClick={() => fremdenOrdnerLoeschen(f.ordner)} className="flex-none rounded-lg border border-rot/40 bg-rot/10 px-2.5 py-0.5 text-[10.5px] text-rot hover:bg-rot/20">
Löschen
</button>
{loeschfrage !== f.ordner ? (
<button onClick={() => setLoeschfrage(f.ordner)} className="flex-none rounded-lg border border-rot/40 bg-rot/10 px-2.5 py-0.5 text-[10.5px] text-rot hover:bg-rot/20">
Löschen
</button>
) : (
<span className="flex flex-none items-center gap-1.5 text-[10.5px] text-rot">
wirklich?
<button
onClick={() => {
setLoeschfrage(null)
fremdenOrdnerLoeschen(f.ordner)
}}
className="rounded-md bg-rot px-2 py-0.5 font-semibold text-buehne hover:brightness-110"
>
Ja
</button>
<button onClick={() => setLoeschfrage(null)} className="rounded-md border border-tinte/20 px-2 py-0.5 text-dunst hover:text-schnee">
Nein
</button>
</span>
)}
</div>
))}
</div>
@@ -159,7 +159,7 @@ export function LaufwerkKachel({
onClick={aufRippen}
disabled={!discDa}
className="inline-flex items-center gap-2 rounded-xl bg-gradient-to-b from-gold-hell to-gold-tief px-5 py-1.5 text-xs font-bold text-buehne shadow-[inset_0_1px_0_oklch(1_0_0/0.4),0_8px_24px_oklch(0.75_0.13_60/0.3)] hover:from-gold-hell hover:to-gold disabled:cursor-not-allowed disabled:opacity-35 disabled:shadow-none"
title="Titel der Disc lesen und auswaehlen — danach verlustfrei rippen"
title="Titel der Disc lesen und auswählen — danach verlustfrei rippen"
>
<IconPlay />
Rippen
@@ -411,9 +411,9 @@ export function LaufwerkKachel({
<button
onClick={() => ripUeberspringen(geraet.id)}
className="rounded-lg border border-warn/50 bg-warn/15 px-3 py-1 text-[11px] font-semibold text-warn hover:bg-warn/25"
title="Diesen Titel aufgeben und mit dem naechsten weitermachen"
title="Diesen Titel aufgeben und mit dem nächsten weitermachen"
>
Titel ueberspringen
Titel überspringen
</button>
<span className="font-mono text-[10.5px] text-nebel">oder weiter versuchen abbrechen geht oben.</span>
</div>