refactor(launcher): standardize helper names and apply batch formatting

- Renamed internal 'preventDefault' to 'prevent_default' in launcher files.
- Fixed native 'event.preventDefault()' call in launcher_file_cont.svelte.
- Applied batch formatting (printWidth: 80) across the (launcher) module.
This commit is contained in:
Scott Idem
2026-02-06 14:48:44 -05:00
parent 7ce5e1f825
commit 969e5610bb
26 changed files with 1536 additions and 604 deletions

View File

@@ -1,7 +1,7 @@
<script lang="ts">
/**
* events/[event_id]/(launcher)/+layout.svelte
* Root layout for the launcher area.
* Root layout for the launcher area.
* Ensures background sync runs globally regardless of active tab.
*/
import { ae_loc } from '$lib/stores/ae_stores';

View File

@@ -3,6 +3,7 @@
This directory contains the files for the new Event Launcher module (v3). Detailed documentation to follow.
This should be able to run in 3 modes:
* Default - What most users will see for demo purposes
* Onsite - This is what is set onsite, usually just in the Speaker Ready Room
* Native App - This is for the native app version of Aether and used on the MacBook laptops in each of the session rooms
- Default - What most users will see for demo purposes
- Onsite - This is what is set onsite, usually just in the Speaker Ready Room
- Native App - This is for the native app version of Aether and used on the MacBook laptops in each of the session rooms

View File

