import type { key_val } from '$lib/stores/ae_stores'; import { api } from '$lib/api/api'; import { pres_mgmt_loc } from '$lib/stores/ae_events_stores__pres_mgmt.svelte'; import type { PressMgmtRemoteCfg } from '$lib/stores/ae_events_stores__pres_mgmt_defaults'; import { badges_loc } from '$lib/stores/ae_events_stores__badges.svelte'; import type { BadgesRemoteCfg } from '$lib/stores/ae_events_stores__badges_defaults'; import { db_save_ae_obj_li__ae_obj } from '$lib/ae_core/core__idb_dexie'; import { db_events } from '$lib/ae_events/db_events'; import type { ae_Event } from '$lib/types/ae_types'; import { load_ae_obj_li__event_device } from './ae_events__event_device'; import { load_ae_obj_li__event_location } from './ae_events__event_location'; import { load_ae_obj_li__event_session } from './ae_events__event_session'; import { load_ae_obj_li__event_badge_template } from '$lib/ae_events/ae_events__event_badge_template'; import { load_ae_obj_li__event_file } from '$lib/ae_events/ae_events__event_file'; const ae_promises: key_val = {}; // Updated 2026-01-27 to V3 String-Only ID Standard export async function load_ae_obj_id__event({ api_cfg, event_id, view = 'default', inc_device_li = false, inc_file_li = false, inc_location_li = false, inc_session_li = false, inc_presentation_li = false, inc_presenter_li = false, inc_template_li = false, enabled = 'enabled', hidden = 'not_hidden', try_cache = true, log_lvl = 0 }: { api_cfg: any; event_id: string; view?: string; inc_device_li?: boolean; inc_file_li?: boolean; inc_location_li?: boolean; inc_session_li?: boolean; inc_presentation_li?: boolean; inc_presenter_li?: boolean; inc_template_li?: boolean; enabled?: 'enabled' | 'all' | 'not_enabled'; hidden?: 'hidden' | 'all' | 'not_hidden'; try_cache?: boolean; log_lvl?: number; }): Promise { if (log_lvl) { console.log( `*** load_ae_obj_id__event() *** event_id=${event_id} (SWR Optimization)` ); } // Hierarchy Enforcement: Pulling presentations/presenters requires pulling sessions first if (inc_presenter_li || inc_presentation_li) inc_session_li = true; // 1. FAST PATH: Return cached data immediately if (try_cache) { try { const cached_event = await db_events.event.get(event_id); if (cached_event) { if (log_lvl) console.log( 'EVENT LOAD: Cache hit. Returning stale data immediately.' ); // Trigger background refresh _refresh_event_background({ api_cfg, event_id, view, try_cache, inc_device_li, inc_file_li, inc_location_li, inc_session_li, inc_presentation_li, inc_presenter_li, inc_template_li, enabled, hidden, log_lvl: 0 }); // Still handle nested loads for the cached version to ensure UI richness return await _handle_nested_loads(cached_event, { api_cfg, inc_device_li, inc_file_li, inc_location_li, inc_session_li, inc_presentation_li, inc_presenter_li, inc_template_li, enabled, hidden, try_cache, log_lvl }); } } catch (e) { if (log_lvl) console.warn('EVENT LOAD: Cache read failed.', e); } } // 2. SLOW PATH: Wait for API if cache is empty or try_cache is false return await _refresh_event_background({ api_cfg, event_id, view, try_cache, inc_device_li, inc_file_li, inc_location_li, inc_session_li, inc_presentation_li, inc_presenter_li, inc_template_li, enabled, hidden, log_lvl }); } /** * Internal helper to perform the actual API fetch and cache update for events */ async function _refresh_event_background({ api_cfg, event_id, view, try_cache, inc_device_li, inc_file_li, inc_location_li, inc_session_li, inc_presentation_li, inc_presenter_li, inc_template_li, enabled, hidden, log_lvl }: any) { // Check if offline if (typeof navigator !== 'undefined' && !navigator.onLine) { if (log_lvl) console.log('Browser is offline. Skipping API refresh.'); return null; } try { const result = await api.get_ae_obj({ api_cfg: api_cfg, obj_type: 'event', obj_id: event_id, view, log_lvl: log_lvl }); if (result) { let processed_obj = result; if (try_cache) { const processed = await process_ae_obj__event_props({ obj_li: [result], log_lvl: log_lvl }); processed_obj = processed[0]; await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'event', obj_li: processed, properties_to_save: properties_to_save, log_lvl: log_lvl }); } return await _handle_nested_loads(processed_obj, { api_cfg, inc_device_li, inc_file_li, inc_location_li, inc_session_li, inc_presentation_li, inc_presenter_li, inc_template_li, enabled, hidden, try_cache: false, log_lvl }); } } catch (error: any) { if (log_lvl) console.log('Event API refresh failed.', error); } return null; } /** * Shared logic for loading nested child collections */ async function _handle_nested_loads( event_obj: any, { api_cfg, inc_device_li, inc_file_li, inc_location_li, inc_session_li, inc_presentation_li, inc_presenter_li, inc_template_li, enabled, hidden, try_cache, log_lvl }: any ) { if (log_lvl) console.log( `Loading nested collections for event: ${event_obj.event_id} (Devices: ${inc_device_li}, Files: ${inc_file_li}, Locations: ${inc_location_li}, Sessions: ${inc_session_li}, Presentations: ${inc_presentation_li}, Presenters: ${inc_presenter_li}, Templates: ${inc_template_li})` ); // String-Only ID Vision: the '_id' field IS the string ID const current_event_id = event_obj.id || event_obj.event_id; // Use Promise.all for concurrent nested loads to further reduce delay const tasks = []; if (inc_device_li) { tasks.push( load_ae_obj_li__event_device({ api_cfg, for_obj_type: 'event', for_obj_id: current_event_id, try_cache, log_lvl }).then((res) => (event_obj.event_device_obj_li = res)) ); } if (inc_file_li) { tasks.push( load_ae_obj_li__event_file({ api_cfg, for_obj_type: 'event', for_obj_id: current_event_id, enabled: 'all', limit: 100, try_cache, log_lvl }).then((res) => (event_obj.event_file_li = res)) ); } if (inc_location_li) { tasks.push( load_ae_obj_li__event_location({ api_cfg, for_obj_type: 'event', for_obj_id: current_event_id, enabled, hidden, try_cache, log_lvl }).then((res) => (event_obj.event_location_obj_li = res)) ); } if (inc_session_li) { tasks.push( load_ae_obj_li__event_session({ api_cfg, for_obj_type: 'event', for_obj_id: current_event_id, inc_presentation_li, inc_presenter_li, enabled, hidden, try_cache, log_lvl }).then((res) => (event_obj.event_session_obj_li = res)) ); } if (inc_template_li) { tasks.push( load_ae_obj_li__event_badge_template({ api_cfg, event_id: current_event_id, try_cache, log_lvl }).then((res) => (event_obj.event_badge_template_obj_li = res)) ); } if (tasks.length > 0) await Promise.all(tasks); return event_obj; } // Updated 2026-01-27 to V3 String-Only ID Standard export async function load_ae_obj_li__event({ api_cfg, for_obj_type = 'account', for_obj_id, qry_conference = null, enabled = 'enabled', hidden = 'not_hidden', view = 'default', inc_session_li = false, inc_presentation_li = false, inc_presenter_li = false, limit = 9, offset = 0, order_by_li = { start_datetime: 'DESC' } as const, try_cache = true, log_lvl = 0 }: { api_cfg: any; for_obj_type?: string; for_obj_id: string; qry_conference?: boolean | null; enabled?: 'enabled' | 'all' | 'not_enabled'; hidden?: 'hidden' | 'all' | 'not_hidden'; view?: string; inc_session_li?: boolean; inc_presentation_li?: boolean; inc_presenter_li?: boolean; limit?: number; offset?: number; order_by_li?: | Record | Record[]; params?: key_val; try_cache?: boolean; log_lvl?: number; }): Promise { // Hierarchy Enforcement: Pulling presentations/presenters requires pulling sessions first if (inc_presenter_li || inc_presentation_li) inc_session_li = true; // Check if offline if (typeof navigator !== 'undefined' && !navigator.onLine) { if (log_lvl) console.log( 'Browser is offline. Skipping API and attempting cache load.' ); ae_promises.load__event_obj_li = await db_events.event .where('account_id') .equals(for_obj_id) .toArray(); return ae_promises.load__event_obj_li || []; } let promise; if (qry_conference !== null) { const search_query: any = { and: [{ field: 'conference', op: 'eq', value: qry_conference }] }; if (for_obj_id) { search_query.and.push({ field: `${for_obj_type}_id`, op: 'eq', value: for_obj_id }); } promise = api.search_ae_obj({ api_cfg, obj_type: 'event', headers: { 'x-account-id': for_obj_id }, search_query, enabled, hidden, view, limit, offset, order_by_li, log_lvl }); } else { promise = api.get_ae_obj_li({ api_cfg, obj_type: 'event', for_obj_type, for_obj_id, enabled, hidden, view, limit, offset, order_by_li, log_lvl }); } try { const result_li = await promise; if (result_li) { let processed_results = result_li; if (try_cache) { const processed = await process_ae_obj__event_props({ obj_li: result_li, log_lvl: log_lvl }); processed_results = processed; await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'event', obj_li: processed, properties_to_save: properties_to_save, log_lvl: log_lvl }); } ae_promises.load__event_obj_li = processed_results; } else { console.log('No results returned from API.'); if (try_cache) { if (log_lvl) console.log('Attempting to load from local cache...'); ae_promises.load__event_obj_li = await db_events.event .where('account_id') .equals(for_obj_id) .toArray(); } else { ae_promises.load__event_obj_li = []; } } } catch (error: any) { console.log('API request failed.', error); if (try_cache) { if (log_lvl) console.log( 'Attempting to load from local cache after error...' ); ae_promises.load__event_obj_li = await db_events.event .where('account_id') .equals(for_obj_id) .toArray(); } else { ae_promises.load__event_obj_li = []; } } if (inc_session_li && ae_promises.load__event_obj_li) { const session_tasks = ae_promises.load__event_obj_li.map( (event_obj: any) => { const current_event_id = event_obj.id || event_obj.event_id; return load_ae_obj_li__event_session({ api_cfg, for_obj_type: 'event', for_obj_id: current_event_id, inc_presentation_li, inc_presenter_li, try_cache, log_lvl }).then((res) => (event_obj.event_session_obj_li = res)); } ); await Promise.all(session_tasks); } return ae_promises.load__event_obj_li; } // Updated 2026-01-06 export async function create_ae_obj__event({ api_cfg, account_id, data_kv, params = {}, try_cache = true, log_lvl = 0 }: { api_cfg: any; account_id: string; data_kv: key_val; params?: key_val; try_cache?: boolean; log_lvl?: number; }): Promise { const result = await api.create_ae_obj({ api_cfg, obj_type: 'event', fields: { account_id, ...data_kv }, params, log_lvl }); if (result) { const processed = await process_ae_obj__event_props({ obj_li: [result], log_lvl: log_lvl }); const processed_obj = processed[0]; if (try_cache) { await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'event', obj_li: processed, properties_to_save: properties_to_save, log_lvl: log_lvl }); } return processed_obj; } return null; } // Updated 2026-01-06 export async function delete_ae_obj_id__event({ api_cfg, event_id, method = 'delete', params = {}, try_cache = true, log_lvl = 0 }: { api_cfg: any; event_id: string; method?: 'delete' | 'soft_delete' | 'disable' | 'hide'; params?: key_val; try_cache?: boolean; log_lvl?: number; }) { const result = await api.delete_ae_obj({ api_cfg, obj_type: 'event', obj_id: event_id, method, params, log_lvl }); if (try_cache) { await db_events.event.delete(event_id); } return result; } // Updated 2026-01-06 export async function update_ae_obj__event({ api_cfg, event_id, data_kv, params = {}, try_cache = true, log_lvl = 0 }: { api_cfg: any; event_id: string; data_kv: key_val; params?: key_val; try_cache?: boolean; log_lvl?: number; }): Promise { const result = await api.update_ae_obj({ api_cfg, obj_type: 'event', obj_id: event_id, fields: data_kv, params, log_lvl }); if (result) { const processed = await process_ae_obj__event_props({ obj_li: [result], log_lvl: log_lvl }); const processed_obj = processed[0]; if (try_cache) { await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'event', obj_li: processed, properties_to_save: properties_to_save, log_lvl: log_lvl }); } return processed_obj; } return null; } // Updated 2026-01-21 /** * Unified Search for Events (V3 API) * * STRATEGY: Hybrid Search/Filter * 1. Server-side (V3 Search): Used for text search (qry_str) to reduce payload. * 2. Client-side (Filter Layer): Handles all other filters (Type, Location, Person) * to ensure correct inclusive OR logic and stable ID matching. */ export async function search__event({ api_cfg, for_obj_type = 'account', for_obj_id, qry_str, qry_person_id = null, qry_conference = null, qry_physical = null, qry_virtual = null, qry_type = null, enabled = 'enabled', hidden = 'not_hidden', view = 'default', limit = 99, offset = 0, order_by_li = { start_datetime: 'DESC' } as const, try_cache = true, log_lvl = 0 }: { api_cfg: any; for_obj_type?: string; for_obj_id: string; qry_str?: string; qry_person_id?: string | null; qry_conference?: boolean | null; qry_physical?: boolean | null; qry_virtual?: boolean | null; qry_type?: string | null; enabled?: 'enabled' | 'all' | 'not_enabled'; hidden?: 'hidden' | 'all' | 'not_hidden'; view?: string; limit?: number; offset?: number; order_by_li?: Record; try_cache?: boolean; log_lvl?: number; }): Promise { if (log_lvl) console.log('*** search__event() *** [V3]'); let result_li: ae_Event[] | null = null; if (qry_str && qry_str.trim().length > 0) { // Option A: Active Text Search const search_query: any = { and: [] }; const params: key_val = {}; search_query.and.push({ field: 'default_qry_str', op: 'like', value: `%${qry_str.trim()}%` }); params['lk_qry'] = { default_qry_str: qry_str.trim() }; if (for_obj_id) { // V3 Standard: Use random string ID for body filters. // The API resolves this to the integer column automatically. search_query.and.push({ field: `${for_obj_type}_id_random`, op: 'eq', value: for_obj_id }); } // NOTE: We do NOT push 'physical' and 'virtual' to the server-side query here. // The V3 Search API uses AND logic for the body, which would exclude // meetings that are only physical or only virtual if both filters are active. // We handle this in the Client-side Filter Layer below for correct OR logic. result_li = await api.search_ae_obj({ api_cfg, obj_type: 'event', headers: { 'x-account-id': for_obj_id }, search_query, params, enabled, hidden, view, limit, offset, order_by_li, log_lvl }); } else { // Option B: List All result_li = await api.get_ae_obj_li({ api_cfg, obj_type: 'event', for_obj_type, for_obj_id, enabled, hidden, view, limit, offset, order_by_li, headers: { 'x-account-id': for_obj_id }, log_lvl }); } // Handle potential V3 API envelope let valid_result_li: ae_Event[] = []; if (Array.isArray(result_li)) { valid_result_li = result_li; } else if ( result_li && typeof result_li === 'object' && Array.isArray((result_li as any).data) ) { valid_result_li = (result_li as any).data; } else { return []; } const processed_obj_li = await process_ae_obj__event_props({ obj_li: valid_result_li, log_lvl: log_lvl }); if (try_cache) { await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'event', obj_li: processed_obj_li, properties_to_save: properties_to_save, log_lvl: log_lvl }); } // Client-side Filter Layer const filtered_obj_li = processed_obj_li.filter((ev: any) => { // Handle conference filter if (qry_conference != null) { const ev_conf = ev.conference == true; if (ev_conf !== !!qry_conference) return false; } // Handle type filter if (qry_type != null && qry_type !== 'all' && qry_type !== '') { if (ev.type !== qry_type) return false; } // Location Filtering (Inclusive OR logic) if (qry_physical === true || qry_virtual === true) { const ev_physical = ev.physical == true; const ev_virtual = ev.virtual == true; let match = false; if (qry_physical === true && ev_physical) match = true; if (qry_virtual === true && ev_virtual) match = true; if (!match) return false; } // Handle person ID filter if (qry_person_id) { const match = ev.external_person_id === qry_person_id || ev.poc_person_id === qry_person_id || ev.poc_person_id_random === qry_person_id || ev.poc_event_person_id === qry_person_id || ev.poc_event_person_id_random === qry_person_id; if (!match) return false; } return true; }); if (log_lvl) { console.log( `Filter results (Hybrid): Input=${processed_obj_li.length}, Output=${filtered_obj_li.length}` ); } return filtered_obj_li.slice(0, limit); } export const qry_ae_obj_li__event = search__event; // Updated 2026-03-10 export const properties_to_save = [ 'id', 'event_id', 'code', 'account_id', // 'account_id_random', 'conference', 'type', 'name', 'summary', 'description', 'start_datetime', 'end_datetime', 'timezone', 'location_address_json', 'location_text', 'attend_json', 'attend_text', 'status', 'mod_abstracts_json', 'mod_badges_json', 'mod_exhibits_json', 'mod_meetings_json', 'mod_pres_mgmt_json', 'cfg_json', 'enable', 'hide', 'priority', 'sort', 'group', 'notes', 'created_on', 'updated_on', 'contact_li_json', 'external_person_id', 'physical', 'virtual', 'recurring', 'recurring_pattern', 'recurring_start_time', 'recurring_end_time', 'recurring_text', 'weekday_sunday', 'weekday_monday', 'weekday_tuesday', 'weekday_wednesday', 'weekday_thursday', 'weekday_friday', 'weekday_saturday', 'attend_url', 'attend_url_text', 'attend_url_code', 'attend_url_passcode', 'attend_phone', 'attend_phone_passcode', 'default_qry_str', 'tmp_sort_1', 'tmp_sort_2', 'file_count', 'file_count_all', 'internal_use_count', 'event_file_id_li_json', // Legacy flat address fields — preserved so background SWR re-fires do not wipe // address data for events that predate location_address_json. The edit form reads // these as a fallback: location_address_json?.name ?? address_name ?? ''. // New saves always write structured data into location_address_json, but the API // may still return the flat fields for older records and they must survive an IDB // round-trip without being stripped by _process_generic_props. 'address_location_id', 'address_name', 'address_line_1', 'address_line_2', 'address_city', 'address_state', 'address_postal_code', 'address_country', 'address_country_alpha_2_code', 'address_country_subdivision_code' ]; async function _process_generic_props>({ obj_li, obj_type, log_lvl = 0, specific_processor }: { obj_li: T[]; obj_type: string; log_lvl?: number; specific_processor?: (obj: T) => Promise | T; }): Promise { if (!obj_li || obj_li.length === 0) return []; const processed_obj_li: T[] = []; for (const original_obj of obj_li) { let processed_obj = { ...original_obj }; for (const key in processed_obj) { if (key.endsWith('_random')) { const newKey = key.slice(0, -7); (processed_obj as any)[newKey] = processed_obj[key]; } } const randomIdKey = `${obj_type}_id_random`; const baseIdKey = `${obj_type}_id`; if (processed_obj[randomIdKey]) { (processed_obj as any).id = processed_obj[randomIdKey]; (processed_obj as any)[baseIdKey] = processed_obj[randomIdKey]; } else if (processed_obj[baseIdKey]) { (processed_obj as any).id = processed_obj[baseIdKey]; } const group = processed_obj.group ?? '0'; const priority = processed_obj.priority ? 1 : 0; const sort = processed_obj.sort ?? '0'; const updated = processed_obj.updated_on ?? processed_obj.created_on ?? new Date(0).toISOString(); const name = processed_obj.name ?? ''; (processed_obj as any).tmp_sort_1 = `${group}_${priority}_${sort}_${updated}`; (processed_obj as any).tmp_sort_2 = `${group}_${priority}_${sort}_${name}_${updated}`; if (specific_processor) { processed_obj = await Promise.resolve( specific_processor(processed_obj) ); } processed_obj_li.push(processed_obj as T); } return processed_obj_li; } // Updated 2026-01-20 export async function process_ae_obj__event_props({ obj_li, log_lvl = 0 }: { obj_li: any[]; log_lvl?: number; }) { return _process_generic_props({ obj_li, obj_type: 'event', log_lvl, specific_processor: (obj) => { if (obj.event_code) { obj.code = obj.event_code; } // Ensure ID consistency for components relying on specific ID fields if (obj.event_id_random) { obj.event_id = obj.event_id_random; } else if (obj.id && !obj.event_id) { obj.event_id = obj.id; } return obj; } }); } /** * sync_config__event_pres_mgmt * * Syncs the server-side event pres_mgmt config (mod_pres_mgmt_json) into the * local PersistedState store (pres_mgmt_loc.current). * * Called reactively in pres_mgmt/+page.svelte whenever the event object changes. * * Always-synced fields: labels, requirements, access links, file config. * Lock-synced fields (only when lock_config=true): all visibility/hide__ toggles, * launcher links, navigation limits. This prevents presenter laptops from * drifting into different display configs across a conference. * * Canonical key convention (enforced here): * hide__* → feature ON by default; true = turn off (default: false = visible) * show__* → feature OFF by default; true = turn on (default: false = hidden) */ export function sync_config__event_pres_mgmt({ pres_mgmt_cfg_remote, log_lvl = 0 }: { pres_mgmt_cfg_remote: Partial; log_lvl?: number; }) { if (log_lvl) { console.log( `*** sync_config__event_pres_mgmt() *** remote:`, pres_mgmt_cfg_remote ); } const loc = pres_mgmt_loc.current; // --- Always sync: labels, requirements, opt-in features, file config --- // These are safe to always apply — they don't override local display preferences. loc.label__person_external_id = pres_mgmt_cfg_remote?.label__person_external_id ?? 'External ID'; loc.label__presenter_external_id = pres_mgmt_cfg_remote?.label__presenter_external_id ?? 'External ID'; loc.label__session_poc_type = pres_mgmt_cfg_remote?.label__session_poc_type ?? 'poc'; loc.label__session_poc_name = pres_mgmt_cfg_remote?.label__session_poc_name ?? 'Point of Contact'; loc.require__presenter_agree = pres_mgmt_cfg_remote?.require__presenter_agree ?? false; loc.require__session_agree = pres_mgmt_cfg_remote?.require__session_agree ?? false; loc.show__copy_access_link = pres_mgmt_cfg_remote?.show__copy_access_link ?? false; loc.show__email_access_link = pres_mgmt_cfg_remote?.show__email_access_link ?? false; loc.file_purpose_option_kv = pres_mgmt_cfg_remote?.file_purpose_option_kv ?? null; loc.hide__report_kv = pres_mgmt_cfg_remote?.hide__report_kv ?? {}; // --- Lock-synced: visibility, launcher, navigation, session/presenter fields --- // When lock_config=true the remote config wins for ALL display flags, preventing // local browser state from overriding the admin's event-level configuration. if (pres_mgmt_cfg_remote?.lock_config) { if (log_lvl) console.log(`sync_config__event_pres_mgmt: lock_config=true, forcing full sync`); // Codes loc.hide__location_code = pres_mgmt_cfg_remote?.hide__location_code ?? false; loc.hide__presentation_code = pres_mgmt_cfg_remote?.hide__presentation_code ?? false; loc.hide__presenter_code = pres_mgmt_cfg_remote?.hide__presenter_code ?? false; loc.hide__session_code = pres_mgmt_cfg_remote?.hide__session_code ?? false; // Session fields loc.hide__session_description = pres_mgmt_cfg_remote?.hide__session_description ?? false; loc.hide__session_location = pres_mgmt_cfg_remote?.hide__session_location ?? false; loc.hide__session_msg = pres_mgmt_cfg_remote?.hide__session_msg ?? false; loc.hide__session_poc = pres_mgmt_cfg_remote?.hide__session_poc ?? false; loc.hide__session_poc_biography = pres_mgmt_cfg_remote?.hide__session_poc_biography ?? false; loc.hide__session_poc_profile = pres_mgmt_cfg_remote?.hide__session_poc_profile ?? false; loc.hide__session_poc_profile_pic = pres_mgmt_cfg_remote?.hide__session_poc_profile_pic ?? false; // Presenter fields loc.hide__presenter_biography = pres_mgmt_cfg_remote?.hide__presenter_biography ?? false; // Presentation fields loc.hide__presentation_datetime = pres_mgmt_cfg_remote?.hide__presentation_datetime ?? false; loc.hide__presentation_description = pres_mgmt_cfg_remote?.hide__presentation_description ?? false; // Launcher links (show__ in remote → invert to hide__ in local display state) loc.hide__launcher_link = !(pres_mgmt_cfg_remote?.show__launcher_link ?? false); // Legacy Flask launcher is retired — always hide regardless of remote config loc.hide__launcher_link_legacy = true; // Navigation / UI constraints loc.limit__navigation = pres_mgmt_cfg_remote?.limit__navigation ?? false; loc.limit__options = pres_mgmt_cfg_remote?.limit__options ?? false; } } /** * sync_config__event_badges * * Mirror a subset of server-side `mod_badges_json` into the local persisted * `badges_loc.current` store so the UI can read a fast local copy of * authoritative feature flags (search mode, QR enable, mass-print, etc.). * * Called reactively from badge-related pages when the event object changes. */ export function sync_config__event_badges({ badges_cfg_remote, log_lvl = 0 }: { badges_cfg_remote: Partial; log_lvl?: number; }) { if (log_lvl) { console.log( `*** sync_config__event_badges() *** remote:`, badges_cfg_remote ); } const loc = badges_loc.current; // Always-sync: feature flags and simple values loc.enable_mass_print = badges_cfg_remote?.enable_mass_print ?? true; loc.enable_add_badge_btn = badges_cfg_remote?.enable_add_badge_btn ?? true; loc.enable_upload_badge_li_btn = badges_cfg_remote?.enable_upload_badge_li_btn ?? true; loc.enable_search_qr = badges_cfg_remote?.enable_search_qr ?? true; loc.qr_type = badges_cfg_remote?.qr_type ?? null; // Per-tier search constraints loc.anon_search_result_limit = badges_cfg_remote?.anon_search_result_limit ?? 15; loc.anon_search_min_chars = badges_cfg_remote?.anon_search_min_chars ?? 3; loc.auth_search_result_limit = badges_cfg_remote?.auth_search_result_limit ?? 25; loc.auth_search_min_chars = badges_cfg_remote?.auth_search_min_chars ?? 2; loc.trusted_search_result_limit = badges_cfg_remote?.trusted_search_result_limit ?? 150; loc.trusted_search_min_chars = badges_cfg_remote?.trusted_search_min_chars ?? 1; // Passcodes and permissions (may be null) loc.trusted_passcode = badges_cfg_remote?.trusted_passcode ?? null; loc.administrator_passcode = badges_cfg_remote?.administrator_passcode ?? null; loc.edit_permissions = badges_cfg_remote?.edit_permissions ?? null; loc.remote_cfg_last_synced_on = new Date().toISOString(); }