feat: custom formats (WxH/stickerN), JPG output option, failed-job status

- format.ts parses free formats from the key: sticker<N> -> N×N cm,
  <W>x<H> -> W×H cm. Fixes 'Unbekanntes Format: sticker5' (the seeded
  'Sticker 5 cm' recipe was unusable) and lets admins define ANY format
  without a migration. Guarded to <=300 cm.
- Output file format PNG/JPG: global default (settings.output_ext) plus
  per-recipe/per-conversion override. JPG only for non-alpha; cutouts
  stay PNG. file.ts sniffs content-type from magic bytes.
- Jobs can now be 'failed' (all items failed) -> red in the queue; add
  finished time to queue list + detail. migration 012.
- Studio: custom-measure input + Dateiformat selector; Admin: global
  Standard-Dateiformat.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNQ8ghPfzAfsyVYd6HgFb6
This commit is contained in:
2026-07-24 08:32:03 +00:00
parent efcb889c96
commit 3a061aa612
12 changed files with 142 additions and 21 deletions
+11 -1
View File
@@ -45,7 +45,17 @@ export function resolveDimensions(input: ResolveInput): Dimensions | null {
let cm: [number, number] | undefined;
if (format in CM) cm = CM[format];
else if (format === 'sticker' || format === 'custom') cm = input.customCm ?? [5, 5];
if (!cm) throw new Error(`Unbekanntes Format: ${format}`);
else {
// Frei definierte Formate direkt aus dem Schlüssel lesen — keine DB-Migration nötig:
// „sticker5" / „sticker-7,5" → quadratisch N×N cm; „25x35" / „25×35" → B×H cm.
const sq = /^sticker[-_ ]?(\d+(?:[.,]\d+)?)$/i.exec(format);
const wh = /^(\d+(?:[.,]\d+)?)\s*[x×*]\s*(\d+(?:[.,]\d+)?)$/i.exec(format);
const num = (s: string) => parseFloat(s.replace(',', '.'));
if (sq) { const n = num(sq[1]); cm = [n, n]; }
else if (wh) cm = [num(wh[1]), num(wh[2])];
}
if (!cm || !cm[0] || !cm[1] || cm[0] > 300 || cm[1] > 300)
throw new Error(`Unbekanntes Format: ${format}`);
// Hochformat-Konvention → gewünschte Orientierung anwenden
let [wCm, hCm] = cm;