Files
tessera-ctl/apps/web/src/lib/dashboard-images-api.ts
T
schalli c080580459 feat(quick-260921-pi9): Bilderrahmen-Widget - Diashow mit Grossansicht, Bildverwaltung in den Einstellungen
- picture-frame-config.ts: Eintragstyp als Vereinigung (upload | url) in EINER
  geordneten Liste, resolvePictureFrameConfig laesst alles ausser https weg
  (T-PI9-07), Intervall 0 oder 5..3600 s, pickNextIndex (Zufall nie dasselbe)
- dashboard-images-api.ts: Upload als FormData-Feld image ohne eigenen
  Content-Type, 413 -> deutsche Meldung, Proxy-Pfad fuer <img src>
- PictureFrameWidget: Leerzustand, <img referrerPolicy=no-referrer> (Browser
  laedt Fremdbilder, Server nie), object-contain/cover, Unterschrift-Streifen,
  Wechsel per Timer mit Raeumung, kaputte Bilder verlassen den Umlauf,
  Grossansicht nur ausserhalb des Bearbeitungsmodus mit Fokus-Rueckgabe und
  Pause des Wechsels; im Bearbeitungsmodus kein Knopf (Karte bleibt Griff)
- PictureFrameConfigForm im WidgetSettingsPanel: Ausschnitt, Intervall,
  Reihenfolge, Liste mit Vorschau/Unterschrift/Pfeilen/Entfernen (Upload wird
  auch serverseitig geloescht), Datei hochladen, https-Adresse hinzufuegen
- Registry (4x4 min, 8x8 Vorgabe), Katalog, Seite, Uebersetzungen de/en
  (widgets.pictureFrame, 28 Schluessel), bestehende Tests auf acht Typen

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 18:56:02 +02:00

77 lines
2.6 KiB
TypeScript
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.
/**
* dashboard-images-api — Aufrufe des Bilderrahmen-Widgets gegen
* /dashboard/images (quick-260921-pi9). Muster favorites-api.ts: Cookie
* immer mit (`credentials: 'include'`).
*
* `uploadDashboardImage` schickt `multipart/form-data` mit genau dem Feld
* `image` und OHNE eigenen Content-Type-Header — die Multipart-Grenze setzt
* der Browser (Muster bug-report-api.ts). Die deutsche Meldung fuer „zu
* gross“ entsteht HIER aus dem Status 413 (multer meldet englisch), die
* Meldungen fuer 400 kommen von der API (bereits deutsch).
*
* `dashboardImageSrc` liefert den Proxy-Pfad fuer `<img src>`: der
* Next-Rewrite `/api-proxy/:path*` (next.config.ts) reicht Cookies mit,
* die Bytes kommen aus Tesseras eigenem Origin (Muster FavoriteIcon).
*/
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';
export interface DashboardImageMeta {
id: string;
originalName: string;
mimeType: string;
size: number;
createdAt: string;
}
export async function fetchDashboardImages(): Promise<DashboardImageMeta[]> {
const res = await fetch(`${API_URL}/dashboard/images`, { credentials: 'include' });
if (!res.ok) throw new Error('Failed to fetch dashboard images');
return res.json();
}
export async function uploadDashboardImage(file: File): Promise<DashboardImageMeta> {
const body = new FormData();
body.append('image', file, file.name);
const res = await fetch(`${API_URL}/dashboard/images`, {
method: 'POST',
credentials: 'include',
body,
});
if (res.ok) return res.json();
if (res.status === 413) {
throw new Error('Die Datei ist zu groß – erlaubt sind höchstens 5 MB.');
}
if (res.status === 400) {
const message = await readMessage(res);
if (message) throw new Error(message);
}
throw new Error('Das Bild konnte nicht hochgeladen werden.');
}
export async function deleteDashboardImage(id: string): Promise<void> {
const res = await fetch(`${API_URL}/dashboard/images/${encodeURIComponent(id)}`, {
method: 'DELETE',
credentials: 'include',
});
if (!res.ok) throw new Error('Failed to delete dashboard image');
}
export function dashboardImageSrc(id: string): string {
return `/api-proxy/dashboard/images/${encodeURIComponent(id)}`;
}
async function readMessage(res: Response): Promise<string | null> {
try {
const data: unknown = await res.json();
if (typeof data === 'object' && data !== null && 'message' in data) {
const message = (data as { message: unknown }).message;
if (typeof message === 'string' && message !== '') return message;
}
} catch {
// kein JSON — allgemeine Meldung
}
return null;
}