Drei Zahlen standen als Konstante im Quelltext und bestimmten, wie sich der Server anfühlt. Jetzt stehen sie in der Verwaltung, unter „Wiedergabe". **Weiterschauen ab.** Gemessen: `BelongsInContinueWatching` verlangte nur `Position.Ticks > 0` — ein Tick genügte. Wer im Aufmacher eine Sekunde hineinschaute, fand den Titel danach in „Fortsetzen". Jetzt gibt es eine Untergrenze, voreingestellt auf 1½ Minuten. Was sich dabei **nicht** ändert: die Stelle wird weiter gemerkt. Das ist der Unterschied zu Jellyfin, um dessentwillen es diesen Server gibt — die Schwelle entscheidet, ob der Titel in der Reihe steht, nicht ob man ihn wiederfindet. Nachgemessen: nach dem Umstellen ist „Heat" aus der Reihe verschwunden und steht weiterhin bei Sekunde 1. **Gesehen ab.** War 90 % als `const`, ist jetzt 85/90/95. **Umkodieren.** `-preset veryfast -crf 21` und VAAPI `-qp 23` waren fest verdrahtet, ohne Einstellung und ohne Umgebungsvariable. Drei Stufen: Qualität (medium/19, qp 21), Ausgewogen (wie bisher), Tempo (ultrafast/23, qp 26). Mit dem Warnsatz daneben, und der ist ernst gemeint: was in Echtzeit laufen muss, darf nicht langsamer rechnen als es abspielt. **Und das Vorhandene wird neu bewertet.** `InProgress` ist eine gespeicherte Spalte — die Bruchrechnung lässt sich nicht nach SQL übersetzen. Eine neue Schwelle wirkte sonst nur auf künftige Meldungen. Der Endpunkt läuft deshalb einmal in Stapeln durch und meldet zurück, wie viele Zeilen das umgeworfen hat: „Übernommen — 2 Einträge wurden neu bewertet." Ohne diesen Satz änderte sich die Startseite scheinbar von selbst. `ProgressRule` bleibt ein reiner Typ: die Regeln kommen als Wert herein. Nebenbei zieht der doppelte `zeile()`-Bauer nach `teile.ts` — er stand zweimal wortgleich da. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
107 lines
4.4 KiB
TypeScript
107 lines
4.4 KiB
TypeScript
import { type Bibliothek } from '../../api';
|
|
import { icon, type IconName } from '../../icons';
|
|
import { dashboard, serverbereich, speicherbereich } from './uebersicht';
|
|
import { bibliotheksbereich, vermisstenbereich } from './mediatheken';
|
|
import { pruefbereich } from './pruefung';
|
|
import { ohneZuordnungBereich, unzugeordnetbereich } from './zuordnung';
|
|
import { wiedergabebereich } from './wiedergabe';
|
|
import { sprachbereich } from './metadaten';
|
|
import { benutzerbereich } from './benutzer';
|
|
import { protokollbereich } from './protokolle';
|
|
|
|
/** Welcher Teil der Verwaltung offen ist. */
|
|
export type Bereich =
|
|
| 'uebersicht' | 'mediatheken' | 'pruefung' | 'zuordnung'
|
|
| 'wiedergabe' | 'metadaten' | 'benutzer' | 'protokolle';
|
|
|
|
const BEREICHE: { wert: Bereich; text: string; bild: IconName }[] = [
|
|
{ wert: 'uebersicht', text: 'Übersicht', bild: 'verwaltung' },
|
|
{ wert: 'mediatheken', text: 'Mediatheken', bild: 'filme' },
|
|
{ wert: 'pruefung', text: 'Zu prüfen', bild: 'hakenKreis' },
|
|
{ wert: 'zuordnung', text: 'Zuordnung', bild: 'suche' },
|
|
{ wert: 'wiedergabe', text: 'Wiedergabe', bild: 'stufen' },
|
|
{ wert: 'metadaten', text: 'Metadaten', bild: 'stift' },
|
|
{ wert: 'benutzer', text: 'Benutzer', bild: 'person' },
|
|
{ wert: 'protokolle', text: 'Protokolle', bild: 'protokoll' },
|
|
];
|
|
|
|
/** Macht aus einem Adressteil einen Bereich — Unbekanntes wird zur Übersicht. */
|
|
export function alsBereich(wert: string | undefined): Bereich {
|
|
return BEREICHE.some(b => b.wert === wert) ? wert as Bereich : 'uebersicht';
|
|
}
|
|
|
|
/** Die Verwaltung.
|
|
*
|
|
* **Was etwas anlegt oder ändert, steht in einem Dialog**, nicht als Block
|
|
* in der Seite. Ein Formular, das immer dasteht, nimmt Platz für etwas weg,
|
|
* das man einmal im Monat braucht — und sieht aus wie eine Eingabe, die
|
|
* noch aussteht.
|
|
*
|
|
* **Warum eine zweite Leiste.** Vorher hingen elf Abschnitte untereinander in
|
|
* einer Seite — 1786 Zeilen, und jede neue Einstellung machte sie länger. Wer
|
|
* das Protokoll sehen wollte, scrollte an den Mediatheken, der Prüfliste und
|
|
* dem Speicher vorbei. Jetzt hat jeder Bereich seine eigene Adresse: man kann
|
|
* ihn als Lesezeichen ablegen, und ein Neuladen bleibt dort stehen.
|
|
*
|
|
* **Nur der offene Bereich wird gebaut.** Das ist nicht nur sparsamer — das
|
|
* Abspielfenster der Übersicht fragt im Takt von fünf Sekunden nach, und die
|
|
* Mediathekenliste beobachtet laufende Scans. Was nicht dasteht, fragt auch
|
|
* nicht.
|
|
*
|
|
* `geaendert` wird gerufen, wenn sich die Mediathekenliste geändert hat; die
|
|
* Seitenleiste steht auf ihr und muss dann neu gezeichnet werden. */
|
|
export function verwaltung(
|
|
welcher: Bereich = 'uebersicht',
|
|
geaendert: (bekannt?: Bibliothek[]) => void = () => {},
|
|
signal: AbortSignal = new AbortController().signal,
|
|
): HTMLElement {
|
|
const wurzel = document.createElement('div');
|
|
wurzel.className = 'verwaltung';
|
|
|
|
const leiste = document.createElement('nav');
|
|
leiste.className = 'verw-leiste';
|
|
leiste.setAttribute('aria-label', 'Bereiche der Verwaltung');
|
|
BEREICHE.forEach(b => {
|
|
const k = document.createElement('button');
|
|
k.type = 'button';
|
|
k.className = 'verw-eintrag' + (b.wert === welcher ? ' check' : '');
|
|
k.appendChild(icon(b.bild));
|
|
k.append(document.createTextNode(b.text));
|
|
if (b.wert === welcher) k.setAttribute('aria-current', 'page');
|
|
k.onclick = () => { location.hash = `#/verwaltung/${b.wert}`; };
|
|
leiste.appendChild(k);
|
|
});
|
|
|
|
const buehne = document.createElement('div');
|
|
buehne.className = 'verw-buehne';
|
|
inhalt().forEach(a => buehne.appendChild(a));
|
|
|
|
wurzel.append(leiste, buehne);
|
|
return wurzel;
|
|
|
|
function inhalt(): HTMLElement[] {
|
|
switch (welcher) {
|
|
case 'mediatheken':
|
|
return [bibliotheksbereich(geaendert, signal), vermisstenbereich(geaendert)];
|
|
case 'pruefung': {
|
|
// **Die Prüfliste hört auf sich selbst.** Wird etwas freigegeben,
|
|
// ändert sich der Bestand — und die Liste darüber muss nachziehen.
|
|
const p = pruefbereich(() => { geaendert(); p.auffrischen(); });
|
|
return [p.wurzel];
|
|
}
|
|
case 'zuordnung':
|
|
return [ohneZuordnungBereich(), unzugeordnetbereich()];
|
|
case 'wiedergabe':
|
|
return [wiedergabebereich()];
|
|
case 'metadaten':
|
|
return [sprachbereich()];
|
|
case 'benutzer':
|
|
return [benutzerbereich()];
|
|
case 'protokolle':
|
|
return [protokollbereich()];
|
|
default:
|
|
return [dashboard(signal), serverbereich(geaendert), speicherbereich()];
|
|
}
|
|
}
|
|
}
|