diff --git a/web/app/src/bilder.ts b/web/app/src/bilder.ts index dc18b99..917118a 100644 --- a/web/app/src/bilder.ts +++ b/web/app/src/bilder.ts @@ -16,6 +16,19 @@ type Bildart = 'poster' | 'backdrop' | 'logo'; * Fenster sähe man dasselbe zweimal im Monat. */ const AUFMACHER_HOECHSTENS = 5; +/** Sagt der ganzen Seite, dass ein Bild gewechselt hat. + * + * **Weil der Rückruf nur den erreicht, der den Dialog geöffnet hat.** Wer + * auf der Startseite das Plakat einer Kachel tauscht, ändert damit auch den + * Aufmacher — und der steht ganz woanders und weiss von nichts. Bisher + * musste man die Seite neu laden, um das neue Bild zu sehen. + * + * Ein Ereignis am Dokument statt eines durchgereichten Rückrufs: der + * Aufmacher hört zu, ohne dass die Kachel ihn kennen müsste. */ +export function bildgeaendertMelden(art: 'movies' | 'series', id: string): void { + document.dispatchEvent(new CustomEvent('flaro:bildgeaendert', { detail: { art, id } })); +} + /** Was jede Bildart ist — und in welcher Form sie hochgeladen wird. * * **`zuschnitt: null` beim Schriftzug, und das ist kein Versehen.** Ein @@ -212,6 +225,7 @@ export function bilderflaeche( // Kachel zeigte danach das alte Bild weiter — bis man die ganze // Oberfläche neu lud. beiAenderung?.(); + bildgeaendertMelden(art, id); hinweisZeigen(`${name} gesetzt.`); await laden(); } catch (fehler) { @@ -232,6 +246,7 @@ export function bilderflaeche( try { await bildFreigeben(art, id, bildart); beiAenderung?.(); + bildgeaendertMelden(art, id); hinweisZeigen('Kommt beim nächsten Auffrischen wieder aus der Quelle.'); await laden(); } catch (fehler) { @@ -345,6 +360,7 @@ export function bilderflaeche( async function speichern(neu: string[]): Promise { await aufmacherSetzen(art, id, neu); beiAenderung?.(); + bildgeaendertMelden(art, id); await laden(); } } @@ -412,6 +428,7 @@ export function bilderflaeche( try { await bildSetzen(art, id, bildart, kandidat!.url); beiAenderung?.(); + bildgeaendertMelden(art, id); await laden(); } catch (fehler) { diff --git a/web/app/src/card.ts b/web/app/src/card.ts index df4aa0a..d10d009 100644 --- a/web/app/src/card.ts +++ b/web/app/src/card.ts @@ -22,7 +22,14 @@ export type Kachel = { * * **Bisher nur für „Als Nächstes".** Dort heisst Weglegen etwas anderes * als in „Fortsetzen": nicht diese Folge, sondern diese Serie. */ - reihe?: 'naechste'; + reihe?: 'naechste' | 'fortsetzen'; + /** Was ein Tipp auslöst, wenn es nicht die Detailseite sein soll. + * + * **Nur in „Fortsetzen".** Dort ist die Absicht eindeutig: der Titel steht + * in der Reihe, *weil* man ihn angefangen hat, und die Stelle steht + * daneben. Überall sonst führt der Tipp auf die Seite — wer nur nachsehen + * will, wie viele Staffeln eine Serie hat, soll nicht den Film bekommen. */ + starten?: () => void; art: 'filme' | 'serien'; /** Die obere, fette Zeile. Bei einer Folge die Serie. */ titel: string; @@ -107,7 +114,7 @@ export function kachel(k: Kachel, oeffnen: (art: 'filme' | 'serien', id: string) // muss — sie liest sie vom angeklickten Element ab. knopf.dataset.id = k.id; knopf.dataset.art = k.art; - knopf.onclick = () => oeffnen(k.art, k.id); + knopf.onclick = () => (k.starten ? k.starten() : oeffnen(k.art, k.id)); // **Das Kontextmenü an jeder Kachel, überall.** Gesehen, Favorit und // Weglegen gab es nur auf der Detailseite — in einer Übersicht mit zwanzig @@ -297,14 +304,14 @@ function kachelpunkte( // weiss nur der Server. k.stand = await gesehen(k.standId ?? k.id, !istGesehen); - // **In „Als Nächstes" ist ein Haken ein Abschied.** + // **In „Fortsetzen" und „Als Nächstes" ist ein Haken ein Abschied.** // - // Die Reihe zeigt, was dran ist. Eine abgehakte Folge ist nicht mehr - // dran — sie mit einem Haken stehen zu lassen sah aus, als hätte das - // Abhaken nicht gewirkt. Gemessen: der Server lieferte beim nächsten - // Laden längst die richtige Folge, nur sah man das nie, weil die - // Reihe nicht neu geladen wurde. - if (k.reihe === 'naechste' && !istGesehen) { + // Beide Reihen zeigen, was offen ist. Eine abgehakte Folge ist nicht + // mehr offen — sie mit einem Haken stehen zu lassen sah aus, als + // hätte das Abhaken nicht gewirkt. Der Server lieferte beim nächsten + // Laden längst das Richtige; nur sah man das nie, weil die Reihe + // nicht neu geladen wurde. + if (k.reihe && !istGesehen) { knopf.remove(); return; } @@ -335,6 +342,8 @@ function kachelpunkte( text: 'Aus Fortsetzen entfernen', tun: async () => { await weglegen(k.standId ?? k.id); + // In der Reihe selbst heisst „entfernen" genau das. + if (k.reihe === 'fortsetzen') { knopf.remove(); return; } if (k.stand) k.stand.dismissed = true; knopf.querySelector('.fortschritt')?.remove(); }, diff --git a/web/app/src/views/home.ts b/web/app/src/views/home.ts index ce92f83..827c366 100644 --- a/web/app/src/views/home.ts +++ b/web/app/src/views/home.ts @@ -1,6 +1,6 @@ import { inhalt, weiterschauen, naechsteFolgen, empfehlungen, saisonal, - type Bibliothek, + type Bibliothek, type Angefangen, } from '../api'; import { leer, fehlerzeile, meldung } from '../util'; import { kachel, alsKachel, angefangenAlsKachel, reihe } from '../card'; @@ -77,6 +77,21 @@ export function startseite( }); vorschlag.items.forEach(e => dazu(alsHero(e, 'filme'))); + /** Was ein Tipp in „Fortsetzen" tut. */ + function fortsetzenStarten(e: Angefangen): void { + // Ohne Datei gibt es nichts zu starten — dann führt der Tipp + // dorthin, wo man wählen kann. Dieselbe Regel wie im Aufmacher. + if (!e.fileId) { oeffnen(e.seriesId ? 'serien' : 'filme', e.seriesId ?? e.id); return; } + spieler({ + dateiId: e.fileId, + titelId: e.id, + name: e.subTitle ?? e.title, + ab: e.state.positionSeconds, + laenge: e.durationSeconds ?? null, + danach: () => void aufbauen(), + }); + } + const aufmacher = hero(heroTitel, (art, id) => oeffnen(art, id), e => { @@ -95,9 +110,24 @@ export function startseite( signal); if (aufmacher) wurzel.appendChild(aufmacher); + // **Ein getauschtes Bild gehört sofort in den Aufmacher.** + // + // Wer auf dieser Seite das Plakat oder den Hintergrund eines Titels + // wechselt, sieht ihn gleich darüber in voller Breite — und dort stand + // bis zum nächsten Neuladen das alte. Die Kachel frischt sich selbst + // auf; der Aufmacher hat davon nichts erfahren, weil er ganz woanders + // steht. Siehe `bildgeaendertMelden`. + document.addEventListener('flaro:bildgeaendert', () => void aufbauen(), { signal }); + if (weiter.items.length) { + // **Hier startet ein Tipp.** In „Fortsetzen" steht ein Titel, *weil* + // man ihn angefangen hat, und die Stelle steht daneben — den Umweg + // über die Detailseite zu nehmen hiesse, eine Frage zu stellen, die + // schon beantwortet ist. wurzel.appendChild(reihe('Fortsetzen', - weiter.items.map(e => kachel(angefangenAlsKachel(e), oeffnen)))); + weiter.items.map(e => kachel( + { ...angefangenAlsKachel(e, 'fortsetzen'), starten: () => fortsetzenStarten(e) }, + oeffnen)))); } if (naechste.items.length) { wurzel.appendChild(reihe('Nächste Folge',