Files
Flaro/web/app/src/zuordnung.ts
T
ScarriffleandClaude Opus 5 a6e9a6e425 Ein Klick auf einen Vorschlag sah aus wie gar kein Klick
Hinter der Auswahl steckt ein Abruf bei TMDB, der Titel, Besetzung und
Bilder nachlädt — mehrere Sekunden, in denen die Oberfläche aussah wie
vorher. Wer nichts sieht, klickt noch einmal, und dann laufen zwei.
Jetzt tritt die Liste zurück, die gewählte Zeile bekommt einen
Akzentrahmen und daneben steht „Wird zugeordnet…" mit laufendem Kreis.

Dazu das Plakat je Vorschlag. „Dark" von 2017 und „Dark Shadows" von
1966 sind an einer Textzeile kaum zu unterscheiden, am Bild in einer
Zehntelsekunde — und der Server lieferte die Adresse ohnehin schon mit,
sie wurde nur weggeworfen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-04 17:58:06 +02:00

177 lines
6.4 KiB
TypeScript

import { vorschlaege, zuordnen, herkunft } from './api';
import { esc, knopf, feld, fehlerzeile, leer, meldung } from './util';
import { icons } from './icons';
/** Einen Titel einem anderen Eintrag der Metadatenquelle zuordnen.
*
* **Als eigener Dialog und nicht nur auf der Detailseite.** Wer in der
* Mediathek zwanzig falsch erkannte Titel vor sich hat, soll sie dort
* geradeziehen können — vierzig Seitenwechsel sind keine Bedienung.
*
* `fertig` wird gerufen, wenn etwas zugeordnet wurde; der Aufrufer frischt
* damit auf, was er zeigt.
*/
export function zuordnungsdialog(
art: 'movies' | 'series',
id: string,
titel: string,
jahr: number | null,
fertig?: () => void,
): void {
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 = 'Zuordnung korrigieren';
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';
// **Woher der Titel stammt, über allem anderen.**
//
// Liegt die Zuordnung völlig daneben, sagt der angezeigte Titel nichts mehr
// darüber, welche Datei hier eigentlich gemeint ist — „Dark Shadows" von
// 1966 kann alles sein. Der Ordner sagt es in einer Zeile.
const quelle = document.createElement('div');
quelle.className = 'quelle';
quelle.innerHTML = '<span class="winzig">Herkunft wird gelesen…</span>';
void herkunft(art, id).then(h => {
quelle.innerHTML = '';
const ordner = document.createElement('div');
ordner.className = 'quelle-pfad';
ordner.textContent = h.ordner ?? 'Kein gemeinsamer Ordner';
ordner.title = h.ordner ?? '';
quelle.appendChild(ordner);
const dazu = document.createElement('span');
dazu.className = 'winzig';
const namen = h.beispiele.join(' · ');
dazu.textContent = h.dateien === 0
? 'Keine Datei mehr zugeordnet.'
: `${h.dateien} ${h.dateien === 1 ? 'Datei' : 'Dateien'}`
+ (namen ? ` — ${namen}${h.dateien > h.beispiele.length ? ' …' : ''}` : '');
quelle.appendChild(dazu);
}).catch(() => {
quelle.innerHTML = '<span class="winzig">Herkunft nicht lesbar.</span>';
});
const hinweis = document.createElement('p');
hinweis.className = 'hinweis';
hinweis.textContent = 'Der gewählte Eintrag ersetzt Titel, Beschreibung, Bilder '
+ 'und Besetzung. Von Hand gewählte Bilder bleiben.';
const suche = feld('Anderer Suchbegriff');
suche.value = titel;
const jahrfeld = feld('Jahr', 'number');
if (jahr) jahrfeld.value = String(jahr);
const suchen = knopf('Suchen', 'btn');
const reihe = document.createElement('div');
reihe.className = 'knopfreihe';
reihe.append(suche, jahrfeld, suchen);
const treffer = document.createElement('div');
treffer.className = 'einstellungen';
koerper.append(quelle, hinweis, reihe, treffer);
d.append(kopf, koerper);
d.addEventListener('close', () => d.remove());
d.addEventListener('click', e => { if (e.target === d) d.close(); });
// Enter im Suchfeld sucht, statt den Dialog zu schliessen.
suche.onkeydown = e => { if (e.key === 'Enter') { e.preventDefault(); void fragen(); } };
document.body.appendChild(d);
d.showModal();
suchen.onclick = () => void fragen();
void fragen();
async function fragen(): Promise<void> {
treffer.innerHTML = '<div class="spinner"></div>';
try {
const liste = await vorschlaege(art, id,
suche.value.trim(), jahrfeld.value ? Number(jahrfeld.value) : undefined);
treffer.innerHTML = '';
if (liste.length === 0) { treffer.appendChild(leer('Nichts gefunden.')); return; }
const zeilen: HTMLButtonElement[] = [];
liste.forEach(v => {
const zeile = document.createElement('button');
zeile.className = 'vorschlag';
// **Mit Plakat.** „Dark" von 2017 und „Dark Shadows" von 1966 sind an
// einer Textzeile kaum zu unterscheiden, am Plakat in einer
// Zehntelsekunde. Der Server liefert die Adresse längst mit; sie wurde
// bisher weggeworfen.
const bild = document.createElement('div');
bild.className = 'vorschlag-bild';
if (v.posterUrl) {
const img = document.createElement('img');
img.src = v.posterUrl;
img.alt = '';
img.loading = 'lazy';
img.onload = () => bild.classList.add('geladen');
bild.appendChild(img);
}
const text = document.createElement('div');
text.className = 'vorschlag-text';
text.innerHTML =
`<div class="zeile"><strong>${esc(v.title)}</strong>`
+ `<span class="winzig">${esc(v.year ?? '')} · TMDB ${v.tmdbId}</span></div>`
+ `<div class="hinweis">${esc((v.overview ?? '').slice(0, 160))}</div>`;
zeile.append(bild, text);
zeile.onclick = async () => {
// **Sichtbar machen, dass etwas passiert.**
//
// Ein Klick löste einen Abruf bei TMDB aus, der Bilder nachlädt und
// mehrere Sekunden braucht — und die Oberfläche sah aus wie vorher.
// Wer nichts sieht, klickt noch einmal, und dann laufen zwei.
zeilen.forEach(z => { z.disabled = true; });
zeile.classList.add('laeuft');
const lauf = document.createElement('span');
lauf.className = 'vorschlag-lauf';
lauf.innerHTML = '<span class="spinner klein"></span><span>Wird zugeordnet…</span>';
zeile.appendChild(lauf);
try {
await zuordnen(art, id, v.tmdbId);
// Erst melden, dann schliessen: sonst blitzt nur der Dialog weg,
// und es bleibt offen, ob es geklappt hat.
lauf.innerHTML = '<span>Übernommen.</span>';
d.close();
fertig?.();
} catch (fehler) {
lauf.remove();
zeile.classList.remove('laeuft');
zeilen.forEach(z => { z.disabled = false; });
treffer.appendChild(fehlerzeile(meldung(fehler)));
}
};
zeilen.push(zeile);
treffer.appendChild(zeile);
});
} catch (fehler) {
treffer.innerHTML = '';
treffer.appendChild(fehlerzeile(meldung(fehler)));
}
}
}