Prettier for Event Launcher

This commit is contained in:
Scott Idem
2026-03-24 12:13:59 -04:00
parent a3ed379b17
commit 7f6e286b73
27 changed files with 3766 additions and 3807 deletions

View File

@@ -44,7 +44,8 @@
// 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 && $events_loc.launcher.ws_connect &&
($events_loc.launcher.controller === 'local_push' || $events_loc.launcher.controller === 'remote') ($events_loc.launcher.controller === 'local_push' ||
$events_loc.launcher.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';
@@ -57,135 +58,126 @@
icon={LayoutGrid} icon={LayoutGrid}
bind:state={$events_loc.launcher.section_state__app_modes} bind:state={$events_loc.launcher.section_state__app_modes}
{on_expand} {on_expand}
description="Mode: {$events_loc.launcher.app_mode} | UI Layout" description="Mode: {$events_loc.launcher.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 -->
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<p class="text-[9px] font-bold uppercase opacity-50 ml-1">Session Mode Preset</p> <p class="ml-1 text-[9px] font-bold uppercase opacity-50">
<div class="grid grid-cols-2 gap-1 bg-surface-500/5 p-1 rounded-lg"> Session Mode Preset
</p>
<div class="bg-surface-500/5 grid grid-cols-2 gap-1 rounded-lg p-1">
<button <button
type="button" type="button"
onclick={() => apply_mode('oral')} onclick={() => apply_mode('oral')}
class="btn btn-xs font-bold text-[10px]" class="btn btn-xs text-[10px] font-bold"
class:preset-filled-secondary={!is_poster_mode} class:preset-filled-secondary={!is_poster_mode}
class:preset-tonal-surface={is_poster_mode} class:preset-tonal-surface={is_poster_mode}
title="Standard oral/presentation layout — menus and headers visible" title="Standard oral/presentation layout — menus and headers visible">
>
<GraduationCap size="0.85em" class="mr-1 opacity-70" /> <GraduationCap size="0.85em" class="mr-1 opacity-70" />
Oral / Default Oral / Default
</button> </button>
<button <button
type="button" type="button"
onclick={() => apply_mode('poster')} onclick={() => apply_mode('poster')}
class="btn btn-xs font-bold text-[10px]" class="btn btn-xs text-[10px] font-bold"
class:preset-filled-primary={is_poster_mode} class:preset-filled-primary={is_poster_mode}
class:preset-tonal-surface={!is_poster_mode} class:preset-tonal-surface={!is_poster_mode}
title="Digital Poster kiosk — hides site chrome, menu, header & footer" title="Digital Poster kiosk — hides site chrome, menu, header & footer">
>
<IdCard size="0.85em" class="mr-1 opacity-70" /> <IdCard size="0.85em" class="mr-1 opacity-70" />
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 $events_loc.launcher.ws_connect && ($events_loc.launcher.controller === 'local_push' || $events_loc.launcher.controller === 'remote')}
<p class="text-[8px] opacity-40 italic ml-1">Applies to all connected WS devices</p> <p class="ml-1 text-[8px] italic opacity-40">
Applies to all connected WS devices
</p>
{/if} {/if}
</div> </div>
<!-- 1. App Mode Selection --> <!-- 1. App Mode Selection -->
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<p class="text-[9px] font-bold uppercase opacity-50 ml-1" <p class="ml-1 text-[9px] font-bold uppercase opacity-50">
>Operational Environment</p Operational Environment
> </p>
<div class="grid grid-cols-3 gap-1 bg-surface-500/5 p-1 rounded-lg"> <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={() => ($events_loc.launcher.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={$events_loc.launcher
.app_mode === 'default'} .app_mode === 'default'}
class:preset-tonal-surface={$events_loc.launcher.app_mode !== class:preset-tonal-surface={$events_loc.launcher
'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={() => ($events_loc.launcher.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={$events_loc.launcher
.app_mode === 'native'} .app_mode === 'native'}
class:preset-tonal-surface={$events_loc.launcher.app_mode !== class:preset-tonal-surface={$events_loc.launcher
'native'} .app_mode !== 'native'}
title="Native Electron based app launcher, for onsite presenters in session rooms">App</button title="Native Electron based app launcher, for onsite presenters in session rooms"
> >App</button>
<button <button
type="button" type="button"
onclick={() => ($events_loc.launcher.app_mode = 'onsite')} onclick={() => ($events_loc.launcher.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={$events_loc.launcher
.app_mode === 'onsite'} .app_mode === 'onsite'}
class:preset-tonal-surface={$events_loc.launcher.app_mode !== class:preset-tonal-surface={$events_loc.launcher
'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">Onsite</button title="Customized onsite OS and web browser (Chromium or Firefox based) launcher, for onsite presenters in for practice and onsite backup"
> >Onsite</button>
</div> </div>
</div> </div>
<!-- 2. UI Layout Toggles --> <!-- 2. UI Layout Toggles -->
<div <div
class="flex flex-col gap-1 border-t border-surface-500/10 pt-2 mt-1" class="border-surface-500/10 mt-1 flex flex-col gap-1 border-t pt-2">
> <p class="ml-1 text-[9px] font-bold uppercase opacity-50">
<p class="text-[9px] font-bold uppercase opacity-50 ml-1" Interface Visibility
>Interface Visibility</p </p>
>
<div class="grid grid-cols-2 gap-2 p-1"> <div class="grid grid-cols-2 gap-2 p-1">
<label class="flex items-center gap-2 cursor-pointer group"> <label class="group flex cursor-pointer items-center gap-2">
<input <input
type="checkbox" type="checkbox"
bind:checked={ bind:checked={
$events_loc.launcher.hide__launcher_header $events_loc.launcher.hide__launcher_header
} }
class="checkbox checkbox-sm" class="checkbox checkbox-sm" />
/> <span class="group-hover:text-primary-500 text-xs"
<span class="text-xs group-hover:text-primary-500" >Hide Header</span>
>Hide Header</span
>
</label> </label>
<label class="flex items-center gap-2 cursor-pointer group"> <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={$events_loc.launcher.hide__launcher_menu}
class="checkbox checkbox-sm" class="checkbox checkbox-sm" />
/> <span class="group-hover:text-primary-500 text-xs"
<span class="text-xs group-hover:text-primary-500" >Hide Menu</span>
>Hide Menu</span
>
</label> </label>
<label class="flex items-center gap-2 cursor-pointer group"> <label class="group flex cursor-pointer items-center gap-2">
<input <input
type="checkbox" type="checkbox"
bind:checked={ bind:checked={
$events_loc.launcher.hide__launcher_footer $events_loc.launcher.hide__launcher_footer
} }
class="checkbox checkbox-sm" class="checkbox checkbox-sm" />
/> <span class="group-hover:text-primary-500 text-xs"
<span class="text-xs group-hover:text-primary-500" >Hide Footer</span>
>Hide Footer</span
>
</label> </label>
<label class="flex items-center gap-2 cursor-pointer group"> <label class="group flex cursor-pointer items-center gap-2">
<input <input
type="checkbox" type="checkbox"
bind:checked={ bind:checked={
$events_loc.launcher.hide__session_datetimes $events_loc.launcher.hide__session_datetimes
} }
class="checkbox checkbox-sm" class="checkbox checkbox-sm" />
/> <span class="group-hover:text-primary-500 text-xs"
<span class="text-xs group-hover:text-primary-500" >Hide Times</span>
>Hide Times</span
>
</label> </label>
</div> </div>
</div> </div>
@@ -202,8 +194,7 @@
$events_loc.launcher.time_hours = 12; $events_loc.launcher.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>{$events_loc.launcher.time_hours}-hour</strong>
@@ -212,35 +203,30 @@
<!-- 4. Advanced Toggles (Edit Mode Only) --> <!-- 4. Advanced Toggles (Edit Mode Only) -->
{#if $ae_loc.edit_mode} {#if $ae_loc.edit_mode}
<div <div
class="col-span-full border-t border-surface-500/20 pt-3 mt-1 flex flex-col gap-2" class="border-surface-500/20 col-span-full mt-1 flex flex-col gap-2 border-t pt-3">
> <p class="ml-1 text-[9px] font-bold uppercase opacity-50">
<p class="text-[9px] font-bold uppercase opacity-50 ml-1" Technical Layout
>Technical Layout</p </p>
>
<div class="grid grid-cols-1 gap-2 p-1"> <div class="grid grid-cols-1 gap-2 p-1">
<label class="flex items-center gap-2 cursor-pointer group"> <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={$events_loc.launcher.hide__ws_element}
class="checkbox checkbox-sm" class="checkbox checkbox-sm" />
/>
<span <span
class="text-xs group-hover:text-primary-500 italic" class="group-hover:text-primary-500 text-xs italic"
>Hide WebSocket Debugger</span >Hide WebSocket Debugger</span>
>
</label> </label>
<label class="flex items-center gap-2 cursor-pointer group"> <label class="group flex cursor-pointer items-center gap-2">
<input <input
type="checkbox" type="checkbox"
bind:checked={ bind:checked={
$events_loc.launcher.hide__modal_header_title $events_loc.launcher.hide__modal_header_title
} }
class="checkbox checkbox-sm" class="checkbox checkbox-sm" />
/>
<span <span
class="text-xs group-hover:text-primary-500 italic" class="group-hover:text-primary-500 text-xs italic"
>Hide Poster Modal Title</span >Hide Poster Modal Title</span>
>
</label> </label>
</div> </div>
</div> </div>

View File

@@ -2,7 +2,15 @@
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_loc, events_sess } from '$lib/stores/ae_events_stores';
import Launcher_Cfg_Section from './launcher_cfg_section.svelte'; import Launcher_Cfg_Section from './launcher_cfg_section.svelte';
import { Gamepad2, Link, Lock, LockOpen, Plug, RefreshCw, Unlink } from '@lucide/svelte'; import {
Gamepad2,
Link,
Lock,
LockOpen,
Plug,
RefreshCw,
Unlink
} from '@lucide/svelte';
interface Props { interface Props {
on_expand?: () => void; on_expand?: () => void;
} }
@@ -20,41 +28,35 @@
{on_expand} {on_expand}
description="Mode: {$events_loc.launcher?.controller} | WS: {ws_connected description="Mode: {$events_loc.launcher?.controller} | WS: {ws_connected
? 'Connected' ? 'Connected'
: 'Offline'}" : 'Offline'}">
>
<!-- 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">
<!-- 1. Connection Status Badge --> <!-- 1. Connection Status Badge -->
<div <div
class="flex items-center justify-between bg-surface-500/5 p-2 rounded border border-surface-500/10" class="bg-surface-500/5 border-surface-500/10 flex items-center justify-between rounded border p-2">
>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<Plug size="1em" class="opacity-50" /> <Plug size="1em" class="opacity-50" />
<span class="text-[10px] font-bold uppercase tracking-wider" <span class="text-[10px] font-bold tracking-wider uppercase"
>WebSocket Link</span >WebSocket Link</span>
>
</div> </div>
{#if ws_connected} {#if ws_connected}
<span <span
class="badge preset-filled-success text-[8px] animate-pulse" class="badge preset-filled-success animate-pulse text-[8px]"
>Connected</span >Connected</span>
>
{:else} {:else}
<span class="badge preset-filled-error text-[8px]" <span class="badge preset-filled-error text-[8px]"
>Disconnected</span >Disconnected</span>
>
{/if} {/if}
</div> </div>
<!-- 2. Controller Mode Selection --> <!-- 2. Controller Mode Selection -->
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<p class="text-[9px] font-bold uppercase opacity-50 ml-1" <p class="ml-1 text-[9px] font-bold uppercase opacity-50">
>Controller Strategy</p Controller Strategy
> </p>
<select <select
bind:value={$events_loc.launcher.controller} bind:value={$events_loc.launcher.controller}
class="select select-sm text-xs preset-tonal-surface h-8" 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>
<option value="local_push">Local and WS Controller</option> <option value="local_push">Local and WS Controller</option>
@@ -62,7 +64,7 @@
</div> </div>
<!-- 3. Connection Actions --> <!-- 3. Connection Actions -->
<div class="grid grid-cols-2 gap-2 mt-1"> <div class="mt-1 grid grid-cols-2 gap-2">
<button <button
type="button" type="button"
onclick={() => { onclick={() => {
@@ -76,8 +78,7 @@
}} }}
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={$events_loc.launcher.ws_connect}
class:preset-tonal-success={!$events_loc.launcher.ws_connect} class:preset-tonal-success={!$events_loc.launcher.ws_connect}>
>
{#if $events_loc.launcher.ws_connect} {#if $events_loc.launcher.ws_connect}
<Unlink size="0.85em" class="mr-1" /> Disconnect <Unlink size="0.85em" class="mr-1" /> Disconnect
{:else} {:else}
@@ -92,8 +93,7 @@
$events_sess.launcher.controller_trigger_send = 'trigger'; $events_sess.launcher.controller_trigger_send = 'trigger';
}} }}
class="btn btn-sm preset-tonal-secondary hover:preset-filled-secondary-500 text-[10px] font-bold" class="btn btn-sm preset-tonal-secondary hover:preset-filled-secondary-500 text-[10px] font-bold"
disabled={!ws_connected} disabled={!ws_connected}>
>
<RefreshCw size="0.85em" class="mr-1" /> Group Reload <RefreshCw size="0.85em" class="mr-1" /> Group Reload
</button> </button>
</div> </div>
@@ -101,21 +101,19 @@
<!-- 4. Technical Config (Edit Mode Only) --> <!-- 4. Technical Config (Edit Mode Only) -->
{#if $ae_loc.edit_mode} {#if $ae_loc.edit_mode}
<div <div
class="col-span-full border-t border-surface-500/20 pt-3 mt-1 flex flex-col gap-2" class="border-surface-500/20 col-span-full mt-1 flex flex-col gap-2 border-t pt-3">
> <p class="ml-1 text-[9px] font-bold uppercase opacity-50">
<p class="text-[9px] font-bold uppercase opacity-50 ml-1" Channel Configuration
>Channel Configuration</p </p>
>
<div class="flex gap-1"> <div class="flex gap-1">
<input <input
bind:value={$events_loc.launcher.controller_group_code} bind:value={$events_loc.launcher.controller_group_code}
placeholder="Group Code" placeholder="Group Code"
class="input input-sm grow text-[10px] h-7 preset-tonal-surface font-mono" class="input input-sm preset-tonal-surface h-7 grow font-mono text-[10px]"
readonly={!$events_sess.launcher readonly={!$events_sess.launcher
.controller_unlock_group_code} .controller_unlock_group_code}
ondblclick={() => ondblclick={() =>
($events_sess.launcher.controller_unlock_group_code = true)} ($events_sess.launcher.controller_unlock_group_code = true)} />
/>
<button <button
type="button" type="button"
onclick={() => onclick={() =>
@@ -123,8 +121,7 @@
!$events_sess.launcher !$events_sess.launcher
.controller_unlock_group_code)} .controller_unlock_group_code)}
class="btn btn-xs preset-tonal-surface" class="btn btn-xs preset-tonal-surface"
title="Toggle Unlock" title="Toggle Unlock">
>
{#if $events_sess.launcher.controller_unlock_group_code} {#if $events_sess.launcher.controller_unlock_group_code}
<LockOpen size="0.85em" class="text-primary-500" /> <LockOpen size="0.85em" class="text-primary-500" />
{:else} {:else}
@@ -132,7 +129,7 @@
{/if} {/if}
</button> </button>
</div> </div>
<p class="text-[8px] opacity-40 italic ml-1"> <p class="ml-1 text-[8px] italic opacity-40">
Double-click input to unlock editing. Changing code triggers Double-click input to unlock editing. Changing code triggers
reconnect. reconnect.
</p> </p>

View File

@@ -47,53 +47,48 @@
bind:state={$events_loc.launcher.section_state__health} bind:state={$events_loc.launcher.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'}">
>
<!-- Content omitted for brevity in instruction, but preserved in file --> <!-- Content omitted for brevity in instruction, but preserved in file -->
<!-- Telemetry Dashboard --> <!-- Telemetry Dashboard -->
<div <div
class="col-span-full flex flex-col gap-3 bg-surface-500/5 p-3 rounded-lg border border-surface-500/10" class="bg-surface-500/5 border-surface-500/10 col-span-full flex flex-col gap-3 rounded-lg border p-3">
>
<!-- CPU Usage (Mock Logic if load not available yet) --> <!-- CPU Usage (Mock Logic if load not available yet) -->
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<div <div
class="flex justify-between text-[9px] uppercase font-bold opacity-60" class="flex justify-between text-[9px] font-bold uppercase opacity-60">
>
<span <span
>CPU Architecture: {$ae_loc.native_device?.meta_json >CPU Architecture: {$ae_loc.native_device?.meta_json
?.arch || '...'}</span ?.arch || '...'}</span>
>
<span>Load: {cpu_load_pct}%</span> <span>Load: {cpu_load_pct}%</span>
</div> </div>
<div <div
class="w-full h-1.5 bg-surface-500/20 rounded-full overflow-hidden" class="bg-surface-500/20 h-1.5 w-full overflow-hidden rounded-full">
>
<div <div
class="h-full transition-all duration-1000 {get_usage_color(cpu_load_pct)}" class="h-full transition-all duration-1000 {get_usage_color(
style="width: {cpu_load_pct}%" cpu_load_pct
></div> )}"
style="width: {cpu_load_pct}%">
</div>
</div> </div>
</div> </div>
<!-- RAM Usage --> <!-- RAM Usage -->
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<div <div
class="flex justify-between text-[9px] uppercase font-bold opacity-60" class="flex justify-between text-[9px] font-bold uppercase opacity-60">
>
<span>Memory (RAM)</span> <span>Memory (RAM)</span>
<span>{ram_usage_pct}% Used</span> <span>{ram_usage_pct}% Used</span>
</div> </div>
<div <div
class="w-full h-1.5 bg-surface-500/20 rounded-full overflow-hidden" class="bg-surface-500/20 h-1.5 w-full overflow-hidden rounded-full">
>
<div <div
class="h-full transition-all duration-1000 {get_usage_color( class="h-full transition-all duration-1000 {get_usage_color(
ram_usage_pct ram_usage_pct
)}" )}"
style="width: {ram_usage_pct}%" style="width: {ram_usage_pct}%">
></div>
</div> </div>
<div class="text-[8px] opacity-40 text-right italic"> </div>
<div class="text-right text-[8px] italic opacity-40">
Free: {$ae_loc.native_device?.meta_json?.free_mem || '...'} / {$ae_loc Free: {$ae_loc.native_device?.meta_json?.free_mem || '...'} / {$ae_loc
.native_device?.meta_json?.total_mem || '...'} .native_device?.meta_json?.total_mem || '...'}
</div> </div>
@@ -101,26 +96,23 @@
</div> </div>
<!-- Heartbeat & Sync Info --> <!-- Heartbeat & Sync Info -->
<div class="grid grid-cols-2 gap-x-2 gap-y-2 w-full text-[10px] p-1"> <div class="grid w-full grid-cols-2 gap-x-2 gap-y-2 p-1 text-[10px]">
<div class="flex flex-col"> <div class="flex flex-col">
<span class="opacity-50 text-[8px] uppercase font-bold" <span class="text-[8px] font-bold uppercase opacity-50"
>Last Heartbeat</span >Last Heartbeat</span>
>
<span <span
class="font-mono {$events_sess.launcher.heartbeat_info class="font-mono {$events_sess.launcher.heartbeat_info
.status === 'success' .status === 'success'
? 'text-success-500' ? 'text-success-500'
: 'text-error-500'}" : 'text-error-500'}">
>
{$events_sess.launcher.heartbeat_info.last_timestamp || {$events_sess.launcher.heartbeat_info.last_timestamp ||
'Pending...'} 'Pending...'}
</span> </span>
</div> </div>
<div class="flex flex-col text-right"> <div class="flex flex-col text-right">
<span class="opacity-50 text-[8px] uppercase font-bold" <span class="text-[8px] font-bold uppercase opacity-50"
>Local File Cache</span >Local File Cache</span>
>
<span class="font-mono"> <span class="font-mono">
{$events_sess.launcher.sync_stats.cached} / {$events_sess {$events_sess.launcher.sync_stats.cached} / {$events_sess
.launcher.sync_stats.total} .launcher.sync_stats.total}
@@ -129,19 +121,18 @@
{#if $events_sess.launcher.sync_stats.currently_syncing} {#if $events_sess.launcher.sync_stats.currently_syncing}
<div <div
class="col-span-full bg-primary-500/10 p-2 rounded border border-primary-500/20 animate-pulse mt-1" class="bg-primary-500/10 border-primary-500/20 col-span-full mt-1 animate-pulse rounded border p-2">
>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<RefreshCw size="1em" class="animate-spin text-primary-500" /> <RefreshCw
size="1em"
class="text-primary-500 animate-spin" />
<div class="flex flex-col truncate"> <div class="flex flex-col truncate">
<span <span
class="text-[8px] uppercase font-bold text-primary-500" class="text-primary-500 text-[8px] font-bold uppercase"
>Syncing File...</span >Syncing File...</span>
>
<span class="truncate italic opacity-80" <span class="truncate italic opacity-80"
>{$events_sess.launcher.sync_stats >{$events_sess.launcher.sync_stats
.currently_syncing}</span .currently_syncing}</span>
>
</div> </div>
</div> </div>
</div> </div>
@@ -151,26 +142,22 @@
<!-- Device Metadata (Edit Mode Only) --> <!-- Device Metadata (Edit Mode Only) -->
{#if $ae_loc.edit_mode} {#if $ae_loc.edit_mode}
<div <div
class="col-span-full mt-1 pt-2 border-t border-surface-500/10 flex flex-col gap-1 text-[9px] opacity-60 px-1" class="border-surface-500/10 col-span-full mt-1 flex flex-col gap-1 border-t px-1 pt-2 text-[9px] opacity-60">
>
<div class="flex justify-between"> <div class="flex justify-between">
<span>Hostname:</span> <span>Hostname:</span>
<span class="font-mono" <span class="font-mono"
>{$ae_loc.native_device.info_hostname || '...'}</span >{$ae_loc.native_device.info_hostname || '...'}</span>
>
</div> </div>
<div class="flex justify-between gap-4"> <div class="flex justify-between gap-4">
<span>IP Addresses:</span> <span>IP Addresses:</span>
<span class="font-mono truncate" <span class="truncate font-mono"
>{$ae_loc.native_device.info_ip_list || '...'}</span >{$ae_loc.native_device.info_ip_list || '...'}</span>
>
</div> </div>
<div class="mt-2 opacity-40"> <div class="mt-2 opacity-40">
<span class="text-[8px] uppercase font-bold" <span class="text-[8px] font-bold uppercase"
>Raw Device JSON</span >Raw Device JSON</span>
>
<pre <pre
class="text-[7px] max-h-32 overflow-y-auto bg-black/20 p-1 rounded mt-1"> class="mt-1 max-h-32 overflow-y-auto rounded bg-black/20 p-1 text-[7px]">
{JSON.stringify($ae_loc.native_device, null, 2)} {JSON.stringify($ae_loc.native_device, null, 2)}
</pre> </pre>
</div> </div>

View File

@@ -14,11 +14,20 @@
async function handle_cleanup_now() { async function handle_cleanup_now() {
const cache_root = $ae_loc.local_file_cache_path; const cache_root = $ae_loc.local_file_cache_path;
if (!cache_root) { cleanup_status = 'Error: Cache path not set.'; return; } if (!cache_root) {
cleanup_status = 'Error: Cache path not set.';
return;
}
const max_age_hours = $events_loc.launcher.cleanup_tmp_max_age_hours ?? 24; const max_age_hours = $events_loc.launcher.cleanup_tmp_max_age_hours ?? 24;
cleanup_status = 'Cleaning...'; cleanup_status = 'Cleaning...';
const result = await cleanup_tmp_files({ cache_root, max_age_minutes: max_age_hours * 60 }); const result = await cleanup_tmp_files({
cleanup_status = (result as any).success !== false ? 'Done.' : `Error: ${(result as any).error}`; cache_root,
max_age_minutes: max_age_hours * 60
});
cleanup_status =
(result as any).success !== false
? 'Done.'
: `Error: ${(result as any).error}`;
setTimeout(() => (cleanup_status = ''), 4000); setTimeout(() => (cleanup_status = ''), 4000);
} }
@@ -27,9 +36,7 @@
if (val == 'delete_idbs') { if (val == 'delete_idbs') {
if ( if (
confirm( confirm('Are you sure you want to delete ALL IndexedDB databases?')
'Are you sure you want to delete ALL IndexedDB databases?'
)
) { ) {
indexedDB.deleteDatabase('ae_archives_db'); indexedDB.deleteDatabase('ae_archives_db');
indexedDB.deleteDatabase('ae_core_db'); indexedDB.deleteDatabase('ae_core_db');
@@ -37,9 +44,7 @@
indexedDB.deleteDatabase('ae_journals_db'); indexedDB.deleteDatabase('ae_journals_db');
indexedDB.deleteDatabase('ae_posts_db'); indexedDB.deleteDatabase('ae_posts_db');
indexedDB.deleteDatabase('ae_sponsorships_db'); indexedDB.deleteDatabase('ae_sponsorships_db');
alert( alert('All IndexedDB databases deleted. Please reload the app.');
'All IndexedDB databases deleted. Please reload the app.'
);
} }
} else if (val == 'delete_idbs_events') { } else if (val == 'delete_idbs_events') {
if ( if (
@@ -48,9 +53,7 @@
) )
) { ) {
indexedDB.deleteDatabase('ae_events_db'); indexedDB.deleteDatabase('ae_events_db');
alert( alert('Events IndexedDB database deleted. Please reload the app.');
'Events IndexedDB database deleted. Please reload the app.'
);
} }
} else if (val == 'delete_local') { } else if (val == 'delete_local') {
if (confirm('Are you sure you want to delete ALL local config?')) { if (confirm('Are you sure you want to delete ALL local config?')) {
@@ -79,44 +82,43 @@
icon={Wrench} icon={Wrench}
bind:state={$events_loc.launcher.section_state__local_actions} bind:state={$events_loc.launcher.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">
<!-- 1. Reset Actions --> <!-- 1. Reset Actions -->
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<span <span
class="text-[9px] font-bold uppercase opacity-50 ml-1 text-error-500" class="text-error-500 ml-1 text-[9px] font-bold uppercase opacity-50"
>Maintenance & Resets</span >Maintenance & Resets</span>
>
<select <select
bind:value={selected_reset} bind:value={selected_reset}
onchange={(e) => onchange={(e) =>
handle_reset_action((e.target as HTMLSelectElement).value)} handle_reset_action((e.target as HTMLSelectElement).value)}
class="select select-sm text-xs preset-tonal-surface h-8 text-error-500 border-error-500/20" class="select select-sm preset-tonal-surface text-error-500 border-error-500/20 h-8 text-xs">
>
<option value="">-- Select a reset action --</option> <option value="">-- Select a reset action --</option>
<option value="delete_idbs">Delete ALL Databases</option> <option value="delete_idbs">Delete ALL Databases</option>
<option value="delete_idbs_events">Delete Events DB Only</option <option value="delete_idbs_events"
> >Delete Events DB Only</option>
<option value="delete_local">Wipe ALL Local Storage</option> <option value="delete_local">Wipe ALL Local Storage</option>
<option value="delete_local_events" <option value="delete_local_events"
>Wipe Events Storage Only</option >Wipe Events Storage Only</option>
>
</select> </select>
<span class="text-[8px] opacity-40 italic ml-1 leading-tight"> <span class="ml-1 text-[8px] leading-tight italic opacity-40">
* Destructive actions require browser confirmation. * Destructive actions require browser confirmation.
</span> </span>
</div> </div>
<!-- 2. UI Toggles --> <!-- 2. UI Toggles -->
<div class="grid grid-cols-2 gap-2 mt-1"> <div class="mt-1 grid grid-cols-2 gap-2">
<button <button
type="button" type="button"
onclick={() => ($ae_loc.sys_menu.hide = !$ae_loc.sys_menu.hide)} onclick={() => ($ae_loc.sys_menu.hide = !$ae_loc.sys_menu.hide)}
class="btn btn-xs preset-tonal-surface hover:preset-filled-primary-500" class="btn btn-xs preset-tonal-surface hover:preset-filled-primary-500"
title="Show/Hide Aether global system menu" title="Show/Hide Aether global system menu">
> {#if $ae_loc.sys_menu.hide}<Eye
{#if $ae_loc.sys_menu.hide}<Eye size="1em" class="mr-2" />{:else}<EyeOff size="1em" class="mr-2" />{/if} size="1em"
class="mr-2" />{:else}<EyeOff
size="1em"
class="mr-2" />{/if}
{$ae_loc.sys_menu.hide ? 'Show' : 'Hide'} Sys Menu {$ae_loc.sys_menu.hide ? 'Show' : 'Hide'} Sys Menu
</button> </button>
@@ -125,41 +127,51 @@
onclick={() => onclick={() =>
($ae_loc.debug_menu.hide = !$ae_loc.debug_menu.hide)} ($ae_loc.debug_menu.hide = !$ae_loc.debug_menu.hide)}
class="btn btn-xs preset-tonal-surface hover:preset-filled-primary-500" class="btn btn-xs preset-tonal-surface hover:preset-filled-primary-500"
title="Show/Hide Aether global debug menu" title="Show/Hide Aether global debug menu">
> {#if $ae_loc.debug_menu.hide}<Bug
{#if $ae_loc.debug_menu.hide}<Bug size="1em" class="mr-2" />{:else}<BugOff size="1em" class="mr-2" />{/if} size="1em"
class="mr-2" />{:else}<BugOff
size="1em"
class="mr-2" />{/if}
{$ae_loc.debug_menu.hide ? 'Show' : 'Hide'} Debug {$ae_loc.debug_menu.hide ? 'Show' : 'Hide'} Debug
</button> </button>
</div> </div>
<!-- 3. Cache .tmp Cleanup (Native Only) --> <!-- 3. Cache .tmp Cleanup (Native Only) -->
{#if $ae_loc.is_native && $ae_loc.local_file_cache_path} {#if $ae_loc.is_native && $ae_loc.local_file_cache_path}
<div class="flex flex-col gap-1 border-t border-surface-500/20 pt-2 mt-1"> <div
<span class="text-[9px] font-bold uppercase opacity-50 ml-1">Cache Maintenance</span> class="border-surface-500/20 mt-1 flex flex-col gap-1 border-t pt-2">
<span class="ml-1 text-[9px] font-bold uppercase opacity-50"
>Cache Maintenance</span>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<label for="cleanup_max_age" class="text-[10px] opacity-70 whitespace-nowrap">Max age (hrs):</label> <label
for="cleanup_max_age"
class="text-[10px] whitespace-nowrap opacity-70"
>Max age (hrs):</label>
<input <input
id="cleanup_max_age" id="cleanup_max_age"
type="number" type="number"
min="1" min="1"
max="168" max="168"
bind:value={$events_loc.launcher.cleanup_tmp_max_age_hours} bind:value={
class="input input-sm w-16 h-7 text-xs text-center preset-tonal-surface" $events_loc.launcher.cleanup_tmp_max_age_hours
placeholder="24" }
/> class="input input-sm preset-tonal-surface h-7 w-16 text-center text-xs"
placeholder="24" />
<button <button
type="button" type="button"
onclick={handle_cleanup_now} onclick={handle_cleanup_now}
class="btn btn-xs preset-tonal-warning hover:preset-filled-warning-500 grow" class="btn btn-xs preset-tonal-warning hover:preset-filled-warning-500 grow">
>
<Eraser size="0.85em" class="mr-1" /> Clean .tmp Now <Eraser size="0.85em" class="mr-1" /> Clean .tmp Now
</button> </button>
</div> </div>
{#if cleanup_status} {#if cleanup_status}
<span class="text-[9px] italic opacity-60 ml-1">{cleanup_status}</span> <span class="ml-1 text-[9px] italic opacity-60"
>{cleanup_status}</span>
{/if} {/if}
<span class="text-[8px] opacity-40 italic ml-1 leading-tight"> <span class="ml-1 text-[8px] leading-tight italic opacity-40">
Removes stale in-progress download artifacts. Auto-runs on startup. Removes stale in-progress download artifacts. Auto-runs on
startup.
</span> </span>
</div> </div>
{/if} {/if}
@@ -167,14 +179,12 @@
<!-- 4. Connection Summary (Edit Mode Only) --> <!-- 4. Connection Summary (Edit Mode Only) -->
{#if $ae_loc.edit_mode} {#if $ae_loc.edit_mode}
<div <div
class="col-span-full border-t border-surface-500/20 pt-2 mt-1 flex flex-col gap-1" class="border-surface-500/20 col-span-full mt-1 flex flex-col gap-1 border-t pt-2">
> <p class="ml-1 text-[9px] font-bold uppercase opacity-50">
<p class="text-[9px] font-bold uppercase opacity-50 ml-1" API Context
>API Context</p </p>
>
<div <div
class="bg-black/10 p-2 rounded text-[9px] font-mono opacity-60 break-all leading-tight" class="rounded bg-black/10 p-2 font-mono text-[9px] leading-tight break-all opacity-60">
>
Endpoint: {$ae_api.base_url}<br /> Endpoint: {$ae_api.base_url}<br />
Account: {$ae_loc.account_id} Account: {$ae_loc.account_id}
</div> </div>

View File

@@ -3,7 +3,21 @@
import { events_loc, events_sess } from '$lib/stores/ae_events_stores'; import { events_loc, events_sess } from '$lib/stores/ae_events_stores';
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 { Code, Columns2, FlaskConical, FolderOpen, Image, Maximize2, Monitor, Play, Power, RefreshCw, SkipBack, SkipForward, Square } from '@lucide/svelte'; import {
Code,
Columns2,
FlaskConical,
FolderOpen,
Image,
Maximize2,
Monitor,
Play,
Power,
RefreshCw,
SkipBack,
SkipForward,
Square
} from '@lucide/svelte';
interface Props { interface Props {
on_expand?: () => void; on_expand?: () => void;
} }
@@ -42,9 +56,7 @@
} }
// Modal state for dangerous actions // Modal state for dangerous actions
let show_power_confirm = $state<{ action: string; label: string } | null>( let show_power_confirm = $state<{ action: string; label: string } | null>(null);
null
);
</script> </script>
<Launcher_Cfg_Section <Launcher_Cfg_Section
@@ -53,51 +65,50 @@
bind:state={$events_loc.launcher.section_state__native_os} bind:state={$events_loc.launcher.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">
>
<!-- Dev preview banner: shown when edit_mode is on but not running in Electron. <!-- Dev preview banner: shown when edit_mode is on but not running in Electron.
electron_relay functions all return null when native is absent — no errors. --> electron_relay functions all return null when native is absent — no errors. -->
{#if $ae_loc.edit_mode && !$ae_loc.is_native} {#if $ae_loc.edit_mode && !$ae_loc.is_native}
<div class="flex items-center gap-2 px-2 py-1.5 rounded-lg bg-warning-500/10 border border-warning-500/30 mb-1"> <div
class="bg-warning-500/10 border-warning-500/30 mb-1 flex items-center gap-2 rounded-lg border px-2 py-1.5">
<FlaskConical size="0.75em" class="text-warning-500" /> <FlaskConical size="0.75em" class="text-warning-500" />
<span class="text-[9px] text-warning-500 font-bold uppercase tracking-wide">Dev Preview — controls visible but non-functional without Electron</span> <span
class="text-warning-500 text-[9px] font-bold tracking-wide uppercase"
>Dev Preview — controls visible but non-functional without
Electron</span>
</div> </div>
{/if} {/if}
{#if system_status} {#if system_status}
<div <div
class="col-span-full text-[10px] text-center italic bg-surface-500/10 py-1 rounded animate-pulse text-primary-500 border border-primary-500/20" class="bg-surface-500/10 text-primary-500 border-primary-500/20 col-span-full animate-pulse rounded border py-1 text-center text-[10px] italic">
>
{system_status} {system_status}
</div> </div>
{/if} {/if}
<!-- 1. Window & Folders (Common) --> <!-- 1. Window & Folders (Common) -->
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<p class="text-[9px] font-bold uppercase opacity-50 ml-1" <p class="ml-1 text-[9px] font-bold uppercase opacity-50">
>Folders & View</p Folders & View
> </p>
<div class="grid grid-cols-2 gap-1"> <div class="grid grid-cols-2 gap-1">
<button <button
type="button" type="button"
onclick={() => onclick={() =>
native.open_folder($ae_loc.local_file_cache_path)} native.open_folder($ae_loc.local_file_cache_path)}
class="btn btn-xs preset-tonal-primary hover:preset-filled-primary-500 justify-start" class="btn btn-xs preset-tonal-primary hover:preset-filled-primary-500 justify-start">
>
<FolderOpen size="0.85em" class="mr-1 shrink-0" /> Cache <FolderOpen size="0.85em" class="mr-1 shrink-0" /> Cache
</button> </button>
<button <button
type="button" type="button"
onclick={() => native.open_folder($ae_loc.host_file_temp_path)} onclick={() => native.open_folder($ae_loc.host_file_temp_path)}
class="btn btn-xs preset-tonal-primary hover:preset-filled-primary-500 justify-start" class="btn btn-xs preset-tonal-primary hover:preset-filled-primary-500 justify-start">
>
<FolderOpen size="0.85em" class="mr-1 shrink-0" /> Temp <FolderOpen size="0.85em" class="mr-1 shrink-0" /> Temp
</button> </button>
<button <button
type="button" type="button"
onclick={() => native.window_control({ action: 'maximize' })} onclick={() => native.window_control({ action: 'maximize' })}
class="btn btn-xs preset-tonal-surface hover:preset-filled-primary-500" class="btn btn-xs preset-tonal-surface hover:preset-filled-primary-500">
>
<Maximize2 size="0.85em" class="mr-1" /> Maximize <Maximize2 size="0.85em" class="mr-1" /> Maximize
</button> </button>
<button <button
@@ -107,8 +118,7 @@
native.window_control({ action: 'kiosk', value: true }), native.window_control({ action: 'kiosk', value: true }),
'Kiosk Mode' 'Kiosk Mode'
)} )}
class="btn btn-xs preset-tonal-surface hover:preset-filled-primary-500" class="btn btn-xs preset-tonal-surface hover:preset-filled-primary-500">
>
<Monitor size="0.85em" class="mr-1" /> Kiosk <Monitor size="0.85em" class="mr-1" /> Kiosk
</button> </button>
</div> </div>
@@ -116,14 +126,13 @@
<!-- 2. Presentation Remote Control (Common) --> <!-- 2. Presentation Remote Control (Common) -->
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<div class="flex flex-row justify-between items-center px-1"> <div class="flex flex-row items-center justify-between px-1">
<p class="text-[9px] font-bold uppercase opacity-50" <p class="text-[9px] font-bold uppercase opacity-50">
>Remote Control</p Remote Control
> </p>
<select <select
bind:value={remote_app} bind:value={remote_app}
class="select select-sm py-0 h-5 text-[9px] w-24 preset-tonal-surface" class="select select-sm preset-tonal-surface h-5 w-24 py-0 text-[9px]">
>
<option value="powerpoint">PowerPoint</option> <option value="powerpoint">PowerPoint</option>
<option value="keynote">Keynote</option> <option value="keynote">Keynote</option>
</select> </select>
@@ -134,39 +143,34 @@
type="button" type="button"
onclick={() => handle_remote_control('prev')} onclick={() => handle_remote_control('prev')}
class="btn btn-sm preset-tonal-secondary" class="btn btn-sm preset-tonal-secondary"
title="Previous Slide" title="Previous Slide">
>
<SkipBack size="1em" /> <SkipBack size="1em" />
</button> </button>
<button <button
type="button" type="button"
onclick={() => handle_remote_control('start')} onclick={() => handle_remote_control('start')}
class="btn btn-sm preset-tonal-success" class="btn btn-sm preset-tonal-success"
title="Start/Resume Slideshow" title="Start/Resume Slideshow">
>
<Play size="1em" /> <Play size="1em" />
</button> </button>
<button <button
type="button" type="button"
onclick={() => handle_remote_control('stop')} onclick={() => handle_remote_control('stop')}
class="btn btn-sm preset-tonal-error" class="btn btn-sm preset-tonal-error"
title="Stop Slideshow" title="Stop Slideshow">
>
<Square size="1em" /> <Square size="1em" />
</button> </button>
<button <button
type="button" type="button"
onclick={() => handle_remote_control('next')} onclick={() => handle_remote_control('next')}
class="btn btn-sm preset-tonal-secondary" class="btn btn-sm preset-tonal-secondary"
title="Next Slide" title="Next Slide">
>
<SkipForward size="1em" /> <SkipForward size="1em" />
</button> </button>
</div> </div>
{#if remote_status} {#if remote_status}
<div <div
class="text-[9px] text-center italic animate-pulse text-primary-500" class="text-primary-500 animate-pulse text-center text-[9px] italic">
>
{remote_status} {remote_status}
</div> </div>
{/if} {/if}
@@ -175,13 +179,12 @@
<!-- 3. Technical Management (Edit Mode Only) --> <!-- 3. Technical Management (Edit Mode Only) -->
{#if $ae_loc.edit_mode} {#if $ae_loc.edit_mode}
<div <div
class="col-span-full border-t border-surface-500/20 pt-3 mt-1 flex flex-col gap-3" class="border-surface-500/20 col-span-full mt-1 flex flex-col gap-3 border-t pt-3">
>
<div class="grid grid-cols-2 gap-2"> <div class="grid grid-cols-2 gap-2">
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<p class="text-[9px] font-bold uppercase opacity-50" <p class="text-[9px] font-bold uppercase opacity-50">
>System Actions</p System Actions
> </p>
<div class="grid grid-cols-1 gap-1"> <div class="grid grid-cols-1 gap-1">
<button <button
type="button" type="button"
@@ -192,9 +195,9 @@
}), }),
'Extend Display' 'Extend Display'
)} )}
class="btn btn-xs preset-tonal-surface hover:preset-filled-primary-500 justify-start" class="btn btn-xs preset-tonal-surface hover:preset-filled-primary-500 justify-start">
> <Columns2 size="0.85em" class="mr-1 shrink-0" /> Extend
<Columns2 size="0.85em" class="mr-1 shrink-0" /> Extend Mode Mode
</button> </button>
<button <button
type="button" type="button"
@@ -206,17 +209,15 @@
'Wallpaper' 'Wallpaper'
)} )}
class="btn btn-xs preset-tonal-surface hover:preset-filled-primary-500 justify-start" class="btn btn-xs preset-tonal-surface hover:preset-filled-primary-500 justify-start"
disabled={!$ae_loc.site_header_image_path} disabled={!$ae_loc.site_header_image_path}>
>
<Image size="0.85em" class="mr-1 shrink-0" /> Reset Wallpaper <Image size="0.85em" class="mr-1 shrink-0" /> Reset Wallpaper
</button> </button>
</div> </div>
</div> </div>
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<span <span
class="text-[9px] font-bold uppercase opacity-50 text-error-500" class="text-error-500 text-[9px] font-bold uppercase opacity-50"
>Power</span >Power</span>
>
<div class="grid grid-cols-1 gap-1"> <div class="grid grid-cols-1 gap-1">
<button <button
type="button" type="button"
@@ -225,8 +226,7 @@
action: 'reboot', action: 'reboot',
label: 'Reboot Laptop' label: 'Reboot Laptop'
})} })}
class="btn btn-xs preset-tonal-warning hover:preset-filled-warning-500 justify-start" class="btn btn-xs preset-tonal-warning hover:preset-filled-warning-500 justify-start">
>
<RefreshCw size="0.85em" class="mr-1 shrink-0" /> Reboot <RefreshCw size="0.85em" class="mr-1 shrink-0" /> Reboot
</button> </button>
<button <button
@@ -236,8 +236,7 @@
action: 'shutdown', action: 'shutdown',
label: 'Shutdown Laptop' label: 'Shutdown Laptop'
})} })}
class="btn btn-xs preset-tonal-error hover:preset-filled-error-500 justify-start" class="btn btn-xs preset-tonal-error hover:preset-filled-error-500 justify-start">
>
<Power size="0.85em" class="mr-1 shrink-0" /> Shutdown <Power size="0.85em" class="mr-1 shrink-0" /> Shutdown
</button> </button>
</div> </div>
@@ -245,16 +244,15 @@
</div> </div>
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<p class="text-[9px] font-bold uppercase opacity-50 ml-1" <p class="ml-1 text-[9px] font-bold uppercase opacity-50">
>Terminal Access</p Terminal Access
> </p>
<div class="flex gap-1"> <div class="flex gap-1">
<input <input
type="text" type="text"
bind:value={$events_sess.launcher.manual_cmd} bind:value={$events_sess.launcher.manual_cmd}
placeholder="ls -la" placeholder="ls -la"
class="input input-sm grow text-[10px] preset-tonal-surface h-7" class="input input-sm preset-tonal-surface h-7 grow text-[10px]" />
/>
<button <button
type="button" type="button"
onclick={async () => { onclick={async () => {
@@ -268,13 +266,12 @@
(res as any).error || (res as any).error ||
'No Output'; 'No Output';
}} }}
class="btn btn-sm preset-filled-secondary hover:preset-filled-primary-500 text-[10px] h-7" class="btn btn-sm preset-filled-secondary hover:preset-filled-primary-500 h-7 text-[10px]"
>Run</button >Run</button>
>
</div> </div>
{#if test_cmd_result} {#if test_cmd_result}
<pre <pre
class="text-[8px] bg-black text-green-500 p-2 mt-1 overflow-x-auto rounded border border-surface-500/50 max-h-24 shadow-inner">{test_cmd_result}</pre> class="border-surface-500/50 mt-1 max-h-24 overflow-x-auto rounded border bg-black p-2 text-[8px] text-green-500 shadow-inner">{test_cmd_result}</pre>
{/if} {/if}
</div> </div>
</div> </div>
@@ -284,25 +281,21 @@
<!-- Power Confirmation Modal --> <!-- Power Confirmation Modal -->
{#if show_power_confirm} {#if show_power_confirm}
<div <div
class="fixed inset-0 z-[1000] flex items-center justify-center bg-black/50 backdrop-blur-sm p-4" class="fixed inset-0 z-[1000] flex items-center justify-center bg-black/50 p-4 backdrop-blur-sm">
>
<div <div
class="card p-6 w-full max-w-sm preset-filled-surface-100-900 border border-error-500 shadow-2xl animate-in zoom-in-95 duration-200" class="card preset-filled-surface-100-900 border-error-500 animate-in zoom-in-95 w-full max-w-sm border p-6 shadow-2xl duration-200">
> <h4 class="h4 text-error-500 mb-2 font-bold">
<h4 class="h4 text-error-500 font-bold mb-2">
Confirm System Action Confirm System Action
</h4> </h4>
<p class="text-sm opacity-80 mb-6"> <p class="mb-6 text-sm opacity-80">
Are you sure you want to <strong Are you sure you want to <strong
>{show_power_confirm.action}</strong >{show_power_confirm.action}</strong> this host machine?
> this host machine?
</p> </p>
<div class="flex justify-end gap-2"> <div class="flex justify-end gap-2">
<button <button
type="button" type="button"
onclick={() => (show_power_confirm = null)} onclick={() => (show_power_confirm = null)}
class="btn btn-sm preset-tonal-surface">Cancel</button class="btn btn-sm preset-tonal-surface">Cancel</button>
>
<button <button
type="button" type="button"
onclick={() => { onclick={() => {
@@ -314,8 +307,7 @@
action action
); );
}} }}
class="btn btn-sm preset-filled-error" class="btn btn-sm preset-filled-error">
>
Confirm {show_power_confirm.action} Confirm {show_power_confirm.action}
</button> </button>
</div> </div>

