feat(stores): promote launcher_loc to Svelte 5 PersistedState

Creates ae_events_stores__launcher.svelte.ts with PersistedState keyed
'ae_launcher_loc', following the same pattern as badges, leads, and
pres_mgmt. All 28 launcher component files migrated from
$events_loc.launcher.* to launcher_loc.current.*.

events_local_data_struct in ae_events_stores.ts now carries no sub-module
objects — all four sub-modules (badges, launcher, leads, pres_mgmt) are
authoritative in their own stores. Session state (events_sess.launcher)
is unchanged.

svelte-check: 0 errors, 0 warnings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-06-11 16:00:40 -04:00
parent 5823f18161
commit 27c775d816
28 changed files with 289 additions and 315 deletions

View File

@@ -9,10 +9,7 @@ import type { Writable } from 'svelte/store';
import type { key_val } from '$lib/stores/ae_stores'; import type { key_val } from '$lib/stores/ae_stores';
import { badges_sess_defaults } from '$lib/stores/ae_events_stores__badges_defaults'; import { badges_sess_defaults } from '$lib/stores/ae_events_stores__badges_defaults';
import { import { launcher_sess_defaults } from '$lib/stores/ae_events_stores__launcher_defaults';
launcher_loc_defaults,
launcher_sess_defaults
} from '$lib/stores/ae_events_stores__launcher_defaults';
import { leads_sess_defaults } from '$lib/stores/ae_events_stores__leads_defaults'; import { leads_sess_defaults } from '$lib/stores/ae_events_stores__leads_defaults';
import { pres_mgmt_sess_defaults } from '$lib/stores/ae_events_stores__pres_mgmt_defaults'; import { pres_mgmt_sess_defaults } from '$lib/stores/ae_events_stores__pres_mgmt_defaults';
@@ -63,8 +60,7 @@ const events_local_data_struct: key_val = {
}, },
// Event Presentation Launcher — see ae_events_stores__launcher_defaults.ts // Event Presentation Launcher — see ae_events_stores__launcher_defaults.ts
// badges, leads, pres_mgmt have been promoted to their own PersistedState stores. // badges, leads, pres_mgmt, launcher have all been promoted to their own PersistedState stores.
launcher: launcher_loc_defaults
}; };
export const events_loc: Writable<key_val> = persisted( export const events_loc: Writable<key_val> = persisted(

View File

@@ -0,0 +1,9 @@
import { PersistedState } from 'runed';
import { launcher_loc_defaults } from './ae_events_stores__launcher_defaults';
export const launcher_loc = new PersistedState('ae_launcher_loc', launcher_loc_defaults, {
serializer: {
serialize: JSON.stringify,
deserialize: (raw: string) => ({ ...launcher_loc_defaults, ...JSON.parse(raw) })
}
});

View File

@@ -5,7 +5,7 @@
* Ensures background sync runs globally regardless of active tab. * Ensures background sync runs globally regardless of active tab.
*/ */
import { ae_loc } from '$lib/stores/ae_stores'; import { ae_loc } from '$lib/stores/ae_stores';
import { events_loc } from '$lib/stores/ae_events_stores'; import { launcher_loc } from '$lib/stores/ae_events_stores__launcher.svelte';
import Launcher_Background_Sync from './launcher_background_sync.svelte'; import Launcher_Background_Sync from './launcher_background_sync.svelte';
interface Props { interface Props {
@@ -20,8 +20,8 @@ let { children }: Props = $props();
// design (default / onsite / native) — browser sessions are unaffected because // design (default / onsite / native) — browser sessions are unaffected because
// is_native is only ever true inside the Electron shell. // is_native is only ever true inside the Electron shell.
$effect(() => { $effect(() => {
if ($ae_loc.is_native && $events_loc.launcher.app_mode !== 'native') { if ($ae_loc.is_native && launcher_loc.current.app_mode !== 'native') {
$events_loc.launcher.app_mode = 'native'; launcher_loc.current.app_mode = 'native';
} }
}); });
</script> </script>

View File

@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { ae_loc } from '$lib/stores/ae_stores'; import { ae_loc } from '$lib/stores/ae_stores';
import { events_loc, events_sess } from '$lib/stores/ae_events_stores'; import { events_sess } from '$lib/stores/ae_events_stores';
import { launcher_loc } from '$lib/stores/ae_events_stores__launcher.svelte';
import Launcher_Cfg_Section from './launcher_cfg_section.svelte'; import Launcher_Cfg_Section from './launcher_cfg_section.svelte';
import { Clock, GraduationCap, IdCard, LayoutGrid } from '@lucide/svelte'; import { Clock, GraduationCap, IdCard, LayoutGrid } from '@lucide/svelte';
interface Props { interface Props {
@@ -30,22 +31,22 @@ const ORAL_PRESET = {
// Detect current mode: if both iframe AND hide_menu are on, we're in poster mode. // Detect current mode: if both iframe AND hide_menu are on, we're in poster mode.
// Individual overrides are still possible via the checkboxes below. // Individual overrides are still possible via the checkboxes below.
let is_poster_mode = $derived( let is_poster_mode = $derived(
$ae_loc.iframe === true && $events_loc.launcher.hide__launcher_menu === true $ae_loc.iframe === true && launcher_loc.current.hide__launcher_menu === true
); );
function apply_mode(mode: 'poster' | 'oral') { function apply_mode(mode: 'poster' | 'oral') {
const preset = mode === 'poster' ? POSTER_PRESET : ORAL_PRESET; const preset = mode === 'poster' ? POSTER_PRESET : ORAL_PRESET;
$ae_loc.iframe = preset.iframe; $ae_loc.iframe = preset.iframe;
$events_loc.launcher.hide__launcher_menu = preset.hide__launcher_menu; launcher_loc.current.hide__launcher_menu = preset.hide__launcher_menu;
$events_loc.launcher.hide__launcher_header = preset.hide__launcher_header; launcher_loc.current.hide__launcher_header = preset.hide__launcher_header;
$events_loc.launcher.hide__launcher_footer = preset.hide__launcher_footer; launcher_loc.current.hide__launcher_footer = preset.hide__launcher_footer;
// Push to WS-connected remote devices when we're acting as a controller. // Push to WS-connected remote devices when we're acting as a controller.
// Only send when connected so the UI button doesn't silently no-op. // Only send when connected so the UI button doesn't silently no-op.
if ( if (
$events_loc.launcher.ws_connect && launcher_loc.current.ws_connect &&
($events_loc.launcher.controller === 'local_push' || (launcher_loc.current.controller === 'local_push' ||
$events_loc.launcher.controller === 'remote') launcher_loc.current.controller === 'remote')
) { ) {
$events_sess.launcher.controller_cmd = `ae_mode:${mode}`; $events_sess.launcher.controller_cmd = `ae_mode:${mode}`;
$events_sess.launcher.controller_trigger_send = 'trigger'; $events_sess.launcher.controller_trigger_send = 'trigger';
@@ -56,9 +57,9 @@ function apply_mode(mode: 'poster' | 'oral') {
<Launcher_Cfg_Section <Launcher_Cfg_Section
title="Display & App Modes" title="Display & App Modes"
icon={LayoutGrid} icon={LayoutGrid}
bind:state={$events_loc.launcher.section_state__app_modes} bind:state={launcher_loc.current.section_state__app_modes}
{on_expand} {on_expand}
description="Mode: {$events_loc.launcher.app_mode} | UI Layout"> description="Mode: {launcher_loc.current.app_mode} | UI Layout">
<!-- Content omitted for brevity, preserved in file --> <!-- Content omitted for brevity, preserved in file -->
<div class="col-span-full flex flex-col gap-3"> <div class="col-span-full flex flex-col gap-3">
<!-- 0. Oral / Poster Kiosk Mode Preset Toggle --> <!-- 0. Oral / Poster Kiosk Mode Preset Toggle -->
@@ -88,7 +89,7 @@ function apply_mode(mode: 'poster' | 'oral') {
Poster Kiosk Poster Kiosk
</button> </button>
</div> </div>
{#if $events_loc.launcher.ws_connect && ($events_loc.launcher.controller === 'local_push' || $events_loc.launcher.controller === 'remote')} {#if launcher_loc.current.ws_connect && (launcher_loc.current.controller === 'local_push' || launcher_loc.current.controller === 'remote')}
<p class="ml-1 text-[8px] italic opacity-40"> <p class="ml-1 text-[8px] italic opacity-40">
Applies to all connected WS devices Applies to all connected WS devices
</p> </p>
@@ -103,31 +104,31 @@ function apply_mode(mode: 'poster' | 'oral') {
<div class="bg-surface-500/5 grid grid-cols-3 gap-1 rounded-lg p-1"> <div class="bg-surface-500/5 grid grid-cols-3 gap-1 rounded-lg p-1">
<button <button
type="button" type="button"
onclick={() => ($events_loc.launcher.app_mode = 'default')} onclick={() => (launcher_loc.current.app_mode = 'default')}
class="btn btn-xs text-[9px] font-bold" class="btn btn-xs text-[9px] font-bold"
class:preset-filled-primary={$events_loc.launcher class:preset-filled-primary={launcher_loc.current
.app_mode === 'default'} .app_mode === 'default'}
class:preset-tonal-surface={$events_loc.launcher class:preset-tonal-surface={launcher_loc.current
.app_mode !== 'default'} .app_mode !== 'default'}
title="Default standard web browser (Chromium, Firefox, Safari based) launcher, for remote presenters and testing before being onsite"> title="Default standard web browser (Chromium, Firefox, Safari based) launcher, for remote presenters and testing before being onsite">
Web</button> Web</button>
<button <button
type="button" type="button"
onclick={() => ($events_loc.launcher.app_mode = 'native')} onclick={() => (launcher_loc.current.app_mode = 'native')}
class="btn btn-xs text-[9px] font-bold" class="btn btn-xs text-[9px] font-bold"
class:preset-filled-primary={$events_loc.launcher class:preset-filled-primary={launcher_loc.current
.app_mode === 'native'} .app_mode === 'native'}
class:preset-tonal-surface={$events_loc.launcher class:preset-tonal-surface={launcher_loc.current
.app_mode !== 'native'} .app_mode !== 'native'}
title="Native Electron based app launcher, for onsite presenters in session rooms" title="Native Electron based app launcher, for onsite presenters in session rooms"
>App</button> >App</button>
<button <button
type="button" type="button"
onclick={() => ($events_loc.launcher.app_mode = 'onsite')} onclick={() => (launcher_loc.current.app_mode = 'onsite')}
class="btn btn-xs text-[9px] font-bold" class="btn btn-xs text-[9px] font-bold"
class:preset-filled-primary={$events_loc.launcher class:preset-filled-primary={launcher_loc.current
.app_mode === 'onsite'} .app_mode === 'onsite'}
class:preset-tonal-surface={$events_loc.launcher class:preset-tonal-surface={launcher_loc.current
.app_mode !== 'onsite'} .app_mode !== 'onsite'}
title="Customized onsite OS and web browser (Chromium or Firefox based) launcher, for onsite presenters in for practice and onsite backup" title="Customized onsite OS and web browser (Chromium or Firefox based) launcher, for onsite presenters in for practice and onsite backup"
>Onsite</button> >Onsite</button>
@@ -145,7 +146,7 @@ function apply_mode(mode: 'poster' | 'oral') {
<input <input
type="checkbox" type="checkbox"
bind:checked={ bind:checked={
$events_loc.launcher.hide__launcher_header launcher_loc.current.hide__launcher_header
} }
class="checkbox checkbox-sm" /> class="checkbox checkbox-sm" />
<span class="group-hover:text-primary-500 text-xs" <span class="group-hover:text-primary-500 text-xs"
@@ -154,7 +155,7 @@ function apply_mode(mode: 'poster' | 'oral') {
<label class="group flex cursor-pointer items-center gap-2"> <label class="group flex cursor-pointer items-center gap-2">
<input <input
type="checkbox" type="checkbox"
bind:checked={$events_loc.launcher.hide__launcher_menu} bind:checked={launcher_loc.current.hide__launcher_menu}
class="checkbox checkbox-sm" /> class="checkbox checkbox-sm" />
<span class="group-hover:text-primary-500 text-xs" <span class="group-hover:text-primary-500 text-xs"
>Hide Menu</span> >Hide Menu</span>
@@ -163,7 +164,7 @@ function apply_mode(mode: 'poster' | 'oral') {
<input <input
type="checkbox" type="checkbox"
bind:checked={ bind:checked={
$events_loc.launcher.hide__launcher_footer launcher_loc.current.hide__launcher_footer
} }
class="checkbox checkbox-sm" /> class="checkbox checkbox-sm" />
<span class="group-hover:text-primary-500 text-xs" <span class="group-hover:text-primary-500 text-xs"
@@ -173,7 +174,7 @@ function apply_mode(mode: 'poster' | 'oral') {
<input <input
type="checkbox" type="checkbox"
bind:checked={ bind:checked={
$events_loc.launcher.hide__session_datetimes launcher_loc.current.hide__session_datetimes
} }
class="checkbox checkbox-sm" /> class="checkbox checkbox-sm" />
<span class="group-hover:text-primary-500 text-xs" <span class="group-hover:text-primary-500 text-xs"
@@ -186,18 +187,18 @@ function apply_mode(mode: 'poster' | 'oral') {
<button <button
type="button" type="button"
onclick={() => { onclick={() => {
if ($events_loc.launcher.time_format == 'time_12_short') { if (launcher_loc.current.time_format == 'time_12_short') {
$events_loc.launcher.time_format = 'time_short'; launcher_loc.current.time_format = 'time_short';
$events_loc.launcher.time_hours = 24; launcher_loc.current.time_hours = 24;
} else { } else {
$events_loc.launcher.time_format = 'time_12_short'; launcher_loc.current.time_format = 'time_12_short';
$events_loc.launcher.time_hours = 12; launcher_loc.current.time_hours = 12;
} }
}} }}
class="btn btn-xs preset-tonal-surface w-full text-[10px]"> class="btn btn-xs preset-tonal-surface w-full text-[10px]">
<Clock size="0.85em" class="mr-1 opacity-50" /> <Clock size="0.85em" class="mr-1 opacity-50" />
Clock Format: Clock Format:
<strong>{$events_loc.launcher.time_hours}-hour</strong> <strong>{launcher_loc.current.time_hours}-hour</strong>
</button> </button>
<!-- 4. Advanced Toggles (Edit Mode Only) --> <!-- 4. Advanced Toggles (Edit Mode Only) -->
@@ -211,7 +212,7 @@ function apply_mode(mode: 'poster' | 'oral') {
<label class="group flex cursor-pointer items-center gap-2"> <label class="group flex cursor-pointer items-center gap-2">
<input <input
type="checkbox" type="checkbox"
bind:checked={$events_loc.launcher.hide__ws_element} bind:checked={launcher_loc.current.hide__ws_element}
class="checkbox checkbox-sm" /> class="checkbox checkbox-sm" />
<span <span
class="group-hover:text-primary-500 text-xs italic" class="group-hover:text-primary-500 text-xs italic"
@@ -221,7 +222,7 @@ function apply_mode(mode: 'poster' | 'oral') {
<input <input
type="checkbox" type="checkbox"
bind:checked={ bind:checked={
$events_loc.launcher.hide__modal_header_title launcher_loc.current.hide__modal_header_title
} }
class="checkbox checkbox-sm" /> class="checkbox checkbox-sm" />
<span <span
@@ -243,13 +244,13 @@ function apply_mode(mode: 'poster' | 'oral') {
<label class="group flex cursor-pointer items-center gap-2 p-1"> <label class="group flex cursor-pointer items-center gap-2 p-1">
<input <input
type="checkbox" type="checkbox"
bind:checked={$events_loc.launcher.native_test_mode} bind:checked={launcher_loc.current.native_test_mode}
class="checkbox checkbox-sm" /> class="checkbox checkbox-sm" />
<span class="group-hover:text-warning-500 text-xs italic"> <span class="group-hover:text-warning-500 text-xs italic">
Native Test Mode Native Test Mode
</span> </span>
</label> </label>
{#if $events_loc.launcher.native_test_mode} {#if launcher_loc.current.native_test_mode}
<p class="badge preset-tonal-warning ml-1 text-[8px] leading-tight italic"> <p class="badge preset-tonal-warning ml-1 text-[8px] leading-tight italic">
⚠ Active: Open buttons will simulate native launch and ⚠ Active: Open buttons will simulate native launch and
show a debug popup instead of running commands. show a debug popup instead of running commands.

View File

@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { ae_loc } from '$lib/stores/ae_stores'; import { ae_loc } from '$lib/stores/ae_stores';
import { events_loc, events_sess } from '$lib/stores/ae_events_stores'; import { events_sess } from '$lib/stores/ae_events_stores';
import { launcher_loc } from '$lib/stores/ae_events_stores__launcher.svelte';
import Launcher_Cfg_Section from './launcher_cfg_section.svelte'; import Launcher_Cfg_Section from './launcher_cfg_section.svelte';
import { import {
Gamepad2, Gamepad2,
@@ -24,9 +25,9 @@ const ws_connected = $derived(
<Launcher_Cfg_Section <Launcher_Cfg_Section
title="Remote Controller" title="Remote Controller"
icon={Gamepad2} icon={Gamepad2}
bind:state={$events_loc.launcher.section_state__controller} bind:state={launcher_loc.current.section_state__controller}
{on_expand} {on_expand}
description="Mode: {$events_loc.launcher?.controller} | WS: {ws_connected description="Mode: {launcher_loc.current?.controller} | WS: {ws_connected
? 'Connected' ? 'Connected'
: 'Offline'}"> : 'Offline'}">
<!-- Content omitted for brevity, preserved in file --> <!-- Content omitted for brevity, preserved in file -->
@@ -55,7 +56,7 @@ const ws_connected = $derived(
Controller Strategy Controller Strategy
</p> </p>
<select <select
bind:value={$events_loc.launcher.controller} bind:value={launcher_loc.current.controller}
class="select select-sm preset-tonal-surface h-8 text-xs"> class="select select-sm preset-tonal-surface h-8 text-xs">
<option value="local">Local Only</option> <option value="local">Local Only</option>
<option value="remote">Remotely WS Controlled</option> <option value="remote">Remotely WS Controlled</option>
@@ -68,18 +69,18 @@ const ws_connected = $derived(
<button <button
type="button" type="button"
onclick={() => { onclick={() => {
if ($events_loc.launcher.ws_connect) { if (launcher_loc.current.ws_connect) {
$events_sess.launcher.trigger__ws_disconnect = true; $events_sess.launcher.trigger__ws_disconnect = true;
} else { } else {
$events_loc.launcher.ws_connect = true; launcher_loc.current.ws_connect = true;
$events_sess.launcher.trigger__ws_connect = true; $events_sess.launcher.trigger__ws_connect = true;
} }
$events_sess.launcher.controller_unlock_group_code = false; $events_sess.launcher.controller_unlock_group_code = false;
}} }}
class="btn btn-sm text-[10px] font-bold transition-all" class="btn btn-sm text-[10px] font-bold transition-all"
class:preset-tonal-error={$events_loc.launcher.ws_connect} class:preset-tonal-error={launcher_loc.current.ws_connect}
class:preset-tonal-success={!$events_loc.launcher.ws_connect}> class:preset-tonal-success={!launcher_loc.current.ws_connect}>
{#if $events_loc.launcher.ws_connect} {#if launcher_loc.current.ws_connect}
<Unlink size="0.85em" class="mr-1" /> Disconnect <Unlink size="0.85em" class="mr-1" /> Disconnect
{:else} {:else}
<Link size="0.85em" class="mr-1" /> Connect Now <Link size="0.85em" class="mr-1" /> Connect Now
@@ -107,7 +108,7 @@ const ws_connected = $derived(
</p> </p>
<div class="flex gap-1"> <div class="flex gap-1">
<input <input
bind:value={$events_loc.launcher.controller_group_code} bind:value={launcher_loc.current.controller_group_code}
placeholder="Group Code" placeholder="Group Code"
class="input input-sm preset-tonal-surface h-7 grow font-mono text-[10px]" class="input input-sm preset-tonal-surface h-7 grow font-mono text-[10px]"
readonly={!$events_sess.launcher readonly={!$events_sess.launcher

View File

@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { ae_loc } from '$lib/stores/ae_stores'; import { ae_loc } from '$lib/stores/ae_stores';
import { events_loc, events_sess } from '$lib/stores/ae_events_stores'; import { events_sess } from '$lib/stores/ae_events_stores';
import { launcher_loc } from '$lib/stores/ae_events_stores__launcher.svelte';
import Launcher_Cfg_Section from './launcher_cfg_section.svelte'; import Launcher_Cfg_Section from './launcher_cfg_section.svelte';
import { HeartPulse, RefreshCw } from '@lucide/svelte'; import { HeartPulse, RefreshCw } from '@lucide/svelte';
interface Props { interface Props {
@@ -44,7 +45,7 @@ function get_usage_color(pct: number) {
<Launcher_Cfg_Section <Launcher_Cfg_Section
title="System & Sync Health" title="System & Sync Health"
icon={HeartPulse} icon={HeartPulse}
bind:state={$events_loc.launcher.section_state__health} bind:state={launcher_loc.current.section_state__health}
{on_expand} {on_expand}
description="Heartbeat: {$events_sess.launcher.heartbeat_info description="Heartbeat: {$events_sess.launcher.heartbeat_info
.last_timestamp || 'Pending'}"> .last_timestamp || 'Pending'}">

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { ae_api, ae_loc } from '$lib/stores/ae_stores'; import { ae_api, ae_loc } from '$lib/stores/ae_stores';
import { events_func } from '$lib/ae_events/ae_events_functions'; import { events_func } from '$lib/ae_events/ae_events_functions';
import { events_loc } from '$lib/stores/ae_events_stores'; import { launcher_loc } from '$lib/stores/ae_events_stores__launcher.svelte';
import { import {
DEFAULT_LAUNCH_PROFILE_DEFS, DEFAULT_LAUNCH_PROFILE_DEFS,
resolve_launch_profile, resolve_launch_profile,
@@ -268,7 +268,7 @@ async function reset_profile_delay(profile_name: string) {
<Launcher_Cfg_Section <Launcher_Cfg_Section
title="Launch Timing" title="Launch Timing"
icon={Timer} icon={Timer}
bind:state={$events_loc.launcher.section_state__launch_timing} bind:state={launcher_loc.current.section_state__launch_timing}
{on_expand} {on_expand}
description="Per-profile post-open delay overrides"> description="Per-profile post-open delay overrides">
{#if $ae_loc.edit_mode && !$ae_loc.is_native} {#if $ae_loc.edit_mode && !$ae_loc.is_native}

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { ae_loc, ae_api } from '$lib/stores/ae_stores'; import { ae_loc, ae_api } from '$lib/stores/ae_stores';
import { events_loc } from '$lib/stores/ae_events_stores'; import { launcher_loc } from '$lib/stores/ae_events_stores__launcher.svelte';
import { cleanup_tmp_files } from '$lib/electron/electron_relay'; import { cleanup_tmp_files } from '$lib/electron/electron_relay';
import Launcher_Cfg_Section from './launcher_cfg_section.svelte'; import Launcher_Cfg_Section from './launcher_cfg_section.svelte';
import { Bug, BugOff, Eraser, Eye, EyeOff, Wrench } from '@lucide/svelte'; import { Bug, BugOff, Eraser, Eye, EyeOff, Wrench } from '@lucide/svelte';
@@ -18,7 +18,7 @@ async function handle_cleanup_now() {
cleanup_status = 'Error: Cache path not set.'; cleanup_status = 'Error: Cache path not set.';
return; return;
} }
const max_age_hours = $events_loc.launcher.cleanup_tmp_max_age_hours ?? 24; const max_age_hours = launcher_loc.current.cleanup_tmp_max_age_hours ?? 24;
cleanup_status = 'Cleaning...'; cleanup_status = 'Cleaning...';
const result = await cleanup_tmp_files({ const result = await cleanup_tmp_files({
cache_root, cache_root,
@@ -80,7 +80,7 @@ function handle_reset_action(val: string) {
<Launcher_Cfg_Section <Launcher_Cfg_Section
title="Local Reset & Actions" title="Local Reset & Actions"
icon={Wrench} icon={Wrench}
bind:state={$events_loc.launcher.section_state__local_actions} bind:state={launcher_loc.current.section_state__local_actions}
{on_expand} {on_expand}
description="Cache wiping and global menu toggles"> description="Cache wiping and global menu toggles">
<div class="col-span-full flex flex-col gap-3"> <div class="col-span-full flex flex-col gap-3">
@@ -154,7 +154,7 @@ function handle_reset_action(val: string) {
min="1" min="1"
max="168" max="168"
bind:value={ bind:value={
$events_loc.launcher.cleanup_tmp_max_age_hours launcher_loc.current.cleanup_tmp_max_age_hours
} }
class="input input-sm preset-tonal-surface h-7 w-16 text-center text-xs" class="input input-sm preset-tonal-surface h-7 w-16 text-center text-xs"
placeholder="24" /> placeholder="24" />

View File

@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { ae_loc, ae_api, ae_sess } from '$lib/stores/ae_stores'; import { ae_loc, ae_api, ae_sess } from '$lib/stores/ae_stores';
import { events_loc, events_sess } from '$lib/stores/ae_events_stores'; import { events_sess } from '$lib/stores/ae_events_stores';
import { launcher_loc } from '$lib/stores/ae_events_stores__launcher.svelte';
import * as native from '$lib/electron/electron_relay'; import * as native from '$lib/electron/electron_relay';
import Launcher_Cfg_Section from './launcher_cfg_section.svelte'; import Launcher_Cfg_Section from './launcher_cfg_section.svelte';
import { import {
@@ -98,7 +99,7 @@ let show_power_confirm = $state<{ action: string; label: string } | null>(null);
<Launcher_Cfg_Section <Launcher_Cfg_Section
title="Native OS Management" title="Native OS Management"
icon={Code} icon={Code}
bind:state={$events_loc.launcher.section_state__native_os} bind:state={launcher_loc.current.section_state__native_os}
{on_expand} {on_expand}
description="OS: {$ae_loc.native_device?.meta_json?.platform || description="OS: {$ae_loc.native_device?.meta_json?.platform ||
'...'} | Kiosk & Apps"> '...'} | Kiosk & Apps">

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { ae_loc } from '$lib/stores/ae_stores'; import { ae_loc } from '$lib/stores/ae_stores';
import { events_loc } from '$lib/stores/ae_events_stores'; import { launcher_loc } from '$lib/stores/ae_events_stores__launcher.svelte';
import Launcher_Cfg_Section from './launcher_cfg_section.svelte'; import Launcher_Cfg_Section from './launcher_cfg_section.svelte';
import { IdCard } from '@lucide/svelte'; import { IdCard } from '@lucide/svelte';
interface Props { interface Props {
@@ -12,9 +12,9 @@ let { on_expand }: Props = $props();
<Launcher_Cfg_Section <Launcher_Cfg_Section
title="Poster Screen Saver" title="Poster Screen Saver"
icon={IdCard} icon={IdCard}
bind:state={$events_loc.launcher.section_state__screen_saver} bind:state={launcher_loc.current.section_state__screen_saver}
{on_expand} {on_expand}
description="Idle: {($events_loc.launcher.idle_timer / 60000).toFixed( description="Idle: {(launcher_loc.current.idle_timer / 60000).toFixed(
1 1
)}m | Auto-Posters"> )}m | Auto-Posters">
<!-- Content omitted for brevity, preserved in file --> <!-- Content omitted for brevity, preserved in file -->
@@ -32,7 +32,7 @@ let { on_expand }: Props = $props();
<input <input
type="number" type="number"
min={3000} min={3000}
bind:value={$events_loc.launcher.idle_timer} bind:value={launcher_loc.current.idle_timer}
class="input input-sm preset-tonal-surface h-7 w-24 text-right text-[10px]" /> class="input input-sm preset-tonal-surface h-7 w-24 text-right text-[10px]" />
</div> </div>
<div class="flex items-center justify-between gap-4"> <div class="flex items-center justify-between gap-4">
@@ -40,7 +40,7 @@ let { on_expand }: Props = $props();
<input <input
type="number" type="number"
min={500} min={500}
bind:value={$events_loc.launcher.idle_cycle} bind:value={launcher_loc.current.idle_cycle}
class="input input-sm preset-tonal-surface h-7 w-24 text-right text-[10px]" /> class="input input-sm preset-tonal-surface h-7 w-24 text-right text-[10px]" />
</div> </div>
<div class="flex items-center justify-between gap-4"> <div class="flex items-center justify-between gap-4">
@@ -49,7 +49,7 @@ let { on_expand }: Props = $props();
<input <input
type="number" type="number"
min={750} min={750}
bind:value={$events_loc.launcher.idle_loop_period} bind:value={launcher_loc.current.idle_loop_period}
class="input input-sm preset-tonal-surface h-7 w-24 text-right text-[10px]" /> class="input input-sm preset-tonal-surface h-7 w-24 text-right text-[10px]" />
</div> </div>
</div> </div>
@@ -61,7 +61,7 @@ let { on_expand }: Props = $props();
<div class="flex items-center justify-between text-xs"> <div class="flex items-center justify-between text-xs">
<span class="opacity-60">Active Idle Timeout:</span> <span class="opacity-60">Active Idle Timeout:</span>
<span class="text-primary-500 font-bold" <span class="text-primary-500 font-bold"
>{($events_loc.launcher.idle_timer / 60000).toFixed(1)} minutes</span> >{(launcher_loc.current.idle_timer / 60000).toFixed(1)} minutes</span>
</div> </div>
<p class="text-[9px] italic opacity-40"> <p class="text-[9px] italic opacity-40">
The screen saver automatically rotates digital posters when The screen saver automatically rotates digital posters when

View File

@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { ae_loc } from '$lib/stores/ae_stores'; import { ae_loc } from '$lib/stores/ae_stores';
import { events_loc, events_sess } from '$lib/stores/ae_events_stores'; import { events_sess } from '$lib/stores/ae_events_stores';
import { launcher_loc } from '$lib/stores/ae_events_stores__launcher.svelte';
import Launcher_Cfg_Section from './launcher_cfg_section.svelte'; import Launcher_Cfg_Section from './launcher_cfg_section.svelte';
import { CloudDownload, Pause, Play, RefreshCw } from '@lucide/svelte'; import { CloudDownload, Pause, Play, RefreshCw } from '@lucide/svelte';
interface Props { interface Props {
@@ -12,7 +13,7 @@ let { on_expand }: Props = $props();
<Launcher_Cfg_Section <Launcher_Cfg_Section
title="Sync Engine & Timers" title="Sync Engine & Timers"
icon={RefreshCw} icon={RefreshCw}
bind:state={$events_loc.launcher.section_state__sync_timers} bind:state={launcher_loc.current.section_state__sync_timers}
{on_expand} {on_expand}
description="Prefix: {$ae_loc.native_device?.hash_prefix_length || description="Prefix: {$ae_loc.native_device?.hash_prefix_length ||
2} | Loops: Active"> 2} | Loops: Active">
@@ -21,19 +22,19 @@ let { on_expand }: Props = $props();
<div <div
class="border-surface-500/10 bg-surface-500/5 mb-2 flex items-center justify-between rounded border p-2"> class="border-surface-500/10 bg-surface-500/5 mb-2 flex items-center justify-between rounded border p-2">
<span class="text-[10px] font-bold tracking-wider uppercase opacity-70"> <span class="text-[10px] font-bold tracking-wider uppercase opacity-70">
{$events_loc.launcher.sync_paused {launcher_loc.current.sync_paused
? '⏸ Sync Paused' ? '⏸ Sync Paused'
: '▶ Sync Active'} : '▶ Sync Active'}
</span> </span>
<button <button
type="button" type="button"
onclick={() => onclick={() =>
($events_loc.launcher.sync_paused = (launcher_loc.current.sync_paused =
!$events_loc.launcher.sync_paused)} !launcher_loc.current.sync_paused)}
class="btn btn-xs transition-all" class="btn btn-xs transition-all"
class:preset-tonal-warning={$events_loc.launcher.sync_paused} class:preset-tonal-warning={launcher_loc.current.sync_paused}
class:preset-tonal-success={!$events_loc.launcher.sync_paused}> class:preset-tonal-success={!launcher_loc.current.sync_paused}>
{#if $events_loc.launcher.sync_paused} {#if launcher_loc.current.sync_paused}
<Play size="0.85em" class="mr-1" /> Resume <Play size="0.85em" class="mr-1" /> Resume
{:else} {:else}
<Pause size="0.85em" class="mr-1" /> Pause <Pause size="0.85em" class="mr-1" /> Pause
@@ -51,7 +52,7 @@ let { on_expand }: Props = $props();
Force Sync Location Force Sync Location
</button> </button>
{#if $events_loc.launcher.sync_intervals} {#if launcher_loc.current.sync_intervals}
<div class="grid grid-cols-1 gap-3"> <div class="grid grid-cols-1 gap-3">
<!-- Technical Timers (Edit Mode Only) --> <!-- Technical Timers (Edit Mode Only) -->
{#if $ae_loc.edit_mode} {#if $ae_loc.edit_mode}
@@ -66,7 +67,7 @@ let { on_expand }: Props = $props();
<input <input
type="number" type="number"
bind:value={ bind:value={
$events_loc.launcher.sync_intervals.event launcher_loc.current.sync_intervals.event
} }
class="input input-sm preset-tonal-surface h-7 text-[10px]" /> class="input input-sm preset-tonal-surface h-7 text-[10px]" />
</div> </div>
@@ -76,7 +77,7 @@ let { on_expand }: Props = $props();
<input <input
type="number" type="number"
bind:value={ bind:value={
$events_loc.launcher.sync_intervals.device launcher_loc.current.sync_intervals.device
} }
class="input input-sm preset-tonal-surface h-7 text-[10px]" /> class="input input-sm preset-tonal-surface h-7 text-[10px]" />
</div> </div>
@@ -86,7 +87,7 @@ let { on_expand }: Props = $props();
<input <input
type="number" type="number"
bind:value={ bind:value={
$events_loc.launcher.sync_intervals.location launcher_loc.current.sync_intervals.location
} }
class="input input-sm preset-tonal-surface h-7 text-[10px]" /> class="input input-sm preset-tonal-surface h-7 text-[10px]" />
</div> </div>
@@ -96,7 +97,7 @@ let { on_expand }: Props = $props();
<input <input
type="number" type="number"
bind:value={ bind:value={
$events_loc.launcher.sync_intervals.session launcher_loc.current.sync_intervals.session
} }
class="input input-sm preset-tonal-surface h-7 text-[10px]" /> class="input input-sm preset-tonal-surface h-7 text-[10px]" />
</div> </div>
@@ -106,7 +107,7 @@ let { on_expand }: Props = $props();
<input <input
type="number" type="number"
bind:value={ bind:value={
$events_loc.launcher.sync_intervals launcher_loc.current.sync_intervals
.presentation .presentation
} }
class="input input-sm preset-tonal-surface h-7 text-[10px]" /> class="input input-sm preset-tonal-surface h-7 text-[10px]" />
@@ -117,7 +118,7 @@ let { on_expand }: Props = $props();
<input <input
type="number" type="number"
bind:value={ bind:value={
$events_loc.launcher.sync_intervals launcher_loc.current.sync_intervals
.presenter .presenter
} }
class="input input-sm preset-tonal-surface h-7 text-[10px]" /> class="input input-sm preset-tonal-surface h-7 text-[10px]" />
@@ -161,7 +162,7 @@ let { on_expand }: Props = $props();
<span>Event Sync:</span> <span>Event Sync:</span>
<span <span
>{( >{(
$events_loc.launcher.sync_intervals.event / 1000 launcher_loc.current.sync_intervals.event / 1000
).toFixed(1)}s</span> ).toFixed(1)}s</span>
</div> </div>
<div <div
@@ -169,7 +170,7 @@ let { on_expand }: Props = $props();
<span>Room Monitor:</span> <span>Room Monitor:</span>
<span <span
>{( >{(
$events_loc.launcher.sync_intervals.location / launcher_loc.current.sync_intervals.location /
1000 1000
).toFixed(1)}s</span> ).toFixed(1)}s</span>
</div> </div>

View File

@@ -5,7 +5,8 @@
* for the Launcher Configuration overhaul. * for the Launcher Configuration overhaul.
*/ */
import { ae_loc, ae_api, ae_sess } from '$lib/stores/ae_stores'; import { ae_loc, ae_api, ae_sess } from '$lib/stores/ae_stores';
import { events_loc, events_sess } from '$lib/stores/ae_events_stores'; import { events_sess } from '$lib/stores/ae_events_stores';
import { launcher_loc } from '$lib/stores/ae_events_stores__launcher.svelte';
import Launcher_Cfg_Section from './launcher_cfg_section.svelte'; import Launcher_Cfg_Section from './launcher_cfg_section.svelte';
import { import {
TriangleAlert, TriangleAlert,
@@ -48,7 +49,7 @@ let show_confirm = $state(false);
<Launcher_Cfg_Section <Launcher_Cfg_Section
title="Template Section" title="Template Section"
icon={Boxes} icon={Boxes}
bind:state={$events_loc.launcher.section_state__template} bind:state={launcher_loc.current.section_state__template}
{on_expand} {on_expand}
description="Kitchen Sink Scaffold | Demo Only"> description="Kitchen Sink Scaffold | Demo Only">
<!-- A. TOP STATUS BAR (Optional) --> <!-- A. TOP STATUS BAR (Optional) -->

View File

@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { ae_loc, ae_api, ae_sess } from '$lib/stores/ae_stores'; import { ae_loc, ae_api, ae_sess } from '$lib/stores/ae_stores';
import { events_loc, events_sess } from '$lib/stores/ae_events_stores'; import { events_sess } from '$lib/stores/ae_events_stores';
import { launcher_loc } from '$lib/stores/ae_events_stores__launcher.svelte';
import * as native from '$lib/electron/electron_relay'; import * as native from '$lib/electron/electron_relay';
import Launcher_Cfg_Section from './launcher_cfg_section.svelte'; import Launcher_Cfg_Section from './launcher_cfg_section.svelte';
import { CloudDownload, LoaderCircle, Search, Wand2 } from '@lucide/svelte'; import { CloudDownload, LoaderCircle, Search, Wand2 } from '@lucide/svelte';
@@ -55,7 +56,7 @@ async function handle_install() {
<Launcher_Cfg_Section <Launcher_Cfg_Section
title="Application Updates" title="Application Updates"
icon={CloudDownload} icon={CloudDownload}
bind:state={$events_loc.launcher.section_state__updates} bind:state={launcher_loc.current.section_state__updates}
{on_expand} {on_expand}
description="v1.0.0 | Source: {update_source}"> description="v1.0.0 | Source: {update_source}">
<!-- Content omitted for brevity, preserved in file --> <!-- Content omitted for brevity, preserved in file -->

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { ae_api, ae_loc } from '$lib/stores/ae_stores'; import { ae_api, ae_loc } from '$lib/stores/ae_stores';
import { events_func } from '$lib/ae_events/ae_events_functions'; import { events_func } from '$lib/ae_events/ae_events_functions';
import { events_loc } from '$lib/stores/ae_events_stores'; import { launcher_loc } from '$lib/stores/ae_events_stores__launcher.svelte';
import * as native from '$lib/electron/electron_relay'; import * as native from '$lib/electron/electron_relay';
import Launcher_Cfg_Section from './launcher_cfg_section.svelte'; import Launcher_Cfg_Section from './launcher_cfg_section.svelte';
import { FlaskConical, Image, RotateCcw, Save, Zap } from '@lucide/svelte'; import { FlaskConical, Image, RotateCcw, Save, Zap } from '@lucide/svelte';
@@ -150,8 +150,8 @@ async function handle_apply(): Promise<{ success: boolean; linux_test?: boolean
linux_test_popup_open = true; linux_test_popup_open = true;
return { success: true, linux_test: true }; return { success: true, linux_test: true };
} else if (result?.success) { } else if (result?.success) {
$events_loc.launcher.wallpaper_applied_url = url || null; launcher_loc.current.wallpaper_applied_url = url || null;
$events_loc.launcher.wallpaper_applied_url_external = url_ext || null; launcher_loc.current.wallpaper_applied_url_external = url_ext || null;
return { success: true }; return { success: true };
} }
return { success: false }; return { success: false };
@@ -181,8 +181,8 @@ async function handle_restore_default() {
const result = await native.restore_macos_default_wallpaper('all'); const result = await native.restore_macos_default_wallpaper('all');
if (result?.success) { if (result?.success) {
// Clear tracked applied URL so the next config URL re-applies correctly. // Clear tracked applied URL so the next config URL re-applies correctly.
$events_loc.launcher.wallpaper_applied_url = null; launcher_loc.current.wallpaper_applied_url = null;
$events_loc.launcher.wallpaper_applied_url_external = null; launcher_loc.current.wallpaper_applied_url_external = null;
set_status('Restored ✓'); set_status('Restored ✓');
} else { } else {
set_status(`Restore failed: ${(result as { error?: string })?.error ?? 'Unknown'}`); set_status(`Restore failed: ${(result as { error?: string })?.error ?? 'Unknown'}`);
@@ -192,7 +192,7 @@ async function handle_restore_default() {
const configured_url = $derived(get_configured_wallpaper().url ?? ''); const configured_url = $derived(get_configured_wallpaper().url ?? '');
const is_applied = $derived( const is_applied = $derived(
!!url_input.trim() && !!url_input.trim() &&
$events_loc.launcher.wallpaper_applied_url === url_input.trim() launcher_loc.current.wallpaper_applied_url === url_input.trim()
); );
const section_description = $derived( const section_description = $derived(
configured_url configured_url
@@ -206,7 +206,7 @@ const section_description = $derived(
<Launcher_Cfg_Section <Launcher_Cfg_Section
title="Wallpaper" title="Wallpaper"
icon={Image} icon={Image}
bind:state={$events_loc.launcher.section_state__wallpaper} bind:state={launcher_loc.current.section_state__wallpaper}
{on_expand} {on_expand}
description={section_description}> description={section_description}>

View File

@@ -64,6 +64,7 @@ import {
events_trigger, events_trigger,
events_trig events_trig
} from '$lib/stores/ae_events_stores'; } from '$lib/stores/ae_events_stores';
import { launcher_loc } from '$lib/stores/ae_events_stores__launcher.svelte';
import { events_func } from '$lib/ae_events/ae_events_functions'; import { events_func } from '$lib/ae_events/ae_events_functions';
import Launcher_cfg from '../launcher_cfg.svelte'; import Launcher_cfg from '../launcher_cfg.svelte';
@@ -82,63 +83,37 @@ $ae_sess.disable_sys_nav = true;
$ae_sess.disable_sys_header = true; $ae_sess.disable_sys_header = true;
$ae_sess.disable_sys_footer = true; $ae_sess.disable_sys_footer = true;
if (!$events_loc?.launcher) { // WHY: launcher_loc is a PersistedState store — always initialized with defaults.
$events_loc.launcher = { // No need for initialization guards; all fields have default values in launcher_loc_defaults.
app_mode: 'default',
controller: 'local',
controller_group_code: 'launcher-00',
ws_connect: false,
hide_drawer__cfg: true,
hide_drawer__debug: true
};
}
// WHY: The initialization block above only runs when launcher is completely absent.
// If the user has an older persisted config (from before the Modal migration),
// hide_drawer__cfg may be missing → undefined → !undefined = true → modal opens
// on every load. Explicitly initialize it here to ensure it is always a boolean.
if ($events_loc.launcher.hide_drawer__cfg === undefined) {
$events_loc.launcher.hide_drawer__cfg = true;
}
let modal_cfg_open = $state(!$events_loc.launcher.hide_drawer__cfg); let modal_cfg_open = $state(!launcher_loc.current.hide_drawer__cfg);
// Sync store → modal: biohazard button writes hide_drawer__cfg = false to open. // Sync store → modal: biohazard button writes hide_drawer__cfg = false to open.
// Equality guard prevents spurious writes from unrelated $events_loc updates
// (Svelte 4 whole-store subscription fires on every field write to the store).
$effect(() => { $effect(() => {
const should_open = !$events_loc.launcher.hide_drawer__cfg; const should_open = !launcher_loc.current.hide_drawer__cfg;
if (modal_cfg_open !== should_open) { if (modal_cfg_open !== should_open) {
modal_cfg_open = should_open; modal_cfg_open = should_open;
} }
}); });
// Sync modal → store: use events_loc.update() directly rather than $-syntax so // Sync modal → store: direct assignment to PersistedState triggers localStorage persistence.
// the write always reaches the persisted store's serialization. $-syntax writes
// inside $effect contexts may be suppressed and not trigger localStorage persistence.
$effect(() => { $effect(() => {
const should_hide = !modal_cfg_open; launcher_loc.current.hide_drawer__cfg = !modal_cfg_open;
events_loc.update((loc) => {
if (loc.launcher) loc.launcher.hide_drawer__cfg = should_hide;
return loc;
});
}); });
// Called by backdrop click. Writes to store immediately (don't rely on effect timing). // Called by backdrop click. Writes to store immediately (don't rely on effect timing).
function close_cfg() { function close_cfg() {
modal_cfg_open = false; modal_cfg_open = false;
events_loc.update((loc) => { launcher_loc.current.hide_drawer__cfg = true;
if (loc.launcher) loc.launcher.hide_drawer__cfg = true;
return loc;
});
} }
// Generate a stable per-device client ID on first load and persist it. // Generate a stable per-device client ID on first load and persist it.
// events_loc is backed by svelte-persisted-store (localStorage) so this // launcher_loc is a PersistedState store (localStorage) so this survives
// survives page reloads. Without this, client_id falls back to Date.now() // page reloads. Without this, client_id falls back to Date.now() inside
// inside element_websocket — a new ID on every reload, which breaks // element_websocket — a new ID on every reload, which breaks direct-target
// direct-target WS messages and doesn't match V3 Vision ID expectations. // WS messages and doesn't match V3 Vision ID expectations.
if (!$events_loc.launcher.controller_client_id) { if (!launcher_loc.current.controller_client_id) {
$events_loc.launcher.controller_client_id = crypto.randomUUID(); launcher_loc.current.controller_client_id = crypto.randomUUID();
} }
// Unified Selection Sync (Refactored 2026-02-11) // Unified Selection Sync (Refactored 2026-02-11)
@@ -198,19 +173,19 @@ $effect(() => {
else if (param_iframe === 'false') $ae_loc.iframe = false; else if (param_iframe === 'false') $ae_loc.iframe = false;
if (param_launcher_menu === 'hide') if (param_launcher_menu === 'hide')
$events_loc.launcher.hide__launcher_menu = true; launcher_loc.current.hide__launcher_menu = true;
else if (param_launcher_menu === 'show') else if (param_launcher_menu === 'show')
$events_loc.launcher.hide__launcher_menu = false; launcher_loc.current.hide__launcher_menu = false;
if (param_launcher_header === 'hide') if (param_launcher_header === 'hide')
$events_loc.launcher.hide__launcher_header = true; launcher_loc.current.hide__launcher_header = true;
else if (param_launcher_header === 'show') else if (param_launcher_header === 'show')
$events_loc.launcher.hide__launcher_header = false; launcher_loc.current.hide__launcher_header = false;
if (param_launcher_footer === 'hide') if (param_launcher_footer === 'hide')
$events_loc.launcher.hide__launcher_footer = true; launcher_loc.current.hide__launcher_footer = true;
else if (param_launcher_footer === 'show') else if (param_launcher_footer === 'show')
$events_loc.launcher.hide__launcher_footer = false; launcher_loc.current.hide__launcher_footer = false;
}); });
// Strip launcher display params from the URL after applying them — same pattern // Strip launcher display params from the URL after applying them — same pattern
@@ -422,7 +397,7 @@ function handle_ws_recv(ws_recv_status: any) {
// V3 schema uses msg_type instead of type // V3 schema uses msg_type instead of type
if (ws_recv_status.msg_type == 'cmd' && ws_recv_status.cmd) { if (ws_recv_status.msg_type == 'cmd' && ws_recv_status.cmd) {
let cmd = ws_recv_status.cmd; let cmd = ws_recv_status.cmd;
if ($events_loc.launcher.controller != 'remote') return; if (launcher_loc.current.controller != 'remote') return;
if (cmd.startsWith('ae_load:')) { if (cmd.startsWith('ae_load:')) {
let cmd_parts = cmd.split(':'); let cmd_parts = cmd.split(':');
@@ -508,14 +483,14 @@ function handle_ws_recv(ws_recv_status: any) {
const mode_target = cmd.split(':')[1]; const mode_target = cmd.split(':')[1];
if (mode_target === 'poster') { if (mode_target === 'poster') {
$ae_loc.iframe = true; $ae_loc.iframe = true;
$events_loc.launcher.hide__launcher_menu = true; launcher_loc.current.hide__launcher_menu = true;
$events_loc.launcher.hide__launcher_header = true; launcher_loc.current.hide__launcher_header = true;
$events_loc.launcher.hide__launcher_footer = true; launcher_loc.current.hide__launcher_footer = true;
} else if (mode_target === 'oral') { } else if (mode_target === 'oral') {
$ae_loc.iframe = false; $ae_loc.iframe = false;
$events_loc.launcher.hide__launcher_menu = false; launcher_loc.current.hide__launcher_menu = false;
$events_loc.launcher.hide__launcher_header = false; launcher_loc.current.hide__launcher_header = false;
$events_loc.launcher.hide__launcher_footer = false; launcher_loc.current.hide__launcher_footer = false;
} }
} }
} }
@@ -547,16 +522,16 @@ $effect(() => {
} }
}); });
if (!$events_loc.launcher.idle_timer) if (!launcher_loc.current.idle_timer)
$events_loc.launcher.idle_timer = 5 * 60 * 1000; launcher_loc.current.idle_timer = 5 * 60 * 1000;
if (!$events_loc.launcher.idle_cycle) if (!launcher_loc.current.idle_cycle)
$events_loc.launcher.idle_cycle = 5 * 1000; launcher_loc.current.idle_cycle = 5 * 1000;
if (!$events_loc.launcher.idle_loop_period) if (!launcher_loc.current.idle_loop_period)
$events_loc.launcher.idle_loop_period = 3 * 60 * 1000; launcher_loc.current.idle_loop_period = 3 * 60 * 1000;
listen({ listen({
timer: $events_loc.launcher.idle_timer, timer: launcher_loc.current.idle_timer,
cycle: $events_loc.launcher.idle_cycle cycle: launcher_loc.current.idle_cycle
}); });
let idle_timer_interval: any = $state(); let idle_timer_interval: any = $state();
@@ -575,13 +550,13 @@ function handle_idle_client() {
idle_timer_interval = setInterval( idle_timer_interval = setInterval(
() => { () => {
if ($events_loc.launcher.screen_saver_img_kv) { if (launcher_loc.current.screen_saver_img_kv) {
const keys = Object.keys( const keys = Object.keys(
$events_loc.launcher.screen_saver_img_kv launcher_loc.current.screen_saver_img_kv
); );
const rand_index = Math.floor(Math.random() * keys.length); const rand_index = Math.floor(Math.random() * keys.length);
let event_file_obj = let event_file_obj =
$events_loc.launcher.screen_saver_img_kv[ launcher_loc.current.screen_saver_img_kv[
keys[rand_index] keys[rand_index]
]; ];
@@ -598,7 +573,7 @@ function handle_idle_client() {
} }
return false; return false;
}, },
$events_loc.launcher.idle_loop_period ?? 2 * 60 * 1000 launcher_loc.current.idle_loop_period ?? 2 * 60 * 1000
); );
} else { } else {
saver_looping = false; saver_looping = false;
@@ -636,8 +611,8 @@ $effect(() => {
</svelte:head> </svelte:head>
<div <div
class:mt-12={!$events_loc.launcher.hide__launcher_header} class:mt-12={!launcher_loc.current.hide__launcher_header}
class:mt-2={$events_loc.launcher.hide__launcher_header} class:mt-2={launcher_loc.current.hide__launcher_header}
class=" class="
static static
m-auto m-auto
@@ -651,7 +626,7 @@ $effect(() => {
"> ">
<header <header
id="Main-Header" id="Main-Header"
class:hidden={$events_loc.launcher.hide__launcher_header} class:hidden={launcher_loc.current.hide__launcher_header}
class=" class="
absolute absolute
top-0 right-0 left-0 z-20 top-0 right-0 left-0 z-20
@@ -674,8 +649,8 @@ $effect(() => {
type="button" type="button"
class="hover:bg-surface-500/10 rounded px-2 py-1 transition-colors" class="hover:bg-surface-500/10 rounded px-2 py-1 transition-colors"
onclick={() => { onclick={() => {
$events_loc.launcher.hide__launcher_menu = launcher_loc.current.hide__launcher_menu =
!$events_loc.launcher.hide__launcher_menu; !launcher_loc.current.hide__launcher_menu;
}} }}
title="Toggle Launcher menu"> title="Toggle Launcher menu">
<Satellite class="mx-1 inline-block text-base text-gray-500" /> <Satellite class="mx-1 inline-block text-base text-gray-500" />
@@ -739,7 +714,7 @@ $effect(() => {
md:min-w-64 lg:min-w-72 dark:border-gray-700 md:min-w-64 lg:min-w-72 dark:border-gray-700
" "
class:hidden={$events_loc.launcher.hide__launcher_menu}> class:hidden={launcher_loc.current.hide__launcher_menu}>
<Launcher_menu <Launcher_menu
{lq__event_obj} {lq__event_obj}
{lq__event_event_file_obj_li} {lq__event_event_file_obj_li}
@@ -808,7 +783,7 @@ $effect(() => {
<footer <footer
id="Main-Footer" id="Main-Footer"
class:hidden={$events_loc.launcher.hide__launcher_footer} class:hidden={launcher_loc.current.hide__launcher_footer}
class=" class="
absolute absolute
right-0 bottom-0 left-0 z-20 right-0 bottom-0 left-0 z-20
@@ -868,7 +843,7 @@ $effect(() => {
</span> </span>
<span <span
class:hidden={!$events_loc.launcher.ws_connect} class:hidden={!launcher_loc.current.ws_connect}
class:preset-tonal-warning={$events_sess.launcher.ws_connect_status != class:preset-tonal-warning={$events_sess.launcher.ws_connect_status !=
'connected'} 'connected'}
class:preset-tonal-success={$events_sess.launcher.ws_connect_status == class:preset-tonal-success={$events_sess.launcher.ws_connect_status ==
@@ -918,7 +893,7 @@ $effect(() => {
<span class="hidden sm:inline"> <span class="hidden sm:inline">
<Clock size="1em" /> <Clock size="1em" />
</span> </span>
{#if $events_loc.launcher?.time_hours == 12} {#if launcher_loc.current?.time_hours == 12}
{ae_util.iso_datetime_formatter($time, 'time_12_long')} {ae_util.iso_datetime_formatter($time, 'time_12_long')}
{:else} {:else}
{ae_util.iso_datetime_formatter($time, 'time_long')} {ae_util.iso_datetime_formatter($time, 'time_long')}
@@ -929,7 +904,7 @@ $effect(() => {
<div class="absolute top-0 left-0 z-20 text-center"> <div class="absolute top-0 left-0 z-20 text-center">
<button <button
type="button" type="button"
onclick={() => ($events_loc.launcher.hide_drawer__cfg = false)} onclick={() => (launcher_loc.current.hide_drawer__cfg = false)}
class="btn btn-sm preset-tonal-error hover:preset-filled-error-500 p-3 transition-colors duration-300" class="btn btn-sm preset-tonal-error hover:preset-filled-error-500 p-3 transition-colors duration-300"
class:opacity-25={!$ae_loc.trusted_access} class:opacity-25={!$ae_loc.trusted_access}
class:hover:opacity-75={!$ae_loc.trusted_access}> class:hover:opacity-75={!$ae_loc.trusted_access}>
@@ -998,7 +973,7 @@ $effect(() => {
easing: sineIn easing: sineIn
} }
}} }}
bind:hidden={$events_loc.launcher.hide_drawer__debug} bind:hidden={launcher_loc.current.hide_drawer__debug}
id="sidebar2"> id="sidebar2">
<div class="flex flex-row items-center justify-between"> <div class="flex flex-row items-center justify-between">
<h2 <h2
@@ -1007,7 +982,7 @@ $effect(() => {
</h2> </h2>
<button <button
type="button" type="button"
onclick={() => ($events_loc.launcher.hide_drawer__debug = true)} onclick={() => (launcher_loc.current.hide_drawer__debug = true)}
class="mb-4 dark:text-white"> class="mb-4 dark:text-white">
<X size="1em" /> <X size="1em" />
<span class="hidden">Close Debug Drawer</span> <span class="hidden">Close Debug Drawer</span>
@@ -1016,7 +991,7 @@ $effect(() => {
<div> <div>
<pre class="text-xs"> <pre class="text-xs">
{JSON.stringify($events_loc.launcher, null, 2)} {JSON.stringify(launcher_loc.current, null, 2)}
</pre> </pre>
<hr /> <hr />
<pre class="text-xs"> <pre class="text-xs">
@@ -1035,15 +1010,15 @@ $effect(() => {
rounded-lg border-gray-200 bg-gray-500/90 text-gray-800 rounded-lg border-gray-200 bg-gray-500/90 text-gray-800
shadow-md shadow-md
dark:divide-gray-700 dark:border-gray-700 dark:bg-gray-800/90 dark:text-gray-200 dark:divide-gray-700 dark:border-gray-700 dark:bg-gray-800/90 dark:text-gray-200
{$events_loc.launcher.controller == 'remote' ? 'min-h-full' : ''} {launcher_loc.current.controller == 'remote' ? 'min-h-full' : ''}
min-w-full min-w-full
" "
bodyClass="p-0 space-y-0 overflow-auto flex flex-col gap-1 items-center justify-center pb-14" bodyClass="p-0 space-y-0 overflow-auto flex flex-col gap-1 items-center justify-center pb-14"
headerClass={`fixed top-0 right-0 left-0 p-1 md:p-2 flex flex-row items-center ${$events_loc.launcher.controller == 'remote' ? 'hidden' : ''} bg-white dark:bg-gray-800 opacity-50 ${$events_loc.launcher.hide__modal_header_title ? 'justify-center' : 'justify-between'}`} headerClass={`fixed top-0 right-0 left-0 p-1 md:p-2 flex flex-row items-center ${launcher_loc.current.controller == 'remote' ? 'hidden' : ''} bg-white dark:bg-gray-800 opacity-50 ${launcher_loc.current.hide__modal_header_title ? 'justify-center' : 'justify-between'}`}
footerClass="text-center hidden"> footerClass="text-center hidden">
{#snippet header()} {#snippet header()}
<h3 <h3
class:hidden={$events_loc.launcher.hide__modal_header_title} class:hidden={launcher_loc.current.hide__modal_header_title}
class="text-lg font-semibold opacity-20 transition-all hover:opacity-100"> class="text-lg font-semibold opacity-20 transition-all hover:opacity-100">
{$events_sess.launcher?.modal__title ?? 'Digital Poster Display'} {$events_sess.launcher?.modal__title ?? 'Digital Poster Display'}
</h3> </h3>
@@ -1084,7 +1059,7 @@ $effect(() => {
modal_zoom_fit = !modal_zoom_fit; modal_zoom_fit = !modal_zoom_fit;
// Sync zoom state to the remote display when acting as controller. // Sync zoom state to the remote display when acting as controller.
if ( if (
$events_loc.launcher.controller == 'local_push' && launcher_loc.current.controller == 'local_push' &&
$events_sess.launcher.ws_connect_status == 'connected' $events_sess.launcher.ws_connect_status == 'connected'
) { ) {
$events_sess.launcher.controller_cmd = `ae_zoom:${modal_zoom_fit ? 'fit' : 'zoom'}`; $events_sess.launcher.controller_cmd = `ae_zoom:${modal_zoom_fit ? 'fit' : 'zoom'}`;
@@ -1117,7 +1092,7 @@ $effect(() => {
bg-black/30 bg-black/30
p-1.5 backdrop-blur-sm p-1.5 backdrop-blur-sm
" "
class:hidden={$events_loc.launcher.controller == 'remote'}> class:hidden={launcher_loc.current.controller == 'remote'}>
<!-- Zoom / Fit toggle: accessibility accommodation — lets operators and general <!-- Zoom / Fit toggle: accessibility accommodation — lets operators and general
public zoom in to read details, pinch on mobile, or double-tap the image. --> public zoom in to read details, pinch on mobile, or double-tap the image. -->
<button <button
@@ -1126,7 +1101,7 @@ $effect(() => {
modal_zoom_fit = !modal_zoom_fit; modal_zoom_fit = !modal_zoom_fit;
// Sync zoom state to the remote display when acting as controller. // Sync zoom state to the remote display when acting as controller.
if ( if (
$events_loc.launcher.controller == 'local_push' && launcher_loc.current.controller == 'local_push' &&
$events_sess.launcher.ws_connect_status == 'connected' $events_sess.launcher.ws_connect_status == 'connected'
) { ) {
$events_sess.launcher.controller_cmd = `ae_zoom:${modal_zoom_fit ? 'fit' : 'zoom'}`; $events_sess.launcher.controller_cmd = `ae_zoom:${modal_zoom_fit ? 'fit' : 'zoom'}`;
@@ -1158,7 +1133,7 @@ $effect(() => {
$events_sess.launcher.modal__event_file_obj = null; $events_sess.launcher.modal__event_file_obj = null;
}} }}
class="btn btn-sm preset-tonal-error opacity-80 transition-all hover:opacity-100" class="btn btn-sm preset-tonal-error opacity-80 transition-all hover:opacity-100"
class:hidden={$events_loc.launcher.controller != 'local_push' || class:hidden={launcher_loc.current.controller != 'local_push' ||
$events_sess.launcher.ws_connect_status != 'connected'} $events_sess.launcher.ws_connect_status != 'connected'}
title="Close poster on this device and on the remote display (screensaver resumes)"> title="Close poster on this device and on the remote display (screensaver resumes)">
<Monitor size="1em" class="mr-1" /> <Monitor size="1em" class="mr-1" />
@@ -1177,7 +1152,7 @@ $effect(() => {
}} }}
class="btn btn-sm preset-tonal-surface border-surface-400/50 border opacity-80 transition-all hover:opacity-100" class="btn btn-sm preset-tonal-surface border-surface-400/50 border opacity-80 transition-all hover:opacity-100"
class:hidden={!$ae_loc.trusted_access && class:hidden={!$ae_loc.trusted_access &&
($events_loc.launcher.controller != 'local_push' || (launcher_loc.current.controller != 'local_push' ||
$events_sess.launcher.ws_connect_status != 'connected')} $events_sess.launcher.ws_connect_status != 'connected')}
title="Close poster on this device only — remote display keeps showing"> title="Close poster on this device only — remote display keeps showing">
<List size="1em" class="mr-1" /> <List size="1em" class="mr-1" />
@@ -1186,24 +1161,24 @@ $effect(() => {
</div> </div>
</Modal> </Modal>
{#if $events_loc.launcher.controller_group_code && $events_loc.launcher.ws_connect} {#if launcher_loc.current.controller_group_code && launcher_loc.current.ws_connect}
<Element_websocket <Element_websocket
{log_lvl} {log_lvl}
bind:ws_connect={$events_loc.launcher.ws_connect} bind:ws_connect={launcher_loc.current.ws_connect}
bind:ws_connect_status={$events_sess.launcher.ws_connect_status} bind:ws_connect_status={$events_sess.launcher.ws_connect_status}
ws_server={$ae_api.fqdn} ws_server={$ae_api.fqdn}
api_key={$ae_api.api_secret_key} api_key={$ae_api.api_secret_key}
jwt={$ae_loc.jwt} jwt={$ae_loc.jwt}
bind:group_id={$events_loc.launcher.controller_group_code} bind:group_id={launcher_loc.current.controller_group_code}
bind:client_id={$events_loc.launcher.controller_client_id} bind:client_id={launcher_loc.current.controller_client_id}
bind:cmd={$events_sess.launcher.controller_cmd} bind:cmd={$events_sess.launcher.controller_cmd}
bind:trigger_send={$events_sess.launcher.controller_trigger_send} bind:trigger_send={$events_sess.launcher.controller_trigger_send}
bind:trigger_connect={$events_sess.launcher.trigger__ws_connect} bind:trigger_connect={$events_sess.launcher.trigger__ws_connect}
bind:trigger_disconnect={$events_sess.launcher.trigger__ws_disconnect} bind:trigger_disconnect={$events_sess.launcher.trigger__ws_disconnect}
bind:hide__ws_element={$events_loc.launcher.hide__ws_element} bind:hide__ws_element={launcher_loc.current.hide__ws_element}
bind:hide__ws_form={$events_loc.launcher.hide__ws_form} bind:hide__ws_form={launcher_loc.current.hide__ws_form}
bind:hide__ws_messages={$events_loc.launcher.hide__ws_messages} bind:hide__ws_messages={launcher_loc.current.hide__ws_messages}
bind:hide__ws_commands={$events_loc.launcher.hide__ws_commands} bind:hide__ws_commands={launcher_loc.current.hide__ws_commands}
bind:ws_conn_status={trigger_handle_ws_conn} bind:ws_conn_status={trigger_handle_ws_conn}
bind:ws_recv_status={trigger_handle_ws_recv} bind:ws_recv_status={trigger_handle_ws_recv}
bind:ws_sent_status={trigger_handle_ws_sent} /> bind:ws_sent_status={trigger_handle_ws_sent} />

View File

@@ -11,7 +11,6 @@ let log_lvl: number = $state(0);
import { untrack } from 'svelte'; import { untrack } from 'svelte';
import { ae_loc, ae_sess, ae_api } from '$lib/stores/ae_stores'; import { ae_loc, ae_sess, ae_api } from '$lib/stores/ae_stores';
import { import {
events_loc,
events_sess, events_sess,
events_slct events_slct
} from '$lib/stores/ae_events_stores'; } from '$lib/stores/ae_events_stores';
@@ -47,23 +46,9 @@ $effect(() => {
} }
}); });
// Set localStorage defaults for launcher state // WHY: launcher_loc is a PersistedState store — always initialized with defaults from
if (!$events_loc.launcher) { // launcher_loc_defaults. No initialization guards needed; all fields (slct, show_content__*)
$events_loc.launcher = {}; // are already set to their default values on first load.
$events_loc.launcher.slct = { event_id: null };
$events_loc.launcher.show_content__session_code = true;
$events_loc.launcher.show_content__presentation_code = true;
$events_loc.launcher.show_content__presenter_code = true;
}
if (!$events_loc.launcher.slct) {
$events_loc.launcher.slct = {
event_id: null,
event_location_id: null,
event_session_id: null,
event_presentation_id: null,
event_presenter_id: null
};
}
// Set session storage defaults // Set session storage defaults
if (!$events_sess.launcher) $events_sess.launcher = {}; if (!$events_sess.launcher) $events_sess.launcher = {};

View File

@@ -7,10 +7,10 @@ import { ChevronDown, ChevronUp, Cpu, RefreshCw } from '@lucide/svelte';
import { onMount, onDestroy } from 'svelte'; import { onMount, onDestroy } from 'svelte';
import { ae_loc, ae_api } from '$lib/stores/ae_stores'; import { ae_loc, ae_api } from '$lib/stores/ae_stores';
import { import {
events_loc,
events_slct, events_slct,
events_sess events_sess
} from '$lib/stores/ae_events_stores'; } from '$lib/stores/ae_events_stores';
import { launcher_loc } from '$lib/stores/ae_events_stores__launcher.svelte';
import { events_func } from '$lib/ae_events/ae_events_functions'; import { events_func } from '$lib/ae_events/ae_events_functions';
import { ae_util } from '$lib/ae_utils/ae_utils'; import { ae_util } from '$lib/ae_utils/ae_utils';
import { db_events } from '$lib/ae_events/db_events'; import { db_events } from '$lib/ae_events/db_events';
@@ -50,8 +50,8 @@ async function apply_wallpaper_if_changed(device_other_json: unknown) {
const configured_url = wallpaper.url ?? ''; const configured_url = wallpaper.url ?? '';
const configured_url_external = wallpaper.url_external ?? ''; const configured_url_external = wallpaper.url_external ?? '';
const applied_url = $events_loc.launcher.wallpaper_applied_url ?? ''; const applied_url = launcher_loc.current.wallpaper_applied_url ?? '';
const applied_url_external = $events_loc.launcher.wallpaper_applied_url_external ?? ''; const applied_url_external = launcher_loc.current.wallpaper_applied_url_external ?? '';
const url_changed = configured_url && configured_url !== applied_url; const url_changed = configured_url && configured_url !== applied_url;
const ext_changed = configured_url_external !== applied_url_external; const ext_changed = configured_url_external !== applied_url_external;
@@ -76,8 +76,8 @@ async function apply_wallpaper_if_changed(device_other_json: unknown) {
// the cfg component popup can show the details when triggered manually. // the cfg component popup can show the details when triggered manually.
console.info('Sync: Wallpaper linux_test_mode — would have applied:', (result as any).would_run); console.info('Sync: Wallpaper linux_test_mode — would have applied:', (result as any).would_run);
} else if (result?.success) { } else if (result?.success) {
$events_loc.launcher.wallpaper_applied_url = configured_url || null; launcher_loc.current.wallpaper_applied_url = configured_url || null;
$events_loc.launcher.wallpaper_applied_url_external = configured_url_external || null; launcher_loc.current.wallpaper_applied_url_external = configured_url_external || null;
if (log_lvl) console.log('Sync: Wallpaper applied.'); if (log_lvl) console.log('Sync: Wallpaper applied.');
} else { } else {
console.warn('Sync: Wallpaper apply failed.', (result as any)?.error); console.warn('Sync: Wallpaper apply failed.', (result as any)?.error);
@@ -154,7 +154,7 @@ onMount(async () => {
} }
const dev = $ae_loc.native_device || {}; const dev = $ae_loc.native_device || {};
const cfg = $events_loc.launcher.sync_intervals || {}; const cfg = launcher_loc.current.sync_intervals || {};
// Load timings from persistent config, with fallbacks to device config or defaults. // Load timings from persistent config, with fallbacks to device config or defaults.
// Fallback values here must match the loop_info $state defaults above — keep in sync. // Fallback values here must match the loop_info $state defaults above — keep in sync.
@@ -228,7 +228,7 @@ onMount(async () => {
const cache_root = $ae_loc.local_file_cache_path; const cache_root = $ae_loc.local_file_cache_path;
if ($ae_loc.is_native && cache_root) { if ($ae_loc.is_native && cache_root) {
const max_age_hours = const max_age_hours =
$events_loc.launcher.cleanup_tmp_max_age_hours ?? 24; launcher_loc.current.cleanup_tmp_max_age_hours ?? 24;
cleanup_tmp_files({ cleanup_tmp_files({
cache_root, cache_root,
max_age_minutes: max_age_hours * 60 max_age_minutes: max_age_hours * 60
@@ -285,7 +285,7 @@ $effect(() => {
* API Refresh: Event * API Refresh: Event
*/ */
async function refresh_event_data() { async function refresh_event_data() {
if ($events_loc.launcher.sync_paused) return; if (launcher_loc.current.sync_paused) return;
if (!$events_slct.event_id) return; if (!$events_slct.event_id) return;
try { try {
await events_func.load_ae_obj_id__event({ await events_func.load_ae_obj_id__event({
@@ -304,7 +304,7 @@ async function refresh_event_data() {
* API Refresh: Sessions in Room * API Refresh: Sessions in Room
*/ */
async function refresh_session_data() { async function refresh_session_data() {
if ($events_loc.launcher.sync_paused) return; if (launcher_loc.current.sync_paused) return;
const location_id = $events_slct.event_location_id; const location_id = $events_slct.event_location_id;
if (!location_id) return; if (!location_id) return;
try { try {
@@ -328,7 +328,7 @@ async function refresh_session_data() {
* API Refresh: Presentations for Selected Session * API Refresh: Presentations for Selected Session
*/ */
async function refresh_presentation_data() { async function refresh_presentation_data() {
if ($events_loc.launcher.sync_paused) return; if (launcher_loc.current.sync_paused) return;
const session_id = $events_slct.event_session_id; const session_id = $events_slct.event_session_id;
if (!session_id) return; if (!session_id) return;
try { try {
@@ -350,7 +350,7 @@ async function refresh_presentation_data() {
* API Refresh: Presenters for Selected Session * API Refresh: Presenters for Selected Session
*/ */
async function refresh_presenter_data() { async function refresh_presenter_data() {
if ($events_loc.launcher.sync_paused) return; if (launcher_loc.current.sync_paused) return;
const session_id = $events_slct.event_session_id; const session_id = $events_slct.event_session_id;
if (!session_id) return; if (!session_id) return;
try { try {
@@ -370,7 +370,7 @@ async function refresh_presenter_data() {
} }
async function run_sync_cycle() { async function run_sync_cycle() {
if ($events_loc.launcher.sync_paused) return; if (launcher_loc.current.sync_paused) return;
const location_id = $events_slct.event_location_id; const location_id = $events_slct.event_location_id;
const cache_root = $ae_loc.local_file_cache_path; const cache_root = $ae_loc.local_file_cache_path;
const prefix_len = $ae_loc.native_device?.hash_prefix_length || 2; const prefix_len = $ae_loc.native_device?.hash_prefix_length || 2;
@@ -454,7 +454,7 @@ async function run_sync_cycle() {
// Re-check pause flag each iteration — a sync cycle can run for many // Re-check pause flag each iteration — a sync cycle can run for many
// seconds if there are missing files, so we must honour a pause request // seconds if there are missing files, so we must honour a pause request
// mid-loop rather than waiting for the entire batch to finish. // mid-loop rather than waiting for the entire batch to finish.
if ($events_loc.launcher.sync_paused) break; if (launcher_loc.current.sync_paused) break;
if (!file_obj.hash_sha256) continue; if (!file_obj.hash_sha256) continue;
if (sync_results[file_obj.event_file_id] === 'success') { if (sync_results[file_obj.event_file_id] === 'success') {
@@ -602,7 +602,7 @@ async function run_device_heartbeat() {
* Ensures we have latest room settings. * Ensures we have latest room settings.
*/ */
async function refresh_location_config() { async function refresh_location_config() {
if ($events_loc.launcher.sync_paused) return; if (launcher_loc.current.sync_paused) return;
const location_id = $events_slct.event_location_id; const location_id = $events_slct.event_location_id;
if (!location_id) return; if (!location_id) return;
@@ -631,7 +631,7 @@ async function refresh_location_config() {
* handled separately via inc_file_li=true in refresh_presenter_data (120s timer). * handled separately via inc_file_li=true in refresh_presenter_data (120s timer).
*/ */
async function refresh_current_session_files() { async function refresh_current_session_files() {
if ($events_loc.launcher.sync_paused) return; if (launcher_loc.current.sync_paused) return;
const session_id = $events_slct.event_session_id; const session_id = $events_slct.event_session_id;
if (!session_id) return; if (!session_id) return;
try { try {
@@ -688,7 +688,7 @@ async function force_location_sync() {
<!-- Monitor Overlay: only shown in Native/App mode. <!-- Monitor Overlay: only shown in Native/App mode.
Positioned bottom-left at bottom-20 to clear the debug π button (bottom-8 left-2) Positioned bottom-left at bottom-20 to clear the debug π button (bottom-8 left-2)
and the sys bar (bottom-12 right-2). Panel grows upward from the status chip. --> and the sys bar (bottom-12 right-2). Panel grows upward from the status chip. -->
{#if $events_loc.launcher.app_mode === 'native' || $ae_loc.is_native} {#if launcher_loc.current.app_mode === 'native' || $ae_loc.is_native}
<div <div
class="pointer-events-none fixed bottom-15 left-2 z-10 flex flex-col items-start gap-2"> class="pointer-events-none fixed bottom-15 left-2 z-10 flex flex-col items-start gap-2">
{#if show_monitor} {#if show_monitor}

View File

@@ -16,12 +16,12 @@ import {
time time
} from '$lib/stores/ae_stores'; } from '$lib/stores/ae_stores';
import { import {
events_loc,
events_sess, events_sess,
events_slct, events_slct,
events_trigger, events_trigger,
events_trig events_trig
} from '$lib/stores/ae_events_stores'; } from '$lib/stores/ae_events_stores';
import { launcher_loc } from '$lib/stores/ae_events_stores__launcher.svelte';
// Sub-components // Sub-components
import Launcher_Cfg_Native_OS from './cfg_components/launcher_cfg_native_os.svelte'; import Launcher_Cfg_Native_OS from './cfg_components/launcher_cfg_native_os.svelte';
@@ -112,7 +112,7 @@ const TABS = [
* Pinned sections are ignored and remain open. * Pinned sections are ignored and remain open.
*/ */
function handle_section_expand(current_key: string) { function handle_section_expand(current_key: string) {
const launcher = $events_loc.launcher; const launcher = launcher_loc.current;
Object.keys(launcher).forEach((key) => { Object.keys(launcher).forEach((key) => {
if ( if (
key.startsWith('section_state__') && key.startsWith('section_state__') &&
@@ -123,7 +123,7 @@ function handle_section_expand(current_key: string) {
} }
} }
}); });
$events_loc.launcher = launcher; // Trigger store update launcher_loc.current = launcher; // Trigger store update
} }
const current_tab_info = $derived(TABS.find((t) => t.id === active_tab)); const current_tab_info = $derived(TABS.find((t) => t.id === active_tab));
@@ -172,7 +172,7 @@ const current_tab_info = $derived(TABS.find((t) => t.id === active_tab));
<button <button
type="button" type="button"
onclick={() => ($events_loc.launcher.hide_drawer__cfg = true)} onclick={() => (launcher_loc.current.hide_drawer__cfg = true)}
class="btn btn-sm preset-tonal-surface hover:preset-filled-surface-500 justify-start gap-3 px-3 py-2 text-[10px] transition-all"> class="btn btn-sm preset-tonal-surface hover:preset-filled-surface-500 justify-start gap-3 px-3 py-2 text-[10px] transition-all">
<X size="1em" /> <X size="1em" />
<span>Close Settings</span> <span>Close Settings</span>
@@ -304,7 +304,7 @@ const current_tab_info = $derived(TABS.find((t) => t.id === active_tab));
<button <button
type="button" type="button"
onclick={() => onclick={() =>
($events_loc.launcher.hide_drawer__debug = false)} (launcher_loc.current.hide_drawer__debug = false)}
class="btn btn-sm preset-tonal-warning hover:preset-filled-warning-500 w-full transition-all"> class="btn btn-sm preset-tonal-warning hover:preset-filled-warning-500 w-full transition-all">
<Bug size="1.2em" class="mr-2" /> <Bug size="1.2em" class="mr-2" />
Open Debug Panel Open Debug Panel

View File

@@ -58,10 +58,10 @@ import { api } from '$lib/api/api';
import { ae_loc, ae_api, ae_sess, slct } from '$lib/stores/ae_stores'; import { ae_loc, ae_api, ae_sess, slct } from '$lib/stores/ae_stores';
import { core_func } from '$lib/ae_core/ae_core_functions'; import { core_func } from '$lib/ae_core/ae_core_functions';
import { import {
events_loc,
events_sess, events_sess,
events_slct events_slct
} from '$lib/stores/ae_events_stores'; } from '$lib/stores/ae_events_stores';
import { launcher_loc } from '$lib/stores/ae_events_stores__launcher.svelte';
import { events_func } from '$lib/ae_events/ae_events_functions'; import { events_func } from '$lib/ae_events/ae_events_functions';
import { import {
AlertCircle, AlertCircle,
@@ -97,7 +97,7 @@ let open_in_os_loading: boolean = $state(false);
/** Reactive display override for this file — stored in $events_loc (localStorage) not in the backend. */ /** Reactive display override for this file — stored in $events_loc (localStorage) not in the backend. */
const current_display_override = $derived.by(() => { const current_display_override = $derived.by(() => {
const overrides = (($events_loc.launcher as Record<string, unknown>)?.file_display_overrides ?? {}) as Record<string, string>; const overrides = ((launcher_loc.current as Record<string, unknown>)?.file_display_overrides ?? {}) as Record<string, string>;
return (overrides[event_file_id] ?? null) as 'extend' | 'mirror' | 'none' | null; return (overrides[event_file_id] ?? null) as 'extend' | 'mirror' | 'none' | null;
}); });
@@ -128,7 +128,7 @@ let screen_saver_exts = ['jpg', 'png', 'PNG', 'webp'];
* Resolves the LaunchProfile for a given file extension and optional per-file * Resolves the LaunchProfile for a given file extension and optional per-file
* display override. Checked in priority order: * display override. Checked in priority order:
* 1. event_device.data_json.launch_profiles[profile] (API-driven, per-device) * 1. event_device.data_json.launch_profiles[profile] (API-driven, per-device)
* 2. $events_loc.launcher.launch_profiles[profile] (local persistent override) * 2. launcher_loc.current.launch_profiles[profile] (local persistent override)
* 3. DEFAULT_LAUNCH_PROFILES[profile/alias] (Svelte built-in defaults) * 3. DEFAULT_LAUNCH_PROFILES[profile/alias] (Svelte built-in defaults)
* 4. DEFAULT_LAUNCH_PROFILES['default'] (catch-all) * 4. DEFAULT_LAUNCH_PROFILES['default'] (catch-all)
* Per-file display_override from event_file.cfg_json overrides display_mode only. * Per-file display_override from event_file.cfg_json overrides display_mode only.
@@ -142,10 +142,10 @@ function get_launch_profile(
native_device?.other_json?.launcher?.launch_profiles ?? native_device?.other_json?.launcher?.launch_profiles ??
native_device?.launch_profiles ?? native_device?.launch_profiles ??
null; null;
const local_profiles = ($events_loc as any).launcher?.launch_profiles ?? null; const local_profiles = launcher_loc.current?.launch_profiles ?? null;
// Display override is stored per-device in $events_loc — not in the backend (event_file has no JSON column). // Display override is stored per-device in $events_loc — not in the backend (event_file has no JSON column).
// This is intentional: display mode is a room/device preference, not a global file property. // This is intentional: display mode is a room/device preference, not a global file property.
const launcher_kv = $events_loc.launcher as Record<string, unknown>; const launcher_kv = launcher_loc.current as Record<string, unknown>;
const file_display_overrides = (launcher_kv?.file_display_overrides ?? {}) as Record<string, string>; const file_display_overrides = (launcher_kv?.file_display_overrides ?? {}) as Record<string, string>;
const display_override = (file_display_overrides[event_file_id] ?? null) as 'extend' | 'mirror' | 'none' | null; const display_override = (file_display_overrides[event_file_id] ?? null) as 'extend' | 'mirror' | 'none' | null;
@@ -172,9 +172,9 @@ function get_launch_profile(
onMount(() => { onMount(() => {
if (screen_saver_exts.includes(event_file_obj.extension)) { if (screen_saver_exts.includes(event_file_obj.extension)) {
if (!$events_loc.launcher.screen_saver_img_kv) if (!launcher_loc.current.screen_saver_img_kv)
$events_loc.launcher.screen_saver_img_kv = {}; launcher_loc.current.screen_saver_img_kv = {};
$events_loc.launcher.screen_saver_img_kv[event_file_id] = { launcher_loc.current.screen_saver_img_kv[event_file_id] = {
...event_file_obj ...event_file_obj
}; };
} }
@@ -204,7 +204,7 @@ async function handle_open_file() {
const url = event_file_obj.filename as string; const url = event_file_obj.filename as string;
// Test mode: show debug popup instead of opening // Test mode: show debug popup instead of opening
if ($events_loc.launcher.native_test_mode && $events_loc.launcher.app_mode === 'native') { if (launcher_loc.current.native_test_mode && launcher_loc.current.app_mode === 'native') {
open_file_clicked = true; open_file_clicked = true;
open_file_status = 'opening_file'; open_file_status = 'opening_file';
const profile = get_launch_profile('url', event_file_obj); const profile = get_launch_profile('url', event_file_obj);
@@ -251,7 +251,7 @@ async function handle_open_file() {
open_file_status_message = `Opening ${event_file_obj.title || 'URL'}...`; open_file_status_message = `Opening ${event_file_obj.title || 'URL'}...`;
if ($ae_loc.is_native && $events_loc.launcher.app_mode === 'native') { if ($ae_loc.is_native && launcher_loc.current.app_mode === 'native') {
// Native: open in Chrome for kiosk-style presentation; fall back to default browser // Native: open in Chrome for kiosk-style presentation; fall back to default browser
const result = await native.open_external({ url, app: 'chrome' }); const result = await native.open_external({ url, app: 'chrome' });
if (!result?.success) { if (!result?.success) {
@@ -270,7 +270,7 @@ async function handle_open_file() {
// 0. NATIVE TEST MODE — simulate full native flow, show debug popup instead of running commands // 0. NATIVE TEST MODE — simulate full native flow, show debug popup instead of running commands
// Active when native_test_mode toggle is on (regardless of is_native / app_mode). // Active when native_test_mode toggle is on (regardless of is_native / app_mode).
// Lets you preview the resolved profile, open command, and post-script from any device. // Lets you preview the resolved profile, open command, and post-script from any device.
if ($events_loc.launcher.native_test_mode && $events_loc.launcher.app_mode === 'native') { if (launcher_loc.current.native_test_mode && launcher_loc.current.app_mode === 'native') {
open_file_clicked = true; open_file_clicked = true;
open_file_status = 'checking_cache'; open_file_status = 'checking_cache';
open_file_status_message = 'Test Mode: simulating cache check...'; open_file_status_message = 'Test Mode: simulating cache check...';
@@ -305,7 +305,7 @@ async function handle_open_file() {
} }
// 1. NATIVE MODE (Electron) // 1. NATIVE MODE (Electron)
if ($ae_loc.is_native && $events_loc.launcher.app_mode === 'native') { if ($ae_loc.is_native && launcher_loc.current.app_mode === 'native') {
const cache_root = $ae_loc.local_file_cache_path; const cache_root = $ae_loc.local_file_cache_path;
const temp_root = $ae_loc.host_file_temp_path; const temp_root = $ae_loc.host_file_temp_path;
@@ -475,7 +475,7 @@ async function handle_open_file() {
return true; return true;
} }
// 2. ONSITE MODE (Browser with Modified Extensions) // 2. ONSITE MODE (Browser with Modified Extensions)
else if ($events_loc.launcher.app_mode === 'onsite') { else if (launcher_loc.current.app_mode === 'onsite') {
open_file_clicked = true; open_file_clicked = true;
open_file_status = 'downloading_onsite'; open_file_status = 'downloading_onsite';
open_file_status_message = 'Downloading (Onsite Mode)...'; open_file_status_message = 'Downloading (Onsite Mode)...';
@@ -525,7 +525,7 @@ async function handle_open_file() {
log_lvl: 1 log_lvl: 1
}); });
if ($events_loc.launcher.controller == 'local_push') { if (launcher_loc.current.controller == 'local_push') {
$events_sess.launcher.controller_cmd = `ae_download:hosted_file=${event_file_obj.hosted_file_id}:${event_file_obj.filename}:${event_file_obj.extension}`; $events_sess.launcher.controller_cmd = `ae_download:hosted_file=${event_file_obj.hosted_file_id}:${event_file_obj.filename}:${event_file_obj.extension}`;
$events_sess.launcher.controller_trigger_send = true; $events_sess.launcher.controller_trigger_send = true;
} }
@@ -561,7 +561,7 @@ function prevent_default<T extends Event>(fn: (event: T) => void) {
>*** {open_file_status_message || >*** {open_file_status_message ||
'Please wait while this file downloads...'} ***</strong> 'Please wait while this file downloads...'} ***</strong>
</div> </div>
{#if $ae_loc.is_native && $events_loc.launcher.app_mode === 'native'} {#if $ae_loc.is_native && launcher_loc.current.app_mode === 'native'}
{#if open_file_status === 'error'} {#if open_file_status === 'error'}
<p class="text-red-400">Failed to open file.</p> <p class="text-red-400">Failed to open file.</p>
{#if open_file_error_detail} {#if open_file_error_detail}
@@ -597,7 +597,7 @@ function prevent_default<T extends Event>(fn: (event: T) => void) {
$events_slct.event_file_obj = event_file_obj; $events_slct.event_file_obj = event_file_obj;
// Push the open command to the remote display when in local_push mode // Push the open command to the remote display when in local_push mode
if ( if (
$events_loc.launcher.controller == 'local_push' && launcher_loc.current.controller == 'local_push' &&
$events_sess.launcher.ws_connect_status == 'connected' $events_sess.launcher.ws_connect_status == 'connected'
) { ) {
$events_sess.launcher.controller_cmd = `ae_open:event_file=${event_file_id}`; $events_sess.launcher.controller_cmd = `ae_open:event_file=${event_file_id}`;
@@ -626,7 +626,7 @@ function prevent_default<T extends Event>(fn: (event: T) => void) {
hosted_file_id={event_file_id} hosted_file_id={event_file_id}
hosted_file_obj={event_file_obj} hosted_file_obj={event_file_obj}
require_auth={false} require_auth={false}
track_click_promise={!($ae_loc.is_native && $events_loc.launcher.app_mode === 'native')} track_click_promise={!($ae_loc.is_native && launcher_loc.current.app_mode === 'native')}
classes="btn {btn_size} gap-1 justify-between min-w-full w-full max-w-96 preset-tonal-primary border border-primary-500" classes="btn {btn_size} gap-1 justify-between min-w-full w-full max-w-96 preset-tonal-primary border border-primary-500"
click={handle_open_file}> click={handle_open_file}>
{#snippet label()} {#snippet label()}
@@ -749,7 +749,7 @@ function prevent_default<T extends Event>(fn: (event: T) => void) {
onclick={() => { onclick={() => {
const cur = current_display_override; const cur = current_display_override;
const next: 'extend' | 'mirror' | null = !cur ? 'extend' : cur === 'extend' ? 'mirror' : null; const next: 'extend' | 'mirror' | null = !cur ? 'extend' : cur === 'extend' ? 'mirror' : null;
const launcher = $events_loc.launcher as Record<string, unknown>; const launcher = launcher_loc.current as Record<string, unknown>;
const new_overrides = { ...((launcher?.file_display_overrides ?? {}) as Record<string, string>) }; const new_overrides = { ...((launcher?.file_display_overrides ?? {}) as Record<string, string>) };
if (next === null) { if (next === null) {
delete new_overrides[event_file_id]; delete new_overrides[event_file_id];

View File

@@ -109,11 +109,11 @@ import {
} from '$lib/stores/ae_stores'; } from '$lib/stores/ae_stores';
// import { db_events } from "$lib/ae_events/db_events"; // import { db_events } from "$lib/ae_events/db_events";
import { import {
events_loc,
events_sess, events_sess,
events_slct, events_slct,
events_trigger events_trigger
} from '$lib/stores/ae_events_stores'; } from '$lib/stores/ae_events_stores';
import { launcher_loc } from '$lib/stores/ae_events_stores__launcher.svelte';
import { events_func } from '$lib/ae_events/ae_events_functions'; import { events_func } from '$lib/ae_events/ae_events_functions';
import Event_launcher_file_cont from './launcher_file_cont.svelte'; import Event_launcher_file_cont from './launcher_file_cont.svelte';
@@ -173,7 +173,7 @@ let ae_promises: key_val = $state({
hide_created_on={true} hide_created_on={true}
hide_os={true} hide_os={true}
hide_size={true} hide_size={true}
show_internal_purpose_files={$events_loc.launcher.show_content__internal_files} show_internal_purpose_files={launcher_loc.current.show_content__internal_files}
show_bak_download={$ae_loc.trusted_access && show_bak_download={$ae_loc.trusted_access &&
$ae_loc.edit_mode} $ae_loc.edit_mode}
btn_size={'btn-sm'} btn_size={'btn-sm'}
@@ -216,7 +216,7 @@ let ae_promises: key_val = $state({
hide_created_on={true} hide_created_on={true}
hide_os={true} hide_os={true}
hide_size={true} hide_size={true}
show_internal_purpose_files={$events_loc.launcher.show_content__internal_files} show_internal_purpose_files={launcher_loc.current.show_content__internal_files}
show_bak_download={$ae_loc.trusted_access && show_bak_download={$ae_loc.trusted_access &&
$ae_loc.edit_mode} $ae_loc.edit_mode}
btn_size={'btn-sm'} btn_size={'btn-sm'}

View File

@@ -9,7 +9,8 @@ let { lq__event_presentation_obj, session_type = '' }: Props = $props();
import { liveQuery } from 'dexie'; import { liveQuery } from 'dexie';
import { db_events } from '$lib/ae_events/db_events'; import { db_events } from '$lib/ae_events/db_events';
import { ae_loc, ae_api } from '$lib/stores/ae_stores'; import { ae_loc, ae_api } from '$lib/stores/ae_stores';
import { events_loc, events_sess } from '$lib/stores/ae_events_stores'; import { events_sess } from '$lib/stores/ae_events_stores';
import { launcher_loc } from '$lib/stores/ae_events_stores__launcher.svelte';
import { events_func } from '$lib/ae_events/ae_events_functions'; import { events_func } from '$lib/ae_events/ae_events_functions';
import Event_launcher_file_cont from './launcher_file_cont.svelte'; import Event_launcher_file_cont from './launcher_file_cont.svelte';
@@ -60,7 +61,7 @@ let lq__event_file_obj_li = $derived(
{#each $lq__event_file_obj_li as event_file_obj (event_file_obj.event_file_id)} {#each $lq__event_file_obj_li as event_file_obj (event_file_obj.event_file_id)}
<li <li
class="flex flex-col flex-wrap items-center justify-start gap-1 md:flex-row" class="flex flex-col flex-wrap items-center justify-start gap-1 md:flex-row"
class:hidden={!$events_loc.launcher class:hidden={!launcher_loc.current
.show_content__hidden_files && event_file_obj.hide}> .show_content__hidden_files && event_file_obj.hide}>
<Event_launcher_file_cont <Event_launcher_file_cont
event_file_id={event_file_obj.event_file_id} event_file_id={event_file_obj.event_file_id}
@@ -68,7 +69,7 @@ let lq__event_file_obj_li = $derived(
hide_created_on={false} hide_created_on={false}
hide_meta={session_type == 'poster'} hide_meta={session_type == 'poster'}
bind:show_internal_purpose_files={ bind:show_internal_purpose_files={
$events_loc.launcher.show_content__internal_files launcher_loc.current.show_content__internal_files
} }
show_bak_download={$ae_loc.trusted_access} show_bak_download={$ae_loc.trusted_access}
session_type={session_type || 'oral'} session_type={session_type || 'oral'}

View File

@@ -24,11 +24,11 @@ import {
} from '$lib/stores/ae_stores'; } from '$lib/stores/ae_stores';
import { db_events } from '$lib/ae_events/db_events'; import { db_events } from '$lib/ae_events/db_events';
import { import {
events_loc,
events_sess, events_sess,
events_slct, events_slct,
events_trigger events_trigger
} from '$lib/stores/ae_events_stores'; } from '$lib/stores/ae_events_stores';
import { launcher_loc } from '$lib/stores/ae_events_stores__launcher.svelte';
// import { events_func } from '$lib/ae_events/ae_events_functions'; // import { events_func } from '$lib/ae_events/ae_events_functions';
import Event_launcher_file_cont from './launcher_file_cont.svelte'; import Event_launcher_file_cont from './launcher_file_cont.svelte';
@@ -90,14 +90,14 @@ let lq__event_file_obj_li = $derived(
{#each $lq__event_file_obj_li as event_file_obj, index (event_file_obj.event_file_id)} {#each $lq__event_file_obj_li as event_file_obj, index (event_file_obj.event_file_id)}
<li <li
class="flex flex-col flex-wrap items-center justify-start gap-1 md:flex-row" class="flex flex-col flex-wrap items-center justify-start gap-1 md:flex-row"
class:hidden={!$events_loc.launcher class:hidden={!launcher_loc.current
.show_content__hidden_files && event_file_obj.hide}> .show_content__hidden_files && event_file_obj.hide}>
<Event_launcher_file_cont <Event_launcher_file_cont
event_file_id={event_file_obj.event_file_id} event_file_id={event_file_obj.event_file_id}
{event_file_obj} {event_file_obj}
hide_created_on={false} hide_created_on={false}
bind:show_internal_purpose_files={ bind:show_internal_purpose_files={
$events_loc.launcher.show_content__internal_files launcher_loc.current.show_content__internal_files
} }
show_bak_download={$ae_loc.trusted_access} show_bak_download={$ae_loc.trusted_access}
session_type={session_type || 'oral'} session_type={session_type || 'oral'}

View File

@@ -24,11 +24,11 @@ import {
} from '$lib/stores/ae_stores'; } from '$lib/stores/ae_stores';
import { db_events } from '$lib/ae_events/db_events'; import { db_events } from '$lib/ae_events/db_events';
import { import {
events_loc,
events_sess, events_sess,
events_slct, events_slct,
events_trigger events_trigger
} from '$lib/stores/ae_events_stores'; } from '$lib/stores/ae_events_stores';
import { launcher_loc } from '$lib/stores/ae_events_stores__launcher.svelte';
// import { events_func } from '$lib/ae_events/ae_events_functions'; // import { events_func } from '$lib/ae_events/ae_events_functions';
import Event_launcher_file_cont from './launcher_file_cont.svelte'; import Event_launcher_file_cont from './launcher_file_cont.svelte';
@@ -84,14 +84,14 @@ let lq__event_file_obj_li = $derived(
{#each $lq__event_file_obj_li as event_file_obj, index (event_file_obj.event_file_id)} {#each $lq__event_file_obj_li as event_file_obj, index (event_file_obj.event_file_id)}
<li <li
class="wrap gap flex flex-col items-center justify-center md:flex-row" class="wrap gap flex flex-col items-center justify-center md:flex-row"
class:hidden={!$events_loc.launcher class:hidden={!launcher_loc.current
.show_content__hidden_files && event_file_obj.hide}> .show_content__hidden_files && event_file_obj.hide}>
<Event_launcher_file_cont <Event_launcher_file_cont
event_file_id={event_file_obj.event_file_id} event_file_id={event_file_obj.event_file_id}
{event_file_obj} {event_file_obj}
hide_created_on={false} hide_created_on={false}
hide_meta={true} hide_meta={true}
show_internal_purpose_files={$events_loc.launcher.show_content__internal_files} show_internal_purpose_files={launcher_loc.current.show_content__internal_files}
show_bak_download={$ae_loc.trusted_access} show_bak_download={$ae_loc.trusted_access}
session_type="poster" session_type="poster"
open_method="modal" open_method="modal"

View File

@@ -33,11 +33,11 @@ import {
} from '$lib/stores/ae_stores'; } from '$lib/stores/ae_stores';
import { db_events } from '$lib/ae_events/db_events'; import { db_events } from '$lib/ae_events/db_events';
import { import {
events_loc,
events_sess, events_sess,
events_slct, events_slct,
events_trigger events_trigger
} from '$lib/stores/ae_events_stores'; } from '$lib/stores/ae_events_stores';
import { launcher_loc } from '$lib/stores/ae_events_stores__launcher.svelte';
import { events_func } from '$lib/ae_events/ae_events_functions'; import { events_func } from '$lib/ae_events/ae_events_functions';
import { import {
TriangleAlert, TriangleAlert,
@@ -198,23 +198,23 @@ let ae_promises: key_val = $state({});
class="event_session_about flex flex-col items-stretch gap-0.5 border-b-2 border-gray-400 dark:border-gray-600"> class="event_session_about flex flex-col items-stretch gap-0.5 border-b-2 border-gray-400 dark:border-gray-600">
<h3 <h3
class:hidden={!$lq__event_session_obj?.start_datetime || class:hidden={!$lq__event_session_obj?.start_datetime ||
$events_loc.launcher.hide__session_datetimes} launcher_loc.current.hide__session_datetimes}
class="event_session_datetimes text-center text-sm"> class="event_session_datetimes text-center text-sm">
<button <button
type="button" type="button"
onclick={() => { onclick={() => {
if ( if (
$events_loc.launcher.time_format == launcher_loc.current.time_format ==
'time_12_short' 'time_12_short'
) { ) {
// $events_loc.launcher.datetime_format = 'datetime_long'; // launcher_loc.current.datetime_format = 'datetime_long';
$events_loc.launcher.time_format = 'time_short'; launcher_loc.current.time_format = 'time_short';
$events_loc.launcher.time_hours = 24; launcher_loc.current.time_hours = 24;
} else { } else {
$events_loc.launcher.time_format = launcher_loc.current.time_format =
'time_12_short'; 'time_12_short';
// $events_loc.launcher.datetime_format = 'datetime_12_long'; // launcher_loc.current.datetime_format = 'datetime_12_long';
$events_loc.launcher.time_hours = 12; launcher_loc.current.time_hours = 12;
} }
}}> }}>
<strong <strong
@@ -231,13 +231,13 @@ let ae_promises: key_val = $state({});
<strong <strong
>{ae_util.iso_datetime_formatter( >{ae_util.iso_datetime_formatter(
$lq__event_session_obj.start_datetime, $lq__event_session_obj.start_datetime,
$events_loc.launcher.time_format launcher_loc.current.time_format
)}</strong> )}</strong>
<span class="font-normal"> <span class="font-normal">
{ae_util.iso_datetime_formatter( {ae_util.iso_datetime_formatter(
$lq__event_session_obj.end_datetime, $lq__event_session_obj.end_datetime,
$events_loc.launcher.time_format launcher_loc.current.time_format
)} )}
</span> </span>
</button> </button>
@@ -248,7 +248,7 @@ let ae_promises: key_val = $state({});
<!-- grow + line-clamp-2 = stable 2-line max; title provides full text for screen readers + hover --> <!-- grow + line-clamp-2 = stable 2-line max; title provides full text for screen readers + hover -->
<h2 <h2
class="line-clamp-2 min-w-0 grow text-xl" class="line-clamp-2 min-w-0 grow text-xl"
title={`Name: ${$lq__event_session_obj.name}\nType: ${$lq__event_session_obj.type_code} \nCode: ${$lq__event_session_obj.code} \nID: ${$lq__event_session_obj.event_session_id} \nStart Date/Time: ${ae_util.iso_datetime_formatter($lq__event_session_obj.start_datetime, 'week_long')} ${ae_util.iso_datetime_formatter($lq__event_session_obj.start_datetime, $events_loc.launcher.time_format)} \nEnd Date/Time: ${ae_util.iso_datetime_formatter($lq__event_session_obj.end_datetime, $events_loc.launcher.time_format)}`}> title={`Name: ${$lq__event_session_obj.name}\nType: ${$lq__event_session_obj.type_code} \nCode: ${$lq__event_session_obj.code} \nID: ${$lq__event_session_obj.event_session_id} \nStart Date/Time: ${ae_util.iso_datetime_formatter($lq__event_session_obj.start_datetime, 'week_long')} ${ae_util.iso_datetime_formatter($lq__event_session_obj.start_datetime, launcher_loc.current.time_format)} \nEnd Date/Time: ${ae_util.iso_datetime_formatter($lq__event_session_obj.end_datetime, launcher_loc.current.time_format)}`}>
{$lq__event_session_obj?.name} {$lq__event_session_obj?.name}
</h2> </h2>
{#if $lq__event_session_obj?.code} {#if $lq__event_session_obj?.code}
@@ -292,7 +292,7 @@ let ae_promises: key_val = $state({});
</span> </span>
</strong> </strong>
</div> </div>
<!-- {#if $ae_loc.trusted_access || $events_loc.launcher.trusted_access} <!-- {#if $ae_loc.trusted_access || launcher_loc.current.trusted_access}
<button type="button" <button type="button"
type="button" class="ae_btn btn_outline_warning btn_xs" title="Upload updated or additional files" type="button" class="ae_btn btn_outline_warning btn_xs" title="Upload updated or additional files"
> >
@@ -304,14 +304,14 @@ let ae_promises: key_val = $state({});
{#each $lq__event_file_obj_li as event_file_obj (event_file_obj.event_file_id)} {#each $lq__event_file_obj_li as event_file_obj (event_file_obj.event_file_id)}
<li <li
class="flex flex-row flex-wrap items-center justify-center gap-1" class="flex flex-row flex-wrap items-center justify-center gap-1"
class:hidden={!$events_loc.launcher class:hidden={!launcher_loc.current
.show_content__hidden_files && .show_content__hidden_files &&
event_file_obj.hide}> event_file_obj.hide}>
<Event_launcher_file_cont <Event_launcher_file_cont
event_file_id={event_file_obj.event_file_id} event_file_id={event_file_obj.event_file_id}
{event_file_obj} {event_file_obj}
hide_created_on={true} hide_created_on={true}
show_internal_purpose_files={$events_loc.launcher show_internal_purpose_files={launcher_loc.current
.show_content__internal_files} .show_content__internal_files}
show_bak_download={$ae_loc.trusted_access && show_bak_download={$ae_loc.trusted_access &&
$ae_loc.edit_mode} $ae_loc.edit_mode}
@@ -332,7 +332,7 @@ let ae_promises: key_val = $state({});
.modal__event_file_obj .modal__event_file_obj
} /> } />
<!-- <Launcher_file_cont {event_file_obj} hide_created_on={false} show_bak_download={($ae_loc.trusted_access || $events_loc.launcher.trusted_access)} open_file_as={$lq__event_session_obj.type_code} poster_title={$lq__event_session_obj.title} /> --> <!-- <Launcher_file_cont {event_file_obj} hide_created_on={false} show_bak_download={($ae_loc.trusted_access || launcher_loc.current.trusted_access)} open_file_as={$lq__event_session_obj.type_code} poster_title={$lq__event_session_obj.title} /> -->
<!-- <a <!-- <a
href="{$ae_api.base_url}/event/file/{event_file_obj.event_file_id}/download?filename={event_file_obj.filename}&key={$ae_api.account_id}" href="{$ae_api.base_url}/event/file/{event_file_obj.event_file_id}/download?filename={event_file_obj.filename}&key={$ae_api.account_id}"

View File

@@ -25,7 +25,8 @@ let {
import { liveQuery } from 'dexie'; import { liveQuery } from 'dexie';
import { ae_loc } from '$lib/stores/ae_stores'; import { ae_loc } from '$lib/stores/ae_stores';
import { db_events } from '$lib/ae_events/db_events'; import { db_events } from '$lib/ae_events/db_events';
import { events_loc, events_sess } from '$lib/stores/ae_events_stores'; import { events_sess } from '$lib/stores/ae_events_stores';
import { launcher_loc } from '$lib/stores/ae_events_stores__launcher.svelte';
import Event_launcher_file_cont from './launcher_file_cont.svelte'; import Event_launcher_file_cont from './launcher_file_cont.svelte';
import { Image, Images, LoaderCircle, User, Users } from '@lucide/svelte'; import { Image, Images, LoaderCircle, User, Users } from '@lucide/svelte';
@@ -142,14 +143,14 @@ let poster_count = $derived($lq__event_presentation_obj_li?.length ?? 0);
<ul class="flex flex-row flex-wrap gap-2"> <ul class="flex flex-row flex-wrap gap-2">
{#each $lq__event_file_obj_li as event_file_obj (event_file_obj.event_file_id)} {#each $lq__event_file_obj_li as event_file_obj (event_file_obj.event_file_id)}
<li <li
class:hidden={!$events_loc.launcher class:hidden={!launcher_loc.current
.show_content__hidden_files && .show_content__hidden_files &&
event_file_obj.hide}> event_file_obj.hide}>
<Event_launcher_file_cont <Event_launcher_file_cont
event_file_id={event_file_obj.event_file_id} event_file_id={event_file_obj.event_file_id}
{event_file_obj} {event_file_obj}
hide_created_on={true} hide_created_on={true}
show_internal_purpose_files={$events_loc.launcher show_internal_purpose_files={launcher_loc.current
.show_content__internal_files} .show_content__internal_files}
show_bak_download={$ae_loc.trusted_access && show_bak_download={$ae_loc.trusted_access &&
$ae_loc.edit_mode} $ae_loc.edit_mode}

View File

@@ -26,7 +26,7 @@
import { Moon, Sun, Eye, EyeOff, Columns2, Copy, XCircle, Trash, Recycle } from '@lucide/svelte'; import { Moon, Sun, Eye, EyeOff, Columns2, Copy, XCircle, Trash, Recycle } from '@lucide/svelte';
import { ae_loc } from '$lib/stores/ae_stores'; import { ae_loc } from '$lib/stores/ae_stores';
import { events_loc } from '$lib/stores/ae_events_stores'; import { launcher_loc } from '$lib/stores/ae_events_stores__launcher.svelte';
import * as native from '$lib/electron/electron_relay'; import * as native from '$lib/electron/electron_relay';
interface Props { interface Props {
@@ -37,11 +37,11 @@ let { log_lvl = $bindable(0) }: Props = $props();
// Persist display mode across reloads — reflects the last-set state, not hardware-queried state. // Persist display mode across reloads — reflects the last-set state, not hardware-queried state.
let quick_display_mode = $state<'extend' | 'mirror'>( let quick_display_mode = $state<'extend' | 'mirror'>(
($events_loc.launcher as any)?.display_mode ?? 'extend' (launcher_loc.current as any)?.display_mode ?? 'extend'
); );
const is_native_launcher_mode = $derived( const is_native_launcher_mode = $derived(
!!$ae_loc.is_native && $events_loc.launcher.app_mode === 'native' !!$ae_loc.is_native && launcher_loc.current.app_mode === 'native'
); );
async function toggle_display_mode() { async function toggle_display_mode() {
@@ -50,11 +50,11 @@ async function toggle_display_mode() {
const res = await native.set_display_layout({ mode: next }); const res = await native.set_display_layout({ mode: next });
if (res?.success) { if (res?.success) {
quick_display_mode = next; quick_display_mode = next;
($events_loc.launcher as any).display_mode = next; (launcher_loc.current as any).display_mode = next;
} }
} else { } else {
quick_display_mode = next; quick_display_mode = next;
($events_loc.launcher as any).display_mode = next; (launcher_loc.current as any).display_mode = next;
} }
} }
@@ -112,25 +112,25 @@ function handle_reload_launcher() {
<button <button
type="button" type="button"
onclick={() => { onclick={() => {
if ($events_loc.launcher.show_content__hidden_files) { if (launcher_loc.current.show_content__hidden_files) {
$events_loc.launcher.show_content__hidden_files = false; launcher_loc.current.show_content__hidden_files = false;
$events_loc.launcher.show_content__internal_files = false; launcher_loc.current.show_content__internal_files = false;
} else { } else {
$events_loc.launcher.show_content__hidden_files = true; launcher_loc.current.show_content__hidden_files = true;
$events_loc.launcher.show_content__internal_files = true; launcher_loc.current.show_content__internal_files = true;
} }
}} }}
class="btn btn-sm min-w-34 w-34 max-w-1/2 text-xs transition-all" class="btn btn-sm min-w-34 w-34 max-w-1/2 text-xs transition-all"
class:preset-tonal-warning={$events_loc.launcher.show_content__hidden_files} class:preset-tonal-warning={launcher_loc.current.show_content__hidden_files}
class:hover:preset-filled-warning-500={$events_loc.launcher class:hover:preset-filled-warning-500={launcher_loc.current
.show_content__hidden_files} .show_content__hidden_files}
class:preset-tonal-tertiary={!$events_loc.launcher.show_content__hidden_files} class:preset-tonal-tertiary={!launcher_loc.current.show_content__hidden_files}
class:hover:preset-filled-tertiary-500={!$events_loc.launcher class:hover:preset-filled-tertiary-500={!launcher_loc.current
.show_content__hidden_files} .show_content__hidden_files}
title={$events_loc.launcher.show_content__hidden_files title={launcher_loc.current.show_content__hidden_files
? 'Showing all files including hidden and draft. Tap to hide them again.' ? 'Showing all files including hidden and draft. Tap to hide them again.'
: 'Showing only public files. Tap to show all files including hidden and draft.'}> : 'Showing only public files. Tap to show all files including hidden and draft.'}>
{#if $events_loc.launcher.show_content__hidden_files} {#if launcher_loc.current.show_content__hidden_files}
<EyeOff size="0.85em" class="m-1" /> <EyeOff size="0.85em" class="m-1" />
Hide Files Hide Files
{:else} {:else}
@@ -143,20 +143,20 @@ function handle_reload_launcher() {
<button <button
type="button" type="button"
onclick={() => { onclick={() => {
$events_loc.launcher.show_content__hidden_sessions = launcher_loc.current.show_content__hidden_sessions =
!$events_loc.launcher.show_content__hidden_sessions; !launcher_loc.current.show_content__hidden_sessions;
}} }}
class="btn btn-sm min-w-34 w-34 max-w-1/2 text-xs transition-all" class="btn btn-sm min-w-34 w-34 max-w-1/2 text-xs transition-all"
class:preset-tonal-warning={$events_loc.launcher.show_content__hidden_sessions} class:preset-tonal-warning={launcher_loc.current.show_content__hidden_sessions}
class:hover:preset-filled-warning-500={$events_loc.launcher class:hover:preset-filled-warning-500={launcher_loc.current
.show_content__hidden_sessions} .show_content__hidden_sessions}
class:preset-tonal-tertiary={!$events_loc.launcher.show_content__hidden_sessions} class:preset-tonal-tertiary={!launcher_loc.current.show_content__hidden_sessions}
class:hover:preset-filled-tertiary-500={!$events_loc.launcher class:hover:preset-filled-tertiary-500={!launcher_loc.current
.show_content__hidden_sessions} .show_content__hidden_sessions}
title={$events_loc.launcher.show_content__hidden_sessions title={launcher_loc.current.show_content__hidden_sessions
? 'Showing all sessions including cancelled and hidden. Tap to hide them again.' ? 'Showing all sessions including cancelled and hidden. Tap to hide them again.'
: 'Showing only active sessions. Tap to show all sessions including hidden and cancelled.'}> : 'Showing only active sessions. Tap to show all sessions including hidden and cancelled.'}>
{#if $events_loc.launcher.show_content__hidden_sessions} {#if launcher_loc.current.show_content__hidden_sessions}
<EyeOff size="0.85em" class="shrink-0" /> <EyeOff size="0.85em" class="shrink-0" />
Hide Sessions Hide Sessions
{:else} {:else}

View File

@@ -15,7 +15,7 @@
* DATA FLOW: * DATA FLOW:
* lq__event_location_obj_li (Dexie liveQuery, passed from launcher_menu.svelte) * lq__event_location_obj_li (Dexie liveQuery, passed from launcher_menu.svelte)
* → rendered as <select> options * → rendered as <select> options
* → onchange writes to $events_slct.event_location_id and $events_loc.launcher.slct * → onchange writes to $events_slct.event_location_id and launcher_loc.current.slct
* → calls handle_load_ae_obj_li__event_session() to fetch sessions for the new room * → calls handle_load_ae_obj_li__event_session() to fetch sessions for the new room
* → navigates to /launcher/{location_id} via goto() * → navigates to /launcher/{location_id} via goto()
* *
@@ -68,11 +68,11 @@ import {
slct_trigger slct_trigger
} from '$lib/stores/ae_stores'; } from '$lib/stores/ae_stores';
import { import {
events_loc,
events_sess, events_sess,
events_slct, events_slct,
events_trigger events_trigger
} from '$lib/stores/ae_events_stores'; } from '$lib/stores/ae_events_stores';
import { launcher_loc } from '$lib/stores/ae_events_stores__launcher.svelte';
import { events_func } from '$lib/ae_events/ae_events_functions'; import { events_func } from '$lib/ae_events/ae_events_functions';
import { Check, LoaderCircle } from '@lucide/svelte'; import { Check, LoaderCircle } from '@lucide/svelte';
// export let slct_event_session_id: any; // export let slct_event_session_id: any;
@@ -110,10 +110,10 @@ function handle_load_ae_obj_li__event_session(event_location_id: string) {
inc_all_file_li: false, // Also include files under presentations and presenters as well? inc_all_file_li: false, // Also include files under presentations and presenters as well?
inc_presentation_li: true, inc_presentation_li: true,
inc_presenter_li: true, inc_presenter_li: true,
enabled: $events_loc.launcher.show_content__enabled_sessions enabled: launcher_loc.current.show_content__enabled_sessions
? 'all' ? 'all'
: 'enabled', : 'enabled',
hidden: $events_loc.launcher.show_content__hidden_sessions hidden: launcher_loc.current.show_content__hidden_sessions
? 'all' ? 'all'
: 'not_hidden', : 'not_hidden',
limit: 49, limit: 49,
@@ -195,7 +195,7 @@ function handle_load_ae_obj_li__event_session(event_location_id: string) {
if (slct_event_location_id) { if (slct_event_location_id) {
loading__session_li_status = 'loading'; loading__session_li_status = 'loading';
$events_loc.launcher.slct.event_location_id = launcher_loc.current.slct.event_location_id =
slct_event_location_id; slct_event_location_id;
$events_slct.event_location_id = slct_event_location_id; $events_slct.event_location_id = slct_event_location_id;
@@ -211,9 +211,9 @@ function handle_load_ae_obj_li__event_session(event_location_id: string) {
// This will hide the selected session if the location is changed to false. // This will hide the selected session if the location is changed to false.
// WARNING: This may need to be commented out later. // WARNING: This may need to be commented out later.
// slct_event_session_id = null; // slct_event_session_id = null;
$events_loc.launcher.slct.event_location_id = null; launcher_loc.current.slct.event_location_id = null;
$events_slct.event_location_id = null; $events_slct.event_location_id = null;
$events_loc.launcher.slct.event_session_id = null; launcher_loc.current.slct.event_session_id = null;
$events_slct.event_session_id = null; $events_slct.event_session_id = null;
loading__session_li_status = 'finished'; loading__session_li_status = 'finished';

View File

@@ -84,11 +84,11 @@ import {
slct_trigger slct_trigger
} from '$lib/stores/ae_stores'; } from '$lib/stores/ae_stores';
import { import {
events_loc,
events_sess, events_sess,
events_slct, events_slct,
events_trigger events_trigger
} from '$lib/stores/ae_events_stores'; } from '$lib/stores/ae_events_stores';
import { launcher_loc } from '$lib/stores/ae_events_stores__launcher.svelte';
import { import {
CalendarCheck, CalendarCheck,
CalendarDays, CalendarDays,
@@ -141,7 +141,7 @@ $effect(() => {
} }
// 3. Remote Control Sync // 3. Remote Control Sync
if ($events_loc.launcher.controller == 'local_push') { if (launcher_loc.current.controller == 'local_push') {
$events_sess.launcher.controller_cmd = `ae_load:event_session=${event_session_id}`; $events_sess.launcher.controller_cmd = `ae_load:event_session=${event_session_id}`;
$events_sess.launcher.controller_trigger_send = true; $events_sess.launcher.controller_trigger_send = true;
} }
@@ -212,7 +212,7 @@ $effect(() => {
" "
class:session-active={slct__event_session_id === class:session-active={slct__event_session_id ===
event_session_obj?.id} event_session_obj?.id}
class:hidden={!$events_loc.launcher class:hidden={!launcher_loc.current
.show_content__hidden_sessions && .show_content__hidden_sessions &&
(event_session_obj?.hide || (event_session_obj?.hide ||
event_session_obj?.hide_event_launcher)}> event_session_obj?.hide_event_launcher)}>
@@ -263,7 +263,7 @@ $effect(() => {
class:preset-tonal-secondary={slct__event_session_id != event_session_obj?.id} class:preset-tonal-secondary={slct__event_session_id != event_session_obj?.id}
class:preset-outlined-secondary-200-800={slct__event_session_id != event_session_obj?.id} class:preset-outlined-secondary-200-800={slct__event_session_id != event_session_obj?.id}
class:opacity-40={event_session_obj?.hide || event_session_obj?.hide_event_launcher} class:opacity-40={event_session_obj?.hide || event_session_obj?.hide_event_launcher}
title={`Session: ${event_session_obj?.name}\nID: ${event_session_obj?.id} | ${ae_util.iso_datetime_formatter(event_session_obj?.start_datetime, $events_loc.launcher.time_format)}`}> title={`Session: ${event_session_obj?.name}\nID: ${event_session_obj?.id} | ${ae_util.iso_datetime_formatter(event_session_obj?.start_datetime, launcher_loc.current.time_format)}`}>
<!-- Session row layout: [date column | session name] <!-- Session row layout: [date column | session name]
Date column is fixed-width (shrink-0) so name column always Date column is fixed-width (shrink-0) so name column always
gets consistent space regardless of date string length. gets consistent space regardless of date string length.
@@ -293,7 +293,7 @@ $effect(() => {
<span class="text-xs"> <span class="text-xs">
{ae_util.iso_datetime_formatter( {ae_util.iso_datetime_formatter(
event_session_obj?.start_datetime, event_session_obj?.start_datetime,
$events_loc.launcher.time_format launcher_loc.current.time_format
)} )}
</span> </span>
</span> </span>