Files
Vorrania/web/src/components/BrandingUpload.jsx
Scarriffle 4448718d8d Logo-Upload sichtbar machen; empfohlene Abmessungen angeben
Fehler: Ein hochgeladenes Logo blieb unsichtbar, auch nach dem Neuladen.
Die Oberflaeche prueft per Anfrage, ob ein Bild hinterlegt ist, und nutzte dafuer
HEAD. FastAPI registriert fuer eine GET-Route aber - anders als Starlette - kein
HEAD; die Anfrage lief in ein 405, und jedes hinterlegte Bild galt als "nicht
vorhanden". Der Upload selbst war die ganze Zeit in Ordnung.

- Neuer Endpunkt GET /branding liefert nur den Status ({"logo": true, ...}),
  ohne Bilddaten zu uebertragen. Die Oberflaeche fragt jetzt diesen ab.
- Die Bild-Route beantwortet zusaetzlich HEAD, damit sie sich erwartungsgemaess
  verhaelt.

Ausserdem:
- Die empfohlenen Abmessungen stehen jetzt sichtbar ueber der Vorschau
  (Logo etwa 400x72 px, Favicon 64x64 px, jeweils hoechstens 512 KB).
- Nach dem Hochladen wird die tatsaechliche Bildgroesse angezeigt, damit man
  sie mit der Empfehlung vergleichen kann.

Geprueft: "npm run build" laeuft durch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 08:55:17 +02:00

91 lines
2.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useRef, useState } from "react";
import { api } from "../api";
import { brandingUrl, hasBranding } from "../branding";
import Icon from "./Icon";
/**
* Ein Bild der Installation setzen oder wieder entfernen (Logo bzw. Favicon).
* Ohne eigenes Bild bleibt das eingebaute Zeichen der Oberfläche stehen.
*/
export default function BrandingUpload({ kind, title, hint, empfehlung, onChanged }) {
const [vorhanden, setVorhanden] = useState(false);
const [version, setVersion] = useState(0);
const [busy, setBusy] = useState(false);
const [fehler, setFehler] = useState(null);
// Tatsaechliche Bildgroesse - damit man sieht, ob sie zur Empfehlung passt.
const [groesse, setGroesse] = useState(null);
const dateiFeld = useRef(null);
useEffect(() => {
hasBranding(kind).then(setVorhanden);
}, [kind]);
async function auswaehlen(event) {
const datei = event.target.files?.[0];
if (!datei) return;
setFehler(null);
setBusy(true);
try {
await api.uploadBranding(kind, datei);
setVorhanden(true);
setVersion(Date.now());
onChanged?.();
} catch (err) {
setFehler(err.message);
} finally {
setBusy(false);
// Zuruecksetzen, damit dieselbe Datei erneut gewaehlt werden kann.
if (dateiFeld.current) dateiFeld.current.value = "";
}
}
async function entfernen() {
setFehler(null);
setBusy(true);
try {
await api.deleteBranding(kind);
setVorhanden(false);
setVersion(Date.now());
onChanged?.();
} catch (err) {
setFehler(err.message);
} finally {
setBusy(false);
}
}
return (
<div style={{ marginBottom: "var(--sp-4)" }}>
<label style={{ marginBottom: "var(--sp-2)" }}>{title}</label>
{empfehlung && (
<p className="muted small mt-0"><strong>Empfohlen:</strong> {empfehlung}</p>
)}
<div className="brand-preview">
{vorhanden ? (
<img src={brandingUrl(kind, version)} alt={title}
onLoad={(e) => setGroesse({
w: e.currentTarget.naturalWidth,
h: e.currentTarget.naturalHeight,
})} />
) : (
<span className="muted small">Kein eigenes Bild Standard wird verwendet</span>
)}
</div>
{vorhanden && groesse?.w > 0 && (
<p className="muted small mt-0">Hochgeladen: {groesse.w} × {groesse.h} px</p>
)}
<p className="muted small mt-0">{hint}</p>
{fehler && <div className="alert error"><Icon name="alert" size={16} />{fehler}</div>}
<div className="field-inline">
<input ref={dateiFeld} type="file" onChange={auswaehlen} disabled={busy}
accept="image/png,image/jpeg,image/svg+xml,image/webp,image/gif,image/x-icon" />
{vorhanden && (
<button type="button" className="btn ghost" onClick={entfernen} disabled={busy}>
<Icon name="trash" size={14} />Zurücksetzen
</button>
)}
</div>
</div>
);
}