Erscheinungsbild im Admin-Bereich anpassbar
Name, Zusatz in der Fusszeile, Akzentfarbe, Logo und Favicon lassen sich unter Verwaltung -> Erscheinungsbild einstellen. Sie gelten fuer die ganze Installation und liegen in einer eigenen Tabelle mit genau einer Zeile. Zur Farbe: Tailwind 4 uebersetzt bg-indigo-600 zu background-color: var(--color-indigo-600). Die Anwendung schreibt diese Variablen zur Laufzeit neu — damit folgen alle rund 60 eingefaerbten Stellen der gewaehlten Farbe, ohne dass eine einzige Klasse angefasst werden muss. Uebernommen werden nur Farbton und Saettigung, nicht die Helligkeit. Die Helligkeitskurve stammt aus der Tailwind-Vorlage. Sonst wuerde ein helles Gelb die Kopfzeile unlesbar machen, denn auf Stufe 600 steht weisse Schrift. Die Umrechnung laeuft ueber OKLCH und trifft Tailwinds eigenes Indigo exakt. Logo und Favicon sind Uploads mit eigener Groessengrenze (2 MB bzw. 512 KB) und eigener Rechtepruefung: instance:manage statt codes:create. Uploads haben jetzt ein Feld "purpose" — sonst haette die Aufraeumroutine fuer herrenlose Dateien Logo und Favicon nach einem Tag geloescht. Ein ersetztes Bild wird gelaescht, und eine Datei aus einem QR-Code laesst sich nicht als Logo einhaengen. Auf den Landingpages gelten Farbe und Favicon, aber weder Logo noch Navigation — wer scannt, soll die Seite des Inhalts sehen. Ein erster Entwurf hielt das Erscheinungsbild 15 Sekunden im Speicher, um die Abfrage je Seitenaufruf zu sparen. Das ging schief: API-Route und Layout laufen in getrennten Modulinstanzen, das Verwerfen beim Speichern erreichte die andere Instanz nicht — nach dem Speichern blieb die Oberflaeche unveraendert. Jetzt ohne Puffer.
This commit is contained in:
@@ -6,6 +6,15 @@
|
||||
* Dateiarten und die Größenformatierung.
|
||||
*/
|
||||
|
||||
/** Bilder, die die Oberfläche selbst anzeigt. */
|
||||
const BILD_TYPEN = [
|
||||
"image/png",
|
||||
"image/jpeg",
|
||||
"image/webp",
|
||||
"image/gif",
|
||||
"image/avif",
|
||||
] as const;
|
||||
|
||||
export const UPLOAD_KINDS = {
|
||||
pdf: {
|
||||
label: "PDF-Dokument",
|
||||
@@ -14,21 +23,52 @@ export const UPLOAD_KINDS = {
|
||||
},
|
||||
image: {
|
||||
label: "Bild",
|
||||
mimeTypes: [
|
||||
"image/png",
|
||||
"image/jpeg",
|
||||
"image/webp",
|
||||
"image/gif",
|
||||
"image/avif",
|
||||
],
|
||||
mimeTypes: BILD_TYPEN,
|
||||
accept: "image/png,image/jpeg,image/webp,image/gif,image/avif",
|
||||
},
|
||||
logo: {
|
||||
label: "Logo",
|
||||
mimeTypes: BILD_TYPEN,
|
||||
accept: "image/png,image/jpeg,image/webp,image/avif",
|
||||
// Ein Logo in der Kopfzeile ist ein paar Dutzend Kilobyte groß. Die
|
||||
// Grenze soll verhindern, dass jemand ein Foto aus der Kamera hochlädt,
|
||||
// das dann bei jedem Seitenaufruf mitgeht.
|
||||
maxBytes: 2 * 1024 * 1024,
|
||||
},
|
||||
favicon: {
|
||||
label: "Favicon",
|
||||
// Zusätzlich ICO — viele haben genau das noch herumliegen.
|
||||
mimeTypes: [...BILD_TYPEN, "image/x-icon"],
|
||||
accept: "image/png,image/x-icon,.ico,image/webp",
|
||||
maxBytes: 512 * 1024,
|
||||
},
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Wozu die Datei gehört.
|
||||
*
|
||||
* `code` hängt an einem QR-Code und verschwindet mit ihm; `branding` gehört
|
||||
* zur Oberfläche und darf deshalb nicht als herrenlos weggeräumt werden.
|
||||
*/
|
||||
export const UPLOAD_PURPOSES = ["code", "branding"] as const;
|
||||
export type UploadPurpose = (typeof UPLOAD_PURPOSES)[number];
|
||||
|
||||
export function uploadPurposeOf(kind: UploadKind): UploadPurpose {
|
||||
return kind === "logo" || kind === "favicon" ? "branding" : "code";
|
||||
}
|
||||
|
||||
/** Obergrenze dieser Art — sonst gilt die allgemeine aus MAX_UPLOAD_MB. */
|
||||
export function kindMaxBytes(kind: UploadKind): number | null {
|
||||
const eintrag = UPLOAD_KINDS[kind] as { maxBytes?: number };
|
||||
return eintrag.maxBytes ?? null;
|
||||
}
|
||||
|
||||
export type UploadKind = keyof typeof UPLOAD_KINDS;
|
||||
|
||||
export function isUploadKind(value: unknown): value is UploadKind {
|
||||
return value === "pdf" || value === "image";
|
||||
return (
|
||||
typeof value === "string" && Object.hasOwn(UPLOAD_KINDS, value)
|
||||
);
|
||||
}
|
||||
|
||||
export function formatBytes(bytes: number): string {
|
||||
|
||||
Reference in New Issue
Block a user