Prettier for Event Launcher
This commit is contained in:
@@ -44,7 +44,8 @@
|
||||
// Only send when connected so the UI button doesn't silently no-op.
|
||||
if (
|
||||
$events_loc.launcher.ws_connect &&
|
||||
($events_loc.launcher.controller === 'local_push' || $events_loc.launcher.controller === 'remote')
|
||||
($events_loc.launcher.controller === 'local_push' ||
|
||||
$events_loc.launcher.controller === 'remote')
|
||||
) {
|
||||
$events_sess.launcher.controller_cmd = `ae_mode:${mode}`;
|
||||
$events_sess.launcher.controller_trigger_send = 'trigger';
|
||||
@@ -57,135 +58,126 @@
|
||||
icon={LayoutGrid}
|
||||
bind:state={$events_loc.launcher.section_state__app_modes}
|
||||
{on_expand}
|
||||
description="Mode: {$events_loc.launcher.app_mode} | UI Layout"
|
||||
>
|
||||
description="Mode: {$events_loc.launcher.app_mode} | UI Layout">
|
||||
<!-- Content omitted for brevity, preserved in file -->
|
||||
<div class="col-span-full flex flex-col gap-3">
|
||||
<!-- 0. Oral / Poster Kiosk Mode Preset Toggle -->
|
||||
<div class="flex flex-col gap-1">
|
||||
<p class="text-[9px] font-bold uppercase opacity-50 ml-1">Session Mode Preset</p>
|
||||
<div class="grid grid-cols-2 gap-1 bg-surface-500/5 p-1 rounded-lg">
|
||||
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">
|
||||
Session Mode Preset
|
||||
</p>
|
||||
<div class="bg-surface-500/5 grid grid-cols-2 gap-1 rounded-lg p-1">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => apply_mode('oral')}
|
||||
class="btn btn-xs font-bold text-[10px]"
|
||||
class="btn btn-xs text-[10px] font-bold"
|
||||
class:preset-filled-secondary={!is_poster_mode}
|
||||
class:preset-tonal-surface={is_poster_mode}
|
||||
title="Standard oral/presentation layout — menus and headers visible"
|
||||
>
|
||||
title="Standard oral/presentation layout — menus and headers visible">
|
||||
<GraduationCap size="0.85em" class="mr-1 opacity-70" />
|
||||
Oral / Default
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => apply_mode('poster')}
|
||||
class="btn btn-xs font-bold text-[10px]"
|
||||
class="btn btn-xs text-[10px] font-bold"
|
||||
class:preset-filled-primary={is_poster_mode}
|
||||
class:preset-tonal-surface={!is_poster_mode}
|
||||
title="Digital Poster kiosk — hides site chrome, menu, header & footer"
|
||||
>
|
||||
title="Digital Poster kiosk — hides site chrome, menu, header & footer">
|
||||
<IdCard size="0.85em" class="mr-1 opacity-70" />
|
||||
Poster Kiosk
|
||||
</button>
|
||||
</div>
|
||||
{#if $events_loc.launcher.ws_connect && ($events_loc.launcher.controller === 'local_push' || $events_loc.launcher.controller === 'remote')}
|
||||
<p class="text-[8px] opacity-40 italic ml-1">Applies to all connected WS devices</p>
|
||||
<p class="ml-1 text-[8px] italic opacity-40">
|
||||
Applies to all connected WS devices
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- 1. App Mode Selection -->
|
||||
<div class="flex flex-col gap-1">
|
||||
<p class="text-[9px] font-bold uppercase opacity-50 ml-1"
|
||||
>Operational Environment</p
|
||||
>
|
||||
<div class="grid grid-cols-3 gap-1 bg-surface-500/5 p-1 rounded-lg">
|
||||
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">
|
||||
Operational Environment
|
||||
</p>
|
||||
<div class="bg-surface-500/5 grid grid-cols-3 gap-1 rounded-lg p-1">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => ($events_loc.launcher.app_mode = 'default')}
|
||||
class="btn btn-xs text-[9px] font-bold"
|
||||
class:preset-filled-primary={$events_loc.launcher
|
||||
.app_mode === 'default'}
|
||||
class:preset-tonal-surface={$events_loc.launcher.app_mode !==
|
||||
'default'}
|
||||
class:preset-tonal-surface={$events_loc.launcher
|
||||
.app_mode !== 'default'}
|
||||
title="Default standard web browser (Chromium, Firefox, Safari based) launcher, for remote presenters and testing before being onsite">
|
||||
Web</button
|
||||
>
|
||||
Web</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => ($events_loc.launcher.app_mode = 'native')}
|
||||
class="btn btn-xs text-[9px] font-bold"
|
||||
class:preset-filled-primary={$events_loc.launcher
|
||||
.app_mode === 'native'}
|
||||
class:preset-tonal-surface={$events_loc.launcher.app_mode !==
|
||||
'native'}
|
||||
title="Native Electron based app launcher, for onsite presenters in session rooms">App</button
|
||||
>
|
||||
class:preset-tonal-surface={$events_loc.launcher
|
||||
.app_mode !== 'native'}
|
||||
title="Native Electron based app launcher, for onsite presenters in session rooms"
|
||||
>App</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => ($events_loc.launcher.app_mode = 'onsite')}
|
||||
class="btn btn-xs text-[9px] font-bold"
|
||||
class:preset-filled-primary={$events_loc.launcher
|
||||
.app_mode === 'onsite'}
|
||||
class:preset-tonal-surface={$events_loc.launcher.app_mode !==
|
||||
'onsite'}
|
||||
title="Customized onsite OS and web browser (Chromium or Firefox based) launcher, for onsite presenters in for practice and onsite backup">Onsite</button
|
||||
>
|
||||
class:preset-tonal-surface={$events_loc.launcher
|
||||
.app_mode !== 'onsite'}
|
||||
title="Customized onsite OS and web browser (Chromium or Firefox based) launcher, for onsite presenters in for practice and onsite backup"
|
||||
>Onsite</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. UI Layout Toggles -->
|
||||
<div
|
||||
class="flex flex-col gap-1 border-t border-surface-500/10 pt-2 mt-1"
|
||||
>
|
||||
<p class="text-[9px] font-bold uppercase opacity-50 ml-1"
|
||||
>Interface Visibility</p
|
||||
>
|
||||
class="border-surface-500/10 mt-1 flex flex-col gap-1 border-t pt-2">
|
||||
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">
|
||||
Interface Visibility
|
||||
</p>
|
||||
<div class="grid grid-cols-2 gap-2 p-1">
|
||||
<label class="flex items-center gap-2 cursor-pointer group">
|
||||
<label class="group flex cursor-pointer items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={
|
||||
$events_loc.launcher.hide__launcher_header
|
||||
}
|
||||
class="checkbox checkbox-sm"
|
||||
/>
|
||||
<span class="text-xs group-hover:text-primary-500"
|
||||
>Hide Header</span
|
||||
>
|
||||
class="checkbox checkbox-sm" />
|
||||
<span class="group-hover:text-primary-500 text-xs"
|
||||
>Hide Header</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 cursor-pointer group">
|
||||
<label class="group flex cursor-pointer items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$events_loc.launcher.hide__launcher_menu}
|
||||
class="checkbox checkbox-sm"
|
||||
/>
|
||||
<span class="text-xs group-hover:text-primary-500"
|
||||
>Hide Menu</span
|
||||
>
|
||||
class="checkbox checkbox-sm" />
|
||||
<span class="group-hover:text-primary-500 text-xs"
|
||||
>Hide Menu</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 cursor-pointer group">
|
||||
<label class="group flex cursor-pointer items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={
|
||||
$events_loc.launcher.hide__launcher_footer
|
||||
}
|
||||
class="checkbox checkbox-sm"
|
||||
/>
|
||||
<span class="text-xs group-hover:text-primary-500"
|
||||
>Hide Footer</span
|
||||
>
|
||||
class="checkbox checkbox-sm" />
|
||||
<span class="group-hover:text-primary-500 text-xs"
|
||||
>Hide Footer</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 cursor-pointer group">
|
||||
<label class="group flex cursor-pointer items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={
|
||||
$events_loc.launcher.hide__session_datetimes
|
||||
}
|
||||
class="checkbox checkbox-sm"
|
||||
/>
|
||||
<span class="text-xs group-hover:text-primary-500"
|
||||
>Hide Times</span
|
||||
>
|
||||
class="checkbox checkbox-sm" />
|
||||
<span class="group-hover:text-primary-500 text-xs"
|
||||
>Hide Times</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -202,8 +194,7 @@
|
||||
$events_loc.launcher.time_hours = 12;
|
||||
}
|
||||
}}
|
||||
class="btn btn-xs preset-tonal-surface w-full text-[10px]"
|
||||
>
|
||||
class="btn btn-xs preset-tonal-surface w-full text-[10px]">
|
||||
<Clock size="0.85em" class="mr-1 opacity-50" />
|
||||
Clock Format:
|
||||
<strong>{$events_loc.launcher.time_hours}-hour</strong>
|
||||
@@ -212,35 +203,30 @@
|
||||
<!-- 4. Advanced Toggles (Edit Mode Only) -->
|
||||
{#if $ae_loc.edit_mode}
|
||||
<div
|
||||
class="col-span-full border-t border-surface-500/20 pt-3 mt-1 flex flex-col gap-2"
|
||||
>
|
||||
<p class="text-[9px] font-bold uppercase opacity-50 ml-1"
|
||||
>Technical Layout</p
|
||||
>
|
||||
class="border-surface-500/20 col-span-full mt-1 flex flex-col gap-2 border-t pt-3">
|
||||
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">
|
||||
Technical Layout
|
||||
</p>
|
||||
<div class="grid grid-cols-1 gap-2 p-1">
|
||||
<label class="flex items-center gap-2 cursor-pointer group">
|
||||
<label class="group flex cursor-pointer items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$events_loc.launcher.hide__ws_element}
|
||||
class="checkbox checkbox-sm"
|
||||
/>
|
||||
class="checkbox checkbox-sm" />
|
||||
<span
|
||||
class="text-xs group-hover:text-primary-500 italic"
|
||||
>Hide WebSocket Debugger</span
|
||||
>
|
||||
class="group-hover:text-primary-500 text-xs italic"
|
||||
>Hide WebSocket Debugger</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 cursor-pointer group">
|
||||
<label class="group flex cursor-pointer items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={
|
||||
$events_loc.launcher.hide__modal_header_title
|
||||
}
|
||||
class="checkbox checkbox-sm"
|
||||
/>
|
||||
class="checkbox checkbox-sm" />
|
||||
<span
|
||||
class="text-xs group-hover:text-primary-500 italic"
|
||||
>Hide Poster Modal Title</span
|
||||
>
|
||||
class="group-hover:text-primary-500 text-xs italic"
|
||||
>Hide Poster Modal Title</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,15 @@
|
||||
import { ae_loc } from '$lib/stores/ae_stores';
|
||||
import { events_loc, events_sess } from '$lib/stores/ae_events_stores';
|
||||
import Launcher_Cfg_Section from './launcher_cfg_section.svelte';
|
||||
import { Gamepad2, Link, Lock, LockOpen, Plug, RefreshCw, Unlink } from '@lucide/svelte';
|
||||
import {
|
||||
Gamepad2,
|
||||
Link,
|
||||
Lock,
|
||||
LockOpen,
|
||||
Plug,
|
||||
RefreshCw,
|
||||
Unlink
|
||||
} from '@lucide/svelte';
|
||||
interface Props {
|
||||
on_expand?: () => void;
|
||||
}
|
||||
@@ -20,41 +28,35 @@
|
||||
{on_expand}
|
||||
description="Mode: {$events_loc.launcher?.controller} | WS: {ws_connected
|
||||
? 'Connected'
|
||||
: 'Offline'}"
|
||||
>
|
||||
: 'Offline'}">
|
||||
<!-- Content omitted for brevity, preserved in file -->
|
||||
<div class="col-span-full flex flex-col gap-3">
|
||||
<!-- 1. Connection Status Badge -->
|
||||
<div
|
||||
class="flex items-center justify-between bg-surface-500/5 p-2 rounded border border-surface-500/10"
|
||||
>
|
||||
class="bg-surface-500/5 border-surface-500/10 flex items-center justify-between rounded border p-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<Plug size="1em" class="opacity-50" />
|
||||
<span class="text-[10px] font-bold uppercase tracking-wider"
|
||||
>WebSocket Link</span
|
||||
>
|
||||
<span class="text-[10px] font-bold tracking-wider uppercase"
|
||||
>WebSocket Link</span>
|
||||
</div>
|
||||
{#if ws_connected}
|
||||
<span
|
||||
class="badge preset-filled-success text-[8px] animate-pulse"
|
||||
>Connected</span
|
||||
>
|
||||
class="badge preset-filled-success animate-pulse text-[8px]"
|
||||
>Connected</span>
|
||||
{:else}
|
||||
<span class="badge preset-filled-error text-[8px]"
|
||||
>Disconnected</span
|
||||
>
|
||||
>Disconnected</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- 2. Controller Mode Selection -->
|
||||
<div class="flex flex-col gap-1">
|
||||
<p class="text-[9px] font-bold uppercase opacity-50 ml-1"
|
||||
>Controller Strategy</p
|
||||
>
|
||||
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">
|
||||
Controller Strategy
|
||||
</p>
|
||||
<select
|
||||
bind:value={$events_loc.launcher.controller}
|
||||
class="select select-sm text-xs preset-tonal-surface h-8"
|
||||
>
|
||||
class="select select-sm preset-tonal-surface h-8 text-xs">
|
||||
<option value="local">Local Only</option>
|
||||
<option value="remote">Remotely WS Controlled</option>
|
||||
<option value="local_push">Local and WS Controller</option>
|
||||
@@ -62,7 +64,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 3. Connection Actions -->
|
||||
<div class="grid grid-cols-2 gap-2 mt-1">
|
||||
<div class="mt-1 grid grid-cols-2 gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
@@ -76,8 +78,7 @@
|
||||
}}
|
||||
class="btn btn-sm text-[10px] font-bold transition-all"
|
||||
class:preset-tonal-error={$events_loc.launcher.ws_connect}
|
||||
class:preset-tonal-success={!$events_loc.launcher.ws_connect}
|
||||
>
|
||||
class:preset-tonal-success={!$events_loc.launcher.ws_connect}>
|
||||
{#if $events_loc.launcher.ws_connect}
|
||||
<Unlink size="0.85em" class="mr-1" /> Disconnect
|
||||
{:else}
|
||||
@@ -92,8 +93,7 @@
|
||||
$events_sess.launcher.controller_trigger_send = 'trigger';
|
||||
}}
|
||||
class="btn btn-sm preset-tonal-secondary hover:preset-filled-secondary-500 text-[10px] font-bold"
|
||||
disabled={!ws_connected}
|
||||
>
|
||||
disabled={!ws_connected}>
|
||||
<RefreshCw size="0.85em" class="mr-1" /> Group Reload
|
||||
</button>
|
||||
</div>
|
||||
@@ -101,21 +101,19 @@
|
||||
<!-- 4. Technical Config (Edit Mode Only) -->
|
||||
{#if $ae_loc.edit_mode}
|
||||
<div
|
||||
class="col-span-full border-t border-surface-500/20 pt-3 mt-1 flex flex-col gap-2"
|
||||
>
|
||||
<p class="text-[9px] font-bold uppercase opacity-50 ml-1"
|
||||
>Channel Configuration</p
|
||||
>
|
||||
class="border-surface-500/20 col-span-full mt-1 flex flex-col gap-2 border-t pt-3">
|
||||
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">
|
||||
Channel Configuration
|
||||
</p>
|
||||
<div class="flex gap-1">
|
||||
<input
|
||||
bind:value={$events_loc.launcher.controller_group_code}
|
||||
placeholder="Group Code"
|
||||
class="input input-sm grow text-[10px] h-7 preset-tonal-surface font-mono"
|
||||
class="input input-sm preset-tonal-surface h-7 grow font-mono text-[10px]"
|
||||
readonly={!$events_sess.launcher
|
||||
.controller_unlock_group_code}
|
||||
ondblclick={() =>
|
||||
($events_sess.launcher.controller_unlock_group_code = true)}
|
||||
/>
|
||||
($events_sess.launcher.controller_unlock_group_code = true)} />
|
||||
<button
|
||||
type="button"
|
||||
onclick={() =>
|
||||
@@ -123,8 +121,7 @@
|
||||
!$events_sess.launcher
|
||||
.controller_unlock_group_code)}
|
||||
class="btn btn-xs preset-tonal-surface"
|
||||
title="Toggle Unlock"
|
||||
>
|
||||
title="Toggle Unlock">
|
||||
{#if $events_sess.launcher.controller_unlock_group_code}
|
||||
<LockOpen size="0.85em" class="text-primary-500" />
|
||||
{:else}
|
||||
@@ -132,7 +129,7 @@
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-[8px] opacity-40 italic ml-1">
|
||||
<p class="ml-1 text-[8px] italic opacity-40">
|
||||
Double-click input to unlock editing. Changing code triggers
|
||||
reconnect.
|
||||
</p>
|
||||
|
||||
@@ -47,53 +47,48 @@
|
||||
bind:state={$events_loc.launcher.section_state__health}
|
||||
{on_expand}
|
||||
description="Heartbeat: {$events_sess.launcher.heartbeat_info
|
||||
.last_timestamp || 'Pending'}"
|
||||
>
|
||||
.last_timestamp || 'Pending'}">
|
||||
<!-- Content omitted for brevity in instruction, but preserved in file -->
|
||||
<!-- Telemetry Dashboard -->
|
||||
<div
|
||||
class="col-span-full flex flex-col gap-3 bg-surface-500/5 p-3 rounded-lg border border-surface-500/10"
|
||||
>
|
||||
class="bg-surface-500/5 border-surface-500/10 col-span-full flex flex-col gap-3 rounded-lg border p-3">
|
||||
<!-- CPU Usage (Mock Logic if load not available yet) -->
|
||||
<div class="flex flex-col gap-1">
|
||||
<div
|
||||
class="flex justify-between text-[9px] uppercase font-bold opacity-60"
|
||||
>
|
||||
class="flex justify-between text-[9px] font-bold uppercase opacity-60">
|
||||
<span
|
||||
>CPU Architecture: {$ae_loc.native_device?.meta_json
|
||||
?.arch || '...'}</span
|
||||
>
|
||||
?.arch || '...'}</span>
|
||||
<span>Load: {cpu_load_pct}%</span>
|
||||
</div>
|
||||
<div
|
||||
class="w-full h-1.5 bg-surface-500/20 rounded-full overflow-hidden"
|
||||
>
|
||||
class="bg-surface-500/20 h-1.5 w-full overflow-hidden rounded-full">
|
||||
<div
|
||||
class="h-full transition-all duration-1000 {get_usage_color(cpu_load_pct)}"
|
||||
style="width: {cpu_load_pct}%"
|
||||
></div>
|
||||
class="h-full transition-all duration-1000 {get_usage_color(
|
||||
cpu_load_pct
|
||||
)}"
|
||||
style="width: {cpu_load_pct}%">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RAM Usage -->
|
||||
<div class="flex flex-col gap-1">
|
||||
<div
|
||||
class="flex justify-between text-[9px] uppercase font-bold opacity-60"
|
||||
>
|
||||
class="flex justify-between text-[9px] font-bold uppercase opacity-60">
|
||||
<span>Memory (RAM)</span>
|
||||
<span>{ram_usage_pct}% Used</span>
|
||||
</div>
|
||||
<div
|
||||
class="w-full h-1.5 bg-surface-500/20 rounded-full overflow-hidden"
|
||||
>
|
||||
class="bg-surface-500/20 h-1.5 w-full overflow-hidden rounded-full">
|
||||
<div
|
||||
class="h-full transition-all duration-1000 {get_usage_color(
|
||||
ram_usage_pct
|
||||
)}"
|
||||
style="width: {ram_usage_pct}%"
|
||||
></div>
|
||||
style="width: {ram_usage_pct}%">
|
||||
</div>
|
||||
<div class="text-[8px] opacity-40 text-right italic">
|
||||
</div>
|
||||
<div class="text-right text-[8px] italic opacity-40">
|
||||
Free: {$ae_loc.native_device?.meta_json?.free_mem || '...'} / {$ae_loc
|
||||
.native_device?.meta_json?.total_mem || '...'}
|
||||
</div>
|
||||
@@ -101,26 +96,23 @@
|
||||
</div>
|
||||
|
||||
<!-- Heartbeat & Sync Info -->
|
||||
<div class="grid grid-cols-2 gap-x-2 gap-y-2 w-full text-[10px] p-1">
|
||||
<div class="grid w-full grid-cols-2 gap-x-2 gap-y-2 p-1 text-[10px]">
|
||||
<div class="flex flex-col">
|
||||
<span class="opacity-50 text-[8px] uppercase font-bold"
|
||||
>Last Heartbeat</span
|
||||
>
|
||||
<span class="text-[8px] font-bold uppercase opacity-50"
|
||||
>Last Heartbeat</span>
|
||||
<span
|
||||
class="font-mono {$events_sess.launcher.heartbeat_info
|
||||
.status === 'success'
|
||||
? 'text-success-500'
|
||||
: 'text-error-500'}"
|
||||
>
|
||||
: 'text-error-500'}">
|
||||
{$events_sess.launcher.heartbeat_info.last_timestamp ||
|
||||
'Pending...'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col text-right">
|
||||
<span class="opacity-50 text-[8px] uppercase font-bold"
|
||||
>Local File Cache</span
|
||||
>
|
||||
<span class="text-[8px] font-bold uppercase opacity-50"
|
||||
>Local File Cache</span>
|
||||
<span class="font-mono">
|
||||
{$events_sess.launcher.sync_stats.cached} / {$events_sess
|
||||
.launcher.sync_stats.total}
|
||||
@@ -129,19 +121,18 @@
|
||||
|
||||
{#if $events_sess.launcher.sync_stats.currently_syncing}
|
||||
<div
|
||||
class="col-span-full bg-primary-500/10 p-2 rounded border border-primary-500/20 animate-pulse mt-1"
|
||||
>
|
||||
class="bg-primary-500/10 border-primary-500/20 col-span-full mt-1 animate-pulse rounded border p-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<RefreshCw size="1em" class="animate-spin text-primary-500" />
|
||||
<RefreshCw
|
||||
size="1em"
|
||||
class="text-primary-500 animate-spin" />
|
||||
<div class="flex flex-col truncate">
|
||||
<span
|
||||
class="text-[8px] uppercase font-bold text-primary-500"
|
||||
>Syncing File...</span
|
||||
>
|
||||
class="text-primary-500 text-[8px] font-bold uppercase"
|
||||
>Syncing File...</span>
|
||||
<span class="truncate italic opacity-80"
|
||||
>{$events_sess.launcher.sync_stats
|
||||
.currently_syncing}</span
|
||||
>
|
||||
.currently_syncing}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -151,26 +142,22 @@
|
||||
<!-- Device Metadata (Edit Mode Only) -->
|
||||
{#if $ae_loc.edit_mode}
|
||||
<div
|
||||
class="col-span-full mt-1 pt-2 border-t border-surface-500/10 flex flex-col gap-1 text-[9px] opacity-60 px-1"
|
||||
>
|
||||
class="border-surface-500/10 col-span-full mt-1 flex flex-col gap-1 border-t px-1 pt-2 text-[9px] opacity-60">
|
||||
<div class="flex justify-between">
|
||||
<span>Hostname:</span>
|
||||
<span class="font-mono"
|
||||
>{$ae_loc.native_device.info_hostname || '...'}</span
|
||||
>
|
||||
>{$ae_loc.native_device.info_hostname || '...'}</span>
|
||||
</div>
|
||||
<div class="flex justify-between gap-4">
|
||||
<span>IP Addresses:</span>
|
||||
<span class="font-mono truncate"
|
||||
>{$ae_loc.native_device.info_ip_list || '...'}</span
|
||||
>
|
||||
<span class="truncate font-mono"
|
||||
>{$ae_loc.native_device.info_ip_list || '...'}</span>
|
||||
</div>
|
||||
<div class="mt-2 opacity-40">
|
||||
<span class="text-[8px] uppercase font-bold"
|
||||
>Raw Device JSON</span
|
||||
>
|
||||
<span class="text-[8px] font-bold uppercase"
|
||||
>Raw Device JSON</span>
|
||||
<pre
|
||||
class="text-[7px] max-h-32 overflow-y-auto bg-black/20 p-1 rounded mt-1">
|
||||
class="mt-1 max-h-32 overflow-y-auto rounded bg-black/20 p-1 text-[7px]">
|
||||
{JSON.stringify($ae_loc.native_device, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
@@ -14,11 +14,20 @@
|
||||
|
||||
async function handle_cleanup_now() {
|
||||
const cache_root = $ae_loc.local_file_cache_path;
|
||||
if (!cache_root) { cleanup_status = 'Error: Cache path not set.'; return; }
|
||||
if (!cache_root) {
|
||||
cleanup_status = 'Error: Cache path not set.';
|
||||
return;
|
||||
}
|
||||
const max_age_hours = $events_loc.launcher.cleanup_tmp_max_age_hours ?? 24;
|
||||
cleanup_status = 'Cleaning...';
|
||||
const result = await cleanup_tmp_files({ cache_root, max_age_minutes: max_age_hours * 60 });
|
||||
cleanup_status = (result as any).success !== false ? 'Done.' : `Error: ${(result as any).error}`;
|
||||
const result = await cleanup_tmp_files({
|
||||
cache_root,
|
||||
max_age_minutes: max_age_hours * 60
|
||||
});
|
||||
cleanup_status =
|
||||
(result as any).success !== false
|
||||
? 'Done.'
|
||||
: `Error: ${(result as any).error}`;
|
||||
setTimeout(() => (cleanup_status = ''), 4000);
|
||||
}
|
||||
|
||||
@@ -27,9 +36,7 @@
|
||||
|
||||
if (val == 'delete_idbs') {
|
||||
if (
|
||||
confirm(
|
||||
'Are you sure you want to delete ALL IndexedDB databases?'
|
||||
)
|
||||
confirm('Are you sure you want to delete ALL IndexedDB databases?')
|
||||
) {
|
||||
indexedDB.deleteDatabase('ae_archives_db');
|
||||
indexedDB.deleteDatabase('ae_core_db');
|
||||
@@ -37,9 +44,7 @@
|
||||
indexedDB.deleteDatabase('ae_journals_db');
|
||||
indexedDB.deleteDatabase('ae_posts_db');
|
||||
indexedDB.deleteDatabase('ae_sponsorships_db');
|
||||
alert(
|
||||
'All IndexedDB databases deleted. Please reload the app.'
|
||||
);
|
||||
alert('All IndexedDB databases deleted. Please reload the app.');
|
||||
}
|
||||
} else if (val == 'delete_idbs_events') {
|
||||
if (
|
||||
@@ -48,9 +53,7 @@
|
||||
)
|
||||
) {
|
||||
indexedDB.deleteDatabase('ae_events_db');
|
||||
alert(
|
||||
'Events IndexedDB database deleted. Please reload the app.'
|
||||
);
|
||||
alert('Events IndexedDB database deleted. Please reload the app.');
|
||||
}
|
||||
} else if (val == 'delete_local') {
|
||||
if (confirm('Are you sure you want to delete ALL local config?')) {
|
||||
@@ -79,44 +82,43 @@
|
||||
icon={Wrench}
|
||||
bind:state={$events_loc.launcher.section_state__local_actions}
|
||||
{on_expand}
|
||||
description="Cache wiping and global menu toggles"
|
||||
>
|
||||
description="Cache wiping and global menu toggles">
|
||||
<div class="col-span-full flex flex-col gap-3">
|
||||
<!-- 1. Reset Actions -->
|
||||
<div class="flex flex-col gap-1">
|
||||
<span
|
||||
class="text-[9px] font-bold uppercase opacity-50 ml-1 text-error-500"
|
||||
>Maintenance & Resets</span
|
||||
>
|
||||
class="text-error-500 ml-1 text-[9px] font-bold uppercase opacity-50"
|
||||
>Maintenance & Resets</span>
|
||||
<select
|
||||
bind:value={selected_reset}
|
||||
onchange={(e) =>
|
||||
handle_reset_action((e.target as HTMLSelectElement).value)}
|
||||
class="select select-sm text-xs preset-tonal-surface h-8 text-error-500 border-error-500/20"
|
||||
>
|
||||
class="select select-sm preset-tonal-surface text-error-500 border-error-500/20 h-8 text-xs">
|
||||
<option value="">-- Select a reset action --</option>
|
||||
<option value="delete_idbs">Delete ALL Databases</option>
|
||||
<option value="delete_idbs_events">Delete Events DB Only</option
|
||||
>
|
||||
<option value="delete_idbs_events"
|
||||
>Delete Events DB Only</option>
|
||||
<option value="delete_local">Wipe ALL Local Storage</option>
|
||||
<option value="delete_local_events"
|
||||
>Wipe Events Storage Only</option
|
||||
>
|
||||
>Wipe Events Storage Only</option>
|
||||
</select>
|
||||
<span class="text-[8px] opacity-40 italic ml-1 leading-tight">
|
||||
<span class="ml-1 text-[8px] leading-tight italic opacity-40">
|
||||
* Destructive actions require browser confirmation.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 2. UI Toggles -->
|
||||
<div class="grid grid-cols-2 gap-2 mt-1">
|
||||
<div class="mt-1 grid grid-cols-2 gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => ($ae_loc.sys_menu.hide = !$ae_loc.sys_menu.hide)}
|
||||
class="btn btn-xs preset-tonal-surface hover:preset-filled-primary-500"
|
||||
title="Show/Hide Aether global system menu"
|
||||
>
|
||||
{#if $ae_loc.sys_menu.hide}<Eye size="1em" class="mr-2" />{:else}<EyeOff size="1em" class="mr-2" />{/if}
|
||||
title="Show/Hide Aether global system menu">
|
||||
{#if $ae_loc.sys_menu.hide}<Eye
|
||||
size="1em"
|
||||
class="mr-2" />{:else}<EyeOff
|
||||
size="1em"
|
||||
class="mr-2" />{/if}
|
||||
{$ae_loc.sys_menu.hide ? 'Show' : 'Hide'} Sys Menu
|
||||
</button>
|
||||
|
||||
@@ -125,41 +127,51 @@
|
||||
onclick={() =>
|
||||
($ae_loc.debug_menu.hide = !$ae_loc.debug_menu.hide)}
|
||||
class="btn btn-xs preset-tonal-surface hover:preset-filled-primary-500"
|
||||
title="Show/Hide Aether global debug menu"
|
||||
>
|
||||
{#if $ae_loc.debug_menu.hide}<Bug size="1em" class="mr-2" />{:else}<BugOff size="1em" class="mr-2" />{/if}
|
||||
title="Show/Hide Aether global debug menu">
|
||||
{#if $ae_loc.debug_menu.hide}<Bug
|
||||
size="1em"
|
||||
class="mr-2" />{:else}<BugOff
|
||||
size="1em"
|
||||
class="mr-2" />{/if}
|
||||
{$ae_loc.debug_menu.hide ? 'Show' : 'Hide'} Debug
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 3. Cache .tmp Cleanup (Native Only) -->
|
||||
{#if $ae_loc.is_native && $ae_loc.local_file_cache_path}
|
||||
<div class="flex flex-col gap-1 border-t border-surface-500/20 pt-2 mt-1">
|
||||
<span class="text-[9px] font-bold uppercase opacity-50 ml-1">Cache Maintenance</span>
|
||||
<div
|
||||
class="border-surface-500/20 mt-1 flex flex-col gap-1 border-t pt-2">
|
||||
<span class="ml-1 text-[9px] font-bold uppercase opacity-50"
|
||||
>Cache Maintenance</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<label for="cleanup_max_age" class="text-[10px] opacity-70 whitespace-nowrap">Max age (hrs):</label>
|
||||
<label
|
||||
for="cleanup_max_age"
|
||||
class="text-[10px] whitespace-nowrap opacity-70"
|
||||
>Max age (hrs):</label>
|
||||
<input
|
||||
id="cleanup_max_age"
|
||||
type="number"
|
||||
min="1"
|
||||
max="168"
|
||||
bind:value={$events_loc.launcher.cleanup_tmp_max_age_hours}
|
||||
class="input input-sm w-16 h-7 text-xs text-center preset-tonal-surface"
|
||||
placeholder="24"
|
||||
/>
|
||||
bind:value={
|
||||
$events_loc.launcher.cleanup_tmp_max_age_hours
|
||||
}
|
||||
class="input input-sm preset-tonal-surface h-7 w-16 text-center text-xs"
|
||||
placeholder="24" />
|
||||
<button
|
||||
type="button"
|
||||
onclick={handle_cleanup_now}
|
||||
class="btn btn-xs preset-tonal-warning hover:preset-filled-warning-500 grow"
|
||||
>
|
||||
class="btn btn-xs preset-tonal-warning hover:preset-filled-warning-500 grow">
|
||||
<Eraser size="0.85em" class="mr-1" /> Clean .tmp Now
|
||||
</button>
|
||||
</div>
|
||||
{#if cleanup_status}
|
||||
<span class="text-[9px] italic opacity-60 ml-1">{cleanup_status}</span>
|
||||
<span class="ml-1 text-[9px] italic opacity-60"
|
||||
>{cleanup_status}</span>
|
||||
{/if}
|
||||
<span class="text-[8px] opacity-40 italic ml-1 leading-tight">
|
||||
Removes stale in-progress download artifacts. Auto-runs on startup.
|
||||
<span class="ml-1 text-[8px] leading-tight italic opacity-40">
|
||||
Removes stale in-progress download artifacts. Auto-runs on
|
||||
startup.
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -167,14 +179,12 @@
|
||||
<!-- 4. Connection Summary (Edit Mode Only) -->
|
||||
{#if $ae_loc.edit_mode}
|
||||
<div
|
||||
class="col-span-full border-t border-surface-500/20 pt-2 mt-1 flex flex-col gap-1"
|
||||
>
|
||||
<p class="text-[9px] font-bold uppercase opacity-50 ml-1"
|
||||
>API Context</p
|
||||
>
|
||||
class="border-surface-500/20 col-span-full mt-1 flex flex-col gap-1 border-t pt-2">
|
||||
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">
|
||||
API Context
|
||||
</p>
|
||||
<div
|
||||
class="bg-black/10 p-2 rounded text-[9px] font-mono opacity-60 break-all leading-tight"
|
||||
>
|
||||
class="rounded bg-black/10 p-2 font-mono text-[9px] leading-tight break-all opacity-60">
|
||||
Endpoint: {$ae_api.base_url}<br />
|
||||
Account: {$ae_loc.account_id}
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,21 @@
|
||||
import { events_loc, events_sess } from '$lib/stores/ae_events_stores';
|
||||
import * as native from '$lib/electron/electron_relay';
|
||||
import Launcher_Cfg_Section from './launcher_cfg_section.svelte';
|
||||
import { Code, Columns2, FlaskConical, FolderOpen, Image, Maximize2, Monitor, Play, Power, RefreshCw, SkipBack, SkipForward, Square } from '@lucide/svelte';
|
||||
import {
|
||||
Code,
|
||||
Columns2,
|
||||
FlaskConical,
|
||||
FolderOpen,
|
||||
Image,
|
||||
Maximize2,
|
||||
Monitor,
|
||||
Play,
|
||||
Power,
|
||||
RefreshCw,
|
||||
SkipBack,
|
||||
SkipForward,
|
||||
Square
|
||||
} from '@lucide/svelte';
|
||||
interface Props {
|
||||
on_expand?: () => void;
|
||||
}
|
||||
@@ -42,9 +56,7 @@
|
||||
}
|
||||
|
||||
// Modal state for dangerous actions
|
||||
let show_power_confirm = $state<{ action: string; label: string } | null>(
|
||||
null
|
||||
);
|
||||
let show_power_confirm = $state<{ action: string; label: string } | null>(null);
|
||||
</script>
|
||||
|
||||
<Launcher_Cfg_Section
|
||||
@@ -53,51 +65,50 @@
|
||||
bind:state={$events_loc.launcher.section_state__native_os}
|
||||
{on_expand}
|
||||
description="OS: {$ae_loc.native_device?.meta_json?.platform ||
|
||||
'...'} | Kiosk & Apps"
|
||||
>
|
||||
'...'} | Kiosk & Apps">
|
||||
<!-- Dev preview banner: shown when edit_mode is on but not running in Electron.
|
||||
electron_relay functions all return null when native is absent — no errors. -->
|
||||
{#if $ae_loc.edit_mode && !$ae_loc.is_native}
|
||||
<div class="flex items-center gap-2 px-2 py-1.5 rounded-lg bg-warning-500/10 border border-warning-500/30 mb-1">
|
||||
<div
|
||||
class="bg-warning-500/10 border-warning-500/30 mb-1 flex items-center gap-2 rounded-lg border px-2 py-1.5">
|
||||
<FlaskConical size="0.75em" class="text-warning-500" />
|
||||
<span class="text-[9px] text-warning-500 font-bold uppercase tracking-wide">Dev Preview — controls visible but non-functional without Electron</span>
|
||||
<span
|
||||
class="text-warning-500 text-[9px] font-bold tracking-wide uppercase"
|
||||
>Dev Preview — controls visible but non-functional without
|
||||
Electron</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if system_status}
|
||||
<div
|
||||
class="col-span-full text-[10px] text-center italic bg-surface-500/10 py-1 rounded animate-pulse text-primary-500 border border-primary-500/20"
|
||||
>
|
||||
class="bg-surface-500/10 text-primary-500 border-primary-500/20 col-span-full animate-pulse rounded border py-1 text-center text-[10px] italic">
|
||||
{system_status}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- 1. Window & Folders (Common) -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<p class="text-[9px] font-bold uppercase opacity-50 ml-1"
|
||||
>Folders & View</p
|
||||
>
|
||||
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">
|
||||
Folders & View
|
||||
</p>
|
||||
<div class="grid grid-cols-2 gap-1">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() =>
|
||||
native.open_folder($ae_loc.local_file_cache_path)}
|
||||
class="btn btn-xs preset-tonal-primary hover:preset-filled-primary-500 justify-start"
|
||||
>
|
||||
class="btn btn-xs preset-tonal-primary hover:preset-filled-primary-500 justify-start">
|
||||
<FolderOpen size="0.85em" class="mr-1 shrink-0" /> Cache
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => native.open_folder($ae_loc.host_file_temp_path)}
|
||||
class="btn btn-xs preset-tonal-primary hover:preset-filled-primary-500 justify-start"
|
||||
>
|
||||
class="btn btn-xs preset-tonal-primary hover:preset-filled-primary-500 justify-start">
|
||||
<FolderOpen size="0.85em" class="mr-1 shrink-0" /> Temp
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => native.window_control({ action: 'maximize' })}
|
||||
class="btn btn-xs preset-tonal-surface hover:preset-filled-primary-500"
|
||||
>
|
||||
class="btn btn-xs preset-tonal-surface hover:preset-filled-primary-500">
|
||||
<Maximize2 size="0.85em" class="mr-1" /> Maximize
|
||||
</button>
|
||||
<button
|
||||
@@ -107,8 +118,7 @@
|
||||
native.window_control({ action: 'kiosk', value: true }),
|
||||
'Kiosk Mode'
|
||||
)}
|
||||
class="btn btn-xs preset-tonal-surface hover:preset-filled-primary-500"
|
||||
>
|
||||
class="btn btn-xs preset-tonal-surface hover:preset-filled-primary-500">
|
||||
<Monitor size="0.85em" class="mr-1" /> Kiosk
|
||||
</button>
|
||||
</div>
|
||||
@@ -116,14 +126,13 @@
|
||||
|
||||
<!-- 2. Presentation Remote Control (Common) -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-row justify-between items-center px-1">
|
||||
<p class="text-[9px] font-bold uppercase opacity-50"
|
||||
>Remote Control</p
|
||||
>
|
||||
<div class="flex flex-row items-center justify-between px-1">
|
||||
<p class="text-[9px] font-bold uppercase opacity-50">
|
||||
Remote Control
|
||||
</p>
|
||||
<select
|
||||
bind:value={remote_app}
|
||||
class="select select-sm py-0 h-5 text-[9px] w-24 preset-tonal-surface"
|
||||
>
|
||||
class="select select-sm preset-tonal-surface h-5 w-24 py-0 text-[9px]">
|
||||
<option value="powerpoint">PowerPoint</option>
|
||||
<option value="keynote">Keynote</option>
|
||||
</select>
|
||||
@@ -134,39 +143,34 @@
|
||||
type="button"
|
||||
onclick={() => handle_remote_control('prev')}
|
||||
class="btn btn-sm preset-tonal-secondary"
|
||||
title="Previous Slide"
|
||||
>
|
||||
title="Previous Slide">
|
||||
<SkipBack size="1em" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handle_remote_control('start')}
|
||||
class="btn btn-sm preset-tonal-success"
|
||||
title="Start/Resume Slideshow"
|
||||
>
|
||||
title="Start/Resume Slideshow">
|
||||
<Play size="1em" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handle_remote_control('stop')}
|
||||
class="btn btn-sm preset-tonal-error"
|
||||
title="Stop Slideshow"
|
||||
>
|
||||
title="Stop Slideshow">
|
||||
<Square size="1em" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handle_remote_control('next')}
|
||||
class="btn btn-sm preset-tonal-secondary"
|
||||
title="Next Slide"
|
||||
>
|
||||
title="Next Slide">
|
||||
<SkipForward size="1em" />
|
||||
</button>
|
||||
</div>
|
||||
{#if remote_status}
|
||||
<div
|
||||
class="text-[9px] text-center italic animate-pulse text-primary-500"
|
||||
>
|
||||
class="text-primary-500 animate-pulse text-center text-[9px] italic">
|
||||
{remote_status}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -175,13 +179,12 @@
|
||||
<!-- 3. Technical Management (Edit Mode Only) -->
|
||||
{#if $ae_loc.edit_mode}
|
||||
<div
|
||||
class="col-span-full border-t border-surface-500/20 pt-3 mt-1 flex flex-col gap-3"
|
||||
>
|
||||
class="border-surface-500/20 col-span-full mt-1 flex flex-col gap-3 border-t pt-3">
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div class="flex flex-col gap-1">
|
||||
<p class="text-[9px] font-bold uppercase opacity-50"
|
||||
>System Actions</p
|
||||
>
|
||||
<p class="text-[9px] font-bold uppercase opacity-50">
|
||||
System Actions
|
||||
</p>
|
||||
<div class="grid grid-cols-1 gap-1">
|
||||
<button
|
||||
type="button"
|
||||
@@ -192,9 +195,9 @@
|
||||
}),
|
||||
'Extend Display'
|
||||
)}
|
||||
class="btn btn-xs preset-tonal-surface hover:preset-filled-primary-500 justify-start"
|
||||
>
|
||||
<Columns2 size="0.85em" class="mr-1 shrink-0" /> Extend Mode
|
||||
class="btn btn-xs preset-tonal-surface hover:preset-filled-primary-500 justify-start">
|
||||
<Columns2 size="0.85em" class="mr-1 shrink-0" /> Extend
|
||||
Mode
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -206,17 +209,15 @@
|
||||
'Wallpaper'
|
||||
)}
|
||||
class="btn btn-xs preset-tonal-surface hover:preset-filled-primary-500 justify-start"
|
||||
disabled={!$ae_loc.site_header_image_path}
|
||||
>
|
||||
disabled={!$ae_loc.site_header_image_path}>
|
||||
<Image size="0.85em" class="mr-1 shrink-0" /> Reset Wallpaper
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span
|
||||
class="text-[9px] font-bold uppercase opacity-50 text-error-500"
|
||||
>Power</span
|
||||
>
|
||||
class="text-error-500 text-[9px] font-bold uppercase opacity-50"
|
||||
>Power</span>
|
||||
<div class="grid grid-cols-1 gap-1">
|
||||
<button
|
||||
type="button"
|
||||
@@ -225,8 +226,7 @@
|
||||
action: 'reboot',
|
||||
label: 'Reboot Laptop'
|
||||
})}
|
||||
class="btn btn-xs preset-tonal-warning hover:preset-filled-warning-500 justify-start"
|
||||
>
|
||||
class="btn btn-xs preset-tonal-warning hover:preset-filled-warning-500 justify-start">
|
||||
<RefreshCw size="0.85em" class="mr-1 shrink-0" /> Reboot
|
||||
</button>
|
||||
<button
|
||||
@@ -236,8 +236,7 @@
|
||||
action: 'shutdown',
|
||||
label: 'Shutdown Laptop'
|
||||
})}
|
||||
class="btn btn-xs preset-tonal-error hover:preset-filled-error-500 justify-start"
|
||||
>
|
||||
class="btn btn-xs preset-tonal-error hover:preset-filled-error-500 justify-start">
|
||||
<Power size="0.85em" class="mr-1 shrink-0" /> Shutdown
|
||||
</button>
|
||||
</div>
|
||||
@@ -245,16 +244,15 @@
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<p class="text-[9px] font-bold uppercase opacity-50 ml-1"
|
||||
>Terminal Access</p
|
||||
>
|
||||
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">
|
||||
Terminal Access
|
||||
</p>
|
||||
<div class="flex gap-1">
|
||||
<input
|
||||
type="text"
|
||||
bind:value={$events_sess.launcher.manual_cmd}
|
||||
placeholder="ls -la"
|
||||
class="input input-sm grow text-[10px] preset-tonal-surface h-7"
|
||||
/>
|
||||
class="input input-sm preset-tonal-surface h-7 grow text-[10px]" />
|
||||
<button
|
||||
type="button"
|
||||
onclick={async () => {
|
||||
@@ -268,13 +266,12 @@
|
||||
(res as any).error ||
|
||||
'No Output';
|
||||
}}
|
||||
class="btn btn-sm preset-filled-secondary hover:preset-filled-primary-500 text-[10px] h-7"
|
||||
>Run</button
|
||||
>
|
||||
class="btn btn-sm preset-filled-secondary hover:preset-filled-primary-500 h-7 text-[10px]"
|
||||
>Run</button>
|
||||
</div>
|
||||
{#if test_cmd_result}
|
||||
<pre
|
||||
class="text-[8px] bg-black text-green-500 p-2 mt-1 overflow-x-auto rounded border border-surface-500/50 max-h-24 shadow-inner">{test_cmd_result}</pre>
|
||||
class="border-surface-500/50 mt-1 max-h-24 overflow-x-auto rounded border bg-black p-2 text-[8px] text-green-500 shadow-inner">{test_cmd_result}</pre>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -284,25 +281,21 @@
|
||||
<!-- Power Confirmation Modal -->
|
||||
{#if show_power_confirm}
|
||||
<div
|
||||
class="fixed inset-0 z-[1000] flex items-center justify-center bg-black/50 backdrop-blur-sm p-4"
|
||||
>
|
||||
class="fixed inset-0 z-[1000] flex items-center justify-center bg-black/50 p-4 backdrop-blur-sm">
|
||||
<div
|
||||
class="card p-6 w-full max-w-sm preset-filled-surface-100-900 border border-error-500 shadow-2xl animate-in zoom-in-95 duration-200"
|
||||
>
|
||||
<h4 class="h4 text-error-500 font-bold mb-2">
|
||||
class="card preset-filled-surface-100-900 border-error-500 animate-in zoom-in-95 w-full max-w-sm border p-6 shadow-2xl duration-200">
|
||||
<h4 class="h4 text-error-500 mb-2 font-bold">
|
||||
Confirm System Action
|
||||
</h4>
|
||||
<p class="text-sm opacity-80 mb-6">
|
||||
<p class="mb-6 text-sm opacity-80">
|
||||
Are you sure you want to <strong
|
||||
>{show_power_confirm.action}</strong
|
||||
> this host machine?
|
||||
>{show_power_confirm.action}</strong> this host machine?
|
||||
</p>
|
||||
<div class="flex justify-end gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (show_power_confirm = null)}
|
||||
class="btn btn-sm preset-tonal-surface">Cancel</button
|
||||
>
|
||||
class="btn btn-sm preset-tonal-surface">Cancel</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
@@ -314,8 +307,7 @@
|
||||
action
|
||||
);
|
||||
}}
|
||||
class="btn btn-sm preset-filled-error"
|
||||
>
|
||||
class="btn btn-sm preset-filled-error">
|
||||
Confirm {show_power_confirm.action}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -16,62 +16,54 @@
|
||||
{on_expand}
|
||||
description="Idle: {($events_loc.launcher.idle_timer / 60000).toFixed(
|
||||
1
|
||||
)}m | Auto-Posters"
|
||||
>
|
||||
)}m | Auto-Posters">
|
||||
<!-- Content omitted for brevity, preserved in file -->
|
||||
<div class="col-span-full flex flex-col gap-3">
|
||||
<!-- 1. Technical Timers (Edit Mode Only) -->
|
||||
{#if $ae_loc.edit_mode}
|
||||
<div class="flex flex-col gap-2">
|
||||
<p class="text-[9px] font-bold uppercase opacity-50 ml-1"
|
||||
>Screen Saver Timers (ms)</p
|
||||
>
|
||||
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">
|
||||
Screen Saver Timers (ms)
|
||||
</p>
|
||||
<div
|
||||
class="grid grid-cols-1 gap-2 bg-surface-500/5 p-2 rounded border border-surface-500/10"
|
||||
>
|
||||
<div class="flex justify-between items-center gap-4">
|
||||
class="bg-surface-500/5 border-surface-500/10 grid grid-cols-1 gap-2 rounded border p-2">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<span class="text-[10px] opacity-60">Idle Wait</span>
|
||||
<input
|
||||
type="number"
|
||||
min={3000}
|
||||
bind:value={$events_loc.launcher.idle_timer}
|
||||
class="input input-sm text-[10px] h-7 w-24 text-right preset-tonal-surface"
|
||||
/>
|
||||
class="input input-sm preset-tonal-surface h-7 w-24 text-right text-[10px]" />
|
||||
</div>
|
||||
<div class="flex justify-between items-center gap-4">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<span class="text-[10px] opacity-60">Cycle Check</span>
|
||||
<input
|
||||
type="number"
|
||||
min={500}
|
||||
bind:value={$events_loc.launcher.idle_cycle}
|
||||
class="input input-sm text-[10px] h-7 w-24 text-right preset-tonal-surface"
|
||||
/>
|
||||
class="input input-sm preset-tonal-surface h-7 w-24 text-right text-[10px]" />
|
||||
</div>
|
||||
<div class="flex justify-between items-center gap-4">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<span class="text-[10px] opacity-60"
|
||||
>Image Rotation</span
|
||||
>
|
||||
>Image Rotation</span>
|
||||
<input
|
||||
type="number"
|
||||
min={750}
|
||||
bind:value={$events_loc.launcher.idle_loop_period}
|
||||
class="input input-sm text-[10px] h-7 w-24 text-right preset-tonal-surface"
|
||||
/>
|
||||
class="input input-sm preset-tonal-surface h-7 w-24 text-right text-[10px]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- 2. Read Only Summary (Normal Mode) -->
|
||||
<div
|
||||
class="bg-surface-500/5 p-3 rounded-lg border border-surface-500/10 flex flex-col gap-2"
|
||||
>
|
||||
<div class="flex justify-between items-center text-xs">
|
||||
class="bg-surface-500/5 border-surface-500/10 flex flex-col gap-2 rounded-lg border p-3">
|
||||
<div class="flex items-center justify-between text-xs">
|
||||
<span class="opacity-60">Active Idle Timeout:</span>
|
||||
<span class="font-bold text-primary-500"
|
||||
>{($events_loc.launcher.idle_timer / 60000).toFixed(1)} minutes</span
|
||||
>
|
||||
<span class="text-primary-500 font-bold"
|
||||
>{($events_loc.launcher.idle_timer / 60000).toFixed(1)} minutes</span>
|
||||
</div>
|
||||
<p class="text-[9px] opacity-40 italic">
|
||||
<p class="text-[9px] italic opacity-40">
|
||||
The screen saver automatically rotates digital posters when
|
||||
no activity is detected for the specified time.
|
||||
</p>
|
||||
@@ -79,7 +71,7 @@
|
||||
{/if}
|
||||
|
||||
<div class="text-center">
|
||||
<p class="text-[8px] opacity-40 italic uppercase tracking-tighter">
|
||||
<p class="text-[8px] tracking-tighter uppercase italic opacity-40">
|
||||
Applies to "Poster" session types only
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -54,44 +54,39 @@
|
||||
</script>
|
||||
|
||||
<section
|
||||
class="w-full transition-all duration-300 border rounded-lg overflow-hidden mb-2 {!is_open
|
||||
class="mb-2 w-full overflow-hidden rounded-lg border transition-all duration-300 {!is_open
|
||||
? 'preset-outlined-surface-300-700'
|
||||
: ''} {state === 'auto'
|
||||
? 'preset-outlined-primary-500 shadow-xl'
|
||||
: ''} {state === 'pinned'
|
||||
? 'preset-outlined-warning-500 shadow-xl'
|
||||
: ''}"
|
||||
>
|
||||
: ''}">
|
||||
<!-- Header -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<header
|
||||
class="flex flex-row items-center justify-between p-2 cursor-pointer transition-colors {!is_open
|
||||
class="flex cursor-pointer flex-row items-center justify-between p-2 transition-colors {!is_open
|
||||
? 'bg-surface-500/5'
|
||||
: ''} {state === 'auto' ? 'bg-primary-500/10' : ''} {state ===
|
||||
'pinned'
|
||||
? 'bg-warning-500/10'
|
||||
: ''}"
|
||||
onclick={toggle_expand}
|
||||
>
|
||||
onclick={toggle_expand}>
|
||||
<div class="flex items-center gap-3">
|
||||
<Icon
|
||||
size="1em"
|
||||
class="w-5 text-center opacity-70 {state === 'auto'
|
||||
? 'text-primary-500'
|
||||
: ''} {state === 'pinned' ? 'text-warning-500' : ''}"
|
||||
/>
|
||||
: ''} {state === 'pinned' ? 'text-warning-500' : ''}" />
|
||||
<div class="flex flex-col">
|
||||
<span
|
||||
class="text-sm font-bold tracking-tight uppercase {!is_open
|
||||
? 'opacity-50'
|
||||
: ''}">{title}</span
|
||||
>
|
||||
: ''}">{title}</span>
|
||||
{#if description && !is_open}
|
||||
<span
|
||||
class="text-[9px] opacity-40 italic truncate max-w-[180px]"
|
||||
>{description}</span
|
||||
>
|
||||
class="max-w-[180px] truncate text-[9px] italic opacity-40"
|
||||
>{description}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -106,16 +101,19 @@
|
||||
class:text-warning-500={state === 'pinned'}
|
||||
title={state === 'pinned'
|
||||
? 'Unpin Section'
|
||||
: 'Pin Section (Stay open)'}
|
||||
>
|
||||
: 'Pin Section (Stay open)'}>
|
||||
<Pin size="0.7em" />
|
||||
</button>
|
||||
|
||||
<!-- Collapse Icon -->
|
||||
{#if is_open}
|
||||
<ChevronDown size="1em" class="transition-transform duration-300 opacity-30" />
|
||||
<ChevronDown
|
||||
size="1em"
|
||||
class="opacity-30 transition-transform duration-300" />
|
||||
{:else}
|
||||
<ChevronRight size="1em" class="transition-transform duration-300 opacity-30" />
|
||||
<ChevronRight
|
||||
size="1em"
|
||||
class="opacity-30 transition-transform duration-300" />
|
||||
{/if}
|
||||
</div>
|
||||
</header>
|
||||
@@ -124,27 +122,22 @@
|
||||
{#if is_open}
|
||||
<div
|
||||
transition:slide={{ duration: 300 }}
|
||||
class="p-3 bg-white/5 dark:bg-black/5"
|
||||
>
|
||||
class="bg-white/5 p-3 dark:bg-black/5">
|
||||
{#if $ae_loc.edit_mode}
|
||||
<div class="mb-2 flex justify-between items-center px-1">
|
||||
<div class="mb-2 flex items-center justify-between px-1">
|
||||
<span
|
||||
class="text-[8px] uppercase font-bold tracking-widest text-primary-500/60 flex items-center gap-1"
|
||||
>
|
||||
class="text-primary-500/60 flex items-center gap-1 text-[8px] font-bold tracking-widest uppercase">
|
||||
<Pencil size="0.7em" /> Technical Mode
|
||||
</span>
|
||||
{#if state === 'pinned'}
|
||||
<span
|
||||
class="badge preset-filled-warning text-[8px] uppercase"
|
||||
>Pinned</span
|
||||
>
|
||||
>Pinned</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
class="grid grid-cols-1 gap-3"
|
||||
>
|
||||
<div class="grid grid-cols-1 gap-3">
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -15,21 +15,24 @@
|
||||
bind:state={$events_loc.launcher.section_state__sync_timers}
|
||||
{on_expand}
|
||||
description="Prefix: {$ae_loc.native_device?.hash_prefix_length ||
|
||||
2} | Loops: Active"
|
||||
>
|
||||
2} | Loops: Active">
|
||||
<!-- Content omitted for brevity, preserved in file -->
|
||||
<!-- Pause toggle: always visible — useful during testing or onsite troubleshooting -->
|
||||
<div class="flex items-center justify-between mb-2 p-2 rounded border border-surface-500/10 bg-surface-500/5">
|
||||
<span class="text-[10px] font-bold uppercase tracking-wider opacity-70">
|
||||
{$events_loc.launcher.sync_paused ? '⏸ Sync Paused' : '▶ Sync Active'}
|
||||
<div
|
||||
class="border-surface-500/10 bg-surface-500/5 mb-2 flex items-center justify-between rounded border p-2">
|
||||
<span class="text-[10px] font-bold tracking-wider uppercase opacity-70">
|
||||
{$events_loc.launcher.sync_paused
|
||||
? '⏸ Sync Paused'
|
||||
: '▶ Sync Active'}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => ($events_loc.launcher.sync_paused = !$events_loc.launcher.sync_paused)}
|
||||
onclick={() =>
|
||||
($events_loc.launcher.sync_paused =
|
||||
!$events_loc.launcher.sync_paused)}
|
||||
class="btn btn-xs transition-all"
|
||||
class:preset-tonal-warning={$events_loc.launcher.sync_paused}
|
||||
class:preset-tonal-success={!$events_loc.launcher.sync_paused}
|
||||
>
|
||||
class:preset-tonal-success={!$events_loc.launcher.sync_paused}>
|
||||
{#if $events_loc.launcher.sync_paused}
|
||||
<Play size="0.85em" class="mr-1" /> Resume
|
||||
{:else}
|
||||
@@ -43,92 +46,80 @@
|
||||
<!-- Technical Timers (Edit Mode Only) -->
|
||||
{#if $ae_loc.edit_mode}
|
||||
<div class="flex flex-col gap-2">
|
||||
<p
|
||||
class="text-[9px] font-bold uppercase opacity-50 ml-1"
|
||||
>Polling Periods (ms)</p
|
||||
>
|
||||
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">
|
||||
Polling Periods (ms)
|
||||
</p>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-[8px] opacity-60">Event Data</span
|
||||
>
|
||||
<span class="text-[8px] opacity-60"
|
||||
>Event Data</span>
|
||||
<input
|
||||
type="number"
|
||||
bind:value={
|
||||
$events_loc.launcher.sync_intervals.event
|
||||
}
|
||||
class="input input-sm text-[10px] h-7 preset-tonal-surface"
|
||||
/>
|
||||
class="input input-sm preset-tonal-surface h-7 text-[10px]" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-[8px] opacity-60"
|
||||
>Device Config</span
|
||||
>
|
||||
>Device Config</span>
|
||||
<input
|
||||
type="number"
|
||||
bind:value={
|
||||
$events_loc.launcher.sync_intervals.device
|
||||
}
|
||||
class="input input-sm text-[10px] h-7 preset-tonal-surface"
|
||||
/>
|
||||
class="input input-sm preset-tonal-surface h-7 text-[10px]" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-[8px] opacity-60"
|
||||
>Room/Location</span
|
||||
>
|
||||
>Room/Location</span>
|
||||
<input
|
||||
type="number"
|
||||
bind:value={
|
||||
$events_loc.launcher.sync_intervals.location
|
||||
}
|
||||
class="input input-sm text-[10px] h-7 preset-tonal-surface"
|
||||
/>
|
||||
class="input input-sm preset-tonal-surface h-7 text-[10px]" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-[8px] opacity-60"
|
||||
>Session Loop</span
|
||||
>
|
||||
>Session Loop</span>
|
||||
<input
|
||||
type="number"
|
||||
bind:value={
|
||||
$events_loc.launcher.sync_intervals.session
|
||||
}
|
||||
class="input input-sm text-[10px] h-7 preset-tonal-surface"
|
||||
/>
|
||||
class="input input-sm preset-tonal-surface h-7 text-[10px]" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-[8px] opacity-60"
|
||||
>Presentation Loop</span
|
||||
>
|
||||
>Presentation Loop</span>
|
||||
<input
|
||||
type="number"
|
||||
bind:value={
|
||||
$events_loc.launcher.sync_intervals.presentation
|
||||
$events_loc.launcher.sync_intervals
|
||||
.presentation
|
||||
}
|
||||
class="input input-sm text-[10px] h-7 preset-tonal-surface"
|
||||
/>
|
||||
class="input input-sm preset-tonal-surface h-7 text-[10px]" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-[8px] opacity-60"
|
||||
>Presenter Loop</span
|
||||
>
|
||||
>Presenter Loop</span>
|
||||
<input
|
||||
type="number"
|
||||
bind:value={
|
||||
$events_loc.launcher.sync_intervals.presenter
|
||||
$events_loc.launcher.sync_intervals
|
||||
.presenter
|
||||
}
|
||||
class="input input-sm text-[10px] h-7 preset-tonal-surface"
|
||||
/>
|
||||
class="input input-sm preset-tonal-surface h-7 text-[10px]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-col gap-1 mt-1 border-t border-surface-500/10 pt-2"
|
||||
>
|
||||
<p
|
||||
class="text-[9px] font-bold uppercase opacity-50 ml-1"
|
||||
>Cache Structure</p
|
||||
>
|
||||
class="border-surface-500/10 mt-1 flex flex-col gap-1 border-t pt-2">
|
||||
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">
|
||||
Cache Structure
|
||||
</p>
|
||||
<div class="flex items-center justify-between px-1">
|
||||
<span class="text-[9px]">Hash Prefix Length</span>
|
||||
{#if $ae_loc.native_device}
|
||||
@@ -136,17 +127,17 @@
|
||||
bind:value={
|
||||
$ae_loc.native_device.hash_prefix_length
|
||||
}
|
||||
class="select select-sm h-6 py-0 text-[10px] w-16 preset-tonal-surface"
|
||||
>
|
||||
class="select select-sm preset-tonal-surface h-6 w-16 py-0 text-[10px]">
|
||||
<option value={1}>1 char</option>
|
||||
<option value={2}>2 chars</option>
|
||||
<option value={3}>3 chars</option>
|
||||
</select>
|
||||
{:else}
|
||||
<span class="text-[9px] opacity-50 italic">loading…</span>
|
||||
<span class="text-[9px] italic opacity-50"
|
||||
>loading…</span>
|
||||
{/if}
|
||||
</div>
|
||||
<p class="text-[8px] opacity-40 italic mt-1">
|
||||
<p class="mt-1 text-[8px] italic opacity-40">
|
||||
* Prefix change requires a full app reload to take
|
||||
effect.
|
||||
</p>
|
||||
@@ -154,47 +145,40 @@
|
||||
{:else}
|
||||
<!-- Read Only Summary (Normal Mode) -->
|
||||
<div
|
||||
class="bg-surface-500/5 p-2 rounded border border-surface-500/10 flex flex-col gap-1"
|
||||
>
|
||||
class="bg-surface-500/5 border-surface-500/10 flex flex-col gap-1 rounded border p-2">
|
||||
<div
|
||||
class="flex justify-between text-[9px] opacity-60 font-mono"
|
||||
>
|
||||
class="flex justify-between font-mono text-[9px] opacity-60">
|
||||
<span>Event Sync:</span>
|
||||
<span
|
||||
>{(
|
||||
$events_loc.launcher.sync_intervals.event /
|
||||
1000
|
||||
).toFixed(1)}s</span
|
||||
>
|
||||
$events_loc.launcher.sync_intervals.event / 1000
|
||||
).toFixed(1)}s</span>
|
||||
</div>
|
||||
<div
|
||||
class="flex justify-between text-[9px] opacity-60 font-mono"
|
||||
>
|
||||
class="flex justify-between font-mono text-[9px] opacity-60">
|
||||
<span>Room Monitor:</span>
|
||||
<span
|
||||
>{(
|
||||
$events_loc.launcher.sync_intervals.location / 1000
|
||||
).toFixed(1)}s</span
|
||||
>
|
||||
$events_loc.launcher.sync_intervals.location /
|
||||
1000
|
||||
).toFixed(1)}s</span>
|
||||
</div>
|
||||
<div
|
||||
class="flex justify-between text-[9px] opacity-60 font-mono border-t border-surface-500/10 pt-1"
|
||||
>
|
||||
class="border-surface-500/10 flex justify-between border-t pt-1 font-mono text-[9px] opacity-60">
|
||||
<span>Prefix Sharding:</span>
|
||||
<span
|
||||
>{$ae_loc.native_device?.hash_prefix_length || 2} chars</span
|
||||
>
|
||||
>{$ae_loc.native_device?.hash_prefix_length || 2} chars</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<p class="text-[8px] opacity-40 italic">
|
||||
<p class="text-[8px] italic opacity-40">
|
||||
Enable Edit Mode to adjust polling intervals.
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-center p-4 opacity-50 italic text-xs">
|
||||
<div class="p-4 text-center text-xs italic opacity-50">
|
||||
Device configuration not loaded.
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -7,7 +7,14 @@
|
||||
import { ae_loc, ae_api, ae_sess } from '$lib/stores/ae_stores';
|
||||
import { events_loc, events_sess } from '$lib/stores/ae_events_stores';
|
||||
import Launcher_Cfg_Section from './launcher_cfg_section.svelte';
|
||||
import { AlertTriangle, Boxes, RefreshCw, Settings, Trash2, Zap } from '@lucide/svelte';
|
||||
import {
|
||||
AlertTriangle,
|
||||
Boxes,
|
||||
RefreshCw,
|
||||
Settings,
|
||||
Trash2,
|
||||
Zap
|
||||
} from '@lucide/svelte';
|
||||
interface Props {
|
||||
on_expand?: () => void;
|
||||
}
|
||||
@@ -43,100 +50,88 @@
|
||||
icon={Boxes}
|
||||
bind:state={$events_loc.launcher.section_state__template}
|
||||
{on_expand}
|
||||
description="Kitchen Sink Scaffold | Demo Only"
|
||||
>
|
||||
description="Kitchen Sink Scaffold | Demo Only">
|
||||
<!-- A. TOP STATUS BAR (Optional) -->
|
||||
{#if action_status}
|
||||
<div
|
||||
class="col-span-full text-[10px] text-center italic bg-primary-500/10 py-1 rounded animate-pulse text-primary-500 border border-primary-500/20"
|
||||
>
|
||||
class="bg-primary-500/10 text-primary-500 border-primary-500/20 col-span-full animate-pulse rounded border py-1 text-center text-[10px] italic">
|
||||
{action_status}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- B. COMMON GRID SECTION (Read Only / High Level) -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<p class="text-[9px] font-bold uppercase opacity-50 ml-1"
|
||||
>Standard Actions</p
|
||||
>
|
||||
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">
|
||||
Standard Actions
|
||||
</p>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="grid grid-cols-2 gap-1">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handle_test_action('Primary')}
|
||||
class="btn btn-xs preset-tonal-primary hover:preset-filled-primary-500 justify-start"
|
||||
>
|
||||
class="btn btn-xs preset-tonal-primary hover:preset-filled-primary-500 justify-start">
|
||||
<Zap size="0.85em" class="mr-1 shrink-0" /> Primary
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handle_test_action('Secondary')}
|
||||
class="btn btn-xs preset-tonal-secondary hover:preset-filled-secondary-500 justify-start"
|
||||
>
|
||||
class="btn btn-xs preset-tonal-secondary hover:preset-filled-secondary-500 justify-start">
|
||||
<Settings size="0.85em" class="mr-1 shrink-0" /> Secondary
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Toggles & Checkboxes -->
|
||||
<div class="flex flex-col gap-1 mt-1 bg-surface-500/5 p-2 rounded">
|
||||
<label class="flex items-center gap-2 cursor-pointer group">
|
||||
<div class="bg-surface-500/5 mt-1 flex flex-col gap-1 rounded p-2">
|
||||
<label class="group flex cursor-pointer items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={toggle_val}
|
||||
class="checkbox checkbox-sm"
|
||||
/>
|
||||
class="checkbox checkbox-sm" />
|
||||
<span
|
||||
class="text-xs group-hover:text-primary-500 transition-colors"
|
||||
>Toggle Feature Alpha</span
|
||||
>
|
||||
class="group-hover:text-primary-500 text-xs transition-colors"
|
||||
>Toggle Feature Alpha</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 cursor-pointer group">
|
||||
<label class="group flex cursor-pointer items-center gap-2">
|
||||
<input
|
||||
type="radio"
|
||||
name="demo"
|
||||
value="a"
|
||||
class="radio radio-sm"
|
||||
/>
|
||||
class="radio radio-sm" />
|
||||
<span
|
||||
class="text-xs group-hover:text-primary-500 transition-colors"
|
||||
>Mode A</span
|
||||
>
|
||||
class="group-hover:text-primary-500 text-xs transition-colors"
|
||||
>Mode A</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- C. STATUS & GAUGES SECTION -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<p class="text-[9px] font-bold uppercase opacity-50 ml-1"
|
||||
>Current Status</p
|
||||
>
|
||||
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">
|
||||
Current Status
|
||||
</p>
|
||||
|
||||
<div
|
||||
class="flex flex-col gap-2 p-2 border border-surface-500/10 rounded-lg"
|
||||
>
|
||||
<div class="flex justify-between items-center">
|
||||
class="border-surface-500/10 flex flex-col gap-2 rounded-lg border p-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-[10px] font-medium">Engine Health</span>
|
||||
<span class="badge preset-filled-success text-[8px] uppercase"
|
||||
>Stable</span
|
||||
>
|
||||
>Stable</span>
|
||||
</div>
|
||||
|
||||
<!-- Progress / Gauge Example -->
|
||||
<div class="flex flex-col gap-1">
|
||||
<div
|
||||
class="flex justify-between text-[8px] uppercase opacity-60"
|
||||
>
|
||||
class="flex justify-between text-[8px] uppercase opacity-60">
|
||||
<span>Processing Load</span>
|
||||
<span>45%</span>
|
||||
</div>
|
||||
<div
|
||||
class="w-full h-1.5 bg-surface-500/20 rounded-full overflow-hidden"
|
||||
>
|
||||
class="bg-surface-500/20 h-1.5 w-full overflow-hidden rounded-full">
|
||||
<div
|
||||
class="h-full bg-success-500 transition-all duration-1000"
|
||||
style="width: 45%"
|
||||
></div>
|
||||
class="bg-success-500 h-full transition-all duration-1000"
|
||||
style="width: 45%">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -144,9 +139,10 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handle_test_action('Refresh')}
|
||||
class="btn btn-xs preset-outlined-surface-500 w-full text-[10px]"
|
||||
>
|
||||
<RefreshCw size="0.85em" class="mr-1 {is_loading ? 'animate-spin' : ''}" />
|
||||
class="btn btn-xs preset-outlined-surface-500 w-full text-[10px]">
|
||||
<RefreshCw
|
||||
size="0.85em"
|
||||
class="mr-1 {is_loading ? 'animate-spin' : ''}" />
|
||||
Refresh State
|
||||
</button>
|
||||
</div>
|
||||
@@ -154,32 +150,28 @@
|
||||
<!-- D. TECHNICAL SECTION (Edit Mode Only) -->
|
||||
{#if $ae_loc.edit_mode}
|
||||
<div
|
||||
class="col-span-full border-t border-surface-500/20 pt-3 mt-1 flex flex-col gap-3"
|
||||
>
|
||||
class="border-surface-500/20 col-span-full mt-1 flex flex-col gap-3 border-t pt-3">
|
||||
<!-- Dangerous Actions -->
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div class="flex flex-col gap-1">
|
||||
<span
|
||||
class="text-[9px] font-bold uppercase opacity-50 text-warning-500"
|
||||
>System Config</span
|
||||
>
|
||||
class="text-warning-500 text-[9px] font-bold uppercase opacity-50"
|
||||
>System Config</span>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (show_confirm = true)}
|
||||
class="btn btn-xs preset-tonal-warning hover:preset-filled-warning-500 justify-start"
|
||||
>
|
||||
<AlertTriangle size="0.85em" class="mr-1 shrink-0" /> Reset All
|
||||
class="btn btn-xs preset-tonal-warning hover:preset-filled-warning-500 justify-start">
|
||||
<AlertTriangle size="0.85em" class="mr-1 shrink-0" /> Reset
|
||||
All
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span
|
||||
class="text-[9px] font-bold uppercase opacity-50 text-error-500"
|
||||
>Danger Zone</span
|
||||
>
|
||||
class="text-error-500 text-[9px] font-bold uppercase opacity-50"
|
||||
>Danger Zone</span>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-xs preset-tonal-error hover:preset-filled-error-500 justify-start"
|
||||
>
|
||||
class="btn btn-xs preset-tonal-error hover:preset-filled-error-500 justify-start">
|
||||
<Trash2 size="0.85em" class="mr-1 shrink-0" /> Wipe Cache
|
||||
</button>
|
||||
</div>
|
||||
@@ -188,21 +180,17 @@
|
||||
<!-- Form Inputs -->
|
||||
<div class="grid grid-cols-1 gap-2">
|
||||
<div class="flex flex-col gap-1">
|
||||
<span
|
||||
class="text-[9px] font-bold uppercase opacity-50 ml-1"
|
||||
>Raw Settings</span
|
||||
>
|
||||
<span class="ml-1 text-[9px] font-bold uppercase opacity-50"
|
||||
>Raw Settings</span>
|
||||
<div class="flex gap-1">
|
||||
<input
|
||||
type="text"
|
||||
bind:value={text_input}
|
||||
placeholder="Enter string parameter..."
|
||||
class="input input-sm grow text-[10px] preset-tonal-surface h-7"
|
||||
/>
|
||||
class="input input-sm preset-tonal-surface h-7 grow text-[10px]" />
|
||||
<select
|
||||
bind:value={select_val}
|
||||
class="select select-sm h-7 py-0 text-[10px] w-24 preset-tonal-surface"
|
||||
>
|
||||
class="select select-sm preset-tonal-surface h-7 w-24 py-0 text-[10px]">
|
||||
<option value="option1">Global</option>
|
||||
<option value="option2">Local</option>
|
||||
</select>
|
||||
@@ -210,24 +198,22 @@
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-[8px] opacity-60 ml-1"
|
||||
>Threshold (ms)</span
|
||||
>
|
||||
<span class="ml-1 text-[8px] opacity-60"
|
||||
>Threshold (ms)</span>
|
||||
<input
|
||||
type="number"
|
||||
bind:value={number_input}
|
||||
class="input input-sm text-[10px] h-7 preset-tonal-surface"
|
||||
/>
|
||||
class="input input-sm preset-tonal-surface h-7 text-[10px]" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Terminal / Output Log -->
|
||||
<div class="flex flex-col gap-1">
|
||||
<p class="text-[9px] font-bold uppercase opacity-50 ml-1"
|
||||
>Debug Output</p
|
||||
>
|
||||
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">
|
||||
Debug Output
|
||||
</p>
|
||||
<pre
|
||||
class="text-[8px] bg-black text-green-500 p-2 overflow-x-auto rounded border border-surface-500/50 max-h-24 shadow-inner">
|
||||
class="border-surface-500/50 max-h-24 overflow-x-auto rounded border bg-black p-2 text-[8px] text-green-500 shadow-inner">
|
||||
[LOG] System Initialized
|
||||
[INFO] Store synced with IndexedDB
|
||||
[DEBUG] active_tab: template
|
||||
@@ -241,13 +227,11 @@
|
||||
<!-- Confirmation Modal Demo -->
|
||||
{#if show_confirm}
|
||||
<div
|
||||
class="fixed inset-0 z-[1000] flex items-center justify-center bg-black/50 backdrop-blur-sm p-4"
|
||||
>
|
||||
class="fixed inset-0 z-[1000] flex items-center justify-center bg-black/50 p-4 backdrop-blur-sm">
|
||||
<div
|
||||
class="card p-6 w-full max-w-sm preset-filled-surface-100-900 border border-warning-500 shadow-2xl animate-in zoom-in-95 duration-200"
|
||||
>
|
||||
<h4 class="h4 text-warning-500 font-bold mb-2">Confirm Action</h4>
|
||||
<p class="text-sm opacity-80 mb-6">
|
||||
class="card preset-filled-surface-100-900 border-warning-500 animate-in zoom-in-95 w-full max-w-sm border p-6 shadow-2xl duration-200">
|
||||
<h4 class="h4 text-warning-500 mb-2 font-bold">Confirm Action</h4>
|
||||
<p class="mb-6 text-sm opacity-80">
|
||||
Are you sure you want to perform this test operation? This
|
||||
demonstrate the standard confirmation pattern.
|
||||
</p>
|
||||
@@ -255,16 +239,14 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (show_confirm = false)}
|
||||
class="btn btn-sm preset-tonal-surface">Cancel</button
|
||||
>
|
||||
class="btn btn-sm preset-tonal-surface">Cancel</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
show_confirm = false;
|
||||
handle_test_action('Confirm');
|
||||
}}
|
||||
class="btn btn-sm preset-filled-warning"
|
||||
>
|
||||
class="btn btn-sm preset-filled-warning">
|
||||
Yes, Proceed
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -13,9 +13,7 @@
|
||||
let update_path = $state(
|
||||
'~/OSIT/Speaker Ready System/Admin Share/Custom Applications/osit_binaries/'
|
||||
);
|
||||
let update_url = $state(
|
||||
'https://dev-demo.oneskyit.com/updates/ae_native.zip'
|
||||
);
|
||||
let update_url = $state('https://dev-demo.oneskyit.com/updates/ae_native.zip');
|
||||
|
||||
let update_status = $state('');
|
||||
let is_checking = $state(false);
|
||||
@@ -59,35 +57,31 @@
|
||||
icon={CloudDownload}
|
||||
bind:state={$events_loc.launcher.section_state__updates}
|
||||
{on_expand}
|
||||
description="v1.0.0 | Source: {update_source}"
|
||||
>
|
||||
description="v1.0.0 | Source: {update_source}">
|
||||
<!-- Content omitted for brevity, preserved in file -->
|
||||
<div class="col-span-full flex flex-col gap-2">
|
||||
<!-- TECHNICAL: Source Config (Edit Mode Only) -->
|
||||
{#if $ae_loc.edit_mode}
|
||||
<div
|
||||
class="flex flex-col gap-2 bg-surface-500/5 p-2 rounded border border-surface-500/10 mb-1"
|
||||
>
|
||||
<div class="flex flex-row justify-between items-center px-1">
|
||||
<p class="text-[9px] font-bold uppercase opacity-50"
|
||||
>Source Type</p
|
||||
>
|
||||
class="bg-surface-500/5 border-surface-500/10 mb-1 flex flex-col gap-2 rounded border p-2">
|
||||
<div class="flex flex-row items-center justify-between px-1">
|
||||
<p class="text-[9px] font-bold uppercase opacity-50">
|
||||
Source Type
|
||||
</p>
|
||||
<div class="flex gap-2">
|
||||
<label class="flex items-center gap-1 text-[10px]">
|
||||
<input
|
||||
type="radio"
|
||||
bind:group={update_source}
|
||||
value="file"
|
||||
class="radio radio-sm"
|
||||
/> Local
|
||||
class="radio radio-sm" /> Local
|
||||
</label>
|
||||
<label class="flex items-center gap-1 text-[10px]">
|
||||
<input
|
||||
type="radio"
|
||||
bind:group={update_source}
|
||||
value="url"
|
||||
class="radio radio-sm"
|
||||
/> Web
|
||||
class="radio radio-sm" /> Web
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -97,15 +91,13 @@
|
||||
type="text"
|
||||
bind:value={update_path}
|
||||
placeholder="Path to update package"
|
||||
class="input input-sm text-[10px] preset-tonal-surface h-7 w-full"
|
||||
/>
|
||||
class="input input-sm preset-tonal-surface h-7 w-full text-[10px]" />
|
||||
{:else}
|
||||
<input
|
||||
type="text"
|
||||
bind:value={update_url}
|
||||
placeholder="URL to update package"
|
||||
class="input input-sm text-[10px] preset-tonal-surface h-7 w-full"
|
||||
/>
|
||||
class="input input-sm preset-tonal-surface h-7 w-full text-[10px]" />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -115,10 +107,9 @@
|
||||
type="button"
|
||||
onclick={handle_check_update}
|
||||
disabled={is_checking}
|
||||
class="btn btn-sm preset-filled-tertiary hover:preset-filled-primary-500 text-[10px] w-full"
|
||||
>
|
||||
class="btn btn-sm preset-filled-tertiary hover:preset-filled-primary-500 w-full text-[10px]">
|
||||
{#if is_checking}
|
||||
<LoaderCircle size="0.85em" class="animate-spin mr-1" /> Checking...
|
||||
<LoaderCircle size="0.85em" class="mr-1 animate-spin" /> Checking...
|
||||
{:else}
|
||||
<Search size="0.85em" class="mr-1" /> Check for Updates
|
||||
{/if}
|
||||
@@ -126,8 +117,7 @@
|
||||
|
||||
{#if update_status}
|
||||
<div
|
||||
class="text-[9px] text-center italic p-1 border border-surface-500/20 rounded bg-surface-500/5 mt-1"
|
||||
>
|
||||
class="border-surface-500/20 bg-surface-500/5 mt-1 rounded border p-1 text-center text-[9px] italic">
|
||||
{update_status}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -136,8 +126,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={handle_install}
|
||||
class="btn btn-sm preset-filled-success hover:preset-filled-primary-500 text-[10px] w-full animate-bounce mt-2 shadow-lg"
|
||||
>
|
||||
class="btn btn-sm preset-filled-success hover:preset-filled-primary-500 mt-2 w-full animate-bounce text-[10px] shadow-lg">
|
||||
<Wand2 size="0.85em" class="mr-1" /> Install & Relaunch
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
@@ -132,7 +132,9 @@
|
||||
const param_launcher_footer = data.url.searchParams.get('launcher_footer');
|
||||
|
||||
if (log_lvl > 1) {
|
||||
console.log(`[Launcher Sync] URL Change: event=${path_event_id}, loc=${path_location_id}, sess=${url_session_id}`);
|
||||
console.log(
|
||||
`[Launcher Sync] URL Change: event=${path_event_id}, loc=${path_location_id}, sess=${url_session_id}`
|
||||
);
|
||||
}
|
||||
|
||||
untrack(() => {
|
||||
@@ -144,7 +146,10 @@
|
||||
}
|
||||
// CRITICAL: Ensure session_id is synced to store so LiveQueries react
|
||||
if ($events_slct.event_session_id !== url_session_id) {
|
||||
if (log_lvl) console.log(`[Launcher Sync] Updating store session_id: ${url_session_id}`);
|
||||
if (log_lvl)
|
||||
console.log(
|
||||
`[Launcher Sync] Updating store session_id: ${url_session_id}`
|
||||
);
|
||||
$events_slct.event_session_id = url_session_id;
|
||||
}
|
||||
|
||||
@@ -152,14 +157,20 @@
|
||||
if (param_iframe === 'true') $ae_loc.iframe = true;
|
||||
else if (param_iframe === 'false') $ae_loc.iframe = false;
|
||||
|
||||
if (param_launcher_menu === 'hide') $events_loc.launcher.hide__launcher_menu = true;
|
||||
else if (param_launcher_menu === 'show') $events_loc.launcher.hide__launcher_menu = false;
|
||||
if (param_launcher_menu === 'hide')
|
||||
$events_loc.launcher.hide__launcher_menu = true;
|
||||
else if (param_launcher_menu === 'show')
|
||||
$events_loc.launcher.hide__launcher_menu = false;
|
||||
|
||||
if (param_launcher_header === 'hide') $events_loc.launcher.hide__launcher_header = true;
|
||||
else if (param_launcher_header === 'show') $events_loc.launcher.hide__launcher_header = false;
|
||||
if (param_launcher_header === 'hide')
|
||||
$events_loc.launcher.hide__launcher_header = true;
|
||||
else if (param_launcher_header === 'show')
|
||||
$events_loc.launcher.hide__launcher_header = false;
|
||||
|
||||
if (param_launcher_footer === 'hide') $events_loc.launcher.hide__launcher_footer = true;
|
||||
else if (param_launcher_footer === 'show') $events_loc.launcher.hide__launcher_footer = false;
|
||||
if (param_launcher_footer === 'hide')
|
||||
$events_loc.launcher.hide__launcher_footer = true;
|
||||
else if (param_launcher_footer === 'show')
|
||||
$events_loc.launcher.hide__launcher_footer = false;
|
||||
});
|
||||
|
||||
// Strip launcher display params from the URL after applying them — same pattern
|
||||
@@ -196,7 +207,9 @@
|
||||
$effect(() => {
|
||||
if (ae_acct) {
|
||||
untrack(() => {
|
||||
const new_location_obj_li = ae_acct.slct.event_location_obj_li ?? [''];
|
||||
const new_location_obj_li = ae_acct.slct.event_location_obj_li ?? [
|
||||
''
|
||||
];
|
||||
// Compare by extracting IDs only — object identity (===) won't work for
|
||||
// plain JS objects from the store. Joining IDs is cheap and avoids a full
|
||||
// JSON.stringify of potentially large location objects on every navigation.
|
||||
@@ -210,9 +223,13 @@
|
||||
$events_slct.event_location_obj_li = new_location_obj_li;
|
||||
}
|
||||
|
||||
const new_id_li__event_location = ae_acct.slct.id_li__event_location ?? [''];
|
||||
const new_id_li__event_location = ae_acct.slct
|
||||
.id_li__event_location ?? [''];
|
||||
// ID list contains plain strings — join-compare is O(n) and avoids JSON.stringify.
|
||||
if (($events_slct.id_li__event_location ?? []).join(',') !== new_id_li__event_location.join(',')) {
|
||||
if (
|
||||
($events_slct.id_li__event_location ?? []).join(',') !==
|
||||
new_id_li__event_location.join(',')
|
||||
) {
|
||||
$events_slct.id_li__event_location = new_id_li__event_location;
|
||||
}
|
||||
});
|
||||
@@ -240,10 +257,7 @@
|
||||
let lq__event_event_file_obj_li = liveQuery(async () => {
|
||||
const id = $events_slct.event_id;
|
||||
if (!id) return [];
|
||||
return await db_events.file
|
||||
.where('for_id')
|
||||
.equals(id)
|
||||
.sortBy('filename');
|
||||
return await db_events.file.where('for_id').equals(id).sortBy('filename');
|
||||
});
|
||||
|
||||
// Event File - For Location
|
||||
@@ -274,10 +288,7 @@
|
||||
let lq__event_location_obj_li = liveQuery(async () => {
|
||||
const id = $events_slct.event_id;
|
||||
if (!id) return [];
|
||||
return await db_events.location
|
||||
.where('event_id')
|
||||
.equals(id)
|
||||
.sortBy('name');
|
||||
return await db_events.location.where('event_id').equals(id).sortBy('name');
|
||||
});
|
||||
|
||||
// $derived.by: must recreate when event_location_id changes (see comment above).
|
||||
@@ -311,8 +322,10 @@
|
||||
const result = $lq__event_obj;
|
||||
if (result) {
|
||||
untrack(() => {
|
||||
if (result.updated_on !== $events_slct.event_obj?.updated_on ||
|
||||
result.id !== $events_slct.event_obj?.id) {
|
||||
if (
|
||||
result.updated_on !== $events_slct.event_obj?.updated_on ||
|
||||
result.id !== $events_slct.event_obj?.id
|
||||
) {
|
||||
$events_slct.event_obj = { ...result };
|
||||
}
|
||||
});
|
||||
@@ -323,8 +336,11 @@
|
||||
const result = $lq__event_device_obj;
|
||||
if (result) {
|
||||
untrack(() => {
|
||||
if (result.updated_on !== $events_slct.event_device_obj?.updated_on ||
|
||||
result.id !== $events_slct.event_device_obj?.id) {
|
||||
if (
|
||||
result.updated_on !==
|
||||
$events_slct.event_device_obj?.updated_on ||
|
||||
result.id !== $events_slct.event_device_obj?.id
|
||||
) {
|
||||
$events_slct.event_device_obj = { ...result };
|
||||
}
|
||||
});
|
||||
@@ -337,8 +353,12 @@
|
||||
untrack(() => {
|
||||
const current = $events_slct.event_session_obj_li ?? [];
|
||||
// Compare by joining IDs — O(n) string compare vs O(n*m) JSON.stringify.
|
||||
const new_ids = (results as any[]).map((r: any) => r.id ?? r.event_session_id).join(',');
|
||||
const cur_ids = current.map((r: any) => r.id ?? r.event_session_id).join(',');
|
||||
const new_ids = (results as any[])
|
||||
.map((r: any) => r.id ?? r.event_session_id)
|
||||
.join(',');
|
||||
const cur_ids = current
|
||||
.map((r: any) => r.id ?? r.event_session_id)
|
||||
.join(',');
|
||||
if (new_ids !== cur_ids) {
|
||||
$events_slct.event_session_obj_li = [...(results as any[])];
|
||||
}
|
||||
@@ -522,16 +542,13 @@
|
||||
const keys = Object.keys(
|
||||
$events_loc.launcher.screen_saver_img_kv
|
||||
);
|
||||
const rand_index = Math.floor(
|
||||
Math.random() * keys.length
|
||||
);
|
||||
const rand_index = Math.floor(Math.random() * keys.length);
|
||||
let event_file_obj =
|
||||
$events_loc.launcher.screen_saver_img_kv[
|
||||
keys[rand_index]
|
||||
];
|
||||
|
||||
$events_slct.event_file_id =
|
||||
event_file_obj.event_file_id;
|
||||
$events_slct.event_file_id = event_file_obj.event_file_id;
|
||||
$events_slct.event_file_obj = event_file_obj;
|
||||
$events_sess.launcher.modal__open_event_file_id = null;
|
||||
$events_sess.launcher.modal__title =
|
||||
@@ -587,81 +604,74 @@
|
||||
class="
|
||||
static
|
||||
m-auto
|
||||
border-x border-gray-200 dark:border-gray-600
|
||||
mb-16 sm:mb-12
|
||||
h-full
|
||||
w-full max-w-7xl
|
||||
transition-all
|
||||
"
|
||||
>
|
||||
mb-16 h-full w-full
|
||||
max-w-7xl border-x
|
||||
border-gray-200
|
||||
transition-all sm:mb-12
|
||||
dark:border-gray-600
|
||||
">
|
||||
<header
|
||||
id="Main-Header"
|
||||
class:hidden={$events_loc.launcher.hide__launcher_header}
|
||||
class="
|
||||
z-20
|
||||
absolute top-0 left-0 right-0
|
||||
w-full max-w-7xl
|
||||
absolute
|
||||
top-0 right-0 left-0 z-20
|
||||
m-auto flex
|
||||
h-12
|
||||
p-1 px-12 m-auto
|
||||
w-full max-w-7xl flex-row
|
||||
|
||||
flex flex-row items-center justify-around sm:justify-between
|
||||
items-center justify-around bg-slate-200 p-1 px-12
|
||||
|
||||
text-sm
|
||||
|
||||
bg-slate-200 dark:bg-slate-800
|
||||
opacity-95 transition-colors
|
||||
|
||||
opacity-95 hover:opacity-100
|
||||
transition-colors duration-300
|
||||
"
|
||||
>
|
||||
<h3 class="h4 text-center italic text-surface-600-400">
|
||||
duration-300 hover:opacity-100
|
||||
sm:justify-between dark:bg-slate-800
|
||||
">
|
||||
<h3 class="h4 text-surface-600-400 text-center italic">
|
||||
<!-- Menu toggle: needs a real tap target for tablet/touch operators -->
|
||||
<button
|
||||
type="button"
|
||||
class="px-2 py-1 rounded hover:bg-surface-500/10 transition-colors"
|
||||
class="hover:bg-surface-500/10 rounded px-2 py-1 transition-colors"
|
||||
onclick={() => {
|
||||
$events_loc.launcher.hide__launcher_menu =
|
||||
!$events_loc.launcher.hide__launcher_menu;
|
||||
}}
|
||||
title="Toggle Launcher menu"
|
||||
>
|
||||
<Satellite class="text-base mx-1 inline-block text-gray-500" />
|
||||
title="Toggle Launcher menu">
|
||||
<Satellite class="mx-1 inline-block text-base text-gray-500" />
|
||||
<abbr title="Aether - Events Module Launcher">
|
||||
Æ Launcher
|
||||
<span
|
||||
class="text-xs align-super font-normal"
|
||||
title="Version 3">v3</span
|
||||
>
|
||||
class="align-super text-xs font-normal"
|
||||
title="Version 3">v3</span>
|
||||
</abbr>
|
||||
</button>
|
||||
</h3>
|
||||
|
||||
{#if $lq__event_obj}
|
||||
<h2
|
||||
class="hidden md:inline-block h3 text-center text-surface-600-400"
|
||||
>
|
||||
class="h3 text-surface-600-400 hidden text-center md:inline-block">
|
||||
{$lq__event_obj.cfg_json?.short_name}
|
||||
</h2>
|
||||
<h3
|
||||
class="h4 text-center italic text-surface-600-400"
|
||||
title="Location ID: {$lq__event_location_obj?.event_location_id} Name: {$lq__event_location_obj?.name}"
|
||||
>
|
||||
class="h4 text-surface-600-400 text-center italic"
|
||||
title="Location ID: {$lq__event_location_obj?.event_location_id} Name: {$lq__event_location_obj?.name}">
|
||||
<button
|
||||
type="button"
|
||||
class="text-base"
|
||||
onclick={() => {
|
||||
$ae_loc.edit_mode = !$ae_loc.edit_mode;
|
||||
}}
|
||||
title="Toggle Edit Mode to show location options and more"
|
||||
>
|
||||
title="Toggle Edit Mode to show location options and more">
|
||||
<MapPin size="1em" />
|
||||
<span class="sr-only">Location:</span>
|
||||
</button>
|
||||
{$lq__event_location_obj?.name}
|
||||
</h3>
|
||||
{:else}
|
||||
<div class="flex flex-row gap-1 items-center justify-center">
|
||||
<LoaderCircle size="1em" class="animate-spin mx-1" />
|
||||
<div class="flex flex-row items-center justify-center gap-1">
|
||||
<LoaderCircle size="1em" class="mx-1 animate-spin" />
|
||||
<span>Loading event...</span>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -669,30 +679,28 @@
|
||||
|
||||
<div
|
||||
class="
|
||||
h-full min-w-full w-full max-w-full
|
||||
flex flex-col sm:flex-row flex-wrap sm:flex-nowrap gap-0
|
||||
items-center
|
||||
justify-start sm:justify-center
|
||||
py-1 px-0.5
|
||||
bg-gray-100 dark:bg-gray-900
|
||||
flex h-full w-full max-w-full
|
||||
min-w-full flex-col flex-wrap items-center justify-start gap-0
|
||||
bg-gray-100
|
||||
px-0.5 py-1
|
||||
sm:flex-row sm:flex-nowrap
|
||||
sm:justify-center dark:bg-gray-900
|
||||
|
||||
"
|
||||
>
|
||||
">
|
||||
<section
|
||||
id="Main-Nav-Menu"
|
||||
class="event_launcher_menu
|
||||
flex
|
||||
h-full
|
||||
basis-1/5
|
||||
min-w-56 md:min-w-64 lg:min-w-72
|
||||
max-w-xs
|
||||
pt-0.5 pr-0.5
|
||||
flex flex-col gap-1 items-center justify-start
|
||||
overflow-y-auto
|
||||
max-w-xs min-w-56 basis-1/5
|
||||
flex-col
|
||||
items-center justify-start
|
||||
gap-1 overflow-y-auto border-r border-gray-200 pt-0.5
|
||||
pr-0.5
|
||||
|
||||
border-r border-gray-200 dark:border-gray-700
|
||||
md:min-w-64 lg:min-w-72 dark:border-gray-700
|
||||
"
|
||||
class:hidden={$events_loc.launcher.hide__launcher_menu}
|
||||
>
|
||||
class:hidden={$events_loc.launcher.hide__launcher_menu}>
|
||||
<Launcher_menu
|
||||
{lq__event_obj}
|
||||
{lq__event_event_file_obj_li}
|
||||
@@ -717,28 +725,25 @@
|
||||
}
|
||||
bind:trigger_reload__event_location_obj_li={
|
||||
$events_sess.launcher.trigger_reload__event_location_obj_li
|
||||
}
|
||||
></Launcher_menu>
|
||||
}></Launcher_menu>
|
||||
</section>
|
||||
|
||||
<section
|
||||
id="Main-Content"
|
||||
class="event_launcher_main
|
||||
flex
|
||||
h-full
|
||||
min-w-xs
|
||||
max-w-full
|
||||
py-1 px-0.5
|
||||
basis-4/5
|
||||
flex flex-col gap-1
|
||||
items-center
|
||||
justify-center
|
||||
min-w-xs basis-4/5
|
||||
flex-col
|
||||
items-center justify-center gap-1
|
||||
overflow-y-auto
|
||||
"
|
||||
>
|
||||
px-0.5
|
||||
py-1
|
||||
">
|
||||
{#if !$events_slct.event_location_id}
|
||||
<div
|
||||
class="flex flex-row items-center justify-center p-8 opacity-50"
|
||||
>
|
||||
class="flex flex-row items-center justify-center p-8 opacity-50">
|
||||
<MapPin size="1.5em" class="mx-2" />
|
||||
<span>Please select a location from the menu</span>
|
||||
</div>
|
||||
@@ -751,9 +756,8 @@
|
||||
{:else if $events_slct.event_location_id}
|
||||
<!-- Location selected but no session chosen yet — prompt operator -->
|
||||
<div
|
||||
class="flex flex-col items-center justify-center p-8 opacity-50"
|
||||
>
|
||||
<LoaderCircle class="animate-spin mb-2" />
|
||||
class="flex flex-col items-center justify-center p-8 opacity-50">
|
||||
<LoaderCircle class="mb-2 animate-spin" />
|
||||
<span>Select a session from the menu</span>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -767,35 +771,32 @@
|
||||
id="Main-Footer"
|
||||
class:hidden={$events_loc.launcher.hide__launcher_footer}
|
||||
class="
|
||||
z-20
|
||||
absolute bottom-0 left-0 right-0
|
||||
absolute
|
||||
right-0 bottom-0 left-0 z-20
|
||||
m-auto flex
|
||||
w-full max-w-7xl
|
||||
p-1 m-auto
|
||||
|
||||
flex flex-row items-center justify-between
|
||||
flex-row items-center justify-between border-t
|
||||
|
||||
text-xs
|
||||
border-gray-300
|
||||
|
||||
bg-gray-200 border-t border-gray-300
|
||||
dark:bg-gray-800 dark:border-gray-600
|
||||
bg-gray-200 p-1 text-xs
|
||||
opacity-70 transition-opacity
|
||||
|
||||
opacity-70 hover:opacity-100
|
||||
transition-opacity duration-500
|
||||
"
|
||||
>
|
||||
duration-500 hover:opacity-100
|
||||
dark:border-gray-600 dark:bg-gray-800
|
||||
">
|
||||
<div
|
||||
class="slct_location_name transition-colors duration-300"
|
||||
title="Location ID: {$lq__event_location_obj?.event_location_id} Name: {$lq__event_location_obj?.name} | Device ID: {$lq__event_device_obj?.event_device_id} Name: {$lq__event_device_obj?.name}"
|
||||
>
|
||||
title="Location ID: {$lq__event_location_obj?.event_location_id} Name: {$lq__event_location_obj?.name} | Device ID: {$lq__event_device_obj?.event_device_id} Name: {$lq__event_device_obj?.name}">
|
||||
<!-- Edit mode toggle: needs tap target for tablet operators -->
|
||||
<button
|
||||
type="button"
|
||||
class="px-1.5 py-1 rounded hover:bg-surface-500/10 transition-colors"
|
||||
class="hover:bg-surface-500/10 rounded px-1.5 py-1 transition-colors"
|
||||
onclick={() => {
|
||||
$ae_loc.edit_mode = !$ae_loc.edit_mode;
|
||||
}}
|
||||
title="Toggle Edit Mode to show location options and more"
|
||||
>
|
||||
title="Toggle Edit Mode to show location options and more">
|
||||
<span class="sr-only">Location:</span>
|
||||
<MapPin size="1em" />
|
||||
</button>
|
||||
@@ -809,9 +810,8 @@
|
||||
<span
|
||||
class:preset-tonal-warning={!$idle}
|
||||
class:preset-tonal-success={$idle}
|
||||
class="group px-2 py-0.5 rounded-md transition-colors duration-300"
|
||||
title="The user is currently {$idle ? 'idle' : 'active'}"
|
||||
>
|
||||
class="group rounded-md px-2 py-0.5 transition-colors duration-300"
|
||||
title="The user is currently {$idle ? 'idle' : 'active'}">
|
||||
{#if $idle}
|
||||
<BedDouble size="1em" class="mx-1" />
|
||||
<span class="hidden group-hover:inline"> Idle </span>
|
||||
@@ -822,9 +822,8 @@
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="group px-2 py-0.5 rounded-md transition-colors duration-300"
|
||||
title="Online status = {online?.current}"
|
||||
>
|
||||
class="group rounded-md px-2 py-0.5 transition-colors duration-300"
|
||||
title="Online status = {online?.current}">
|
||||
<Wifi size="1em" class="mx-1" />
|
||||
{online?.current ? '' : 'Offline!'}
|
||||
</span>
|
||||
@@ -835,12 +834,11 @@
|
||||
'connected'}
|
||||
class:preset-tonal-success={$events_sess.launcher.ws_connect_status ==
|
||||
'connected'}
|
||||
class="group px-2 py-0.5 rounded-md transition-colors duration-300"
|
||||
class="group rounded-md px-2 py-0.5 transition-colors duration-300"
|
||||
title="WebSocket is {$events_sess.launcher.ws_connect_status ==
|
||||
'connected'
|
||||
? 'connected'
|
||||
: 'disconnected'} API: {$ae_api?.base_url}"
|
||||
>
|
||||
: 'disconnected'} API: {$ae_api?.base_url}">
|
||||
{#if $events_sess.launcher.ws_connect_status == 'connected'}
|
||||
<Network size="1em" class="mx-1 text-green-700" />
|
||||
<span class="hidden group-hover:inline"> WS Connected </span>
|
||||
@@ -860,9 +858,9 @@
|
||||
else if (mode === 'larger') $ae_loc.font_size_mode = 'smaller';
|
||||
else $ae_loc.font_size_mode = 'default';
|
||||
}}
|
||||
class="group px-2 py-0.5 rounded-md font-mono font-bold hover:bg-surface-500/10 transition-colors duration-200"
|
||||
title="Font size: {$ae_loc.font_size_mode ?? 'default'} — tap to cycle (default → larger → smaller)"
|
||||
>
|
||||
class="group hover:bg-surface-500/10 rounded-md px-2 py-0.5 font-mono font-bold transition-colors duration-200"
|
||||
title="Font size: {$ae_loc.font_size_mode ??
|
||||
'default'} — tap to cycle (default → larger → smaller)">
|
||||
{#if $ae_loc.font_size_mode === 'larger'}
|
||||
<span>A+</span>
|
||||
{:else if $ae_loc.font_size_mode === 'smaller'}
|
||||
@@ -873,8 +871,7 @@
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="current_datetime font-mono px-2 hover:font-bold hover:bg-white dark:hover:bg-slate-700 transition-colors"
|
||||
>
|
||||
class="current_datetime px-2 font-mono transition-colors hover:bg-white hover:font-bold dark:hover:bg-slate-700">
|
||||
<span class="hidden md:inline">
|
||||
<CalendarDays size="1em" />
|
||||
{ae_util.iso_datetime_formatter($time, 'date_full_no_year')}
|
||||
@@ -894,10 +891,9 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => ($events_loc.launcher.hide_drawer__cfg = false)}
|
||||
class="btn btn-sm p-3 preset-tonal-error hover:preset-filled-error-500 transition-colors duration-300"
|
||||
class="btn btn-sm preset-tonal-error hover:preset-filled-error-500 p-3 transition-colors duration-300"
|
||||
class:opacity-25={!$ae_loc.trusted_access}
|
||||
class:hover:opacity-75={!$ae_loc.trusted_access}
|
||||
>
|
||||
class:hover:opacity-75={!$ae_loc.trusted_access}>
|
||||
<Biohazard size="1em" />
|
||||
<span class="hidden">Launcher Config</span>
|
||||
</button>
|
||||
@@ -906,7 +902,7 @@
|
||||
<Drawer
|
||||
dismissable={false}
|
||||
onclick={() => ($events_loc.launcher.hide_drawer__cfg = true)}
|
||||
class="bg-orange-50 dark:bg-slate-800 opacity-90 hover:opacity-97 transition-all duration-300 border border-gray-300 dark:border-gray-600 w-full md:w-96 lg:w-[32rem]"
|
||||
class="w-full border border-gray-300 bg-orange-50 opacity-90 transition-all duration-300 hover:opacity-97 md:w-96 lg:w-[32rem] dark:border-gray-600 dark:bg-slate-800"
|
||||
placement="left"
|
||||
{...{
|
||||
transitionType: 'fly',
|
||||
@@ -917,8 +913,7 @@
|
||||
}
|
||||
}}
|
||||
bind:hidden={$events_loc.launcher.hide_drawer__cfg}
|
||||
id="sidebar1"
|
||||
>
|
||||
id="sidebar1">
|
||||
<!-- Stop-propagation wrapper: prevents clicks inside the visual panel from
|
||||
bubbling up to the <dialog> element. The onclick on the <Drawer> above
|
||||
is spread through to the native <dialog> by Flowbite, overriding its
|
||||
@@ -931,20 +926,17 @@
|
||||
<hr class="my-2 border-gray-300 dark:border-gray-600" />
|
||||
|
||||
<div
|
||||
class="flex flex-row flex-wrap gap-0.5 items-center justify-center max-w-md"
|
||||
>
|
||||
class="flex max-w-md flex-row flex-wrap items-center justify-center gap-0.5">
|
||||
<a
|
||||
href="/events/{$events_slct.event_id}"
|
||||
class="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500"
|
||||
>
|
||||
class="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500">
|
||||
<Search size="1em" class="m-1" />
|
||||
Session Search
|
||||
</a>
|
||||
{#if $events_slct?.event_location_id}
|
||||
<a
|
||||
href="/events/{$events_slct.event_id}/location/{$events_slct.event_location_id}"
|
||||
class="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500"
|
||||
>
|
||||
class="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500">
|
||||
<MapPin size="1em" class="m-1" />
|
||||
View Selected Location
|
||||
</a>
|
||||
@@ -952,8 +944,7 @@
|
||||
{#if $events_slct?.event_session_id}
|
||||
<a
|
||||
href="/events/{$events_slct.event_id}/session/{$events_slct.event_session_id}"
|
||||
class="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500"
|
||||
>
|
||||
class="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500">
|
||||
<GraduationCap size="1em" class="m-1" />
|
||||
View Selected Session
|
||||
</a>
|
||||
@@ -964,7 +955,7 @@
|
||||
|
||||
<Drawer
|
||||
activateClickOutside={false}
|
||||
class="bg-red-50 dark:bg-slate-900 opacity-75 hover:opacity-95 transition-all duration-300"
|
||||
class="bg-red-50 opacity-75 transition-all duration-300 hover:opacity-95 dark:bg-slate-900"
|
||||
placement="bottom"
|
||||
{...{
|
||||
transitionType: 'fly',
|
||||
@@ -975,19 +966,16 @@
|
||||
}
|
||||
}}
|
||||
bind:hidden={$events_loc.launcher.hide_drawer__debug}
|
||||
id="sidebar2"
|
||||
>
|
||||
id="sidebar2">
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<h2
|
||||
class="text-center mb-4 text-base font-semibold text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
class="mb-4 text-center text-base font-semibold text-gray-500 dark:text-gray-400">
|
||||
Debug
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => ($events_loc.launcher.hide_drawer__debug = true)}
|
||||
class="mb-4 dark:text-white"
|
||||
>
|
||||
class="mb-4 dark:text-white">
|
||||
<X size="1em" />
|
||||
<span class="hidden">Close Debug Drawer</span>
|
||||
</button>
|
||||
@@ -1009,33 +997,30 @@
|
||||
autoclose={false}
|
||||
placement="top-center"
|
||||
class="
|
||||
bg-gray-500/90 dark:bg-gray-800/90 text-gray-800 dark:text-gray-200
|
||||
rounded-lg border-gray-200 dark:border-gray-700
|
||||
divide-y divide-gray-200 dark:divide-gray-700 shadow-md
|
||||
relative
|
||||
flex flex-col items-center justify-center
|
||||
relative flex flex-col items-center
|
||||
justify-center divide-y divide-gray-200
|
||||
rounded-lg border-gray-200 bg-gray-500/90 text-gray-800
|
||||
shadow-md
|
||||
dark:divide-gray-700 dark:border-gray-700 dark:bg-gray-800/90 dark:text-gray-200
|
||||
{$events_loc.launcher.controller == 'remote' ? 'min-h-full' : ''}
|
||||
min-w-full
|
||||
"
|
||||
bodyClass="p-0 space-y-0 overflow-auto flex flex-col gap-1 items-center justify-center pb-14"
|
||||
headerClass={`fixed top-0 right-0 left-0 p-1 md:p-2 flex flex-row items-center ${$events_loc.launcher.controller == 'remote' ? 'hidden' : ''} bg-white dark:bg-gray-800 opacity-50 ${$events_loc.launcher.hide__modal_header_title ? 'justify-center' : 'justify-between'}`}
|
||||
footerClass="text-center hidden"
|
||||
>
|
||||
footerClass="text-center hidden">
|
||||
{#snippet header()}
|
||||
<h3
|
||||
class:hidden={$events_loc.launcher.hide__modal_header_title}
|
||||
class="text-lg font-semibold opacity-20 hover:opacity-100 transition-all"
|
||||
>
|
||||
class="text-lg font-semibold opacity-20 transition-all hover:opacity-100">
|
||||
{$events_sess.launcher?.modal__title ?? 'Digital Poster Display'}
|
||||
</h3>
|
||||
<button
|
||||
type="button"
|
||||
class="btn flex-row-reverse group transition-all justify-self-end"
|
||||
class="btn group flex-row-reverse justify-self-end transition-all"
|
||||
onclick={() => {
|
||||
$events_sess.launcher.modal__open_event_file_id = null;
|
||||
}}
|
||||
title="Close Modal"
|
||||
>
|
||||
title="Close Modal">
|
||||
<X size="1em" class="my-1.5" />
|
||||
<span class="hidden group-hover:inline"> Close</span>
|
||||
</button>
|
||||
@@ -1047,25 +1032,28 @@
|
||||
and 'natural size' mode where the operator can pan/scroll and pinch-zoom freely
|
||||
for closer inspection or accessibility accommodation. -->
|
||||
<div
|
||||
class="w-full flex-1 flex items-center justify-center"
|
||||
class="flex w-full flex-1 items-center justify-center"
|
||||
class:overflow-auto={!modal_zoom_fit}
|
||||
class:overflow-hidden={modal_zoom_fit}
|
||||
>
|
||||
class:overflow-hidden={modal_zoom_fit}>
|
||||
{#if $events_sess.launcher.modal__event_file_obj?.hosted_file_id}
|
||||
<!-- WHY: Use hosted_file endpoint (not event_file) — the event_file download
|
||||
endpoint requires auth headers that a plain <img> tag cannot send (→ 403).
|
||||
The hosted_file endpoint accepts key=account_id as a query param and is
|
||||
the proven browser-compatible path for direct file display. -->
|
||||
<img
|
||||
src="{$ae_api.base_url}/v3/action/hosted_file/{$events_sess.launcher
|
||||
.modal__event_file_obj.hosted_file_id}/download?return_file=true&filename={encodeURIComponent(
|
||||
src="{$ae_api.base_url}/v3/action/hosted_file/{$events_sess
|
||||
.launcher.modal__event_file_obj
|
||||
.hosted_file_id}/download?return_file=true&filename={encodeURIComponent(
|
||||
$events_sess.launcher.modal__event_file_obj.filename ?? ''
|
||||
)}&key={$ae_api.account_id}"
|
||||
alt="Poster: {$events_sess.launcher.modal__title}"
|
||||
ondblclick={() => {
|
||||
modal_zoom_fit = !modal_zoom_fit;
|
||||
// Sync zoom state to the remote display when acting as controller.
|
||||
if ($events_loc.launcher.controller == 'local_push' && $events_sess.launcher.ws_connect_status == 'connected') {
|
||||
if (
|
||||
$events_loc.launcher.controller == 'local_push' &&
|
||||
$events_sess.launcher.ws_connect_status == 'connected'
|
||||
) {
|
||||
$events_sess.launcher.controller_cmd = `ae_zoom:${modal_zoom_fit ? 'fit' : 'zoom'}`;
|
||||
$events_sess.launcher.controller_trigger_send = true;
|
||||
}
|
||||
@@ -1078,8 +1066,7 @@
|
||||
class:object-contain={modal_zoom_fit}
|
||||
class:cursor-zoom-in={modal_zoom_fit}
|
||||
class:cursor-zoom-out={!modal_zoom_fit}
|
||||
style="touch-action: pinch-zoom;"
|
||||
/>
|
||||
style="touch-action: pinch-zoom;" />
|
||||
{:else}
|
||||
<div class="flex flex-row items-center justify-center p-4">
|
||||
<Info size="1em" class="mx-1" />
|
||||
@@ -1092,13 +1079,12 @@
|
||||
<!-- WHY: pb-14 on bodyClass reserves space so these buttons don't obscure poster content. -->
|
||||
<div
|
||||
class="
|
||||
absolute bottom-0 left-0 right-0
|
||||
absolute right-0 bottom-0 left-0
|
||||
flex flex-row items-center justify-between gap-2
|
||||
p-1.5
|
||||
bg-black/30 backdrop-blur-sm
|
||||
bg-black/30
|
||||
p-1.5 backdrop-blur-sm
|
||||
"
|
||||
class:hidden={$events_loc.launcher.controller == 'remote'}
|
||||
>
|
||||
class:hidden={$events_loc.launcher.controller == 'remote'}>
|
||||
<!-- Zoom / Fit toggle: accessibility accommodation — lets operators and general
|
||||
public zoom in to read details, pinch on mobile, or double-tap the image. -->
|
||||
<button
|
||||
@@ -1106,16 +1092,18 @@
|
||||
onclick={() => {
|
||||
modal_zoom_fit = !modal_zoom_fit;
|
||||
// Sync zoom state to the remote display when acting as controller.
|
||||
if ($events_loc.launcher.controller == 'local_push' && $events_sess.launcher.ws_connect_status == 'connected') {
|
||||
if (
|
||||
$events_loc.launcher.controller == 'local_push' &&
|
||||
$events_sess.launcher.ws_connect_status == 'connected'
|
||||
) {
|
||||
$events_sess.launcher.controller_cmd = `ae_zoom:${modal_zoom_fit ? 'fit' : 'zoom'}`;
|
||||
$events_sess.launcher.controller_trigger_send = true;
|
||||
}
|
||||
}}
|
||||
class="btn btn-sm preset-tonal-surface opacity-80 hover:opacity-100 transition-opacity"
|
||||
class="btn btn-sm preset-tonal-surface opacity-80 transition-opacity hover:opacity-100"
|
||||
title={modal_zoom_fit
|
||||
? 'Pan / Zoom mode — pinch or double-tap image to zoom'
|
||||
: 'Fit image to screen'}
|
||||
>
|
||||
: 'Fit image to screen'}>
|
||||
{#if modal_zoom_fit}
|
||||
<ZoomIn size="1em" class="mr-1" />
|
||||
<span class="hidden sm:inline">Zoom</span>
|
||||
@@ -1136,14 +1124,13 @@
|
||||
$events_sess.launcher.modal__open_event_file_id = null;
|
||||
$events_sess.launcher.modal__event_file_obj = null;
|
||||
}}
|
||||
class="btn btn-sm preset-tonal-error opacity-80 hover:opacity-100 transition-all"
|
||||
class="btn btn-sm preset-tonal-error opacity-80 transition-all hover:opacity-100"
|
||||
class:hidden={$events_loc.launcher.controller != 'local_push' ||
|
||||
$events_sess.launcher.ws_connect_status != 'connected'}
|
||||
title="Close poster on this device and on the remote display (screensaver resumes)"
|
||||
>
|
||||
title="Close poster on this device and on the remote display (screensaver resumes)">
|
||||
<Monitor size="1em" class="mr-1" />
|
||||
<X size="1em" />
|
||||
<span class="hidden sm:inline ml-1">Close Both</span>
|
||||
<span class="ml-1 hidden sm:inline">Close Both</span>
|
||||
</button>
|
||||
|
||||
<!-- Back to List: dismisses this controller's view only.
|
||||
@@ -1155,12 +1142,11 @@
|
||||
$events_sess.launcher.modal__open_event_file_id = null;
|
||||
$events_sess.launcher.modal__event_file_obj = null;
|
||||
}}
|
||||
class="btn btn-sm preset-tonal-surface border border-surface-400/50 opacity-80 hover:opacity-100 transition-all"
|
||||
class="btn btn-sm preset-tonal-surface border-surface-400/50 border opacity-80 transition-all hover:opacity-100"
|
||||
class:hidden={!$ae_loc.trusted_access &&
|
||||
($events_loc.launcher.controller != 'local_push' ||
|
||||
$events_sess.launcher.ws_connect_status != 'connected')}
|
||||
title="Close poster on this device only — remote display keeps showing"
|
||||
>
|
||||
title="Close poster on this device only — remote display keeps showing">
|
||||
<List size="1em" class="mr-1" />
|
||||
<span class="hidden sm:inline">Back to List</span>
|
||||
</button>
|
||||
@@ -1187,6 +1173,5 @@
|
||||
bind:hide__ws_commands={$events_loc.launcher.hide__ws_commands}
|
||||
bind:ws_conn_status={trigger_handle_ws_conn}
|
||||
bind:ws_recv_status={trigger_handle_ws_recv}
|
||||
bind:ws_sent_status={trigger_handle_ws_sent}
|
||||
/>
|
||||
bind:ws_sent_status={trigger_handle_ws_sent} />
|
||||
{/if}
|
||||
|
||||
@@ -9,15 +9,11 @@
|
||||
|
||||
// Imports
|
||||
import { untrack } from 'svelte';
|
||||
import {
|
||||
ae_loc,
|
||||
ae_sess,
|
||||
ae_api,
|
||||
} from '$lib/stores/ae_stores';
|
||||
import { ae_loc, ae_sess, ae_api } from '$lib/stores/ae_stores';
|
||||
import {
|
||||
events_loc,
|
||||
events_sess,
|
||||
events_slct,
|
||||
events_slct
|
||||
} from '$lib/stores/ae_events_stores';
|
||||
|
||||
// NOTE: Derived from data.account_id (prop) instead of $slct.account_id (store)
|
||||
@@ -41,9 +37,12 @@
|
||||
$effect(() => {
|
||||
if (ae_acct) {
|
||||
untrack(() => {
|
||||
$events_slct.event_location_obj_li = ae_acct.slct.event_location_obj_li ?? [''];
|
||||
$events_slct.id_li__event_location = ae_acct.slct.id_li__event_location ?? [''];
|
||||
$events_slct.event_session_obj_li = ae_acct.slct.event_session_obj_li ?? [''];
|
||||
$events_slct.event_location_obj_li = ae_acct.slct
|
||||
.event_location_obj_li ?? [''];
|
||||
$events_slct.id_li__event_location = ae_acct.slct
|
||||
.id_li__event_location ?? [''];
|
||||
$events_slct.event_session_obj_li = ae_acct.slct
|
||||
.event_session_obj_li ?? [''];
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -71,7 +71,8 @@ export async function load({ params, parent, url }) {
|
||||
|
||||
const session_id = url.searchParams.get('session_id');
|
||||
if (browser && session_id) {
|
||||
if (log_lvl) console.log(`Triggering deep load for session_id: ${session_id}`);
|
||||
if (log_lvl)
|
||||
console.log(`Triggering deep load for session_id: ${session_id}`);
|
||||
events_func.load_ae_obj_id__event_session({
|
||||
api_cfg: ae_acct.api,
|
||||
event_session_id: session_id,
|
||||
|
||||
@@ -6,7 +6,11 @@
|
||||
*/
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { ae_loc, ae_api } from '$lib/stores/ae_stores';
|
||||
import { events_loc, events_slct, events_sess } from '$lib/stores/ae_events_stores';
|
||||
import {
|
||||
events_loc,
|
||||
events_slct,
|
||||
events_sess
|
||||
} from '$lib/stores/ae_events_stores';
|
||||
import { events_func } from '$lib/ae_events/ae_events_functions';
|
||||
import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
import { db_events } from '$lib/ae_events/db_events';
|
||||
@@ -16,9 +20,7 @@
|
||||
let { log_lvl = 1 } = $props();
|
||||
|
||||
let currently_syncing: string | null = $state(null);
|
||||
let sync_results: Record<string, 'success' | 'error' | 'pending'> = $state(
|
||||
{}
|
||||
);
|
||||
let sync_results: Record<string, 'success' | 'error' | 'pending'> = $state({});
|
||||
let sync_stats = $state({ total: 0, cached: 0, missing: 0 });
|
||||
let last_heartbeat: string | null = $state(null);
|
||||
|
||||
@@ -79,22 +81,40 @@
|
||||
// Load timings from persistent config, with fallbacks to device config or defaults.
|
||||
// Fallback values here must match the loop_info $state defaults above — keep in sync.
|
||||
loop_info.event = cfg.event || dev.check_event_loop_period || 90000;
|
||||
loop_info.device = cfg.device || dev.check_event_device_loop_period || 60000;
|
||||
loop_info.location = cfg.location || dev.check_event_location_loop_period || 60000;
|
||||
loop_info.session = cfg.session || dev.check_event_session_loop_period || 60000;
|
||||
loop_info.presentation = cfg.presentation || dev.check_event_presentation_loop_period || 120000;
|
||||
loop_info.presenter = cfg.presenter || dev.check_event_presenter_loop_period || 120000;
|
||||
loop_info.file_sync = cfg.file_sync || dev.check_file_sync_loop_period || 30000;
|
||||
loop_info.device =
|
||||
cfg.device || dev.check_event_device_loop_period || 60000;
|
||||
loop_info.location =
|
||||
cfg.location || dev.check_event_location_loop_period || 60000;
|
||||
loop_info.session =
|
||||
cfg.session || dev.check_event_session_loop_period || 60000;
|
||||
loop_info.presentation =
|
||||
cfg.presentation || dev.check_event_presentation_loop_period || 120000;
|
||||
loop_info.presenter =
|
||||
cfg.presenter || dev.check_event_presenter_loop_period || 120000;
|
||||
loop_info.file_sync =
|
||||
cfg.file_sync || dev.check_file_sync_loop_period || 30000;
|
||||
|
||||
// 1. Structural/Metadata Loops
|
||||
timer__event = setInterval(() => refresh_event_data(), loop_info.event);
|
||||
timer__device = setInterval(() => run_device_heartbeat(), loop_info.device);
|
||||
timer__location = setInterval(() => refresh_location_config(), loop_info.location);
|
||||
timer__location = setInterval(
|
||||
() => refresh_location_config(),
|
||||
loop_info.location
|
||||
);
|
||||
|
||||
// 2. Room Content Refresh Loops (API -> Dexie)
|
||||
timer__session = setInterval(() => refresh_session_data(), loop_info.session);
|
||||
timer__presentation = setInterval(() => refresh_presentation_data(), loop_info.presentation);
|
||||
timer__presenter = setInterval(() => refresh_presenter_data(), loop_info.presenter);
|
||||
timer__session = setInterval(
|
||||
() => refresh_session_data(),
|
||||
loop_info.session
|
||||
);
|
||||
timer__presentation = setInterval(
|
||||
() => refresh_presentation_data(),
|
||||
loop_info.presentation
|
||||
);
|
||||
timer__presenter = setInterval(
|
||||
() => refresh_presenter_data(),
|
||||
loop_info.presenter
|
||||
);
|
||||
|
||||
// 3. Native File Sync Loop (Dexie -> Disk)
|
||||
timer__file_sync = setInterval(() => run_sync_cycle(), loop_info.file_sync);
|
||||
@@ -113,8 +133,12 @@
|
||||
// Age threshold is user-configurable (cfg → General → Cache Maintenance), default 24h.
|
||||
const cache_root = $ae_loc.local_file_cache_path;
|
||||
if ($ae_loc.is_native && cache_root) {
|
||||
const max_age_hours = $events_loc.launcher.cleanup_tmp_max_age_hours ?? 24;
|
||||
cleanup_tmp_files({ cache_root, max_age_minutes: max_age_hours * 60 }).then((result) => {
|
||||
const max_age_hours =
|
||||
$events_loc.launcher.cleanup_tmp_max_age_hours ?? 24;
|
||||
cleanup_tmp_files({
|
||||
cache_root,
|
||||
max_age_minutes: max_age_hours * 60
|
||||
}).then((result) => {
|
||||
if (log_lvl) console.log('Sync: .tmp cleanup complete.', result);
|
||||
});
|
||||
}
|
||||
@@ -157,7 +181,10 @@
|
||||
const location_id = $events_slct.event_location_id;
|
||||
if (!location_id) return;
|
||||
try {
|
||||
if (log_lvl > 1) console.log(`Sync: Refreshing sessions for location: ${location_id}`);
|
||||
if (log_lvl > 1)
|
||||
console.log(
|
||||
`Sync: Refreshing sessions for location: ${location_id}`
|
||||
);
|
||||
await events_func.load_ae_obj_li__event_session({
|
||||
api_cfg: $ae_api,
|
||||
for_obj_type: 'event_location',
|
||||
@@ -178,7 +205,10 @@
|
||||
const session_id = $events_slct.event_session_id;
|
||||
if (!session_id) return;
|
||||
try {
|
||||
if (log_lvl > 1) console.log(`Sync: Refreshing presentations for session: ${session_id}`);
|
||||
if (log_lvl > 1)
|
||||
console.log(
|
||||
`Sync: Refreshing presentations for session: ${session_id}`
|
||||
);
|
||||
await events_func.load_ae_obj_li__event_presentation({
|
||||
api_cfg: $ae_api,
|
||||
for_obj_type: 'event_session',
|
||||
@@ -197,7 +227,10 @@
|
||||
const session_id = $events_slct.event_session_id;
|
||||
if (!session_id) return;
|
||||
try {
|
||||
if (log_lvl > 1) console.log(`Sync: Refreshing presenters for session: ${session_id}`);
|
||||
if (log_lvl > 1)
|
||||
console.log(
|
||||
`Sync: Refreshing presenters for session: ${session_id}`
|
||||
);
|
||||
await events_func.load_ae_obj_li__event_presenter({
|
||||
api_cfg: $ae_api,
|
||||
for_obj_type: 'event_session',
|
||||
@@ -214,8 +247,7 @@
|
||||
const cache_root = $ae_loc.local_file_cache_path;
|
||||
const prefix_len = $ae_loc.native_device?.hash_prefix_length || 2;
|
||||
|
||||
if (!location_id || !cache_root || is_syncing || !$ae_loc.is_native)
|
||||
return;
|
||||
if (!location_id || !cache_root || is_syncing || !$ae_loc.is_native) return;
|
||||
|
||||
is_syncing = true;
|
||||
try {
|
||||
@@ -362,15 +394,16 @@
|
||||
update_payload.info_hostname = info.hostname;
|
||||
// Safely handle IP list (bridge may return ip_addresses or networkInterfaces)
|
||||
const ips = info.ip_addresses || [];
|
||||
update_payload.info_ip_list = Array.isArray(ips) ? ips.join(', ') : 'Unknown';
|
||||
update_payload.info_ip_list = Array.isArray(ips)
|
||||
? ips.join(', ')
|
||||
: 'Unknown';
|
||||
|
||||
update_payload.meta_json = {
|
||||
platform: info.platform,
|
||||
release: info.release,
|
||||
arch: info.arch,
|
||||
cpus: info.cpus,
|
||||
total_mem:
|
||||
Math.round(info.total_mem / (1024 * 1024)) + 'MB',
|
||||
total_mem: Math.round(info.total_mem / (1024 * 1024)) + 'MB',
|
||||
free_mem: Math.round(info.free_mem / (1024 * 1024)) + 'MB'
|
||||
};
|
||||
} else {
|
||||
@@ -424,81 +457,76 @@
|
||||
and the sys bar (bottom-12 right-2). Panel grows upward from the status chip. -->
|
||||
{#if $events_loc.launcher.app_mode === 'native' || $ae_loc.is_native}
|
||||
<div
|
||||
class="fixed bottom-20 left-4 z-[9999] flex flex-col items-start gap-2 pointer-events-none"
|
||||
>
|
||||
class="pointer-events-none fixed bottom-20 left-4 z-[9999] flex flex-col items-start gap-2">
|
||||
{#if show_monitor}
|
||||
<div
|
||||
class="bg-surface-50/95 dark:bg-surface-900/95 text-surface-800 dark:text-surface-100 p-3 rounded-lg border border-surface-200 dark:border-primary-700 shadow-2xl text-[10px] font-mono min-w-52 pointer-events-auto backdrop-blur-sm"
|
||||
>
|
||||
class="bg-surface-50/95 dark:bg-surface-900/95 text-surface-800 dark:text-surface-100 border-surface-200 dark:border-primary-700 pointer-events-auto min-w-52 rounded-lg border p-3 font-mono text-[10px] shadow-2xl backdrop-blur-sm">
|
||||
<div
|
||||
class="flex justify-between border-b border-surface-200 dark:border-primary-700 pb-1 mb-2"
|
||||
>
|
||||
<span class="font-bold text-primary-600 dark:text-primary-400"
|
||||
>NATIVE SYNC MONITOR</span
|
||||
>
|
||||
class="border-surface-200 dark:border-primary-700 mb-2 flex justify-between border-b pb-1">
|
||||
<span
|
||||
class="text-primary-600 dark:text-primary-400 font-bold"
|
||||
>NATIVE SYNC MONITOR</span>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (show_monitor = false)}
|
||||
class="text-error-500 hover:text-error-400 ml-2">×</button
|
||||
>
|
||||
class="text-error-500 hover:text-error-400 ml-2"
|
||||
>×</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-1 mb-2">
|
||||
<span class="opacity-60 text-primary-700 dark:text-primary-300">Room Status:</span>
|
||||
<div class="mb-2 grid grid-cols-2 gap-x-4 gap-y-1">
|
||||
<span
|
||||
class="text-primary-700 dark:text-primary-300 opacity-60"
|
||||
>Room Status:</span>
|
||||
<span class="text-right"
|
||||
>{sync_stats.cached} / {sync_stats.total} Files</span
|
||||
>
|
||||
>{sync_stats.cached} / {sync_stats.total} Files</span>
|
||||
|
||||
<span class="opacity-60 text-primary-700 dark:text-primary-300">Prefix Len:</span>
|
||||
<span
|
||||
class="text-primary-700 dark:text-primary-300 opacity-60"
|
||||
>Prefix Len:</span>
|
||||
<span class="text-right"
|
||||
>{$ae_loc.native_device?.hash_prefix_length || 2} chars</span
|
||||
>
|
||||
>{$ae_loc.native_device?.hash_prefix_length || 2} chars</span>
|
||||
|
||||
<span class="opacity-60 text-primary-700 dark:text-primary-300">Heartbeat:</span>
|
||||
<span
|
||||
class="text-primary-700 dark:text-primary-300 opacity-60"
|
||||
>Heartbeat:</span>
|
||||
<span
|
||||
class="text-right {last_heartbeat
|
||||
? 'text-success-600 dark:text-success-400'
|
||||
: 'text-error-600 dark:text-error-400'}"
|
||||
>
|
||||
: 'text-error-600 dark:text-error-400'}">
|
||||
{last_heartbeat || 'Pending...'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-surface-200 dark:border-surface-700 pt-2 flex flex-col gap-1">
|
||||
<div class="flex justify-between items-center">
|
||||
<div
|
||||
class="border-surface-200 dark:border-surface-700 flex flex-col gap-1 border-t pt-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class:text-primary-500={timer__event}
|
||||
>Event Loop:</span
|
||||
>
|
||||
>Event Loop:</span>
|
||||
<span>{loop_info.event / 1000}s</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class:text-primary-500={timer__device}
|
||||
>Device Loop:</span
|
||||
>
|
||||
>Device Loop:</span>
|
||||
<span>{loop_info.device / 1000}s</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class:text-primary-500={timer__location}
|
||||
>Location Loop:</span
|
||||
>
|
||||
>Location Loop:</span>
|
||||
<span>{loop_info.location / 1000}s</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class:text-primary-500={timer__session}
|
||||
>Session Loop:</span
|
||||
>
|
||||
>Session Loop:</span>
|
||||
<span>{loop_info.session / 1000}s</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class:text-primary-500={timer__presentation}
|
||||
>Pres Loop:</span
|
||||
>
|
||||
>Pres Loop:</span>
|
||||
<span>{loop_info.presentation / 1000}s</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class:text-primary-500={timer__presenter}
|
||||
>Speaker Loop:</span
|
||||
>
|
||||
>Speaker Loop:</span>
|
||||
<span>{loop_info.presenter / 1000}s</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -512,25 +540,36 @@
|
||||
type="button"
|
||||
onclick={() => (show_monitor = !show_monitor)}
|
||||
class="
|
||||
flex items-center gap-1.5
|
||||
px-2 py-1.5 rounded-lg
|
||||
text-[10px] font-mono
|
||||
pointer-events-auto
|
||||
pointer-events-auto flex items-center
|
||||
gap-1.5 rounded-lg px-2
|
||||
py-1.5 font-mono
|
||||
text-[10px]
|
||||
transition-all
|
||||
{currently_syncing
|
||||
? 'bg-primary-500/15 dark:bg-primary-500/25 border border-primary-500 text-primary-700 dark:text-primary-300 shadow-md animate-pulse'
|
||||
: 'bg-surface-100/90 dark:bg-surface-800/80 border border-surface-300 dark:border-surface-600 text-surface-600 dark:text-surface-300 opacity-60 hover:opacity-100 shadow-sm'}
|
||||
? 'bg-primary-500/15 dark:bg-primary-500/25 border-primary-500 text-primary-700 dark:text-primary-300 animate-pulse border shadow-md'
|
||||
: 'bg-surface-100/90 dark:bg-surface-800/80 border-surface-300 dark:border-surface-600 text-surface-600 dark:text-surface-300 border opacity-60 shadow-sm hover:opacity-100'}
|
||||
"
|
||||
title="Native Sync Monitor · {sync_stats.cached}/{sync_stats.total} files · click to {show_monitor ? 'close' : 'open'}"
|
||||
>
|
||||
{#if currently_syncing}<RefreshCw size="1em" class="text-[9px] animate-spin text-primary-500" />{:else}<Cpu size="1em" class="text-[9px] opacity-50" />{/if}
|
||||
title="Native Sync Monitor · {sync_stats.cached}/{sync_stats.total} files · click to {show_monitor
|
||||
? 'close'
|
||||
: 'open'}">
|
||||
{#if currently_syncing}<RefreshCw
|
||||
size="1em"
|
||||
class="text-primary-500 animate-spin text-[9px]" />{:else}<Cpu
|
||||
size="1em"
|
||||
class="text-[9px] opacity-50" />{/if}
|
||||
{#if currently_syncing}
|
||||
<span class="truncate max-w-40 text-left">{currently_syncing}</span>
|
||||
<span class="max-w-40 truncate text-left"
|
||||
>{currently_syncing}</span>
|
||||
{:else}
|
||||
<span>Native Sync</span>
|
||||
<span class="opacity-50 ml-0.5">{sync_stats.cached}/{sync_stats.total}</span>
|
||||
<span class="ml-0.5 opacity-50"
|
||||
>{sync_stats.cached}/{sync_stats.total}</span>
|
||||
{/if}
|
||||
{#if show_monitor}<ChevronDown size="1em" class="text-[7px] opacity-40 ml-0.5" />{:else}<ChevronUp size="1em" class="text-[7px] opacity-40 ml-0.5" />{/if}
|
||||
{#if show_monitor}<ChevronDown
|
||||
size="1em"
|
||||
class="ml-0.5 text-[7px] opacity-40" />{:else}<ChevronUp
|
||||
size="1em"
|
||||
class="ml-0.5 text-[7px] opacity-40" />{/if}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -32,7 +32,16 @@
|
||||
import Launcher_Cfg_Screen_Saver from './cfg_components/launcher_cfg_screen_saver.svelte';
|
||||
import Launcher_Cfg_App_Modes from './cfg_components/launcher_cfg_app_modes.svelte';
|
||||
import Launcher_Cfg_Local_Actions from './cfg_components/launcher_cfg_local_actions.svelte';
|
||||
import { Bug, Code, Monitor, Pencil, RefreshCw, Settings, SlidersHorizontal, X } from '@lucide/svelte';
|
||||
import {
|
||||
Bug,
|
||||
Code,
|
||||
Monitor,
|
||||
Pencil,
|
||||
RefreshCw,
|
||||
Settings,
|
||||
SlidersHorizontal,
|
||||
X
|
||||
} from '@lucide/svelte';
|
||||
// UI Tab State
|
||||
// Tabs are audience-oriented:
|
||||
// setup — what every onsite operator needs (mode preset, display, WS, screen saver)
|
||||
@@ -63,16 +72,13 @@
|
||||
|
||||
<div
|
||||
class="
|
||||
w-full max-w-full
|
||||
flex flex-col gap-4 items-center justify-start
|
||||
"
|
||||
>
|
||||
flex w-full
|
||||
max-w-full flex-col items-center justify-start gap-4
|
||||
">
|
||||
<div
|
||||
class="w-full flex flex-row items-center justify-between border-b border-surface-500/20 pb-2"
|
||||
>
|
||||
class="border-surface-500/20 flex w-full flex-row items-center justify-between border-b pb-2">
|
||||
<h2
|
||||
class="text-center text-lg font-bold text-gray-700 dark:text-gray-200"
|
||||
>
|
||||
class="text-center text-lg font-bold text-gray-700 dark:text-gray-200">
|
||||
<Settings size="1em" class="mr-2 opacity-50" />
|
||||
Launcher Configuration
|
||||
</h2>
|
||||
@@ -88,8 +94,7 @@
|
||||
class:text-primary-500={$ae_loc.edit_mode}
|
||||
class:opacity-20={!$ae_loc.edit_mode}
|
||||
class:hover:opacity-60={!$ae_loc.edit_mode}
|
||||
title="{$ae_loc.edit_mode ? 'Disable' : 'Enable'} Edit Mode"
|
||||
>
|
||||
title="{$ae_loc.edit_mode ? 'Disable' : 'Enable'} Edit Mode">
|
||||
<Pencil size="0.75em" />
|
||||
<span class="sr-only">Toggle Edit Mode</span>
|
||||
</button>
|
||||
@@ -97,8 +102,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => ($events_loc.launcher.hide_drawer__cfg = true)}
|
||||
class="btn btn-icon dark:text-white hover:bg-surface-500/10 transition-colors"
|
||||
>
|
||||
class="btn btn-icon hover:bg-surface-500/10 transition-colors dark:text-white">
|
||||
<X size="1em" />
|
||||
<span class="sr-only">Close Config</span>
|
||||
</button>
|
||||
@@ -110,88 +114,80 @@
|
||||
for onsite operators who never need those tools. Edit Mode is toggled via
|
||||
the pencil icon in the header above. -->
|
||||
<div
|
||||
class="w-full gap-1 bg-surface-500/10 p-1 rounded-lg"
|
||||
class="bg-surface-500/10 w-full gap-1 rounded-lg p-1"
|
||||
class:grid={true}
|
||||
class:grid-cols-2={!$ae_loc.edit_mode}
|
||||
class:grid-cols-3={$ae_loc.edit_mode}
|
||||
>
|
||||
class:grid-cols-3={$ae_loc.edit_mode}>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (active_tab = 'setup')}
|
||||
class="btn btn-sm text-[10px] uppercase font-bold transition-all"
|
||||
class="btn btn-sm text-[10px] font-bold uppercase transition-all"
|
||||
class:preset-filled-primary={active_tab === 'setup'}
|
||||
class:preset-tonal-surface={active_tab !== 'setup'}
|
||||
title="Display presets, interface toggles, WS controller, screen saver"
|
||||
>
|
||||
title="Display presets, interface toggles, WS controller, screen saver">
|
||||
<SlidersHorizontal size="0.85em" class="mr-1" /> Setup
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (active_tab = 'device')}
|
||||
class="btn btn-sm text-[10px] uppercase font-bold transition-all"
|
||||
class="btn btn-sm text-[10px] font-bold uppercase transition-all"
|
||||
class:preset-filled-primary={active_tab === 'device'}
|
||||
class:preset-tonal-surface={active_tab !== 'device'}
|
||||
title="Sync engine, device health & native OS controls"
|
||||
>
|
||||
title="Sync engine, device health & native OS controls">
|
||||
<Monitor size="0.85em" class="mr-1" /> Device
|
||||
</button>
|
||||
{#if $ae_loc.edit_mode}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (active_tab = 'dev')}
|
||||
class="btn btn-sm text-[10px] uppercase font-bold transition-all"
|
||||
class="btn btn-sm text-[10px] font-bold uppercase transition-all"
|
||||
class:preset-filled-warning={active_tab === 'dev'}
|
||||
class:preset-tonal-surface={active_tab !== 'dev'}
|
||||
title="Developer & debug tools"
|
||||
>
|
||||
title="Developer & debug tools">
|
||||
<Code size="0.85em" class="mr-1" /> Dev
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Tab Content -->
|
||||
<div class="w-full flex flex-col gap-2 min-h-[400px]">
|
||||
|
||||
<div class="flex min-h-[400px] w-full flex-col gap-2">
|
||||
<!-- SETUP: everything onsite operators need day-to-day -->
|
||||
{#if active_tab === 'setup'}
|
||||
<div class="animate-in fade-in slide-in-from-left-2 duration-300 flex flex-col gap-2">
|
||||
<div
|
||||
class="animate-in fade-in slide-in-from-left-2 flex flex-col gap-2 duration-300">
|
||||
<!-- Mode preset is the #1 onsite action — give it prominent placement -->
|
||||
<Launcher_Cfg_App_Modes
|
||||
on_expand={() => handle_section_expand('app_modes')}
|
||||
/>
|
||||
on_expand={() => handle_section_expand('app_modes')} />
|
||||
<Launcher_Cfg_Controller
|
||||
on_expand={() => handle_section_expand('controller')}
|
||||
/>
|
||||
on_expand={() => handle_section_expand('controller')} />
|
||||
<Launcher_Cfg_Screen_Saver
|
||||
on_expand={() => handle_section_expand('screen_saver')}
|
||||
/>
|
||||
on_expand={() => handle_section_expand('screen_saver')} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- DEVICE: sync engine first (all devices) + native OS controls (native or edit_mode preview) -->
|
||||
{#if active_tab === 'device'}
|
||||
<div class="animate-in fade-in slide-in-from-bottom-2 duration-300 flex flex-col gap-2">
|
||||
<div
|
||||
class="animate-in fade-in slide-in-from-bottom-2 flex flex-col gap-2 duration-300">
|
||||
<!-- Sync pause/timers — relevant to every device, not just native -->
|
||||
<Launcher_Cfg_Sync_Timers
|
||||
on_expand={() => handle_section_expand('sync_timers')}
|
||||
/>
|
||||
on_expand={() => handle_section_expand('sync_timers')} />
|
||||
|
||||
<!-- Native sections: always in Electron; visible in edit_mode for dev preview.
|
||||
electron_relay.ts guards all calls — safe to import/render without Electron. -->
|
||||
{#if $ae_loc.is_native || $ae_loc.edit_mode}
|
||||
<Launcher_Cfg_Health
|
||||
on_expand={() => handle_section_expand('health')}
|
||||
/>
|
||||
on_expand={() => handle_section_expand('health')} />
|
||||
<Launcher_Cfg_Native_OS
|
||||
on_expand={() => handle_section_expand('native_os')}
|
||||
/>
|
||||
on_expand={() => handle_section_expand('native_os')} />
|
||||
{#if $ae_loc.is_native}
|
||||
<Launcher_Cfg_Updates
|
||||
on_expand={() => handle_section_expand('updates')}
|
||||
/>
|
||||
on_expand={() =>
|
||||
handle_section_expand('updates')} />
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="py-3 text-center opacity-40 italic text-xs flex flex-col gap-1 items-center">
|
||||
<div
|
||||
class="flex flex-col items-center gap-1 py-3 text-center text-xs italic opacity-40">
|
||||
<Monitor size="1.2em" class="opacity-30" />
|
||||
<p>Native OS controls available in Aether Desktop.</p>
|
||||
<p class="text-[9px]">Enable Edit Mode to preview.</p>
|
||||
@@ -202,27 +198,24 @@
|
||||
|
||||
<!-- DEV: developer/debug tools — only reachable when Edit Mode is on -->
|
||||
{#if active_tab === 'dev' && $ae_loc.edit_mode}
|
||||
<div class="animate-in fade-in slide-in-from-right-2 duration-300 flex flex-col gap-2">
|
||||
<div
|
||||
class="animate-in fade-in slide-in-from-right-2 flex flex-col gap-2 duration-300">
|
||||
<Launcher_Cfg_Local_Actions
|
||||
on_expand={() => handle_section_expand('local_actions')}
|
||||
/>
|
||||
on_expand={() => handle_section_expand('local_actions')} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Global Actions Footer -->
|
||||
<div
|
||||
class="w-full flex flex-col gap-2 border-t border-surface-500/20 pt-4 mt-auto"
|
||||
>
|
||||
class="border-surface-500/20 mt-auto flex w-full flex-col gap-2 border-t pt-4">
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<!-- Close button — always visible in lower-left as a second dismissal point.
|
||||
Useful in kiosk/iframe mode where the top-right close btn may scroll out of view. -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => ($events_loc.launcher.hide_drawer__cfg = true)}
|
||||
class="btn btn-sm preset-tonal-surface hover:preset-filled-surface-500 transition-all"
|
||||
>
|
||||
class="btn btn-sm preset-tonal-surface hover:preset-filled-surface-500 transition-all">
|
||||
<X size="0.85em" class="mr-1" />
|
||||
Close
|
||||
</button>
|
||||
@@ -230,8 +223,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => location.reload()}
|
||||
class="btn btn-sm preset-tonal-secondary hover:preset-filled-secondary-500 transition-all"
|
||||
>
|
||||
class="btn btn-sm preset-tonal-secondary hover:preset-filled-secondary-500 transition-all">
|
||||
<RefreshCw size="0.85em" class="mr-1" />
|
||||
Reload
|
||||
</button>
|
||||
@@ -242,16 +234,14 @@
|
||||
type="button"
|
||||
onclick={() =>
|
||||
($events_loc.launcher.hide_drawer__debug = false)}
|
||||
class="btn btn-sm preset-tonal-warning hover:preset-filled-warning-500 transition-all w-full"
|
||||
>
|
||||
class="btn btn-sm preset-tonal-warning hover:preset-filled-warning-500 w-full transition-all">
|
||||
<Bug size="0.85em" class="mr-1" />
|
||||
Debug Panel
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<p
|
||||
class="text-[9px] text-center opacity-40 uppercase font-bold tracking-widest mt-2"
|
||||
>
|
||||
class="mt-2 text-center text-[9px] font-bold tracking-widest uppercase opacity-40">
|
||||
Aether Platform • Events Launcher v3.0
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -63,7 +63,18 @@
|
||||
events_slct
|
||||
} from '$lib/stores/ae_events_stores';
|
||||
import { events_func } from '$lib/ae_events/ae_events_functions';
|
||||
import { AlertCircle, AlertTriangle, BarChart2, CalendarDays, FolderOpen, Laptop, LoaderCircle, Monitor, Save, Send } from '@lucide/svelte';
|
||||
import {
|
||||
AlertCircle,
|
||||
AlertTriangle,
|
||||
BarChart2,
|
||||
CalendarDays,
|
||||
FolderOpen,
|
||||
Laptop,
|
||||
LoaderCircle,
|
||||
Monitor,
|
||||
Save,
|
||||
Send
|
||||
} from '@lucide/svelte';
|
||||
import AE_Comp_Hosted_Files_Download_Button from '$lib/ae_core/ae_comp__hosted_files_download_button.svelte';
|
||||
|
||||
// Import the relay
|
||||
@@ -227,24 +238,22 @@
|
||||
class:hidden={hide_draft &&
|
||||
(event_file_obj.file_purpose == 'outline' ||
|
||||
event_file_obj.file_purpose == 'draft')}
|
||||
class="event_launcher_file_cont grow flex flex-col md:flex-row flex-wrap gap-1 items-center justify-center max-w-full transition-all"
|
||||
>
|
||||
class="event_launcher_file_cont flex max-w-full grow flex-col flex-wrap items-center justify-center gap-1 transition-all md:flex-row">
|
||||
{#if open_file_clicked}
|
||||
<div
|
||||
class="open_file_clicked alert"
|
||||
in:fade={{ duration: 250 }}
|
||||
out:fade={{ duration: 2000 }}
|
||||
>
|
||||
out:fade={{ duration: 2000 }}>
|
||||
<div class="alert_msg_pulse">
|
||||
<strong
|
||||
>*** {open_file_status_message ||
|
||||
'Please wait while this file downloads...'} ***</strong
|
||||
>
|
||||
'Please wait while this file downloads...'} ***</strong>
|
||||
</div>
|
||||
{#if $ae_loc.is_native && $events_loc.launcher.app_mode === 'native'}
|
||||
<p>Most files will automatically be opened full screen.</p>
|
||||
<p>
|
||||
PowerPoint or KeyNote will attempt to display in presenter view.
|
||||
PowerPoint or KeyNote will attempt to display in presenter
|
||||
view.
|
||||
</p>
|
||||
<p>Please close the file when finished.</p>
|
||||
{/if}
|
||||
@@ -252,8 +261,7 @@
|
||||
{/if}
|
||||
|
||||
<span
|
||||
class="event_file_action grow max-w-full flex flex-row flex-wrap gap-1 items-center justify-center"
|
||||
>
|
||||
class="event_file_action flex max-w-full grow flex-row flex-wrap items-center justify-center gap-1">
|
||||
{#if session_type == 'poster' || open_method == 'modal'}
|
||||
<AE_Comp_Hosted_Files_Download_Button
|
||||
hosted_file_id={event_file_id}
|
||||
@@ -267,17 +275,24 @@
|
||||
$events_slct.event_file_id = event_file_id;
|
||||
$events_slct.event_file_obj = event_file_obj;
|
||||
// Push the open command to the remote display when in local_push mode
|
||||
if ($events_loc.launcher.controller == 'local_push' && $events_sess.launcher.ws_connect_status == 'connected') {
|
||||
if (
|
||||
$events_loc.launcher.controller == 'local_push' &&
|
||||
$events_sess.launcher.ws_connect_status == 'connected'
|
||||
) {
|
||||
$events_sess.launcher.controller_cmd = `ae_open:event_file=${event_file_id}`;
|
||||
$events_sess.launcher.controller_trigger_send = true;
|
||||
}
|
||||
}}
|
||||
>
|
||||
}}>
|
||||
{#snippet label()}
|
||||
{#if screen_saver_exts.includes(event_file_obj.extension)}
|
||||
<BarChart2 size="1em" class="{hide_launch_icon ? 'hidden' : ''} m-1" /> Open Poster
|
||||
<BarChart2
|
||||
size="1em"
|
||||
class="{hide_launch_icon ? 'hidden' : ''} m-1" /> Open
|
||||
Poster
|
||||
{:else}
|
||||
<Send size="1em" class="{hide_launch_icon ? 'hidden' : ''} m-1" />
|
||||
<Send
|
||||
size="1em"
|
||||
class="{hide_launch_icon ? 'hidden' : ''} m-1" />
|
||||
{ae_util.shorten_filename({
|
||||
filename: event_file_obj.filename,
|
||||
max_length: max_filename_length
|
||||
@@ -291,13 +306,14 @@
|
||||
hosted_file_obj={event_file_obj}
|
||||
require_auth={false}
|
||||
classes="btn {btn_size} gap-1 justify-between min-w-full w-full max-w-96 preset-tonal-primary border border-primary-500"
|
||||
click={handle_open_file}
|
||||
>
|
||||
click={handle_open_file}>
|
||||
{#snippet label()}
|
||||
{@const file_id = event_file_obj.hosted_file_id}
|
||||
<span class="shrink text-xs border-r border-gray-400 pr-1">
|
||||
<span class="shrink border-r border-gray-400 pr-1 text-xs">
|
||||
{#await ae_promises[event_file_id]}
|
||||
<LoaderCircle size="1em" class="inline animate-spin mx-0.5" />
|
||||
<LoaderCircle
|
||||
size="1em"
|
||||
class="mx-0.5 inline animate-spin" />
|
||||
<span>
|
||||
{#if $ae_sess.api_download_kv[file_id]}
|
||||
{$ae_sess.api_download_kv[file_id]
|
||||
@@ -307,24 +323,28 @@
|
||||
{/if}
|
||||
</span>
|
||||
{:then result}
|
||||
{@const FileIcon = ae_util.file_extension_icon_lucide(event_file_obj.extension)}
|
||||
<FileIcon size="1em" class="inline mx-0.5" />
|
||||
{@const FileIcon =
|
||||
ae_util.file_extension_icon_lucide(
|
||||
event_file_obj.extension
|
||||
)}
|
||||
<FileIcon size="1em" class="mx-0.5 inline" />
|
||||
{event_file_obj.extension}
|
||||
{#if result === null || result === false}
|
||||
<span class="text-error-500"
|
||||
><AlertTriangle size="1em" class="inline mx-1" />Failed!</span
|
||||
>
|
||||
><AlertTriangle
|
||||
size="1em"
|
||||
class="mx-1 inline" />Failed!</span>
|
||||
{/if}
|
||||
{:catch error}
|
||||
<span class="text-error-500" title={error?.message}
|
||||
><AlertCircle size="1em" class="inline mx-0.5" />Error!</span
|
||||
>
|
||||
><AlertCircle
|
||||
size="1em"
|
||||
class="mx-0.5 inline" />Error!</span>
|
||||
{/await}
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="grow {text_size} {text_size_md} w-full max-w-full overflow-hidden text-ellipsis {btn_text_align}"
|
||||
>
|
||||
class="grow {text_size} {text_size_md} w-full max-w-full overflow-hidden text-ellipsis {btn_text_align}">
|
||||
{ae_util.shorten_string({
|
||||
string: event_file_obj.filename_no_ext,
|
||||
begin_length: 45,
|
||||
@@ -333,9 +353,8 @@
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="badge my-0 py-0.5 preset-tonal-success hover:preset-filled-success-500 text-xs xl:text-sm"
|
||||
class:hidden={!event_file_obj.file_purpose}
|
||||
>
|
||||
class="badge preset-tonal-success hover:preset-filled-success-500 my-0 py-0.5 text-xs xl:text-sm"
|
||||
class:hidden={!event_file_obj.file_purpose}>
|
||||
{event_file_obj.file_purpose}
|
||||
</span>
|
||||
{/snippet}
|
||||
@@ -344,9 +363,8 @@
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="event_file_meta grow text-sm text-gray-500 flex flex-col sm:flex-row gap-1 wrap items-center justify-between w-64 max-w-80 font-mono"
|
||||
class:hidden={hide_meta}
|
||||
>
|
||||
class="event_file_meta wrap flex w-64 max-w-80 grow flex-col items-center justify-between gap-1 font-mono text-sm text-gray-500 sm:flex-row"
|
||||
class:hidden={hide_meta}>
|
||||
<button
|
||||
type="button"
|
||||
onclick={async () => {
|
||||
@@ -366,33 +384,33 @@
|
||||
log_lvl
|
||||
});
|
||||
}}
|
||||
class="btn btn-sm transition-all group"
|
||||
class="btn btn-sm group transition-all"
|
||||
class:preset-tonal-success={event_file_obj?.open_in_os == 'win'}
|
||||
class:preset-tonal-warning={event_file_obj?.open_in_os == 'mac'}
|
||||
disabled={!$ae_loc.trusted_access}
|
||||
>
|
||||
{#if event_file_obj?.open_in_os == 'win'}<Monitor size="1em" class="m-1" />
|
||||
{:else if event_file_obj?.open_in_os == 'mac'}<Laptop size="1em" class="m-1" />
|
||||
disabled={!$ae_loc.trusted_access}>
|
||||
{#if event_file_obj?.open_in_os == 'win'}<Monitor
|
||||
size="1em"
|
||||
class="m-1" />
|
||||
{:else if event_file_obj?.open_in_os == 'mac'}<Laptop
|
||||
size="1em"
|
||||
class="m-1" />
|
||||
{:else}<FolderOpen size="1em" class="m-1" />{/if}
|
||||
</button>
|
||||
|
||||
<span
|
||||
class="event_file_created_on text-xs text-center flex flex-row gap-1 items-center justify-end w-24 md:w-44 preset-filled-surface-100-900 rounded px-1 py-0.5"
|
||||
class:hidden={hide_created_on}
|
||||
>
|
||||
class="event_file_created_on preset-filled-surface-100-900 flex w-24 flex-row items-center justify-end gap-1 rounded px-1 py-0.5 text-center text-xs md:w-44"
|
||||
class:hidden={hide_created_on}>
|
||||
<CalendarDays size="0.85em" class="inline" />
|
||||
<span class="w-18"
|
||||
>{ae_util.iso_datetime_formatter(
|
||||
event_file_obj.created_on,
|
||||
'date_short'
|
||||
)}</span
|
||||
>
|
||||
)}</span>
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="event_file_size text-xs text-center flex flex-row gap-1 items-center justify-end preset-filled-surface-100-900 w-22 max-w-28 rounded py-0.5"
|
||||
class:hidden={hide_size}
|
||||
>
|
||||
class="event_file_size preset-filled-surface-100-900 flex w-22 max-w-28 flex-row items-center justify-end gap-1 rounded py-0.5 text-center text-xs"
|
||||
class:hidden={hide_size}>
|
||||
<Save size="0.85em" class="inline" />
|
||||
{#if event_file_obj.file_size}{ae_util.format_bytes(
|
||||
event_file_obj.file_size
|
||||
|
||||
@@ -146,15 +146,14 @@
|
||||
<div
|
||||
class="
|
||||
event_launcher_menu
|
||||
shrink h-full w-full max-w-full
|
||||
flex flex-col flex-wrap gap-1 items-center justify-start
|
||||
flex h-full w-full max-w-full
|
||||
shrink flex-col flex-wrap items-center justify-start gap-1
|
||||
|
||||
"
|
||||
>
|
||||
">
|
||||
<!-- overflow-x-clip -->
|
||||
|
||||
{#if $lq__event_event_file_obj_li}
|
||||
<div class="w-full flex flex-col gap-0.5 overflow-y-auto">
|
||||
<div class="flex w-full flex-col gap-0.5 overflow-y-auto">
|
||||
<!-- <div class="text-xs text-neutral-800/80">
|
||||
<strong>
|
||||
Event Files:
|
||||
@@ -191,8 +190,7 @@
|
||||
}
|
||||
bind:modal__event_file_obj={
|
||||
$events_sess.launcher.modal__event_file_obj
|
||||
}
|
||||
/>
|
||||
} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -201,12 +199,11 @@
|
||||
<Menu_location_list_menu
|
||||
{lq__event_location_obj_li}
|
||||
slct_event_location_id={$events_slct.event_location_id}
|
||||
bind:loading__session_li_status
|
||||
/>
|
||||
bind:loading__session_li_status />
|
||||
{/if}
|
||||
|
||||
{#if $lq__location_event_file_obj_li}
|
||||
<div class="w-full flex flex-col gap-0.5">
|
||||
<div class="flex w-full flex-col gap-0.5">
|
||||
{#each $lq__location_event_file_obj_li as event_file_obj, index (event_file_obj.event_file_id)}
|
||||
<Event_launcher_file_cont
|
||||
event_file_id={event_file_obj.event_file_id}
|
||||
@@ -235,8 +232,7 @@
|
||||
}
|
||||
bind:modal__event_file_obj={
|
||||
$events_sess.launcher.modal__event_file_obj
|
||||
}
|
||||
/>
|
||||
} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -246,8 +242,7 @@
|
||||
bind:slct__event_session_id
|
||||
bind:loading__session_id_status
|
||||
{lq__event_session_obj_li}
|
||||
bind:trigger_reload__event_session_obj_id
|
||||
/>
|
||||
bind:trigger_reload__event_session_obj_id />
|
||||
{/if}
|
||||
|
||||
<Menu_launcher_controls />
|
||||
|
||||
@@ -53,17 +53,15 @@
|
||||
{#if $lq__event_file_obj_li && $lq__event_file_obj_li.length}
|
||||
<section class="event_presentation_file_list my-1">
|
||||
<div
|
||||
class="text-[10px] text-surface-600-400 uppercase font-bold tracking-wider opacity-70"
|
||||
>
|
||||
class="text-surface-600-400 text-[10px] font-bold tracking-wider uppercase opacity-70">
|
||||
Presentation Files:
|
||||
</div>
|
||||
<ul class="space-y-1">
|
||||
{#each $lq__event_file_obj_li as event_file_obj (event_file_obj.event_file_id)}
|
||||
<li
|
||||
class="flex flex-col md:flex-row flex-wrap gap-1 items-center justify-start"
|
||||
class="flex flex-col flex-wrap items-center justify-start gap-1 md:flex-row"
|
||||
class:hidden={!$events_loc.launcher
|
||||
.show_content__hidden_files && event_file_obj.hide}
|
||||
>
|
||||
.show_content__hidden_files && event_file_obj.hide}>
|
||||
<Event_launcher_file_cont
|
||||
event_file_id={event_file_obj.event_file_id}
|
||||
{event_file_obj}
|
||||
@@ -82,8 +80,7 @@
|
||||
}
|
||||
bind:modal__event_file_obj={
|
||||
$events_sess.launcher.modal__event_file_obj
|
||||
}
|
||||
/>
|
||||
} />
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
</strong>
|
||||
|
||||
{#if !lq__event_presenter_obj?.file_count}
|
||||
<p class="text-sm text-center text-gray-400">
|
||||
<p class="text-center text-sm text-gray-400">
|
||||
<!-- <span class="fas fa-exclamation"></span> -->
|
||||
No files to show for this presenter at this time.
|
||||
<!-- <span class="fas fa-exclamation"></span> -->
|
||||
@@ -76,7 +76,7 @@
|
||||
{#if $lq__event_file_obj_li && $lq__event_file_obj_li.length}
|
||||
<section class="event_session_file_list">
|
||||
<div>
|
||||
<div class="text-xs text-surface-600-400">
|
||||
<div class="text-surface-600-400 text-xs">
|
||||
<strong>
|
||||
<Archive size="1em" class="inline" />
|
||||
Presenter Files:
|
||||
@@ -89,10 +89,9 @@
|
||||
<ul class="space-y-1">
|
||||
{#each $lq__event_file_obj_li as event_file_obj, index (event_file_obj.event_file_id)}
|
||||
<li
|
||||
class="flex flex-col md:flex-row flex-wrap gap-1 items-center justify-start"
|
||||
class="flex flex-col flex-wrap items-center justify-start gap-1 md:flex-row"
|
||||
class:hidden={!$events_loc.launcher
|
||||
.show_content__hidden_files && event_file_obj.hide}
|
||||
>
|
||||
.show_content__hidden_files && event_file_obj.hide}>
|
||||
<Event_launcher_file_cont
|
||||
event_file_id={event_file_obj.event_file_id}
|
||||
{event_file_obj}
|
||||
@@ -110,8 +109,7 @@
|
||||
}
|
||||
bind:modal__event_file_obj={
|
||||
$events_sess.launcher.modal__event_file_obj
|
||||
}
|
||||
/>
|
||||
} />
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
@@ -83,10 +83,9 @@
|
||||
<ul class="space-y-1">
|
||||
{#each $lq__event_file_obj_li as event_file_obj, index (event_file_obj.event_file_id)}
|
||||
<li
|
||||
class="flex flex-col md:flex-row wrap gap items-center justify-center"
|
||||
class="wrap gap flex flex-col items-center justify-center md:flex-row"
|
||||
class:hidden={!$events_loc.launcher
|
||||
.show_content__hidden_files && event_file_obj.hide}
|
||||
>
|
||||
.show_content__hidden_files && event_file_obj.hide}>
|
||||
<Event_launcher_file_cont
|
||||
event_file_id={event_file_obj.event_file_id}
|
||||
{event_file_obj}
|
||||
@@ -102,8 +101,7 @@
|
||||
}
|
||||
bind:modal__event_file_obj={
|
||||
$events_sess.launcher.modal__event_file_obj
|
||||
}
|
||||
/>
|
||||
} />
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
@@ -39,7 +39,16 @@
|
||||
events_trigger
|
||||
} from '$lib/stores/ae_events_stores';
|
||||
import { events_func } from '$lib/ae_events/ae_events_functions';
|
||||
import { AlertTriangle, Archive, Barcode, Image, LoaderCircle, Monitor, User, Users } from '@lucide/svelte';
|
||||
import {
|
||||
AlertTriangle,
|
||||
Archive,
|
||||
Barcode,
|
||||
Image,
|
||||
LoaderCircle,
|
||||
Monitor,
|
||||
User,
|
||||
Users
|
||||
} from '@lucide/svelte';
|
||||
// Event Session (Main View Trigger)
|
||||
// WHY: We use a simple derived observable. The template handles the $ prefix.
|
||||
let lq__event_session_obj = $derived(
|
||||
@@ -60,7 +69,9 @@
|
||||
if (!slct__event_session_id) return [];
|
||||
|
||||
if (log_lvl > 1) {
|
||||
console.log(`[LQ] Fetching files for session: ${slct__event_session_id}`);
|
||||
console.log(
|
||||
`[LQ] Fetching files for session: ${slct__event_session_id}`
|
||||
);
|
||||
}
|
||||
|
||||
return await db_events.file
|
||||
@@ -77,7 +88,9 @@
|
||||
if (!slct__event_session_id) return [];
|
||||
|
||||
if (log_lvl > 1) {
|
||||
console.log(`[LQ] Fetching presentations for session: ${slct__event_session_id}`);
|
||||
console.log(
|
||||
`[LQ] Fetching presentations for session: ${slct__event_session_id}`
|
||||
);
|
||||
}
|
||||
|
||||
let sort_by = 'start_datetime';
|
||||
@@ -97,7 +110,9 @@
|
||||
if (!slct__event_session_id) return [];
|
||||
|
||||
if (log_lvl > 1) {
|
||||
console.log(`[LQ] Fetching presenters for session: ${slct__event_session_id}`);
|
||||
console.log(
|
||||
`[LQ] Fetching presenters for session: ${slct__event_session_id}`
|
||||
);
|
||||
}
|
||||
|
||||
return await db_events.presenter
|
||||
@@ -146,15 +161,14 @@
|
||||
<div
|
||||
class="
|
||||
event_launcher_session_view
|
||||
grow h-full w-full
|
||||
relative h-full w-full
|
||||
grow
|
||||
space-y-1
|
||||
relative
|
||||
"
|
||||
>
|
||||
">
|
||||
<!-- <slot name="event_session_message">event session message</slot> -->
|
||||
|
||||
{#if $events_sess.launcher.loading__session_id_status}
|
||||
<span class="absolute top-0 right-0 text-sm text-center text-gray-400">
|
||||
<span class="absolute top-0 right-0 text-center text-sm text-gray-400">
|
||||
<LoaderCircle size="1em" class="inline animate-spin" />
|
||||
Loading session information...
|
||||
</span>
|
||||
@@ -176,35 +190,33 @@
|
||||
collapse the header below that height. Zero layout shift between sessions.
|
||||
-->
|
||||
<header
|
||||
class="event_session_about border-b-2 border-gray-400 dark:border-gray-600 flex flex-col gap-0.5 items-stretch"
|
||||
>
|
||||
class="event_session_about flex flex-col items-stretch gap-0.5 border-b-2 border-gray-400 dark:border-gray-600">
|
||||
<h3
|
||||
class:hidden={!$lq__event_session_obj?.start_datetime ||
|
||||
$events_loc.launcher.hide__session_datetimes}
|
||||
class="event_session_datetimes text-sm text-center"
|
||||
>
|
||||
class="event_session_datetimes text-center text-sm">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
if (
|
||||
$events_loc.launcher.time_format == 'time_12_short'
|
||||
$events_loc.launcher.time_format ==
|
||||
'time_12_short'
|
||||
) {
|
||||
// $events_loc.launcher.datetime_format = 'datetime_long';
|
||||
$events_loc.launcher.time_format = 'time_short';
|
||||
$events_loc.launcher.time_hours = 24;
|
||||
} else {
|
||||
$events_loc.launcher.time_format = 'time_12_short';
|
||||
$events_loc.launcher.time_format =
|
||||
'time_12_short';
|
||||
// $events_loc.launcher.datetime_format = 'datetime_12_long';
|
||||
$events_loc.launcher.time_hours = 12;
|
||||
}
|
||||
}}
|
||||
>
|
||||
}}>
|
||||
<strong
|
||||
>{ae_util.iso_datetime_formatter(
|
||||
$lq__event_session_obj.start_datetime,
|
||||
'week_long'
|
||||
)}</strong
|
||||
>
|
||||
)}</strong>
|
||||
<span class="font-normal">
|
||||
{ae_util.iso_datetime_formatter(
|
||||
$lq__event_session_obj.start_datetime,
|
||||
@@ -215,8 +227,7 @@
|
||||
>{ae_util.iso_datetime_formatter(
|
||||
$lq__event_session_obj.start_datetime,
|
||||
$events_loc.launcher.time_format
|
||||
)}</strong
|
||||
>
|
||||
)}</strong>
|
||||
<span class="font-normal">
|
||||
–
|
||||
{ae_util.iso_datetime_formatter(
|
||||
@@ -228,21 +239,18 @@
|
||||
</h3>
|
||||
|
||||
<span
|
||||
class="w-full flex flex-row gap-2 items-center justify-between"
|
||||
>
|
||||
class="flex w-full flex-row items-center justify-between gap-2">
|
||||
<!-- grow + line-clamp-2 = stable 2-line max; title provides full text for screen readers + hover -->
|
||||
<h2
|
||||
class="grow text-xl line-clamp-2 min-w-0"
|
||||
title={`Name: ${$lq__event_session_obj.name}\nType: ${$lq__event_session_obj.type_code} \nCode: ${$lq__event_session_obj.code} \nID: ${$lq__event_session_obj.event_session_id} \nStart Date/Time: ${ae_util.iso_datetime_formatter($lq__event_session_obj.start_datetime, 'week_long')} ${ae_util.iso_datetime_formatter($lq__event_session_obj.start_datetime, $events_loc.launcher.time_format)} \nEnd Date/Time: ${ae_util.iso_datetime_formatter($lq__event_session_obj.end_datetime, $events_loc.launcher.time_format)}`}
|
||||
>
|
||||
class="line-clamp-2 min-w-0 grow text-xl"
|
||||
title={`Name: ${$lq__event_session_obj.name}\nType: ${$lq__event_session_obj.type_code} \nCode: ${$lq__event_session_obj.code} \nID: ${$lq__event_session_obj.event_session_id} \nStart Date/Time: ${ae_util.iso_datetime_formatter($lq__event_session_obj.start_datetime, 'week_long')} ${ae_util.iso_datetime_formatter($lq__event_session_obj.start_datetime, $events_loc.launcher.time_format)} \nEnd Date/Time: ${ae_util.iso_datetime_formatter($lq__event_session_obj.end_datetime, $events_loc.launcher.time_format)}`}>
|
||||
{$lq__event_session_obj?.name}
|
||||
</h2>
|
||||
{#if $lq__event_session_obj?.code}
|
||||
<!-- shrink-0: code badge never gets squeezed by a long name -->
|
||||
<span
|
||||
class="shrink-0 text-base text-gray-500 font-normal p-1"
|
||||
title="Session code {$lq__event_session_obj.code}"
|
||||
>
|
||||
class="shrink-0 p-1 text-base font-normal text-gray-500"
|
||||
title="Session code {$lq__event_session_obj.code}">
|
||||
<Barcode size="1em" class="inline" />
|
||||
{$lq__event_session_obj?.code}
|
||||
</span>
|
||||
@@ -255,7 +263,7 @@
|
||||
</section> -->
|
||||
|
||||
{#if $lq__event_session_obj?.file_count_all === 0}
|
||||
<p class="text-2xl text-center text-red-500 font-bold">
|
||||
<p class="text-center text-2xl font-bold text-red-500">
|
||||
<AlertTriangle size="1em" class="inline" />
|
||||
Warning
|
||||
<AlertTriangle size="1em" class="inline" />
|
||||
@@ -267,15 +275,14 @@
|
||||
{#if $lq__event_file_obj_li && $lq__event_file_obj_li.length}
|
||||
<section class="event_session_file_list">
|
||||
<div>
|
||||
<div class="text-xs text-surface-600-400">
|
||||
<div class="text-surface-600-400 text-xs">
|
||||
<strong>
|
||||
<Archive size="1em" class="inline" />
|
||||
Session Files:
|
||||
|
||||
<span
|
||||
class:hidden={!$ae_loc.trusted_access ||
|
||||
!$ae_loc.edit_mode}
|
||||
>
|
||||
!$ae_loc.edit_mode}>
|
||||
({$lq__event_file_obj_li?.length}×)
|
||||
</span>
|
||||
</strong>
|
||||
@@ -291,11 +298,10 @@
|
||||
<ul class="space-y-1">
|
||||
{#each $lq__event_file_obj_li as event_file_obj (event_file_obj.event_file_id)}
|
||||
<li
|
||||
class="flex flex-row flex-wrap gap-1 items-center justify-center"
|
||||
class="flex flex-row flex-wrap items-center justify-center gap-1"
|
||||
class:hidden={!$events_loc.launcher
|
||||
.show_content__hidden_files &&
|
||||
event_file_obj.hide}
|
||||
>
|
||||
event_file_obj.hide}>
|
||||
<Event_launcher_file_cont
|
||||
event_file_id={event_file_obj.event_file_id}
|
||||
{event_file_obj}
|
||||
@@ -303,7 +309,9 @@
|
||||
show_bak_download={$ae_loc.trusted_access &&
|
||||
$ae_loc.edit_mode}
|
||||
session_type={type_code || 'oral'}
|
||||
open_method={type_code == 'poster' ? 'modal' : null}
|
||||
open_method={type_code == 'poster'
|
||||
? 'modal'
|
||||
: null}
|
||||
modal_title={$lq__event_session_obj?.name}
|
||||
bind:modal__title={
|
||||
$events_sess.launcher.modal__title
|
||||
@@ -313,9 +321,9 @@
|
||||
.modal__open_event_file_id
|
||||
}
|
||||
bind:modal__event_file_obj={
|
||||
$events_sess.launcher.modal__event_file_obj
|
||||
}
|
||||
/>
|
||||
$events_sess.launcher
|
||||
.modal__event_file_obj
|
||||
} />
|
||||
|
||||
<!-- <Launcher_file_cont {event_file_obj} hide_created_on={false} show_bak_download={($ae_loc.trusted_access || $events_loc.launcher.trusted_access)} open_file_as={$lq__event_session_obj.type_code} poster_title={$lq__event_session_obj.title} /> -->
|
||||
|
||||
@@ -342,7 +350,7 @@
|
||||
<!-- {$lq__event_session_obj?.event_presentation_li?.length ?? 'loading...?'} -->
|
||||
|
||||
{#if $lq__event_presentation_obj_li}
|
||||
<div class="text-xs text-surface-600-400">
|
||||
<div class="text-surface-600-400 text-xs">
|
||||
<strong>
|
||||
{#if type_code == 'poster'}
|
||||
<Image size="1em" class="inline" />
|
||||
@@ -361,27 +369,24 @@
|
||||
<ul class="event_presentation_list max-w-full space-y-2">
|
||||
{#each $lq__event_presentation_obj_li as event_presentation_obj (event_presentation_obj.event_presentation_id)}
|
||||
<li
|
||||
class="border-b-2 border-gray-300 dark:border-gray-700 my-1 py-1 text-center md:text-left"
|
||||
>
|
||||
class="my-1 border-b-2 border-gray-300 py-1 text-center md:text-left dark:border-gray-700">
|
||||
<!-- The presentation information -->
|
||||
<div
|
||||
class="event_presentation_datetime_name flex flex-row justify-evenly gap-4"
|
||||
>
|
||||
class="event_presentation_datetime_name flex flex-row justify-evenly gap-4">
|
||||
<!-- <div class="event_presentation_datetime_name"> -->
|
||||
{#if event_presentation_obj?.start_datetime}
|
||||
<span class="event_presentation_datetime"
|
||||
<span
|
||||
class="event_presentation_datetime"
|
||||
><strong
|
||||
>{ae_util.iso_datetime_formatter(
|
||||
event_presentation_obj?.start_datetime,
|
||||
'time_12_short_no_leading'
|
||||
)}</strong
|
||||
></span
|
||||
>
|
||||
></span>
|
||||
{/if}
|
||||
|
||||
<span class="event_presentation_name grow"
|
||||
>{event_presentation_obj?.name}</span
|
||||
>
|
||||
>{event_presentation_obj?.name}</span>
|
||||
<!-- </div> -->
|
||||
|
||||
<!-- Yes, this is kind of inefficient, but it works for now. -->
|
||||
@@ -389,15 +394,18 @@
|
||||
{#each $lq__event_presenter_obj_li as event_presenter_obj, index (event_presenter_obj.event_presenter_id)}
|
||||
{#if event_presenter_obj.event_presentation_id == event_presentation_obj.event_presentation_id}
|
||||
<span
|
||||
class="event_presentation_single_presenter italic text-sm text-gray-500"
|
||||
>
|
||||
class="event_presentation_single_presenter text-sm text-gray-500 italic">
|
||||
{#if $lq__event_presenter_obj_li[index]?.given_name && $lq__event_presenter_obj_li[index]?.given_name != 'Group'}
|
||||
<User size="0.85em" class="inline" />
|
||||
<User
|
||||
size="0.85em"
|
||||
class="inline" />
|
||||
{$lq__event_presenter_obj_li[
|
||||
index
|
||||
]?.full_name}
|
||||
{:else if $lq__event_presenter_obj_li[index]?.given_name == 'Group'}
|
||||
<Users size="0.85em" class="inline" />
|
||||
<Users
|
||||
size="0.85em"
|
||||
class="inline" />
|
||||
{$lq__event_presenter_obj_li[
|
||||
index
|
||||
]?.affiliations}
|
||||
@@ -413,8 +421,7 @@
|
||||
<!-- Presentation-level files -->
|
||||
<Launcher_presentation_view
|
||||
lq__event_presentation_obj={event_presentation_obj}
|
||||
session_type={type_code}
|
||||
/>
|
||||
session_type={type_code} />
|
||||
|
||||
<!-- The presenter list -->
|
||||
<!-- WHY: In poster mode, presenter names are already shown inline
|
||||
@@ -426,29 +433,27 @@
|
||||
so this has no visual cost for events that use presentation-level files. -->
|
||||
|
||||
{#if $lq__event_presenter_obj_li && $lq__event_presenter_obj_li.length}
|
||||
<ul class="event_presentation_presenter_list">
|
||||
<ul
|
||||
class="event_presentation_presenter_list">
|
||||
{#each $lq__event_presenter_obj_li as event_presenter_obj (event_presenter_obj.event_presenter_id)}
|
||||
{#if event_presenter_obj.event_presentation_id == event_presentation_obj.event_presentation_id}
|
||||
<li
|
||||
class="
|
||||
border border-transparent
|
||||
hover:bg-surface-100-900 hover:border-surface-400-600
|
||||
rounded-lg
|
||||
hover:bg-surface-100-900
|
||||
hover:border-surface-400-600
|
||||
border
|
||||
border-transparent
|
||||
p-1
|
||||
transition-all
|
||||
"
|
||||
>
|
||||
">
|
||||
{#if type_code == 'poster'}
|
||||
<Launcher_presenter_view_posters
|
||||
lq__event_presenter_obj={event_presenter_obj}
|
||||
hide_name={true}
|
||||
/>
|
||||
hide_name={true} />
|
||||
{:else}
|
||||
<Launcher_presenter_view
|
||||
lq__event_presenter_obj={event_presenter_obj}
|
||||
session_type={type_code}
|
||||
/>
|
||||
session_type={type_code} />
|
||||
{/if}
|
||||
</li>
|
||||
{/if}
|
||||
|
||||
@@ -80,43 +80,42 @@
|
||||
The outer div mirrors the grow/h-full/w-full contract expected by
|
||||
+layout.svelte so this slots in as a drop-in replacement for the oral view.
|
||||
-->
|
||||
<div class="poster_session_view flex flex-col gap-0 w-full h-full overflow-hidden">
|
||||
|
||||
<div
|
||||
class="poster_session_view flex h-full w-full flex-col gap-0 overflow-hidden">
|
||||
{#if $events_sess.launcher?.loading__session_id_status}
|
||||
<span class="absolute top-0 right-0 text-sm text-gray-400 flex items-center gap-1 p-1 z-10">
|
||||
<span
|
||||
class="absolute top-0 right-0 z-10 flex items-center gap-1 p-1 text-sm text-gray-400">
|
||||
<LoaderCircle size="1em" class="animate-spin" />
|
||||
Loading...
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
{#if $lq__event_session_obj && $lq__event_session_obj.event_session_id}
|
||||
|
||||
<!-- ── Compact session identity strip ──────────────────────────────── -->
|
||||
<header
|
||||
class="
|
||||
poster_session_header
|
||||
flex flex-row gap-2 items-center justify-between
|
||||
px-2 py-1.5
|
||||
border-b-2 border-surface-300 dark:border-surface-600
|
||||
bg-surface-100/60 dark:bg-surface-800/60
|
||||
shrink-0
|
||||
"
|
||||
>
|
||||
border-surface-300 dark:border-surface-600 bg-surface-100/60 dark:bg-surface-800/60 flex
|
||||
shrink-0 flex-row
|
||||
items-center justify-between gap-2
|
||||
border-b-2 px-2
|
||||
py-1.5
|
||||
">
|
||||
<h2
|
||||
class="text-base font-bold line-clamp-1 grow min-w-0"
|
||||
title={$lq__event_session_obj.name}
|
||||
>
|
||||
<Images size="1em" class="inline mr-1.5 text-primary-500 opacity-70" />
|
||||
class="line-clamp-1 min-w-0 grow text-base font-bold"
|
||||
title={$lq__event_session_obj.name}>
|
||||
<Images
|
||||
size="1em"
|
||||
class="text-primary-500 mr-1.5 inline opacity-70" />
|
||||
{$lq__event_session_obj.name}
|
||||
</h2>
|
||||
|
||||
<span class="flex flex-row gap-1.5 items-center shrink-0">
|
||||
<span class="flex shrink-0 flex-row items-center gap-1.5">
|
||||
<!-- Poster count badge -->
|
||||
{#if poster_count > 0}
|
||||
<span
|
||||
class="text-xs font-mono font-semibold text-surface-500 bg-surface-200 dark:bg-surface-700 px-2 py-0.5 rounded-full"
|
||||
title="Number of posters in this session"
|
||||
>
|
||||
class="text-surface-500 bg-surface-200 dark:bg-surface-700 rounded-full px-2 py-0.5 font-mono text-xs font-semibold"
|
||||
title="Number of posters in this session">
|
||||
{poster_count}×
|
||||
</span>
|
||||
{/if}
|
||||
@@ -124,9 +123,8 @@
|
||||
<!-- Session code -->
|
||||
{#if $lq__event_session_obj.code}
|
||||
<span
|
||||
class="text-xs font-mono font-bold text-surface-400 bg-surface-100 dark:bg-surface-800 px-2 py-0.5 rounded border border-surface-300 dark:border-surface-600"
|
||||
title="Session code: {$lq__event_session_obj.code}"
|
||||
>
|
||||
class="text-surface-400 bg-surface-100 dark:bg-surface-800 border-surface-300 dark:border-surface-600 rounded border px-2 py-0.5 font-mono text-xs font-bold"
|
||||
title="Session code: {$lq__event_session_obj.code}">
|
||||
{$lq__event_session_obj.code}
|
||||
</span>
|
||||
{/if}
|
||||
@@ -135,16 +133,18 @@
|
||||
|
||||
<!-- ── Session-level files (rarely present — program, schedule, etc.) ── -->
|
||||
{#if $lq__event_file_obj_li && $lq__event_file_obj_li.length}
|
||||
<section class="session_resources_strip px-2 pb-2 pt-1 border-b border-surface-200 dark:border-surface-700 shrink-0">
|
||||
<p class="text-[10px] text-surface-500 uppercase font-bold tracking-wider mb-1 opacity-60">
|
||||
<section
|
||||
class="session_resources_strip border-surface-200 dark:border-surface-700 shrink-0 border-b px-2 pt-1 pb-2">
|
||||
<p
|
||||
class="text-surface-500 mb-1 text-[10px] font-bold tracking-wider uppercase opacity-60">
|
||||
Session Resources:
|
||||
</p>
|
||||
<ul class="flex flex-row flex-wrap gap-2">
|
||||
{#each $lq__event_file_obj_li as event_file_obj (event_file_obj.event_file_id)}
|
||||
<li
|
||||
class:hidden={!$events_loc.launcher.show_content__hidden_files &&
|
||||
event_file_obj.hide}
|
||||
>
|
||||
class:hidden={!$events_loc.launcher
|
||||
.show_content__hidden_files &&
|
||||
event_file_obj.hide}>
|
||||
<Event_launcher_file_cont
|
||||
event_file_id={event_file_obj.event_file_id}
|
||||
{event_file_obj}
|
||||
@@ -163,8 +163,7 @@
|
||||
}
|
||||
bind:modal__event_file_obj={
|
||||
$events_sess.launcher.modal__event_file_obj
|
||||
}
|
||||
/>
|
||||
} />
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
@@ -174,18 +173,20 @@
|
||||
<!-- ── Poster card grid ────────────────────────────────────────────── -->
|
||||
{#if $lq__event_presentation_obj_li === undefined}
|
||||
<!-- Still resolving from Dexie -->
|
||||
<div class="flex items-center justify-center gap-2 p-10 opacity-40 grow">
|
||||
<div
|
||||
class="flex grow items-center justify-center gap-2 p-10 opacity-40">
|
||||
<LoaderCircle size="2em" class="animate-spin" />
|
||||
<span>Loading posters…</span>
|
||||
</div>
|
||||
|
||||
{:else if $lq__event_presentation_obj_li.length === 0}
|
||||
<!-- Loaded but empty -->
|
||||
<div class="flex flex-col items-center justify-center gap-3 p-12 opacity-40 text-center grow">
|
||||
<div
|
||||
class="flex grow flex-col items-center justify-center gap-3 p-12 text-center opacity-40">
|
||||
<Image size="3em" />
|
||||
<p class="text-lg font-medium">No posters in this session yet.</p>
|
||||
<p class="text-lg font-medium">
|
||||
No posters in this session yet.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{:else}
|
||||
<!--
|
||||
Grid: 1 col on phone, 2 on tablet (sm), 3 on large desktop (xl).
|
||||
@@ -197,15 +198,14 @@
|
||||
class="
|
||||
poster_card_grid
|
||||
grid
|
||||
grow
|
||||
grid-cols-1
|
||||
gap-3
|
||||
overflow-y-auto
|
||||
p-3
|
||||
sm:grid-cols-2
|
||||
xl:grid-cols-3
|
||||
gap-3
|
||||
p-3
|
||||
overflow-y-auto
|
||||
grow
|
||||
"
|
||||
>
|
||||
">
|
||||
{#each $lq__event_presentation_obj_li as presentation, i (presentation.event_presentation_id)}
|
||||
{@const presenters_for_this = (
|
||||
$lq__event_presenter_obj_li ?? []
|
||||
@@ -223,18 +223,17 @@
|
||||
<li
|
||||
class="
|
||||
poster_card
|
||||
relative flex flex-col gap-2
|
||||
rounded-xl
|
||||
border border-surface-200 dark:border-surface-700
|
||||
bg-white dark:bg-surface-900
|
||||
hover:border-primary-400 dark:hover:border-primary-500
|
||||
active:scale-[0.98] active:shadow-sm
|
||||
transition-all duration-150
|
||||
shadow-sm hover:shadow-md
|
||||
p-3
|
||||
min-h-40
|
||||
"
|
||||
>
|
||||
border-surface-200 dark:border-surface-700 dark:bg-surface-900 hover:border-primary-400
|
||||
dark:hover:border-primary-500
|
||||
relative flex min-h-40
|
||||
flex-col gap-2
|
||||
rounded-xl border
|
||||
bg-white p-3
|
||||
shadow-sm transition-all
|
||||
duration-150 hover:shadow-md
|
||||
active:scale-[0.98]
|
||||
active:shadow-sm
|
||||
">
|
||||
<!--
|
||||
Top-right badge: prefer the presentation code (e.g. "P-042")
|
||||
as it matches physical poster board numbers; fall back to
|
||||
@@ -242,18 +241,17 @@
|
||||
-->
|
||||
<span
|
||||
class="
|
||||
absolute top-2 right-2
|
||||
text-xs font-mono font-bold leading-tight
|
||||
text-primary-600 dark:text-primary-400
|
||||
bg-primary-50 dark:bg-primary-950/60
|
||||
border border-primary-200 dark:border-primary-800
|
||||
px-2 py-0.5
|
||||
rounded-full
|
||||
text-primary-600 dark:text-primary-400 bg-primary-50
|
||||
dark:bg-primary-950/60 border-primary-200 dark:border-primary-800 absolute
|
||||
top-2 right-2
|
||||
rounded-full border
|
||||
px-2 py-0.5 font-mono
|
||||
text-xs leading-tight
|
||||
font-bold
|
||||
"
|
||||
title="{presentation.code
|
||||
title={presentation.code
|
||||
? 'Poster code: ' + presentation.code
|
||||
: 'Poster #' + (i + 1)}"
|
||||
>
|
||||
: 'Poster #' + (i + 1)}>
|
||||
{presentation.code || '#' + (i + 1)}
|
||||
</span>
|
||||
|
||||
@@ -266,15 +264,14 @@
|
||||
<h3
|
||||
class="
|
||||
poster_title
|
||||
text-base md:text-lg
|
||||
font-bold leading-snug
|
||||
line-clamp-3
|
||||
text-surface-950 dark:text-surface-50
|
||||
line-clamp-3 grow
|
||||
pr-14
|
||||
grow
|
||||
text-base leading-snug
|
||||
font-bold
|
||||
md:text-lg
|
||||
"
|
||||
title={presentation.name}
|
||||
>
|
||||
title={presentation.name}>
|
||||
{presentation.name}
|
||||
</h3>
|
||||
|
||||
@@ -286,37 +283,38 @@
|
||||
"Group" presenter whose full name is stored in affiliations.
|
||||
-->
|
||||
{#if presenters_for_this.length}
|
||||
<div class="presenter_info space-y-0.5 shrink-0">
|
||||
<div class="presenter_info shrink-0 space-y-0.5">
|
||||
{#each presenters_for_this as presenter (presenter.event_presenter_id)}
|
||||
<p
|
||||
class="
|
||||
flex flex-row flex-wrap items-baseline gap-x-1.5
|
||||
text-sm text-surface-500 dark:text-surface-400
|
||||
text-surface-500 dark:text-surface-400 flex flex-row flex-wrap
|
||||
items-baseline gap-x-1.5 text-sm
|
||||
leading-snug
|
||||
"
|
||||
>
|
||||
">
|
||||
{#if presenter.given_name && presenter.given_name !== 'Group'}
|
||||
<User size="0.7em" class="opacity-50 shrink-0 mt-px" />
|
||||
<User
|
||||
size="0.7em"
|
||||
class="mt-px shrink-0 opacity-50" />
|
||||
<span
|
||||
class="font-medium text-surface-700 dark:text-surface-300"
|
||||
>{presenter.full_name}</span
|
||||
>
|
||||
class="text-surface-700 dark:text-surface-300 font-medium"
|
||||
>{presenter.full_name}</span>
|
||||
{#if presenter.affiliations}
|
||||
<span
|
||||
class="italic text-xs opacity-70 line-clamp-1 min-w-0"
|
||||
title={presenter.affiliations}
|
||||
>
|
||||
class="line-clamp-1 min-w-0 text-xs italic opacity-70"
|
||||
title={presenter.affiliations}>
|
||||
— {presenter.affiliations}
|
||||
</span>
|
||||
{/if}
|
||||
{:else if presenter.given_name === 'Group'}
|
||||
<Users size="0.7em" class="opacity-50 shrink-0 mt-px" />
|
||||
<Users
|
||||
size="0.7em"
|
||||
class="mt-px shrink-0 opacity-50" />
|
||||
<span
|
||||
class="font-medium text-surface-700 dark:text-surface-300"
|
||||
>{presenter.affiliations}</span
|
||||
>
|
||||
class="text-surface-700 dark:text-surface-300 font-medium"
|
||||
>{presenter.affiliations}</span>
|
||||
{:else}
|
||||
<span class="opacity-40 text-xs">—</span>
|
||||
<span class="text-xs opacity-40"
|
||||
>—</span>
|
||||
{/if}
|
||||
</p>
|
||||
{/each}
|
||||
@@ -329,12 +327,12 @@
|
||||
presenter level, or both — render both sub-components so
|
||||
neither source is missed.
|
||||
-->
|
||||
<div class="poster_actions flex flex-col gap-1 mt-auto pt-1 shrink-0">
|
||||
<div
|
||||
class="poster_actions mt-auto flex shrink-0 flex-col gap-1 pt-1">
|
||||
<!-- Presentation-level files (the most common attachment point) -->
|
||||
<Launcher_presentation_view
|
||||
lq__event_presentation_obj={presentation}
|
||||
session_type="poster"
|
||||
/>
|
||||
session_type="poster" />
|
||||
|
||||
<!--
|
||||
Presenter-level files (some events attach the PDF here instead).
|
||||
@@ -344,18 +342,16 @@
|
||||
{#each presenters_for_this as presenter (presenter.event_presenter_id)}
|
||||
<Launcher_presenter_view_posters
|
||||
lq__event_presenter_obj={presenter}
|
||||
hide_name={true}
|
||||
/>
|
||||
hide_name={true} />
|
||||
{/each}
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
|
||||
{:else}
|
||||
<!-- No session selected or still loading -->
|
||||
<div class="flex items-center justify-center gap-2 p-8 opacity-40 grow">
|
||||
<div class="flex grow items-center justify-center gap-2 p-8 opacity-40">
|
||||
<LoaderCircle size="1em" class="animate-spin" />
|
||||
<span>No session selected</span>
|
||||
</div>
|
||||
|
||||
@@ -30,10 +30,11 @@
|
||||
let { log_lvl = $bindable(0) }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="w-full max-w-full flex flex-col gap-1 items-center justify-center">
|
||||
<div class="flex w-full max-w-full flex-col items-center justify-center gap-1">
|
||||
<!-- ── Visibility toggles — edit mode only ── -->
|
||||
{#if $ae_loc.edit_mode}
|
||||
<div class="w-full max-w-full flex flex-row gap-1 items-center justify-center">
|
||||
<div
|
||||
class="flex w-full max-w-full flex-row items-center justify-center gap-1">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
@@ -46,13 +47,12 @@
|
||||
}
|
||||
}}
|
||||
class="
|
||||
btn btn-sm text-xs
|
||||
w-1/2 max-w-1/2
|
||||
preset-tonal-tertiary hover:preset-filled-tertiary-500
|
||||
btn btn-sm preset-tonal-tertiary
|
||||
hover:preset-filled-tertiary-500 w-1/2
|
||||
max-w-1/2 text-xs
|
||||
transition-all
|
||||
"
|
||||
title="Toggle visibility of hidden and draft files in the launcher file list."
|
||||
>
|
||||
title="Toggle visibility of hidden and draft files in the launcher file list.">
|
||||
{#if $events_loc.launcher.show_content__hidden_files}
|
||||
<EyeOff size="0.85em" class="m-1 text-neutral-800/80" />
|
||||
Hide Files
|
||||
@@ -69,13 +69,12 @@
|
||||
!$events_loc.launcher.show_content__hidden_sessions;
|
||||
}}
|
||||
class="
|
||||
btn btn-sm text-xs
|
||||
w-1/2 max-w-1/2
|
||||
preset-tonal-tertiary hover:preset-filled-tertiary-500
|
||||
btn btn-sm preset-tonal-tertiary
|
||||
hover:preset-filled-tertiary-500 w-1/2
|
||||
max-w-1/2 text-xs
|
||||
transition-all
|
||||
"
|
||||
title="Toggle visibility of hidden and cancelled sessions in the launcher session list."
|
||||
>
|
||||
title="Toggle visibility of hidden and cancelled sessions in the launcher session list.">
|
||||
{#if $events_loc.launcher.show_content__hidden_sessions}
|
||||
<EyeOff size="0.85em" class="m-1 text-neutral-800/80" />
|
||||
Hide Sessions
|
||||
@@ -88,7 +87,8 @@
|
||||
{/if}
|
||||
|
||||
<!-- ── Accessibility controls — always visible ── -->
|
||||
<div class="w-full max-w-full flex flex-row gap-1 items-center justify-center">
|
||||
<div
|
||||
class="flex w-full max-w-full flex-row items-center justify-center gap-1">
|
||||
<!-- Font size cycler: default → larger → smaller → default -->
|
||||
<button
|
||||
type="button"
|
||||
@@ -103,22 +103,28 @@
|
||||
}
|
||||
}}
|
||||
class="
|
||||
btn btn-sm text-xs
|
||||
btn btn-sm preset-tonal-tertiary
|
||||
hover:preset-filled-tertiary-500 group
|
||||
w-1/2 max-w-1/2
|
||||
preset-tonal-tertiary hover:preset-filled-tertiary-500
|
||||
transition-all group
|
||||
text-xs transition-all
|
||||
"
|
||||
title="Cycle font size (default → larger → smaller). Current: {$ae_loc.font_size_mode ?? 'default'}"
|
||||
>
|
||||
title="Cycle font size (default → larger → smaller). Current: {$ae_loc.font_size_mode ??
|
||||
'default'}">
|
||||
{#if !$ae_loc.font_size_mode || $ae_loc.font_size_mode === 'default'}
|
||||
<span class="font-bold text-sm font-mono leading-none m-1">A</span>
|
||||
<span class="hidden group-hover:inline-block text-xs">Font: Normal</span>
|
||||
<span class="m-1 font-mono text-sm leading-none font-bold"
|
||||
>A</span>
|
||||
<span class="hidden text-xs group-hover:inline-block"
|
||||
>Font: Normal</span>
|
||||
{:else if $ae_loc.font_size_mode === 'larger'}
|
||||
<span class="font-bold text-base font-mono leading-none m-1">A+</span>
|
||||
<span class="hidden group-hover:inline-block text-xs">Font: Larger</span>
|
||||
<span class="m-1 font-mono text-base leading-none font-bold"
|
||||
>A+</span>
|
||||
<span class="hidden text-xs group-hover:inline-block"
|
||||
>Font: Larger</span>
|
||||
{:else}
|
||||
<span class="font-bold text-xs font-mono leading-none m-1">A−</span>
|
||||
<span class="hidden group-hover:inline-block text-xs">Font: Smaller</span>
|
||||
<span class="m-1 font-mono text-xs leading-none font-bold"
|
||||
>A−</span>
|
||||
<span class="hidden text-xs group-hover:inline-block"
|
||||
>Font: Smaller</span>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
@@ -126,16 +132,17 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
$ae_loc.theme_mode = $ae_loc.theme_mode === 'dark' ? 'light' : 'dark';
|
||||
$ae_loc.theme_mode =
|
||||
$ae_loc.theme_mode === 'dark' ? 'light' : 'dark';
|
||||
}}
|
||||
class="
|
||||
btn btn-sm text-xs
|
||||
btn btn-sm preset-tonal-tertiary
|
||||
hover:preset-filled-tertiary-500 group
|
||||
w-1/2 max-w-1/2
|
||||
preset-tonal-tertiary hover:preset-filled-tertiary-500
|
||||
transition-all group
|
||||
text-xs transition-all
|
||||
"
|
||||
title="Toggle light/dark display mode. Current: {$ae_loc.theme_mode ?? 'light'}"
|
||||
>
|
||||
title="Toggle light/dark display mode. Current: {$ae_loc.theme_mode ??
|
||||
'light'}">
|
||||
{#if $ae_loc.theme_mode === 'dark'}
|
||||
<Moon class="m-1 inline-block" size="1em" />
|
||||
<span class="hidden group-hover:inline-block">Dark Mode</span>
|
||||
|
||||
@@ -148,23 +148,24 @@
|
||||
<!-- text-neutral-800/80 -->
|
||||
<div
|
||||
class="
|
||||
w-full max-w-full
|
||||
flex flex-col md:flex-row flex-wrap gap-1 items-center justify-center
|
||||
"
|
||||
>
|
||||
flex w-full
|
||||
max-w-full flex-col flex-wrap items-center justify-center gap-1 md:flex-row
|
||||
">
|
||||
{#if $lq__event_location_obj_li && $lq__event_location_obj_li.length > 0}
|
||||
<div class="text-xs text-surface-600-400">
|
||||
<div class="text-surface-600-400 text-xs">
|
||||
<strong>
|
||||
Location:
|
||||
<span
|
||||
class:hidden={!$ae_loc.trusted_access || !$ae_loc.edit_mode}
|
||||
>
|
||||
class:hidden={!$ae_loc.trusted_access ||
|
||||
!$ae_loc.edit_mode}>
|
||||
({$lq__event_location_obj_li?.length}×)
|
||||
</span>
|
||||
|
||||
<!-- This should fade out once the data is loaded. -->
|
||||
{#await ae_promises[slct_event_location_id ?? '']}
|
||||
<LoaderCircle size="0.85em" class="inline animate-spin text-blue-500" />
|
||||
<LoaderCircle
|
||||
size="0.85em"
|
||||
class="inline animate-spin text-blue-500" />
|
||||
{:then result}
|
||||
<Check size="0.85em" class="inline text-green-500/80" />
|
||||
{/await}
|
||||
@@ -172,7 +173,7 @@
|
||||
</div>
|
||||
|
||||
<select
|
||||
class="select text-xs p-1 max-w-42"
|
||||
class="select max-w-42 p-1 text-xs"
|
||||
bind:value={slct_event_location_id}
|
||||
onchange={async () => {
|
||||
// console.log(`slct_event_location_id:`, slct_event_location_id);
|
||||
@@ -222,9 +223,8 @@
|
||||
loading__session_li_status = null;
|
||||
// goto(new_url, {replaceState: true}); // Updates the URL without reloading the page
|
||||
goto(new_url, { replaceState: false }); // Updates the URL history without reloading the page
|
||||
}}
|
||||
>
|
||||
<option value="" class="italic text-surface-800-200">
|
||||
}}>
|
||||
<option value="" class="text-surface-800-200 italic">
|
||||
-- select --
|
||||
</option>
|
||||
{#each $lq__event_location_obj_li as event_location_obj (event_location_obj.event_location_id)}
|
||||
|
||||
@@ -91,7 +91,14 @@
|
||||
events_trigger
|
||||
} from '$lib/stores/ae_events_stores';
|
||||
import { events_func } from '$lib/ae_events/ae_events_functions';
|
||||
import { CalendarCheck, CalendarDays, Check, EyeOff, Image, LoaderCircle } from '@lucide/svelte';
|
||||
import {
|
||||
CalendarCheck,
|
||||
CalendarDays,
|
||||
Check,
|
||||
EyeOff,
|
||||
Image,
|
||||
LoaderCircle
|
||||
} from '@lucide/svelte';
|
||||
// export let slct__event_session_id: any;
|
||||
|
||||
// *** Functions and Logic
|
||||
@@ -121,7 +128,9 @@
|
||||
const event_session_id = String(trigger_reload__event_session_obj_id);
|
||||
|
||||
if (log_lvl) {
|
||||
console.log(`[UI Trace] trigger_reload changed to: ${event_session_id}`);
|
||||
console.log(
|
||||
`[UI Trace] trigger_reload changed to: ${event_session_id}`
|
||||
);
|
||||
}
|
||||
|
||||
untrack(() => {
|
||||
@@ -154,7 +163,9 @@
|
||||
keepFocus: true
|
||||
}).then(() => {
|
||||
if (log_lvl)
|
||||
console.log(`🏁 [Trace] Navigation Roundtrip: ${(performance.now() - start).toFixed(2)}ms`);
|
||||
console.log(
|
||||
`🏁 [Trace] Navigation Roundtrip: ${(performance.now() - start).toFixed(2)}ms`
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -202,23 +213,24 @@
|
||||
|
||||
<div
|
||||
class="
|
||||
w-full max-w-80
|
||||
flex flex-col flex-wrap gap-1 items-center justify-start md:justify-center
|
||||
"
|
||||
>
|
||||
flex w-full
|
||||
max-w-80 flex-col flex-wrap items-center justify-start gap-1 md:justify-center
|
||||
">
|
||||
{#if $lq__event_session_obj_li && $lq__event_session_obj_li.length > 0}
|
||||
<div class="text-xs text-surface-600-400">
|
||||
<div class="text-surface-600-400 text-xs">
|
||||
<strong>
|
||||
Sessions:
|
||||
<span
|
||||
class:hidden={!$ae_loc.trusted_access || !$ae_loc.edit_mode}
|
||||
>
|
||||
class:hidden={!$ae_loc.trusted_access ||
|
||||
!$ae_loc.edit_mode}>
|
||||
({$lq__event_session_obj_li?.length}×)
|
||||
</span>
|
||||
|
||||
<!-- This should fade out once the data is loaded. -->
|
||||
{#await ae_promises.slct__event_session_id}
|
||||
<LoaderCircle size="0.85em" class="inline animate-spin text-blue-500" />
|
||||
<LoaderCircle
|
||||
size="0.85em"
|
||||
class="inline animate-spin text-blue-500" />
|
||||
{:then result}
|
||||
<Check size="0.85em" class="inline text-green-500/80" />
|
||||
{/await}
|
||||
@@ -227,22 +239,20 @@
|
||||
|
||||
<ul
|
||||
class="
|
||||
m-0 flex
|
||||
w-full max-w-full
|
||||
p-0 m-0
|
||||
flex flex-col gap-0 items-start justify-start
|
||||
"
|
||||
>
|
||||
flex-col items-start justify-start gap-0 p-0
|
||||
">
|
||||
{#each $lq__event_session_obj_li as event_session_obj (event_session_obj.event_session_id)}
|
||||
<li
|
||||
class="
|
||||
session-item
|
||||
relative
|
||||
p-0 m-0
|
||||
w-full max-w-full
|
||||
m-0 w-full
|
||||
max-w-full p-0
|
||||
"
|
||||
class:session-active={slct__event_session_id ===
|
||||
event_session_obj?.id}
|
||||
>
|
||||
event_session_obj?.id}>
|
||||
<button
|
||||
type="button"
|
||||
onmouseenter={() => {
|
||||
@@ -272,17 +282,17 @@
|
||||
class="
|
||||
session-btn
|
||||
btn btn-sm
|
||||
focus-visible:ring-2 focus-visible:ring-primary-400 focus-visible:ring-offset-1
|
||||
focus-visible:ring-primary-400 m-0 flex
|
||||
|
||||
text-sm
|
||||
w-full max-w-full
|
||||
text-left
|
||||
m-0
|
||||
px-1.5 py-1
|
||||
w-full
|
||||
max-w-full flex-row
|
||||
items-center
|
||||
justify-start
|
||||
rounded-md px-1.5
|
||||
|
||||
rounded-md
|
||||
flex flex-row items-center justify-start
|
||||
transition-colors duration-200
|
||||
py-1
|
||||
text-left text-sm transition-colors duration-200
|
||||
focus-visible:ring-2 focus-visible:ring-offset-1
|
||||
"
|
||||
class:preset-filled-primary={slct__event_session_id ===
|
||||
event_session_obj?.id}
|
||||
@@ -298,8 +308,7 @@
|
||||
event_session_obj?.hide_event_launcher)}
|
||||
class:opacity-40={event_session_obj?.hide ||
|
||||
event_session_obj?.hide_event_launcher}
|
||||
title={`Session: ${event_session_obj?.name}\nID: ${event_session_obj?.id} | ${ae_util.iso_datetime_formatter(event_session_obj?.start_datetime, $events_loc.launcher.time_format)}`}
|
||||
>
|
||||
title={`Session: ${event_session_obj?.name}\nID: ${event_session_obj?.id} | ${ae_util.iso_datetime_formatter(event_session_obj?.start_datetime, $events_loc.launcher.time_format)}`}>
|
||||
<!-- Session row layout: [date column | session name]
|
||||
Date column is fixed-width (shrink-0) so name column always
|
||||
gets consistent space regardless of date string length.
|
||||
@@ -311,8 +320,7 @@
|
||||
When revealed, dimmed (opacity-40) with eye-slash icon. -->
|
||||
|
||||
<span
|
||||
class="border-r border-surface-400-600 pr-1 min-w-20 shrink-0"
|
||||
>
|
||||
class="border-surface-400-600 min-w-20 shrink-0 border-r pr-1">
|
||||
{#if slct__event_session_id === event_session_obj?.id}
|
||||
<CalendarCheck size="0.85em" class="inline" />
|
||||
{:else}
|
||||
@@ -321,8 +329,7 @@
|
||||
<span
|
||||
class="text-xs"
|
||||
class:hidden={slct__event_session_id ===
|
||||
event_session_obj?.id}
|
||||
>
|
||||
event_session_obj?.id}>
|
||||
{ae_util.iso_datetime_formatter(
|
||||
event_session_obj?.start_datetime,
|
||||
'week_medium'
|
||||
@@ -339,20 +346,28 @@
|
||||
<span
|
||||
class="
|
||||
session-name
|
||||
grow text-sm
|
||||
min-w-0
|
||||
"
|
||||
>
|
||||
min-w-0 grow
|
||||
text-sm
|
||||
">
|
||||
{#if event_session_obj?.type_code == 'poster'}
|
||||
<span title="Digital Poster Session"><Image size="0.85em" class="inline mr-1 text-primary-500" /></span>
|
||||
<span title="Digital Poster Session"
|
||||
><Image
|
||||
size="0.85em"
|
||||
class="text-primary-500 mr-1 inline" /></span>
|
||||
{/if}
|
||||
<!-- Distinct icon styles distinguish the two hidden states:
|
||||
amber = hide (globally hidden — draft, cancelled, or admin-only)
|
||||
muted = hide_event_launcher (suppressed in Launcher view only) -->
|
||||
{#if event_session_obj?.hide}
|
||||
<span title="Hidden session"><EyeOff size="0.85em" class="inline mr-1 text-warning-600" /></span>
|
||||
<span title="Hidden session"
|
||||
><EyeOff
|
||||
size="0.85em"
|
||||
class="text-warning-600 mr-1 inline" /></span>
|
||||
{:else if event_session_obj?.hide_event_launcher}
|
||||
<span title="Hidden from Launcher"><EyeOff size="0.85em" class="inline mr-1 opacity-60" /></span>
|
||||
<span title="Hidden from Launcher"
|
||||
><EyeOff
|
||||
size="0.85em"
|
||||
class="mr-1 inline opacity-60" /></span>
|
||||
{/if}
|
||||
{event_session_obj?.name}
|
||||
</span>
|
||||
@@ -453,7 +468,9 @@
|
||||
background-color: #f1f5f9; /* slate-100 — solid light surface */
|
||||
border-left: 3px solid rgb(var(--color-primary-500, 99 102 241));
|
||||
border-radius: 0.375rem;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15), 0 1px 4px rgba(0, 0, 0, 0.08);
|
||||
box-shadow:
|
||||
0 4px 20px rgba(0, 0, 0, 0.15),
|
||||
0 1px 4px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
/* Dark mode overlay — solid dark surface, light readable text */
|
||||
@@ -461,7 +478,9 @@
|
||||
:global(.dark) .session-item:not(.session-active):focus-within .session-btn {
|
||||
background-color: #1e293b; /* slate-800 */
|
||||
color: #f1f5f9; /* slate-100 */
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.6), 0 1px 4px rgba(0, 0, 0, 0.4);
|
||||
box-shadow:
|
||||
0 4px 24px rgba(0, 0, 0, 0.6),
|
||||
0 1px 4px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
/* ── Session name: single-line truncated at rest ── */
|
||||
|
||||
Reference in New Issue
Block a user