From c78ff22ab76f657be1ebd58007a9c8ed05187bed Mon Sep 17 00:00:00 2001 From: Hitonabi Date: Sun, 30 Aug 2026 18:47:39 +0200 Subject: [PATCH] =?UTF-8?q?feat(v5):=20Etappe=20W-5=20=E2=80=94=20Oberflae?= =?UTF-8?q?che:=20Tray,=20Toasts,=20Taskleiste,=20Einstellungen,=20Bibliot?= =?UTF-8?q?hek?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit haupt/: tray.ts (Symbol mit Zustand, Kontextmenue, Fenster zu = weiter- laufen, Beenden nur noch gewollt), Windows-Toasts bei Disc erkannt / fertig / Fehler (Klick holt das Fenster), Taskleisten-Fortschritt (setProgressBar, Fehlermodus), echter Windows-Ordnerdialog per IPC. Eigenes Icon (bau/icon.png + tray.png, Disc-Optik). Kern: Einstellungen lesen/schreiben NUR ueber die eine Quelle (§ 6.7, Whitelist der Schluessel), Werkzeug-Auskunft (Katalog + gemessene HandBrake-Presets/Backends), Bibliothek in db.ts (Schema v2 — fingerprint, Titel, Groesse, Ort, Dauer), Duplikat-Erkennung beim Einlegen (§ 6.6: 'schon einmal gerippt am …' als disc-info + Toast), Pipeline traegt fertige Vorgaenge ein. Fenster: drei Bereiche (Uebersicht / Einstellungen / Bibliothek). Einstellungen: Ablage mit Durchsuchen-Dialog, Kompression je Disc-Typ (Preset-Liste VOM eigenen HandBrake gemessen, 'NICHT komprimieren' waehlbar), Sprachlisten, TMDb/OMDb-Keys, Medienserver, Werkzeug-Stand mit Klartext (gefunden/fehlt + Folgen). Bibliothek als Tabelle. Der Smoke-Screenshot zeigt nebenbei den Ernstfall in schoen: Das Laufwerk ist nach dem rc11-Vorfall inzwischen auch am Storage-Stack in 'unknown' gekippt — und die Kachel zeigt den ZUGRIFFS_GRUENDE-Klartext samt Abhilfe statt eines stummen Zustands. 165 Tests gruen, Smoke gruen. Co-Authored-By: Claude Fable 5 --- rippy-windows/bau/icon.png | Bin 0 -> 7358 bytes rippy-windows/bau/tray.png | Bin 0 -> 486 bytes rippy-windows/src/bruecke/index.ts | 4 + rippy-windows/src/fenster/App.tsx | 463 +++++++++++++++----- rippy-windows/src/fenster/global.d.ts | 1 + rippy-windows/src/fenster/kernverbindung.ts | 33 ++ rippy-windows/src/gemeinsam/nachrichten.ts | 42 +- rippy-windows/src/haupt/index.ts | 126 +++++- rippy-windows/src/haupt/tray.ts | 52 +++ rippy-windows/src/kern/ablauf/pipeline.ts | 35 +- rippy-windows/src/kern/index.ts | 92 +++- rippy-windows/src/kern/speicher/db.ts | 58 ++- rippy-windows/test/db.test.ts | 4 +- 13 files changed, 782 insertions(+), 128 deletions(-) create mode 100644 rippy-windows/bau/icon.png create mode 100644 rippy-windows/bau/tray.png create mode 100644 rippy-windows/src/haupt/tray.ts diff --git a/rippy-windows/bau/icon.png b/rippy-windows/bau/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..cb5d7f4dd20e8a3a991dd026b977ba297d712a4b GIT binary patch literal 7358 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 Giu@mdQAyqa literal 0 HcmV?d00001 diff --git a/rippy-windows/bau/tray.png b/rippy-windows/bau/tray.png new file mode 100644 index 0000000000000000000000000000000000000000..12e8754a62a59e0941a165b6366a6c19ebaf6de3 GIT binary patch literal 486 zcmV@P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0eeYAK~z{r)t5a^ z!$1&)bAXs2p*RH%6&o6&NGM9l5jX-R$Drc^G&B_SG*mR{($Udy0=hKm=!iDTSd-7Y z8}F_y!IM5IKQr$=+mY<_djDO6k-6q)o$06?|2BoYS)zFi^ommd7dtYyNu3o?6O6b#`5=`jPFE4JGxhEN%zIky_Fue*N`ST*x!OK!R$_tUZAhD0Dm-XLT_&YF1AUl1>}fgcjR?e0UaDu zLeC9G=4=;k?py;QILG%s^jtAC_kZIIq!f@@;rMh?``(52ufkIcq|m59S0(7}EFgni zZUg%670@N@An)V3OrAdcM=#O~Y?}-Hm|aN* { + return ipcRenderer.invoke('ordner-waehlen', String(titel)) as Promise + }, }) diff --git a/rippy-windows/src/fenster/App.tsx b/rippy-windows/src/fenster/App.tsx index 6bfce0c..14cee29 100644 --- a/rippy-windows/src/fenster/App.tsx +++ b/rippy-windows/src/fenster/App.tsx @@ -1,11 +1,12 @@ -// Statusbild W-0/W-1: die drei Prozesse plus die echten Laufwerke aus der -// Disc-Wache. Die große Oberfläche (Dashboard, Bibliothek, Einstellungen) -// ist Etappe W-5 — hier wird das Gerüst SICHTBAR bewiesen. +// Die Oberfläche (W-5): Übersicht (Laufwerke + Gerüst-Status), +// Einstellungen (EINE Quelle — alles läuft über den Kern, § 6.7) und die +// Bibliothek (§ 6.6). Design-Sprache wie das heutige Rippy: dunkel, Kacheln. import { useEffect, useState } from 'react' import type { DiscInfoStand, GeraeteInfo, HauptStatus, RipStatus } from '../gemeinsam/nachrichten' import { aufVerbindung, auswerfen, + einstellungSetzen, ripAbbrechen, ripStarten, verbindungStarten, @@ -32,6 +33,15 @@ const TYP_NAMEN: Record = { unknown: 'unbekannt', } +const LAUFENDE_PHASEN = ['liest-info', 'rippt', 'komprimiert', 'legt-ab'] + +function sicherheitsWort(confidence: number): string { + if (confidence >= 0.9) return 'sehr wahrscheinlich' + if (confidence >= 0.8) return 'wahrscheinlich' + if (confidence >= 0.6) return 'Vorschlag' + return 'unsicher' +} + interface KachelProps { titel: string zustand: 'ok' | 'fehler' | 'wartet' @@ -58,15 +68,6 @@ function Kachel({ titel, zustand, zeilen }: KachelProps) { ) } -const LAUFENDE_PHASEN = ['liest-info', 'rippt', 'komprimiert', 'legt-ab'] - -function sicherheitsWort(confidence: number): string { - if (confidence >= 0.9) return 'sehr wahrscheinlich' - if (confidence >= 0.8) return 'wahrscheinlich' - if (confidence >= 0.6) return 'Vorschlag' - return 'unsicher' -} - function LaufwerkKachel({ geraet, rip, @@ -105,7 +106,7 @@ function LaufwerkKachel({ onClick={() => ripStarten(geraet.id)} disabled={geraet.status !== 'ready'} className="rounded-lg border border-emerald-500/60 px-3 py-1 text-xs text-emerald-300 hover:bg-emerald-500/10 disabled:cursor-not-allowed disabled:opacity-40" - title="Alle Titel, verlustfrei (MakeMKV)" + title="Alle Titel, verlustfrei (MakeMKV) — danach Kompression laut Einstellungen" > Rippen @@ -135,6 +136,11 @@ function LaufwerkKachel({ {info.quelle.length > 0 ? ` · ${info.quelle}` : ''}

)} + {geraet.status === 'ready' && info !== undefined && info.schonGerippt !== null && ( +

+ ⚠ Diese Disc wurde schon einmal gerippt ({info.schonGerippt.slice(0, 10)}) — siehe Bibliothek. +

+ )} {geraet.status === 'empty' &&

keine Disc eingelegt

} {geraet.status === 'unknown' &&

{geraet.grund}

} {geraet.status !== 'unknown' && geraet.grund.length > 0 && ( @@ -176,8 +182,240 @@ function LaufwerkKachel({ ) } +// ── Einstellungen ─────────────────────────────────────────────────────── +function Feld({ + name, + wert, + hinweis, + geheim, + aufAendern, +}: { + name: string + wert: string + hinweis?: string + geheim?: boolean + aufAendern: (wert: string) => void +}) { + const [entwurf, setEntwurf] = useState(wert) + useEffect(() => setEntwurf(wert), [wert]) + return ( + + ) +} + +function PresetWahl({ + name, + schluessel, + wert, + presets, + aufAendern, +}: { + name: string + schluessel: string + wert: string + presets: string[] + aufAendern: (schluessel: string, wert: string) => void +}) { + return ( + + ) +} + +function Einstellungen({ verbindung }: { verbindung: Verbindung }) { + const werte = verbindung.einstellungen + const presets = verbindung.werkzeuge?.presets ?? [] + const setzen = einstellungSetzen + + const ordnerWaehlen = async (): Promise => { + const ordner = await window.rippy.ordnerWaehlen('Ablage-Ordner wählen') + if (ordner !== null) setzen('ablage', ordner) + } + + return ( +
+
+

Ablage

+
+
+ setzen('ablage', w)} /> +
+ +
+
+ +
+

+ Kompression {verbindung.werkzeuge !== null && verbindung.werkzeuge.handbrake.length === 0 && ( + — HandBrakeCLI nicht gefunden + )} +

+ +
+ + + +
+ setzen('transcodePreset', w)} + /> +
+ setzen('audioSprachen', w)} + /> + setzen('untertitelSprachen', w)} + /> +
+
+ +
+

