Die Oberfläche nach Plex' Vorbild — Hintergrund, Schriftzug, Besetzung

Nach den Bildern deiner Plex-Installation umgebaut. Übernommen ist der
Aufbau, nicht die Farbe: Plex ist bernsteingelb, Flaro bleibt Orchidee.

**Kopfleiste oben** mit Marke, Suche und Konto. Die Suche gehört dorthin und
nicht in jede Mediathek: wer einen Titel sucht, weiss oft gar nicht, in
welcher er liegt. Sie geht quer durch alles.

**Seitenleiste mit Icons** — echte SVG, keine Zeichen aus einer Schriftart.
Ein ▶ aus Unicode sieht auf jedem System anders aus, lässt sich nicht
einfärben und springt in der Grundlinie. Die aktive Zeile bekommt einen
schmalen Balken links statt eines leuchtenden Blocks; das liest sich in
einer langen Liste ruhiger.

**Die Kachel steht jetzt an einer Stelle** statt an fünfen. Sie trägt den
Haken für Gesehenes in der Ecke — wie bei Plex, ein Blick über das Raster
genügt —, die Zahl offener Folgen an derselben Stelle, den Fortschrittsbalken
unten und beim Überfahren einen Abspielknopf. Folgen sind dreizeilig: Serie,
Folgentitel, „S14 · F22".

**Reihen haben Pfeile.** Zwanzig Kacheln passen nicht in den Bildschirm, und
quer zu scrollen geht mit dem Rad nur mit Umschalttaste. Die Überschrift
nennt die Mediathek beim Namen: „Zuletzt in Dokumentationen hinzugefügt".

**Mediatheken haben drei Reiter** — Empfehlungen, Mediathek, Kategorien.
Darunter eine Filterzeile mit Genre, Sortierung und der Anzahl. Die
Kategorien sind farbige Kacheln; die Farbe kommt aus dem Namen, nicht aus
einer festen Liste, die jedes Genre kennen müsste, das TMDB je erfindet.

**Die Detailseite** hat jetzt den unscharfen Hintergrund über die ganze
Seite, den **Schriftzug des Films** statt der Systemschrift, eine Zeile mit
Jahr, Genres, Laufzeit, Altersfreigabe und Bewertung, runde Knöpfe für
gesehen/Favorit/weglegen, die Medienangaben als Tabelle — Datei, Video, Ton,
Untertitel — und zuletzt Besetzung & Stab mit runden Porträts.

**Dazu am Server:** ein Name. Ohne ihn zeigten die Clients den Hostnamen,
und die App schrieb „localhost" über die Anmeldung. Vorgabe ist der
Rechnername des Containers.

---

**Der Fehler, den das erste Bild zeigte:** die Lupe in der Suchleiste war
dreihundert Bildpunkte gross und lag über der halben Seite. Ein rohes
`<svg>` ohne Breite füllt seinen Container; die Regel galt nur für `.icon`.
Jetzt für jedes.

368 Tests grün. Am Container nachgesehen: „Der Untergang" mit eigenem
Schriftzug, FSK 12, Bewertung 7,9, Genres Drama/Historie/Kriegsfilm, beide
Tonspuren in der Tabelle, 22 Porträts darunter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-04 01:04:27 +02:00
co-authored by Claude Opus 5
parent ede4069cd4
commit 8c395ac051
15 changed files with 1428 additions and 693 deletions
+4 -1
View File
@@ -5,7 +5,10 @@ namespace Flaro.Api.Endpoints;
// und bricht Clients, die niemand mehr aktualisiert.
public sealed record SystemInfo(
string Version, bool NeedsSetup, IReadOnlyList<AttributionResponse> Attributions);
string Version, string ServerName, bool NeedsSetup,
IReadOnlyList<AttributionResponse> Attributions);
public sealed record ServerNameRequest(string Name);
public sealed record SetupRequest(string UserName, string Password);
@@ -1,4 +1,5 @@
using Flaro.Core.Users;
using Flaro.Core;
using Flaro.Metadata;
using Flaro.Data;
using Microsoft.EntityFrameworkCore;
@@ -16,11 +17,23 @@ public static class SystemEndpoints
/// </remarks>
private static readonly SemaphoreSlim EinrichtungsSchloss = new(1, 1);
/// <summary>Unter welchem Schlüssel der Servername liegt.</summary>
public const string NameSchlüssel = "server.name";
public static void MapSystem(this IEndpointRouteBuilder app)
{
app.MapGet("/api/v1/system/info", async (FlaroContext db) =>
Results.Ok(new SystemInfo(
Version: typeof(SystemEndpoints).Assembly.GetName().Version?.ToString(3) ?? "0.0.0",
// **Wie dieser Server heisst.** Ohne ihn zeigen die Clients
// den Hostnamen — „localhost", „192.168.1.50" —, und wer zwei
// Server hat, unterscheidet sie an einer Zahl.
//
// Vorgabe ist der Rechnername des Containers: besser als
// nichts und besser als „Flaro", weil davon jeder einen hat.
ServerName: (await db.Settings.AsNoTracking()
.FirstOrDefaultAsync(e => e.Key == NameSchlüssel))?.Value
?? Environment.MachineName,
NeedsSetup: !await db.Users.AnyAsync(),
// Ohne Anmeldung abrufbar: ein Client muss den Hinweis auch
// auf dem Anmeldebildschirm zeigen können.
@@ -30,6 +43,23 @@ public static class SystemEndpoints
])))
.AllowAnonymous();
app.MapPut("/api/v1/system/name", async (ServerNameRequest anfrage, FlaroContext db) =>
{
var name = anfrage.Name.Trim();
if (name.Length == 0)
return Results.ValidationProblem(new Dictionary<string, string[]>
{
["name"] = ["Ein Name darf nicht leer sein."],
});
var vorhanden = await db.Settings.FirstOrDefaultAsync(e => e.Key == NameSchlüssel);
if (vorhanden is null) db.Settings.Add(new ServerSetting { Key = NameSchlüssel, Value = name });
else vorhanden.Value = name;
await db.SaveChangesAsync();
return Results.NoContent();
}).RequireAuthorization(FlaroPolicies.Administrator);
app.MapPost("/api/v1/system/setup", async (SetupRequest anfrage, FlaroContext db) =>
{
if (string.IsNullOrWhiteSpace(anfrage.UserName) || anfrage.Password.Length < 8)