import { inhalt, type Bibliothek, type Film, type Serie, type Ordnung } from '../api'; import { esc, plakat, leer, fehlerzeile, anteil } from '../util'; type Art = 'filme' | 'serien'; /** Eine Mediathek als Raster. * * **Eine Ansicht je Mediathek, nicht eine je Art.** Welche gezeigt wird, * entscheidet die Seitenleiste — wie bei Plex, wo jede Mediathek ihr eigener * Punkt in der Navigation ist. Wer „Filme" und „Dokumentationen" getrennt * angelegt hat, hat sie getrennt gemeint. * * Gesucht und sortiert wird am Server: bei zehntausend Titeln wäre alles zu * laden und dann zu sieben der falsche Weg. */ export function mediathek( bibliothek: Bibliothek, oeffnen: (art: Art, id: string) => void, ): HTMLElement { const wurzel = document.createElement('div'); const art: Art = bibliothek.kind === 'Shows' ? 'serien' : 'filme'; let suche = ''; let ordnung: Ordnung = 'title'; let tippt: number | undefined; const kopf = document.createElement('div'); kopf.className = 'row'; const titel = document.createElement('h1'); titel.className = 'seitentitel'; titel.textContent = bibliothek.name; const sortierung = document.createElement('select'); sortierung.className = 'field schmal'; ([ ['title', 'A–Z'], ['added', 'Zuletzt hinzugefügt'], ['year', 'Jahr'], ] as [Ordnung, string][]).forEach(([wert, text]) => { const o = document.createElement('option'); o.value = wert; o.textContent = text; sortierung.appendChild(o); }); sortierung.onchange = () => { ordnung = sortierung.value as Ordnung; void laden(); }; const suchfeld = document.createElement('input'); suchfeld.className = 'field'; suchfeld.type = 'search'; suchfeld.placeholder = `In ${bibliothek.name} suchen`; suchfeld.oninput = () => { // Nicht bei jedem Tastendruck fragen: das wären bei „Untergang" neun // Anfragen, von denen acht niemanden interessieren. window.clearTimeout(tippt); tippt = window.setTimeout(() => { suche = suchfeld.value.trim(); void laden(); }, 250); }; kopf.append(titel, sortierung, suchfeld); const raster = document.createElement('div'); raster.className = 'grid'; wurzel.append(kopf, raster); async function laden(): Promise { raster.innerHTML = ''; raster.appendChild(ladeanzeige()); try { const seite = await inhalt(bibliothek, { search: suche, sort: ordnung, take: 100 }); raster.innerHTML = ''; if (seite.items.length === 0) { raster.appendChild(leer(suche ? `Nichts gefunden für „${suche}“.` : `„${bibliothek.name}“ ist leer. Starte unter Verwaltung einen Scan.`)); return; } seite.items.forEach(eintrag => raster.appendChild(karte(eintrag))); } catch (fehler) { raster.innerHTML = ''; raster.appendChild(fehlerzeile((fehler as Error).message)); } } function karte(eintrag: Film | Serie): HTMLElement { const k = document.createElement('button'); k.className = 'card'; const bild = plakat( eintrag.posterImage, eintrag.title, 'state' in eintrag ? anteil(eintrag.state) : null); if ('unwatchedCount' in eintrag && eintrag.unwatchedCount > 0) { const plakette = document.createElement('span'); plakette.className = 'offen'; plakette.textContent = String(eintrag.unwatchedCount); bild.appendChild(plakette); } k.appendChild(bild); const unten = document.createElement('div'); unten.className = 'rowline'; // Zusammengesetzt aus dem, was da ist — ein fest verdrahtetes „ · " davor // hinterlässt bei fehlendem Jahr einen Punkt ohne Angabe. const teile = [ eintrag.year ? String(eintrag.year) : null, 'episodeCount' in eintrag ? `${eintrag.episodeCount} Folgen` : null, 'state' in eintrag && eintrag.state?.watched ? 'Gesehen' : null, ].filter(Boolean); unten.innerHTML = `${esc(eintrag.title)}` + `${esc(teile.join(' · '))}`; k.appendChild(unten); k.onclick = () => oeffnen(art, eintrag.id); return k; } void laden(); return wurzel; } function ladeanzeige(): HTMLElement { const d = document.createElement('div'); d.className = 'spinner'; return d; }