import { inhalt, weiterschauen, naechsteFolgen, empfehlungen, saisonal, type Bibliothek, } from '../api'; import { leer, fehlerzeile, meldung } from '../util'; import { kachel, alsKachel, angefangenAlsKachel, reihe } from '../card'; import { hero, alsHero, angefangenAlsHero, type Heroeintrag } from '../hero'; import { spieler } from '../spieler'; type Art = 'filme' | 'serien'; /** Die Startseite — waagerechte Reihen, wie bei Plex. * * Oben, was angefangen ist. Darunter **je Mediathek eine Reihe** mit dem * zuletzt Hinzugefügten: wer „Filme" und „Dokumentationen" getrennt angelegt * hat, hat sie getrennt gemeint, und eine Startseite, die sie wieder * zusammenwirft, nimmt die Entscheidung zurück. * * Eine Reihe ohne Inhalt wird gar nicht erst gezeigt. */ export function startseite( bibliotheken: Bibliothek[], oeffnen: (art: Art, id: string) => void, alleZeigen: (b: Bibliothek) => void, /** **Wird gebraucht, nicht nur mitgeführt.** Die Startseite wird gemerkt; * ohne dieses Signal lief der Takt des Aufmachers weiter, während man * längst woanders war, und seine Zuhörer am Fenster blieben hängen. */ signal?: AbortSignal, ): HTMLElement { const wurzel = document.createElement('div'); wurzel.innerHTML = '
'; // **Benannt, damit der Spieler sie danach neu bauen kann.** Wer aus dem // Aufmacher heraus zu Ende schaut, soll die Zeile „Fortsetzen" nicht // unverändert wiederfinden. void aufbauen(); async function aufbauen(): Promise { try { // **Alles auf einmal anfragen.** Bei fünf Mediatheken wären es sonst // acht Anfragen nacheinander, und die Startseite baut sich vor den // Augen des Benutzers Zeile für Zeile auf. const [weiter, naechste, vorschlag, saison, ...bestaende] = await Promise.all([ weiterschauen(), naechsteFolgen(), empfehlungen(24).catch(() => ({ items: [], total: 0 })), // **Scheitert sie, fehlt eine Reihe und nicht die Startseite.** Ein // älterer Server kennt den Weg nicht; das darf nicht dazu führen, // dass gar nichts erscheint. saisonal(24).catch(() => ({ occasion: null, items: { items: [], total: 0 } })), ...bibliotheken.map(b => inhalt(b, { sort: 'added', take: 24 })), ]); wurzel.innerHTML = ''; if (bibliotheken.length === 0) { wurzel.appendChild(leer('Noch keine Mediathek. Lege unter Verwaltung eine an ' + 'und starte einen Scan.')); return; } // **Der Aufmacher, aus dem, was ohnehin schon geholt wurde.** // Erst Angefangenes — wer mittendrin ist, will dort weiter —, dann // Neuzugänge, dann Vorschläge. Titel ohne Hintergrundbild fallen im // Hero selbst heraus; ohne einen einzigen entfällt er ganz. const heroTitel: Heroeintrag[] = []; const schonDa = new Set(); const dazu = (e: Heroeintrag) => { if (schonDa.has(e.id) || !e.hintergrund) return; schonDa.add(e.id); heroTitel.push(e); }; weiter.items.forEach(e => dazu(angefangenAlsHero(e))); bibliotheken.forEach((b, i) => { const art: Art = b.kind === 'Shows' ? 'serien' : 'filme'; bestaende[i]?.items.forEach(e => dazu(alsHero(e as never, art))); }); vorschlag.items.forEach(e => dazu(alsHero(e, 'filme'))); const aufmacher = hero(heroTitel, (art, id) => oeffnen(art, id), e => { // Ohne Datei gibt es nichts zu starten — dann führt der Knopf // dorthin, wo man wählen kann. if (e.dateiId === null) { oeffnen(e.art, e.id); return; } spieler({ dateiId: e.dateiId, titelId: e.standId, name: e.titel, ab: e.stelle, laenge: e.laenge, danach: () => void aufbauen(), }); }, signal); if (aufmacher) wurzel.appendChild(aufmacher); if (weiter.items.length) { wurzel.appendChild(reihe('Fortsetzen', weiter.items.map(e => kachel(angefangenAlsKachel(e), oeffnen)))); } if (naechste.items.length) { wurzel.appendChild(reihe('Nächste Folge', naechste.items.map(e => kachel(angefangenAlsKachel(e, 'naechste'), oeffnen)))); } // **Weit oben, weil sie nur jetzt gilt.** „Zu Halloween" unter den // Neuzugängen wäre im November verschwunden, ohne dass jemand sie // gesehen hat. Ohne Anlass oder ohne Treffer kommt `occasion` leer // zurück — dann steht hier gar keine Reihe statt einer mit // Überschrift und nichts darunter. if (saison.occasion && saison.items.items.length) { wurzel.appendChild(reihe(saison.occasion, saison.items.items.map(e => kachel(alsKachel(e), oeffnen)))); } let etwasDa = false; bibliotheken.forEach((b, i) => { const seite = bestaende[i]; if (!seite?.items.length) return; etwasDa = true; wurzel.appendChild(reihe( // Plex schreibt den Namen der Mediathek in die Überschrift — // „Zuletzt in Filme hinzugefügt". Bei mehreren Mediatheken ist das // der Unterschied zwischen einer Reihe und sieben gleich // aussehenden. `Zuletzt in ${b.name} hinzugefügt`, seite.items.map(e => kachel(alsKachel(e), oeffnen)), seite.total > seite.items.length ? { text: 'Alle zeigen', tun: () => alleZeigen(b) } : undefined)); }); if (vorschlag.items.length) { wurzel.appendChild(reihe('Weil du Ähnliches gesehen hast', vorschlag.items.map(e => kachel(alsKachel(e), oeffnen)))); } if (!etwasDa && !weiter.items.length && !naechste.items.length) { wurzel.appendChild(leer('Noch nichts da. Starte unter Verwaltung einen Scan.')); } } catch (fehler) { wurzel.innerHTML = ''; wurzel.appendChild(fehlerzeile(meldung(fehler))); } } return wurzel; }