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.
if (
$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_trigger_send = 'trigger';
@@ -57,135 +58,126 @@
icon={LayoutGrid}
bind:state={$events_loc.launcher.section_state__app_modes}
{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 -->
<div class="col-span-full flex flex-col gap-3">
<!-- 0. Oral / Poster Kiosk Mode Preset Toggle -->
<div class="flex flex-col gap-1">
<p class="text-[9px] font-bold uppercase opacity-50 ml-1">Session Mode Preset</p>
<div class="grid grid-cols-2 gap-1 bg-surface-500/5 p-1 rounded-lg">
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">
Session Mode Preset
</p>
<div class="bg-surface-500/5 grid grid-cols-2 gap-1 rounded-lg p-1">
<button
type="button"
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-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" />
Oral / Default
</button>
<button
type="button"
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-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" />
Poster Kiosk
</button>
</div>
{#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}
</div>
<!-- 1. App Mode Selection -->
<div class="flex flex-col gap-1">
<p class="text-[9px] font-bold uppercase opacity-50 ml-1"
>Operational Environment</p
>
<div class="grid grid-cols-3 gap-1 bg-surface-500/5 p-1 rounded-lg">
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">
Operational Environment
</p>
<div class="bg-surface-500/5 grid grid-cols-3 gap-1 rounded-lg p-1">
<button
type="button"
onclick={() => ($events_loc.launcher.app_mode = 'default')}
class="btn btn-xs text-[9px] font-bold"
class:preset-filled-primary={$events_loc.launcher
.app_mode === 'default'}
class:preset-tonal-surface={$events_loc.launcher.app_mode !==
'default'}
class:preset-tonal-surface={$events_loc.launcher
.app_mode !== 'default'}
title="Default standard web browser (Chromium, Firefox, Safari based) launcher, for remote presenters and testing before being onsite">
Web</button
>
Web</button>
<button
type="button"
onclick={() => ($events_loc.launcher.app_mode = 'native')}
class="btn btn-xs text-[9px] font-bold"
class:preset-filled-primary={$events_loc.launcher
.app_mode === 'native'}
class:preset-tonal-surface={$events_loc.launcher.app_mode !==
'native'}
title="Native Electron based app launcher, for onsite presenters in session rooms">App</button
>
class:preset-tonal-surface={$events_loc.launcher
.app_mode !== 'native'}
title="Native Electron based app launcher, for onsite presenters in session rooms"
>App</button>
<button
type="button"
onclick={() => ($events_loc.launcher.app_mode = 'onsite')}
class="btn btn-xs text-[9px] font-bold"
class:preset-filled-primary={$events_loc.launcher
.app_mode === 'onsite'}
class:preset-tonal-surface={$events_loc.launcher.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
>
class:preset-tonal-surface={$events_loc.launcher
.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>
</div>
</div>
<!-- 2. UI Layout Toggles -->
<div
class="flex flex-col gap-1 border-t border-surface-500/10 pt-2 mt-1"
>
<p class="text-[9px] font-bold uppercase opacity-50 ml-1"
>Interface Visibility</p
>
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">
Interface Visibility
</p>
<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
type="checkbox"
bind:checked={
$events_loc.launcher.hide__launcher_header
}
class="checkbox checkbox-sm"
/>
<span class="text-xs group-hover:text-primary-500"
>Hide Header</span
>
class="checkbox checkbox-sm" />
<span class="group-hover:text-primary-500 text-xs"
>Hide Header</span>
</label>
<label class="flex items-center gap-2 cursor-pointer group">
<label class="group flex cursor-pointer items-center gap-2">
<input
type="checkbox"
bind:checked={$events_loc.launcher.hide__launcher_menu}
class="checkbox checkbox-sm"
/>
<span class="text-xs group-hover:text-primary-500"
>Hide Menu</span
>
class="checkbox checkbox-sm" />
<span class="group-hover:text-primary-500 text-xs"
>Hide Menu</span>
</label>
<label class="flex items-center gap-2 cursor-pointer group">
<label class="group flex cursor-pointer items-center gap-2">
<input
type="checkbox"
bind:checked={
$events_loc.launcher.hide__launcher_footer
}
class="checkbox checkbox-sm"
/>
<span class="text-xs group-hover:text-primary-500"
>Hide Footer</span
>
class="checkbox checkbox-sm" />
<span class="group-hover:text-primary-500 text-xs"
>Hide Footer</span>
</label>
<label class="flex items-center gap-2 cursor-pointer group">
<label class="group flex cursor-pointer items-center gap-2">
<input
type="checkbox"
bind:checked={
$events_loc.launcher.hide__session_datetimes
}
class="checkbox checkbox-sm"
/>
<span class="text-xs group-hover:text-primary-500"
>Hide Times</span
>
class="checkbox checkbox-sm" />
<span class="group-hover:text-primary-500 text-xs"
>Hide Times</span>
</label>
</div>
</div>
@@ -202,8 +194,7 @@
$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 Format:
<strong>{$events_loc.launcher.time_hours}-hour</strong>
@@ -212,35 +203,30 @@
<!-- 4. Advanced Toggles (Edit Mode Only) -->
{#if $ae_loc.edit_mode}
<div
class="col-span-full border-t border-surface-500/20 pt-3 mt-1 flex flex-col gap-2"
>
<p class="text-[9px] font-bold uppercase opacity-50 ml-1"
>Technical Layout</p
>
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">
Technical Layout
</p>
<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
type="checkbox"
bind:checked={$events_loc.launcher.hide__ws_element}
class="checkbox checkbox-sm"
/>
class="checkbox checkbox-sm" />
<span
class="text-xs group-hover:text-primary-500 italic"
>Hide WebSocket Debugger</span
>
class="group-hover:text-primary-500 text-xs italic"
>Hide WebSocket Debugger</span>
</label>
<label class="flex items-center gap-2 cursor-pointer group">
<label class="group flex cursor-pointer items-center gap-2">
<input
type="checkbox"
bind:checked={
$events_loc.launcher.hide__modal_header_title
}
class="checkbox checkbox-sm"
/>
class="checkbox checkbox-sm" />
<span
class="text-xs group-hover:text-primary-500 italic"
>Hide Poster Modal Title</span
>
class="group-hover:text-primary-500 text-xs italic"
>Hide Poster Modal Title</span>
</label>
</div>
</div>

View File

@@ -2,7 +2,15 @@
import { ae_loc } from '$lib/stores/ae_stores';
import { events_loc, events_sess } from '$lib/stores/ae_events_stores';
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 {
on_expand?: () => void;
}
@@ -20,41 +28,35 @@
{on_expand}
description="Mode: {$events_loc.launcher?.controller} | WS: {ws_connected
? 'Connected'
: 'Offline'}"
>
: 'Offline'}">
<!-- Content omitted for brevity, preserved in file -->
<div class="col-span-full flex flex-col gap-3">
<!-- 1. Connection Status Badge -->
<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">
<Plug size="1em" class="opacity-50" />
<span class="text-[10px] font-bold uppercase tracking-wider"
>WebSocket Link</span
>
<span class="text-[10px] font-bold tracking-wider uppercase"
>WebSocket Link</span>
</div>
{#if ws_connected}
<span
class="badge preset-filled-success text-[8px] animate-pulse"
>Connected</span
>
class="badge preset-filled-success animate-pulse text-[8px]"
>Connected</span>
{:else}
<span class="badge preset-filled-error text-[8px]"
>Disconnected</span
>
>Disconnected</span>
{/if}
</div>
<!-- 2. Controller Mode Selection -->
<div class="flex flex-col gap-1">
<p class="text-[9px] font-bold uppercase opacity-50 ml-1"
>Controller Strategy</p
>
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">
Controller Strategy
</p>
<select
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="remote">Remotely WS Controlled</option>
<option value="local_push">Local and WS Controller</option>
@@ -62,7 +64,7 @@
</div>
<!-- 3. Connection Actions -->
<div class="grid grid-cols-2 gap-2 mt-1">
<div class="mt-1 grid grid-cols-2 gap-2">
<button
type="button"
onclick={() => {
@@ -76,8 +78,7 @@
}}
class="btn btn-sm text-[10px] font-bold transition-all"
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}
<Unlink size="0.85em" class="mr-1" /> Disconnect
{:else}
@@ -92,8 +93,7 @@
$events_sess.launcher.controller_trigger_send = 'trigger';
}}
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
</button>
</div>
@@ -101,21 +101,19 @@
<!-- 4. Technical Config (Edit Mode Only) -->
{#if $ae_loc.edit_mode}
<div
class="col-span-full border-t border-surface-500/20 pt-3 mt-1 flex flex-col gap-2"
>
<p class="text-[9px] font-bold uppercase opacity-50 ml-1"
>Channel Configuration</p
>
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">
Channel Configuration
</p>
<div class="flex gap-1">
<input
bind:value={$events_loc.launcher.controller_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
.controller_unlock_group_code}
ondblclick={() =>
($events_sess.launcher.controller_unlock_group_code = true)}
/>
($events_sess.launcher.controller_unlock_group_code = true)} />
<button
type="button"
onclick={() =>
@@ -123,8 +121,7 @@
!$events_sess.launcher
.controller_unlock_group_code)}
class="btn btn-xs preset-tonal-surface"
title="Toggle Unlock"
>
title="Toggle Unlock">
{#if $events_sess.launcher.controller_unlock_group_code}
<LockOpen size="0.85em" class="text-primary-500" />
{:else}
@@ -132,7 +129,7 @@
{/if}
</button>
</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
reconnect.
</p>

View File

@@ -47,53 +47,48 @@
bind:state={$events_loc.launcher.section_state__health}
{on_expand}
description="Heartbeat: {$events_sess.launcher.heartbeat_info
.last_timestamp || 'Pending'}"
>
.last_timestamp || 'Pending'}">
<!-- Content omitted for brevity in instruction, but preserved in file -->
<!-- Telemetry Dashboard -->
<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) -->
<div class="flex flex-col gap-1">
<div
class="flex justify-between text-[9px] uppercase font-bold opacity-60"
>
class="flex justify-between text-[9px] font-bold uppercase opacity-60">
<span
>CPU Architecture: {$ae_loc.native_device?.meta_json
?.arch || '...'}</span
>
?.arch || '...'}</span>
<span>Load: {cpu_load_pct}%</span>
</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
class="h-full transition-all duration-1000 {get_usage_color(cpu_load_pct)}"
style="width: {cpu_load_pct}%"
></div>
class="h-full transition-all duration-1000 {get_usage_color(
cpu_load_pct
)}"
style="width: {cpu_load_pct}%">
</div>
</div>
</div>
<!-- RAM Usage -->
<div class="flex flex-col gap-1">
<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>{ram_usage_pct}% Used</span>
</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
class="h-full transition-all duration-1000 {get_usage_color(
ram_usage_pct
)}"
style="width: {ram_usage_pct}%"
></div>
style="width: {ram_usage_pct}%">
</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
.native_device?.meta_json?.total_mem || '...'}
</div>
@@ -101,26 +96,23 @@
</div>
<!-- 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">
<span class="opacity-50 text-[8px] uppercase font-bold"
>Last Heartbeat</span
>
<span class="text-[8px] font-bold uppercase opacity-50"
>Last Heartbeat</span>
<span
class="font-mono {$events_sess.launcher.heartbeat_info
.status === 'success'
? 'text-success-500'
: 'text-error-500'}"
>
: 'text-error-500'}">
{$events_sess.launcher.heartbeat_info.last_timestamp ||
'Pending...'}
</span>
</div>
<div class="flex flex-col text-right">
<span class="opacity-50 text-[8px] uppercase font-bold"
>Local File Cache</span
>
<span class="text-[8px] font-bold uppercase opacity-50"
>Local File Cache</span>
<span class="font-mono">
{$events_sess.launcher.sync_stats.cached} / {$events_sess
.launcher.sync_stats.total}
@@ -129,19 +121,18 @@
{#if $events_sess.launcher.sync_stats.currently_syncing}
<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">
<RefreshCw size="1em" class="animate-spin text-primary-500" />
<RefreshCw
size="1em"
class="text-primary-500 animate-spin" />
<div class="flex flex-col truncate">
<span
class="text-[8px] uppercase font-bold text-primary-500"
>Syncing File...</span
>
class="text-primary-500 text-[8px] font-bold uppercase"
>Syncing File...</span>
<span class="truncate italic opacity-80"
>{$events_sess.launcher.sync_stats
.currently_syncing}</span
>
.currently_syncing}</span>
</div>
</div>
</div>
@@ -151,26 +142,22 @@
<!-- Device Metadata (Edit Mode Only) -->
{#if $ae_loc.edit_mode}
<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">
<span>Hostname:</span>
<span class="font-mono"
>{$ae_loc.native_device.info_hostname || '...'}</span
>
>{$ae_loc.native_device.info_hostname || '...'}</span>
</div>
<div class="flex justify-between gap-4">
<span>IP Addresses:</span>
<span class="font-mono truncate"
>{$ae_loc.native_device.info_ip_list || '...'}</span
>
<span class="truncate font-mono"
>{$ae_loc.native_device.info_ip_list || '...'}</span>
</div>
<div class="mt-2 opacity-40">
<span class="text-[8px] uppercase font-bold"
>Raw Device JSON</span
>
<span class="text-[8px] font-bold uppercase"
>Raw Device JSON</span>
<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)}
</pre>
</div>

