Files
Flaro/web/app/src/zuordnung.ts
T
ScarriffleandClaude Opus 5 0c07d4efc7 Ein falscher Treffer verhinderte seine eigene Korrektur
„Doctor Strange" war fälschlich auf ein Making-of von 2022 gelaufen. Beim
Korrigieren hängte die Vorschlagssuche `year=2022` an — aus genau der
Zuordnung, die weg sollte — und lieferte nur noch Titel von 2022. Der
richtige Film von 2016 konnte gar nicht erscheinen, egal wie oft man
suchte.

An TMDB nachgemessen:

  query='Doctor Strange' ohne Jahr     → 2016 Doctor Strange, 2022 Multiverse …
  query='Doctor Strange' mit year=2022 → genau die drei Treffer aus der Meldung

Gesucht wird jetzt mit dem Jahr aus dem Dateinamen, nie mit dem aus dem
Treffer. Die Oberfläche trägt ebenfalls keines mehr vor: das angezeigte
stammt aus derselben falschen Zuordnung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-04 21:14:55 +02:00

180 lines
6.6 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;
// Leer heisst: der Server nimmt das Jahr aus dem Dateinamen. Hier etwas
// vorzutragen, das aus der zu korrigierenden Zuordnung stammt, hiesse die
// Suche auf genau den falschen Jahrgang einzugrenzen.
const jahrfeld = feld('Jahr (aus dem Dateinamen)', '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)));
}
}
}