Metadaten

+
+ setzen('tmdbApiKey', w)} /> + setzen('omdbApiKey', w)} /> +
+
+ +
+

Medienserver

+
+ + setzen('mediaServerUrl', w)} /> + setzen('mediaServerApiKey', w)} /> +
+
+ +
+

Werkzeuge

+
+

0 ? 'text-emerald-300' : 'text-red-300'}> + makemkvcon: {verbindung.werkzeuge === null ? 'wird gesucht …' : verbindung.werkzeuge.makemkvcon || 'NICHT GEFUNDEN — MakeMKV installieren'} +

+

0 ? 'text-emerald-300' : 'text-amber-300'}> + HandBrakeCLI: {verbindung.werkzeuge === null ? 'wird gesucht …' : verbindung.werkzeuge.handbrake || 'nicht gefunden — Rippen geht, Kompression nicht'} +

+

0 ? 'text-emerald-300' : 'text-amber-300'}> + flac: {verbindung.werkzeuge === null ? 'wird gesucht …' : verbindung.werkzeuge.flac || 'nicht gefunden — nur für Audio-CDs nötig'} +

+ {verbindung.werkzeuge !== null && verbindung.werkzeuge.backends.length > 0 && ( +

Encoder: {verbindung.werkzeuge.backends.join(', ')}

+ )} +
+
+
+ ) +} + +// ── Bibliothek ────────────────────────────────────────────────────────── +function Bibliothek({ verbindung }: { verbindung: Verbindung }) { + if (verbindung.bibliothek.length === 0) { + return ( +

