feat(v5): 5.6.0 — Vorgangs-Leiste, mehrere Filme je Disc, Filmnamen für Ordner und Dateien, Untertitel-Vorwahl je Extra, Restzeit, Hell-Modus
Die sechs Punkte des Commanders vom 02.09.2026 abends und seine Entscheide:
* Restzeit der Kompression aus HandBrakes eigener ETA-Klammer (Formatstring
aus dem Binary 1.11.2 gelesen), KompressionStand.restS.
* Kachel zeigt nach dem Rip nur noch eine Zeile — Balken, Prozent, Ziel in
der Kompressions-Karte und im Verlauf ("nur in der Kompressions-Karte").
* Kopfzeile bleibt beim Scrollen stehen (sticky), Sektionswahl darunter.
* Untertitel-Vorwahl je Extra ("NUR für Extras, IMMER OPTIONAL"): welche
Untertitel-Spur beim Abspielen an ist — weiche Spur, nie eingebrannt;
RipAuftragTitel/RohDatei.untertitelVorwahl, sprachwahl.
* Mehrere Filme je Disc: "anderer Film …" je Titel mit TMDb-Suche;
filme.ts filmGruppen trennt am Rip-Ende in eigene Roh-Ordner, Vorgänge
und Kompressions-Aufträge mit voller Zuordnung (erkennung.zuordnungAus).
* Roh-Ordner und Dateien tragen den Filmnamen (filme.ts rohOrdnerName,
ablage/dateinamen.ts: Akira (1988).mkv, - Fassung n, - Extra nn).
* Hell-Modus: Einstellung design (dunkel/hell/system), data-design auf
<html>, Token --color-tinte statt white/-Alpha-Klassen.
* Vorgangs-Leiste (Commander: "Phase 1–4 plus Komprimierung — zu einem
machen"): gemeinsam/vorgangsleiste.ts + uebersicht/VorgangsLeiste.tsx,
vier Stationen (Rettung als Zwischenstation), Segment-Balken in der
aktiven Station; Kachel, Kompressions-Karte, Verlauf. PHASE-x/4-Texte weg.
* Automatik-Kasten leert sich, sobald ein Rip startet.
Beweise: 446 Tests (neu: filme, dateinamen, vorgangsleiste; erweitert:
auftrag, handbrake, automatik, ablegen), drei Typprüfungen, Klick-Beweis
33 Schritte inkl. Vorwahl, anderer Film, Vorgangs-Leiste, Restzeit,
Hell-Modus (Bilder beweise/klick/01…11).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
76ed3b337a
commit
f5dab5d1c1
@@ -49,13 +49,13 @@ export function Einstellungen({ verbindung, haupt, start }: { verbindung: Verbin
|
||||
// 01.09.2026: „alles gequetscht").
|
||||
return (
|
||||
<div className="flex flex-col gap-5 lg:flex-row lg:gap-8">
|
||||
<nav className="flex flex-row flex-wrap gap-1 lg:sticky lg:top-4 lg:h-fit lg:w-44 lg:flex-none lg:flex-col">
|
||||
<nav className="flex flex-row flex-wrap gap-1 lg:sticky lg:top-[76px] lg:h-fit lg:w-44 lg:flex-none lg:flex-col">
|
||||
{SEKTIONEN.map(([id, name]) => (
|
||||
<button
|
||||
key={id}
|
||||
onClick={() => springe(id)}
|
||||
className={`rounded-lg px-3 py-1.5 text-left text-[13px] ${
|
||||
aktiv === id ? 'border border-gold/40 bg-gold/12 font-semibold text-gold-hell' : 'border border-transparent text-dunst hover:bg-white/5 hover:text-schnee'
|
||||
aktiv === id ? 'border border-gold/40 bg-gold/12 font-semibold text-gold-hell' : 'border border-transparent text-dunst hover:bg-tinte/5 hover:text-schnee'
|
||||
}`}
|
||||
>
|
||||
{name}
|
||||
|
||||
@@ -66,7 +66,7 @@ export function AblageKarte({ verbindung }: { verbindung: Verbindung }) {
|
||||
{gbKurz(speicher.arbeit.freiBytes)} frei von {gbKurz(speicher.arbeit.gesamtBytes)} — eine Blu-ray braucht rund 50 GB.
|
||||
</p>
|
||||
)}
|
||||
<div className="rounded-xl border border-white/8 bg-buehne/60 p-3 font-mono text-[11px] leading-5 text-nebel">
|
||||
<div className="rounded-xl border border-tinte/8 bg-buehne/60 p-3 font-mono text-[11px] leading-5 text-nebel">
|
||||
<p className="text-dunst">So legt Rippy ab:</p>
|
||||
<p>
|
||||
├─ Filme\Akira (1988)\ <span className="text-nebel/70">— Film, Poster und Infodatei beieinander</span>
|
||||
@@ -238,7 +238,7 @@ export function RohDateienKarte({ verbindung, aktivFuer }: { verbindung: Verbind
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-xl border border-white/8 bg-buehne/60 p-3 font-mono text-[11px] leading-5 text-nebel">
|
||||
<div className="rounded-xl border border-tinte/8 bg-buehne/60 p-3 font-mono text-[11px] leading-5 text-nebel">
|
||||
{v === null ? (
|
||||
<p>Belegung wird gemessen …</p>
|
||||
) : (
|
||||
@@ -258,7 +258,7 @@ export function RohDateienKarte({ verbindung, aktivFuer }: { verbindung: Verbind
|
||||
{sichtbar.map((e) => {
|
||||
const ruhig = e.phase === 'fertig' || e.phase === 'fehler' || e.phase === 'abgebrochen'
|
||||
return (
|
||||
<div key={e.id} className="rounded-xl border border-white/8 bg-buehne/60 p-3">
|
||||
<div key={e.id} className="rounded-xl border border-tinte/8 bg-buehne/60 p-3">
|
||||
<div className="flex flex-wrap items-baseline gap-x-3 gap-y-1">
|
||||
<span className="font-display text-[14px] font-bold text-schnee">
|
||||
{e.titel}
|
||||
@@ -293,7 +293,7 @@ export function RohDateienKarte({ verbindung, aktivFuer }: { verbindung: Verbind
|
||||
<select
|
||||
value={presetWahl[e.id] ?? ''}
|
||||
onChange={(ev) => setPresetWahl({ ...presetWahl, [e.id]: ev.target.value })}
|
||||
className="rounded-lg border border-white/15 bg-samt/70 px-2 py-1 text-[11px] text-dunst"
|
||||
className="rounded-lg border border-tinte/15 bg-samt/70 px-2 py-1 text-[11px] text-dunst"
|
||||
title="Preset für die Wiederholung — leer: wie eingestellt"
|
||||
>
|
||||
<option value="">Preset wie eingestellt</option>
|
||||
@@ -318,12 +318,12 @@ export function RohDateienKarte({ verbindung, aktivFuer }: { verbindung: Verbind
|
||||
</button>
|
||||
)}
|
||||
{e.phase === 'fertig' && e.zielOrdner.length > 0 && (
|
||||
<button onClick={() => void window.rippy.ordnerOeffnen(e.zielOrdner)} className="rounded-lg border border-white/15 px-3 py-1 text-[11px] text-dunst hover:text-schnee">
|
||||
<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
|
||||
</button>
|
||||
)}
|
||||
{(e.phase === 'wartet' || e.phase === 'komprimiert' || e.phase === 'legt-ab') && (
|
||||
<button onClick={() => kompressionAbbrechen(e.id)} className="rounded-lg border border-white/15 px-3 py-1 text-[11px] text-dunst hover:text-rot">
|
||||
<button onClick={() => kompressionAbbrechen(e.id)} className="rounded-lg border border-tinte/15 px-3 py-1 text-[11px] text-dunst hover:text-rot">
|
||||
Kompression abbrechen
|
||||
</button>
|
||||
)}
|
||||
@@ -401,7 +401,7 @@ export function MakemkvKarte({ verbindung }: { verbindung: Verbindung }) {
|
||||
titel="MAKEMKV"
|
||||
intro="MakeMKV liest die Discs. Rippy holt den frei veröffentlichten Beta-Key automatisch aus dem Hersteller-Forum, hinterlegt ihn und fragt MakeMKV selbst, ob er angenommen wird — täglich."
|
||||
>
|
||||
<div className="rounded-xl border border-white/8 bg-buehne/60 p-3">
|
||||
<div className="rounded-xl border border-tinte/8 bg-buehne/60 p-3">
|
||||
{s === null ? (
|
||||
<p className="font-mono text-xs text-nebel">Schlüssel wird geprüft …</p>
|
||||
) : (
|
||||
@@ -437,10 +437,10 @@ export function MakemkvKarte({ verbindung }: { verbindung: Verbindung }) {
|
||||
</button>
|
||||
</div>
|
||||
{b !== null && (
|
||||
<div className="rounded-lg border border-white/10 bg-buehne/70 p-2.5">
|
||||
<div className="rounded-lg border border-tinte/10 bg-buehne/70 p-2.5">
|
||||
<p className={`font-mono text-[11px] leading-5 ${b.fehler ? 'text-rot' : b.laeuft ? 'text-gold-hell' : 'text-gruen'}`}>{b.text}</p>
|
||||
{b.laeuft && b.anteil !== null && (
|
||||
<div className="mt-1.5 h-1.5 overflow-hidden rounded-full bg-white/8">
|
||||
<div className="mt-1.5 h-1.5 overflow-hidden rounded-full bg-tinte/8">
|
||||
<div className="h-full rounded-full bg-gradient-to-r from-gold-tief to-gold-hell transition-all" style={{ width: `${Math.round(b.anteil * 100)}%` }} />
|
||||
</div>
|
||||
)}
|
||||
@@ -519,6 +519,14 @@ export function AllgemeinKarte({ verbindung, haupt }: { verbindung: Verbindung;
|
||||
return (
|
||||
<SektionsKarte id="allgemein" titel="ALLGEMEIN" intro="Rippy lebt im Infobereich neben der Uhr: Fenster zu heißt weiterlaufen, beendet wird über das Tray-Menü.">
|
||||
<Schalter an={haupt?.autostart === true} aufWechsel={(an) => void window.rippy.autostartSetzen(an)} label="Rippy mit Windows starten (Autostart im Benutzerkonto — kein Dienst, keine Adminrechte)" />
|
||||
<label className="block max-w-sm">
|
||||
<span className="text-xs font-semibold text-dunst">Erscheinungsbild</span>
|
||||
<select value={werte['design'] ?? 'dunkel'} onChange={(e) => setzen('design', e.target.value)} className={FELD_KLASSE}>
|
||||
<option value="dunkel">Dunkel — der Kinosaal (Vorgabe)</option>
|
||||
<option value="hell">Hell — Papier und Gold</option>
|
||||
<option value="system">Wie Windows eingestellt</option>
|
||||
</select>
|
||||
</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<button onClick={() => void window.rippy.protokollOeffnen()} className={KNOPF_LEISE} title="Eine Datei je Tag, 14 Tage aufgehoben — Haupt und Kern schreiben hinein">
|
||||
Protokoll öffnen
|
||||
@@ -532,7 +540,7 @@ export function AllgemeinKarte({ verbindung, haupt }: { verbindung: Verbindung;
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="rounded-xl border border-white/8 bg-buehne/60 p-3 font-mono text-[11px] leading-5 text-nebel">
|
||||
<div className="rounded-xl border border-tinte/8 bg-buehne/60 p-3 font-mono text-[11px] leading-5 text-nebel">
|
||||
<p>
|
||||
Rippy v{haupt?.version ?? '…'}
|
||||
{kern !== null && ` · Kern: Node ${kern.nodeVersion} (PID ${kern.pid})`}
|
||||
@@ -559,7 +567,7 @@ export function UpdateKarte({ verbindung, haupt }: { verbindung: Verbindung; hau
|
||||
ihn in der Datenbank, und § 6.8 prüft ihn wie zuvor (nur
|
||||
https://). */}
|
||||
{haupt !== null && (
|
||||
<div className="rounded-xl border border-white/8 bg-buehne/60 p-3 font-mono text-[11px] leading-5 text-nebel">
|
||||
<div className="rounded-xl border border-tinte/8 bg-buehne/60 p-3 font-mono text-[11px] leading-5 text-nebel">
|
||||
<p>
|
||||
<span className="text-dunst">Stand:</span> {haupt.update.text}
|
||||
</p>
|
||||
@@ -597,7 +605,7 @@ function WerkzeugZeile({
|
||||
wert: string
|
||||
}) {
|
||||
return (
|
||||
<div className="rounded-xl border border-white/8 bg-samt/50 p-3">
|
||||
<div className="rounded-xl border border-tinte/8 bg-samt/50 p-3">
|
||||
<p className={`break-all font-mono text-xs ${pfad === null ? 'text-nebel' : pfad.length > 0 ? 'text-gruen' : pflicht ? 'text-rot' : 'text-warn'}`}>
|
||||
{name}: {pfad === null ? 'wird gesucht …' : pfad.length > 0 ? pfad : fehlt}
|
||||
</p>
|
||||
|
||||
@@ -30,7 +30,7 @@ export function SprachAuswahl({
|
||||
<div>
|
||||
<span className="text-xs font-semibold text-dunst">{name}</span>
|
||||
<span className="ml-2 text-xs text-nebel">{hinweis}</span>
|
||||
<div className="mt-1 flex min-h-[38px] flex-wrap items-center gap-1.5 rounded-lg border border-white/12 bg-samt px-2 py-1.5">
|
||||
<div className="mt-1 flex min-h-[38px] flex-wrap items-center gap-1.5 rounded-lg border border-tinte/12 bg-samt px-2 py-1.5">
|
||||
{gewaehlt.length === 0 && <span className="font-mono text-[11px] text-nebel">alle Sprachen der Disc</span>}
|
||||
{gewaehlt.map((code, i) => (
|
||||
<span key={code} className="inline-flex items-center gap-1 rounded-md border border-gold/40 bg-gold/12 px-2 py-0.5 text-[11px] text-gold-hell">
|
||||
@@ -53,7 +53,7 @@ export function SprachAuswahl({
|
||||
onChange={(e) => {
|
||||
if (e.target.value.length > 0) setzen([...gewaehlt, e.target.value])
|
||||
}}
|
||||
className="ml-auto rounded-md border border-white/12 bg-buehne px-2 py-1 font-mono text-[11px] text-dunst"
|
||||
className="ml-auto rounded-md border border-tinte/12 bg-buehne px-2 py-1 font-mono text-[11px] text-dunst"
|
||||
>
|
||||
<option value="">+ Sprache …</option>
|
||||
{frei.map((s) => (
|
||||
|
||||
Reference in New Issue
Block a user