Die Höhe des Aufmachers ist jetzt einstellbar
Vier Stufen unter Einstellungen → Verwaltung → Übersicht: niedrig, mittel, hoch, sehr hoch. Mittel ist, was gestern fest verdrahtet war. **Keine Pixelzahl.** Eine einzustellen hiesse, sie an jedem Bildschirm neu einzustellen — der Aufmacher misst sich an der Fensterhöhe, nicht an einer festen Grösse. Gemessen bei 800 Bildpunkten Fensterhöhe: 496, 576, 656, 736. Oben ist Schluss, bevor von der ersten Plakatreihe nichts mehr übrig bleibt; sie soll zeigen, dass unten noch etwas kommt. Am Telefon bleibt es flacher, dort wäre auch das weg. **Sie reist mit der Systemauskunft mit**, nicht in einer eigenen Abfrage. Die holt der Client ohnehin als Erstes, noch vor der Anmeldung; käme die Höhe später, stünde der Aufmacher einen Augenblick in der Vorgabe und spränge dann — genau das, was eine einstellbare Höhe vermeiden soll. Gesetzt wird sie als Marke am `<html>`, das Regelwerk hängt vier Werte daran. Serverweit und nur für Admins, dieselbe Linie wie bei den Wiedergaberegeln: eine Entscheidung des Gastgebers, nicht des Gastes. Unbekanntes in der Spalte wird zur Vorgabe statt zu einem Fehler — eine Startseite, die an einem alten Eintrag scheitert, wäre die schlechtere Antwort. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
a7a4419626
commit
cdea8e3b0a
@@ -0,0 +1,47 @@
|
|||||||
|
namespace Flaro.Api;
|
||||||
|
|
||||||
|
/// <summary>Wie hoch der Aufmacher auf der Startseite steht.</summary>
|
||||||
|
///
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// <b>Vier Stufen, keine Zahl.</b> Eine Pixelzahl einzustellen hiesse, sie
|
||||||
|
/// für jeden Bildschirm neu einzustellen — der Aufmacher misst sich an der
|
||||||
|
/// Fensterhöhe, nicht an einer festen Grösse. Vier Wörter sind das, was man
|
||||||
|
/// dazu zu sagen hat.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// <b>Was die Stufen bedeuten, steht im Regelwerk</b>, nicht hier: der Server
|
||||||
|
/// weiss nichts von Bildpunkten, er merkt sich nur die Wahl. Nachzulesen bei
|
||||||
|
/// <c>.hero</c> in <c>style.css</c>.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// <b>Serverweit.</b> Dieselbe Linie wie bei den Wiedergaberegeln: eine
|
||||||
|
/// Entscheidung des Gastgebers, nicht des Gastes.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public static class Aufmacherhoehe
|
||||||
|
{
|
||||||
|
public const string Schlüssel = "darstellung.aufmacher.hoehe";
|
||||||
|
|
||||||
|
/// <summary>Die Mitte — das, was vorher fest verdrahtet war.</summary>
|
||||||
|
public const string Vorgabe = "mittel";
|
||||||
|
|
||||||
|
private static readonly string[] Stufen = ["niedrig", "mittel", "hoch", "sehrhoch"];
|
||||||
|
|
||||||
|
/// <summary>Macht aus dem Gespeicherten eine der vier Stufen.</summary>
|
||||||
|
///
|
||||||
|
/// <remarks>
|
||||||
|
/// Unbekanntes wird zur Vorgabe statt zu einem Fehler: in der Spalte
|
||||||
|
/// steht, was irgendwann einmal hineingeschrieben wurde, und eine
|
||||||
|
/// Startseite, die daran scheitert, wäre die schlechtere Antwort.
|
||||||
|
/// </remarks>
|
||||||
|
public static string Lesen(string? wert)
|
||||||
|
{
|
||||||
|
var blank = wert?.Trim().ToLowerInvariant() ?? "";
|
||||||
|
return Stufen.Contains(blank) ? blank : Vorgabe;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Ob das eine Stufe ist, die es gibt.</summary>
|
||||||
|
public static bool Kennt(string? wert) =>
|
||||||
|
wert is not null && Stufen.Contains(wert.Trim().ToLowerInvariant());
|
||||||
|
}
|
||||||
@@ -6,7 +6,16 @@ namespace Flaro.Api.Endpoints;
|
|||||||
|
|
||||||
public sealed record SystemInfo(
|
public sealed record SystemInfo(
|
||||||
string Version, string Build, string ServerName, bool NeedsSetup,
|
string Version, string Build, string ServerName, bool NeedsSetup,
|
||||||
IReadOnlyList<AttributionResponse> Attributions);
|
IReadOnlyList<AttributionResponse> Attributions,
|
||||||
|
/// <summary>Wie hoch der Aufmacher steht: „niedrig", „mittel", „hoch", „sehrhoch".</summary>
|
||||||
|
///
|
||||||
|
/// <remarks>
|
||||||
|
/// <b>Hier und nicht in einer eigenen Abfrage.</b> Diese Auskunft holt
|
||||||
|
/// der Client ohnehin als Erstes, noch vor der Anmeldung. Käme die Höhe
|
||||||
|
/// später, stünde der Aufmacher einen Augenblick in der Vorgabe und
|
||||||
|
/// spränge dann — genau das, was eine einstellbare Höhe vermeiden soll.
|
||||||
|
/// </remarks>
|
||||||
|
string HeroHeight = "mittel");
|
||||||
|
|
||||||
/// <summary>Ein Ablageort des Servers, mitsamt Belegung.</summary>
|
/// <summary>Ein Ablageort des Servers, mitsamt Belegung.</summary>
|
||||||
///
|
///
|
||||||
@@ -121,6 +130,9 @@ public sealed record CreateUserRequest(string UserName, string Password, bool Is
|
|||||||
/// <summary>Adminrecht geben oder nehmen.</summary>
|
/// <summary>Adminrecht geben oder nehmen.</summary>
|
||||||
public sealed record RechtRequest(bool IsAdministrator);
|
public sealed record RechtRequest(bool IsAdministrator);
|
||||||
|
|
||||||
|
/// <summary>Eine der vier Stufen für den Aufmacher.</summary>
|
||||||
|
public sealed record HoeheRequest(string Height);
|
||||||
|
|
||||||
/// <summary>Eine Bibliothek, wie die Oberfläche sie zeigt.</summary>
|
/// <summary>Eine Bibliothek, wie die Oberfläche sie zeigt.</summary>
|
||||||
///
|
///
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
|
|||||||
@@ -48,7 +48,9 @@ public static class SystemEndpoints
|
|||||||
Attributions:
|
Attributions:
|
||||||
[
|
[
|
||||||
new AttributionResponse(Attribution.Tmdb, Attribution.TmdbLogoUrl, Attribution.TmdbUrl),
|
new AttributionResponse(Attribution.Tmdb, Attribution.TmdbLogoUrl, Attribution.TmdbUrl),
|
||||||
])))
|
],
|
||||||
|
HeroHeight: Aufmacherhoehe.Lesen((await db.Settings.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(e => e.Key == Aufmacherhoehe.Schlüssel))?.Value))))
|
||||||
.AllowAnonymous();
|
.AllowAnonymous();
|
||||||
|
|
||||||
// **Wer schaut gerade was.**
|
// **Wer schaut gerade was.**
|
||||||
@@ -190,6 +192,26 @@ public static class SystemEndpoints
|
|||||||
return Results.NoContent();
|
return Results.NoContent();
|
||||||
}).RequireAuthorization(FlaroPolicies.Administrator);
|
}).RequireAuthorization(FlaroPolicies.Administrator);
|
||||||
|
|
||||||
|
// **Wie hoch der Aufmacher steht.** Gelesen wird er mit der
|
||||||
|
// Systemauskunft weiter oben — die holt der Client ohnehin als
|
||||||
|
// Erstes, und so steht die Höhe schon beim ersten Zeichnen fest.
|
||||||
|
app.MapPut("/api/v1/system/hero-height", async (
|
||||||
|
HoeheRequest anfrage, FlaroContext db, CancellationToken ct) =>
|
||||||
|
{
|
||||||
|
if (!Aufmacherhoehe.Kennt(anfrage.Height))
|
||||||
|
return Einwand.Bitte("Unbekannte Stufe. Möglich sind: niedrig, mittel, hoch, sehrhoch.");
|
||||||
|
|
||||||
|
var wert = anfrage.Height.Trim().ToLowerInvariant();
|
||||||
|
var vorhanden = await db.Settings
|
||||||
|
.FirstOrDefaultAsync(e => e.Key == Aufmacherhoehe.Schlüssel, ct);
|
||||||
|
if (vorhanden is null)
|
||||||
|
db.Settings.Add(new ServerSetting { Key = Aufmacherhoehe.Schlüssel, Value = wert });
|
||||||
|
else vorhanden.Value = wert;
|
||||||
|
|
||||||
|
await db.SaveChangesAsync(ct);
|
||||||
|
return Results.NoContent();
|
||||||
|
}).RequireAuthorization(FlaroPolicies.Administrator);
|
||||||
|
|
||||||
app.MapPost("/api/v1/system/setup", async (SetupRequest anfrage, FlaroContext db) =>
|
app.MapPost("/api/v1/system/setup", async (SetupRequest anfrage, FlaroContext db) =>
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(anfrage.UserName) || anfrage.Password.Length < 8)
|
if (string.IsNullOrWhiteSpace(anfrage.UserName) || anfrage.Password.Length < 8)
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ async function hochfahren(): Promise<void> {
|
|||||||
const auskunft = await systemauskunft();
|
const auskunft = await systemauskunft();
|
||||||
hinweise = auskunft.attributions;
|
hinweise = auskunft.attributions;
|
||||||
serverName = auskunft.serverName ?? 'Flaro';
|
serverName = auskunft.serverName ?? 'Flaro';
|
||||||
|
// **So früh wie möglich.** Die Höhe des Aufmachers steht als Marke am
|
||||||
|
// `<html>`; käme sie später, stünde er einen Augenblick in der Vorgabe
|
||||||
|
// und spränge dann — genau das, was eine einstellbare Höhe vermeiden soll.
|
||||||
|
document.documentElement.dataset.aufmacher = auskunft.heroHeight ?? 'mittel';
|
||||||
|
|
||||||
if (auskunft.needsSetup) { zeigeAnmeldung(einrichtung(() => location.reload())); return; }
|
if (auskunft.needsSetup) { zeigeAnmeldung(einrichtung(() => location.reload())); return; }
|
||||||
|
|
||||||
|
|||||||
+46
-1
@@ -498,6 +498,33 @@ svg { width: 20px; height: 20px; display: block; flex: none; }
|
|||||||
.strip::-webkit-scrollbar { display: none; }
|
.strip::-webkit-scrollbar { display: none; }
|
||||||
.strip > * { scroll-snap-align: start; }
|
.strip > * { scroll-snap-align: start; }
|
||||||
|
|
||||||
|
/* **Ein Text, der beim Überfahren einen anderen zeigt.**
|
||||||
|
*
|
||||||
|
* Für die Auflösung: in Ruhe steht „Full HD" da, beim Überfahren „1080p ·
|
||||||
|
* 1920 × 800". Ein Tooltip täte dasselbe, aber verzögert, über der
|
||||||
|
* Nachbarzeile und am Finger gar nicht.
|
||||||
|
*
|
||||||
|
* **Beide liegen übereinander, nicht nacheinander.** Das Feld ist so breit
|
||||||
|
* wie der breitere der beiden — sonst rückte beim Überfahren die halbe
|
||||||
|
* Tabelle. Gerastert statt absolut positioniert, damit die Zeilenhöhe von
|
||||||
|
* selbst stimmt. */
|
||||||
|
.wechseltext { display: inline-grid; }
|
||||||
|
.wechseltext > span { grid-area: 1 / 1; transition: opacity .15s ease; }
|
||||||
|
.wechseltext > .genau { opacity: 0; color: var(--text-dim); }
|
||||||
|
|
||||||
|
@media (hover: hover) {
|
||||||
|
.wechseltext:hover > span { opacity: 0; }
|
||||||
|
.wechseltext:hover > .genau { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ohne Zeiger lässt sich nichts überfahren — dort steht das Genaue
|
||||||
|
dauerhaft daneben, statt unerreichbar zu sein. */
|
||||||
|
@media (hover: none) {
|
||||||
|
.wechseltext { display: inline; }
|
||||||
|
.wechseltext > .genau { opacity: 1; }
|
||||||
|
.wechseltext > .genau::before { content: ' · '; }
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------- Detailseite ---------- */
|
/* ---------- Detailseite ---------- */
|
||||||
|
|
||||||
.detail { position: relative; }
|
.detail { position: relative; }
|
||||||
@@ -1137,9 +1164,24 @@ body:has(.pl.pl-klein) .auswahlleiste { bottom: 92px; }
|
|||||||
* Breite ist der grösste Einzelunterschied im Eindruck — und ein Layout-,
|
* Breite ist der grösste Einzelunterschied im Eindruck — und ein Layout-,
|
||||||
* kein Farbproblem. */
|
* kein Farbproblem. */
|
||||||
|
|
||||||
|
/* **Vier Stufen, serverweit eingestellt.**
|
||||||
|
*
|
||||||
|
* Eine Pixelzahl einzustellen hiesse, sie für jeden Bildschirm neu
|
||||||
|
* einzustellen — der Aufmacher misst sich an der Fensterhöhe. Vier Wörter
|
||||||
|
* sind das, was man dazu zu sagen hat, und `main.ts` setzt sie als Marke am
|
||||||
|
* `<html>`, sobald die Systemauskunft da ist.
|
||||||
|
*
|
||||||
|
* Die Mitte ist, was vorher fest verdrahtet war. Oben ist Schluss, bevor die
|
||||||
|
* erste Plakatreihe ganz unter den Falz rutscht: sie soll zeigen, dass unten
|
||||||
|
* noch etwas kommt. */
|
||||||
|
:root { --hero-hoehe: min(72vh, 640px); }
|
||||||
|
html[data-aufmacher="niedrig"] { --hero-hoehe: min(62vh, 560px); }
|
||||||
|
html[data-aufmacher="hoch"] { --hero-hoehe: min(82vh, 720px); }
|
||||||
|
html[data-aufmacher="sehrhoch"] { --hero-hoehe: min(92vh, 800px); }
|
||||||
|
|
||||||
.hero {
|
.hero {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: min(72vh, 640px);
|
height: var(--hero-hoehe);
|
||||||
/* Randlos: der Inhaltsbereich hat seitlich Polster, das Bild soll es
|
/* Randlos: der Inhaltsbereich hat seitlich Polster, das Bild soll es
|
||||||
nicht haben. Negative Ränder statt eines eigenen Containers. */
|
nicht haben. Negative Ränder statt eines eigenen Containers. */
|
||||||
margin: calc(var(--s-page) * -1) calc(var(--s-page) * -1) var(--s-section);
|
margin: calc(var(--s-page) * -1) calc(var(--s-page) * -1) var(--s-section);
|
||||||
@@ -1334,6 +1376,9 @@ body:has(.pl.pl-klein) .auswahlleiste { bottom: 92px; }
|
|||||||
|
|
||||||
@media (max-width: 860px) {
|
@media (max-width: 860px) {
|
||||||
.hero {
|
.hero {
|
||||||
|
/* Am Telefon eine Stufe tiefer als gewählt: dort ist der Aufmacher im
|
||||||
|
Verhältnis zum Bildschirm ohnehin höher, und über 60 vh bleibt von der
|
||||||
|
ersten Reihe nichts mehr übrig. */
|
||||||
height: min(52vh, 420px);
|
height: min(52vh, 420px);
|
||||||
margin: calc(var(--s-loose) * -1) calc(var(--s-loose) * -1) var(--s-section);
|
margin: calc(var(--s-loose) * -1) calc(var(--s-loose) * -1) var(--s-section);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
doppelteZusammenlegen, metadatenEinstellung, metadatenSetzen, servernameSetzen,
|
doppelteZusammenlegen, metadatenEinstellung, metadatenSetzen, servernameSetzen, aufmacherhoeheSetzen,
|
||||||
systemauskunft, laufendeWiedergaben, serverzustand, bildAdresse, speicherorte,
|
systemauskunft, laufendeWiedergaben, serverzustand, bildAdresse, speicherorte,
|
||||||
bildspeicherLeeren, type Wiedergabe,
|
bildspeicherLeeren, type Wiedergabe,
|
||||||
} from '../../api';
|
} from '../../api';
|
||||||
@@ -144,6 +144,30 @@ export function serverbereich(geaendert: () => void): HTMLElement {
|
|||||||
tun: async wert => { await servernameSetzen(wert('name')); location.reload(); },
|
tun: async wert => { await servernameSetzen(wert('name')); location.reload(); },
|
||||||
})));
|
})));
|
||||||
|
|
||||||
|
const stufen = [
|
||||||
|
{ wert: 'niedrig', text: 'Niedrig' },
|
||||||
|
{ wert: 'mittel', text: 'Mittel' },
|
||||||
|
{ wert: 'hoch', text: 'Hoch' },
|
||||||
|
{ wert: 'sehrhoch', text: 'Sehr hoch' },
|
||||||
|
];
|
||||||
|
const jetzige = auskunft.heroHeight ?? 'mittel';
|
||||||
|
liste.appendChild(zeile(
|
||||||
|
'Höhe des Aufmachers',
|
||||||
|
stufen.find(s => s.wert === jetzige)?.text ?? 'Mittel',
|
||||||
|
'Das grosse Bild oben auf der Startseite. Gemessen wird in '
|
||||||
|
+ 'Fensterhöhe, nicht in Bildpunkten — sonst müsste man es an jedem '
|
||||||
|
+ 'Bildschirm neu einstellen. Am Telefon bleibt es flacher: dort '
|
||||||
|
+ 'wäre von der ersten Plakatreihe sonst nichts mehr zu sehen.',
|
||||||
|
() => dialog({
|
||||||
|
titel: 'Höhe des Aufmachers',
|
||||||
|
text: 'Gilt für alle auf diesem Server.',
|
||||||
|
felder: [{
|
||||||
|
name: 'stufe', beschriftung: 'Höhe', auswahl: stufen, wert: jetzige,
|
||||||
|
}],
|
||||||
|
bestaetigen: 'Übernehmen',
|
||||||
|
tun: async wert => { await aufmacherhoeheSetzen(wert('stufe')); location.reload(); },
|
||||||
|
})));
|
||||||
|
|
||||||
// **Wartung, nicht Alltag.** Nach jedem Scan passiert das von selbst;
|
// **Wartung, nicht Alltag.** Nach jedem Scan passiert das von selbst;
|
||||||
// dieser Knopf ist für den Fall, dass doch wieder Dubletten auftauchen —
|
// dieser Knopf ist für den Fall, dass doch wieder Dubletten auftauchen —
|
||||||
// etwa nachdem eine Zuordnung von Hand geändert wurde.
|
// etwa nachdem eine Zuordnung von Hand geändert wurde.
|
||||||
|
|||||||
Reference in New Issue
Block a user