feat(card): Konfiguration, Geometrie und Bildausschnitt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
05a735993b
commit
1a4ed0d595
6 files changed
+858
No files matched your search
@@ -0,0 +1,116 @@
|
||||
// Kartenkonfiguration: was der Nutzer im Editor einstellt und in der Tabelle `cards` speichert.
|
||||
// Reine Daten, kein DOM, keine Appwrite-Aufrufe.
|
||||
|
||||
const TEMPLATES = ['image', 'minimal', 'classic', 'signature'] as const;
|
||||
const TONES = ['light', 'dark'] as const;
|
||||
const BRANDS = ['aircards-symbol', 'aircards-text', 'apple', 'none'] as const;
|
||||
const NETWORKS = [
|
||||
'visa-auto', 'visa-blue', 'visa-navy', 'visa-silver',
|
||||
'mc-color', 'mc-silver', 'mc-wordmark', 'mc-silver-text', 'mc-classic',
|
||||
'up-color', 'up-silver', 'up-white-frame', 'up-english',
|
||||
'amex-blue', 'amex-outline', 'amex-select',
|
||||
'jcb', 'discover',
|
||||
] as const;
|
||||
|
||||
export type TemplateId = (typeof TEMPLATES)[number];
|
||||
export type DetailsTone = (typeof TONES)[number];
|
||||
export type BrandId = (typeof BRANDS)[number];
|
||||
export type NetworkId = (typeof NETWORKS)[number];
|
||||
|
||||
export const NETWORK_IDS: readonly NetworkId[] = NETWORKS;
|
||||
|
||||
/** Zoom-Grenzen relativ zum Cover-Maßstab. */
|
||||
export const MIN_ZOOM = 1;
|
||||
export const MAX_ZOOM = 4;
|
||||
|
||||
/** x/y = Versatz der Bildmitte zur Kartenmitte in Karten-px, zoom >= 1 relativ zum Cover-Maßstab. */
|
||||
export interface Crop {
|
||||
x: number;
|
||||
y: number;
|
||||
zoom: number;
|
||||
}
|
||||
|
||||
export interface CardConfig {
|
||||
v: 1;
|
||||
template: TemplateId;
|
||||
network: NetworkId;
|
||||
details: DetailsTone;
|
||||
brand: BrandId;
|
||||
crop: Crop;
|
||||
}
|
||||
|
||||
export const DEFAULT_CROP: Crop = { x: 0, y: 0, zoom: 1 };
|
||||
|
||||
export const DEFAULT_CONFIG: CardConfig = {
|
||||
v: 1,
|
||||
template: 'signature',
|
||||
network: 'visa-auto',
|
||||
details: 'light',
|
||||
brand: 'aircards-symbol',
|
||||
crop: { ...DEFAULT_CROP },
|
||||
};
|
||||
|
||||
export const TONE_COLOR: Record<DetailsTone, string> = {
|
||||
light: '#FFFFFF',
|
||||
dark: '#111111',
|
||||
};
|
||||
|
||||
function pick<T extends string>(value: unknown, allowed: readonly T[], fallback: T): T {
|
||||
return typeof value === 'string' && (allowed as readonly string[]).includes(value)
|
||||
? (value as T)
|
||||
: fallback;
|
||||
}
|
||||
|
||||
function finite(value: unknown, fallback: number): number {
|
||||
return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function parseCrop(value: unknown): Crop {
|
||||
const c = isRecord(value) ? value : {};
|
||||
const zoom = finite(c.zoom, DEFAULT_CROP.zoom);
|
||||
return {
|
||||
x: finite(c.x, DEFAULT_CROP.x),
|
||||
y: finite(c.y, DEFAULT_CROP.y),
|
||||
zoom: Math.min(Math.max(zoom, MIN_ZOOM), MAX_ZOOM),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Liest eine gespeicherte Konfiguration. Jedes Feld wird einzeln geprüft, Unbekanntes oder
|
||||
* Kaputtes fällt auf den Standard dieses Feldes zurück. Liefert immer ein neues Objekt.
|
||||
*/
|
||||
export function parseConfig(json: string | null | undefined): CardConfig {
|
||||
let raw: unknown = null;
|
||||
if (json) {
|
||||
try {
|
||||
raw = JSON.parse(json);
|
||||
} catch {
|
||||
raw = null;
|
||||
}
|
||||
}
|
||||
const r = isRecord(raw) ? raw : {};
|
||||
return {
|
||||
v: 1,
|
||||
template: pick(r.template, TEMPLATES, DEFAULT_CONFIG.template),
|
||||
network: pick(r.network, NETWORKS, DEFAULT_CONFIG.network),
|
||||
details: pick(r.details, TONES, DEFAULT_CONFIG.details),
|
||||
brand: pick(r.brand, BRANDS, DEFAULT_CONFIG.brand),
|
||||
crop: parseCrop(r.crop),
|
||||
};
|
||||
}
|
||||
|
||||
/** Schreibt nur die bekannten Felder (feste Reihenfolge), damit die Spalte klein bleibt. */
|
||||
export function serializeConfig(c: CardConfig): string {
|
||||
return JSON.stringify({
|
||||
v: 1,
|
||||
template: c.template,
|
||||
network: c.network,
|
||||
details: c.details,
|
||||
brand: c.brand,
|
||||
crop: { x: c.crop.x, y: c.crop.y, zoom: c.crop.zoom },
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user