View File

@@ -16,62 +16,54 @@
{on_expand} {on_expand}
description="Idle: {($events_loc.launcher.idle_timer / 60000).toFixed( description="Idle: {($events_loc.launcher.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 -->
<div class="col-span-full flex flex-col gap-3"> <div class="col-span-full flex flex-col gap-3">
<!-- 1. Technical Timers (Edit Mode Only) --> <!-- 1. Technical Timers (Edit Mode Only) -->
{#if $ae_loc.edit_mode} {#if $ae_loc.edit_mode}
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<p class="text-[9px] font-bold uppercase opacity-50 ml-1" <p class="ml-1 text-[9px] font-bold uppercase opacity-50">
>Screen Saver Timers (ms)</p Screen Saver Timers (ms)
> </p>
<div <div
class="grid grid-cols-1 gap-2 bg-surface-500/5 p-2 rounded border border-surface-500/10" class="bg-surface-500/5 border-surface-500/10 grid grid-cols-1 gap-2 rounded border p-2">
> <div class="flex items-center justify-between gap-4">
<div class="flex justify-between items-center gap-4">
<span class="text-[10px] opacity-60">Idle Wait</span> <span class="text-[10px] opacity-60">Idle Wait</span>
<input <input
type="number" type="number"
min={3000} min={3000}
bind:value={$events_loc.launcher.idle_timer} bind:value={$events_loc.launcher.idle_timer}
class="input input-sm text-[10px] h-7 w-24 text-right preset-tonal-surface" class="input input-sm preset-tonal-surface h-7 w-24 text-right text-[10px]" />
/>
</div> </div>
<div class="flex justify-between items-center gap-4"> <div class="flex items-center justify-between gap-4">
<span class="text-[10px] opacity-60">Cycle Check</span> <span class="text-[10px] opacity-60">Cycle Check</span>
<input <input
type="number" type="number"
min={500} min={500}
bind:value={$events_loc.launcher.idle_cycle} bind:value={$events_loc.launcher.idle_cycle}
class="input input-sm text-[10px] h-7 w-24 text-right preset-tonal-surface" class="input input-sm preset-tonal-surface h-7 w-24 text-right text-[10px]" />
/>
</div> </div>
<div class="flex justify-between items-center gap-4"> <div class="flex items-center justify-between gap-4">
<span class="text-[10px] opacity-60" <span class="text-[10px] opacity-60"
>Image Rotation</span >Image Rotation</span>
>
<input <input
type="number" type="number"
min={750} min={750}
bind:value={$events_loc.launcher.idle_loop_period} bind:value={$events_loc.launcher.idle_loop_period}
class="input input-sm text-[10px] h-7 w-24 text-right preset-tonal-surface" class="input input-sm preset-tonal-surface h-7 w-24 text-right text-[10px]" />
/>
</div> </div>
</div> </div>
</div> </div>
{:else} {:else}
<!-- 2. Read Only Summary (Normal Mode) --> <!-- 2. Read Only Summary (Normal Mode) -->
<div <div
class="bg-surface-500/5 p-3 rounded-lg border border-surface-500/10 flex flex-col gap-2" class="bg-surface-500/5 border-surface-500/10 flex flex-col gap-2 rounded-lg border p-3">
> <div class="flex items-center justify-between text-xs">
<div class="flex justify-between items-center text-xs">
<span class="opacity-60">Active Idle Timeout:</span> <span class="opacity-60">Active Idle Timeout:</span>
<span class="font-bold text-primary-500" <span class="text-primary-500 font-bold"
>{($events_loc.launcher.idle_timer / 60000).toFixed(1)} minutes</span >{($events_loc.launcher.idle_timer / 60000).toFixed(1)} minutes</span>
>
</div> </div>
<p class="text-[9px] opacity-40 italic"> <p class="text-[9px] italic opacity-40">
The screen saver automatically rotates digital posters when The screen saver automatically rotates digital posters when
no activity is detected for the specified time. no activity is detected for the specified time.
</p> </p>
@@ -79,7 +71,7 @@
{/if} {/if}
<div class="text-center"> <div class="text-center">
<p class="text-[8px] opacity-40 italic uppercase tracking-tighter"> <p class="text-[8px] tracking-tighter uppercase italic opacity-40">
Applies to "Poster" session types only Applies to "Poster" session types only
</p> </p>
</div> </div>

View File

@@ -54,44 +54,39 @@
</script> </script>
<section <section
class="w-full transition-all duration-300 border rounded-lg overflow-hidden mb-2 {!is_open class="mb-2 w-full overflow-hidden rounded-lg border transition-all duration-300 {!is_open
? 'preset-outlined-surface-300-700' ? 'preset-outlined-surface-300-700'
: ''} {state === 'auto' : ''} {state === 'auto'
? 'preset-outlined-primary-500 shadow-xl' ? 'preset-outlined-primary-500 shadow-xl'
: ''} {state === 'pinned' : ''} {state === 'pinned'
? 'preset-outlined-warning-500 shadow-xl' ? 'preset-outlined-warning-500 shadow-xl'
: ''}" : ''}">
>
<!-- Header --> <!-- Header -->
<!-- svelte-ignore a11y_click_events_have_key_events --> <!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<header <header
class="flex flex-row items-center justify-between p-2 cursor-pointer transition-colors {!is_open class="flex cursor-pointer flex-row items-center justify-between p-2 transition-colors {!is_open
? 'bg-surface-500/5' ? 'bg-surface-500/5'
: ''} {state === 'auto' ? 'bg-primary-500/10' : ''} {state === : ''} {state === 'auto' ? 'bg-primary-500/10' : ''} {state ===
'pinned' 'pinned'
? 'bg-warning-500/10' ? 'bg-warning-500/10'
: ''}" : ''}"
onclick={toggle_expand} onclick={toggle_expand}>
>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<Icon <Icon
size="1em" size="1em"
class="w-5 text-center opacity-70 {state === 'auto' class="w-5 text-center opacity-70 {state === 'auto'
? 'text-primary-500' ? 'text-primary-500'
: ''} {state === 'pinned' ? 'text-warning-500' : ''}" : ''} {state === 'pinned' ? 'text-warning-500' : ''}" />
/>
<div class="flex flex-col"> <div class="flex flex-col">
<span <span
class="text-sm font-bold tracking-tight uppercase {!is_open class="text-sm font-bold tracking-tight uppercase {!is_open
? 'opacity-50' ? 'opacity-50'
: ''}">{title}</span : ''}">{title}</span>
>
{#if description && !is_open} {#if description && !is_open}
<span <span
class="text-[9px] opacity-40 italic truncate max-w-[180px]" class="max-w-[180px] truncate text-[9px] italic opacity-40"
>{description}</span >{description}</span>
>
{/if} {/if}
</div> </div>
</div> </div>
@@ -106,16 +101,19 @@
class:text-warning-500={state === 'pinned'} class:text-warning-500={state === 'pinned'}
title={state === 'pinned' title={state === 'pinned'
? 'Unpin Section' ? 'Unpin Section'
: 'Pin Section (Stay open)'} : 'Pin Section (Stay open)'}>
>
<Pin size="0.7em" /> <Pin size="0.7em" />
</button> </button>
<!-- Collapse Icon --> <!-- Collapse Icon -->
{#if is_open} {#if is_open}
<ChevronDown size="1em" class="transition-transform duration-300 opacity-30" /> <ChevronDown
size="1em"
class="opacity-30 transition-transform duration-300" />
{:else} {:else}
<ChevronRight size="1em" class="transition-transform duration-300 opacity-30" /> <ChevronRight
size="1em"
class="opacity-30 transition-transform duration-300" />
{/if} {/if}
</div> </div>
</header> </header>
@@ -124,27 +122,22 @@
{#if is_open} {#if is_open}
<div <div
transition:slide={{ duration: 300 }} transition:slide={{ duration: 300 }}
class="p-3 bg-white/5 dark:bg-black/5" class="bg-white/5 p-3 dark:bg-black/5">
>
{#if $ae_loc.edit_mode} {#if $ae_loc.edit_mode}
<div class="mb-2 flex justify-between items-center px-1"> <div class="mb-2 flex items-center justify-between px-1">
<span <span
class="text-[8px] uppercase font-bold tracking-widest text-primary-500/60 flex items-center gap-1" class="text-primary-500/60 flex items-center gap-1 text-[8px] font-bold tracking-widest uppercase">
>
<Pencil size="0.7em" /> Technical Mode <Pencil size="0.7em" /> Technical Mode
</span> </span>
{#if state === 'pinned'} {#if state === 'pinned'}
<span <span
class="badge preset-filled-warning text-[8px] uppercase" class="badge preset-filled-warning text-[8px] uppercase"
>Pinned</span >Pinned</span>
>
{/if} {/if}
</div> </div>
{/if} {/if}
<div <div class="grid grid-cols-1 gap-3">
class="grid grid-cols-1 gap-3"
>
{@render children?.()} {@render children?.()}
</div> </div>
</div> </div>

View File

@@ -15,21 +15,24 @@
bind:state={$events_loc.launcher.section_state__sync_timers} bind:state={$events_loc.launcher.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">
>
<!-- Content omitted for brevity, preserved in file --> <!-- Content omitted for brevity, preserved in file -->
<!-- Pause toggle: always visible — useful during testing or onsite troubleshooting --> <!-- Pause toggle: always visible — useful during testing or onsite troubleshooting -->
<div class="flex items-center justify-between mb-2 p-2 rounded border border-surface-500/10 bg-surface-500/5"> <div
<span class="text-[10px] font-bold uppercase tracking-wider opacity-70"> class="border-surface-500/10 bg-surface-500/5 mb-2 flex items-center justify-between rounded border p-2">
{$events_loc.launcher.sync_paused ? '⏸ Sync Paused' : '▶ Sync Active'} <span class="text-[10px] font-bold tracking-wider uppercase opacity-70">
{$events_loc.launcher.sync_paused
? '⏸ Sync Paused'
: '▶ Sync Active'}
</span> </span>
<button <button
type="button" type="button"
onclick={() => ($events_loc.launcher.sync_paused = !$events_loc.launcher.sync_paused)} onclick={() =>
($events_loc.launcher.sync_paused =
!$events_loc.launcher.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={$events_loc.launcher.sync_paused}
class:preset-tonal-success={!$events_loc.launcher.sync_paused} class:preset-tonal-success={!$events_loc.launcher.sync_paused}>
>
{#if $events_loc.launcher.sync_paused} {#if $events_loc.launcher.sync_paused}
<Play size="0.85em" class="mr-1" /> Resume <Play size="0.85em" class="mr-1" /> Resume
{:else} {:else}
@@ -43,92 +46,80 @@
<!-- Technical Timers (Edit Mode Only) --> <!-- Technical Timers (Edit Mode Only) -->
{#if $ae_loc.edit_mode} {#if $ae_loc.edit_mode}
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<p <p class="ml-1 text-[9px] font-bold uppercase opacity-50">
class="text-[9px] font-bold uppercase opacity-50 ml-1" Polling Periods (ms)
>Polling Periods (ms)</p </p>
>
<div class="grid grid-cols-2 gap-2"> <div class="grid grid-cols-2 gap-2">
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<span class="text-[8px] opacity-60">Event Data</span <span class="text-[8px] opacity-60"
> >Event Data</span>
<input <input
type="number" type="number"
bind:value={ bind:value={
$events_loc.launcher.sync_intervals.event $events_loc.launcher.sync_intervals.event
} }
class="input input-sm text-[10px] h-7 preset-tonal-surface" class="input input-sm preset-tonal-surface h-7 text-[10px]" />
/>
</div> </div>
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<span class="text-[8px] opacity-60" <span class="text-[8px] opacity-60"
>Device Config</span >Device Config</span>
>
<input <input
type="number" type="number"
bind:value={ bind:value={
$events_loc.launcher.sync_intervals.device $events_loc.launcher.sync_intervals.device
} }
class="input input-sm text-[10px] h-7 preset-tonal-surface" class="input input-sm preset-tonal-surface h-7 text-[10px]" />
/>
</div> </div>
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<span class="text-[8px] opacity-60" <span class="text-[8px] opacity-60"
>Room/Location</span >Room/Location</span>
>
<input <input
type="number" type="number"
bind:value={ bind:value={
$events_loc.launcher.sync_intervals.location $events_loc.launcher.sync_intervals.location
} }
class="input input-sm text-[10px] h-7 preset-tonal-surface" class="input input-sm preset-tonal-surface h-7 text-[10px]" />
/>
</div> </div>
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<span class="text-[8px] opacity-60" <span class="text-[8px] opacity-60"
>Session Loop</span >Session Loop</span>
>
<input <input
type="number" type="number"
bind:value={ bind:value={
$events_loc.launcher.sync_intervals.session $events_loc.launcher.sync_intervals.session
} }
class="input input-sm text-[10px] h-7 preset-tonal-surface" class="input input-sm preset-tonal-surface h-7 text-[10px]" />
/>
</div> </div>
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<span class="text-[8px] opacity-60" <span class="text-[8px] opacity-60"
>Presentation Loop</span >Presentation Loop</span>
>
<input <input
type="number" type="number"
bind:value={ bind:value={
$events_loc.launcher.sync_intervals.presentation $events_loc.launcher.sync_intervals
.presentation
} }
class="input input-sm text-[10px] h-7 preset-tonal-surface" class="input input-sm preset-tonal-surface h-7 text-[10px]" />
/>
</div> </div>
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<span class="text-[8px] opacity-60" <span class="text-[8px] opacity-60"
>Presenter Loop</span >Presenter Loop</span>
>
<input <input
type="number" type="number"
bind:value={ bind:value={
$events_loc.launcher.sync_intervals.presenter $events_loc.launcher.sync_intervals
.presenter
} }
class="input input-sm text-[10px] h-7 preset-tonal-surface" class="input input-sm preset-tonal-surface h-7 text-[10px]" />
/>
</div> </div>
</div> </div>
</div> </div>
<div <div
class="flex flex-col gap-1 mt-1 border-t border-surface-500/10 pt-2" class="border-surface-500/10 mt-1 flex flex-col gap-1 border-t pt-2">
> <p class="ml-1 text-[9px] font-bold uppercase opacity-50">
<p Cache Structure
class="text-[9px] font-bold uppercase opacity-50 ml-1" </p>
>Cache Structure</p
>
<div class="flex items-center justify-between px-1"> <div class="flex items-center justify-between px-1">
<span class="text-[9px]">Hash Prefix Length</span> <span class="text-[9px]">Hash Prefix Length</span>
{#if $ae_loc.native_device} {#if $ae_loc.native_device}
@@ -136,17 +127,17 @@
bind:value={ bind:value={
$ae_loc.native_device.hash_prefix_length $ae_loc.native_device.hash_prefix_length
} }
class="select select-sm h-6 py-0 text-[10px] w-16 preset-tonal-surface" class="select select-sm preset-tonal-surface h-6 w-16 py-0 text-[10px]">
>
<option value={1}>1 char</option> <option value={1}>1 char</option>
<option value={2}>2 chars</option> <option value={2}>2 chars</option>
<option value={3}>3 chars</option> <option value={3}>3 chars</option>
</select> </select>
{:else} {:else}
<span class="text-[9px] opacity-50 italic">loading…</span> <span class="text-[9px] italic opacity-50"
>loading…</span>
{/if} {/if}
</div> </div>
<p class="text-[8px] opacity-40 italic mt-1"> <p class="mt-1 text-[8px] italic opacity-40">
* Prefix change requires a full app reload to take * Prefix change requires a full app reload to take
effect. effect.
</p> </p>
@@ -154,47 +145,40 @@
{:else} {:else}
<!-- Read Only Summary (Normal Mode) --> <!-- Read Only Summary (Normal Mode) -->
<div <div
class="bg-surface-500/5 p-2 rounded border border-surface-500/10 flex flex-col gap-1" class="bg-surface-500/5 border-surface-500/10 flex flex-col gap-1 rounded border p-2">
>
<div <div
class="flex justify-between text-[9px] opacity-60 font-mono" class="flex justify-between font-mono text-[9px] opacity-60">
>
<span>Event Sync:</span> <span>Event Sync:</span>
<span <span
>{( >{(
$events_loc.launcher.sync_intervals.event / $events_loc.launcher.sync_intervals.event / 1000
1000 ).toFixed(1)}s</span>
).toFixed(1)}s</span
>
</div> </div>
<div <div
class="flex justify-between text-[9px] opacity-60 font-mono" class="flex justify-between font-mono text-[9px] opacity-60">
>
<span>Room Monitor:</span> <span>Room Monitor:</span>
<span <span
>{( >{(
$events_loc.launcher.sync_intervals.location / 1000 $events_loc.launcher.sync_intervals.location /
).toFixed(1)}s</span 1000
> ).toFixed(1)}s</span>
</div> </div>
<div <div
class="flex justify-between text-[9px] opacity-60 font-mono border-t border-surface-500/10 pt-1" class="border-surface-500/10 flex justify-between border-t pt-1 font-mono text-[9px] opacity-60">
>
<span>Prefix Sharding:</span> <span>Prefix Sharding:</span>
<span <span
>{$ae_loc.native_device?.hash_prefix_length || 2} chars</span >{$ae_loc.native_device?.hash_prefix_length || 2} chars</span>
>
</div> </div>
</div> </div>
<div class="text-center"> <div class="text-center">
<p class="text-[8px] opacity-40 italic"> <p class="text-[8px] italic opacity-40">
Enable Edit Mode to adjust polling intervals. Enable Edit Mode to adjust polling intervals.
</p> </p>
</div> </div>
{/if} {/if}
</div> </div>
{:else} {:else}
<div class="text-center p-4 opacity-50 italic text-xs"> <div class="p-4 text-center text-xs italic opacity-50">
Device configuration not loaded. Device configuration not loaded.
</div> </div>
{/if} {/if}

View File

@@ -7,7 +7,14 @@
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_loc, events_sess } from '$lib/stores/ae_events_stores';
import Launcher_Cfg_Section from './launcher_cfg_section.svelte'; import Launcher_Cfg_Section from './launcher_cfg_section.svelte';
import { AlertTriangle, Boxes, RefreshCw, Settings, Trash2, Zap } from '@lucide/svelte'; import {
AlertTriangle,
Boxes,
RefreshCw,
Settings,
Trash2,
Zap
} from '@lucide/svelte';
interface Props { interface Props {
on_expand?: () => void; on_expand?: () => void;
} }
@@ -43,100 +50,88 @@
icon={Boxes} icon={Boxes}
bind:state={$events_loc.launcher.section_state__template} bind:state={$events_loc.launcher.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) -->
{#if action_status} {#if action_status}
<div <div
class="col-span-full text-[10px] text-center italic bg-primary-500/10 py-1 rounded animate-pulse text-primary-500 border border-primary-500/20" class="bg-primary-500/10 text-primary-500 border-primary-500/20 col-span-full animate-pulse rounded border py-1 text-center text-[10px] italic">
>
{action_status} {action_status}
</div> </div>
{/if} {/if}
<!-- B. COMMON GRID SECTION (Read Only / High Level) --> <!-- B. COMMON GRID SECTION (Read Only / High Level) -->
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<p class="text-[9px] font-bold uppercase opacity-50 ml-1" <p class="ml-1 text-[9px] font-bold uppercase opacity-50">
>Standard Actions</p Standard Actions
> </p>
<!-- Action Buttons --> <!-- Action Buttons -->
<div class="grid grid-cols-2 gap-1"> <div class="grid grid-cols-2 gap-1">
<button <button
type="button" type="button"
onclick={() => handle_test_action('Primary')} onclick={() => handle_test_action('Primary')}
class="btn btn-xs preset-tonal-primary hover:preset-filled-primary-500 justify-start" class="btn btn-xs preset-tonal-primary hover:preset-filled-primary-500 justify-start">
>
<Zap size="0.85em" class="mr-1 shrink-0" /> Primary <Zap size="0.85em" class="mr-1 shrink-0" /> Primary
</button> </button>
<button <button
type="button" type="button"
onclick={() => handle_test_action('Secondary')} onclick={() => handle_test_action('Secondary')}
class="btn btn-xs preset-tonal-secondary hover:preset-filled-secondary-500 justify-start" class="btn btn-xs preset-tonal-secondary hover:preset-filled-secondary-500 justify-start">
>
<Settings size="0.85em" class="mr-1 shrink-0" /> Secondary <Settings size="0.85em" class="mr-1 shrink-0" /> Secondary
</button> </button>
</div> </div>
<!-- Toggles & Checkboxes --> <!-- Toggles & Checkboxes -->
<div class="flex flex-col gap-1 mt-1 bg-surface-500/5 p-2 rounded"> <div class="bg-surface-500/5 mt-1 flex flex-col gap-1 rounded p-2">
<label class="flex items-center gap-2 cursor-pointer group"> <label class="group flex cursor-pointer items-center gap-2">
<input <input
type="checkbox" type="checkbox"
bind:checked={toggle_val} bind:checked={toggle_val}
class="checkbox checkbox-sm" class="checkbox checkbox-sm" />
/>
<span <span
class="text-xs group-hover:text-primary-500 transition-colors" class="group-hover:text-primary-500 text-xs transition-colors"
>Toggle Feature Alpha</span >Toggle Feature Alpha</span>
>
</label> </label>
<label class="flex items-center gap-2 cursor-pointer group"> <label class="group flex cursor-pointer items-center gap-2">
<input <input
type="radio" type="radio"
name="demo" name="demo"
value="a" value="a"
class="radio radio-sm" class="radio radio-sm" />
/>
<span <span
class="text-xs group-hover:text-primary-500 transition-colors" class="group-hover:text-primary-500 text-xs transition-colors"
>Mode A</span >Mode A</span>
>
</label> </label>
</div> </div>
</div> </div>
<!-- C. STATUS & GAUGES SECTION --> <!-- C. STATUS & GAUGES SECTION -->
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<p class="text-[9px] font-bold uppercase opacity-50 ml-1" <p class="ml-1 text-[9px] font-bold uppercase opacity-50">
>Current Status</p Current Status
> </p>
<div <div
class="flex flex-col gap-2 p-2 border border-surface-500/10 rounded-lg" class="border-surface-500/10 flex flex-col gap-2 rounded-lg border p-2">
> <div class="flex items-center justify-between">
<div class="flex justify-between items-center">
<span class="text-[10px] font-medium">Engine Health</span> <span class="text-[10px] font-medium">Engine Health</span>
<span class="badge preset-filled-success text-[8px] uppercase" <span class="badge preset-filled-success text-[8px] uppercase"
>Stable</span >Stable</span>
>
</div> </div>
<!-- Progress / Gauge Example --> <!-- Progress / Gauge Example -->
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<div <div
class="flex justify-between text-[8px] uppercase opacity-60" class="flex justify-between text-[8px] uppercase opacity-60">
>
<span>Processing Load</span> <span>Processing Load</span>
<span>45%</span> <span>45%</span>
</div> </div>
<div <div
class="w-full h-1.5 bg-surface-500/20 rounded-full overflow-hidden" class="bg-surface-500/20 h-1.5 w-full overflow-hidden rounded-full">
>
<div <div
class="h-full bg-success-500 transition-all duration-1000" class="bg-success-500 h-full transition-all duration-1000"
style="width: 45%" style="width: 45%">
></div> </div>
</div> </div>
</div> </div>
</div> </div>
@@ -144,9 +139,10 @@
<button <button
type="button" type="button"
onclick={() => handle_test_action('Refresh')} onclick={() => handle_test_action('Refresh')}
class="btn btn-xs preset-outlined-surface-500 w-full text-[10px]" class="btn btn-xs preset-outlined-surface-500 w-full text-[10px]">
> <RefreshCw
<RefreshCw size="0.85em" class="mr-1 {is_loading ? 'animate-spin' : ''}" /> size="0.85em"
class="mr-1 {is_loading ? 'animate-spin' : ''}" />
Refresh State Refresh State
</button> </button>
</div> </div>
@@ -154,32 +150,28 @@
<!-- D. TECHNICAL SECTION (Edit Mode Only) --> <!-- D. TECHNICAL SECTION (Edit Mode Only) -->
{#if $ae_loc.edit_mode} {#if $ae_loc.edit_mode}
<div <div
class="col-span-full border-t border-surface-500/20 pt-3 mt-1 flex flex-col gap-3" class="border-surface-500/20 col-span-full mt-1 flex flex-col gap-3 border-t pt-3">
>
<!-- Dangerous Actions --> <!-- Dangerous Actions -->
<div class="grid grid-cols-2 gap-2"> <div class="grid grid-cols-2 gap-2">
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<span <span
class="text-[9px] font-bold uppercase opacity-50 text-warning-500" class="text-warning-500 text-[9px] font-bold uppercase opacity-50"
>System Config</span >System Config</span>
>
<button <button
type="button" type="button"
onclick={() => (show_confirm = true)} onclick={() => (show_confirm = true)}
class="btn btn-xs preset-tonal-warning hover:preset-filled-warning-500 justify-start" class="btn btn-xs preset-tonal-warning hover:preset-filled-warning-500 justify-start">
> <AlertTriangle size="0.85em" class="mr-1 shrink-0" /> Reset
<AlertTriangle size="0.85em" class="mr-1 shrink-0" /> Reset All All
</button> </button>
</div> </div>
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<span <span
class="text-[9px] font-bold uppercase opacity-50 text-error-500" class="text-error-500 text-[9px] font-bold uppercase opacity-50"
>Danger Zone</span >Danger Zone</span>
>
<button <button
type="button" type="button"
class="btn btn-xs preset-tonal-error hover:preset-filled-error-500 justify-start" class="btn btn-xs preset-tonal-error hover:preset-filled-error-500 justify-start">
>
<Trash2 size="0.85em" class="mr-1 shrink-0" /> Wipe Cache <Trash2 size="0.85em" class="mr-1 shrink-0" /> Wipe Cache
</button> </button>
</div> </div>
@@ -188,21 +180,17 @@
<!-- Form Inputs --> <!-- Form Inputs -->
<div class="grid grid-cols-1 gap-2"> <div class="grid grid-cols-1 gap-2">
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<span <span class="ml-1 text-[9px] font-bold uppercase opacity-50"
class="text-[9px] font-bold uppercase opacity-50 ml-1" >Raw Settings</span>
>Raw Settings</span
>
<div class="flex gap-1"> <div class="flex gap-1">
<input <input
type="text" type="text"
bind:value={text_input} bind:value={text_input}
placeholder="Enter string parameter..." placeholder="Enter string parameter..."
class="input input-sm grow text-[10px] preset-tonal-surface h-7" class="input input-sm preset-tonal-surface h-7 grow text-[10px]" />
/>
<select <select
bind:value={select_val} bind:value={select_val}
class="select select-sm h-7 py-0 text-[10px] w-24 preset-tonal-surface" class="select select-sm preset-tonal-surface h-7 w-24 py-0 text-[10px]">
>
<option value="option1">Global</option> <option value="option1">Global</option>
<option value="option2">Local</option> <option value="option2">Local</option>
</select> </select>
@@ -210,24 +198,22 @@
</div> </div>
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<span class="text-[8px] opacity-60 ml-1" <span class="ml-1 text-[8px] opacity-60"
>Threshold (ms)</span >Threshold (ms)</span>
>
<input <input
type="number" type="number"
bind:value={number_input} bind:value={number_input}
class="input input-sm text-[10px] h-7 preset-tonal-surface" class="input input-sm preset-tonal-surface h-7 text-[10px]" />
/>
</div> </div>
</div> </div>
<!-- Terminal / Output Log --> <!-- Terminal / Output Log -->
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<p class="text-[9px] font-bold uppercase opacity-50 ml-1" <p class="ml-1 text-[9px] font-bold uppercase opacity-50">
>Debug Output</p Debug Output
> </p>
<pre <pre
class="text-[8px] bg-black text-green-500 p-2 overflow-x-auto rounded border border-surface-500/50 max-h-24 shadow-inner"> class="border-surface-500/50 max-h-24 overflow-x-auto rounded border bg-black p-2 text-[8px] text-green-500 shadow-inner">
[LOG] System Initialized [LOG] System Initialized
[INFO] Store synced with IndexedDB [INFO] Store synced with IndexedDB
[DEBUG] active_tab: template [DEBUG] active_tab: template
@@ -241,13 +227,11 @@
<!-- Confirmation Modal Demo --> <!-- Confirmation Modal Demo -->
{#if show_confirm} {#if show_confirm}
<div <div
class="fixed inset-0 z-[1000] flex items-center justify-center bg-black/50 backdrop-blur-sm p-4" class="fixed inset-0 z-[1000] flex items-center justify-center bg-black/50 p-4 backdrop-blur-sm">
>
<div <div
class="card p-6 w-full max-w-sm preset-filled-surface-100-900 border border-warning-500 shadow-2xl animate-in zoom-in-95 duration-200" class="card preset-filled-surface-100-900 border-warning-500 animate-in zoom-in-95 w-full max-w-sm border p-6 shadow-2xl duration-200">
> <h4 class="h4 text-warning-500 mb-2 font-bold">Confirm Action</h4>
<h4 class="h4 text-warning-500 font-bold mb-2">Confirm Action</h4> <p class="mb-6 text-sm opacity-80">
<p class="text-sm opacity-80 mb-6">
Are you sure you want to perform this test operation? This Are you sure you want to perform this test operation? This
demonstrate the standard confirmation pattern. demonstrate the standard confirmation pattern.
</p> </p>
@@ -255,16 +239,14 @@
<button <button
type="button" type="button"
onclick={() => (show_confirm = false)} onclick={() => (show_confirm = false)}
class="btn btn-sm preset-tonal-surface">Cancel</button class="btn btn-sm preset-tonal-surface">Cancel</button>
>
<button <button
type="button" type="button"
onclick={() => { onclick={() => {
show_confirm = false; show_confirm = false;
handle_test_action('Confirm'); handle_test_action('Confirm');
}} }}
class="btn btn-sm preset-filled-warning" class="btn btn-sm preset-filled-warning">
>
Yes, Proceed Yes, Proceed
</button> </button>
</div> </div>

View File

@@ -13,9 +13,7 @@
let update_path = $state( let update_path = $state(
'~/OSIT/Speaker Ready System/Admin Share/Custom Applications/osit_binaries/' '~/OSIT/Speaker Ready System/Admin Share/Custom Applications/osit_binaries/'
); );
let update_url = $state( let update_url = $state('https://dev-demo.oneskyit.com/updates/ae_native.zip');
'https://dev-demo.oneskyit.com/updates/ae_native.zip'
);
let update_status = $state(''); let update_status = $state('');
let is_checking = $state(false); let is_checking = $state(false);
@@ -59,35 +57,31 @@
icon={CloudDownload} icon={CloudDownload}
bind:state={$events_loc.launcher.section_state__updates} bind:state={$events_loc.launcher.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 -->
<div class="col-span-full flex flex-col gap-2"> <div class="col-span-full flex flex-col gap-2">
<!-- TECHNICAL: Source Config (Edit Mode Only) --> <!-- TECHNICAL: Source Config (Edit Mode Only) -->
{#if $ae_loc.edit_mode} {#if $ae_loc.edit_mode}
<div <div
class="flex flex-col gap-2 bg-surface-500/5 p-2 rounded border border-surface-500/10 mb-1" class="bg-surface-500/5 border-surface-500/10 mb-1 flex flex-col gap-2 rounded border p-2">
> <div class="flex flex-row items-center justify-between px-1">
<div class="flex flex-row justify-between items-center px-1"> <p class="text-[9px] font-bold uppercase opacity-50">
<p class="text-[9px] font-bold uppercase opacity-50" Source Type
>Source Type</p </p>
>
<div class="flex gap-2"> <div class="flex gap-2">
<label class="flex items-center gap-1 text-[10px]"> <label class="flex items-center gap-1 text-[10px]">
<input <input
type="radio" type="radio"
bind:group={update_source} bind:group={update_source}
value="file" value="file"
class="radio radio-sm" class="radio radio-sm" /> Local
/> Local
</label> </label>
<label class="flex items-center gap-1 text-[10px]"> <label class="flex items-center gap-1 text-[10px]">
<input <input
type="radio" type="radio"
bind:group={update_source} bind:group={update_source}
value="url" value="url"
class="radio radio-sm" class="radio radio-sm" /> Web
/> Web
</label> </label>
</div> </div>
</div> </div>
@@ -97,15 +91,13 @@
type="text" type="text"
bind:value={update_path} bind:value={update_path}
placeholder="Path to update package" placeholder="Path to update package"
class="input input-sm text-[10px] preset-tonal-surface h-7 w-full" class="input input-sm preset-tonal-surface h-7 w-full text-[10px]" />
/>
{:else} {:else}
<input <input
type="text" type="text"
bind:value={update_url} bind:value={update_url}
placeholder="URL to update package" placeholder="URL to update package"
class="input input-sm text-[10px] preset-tonal-surface h-7 w-full" class="input input-sm preset-tonal-surface h-7 w-full text-[10px]" />
/>
{/if} {/if}
</div> </div>
{/if} {/if}
@@ -115,10 +107,9 @@
type="button" type="button"
onclick={handle_check_update} onclick={handle_check_update}
disabled={is_checking} disabled={is_checking}
class="btn btn-sm preset-filled-tertiary hover:preset-filled-primary-500 text-[10px] w-full" class="btn btn-sm preset-filled-tertiary hover:preset-filled-primary-500 w-full text-[10px]">
>
{#if is_checking} {#if is_checking}
<LoaderCircle size="0.85em" class="animate-spin mr-1" /> Checking... <LoaderCircle size="0.85em" class="mr-1 animate-spin" /> Checking...
{:else} {:else}
<Search size="0.85em" class="mr-1" /> Check for Updates <Search size="0.85em" class="mr-1" /> Check for Updates
{/if} {/if}
@@ -126,8 +117,7 @@
{#if update_status} {#if update_status}
<div <div
class="text-[9px] text-center italic p-1 border border-surface-500/20 rounded bg-surface-500/5 mt-1" class="border-surface-500/20 bg-surface-500/5 mt-1 rounded border p-1 text-center text-[9px] italic">
>
{update_status} {update_status}
</div> </div>
{/if} {/if}
@@ -136,8 +126,7 @@
<button <button
type="button" type="button"
onclick={handle_install} onclick={handle_install}
class="btn btn-sm preset-filled-success hover:preset-filled-primary-500 text-[10px] w-full animate-bounce mt-2 shadow-lg" class="btn btn-sm preset-filled-success hover:preset-filled-primary-500 mt-2 w-full animate-bounce text-[10px] shadow-lg">
>
<Wand2 size="0.85em" class="mr-1" /> Install & Relaunch <Wand2 size="0.85em" class="mr-1" /> Install & Relaunch
</button> </button>
{/if} {/if}

View File

@@ -132,7 +132,9 @@
const param_launcher_footer = data.url.searchParams.get('launcher_footer'); const param_launcher_footer = data.url.searchParams.get('launcher_footer');
if (log_lvl > 1) { if (log_lvl > 1) {
console.log(`[Launcher Sync] URL Change: event=${path_event_id}, loc=${path_location_id}, sess=${url_session_id}`); console.log(
`[Launcher Sync] URL Change: event=${path_event_id}, loc=${path_location_id}, sess=${url_session_id}`
);
} }
untrack(() => { untrack(() => {
@@ -144,7 +146,10 @@
} }
// CRITICAL: Ensure session_id is synced to store so LiveQueries react // CRITICAL: Ensure session_id is synced to store so LiveQueries react
if ($events_slct.event_session_id !== url_session_id) { if ($events_slct.event_session_id !== url_session_id) {
if (log_lvl) console.log(`[Launcher Sync] Updating store session_id: ${url_session_id}`); if (log_lvl)
console.log(
`[Launcher Sync] Updating store session_id: ${url_session_id}`
);
$events_slct.event_session_id = url_session_id; $events_slct.event_session_id = url_session_id;
} }
@@ -152,14 +157,20 @@
if (param_iframe === 'true') $ae_loc.iframe = true; if (param_iframe === 'true') $ae_loc.iframe = true;
else if (param_iframe === 'false') $ae_loc.iframe = false; else if (param_iframe === 'false') $ae_loc.iframe = false;
if (param_launcher_menu === 'hide') $events_loc.launcher.hide__launcher_menu = true; if (param_launcher_menu === 'hide')
else if (param_launcher_menu === 'show') $events_loc.launcher.hide__launcher_menu = false; $events_loc.launcher.hide__launcher_menu = true;
else if (param_launcher_menu === 'show')
$events_loc.launcher.hide__launcher_menu = false;
if (param_launcher_header === 'hide') $events_loc.launcher.hide__launcher_header = true; if (param_launcher_header === 'hide')
else if (param_launcher_header === 'show') $events_loc.launcher.hide__launcher_header = false; $events_loc.launcher.hide__launcher_header = true;
else if (param_launcher_header === 'show')
$events_loc.launcher.hide__launcher_header = false;
if (param_launcher_footer === 'hide') $events_loc.launcher.hide__launcher_footer = true; if (param_launcher_footer === 'hide')
else if (param_launcher_footer === 'show') $events_loc.launcher.hide__launcher_footer = false; $events_loc.launcher.hide__launcher_footer = true;
else if (param_launcher_footer === 'show')
$events_loc.launcher.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
@@ -196,7 +207,9 @@
$effect(() => { $effect(() => {
if (ae_acct) { if (ae_acct) {
untrack(() => { untrack(() => {
const new_location_obj_li = ae_acct.slct.event_location_obj_li ?? ['']; const new_location_obj_li = ae_acct.slct.event_location_obj_li ?? [
''
];
// Compare by extracting IDs only — object identity (===) won't work for // Compare by extracting IDs only — object identity (===) won't work for
// plain JS objects from the store. Joining IDs is cheap and avoids a full // plain JS objects from the store. Joining IDs is cheap and avoids a full
// JSON.stringify of potentially large location objects on every navigation. // JSON.stringify of potentially large location objects on every navigation.
@@ -210,9 +223,13 @@
$events_slct.event_location_obj_li = new_location_obj_li; $events_slct.event_location_obj_li = new_location_obj_li;
} }
const new_id_li__event_location = ae_acct.slct.id_li__event_location ?? ['']; const new_id_li__event_location = ae_acct.slct
.id_li__event_location ?? [''];
// ID list contains plain strings — join-compare is O(n) and avoids JSON.stringify. // ID list contains plain strings — join-compare is O(n) and avoids JSON.stringify.
if (($events_slct.id_li__event_location ?? []).join(',') !== new_id_li__event_location.join(',')) { if (
($events_slct.id_li__event_location ?? []).join(',') !==
new_id_li__event_location.join(',')
) {
$events_slct.id_li__event_location = new_id_li__event_location; $events_slct.id_li__event_location = new_id_li__event_location;
} }
}); });
@@ -240,10 +257,7 @@
let lq__event_event_file_obj_li = liveQuery(async () => { let lq__event_event_file_obj_li = liveQuery(async () => {
const id = $events_slct.event_id; const id = $events_slct.event_id;
if (!id) return []; if (!id) return [];
return await db_events.file return await db_events.file.where('for_id').equals(id).sortBy('filename');
.where('for_id')
.equals(id)
.sortBy('filename');
}); });
// Event File - For Location // Event File - For Location
@@ -274,10 +288,7 @@
let lq__event_location_obj_li = liveQuery(async () => { let lq__event_location_obj_li = liveQuery(async () => {
const id = $events_slct.event_id; const id = $events_slct.event_id;
if (!id) return []; if (!id) return [];
return await db_events.location return await db_events.location.where('event_id').equals(id).sortBy('name');
.where('event_id')
.equals(id)
.sortBy('name');
}); });
// $derived.by: must recreate when event_location_id changes (see comment above). // $derived.by: must recreate when event_location_id changes (see comment above).
@@ -311,8 +322,10 @@
const result = $lq__event_obj; const result = $lq__event_obj;
if (result) { if (result) {
untrack(() => { untrack(() => {
if (result.updated_on !== $events_slct.event_obj?.updated_on || if (
result.id !== $events_slct.event_obj?.id) { result.updated_on !== $events_slct.event_obj?.updated_on ||
result.id !== $events_slct.event_obj?.id
) {
$events_slct.event_obj = { ...result }; $events_slct.event_obj = { ...result };
} }
}); });
@@ -323,8 +336,11 @@
const result = $lq__event_device_obj; const result = $lq__event_device_obj;
if (result) { if (result) {
untrack(() => { untrack(() => {
if (result.updated_on !== $events_slct.event_device_obj?.updated_on || if (
result.id !== $events_slct.event_device_obj?.id) { result.updated_on !==
$events_slct.event_device_obj?.updated_on ||
result.id !== $events_slct.event_device_obj?.id
) {
$events_slct.event_device_obj = { ...result }; $events_slct.event_device_obj = { ...result };
} }
}); });
@@ -337,8 +353,12 @@
untrack(() => { untrack(() => {
const current = $events_slct.event_session_obj_li ?? []; const current = $events_slct.event_session_obj_li ?? [];
// Compare by joining IDs — O(n) string compare vs O(n*m) JSON.stringify. // Compare by joining IDs — O(n) string compare vs O(n*m) JSON.stringify.
const new_ids = (results as any[]).map((r: any) => r.id ?? r.event_session_id).join(','); const new_ids = (results as any[])
const cur_ids = current.map((r: any) => r.id ?? r.event_session_id).join(','); .map((r: any) => r.id ?? r.event_session_id)
.join(',');
const cur_ids = current
.map((r: any) => r.id ?? r.event_session_id)
.join(',');
if (new_ids !== cur_ids) { if (new_ids !== cur_ids) {
$events_slct.event_session_obj_li = [...(results as any[])]; $events_slct.event_session_obj_li = [...(results as any[])];
} }
@@ -522,16 +542,13 @@
const keys = Object.keys( const keys = Object.keys(
$events_loc.launcher.screen_saver_img_kv $events_loc.launcher.screen_saver_img_kv
); );
const rand_index = Math.floor( const rand_index = Math.floor(Math.random() * keys.length);
Math.random() * keys.length
);
let event_file_obj = let event_file_obj =
$events_loc.launcher.screen_saver_img_kv[ $events_loc.launcher.screen_saver_img_kv[
keys[rand_index] keys[rand_index]
]; ];
$events_slct.event_file_id = $events_slct.event_file_id = event_file_obj.event_file_id;
event_file_obj.event_file_id;
$events_slct.event_file_obj = event_file_obj; $events_slct.event_file_obj = event_file_obj;
$events_sess.launcher.modal__open_event_file_id = null; $events_sess.launcher.modal__open_event_file_id = null;
$events_sess.launcher.modal__title = $events_sess.launcher.modal__title =
@@ -587,81 +604,74 @@
class=" class="
static static
m-auto m-auto
border-x border-gray-200 dark:border-gray-600 mb-16 h-full w-full
mb-16 sm:mb-12 max-w-7xl border-x
h-full border-gray-200
w-full max-w-7xl transition-all sm:mb-12
transition-all dark:border-gray-600
" ">
>
<header <header
id="Main-Header" id="Main-Header"
class:hidden={$events_loc.launcher.hide__launcher_header} class:hidden={$events_loc.launcher.hide__launcher_header}
class=" class="
z-20 absolute
absolute top-0 left-0 right-0 top-0 right-0 left-0 z-20
w-full max-w-7xl m-auto flex
h-12 h-12
p-1 px-12 m-auto w-full max-w-7xl flex-row
flex flex-row items-center justify-around sm:justify-between items-center justify-around bg-slate-200 p-1 px-12
text-sm text-sm
bg-slate-200 dark:bg-slate-800 opacity-95 transition-colors
opacity-95 hover:opacity-100 duration-300 hover:opacity-100
transition-colors duration-300 sm:justify-between dark:bg-slate-800
" ">
> <h3 class="h4 text-surface-600-400 text-center italic">
<h3 class="h4 text-center italic text-surface-600-400">
<!-- Menu toggle: needs a real tap target for tablet/touch operators --> <!-- Menu toggle: needs a real tap target for tablet/touch operators -->
<button <button
type="button" type="button"
class="px-2 py-1 rounded hover:bg-surface-500/10 transition-colors" class="hover:bg-surface-500/10 rounded px-2 py-1 transition-colors"
onclick={() => { onclick={() => {
$events_loc.launcher.hide__launcher_menu = $events_loc.launcher.hide__launcher_menu =
!$events_loc.launcher.hide__launcher_menu; !$events_loc.launcher.hide__launcher_menu;
}} }}
title="Toggle Launcher menu" title="Toggle Launcher menu">
> <Satellite class="mx-1 inline-block text-base text-gray-500" />
<Satellite class="text-base mx-1 inline-block text-gray-500" />
<abbr title="Aether - Events Module Launcher"> <abbr title="Aether - Events Module Launcher">
Æ Launcher Æ Launcher
<span <span
class="text-xs align-super font-normal" class="align-super text-xs font-normal"
title="Version 3">v3</span title="Version 3">v3</span>
>
</abbr> </abbr>
</button> </button>
</h3> </h3>
{#if $lq__event_obj} {#if $lq__event_obj}
<h2 <h2
class="hidden md:inline-block h3 text-center text-surface-600-400" class="h3 text-surface-600-400 hidden text-center md:inline-block">
>
{$lq__event_obj.cfg_json?.short_name} {$lq__event_obj.cfg_json?.short_name}
</h2> </h2>
<h3 <h3
class="h4 text-center italic text-surface-600-400" class="h4 text-surface-600-400 text-center italic"
title="Location ID: {$lq__event_location_obj?.event_location_id} Name: {$lq__event_location_obj?.name}" title="Location ID: {$lq__event_location_obj?.event_location_id} Name: {$lq__event_location_obj?.name}">
>
<button <button
type="button" type="button"
class="text-base" class="text-base"
onclick={() => { onclick={() => {
$ae_loc.edit_mode = !$ae_loc.edit_mode; $ae_loc.edit_mode = !$ae_loc.edit_mode;
}} }}
title="Toggle Edit Mode to show location options and more" title="Toggle Edit Mode to show location options and more">
>
<MapPin size="1em" /> <MapPin size="1em" />
<span class="sr-only">Location:</span> <span class="sr-only">Location:</span>
</button> </button>
{$lq__event_location_obj?.name} {$lq__event_location_obj?.name}
</h3> </h3>
{:else} {:else}
<div class="flex flex-row gap-1 items-center justify-center"> <div class="flex flex-row items-center justify-center gap-1">
<LoaderCircle size="1em" class="animate-spin mx-1" /> <LoaderCircle size="1em" class="mx-1 animate-spin" />
<span>Loading event...</span> <span>Loading event...</span>
</div> </div>
{/if} {/if}
@@ -669,30 +679,28 @@
<div <div
class=" class="
h-full min-w-full w-full max-w-full flex h-full w-full max-w-full
flex flex-col sm:flex-row flex-wrap sm:flex-nowrap gap-0 min-w-full flex-col flex-wrap items-center justify-start gap-0
items-center bg-gray-100
justify-start sm:justify-center px-0.5 py-1
py-1 px-0.5 sm:flex-row sm:flex-nowrap
bg-gray-100 dark:bg-gray-900 sm:justify-center dark:bg-gray-900
" ">
>
<section <section
id="Main-Nav-Menu" id="Main-Nav-Menu"
class="event_launcher_menu class="event_launcher_menu
flex
h-full h-full
basis-1/5 max-w-xs min-w-56 basis-1/5
min-w-56 md:min-w-64 lg:min-w-72 flex-col
max-w-xs items-center justify-start
pt-0.5 pr-0.5 gap-1 overflow-y-auto border-r border-gray-200 pt-0.5
flex flex-col gap-1 items-center justify-start pr-0.5
overflow-y-auto
border-r border-gray-200 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={$events_loc.launcher.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}
@@ -717,28 +725,25 @@
} }
bind:trigger_reload__event_location_obj_li={ bind:trigger_reload__event_location_obj_li={
$events_sess.launcher.trigger_reload__event_location_obj_li $events_sess.launcher.trigger_reload__event_location_obj_li
} }></Launcher_menu>
></Launcher_menu>
</section> </section>
<section <section
id="Main-Content" id="Main-Content"
class="event_launcher_main class="event_launcher_main
flex
h-full h-full
min-w-xs
max-w-full max-w-full
py-1 px-0.5 min-w-xs basis-4/5
basis-4/5 flex-col
flex flex-col gap-1 items-center justify-center gap-1
items-center
justify-center
overflow-y-auto overflow-y-auto
" px-0.5
> py-1
">
{#if !$events_slct.event_location_id} {#if !$events_slct.event_location_id}
<div <div
class="flex flex-row items-center justify-center p-8 opacity-50" class="flex flex-row items-center justify-center p-8 opacity-50">
>
<MapPin size="1.5em" class="mx-2" /> <MapPin size="1.5em" class="mx-2" />
<span>Please select a location from the menu</span> <span>Please select a location from the menu</span>
</div> </div>
@@ -751,9 +756,8 @@
{:else if $events_slct.event_location_id} {:else if $events_slct.event_location_id}
<!-- Location selected but no session chosen yet — prompt operator --> <!-- Location selected but no session chosen yet — prompt operator -->
<div <div
class="flex flex-col items-center justify-center p-8 opacity-50" class="flex flex-col items-center justify-center p-8 opacity-50">
> <LoaderCircle class="mb-2 animate-spin" />
<LoaderCircle class="animate-spin mb-2" />
<span>Select a session from the menu</span> <span>Select a session from the menu</span>
</div> </div>
{/if} {/if}
@@ -767,35 +771,32 @@
id="Main-Footer" id="Main-Footer"
class:hidden={$events_loc.launcher.hide__launcher_footer} class:hidden={$events_loc.launcher.hide__launcher_footer}
class=" class="
z-20 absolute
absolute bottom-0 left-0 right-0 right-0 bottom-0 left-0 z-20
m-auto flex
w-full max-w-7xl w-full max-w-7xl
p-1 m-auto
flex flex-row items-center justify-between flex-row items-center justify-between border-t
text-xs border-gray-300
bg-gray-200 border-t border-gray-300 bg-gray-200 p-1 text-xs
dark:bg-gray-800 dark:border-gray-600 opacity-70 transition-opacity
opacity-70 hover:opacity-100 duration-500 hover:opacity-100
transition-opacity duration-500 dark:border-gray-600 dark:bg-gray-800
" ">
>
<div <div
class="slct_location_name transition-colors duration-300" class="slct_location_name transition-colors duration-300"
title="Location ID: {$lq__event_location_obj?.event_location_id} Name: {$lq__event_location_obj?.name} | Device ID: {$lq__event_device_obj?.event_device_id} Name: {$lq__event_device_obj?.name}" title="Location ID: {$lq__event_location_obj?.event_location_id} Name: {$lq__event_location_obj?.name} | Device ID: {$lq__event_device_obj?.event_device_id} Name: {$lq__event_device_obj?.name}">
>
<!-- Edit mode toggle: needs tap target for tablet operators --> <!-- Edit mode toggle: needs tap target for tablet operators -->
<button <button
type="button" type="button"
class="px-1.5 py-1 rounded hover:bg-surface-500/10 transition-colors" class="hover:bg-surface-500/10 rounded px-1.5 py-1 transition-colors"
onclick={() => { onclick={() => {
$ae_loc.edit_mode = !$ae_loc.edit_mode; $ae_loc.edit_mode = !$ae_loc.edit_mode;
}} }}
title="Toggle Edit Mode to show location options and more" title="Toggle Edit Mode to show location options and more">
>
<span class="sr-only">Location:</span> <span class="sr-only">Location:</span>
<MapPin size="1em" /> <MapPin size="1em" />
</button> </button>
@@ -809,9 +810,8 @@
<span <span
class:preset-tonal-warning={!$idle} class:preset-tonal-warning={!$idle}
class:preset-tonal-success={$idle} class:preset-tonal-success={$idle}
class="group px-2 py-0.5 rounded-md transition-colors duration-300" class="group rounded-md px-2 py-0.5 transition-colors duration-300"
title="The user is currently {$idle ? 'idle' : 'active'}" title="The user is currently {$idle ? 'idle' : 'active'}">
>
{#if $idle} {#if $idle}
<BedDouble size="1em" class="mx-1" /> <BedDouble size="1em" class="mx-1" />
<span class="hidden group-hover:inline"> Idle </span> <span class="hidden group-hover:inline"> Idle </span>
@@ -822,9 +822,8 @@
</span> </span>
<span <span
class="group px-2 py-0.5 rounded-md transition-colors duration-300" class="group rounded-md px-2 py-0.5 transition-colors duration-300"
title="Online status = {online?.current}" title="Online status = {online?.current}">
>
<Wifi size="1em" class="mx-1" /> <Wifi size="1em" class="mx-1" />
{online?.current ? '' : 'Offline!'} {online?.current ? '' : 'Offline!'}
</span> </span>
@@ -835,12 +834,11 @@
'connected'} 'connected'}
class:preset-tonal-success={$events_sess.launcher.ws_connect_status == class:preset-tonal-success={$events_sess.launcher.ws_connect_status ==
'connected'} 'connected'}
class="group px-2 py-0.5 rounded-md transition-colors duration-300" class="group rounded-md px-2 py-0.5 transition-colors duration-300"
title="WebSocket is {$events_sess.launcher.ws_connect_status == title="WebSocket is {$events_sess.launcher.ws_connect_status ==
'connected' 'connected'
? 'connected' ? 'connected'
: 'disconnected'} API: {$ae_api?.base_url}" : 'disconnected'} API: {$ae_api?.base_url}">
>
{#if $events_sess.launcher.ws_connect_status == 'connected'} {#if $events_sess.launcher.ws_connect_status == 'connected'}
<Network size="1em" class="mx-1 text-green-700" /> <Network size="1em" class="mx-1 text-green-700" />
<span class="hidden group-hover:inline"> WS Connected </span> <span class="hidden group-hover:inline"> WS Connected </span>
@@ -860,9 +858,9 @@
else if (mode === 'larger') $ae_loc.font_size_mode = 'smaller'; else if (mode === 'larger') $ae_loc.font_size_mode = 'smaller';
else $ae_loc.font_size_mode = 'default'; else $ae_loc.font_size_mode = 'default';
}} }}
class="group px-2 py-0.5 rounded-md font-mono font-bold hover:bg-surface-500/10 transition-colors duration-200" class="group hover:bg-surface-500/10 rounded-md px-2 py-0.5 font-mono font-bold transition-colors duration-200"
title="Font size: {$ae_loc.font_size_mode ?? 'default'} — tap to cycle (default → larger → smaller)" title="Font size: {$ae_loc.font_size_mode ??
> 'default'} — tap to cycle (default → larger → smaller)">
{#if $ae_loc.font_size_mode === 'larger'} {#if $ae_loc.font_size_mode === 'larger'}
<span>A+</span> <span>A+</span>
{:else if $ae_loc.font_size_mode === 'smaller'} {:else if $ae_loc.font_size_mode === 'smaller'}
@@ -873,8 +871,7 @@
</button> </button>
<div <div
class="current_datetime font-mono px-2 hover:font-bold hover:bg-white dark:hover:bg-slate-700 transition-colors" class="current_datetime px-2 font-mono transition-colors hover:bg-white hover:font-bold dark:hover:bg-slate-700">
>
<span class="hidden md:inline"> <span class="hidden md:inline">
<CalendarDays size="1em" /> <CalendarDays size="1em" />
{ae_util.iso_datetime_formatter($time, 'date_full_no_year')} {ae_util.iso_datetime_formatter($time, 'date_full_no_year')}
@@ -894,10 +891,9 @@
<button <button
type="button" type="button"
onclick={() => ($events_loc.launcher.hide_drawer__cfg = false)} onclick={() => ($events_loc.launcher.hide_drawer__cfg = false)}
class="btn btn-sm p-3 preset-tonal-error hover:preset-filled-error-500 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}>
>
<Biohazard size="1em" /> <Biohazard size="1em" />
<span class="hidden">Launcher Config</span> <span class="hidden">Launcher Config</span>
</button> </button>
@@ -906,7 +902,7 @@
<Drawer <Drawer
dismissable={false} dismissable={false}
onclick={() => ($events_loc.launcher.hide_drawer__cfg = true)} onclick={() => ($events_loc.launcher.hide_drawer__cfg = true)}
class="bg-orange-50 dark:bg-slate-800 opacity-90 hover:opacity-97 transition-all duration-300 border border-gray-300 dark:border-gray-600 w-full md:w-96 lg:w-[32rem]" class="w-full border border-gray-300 bg-orange-50 opacity-90 transition-all duration-300 hover:opacity-97 md:w-96 lg:w-[32rem] dark:border-gray-600 dark:bg-slate-800"
placement="left" placement="left"
{...{ {...{
transitionType: 'fly', transitionType: 'fly',
@@ -917,8 +913,7 @@
} }
}} }}
bind:hidden={$events_loc.launcher.hide_drawer__cfg} bind:hidden={$events_loc.launcher.hide_drawer__cfg}
id="sidebar1" id="sidebar1">
>
<!-- Stop-propagation wrapper: prevents clicks inside the visual panel from <!-- Stop-propagation wrapper: prevents clicks inside the visual panel from
bubbling up to the <dialog> element. The onclick on the <Drawer> above bubbling up to the <dialog> element. The onclick on the <Drawer> above
is spread through to the native <dialog> by Flowbite, overriding its is spread through to the native <dialog> by Flowbite, overriding its
@@ -931,20 +926,17 @@
<hr class="my-2 border-gray-300 dark:border-gray-600" /> <hr class="my-2 border-gray-300 dark:border-gray-600" />
<div <div
class="flex flex-row flex-wrap gap-0.5 items-center justify-center max-w-md" class="flex max-w-md flex-row flex-wrap items-center justify-center gap-0.5">
>
<a <a
href="/events/{$events_slct.event_id}" href="/events/{$events_slct.event_id}"
class="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500" class="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500">
>
<Search size="1em" class="m-1" /> <Search size="1em" class="m-1" />
Session Search Session Search
</a> </a>
{#if $events_slct?.event_location_id} {#if $events_slct?.event_location_id}
<a <a
href="/events/{$events_slct.event_id}/location/{$events_slct.event_location_id}" href="/events/{$events_slct.event_id}/location/{$events_slct.event_location_id}"
class="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500" class="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500">
>
<MapPin size="1em" class="m-1" /> <MapPin size="1em" class="m-1" />
View Selected Location View Selected Location
</a> </a>
@@ -952,8 +944,7 @@
{#if $events_slct?.event_session_id} {#if $events_slct?.event_session_id}
<a <a
href="/events/{$events_slct.event_id}/session/{$events_slct.event_session_id}" href="/events/{$events_slct.event_id}/session/{$events_slct.event_session_id}"
class="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500" class="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500">
>
<GraduationCap size="1em" class="m-1" /> <GraduationCap size="1em" class="m-1" />
View Selected Session View Selected Session
</a> </a>
@@ -964,7 +955,7 @@
<Drawer <Drawer
activateClickOutside={false} activateClickOutside={false}
class="bg-red-50 dark:bg-slate-900 opacity-75 hover:opacity-95 transition-all duration-300" class="bg-red-50 opacity-75 transition-all duration-300 hover:opacity-95 dark:bg-slate-900"
placement="bottom" placement="bottom"
{...{ {...{
transitionType: 'fly', transitionType: 'fly',
@@ -975,19 +966,16 @@
} }
}} }}
bind:hidden={$events_loc.launcher.hide_drawer__debug} bind:hidden={$events_loc.launcher.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
class="text-center mb-4 text-base font-semibold text-gray-500 dark:text-gray-400" class="mb-4 text-center text-base font-semibold text-gray-500 dark:text-gray-400">
>
Debug Debug
</h2> </h2>
<button <button
type="button" type="button"
onclick={() => ($events_loc.launcher.hide_drawer__debug = true)} onclick={() => ($events_loc.launcher.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>
</button> </button>
@@ -1009,33 +997,30 @@
autoclose={false} autoclose={false}
placement="top-center" placement="top-center"
class=" class="
bg-gray-500/90 dark:bg-gray-800/90 text-gray-800 dark:text-gray-200 relative flex flex-col items-center
rounded-lg border-gray-200 dark:border-gray-700 justify-center divide-y divide-gray-200
divide-y divide-gray-200 dark:divide-gray-700 shadow-md rounded-lg border-gray-200 bg-gray-500/90 text-gray-800
relative shadow-md
flex flex-col items-center justify-center 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' : ''} {$events_loc.launcher.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 ${$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'}`}
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={$events_loc.launcher.hide__modal_header_title}
class="text-lg font-semibold opacity-20 hover:opacity-100 transition-all" 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>
<button <button
type="button" type="button"
class="btn flex-row-reverse group transition-all justify-self-end" class="btn group flex-row-reverse justify-self-end transition-all"
onclick={() => { onclick={() => {
$events_sess.launcher.modal__open_event_file_id = null; $events_sess.launcher.modal__open_event_file_id = null;
}} }}
title="Close Modal" title="Close Modal">
>
<X size="1em" class="my-1.5" /> <X size="1em" class="my-1.5" />
<span class="hidden group-hover:inline"> Close</span> <span class="hidden group-hover:inline"> Close</span>
</button> </button>
@@ -1047,25 +1032,28 @@
and 'natural size' mode where the operator can pan/scroll and pinch-zoom freely and 'natural size' mode where the operator can pan/scroll and pinch-zoom freely
for closer inspection or accessibility accommodation. --> for closer inspection or accessibility accommodation. -->
<div <div
class="w-full flex-1 flex items-center justify-center" class="flex w-full flex-1 items-center justify-center"
class:overflow-auto={!modal_zoom_fit} class:overflow-auto={!modal_zoom_fit}
class:overflow-hidden={modal_zoom_fit} class:overflow-hidden={modal_zoom_fit}>
>
{#if $events_sess.launcher.modal__event_file_obj?.hosted_file_id} {#if $events_sess.launcher.modal__event_file_obj?.hosted_file_id}
<!-- WHY: Use hosted_file endpoint (not event_file) — the event_file download <!-- WHY: Use hosted_file endpoint (not event_file) — the event_file download
endpoint requires auth headers that a plain <img> tag cannot send (→ 403). endpoint requires auth headers that a plain <img> tag cannot send (→ 403).
The hosted_file endpoint accepts key=account_id as a query param and is The hosted_file endpoint accepts key=account_id as a query param and is
the proven browser-compatible path for direct file display. --> the proven browser-compatible path for direct file display. -->
<img <img
src="{$ae_api.base_url}/v3/action/hosted_file/{$events_sess.launcher src="{$ae_api.base_url}/v3/action/hosted_file/{$events_sess
.modal__event_file_obj.hosted_file_id}/download?return_file=true&filename={encodeURIComponent( .launcher.modal__event_file_obj
.hosted_file_id}/download?return_file=true&filename={encodeURIComponent(
$events_sess.launcher.modal__event_file_obj.filename ?? '' $events_sess.launcher.modal__event_file_obj.filename ?? ''
)}&key={$ae_api.account_id}" )}&key={$ae_api.account_id}"
alt="Poster: {$events_sess.launcher.modal__title}" alt="Poster: {$events_sess.launcher.modal__title}"
ondblclick={() => { ondblclick={() => {
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 ($events_loc.launcher.controller == 'local_push' && $events_sess.launcher.ws_connect_status == 'connected') { if (
$events_loc.launcher.controller == 'local_push' &&
$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'}`;
$events_sess.launcher.controller_trigger_send = true; $events_sess.launcher.controller_trigger_send = true;
} }
@@ -1078,8 +1066,7 @@
class:object-contain={modal_zoom_fit} class:object-contain={modal_zoom_fit}
class:cursor-zoom-in={modal_zoom_fit} class:cursor-zoom-in={modal_zoom_fit}
class:cursor-zoom-out={!modal_zoom_fit} class:cursor-zoom-out={!modal_zoom_fit}
style="touch-action: pinch-zoom;" style="touch-action: pinch-zoom;" />
/>
{:else} {:else}
<div class="flex flex-row items-center justify-center p-4"> <div class="flex flex-row items-center justify-center p-4">
<Info size="1em" class="mx-1" /> <Info size="1em" class="mx-1" />
@@ -1092,13 +1079,12 @@
<!-- WHY: pb-14 on bodyClass reserves space so these buttons don't obscure poster content. --> <!-- WHY: pb-14 on bodyClass reserves space so these buttons don't obscure poster content. -->
<div <div
class=" class="
absolute bottom-0 left-0 right-0 absolute right-0 bottom-0 left-0
flex flex-row items-center justify-between gap-2 flex flex-row items-center justify-between gap-2
p-1.5 bg-black/30
bg-black/30 backdrop-blur-sm p-1.5 backdrop-blur-sm
" "
class:hidden={$events_loc.launcher.controller == 'remote'} class:hidden={$events_loc.launcher.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
@@ -1106,16 +1092,18 @@
onclick={() => { onclick={() => {
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 ($events_loc.launcher.controller == 'local_push' && $events_sess.launcher.ws_connect_status == 'connected') { if (
$events_loc.launcher.controller == 'local_push' &&
$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'}`;
$events_sess.launcher.controller_trigger_send = true; $events_sess.launcher.controller_trigger_send = true;
} }
}} }}
class="btn btn-sm preset-tonal-surface opacity-80 hover:opacity-100 transition-opacity" class="btn btn-sm preset-tonal-surface opacity-80 transition-opacity hover:opacity-100"
title={modal_zoom_fit title={modal_zoom_fit
? 'Pan / Zoom mode — pinch or double-tap image to zoom' ? 'Pan / Zoom mode — pinch or double-tap image to zoom'
: 'Fit image to screen'} : 'Fit image to screen'}>
>
{#if modal_zoom_fit} {#if modal_zoom_fit}
<ZoomIn size="1em" class="mr-1" /> <ZoomIn size="1em" class="mr-1" />
<span class="hidden sm:inline">Zoom</span> <span class="hidden sm:inline">Zoom</span>
@@ -1136,14 +1124,13 @@
$events_sess.launcher.modal__open_event_file_id = null; $events_sess.launcher.modal__open_event_file_id = null;
$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 hover:opacity-100 transition-all" class="btn btn-sm preset-tonal-error opacity-80 transition-all hover:opacity-100"
class:hidden={$events_loc.launcher.controller != 'local_push' || class:hidden={$events_loc.launcher.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" />
<X size="1em" /> <X size="1em" />
<span class="hidden sm:inline ml-1">Close Both</span> <span class="ml-1 hidden sm:inline">Close Both</span>
</button> </button>
<!-- Back to List: dismisses this controller's view only. <!-- Back to List: dismisses this controller's view only.
@@ -1155,12 +1142,11 @@
$events_sess.launcher.modal__open_event_file_id = null; $events_sess.launcher.modal__open_event_file_id = null;
$events_sess.launcher.modal__event_file_obj = null; $events_sess.launcher.modal__event_file_obj = null;
}} }}
class="btn btn-sm preset-tonal-surface border border-surface-400/50 opacity-80 hover:opacity-100 transition-all" 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' || ($events_loc.launcher.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" />
<span class="hidden sm:inline">Back to List</span> <span class="hidden sm:inline">Back to List</span>
</button> </button>
@@ -1187,6 +1173,5 @@
bind:hide__ws_commands={$events_loc.launcher.hide__ws_commands} bind:hide__ws_commands={$events_loc.launcher.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} />
/>
{/if} {/if}

View File

@@ -9,15 +9,11 @@
// Imports // Imports
import { untrack } from 'svelte'; import { untrack } from 'svelte';
import { import { ae_loc, ae_sess, ae_api } from '$lib/stores/ae_stores';
ae_loc,
ae_sess,
ae_api,
} from '$lib/stores/ae_stores';
import { import {
events_loc, events_loc,
events_sess, events_sess,
events_slct, events_slct
} from '$lib/stores/ae_events_stores'; } from '$lib/stores/ae_events_stores';
// NOTE: Derived from data.account_id (prop) instead of $slct.account_id (store) // NOTE: Derived from data.account_id (prop) instead of $slct.account_id (store)
@@ -41,9 +37,12 @@
$effect(() => { $effect(() => {
if (ae_acct) { if (ae_acct) {
untrack(() => { untrack(() => {
$events_slct.event_location_obj_li = ae_acct.slct.event_location_obj_li ?? ['']; $events_slct.event_location_obj_li = ae_acct.slct
$events_slct.id_li__event_location = ae_acct.slct.id_li__event_location ?? ['']; .event_location_obj_li ?? [''];
$events_slct.event_session_obj_li = ae_acct.slct.event_session_obj_li ?? ['']; $events_slct.id_li__event_location = ae_acct.slct
.id_li__event_location ?? [''];
$events_slct.event_session_obj_li = ae_acct.slct
.event_session_obj_li ?? [''];
}); });
} }
}); });

View File

@@ -71,7 +71,8 @@ export async function load({ params, parent, url }) {
const session_id = url.searchParams.get('session_id'); const session_id = url.searchParams.get('session_id');
if (browser && session_id) { if (browser && session_id) {
if (log_lvl) console.log(`Triggering deep load for session_id: ${session_id}`); if (log_lvl)
console.log(`Triggering deep load for session_id: ${session_id}`);
events_func.load_ae_obj_id__event_session({ events_func.load_ae_obj_id__event_session({
api_cfg: ae_acct.api, api_cfg: ae_acct.api,
event_session_id: session_id, event_session_id: session_id,

View File

@@ -6,7 +6,11 @@
*/ */
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 { events_loc, events_slct, events_sess } from '$lib/stores/ae_events_stores'; import {
events_loc,
events_slct,
events_sess
} from '$lib/stores/ae_events_stores';
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';
@@ -16,9 +20,7 @@
let { log_lvl = 1 } = $props(); let { log_lvl = 1 } = $props();
let currently_syncing: string | null = $state(null); let currently_syncing: string | null = $state(null);
let sync_results: Record<string, 'success' | 'error' | 'pending'> = $state( let sync_results: Record<string, 'success' | 'error' | 'pending'> = $state({});
{}
);
let sync_stats = $state({ total: 0, cached: 0, missing: 0 }); let sync_stats = $state({ total: 0, cached: 0, missing: 0 });
let last_heartbeat: string | null = $state(null); let last_heartbeat: string | null = $state(null);
@@ -79,22 +81,40 @@
// 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.
loop_info.event = cfg.event || dev.check_event_loop_period || 90000; loop_info.event = cfg.event || dev.check_event_loop_period || 90000;
loop_info.device = cfg.device || dev.check_event_device_loop_period || 60000; loop_info.device =
loop_info.location = cfg.location || dev.check_event_location_loop_period || 60000; cfg.device || dev.check_event_device_loop_period || 60000;
loop_info.session = cfg.session || dev.check_event_session_loop_period || 60000; loop_info.location =
loop_info.presentation = cfg.presentation || dev.check_event_presentation_loop_period || 120000; cfg.location || dev.check_event_location_loop_period || 60000;
loop_info.presenter = cfg.presenter || dev.check_event_presenter_loop_period || 120000; loop_info.session =
loop_info.file_sync = cfg.file_sync || dev.check_file_sync_loop_period || 30000; cfg.session || dev.check_event_session_loop_period || 60000;
loop_info.presentation =
cfg.presentation || dev.check_event_presentation_loop_period || 120000;
loop_info.presenter =
cfg.presenter || dev.check_event_presenter_loop_period || 120000;
loop_info.file_sync =
cfg.file_sync || dev.check_file_sync_loop_period || 30000;
// 1. Structural/Metadata Loops // 1. Structural/Metadata Loops
timer__event = setInterval(() => refresh_event_data(), loop_info.event); timer__event = setInterval(() => refresh_event_data(), loop_info.event);
timer__device = setInterval(() => run_device_heartbeat(), loop_info.device); timer__device = setInterval(() => run_device_heartbeat(), loop_info.device);
timer__location = setInterval(() => refresh_location_config(), loop_info.location); timer__location = setInterval(
() => refresh_location_config(),
loop_info.location
);
// 2. Room Content Refresh Loops (API -> Dexie) // 2. Room Content Refresh Loops (API -> Dexie)
timer__session = setInterval(() => refresh_session_data(), loop_info.session); timer__session = setInterval(
timer__presentation = setInterval(() => refresh_presentation_data(), loop_info.presentation); () => refresh_session_data(),
timer__presenter = setInterval(() => refresh_presenter_data(), loop_info.presenter); loop_info.session
);
timer__presentation = setInterval(
() => refresh_presentation_data(),
loop_info.presentation
);
timer__presenter = setInterval(
() => refresh_presenter_data(),
loop_info.presenter
);
// 3. Native File Sync Loop (Dexie -> Disk) // 3. Native File Sync Loop (Dexie -> Disk)
timer__file_sync = setInterval(() => run_sync_cycle(), loop_info.file_sync); timer__file_sync = setInterval(() => run_sync_cycle(), loop_info.file_sync);
@@ -113,8 +133,12 @@
// Age threshold is user-configurable (cfg → General → Cache Maintenance), default 24h. // Age threshold is user-configurable (cfg → General → Cache Maintenance), default 24h.
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 = $events_loc.launcher.cleanup_tmp_max_age_hours ?? 24; const max_age_hours =
cleanup_tmp_files({ cache_root, max_age_minutes: max_age_hours * 60 }).then((result) => { $events_loc.launcher.cleanup_tmp_max_age_hours ?? 24;
cleanup_tmp_files({
cache_root,
max_age_minutes: max_age_hours * 60
}).then((result) => {
if (log_lvl) console.log('Sync: .tmp cleanup complete.', result); if (log_lvl) console.log('Sync: .tmp cleanup complete.', result);
}); });
} }
@@ -157,7 +181,10 @@
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 {
if (log_lvl > 1) console.log(`Sync: Refreshing sessions for location: ${location_id}`); if (log_lvl > 1)
console.log(
`Sync: Refreshing sessions for location: ${location_id}`
);
await events_func.load_ae_obj_li__event_session({ await events_func.load_ae_obj_li__event_session({
api_cfg: $ae_api, api_cfg: $ae_api,
for_obj_type: 'event_location', for_obj_type: 'event_location',
@@ -178,7 +205,10 @@
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 {
if (log_lvl > 1) console.log(`Sync: Refreshing presentations for session: ${session_id}`); if (log_lvl > 1)
console.log(
`Sync: Refreshing presentations for session: ${session_id}`
);
await events_func.load_ae_obj_li__event_presentation({ await events_func.load_ae_obj_li__event_presentation({
api_cfg: $ae_api, api_cfg: $ae_api,
for_obj_type: 'event_session', for_obj_type: 'event_session',
@@ -197,7 +227,10 @@
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 {
if (log_lvl > 1) console.log(`Sync: Refreshing presenters for session: ${session_id}`); if (log_lvl > 1)
console.log(
`Sync: Refreshing presenters for session: ${session_id}`
);
await events_func.load_ae_obj_li__event_presenter({ await events_func.load_ae_obj_li__event_presenter({
api_cfg: $ae_api, api_cfg: $ae_api,
for_obj_type: 'event_session', for_obj_type: 'event_session',
@@ -214,8 +247,7 @@
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;
if (!location_id || !cache_root || is_syncing || !$ae_loc.is_native) if (!location_id || !cache_root || is_syncing || !$ae_loc.is_native) return;
return;
is_syncing = true; is_syncing = true;
try { try {
@@ -362,15 +394,16 @@
update_payload.info_hostname = info.hostname; update_payload.info_hostname = info.hostname;
// Safely handle IP list (bridge may return ip_addresses or networkInterfaces) // Safely handle IP list (bridge may return ip_addresses or networkInterfaces)
const ips = info.ip_addresses || []; const ips = info.ip_addresses || [];
update_payload.info_ip_list = Array.isArray(ips) ? ips.join(', ') : 'Unknown'; update_payload.info_ip_list = Array.isArray(ips)
? ips.join(', ')
: 'Unknown';
update_payload.meta_json = { update_payload.meta_json = {
platform: info.platform, platform: info.platform,
release: info.release, release: info.release,
arch: info.arch, arch: info.arch,
cpus: info.cpus, cpus: info.cpus,
total_mem: total_mem: Math.round(info.total_mem / (1024 * 1024)) + 'MB',
Math.round(info.total_mem / (1024 * 1024)) + 'MB',
free_mem: Math.round(info.free_mem / (1024 * 1024)) + 'MB' free_mem: Math.round(info.free_mem / (1024 * 1024)) + 'MB'
}; };
} else { } else {
@@ -424,81 +457,76 @@
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 $events_loc.launcher.app_mode === 'native' || $ae_loc.is_native}
<div <div
class="fixed bottom-20 left-4 z-[9999] flex flex-col items-start gap-2 pointer-events-none" class="pointer-events-none fixed bottom-20 left-4 z-[9999] flex flex-col items-start gap-2">
>
{#if show_monitor} {#if show_monitor}
<div <div
class="bg-surface-50/95 dark:bg-surface-900/95 text-surface-800 dark:text-surface-100 p-3 rounded-lg border border-surface-200 dark:border-primary-700 shadow-2xl text-[10px] font-mono min-w-52 pointer-events-auto backdrop-blur-sm" class="bg-surface-50/95 dark:bg-surface-900/95 text-surface-800 dark:text-surface-100 border-surface-200 dark:border-primary-700 pointer-events-auto min-w-52 rounded-lg border p-3 font-mono text-[10px] shadow-2xl backdrop-blur-sm">
>
<div <div
class="flex justify-between border-b border-surface-200 dark:border-primary-700 pb-1 mb-2" class="border-surface-200 dark:border-primary-700 mb-2 flex justify-between border-b pb-1">
> <span
<span class="font-bold text-primary-600 dark:text-primary-400" class="text-primary-600 dark:text-primary-400 font-bold"
>NATIVE SYNC MONITOR</span >NATIVE SYNC MONITOR</span>
>
<button <button
type="button" type="button"
onclick={() => (show_monitor = false)} onclick={() => (show_monitor = false)}
class="text-error-500 hover:text-error-400 ml-2">×</button class="text-error-500 hover:text-error-400 ml-2"
> >×</button>
</div> </div>
<div class="grid grid-cols-2 gap-x-4 gap-y-1 mb-2"> <div class="mb-2 grid grid-cols-2 gap-x-4 gap-y-1">
<span class="opacity-60 text-primary-700 dark:text-primary-300">Room Status:</span> <span
class="text-primary-700 dark:text-primary-300 opacity-60"
>Room Status:</span>
<span class="text-right" <span class="text-right"
>{sync_stats.cached} / {sync_stats.total} Files</span >{sync_stats.cached} / {sync_stats.total} Files</span>
>
<span class="opacity-60 text-primary-700 dark:text-primary-300">Prefix Len:</span> <span
class="text-primary-700 dark:text-primary-300 opacity-60"
>Prefix Len:</span>
<span class="text-right" <span class="text-right"
>{$ae_loc.native_device?.hash_prefix_length || 2} chars</span >{$ae_loc.native_device?.hash_prefix_length || 2} chars</span>
>
<span class="opacity-60 text-primary-700 dark:text-primary-300">Heartbeat:</span> <span
class="text-primary-700 dark:text-primary-300 opacity-60"
>Heartbeat:</span>
<span <span
class="text-right {last_heartbeat class="text-right {last_heartbeat
? 'text-success-600 dark:text-success-400' ? 'text-success-600 dark:text-success-400'
: 'text-error-600 dark:text-error-400'}" : 'text-error-600 dark:text-error-400'}">
>
{last_heartbeat || 'Pending...'} {last_heartbeat || 'Pending...'}
</span> </span>
</div> </div>
<div class="border-t border-surface-200 dark:border-surface-700 pt-2 flex flex-col gap-1"> <div
<div class="flex justify-between items-center"> class="border-surface-200 dark:border-surface-700 flex flex-col gap-1 border-t pt-2">
<div class="flex items-center justify-between">
<span class:text-primary-500={timer__event} <span class:text-primary-500={timer__event}
>Event Loop:</span >Event Loop:</span>
>
<span>{loop_info.event / 1000}s</span> <span>{loop_info.event / 1000}s</span>
</div> </div>
<div class="flex justify-between items-center"> <div class="flex items-center justify-between">
<span class:text-primary-500={timer__device} <span class:text-primary-500={timer__device}
>Device Loop:</span >Device Loop:</span>
>
<span>{loop_info.device / 1000}s</span> <span>{loop_info.device / 1000}s</span>
</div> </div>
<div class="flex justify-between items-center"> <div class="flex items-center justify-between">
<span class:text-primary-500={timer__location} <span class:text-primary-500={timer__location}
>Location Loop:</span >Location Loop:</span>
>
<span>{loop_info.location / 1000}s</span> <span>{loop_info.location / 1000}s</span>
</div> </div>
<div class="flex justify-between items-center"> <div class="flex items-center justify-between">
<span class:text-primary-500={timer__session} <span class:text-primary-500={timer__session}
>Session Loop:</span >Session Loop:</span>
>
<span>{loop_info.session / 1000}s</span> <span>{loop_info.session / 1000}s</span>
</div> </div>
<div class="flex justify-between items-center"> <div class="flex items-center justify-between">
<span class:text-primary-500={timer__presentation} <span class:text-primary-500={timer__presentation}
>Pres Loop:</span >Pres Loop:</span>
>
<span>{loop_info.presentation / 1000}s</span> <span>{loop_info.presentation / 1000}s</span>
</div> </div>
<div class="flex justify-between items-center"> <div class="flex items-center justify-between">
<span class:text-primary-500={timer__presenter} <span class:text-primary-500={timer__presenter}
>Speaker Loop:</span >Speaker Loop:</span>
>
<span>{loop_info.presenter / 1000}s</span> <span>{loop_info.presenter / 1000}s</span>
</div> </div>
</div> </div>
@@ -512,25 +540,36 @@
type="button" type="button"
onclick={() => (show_monitor = !show_monitor)} onclick={() => (show_monitor = !show_monitor)}
class=" class="
flex items-center gap-1.5 pointer-events-auto flex items-center
px-2 py-1.5 rounded-lg gap-1.5 rounded-lg px-2
text-[10px] font-mono py-1.5 font-mono
pointer-events-auto text-[10px]
transition-all transition-all
{currently_syncing {currently_syncing
? 'bg-primary-500/15 dark:bg-primary-500/25 border border-primary-500 text-primary-700 dark:text-primary-300 shadow-md animate-pulse' ? 'bg-primary-500/15 dark:bg-primary-500/25 border-primary-500 text-primary-700 dark:text-primary-300 animate-pulse border shadow-md'
: 'bg-surface-100/90 dark:bg-surface-800/80 border border-surface-300 dark:border-surface-600 text-surface-600 dark:text-surface-300 opacity-60 hover:opacity-100 shadow-sm'} : 'bg-surface-100/90 dark:bg-surface-800/80 border-surface-300 dark:border-surface-600 text-surface-600 dark:text-surface-300 border opacity-60 shadow-sm hover:opacity-100'}
" "
title="Native Sync Monitor · {sync_stats.cached}/{sync_stats.total} files · click to {show_monitor ? 'close' : 'open'}" title="Native Sync Monitor · {sync_stats.cached}/{sync_stats.total} files · click to {show_monitor
> ? 'close'
{#if currently_syncing}<RefreshCw size="1em" class="text-[9px] animate-spin text-primary-500" />{:else}<Cpu size="1em" class="text-[9px] opacity-50" />{/if} : 'open'}">
{#if currently_syncing}<RefreshCw
size="1em"
class="text-primary-500 animate-spin text-[9px]" />{:else}<Cpu
size="1em"
class="text-[9px] opacity-50" />{/if}
{#if currently_syncing} {#if currently_syncing}
<span class="truncate max-w-40 text-left">{currently_syncing}</span> <span class="max-w-40 truncate text-left"
>{currently_syncing}</span>
{:else} {:else}
<span>Native Sync</span> <span>Native Sync</span>
<span class="opacity-50 ml-0.5">{sync_stats.cached}/{sync_stats.total}</span> <span class="ml-0.5 opacity-50"
>{sync_stats.cached}/{sync_stats.total}</span>
{/if} {/if}
{#if show_monitor}<ChevronDown size="1em" class="text-[7px] opacity-40 ml-0.5" />{:else}<ChevronUp size="1em" class="text-[7px] opacity-40 ml-0.5" />{/if} {#if show_monitor}<ChevronDown
size="1em"
class="ml-0.5 text-[7px] opacity-40" />{:else}<ChevronUp
size="1em"
class="ml-0.5 text-[7px] opacity-40" />{/if}
</button> </button>
</div> </div>
{/if} {/if}

View File

@@ -32,7 +32,16 @@
import Launcher_Cfg_Screen_Saver from './cfg_components/launcher_cfg_screen_saver.svelte'; import Launcher_Cfg_Screen_Saver from './cfg_components/launcher_cfg_screen_saver.svelte';
import Launcher_Cfg_App_Modes from './cfg_components/launcher_cfg_app_modes.svelte'; import Launcher_Cfg_App_Modes from './cfg_components/launcher_cfg_app_modes.svelte';
import Launcher_Cfg_Local_Actions from './cfg_components/launcher_cfg_local_actions.svelte'; import Launcher_Cfg_Local_Actions from './cfg_components/launcher_cfg_local_actions.svelte';
import { Bug, Code, Monitor, Pencil, RefreshCw, Settings, SlidersHorizontal, X } from '@lucide/svelte'; import {
Bug,
Code,
Monitor,
Pencil,
RefreshCw,
Settings,
SlidersHorizontal,
X
} from '@lucide/svelte';
// UI Tab State // UI Tab State
// Tabs are audience-oriented: // Tabs are audience-oriented:
// setup — what every onsite operator needs (mode preset, display, WS, screen saver) // setup — what every onsite operator needs (mode preset, display, WS, screen saver)
@@ -63,16 +72,13 @@
<div <div
class=" class="
w-full max-w-full flex w-full
flex flex-col gap-4 items-center justify-start max-w-full flex-col items-center justify-start gap-4
" ">
>
<div <div
class="w-full flex flex-row items-center justify-between border-b border-surface-500/20 pb-2" class="border-surface-500/20 flex w-full flex-row items-center justify-between border-b pb-2">
>
<h2 <h2
class="text-center text-lg font-bold text-gray-700 dark:text-gray-200" class="text-center text-lg font-bold text-gray-700 dark:text-gray-200">
>
<Settings size="1em" class="mr-2 opacity-50" /> <Settings size="1em" class="mr-2 opacity-50" />
Launcher Configuration Launcher Configuration
</h2> </h2>
@@ -88,8 +94,7 @@
class:text-primary-500={$ae_loc.edit_mode} class:text-primary-500={$ae_loc.edit_mode}
class:opacity-20={!$ae_loc.edit_mode} class:opacity-20={!$ae_loc.edit_mode}
class:hover:opacity-60={!$ae_loc.edit_mode} class:hover:opacity-60={!$ae_loc.edit_mode}
title="{$ae_loc.edit_mode ? 'Disable' : 'Enable'} Edit Mode" title="{$ae_loc.edit_mode ? 'Disable' : 'Enable'} Edit Mode">
>
<Pencil size="0.75em" /> <Pencil size="0.75em" />
<span class="sr-only">Toggle Edit Mode</span> <span class="sr-only">Toggle Edit Mode</span>
</button> </button>
@@ -97,8 +102,7 @@
<button <button
type="button" type="button"
onclick={() => ($events_loc.launcher.hide_drawer__cfg = true)} onclick={() => ($events_loc.launcher.hide_drawer__cfg = true)}
class="btn btn-icon dark:text-white hover:bg-surface-500/10 transition-colors" class="btn btn-icon hover:bg-surface-500/10 transition-colors dark:text-white">
>
<X size="1em" /> <X size="1em" />
<span class="sr-only">Close Config</span> <span class="sr-only">Close Config</span>
</button> </button>
@@ -110,88 +114,80 @@
for onsite operators who never need those tools. Edit Mode is toggled via for onsite operators who never need those tools. Edit Mode is toggled via
the pencil icon in the header above. --> the pencil icon in the header above. -->
<div <div
class="w-full gap-1 bg-surface-500/10 p-1 rounded-lg" class="bg-surface-500/10 w-full gap-1 rounded-lg p-1"
class:grid={true} class:grid={true}
class:grid-cols-2={!$ae_loc.edit_mode} class:grid-cols-2={!$ae_loc.edit_mode}
class:grid-cols-3={$ae_loc.edit_mode} class:grid-cols-3={$ae_loc.edit_mode}>
>
<button <button
type="button" type="button"
onclick={() => (active_tab = 'setup')} onclick={() => (active_tab = 'setup')}
class="btn btn-sm text-[10px] uppercase font-bold transition-all" class="btn btn-sm text-[10px] font-bold uppercase transition-all"
class:preset-filled-primary={active_tab === 'setup'} class:preset-filled-primary={active_tab === 'setup'}
class:preset-tonal-surface={active_tab !== 'setup'} class:preset-tonal-surface={active_tab !== 'setup'}
title="Display presets, interface toggles, WS controller, screen saver" title="Display presets, interface toggles, WS controller, screen saver">
>
<SlidersHorizontal size="0.85em" class="mr-1" /> Setup <SlidersHorizontal size="0.85em" class="mr-1" /> Setup
</button> </button>
<button <button
type="button" type="button"
onclick={() => (active_tab = 'device')} onclick={() => (active_tab = 'device')}
class="btn btn-sm text-[10px] uppercase font-bold transition-all" class="btn btn-sm text-[10px] font-bold uppercase transition-all"
class:preset-filled-primary={active_tab === 'device'} class:preset-filled-primary={active_tab === 'device'}
class:preset-tonal-surface={active_tab !== 'device'} class:preset-tonal-surface={active_tab !== 'device'}
title="Sync engine, device health &amp; native OS controls" title="Sync engine, device health &amp; native OS controls">
>
<Monitor size="0.85em" class="mr-1" /> Device <Monitor size="0.85em" class="mr-1" /> Device
</button> </button>
{#if $ae_loc.edit_mode} {#if $ae_loc.edit_mode}
<button <button
type="button" type="button"
onclick={() => (active_tab = 'dev')} onclick={() => (active_tab = 'dev')}
class="btn btn-sm text-[10px] uppercase font-bold transition-all" class="btn btn-sm text-[10px] font-bold uppercase transition-all"
class:preset-filled-warning={active_tab === 'dev'} class:preset-filled-warning={active_tab === 'dev'}
class:preset-tonal-surface={active_tab !== 'dev'} class:preset-tonal-surface={active_tab !== 'dev'}
title="Developer &amp; debug tools" title="Developer &amp; debug tools">
>
<Code size="0.85em" class="mr-1" /> Dev <Code size="0.85em" class="mr-1" /> Dev
</button> </button>
{/if} {/if}
</div> </div>
<!-- Tab Content --> <!-- Tab Content -->
<div class="w-full flex flex-col gap-2 min-h-[400px]"> <div class="flex min-h-[400px] w-full flex-col gap-2">
<!-- SETUP: everything onsite operators need day-to-day --> <!-- SETUP: everything onsite operators need day-to-day -->
{#if active_tab === 'setup'} {#if active_tab === 'setup'}
<div class="animate-in fade-in slide-in-from-left-2 duration-300 flex flex-col gap-2"> <div
class="animate-in fade-in slide-in-from-left-2 flex flex-col gap-2 duration-300">
<!-- Mode preset is the #1 onsite action — give it prominent placement --> <!-- Mode preset is the #1 onsite action — give it prominent placement -->
<Launcher_Cfg_App_Modes <Launcher_Cfg_App_Modes
on_expand={() => handle_section_expand('app_modes')} on_expand={() => handle_section_expand('app_modes')} />
/>
<Launcher_Cfg_Controller <Launcher_Cfg_Controller
on_expand={() => handle_section_expand('controller')} on_expand={() => handle_section_expand('controller')} />
/>
<Launcher_Cfg_Screen_Saver <Launcher_Cfg_Screen_Saver
on_expand={() => handle_section_expand('screen_saver')} on_expand={() => handle_section_expand('screen_saver')} />
/>
</div> </div>
{/if} {/if}
<!-- DEVICE: sync engine first (all devices) + native OS controls (native or edit_mode preview) --> <!-- DEVICE: sync engine first (all devices) + native OS controls (native or edit_mode preview) -->
{#if active_tab === 'device'} {#if active_tab === 'device'}
<div class="animate-in fade-in slide-in-from-bottom-2 duration-300 flex flex-col gap-2"> <div
class="animate-in fade-in slide-in-from-bottom-2 flex flex-col gap-2 duration-300">
<!-- Sync pause/timers — relevant to every device, not just native --> <!-- Sync pause/timers — relevant to every device, not just native -->
<Launcher_Cfg_Sync_Timers <Launcher_Cfg_Sync_Timers
on_expand={() => handle_section_expand('sync_timers')} on_expand={() => handle_section_expand('sync_timers')} />
/>
<!-- Native sections: always in Electron; visible in edit_mode for dev preview. <!-- Native sections: always in Electron; visible in edit_mode for dev preview.
electron_relay.ts guards all calls — safe to import/render without Electron. --> electron_relay.ts guards all calls — safe to import/render without Electron. -->
{#if $ae_loc.is_native || $ae_loc.edit_mode} {#if $ae_loc.is_native || $ae_loc.edit_mode}
<Launcher_Cfg_Health <Launcher_Cfg_Health
on_expand={() => handle_section_expand('health')} on_expand={() => handle_section_expand('health')} />
/>
<Launcher_Cfg_Native_OS <Launcher_Cfg_Native_OS
on_expand={() => handle_section_expand('native_os')} on_expand={() => handle_section_expand('native_os')} />
/>
{#if $ae_loc.is_native} {#if $ae_loc.is_native}
<Launcher_Cfg_Updates <Launcher_Cfg_Updates
on_expand={() => handle_section_expand('updates')} on_expand={() =>
/> handle_section_expand('updates')} />
{/if} {/if}
{:else} {:else}
<div class="py-3 text-center opacity-40 italic text-xs flex flex-col gap-1 items-center"> <div
class="flex flex-col items-center gap-1 py-3 text-center text-xs italic opacity-40">
<Monitor size="1.2em" class="opacity-30" /> <Monitor size="1.2em" class="opacity-30" />
<p>Native OS controls available in Aether Desktop.</p> <p>Native OS controls available in Aether Desktop.</p>
<p class="text-[9px]">Enable Edit Mode to preview.</p> <p class="text-[9px]">Enable Edit Mode to preview.</p>
@@ -202,27 +198,24 @@
<!-- DEV: developer/debug tools — only reachable when Edit Mode is on --> <!-- DEV: developer/debug tools — only reachable when Edit Mode is on -->
{#if active_tab === 'dev' && $ae_loc.edit_mode} {#if active_tab === 'dev' && $ae_loc.edit_mode}
<div class="animate-in fade-in slide-in-from-right-2 duration-300 flex flex-col gap-2"> <div
class="animate-in fade-in slide-in-from-right-2 flex flex-col gap-2 duration-300">
<Launcher_Cfg_Local_Actions <Launcher_Cfg_Local_Actions
on_expand={() => handle_section_expand('local_actions')} on_expand={() => handle_section_expand('local_actions')} />
/>
</div> </div>
{/if} {/if}
</div> </div>
<!-- Global Actions Footer --> <!-- Global Actions Footer -->
<div <div
class="w-full flex flex-col gap-2 border-t border-surface-500/20 pt-4 mt-auto" class="border-surface-500/20 mt-auto flex w-full flex-col gap-2 border-t pt-4">
>
<div class="grid grid-cols-2 gap-2"> <div class="grid grid-cols-2 gap-2">
<!-- Close button — always visible in lower-left as a second dismissal point. <!-- Close button — always visible in lower-left as a second dismissal point.
Useful in kiosk/iframe mode where the top-right close btn may scroll out of view. --> Useful in kiosk/iframe mode where the top-right close btn may scroll out of view. -->
<button <button
type="button" type="button"
onclick={() => ($events_loc.launcher.hide_drawer__cfg = true)} onclick={() => ($events_loc.launcher.hide_drawer__cfg = true)}
class="btn btn-sm preset-tonal-surface hover:preset-filled-surface-500 transition-all" class="btn btn-sm preset-tonal-surface hover:preset-filled-surface-500 transition-all">
>
<X size="0.85em" class="mr-1" /> <X size="0.85em" class="mr-1" />
Close Close
</button> </button>
@@ -230,8 +223,7 @@
<button <button
type="button" type="button"
onclick={() => location.reload()} onclick={() => location.reload()}
class="btn btn-sm preset-tonal-secondary hover:preset-filled-secondary-500 transition-all" class="btn btn-sm preset-tonal-secondary hover:preset-filled-secondary-500 transition-all">
>
<RefreshCw size="0.85em" class="mr-1" /> <RefreshCw size="0.85em" class="mr-1" />
Reload Reload
</button> </button>
@@ -242,16 +234,14 @@
type="button" type="button"
onclick={() => onclick={() =>
($events_loc.launcher.hide_drawer__debug = false)} ($events_loc.launcher.hide_drawer__debug = false)}
class="btn btn-sm preset-tonal-warning hover:preset-filled-warning-500 transition-all w-full" class="btn btn-sm preset-tonal-warning hover:preset-filled-warning-500 w-full transition-all">
>
<Bug size="0.85em" class="mr-1" /> <Bug size="0.85em" class="mr-1" />
Debug Panel Debug Panel
</button> </button>
{/if} {/if}
<p <p
class="text-[9px] text-center opacity-40 uppercase font-bold tracking-widest mt-2" class="mt-2 text-center text-[9px] font-bold tracking-widest uppercase opacity-40">
>
Aether Platform &bull; Events Launcher v3.0 Aether Platform &bull; Events Launcher v3.0
</p> </p>
</div> </div>

View File

@@ -63,7 +63,18 @@
events_slct events_slct
} from '$lib/stores/ae_events_stores'; } from '$lib/stores/ae_events_stores';
import { events_func } from '$lib/ae_events/ae_events_functions'; import { events_func } from '$lib/ae_events/ae_events_functions';
import { AlertCircle, AlertTriangle, BarChart2, CalendarDays, FolderOpen, Laptop, LoaderCircle, Monitor, Save, Send } from '@lucide/svelte'; import {
AlertCircle,
AlertTriangle,
BarChart2,
CalendarDays,
FolderOpen,
Laptop,
LoaderCircle,
Monitor,
Save,
Send
} from '@lucide/svelte';
import AE_Comp_Hosted_Files_Download_Button from '$lib/ae_core/ae_comp__hosted_files_download_button.svelte'; import AE_Comp_Hosted_Files_Download_Button from '$lib/ae_core/ae_comp__hosted_files_download_button.svelte';
// Import the relay // Import the relay
@@ -227,24 +238,22 @@
class:hidden={hide_draft && class:hidden={hide_draft &&
(event_file_obj.file_purpose == 'outline' || (event_file_obj.file_purpose == 'outline' ||
event_file_obj.file_purpose == 'draft')} event_file_obj.file_purpose == 'draft')}
class="event_launcher_file_cont grow flex flex-col md:flex-row flex-wrap gap-1 items-center justify-center max-w-full transition-all" class="event_launcher_file_cont flex max-w-full grow flex-col flex-wrap items-center justify-center gap-1 transition-all md:flex-row">
>
{#if open_file_clicked} {#if open_file_clicked}
<div <div
class="open_file_clicked alert" class="open_file_clicked alert"
in:fade={{ duration: 250 }} in:fade={{ duration: 250 }}
out:fade={{ duration: 2000 }} out:fade={{ duration: 2000 }}>
>
<div class="alert_msg_pulse"> <div class="alert_msg_pulse">
<strong <strong
>*** {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 && $events_loc.launcher.app_mode === 'native'}
<p>Most files will automatically be opened full screen.</p> <p>Most files will automatically be opened full screen.</p>
<p> <p>
PowerPoint or KeyNote will attempt to display in presenter view. PowerPoint or KeyNote will attempt to display in presenter
view.
</p> </p>
<p>Please close the file when finished.</p> <p>Please close the file when finished.</p>
{/if} {/if}
@@ -252,8 +261,7 @@
{/if} {/if}
<span <span
class="event_file_action grow max-w-full flex flex-row flex-wrap gap-1 items-center justify-center" class="event_file_action flex max-w-full grow flex-row flex-wrap items-center justify-center gap-1">
>
{#if session_type == 'poster' || open_method == 'modal'} {#if session_type == 'poster' || open_method == 'modal'}
<AE_Comp_Hosted_Files_Download_Button <AE_Comp_Hosted_Files_Download_Button
hosted_file_id={event_file_id} hosted_file_id={event_file_id}
@@ -267,17 +275,24 @@
$events_slct.event_file_id = event_file_id; $events_slct.event_file_id = event_file_id;
$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 ($events_loc.launcher.controller == 'local_push' && $events_sess.launcher.ws_connect_status == 'connected') { if (
$events_loc.launcher.controller == 'local_push' &&
$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}`;
$events_sess.launcher.controller_trigger_send = true; $events_sess.launcher.controller_trigger_send = true;
} }
}} }}>
>
{#snippet label()} {#snippet label()}
{#if screen_saver_exts.includes(event_file_obj.extension)} {#if screen_saver_exts.includes(event_file_obj.extension)}
<BarChart2 size="1em" class="{hide_launch_icon ? 'hidden' : ''} m-1" /> Open Poster <BarChart2
size="1em"
class="{hide_launch_icon ? 'hidden' : ''} m-1" /> Open
Poster
{:else} {:else}
<Send size="1em" class="{hide_launch_icon ? 'hidden' : ''} m-1" /> <Send
size="1em"
class="{hide_launch_icon ? 'hidden' : ''} m-1" />
{ae_util.shorten_filename({ {ae_util.shorten_filename({
filename: event_file_obj.filename, filename: event_file_obj.filename,
max_length: max_filename_length max_length: max_filename_length
@@ -291,13 +306,14 @@
hosted_file_obj={event_file_obj} hosted_file_obj={event_file_obj}
require_auth={false} require_auth={false}
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()}
{@const file_id = event_file_obj.hosted_file_id} {@const file_id = event_file_obj.hosted_file_id}
<span class="shrink text-xs border-r border-gray-400 pr-1"> <span class="shrink border-r border-gray-400 pr-1 text-xs">
{#await ae_promises[event_file_id]} {#await ae_promises[event_file_id]}
<LoaderCircle size="1em" class="inline animate-spin mx-0.5" /> <LoaderCircle
size="1em"
class="mx-0.5 inline animate-spin" />
<span> <span>
{#if $ae_sess.api_download_kv[file_id]} {#if $ae_sess.api_download_kv[file_id]}
{$ae_sess.api_download_kv[file_id] {$ae_sess.api_download_kv[file_id]
@@ -307,24 +323,28 @@
{/if} {/if}
</span> </span>
{:then result} {:then result}
{@const FileIcon = ae_util.file_extension_icon_lucide(event_file_obj.extension)} {@const FileIcon =
<FileIcon size="1em" class="inline mx-0.5" /> ae_util.file_extension_icon_lucide(
event_file_obj.extension
)}
<FileIcon size="1em" class="mx-0.5 inline" />
{event_file_obj.extension} {event_file_obj.extension}
{#if result === null || result === false} {#if result === null || result === false}
<span class="text-error-500" <span class="text-error-500"
><AlertTriangle size="1em" class="inline mx-1" />Failed!</span ><AlertTriangle
> size="1em"
class="mx-1 inline" />Failed!</span>
{/if} {/if}
{:catch error} {:catch error}
<span class="text-error-500" title={error?.message} <span class="text-error-500" title={error?.message}
><AlertCircle size="1em" class="inline mx-0.5" />Error!</span ><AlertCircle
> size="1em"
class="mx-0.5 inline" />Error!</span>
{/await} {/await}
</span> </span>
<span <span
class="grow {text_size} {text_size_md} w-full max-w-full overflow-hidden text-ellipsis {btn_text_align}" class="grow {text_size} {text_size_md} w-full max-w-full overflow-hidden text-ellipsis {btn_text_align}">
>
{ae_util.shorten_string({ {ae_util.shorten_string({
string: event_file_obj.filename_no_ext, string: event_file_obj.filename_no_ext,
begin_length: 45, begin_length: 45,
@@ -333,9 +353,8 @@
</span> </span>
<span <span
class="badge my-0 py-0.5 preset-tonal-success hover:preset-filled-success-500 text-xs xl:text-sm" class="badge preset-tonal-success hover:preset-filled-success-500 my-0 py-0.5 text-xs xl:text-sm"
class:hidden={!event_file_obj.file_purpose} class:hidden={!event_file_obj.file_purpose}>
>
{event_file_obj.file_purpose} {event_file_obj.file_purpose}
</span> </span>
{/snippet} {/snippet}
@@ -344,9 +363,8 @@
</span> </span>
<span <span
class="event_file_meta grow text-sm text-gray-500 flex flex-col sm:flex-row gap-1 wrap items-center justify-between w-64 max-w-80 font-mono" class="event_file_meta wrap flex w-64 max-w-80 grow flex-col items-center justify-between gap-1 font-mono text-sm text-gray-500 sm:flex-row"
class:hidden={hide_meta} class:hidden={hide_meta}>
>
<button <button
type="button" type="button"
onclick={async () => { onclick={async () => {
@@ -366,33 +384,33 @@
log_lvl log_lvl
}); });
}} }}
class="btn btn-sm transition-all group" class="btn btn-sm group transition-all"
class:preset-tonal-success={event_file_obj?.open_in_os == 'win'} class:preset-tonal-success={event_file_obj?.open_in_os == 'win'}
class:preset-tonal-warning={event_file_obj?.open_in_os == 'mac'} class:preset-tonal-warning={event_file_obj?.open_in_os == 'mac'}
disabled={!$ae_loc.trusted_access} disabled={!$ae_loc.trusted_access}>
> {#if event_file_obj?.open_in_os == 'win'}<Monitor
{#if event_file_obj?.open_in_os == 'win'}<Monitor size="1em" class="m-1" /> size="1em"
{:else if event_file_obj?.open_in_os == 'mac'}<Laptop size="1em" class="m-1" /> class="m-1" />
{:else if event_file_obj?.open_in_os == 'mac'}<Laptop
size="1em"
class="m-1" />
{:else}<FolderOpen size="1em" class="m-1" />{/if} {:else}<FolderOpen size="1em" class="m-1" />{/if}
</button> </button>
<span <span
class="event_file_created_on text-xs text-center flex flex-row gap-1 items-center justify-end w-24 md:w-44 preset-filled-surface-100-900 rounded px-1 py-0.5" class="event_file_created_on preset-filled-surface-100-900 flex w-24 flex-row items-center justify-end gap-1 rounded px-1 py-0.5 text-center text-xs md:w-44"
class:hidden={hide_created_on} class:hidden={hide_created_on}>
>
<CalendarDays size="0.85em" class="inline" /> <CalendarDays size="0.85em" class="inline" />
<span class="w-18" <span class="w-18"
>{ae_util.iso_datetime_formatter( >{ae_util.iso_datetime_formatter(
event_file_obj.created_on, event_file_obj.created_on,
'date_short' 'date_short'
)}</span )}</span>
>
</span> </span>
<span <span
class="event_file_size text-xs text-center flex flex-row gap-1 items-center justify-end preset-filled-surface-100-900 w-22 max-w-28 rounded py-0.5" class="event_file_size preset-filled-surface-100-900 flex w-22 max-w-28 flex-row items-center justify-end gap-1 rounded py-0.5 text-center text-xs"
class:hidden={hide_size} class:hidden={hide_size}>
>
<Save size="0.85em" class="inline" /> <Save size="0.85em" class="inline" />
{#if event_file_obj.file_size}{ae_util.format_bytes( {#if event_file_obj.file_size}{ae_util.format_bytes(
event_file_obj.file_size event_file_obj.file_size

View File

@@ -146,15 +146,14 @@
<div <div
class=" class="
event_launcher_menu event_launcher_menu
shrink h-full w-full max-w-full flex h-full w-full max-w-full
flex flex-col flex-wrap gap-1 items-center justify-start shrink flex-col flex-wrap items-center justify-start gap-1
" ">
>
<!-- overflow-x-clip --> <!-- overflow-x-clip -->
{#if $lq__event_event_file_obj_li} {#if $lq__event_event_file_obj_li}
<div class="w-full flex flex-col gap-0.5 overflow-y-auto"> <div class="flex w-full flex-col gap-0.5 overflow-y-auto">
<!-- <div class="text-xs text-neutral-800/80"> <!-- <div class="text-xs text-neutral-800/80">
<strong> <strong>
Event Files: Event Files:
@@ -191,8 +190,7 @@
} }
bind:modal__event_file_obj={ bind:modal__event_file_obj={
$events_sess.launcher.modal__event_file_obj $events_sess.launcher.modal__event_file_obj
} } />
/>
{/each} {/each}
</div> </div>
{/if} {/if}
@@ -201,12 +199,11 @@
<Menu_location_list_menu <Menu_location_list_menu
{lq__event_location_obj_li} {lq__event_location_obj_li}
slct_event_location_id={$events_slct.event_location_id} slct_event_location_id={$events_slct.event_location_id}
bind:loading__session_li_status bind:loading__session_li_status />
/>
{/if} {/if}
{#if $lq__location_event_file_obj_li} {#if $lq__location_event_file_obj_li}
<div class="w-full flex flex-col gap-0.5"> <div class="flex w-full flex-col gap-0.5">
{#each $lq__location_event_file_obj_li as event_file_obj, index (event_file_obj.event_file_id)} {#each $lq__location_event_file_obj_li as event_file_obj, index (event_file_obj.event_file_id)}
<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}
@@ -235,8 +232,7 @@
} }
bind:modal__event_file_obj={ bind:modal__event_file_obj={
$events_sess.launcher.modal__event_file_obj $events_sess.launcher.modal__event_file_obj
} } />
/>
{/each} {/each}
</div> </div>
{/if} {/if}
@@ -246,8 +242,7 @@
bind:slct__event_session_id bind:slct__event_session_id
bind:loading__session_id_status bind:loading__session_id_status
{lq__event_session_obj_li} {lq__event_session_obj_li}
bind:trigger_reload__event_session_obj_id bind:trigger_reload__event_session_obj_id />
/>
{/if} {/if}
<Menu_launcher_controls /> <Menu_launcher_controls />

View File

@@ -53,17 +53,15 @@
{#if $lq__event_file_obj_li && $lq__event_file_obj_li.length} {#if $lq__event_file_obj_li && $lq__event_file_obj_li.length}
<section class="event_presentation_file_list my-1"> <section class="event_presentation_file_list my-1">
<div <div
class="text-[10px] text-surface-600-400 uppercase font-bold tracking-wider opacity-70" class="text-surface-600-400 text-[10px] font-bold tracking-wider uppercase opacity-70">
>
Presentation Files: Presentation Files:
</div> </div>
<ul class="space-y-1"> <ul class="space-y-1">
{#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 md:flex-row flex-wrap gap-1 items-center justify-start" class="flex flex-col flex-wrap items-center justify-start gap-1 md:flex-row"
class:hidden={!$events_loc.launcher class:hidden={!$events_loc.launcher
.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}
@@ -82,8 +80,7 @@
} }
bind:modal__event_file_obj={ bind:modal__event_file_obj={
$events_sess.launcher.modal__event_file_obj $events_sess.launcher.modal__event_file_obj
} } />
/>
</li> </li>
{/each} {/each}
</ul> </ul>

View File

@@ -66,7 +66,7 @@
</strong> </strong>
{#if !lq__event_presenter_obj?.file_count} {#if !lq__event_presenter_obj?.file_count}
<p class="text-sm text-center text-gray-400"> <p class="text-center text-sm text-gray-400">
<!-- <span class="fas fa-exclamation"></span> --> <!-- <span class="fas fa-exclamation"></span> -->
No files to show for this presenter at this time. No files to show for this presenter at this time.
<!-- <span class="fas fa-exclamation"></span> --> <!-- <span class="fas fa-exclamation"></span> -->
@@ -76,7 +76,7 @@
{#if $lq__event_file_obj_li && $lq__event_file_obj_li.length} {#if $lq__event_file_obj_li && $lq__event_file_obj_li.length}
<section class="event_session_file_list"> <section class="event_session_file_list">
<div> <div>
<div class="text-xs text-surface-600-400"> <div class="text-surface-600-400 text-xs">
<strong> <strong>
<Archive size="1em" class="inline" /> <Archive size="1em" class="inline" />
Presenter Files: Presenter Files:
@@ -89,10 +89,9 @@
<ul class="space-y-1"> <ul class="space-y-1">
{#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 md:flex-row flex-wrap gap-1 items-center justify-start" class="flex flex-col flex-wrap items-center justify-start gap-1 md:flex-row"
class:hidden={!$events_loc.launcher class:hidden={!$events_loc.launcher
.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}
@@ -110,8 +109,7 @@
} }
bind:modal__event_file_obj={ bind:modal__event_file_obj={
$events_sess.launcher.modal__event_file_obj $events_sess.launcher.modal__event_file_obj
} } />
/>
</li> </li>
{/each} {/each}
</ul> </ul>

View File

@@ -83,10 +83,9 @@
<ul class="space-y-1"> <ul class="space-y-1">
{#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 md:flex-row wrap gap items-center justify-center" class="wrap gap flex flex-col items-center justify-center md:flex-row"
class:hidden={!$events_loc.launcher class:hidden={!$events_loc.launcher
.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}
@@ -102,8 +101,7 @@
} }
bind:modal__event_file_obj={ bind:modal__event_file_obj={
$events_sess.launcher.modal__event_file_obj $events_sess.launcher.modal__event_file_obj
} } />
/>
</li> </li>
{/each} {/each}
</ul> </ul>

View File

@@ -39,7 +39,16 @@
events_trigger events_trigger
} from '$lib/stores/ae_events_stores'; } from '$lib/stores/ae_events_stores';
import { events_func } from '$lib/ae_events/ae_events_functions'; import { events_func } from '$lib/ae_events/ae_events_functions';
import { AlertTriangle, Archive, Barcode, Image, LoaderCircle, Monitor, User, Users } from '@lucide/svelte'; import {
AlertTriangle,
Archive,
Barcode,
Image,
LoaderCircle,
Monitor,
User,
Users
} from '@lucide/svelte';
// Event Session (Main View Trigger) // Event Session (Main View Trigger)
// WHY: We use a simple derived observable. The template handles the $ prefix. // WHY: We use a simple derived observable. The template handles the $ prefix.
let lq__event_session_obj = $derived( let lq__event_session_obj = $derived(
@@ -60,7 +69,9 @@
if (!slct__event_session_id) return []; if (!slct__event_session_id) return [];
if (log_lvl > 1) { if (log_lvl > 1) {
console.log(`[LQ] Fetching files for session: ${slct__event_session_id}`); console.log(
`[LQ] Fetching files for session: ${slct__event_session_id}`
);
} }
return await db_events.file return await db_events.file
@@ -77,7 +88,9 @@
if (!slct__event_session_id) return []; if (!slct__event_session_id) return [];
if (log_lvl > 1) { if (log_lvl > 1) {
console.log(`[LQ] Fetching presentations for session: ${slct__event_session_id}`); console.log(
`[LQ] Fetching presentations for session: ${slct__event_session_id}`
);
} }
let sort_by = 'start_datetime'; let sort_by = 'start_datetime';
@@ -97,7 +110,9 @@
if (!slct__event_session_id) return []; if (!slct__event_session_id) return [];
if (log_lvl > 1) { if (log_lvl > 1) {
console.log(`[LQ] Fetching presenters for session: ${slct__event_session_id}`); console.log(
`[LQ] Fetching presenters for session: ${slct__event_session_id}`
);
} }
return await db_events.presenter return await db_events.presenter
@@ -146,15 +161,14 @@
<div <div
class=" class="
event_launcher_session_view event_launcher_session_view
grow h-full w-full relative h-full w-full
grow
space-y-1 space-y-1
relative ">
"
>
<!-- <slot name="event_session_message">event session message</slot> --> <!-- <slot name="event_session_message">event session message</slot> -->
{#if $events_sess.launcher.loading__session_id_status} {#if $events_sess.launcher.loading__session_id_status}
<span class="absolute top-0 right-0 text-sm text-center text-gray-400"> <span class="absolute top-0 right-0 text-center text-sm text-gray-400">
<LoaderCircle size="1em" class="inline animate-spin" /> <LoaderCircle size="1em" class="inline animate-spin" />
Loading session information... Loading session information...
</span> </span>
@@ -176,35 +190,33 @@
collapse the header below that height. Zero layout shift between sessions. collapse the header below that height. Zero layout shift between sessions.
--> -->
<header <header
class="event_session_about border-b-2 border-gray-400 dark:border-gray-600 flex flex-col gap-0.5 items-stretch" 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} $events_loc.launcher.hide__session_datetimes}
class="event_session_datetimes text-sm text-center" class="event_session_datetimes text-center text-sm">
>
<button <button
type="button" type="button"
onclick={() => { onclick={() => {
if ( if (
$events_loc.launcher.time_format == 'time_12_short' $events_loc.launcher.time_format ==
'time_12_short'
) { ) {
// $events_loc.launcher.datetime_format = 'datetime_long'; // $events_loc.launcher.datetime_format = 'datetime_long';
$events_loc.launcher.time_format = 'time_short'; $events_loc.launcher.time_format = 'time_short';
$events_loc.launcher.time_hours = 24; $events_loc.launcher.time_hours = 24;
} else { } else {
$events_loc.launcher.time_format = 'time_12_short'; $events_loc.launcher.time_format =
'time_12_short';
// $events_loc.launcher.datetime_format = 'datetime_12_long'; // $events_loc.launcher.datetime_format = 'datetime_12_long';
$events_loc.launcher.time_hours = 12; $events_loc.launcher.time_hours = 12;
} }
}} }}>
>
<strong <strong
>{ae_util.iso_datetime_formatter( >{ae_util.iso_datetime_formatter(
$lq__event_session_obj.start_datetime, $lq__event_session_obj.start_datetime,
'week_long' 'week_long'
)}</strong )}</strong>
>
<span class="font-normal"> <span class="font-normal">
{ae_util.iso_datetime_formatter( {ae_util.iso_datetime_formatter(
$lq__event_session_obj.start_datetime, $lq__event_session_obj.start_datetime,
@@ -215,8 +227,7 @@
>{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 $events_loc.launcher.time_format
)}</strong )}</strong>
>
<span class="font-normal"> <span class="font-normal">
{ae_util.iso_datetime_formatter( {ae_util.iso_datetime_formatter(
@@ -228,21 +239,18 @@
</h3> </h3>
<span <span
class="w-full flex flex-row gap-2 items-center justify-between" class="flex w-full flex-row items-center justify-between gap-2">
>
<!-- 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="grow text-xl line-clamp-2 min-w-0" 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, $events_loc.launcher.time_format)} \nEnd Date/Time: ${ae_util.iso_datetime_formatter($lq__event_session_obj.end_datetime, $events_loc.launcher.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}
<!-- shrink-0: code badge never gets squeezed by a long name --> <!-- shrink-0: code badge never gets squeezed by a long name -->
<span <span
class="shrink-0 text-base text-gray-500 font-normal p-1" class="shrink-0 p-1 text-base font-normal text-gray-500"
title="Session code {$lq__event_session_obj.code}" title="Session code {$lq__event_session_obj.code}">
>
<Barcode size="1em" class="inline" /> <Barcode size="1em" class="inline" />
{$lq__event_session_obj?.code} {$lq__event_session_obj?.code}
</span> </span>
@@ -255,7 +263,7 @@
</section> --> </section> -->
{#if $lq__event_session_obj?.file_count_all === 0} {#if $lq__event_session_obj?.file_count_all === 0}
<p class="text-2xl text-center text-red-500 font-bold"> <p class="text-center text-2xl font-bold text-red-500">
<AlertTriangle size="1em" class="inline" /> <AlertTriangle size="1em" class="inline" />
Warning Warning
<AlertTriangle size="1em" class="inline" /> <AlertTriangle size="1em" class="inline" />
@@ -267,15 +275,14 @@
{#if $lq__event_file_obj_li && $lq__event_file_obj_li.length} {#if $lq__event_file_obj_li && $lq__event_file_obj_li.length}
<section class="event_session_file_list"> <section class="event_session_file_list">
<div> <div>
<div class="text-xs text-surface-600-400"> <div class="text-surface-600-400 text-xs">
<strong> <strong>
<Archive size="1em" class="inline" /> <Archive size="1em" class="inline" />
Session Files: Session Files:
<span <span
class:hidden={!$ae_loc.trusted_access || class:hidden={!$ae_loc.trusted_access ||
!$ae_loc.edit_mode} !$ae_loc.edit_mode}>
>
({$lq__event_file_obj_li?.length}&times;) ({$lq__event_file_obj_li?.length}&times;)
</span> </span>
</strong> </strong>
@@ -291,11 +298,10 @@
<ul class="space-y-1"> <ul class="space-y-1">
{#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 gap-1 items-center justify-center" class="flex flex-row flex-wrap items-center justify-center gap-1"
class:hidden={!$events_loc.launcher class:hidden={!$events_loc.launcher
.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}
@@ -303,7 +309,9 @@
show_bak_download={$ae_loc.trusted_access && show_bak_download={$ae_loc.trusted_access &&
$ae_loc.edit_mode} $ae_loc.edit_mode}
session_type={type_code || 'oral'} session_type={type_code || 'oral'}
open_method={type_code == 'poster' ? 'modal' : null} open_method={type_code == 'poster'
? 'modal'
: null}
modal_title={$lq__event_session_obj?.name} modal_title={$lq__event_session_obj?.name}
bind:modal__title={ bind:modal__title={
$events_sess.launcher.modal__title $events_sess.launcher.modal__title
@@ -313,9 +321,9 @@
.modal__open_event_file_id .modal__open_event_file_id
} }
bind:modal__event_file_obj={ bind:modal__event_file_obj={
$events_sess.launcher.modal__event_file_obj $events_sess.launcher
} .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 || $events_loc.launcher.trusted_access)} open_file_as={$lq__event_session_obj.type_code} poster_title={$lq__event_session_obj.title} /> -->
@@ -342,7 +350,7 @@
<!-- {$lq__event_session_obj?.event_presentation_li?.length ?? 'loading...?'} --> <!-- {$lq__event_session_obj?.event_presentation_li?.length ?? 'loading...?'} -->
{#if $lq__event_presentation_obj_li} {#if $lq__event_presentation_obj_li}
<div class="text-xs text-surface-600-400"> <div class="text-surface-600-400 text-xs">
<strong> <strong>
{#if type_code == 'poster'} {#if type_code == 'poster'}
<Image size="1em" class="inline" /> <Image size="1em" class="inline" />
@@ -361,27 +369,24 @@
<ul class="event_presentation_list max-w-full space-y-2"> <ul class="event_presentation_list max-w-full space-y-2">
{#each $lq__event_presentation_obj_li as event_presentation_obj (event_presentation_obj.event_presentation_id)} {#each $lq__event_presentation_obj_li as event_presentation_obj (event_presentation_obj.event_presentation_id)}
<li <li
class="border-b-2 border-gray-300 dark:border-gray-700 my-1 py-1 text-center md:text-left" class="my-1 border-b-2 border-gray-300 py-1 text-center md:text-left dark:border-gray-700">
>
<!-- The presentation information --> <!-- The presentation information -->
<div <div
class="event_presentation_datetime_name flex flex-row justify-evenly gap-4" class="event_presentation_datetime_name flex flex-row justify-evenly gap-4">
>
<!-- <div class="event_presentation_datetime_name"> --> <!-- <div class="event_presentation_datetime_name"> -->
{#if event_presentation_obj?.start_datetime} {#if event_presentation_obj?.start_datetime}
<span class="event_presentation_datetime" <span
class="event_presentation_datetime"
><strong ><strong
>{ae_util.iso_datetime_formatter( >{ae_util.iso_datetime_formatter(
event_presentation_obj?.start_datetime, event_presentation_obj?.start_datetime,
'time_12_short_no_leading' 'time_12_short_no_leading'
)}</strong )}</strong
></span ></span>
>
{/if} {/if}
<span class="event_presentation_name grow" <span class="event_presentation_name grow"
>{event_presentation_obj?.name}</span >{event_presentation_obj?.name}</span>
>
<!-- </div> --> <!-- </div> -->
<!-- Yes, this is kind of inefficient, but it works for now. --> <!-- Yes, this is kind of inefficient, but it works for now. -->
@@ -389,15 +394,18 @@
{#each $lq__event_presenter_obj_li as event_presenter_obj, index (event_presenter_obj.event_presenter_id)} {#each $lq__event_presenter_obj_li as event_presenter_obj, index (event_presenter_obj.event_presenter_id)}
{#if event_presenter_obj.event_presentation_id == event_presentation_obj.event_presentation_id} {#if event_presenter_obj.event_presentation_id == event_presentation_obj.event_presentation_id}
<span <span
class="event_presentation_single_presenter italic text-sm text-gray-500" class="event_presentation_single_presenter text-sm text-gray-500 italic">
>
{#if $lq__event_presenter_obj_li[index]?.given_name && $lq__event_presenter_obj_li[index]?.given_name != 'Group'} {#if $lq__event_presenter_obj_li[index]?.given_name && $lq__event_presenter_obj_li[index]?.given_name != 'Group'}
<User size="0.85em" class="inline" /> <User
size="0.85em"
class="inline" />
{$lq__event_presenter_obj_li[ {$lq__event_presenter_obj_li[
index index
]?.full_name} ]?.full_name}
{:else if $lq__event_presenter_obj_li[index]?.given_name == 'Group'} {:else if $lq__event_presenter_obj_li[index]?.given_name == 'Group'}
<Users size="0.85em" class="inline" /> <Users
size="0.85em"
class="inline" />
{$lq__event_presenter_obj_li[ {$lq__event_presenter_obj_li[
index index
]?.affiliations} ]?.affiliations}
@@ -413,8 +421,7 @@
<!-- Presentation-level files --> <!-- Presentation-level files -->
<Launcher_presentation_view <Launcher_presentation_view
lq__event_presentation_obj={event_presentation_obj} lq__event_presentation_obj={event_presentation_obj}
session_type={type_code} session_type={type_code} />
/>
<!-- The presenter list --> <!-- The presenter list -->
<!-- WHY: In poster mode, presenter names are already shown inline <!-- WHY: In poster mode, presenter names are already shown inline
@@ -426,29 +433,27 @@
so this has no visual cost for events that use presentation-level files. --> so this has no visual cost for events that use presentation-level files. -->
{#if $lq__event_presenter_obj_li && $lq__event_presenter_obj_li.length} {#if $lq__event_presenter_obj_li && $lq__event_presenter_obj_li.length}
<ul class="event_presentation_presenter_list"> <ul
class="event_presentation_presenter_list">
{#each $lq__event_presenter_obj_li as event_presenter_obj (event_presenter_obj.event_presenter_id)} {#each $lq__event_presenter_obj_li as event_presenter_obj (event_presenter_obj.event_presenter_id)}
{#if event_presenter_obj.event_presentation_id == event_presentation_obj.event_presentation_id} {#if event_presenter_obj.event_presentation_id == event_presentation_obj.event_presentation_id}
<li <li
class=" class="
border border-transparent hover:bg-surface-100-900 hover:border-surface-400-600
rounded-lg rounded-lg
hover:bg-surface-100-900 border
hover:border-surface-400-600 border-transparent
p-1 p-1
transition-all transition-all
" ">
>
{#if type_code == 'poster'} {#if type_code == 'poster'}
<Launcher_presenter_view_posters <Launcher_presenter_view_posters
lq__event_presenter_obj={event_presenter_obj} lq__event_presenter_obj={event_presenter_obj}
hide_name={true} hide_name={true} />
/>
{:else} {:else}
<Launcher_presenter_view <Launcher_presenter_view
lq__event_presenter_obj={event_presenter_obj} lq__event_presenter_obj={event_presenter_obj}
session_type={type_code} session_type={type_code} />
/>
{/if} {/if}
</li> </li>
{/if} {/if}

View File

@@ -80,43 +80,42 @@
The outer div mirrors the grow/h-full/w-full contract expected by The outer div mirrors the grow/h-full/w-full contract expected by
+layout.svelte so this slots in as a drop-in replacement for the oral view. +layout.svelte so this slots in as a drop-in replacement for the oral view.
--> -->
<div class="poster_session_view flex flex-col gap-0 w-full h-full overflow-hidden"> <div
class="poster_session_view flex h-full w-full flex-col gap-0 overflow-hidden">
{#if $events_sess.launcher?.loading__session_id_status} {#if $events_sess.launcher?.loading__session_id_status}
<span class="absolute top-0 right-0 text-sm text-gray-400 flex items-center gap-1 p-1 z-10"> <span
class="absolute top-0 right-0 z-10 flex items-center gap-1 p-1 text-sm text-gray-400">
<LoaderCircle size="1em" class="animate-spin" /> <LoaderCircle size="1em" class="animate-spin" />
Loading... Loading...
</span> </span>
{/if} {/if}
{#if $lq__event_session_obj && $lq__event_session_obj.event_session_id} {#if $lq__event_session_obj && $lq__event_session_obj.event_session_id}
<!-- ── Compact session identity strip ──────────────────────────────── --> <!-- ── Compact session identity strip ──────────────────────────────── -->
<header <header
class=" class="
poster_session_header poster_session_header
flex flex-row gap-2 items-center justify-between border-surface-300 dark:border-surface-600 bg-surface-100/60 dark:bg-surface-800/60 flex
px-2 py-1.5 shrink-0 flex-row
border-b-2 border-surface-300 dark:border-surface-600 items-center justify-between gap-2
bg-surface-100/60 dark:bg-surface-800/60 border-b-2 px-2
shrink-0 py-1.5
" ">
>
<h2 <h2
class="text-base font-bold line-clamp-1 grow min-w-0" class="line-clamp-1 min-w-0 grow text-base font-bold"
title={$lq__event_session_obj.name} title={$lq__event_session_obj.name}>
> <Images
<Images size="1em" class="inline mr-1.5 text-primary-500 opacity-70" /> size="1em"
class="text-primary-500 mr-1.5 inline opacity-70" />
{$lq__event_session_obj.name} {$lq__event_session_obj.name}
</h2> </h2>
<span class="flex flex-row gap-1.5 items-center shrink-0"> <span class="flex shrink-0 flex-row items-center gap-1.5">
<!-- Poster count badge --> <!-- Poster count badge -->
{#if poster_count > 0} {#if poster_count > 0}
<span <span
class="text-xs font-mono font-semibold text-surface-500 bg-surface-200 dark:bg-surface-700 px-2 py-0.5 rounded-full" class="text-surface-500 bg-surface-200 dark:bg-surface-700 rounded-full px-2 py-0.5 font-mono text-xs font-semibold"
title="Number of posters in this session" title="Number of posters in this session">
>
{poster_count}&times; {poster_count}&times;
</span> </span>
{/if} {/if}
@@ -124,9 +123,8 @@
<!-- Session code --> <!-- Session code -->
{#if $lq__event_session_obj.code} {#if $lq__event_session_obj.code}
<span <span
class="text-xs font-mono font-bold text-surface-400 bg-surface-100 dark:bg-surface-800 px-2 py-0.5 rounded border border-surface-300 dark:border-surface-600" class="text-surface-400 bg-surface-100 dark:bg-surface-800 border-surface-300 dark:border-surface-600 rounded border px-2 py-0.5 font-mono text-xs font-bold"
title="Session code: {$lq__event_session_obj.code}" title="Session code: {$lq__event_session_obj.code}">
>
{$lq__event_session_obj.code} {$lq__event_session_obj.code}
</span> </span>
{/if} {/if}
@@ -135,16 +133,18 @@
<!-- ── Session-level files (rarely present — program, schedule, etc.) ── --> <!-- ── Session-level files (rarely present — program, schedule, etc.) ── -->
{#if $lq__event_file_obj_li && $lq__event_file_obj_li.length} {#if $lq__event_file_obj_li && $lq__event_file_obj_li.length}
<section class="session_resources_strip px-2 pb-2 pt-1 border-b border-surface-200 dark:border-surface-700 shrink-0"> <section
<p class="text-[10px] text-surface-500 uppercase font-bold tracking-wider mb-1 opacity-60"> class="session_resources_strip border-surface-200 dark:border-surface-700 shrink-0 border-b px-2 pt-1 pb-2">
<p
class="text-surface-500 mb-1 text-[10px] font-bold tracking-wider uppercase opacity-60">
Session Resources: Session Resources:
</p> </p>
<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.show_content__hidden_files && class:hidden={!$events_loc.launcher
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}
@@ -163,8 +163,7 @@
} }
bind:modal__event_file_obj={ bind:modal__event_file_obj={
$events_sess.launcher.modal__event_file_obj $events_sess.launcher.modal__event_file_obj
} } />
/>
</li> </li>
{/each} {/each}
</ul> </ul>
@@ -174,18 +173,20 @@
<!-- ── Poster card grid ────────────────────────────────────────────── --> <!-- ── Poster card grid ────────────────────────────────────────────── -->
{#if $lq__event_presentation_obj_li === undefined} {#if $lq__event_presentation_obj_li === undefined}
<!-- Still resolving from Dexie --> <!-- Still resolving from Dexie -->
<div class="flex items-center justify-center gap-2 p-10 opacity-40 grow"> <div
class="flex grow items-center justify-center gap-2 p-10 opacity-40">
<LoaderCircle size="2em" class="animate-spin" /> <LoaderCircle size="2em" class="animate-spin" />
<span>Loading posters…</span> <span>Loading posters…</span>
</div> </div>
{:else if $lq__event_presentation_obj_li.length === 0} {:else if $lq__event_presentation_obj_li.length === 0}
<!-- Loaded but empty --> <!-- Loaded but empty -->
<div class="flex flex-col items-center justify-center gap-3 p-12 opacity-40 text-center grow"> <div
class="flex grow flex-col items-center justify-center gap-3 p-12 text-center opacity-40">
<Image size="3em" /> <Image size="3em" />
<p class="text-lg font-medium">No posters in this session yet.</p> <p class="text-lg font-medium">
No posters in this session yet.
</p>
</div> </div>
{:else} {:else}
<!-- <!--
Grid: 1 col on phone, 2 on tablet (sm), 3 on large desktop (xl). Grid: 1 col on phone, 2 on tablet (sm), 3 on large desktop (xl).
@@ -197,15 +198,14 @@
class=" class="
poster_card_grid poster_card_grid
grid grid
grow
grid-cols-1 grid-cols-1
gap-3
overflow-y-auto
p-3
sm:grid-cols-2 sm:grid-cols-2
xl:grid-cols-3 xl:grid-cols-3
gap-3 ">
p-3
overflow-y-auto
grow
"
>
{#each $lq__event_presentation_obj_li as presentation, i (presentation.event_presentation_id)} {#each $lq__event_presentation_obj_li as presentation, i (presentation.event_presentation_id)}
{@const presenters_for_this = ( {@const presenters_for_this = (
$lq__event_presenter_obj_li ?? [] $lq__event_presenter_obj_li ?? []
@@ -223,18 +223,17 @@
<li <li
class=" class="
poster_card poster_card
relative flex flex-col gap-2 border-surface-200 dark:border-surface-700 dark:bg-surface-900 hover:border-primary-400
rounded-xl dark:hover:border-primary-500
border border-surface-200 dark:border-surface-700 relative flex min-h-40
bg-white dark:bg-surface-900 flex-col gap-2
hover:border-primary-400 dark:hover:border-primary-500 rounded-xl border
active:scale-[0.98] active:shadow-sm bg-white p-3
transition-all duration-150 shadow-sm transition-all
shadow-sm hover:shadow-md duration-150 hover:shadow-md
p-3 active:scale-[0.98]
min-h-40 active:shadow-sm
" ">
>
<!-- <!--
Top-right badge: prefer the presentation code (e.g. "P-042") Top-right badge: prefer the presentation code (e.g. "P-042")
as it matches physical poster board numbers; fall back to as it matches physical poster board numbers; fall back to
@@ -242,18 +241,17 @@
--> -->
<span <span
class=" class="
absolute top-2 right-2 text-primary-600 dark:text-primary-400 bg-primary-50
text-xs font-mono font-bold leading-tight dark:bg-primary-950/60 border-primary-200 dark:border-primary-800 absolute
text-primary-600 dark:text-primary-400 top-2 right-2
bg-primary-50 dark:bg-primary-950/60 rounded-full border
border border-primary-200 dark:border-primary-800 px-2 py-0.5 font-mono
px-2 py-0.5 text-xs leading-tight
rounded-full font-bold
" "
title="{presentation.code title={presentation.code
? 'Poster code: ' + presentation.code ? 'Poster code: ' + presentation.code
: 'Poster #' + (i + 1)}" : 'Poster #' + (i + 1)}>
>
{presentation.code || '#' + (i + 1)} {presentation.code || '#' + (i + 1)}
</span> </span>
@@ -266,15 +264,14 @@
<h3 <h3
class=" class="
poster_title poster_title
text-base md:text-lg
font-bold leading-snug
line-clamp-3
text-surface-950 dark:text-surface-50 text-surface-950 dark:text-surface-50
line-clamp-3 grow
pr-14 pr-14
grow text-base leading-snug
font-bold
md:text-lg
" "
title={presentation.name} title={presentation.name}>
>
{presentation.name} {presentation.name}
</h3> </h3>
@@ -286,37 +283,38 @@
"Group" presenter whose full name is stored in affiliations. "Group" presenter whose full name is stored in affiliations.
--> -->
{#if presenters_for_this.length} {#if presenters_for_this.length}
<div class="presenter_info space-y-0.5 shrink-0"> <div class="presenter_info shrink-0 space-y-0.5">
{#each presenters_for_this as presenter (presenter.event_presenter_id)} {#each presenters_for_this as presenter (presenter.event_presenter_id)}
<p <p
class=" class="
flex flex-row flex-wrap items-baseline gap-x-1.5 text-surface-500 dark:text-surface-400 flex flex-row flex-wrap
text-sm text-surface-500 dark:text-surface-400 items-baseline gap-x-1.5 text-sm
leading-snug leading-snug
" ">
>
{#if presenter.given_name && presenter.given_name !== 'Group'} {#if presenter.given_name && presenter.given_name !== 'Group'}
<User size="0.7em" class="opacity-50 shrink-0 mt-px" /> <User
size="0.7em"
class="mt-px shrink-0 opacity-50" />
<span <span
class="font-medium text-surface-700 dark:text-surface-300" class="text-surface-700 dark:text-surface-300 font-medium"
>{presenter.full_name}</span >{presenter.full_name}</span>
>
{#if presenter.affiliations} {#if presenter.affiliations}
<span <span
class="italic text-xs opacity-70 line-clamp-1 min-w-0" class="line-clamp-1 min-w-0 text-xs italic opacity-70"
title={presenter.affiliations} title={presenter.affiliations}>
>
{presenter.affiliations} {presenter.affiliations}
</span> </span>
{/if} {/if}
{:else if presenter.given_name === 'Group'} {:else if presenter.given_name === 'Group'}
<Users size="0.7em" class="opacity-50 shrink-0 mt-px" /> <Users
size="0.7em"
class="mt-px shrink-0 opacity-50" />
<span <span
class="font-medium text-surface-700 dark:text-surface-300" class="text-surface-700 dark:text-surface-300 font-medium"
>{presenter.affiliations}</span >{presenter.affiliations}</span>
>
{:else} {:else}
<span class="opacity-40 text-xs"></span> <span class="text-xs opacity-40"
></span>
{/if} {/if}
</p> </p>
{/each} {/each}
@@ -329,12 +327,12 @@
presenter level, or both — render both sub-components so presenter level, or both — render both sub-components so
neither source is missed. neither source is missed.
--> -->
<div class="poster_actions flex flex-col gap-1 mt-auto pt-1 shrink-0"> <div
class="poster_actions mt-auto flex shrink-0 flex-col gap-1 pt-1">
<!-- Presentation-level files (the most common attachment point) --> <!-- Presentation-level files (the most common attachment point) -->
<Launcher_presentation_view <Launcher_presentation_view
lq__event_presentation_obj={presentation} lq__event_presentation_obj={presentation}
session_type="poster" session_type="poster" />
/>
<!-- <!--
Presenter-level files (some events attach the PDF here instead). Presenter-level files (some events attach the PDF here instead).
@@ -344,18 +342,16 @@
{#each presenters_for_this as presenter (presenter.event_presenter_id)} {#each presenters_for_this as presenter (presenter.event_presenter_id)}
<Launcher_presenter_view_posters <Launcher_presenter_view_posters
lq__event_presenter_obj={presenter} lq__event_presenter_obj={presenter}
hide_name={true} hide_name={true} />
/>
{/each} {/each}
</div> </div>
</li> </li>
{/each} {/each}
</ul> </ul>
{/if} {/if}
{:else} {:else}
<!-- No session selected or still loading --> <!-- No session selected or still loading -->
<div class="flex items-center justify-center gap-2 p-8 opacity-40 grow"> <div class="flex grow items-center justify-center gap-2 p-8 opacity-40">
<LoaderCircle size="1em" class="animate-spin" /> <LoaderCircle size="1em" class="animate-spin" />
<span>No session selected</span> <span>No session selected</span>
</div> </div>

View File

@@ -30,10 +30,11 @@
let { log_lvl = $bindable(0) }: Props = $props(); let { log_lvl = $bindable(0) }: Props = $props();
</script> </script>
<div class="w-full max-w-full flex flex-col gap-1 items-center justify-center"> <div class="flex w-full max-w-full flex-col items-center justify-center gap-1">
<!-- ── Visibility toggles — edit mode only ── --> <!-- ── Visibility toggles — edit mode only ── -->
{#if $ae_loc.edit_mode} {#if $ae_loc.edit_mode}
<div class="w-full max-w-full flex flex-row gap-1 items-center justify-center"> <div
class="flex w-full max-w-full flex-row items-center justify-center gap-1">
<button <button
type="button" type="button"
onclick={() => { onclick={() => {
@@ -46,13 +47,12 @@
} }
}} }}
class=" class="
btn btn-sm text-xs btn btn-sm preset-tonal-tertiary
w-1/2 max-w-1/2 hover:preset-filled-tertiary-500 w-1/2
preset-tonal-tertiary hover:preset-filled-tertiary-500 max-w-1/2 text-xs
transition-all transition-all
" "
title="Toggle visibility of hidden and draft files in the launcher file list." title="Toggle visibility of hidden and draft files in the launcher file list.">
>
{#if $events_loc.launcher.show_content__hidden_files} {#if $events_loc.launcher.show_content__hidden_files}
<EyeOff size="0.85em" class="m-1 text-neutral-800/80" /> <EyeOff size="0.85em" class="m-1 text-neutral-800/80" />
Hide Files Hide Files
@@ -69,13 +69,12 @@
!$events_loc.launcher.show_content__hidden_sessions; !$events_loc.launcher.show_content__hidden_sessions;
}} }}
class=" class="
btn btn-sm text-xs btn btn-sm preset-tonal-tertiary
w-1/2 max-w-1/2 hover:preset-filled-tertiary-500 w-1/2
preset-tonal-tertiary hover:preset-filled-tertiary-500 max-w-1/2 text-xs
transition-all transition-all
" "
title="Toggle visibility of hidden and cancelled sessions in the launcher session list." title="Toggle visibility of hidden and cancelled sessions in the launcher session list.">
>
{#if $events_loc.launcher.show_content__hidden_sessions} {#if $events_loc.launcher.show_content__hidden_sessions}
<EyeOff size="0.85em" class="m-1 text-neutral-800/80" /> <EyeOff size="0.85em" class="m-1 text-neutral-800/80" />
Hide Sessions Hide Sessions
@@ -88,7 +87,8 @@
{/if} {/if}
<!-- ── Accessibility controls — always visible ── --> <!-- ── Accessibility controls — always visible ── -->
<div class="w-full max-w-full flex flex-row gap-1 items-center justify-center"> <div
class="flex w-full max-w-full flex-row items-center justify-center gap-1">
<!-- Font size cycler: default → larger → smaller → default --> <!-- Font size cycler: default → larger → smaller → default -->
<button <button
type="button" type="button"
@@ -103,22 +103,28 @@
} }
}} }}
class=" class="
btn btn-sm text-xs btn btn-sm preset-tonal-tertiary
hover:preset-filled-tertiary-500 group
w-1/2 max-w-1/2 w-1/2 max-w-1/2
preset-tonal-tertiary hover:preset-filled-tertiary-500 text-xs transition-all
transition-all group
" "
title="Cycle font size (default → larger → smaller). Current: {$ae_loc.font_size_mode ?? 'default'}" title="Cycle font size (default → larger → smaller). Current: {$ae_loc.font_size_mode ??
> 'default'}">
{#if !$ae_loc.font_size_mode || $ae_loc.font_size_mode === 'default'} {#if !$ae_loc.font_size_mode || $ae_loc.font_size_mode === 'default'}
<span class="font-bold text-sm font-mono leading-none m-1">A</span> <span class="m-1 font-mono text-sm leading-none font-bold"
<span class="hidden group-hover:inline-block text-xs">Font: Normal</span> >A</span>
<span class="hidden text-xs group-hover:inline-block"
>Font: Normal</span>
{:else if $ae_loc.font_size_mode === 'larger'} {:else if $ae_loc.font_size_mode === 'larger'}
<span class="font-bold text-base font-mono leading-none m-1">A+</span> <span class="m-1 font-mono text-base leading-none font-bold"
<span class="hidden group-hover:inline-block text-xs">Font: Larger</span> >A+</span>
<span class="hidden text-xs group-hover:inline-block"
>Font: Larger</span>
{:else} {:else}
<span class="font-bold text-xs font-mono leading-none m-1">A</span> <span class="m-1 font-mono text-xs leading-none font-bold"
<span class="hidden group-hover:inline-block text-xs">Font: Smaller</span> >A</span>
<span class="hidden text-xs group-hover:inline-block"
>Font: Smaller</span>
{/if} {/if}
</button> </button>
@@ -126,16 +132,17 @@
<button <button
type="button" type="button"
onclick={() => { onclick={() => {
$ae_loc.theme_mode = $ae_loc.theme_mode === 'dark' ? 'light' : 'dark'; $ae_loc.theme_mode =
$ae_loc.theme_mode === 'dark' ? 'light' : 'dark';
}} }}
class=" class="
btn btn-sm text-xs btn btn-sm preset-tonal-tertiary
hover:preset-filled-tertiary-500 group
w-1/2 max-w-1/2 w-1/2 max-w-1/2
preset-tonal-tertiary hover:preset-filled-tertiary-500 text-xs transition-all
transition-all group
" "
title="Toggle light/dark display mode. Current: {$ae_loc.theme_mode ?? 'light'}" title="Toggle light/dark display mode. Current: {$ae_loc.theme_mode ??
> 'light'}">
{#if $ae_loc.theme_mode === 'dark'} {#if $ae_loc.theme_mode === 'dark'}
<Moon class="m-1 inline-block" size="1em" /> <Moon class="m-1 inline-block" size="1em" />
<span class="hidden group-hover:inline-block">Dark Mode</span> <span class="hidden group-hover:inline-block">Dark Mode</span>

View File

@@ -148,23 +148,24 @@
<!-- text-neutral-800/80 --> <!-- text-neutral-800/80 -->
<div <div
class=" class="
w-full max-w-full flex w-full
flex flex-col md:flex-row flex-wrap gap-1 items-center justify-center max-w-full flex-col flex-wrap items-center justify-center gap-1 md:flex-row
" ">
>
{#if $lq__event_location_obj_li && $lq__event_location_obj_li.length > 0} {#if $lq__event_location_obj_li && $lq__event_location_obj_li.length > 0}
<div class="text-xs text-surface-600-400"> <div class="text-surface-600-400 text-xs">
<strong> <strong>
Location: Location:
<span <span
class:hidden={!$ae_loc.trusted_access || !$ae_loc.edit_mode} class:hidden={!$ae_loc.trusted_access ||
> !$ae_loc.edit_mode}>
({$lq__event_location_obj_li?.length}&times;) ({$lq__event_location_obj_li?.length}&times;)
</span> </span>
<!-- This should fade out once the data is loaded. --> <!-- This should fade out once the data is loaded. -->
{#await ae_promises[slct_event_location_id ?? '']} {#await ae_promises[slct_event_location_id ?? '']}
<LoaderCircle size="0.85em" class="inline animate-spin text-blue-500" /> <LoaderCircle
size="0.85em"
class="inline animate-spin text-blue-500" />
{:then result} {:then result}
<Check size="0.85em" class="inline text-green-500/80" /> <Check size="0.85em" class="inline text-green-500/80" />
{/await} {/await}
@@ -172,7 +173,7 @@
</div> </div>
<select <select
class="select text-xs p-1 max-w-42" class="select max-w-42 p-1 text-xs"
bind:value={slct_event_location_id} bind:value={slct_event_location_id}
onchange={async () => { onchange={async () => {
// console.log(`slct_event_location_id:`, slct_event_location_id); // console.log(`slct_event_location_id:`, slct_event_location_id);
@@ -222,9 +223,8 @@
loading__session_li_status = null; loading__session_li_status = null;
// goto(new_url, {replaceState: true}); // Updates the URL without reloading the page // goto(new_url, {replaceState: true}); // Updates the URL without reloading the page
goto(new_url, { replaceState: false }); // Updates the URL history without reloading the page goto(new_url, { replaceState: false }); // Updates the URL history without reloading the page
}} }}>
> <option value="" class="text-surface-800-200 italic">
<option value="" class="italic text-surface-800-200">
-- select -- -- select --
</option> </option>
{#each $lq__event_location_obj_li as event_location_obj (event_location_obj.event_location_id)} {#each $lq__event_location_obj_li as event_location_obj (event_location_obj.event_location_id)}

View File

@@ -91,7 +91,14 @@
events_trigger events_trigger
} from '$lib/stores/ae_events_stores'; } from '$lib/stores/ae_events_stores';
import { events_func } from '$lib/ae_events/ae_events_functions'; import { events_func } from '$lib/ae_events/ae_events_functions';
import { CalendarCheck, CalendarDays, Check, EyeOff, Image, LoaderCircle } from '@lucide/svelte'; import {
CalendarCheck,
CalendarDays,
Check,
EyeOff,
Image,
LoaderCircle
} from '@lucide/svelte';
// export let slct__event_session_id: any; // export let slct__event_session_id: any;
// *** Functions and Logic // *** Functions and Logic
@@ -121,7 +128,9 @@
const event_session_id = String(trigger_reload__event_session_obj_id); const event_session_id = String(trigger_reload__event_session_obj_id);
if (log_lvl) { if (log_lvl) {
console.log(`[UI Trace] trigger_reload changed to: ${event_session_id}`); console.log(
`[UI Trace] trigger_reload changed to: ${event_session_id}`
);
} }
untrack(() => { untrack(() => {
@@ -154,7 +163,9 @@
keepFocus: true keepFocus: true
}).then(() => { }).then(() => {
if (log_lvl) if (log_lvl)
console.log(`🏁 [Trace] Navigation Roundtrip: ${(performance.now() - start).toFixed(2)}ms`); console.log(
`🏁 [Trace] Navigation Roundtrip: ${(performance.now() - start).toFixed(2)}ms`
);
}); });
}); });
} }
@@ -202,23 +213,24 @@
<div <div
class=" class="
w-full max-w-80 flex w-full
flex flex-col flex-wrap gap-1 items-center justify-start md:justify-center max-w-80 flex-col flex-wrap items-center justify-start gap-1 md:justify-center
" ">
>
{#if $lq__event_session_obj_li && $lq__event_session_obj_li.length > 0} {#if $lq__event_session_obj_li && $lq__event_session_obj_li.length > 0}
<div class="text-xs text-surface-600-400"> <div class="text-surface-600-400 text-xs">
<strong> <strong>
Sessions: Sessions:
<span <span
class:hidden={!$ae_loc.trusted_access || !$ae_loc.edit_mode} class:hidden={!$ae_loc.trusted_access ||
> !$ae_loc.edit_mode}>
({$lq__event_session_obj_li?.length}&times;) ({$lq__event_session_obj_li?.length}&times;)
</span> </span>
<!-- This should fade out once the data is loaded. --> <!-- This should fade out once the data is loaded. -->
{#await ae_promises.slct__event_session_id} {#await ae_promises.slct__event_session_id}
<LoaderCircle size="0.85em" class="inline animate-spin text-blue-500" /> <LoaderCircle
size="0.85em"
class="inline animate-spin text-blue-500" />
{:then result} {:then result}
<Check size="0.85em" class="inline text-green-500/80" /> <Check size="0.85em" class="inline text-green-500/80" />
{/await} {/await}
@@ -227,22 +239,20 @@
<ul <ul
class=" class="
m-0 flex
w-full max-w-full w-full max-w-full
p-0 m-0 flex-col items-start justify-start gap-0 p-0
flex flex-col gap-0 items-start justify-start ">
"
>
{#each $lq__event_session_obj_li as event_session_obj (event_session_obj.event_session_id)} {#each $lq__event_session_obj_li as event_session_obj (event_session_obj.event_session_id)}
<li <li
class=" class="
session-item session-item
relative relative
p-0 m-0 m-0 w-full
w-full max-w-full max-w-full p-0
" "
class:session-active={slct__event_session_id === class:session-active={slct__event_session_id ===
event_session_obj?.id} event_session_obj?.id}>
>
<button <button
type="button" type="button"
onmouseenter={() => { onmouseenter={() => {
@@ -272,17 +282,17 @@
class=" class="
session-btn session-btn
btn btn-sm btn btn-sm
focus-visible:ring-2 focus-visible:ring-primary-400 focus-visible:ring-offset-1 focus-visible:ring-primary-400 m-0 flex
text-sm w-full
w-full max-w-full max-w-full flex-row
text-left items-center
m-0 justify-start
px-1.5 py-1 rounded-md px-1.5
rounded-md py-1
flex flex-row items-center justify-start text-left text-sm transition-colors duration-200
transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-offset-1
" "
class:preset-filled-primary={slct__event_session_id === class:preset-filled-primary={slct__event_session_id ===
event_session_obj?.id} event_session_obj?.id}
@@ -298,8 +308,7 @@
event_session_obj?.hide_event_launcher)} event_session_obj?.hide_event_launcher)}
class:opacity-40={event_session_obj?.hide || class:opacity-40={event_session_obj?.hide ||
event_session_obj?.hide_event_launcher} 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, $events_loc.launcher.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.
@@ -311,8 +320,7 @@
When revealed, dimmed (opacity-40) with eye-slash icon. --> When revealed, dimmed (opacity-40) with eye-slash icon. -->
<span <span
class="border-r border-surface-400-600 pr-1 min-w-20 shrink-0" class="border-surface-400-600 min-w-20 shrink-0 border-r pr-1">
>
{#if slct__event_session_id === event_session_obj?.id} {#if slct__event_session_id === event_session_obj?.id}
<CalendarCheck size="0.85em" class="inline" /> <CalendarCheck size="0.85em" class="inline" />
{:else} {:else}
@@ -321,8 +329,7 @@
<span <span
class="text-xs" class="text-xs"
class:hidden={slct__event_session_id === class:hidden={slct__event_session_id ===
event_session_obj?.id} event_session_obj?.id}>
>
{ae_util.iso_datetime_formatter( {ae_util.iso_datetime_formatter(
event_session_obj?.start_datetime, event_session_obj?.start_datetime,
'week_medium' 'week_medium'
@@ -339,20 +346,28 @@
<span <span
class=" class="
session-name session-name
grow text-sm min-w-0 grow
min-w-0 text-sm
" ">
>
{#if event_session_obj?.type_code == 'poster'} {#if event_session_obj?.type_code == 'poster'}
<span title="Digital Poster Session"><Image size="0.85em" class="inline mr-1 text-primary-500" /></span> <span title="Digital Poster Session"
><Image
size="0.85em"
class="text-primary-500 mr-1 inline" /></span>
{/if} {/if}
<!-- Distinct icon styles distinguish the two hidden states: <!-- Distinct icon styles distinguish the two hidden states:
amber = hide (globally hidden — draft, cancelled, or admin-only) amber = hide (globally hidden — draft, cancelled, or admin-only)
muted = hide_event_launcher (suppressed in Launcher view only) --> muted = hide_event_launcher (suppressed in Launcher view only) -->
{#if event_session_obj?.hide} {#if event_session_obj?.hide}
<span title="Hidden session"><EyeOff size="0.85em" class="inline mr-1 text-warning-600" /></span> <span title="Hidden session"
><EyeOff
size="0.85em"
class="text-warning-600 mr-1 inline" /></span>
{:else if event_session_obj?.hide_event_launcher} {:else if event_session_obj?.hide_event_launcher}
<span title="Hidden from Launcher"><EyeOff size="0.85em" class="inline mr-1 opacity-60" /></span> <span title="Hidden from Launcher"
><EyeOff
size="0.85em"
class="mr-1 inline opacity-60" /></span>
{/if} {/if}
{event_session_obj?.name} {event_session_obj?.name}
</span> </span>
@@ -453,7 +468,9 @@
background-color: #f1f5f9; /* slate-100 — solid light surface */ background-color: #f1f5f9; /* slate-100 — solid light surface */
border-left: 3px solid rgb(var(--color-primary-500, 99 102 241)); border-left: 3px solid rgb(var(--color-primary-500, 99 102 241));
border-radius: 0.375rem; border-radius: 0.375rem;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15), 0 1px 4px rgba(0, 0, 0, 0.08); box-shadow:
0 4px 20px rgba(0, 0, 0, 0.15),
0 1px 4px rgba(0, 0, 0, 0.08);
} }
/* Dark mode overlay — solid dark surface, light readable text */ /* Dark mode overlay — solid dark surface, light readable text */
@@ -461,7 +478,9 @@
:global(.dark) .session-item:not(.session-active):focus-within .session-btn { :global(.dark) .session-item:not(.session-active):focus-within .session-btn {
background-color: #1e293b; /* slate-800 */ background-color: #1e293b; /* slate-800 */
color: #f1f5f9; /* slate-100 */ color: #f1f5f9; /* slate-100 */
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.6), 0 1px 4px rgba(0, 0, 0, 0.4); box-shadow:
0 4px 24px rgba(0, 0, 0, 0.6),
0 1px 4px rgba(0, 0, 0, 0.4);
} }
/* ── Session name: single-line truncated at rest ── */ /* ── Session name: single-line truncated at rest ── */