feat(badges): cfg_json body_text_color applied in renderer

This commit is contained in:
Scott Idem
2026-04-08 12:32:13 -04:00
parent 56b4e5c627
commit b02843e467
4 changed files with 147 additions and 5 deletions

View File

@@ -0,0 +1,35 @@
// Type definition for badge template cfg_json stored on templates.
export interface BadgeTemplateCfg {
hide_badge_header?: boolean;
hide_badge_footer?: boolean;
show_qr_front?: boolean;
show_qr_back?: boolean;
// Per-field hide toggles
hide_title?: boolean;
hide_affiliations?: boolean;
hide_location?: boolean;
// Alignment overrides
align?: {
name?: string;
title?: string;
affiliations?: string;
location?: string;
};
// QR alignment
qr_alignment?: {
front?: string;
back?: string;
};
// Layout fit height overrides
fit_heights?: Record<string, string>;
// Body text color: either a Tailwind `text-*` class or a hex color like `#112233`.
body_text_color?: string;
// Allow arbitrary extra keys to preserve forward-compatibility.
[key: string]: any;
}

View File

@@ -92,6 +92,7 @@ import {
Utensils,
Wifi
} from '@lucide/svelte';
import type { BadgeTemplateCfg } from '$lib/ae_events/types/ae_badge_template_cfg';
// --- Badge type list from template ---
// Each item: { code: string, name: string }. Drives footer display + (in controls) dropdown.
let badge_type_code_li = $derived.by(() => {
@@ -198,9 +199,9 @@ let template_cfg = $derived.by(() => {
const raw = $lq__event_badge_template_obj?.cfg_json;
if (!raw) return {};
try {
return typeof raw === 'string' ? JSON.parse(raw) : raw;
return typeof raw === 'string' ? (JSON.parse(raw) as BadgeTemplateCfg) : (raw as BadgeTemplateCfg);
} catch {
return {};
return {} as BadgeTemplateCfg;
}
});
@@ -294,6 +295,31 @@ let qr_back_justify = $derived.by(() => {
return map[val] ?? 'center';
});
// Body text color: can be a Tailwind `text-*` class or a hex value like `#112233`.
let body_text_color_class = $derived.by(() => {
const cfg = template_cfg || {};
const raw = cfg?.body_text_color ?? cfg?.text_color ?? '';
if (!raw || typeof raw !== 'string') return '';
const v = raw.trim();
if (v.startsWith('text-')) return v;
// Map simple color names to Tailwind where reasonable (e.g., 'white' -> 'text-white')
if (/^[a-zA-Z]+$/.test(v)) {
const pick = v.toLowerCase();
const allowed = ['white','black','gray','red','blue','green','yellow','indigo','purple','pink'];
if (allowed.includes(pick)) return `text-${pick}`;
}
return '';
});
let body_text_color_style = $derived.by(() => {
const cfg = template_cfg || {};
const raw = cfg?.body_text_color ?? cfg?.text_color ?? '';
if (!raw || typeof raw !== 'string') return '';
const v = raw.trim();
if (/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(v)) return `color: ${v};`;
return '';
});
/**
* Layout-aware section heights for Element_fit_text.
*
@@ -660,9 +686,10 @@ const code_to_icon: {
items-center
justify-end overflow-clip
p-0 px-8 pb-1
text-white
{body_text_color_class || 'text-white'}
gap-0
">
"
style="{body_text_color_style}">
<!--
person_name container: explicit height from fit_heights so Element_fit_text
can measure overflow correctly. flex-col with justify-content distributes

View File

@@ -4,6 +4,7 @@ import { Loader2 } from '@lucide/svelte';
import type { key_val } from '$lib/stores/ae_stores';
import { events_func } from '$lib/ae_events/ae_events_functions';
import { ae_api } from '$lib/stores/ae_stores';
import type { BadgeTemplateCfg } from '$lib/ae_events/types/ae_badge_template_cfg';
interface Props {
event_id: string;
@@ -56,6 +57,8 @@ let cfg_show_qr_back = $state(true);
let cfg_hide_title = $state(false);
let cfg_hide_affiliations = $state(false);
let cfg_hide_location = $state(false);
// Body text color: Tailwind class (e.g. 'text-black') or hex (e.g. '#000000')
let cfg_body_text_color = $state('text-black');
// Alignment overrides: 'left' | 'center' | 'right' | 'justify'
let cfg_align_name = $state('center');
let cfg_align_title = $state('center');
@@ -125,6 +128,9 @@ async function load_template(id: string) {
cfg_hide_affiliations = parsed_cfg.hasOwnProperty('hide_affiliations') ? !!parsed_cfg.hide_affiliations : false;
cfg_hide_location = parsed_cfg.hasOwnProperty('hide_location') ? !!parsed_cfg.hide_location : false;
// Body text color
cfg_body_text_color = parsed_cfg.body_text_color ?? parsed_cfg.text_color ?? 'text-white';
// Alignment overrides (nested under cfg_json.align and cfg_json.qr_alignment)
cfg_align_name = parsed_cfg?.align?.name ?? parsed_cfg.align_name ?? 'center';
cfg_align_title = parsed_cfg?.align?.title ?? parsed_cfg.align_title ?? 'center';
@@ -152,7 +158,7 @@ async function handle_submit() {
submit_status = 'loading';
// Merge cfg_json preserving unknown keys, then set our cfg flags
let cfg_obj: any = {};
let cfg_obj: BadgeTemplateCfg = {};
try {
cfg_obj = existing_cfg_raw
? typeof existing_cfg_raw === 'string'
@@ -184,6 +190,9 @@ async function handle_submit() {
cfg_obj.qr_alignment.front = cfg_qr_alignment_front;
cfg_obj.qr_alignment.back = cfg_qr_alignment_back;
// Body text color (Tailwind class or hex)
cfg_obj.body_text_color = cfg_body_text_color;
const data_to_save: key_val = {
name,
background_image_path,
@@ -409,6 +418,10 @@ function handle_cancel() {
<option value="justify">Justify</option>
</select>
</label>
<label class="label">
<span>Body Text Color (Tailwind class or #hex)</span>
<input type="text" bind:value={cfg_body_text_color} class="input" placeholder="text-black or #000000" />
</label>
</div>
<p class="text-xs text-surface-400 italic">
These values are saved into <code>cfg_json</code>. Existing cfg_json keys are preserved.