View File

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

View File

@@ -3,7 +3,21 @@
import { events_loc, events_sess } from '$lib/stores/ae_events_stores';
import * as native from '$lib/electron/electron_relay';
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 {
on_expand?: () => void;
}
@@ -42,9 +56,7 @@
}
// Modal state for dangerous actions
let show_power_confirm = $state<{ action: string; label: string } | null>(
null
);
let show_power_confirm = $state<{ action: string; label: string } | null>(null);
</script>
<Launcher_Cfg_Section
@@ -53,51 +65,50 @@
bind:state={$events_loc.launcher.section_state__native_os}
{on_expand}
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.
electron_relay functions all return null when native is absent — no errors. -->
{#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" />
<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>
{/if}
{#if system_status}
<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}
</div>
{/if}
<!-- 1. Window & Folders (Common) -->
<div class="flex flex-col gap-2">
<p class="text-[9px] font-bold uppercase opacity-50 ml-1"
>Folders & View</p
>
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">
Folders & View
</p>
<div class="grid grid-cols-2 gap-1">
<button
type="button"
onclick={() =>
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
</button>
<button
type="button"
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
</button>
<button
type="button"
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
</button>
<button
@@ -107,8 +118,7 @@
native.window_control({ action: 'kiosk', value: true }),
'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
</button>
</div>
@@ -116,14 +126,13 @@
<!-- 2. Presentation Remote Control (Common) -->
<div class="flex flex-col gap-2">
<div class="flex flex-row justify-between items-center px-1">
<p class="text-[9px] font-bold uppercase opacity-50"
>Remote Control</p
>
<div class="flex flex-row items-center justify-between px-1">
<p class="text-[9px] font-bold uppercase opacity-50">
Remote Control
</p>
<select
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="keynote">Keynote</option>
</select>
@@ -134,39 +143,34 @@
type="button"
onclick={() => handle_remote_control('prev')}
class="btn btn-sm preset-tonal-secondary"
title="Previous Slide"
>
title="Previous Slide">
<SkipBack size="1em" />
</button>
<button
type="button"
onclick={() => handle_remote_control('start')}
class="btn btn-sm preset-tonal-success"
title="Start/Resume Slideshow"
>
title="Start/Resume Slideshow">
<Play size="1em" />
</button>
<button
type="button"
onclick={() => handle_remote_control('stop')}
class="btn btn-sm preset-tonal-error"
title="Stop Slideshow"
>
title="Stop Slideshow">
<Square size="1em" />
</button>
<button
type="button"
onclick={() => handle_remote_control('next')}
class="btn btn-sm preset-tonal-secondary"
title="Next Slide"
>
title="Next Slide">
<SkipForward size="1em" />
</button>
</div>
{#if remote_status}
<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}
</div>
{/if}
@@ -175,13 +179,12 @@
<!-- 3. Technical Management (Edit Mode Only) -->
{#if $ae_loc.edit_mode}
<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="flex flex-col gap-1">
<p class="text-[9px] font-bold uppercase opacity-50"
>System Actions</p
>
<p class="text-[9px] font-bold uppercase opacity-50">
System Actions
</p>
<div class="grid grid-cols-1 gap-1">
<button
type="button"
@@ -192,9 +195,9 @@
}),
'Extend Display'
)}
class="btn btn-xs preset-tonal-surface hover:preset-filled-primary-500 justify-start"
>
<Columns2 size="0.85em" class="mr-1 shrink-0" /> Extend Mode
class="btn btn-xs preset-tonal-surface hover:preset-filled-primary-500 justify-start">
<Columns2 size="0.85em" class="mr-1 shrink-0" /> Extend
Mode
</button>
<button
type="button"
@@ -206,17 +209,15 @@
'Wallpaper'
)}
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
</button>
</div>
</div>
<div class="flex flex-col gap-1">
<span
class="text-[9px] font-bold uppercase opacity-50 text-error-500"
>Power</span
>
class="text-error-500 text-[9px] font-bold uppercase opacity-50"
>Power</span>
<div class="grid grid-cols-1 gap-1">
<button
type="button"
@@ -225,8 +226,7 @@
action: 'reboot',
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
</button>
<button
@@ -236,8 +236,7 @@
action: 'shutdown',
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
</button>
</div>
@@ -245,16 +244,15 @@
</div>
<div class="flex flex-col gap-1">
<p class="text-[9px] font-bold uppercase opacity-50 ml-1"
>Terminal Access</p
>
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">
Terminal Access
</p>
<div class="flex gap-1">
<input
type="text"
bind:value={$events_sess.launcher.manual_cmd}
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
type="button"
onclick={async () => {
@@ -268,13 +266,12 @@
(res as any).error ||
'No Output';
}}
class="btn btn-sm preset-filled-secondary hover:preset-filled-primary-500 text-[10px] h-7"
>Run</button
>
class="btn btn-sm preset-filled-secondary hover:preset-filled-primary-500 h-7 text-[10px]"
>Run</button>
</div>
{#if test_cmd_result}
<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}
</div>
</div>
@@ -284,25 +281,21 @@
<!-- Power Confirmation Modal -->
{#if show_power_confirm}
<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
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"
>
<h4 class="h4 text-error-500 font-bold mb-2">
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">
Confirm System Action
</h4>
<p class="text-sm opacity-80 mb-6">
<p class="mb-6 text-sm opacity-80">
Are you sure you want to <strong
>{show_power_confirm.action}</strong
> this host machine?
>{show_power_confirm.action}</strong> this host machine?
</p>
<div class="flex justify-end gap-2">
<button
type="button"
onclick={() => (show_power_confirm = null)}
class="btn btn-sm preset-tonal-surface">Cancel</button
>
class="btn btn-sm preset-tonal-surface">Cancel</button>
<button
type="button"
onclick={() => {
@@ -314,8 +307,7 @@
action
);
}}
class="btn btn-sm preset-filled-error"
>
class="btn btn-sm preset-filled-error">
Confirm {show_power_confirm.action}
</button>
</div>

