import { istAdmin, meldungen, type Bibliothek } from '../../api'; import { icon, type IconName } from '../../icons'; import { kontoseite } from './konto'; import { geschautseite } from './geschaut'; import { dashboard, serverbereich, speicherbereich } from './uebersicht'; import { bibliotheksbereich, vermisstenbereich } from './mediatheken'; import { pruefbereich, meldungsbereich } from './pruefung'; import { ohneZuordnungBereich, unzugeordnetbereich } from './zuordnung'; import { wiedergabebereich } from './wiedergabe'; import { sprachbereich } from './metadaten'; import { benutzerbereich } from './benutzer'; import { protokollbereich } from './protokolle'; import { wartungsbereich } from './wartung'; /** Welcher Teil der Einstellungen offen ist. */ export type Bereich = | 'konto' | 'geschaut' | 'uebersicht' | 'mediatheken' | 'pruefung' | 'zuordnung' | 'wiedergabe' | 'metadaten' | 'benutzer' | 'wartung' | 'protokolle'; type Eintrag = { wert: Bereich; text: string; bild: IconName }; /** Was jedem gehört — das eigene Konto und die eigenen Zahlen. */ const MEINS: Eintrag[] = [ { wert: 'konto', text: 'Mein Konto', bild: 'person' }, { wert: 'geschaut', text: 'Geschaut', bild: 'hakenKreis' }, ]; /** Was dem Server gehört. Nur Administratoren sehen diese Gruppe. */ const SERVER: Eintrag[] = [ { wert: 'uebersicht', text: 'Übersicht', bild: 'verwaltung' }, { wert: 'mediatheken', text: 'Mediatheken', bild: 'filme' }, { wert: 'pruefung', text: 'Zu prüfen', bild: 'haken' }, { wert: 'zuordnung', text: 'Zuordnung', bild: 'suche' }, { wert: 'wiedergabe', text: 'Wiedergabe', bild: 'stufen' }, { wert: 'metadaten', text: 'Metadaten', bild: 'stift' }, { wert: 'benutzer', text: 'Benutzer', bild: 'serien' }, { wert: 'wartung', text: 'Wartung', bild: 'stufen' }, { wert: 'protokolle', text: 'Protokolle', bild: 'protokoll' }, ]; /** Macht aus einem Adressteil einen Bereich. * * Unbekanntes und Verbotenes werden zum eigenen Konto: das hat jeder, und es * ist die Seite, auf der man landen will, wenn man hier nichts zu suchen hat. */ export function alsBereich(wert: string | undefined, admin: boolean): Bereich { if (MEINS.some(b => b.wert === wert)) return wert as Bereich; if (admin && SERVER.some(b => b.wert === wert)) return wert as Bereich; return 'konto'; } /** Die Einstellungen. * * **Ein Ort, zwei Gruppen.** Oben steht, was einem selbst gehört — Bild, * Sprache, die eigenen Zahlen. Darunter, nur für Administratoren, was dem * Server gehört. Vorher hiess die ganze Seite „Verwaltung" und war für alle * anderen gar nicht erreichbar; „Meine Sprache" lag dadurch hinter einer Tür, * die den meisten verschlossen ist. * * **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. * * **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 einstellungenseite( welcher: Bereich = 'konto', geaendert: (bekannt?: Bibliothek[]) => void = () => {}, signal: AbortSignal = new AbortController().signal, ): HTMLElement { const admin = istAdmin(); const wurzel = document.createElement('div'); wurzel.className = 'verwaltung'; /** Das Feld für die Zahl offener Meldungen — erst beim Bauen der Leiste da. */ let zahl: HTMLElement | null = null; const leiste = document.createElement('nav'); leiste.className = 'verw-leiste'; leiste.setAttribute('aria-label', 'Bereiche der Einstellungen'); gruppe('Einstellungen', MEINS); if (admin) gruppe('Verwaltung', SERVER); const buehne = document.createElement('div'); buehne.className = 'verw-buehne'; inhalt().forEach(a => buehne.appendChild(a)); // **Die Zahl holt sich nur, wer sie nicht ohnehin hat.** Steht „Zu prüfen" // offen, meldet der Meldungsabschnitt sie selbst — und zwar nach jedem // Abhaken. Sonst eine Anfrage je Seitenaufbau, und nur für Administratoren. // // Verworfen: die Zahl an `/system/info` oder am Konto mitführen. Beide // werden ohnehin geholt, wären also „umsonst" — und wären ab dem ersten // Abhaken falsch. // // Scheitert sie, bleibt es still: ein Abzeichen, das nicht lädt, fehlt eben. if (admin && welcher !== 'pruefung') { void meldungen(false).then(m => abzeichen(m.length)).catch(() => {}); } wurzel.append(leiste, buehne); return wurzel; /** Eine beschriftete Gruppe — die Beschriftung ist hier die halbe Aussage. * * „Verwaltung" über den unteren Einträgen sagt ohne weiteren Satz, dass * diese dem Server gehören und nicht einem selbst. */ /** Die Zahl offener Meldungen auf „Zu prüfen". Leer statt „0": eine Null * ist Rauschen. */ function abzeichen(offen: number): void { if (zahl) zahl.textContent = offen > 0 ? String(offen) : ''; } function gruppe(name: string, eintraege: Eintrag[]): void { const kopf = document.createElement('div'); kopf.className = 'verw-gruppe'; kopf.textContent = name; leiste.appendChild(kopf); eintraege.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'); if (b.wert === 'pruefung') { zahl = document.createElement('span'); zahl.className = 'zahl'; k.appendChild(zahl); } k.onclick = () => { location.hash = `#/einstellungen/${b.wert}`; }; leiste.appendChild(k); }); } function inhalt(): HTMLElement[] { switch (welcher) { case 'geschaut': return [geschautseite()]; 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(); }); // Zwei Sektionen, nicht eine: die Prüfliste versteckt sich, sobald // nichts auf Freigabe wartet, und nähme die Meldungen sonst mit. return [p.wurzel, meldungsbereich(abzeichen).wurzel]; } case 'zuordnung': return [ohneZuordnungBereich(), unzugeordnetbereich()]; case 'wiedergabe': return [wiedergabebereich()]; case 'metadaten': return [sprachbereich()]; case 'benutzer': return [benutzerbereich()]; case 'wartung': return [wartungsbereich()]; case 'protokolle': return [protokollbereich()]; case 'uebersicht': return [dashboard(signal), serverbereich(geaendert), speicherbereich()]; default: return [kontoseite()]; } } }