Vierundzwanzig Bilder, und der Weg dorthin fehlte sowieso
Zwei Dinge, die zusammengehören. **Den Bilderdialog gab es nur an der Kachel.** Wer auf der Seite eines Films stand und sah, dass das Plakat falsch ist, stand genau dort, wo er nichts dagegen tun konnte — der Weg führte zurück in die Liste und über das Dreipunktmenü. Jetzt steht „Bearbeiten" in der Knopfreihe der Detailseite, bei Filmen wie bei Serien, und nur im Kopf: dieselbe Reihe steht auch über jeder Fassung und jeder Folge. **Und es waren vierundzwanzig, weil dort vierundzwanzig stand.** Bei „365 Tage" hat TMDB neunundsechzig Plakate. Dazu ein zweiter, leiserer Filter: gefragt wurden nur drei Sprachen. Fürs automatische Holen ist das richtig — dort wird eines genommen, und es soll passen. Wer von Hand wählt, will sehen, was es gibt; sortiert wird ohnehin nach Sprache, also steht das Passende weiterhin oben. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
78e347c58d
commit
bfe263d495
@@ -420,6 +420,9 @@ public sealed class TmdbClient(
|
||||
/// Schriftzug weggeworfen. Die Auswahl kostet also keine zusätzliche
|
||||
/// Anfrage.
|
||||
/// </remarks>
|
||||
/// <summary>Wie viele Bilder je Art höchstens zur Wahl stehen.</summary>
|
||||
private const int HöchstensBilderJeArt = 200;
|
||||
|
||||
public async Task<IReadOnlyList<Bildkandidat>> BilderAsync(
|
||||
int providerId, bool serie, CancellationToken ct = default)
|
||||
{
|
||||
@@ -428,9 +431,16 @@ public sealed class TmdbClient(
|
||||
|
||||
var sprache = Sprachkürzel(einstellung.Language);
|
||||
var weg = serie ? "tv" : "movie";
|
||||
|
||||
// <b>Ohne Sprachfilter.</b> Hier stand
|
||||
// <c>include_image_language={sprache},en,null</c> — drei von
|
||||
// vierzig Sprachen, und alles andere kam gar nicht erst an. Für das
|
||||
// automatische Holen ist das richtig: dort wird eines genommen, und
|
||||
// es soll das passende sein. Hier wählt ein Mensch, und der will
|
||||
// sehen, was es gibt. Die Reihenfolge sortiert ohnehin nach
|
||||
// Sprache, also steht das Passende weiterhin oben.
|
||||
var antwort = await HolenAsync<TmdbBilderAntwort>(
|
||||
$"{weg}/{providerId}/images?include_image_language={Uri.EscapeDataString(sprache)},en,null",
|
||||
einstellung, ct);
|
||||
$"{weg}/{providerId}/images", einstellung, ct);
|
||||
|
||||
if (antwort is null) return [];
|
||||
|
||||
@@ -448,7 +458,13 @@ public sealed class TmdbClient(
|
||||
foreach (var b in (bilder ?? []).Where(b => !string.IsNullOrWhiteSpace(b.FilePath))
|
||||
.OrderBy(b => Sprachrang(b.Language, sprache))
|
||||
.ThenByDescending(b => b.VoteAverage ?? 0)
|
||||
.Take(24))
|
||||
// <b>Nicht vierundzwanzig.</b> Bei „365 Tage" hat
|
||||
// TMDB neunundsechzig Plakate, und die Liste war
|
||||
// nach vierundzwanzig zu Ende — ohne dass irgendwo
|
||||
// stünde, dass da noch mehr ist. Die Grenze bleibt
|
||||
// als Schutz vor einer Seite mit tausend Bildern,
|
||||
// liegt aber dort, wo sie niemanden mehr behindert.
|
||||
.Take(HöchstensBilderJeArt))
|
||||
{
|
||||
liste.Add(new Bildkandidat(
|
||||
art, Bild(b.FilePath, groesse)!.Url,
|
||||
|
||||
@@ -28,6 +28,50 @@ public class TmdbClientTests
|
||||
Assert.That(attrappe.Aufrufe, Is.Empty, "Ohne Schlüssel darf keine Anfrage hinausgehen");
|
||||
}
|
||||
|
||||
/// <summary>Die Bilderliste für die Auswahl von Hand.</summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <b>Zwei Grenzen, die beide störten.</b> Die Anfrage holte nur drei
|
||||
/// Sprachen, und gezählt wurde bei vierundzwanzig je Art zu Ende — bei
|
||||
/// „365 Tage" hat TMDB neunundsechzig Plakate, und die fehlenden
|
||||
/// fünfundvierzig sah niemand. Wer von Hand wählt, soll sehen, was es
|
||||
/// gibt.
|
||||
/// </remarks>
|
||||
[Test]
|
||||
public async Task Die_Bilderliste_ist_nicht_bei_vierundzwanzig_zu_Ende()
|
||||
{
|
||||
var plakate = string.Join(",", Enumerable.Range(0, 69)
|
||||
.Select(i => $$"""{"file_path":"/p{{i}}.jpg","iso_639_1":"en","vote_average":1.0}"""));
|
||||
var attrappe = Attrappe.MitJson($$"""{"posters":[{{plakate}}],"backdrops":[],"logos":[]}""");
|
||||
|
||||
var bilder = await Client(attrappe).BilderAsync(603, serie: false);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(bilder.Count(b => b.Kind == "poster"), Is.EqualTo(69));
|
||||
Assert.That(attrappe.Aufrufe[0], Does.Not.Contain("include_image_language"),
|
||||
"Die Auswahl von Hand soll alle Sprachen sehen");
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>Die passende Sprache steht trotzdem oben.</summary>
|
||||
[Test]
|
||||
public async Task Die_eigene_Sprache_steht_vorn()
|
||||
{
|
||||
var attrappe = Attrappe.MitJson("""
|
||||
{"posters":[
|
||||
{"file_path":"/en.jpg","iso_639_1":"en","vote_average":9.0},
|
||||
{"file_path":"/de.jpg","iso_639_1":"de","vote_average":1.0},
|
||||
{"file_path":"/ja.jpg","iso_639_1":"ja","vote_average":8.0}],
|
||||
"backdrops":[],"logos":[]}
|
||||
""");
|
||||
|
||||
var bilder = await Client(attrappe, FesteEinstellungen.MitSchluessel("de-DE"))
|
||||
.BilderAsync(603, serie: false);
|
||||
|
||||
Assert.That(bilder[0].Url, Does.Contain("/de.jpg"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Ein_Film_wird_gelesen()
|
||||
{
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
} from '../sprachwahl';
|
||||
import { menueAnZiel } from '../menue';
|
||||
import { meldedialog } from '../melden';
|
||||
import { bearbeiten } from '../bearbeiten';
|
||||
|
||||
type Art = 'filme' | 'serien';
|
||||
|
||||
@@ -475,10 +476,36 @@ export function detailseite(art: Art, id: string, zurueck: () => void): HTMLElem
|
||||
}));
|
||||
}
|
||||
|
||||
// **Nicht über `rundKnopf`.** Der schaltet sich beim Klick ab und erst im
|
||||
// Fehlerfall wieder an — richtig für etwas, das danach die Seite neu lädt.
|
||||
// Ein Dialog kehrt sofort zurück; der Knopf bliebe für den Rest der Seite
|
||||
// tot.
|
||||
// **Bearbeiten — und damit die Bilder — von hier aus erreichbar.**
|
||||
//
|
||||
// Den Dialog gab es längst, aber nur am Dreipunktmenü einer Kachel und
|
||||
// in zwei Einstellungsseiten. Wer auf der Seite eines Films steht und
|
||||
// sieht, dass das Plakat falsch ist, stand genau dort, wo er nichts
|
||||
// dagegen tun konnte.
|
||||
//
|
||||
// **Nur im Kopf der Seite** (daran erkennbar, dass `melden` gesetzt ist):
|
||||
// dieselbe Reihe steht über jeder Fassung und jeder Folge, und dort
|
||||
// meinte der Knopf jedes Mal denselben Titel.
|
||||
//
|
||||
// Wie der Melden-Knopf nicht über `rundKnopf`: der schaltet sich beim
|
||||
// Klick ab und erst im Fehlerfall wieder an — richtig für etwas, das
|
||||
// danach neu lädt, falsch für einen Dialog, der sofort zurückkehrt.
|
||||
if (melden && istAdmin()) {
|
||||
const b = document.createElement('button');
|
||||
b.className = 'rund';
|
||||
b.innerHTML = icons.stift;
|
||||
b.title = 'Bearbeiten';
|
||||
b.setAttribute('aria-label', 'Bearbeiten');
|
||||
b.onclick = () => bearbeiten(
|
||||
art === 'serien' ? 'series' : 'movies', eintrag.id,
|
||||
ergebnis => {
|
||||
// Beim Zusammenlegen überlebt eine andere Kennung — dann führt
|
||||
// diese Seite ins Leere, und zurück ist die ehrlichere Antwort.
|
||||
if (ergebnis.verschmolzen) zurueck(); else void laden();
|
||||
});
|
||||
reihe.appendChild(b);
|
||||
}
|
||||
|
||||
if (melden) {
|
||||
const m = document.createElement('button');
|
||||
m.className = 'rund';
|
||||
|
||||
Reference in New Issue
Block a user