feat(launcher): move Reset Apps to always-visible controls bar

Adds a presenter-accessible "Reset Apps" button to menu_launcher_controls
that is always visible (no edit mode required). Kills presentation apps
(PowerPoint, Keynote, Acrobat, VLC, soffice) — critical recovery path for
presenters stuck on stage with a frozen app.

Also: warning colors on All Files / All Sessions when showing non-default
(hidden) content, and state-aware tooltips on the Display Mode toggle that
describe current state and what pressing will do.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-05-22 18:02:22 -04:00
parent dddf4b6170
commit 15bfe6d5d6

View File

@@ -10,15 +10,20 @@
*
* SECTIONS:
* 1. Visibility toggles (edit mode only) — show/hide draft files and hidden sessions
* 2. Accessibility controls (always visible) — font size cycler and light/dark toggle
* 2. Accessibility controls (always visible) — Reset Apps, font size cycler, light/dark toggle
*
* WHY ALWAYS-VISIBLE ACCESSIBILITY CONTROLS:
* Projector-connected screens, tablets, and phones at conference venues vary wildly
* in lighting. Operators and presenters need quick one-tap access to font size and
* theme mode without hunting through the system menu or requiring admin access.
*
* WHY RESET APPS IS ALWAYS VISIBLE:
* If a presentation app (PowerPoint, Keynote, etc.) hangs or stalls on stage, the
* presenter needs a one-tap way to force-close it and recover — without entering edit
* mode or navigating the config menu. This button is intentionally prominent.
*/
import { Moon, Sun, Eye, EyeOff, Columns2, Copy } from '@lucide/svelte';
import { Moon, Sun, Eye, EyeOff, Columns2, Copy, XCircle } from '@lucide/svelte';
import { ae_loc } from '$lib/stores/ae_stores';
import { events_loc } from '$lib/stores/ae_events_stores';
@@ -52,12 +57,35 @@ async function toggle_display_mode() {
($events_loc.launcher as any).display_mode = next;
}
}
// Process names closed when presenter hits "Reset Apps". Covers the standard
// conference app set. Override per device via event_device.other_json.launcher.kill_process_li.
const DEFAULT_KILL_LIST = [
'Microsoft PowerPoint',
'Keynote',
'Adobe Acrobat Reader DC',
'VLC',
'soffice'
];
let reset_apps_status = $state('');
async function handle_reset_apps() {
const native_device = ($ae_loc as any).native_device ?? null;
const process_name_li: string[] =
native_device?.other_json?.launcher?.kill_process_li ?? DEFAULT_KILL_LIST;
reset_apps_status = 'Resetting...';
await native.kill_processes({ process_name_li });
reset_apps_status = 'Done';
setTimeout(() => (reset_apps_status = ''), 3000);
}
</script>
<div class="flex w-full max-w-full flex-col items-center justify-center gap-1">
<!-- ── Edit mode controls ── -->
{#if $ae_loc.edit_mode}
<!-- All Files / All Sessions toggles -->
<!-- All Files / All Sessions toggles.
Warning color when showing hidden content — signals non-default state to operators. -->
<div class="flex w-full max-w-full flex-row items-center justify-center gap-1">
<button
type="button"
@@ -70,8 +98,16 @@ async function toggle_display_mode() {
$events_loc.launcher.show_content__internal_files = true;
}
}}
class="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.">
class="btn btn-sm w-1/2 max-w-1/2 text-xs transition-all"
class:preset-tonal-warning={$events_loc.launcher.show_content__hidden_files}
class:hover:preset-filled-warning-500={$events_loc.launcher
.show_content__hidden_files}
class:preset-tonal-tertiary={!$events_loc.launcher.show_content__hidden_files}
class:hover:preset-filled-tertiary-500={!$events_loc.launcher
.show_content__hidden_files}
title={$events_loc.launcher.show_content__hidden_files
? 'Showing all files including hidden and draft. Tap to hide them again.'
: 'Showing only public files. Tap to show all files including hidden and draft.'}>
{#if $events_loc.launcher.show_content__hidden_files}
<EyeOff size="0.85em" class="m-1" />
Hide Files
@@ -87,8 +123,16 @@ async function toggle_display_mode() {
$events_loc.launcher.show_content__hidden_sessions =
!$events_loc.launcher.show_content__hidden_sessions;
}}
class="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.">
class="btn btn-sm w-1/2 max-w-1/2 text-xs transition-all"
class:preset-tonal-warning={$events_loc.launcher.show_content__hidden_sessions}
class:hover:preset-filled-warning-500={$events_loc.launcher
.show_content__hidden_sessions}
class:preset-tonal-tertiary={!$events_loc.launcher.show_content__hidden_sessions}
class:hover:preset-filled-tertiary-500={!$events_loc.launcher
.show_content__hidden_sessions}
title={$events_loc.launcher.show_content__hidden_sessions
? 'Showing all sessions including cancelled and hidden. Tap to hide them again.'
: 'Showing only active sessions. Tap to show all sessions including hidden and cancelled.'}>
{#if $events_loc.launcher.show_content__hidden_sessions}
<EyeOff size="0.85em" class="m-1" />
Hide Sessions
@@ -99,7 +143,9 @@ async function toggle_display_mode() {
</button>
</div>
<!-- Display mode: single toggle button — tertiary when extending (normal), warning when mirroring (special) -->
<!-- Display mode toggle — warning color when mirroring (non-default operator setting).
Tooltip describes the CURRENT state and what pressing will do, so operators know
which way they are switching before they tap. -->
<button
type="button"
onclick={toggle_display_mode}
@@ -108,7 +154,9 @@ async function toggle_display_mode() {
class:hover:preset-filled-tertiary-500={quick_display_mode === 'extend'}
class:preset-tonal-warning={quick_display_mode === 'mirror'}
class:hover:preset-filled-warning-500={quick_display_mode === 'mirror'}
title="Toggle display layout between Extend (separate screens) and Mirror (same content on both).">
title={quick_display_mode === 'extend'
? 'Screens are extended: laptop can show notes while projector shows slides. Tap to mirror — make both screens show the same content.'
: 'Screens are mirrored: both screens show the same content. Tap to extend — allow the laptop and projector to show different content.'}>
{#if quick_display_mode === 'extend'}
<Columns2 size="0.85em" class="m-1" />
Display: Extend
@@ -119,7 +167,19 @@ async function toggle_display_mode() {
</button>
{/if}
<!-- ── Accessibility controls — always visible ── -->
<!-- ── Always-visible controls ── -->
<!-- Reset Apps: force-closes hung presentation apps without requiring edit mode.
Essential recovery tool for presenters stuck on stage with a frozen app. -->
<button
type="button"
onclick={handle_reset_apps}
class="btn btn-sm preset-tonal-error hover:preset-filled-error-500 w-full text-xs transition-all"
title="Close all presentation apps (PowerPoint, Keynote, Adobe Acrobat, VLC). Use this if a presentation is frozen or stuck.">
<XCircle size="0.85em" class="m-1 shrink-0" />
{reset_apps_status || 'Reset Apps'}
</button>
<div class="flex w-full max-w-full flex-row items-center justify-center gap-1">
<!-- Font size cycler: default → larger → smaller → default -->
<button
@@ -135,7 +195,8 @@ async function toggle_display_mode() {
}
}}
class="btn btn-sm preset-tonal-tertiary hover:preset-filled-tertiary-500 group w-1/2 max-w-1/2 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="m-1 font-mono text-sm leading-none font-bold">A</span>
<span class="hidden text-xs group-hover:inline-block">Font: Normal</span>