import { useState, useEffect } from "react"; export interface FaviconProps { src?: string | null; fallbackLetter: string; size?: "sm" | "md"; } const SIZE_CLASSES: Record, string> = { sm: "h-4 w-4", md: "h-5 w-5", }; /** * Zeigt ein Favicon mit einem immer hellen Hintergrund (unabhängig vom * Dark/Light-Theme der App) – viele Favicons sind selbst dunkel/schwarz und * wären auf dunklem Hintergrund sonst kaum zu erkennen. Ohne Favicon oder bei * einem fehlgeschlagenen Ladeversuch (kaputte URL, 404, CORS) wird * stattdessen der erste Buchstabe des Namens gezeigt. */ /** * Externe/Geräte-Favicons laufen über den Backend-Proxy (/api/favicon-proxy): * Ohne das würden auf der HTTPS-Seite viele http://-Favicons (typisch für * Homelab-Geräte ohne eigenes TLS) vom Browser als "Mixed Content" blockiert * und nie angezeigt - das Backend selbst fetcht sie ohne diese Einschränkung. * Data-URLs (z. B. selbst hochgeladene Icons) werden direkt durchgereicht. */ function resolveFaviconSrc(src: string): string { if (src.startsWith("data:")) return src; return `/api/favicon-proxy?url=${encodeURIComponent(src)}`; } export function Favicon({ src, fallbackLetter, size = "md" }: FaviconProps) { const dimension = SIZE_CLASSES[size]; const [failed, setFailed] = useState(false); // Bei geändertem src (z. B. anderer Listeneintrag durch Virtualisierung/Key-Wiederverwendung) // erneut versuchen statt dauerhaft im Fehlerzustand zu bleiben. useEffect(() => { setFailed(false); }, [src]); if (!src || failed) { return ( {fallbackLetter.charAt(0).toUpperCase()} ); } return ( setFailed(true)} /> ); }