View File

@@ -16,62 +16,54 @@
{on_expand}
description="Idle: {($events_loc.launcher.idle_timer / 60000).toFixed(
1
)}m | Auto-Posters"
>
)}m | Auto-Posters">
<!-- Content omitted for brevity, preserved in file -->
<div class="col-span-full flex flex-col gap-3">
<!-- 1. Technical Timers (Edit Mode Only) -->
{#if $ae_loc.edit_mode}
<div class="flex flex-col gap-2">
<p class="text-[9px] font-bold uppercase opacity-50 ml-1"
>Screen Saver Timers (ms)</p
>
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">
Screen Saver Timers (ms)
</p>
<div
class="grid grid-cols-1 gap-2 bg-surface-500/5 p-2 rounded border border-surface-500/10"
>
<div class="flex justify-between items-center gap-4">
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">
<span class="text-[10px] opacity-60">Idle Wait</span>
<input
type="number"
min={3000}
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 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>
<input
type="number"
min={500}
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 class="flex justify-between items-center gap-4">
<div class="flex items-center justify-between gap-4">
<span class="text-[10px] opacity-60"
>Image Rotation</span
>
>Image Rotation</span>
<input
type="number"
min={750}
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>
{:else}
<!-- 2. Read Only Summary (Normal Mode) -->
<div
class="bg-surface-500/5 p-3 rounded-lg border border-surface-500/10 flex flex-col gap-2"
>
<div class="flex justify-between items-center text-xs">
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">
<span class="opacity-60">Active Idle Timeout:</span>
<span class="font-bold text-primary-500"
>{($events_loc.launcher.idle_timer / 60000).toFixed(1)} minutes</span
>
<span class="text-primary-500 font-bold"
>{($events_loc.launcher.idle_timer / 60000).toFixed(1)} minutes</span>
</div>
<p class="text-[9px] opacity-40 italic">
<p class="text-[9px] italic opacity-40">
The screen saver automatically rotates digital posters when
no activity is detected for the specified time.
</p>
@@ -79,7 +71,7 @@
{/if}
<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
</p>
</div>

View File

@@ -54,44 +54,39 @@
</script>
<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'
: ''} {state === 'auto'
? 'preset-outlined-primary-500 shadow-xl'
: ''} {state === 'pinned'
? 'preset-outlined-warning-500 shadow-xl'
: ''}"
>
: ''}">
<!-- Header -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<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'
: ''} {state === 'auto' ? 'bg-primary-500/10' : ''} {state ===
'pinned'
? 'bg-warning-500/10'
: ''}"
onclick={toggle_expand}
>
onclick={toggle_expand}>
<div class="flex items-center gap-3">
<Icon
size="1em"
class="w-5 text-center opacity-70 {state === 'auto'
? 'text-primary-500'
: ''} {state === 'pinned' ? 'text-warning-500' : ''}"
/>
: ''} {state === 'pinned' ? 'text-warning-500' : ''}" />
<div class="flex flex-col">
<span
class="text-sm font-bold tracking-tight uppercase {!is_open
? 'opacity-50'
: ''}">{title}</span
>
: ''}">{title}</span>
{#if description && !is_open}
<span
class="text-[9px] opacity-40 italic truncate max-w-[180px]"
>{description}</span
>
class="max-w-[180px] truncate text-[9px] italic opacity-40"
>{description}</span>
{/if}
</div>
</div>
@@ -106,16 +101,19 @@
class:text-warning-500={state === 'pinned'}
title={state === 'pinned'
? 'Unpin Section'
: 'Pin Section (Stay open)'}
>
: 'Pin Section (Stay open)'}>
<Pin size="0.7em" />
</button>
<!-- Collapse Icon -->
{#if is_open}
<ChevronDown size="1em" class="transition-transform duration-300 opacity-30" />
<ChevronDown
size="1em"
class="opacity-30 transition-transform duration-300" />
{:else}
<ChevronRight size="1em" class="transition-transform duration-300 opacity-30" />
<ChevronRight
size="1em"
class="opacity-30 transition-transform duration-300" />
{/if}
</div>
</header>
@@ -124,27 +122,22 @@
{#if is_open}
<div
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}
<div class="mb-2 flex justify-between items-center px-1">
<div class="mb-2 flex items-center justify-between px-1">
<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
</span>
{#if state === 'pinned'}
<span
class="badge preset-filled-warning text-[8px] uppercase"
>Pinned</span
>
>Pinned</span>
{/if}
</div>
{/if}
<div
class="grid grid-cols-1 gap-3"
>
<div class="grid grid-cols-1 gap-3">
{@render children?.()}
</div>
</div>

View File

@@ -15,21 +15,24 @@
bind:state={$events_loc.launcher.section_state__sync_timers}
{on_expand}
description="Prefix: {$ae_loc.native_device?.hash_prefix_length ||
2} | Loops: Active"
>
2} | Loops: Active">
<!-- Content omitted for brevity, preserved in file -->
<!-- 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">
<span class="text-[10px] font-bold uppercase tracking-wider opacity-70">
{$events_loc.launcher.sync_paused ? '⏸ Sync Paused' : '▶ Sync Active'}
<div
class="border-surface-500/10 bg-surface-500/5 mb-2 flex items-center justify-between rounded border p-2">
<span class="text-[10px] font-bold tracking-wider uppercase opacity-70">
{$events_loc.launcher.sync_paused
? '⏸ Sync Paused'
: '▶ Sync Active'}
</span>
<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: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}
<Play size="0.85em" class="mr-1" /> Resume
{:else}
@@ -43,92 +46,80 @@
<!-- Technical Timers (Edit Mode Only) -->
{#if $ae_loc.edit_mode}
<div class="flex flex-col gap-2">
<p
class="text-[9px] font-bold uppercase opacity-50 ml-1"
>Polling Periods (ms)</p
>
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">
Polling Periods (ms)
</p>
<div class="grid grid-cols-2 gap-2">
<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
type="number"
bind:value={
$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 class="flex flex-col gap-1">
<span class="text-[8px] opacity-60"
>Device Config</span
>
>Device Config</span>
<input
type="number"
bind:value={
$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 class="flex flex-col gap-1">
<span class="text-[8px] opacity-60"
>Room/Location</span
>
>Room/Location</span>
<input
type="number"
bind:value={
$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 class="flex flex-col gap-1">
<span class="text-[8px] opacity-60"
>Session Loop</span
>
>Session Loop</span>
<input
type="number"
bind:value={
$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 class="flex flex-col gap-1">
<span class="text-[8px] opacity-60"
>Presentation Loop</span
>
>Presentation Loop</span>
<input
type="number"
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 class="flex flex-col gap-1">
<span class="text-[8px] opacity-60"
>Presenter Loop</span
>
>Presenter Loop</span>
<input
type="number"
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
class="flex flex-col gap-1 mt-1 border-t border-surface-500/10 pt-2"
>
<p
class="text-[9px] font-bold uppercase opacity-50 ml-1"
>Cache Structure</p
>
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">
Cache Structure
</p>
<div class="flex items-center justify-between px-1">
<span class="text-[9px]">Hash Prefix Length</span>
{#if $ae_loc.native_device}
@@ -136,17 +127,17 @@
bind:value={
$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={2}>2 chars</option>
<option value={3}>3 chars</option>
</select>
{:else}
<span class="text-[9px] opacity-50 italic">loading…</span>
<span class="text-[9px] italic opacity-50"
>loading…</span>
{/if}
</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
effect.
</p>
@@ -154,47 +145,40 @@
{:else}
<!-- Read Only Summary (Normal Mode) -->
<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
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
>{(
$events_loc.launcher.sync_intervals.event /
1000
).toFixed(1)}s</span
>
$events_loc.launcher.sync_intervals.event / 1000
).toFixed(1)}s</span>
</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
>{(
$events_loc.launcher.sync_intervals.location / 1000
).toFixed(1)}s</span
>
$events_loc.launcher.sync_intervals.location /
1000
).toFixed(1)}s</span>
</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
>{$ae_loc.native_device?.hash_prefix_length || 2} chars</span
>
>{$ae_loc.native_device?.hash_prefix_length || 2} chars</span>
</div>
</div>
<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.
</p>
</div>
{/if}
</div>
{: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.
</div>
{/if}

View File

@@ -7,7 +7,14 @@
import { ae_loc, ae_api, ae_sess } from '$lib/stores/ae_stores';
import { events_loc, events_sess } from '$lib/stores/ae_events_stores';
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 {
on_expand?: () => void;
}
@@ -43,100 +50,88 @@
icon={Boxes}
bind:state={$events_loc.launcher.section_state__template}
{on_expand}
description="Kitchen Sink Scaffold | Demo Only"
>
description="Kitchen Sink Scaffold | Demo Only">
<!-- A. TOP STATUS BAR (Optional) -->
{#if action_status}
<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}
</div>
{/if}
<!-- B. COMMON GRID SECTION (Read Only / High Level) -->
<div class="flex flex-col gap-2">
<p class="text-[9px] font-bold uppercase opacity-50 ml-1"
>Standard Actions</p
>
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">
Standard Actions
</p>
<!-- Action Buttons -->
<div class="grid grid-cols-2 gap-1">
<button
type="button"
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
</button>
<button
type="button"
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
</button>
</div>
<!-- Toggles & Checkboxes -->
<div class="flex flex-col gap-1 mt-1 bg-surface-500/5 p-2 rounded">
<label class="flex items-center gap-2 cursor-pointer group">
<div class="bg-surface-500/5 mt-1 flex flex-col gap-1 rounded p-2">
<label class="group flex cursor-pointer items-center gap-2">
<input
type="checkbox"
bind:checked={toggle_val}
class="checkbox checkbox-sm"
/>
class="checkbox checkbox-sm" />
<span
class="text-xs group-hover:text-primary-500 transition-colors"
>Toggle Feature Alpha</span
>
class="group-hover:text-primary-500 text-xs transition-colors"
>Toggle Feature Alpha</span>
</label>
<label class="flex items-center gap-2 cursor-pointer group">
<label class="group flex cursor-pointer items-center gap-2">
<input
type="radio"
name="demo"
value="a"
class="radio radio-sm"
/>
class="radio radio-sm" />
<span
class="text-xs group-hover:text-primary-500 transition-colors"
>Mode A</span
>
class="group-hover:text-primary-500 text-xs transition-colors"
>Mode A</span>
</label>
</div>
</div>
<!-- C. STATUS & GAUGES SECTION -->
<div class="flex flex-col gap-2">
<p class="text-[9px] font-bold uppercase opacity-50 ml-1"
>Current Status</p
>
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">
Current Status
</p>
<div
class="flex flex-col gap-2 p-2 border border-surface-500/10 rounded-lg"
>
<div class="flex justify-between items-center">
class="border-surface-500/10 flex flex-col gap-2 rounded-lg border p-2">
<div class="flex items-center justify-between">
<span class="text-[10px] font-medium">Engine Health</span>
<span class="badge preset-filled-success text-[8px] uppercase"
>Stable</span
>
>Stable</span>
</div>
<!-- Progress / Gauge Example -->
<div class="flex flex-col gap-1">
<div
class="flex justify-between text-[8px] uppercase opacity-60"
>
class="flex justify-between text-[8px] uppercase opacity-60">
<span>Processing Load</span>
<span>45%</span>
</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
class="h-full bg-success-500 transition-all duration-1000"
style="width: 45%"
></div>
class="bg-success-500 h-full transition-all duration-1000"
style="width: 45%">
</div>
</div>
</div>
</div>
@@ -144,9 +139,10 @@
<button
type="button"
onclick={() => handle_test_action('Refresh')}
class="btn btn-xs preset-outlined-surface-500 w-full text-[10px]"
>
<RefreshCw size="0.85em" class="mr-1 {is_loading ? 'animate-spin' : ''}" />
class="btn btn-xs preset-outlined-surface-500 w-full text-[10px]">
<RefreshCw
size="0.85em"
class="mr-1 {is_loading ? 'animate-spin' : ''}" />
Refresh State
</button>
</div>
@@ -154,32 +150,28 @@
<!-- D. TECHNICAL SECTION (Edit Mode Only) -->
{#if $ae_loc.edit_mode}
<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 -->
<div class="grid grid-cols-2 gap-2">
<div class="flex flex-col gap-1">
<span
class="text-[9px] font-bold uppercase opacity-50 text-warning-500"
>System Config</span
>
class="text-warning-500 text-[9px] font-bold uppercase opacity-50"
>System Config</span>
<button
type="button"
onclick={() => (show_confirm = true)}
class="btn btn-xs preset-tonal-warning hover:preset-filled-warning-500 justify-start"
>
<AlertTriangle size="0.85em" class="mr-1 shrink-0" /> Reset All
class="btn btn-xs preset-tonal-warning hover:preset-filled-warning-500 justify-start">
<AlertTriangle size="0.85em" class="mr-1 shrink-0" /> Reset
All
</button>
</div>
<div class="flex flex-col gap-1">
<span
class="text-[9px] font-bold uppercase opacity-50 text-error-500"
>Danger Zone</span
>
class="text-error-500 text-[9px] font-bold uppercase opacity-50"
>Danger Zone</span>
<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
</button>
</div>
@@ -188,21 +180,17 @@
<!-- Form Inputs -->
<div class="grid grid-cols-1 gap-2">
<div class="flex flex-col gap-1">
<span
class="text-[9px] font-bold uppercase opacity-50 ml-1"
>Raw Settings</span
>
<span class="ml-1 text-[9px] font-bold uppercase opacity-50"
>Raw Settings</span>
<div class="flex gap-1">
<input
type="text"
bind:value={text_input}
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
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="option2">Local</option>
</select>
@@ -210,24 +198,22 @@
</div>
<div class="flex flex-col gap-1">
<span class="text-[8px] opacity-60 ml-1"
>Threshold (ms)</span
>
<span class="ml-1 text-[8px] opacity-60"
>Threshold (ms)</span>
<input
type="number"
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>
<!-- Terminal / Output Log -->
<div class="flex flex-col gap-1">
<p class="text-[9px] font-bold uppercase opacity-50 ml-1"
>Debug Output</p
>
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">
Debug Output
</p>
<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
[INFO] Store synced with IndexedDB
[DEBUG] active_tab: template
@@ -241,13 +227,11 @@
<!-- Confirmation Modal Demo -->
{#if show_confirm}
<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
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"
>
<h4 class="h4 text-warning-500 font-bold mb-2">Confirm Action</h4>
<p class="text-sm opacity-80 mb-6">
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>
<p class="mb-6 text-sm opacity-80">
Are you sure you want to perform this test operation? This
demonstrate the standard confirmation pattern.
</p>
@@ -255,16 +239,14 @@
<button
type="button"
onclick={() => (show_confirm = false)}
class="btn btn-sm preset-tonal-surface">Cancel</button
>
class="btn btn-sm preset-tonal-surface">Cancel</button>
<button
type="button"
onclick={() => {
show_confirm = false;
handle_test_action('Confirm');
}}
class="btn btn-sm preset-filled-warning"
>
class="btn btn-sm preset-filled-warning">
Yes, Proceed
</button>
</div>

View File

@@ -13,9 +13,7 @@
let update_path = $state(
'~/OSIT/Speaker Ready System/Admin Share/Custom Applications/osit_binaries/'
);
let update_url = $state(
'https://dev-demo.oneskyit.com/updates/ae_native.zip'
);
let update_url = $state('https://dev-demo.oneskyit.com/updates/ae_native.zip');
let update_status = $state('');
let is_checking = $state(false);
@@ -59,35 +57,31 @@
icon={CloudDownload}
bind:state={$events_loc.launcher.section_state__updates}
{on_expand}
description="v1.0.0 | Source: {update_source}"
>
description="v1.0.0 | Source: {update_source}">
<!-- Content omitted for brevity, preserved in file -->
<div class="col-span-full flex flex-col gap-2">
<!-- TECHNICAL: Source Config (Edit Mode Only) -->
{#if $ae_loc.edit_mode}
<div
class="flex flex-col gap-2 bg-surface-500/5 p-2 rounded border border-surface-500/10 mb-1"
>
<div class="flex flex-row justify-between items-center px-1">
<p class="text-[9px] font-bold uppercase opacity-50"
>Source Type</p
>
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">
<p class="text-[9px] font-bold uppercase opacity-50">
Source Type
</p>
<div class="flex gap-2">
<label class="flex items-center gap-1 text-[10px]">
<input
type="radio"
bind:group={update_source}
value="file"
class="radio radio-sm"
/> Local
class="radio radio-sm" /> Local
</label>
<label class="flex items-center gap-1 text-[10px]">
<input
type="radio"
bind:group={update_source}
value="url"
class="radio radio-sm"
/> Web
class="radio radio-sm" /> Web
</label>
</div>
</div>
@@ -97,15 +91,13 @@
type="text"
bind:value={update_path}
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}
<input
type="text"
bind:value={update_url}
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}
</div>
{/if}
@@ -115,10 +107,9 @@
type="button"
onclick={handle_check_update}
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}
<LoaderCircle size="0.85em" class="animate-spin mr-1" /> Checking...
<LoaderCircle size="0.85em" class="mr-1 animate-spin" /> Checking...
{:else}
<Search size="0.85em" class="mr-1" /> Check for Updates
{/if}
@@ -126,8 +117,7 @@
{#if update_status}
<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}
</div>
{/if}
@@ -136,8 +126,7 @@
<button
type="button"
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
</button>
{/if}

View File

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

View File

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

View File

@@ -6,7 +6,11 @@
*/
import { onMount, onDestroy } from 'svelte';
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 { ae_util } from '$lib/ae_utils/ae_utils';
import { db_events } from '$lib/ae_events/db_events';
@@ -16,9 +20,7 @@
let { log_lvl = 1 } = $props();
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 last_heartbeat: string | null = $state(null);
@@ -79,22 +81,40 @@
// 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.
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.location = cfg.location || dev.check_event_location_loop_period || 60000;
loop_info.session = 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;
loop_info.device =
cfg.device || dev.check_event_device_loop_period || 60000;
loop_info.location =
cfg.location || dev.check_event_location_loop_period || 60000;
loop_info.session =
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
timer__event = setInterval(() => refresh_event_data(), loop_info.event);
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)
timer__session = setInterval(() => refresh_session_data(), loop_info.session);
timer__presentation = setInterval(() => refresh_presentation_data(), loop_info.presentation);
timer__presenter = setInterval(() => refresh_presenter_data(), loop_info.presenter);
timer__session = setInterval(
() => refresh_session_data(),
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)
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.
const cache_root = $ae_loc.local_file_cache_path;
if ($ae_loc.is_native && cache_root) {
const max_age_hours = $events_loc.launcher.cleanup_tmp_max_age_hours ?? 24;
cleanup_tmp_files({ cache_root, max_age_minutes: max_age_hours * 60 }).then((result) => {
const max_age_hours =
$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);
});
}
@@ -157,7 +181,10 @@
const location_id = $events_slct.event_location_id;
if (!location_id) return;
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({
api_cfg: $ae_api,
for_obj_type: 'event_location',
@@ -178,7 +205,10 @@
const session_id = $events_slct.event_session_id;
if (!session_id) return;
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({
api_cfg: $ae_api,
for_obj_type: 'event_session',
@@ -197,7 +227,10 @@
const session_id = $events_slct.event_session_id;
if (!session_id) return;
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({
api_cfg: $ae_api,
for_obj_type: 'event_session',
@@ -214,8 +247,7 @@
const cache_root = $ae_loc.local_file_cache_path;
const prefix_len = $ae_loc.native_device?.hash_prefix_length || 2;
if (!location_id || !cache_root || is_syncing || !$ae_loc.is_native)
return;
if (!location_id || !cache_root || is_syncing || !$ae_loc.is_native) return;
is_syncing = true;
try {
@@ -362,15 +394,16 @@
update_payload.info_hostname = info.hostname;
// Safely handle IP list (bridge may return ip_addresses or networkInterfaces)
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 = {
platform: info.platform,
release: info.release,
arch: info.arch,
cpus: info.cpus,
total_mem:
Math.round(info.total_mem / (1024 * 1024)) + 'MB',
total_mem: Math.round(info.total_mem / (1024 * 1024)) + 'MB',
free_mem: Math.round(info.free_mem / (1024 * 1024)) + 'MB'
};
} else {
@@ -424,81 +457,76 @@
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}
<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}
<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
class="flex justify-between border-b border-surface-200 dark:border-primary-700 pb-1 mb-2"
>
<span class="font-bold text-primary-600 dark:text-primary-400"
>NATIVE SYNC MONITOR</span
>
class="border-surface-200 dark:border-primary-700 mb-2 flex justify-between border-b pb-1">
<span
class="text-primary-600 dark:text-primary-400 font-bold"
>NATIVE SYNC MONITOR</span>
<button
type="button"
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 class="grid grid-cols-2 gap-x-4 gap-y-1 mb-2">
<span class="opacity-60 text-primary-700 dark:text-primary-300">Room Status:</span>
<div class="mb-2 grid grid-cols-2 gap-x-4 gap-y-1">
<span
class="text-primary-700 dark:text-primary-300 opacity-60"
>Room Status:</span>
<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"
>{$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
class="text-right {last_heartbeat
? 'text-success-600 dark:text-success-400'
: 'text-error-600 dark:text-error-400'}"
>
: 'text-error-600 dark:text-error-400'}">
{last_heartbeat || 'Pending...'}
</span>
</div>
<div class="border-t border-surface-200 dark:border-surface-700 pt-2 flex flex-col gap-1">
<div class="flex justify-between items-center">
<div
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}
>Event Loop:</span
>
>Event Loop:</span>
<span>{loop_info.event / 1000}s</span>
</div>
<div class="flex justify-between items-center">
<div class="flex items-center justify-between">
<span class:text-primary-500={timer__device}
>Device Loop:</span
>
>Device Loop:</span>
<span>{loop_info.device / 1000}s</span>
</div>
<div class="flex justify-between items-center">
<div class="flex items-center justify-between">
<span class:text-primary-500={timer__location}
>Location Loop:</span
>
>Location Loop:</span>
<span>{loop_info.location / 1000}s</span>
</div>
<div class="flex justify-between items-center">
<div class="flex items-center justify-between">
<span class:text-primary-500={timer__session}
>Session Loop:</span
>
>Session Loop:</span>
<span>{loop_info.session / 1000}s</span>
</div>
<div class="flex justify-between items-center">
<div class="flex items-center justify-between">
<span class:text-primary-500={timer__presentation}
>Pres Loop:</span
>
>Pres Loop:</span>
<span>{loop_info.presentation / 1000}s</span>
</div>
<div class="flex justify-between items-center">
<div class="flex items-center justify-between">
<span class:text-primary-500={timer__presenter}
>Speaker Loop:</span
>
>Speaker Loop:</span>
<span>{loop_info.presenter / 1000}s</span>
</div>
</div>
@@ -512,25 +540,36 @@
type="button"
onclick={() => (show_monitor = !show_monitor)}
class="
flex items-center gap-1.5
px-2 py-1.5 rounded-lg
text-[10px] font-mono
pointer-events-auto
pointer-events-auto flex items-center
gap-1.5 rounded-lg px-2
py-1.5 font-mono
text-[10px]
transition-all
{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-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-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-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'}"
>
{#if currently_syncing}<RefreshCw size="1em" class="text-[9px] animate-spin text-primary-500" />{:else}<Cpu size="1em" class="text-[9px] opacity-50" />{/if}
title="Native Sync Monitor · {sync_stats.cached}/{sync_stats.total} files · click to {show_monitor
? 'close'
: '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}
<span class="truncate max-w-40 text-left">{currently_syncing}</span>
<span class="max-w-40 truncate text-left"
>{currently_syncing}</span>
{:else}
<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 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>
</div>
{/if}

View File

@@ -32,7 +32,16 @@
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_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
// Tabs are audience-oriented:
// setup — what every onsite operator needs (mode preset, display, WS, screen saver)
@@ -63,16 +72,13 @@
<div
class="
w-full max-w-full
flex flex-col gap-4 items-center justify-start
"
>
flex w-full
max-w-full flex-col items-center justify-start gap-4
">
<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
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" />
Launcher Configuration
</h2>
@@ -88,8 +94,7 @@
class:text-primary-500={$ae_loc.edit_mode}
class:opacity-20={!$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" />
<span class="sr-only">Toggle Edit Mode</span>
</button>
@@ -97,8 +102,7 @@
<button
type="button"
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" />
<span class="sr-only">Close Config</span>
</button>
@@ -110,88 +114,80 @@
for onsite operators who never need those tools. Edit Mode is toggled via
the pencil icon in the header above. -->
<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-cols-2={!$ae_loc.edit_mode}
class:grid-cols-3={$ae_loc.edit_mode}
>
class:grid-cols-3={$ae_loc.edit_mode}>
<button
type="button"
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-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
</button>
<button
type="button"
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-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
</button>
{#if $ae_loc.edit_mode}
<button
type="button"
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-tonal-surface={active_tab !== 'dev'}
title="Developer &amp; debug tools"
>
title="Developer &amp; debug tools">
<Code size="0.85em" class="mr-1" /> Dev
</button>
{/if}
</div>
<!-- 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 -->
{#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 -->
<Launcher_Cfg_App_Modes
on_expand={() => handle_section_expand('app_modes')}
/>
on_expand={() => handle_section_expand('app_modes')} />
<Launcher_Cfg_Controller
on_expand={() => handle_section_expand('controller')}
/>
on_expand={() => handle_section_expand('controller')} />
<Launcher_Cfg_Screen_Saver
on_expand={() => handle_section_expand('screen_saver')}
/>
on_expand={() => handle_section_expand('screen_saver')} />
</div>
{/if}
<!-- DEVICE: sync engine first (all devices) + native OS controls (native or edit_mode preview) -->
{#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 -->
<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.
electron_relay.ts guards all calls — safe to import/render without Electron. -->
{#if $ae_loc.is_native || $ae_loc.edit_mode}
<Launcher_Cfg_Health
on_expand={() => handle_section_expand('health')}
/>
on_expand={() => handle_section_expand('health')} />
<Launcher_Cfg_Native_OS
on_expand={() => handle_section_expand('native_os')}
/>
on_expand={() => handle_section_expand('native_os')} />
{#if $ae_loc.is_native}
<Launcher_Cfg_Updates
on_expand={() => handle_section_expand('updates')}
/>
on_expand={() =>
handle_section_expand('updates')} />
{/if}
{: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" />
<p>Native OS controls available in Aether Desktop.</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 -->
{#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
on_expand={() => handle_section_expand('local_actions')}
/>
on_expand={() => handle_section_expand('local_actions')} />
</div>
{/if}
</div>
<!-- Global Actions Footer -->
<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">
<!-- 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. -->
<button
type="button"
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" />
Close
</button>
@@ -230,8 +223,7 @@
<button
type="button"
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" />
Reload
</button>
@@ -242,16 +234,14 @@
type="button"
onclick={() =>
($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" />
Debug Panel
</button>
{/if}
<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
</p>
</div>

View File

@@ -63,7 +63,18 @@
events_slct
} from '$lib/stores/ae_events_stores';
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 the relay
@@ -227,24 +238,22 @@
class:hidden={hide_draft &&
(event_file_obj.file_purpose == 'outline' ||
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}
<div
class="open_file_clicked alert"
in:fade={{ duration: 250 }}
out:fade={{ duration: 2000 }}
>
out:fade={{ duration: 2000 }}>
<div class="alert_msg_pulse">
<strong
>*** {open_file_status_message ||
'Please wait while this file downloads...'} ***</strong
>
'Please wait while this file downloads...'} ***</strong>
</div>
{#if $ae_loc.is_native && $events_loc.launcher.app_mode === 'native'}
<p>Most files will automatically be opened full screen.</p>
<p>
PowerPoint or KeyNote will attempt to display in presenter view.
PowerPoint or KeyNote will attempt to display in presenter
view.
</p>
<p>Please close the file when finished.</p>
{/if}
@@ -252,8 +261,7 @@
{/if}
<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'}
<AE_Comp_Hosted_Files_Download_Button
hosted_file_id={event_file_id}
@@ -267,17 +275,24 @@
$events_slct.event_file_id = event_file_id;
$events_slct.event_file_obj = event_file_obj;
// 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_trigger_send = true;
}
}}
>
}}>
{#snippet label()}
{#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}
<Send size="1em" class="{hide_launch_icon ? 'hidden' : ''} m-1" />
<Send
size="1em"
class="{hide_launch_icon ? 'hidden' : ''} m-1" />
{ae_util.shorten_filename({
filename: event_file_obj.filename,
max_length: max_filename_length
@@ -291,13 +306,14 @@
hosted_file_obj={event_file_obj}
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"
click={handle_open_file}
>
click={handle_open_file}>
{#snippet label()}
{@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]}
<LoaderCircle size="1em" class="inline animate-spin mx-0.5" />
<LoaderCircle
size="1em"
class="mx-0.5 inline animate-spin" />
<span>
{#if $ae_sess.api_download_kv[file_id]}
{$ae_sess.api_download_kv[file_id]
@@ -307,24 +323,28 @@
{/if}
</span>
{:then result}
{@const FileIcon = ae_util.file_extension_icon_lucide(event_file_obj.extension)}
<FileIcon size="1em" class="inline mx-0.5" />
{@const FileIcon =
ae_util.file_extension_icon_lucide(
event_file_obj.extension
)}
<FileIcon size="1em" class="mx-0.5 inline" />
{event_file_obj.extension}
{#if result === null || result === false}
<span class="text-error-500"
><AlertTriangle size="1em" class="inline mx-1" />Failed!</span
>
><AlertTriangle
size="1em"
class="mx-1 inline" />Failed!</span>
{/if}
{:catch error}
<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}
</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({
string: event_file_obj.filename_no_ext,
begin_length: 45,
@@ -333,9 +353,8 @@
</span>
<span
class="badge my-0 py-0.5 preset-tonal-success hover:preset-filled-success-500 text-xs xl:text-sm"
class:hidden={!event_file_obj.file_purpose}
>
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}>
{event_file_obj.file_purpose}
</span>
{/snippet}
@@ -344,9 +363,8 @@
</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:hidden={hide_meta}
>
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}>
<button
type="button"
onclick={async () => {
@@ -366,33 +384,33 @@
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-warning={event_file_obj?.open_in_os == 'mac'}
disabled={!$ae_loc.trusted_access}
>
{#if event_file_obj?.open_in_os == 'win'}<Monitor size="1em" class="m-1" />
{:else if event_file_obj?.open_in_os == 'mac'}<Laptop size="1em" class="m-1" />
disabled={!$ae_loc.trusted_access}>
{#if event_file_obj?.open_in_os == 'win'}<Monitor
size="1em"
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}
</button>
<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:hidden={hide_created_on}
>
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}>
<CalendarDays size="0.85em" class="inline" />
<span class="w-18"
>{ae_util.iso_datetime_formatter(
event_file_obj.created_on,
'date_short'
)}</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:hidden={hide_size}
>
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}>
<Save size="0.85em" class="inline" />
{#if event_file_obj.file_size}{ae_util.format_bytes(
event_file_obj.file_size

View File

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

View File

@@ -53,17 +53,15 @@
{#if $lq__event_file_obj_li && $lq__event_file_obj_li.length}
<section class="event_presentation_file_list my-1">
<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:
</div>
<ul class="space-y-1">
{#each $lq__event_file_obj_li as event_file_obj (event_file_obj.event_file_id)}
<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
.show_content__hidden_files && event_file_obj.hide}
>
.show_content__hidden_files && event_file_obj.hide}>
<Event_launcher_file_cont
event_file_id={event_file_obj.event_file_id}
{event_file_obj}
@@ -82,8 +80,7 @@
}
bind:modal__event_file_obj={
$events_sess.launcher.modal__event_file_obj
}
/>
} />
</li>
{/each}
</ul>

View File

@@ -66,7 +66,7 @@
</strong>
{#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> -->
No files to show for this presenter at this time.
<!-- <span class="fas fa-exclamation"></span> -->
@@ -76,7 +76,7 @@
{#if $lq__event_file_obj_li && $lq__event_file_obj_li.length}
<section class="event_session_file_list">
<div>
<div class="text-xs text-surface-600-400">
<div class="text-surface-600-400 text-xs">
<strong>
<Archive size="1em" class="inline" />
Presenter Files:
@@ -89,10 +89,9 @@
<ul class="space-y-1">
{#each $lq__event_file_obj_li as event_file_obj, index (event_file_obj.event_file_id)}
<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
.show_content__hidden_files && event_file_obj.hide}
>
.show_content__hidden_files && event_file_obj.hide}>
<Event_launcher_file_cont
event_file_id={event_file_obj.event_file_id}
{event_file_obj}
@@ -110,8 +109,7 @@
}
bind:modal__event_file_obj={
$events_sess.launcher.modal__event_file_obj
}
/>
} />
</li>
{/each}
</ul>

View File

@@ -83,10 +83,9 @@
<ul class="space-y-1">
{#each $lq__event_file_obj_li as event_file_obj, index (event_file_obj.event_file_id)}
<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
.show_content__hidden_files && event_file_obj.hide}
>
.show_content__hidden_files && event_file_obj.hide}>
<Event_launcher_file_cont
event_file_id={event_file_obj.event_file_id}
{event_file_obj}
@@ -102,8 +101,7 @@
}
bind:modal__event_file_obj={
$events_sess.launcher.modal__event_file_obj
}
/>
} />
</li>
{/each}
</ul>

View File

@@ -39,7 +39,16 @@
events_trigger
} from '$lib/stores/ae_events_stores';
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)
// WHY: We use a simple derived observable. The template handles the $ prefix.
let lq__event_session_obj = $derived(
@@ -60,7 +69,9 @@
if (!slct__event_session_id) return [];
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
@@ -77,7 +88,9 @@
if (!slct__event_session_id) return [];
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';
@@ -97,7 +110,9 @@
if (!slct__event_session_id) return [];
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
@@ -146,15 +161,14 @@
<div
class="
event_launcher_session_view
grow h-full w-full
relative h-full w-full
grow
space-y-1
relative
"
>
">
<!-- <slot name="event_session_message">event session message</slot> -->
{#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" />
Loading session information...
</span>
@@ -176,35 +190,33 @@
collapse the header below that height. Zero layout shift between sessions.
-->
<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
class:hidden={!$lq__event_session_obj?.start_datetime ||
$events_loc.launcher.hide__session_datetimes}
class="event_session_datetimes text-sm text-center"
>
class="event_session_datetimes text-center text-sm">
<button
type="button"
onclick={() => {
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.time_format = 'time_short';
$events_loc.launcher.time_hours = 24;
} 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.time_hours = 12;
}
}}
>
}}>
<strong
>{ae_util.iso_datetime_formatter(
$lq__event_session_obj.start_datetime,
'week_long'
)}</strong
>
)}</strong>
<span class="font-normal">
{ae_util.iso_datetime_formatter(
$lq__event_session_obj.start_datetime,
@@ -215,8 +227,7 @@
>{ae_util.iso_datetime_formatter(
$lq__event_session_obj.start_datetime,
$events_loc.launcher.time_format
)}</strong
>
)}</strong>
<span class="font-normal">
{ae_util.iso_datetime_formatter(
@@ -228,21 +239,18 @@
</h3>
<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 -->
<h2
class="grow text-xl line-clamp-2 min-w-0"
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)}`}
>
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)}`}>
{$lq__event_session_obj?.name}
</h2>
{#if $lq__event_session_obj?.code}
<!-- shrink-0: code badge never gets squeezed by a long name -->
<span
class="shrink-0 text-base text-gray-500 font-normal p-1"
title="Session code {$lq__event_session_obj.code}"
>
class="shrink-0 p-1 text-base font-normal text-gray-500"
title="Session code {$lq__event_session_obj.code}">
<Barcode size="1em" class="inline" />
{$lq__event_session_obj?.code}
</span>
@@ -255,7 +263,7 @@
</section> -->
{#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" />
Warning
<AlertTriangle size="1em" class="inline" />
@@ -267,15 +275,14 @@
{#if $lq__event_file_obj_li && $lq__event_file_obj_li.length}
<section class="event_session_file_list">
<div>
<div class="text-xs text-surface-600-400">
<div class="text-surface-600-400 text-xs">
<strong>
<Archive size="1em" class="inline" />
Session Files:
<span
class:hidden={!$ae_loc.trusted_access ||
!$ae_loc.edit_mode}
>
!$ae_loc.edit_mode}>
({$lq__event_file_obj_li?.length}&times;)
</span>
</strong>
@@ -291,11 +298,10 @@
<ul class="space-y-1">
{#each $lq__event_file_obj_li as event_file_obj (event_file_obj.event_file_id)}
<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
.show_content__hidden_files &&
event_file_obj.hide}
>
event_file_obj.hide}>
<Event_launcher_file_cont
event_file_id={event_file_obj.event_file_id}
{event_file_obj}
@@ -303,7 +309,9 @@
show_bak_download={$ae_loc.trusted_access &&
$ae_loc.edit_mode}
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}
bind:modal__title={
$events_sess.launcher.modal__title
@@ -313,9 +321,9 @@
.modal__open_event_file_id
}
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} /> -->
@@ -342,7 +350,7 @@
<!-- {$lq__event_session_obj?.event_presentation_li?.length ?? 'loading...?'} -->
{#if $lq__event_presentation_obj_li}
<div class="text-xs text-surface-600-400">
<div class="text-surface-600-400 text-xs">
<strong>
{#if type_code == 'poster'}
<Image size="1em" class="inline" />
@@ -361,27 +369,24 @@
<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)}
<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 -->
<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"> -->
{#if event_presentation_obj?.start_datetime}
<span class="event_presentation_datetime"
<span
class="event_presentation_datetime"
><strong
>{ae_util.iso_datetime_formatter(
event_presentation_obj?.start_datetime,
'time_12_short_no_leading'
)}</strong
></span
>
></span>
{/if}
<span class="event_presentation_name grow"
>{event_presentation_obj?.name}</span
>
>{event_presentation_obj?.name}</span>
<!-- </div> -->
<!-- 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)}
{#if event_presenter_obj.event_presentation_id == event_presentation_obj.event_presentation_id}
<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'}
<User size="0.85em" class="inline" />
<User
size="0.85em"
class="inline" />
{$lq__event_presenter_obj_li[
index
]?.full_name}
{: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[
index
]?.affiliations}
@@ -413,8 +421,7 @@
<!-- Presentation-level files -->
<Launcher_presentation_view
lq__event_presentation_obj={event_presentation_obj}
session_type={type_code}
/>
session_type={type_code} />
<!-- The presenter list -->
<!-- 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. -->
{#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)}
{#if event_presenter_obj.event_presentation_id == event_presentation_obj.event_presentation_id}
<li
class="
border border-transparent
hover:bg-surface-100-900 hover:border-surface-400-600
rounded-lg
hover:bg-surface-100-900
hover:border-surface-400-600
border
border-transparent
p-1
transition-all
"
>
">
{#if type_code == 'poster'}
<Launcher_presenter_view_posters
lq__event_presenter_obj={event_presenter_obj}
hide_name={true}
/>
hide_name={true} />
{:else}
<Launcher_presenter_view
lq__event_presenter_obj={event_presenter_obj}
session_type={type_code}
/>
session_type={type_code} />
{/if}
</li>
{/if}

View File

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

View File

@@ -30,10 +30,11 @@
let { log_lvl = $bindable(0) }: Props = $props();
</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 ── -->
{#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
type="button"
onclick={() => {
@@ -46,13 +47,12 @@
}
}}
class="
btn btn-sm text-xs
w-1/2 max-w-1/2
preset-tonal-tertiary hover:preset-filled-tertiary-500
btn btn-sm preset-tonal-tertiary
hover:preset-filled-tertiary-500 w-1/2
max-w-1/2 text-xs
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}
<EyeOff size="0.85em" class="m-1 text-neutral-800/80" />
Hide Files
@@ -69,13 +69,12 @@
!$events_loc.launcher.show_content__hidden_sessions;
}}
class="
btn btn-sm text-xs
w-1/2 max-w-1/2
preset-tonal-tertiary hover:preset-filled-tertiary-500
btn btn-sm preset-tonal-tertiary
hover:preset-filled-tertiary-500 w-1/2
max-w-1/2 text-xs
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}
<EyeOff size="0.85em" class="m-1 text-neutral-800/80" />
Hide Sessions
@@ -88,7 +87,8 @@
{/if}
<!-- ── 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 -->
<button
type="button"
@@ -103,22 +103,28 @@
}
}}
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
preset-tonal-tertiary hover:preset-filled-tertiary-500
transition-all group
text-xs transition-all
"
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'}
<span class="font-bold text-sm font-mono leading-none m-1">A</span>
<span class="hidden group-hover:inline-block text-xs">Font: Normal</span>
<span class="m-1 font-mono text-sm leading-none font-bold"
>A</span>
<span class="hidden text-xs group-hover:inline-block"
>Font: Normal</span>
{:else if $ae_loc.font_size_mode === 'larger'}
<span class="font-bold text-base font-mono leading-none m-1">A+</span>
<span class="hidden group-hover:inline-block text-xs">Font: Larger</span>
<span class="m-1 font-mono text-base leading-none font-bold"
>A+</span>
<span class="hidden text-xs group-hover:inline-block"
>Font: Larger</span>
{:else}
<span class="font-bold text-xs font-mono leading-none m-1">A</span>
<span class="hidden group-hover:inline-block text-xs">Font: Smaller</span>
<span class="m-1 font-mono text-xs leading-none font-bold"
>A</span>
<span class="hidden text-xs group-hover:inline-block"
>Font: Smaller</span>
{/if}
</button>
@@ -126,16 +132,17 @@
<button
type="button"
onclick={() => {
$ae_loc.theme_mode = $ae_loc.theme_mode === 'dark' ? 'light' : 'dark';
$ae_loc.theme_mode =
$ae_loc.theme_mode === 'dark' ? 'light' : 'dark';
}}
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
preset-tonal-tertiary hover:preset-filled-tertiary-500
transition-all group
text-xs transition-all
"
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'}
<Moon class="m-1 inline-block" size="1em" />
<span class="hidden group-hover:inline-block">Dark Mode</span>

View File

@@ -148,23 +148,24 @@
<!-- text-neutral-800/80 -->
<div
class="
w-full max-w-full
flex flex-col md:flex-row flex-wrap gap-1 items-center justify-center
"
>
flex w-full
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}
<div class="text-xs text-surface-600-400">
<div class="text-surface-600-400 text-xs">
<strong>
Location:
<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;)
</span>
<!-- This should fade out once the data is loaded. -->
{#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}
<Check size="0.85em" class="inline text-green-500/80" />
{/await}
@@ -172,7 +173,7 @@
</div>
<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}
onchange={async () => {
// console.log(`slct_event_location_id:`, slct_event_location_id);
@@ -222,9 +223,8 @@
loading__session_li_status = null;
// 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
}}
>
<option value="" class="italic text-surface-800-200">
}}>
<option value="" class="text-surface-800-200 italic">
-- select --
</option>
{#each $lq__event_location_obj_li as event_location_obj (event_location_obj.event_location_id)}

View File

@@ -91,7 +91,14 @@
events_trigger
} from '$lib/stores/ae_events_stores';
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;
// *** Functions and Logic
@@ -121,7 +128,9 @@
const event_session_id = String(trigger_reload__event_session_obj_id);
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(() => {
@@ -154,7 +163,9 @@
keepFocus: true
}).then(() => {
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
class="
w-full max-w-80
flex flex-col flex-wrap gap-1 items-center justify-start md:justify-center
"
>
flex w-full
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}
<div class="text-xs text-surface-600-400">
<div class="text-surface-600-400 text-xs">
<strong>
Sessions:
<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;)
</span>
<!-- This should fade out once the data is loaded. -->
{#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}
<Check size="0.85em" class="inline text-green-500/80" />
{/await}
@@ -227,22 +239,20 @@
<ul
class="
m-0 flex
w-full max-w-full
p-0 m-0
flex flex-col gap-0 items-start justify-start
"
>
flex-col items-start justify-start gap-0 p-0
">
{#each $lq__event_session_obj_li as event_session_obj (event_session_obj.event_session_id)}
<li
class="
session-item
relative
p-0 m-0
w-full max-w-full
m-0 w-full
max-w-full p-0
"
class:session-active={slct__event_session_id ===
event_session_obj?.id}
>
event_session_obj?.id}>
<button
type="button"
onmouseenter={() => {
@@ -272,17 +282,17 @@
class="
session-btn
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 max-w-full
text-left
m-0
px-1.5 py-1
w-full
max-w-full flex-row
items-center
justify-start
rounded-md px-1.5
rounded-md
flex flex-row items-center justify-start
transition-colors duration-200
py-1
text-left text-sm transition-colors duration-200
focus-visible:ring-2 focus-visible:ring-offset-1
"
class:preset-filled-primary={slct__event_session_id ===
event_session_obj?.id}
@@ -298,8 +308,7 @@
event_session_obj?.hide_event_launcher)}
class:opacity-40={event_session_obj?.hide ||
event_session_obj?.hide_event_launcher}
title={`Session: ${event_session_obj?.name}\nID: ${event_session_obj?.id} | ${ae_util.iso_datetime_formatter(event_session_obj?.start_datetime, $events_loc.launcher.time_format)}`}
>
title={`Session: ${event_session_obj?.name}\nID: ${event_session_obj?.id} | ${ae_util.iso_datetime_formatter(event_session_obj?.start_datetime, $events_loc.launcher.time_format)}`}>
<!-- Session row layout: [date column | session name]
Date column is fixed-width (shrink-0) so name column always
gets consistent space regardless of date string length.
@@ -311,8 +320,7 @@
When revealed, dimmed (opacity-40) with eye-slash icon. -->
<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}
<CalendarCheck size="0.85em" class="inline" />
{:else}
@@ -321,8 +329,7 @@
<span
class="text-xs"
class:hidden={slct__event_session_id ===
event_session_obj?.id}
>
event_session_obj?.id}>
{ae_util.iso_datetime_formatter(
event_session_obj?.start_datetime,
'week_medium'
@@ -339,20 +346,28 @@
<span
class="
session-name
grow text-sm
min-w-0
"
>
min-w-0 grow
text-sm
">
{#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}
<!-- Distinct icon styles distinguish the two hidden states:
amber = hide (globally hidden — draft, cancelled, or admin-only)
muted = hide_event_launcher (suppressed in Launcher view only) -->
{#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}
<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}
{event_session_obj?.name}
</span>
@@ -453,7 +468,9 @@
background-color: #f1f5f9; /* slate-100 — solid light surface */
border-left: 3px solid rgb(var(--color-primary-500, 99 102 241));
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 */
@@ -461,7 +478,9 @@
:global(.dark) .session-item:not(.session-active):focus-within .session-btn {
background-color: #1e293b; /* slate-800 */
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 ── */