Badges: per-badge locked font sizes via cfg_json
Allows coordinators to pre-tune font sizes for attendees with long names and have those sizes apply automatically on every kiosk, not just one machine. - ae_types.ts: add cfg_json to ae_EventBadge interface - db_events.ts: add cfg_json to Badge Dexie interface - ae_events__event_badge.ts: add cfg_json to properties_to_save so it is persisted to IndexedDB on load and returned by the API - print/+page.svelte: on first load per badge, read cfg_json.font_sizes and initialize font_size_name/title/affiliations/location state from saved values (guarded by _font_sizes_loaded_for to avoid clobbering user adjustments on background liveQuery refreshes) - ae_comp__badge_print_controls.svelte: add lock_font_sizes() and reset_font_sizes_to_auto() functions; add Lock Sizes / Auto reset UI in the Staff adjustments section (trusted-only); button shows warning style when sizes are unsaved vs success when locked; status indicator shows what is currently locked Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -632,6 +632,7 @@ export const properties_to_save = [
|
|||||||
'print_last_datetime',
|
'print_last_datetime',
|
||||||
'allow_tracking',
|
'allow_tracking',
|
||||||
'agree_to_tc',
|
'agree_to_tc',
|
||||||
|
'cfg_json',
|
||||||
'other_1_code',
|
'other_1_code',
|
||||||
'other_2_code',
|
'other_2_code',
|
||||||
'other_3_code',
|
'other_3_code',
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ export interface Badge {
|
|||||||
|
|
||||||
// passcode?: null|string;
|
// passcode?: null|string;
|
||||||
|
|
||||||
|
cfg_json?: null | string;
|
||||||
// data_json?: null|string;
|
// data_json?: null|string;
|
||||||
|
|
||||||
default_qry_str?: null | string;
|
default_qry_str?: null | string;
|
||||||
|
|||||||
@@ -507,6 +507,7 @@ export interface ae_EventBadge extends ae_BaseObj {
|
|||||||
agree_to_tc?: boolean | null;
|
agree_to_tc?: boolean | null;
|
||||||
|
|
||||||
ticket_list?: any[] | null;
|
ticket_list?: any[] | null;
|
||||||
|
cfg_json?: any;
|
||||||
data_json?: any;
|
data_json?: any;
|
||||||
default_qry_str?: string | null;
|
default_qry_str?: string | null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -624,6 +624,113 @@ let is_dirty_badge_type = $derived(
|
|||||||
null)
|
null)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// --- Lock / Reset font sizes to badge cfg_json ---
|
||||||
|
// "Lock Sizes" saves the current font_size_* values into event_badge.cfg_json.font_sizes.
|
||||||
|
// Any kiosk that opens this badge will then load those sizes instead of auto-sizing.
|
||||||
|
// "Reset to Auto" saves null for all fields, restoring auto-sizing everywhere.
|
||||||
|
// Trusted-only — attendees at the badge table can't permanently alter the layout.
|
||||||
|
type LockStatus = 'idle' | 'saving' | 'done' | 'error';
|
||||||
|
let lock_sizes_status: LockStatus = $state('idle');
|
||||||
|
|
||||||
|
// True when at least one size is non-null (i.e. user has adjusted something)
|
||||||
|
let has_any_size_override = $derived(
|
||||||
|
font_size_name !== null ||
|
||||||
|
font_size_title !== null ||
|
||||||
|
font_size_affiliations !== null ||
|
||||||
|
font_size_location !== null
|
||||||
|
);
|
||||||
|
|
||||||
|
// Detect whether current state differs from what is saved in badge cfg_json
|
||||||
|
// so we can highlight the button when there are unsaved size changes.
|
||||||
|
let saved_font_sizes = $derived.by(() => {
|
||||||
|
try {
|
||||||
|
const cfg = typeof $lq__event_badge_obj?.cfg_json === 'string'
|
||||||
|
? JSON.parse($lq__event_badge_obj.cfg_json)
|
||||||
|
: ($lq__event_badge_obj?.cfg_json ?? {});
|
||||||
|
return cfg?.font_sizes ?? null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let sizes_are_dirty = $derived(
|
||||||
|
font_size_name !== (saved_font_sizes?.name ?? null) ||
|
||||||
|
font_size_title !== (saved_font_sizes?.title ?? null) ||
|
||||||
|
font_size_affiliations !== (saved_font_sizes?.affiliations ?? null) ||
|
||||||
|
font_size_location !== (saved_font_sizes?.location ?? null)
|
||||||
|
);
|
||||||
|
|
||||||
|
async function lock_font_sizes() {
|
||||||
|
if (!$lq__event_badge_obj?.event_badge_id) return;
|
||||||
|
lock_sizes_status = 'saving';
|
||||||
|
try {
|
||||||
|
// Merge into existing cfg_json — preserve any other keys that may be there
|
||||||
|
const existing_cfg = (() => {
|
||||||
|
try {
|
||||||
|
return typeof $lq__event_badge_obj.cfg_json === 'string'
|
||||||
|
? JSON.parse($lq__event_badge_obj.cfg_json)
|
||||||
|
: ($lq__event_badge_obj.cfg_json ?? {});
|
||||||
|
} catch { return {}; }
|
||||||
|
})();
|
||||||
|
const new_cfg = {
|
||||||
|
...existing_cfg,
|
||||||
|
font_sizes: {
|
||||||
|
name: font_size_name,
|
||||||
|
title: font_size_title,
|
||||||
|
affiliations: font_size_affiliations,
|
||||||
|
location: font_size_location
|
||||||
|
}
|
||||||
|
};
|
||||||
|
await events_func.update_ae_obj__event_badge({
|
||||||
|
api_cfg: $ae_api,
|
||||||
|
event_id,
|
||||||
|
event_badge_id: $lq__event_badge_obj.event_badge_id,
|
||||||
|
data_kv: { cfg_json: JSON.stringify(new_cfg) },
|
||||||
|
log_lvl
|
||||||
|
});
|
||||||
|
lock_sizes_status = 'done';
|
||||||
|
setTimeout(() => { lock_sizes_status = 'idle'; }, 1500);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Badge print controls: lock font sizes error:', err);
|
||||||
|
lock_sizes_status = 'error';
|
||||||
|
setTimeout(() => { lock_sizes_status = 'idle'; }, 2500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reset_font_sizes_to_auto() {
|
||||||
|
// Clear all local size state
|
||||||
|
font_size_name = null;
|
||||||
|
font_size_title = null;
|
||||||
|
font_size_affiliations = null;
|
||||||
|
font_size_location = null;
|
||||||
|
if (!$lq__event_badge_obj?.event_badge_id) return;
|
||||||
|
// Persist the reset back to the badge — removes the locked sizes server-side
|
||||||
|
lock_sizes_status = 'saving';
|
||||||
|
try {
|
||||||
|
const existing_cfg = (() => {
|
||||||
|
try {
|
||||||
|
return typeof $lq__event_badge_obj.cfg_json === 'string'
|
||||||
|
? JSON.parse($lq__event_badge_obj.cfg_json)
|
||||||
|
: ($lq__event_badge_obj.cfg_json ?? {});
|
||||||
|
} catch { return {}; }
|
||||||
|
})();
|
||||||
|
const new_cfg = { ...existing_cfg };
|
||||||
|
delete new_cfg.font_sizes;
|
||||||
|
await events_func.update_ae_obj__event_badge({
|
||||||
|
api_cfg: $ae_api,
|
||||||
|
event_id,
|
||||||
|
event_badge_id: $lq__event_badge_obj.event_badge_id,
|
||||||
|
data_kv: { cfg_json: Object.keys(new_cfg).length ? JSON.stringify(new_cfg) : null },
|
||||||
|
log_lvl
|
||||||
|
});
|
||||||
|
lock_sizes_status = 'done';
|
||||||
|
setTimeout(() => { lock_sizes_status = 'idle'; }, 1500);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Badge print controls: reset font sizes error:', err);
|
||||||
|
lock_sizes_status = 'error';
|
||||||
|
setTimeout(() => { lock_sizes_status = 'idle'; }, 2500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TC modal ref for the lead scanning terms & conditions dialog
|
// TC modal ref for the lead scanning terms & conditions dialog
|
||||||
let tc_dialog_ref: HTMLDialogElement | undefined;
|
let tc_dialog_ref: HTMLDialogElement | undefined;
|
||||||
|
|
||||||
@@ -1476,6 +1583,50 @@ let allow_tracking_open = $derived(
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- === LOCK FONT SIZES ===
|
||||||
|
Saves the current font_size_* values into event_badge.cfg_json.font_sizes
|
||||||
|
so they survive page reloads and apply on every kiosk, not just this machine.
|
||||||
|
Use this for attendees with long names that auto-sizing can't handle well.
|
||||||
|
Trusted-only. -->
|
||||||
|
<div class="space-y-1 px-1 pt-1">
|
||||||
|
<p class="field-label px-1">Locked Font Sizes</p>
|
||||||
|
<div class="flex gap-1.5">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-xs flex-1 transition-colors {lock_sizes_status === 'done' ? 'preset-filled-success' : lock_sizes_status === 'error' ? 'preset-tonal-error' : lock_sizes_status === 'saving' ? 'preset-tonal-surface' : sizes_are_dirty ? 'preset-filled-warning' : 'preset-tonal-surface'}"
|
||||||
|
disabled={lock_sizes_status === 'saving' || !has_any_size_override}
|
||||||
|
onclick={lock_font_sizes}
|
||||||
|
title="Save current font sizes to this badge record — applies on all kiosks">
|
||||||
|
{#if lock_sizes_status === 'saving'}
|
||||||
|
<LoaderCircle size="11" class="mr-1 animate-spin" /> Saving…
|
||||||
|
{:else if lock_sizes_status === 'done'}
|
||||||
|
<Check size="11" class="mr-1" /> Locked
|
||||||
|
{:else if lock_sizes_status === 'error'}
|
||||||
|
Error
|
||||||
|
{:else}
|
||||||
|
🔒 Lock Sizes
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
{#if saved_font_sizes || has_any_size_override}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-xs preset-tonal-warning shrink-0"
|
||||||
|
disabled={lock_sizes_status === 'saving'}
|
||||||
|
onclick={reset_font_sizes_to_auto}
|
||||||
|
title="Reset all font sizes to auto and clear saved sizes from badge record">
|
||||||
|
↺ Auto
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{#if saved_font_sizes}
|
||||||
|
<p class="px-1 text-[9px] text-green-600 dark:text-green-400">
|
||||||
|
Sizes locked on this badge
|
||||||
|
{#if saved_font_sizes.name}· Name {saved_font_sizes.name}px{/if}
|
||||||
|
{#if saved_font_sizes.title}· Title {saved_font_sizes.title}px{/if}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Print Position Offset
|
<!-- Print Position Offset
|
||||||
Per-browser calibration for physical printer alignment.
|
Per-browser calibration for physical printer alignment.
|
||||||
Stored in localStorage by the parent page — each workstation keeps
|
Stored in localStorage by the parent page — each workstation keeps
|
||||||
|
|||||||
@@ -100,11 +100,41 @@ function send_review_email() {
|
|||||||
// Controls live in Comp_badge_print_controls (right panel) via $bindable().
|
// Controls live in Comp_badge_print_controls (right panel) via $bindable().
|
||||||
// Constants and adjust logic are defined there; only the state lives here so
|
// Constants and adjust logic are defined there; only the state lives here so
|
||||||
// the values can be forwarded to both the controls and the badge render.
|
// the values can be forwarded to both the controls and the badge render.
|
||||||
|
//
|
||||||
|
// Initialization order: badge cfg_json.font_sizes takes precedence (persisted per-badge),
|
||||||
|
// falling back to null (auto) on first load. The controls panel's "Lock Sizes" button
|
||||||
|
// saves current sizes back to cfg_json so they survive page reloads and cross-kiosk.
|
||||||
let font_size_name: number | null = $state(null);
|
let font_size_name: number | null = $state(null);
|
||||||
let font_size_title: number | null = $state(null);
|
let font_size_title: number | null = $state(null);
|
||||||
let font_size_affiliations: number | null = $state(null);
|
let font_size_affiliations: number | null = $state(null);
|
||||||
let font_size_location: number | null = $state(null);
|
let font_size_location: number | null = $state(null);
|
||||||
|
|
||||||
|
// Track whether we've applied the saved sizes for this badge yet.
|
||||||
|
// Prevents re-applying on every liveQuery tick after the user adjusts sizes.
|
||||||
|
let _font_sizes_loaded_for: string | null = null;
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
const badge = $lq__event_badge_obj;
|
||||||
|
if (!badge?.event_badge_id) return;
|
||||||
|
// Only apply once per badge ID — don't clobber user adjustments on background refreshes
|
||||||
|
if (_font_sizes_loaded_for === badge.event_badge_id) return;
|
||||||
|
_font_sizes_loaded_for = badge.event_badge_id;
|
||||||
|
try {
|
||||||
|
const cfg = typeof badge.cfg_json === 'string'
|
||||||
|
? JSON.parse(badge.cfg_json)
|
||||||
|
: (badge.cfg_json ?? {});
|
||||||
|
const fs = cfg?.font_sizes;
|
||||||
|
if (fs) {
|
||||||
|
font_size_name = fs.name ?? null;
|
||||||
|
font_size_title = fs.title ?? null;
|
||||||
|
font_size_affiliations = fs.affiliations ?? null;
|
||||||
|
font_size_location = fs.location ?? null;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Malformed cfg_json — stay with null (auto) for all fields
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Per-browser print workstation tweaks.
|
// Per-browser print workstation tweaks.
|
||||||
// Each workstation/printer is calibrated independently — values are stored in
|
// Each workstation/printer is calibrated independently — values are stored in
|
||||||
// localStorage so they survive page reloads and browser restarts without any
|
// localStorage so they survive page reloads and browser restarts without any
|
||||||
|
|||||||
Reference in New Issue
Block a user