import { bildAdresse, bildauswahl, bildSetzen, bildHochladen, type Bildkandidat } from './api';
import { knopf, fehlerzeile, meldung } from './util';
import { icons } from './icons';
import { beimSeitenwechselSchliessen } from './dialog';
type Bildart = 'poster' | 'backdrop' | 'logo';
const ARTEN: { art: Bildart; titel: string; verhaeltnis: string }[] = [
{ art: 'poster', titel: 'Plakat', verhaeltnis: '2 / 3' },
{ art: 'backdrop', titel: 'Hintergrund', verhaeltnis: '16 / 9' },
{ art: 'logo', titel: 'Schriftzug', verhaeltnis: '16 / 9' },
];
/** Plakat, Hintergrund und Schriftzug von Hand wählen.
*
* **Erreichbar aus der Übersicht, nicht erst von der Detailseite.** Bei
* zwanzig Titeln mit schlechtem Plakat waren das vierzig Seitenwechsel.
*
* **Die Kandidaten kosten nichts.** TMDB liefert sie beim Einzelabruf ohnehin
* mit; bisher wurde alles ausser dem Schriftzug weggeworfen.
*/
/** Bilder eines Titels austauschen.
*
* **`fertig` wird gerufen, wenn sich wirklich etwas geändert hat** — und erst
* beim Schliessen, nicht nach jeder einzelnen Wahl. Wer drei Bilder tauscht,
* löst nicht dreimal einen Neuaufbau der Kachel aus. Wer nur hineinschaut und
* wieder zumacht, gar keinen.
*
* **Warum es den Rückruf überhaupt gibt.** Ohne ihn zeigte die Kachel das
* alte Plakat weiter, bis man die ganze Oberfläche neu lud — und verlor dabei
* Bildlauf, Filter und offene Reiter.
*/
export function bilddialog(
art: 'movies' | 'series', id: string, titel: string, fertig?: () => void,
): void {
let geaendert = false;
const d = document.createElement('dialog');
d.className = 'dlg dlg-breit';
const kopf = document.createElement('div');
kopf.className = 'dlg-kopf';
const h = document.createElement('h2');
h.textContent = `Bilder — ${titel}`;
const zu = document.createElement('button');
zu.className = 'dlg-zu';
zu.innerHTML = icons.schliessen;
zu.setAttribute('aria-label', 'Schliessen');
zu.onclick = () => d.close();
kopf.append(h, zu);
const koerper = document.createElement('div');
koerper.className = 'dlg-koerper';
d.append(kopf, koerper);
d.addEventListener('close', () => { d.remove(); if (geaendert) fertig?.(); });
d.addEventListener('click', e => { if (e.target === d) d.close(); });
document.body.appendChild(d);
d.showModal();
beimSeitenwechselSchliessen(d);
bilderflaeche(koerper, art, id, () => { geaendert = true; });
}
/** Dieselbe Bildauswahl, aber in eine beliebige Fläche gezeichnet.
*
* **Damit sie als Reiter in „Bearbeiten" stehen kann.**
*
* Vorher ging von dort ein zweiter Dialog über dem ersten auf — ein Fenster
* über einem Fenster, und man wusste nicht mehr, was man eigentlich zumacht.
* Der eigene Dialog bleibt für alle, die nur die Bilder wollen; er ist jetzt
* nur noch ein Rahmen um diese Fläche.
*/
export function bilderflaeche(
koerper: HTMLElement,
art: 'movies' | 'series',
id: string,
beiAenderung?: () => void,
): void {
koerper.innerHTML = '
';
/** Welcher Reiter offen ist — überlebt das Neuzeichnen nach jeder Wahl. */
let offen: Bildart = 'poster';
void laden();
async function laden(): Promise {
let auswahl;
try {
auswahl = await bildauswahl(art, id);
} catch (fehler) {
koerper.innerHTML = '';
koerper.appendChild(fehlerzeile(meldung(fehler)));
return;
}
koerper.innerHTML = '';
if (auswahl.candidates.length === 0) {
const hinweis = document.createElement('p');
hinweis.className = 'hinweis';
// Ohne Zuordnung gibt es nichts zu wählen — dann ist die Zuordnung das
// Problem, nicht das Bild, und das soll hier auch so dastehen.
hinweis.textContent = 'Keine Bilder gefunden. Ist der Titel richtig zugeordnet?';
koerper.appendChild(hinweis);
}
// **Ein Reiter je Bildtyp statt drei Blöcke untereinander.**
//
// Vorher scrollte der Dialogkörper, und jedes der drei Raster darin
// scrollte noch einmal. Zwei Ebenen übereinander heisst: das Rad trifft
// mal die eine, mal die andere, je nachdem wo der Zeiger steht — und man
// landet nie dort, wo man hinwollte. Jetzt ist genau ein Typ sichtbar und
// genau eine Fläche scrollt.
const reiter = document.createElement('div');
reiter.className = 'bildreiter';
reiter.setAttribute('role', 'tablist');
const buehne = document.createElement('div');
buehne.className = 'bildbuehne';
koerper.append(reiter, buehne);
const bloecke = new Map();
ARTEN.forEach(({ art: bildart, titel: name, verhaeltnis }) => {
const aktuell = bildart === 'poster' ? auswahl.current.poster
: bildart === 'backdrop' ? auswahl.current.backdrop
: auswahl.current.logo;
const passende = auswahl.candidates.filter(k => k.kind === bildart);
const knopfReiter = document.createElement('button');
knopfReiter.type = 'button';
knopfReiter.className = 'bildreiter-knopf' + (bildart === offen ? ' an' : '');
knopfReiter.setAttribute('role', 'tab');
knopfReiter.setAttribute('aria-selected', String(bildart === offen));
// Die Zahl dazu: so sieht man vor dem Klicken, ob es dort etwas gibt.
knopfReiter.innerHTML = `${name}`
+ `${passende.length}`;
knopfReiter.onclick = () => { offen = bildart; void laden(); };
reiter.appendChild(knopfReiter);
if (bildart !== offen) return;
const abschnitt = document.createElement('section');
abschnitt.className = 'bildblock';
const kopfzeile = document.createElement('div');
kopfzeile.className = 'knopfreihe';
const u = document.createElement('h3');
u.textContent = aktuell ? `${name} — eines ist gesetzt` : name;
kopfzeile.appendChild(u);
// Eigenes Bild — ein verstecktes Dateifeld hinter einem Knopf, weil
// das native Dateifeld in jedem Browser anders aussieht.
const feld = document.createElement('input');
feld.type = 'file';
feld.accept = 'image/*';
feld.className = 'hidden';
const eigenes = knopf('Eigenes Bild…', 'btn');
eigenes.type = 'button';
eigenes.onclick = () => feld.click();
feld.onchange = async () => {
const datei = feld.files?.[0];
if (!datei) return;
eigenes.disabled = true;
try { await bildHochladen(art, id, bildart, datei); await laden(); }
catch (fehler) {
abschnitt.appendChild(fehlerzeile(meldung(fehler)));
eigenes.disabled = false;
}
};
kopfzeile.append(eigenes, feld);
abschnitt.appendChild(kopfzeile);
if (passende.length === 0 && !aktuell) {
const leer = document.createElement('p');
leer.className = 'winzig';
leer.textContent = 'Nichts zur Auswahl.';
abschnitt.append(leer);
buehne.appendChild(abschnitt);
bloecke.set(bildart, abschnitt);
return;
}
const gitter = document.createElement('div');
gitter.className = 'bildgitter';
gitter.style.setProperty('--verhaeltnis', verhaeltnis);
// Das gesetzte Bild zuerst, damit sichtbar ist, wovon man weggeht.
if (aktuell) gitter.appendChild(kachelFuer(null, bildAdresse(aktuell)!, true, bildart));
passende.forEach(k => gitter.appendChild(kachelFuer(k, k.thumbUrl, false, bildart)));
abschnitt.appendChild(gitter);
buehne.appendChild(abschnitt);
bloecke.set(bildart, abschnitt);
});
}
function kachelFuer(
kandidat: Bildkandidat | null, adresse: string, gesetzt: boolean, bildart: Bildart,
): HTMLElement {
const b = document.createElement('button');
b.type = 'button';
b.className = 'bildwahl' + (gesetzt ? ' an' : '');
b.title = gesetzt ? 'Aktuell gesetzt'
: [kandidat?.language?.toUpperCase(), kandidat?.vote ? `★ ${kandidat.vote.toFixed(1)}` : null]
.filter(Boolean).join(' · ') || 'Auswählen';
const bild = document.createElement('img');
bild.src = adresse;
bild.alt = '';
bild.loading = 'lazy';
b.appendChild(bild);
if (gesetzt) {
const marke = document.createElement('span');
marke.className = 'bildwahl-marke';
marke.innerHTML = icons.haken;
b.appendChild(marke);
b.disabled = true;
return b;
}
b.onclick = async () => {
b.disabled = true;
try {
await bildSetzen(art, id, bildart, kandidat!.url);
beiAenderung?.();
await laden();
}
catch (fehler) {
b.after(fehlerzeile(meldung(fehler)));
b.disabled = false;
}
};
return b;
}
}