@@ -9,66 +9,111 @@
let { on_expand }: Props = $props();
</script>
<Launcher_Cfg_Section
title="Display & App Modes"
icon="fa-object-group"
<Launcher_Cfg_Section
title="Display & App Modes"
icon="fa-object-group"
bind:state={$events_loc.launcher.section_state__app_modes}
{on_expand}
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">
<!-- 1. App Mode Selection -->
<div class="flex flex-col gap-1">
<label class="text-[9px] font-bold uppercase opacity-50 ml-1">Operational Environment</label>
<label class="text-[9px] font-bold uppercase opacity-50 ml-1"
>Operational Environment</label
>
<div class="grid grid-cols-3 gap-1 bg-surface-500/5 p-1 rounded-lg">
<button type="button"
onclick={() => $events_loc.launcher.app_mode = 'default'}
<button
type="button"
onclick={() => ($events_loc.launcher.app_mode = 'default')}
class="btn btn-xs text-[9px] font-bold"
class:preset-filled-primary-500={$events_loc.launcher.app_mode === 'default'}
class:opacity-40={$events_loc.launcher.app_mode !== 'default'}
>Web</button>
<button type="button"
onclick={() => $events_loc.launcher.app_mode = 'native'}
class:preset-filled-primary-500={$events_loc.launcher
.app_mode === 'default'}
class:opacity-40={$events_loc.launcher.app_mode !==
'default'}>Web</button
>
<button
type="button"
onclick={() => ($events_loc.launcher.app_mode = 'native')}
class="btn btn-xs text-[9px] font-bold"
class:preset-filled-primary-500={$events_loc.launcher.app_mode === 'native'}
class:opacity-40={$events_loc.launcher.app_mode !== 'native'}
>App</button>
<button type="button"
onclick={() => $events_loc.launcher.app_mode = 'onsite'}
class:preset-filled-primary-500={$events_loc.launcher
.app_mode === 'native'}
class:opacity-40={$events_loc.launcher.app_mode !==
'native'}>App</button
>
<button
type="button"
onclick={() => ($events_loc.launcher.app_mode = 'onsite')}
class="btn btn-xs text-[9px] font-bold"
class:preset-filled-primary-500={$events_loc.launcher.app_mode === 'onsite'}
class:opacity-40={$events_loc.launcher.app_mode !== 'onsite'}
>Onsite</button>
class:preset-filled-primary-500={$events_loc.launcher
.app_mode === 'onsite'}
class:opacity-40={$events_loc.launcher.app_mode !==
'onsite'}>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">
<label class="text-[9px] font-bold uppercase opacity-50 ml-1">Interface Visibility</label>
<div
class="flex flex-col gap-1 border-t border-surface-500/10 pt-2 mt-1"
>
<label class="text-[9px] font-bold uppercase opacity-50 ml-1"
>Interface Visibility</label
>
<div class="grid grid-cols-2 gap-2 p-1">
<label class="flex items-center gap-2 cursor-pointer group">
<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>
<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
>
</label>
<label class="flex items-center gap-2 cursor-pointer group">
<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>
<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
>
</label>
<label class="flex items-center gap-2 cursor-pointer group">
<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>
<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
>
</label>
<label class="flex items-center gap-2 cursor-pointer group">
<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>
<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
>
</label>
</div>
</div>
<!-- 3. Time Format Toggle -->
<button type="button"
<button
type="button"
onclick={() => {
if ($events_loc.launcher.time_format == 'time_12_short') {
$events_loc.launcher.time_format = 'time_short';
@@ -81,21 +126,42 @@
class="btn btn-xs preset-tonal-surface w-full text-[10px]"
>
<span class="fas fa-clock mr-2 opacity-50"></span>
Clock Format: <strong>{$events_loc.launcher.time_hours}-hour</strong>
Clock Format:
<strong>{$events_loc.launcher.time_hours}-hour</strong>
</button>
<!-- 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">
<label class="text-[9px] font-bold uppercase opacity-50 ml-1">Technical Layout</label>
<div
class="col-span-full border-t border-surface-500/20 pt-3 mt-1 flex flex-col gap-2"
>
<label class="text-[9px] font-bold uppercase opacity-50 ml-1"
>Technical Layout</label
>
<div class="grid grid-cols-1 gap-2 p-1">
<label class="flex items-center gap-2 cursor-pointer group">
<input type="checkbox" bind:checked={$events_loc.launcher.hide__ws_element} class="checkbox checkbox-sm" />
<span class="text-xs group-hover:text-primary-500 italic">Hide WebSocket Debugger</span>
<input
type="checkbox"
bind:checked={$events_loc.launcher.hide__ws_element}
class="checkbox checkbox-sm"
/>
<span
class="text-xs group-hover:text-primary-500 italic"
>Hide WebSocket Debugger</span
>
</label>
<label class="flex items-center gap-2 cursor-pointer group">
<input type="checkbox" bind:checked={$events_loc.launcher.hide__modal_header_title} class="checkbox checkbox-sm" />
<span class="text-xs group-hover:text-primary-500 italic">Hide Poster Modal Title</span>
<input
type="checkbox"
bind:checked={
$events_loc.launcher.hide__modal_header_title
}
class="checkbox checkbox-sm"
/>
<span
class="text-xs group-hover:text-primary-500 italic"
>Hide Poster Modal Title</span
>
</label>
</div>
</div>

View File

@@ -8,35 +8,49 @@
}
let { on_expand }: Props = $props();
const ws_connected = $derived($events_sess.launcher.ws_connect_status === 'connected');
const ws_connected = $derived(
$events_sess.launcher.ws_connect_status === 'connected'
);
</script>
<Launcher_Cfg_Section
title="Remote Controller"
icon="fa-gamepad"
<Launcher_Cfg_Section
title="Remote Controller"
icon="fa-gamepad"
bind:state={$events_loc.launcher.section_state__controller}
{on_expand}
description="Mode: {$events_loc.launcher?.controller} | WS: {ws_connected ? 'Connected' : 'Offline'}"
description="Mode: {$events_loc.launcher?.controller} | WS: {ws_connected
? 'Connected'
: '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">
<div
class="flex items-center justify-between bg-surface-500/5 p-2 rounded border border-surface-500/10"
>
<div class="flex items-center gap-2">
<span class="fas fa-plug opacity-50"></span>
<span class="text-[10px] font-bold uppercase tracking-wider">WebSocket Link</span>
<span class="text-[10px] font-bold uppercase tracking-wider"
>WebSocket Link</span
>
</div>
{#if ws_connected}
<span class="badge variant-filled-success text-[8px] animate-pulse">Connected</span>
<span
class="badge variant-filled-success text-[8px] animate-pulse"
>Connected</span
>
{:else}
<span class="badge variant-filled-error text-[8px]">Disconnected</span>
<span class="badge variant-filled-error text-[8px]"
>Disconnected</span
>
{/if}
</div>
<!-- 2. Controller Mode Selection -->
<div class="flex flex-col gap-1">
<label class="text-[9px] font-bold uppercase opacity-50 ml-1">Controller Strategy</label>
<label class="text-[9px] font-bold uppercase opacity-50 ml-1"
>Controller Strategy</label
>
<select
bind:value={$events_loc.launcher.controller}
class="select select-sm text-xs preset-tonal-surface h-8"
@@ -49,7 +63,8 @@
<!-- 3. Connection Actions -->
<div class="grid grid-cols-2 gap-2 mt-1">
<button type="button"
<button
type="button"
onclick={() => {
if ($events_loc.launcher.ws_connect) {
$events_sess.launcher.trigger__ws_disconnect = true;
@@ -70,7 +85,8 @@
{/if}
</button>
<button type="button"
<button
type="button"
onclick={() => {
$events_sess.launcher.controller_cmd = 'ae_refresh:now';
$events_sess.launcher.controller_trigger_send = 'trigger';
@@ -84,25 +100,43 @@
<!-- 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">
<label class="text-[9px] font-bold uppercase opacity-50 ml-1">Channel Configuration</label>
<div
class="col-span-full border-t border-surface-500/20 pt-3 mt-1 flex flex-col gap-2"
>
<label class="text-[9px] font-bold uppercase opacity-50 ml-1"
>Channel Configuration</label
>
<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"
readonly={!$events_sess.launcher.controller_unlock_group_code}
ondblclick={() => $events_sess.launcher.controller_unlock_group_code = true}
readonly={!$events_sess.launcher
.controller_unlock_group_code}
ondblclick={() =>
($events_sess.launcher.controller_unlock_group_code = true)}
/>
<button type="button"
onclick={() => $events_sess.launcher.controller_unlock_group_code = !$events_sess.launcher.controller_unlock_group_code}
<button
type="button"
onclick={() =>
($events_sess.launcher.controller_unlock_group_code =
!$events_sess.launcher
.controller_unlock_group_code)}
class="btn btn-xs preset-tonal-surface"
title="Toggle Unlock"
>
<span class="fas {$events_sess.launcher.controller_unlock_group_code ? 'fa-lock-open text-primary-500' : 'fa-lock'}"></span>
<span
class="fas {$events_sess.launcher
.controller_unlock_group_code
? 'fa-lock-open text-primary-500'
: 'fa-lock'}"
></span>
</button>
</div>
<p class="text-[8px] opacity-40 italic ml-1">Double-click input to unlock editing. Changing code triggers reconnect.</p>
<p class="text-[8px] opacity-40 italic ml-1">
Double-click input to unlock editing. Changing code triggers
reconnect.
</p>
</div>
{/if}
</div>

View File

@@ -12,12 +12,12 @@
let ram_usage_pct = $derived.by(() => {
const meta = $ae_loc.native_device?.meta_json;
if (!meta?.total_mem || !meta?.free_mem) return 0;
// Parse "16384MB" strings
const total = parseInt(meta.total_mem);
const free = parseInt(meta.free_mem);
if (isNaN(total) || isNaN(free)) return 0;
return Math.round(((total - free) / total) * 100);
});
@@ -29,39 +29,61 @@
}
</script>
<Launcher_Cfg_Section
title="System & Sync Health"
icon="fa-heartbeat"
<Launcher_Cfg_Section
title="System & Sync Health"
icon="fa-heartbeat"
bind:state={$events_loc.launcher.section_state__health}
{on_expand}
description="Heartbeat: {$events_sess.launcher.heartbeat_info.last_timestamp || 'Pending'}"
description="Heartbeat: {$events_sess.launcher.heartbeat_info
.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">
<div
class="col-span-full flex flex-col gap-3 bg-surface-500/5 p-3 rounded-lg border border-surface-500/10"
>
<!-- 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">
<span>CPU Architecture: {$ae_loc.native_device?.meta_json?.arch || '...'}</span>
<div
class="flex justify-between text-[9px] uppercase font-bold opacity-60"
>
<span
>CPU Architecture: {$ae_loc.native_device?.meta_json
?.arch || '...'}</span
>
<span>Load: Healthy</span>
</div>
<div class="w-full h-1.5 bg-surface-500/20 rounded-full overflow-hidden">
<div class="h-full bg-primary-500 transition-all duration-1000" style="width: 15%"></div>
<div
class="w-full h-1.5 bg-surface-500/20 rounded-full overflow-hidden"
>
<div
class="h-full bg-primary-500 transition-all duration-1000"
style="width: 15%"
></div>
</div>
</div>
<!-- RAM Usage -->
<div class="flex flex-col gap-1">
<div class="flex justify-between text-[9px] uppercase font-bold opacity-60">
<div
class="flex justify-between text-[9px] uppercase font-bold 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">
<div class="h-full transition-all duration-1000 {get_usage_color(ram_usage_pct)}" style="width: {ram_usage_pct}%"></div>
<div
class="w-full h-1.5 bg-surface-500/20 rounded-full overflow-hidden"
>
<div
class="h-full transition-all duration-1000 {get_usage_color(
ram_usage_pct
)}"
style="width: {ram_usage_pct}%"
></div>
</div>
<div class="text-[8px] opacity-40 text-right italic">
Free: {$ae_loc.native_device?.meta_json?.free_mem || '...'} / {$ae_loc.native_device?.meta_json?.total_mem || '...'}
Free: {$ae_loc.native_device?.meta_json?.free_mem || '...'} / {$ae_loc
.native_device?.meta_json?.total_mem || '...'}
</div>
</div>
</div>
@@ -69,26 +91,45 @@
<!-- Heartbeat & Sync Info -->
<div class="grid grid-cols-2 gap-x-2 gap-y-2 w-full text-[10px] p-1">
<div class="flex flex-col">
<span class="opacity-50 text-[8px] uppercase font-bold">Last Heartbeat</span>
<span class="font-mono {$events_sess.launcher.heartbeat_info.status === 'success' ? 'text-success-500' : 'text-error-500'}">
{$events_sess.launcher.heartbeat_info.last_timestamp || 'Pending...'}
<span class="opacity-50 text-[8px] uppercase font-bold"
>Last Heartbeat</span
>
<span
class="font-mono {$events_sess.launcher.heartbeat_info
.status === 'success'
? 'text-success-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="opacity-50 text-[8px] uppercase font-bold"
>Local File Cache</span
>
<span class="font-mono">
{$events_sess.launcher.sync_stats.cached} / {$events_sess.launcher.sync_stats.total}
{$events_sess.launcher.sync_stats.cached} / {$events_sess
.launcher.sync_stats.total}
</span>
</div>
{#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">
<div
class="col-span-full bg-primary-500/10 p-2 rounded border border-primary-500/20 animate-pulse mt-1"
>
<div class="flex items-center gap-2">
<span class="fas fa-sync fa-spin text-primary-500"></span>
<div class="flex flex-col truncate">
<span class="text-[8px] uppercase font-bold text-primary-500">Syncing File...</span>
<span class="truncate italic opacity-80">{$events_sess.launcher.sync_stats.currently_syncing}</span>
<span
class="text-[8px] uppercase font-bold text-primary-500"
>Syncing File...</span
>
<span class="truncate italic opacity-80"
>{$events_sess.launcher.sync_stats
.currently_syncing}</span
>
</div>
</div>
</div>
@@ -97,18 +138,27 @@
<!-- 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">
<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"
>
<div class="flex justify-between">
<span>Hostname:</span>
<span class="font-mono">{$ae_loc.native_device.info_hostname || '...'}</span>
<span class="font-mono"
>{$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="font-mono truncate"
>{$ae_loc.native_device.info_ip_list || '...'}</span
>
</div>
<div class="mt-2 opacity-40">
<label class="text-[8px] uppercase font-bold">Raw Device JSON</label>
<pre class="text-[7px] max-h-32 overflow-y-auto bg-black/20 p-1 rounded mt-1">
<label class="text-[8px] uppercase font-bold"
>Raw Device JSON</label
>
<pre
class="text-[7px] max-h-32 overflow-y-auto bg-black/20 p-1 rounded mt-1">
{JSON.stringify($ae_loc.native_device, null, 2)}
</pre>
</div>

View File

@@ -12,21 +12,33 @@
function handle_reset_action(val: string) {
if (!val) return;
if (val == 'delete_idbs') {
if (confirm('Are you sure you want to delete ALL IndexedDB databases?')) {
if (
confirm(
'Are you sure you want to delete ALL IndexedDB databases?'
)
) {
indexedDB.deleteDatabase('ae_archives_db');
indexedDB.deleteDatabase('ae_core_db');
indexedDB.deleteDatabase('ae_events_db');
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 (confirm('Are you sure you want to delete ONLY the Events IndexedDB database?')) {
if (
confirm(
'Are you sure you want to delete ONLY the Events IndexedDB database?'
)
) {
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?')) {
@@ -37,7 +49,11 @@
location.reload();
}
} else if (val == 'delete_local_events') {
if (confirm('Are you sure you want to delete ONLY the Events local config?')) {
if (
confirm(
'Are you sure you want to delete ONLY the Events local config?'
)
) {
localStorage.removeItem('ae_events_loc');
location.reload();
}
@@ -46,28 +62,34 @@
}
</script>
<Launcher_Cfg_Section
title="Local Reset & Actions"
icon="fa-tools"
<Launcher_Cfg_Section
title="Local Reset & Actions"
icon="fa-tools"
bind:state={$events_loc.launcher.section_state__local_actions}
{on_expand}
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">
<label class="text-[9px] font-bold uppercase opacity-50 ml-1 text-error-500">Maintenance & Resets</label>
<label
class="text-[9px] font-bold uppercase opacity-50 ml-1 text-error-500"
>Maintenance & Resets</label
>
<select
bind:value={selected_reset}
onchange={(e) => handle_reset_action((e.target as HTMLSelectElement).value)}
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"
>
<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>
<option value="delete_local_events"
>Wipe Events Storage Only</option
>
</select>
<span class="text-[8px] opacity-40 italic ml-1 leading-tight">
* Destructive actions require browser confirmation.
@@ -76,31 +98,48 @@
<!-- 2. UI Toggles -->
<div class="grid grid-cols-2 gap-2 mt-1">
<button type="button"
<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"
>
<span class="fas {$ae_loc.sys_menu.hide ? 'fa-eye' : 'fa-eye-slash'} mr-2"></span>
<span
class="fas {$ae_loc.sys_menu.hide
? 'fa-eye'
: 'fa-eye-slash'} mr-2"
></span>
{$ae_loc.sys_menu.hide ? 'Show' : 'Hide'} Sys Menu
</button>
<button type="button"
onclick={() => ($ae_loc.debug_menu.hide = !$ae_loc.debug_menu.hide)}
<button
type="button"
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"
>
<span class="fas {$ae_loc.debug_menu.hide ? 'fa-bug' : 'fa-bug-slash'} mr-2"></span>
<span
class="fas {$ae_loc.debug_menu.hide
? 'fa-bug'
: 'fa-bug-slash'} mr-2"
></span>
{$ae_loc.debug_menu.hide ? 'Show' : 'Hide'} Debug
</button>
</div>
<!-- 3. 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">
<label class="text-[9px] font-bold uppercase opacity-50 ml-1">API Context</label>
<div class="bg-black/10 p-2 rounded text-[9px] font-mono opacity-60 break-all leading-tight">
Endpoint: {$ae_api.base_url}<br/>
<div
class="col-span-full border-t border-surface-500/20 pt-2 mt-1 flex flex-col gap-1"
>
<label class="text-[9px] font-bold uppercase opacity-50 ml-1"
>API Context</label
>
<div
class="bg-black/10 p-2 rounded text-[9px] font-mono opacity-60 break-all leading-tight"
>
Endpoint: {$ae_api.base_url}<br />
Account: {$ae_loc.account_id}
</div>
</div>

View File

@@ -14,15 +14,20 @@
let remote_status = $state('');
let system_status = $state('');
async function handle_remote_control(action: 'next' | 'prev' | 'start' | 'stop') {
async function handle_remote_control(
action: 'next' | 'prev' | 'start' | 'stop'
) {
remote_status = `Sending ${action}...`;
const res = await native.control_presentation({ app: remote_app, action });
const res = await native.control_presentation({
app: remote_app,
action
});
if (res.success) {
remote_status = `Success: ${action}`;
} else {
remote_status = `Error: ${res.error}`;
}
setTimeout(() => remote_status = '', 3000);
setTimeout(() => (remote_status = ''), 3000);
}
async function handle_system_action(promise: Promise<any>, label: string) {
@@ -33,51 +38,67 @@
} else {
system_status = `Error: ${res.error || 'Unknown error'}`;
}
setTimeout(() => system_status = '', 3000);
setTimeout(() => (system_status = ''), 3000);
}
// 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
title="Native OS Management"
icon="fa-laptop-code"
<Launcher_Cfg_Section
title="Native OS Management"
icon="fa-laptop-code"
bind:state={$events_loc.launcher.section_state__native_os}
{on_expand}
description="OS: {$ae_loc.native_device?.meta_json?.platform || '...'} | Kiosk & Apps"
description="OS: {$ae_loc.native_device?.meta_json?.platform ||
'...'} | Kiosk & Apps"
>
<!-- Content omitted for brevity, preserved in file -->
{#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">
<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"
>
{system_status}
</div>
{/if}
<!-- 1. Window & Folders (Common) -->
<div class="flex flex-col gap-2">
<label class="text-[9px] font-bold uppercase opacity-50 ml-1">Folders & View</label>
<label class="text-[9px] font-bold uppercase opacity-50 ml-1"
>Folders & View</label
>
<div class="grid grid-cols-2 gap-1">
<button type="button"
onclick={() => native.open_folder($ae_loc.local_file_cache_path)}
<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"
>
<span class="fas fa-folder-open mr-2 w-3"></span> Cache
</button>
<button type="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"
>
<span class="fas fa-folder-open mr-2 w-3"></span> Temp
</button>
<button type="button"
<button
type="button"
onclick={() => native.window_control({ action: 'maximize' })}
class="btn btn-xs preset-tonal-surface hover:preset-filled-primary-500"
>
<span class="fas fa-expand mr-1"></span> Maximize
</button>
<button type="button"
onclick={() => handle_system_action(native.window_control({ action: 'kiosk', value: true }), 'Kiosk Mode')}
<button
type="button"
onclick={() =>
handle_system_action(
native.window_control({ action: 'kiosk', value: true }),
'Kiosk Mode'
)}
class="btn btn-xs preset-tonal-surface hover:preset-filled-primary-500"
>
<span class="fas fa-desktop mr-1"></span> Kiosk
@@ -88,47 +109,94 @@
<!-- 2. Presentation Remote Control (Common) -->
<div class="flex flex-col gap-2">
<div class="flex flex-row justify-between items-center px-1">
<label class="text-[9px] font-bold uppercase opacity-50">Remote Control</label>
<select bind:value={remote_app} class="select select-sm py-0 h-5 text-[9px] w-24 preset-tonal-surface">
<label class="text-[9px] font-bold uppercase opacity-50"
>Remote Control</label
>
<select
bind:value={remote_app}
class="select select-sm py-0 h-5 text-[9px] w-24 preset-tonal-surface"
>
<option value="powerpoint">PowerPoint</option>
<option value="keynote">Keynote</option>
</select>
</div>
<div class="grid grid-cols-4 gap-1">
<button type="button" onclick={() => handle_remote_control('prev')} class="btn btn-sm preset-tonal-secondary" title="Previous Slide">
<button
type="button"
onclick={() => handle_remote_control('prev')}
class="btn btn-sm preset-tonal-secondary"
title="Previous Slide"
>
<span class="fas fa-step-backward"></span>
</button>
<button type="button" onclick={() => handle_remote_control('start')} class="btn btn-sm preset-tonal-success" title="Start/Resume Slideshow">
<button
type="button"
onclick={() => handle_remote_control('start')}
class="btn btn-sm preset-tonal-success"
title="Start/Resume Slideshow"
>
<span class="fas fa-play"></span>
</button>
<button type="button" onclick={() => handle_remote_control('stop')} class="btn btn-sm preset-tonal-error" title="Stop Slideshow">
<button
type="button"
onclick={() => handle_remote_control('stop')}
class="btn btn-sm preset-tonal-error"
title="Stop Slideshow"
>
<span class="fas fa-stop"></span>
</button>
<button type="button" onclick={() => handle_remote_control('next')} class="btn btn-sm preset-tonal-secondary" title="Next Slide">
<button
type="button"
onclick={() => handle_remote_control('next')}
class="btn btn-sm preset-tonal-secondary"
title="Next Slide"
>
<span class="fas fa-step-forward"></span>
</button>
</div>
{#if remote_status}
<div class="text-[9px] text-center italic animate-pulse text-primary-500">{remote_status}</div>
<div
class="text-[9px] text-center italic animate-pulse text-primary-500"
>
{remote_status}
</div>
{/if}
</div>
<!-- 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">
<div
class="col-span-full border-t border-surface-500/20 pt-3 mt-1 flex flex-col gap-3"
>
<div class="grid grid-cols-2 gap-2">
<div class="flex flex-col gap-1">
<label class="text-[9px] font-bold uppercase opacity-50">System Actions</label>
<label class="text-[9px] font-bold uppercase opacity-50"
>System Actions</label
>
<div class="grid grid-cols-1 gap-1">
<button type="button"
onclick={() => handle_system_action(native.set_display_layout({ mode: 'extend' }), 'Extend Display')}
<button
type="button"
onclick={() =>
handle_system_action(
native.set_display_layout({
mode: 'extend'
}),
'Extend Display'
)}
class="btn btn-xs preset-tonal-surface hover:preset-filled-primary-500 justify-start"
>
<span class="fas fa-columns mr-2 w-3"></span> Extend Mode
</button>
<button type="button"
onclick={() => handle_system_action(native.set_wallpaper({ path: $ae_loc.site_header_image_path }), 'Wallpaper')}
<button
type="button"
onclick={() =>
handle_system_action(
native.set_wallpaper({
path: $ae_loc.site_header_image_path
}),
'Wallpaper'
)}
class="btn btn-xs preset-tonal-surface hover:preset-filled-primary-500 justify-start"
disabled={!$ae_loc.site_header_image_path}
>
@@ -137,16 +205,29 @@
</div>
</div>
<div class="flex flex-col gap-1">
<label class="text-[9px] font-bold uppercase opacity-50 text-error-500">Power</label>
<label
class="text-[9px] font-bold uppercase opacity-50 text-error-500"
>Power</label
>
<div class="grid grid-cols-1 gap-1">
<button type="button"
onclick={() => show_power_confirm = { action: 'reboot', label: 'Reboot Laptop' }}
<button
type="button"
onclick={() =>
(show_power_confirm = {
action: 'reboot',
label: 'Reboot Laptop'
})}
class="btn btn-xs preset-tonal-warning hover:preset-filled-warning-500 justify-start"
>
<span class="fas fa-sync-alt mr-2 w-3"></span> Reboot
</button>
<button type="button"
onclick={() => show_power_confirm = { action: 'shutdown', label: 'Shutdown Laptop' }}
<button
type="button"
onclick={() =>
(show_power_confirm = {
action: 'shutdown',
label: 'Shutdown Laptop'
})}
class="btn btn-xs preset-tonal-error hover:preset-filled-error-500 justify-start"
>
<span class="fas fa-power-off mr-2 w-3"></span> Shutdown
@@ -156,25 +237,36 @@
</div>
<div class="flex flex-col gap-1">
<label class="text-[9px] font-bold uppercase opacity-50 ml-1">Terminal Access</label>
<label class="text-[9px] font-bold uppercase opacity-50 ml-1"
>Terminal Access</label
>
<div class="flex gap-1">
<input
type="text"
bind:value={$events_sess.launcher.manual_cmd}
<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"
/>
<button type="button"
<button
type="button"
onclick={async () => {
test_cmd_result = 'Running...';
const res = await native.run_cmd({ cmd: $events_sess.launcher.manual_cmd || 'uptime' });
test_cmd_result = (res as any).stdout || (res as any).error || 'No Output';
const res = await native.run_cmd({
cmd:
$events_sess.launcher.manual_cmd || 'uptime'
});
test_cmd_result =
(res as any).stdout ||
(res as any).error ||
'No Output';
}}
class="btn btn-sm preset-filled-secondary hover:preset-filled-primary-500 text-[10px] h-7"
>Run</button>
>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>
<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>
{/if}
</div>
</div>
@@ -183,20 +275,37 @@
<!-- 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">
<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">Confirm System Action</h4>
<div
class="fixed inset-0 z-[1000] flex items-center justify-center bg-black/50 backdrop-blur-sm p-4"
>
<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">
Confirm System Action
</h4>
<p class="text-sm opacity-80 mb-6">
Are you sure you want to <strong>{show_power_confirm.action}</strong> this host machine?
Are you sure you want to <strong
>{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>
<button type="button"
<button
type="button"
onclick={() => (show_power_confirm = null)}
class="btn btn-sm preset-tonal-surface">Cancel</button
>
<button
type="button"
onclick={() => {
const action = show_power_confirm?.action;
show_power_confirm = null;
if (action) handle_system_action(native.power_control({ action: action as any }), action);
}}
if (action)
handle_system_action(
native.power_control({ action: action as any }),
action
);
}}
class="btn btn-sm preset-filled-error"
>
Confirm {show_power_confirm.action}

View File

@@ -9,59 +9,71 @@
let { on_expand }: Props = $props();
</script>
<Launcher_Cfg_Section
title="Digital Screen Saver"
icon="fa-id-badge"
<Launcher_Cfg_Section
title="Digital Screen Saver"
icon="fa-id-badge"
bind:state={$events_loc.launcher.section_state__screen_saver}
{on_expand}
description="Idle: {($events_loc.launcher.idle_timer / 60000).toFixed(1)}m | Auto-Posters"
description="Idle: {($events_loc.launcher.idle_timer / 60000).toFixed(
1
)}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">
<label class="text-[9px] font-bold uppercase opacity-50 ml-1">Screen Saver Timers (ms)</label>
<div class="grid grid-cols-1 gap-2 bg-surface-500/5 p-2 rounded border border-surface-500/10">
<label class="text-[9px] font-bold uppercase opacity-50 ml-1"
>Screen Saver Timers (ms)</label
>
<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">
<span class="text-[10px] opacity-60">Idle Wait</span>
<input
type="number"
<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 text-[10px] h-7 w-24 text-right preset-tonal-surface"
/>
</div>
<div class="flex justify-between items-center gap-4">
<span class="text-[10px] opacity-60">Cycle Check</span>
<input
type="number"
<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 text-[10px] h-7 w-24 text-right preset-tonal-surface"
/>
</div>
<div class="flex justify-between items-center gap-4">
<span class="text-[10px] opacity-60">Image Rotation</span>
<input
type="number"
<span class="text-[10px] opacity-60"
>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 text-[10px] h-7 w-24 text-right preset-tonal-surface"
/>
</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="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">
<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="font-bold text-primary-500"
>{($events_loc.launcher.idle_timer / 60000).toFixed(1)} minutes</span
>
</div>
<p class="text-[9px] opacity-40 italic">
The screen saver automatically rotates digital posters when no activity is detected for the specified time.
The screen saver automatically rotates digital posters when
no activity is detected for the specified time.
</p>
</div>
{/if}

View File

@@ -13,7 +13,15 @@
on_toggle?: (new_state: 'collapsed' | 'auto' | 'pinned') => void;
}
let { title, icon, state = $bindable(), description, children, on_expand, on_toggle }: Props = $props();
let {
title,
icon,
state = $bindable(),
description,
children,
on_expand,
on_toggle
}: Props = $props();
function toggle_expand() {
if (state === 'collapsed') {
@@ -40,40 +48,66 @@
</script>
<section
class="w-full transition-all duration-300 border rounded-lg overflow-hidden mb-2 { !is_open ? 'preset-outlined-surface-300-700' : '' } { state === 'auto' ? 'preset-outlined-primary-500 shadow-xl' : '' } { state === 'pinned' ? 'preset-outlined-warning-500 shadow-xl' : '' }"
class="w-full transition-all duration-300 border rounded-lg overflow-hidden mb-2 {!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 ? 'bg-surface-500/5' : '' } { state === 'auto' ? 'bg-primary-500/10' : '' } { state === 'pinned' ? 'bg-warning-500/10' : '' }"
<header
class="flex flex-row items-center justify-between p-2 cursor-pointer transition-colors {!is_open
? 'bg-surface-500/5'
: ''} {state === 'auto' ? 'bg-primary-500/10' : ''} {state ===
'pinned'
? 'bg-warning-500/10'
: ''}"
onclick={toggle_expand}
>
<div class="flex items-center gap-3">
<span class="fas {icon} w-5 text-center opacity-70 { state === 'auto' ? 'text-primary-500' : '' } { state === 'pinned' ? 'text-warning-500' : '' }"></span>
<span
class="fas {icon} w-5 text-center opacity-70 {state === 'auto'
? 'text-primary-500'
: ''} {state === 'pinned' ? 'text-warning-500' : ''}"
></span>
<div class="flex flex-col">
<span class="text-sm font-bold tracking-tight uppercase { !is_open ? 'opacity-50' : '' }">{title}</span>
<span
class="text-sm font-bold tracking-tight uppercase {!is_open
? 'opacity-50'
: ''}">{title}</span
>
{#if description && !is_open}
<span class="text-[9px] opacity-40 italic truncate max-w-[180px]">{description}</span>
<span
class="text-[9px] opacity-40 italic truncate max-w-[180px]"
>{description}</span
>
{/if}
</div>
</div>
<div class="flex items-center gap-2">
<!-- Pin Toggle -->
<button type="button"
<button
type="button"
onclick={toggle_pin}
class="btn btn-icon btn-xs transition-all hover:scale-110"
class:opacity-20={state !== 'pinned'}
class:text-warning-500={state === 'pinned'}
title={state === 'pinned' ? 'Unpin Section' : 'Pin Section (Stay open)'}
title={state === 'pinned'
? 'Unpin Section'
: 'Pin Section (Stay open)'}
>
<span class="fas fa-thumbtack text-[10px]"></span>
</button>
<!-- Collapse Icon -->
<span class="fas transition-transform duration-300 opacity-30"
class:fa-chevron-right={!is_open}
<span
class="fas transition-transform duration-300 opacity-30"
class:fa-chevron-right={!is_open}
class:fa-chevron-down={is_open}
class:rotate-180={is_open}
></span>
@@ -82,19 +116,29 @@
<!-- Content -->
{#if is_open}
<div transition:slide={{ duration: 300 }} class="p-3 bg-white/5 dark:bg-black/5">
<div
transition:slide={{ duration: 300 }}
class="p-3 bg-white/5 dark:bg-black/5"
>
{#if $ae_loc.edit_mode}
<div class="mb-2 flex justify-between items-center px-1">
<span class="text-[8px] uppercase font-bold tracking-widest text-primary-500/60 flex items-center gap-1">
<span
class="text-[8px] uppercase font-bold tracking-widest text-primary-500/60 flex items-center gap-1"
>
<span class="fas fa-edit"></span> Technical Mode
</span>
{#if state === 'pinned'}
<span class="badge variant-filled-warning text-[8px] uppercase">Pinned</span>
<span
class="badge variant-filled-warning text-[8px] uppercase"
>Pinned</span
>
{/if}
</div>
{/if}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-1 gap-4 lg:gap-3">
<div
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-1 gap-4 lg:gap-3"
>
{@render children?.()}
</div>
</div>

View File

@@ -9,63 +9,92 @@
let { on_expand }: Props = $props();
</script>
<Launcher_Cfg_Section
title="Sync Engine & Timers"
icon="fa-sync"
<Launcher_Cfg_Section
title="Sync Engine & Timers"
icon="fa-sync"
bind:state={$events_loc.launcher.section_state__sync_timers}
{on_expand}
description="Prefix: {$ae_loc.native_device?.hash_prefix_length || 2} | Loops: Active"
description="Prefix: {$ae_loc.native_device?.hash_prefix_length ||
2} | Loops: Active"
>
<!-- Content omitted for brevity, preserved in file -->
{#if $ae_loc.native_device}
<div class="grid grid-cols-1 gap-3">
<!-- Technical Timers (Edit Mode Only) -->
{#if $ae_loc.edit_mode}
<div class="flex flex-col gap-2">
<label class="text-[9px] font-bold uppercase opacity-50 ml-1">Polling Periods (ms)</label>
<label
class="text-[9px] font-bold uppercase opacity-50 ml-1"
>Polling Periods (ms)</label
>
<div class="grid grid-cols-2 gap-2">
<div class="flex flex-col gap-1">
<span class="text-[8px] opacity-60">Event Data</span>
<input
type="number"
bind:value={$ae_loc.native_device.check_event_loop_period}
class="input input-sm text-[10px] h-7 preset-tonal-surface"
<span class="text-[8px] opacity-60">Event Data</span
>
<input
type="number"
bind:value={
$ae_loc.native_device
.check_event_loop_period
}
class="input input-sm text-[10px] h-7 preset-tonal-surface"
/>
</div>
<div class="flex flex-col gap-1">
<span class="text-[8px] opacity-60">Device Config</span>
<input
type="number"
bind:value={$ae_loc.native_device.check_event_device_loop_period}
class="input input-sm text-[10px] h-7 preset-tonal-surface"
<span class="text-[8px] opacity-60"
>Device Config</span
>
<input
type="number"
bind:value={
$ae_loc.native_device
.check_event_device_loop_period
}
class="input input-sm text-[10px] h-7 preset-tonal-surface"
/>
</div>
<div class="flex flex-col gap-1">
<span class="text-[8px] opacity-60">Room/Location</span>
<input
type="number"
bind:value={$ae_loc.native_device.check_event_location_loop_period}
class="input input-sm text-[10px] h-7 preset-tonal-surface"
<span class="text-[8px] opacity-60"
>Room/Location</span
>
<input
type="number"
bind:value={
$ae_loc.native_device
.check_event_location_loop_period
}
class="input input-sm text-[10px] h-7 preset-tonal-surface"
/>
</div>
<div class="flex flex-col gap-1">
<span class="text-[8px] opacity-60">Session Loop</span>
<input
type="number"
bind:value={$ae_loc.native_device.check_event_session_loop_period}
class="input input-sm text-[10px] h-7 preset-tonal-surface"
<span class="text-[8px] opacity-60"
>Session Loop</span
>
<input
type="number"
bind:value={
$ae_loc.native_device
.check_event_session_loop_period
}
class="input input-sm text-[10px] h-7 preset-tonal-surface"
/>
</div>
</div>
</div>
<div class="flex flex-col gap-1 mt-1 border-t border-surface-500/10 pt-2">
<label class="text-[9px] font-bold uppercase opacity-50 ml-1">Cache Structure</label>
<div
class="flex flex-col gap-1 mt-1 border-t border-surface-500/10 pt-2"
>
<label
class="text-[9px] font-bold uppercase opacity-50 ml-1"
>Cache Structure</label
>
<div class="flex items-center justify-between px-1">
<span class="text-[9px]">Hash Prefix Length</span>
<select
bind:value={$ae_loc.native_device.hash_prefix_length}
<select
bind:value={
$ae_loc.native_device.hash_prefix_length
}
class="select select-sm h-6 py-0 text-[10px] w-16 preset-tonal-surface"
>
<option value={1}>1 char</option>
@@ -73,26 +102,51 @@
<option value={3}>3 chars</option>
</select>
</div>
<p class="text-[8px] opacity-40 italic mt-1">* Prefix change requires a full app reload to take effect.</p>
<p class="text-[8px] opacity-40 italic mt-1">
* Prefix change requires a full app reload to take
effect.
</p>
</div>
{: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">
<div class="flex justify-between text-[9px] opacity-60 font-mono">
<div
class="bg-surface-500/5 p-2 rounded border border-surface-500/10 flex flex-col gap-1"
>
<div
class="flex justify-between text-[9px] opacity-60 font-mono"
>
<span>Event Sync:</span>
<span>{($ae_loc.native_device.check_event_loop_period / 1000).toFixed(1)}s</span>
<span
>{(
$ae_loc.native_device.check_event_loop_period /
1000
).toFixed(1)}s</span
>
</div>
<div class="flex justify-between text-[9px] opacity-60 font-mono">
<div
class="flex justify-between text-[9px] opacity-60 font-mono"
>
<span>Room Monitor:</span>
<span>{($ae_loc.native_device.check_event_location_loop_period / 1000).toFixed(1)}s</span>
<span
>{(
$ae_loc.native_device
.check_event_location_loop_period / 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">
<div
class="flex justify-between text-[9px] opacity-60 font-mono border-t border-surface-500/10 pt-1"
>
<span>Prefix Sharding:</span>
<span>{$ae_loc.native_device.hash_prefix_length || 2} chars</span>
<span
>{$ae_loc.native_device.hash_prefix_length || 2} chars</span
>
</div>
</div>
<div class="text-center">
<p class="text-[8px] opacity-40 italic">Enable Edit Mode to adjust polling intervals.</p>
<p class="text-[8px] opacity-40 italic">
Enable Edit Mode to adjust polling intervals.
</p>
</div>
{/if}
</div>

View File

@@ -25,46 +25,52 @@
async function handle_test_action(label: string) {
is_loading = true;
action_status = `Executing ${label}...`;
// Simulate async work
await new Promise(r => setTimeout(r, 1500));
await new Promise((r) => setTimeout(r, 1500));
is_loading = false;
action_status = `Finished: ${label}`;
setTimeout(() => action_status = '', 3000);
setTimeout(() => (action_status = ''), 3000);
}
// Modal state for destructive actions
let show_confirm = $state(false);
</script>
<Launcher_Cfg_Section
title="Template Section"
icon="fa-cubes"
<Launcher_Cfg_Section
title="Template Section"
icon="fa-cubes"
bind:state={$events_loc.launcher.section_state__template}
{on_expand}
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">
<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"
>
{action_status}
</div>
{/if}
<!-- B. COMMON GRID SECTION (Read Only / High Level) -->
<div class="flex flex-col gap-2">
<label class="text-[9px] font-bold uppercase opacity-50 ml-1">Standard Actions</label>
<label class="text-[9px] font-bold uppercase opacity-50 ml-1"
>Standard Actions</label
>
<!-- Action Buttons -->
<div class="grid grid-cols-2 gap-1">
<button type="button"
<button
type="button"
onclick={() => handle_test_action('Primary')}
class="btn btn-xs preset-tonal-primary hover:preset-filled-primary-500 justify-start"
>
<span class="fas fa-bolt mr-2 w-3 text-center"></span> Primary
</button>
<button type="button"
<button
type="button"
onclick={() => handle_test_action('Secondary')}
class="btn btn-xs preset-tonal-secondary hover:preset-filled-secondary-500 justify-start"
>
@@ -75,39 +81,68 @@
<!-- 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">
<input type="checkbox" bind:checked={toggle_val} class="checkbox checkbox-sm" />
<span class="text-xs group-hover:text-primary-500 transition-colors">Toggle Feature Alpha</span>
<input
type="checkbox"
bind:checked={toggle_val}
class="checkbox checkbox-sm"
/>
<span
class="text-xs group-hover:text-primary-500 transition-colors"
>Toggle Feature Alpha</span
>
</label>
<label class="flex items-center gap-2 cursor-pointer group">
<input type="radio" name="demo" value="a" class="radio radio-sm" />
<span class="text-xs group-hover:text-primary-500 transition-colors">Mode A</span>
<input
type="radio"
name="demo"
value="a"
class="radio radio-sm"
/>
<span
class="text-xs group-hover:text-primary-500 transition-colors"
>Mode A</span
>
</label>
</div>
</div>
<!-- C. STATUS & GAUGES SECTION -->
<div class="flex flex-col gap-2">
<label class="text-[9px] font-bold uppercase opacity-50 ml-1">Current Status</label>
<div class="flex flex-col gap-2 p-2 border border-surface-500/10 rounded-lg">
<label class="text-[9px] font-bold uppercase opacity-50 ml-1"
>Current Status</label
>
<div
class="flex flex-col gap-2 p-2 border border-surface-500/10 rounded-lg"
>
<div class="flex justify-between items-center">
<span class="text-[10px] font-medium">Engine Health</span>
<span class="badge variant-filled-success text-[8px] uppercase">Stable</span>
<span class="badge variant-filled-success text-[8px] uppercase"
>Stable</span
>
</div>
<!-- Progress / Gauge Example -->
<div class="flex flex-col gap-1">
<div class="flex justify-between text-[8px] uppercase opacity-60">
<div
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">
<div class="h-full bg-success-500 transition-all duration-1000" style="width: 45%"></div>
<div
class="w-full h-1.5 bg-surface-500/20 rounded-full overflow-hidden"
>
<div
class="h-full bg-success-500 transition-all duration-1000"
style="width: 45%"
></div>
</div>
</div>
</div>
<button type="button"
<button
type="button"
onclick={() => handle_test_action('Refresh')}
class="btn btn-xs preset-outlined-surface-500 w-full text-[10px]"
>
@@ -118,22 +153,34 @@
<!-- 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">
<div
class="col-span-full border-t border-surface-500/20 pt-3 mt-1 flex flex-col gap-3"
>
<!-- Dangerous Actions -->
<div class="grid grid-cols-2 gap-2">
<div class="flex flex-col gap-1">
<label class="text-[9px] font-bold uppercase opacity-50 text-warning-500">System Config</label>
<button type="button"
onclick={() => show_confirm = true}
<label
class="text-[9px] font-bold uppercase opacity-50 text-warning-500"
>System Config</label
>
<button
type="button"
onclick={() => (show_confirm = true)}
class="btn btn-xs preset-tonal-warning hover:preset-filled-warning-500 justify-start"
>
<span class="fas fa-exclamation-triangle mr-2 w-3"></span> Reset All
<span class="fas fa-exclamation-triangle mr-2 w-3"
></span> Reset All
</button>
</div>
<div class="flex flex-col gap-1">
<label class="text-[9px] font-bold uppercase opacity-50 text-error-500">Danger Zone</label>
<button type="button" class="btn btn-xs preset-tonal-error hover:preset-filled-error-500 justify-start">
<label
class="text-[9px] font-bold uppercase opacity-50 text-error-500"
>Danger Zone</label
>
<button
type="button"
class="btn btn-xs preset-tonal-error hover:preset-filled-error-500 justify-start"
>
<span class="fas fa-trash-alt mr-2 w-3"></span> Wipe Cache
</button>
</div>
@@ -142,35 +189,46 @@
<!-- Form Inputs -->
<div class="grid grid-cols-1 gap-2">
<div class="flex flex-col gap-1">
<label class="text-[9px] font-bold uppercase opacity-50 ml-1">Raw Settings</label>
<label
class="text-[9px] font-bold uppercase opacity-50 ml-1"
>Raw Settings</label
>
<div class="flex gap-1">
<input
type="text"
bind:value={text_input}
<input
type="text"
bind:value={text_input}
placeholder="Enter string parameter..."
class="input input-sm grow text-[10px] preset-tonal-surface h-7"
/>
<select bind:value={select_val} class="select select-sm h-7 py-0 text-[10px] w-24 preset-tonal-surface">
<select
bind:value={select_val}
class="select select-sm h-7 py-0 text-[10px] w-24 preset-tonal-surface"
>
<option value="option1">Global</option>
<option value="option2">Local</option>
</select>
</div>
</div>
<div class="flex flex-col gap-1">
<span class="text-[8px] opacity-60 ml-1">Threshold (ms)</span>
<input
type="number"
<span class="text-[8px] opacity-60 ml-1"
>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 text-[10px] h-7 preset-tonal-surface"
/>
</div>
</div>
<!-- Terminal / Output Log -->
<div class="flex flex-col gap-1">
<label class="text-[9px] font-bold uppercase opacity-50 ml-1">Debug Output</label>
<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">
<label class="text-[9px] font-bold uppercase opacity-50 ml-1"
>Debug Output</label
>
<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">
[LOG] System Initialized
[INFO] Store synced with IndexedDB
[DEBUG] active_tab: template
@@ -183,16 +241,29 @@
<!-- 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">
<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">
<div
class="fixed inset-0 z-[1000] flex items-center justify-center bg-black/50 backdrop-blur-sm p-4"
>
<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">
Are you sure you want to perform this test operation? This demonstrate the standard confirmation pattern.
Are you sure you want to perform this test operation? This
demonstrate the standard confirmation pattern.
</p>
<div class="flex justify-end gap-2">
<button type="button" onclick={() => show_confirm = false} class="btn btn-sm preset-tonal-surface">Cancel</button>
<button type="button"
onclick={() => { show_confirm = false; handle_test_action('Confirm'); }}
<button
type="button"
onclick={() => (show_confirm = false)}
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"
>
Yes, Proceed

View File

@@ -10,9 +10,13 @@
let { on_expand }: Props = $props();
let update_source: 'url' | 'file' = $state('file');
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_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_status = $state('');
let is_checking = $state(false);
let download_result = $state<any>(null);
@@ -20,14 +24,15 @@
async function handle_check_update() {
is_checking = true;
update_status = 'Checking for updates...';
try {
const args = update_source === 'url'
? { source: 'url' as const, url: update_url }
: { source: 'file' as const, path: update_path };
const args =
update_source === 'url'
? { source: 'url' as const, url: update_url }
: { source: 'file' as const, path: update_path };
const res = await native.update_app(args);
if (res.success) {
download_result = res;
update_status = 'Update located/downloaded. Ready to install.';
@@ -43,45 +48,60 @@
async function handle_install() {
update_status = 'Initiating installation...';
alert('Installation logic is OS-specific. This will typically swap the application bundle and restart.');
alert(
'Installation logic is OS-specific. This will typically swap the application bundle and restart.'
);
}
</script>
<Launcher_Cfg_Section
title="Application Updates"
icon="fa-cloud-download-alt"
<Launcher_Cfg_Section
title="Application Updates"
icon="fa-cloud-download-alt"
bind:state={$events_loc.launcher.section_state__updates}
{on_expand}
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-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">
<label class="text-[9px] font-bold uppercase opacity-50">Source Type</label>
<label class="text-[9px] font-bold uppercase opacity-50"
>Source Type</label
>
<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
<input
type="radio"
bind:group={update_source}
value="file"
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
<input
type="radio"
bind:group={update_source}
value="url"
class="radio radio-sm"
/> Web
</label>
</div>
</div>
{#if update_source === 'file'}
<input
type="text"
<input
type="text"
bind:value={update_path}
placeholder="Path to update package"
class="input input-sm text-[10px] preset-tonal-surface h-7 w-full"
/>
{:else}
<input
type="text"
<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"
@@ -91,7 +111,8 @@
{/if}
<!-- COMMON: Check Button -->
<button type="button"
<button
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"
@@ -104,13 +125,16 @@
</button>
{#if update_status}
<div class="text-[9px] text-center italic p-1 border border-surface-500/20 rounded bg-surface-500/5 mt-1">
<div
class="text-[9px] text-center italic p-1 border border-surface-500/20 rounded bg-surface-500/5 mt-1"
>
{update_status}
</div>
{/if}
{#if download_result}
<button type="button"
<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"
>

View File

@@ -58,9 +58,7 @@
// *** Set initial variables
let ae_acct = data[$slct.account_id];
import {
online
} from 'svelte/reactivity/window';
import { online } from 'svelte/reactivity/window';
$ae_sess.disable_sys_nav = true;
$ae_sess.disable_sys_header = true;
@@ -80,7 +78,9 @@
if (log_lvl) {
console.log(`event_id: ${data.params.event_id}`);
console.log(`event_location_id: ${data.params.event_location_id}`);
console.log(`event_session_id: ${data.url.searchParams.get('session_id')}`);
console.log(
`event_session_id: ${data.url.searchParams.get('session_id')}`
);
}
$events_slct.event_id = data.params.event_id;
$events_slct.event_location_id = data.params.event_location_id;
@@ -89,11 +89,19 @@
// String-Only ID Vision: Sync the device ID from the native environment
const native_dev = $ae_loc.native_device;
if (native_dev) {
$events_slct.event_device_id = native_dev.event_device_id || native_dev.id || native_dev.event_device_id_random || native_dev.id_random;
$events_slct.event_device_id =
native_dev.event_device_id ||
native_dev.id ||
native_dev.event_device_id_random ||
native_dev.id_random;
}
$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_location_obj_li = ae_acct.slct.event_location_obj_li ?? [
''
];
$events_slct.id_li__event_location = ae_acct.slct.id_li__event_location ?? [
''
];
// *** Functions and Logic
@@ -103,9 +111,12 @@
if (!id) return null;
if (log_lvl > 1) console.log(`lq__event_obj: event_id = ${id}`);
let results = await db_events.event.get(id);
if ($events_slct.event_obj && results) {
if (JSON.stringify($events_slct.event_obj) !== JSON.stringify(results)) {
if (
JSON.stringify($events_slct.event_obj) !==
JSON.stringify(results)
) {
$events_slct.event_obj = { ...results };
}
}
@@ -118,7 +129,10 @@
if (!id) return null;
let results = await db_events.device.get(id);
if ($events_slct.event_device_obj && results) {
if (JSON.stringify($events_slct.event_device_obj) !== JSON.stringify(results)) {
if (
JSON.stringify($events_slct.event_device_obj) !==
JSON.stringify(results)
) {
$events_slct.event_device_obj = { ...results };
}
}
@@ -129,14 +143,20 @@
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
let lq__location_event_file_obj_li = liveQuery(async () => {
const id = $events_slct.event_location_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 Location
@@ -149,7 +169,10 @@
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');
});
// Event Session (Main View Trigger)
@@ -157,24 +180,37 @@
let lq__event_session_obj = liveQuery(async () => {
const id = $events_slct.event_session_id;
if (!id) return null;
if (log_lvl) console.log(`🔍 [Trace] Launcher Layout LQ: Fetching session_id=${id}`);
if (log_lvl)
console.log(
`🔍 [Trace] Launcher Layout LQ: Fetching session_id=${id}`
);
const start = performance.now();
let results = await db_events.session.get(id);
if (log_lvl) console.log(`📦 [Trace] Launcher Layout LQ: Result obtained in ${(performance.now() - start).toFixed(2)}ms (Result=${results?.name || 'NOT FOUND'})`);
if (log_lvl)
console.log(
`📦 [Trace] Launcher Layout LQ: Result obtained in ${(performance.now() - start).toFixed(2)}ms (Result=${results?.name || 'NOT FOUND'})`
);
return results;
});
let lq__event_session_obj_li = liveQuery(async () => {
const id = $events_slct.event_location_id;
if (!id) return [];
if (log_lvl > 1) console.log(`LQ - Using default sort for Event Session list location_id: ${id}`);
if (log_lvl > 1)
console.log(
`LQ - Using default sort for Event Session list location_id: ${id}`
);
let results = await db_events.session
.where('event_location_id')
.equals(id)
.reverse()
.sortBy('name');
if ($events_slct.event_session_obj_li && JSON.stringify($events_slct.event_session_obj_li) !== JSON.stringify(results)) {
if (
$events_slct.event_session_obj_li &&
JSON.stringify($events_slct.event_session_obj_li) !==
JSON.stringify(results)
) {
$events_slct.event_session_obj_li = [...(results || [])];
}
return results;
@@ -210,7 +246,10 @@
$events_slct.event_session_id = obj_id;
let new_url = new URL(data.url);
new_url.pathname = `/events/${$lq__event_session_obj?.event_id}/launcher/${$lq__event_session_obj?.event_location_id}`;
new_url.searchParams.set('session_id', $events_slct.event_session_id);
new_url.searchParams.set(
'session_id',
$events_slct.event_session_id
);
goto(new_url.toString(), { replaceState: false });
}
} else if (cmd.startsWith('ae_download:')) {
@@ -283,9 +322,12 @@
}
});
if (!$events_loc.launcher.idle_timer) $events_loc.launcher.idle_timer = 5 * 60 * 1000;
if (!$events_loc.launcher.idle_cycle) $events_loc.launcher.idle_cycle = 5 * 1000;
if (!$events_loc.launcher.idle_loop_period) $events_loc.launcher.idle_loop_period = 3 * 60 * 1000;
if (!$events_loc.launcher.idle_timer)
$events_loc.launcher.idle_timer = 5 * 60 * 1000;
if (!$events_loc.launcher.idle_cycle)
$events_loc.launcher.idle_cycle = 5 * 1000;
if (!$events_loc.launcher.idle_loop_period)
$events_loc.launcher.idle_loop_period = 3 * 60 * 1000;
listen({
timer: $events_loc.launcher.idle_timer,
@@ -296,26 +338,43 @@
let saver_looping: boolean = $state(false);
function handle_idle_client() {
if ($lq__event_session_obj && $lq__event_session_obj?.type_code == 'poster') {
if (
$lq__event_session_obj &&
$lq__event_session_obj?.type_code == 'poster'
) {
if (saver_looping) return false;
saver_looping = true;
idle_timer_interval = setInterval(() => {
if ($events_loc.launcher.screen_saver_img_kv) {
const keys = Object.keys($events_loc.launcher.screen_saver_img_kv);
const rand_index = Math.floor(Math.random() * keys.length);
let event_file_obj = $events_loc.launcher.screen_saver_img_kv[keys[rand_index]];
idle_timer_interval = setInterval(
() => {
if ($events_loc.launcher.screen_saver_img_kv) {
const keys = Object.keys(
$events_loc.launcher.screen_saver_img_kv
);
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_obj = event_file_obj;
$events_sess.launcher.modal__open_event_file_id = null;
$events_sess.launcher.modal__title = event_file_obj.filename ?? '*';
$events_sess.launcher.modal__open_event_file_id = $events_slct.event_file_id;
$events_sess.launcher.modal__event_file_obj = event_file_obj;
return true;
}
return false;
}, $events_loc.launcher.idle_loop_period ?? 2 * 60 * 1000);
$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 =
event_file_obj.filename ?? '*';
$events_sess.launcher.modal__open_event_file_id =
$events_slct.event_file_id;
$events_sess.launcher.modal__event_file_obj =
event_file_obj;
return true;
}
return false;
},
$events_loc.launcher.idle_loop_period ?? 2 * 60 * 1000
);
} else {
saver_looping = false;
return false;
@@ -376,7 +435,8 @@
"
>
<h3 class="h4 text-center italic text-surface-600-400">
<button type="button"
<button
type="button"
class=""
onclick={() => {
$events_loc.launcher.hide__launcher_menu =
@@ -387,20 +447,26 @@
<Satellite class="text-base mx-1 inline-block text-gray-500" />
<abbr title="Aether - Events Module Launcher">
Æ Launcher
<span class="text-xs align-super font-normal" title="Version 3">v3</span>
<span
class="text-xs align-super 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">
<h2
class="hidden md:inline-block h3 text-center text-surface-600-400"
>
{$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}"
>
<button type="button"
<button
type="button"
class="text-base"
onclick={() => {
$ae_loc.edit_mode = !$ae_loc.edit_mode;
@@ -455,9 +521,13 @@
{lq__event_location_obj_li}
{lq__event_location_obj}
slct__event_location_id={$events_slct.event_location_id}
bind:loading__session_li_status={$events_sess.launcher.loading__session_li_status}
bind:loading__session_li_status={
$events_sess.launcher.loading__session_li_status
}
{lq__event_session_obj_li}
bind:loading__session_id_status={$events_sess.launcher.loading__session_id_status}
bind:loading__session_id_status={
$events_sess.launcher.loading__session_id_status
}
{lq__event_session_obj}
bind:slct__event_session_id={$events_slct.event_session_id}
bind:trigger_reload__event_session_obj_id={
@@ -487,7 +557,9 @@
"
>
{#if !$events_slct.event_location_id}
<div class="flex flex-row items-center justify-center p-8 opacity-50">
<div
class="flex flex-row items-center justify-center p-8 opacity-50"
>
<span class="fas fa-map-marker-alt mx-2 text-2xl"></span>
<span>Please select a location from the menu</span>
</div>
@@ -500,7 +572,9 @@
bind:type_code={$lq__event_session_obj.type_code}
></Launcher_session_view>
{:else if $events_slct.event_session_id}
<div class="flex flex-col items-center justify-center p-8 opacity-50">
<div
class="flex flex-col items-center justify-center p-8 opacity-50"
>
<LoaderCircle class="animate-spin mb-2" />
<span>Loading session details...</span>
</div>
@@ -537,7 +611,8 @@
class="slct_location_name transition-all duration-1000"
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}"
>
<button type="button"
<button
type="button"
class=""
onclick={() => {
$ae_loc.edit_mode = !$ae_loc.edit_mode;
@@ -579,10 +654,13 @@
<span
class:hidden={!$events_loc.launcher.ws_connect}
class:preset-tonal-warning={$events_sess.launcher.ws_connect_status != 'connected'}
class:preset-tonal-success={$events_sess.launcher.ws_connect_status == 'connected'}
class:preset-tonal-warning={$events_sess.launcher.ws_connect_status !=
'connected'}
class:preset-tonal-success={$events_sess.launcher.ws_connect_status ==
'connected'}
class="group px-1 rounded-md transition-all duration-1000"
title="WebSocket is {$events_sess.launcher.ws_connect_status == 'connected'
title="WebSocket is {$events_sess.launcher.ws_connect_status ==
'connected'
? 'connected'
: 'disconnected'} API: {$ae_api?.base_url}"
>
@@ -595,7 +673,9 @@
{/if}
</span>
<div class="current_datetime font-mono px-2 hover:font-bold hover:bg-white transition-all">
<div
class="current_datetime font-mono px-2 hover:font-bold hover:bg-white transition-all"
>
<span class="hidden md:inline">
<span class="fas fa-calendar-alt"></span>
{ae_util.iso_datetime_formatter($time, 'date_full_no_year')}
@@ -612,7 +692,8 @@
</footer>
<div class="absolute top-0 left-0 z-20 text-center">
<button type="button"
<button
type="button"
onclick={() => ($events_loc.launcher.hide_drawer__cfg = false)}
class="btn btn-sm p-2.5 preset-tonal-error hover:preset-filled-error-500 transition-all duration-1000"
class:opacity-25={!$ae_loc.trusted_access}
@@ -639,7 +720,9 @@
<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">
<div
class="flex flex-row flex-wrap gap-0.5 items-center justify-center max-w-md"
>
<a
href="/events/{$events_slct.event_id}"
class="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500"
@@ -682,10 +765,13 @@
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">
<h2
class="text-center mb-4 text-base font-semibold text-gray-500 dark:text-gray-400"
>
Debug
</h2>
<button type="button"
<button
type="button"
onclick={() => ($events_loc.launcher.hide_drawer__debug = true)}
class="mb-4 dark:text-white"
>
@@ -729,7 +815,8 @@
>
{$events_sess.launcher?.modal__title ?? 'Digital Poster Display'}
</h3>
<button type="button"
<button
type="button"
class="btn flex-row-reverse group transition-all justify-self-end"
onclick={() => {
$events_sess.launcher.modal__open_event_file_id = null;
@@ -741,7 +828,8 @@
</button>
{/snippet}
<button type="button"
<button
type="button"
onclick={() => {
$events_sess.launcher.controller_cmd = `ae_close:event_file_modal`;
$events_sess.launcher.controller_trigger_send = true;
@@ -766,8 +854,8 @@
{#if $events_sess.launcher.modal__open_event_file_id}
<img
src="{$ae_api.base_url}/v3/action/event_file/{$events_sess.launcher
.modal__open_event_file_id}/download?filename={$events_slct.event_file_obj
.filename}&key={$ae_api.account_id}"
.modal__open_event_file_id}/download?filename={$events_slct
.event_file_obj.filename}&key={$ae_api.account_id}"
alt="Poster"
class="min-h-28 min-w-md max-h-full max-w-full"
/>
@@ -778,7 +866,8 @@
</div>
{/if}
<button type="button"
<button
type="button"
onclick={() => {
$events_sess.launcher.controller_cmd = `ae_close:event_file_modal`;
$events_sess.launcher.controller_trigger_send = true;
@@ -800,7 +889,8 @@
Close Remote Poster Display Only
</button>
<button type="button"
<button
type="button"
onclick={() => {
$events_sess.launcher.modal__title = '';
$events_sess.launcher.modal__open_event_file_id = null;
@@ -846,4 +936,4 @@
bind:ws_recv_status={trigger_handle_ws_recv}
bind:ws_sent_status={trigger_handle_ws_sent}
/>
{/if}
{/if}

View File

@@ -17,7 +17,9 @@ export async function load({ params, parent, url }) {
let ae_acct = data[account_id];
if (!ae_acct) {
console.warn(`ae Events - [event_id] launcher +layout.ts: Account ${account_id} not found in data. Initializing ghost acct.`);
console.warn(
`ae Events - [event_id] launcher +layout.ts: Account ${account_id} not found in data. Initializing ghost acct.`
);
ae_acct = {
api: data.ae_api || {},
slct: {
@@ -30,10 +32,13 @@ export async function load({ params, parent, url }) {
const event_id = params.event_id;
if (browser) {
if (log_lvl) console.log(`ae_events Launcher - [event_id] launcher +layout.ts (Non-Blocking)`);
if (log_lvl)
console.log(
`ae_events Launcher - [event_id] launcher +layout.ts (Non-Blocking)`
);
// OPTIMIZATION: Fire these in the background without 'await'.
// The UI components (menu, session_view) already use LiveQuery/Dexie
// The UI components (menu, session_view) already use LiveQuery/Dexie
// and will automatically update when these background tasks save to IDB.
events_func.load_ae_obj_li__event_device({
api_cfg: ae_acct.api,
@@ -51,4 +56,4 @@ export async function load({ params, parent, url }) {
}
return data;
}
}

View File

@@ -49,12 +49,21 @@
$events_slct.event_id = data.params.event_id;
$events_slct.event_location_id = data.params.event_location_id;
$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_location_obj_li = ae_acct.slct.event_location_obj_li ?? [
''
];
$events_slct.id_li__event_location = ae_acct.slct.id_li__event_location ?? [
''
];
if (log_lvl) {
console.log(`$events_slct.event_location_obj_li:`, $events_slct.event_location_obj_li);
console.log(
`$events_slct.event_location_obj_li:`,
$events_slct.event_location_obj_li
);
}
$events_slct.event_session_obj_li = ae_acct.slct.event_session_obj_li ?? [''];
$events_slct.event_session_obj_li = ae_acct.slct.event_session_obj_li ?? [
''
];
// Set the local storage values
if (!$events_loc.launcher) {
@@ -135,7 +144,8 @@
ni_data['name'] = network_interface;
let ni_details = result.networkInterfaces[network_interface];
let ni_details =
result.networkInterfaces[network_interface];
// console.log(ni_details);
for (let i = 0; i < ni_details.length; i++) {
@@ -155,12 +165,13 @@
event_device_data['info_ip_list'] = ip_address_li.join('; ');
console.log(event_device_data['info_ip_list']);
let event_device_obj_up_promise = events_func.update_ae_obj__event_device({
api_cfg: $ae_api,
event_device_id: event_device_id,
data_kv: event_device_data,
log_lvl: 0
})
let event_device_obj_up_promise = events_func
.update_ae_obj__event_device({
api_cfg: $ae_api,
event_device_id: event_device_id,
data_kv: event_device_data,
log_lvl: 0
})
.then(function (up_event_device_result) {
// console.log('UPDATED DEVICE INFO!!! UPDATED DEVICE INFO!!! UPDATED DEVICE INFO!!!');
if (up_event_device_result) {
@@ -177,6 +188,4 @@
}
</script>
<div class="hidden">
This is for forcing data loading.
</div>
<div class="hidden">This is for forcing data loading.</div>

View File

@@ -17,7 +17,9 @@ export async function load({ params, parent, url }) {
let ae_acct = data[account_id];
if (!ae_acct) {
console.warn(`ae Events - [event_id] launcher [event_location_id] +page.ts: Account ${account_id} not found in data. Initializing ghost acct.`);
console.warn(
`ae Events - [event_id] launcher [event_location_id] +page.ts: Account ${account_id} not found in data. Initializing ghost acct.`
);
ae_acct = {
api: data.ae_api || {},
slct: {
@@ -43,9 +45,9 @@ export async function load({ params, parent, url }) {
event_location_id
);
}
// OPTIMIZATION: Fire the session list refresh in the background.
// The Launcher components already use liveQuery to watch Dexie,
// The Launcher components already use liveQuery to watch Dexie,
// so they will update automatically once this background task finishes.
events_func.load_ae_obj_li__event_session({
api_cfg: ae_acct.api,
@@ -61,7 +63,9 @@ export async function load({ params, parent, url }) {
log_lvl: 0
});
} else {
console.log(`ae pres_mgmt launcher [slug] +page.ts: browser = false or location_id missing`);
console.log(
`ae pres_mgmt launcher [slug] +page.ts: browser = false or location_id missing`
);
}
// WARNING: Precaution against shared data between sites and sessions.

View File

@@ -14,7 +14,9 @@
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);
@@ -49,7 +51,10 @@
$ae_loc.native_device.home_directory = info.home_directory;
$ae_loc.native_device.tmp_directory = info.tmp_directory;
if (log_lvl) console.log('Sync: Native OS metadata hydrated.', { home: info.home_directory });
if (log_lvl)
console.log('Sync: Native OS metadata hydrated.', {
home: info.home_directory
});
}
} catch (err) {
console.error('Sync: Metadata hydration FAILED:', err);
@@ -66,8 +71,14 @@
// Timers run in both browser and native for dev/testing
timer__event = setInterval(() => run_sync_cycle(), loop_info.event);
timer__device = setInterval(() => run_device_heartbeat(), loop_info.device);
timer__location = setInterval(() => refresh_location_config(), loop_info.location);
timer__device = setInterval(
() => run_device_heartbeat(),
loop_info.device
);
timer__location = setInterval(
() => refresh_location_config(),
loop_info.location
);
timer__session = setInterval(() => run_sync_cycle(), loop_info.session);
// Immediate first run
@@ -88,22 +99,42 @@
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 {
const sessions = await db_events.session.where('event_location_id').equals(location_id).toArray();
const session_ids = sessions.map(s => s.event_session_id);
const sessions = await db_events.session
.where('event_location_id')
.equals(location_id)
.toArray();
const session_ids = sessions.map((s) => s.event_session_id);
if (session_ids.length === 0) return;
const presentations = await db_events.presentation.where('event_session_id').anyOf(session_ids).toArray();
const presentation_ids = presentations.map(p => p.event_presentation_id);
const presentations = await db_events.presentation
.where('event_session_id')
.anyOf(session_ids)
.toArray();
const presentation_ids = presentations.map(
(p) => p.event_presentation_id
);
const presenters = await db_events.presenter.where('event_session_id').anyOf(session_ids).toArray();
const presenter_ids = presenters.map(p => p.event_presenter_id);
const presenters = await db_events.presenter
.where('event_session_id')
.anyOf(session_ids)
.toArray();
const presenter_ids = presenters.map((p) => p.event_presenter_id);
const all_for_ids = [...session_ids, ...presentation_ids, ...presenter_ids, $events_slct.event_id];
const files = await db_events.file.where('for_id').anyOf(all_for_ids).toArray();
const all_for_ids = [
...session_ids,
...presentation_ids,
...presenter_ids,
$events_slct.event_id
];
const files = await db_events.file
.where('for_id')
.anyOf(all_for_ids)
.toArray();
sync_stats.total = files.length;
let cached_count = 0;
@@ -111,7 +142,10 @@
for (const file_obj of files) {
if (!file_obj.hash_sha256) continue;
if (sync_results[file_obj.event_file_id] === 'success') { cached_count++; continue; }
if (sync_results[file_obj.event_file_id] === 'success') {
cached_count++;
continue;
}
const exists = await native.check_hash_file_cache({
cache_root,
@@ -127,17 +161,22 @@
missing_count++;
currently_syncing = file_obj.filename;
$events_sess.launcher.sync_stats.currently_syncing = currently_syncing;
$events_sess.launcher.sync_stats.currently_syncing =
currently_syncing;
// Use the PROVEN endpoint path from api.ts that is known to work in Default Mode.
const url = `${$ae_api.base_url}/v3/action/hosted_file/${file_obj.hosted_file_id}/download?return_file=true&filename=${encodeURIComponent(file_obj.filename)}&key=${$ae_api.account_id}`;
const result = await native.download_to_cache({
url, cache_root, hash: file_obj.hash_sha256,
api_key: $ae_api.api_secret_key, account_id: $ae_api.account_id,
url,
cache_root,
hash: file_obj.hash_sha256,
api_key: $ae_api.api_secret_key,
account_id: $ae_api.account_id,
hash_prefix_length: prefix_len
});
if (result.success) sync_results[file_obj.event_file_id] = 'success';
if (result.success)
sync_results[file_obj.event_file_id] = 'success';
}
sync_stats.cached = cached_count;
sync_stats.missing = missing_count;
@@ -165,15 +204,23 @@
async function run_device_heartbeat() {
const dev = $ae_loc.native_device;
// String-Only ID Vision: Prioritize semantic string IDs, then generic, then legacy random strings
const device_id = dev?.event_device_id || dev?.id || dev?.event_device_id_random || dev?.id_random;
const device_id =
dev?.event_device_id ||
dev?.id ||
dev?.event_device_id_random ||
dev?.id_random;
if (!device_id) {
if (log_lvl) console.warn('Sync: Heartbeat skipped, no device_id found in $ae_loc.native_device.');
if (log_lvl)
console.warn(
'Sync: Heartbeat skipped, no device_id found in $ae_loc.native_device.'
);
$events_sess.launcher.heartbeat_info.status = 'error';
return;
}
if (log_lvl > 1) console.log(`Sync: Running heartbeat for device: ${device_id}`);
if (log_lvl > 1)
console.log(`Sync: Running heartbeat for device: ${device_id}`);
try {
let info = null;
@@ -183,7 +230,10 @@
const update_payload: any = {
// Use standard SQL format YYYY-MM-DD HH:mm:ss for MySQL compatibility
heartbeat: ae_util.iso_datetime_formatter(new Date(), 'datetime_iso')
heartbeat: ae_util.iso_datetime_formatter(
new Date(),
'datetime_iso'
)
};
if (info) {
@@ -194,7 +244,8 @@
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 {
@@ -243,42 +294,70 @@
</script>
<!-- Monitor Overlay (Hidden by default, triggered by config modal or secret gesture) -->
<div class="fixed bottom-4 right-4 z-[9999] flex flex-col items-end gap-2 pointer-events-none">
<div
class="fixed bottom-4 right-4 z-[9999] flex flex-col items-end gap-2 pointer-events-none"
>
{#if show_monitor}
<div class="bg-black/90 text-white p-3 rounded-lg border border-primary-500 shadow-2xl text-[10px] font-mono min-w-48 pointer-events-auto">
<div class="flex justify-between border-b border-primary-500 pb-1 mb-2">
<span class="font-bold text-primary-400">NATIVE SYNC MONITOR</span>
<button type="button" onclick={() => show_monitor = false} class="text-error-500 hover:text-error-400">×</button>
<div
class="bg-black/90 text-white p-3 rounded-lg border border-primary-500 shadow-2xl text-[10px] font-mono min-w-48 pointer-events-auto"
>
<div
class="flex justify-between border-b border-primary-500 pb-1 mb-2"
>
<span class="font-bold text-primary-400"
>NATIVE SYNC MONITOR</span
>
<button
type="button"
onclick={() => (show_monitor = false)}
class="text-error-500 hover:text-error-400">×</button
>
</div>
<div class="grid grid-cols-2 gap-x-4 gap-y-1 mb-2">
<span class="opacity-70 text-primary-300">Room Status:</span>
<span class="text-right">{sync_stats.cached} / {sync_stats.total} Files</span>
<span class="text-right"
>{sync_stats.cached} / {sync_stats.total} Files</span
>
<span class="opacity-70 text-primary-300">Prefix Len:</span>
<span class="text-right">{$ae_loc.native_device?.hash_prefix_length || 2} chars</span>
<span class="text-right"
>{$ae_loc.native_device?.hash_prefix_length || 2} chars</span
>
<span class="opacity-70 text-primary-300">Heartbeat:</span>
<span class="text-right {last_heartbeat ? 'text-success-500' : 'text-error-500'}">
<span
class="text-right {last_heartbeat
? 'text-success-500'
: 'text-error-500'}"
>
{last_heartbeat || 'Pending...'}
</span>
</div>
<div class="border-t border-white/10 pt-2 flex flex-col gap-1">
<div class="flex justify-between items-center">
<span class:text-primary-500={timer__event}>Event Loop:</span>
<span class:text-primary-500={timer__event}
>Event Loop:</span
>
<span>{loop_info.event / 1000}s</span>
</div>
<div class="flex justify-between items-center">
<span class:text-primary-500={timer__device}>Device Loop:</span>
<span class:text-primary-500={timer__device}
>Device Loop:</span
>
<span>{loop_info.device / 1000}s</span>
</div>
<div class="flex justify-between items-center">
<span class:text-primary-500={timer__location}>Location Loop:</span>
<span class:text-primary-500={timer__location}
>Location Loop:</span
>
<span>{loop_info.location / 1000}s</span>
</div>
<div class="flex justify-between items-center">
<span class:text-primary-500={timer__session}>Session Loop:</span>
<span class:text-primary-500={timer__session}
>Session Loop:</span
>
<span>{loop_info.session / 1000}s</span>
</div>
</div>
@@ -286,27 +365,34 @@
{/if}
{#if currently_syncing}
<button type="button"
onclick={() => show_monitor = !show_monitor}
<button
type="button"
onclick={() => (show_monitor = !show_monitor)}
class="bg-black/80 text-white p-2 rounded-lg text-[10px] border border-primary-500 animate-pulse shadow-2xl pointer-events-auto transition-transform active:scale-95"
>
<div class="flex items-center gap-2 text-left">
<span class="fas fa-sync fa-spin text-primary-500 text-sm"></span>
<span class="fas fa-sync fa-spin text-primary-500 text-sm"
></span>
<div class="flex flex-col">
<span class="font-bold">Syncing Room Files...</span>
<span class="opacity-70 truncate max-w-48">{currently_syncing}</span>
<span class="text-[8px] mt-1 text-primary-300">Monitor Ready (Click to View)</span>
<span class="opacity-70 truncate max-w-48"
>{currently_syncing}</span
>
<span class="text-[8px] mt-1 text-primary-300"
>Monitor Ready (Click to View)</span
>
</div>
</div>
</button>
{:else}
<!-- Secret button area to toggle monitor when not syncing -->
<button type="button"
onclick={() => show_monitor = !show_monitor}
<button
type="button"
onclick={() => (show_monitor = !show_monitor)}
class="w-8 h-8 opacity-0 hover:opacity-20 bg-primary-500 rounded-full pointer-events-auto flex items-center justify-center transition-opacity"
title="Toggle Sync Monitor"
>
<span class="fas fa-microchip text-white text-[10px]"></span>
</button>
{/if}
</div>
</div>

View File

@@ -43,8 +43,11 @@
*/
function handle_section_expand(current_key: string) {
const launcher = $events_loc.launcher;
Object.keys(launcher).forEach(key => {
if (key.startsWith('section_state__') && key !== `section_state__${current_key}`) {
Object.keys(launcher).forEach((key) => {
if (
key.startsWith('section_state__') &&
key !== `section_state__${current_key}`
) {
if (launcher[key] === 'auto') {
launcher[key] = 'collapsed';
}
@@ -60,13 +63,18 @@
flex flex-col gap-4 items-center justify-start
"
>
<div class="w-full flex flex-row items-center justify-between border-b border-surface-500/20 pb-2">
<h2 class="text-center text-lg font-bold text-gray-700 dark:text-gray-200">
<div
class="w-full flex flex-row items-center justify-between border-b border-surface-500/20 pb-2"
>
<h2
class="text-center text-lg font-bold text-gray-700 dark:text-gray-200"
>
<span class="fas fa-cog mr-2 opacity-50"></span>
Launcher Configuration
</h2>
<button type="button"
<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"
>
@@ -77,24 +85,27 @@
<!-- Category Tabs -->
<div class="w-full grid grid-cols-3 gap-1 bg-surface-500/10 p-1 rounded-lg">
<button type="button"
onclick={() => active_tab = 'system'}
<button
type="button"
onclick={() => (active_tab = 'system')}
class="btn btn-sm text-[10px] uppercase font-bold transition-all"
class:preset-filled-primary-500={active_tab === 'system'}
class:opacity-50={active_tab !== 'system'}
>
<span class="fas fa-microchip mr-1"></span> System
</button>
<button type="button"
onclick={() => active_tab = 'sync'}
<button
type="button"
onclick={() => (active_tab = 'sync')}
class="btn btn-sm text-[10px] uppercase font-bold transition-all"
class:preset-filled-primary-500={active_tab === 'sync'}
class:opacity-50={active_tab !== 'sync'}
>
<span class="fas fa-sync mr-1"></span> Sync
</button>
<button type="button"
onclick={() => active_tab = 'general'}
<button
type="button"
onclick={() => (active_tab = 'general')}
class="btn btn-sm text-[10px] uppercase font-bold transition-all"
class:preset-filled-primary-500={active_tab === 'general'}
class:opacity-50={active_tab !== 'general'}
@@ -105,16 +116,22 @@
<!-- Tab Content -->
<div class="w-full flex flex-col gap-2 min-h-[400px]">
{#if active_tab === 'system'}
<div class="animate-in fade-in slide-in-from-left-2 duration-300">
{#if $ae_loc.is_native}
<Launcher_Cfg_Health on_expand={() => handle_section_expand('health')} />
<Launcher_Cfg_Native_OS on_expand={() => handle_section_expand('native_os')} />
<Launcher_Cfg_Updates on_expand={() => handle_section_expand('updates')} />
<Launcher_Cfg_Health
on_expand={() => handle_section_expand('health')}
/>
<Launcher_Cfg_Native_OS
on_expand={() => handle_section_expand('native_os')}
/>
<Launcher_Cfg_Updates
on_expand={() => handle_section_expand('updates')}
/>
{:else}
<div class="card p-8 text-center opacity-50 italic text-sm">
Native OS features are only available when running in the Aether Desktop app.
Native OS features are only available when running in
the Aether Desktop app.
</div>
{/if}
</div>
@@ -122,33 +139,47 @@
{#if active_tab === 'sync'}
<div class="animate-in fade-in slide-in-from-bottom-2 duration-300">
<Launcher_Cfg_Sync_Timers on_expand={() => handle_section_expand('sync_timers')} />
<Launcher_Cfg_Sync_Timers
on_expand={() => handle_section_expand('sync_timers')}
/>
</div>
{/if}
{#if active_tab === 'general'}
<div class="animate-in fade-in slide-in-from-right-2 duration-300">
<Launcher_Cfg_Controller on_expand={() => handle_section_expand('controller')} />
<Launcher_Cfg_App_Modes on_expand={() => handle_section_expand('app_modes')} />
<Launcher_Cfg_Screen_Saver on_expand={() => handle_section_expand('screen_saver')} />
<Launcher_Cfg_Local_Actions on_expand={() => handle_section_expand('local_actions')} />
<Launcher_Cfg_Controller
on_expand={() => handle_section_expand('controller')}
/>
<Launcher_Cfg_App_Modes
on_expand={() => handle_section_expand('app_modes')}
/>
<Launcher_Cfg_Screen_Saver
on_expand={() => handle_section_expand('screen_saver')}
/>
<Launcher_Cfg_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">
<div
class="w-full flex flex-col gap-2 border-t border-surface-500/20 pt-4 mt-auto"
>
<div class="grid grid-cols-2 gap-2">
<button type="button"
onclick={() => ($events_loc.launcher.hide_drawer__debug = false)}
<button
type="button"
onclick={() =>
($events_loc.launcher.hide_drawer__debug = false)}
class="btn btn-sm preset-tonal-error hover:preset-filled-error-500 transition-all"
>
<span class="fas fa-bug mr-2"></span>
Open Debug
</button>
<button type="button"
<button
type="button"
onclick={() => location.reload()}
class="btn btn-sm preset-tonal-secondary hover:preset-filled-secondary-500 transition-all"
>
@@ -156,9 +187,11 @@
Reload Page
</button>
</div>
<p class="text-[9px] text-center opacity-40 uppercase font-bold tracking-widest mt-2">
<p
class="text-[9px] text-center opacity-40 uppercase font-bold tracking-widest mt-2"
>
Aether Platform &bull; Events Launcher v3.0
</p>
</div>
</div>
</div>

View File

@@ -57,12 +57,16 @@
import { api } from '$lib/api/api';
import { ae_loc, ae_api, ae_sess, slct } from '$lib/stores/ae_stores';
import { core_func } from '$lib/ae_core/ae_core_functions';
import { events_loc, events_sess, events_slct } from '$lib/stores/ae_events_stores';
import {
events_loc,
events_sess,
events_slct
} from '$lib/stores/ae_events_stores';
import { events_func } from '$lib/ae_events_functions';
import AE_Comp_Hosted_Files_Download_Button from '$lib/ae_core/ae_comp__hosted_files_download_button.svelte';
import Element_ae_crud from '$lib/elements/element_ae_crud.svelte';
// Import the relay
import * as native from '$lib/electron/electron_relay';
@@ -78,14 +82,17 @@
onMount(() => {
if (screen_saver_exts.includes(event_file_obj.extension)) {
if (!$events_loc.launcher.screen_saver_img_kv) $events_loc.launcher.screen_saver_img_kv = {};
$events_loc.launcher.screen_saver_img_kv[event_file_id] = { ...event_file_obj };
if (!$events_loc.launcher.screen_saver_img_kv)
$events_loc.launcher.screen_saver_img_kv = {};
$events_loc.launcher.screen_saver_img_kv[event_file_id] = {
...event_file_obj
};
}
});
async function handle_open_file() {
if (log_lvl) console.log('*** handle_open_file() ***');
$events_slct.event_file_id = event_file_id;
$events_slct.event_file_obj = event_file_obj;
@@ -98,15 +105,18 @@
open_file_status = 'checking_cache';
open_file_status_message = 'Checking local cache...';
const exists = await native.check_hash_file_cache({ cache_root, hash: event_file_obj.hash_sha256 });
const exists = await native.check_hash_file_cache({
cache_root,
hash: event_file_obj.hash_sha256
});
if (!exists) {
open_file_status = 'downloading_file';
open_file_status_message = 'Downloading file to cache...';
// Use the PROVEN endpoint path from api.ts that is known to work in Default Mode.
const url = `${$ae_api.base_url}/v3/action/hosted_file/${event_file_obj.hosted_file_id}/download?return_file=true&filename=${encodeURIComponent(event_file_obj.filename)}&key=${$ae_api.account_id}`;
const dl_result = await native.download_to_cache({
url,
cache_root,
@@ -118,7 +128,7 @@
if (!dl_result.success) {
open_file_status = 'error';
open_file_status_message = `Download failed: ${dl_result.error}`;
setTimeout(() => open_file_clicked = false, 5000);
setTimeout(() => (open_file_clicked = false), 5000);
return false;
}
}
@@ -127,7 +137,7 @@
open_file_status_message = 'Opening Application';
// Phase 2/5: Use the atomic copy-and-launch operation.
// The main process handler (file_handlers.ts) now handles the
// The main process handler (file_handlers.ts) now handles the
// specialized LibreOffice/AppleScript logic internally after copying.
const launch_result = await native.launch_from_cache({
cache_root,
@@ -141,9 +151,9 @@
open_file_status_message = `Failed to open: ${launch_result.error}`;
}
setTimeout(() => open_file_clicked = false, 5000);
setTimeout(() => (open_file_clicked = false), 5000);
return launch_result.success;
}
}
// 2. ONSITE MODE (Browser with Modified Extensions)
else if ($events_loc.launcher.app_mode === 'onsite') {
open_file_clicked = true;
@@ -151,7 +161,11 @@
open_file_status_message = 'Downloading (Onsite Mode)...';
let filename = event_file_obj.filename;
if ((event_file_obj.extension === 'ppt' || event_file_obj.extension === 'pptx') && event_file_obj.open_in_os === 'win') {
if (
(event_file_obj.extension === 'ppt' ||
event_file_obj.extension === 'pptx') &&
event_file_obj.open_in_os === 'win'
) {
filename = event_file_obj.filename + 'win';
}
@@ -167,7 +181,7 @@
log_lvl: 1
});
setTimeout(() => open_file_clicked = false, 5000);
setTimeout(() => (open_file_clicked = false), 5000);
return dl_promise;
}
// 3. DEFAULT MODE (Standard Browser)
@@ -175,7 +189,7 @@
open_file_clicked = true;
open_file_status = 'downloading_default';
open_file_status_message = 'Downloading...';
const dl_promise = api.get_object({
api_cfg: $ae_api,
endpoint: `/v3/action/hosted_file/${event_file_obj.hosted_file_id}/download`,
@@ -193,14 +207,14 @@
$events_sess.launcher.controller_trigger_send = true;
}
setTimeout(() => open_file_clicked = false, 5000);
setTimeout(() => (open_file_clicked = false), 5000);
return dl_promise;
}
}
function prevent_default<T extends Event>(fn: (event: T) => void) {
return function (event: T) {
event.prevent_default();
event.preventDefault();
fn(event);
};
}
@@ -209,21 +223,34 @@
<div
class:justify-between={!hide_meta}
class:justify-center={hide_meta}
class:hidden={hide_draft && (event_file_obj.file_purpose == 'outline' || event_file_obj.file_purpose == 'draft')}
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"
>
{#if open_file_clicked}
<div class="open_file_clicked alert" in:fade={{ duration: 250 }} out:fade={{ duration: 2000 }}>
<div
class="open_file_clicked alert"
in:fade={{ duration: 250 }}
out:fade={{ duration: 2000 }}
>
<div class="alert_msg_pulse">
<strong>*** {open_file_status_message || 'Please wait while this file downloads...'} ***</strong>
<strong
>*** {open_file_status_message ||
'Please wait while this file downloads...'} ***</strong
>
</div>
<p>Most files will automatically be opened full screen.</p>
<p>PowerPoint or KeyNote will attempt to display in presenter view.</p>
<p>
PowerPoint or KeyNote will attempt to display in presenter view.
</p>
<p>Please close the file when finished.</p>
</div>
{/if}
<span class="event_file_action grow max-w-full flex flex-row flex-wrap gap-1 items-center justify-center">
<span
class="event_file_action grow max-w-full flex flex-row flex-wrap gap-1 items-center justify-center"
>
{#if session_type == 'poster' || open_method == 'modal'}
<AE_Comp_Hosted_Files_Download_Button
hosted_file_id={event_file_id}
@@ -239,10 +266,19 @@
>
{#snippet label()}
{#if screen_saver_exts.includes(event_file_obj.extension)}
<span class="fas fa-chart-bar m-1" class:hidden={hide_launch_icon}></span> Open Poster
<span
class="fas fa-chart-bar m-1"
class:hidden={hide_launch_icon}
></span> Open Poster
{:else}
<span class="fas fa-paper-plane m-1" class:hidden={hide_launch_icon}></span>
{ae_util.shorten_filename({ filename: event_file_obj.filename, max_length: max_filename_length })}
<span
class="fas fa-paper-plane m-1"
class:hidden={hide_launch_icon}
></span>
{ae_util.shorten_filename({
filename: event_file_obj.filename,
max_length: max_filename_length
})}
{/if}
{/snippet}
</AE_Comp_Hosted_Files_Download_Button>
@@ -260,27 +296,48 @@
<span class="fas fa-spinner fa-spin mx-0.5"></span>
<span>
{#if $ae_sess.api_download_kv[file_id]}
{$ae_sess.api_download_kv[file_id].percent_completed}%
{$ae_sess.api_download_kv[file_id]
.percent_completed}%
{:else}
...
{/if}
</span>
{:then result}
<span class="fas fa-{ae_util.file_extension_icon(event_file_obj.extension)} mx-0.5"></span>
<span
class="fas fa-{ae_util.file_extension_icon(
event_file_obj.extension
)} mx-0.5"
></span>
{event_file_obj.extension}
{#if result === null || result === false}
<span class="text-error-500"><span class="fas fa-exclamation-triangle mx-1"></span>Failed!</span>
<span class="text-error-500"
><span
class="fas fa-exclamation-triangle mx-1"
></span>Failed!</span
>
{/if}
{:catch error}
<span class="text-error-500" title={error?.message}><span class="fas fa-exclamation-circle mx-0.5"></span>Error!</span>
<span class="text-error-500" title={error?.message}
><span class="fas fa-exclamation-circle mx-0.5"
></span>Error!</span
>
{/await}
</span>
<span 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, max_length: 65 })}
<span
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,
max_length: 65
})}
</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}>
<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}
>
{event_file_obj.file_purpose}
</span>
{/snippet}
@@ -288,7 +345,10 @@
{/if}
</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}>
<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}
>
<Element_ae_crud
trigger_patch={ae_triggers.open_in_os}
api_cfg={$ae_api}
@@ -298,13 +358,20 @@
field_type={'button'}
field_value={ae_tmp.value__open_in_os}
on:ae_crud_updated={() => {
events_func.load_ae_obj_id__event_file({ api_cfg: $ae_api, event_file_id: event_file_obj?.event_file_id, log_lvl });
events_func.load_ae_obj_id__event_file({
api_cfg: $ae_api,
event_file_id: event_file_obj?.event_file_id,
log_lvl
});
}}
>
<button type="button"
<button
type="button"
onclick={() => {
if (!event_file_obj?.open_in_os) ae_tmp.value__open_in_os = 'win';
else if (event_file_obj?.open_in_os == 'win') ae_tmp.value__open_in_os = 'mac';
if (!event_file_obj?.open_in_os)
ae_tmp.value__open_in_os = 'win';
else if (event_file_obj?.open_in_os == 'win')
ae_tmp.value__open_in_os = 'mac';
else ae_tmp.value__open_in_os = null;
ae_triggers.open_in_os = true;
}}
@@ -313,20 +380,37 @@
class:preset-tonal-warning={event_file_obj?.open_in_os == 'mac'}
disabled={!$ae_loc.trusted_access}
>
{#if event_file_obj?.open_in_os == 'win'}<span class="fab fa-windows m-1"></span>
{:else if event_file_obj?.open_in_os == 'mac'}<span class="fab fa-apple m-1"></span>
{#if event_file_obj?.open_in_os == 'win'}<span
class="fab fa-windows m-1"
></span>
{:else if event_file_obj?.open_in_os == 'mac'}<span
class="fab fa-apple m-1"
></span>
{:else}<span class="fas fa-folder-open m-1"></span>{/if}
</button>
</Element_ae_crud>
<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}>
<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}
>
<span class="fas fa-calendar-day"></span>
<span class="w-18">{ae_util.iso_datetime_formatter(event_file_obj.created_on, 'date_short')}</span>
<span class="w-18"
>{ae_util.iso_datetime_formatter(
event_file_obj.created_on,
'date_short'
)}</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}>
<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}
>
<span class="fas fa-save"></span>
{#if event_file_obj.file_size}{ae_util.format_bytes(event_file_obj.file_size)}{/if}
{#if event_file_obj.file_size}{ae_util.format_bytes(
event_file_obj.file_size
)}{/if}
</span>
</span>
</div>
</div>

View File

@@ -141,19 +141,26 @@
hide_created_on={true}
hide_os={true}
hide_size={true}
show_bak_download={$ae_loc.trusted_access && $ae_loc.edit_mode}
show_bak_download={$ae_loc.trusted_access &&
$ae_loc.edit_mode}
btn_size={'btn-sm'}
btn_text_align={'text-center'}
text_size={'text-xs'}
text_size_md={'text-xs'}
session_type={event_file_obj?.event_session_type_code ?? 'oral'}
open_method={event_file_obj?.event_session_type_code == 'poster'
session_type={event_file_obj?.event_session_type_code ??
'oral'}
open_method={event_file_obj?.event_session_type_code ==
'poster'
? 'modal'
: null}
modal_title={$lq__event_session_obj?.name}
bind:modal__title={$events_sess.launcher.modal__title}
bind:modal__open_event_file_id={$events_sess.launcher.modal__open_event_file_id}
bind:modal__event_file_obj={$events_sess.launcher.modal__event_file_obj}
bind:modal__open_event_file_id={
$events_sess.launcher.modal__open_event_file_id
}
bind:modal__event_file_obj={
$events_sess.launcher.modal__event_file_obj
}
/>
{/each}
</div>
@@ -179,19 +186,26 @@
hide_created_on={true}
hide_os={true}
hide_size={true}
show_bak_download={$ae_loc.trusted_access && $ae_loc.edit_mode}
show_bak_download={$ae_loc.trusted_access &&
$ae_loc.edit_mode}
btn_size={'btn-sm'}
btn_text_align={'text-center'}
text_size={'text-xs'}
text_size_md={'text-xs'}
session_type={event_file_obj?.event_session_type_code ?? 'oral'}
open_method={event_file_obj?.event_session_type_code == 'poster'
session_type={event_file_obj?.event_session_type_code ??
'oral'}
open_method={event_file_obj?.event_session_type_code ==
'poster'
? 'modal'
: null}
modal_title={$lq__event_session_obj?.name}
bind:modal__title={$events_sess.launcher.modal__title}
bind:modal__open_event_file_id={$events_sess.launcher.modal__open_event_file_id}
bind:modal__event_file_obj={$events_sess.launcher.modal__event_file_obj}
bind:modal__open_event_file_id={
$events_sess.launcher.modal__open_event_file_id
}
bind:modal__event_file_obj={
$events_sess.launcher.modal__event_file_obj
}
/>
{/each}
</div>
@@ -214,7 +228,8 @@
flex flex-row gap-1 items-center justify-center
"
>
<button type="button"
<button
type="button"
onclick={() => {
if ($events_loc.launcher.show_content__hidden_files) {
$events_loc.launcher.show_content__hidden_files = false;
@@ -235,14 +250,16 @@
title="Toggle showing hidden files. Including files marked as 'draft'."
>
{#if $events_loc.launcher.show_content__hidden_files}
<span class="fas fa-eye-slash m-1 text-neutral-800/80"></span>
<span class="fas fa-eye-slash m-1 text-neutral-800/80"
></span>
Hide Files
{:else}
<span class="fas fa-eye m-1 text-neutral-800/80"></span>
All Files
{/if}
</button>
<button type="button"
<button
type="button"
onclick={() => {
$events_loc.launcher.show_content__hidden_sessions =
!$events_loc.launcher.show_content__hidden_sessions;
@@ -257,7 +274,8 @@
"
>
{#if $events_loc.launcher.show_content__hidden_sessions}
<span class="fas fa-eye-slash m-1 text-neutral-800/80"></span>
<span class="fas fa-eye-slash m-1 text-neutral-800/80"
></span>
Hide Sessions
{:else}
<span class="fas fa-eye m-1 text-neutral-800/80"></span>

View File

@@ -27,24 +27,30 @@
{#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">
<div
class="text-[10px] text-surface-600-400 uppercase font-bold tracking-wider opacity-70"
>
Presentation Files:
</div>
<ul class="space-y-1">
{#each $lq__event_file_obj_li as event_file_obj}
<li
class="flex flex-col md:flex-row flex-wrap gap-1 items-center justify-start"
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}
hide_created_on={false}
bind:hide_draft={$events_loc.launcher.hide_content__draft_files}
bind:hide_draft={
$events_loc.launcher.hide_content__draft_files
}
show_bak_download={$ae_loc.trusted_access}
session_type={event_file_obj?.event_session_type_code ?? 'oral'}
open_method={event_file_obj?.event_session_type_code == 'poster'
session_type={event_file_obj?.event_session_type_code ??
'oral'}
open_method={event_file_obj?.event_session_type_code ==
'poster'
? 'modal'
: null}
modal_title={lq__event_presentation_obj?.name}
@@ -52,7 +58,9 @@
bind:modal__open_event_file_id={
$events_sess.launcher.modal__open_event_file_id
}
bind:modal__event_file_obj={$events_sess.launcher.modal__event_file_obj}
bind:modal__event_file_obj={
$events_sess.launcher.modal__event_file_obj
}
/>
</li>
{/each}

View File

@@ -89,17 +89,21 @@
{#each $lq__event_file_obj_li as event_file_obj, index}
<li
class="flex flex-col md:flex-row flex-wrap gap-1 items-center justify-start"
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}
hide_created_on={false}
bind:hide_draft={$events_loc.launcher.hide_content__draft_files}
bind:hide_draft={
$events_loc.launcher.hide_content__draft_files
}
show_bak_download={$ae_loc.trusted_access}
session_type={event_file_obj?.event_session_type_code ?? 'oral'}
open_method={event_file_obj?.event_session_type_code == 'poster'
session_type={event_file_obj?.event_session_type_code ??
'oral'}
open_method={event_file_obj?.event_session_type_code ==
'poster'
? 'modal'
: null}
modal_title={lq__event_presenter_obj?.event_presentation_name}
@@ -107,7 +111,9 @@
bind:modal__open_event_file_id={
$events_sess.launcher.modal__open_event_file_id
}
bind:modal__event_file_obj={$events_sess.launcher.modal__event_file_obj}
bind:modal__event_file_obj={
$events_sess.launcher.modal__event_file_obj
}
/>
</li>
{/each}

View File

@@ -88,8 +88,8 @@
{#each $lq__event_file_obj_li as event_file_obj, index}
<li
class="flex flex-col md:flex-row wrap gap items-center justify-center"
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}
@@ -97,8 +97,10 @@
hide_created_on={false}
hide_meta={true}
show_bak_download={$ae_loc.trusted_access}
session_type={event_file_obj?.event_session_type_code ?? 'oral'}
open_method={event_file_obj?.event_session_type_code == 'poster'
session_type={event_file_obj?.event_session_type_code ??
'oral'}
open_method={event_file_obj?.event_session_type_code ==
'poster'
? 'modal'
: null}
modal_title={lq__event_presenter_obj?.event_presentation_name}
@@ -106,7 +108,9 @@
bind:modal__open_event_file_id={
$events_sess.launcher.modal__open_event_file_id
}
bind:modal__event_file_obj={$events_sess.launcher.modal__event_file_obj}
bind:modal__event_file_obj={
$events_sess.launcher.modal__event_file_obj
}
/>
</li>
{/each}

View File

@@ -69,7 +69,9 @@
let lq__event_file_obj_li = $derived(
liveQuery(async () => {
if (log_lvl) {
console.log(`lq__event_file_obj: event_file_id = ${$events_slct?.event_file_id}`);
console.log(
`lq__event_file_obj: event_file_id = ${$events_slct?.event_file_id}`
);
}
let results = await db_events.file
// .where('event_session_id')
@@ -83,7 +85,10 @@
// Check if results are different than the current $events_slct.event_file_obj_li
if ($events_slct.event_file_obj_li && results) {
if (JSON.stringify($events_slct.event_file_obj_li) !== JSON.stringify(results)) {
if (
JSON.stringify($events_slct.event_file_obj_li) !==
JSON.stringify(results)
) {
$events_slct.event_file_obj_li = [...results];
if (log_lvl) {
console.log(
@@ -136,7 +141,10 @@
.sortBy(sort_by);
// .sortBy('name')
if (log_lvl) {
console.log(`lq__event_presentation_obj_li: results = `, results);
console.log(
`lq__event_presentation_obj_li: results = `,
results
);
}
// Check if results are different than the current $events_slct.event_presentation_obj_li
@@ -189,7 +197,8 @@
// Check if results are different than the current $events_slct.event_presenter_obj_li
if ($events_slct.event_presenter_obj_li && results) {
if (
JSON.stringify($events_slct.event_presenter_obj_li) !== JSON.stringify(results)
JSON.stringify($events_slct.event_presenter_obj_li) !==
JSON.stringify(results)
) {
$events_slct.event_presenter_obj_li = { ...results };
if (log_lvl) {
@@ -277,9 +286,12 @@
$events_loc.launcher.hide__session_datetimes}
class="shrink event_session_datetimes"
>
<button type="button"
<button
type="button"
onclick={() => {
if ($events_loc.launcher.time_format == 'time_12_short') {
if (
$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;
@@ -319,8 +331,10 @@
</h3>
<span
class:justify-between={$events_loc.launcher.hide__session_datetimes}
class:justify-end={!$events_loc.launcher.hide__session_datetimes}
class:justify-between={$events_loc.launcher
.hide__session_datetimes}
class:justify-end={!$events_loc.launcher
.hide__session_datetimes}
class="grow flex flex-row gap-2 items-center"
>
<h2
@@ -363,7 +377,10 @@
<span class="fas fa-file-archive"></span>
Session Files:
<span class:hidden={!$ae_loc.trusted_access || !$ae_loc.edit_mode}>
<span
class:hidden={!$ae_loc.trusted_access ||
!$ae_loc.edit_mode}
>
({$lq__event_file_obj_li?.length}&times;)
</span>
</strong>
@@ -384,22 +401,29 @@
{#each $lq__event_file_obj_li as event_file_obj, index}
<li
class="flex flex-row flex-wrap gap-1 items-center justify-center"
class:hidden={!$events_loc.launcher.show_content__hidden_files &&
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}
hide_created_on={true}
show_bak_download={$ae_loc.trusted_access && $ae_loc.edit_mode}
session_type={event_file_obj?.event_session_type_code ?? 'oral'}
open_method={event_file_obj?.event_session_type_code == 'poster'
show_bak_download={$ae_loc.trusted_access &&
$ae_loc.edit_mode}
session_type={event_file_obj?.event_session_type_code ??
'oral'}
open_method={event_file_obj?.event_session_type_code ==
'poster'
? 'modal'
: null}
modal_title={$lq__event_session_obj?.name}
bind:modal__title={$events_sess.launcher.modal__title}
bind:modal__title={
$events_sess.launcher.modal__title
}
bind:modal__open_event_file_id={
$events_sess.launcher.modal__open_event_file_id
$events_sess.launcher
.modal__open_event_file_id
}
bind:modal__event_file_obj={
$events_sess.launcher.modal__event_file_obj
@@ -449,7 +473,9 @@
<!-- Maybe set max with? max-w-(--breakpoint-md) -->
<ul class="event_presentation_list max-w-full space-y-2">
{#each $lq__event_presentation_obj_li as event_presentation_obj}
<li class="border-b-2 border-gray-300 my-1 py-1 text-center md:text-left">
<li
class="border-b-2 border-gray-300 my-1 py-1 text-center md:text-left"
>
<!-- The presentation information -->
<div
class="event_presentation_datetime_name flex flex-row justify-evenly gap-4"
@@ -479,12 +505,17 @@
class="event_presentation_single_presenter italic text-sm text-gray-500"
>
{#if $lq__event_presenter_obj_li[index]?.given_name && $lq__event_presenter_obj_li[index]?.given_name != 'Group'}
<span class="fas fa-user"></span>
{$lq__event_presenter_obj_li[index]?.full_name}
<span class="fas fa-user"
></span>
{$lq__event_presenter_obj_li[
index
]?.full_name}
{:else if $lq__event_presenter_obj_li[index]?.given_name == 'Group'}
<span class="fas fa-users"></span>
{$lq__event_presenter_obj_li[index]
?.affiliations}
<span class="fas fa-users"
></span>
{$lq__event_presenter_obj_li[
index
]?.affiliations}
{:else}
--not set--
{/if}
@@ -495,7 +526,9 @@
</div>
<!-- Presentation-level files -->
<Launcher_presentation_view lq__event_presentation_obj={event_presentation_obj} />
<Launcher_presentation_view
lq__event_presentation_obj={event_presentation_obj}
/>
<!-- The presenter list -->

View File

@@ -69,7 +69,9 @@
);
}
if (!event_location_id) {
console.warn(`handle_load_ae_obj_li__event_session: No event_location_id provided.`);
console.warn(
`handle_load_ae_obj_li__event_session: No event_location_id provided.`
);
return;
}
@@ -84,8 +86,12 @@
inc_all_file_li: false, // Also include files under presentations and presenters as well?
inc_presentation_li: true,
inc_presenter_li: true,
enabled: $events_loc.launcher.show_content__enabled_sessions ? 'all' : 'enabled',
hidden: $events_loc.launcher.show_content__hidden_sessions ? 'all' : 'not_hidden',
enabled: $events_loc.launcher.show_content__enabled_sessions
? 'all'
: 'enabled',
hidden: $events_loc.launcher.show_content__hidden_sessions
? 'all'
: 'not_hidden',
limit: 49,
try_cache: true,
log_lvl: 1
@@ -126,7 +132,9 @@
<div class="text-xs text-surface-600-400">
<strong>
Location:
<span class:hidden={!$ae_loc.trusted_access || !$ae_loc.edit_mode}>
<span
class:hidden={!$ae_loc.trusted_access || !$ae_loc.edit_mode}
>
({$lq__event_location_obj_li?.length}&times;)
</span>
@@ -161,11 +169,14 @@
if (slct_event_location_id) {
loading__session_li_status = 'loading';
$events_loc.launcher.slct.event_location_id = slct_event_location_id;
$events_loc.launcher.slct.event_location_id =
slct_event_location_id;
$events_slct.event_location_id = slct_event_location_id;
// Load the sessions for this location
await handle_load_ae_obj_li__event_session(slct_event_location_id);
await handle_load_ae_obj_li__event_session(
slct_event_location_id
);
new_url = `/events/${$events_slct.event_id}/launcher/${slct_event_location_id}`;
@@ -188,7 +199,9 @@
goto(new_url, { replaceState: false }); // Updates the URL history without reloading the page
}}
>
<option value="" class="italic text-surface-800-200"> -- select -- </option>
<option value="" class="italic text-surface-800-200">
-- select --
</option>
{#each $lq__event_location_obj_li as event_location_obj}
<option value={event_location_obj?.id}>
{event_location_obj?.name}

View File

@@ -73,17 +73,23 @@
}
let event_session_id = String(trigger_reload__event_session_obj_id);
trigger_reload__event_session_obj_id = false;
// OPTIMIZATION: Update slct__event_session_id prop (which is bound to the store)
// OPTIMIZATION: Update slct__event_session_id prop (which is bound to the store)
// BEFORE calling goto. This triggers the LiveQuery in the layout instantly.
slct__event_session_id = event_session_id;
if (log_lvl) console.log(`[UI Trace] UI setting slct__event_session_id = ${slct__event_session_id} (+${(performance.now() - start).toFixed(2)}ms)`);
if (log_lvl)
console.log(
`[UI Trace] UI setting slct__event_session_id = ${slct__event_session_id} (+${(performance.now() - start).toFixed(2)}ms)`
);
handle_load_ae_obj_id__event_session(event_session_id);
if ($events_loc.launcher.controller == 'local_push') {
if (log_lvl) console.log(`[UI Trace] Local Push Controller Command: ae_load:event_session=${event_session_id}`);
if (log_lvl)
console.log(
`[UI Trace] Local Push Controller Command: ae_load:event_session=${event_session_id}`
);
$events_sess.launcher.controller_cmd = `ae_load:event_session=${event_session_id}`;
$events_sess.launcher.controller_trigger_send = true;
}
@@ -91,11 +97,21 @@
data_url.searchParams.set('session_id', event_session_id);
let new_url = data_url.toString();
if (log_lvl) console.log(`[UI Trace] Initiating SvelteKit goto... (+${(performance.now() - start).toFixed(2)}ms)`);
if (log_lvl)
console.log(
`[UI Trace] Initiating SvelteKit goto... (+${(performance.now() - start).toFixed(2)}ms)`
);
// Use noscroll and keepfocus to speed up the transition
goto(new_url, { replaceState: false, noscroll: true, keepfocus: true }).then(() => {
if (log_lvl) console.log(`🏁 [Trace] Total Navigation Roundtrip: ${(performance.now() - start).toFixed(2)}ms`);
goto(new_url, {
replaceState: false,
noscroll: true,
keepfocus: true
}).then(() => {
if (log_lvl)
console.log(
`🏁 [Trace] Total Navigation Roundtrip: ${(performance.now() - start).toFixed(2)}ms`
);
});
}
});
@@ -121,12 +137,17 @@
log_lvl: log_lvl
})
.then(async (load_results) => {
if (log_lvl) console.log(`[UI Trace] handle_load_ae_obj_id: Library returned results in ${(performance.now() - start).toFixed(2)}ms.`);
if (log_lvl)
console.log(
`[UI Trace] handle_load_ae_obj_id: Library returned results in ${(performance.now() - start).toFixed(2)}ms.`
);
if (load_results) {
$events_slct.event_session_obj = load_results;
$events_slct.event_file_obj_li = load_results.event_file_li ?? [];
$events_slct.event_presentation_obj_li = load_results.event_presentation_li ?? [];
$events_slct.event_file_obj_li =
load_results.event_file_li ?? [];
$events_slct.event_presentation_obj_li =
load_results.event_presentation_li ?? [];
}
})
.finally(() => {
@@ -145,7 +166,9 @@
<div class="text-xs text-surface-600-400">
<strong>
Sessions:
<span class:hidden={!$ae_loc.trusted_access || !$ae_loc.edit_mode}>
<span
class:hidden={!$ae_loc.trusted_access || !$ae_loc.edit_mode}
>
({$lq__event_session_obj_li?.length}&times;)
</span>
@@ -177,22 +200,28 @@
transition-all
"
>
<button type="button"
<button
type="button"
onmouseenter={() => {
// Start a 750 ms timer to prevent changing the session too quickly.
hover_timer = setTimeout(async () => {
// Only run if the session ID has changed
if (slct__event_session_id === event_session_obj?.id) {
if (
slct__event_session_id ===
event_session_obj?.id
) {
return;
}
trigger_reload__event_session_obj_id = event_session_obj?.id;
trigger_reload__event_session_obj_id =
event_session_obj?.id;
}, hover_timer_wait);
}}
onmouseleave={() => {
clearTimeout(hover_timer);
}}
onclick={async () => {
trigger_reload__event_session_obj_id = event_session_obj?.id;
trigger_reload__event_session_obj_id =
event_session_obj?.id;
$events_slct.event_file_id = null;
$events_slct.event_file_obj = null;
@@ -214,11 +243,14 @@
"
class:preset-filled-primary-500={slct__event_session_id ===
event_session_obj?.id}
class:preset-tonal-secondary={slct__event_session_id !=
class:preset-tonal-secondary={slct__event_session_id !=
event_session_obj?.id}
class:border-secondary-500={slct__event_session_id != event_session_obj?.id}
class:font-bold={slct__event_session_id === event_session_obj?.id}
class:hidden={!$events_loc.launcher.show_content__hidden_sessions &&
class:border-secondary-500={slct__event_session_id !=
event_session_obj?.id}
class:font-bold={slct__event_session_id ===
event_session_obj?.id}
class:hidden={!$events_loc.launcher
.show_content__hidden_sessions &&
event_session_obj?.hide}
class:dim={event_session_obj?.hide}
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)}`}
@@ -226,7 +258,9 @@
<!-- hover:scale-115 -->
<!-- hover:z-index-10 -->
<span class="border-r border-surface-400-600 pr-1 min-w-28">
<span
class="border-r border-surface-400-600 pr-1 min-w-28"
>
{#if slct__event_session_id === event_session_obj?.id}
<span class="fas fa-calendar-check"></span>
{:else}
@@ -234,7 +268,8 @@
{/if}
<span
class="text-xs"
class:hidden={slct__event_session_id === event_session_obj?.id}
class:hidden={slct__event_session_id ===
event_session_obj?.id}
>
{ae_util.iso_datetime_formatter(
event_session_obj?.start_datetime,
@@ -266,4 +301,4 @@
{:else}
<div class="text-sm">No sessions found.</div>
{/if}
</div>
</div>