+ Noch nichts gerippt — die Bibliothek füllt sich mit jedem fertigen Vorgang. +

+ ) + } + return ( +
+ + + + + + + + + + + + + {verbindung.bibliothek.map((eintrag) => ( + + + + + + + + + ))} + +
TitelTypGrößeDatumDauerAblageort
+ {eintrag.titel} + {eintrag.jahr !== null ? ` (${eintrag.jahr})` : ''} + {eintrag.typ}{gb(eintrag.groesseBytes)}{eintrag.datum.slice(0, 16).replace('T', ' ')}{Math.round(eintrag.dauerS / 60)} min + {eintrag.ablageort} +
+
+ ) +} + +type Bereich = 'uebersicht' | 'einstellungen' | 'bibliothek' + export default function App() { const [haupt, setHaupt] = useState(null) + const [bereich, setBereich] = useState('uebersicht') const [verbindung, setVerbindung] = useState({ verbunden: false, kern: null, @@ -185,6 +423,9 @@ export default function App() { laufwerke: [], rips: {}, discInfos: {}, + einstellungen: {}, + werkzeuge: null, + bibliothek: [], meldungen: [], }) @@ -198,102 +439,124 @@ export default function App() { const db = verbindung.kern?.datenbank ?? null + const knopf = (ziel: Bereich, text: string): React.JSX.Element => ( + + ) + return (
-
-

Rippy

- - v{haupt?.version ?? '…'} · Etappe W-1 — Laufwerk - +
+
+

Rippy

+ v{haupt?.version ?? '…'} +
+
-

- Drei Prozesse, eine Datenbank, eine Leine — und die Disc-Wache läuft. -

-
-

- Laufwerke -

- {verbindung.laufwerke.length === 0 ? ( -

- {verbindung.verbunden ? 'Kein optisches Laufwerk gefunden.' : 'Warte auf den Kern …'} -

- ) : ( -
- {verbindung.laufwerke.map((geraet) => ( - - ))} -
- )} -
- -
-

- Gerüst -

-
- - - - -
-
- - {verbindung.meldungen.length > 0 && ( -
-

- Ereignisse -

-
- {verbindung.meldungen.map((zeile, i) => ( -

- {zeile} + {bereich === 'uebersicht' && ( + <> +

+

+ Laufwerke +

+ {verbindung.laufwerke.length === 0 ? ( +

+ {verbindung.verbunden ? 'Kein optisches Laufwerk gefunden.' : 'Warte auf den Kern …'}

- ))} -
-
+ ) : ( +
+ {verbindung.laufwerke.map((geraet) => ( + + ))} +
+ )} + + +
+

+ Status +

+
+ + + + +
+
+ + {verbindung.meldungen.length > 0 && ( +
+

+ Ereignisse +

+
+ {verbindung.meldungen.map((zeile, i) => ( +

+ {zeile} +

+ ))} +
+
+ )} + )} + + {bereich === 'einstellungen' && } + {bereich === 'bibliothek' && }