feat(launcher): add Clear Cache and Reload Launcher buttons to controls bar

Fills in two new buttons added to menu_launcher_controls.svelte:
- Clear Cache: removes 'ae_events_loc' from localStorage and deletes the
  ae_events_db IndexedDB database, then reloads — clears stale launch state
  without touching downloaded file cache or user prefs (theme/font size).
- Reload Launcher: calls native.window_control({ action: 'reload' }) in
  Electron, falls back to window.location.reload() in browser mode.

Also fixes a stray 'lucide-svelte' import (merged Recycle into '@lucide/svelte')
and separates cache_status from reset_apps_status so button labels stay correct
when multiple actions fire.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-05-22 19:02:26 -04:00
parent b4d0d82141
commit 9c83567430

View File

@@ -23,7 +23,7 @@
* mode or navigating the config menu. This button is intentionally prominent.
*/
import { Moon, Sun, Eye, EyeOff, Columns2, Copy, XCircle } from '@lucide/svelte';
import { Moon, Sun, Eye, EyeOff, Columns2, Copy, XCircle, Trash, Recycle } from '@lucide/svelte';
import { ae_loc } from '$lib/stores/ae_stores';
import { events_loc } from '$lib/stores/ae_events_stores';
@@ -69,6 +69,7 @@ const DEFAULT_KILL_LIST = [
];
let reset_apps_status = $state('');
let cache_status = $state('');
async function handle_reset_apps() {
const native_device = ($ae_loc as any).native_device ?? null;
@@ -79,77 +80,100 @@ async function handle_reset_apps() {
reset_apps_status = 'Done';
setTimeout(() => (reset_apps_status = ''), 3000);
}
async function handle_cache_cleanup() {
cache_status = 'Clearing...';
try {
localStorage.removeItem('ae_events_loc');
indexedDB.deleteDatabase('ae_events_db');
cache_status = 'Done — reloading...';
setTimeout(() => window.location.reload(), 800);
} catch {
cache_status = 'Error';
setTimeout(() => (cache_status = ''), 3000);
}
}
function handle_reload_launcher() {
if ($ae_loc.is_native) {
native.window_control({ action: 'reload' });
} else {
window.location.reload();
}
}
</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.
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"
onclick={() => {
if ($events_loc.launcher.show_content__hidden_files) {
$events_loc.launcher.show_content__hidden_files = false;
$events_loc.launcher.show_content__internal_files = false;
} else {
$events_loc.launcher.show_content__hidden_files = true;
$events_loc.launcher.show_content__internal_files = true;
}
}}
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
{:else}
<Eye size="0.85em" class="m-1" />
All Files
{/if}
</button>
<div class="flex flex-row flex-wrap w-full max-w-full items-center justify-center gap-0.5">
<button
type="button"
onclick={() => {
$events_loc.launcher.show_content__hidden_sessions =
!$events_loc.launcher.show_content__hidden_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
{:else}
<Eye size="0.85em" class="m-1" />
All Sessions
{/if}
</button>
</div>
<!-- All Files / All Sessions toggles.
Warning color when showing hidden content — signals non-default state to operators. -->
<div class="min-w-32 flex flex-row flex-wrap items-center justify-center gap-0.5">
<button
type="button"
onclick={() => {
if ($events_loc.launcher.show_content__hidden_files) {
$events_loc.launcher.show_content__hidden_files = false;
$events_loc.launcher.show_content__internal_files = false;
} else {
$events_loc.launcher.show_content__hidden_files = true;
$events_loc.launcher.show_content__internal_files = true;
}
}}
class="btn btn-sm min-w-34 w-34 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
{:else}
<Eye size="0.85em" class="m-1" />
All Files
{/if}
</button>
{#if $ae_loc.edit_mode}
<button
type="button"
onclick={() => {
$events_loc.launcher.show_content__hidden_sessions =
!$events_loc.launcher.show_content__hidden_sessions;
}}
class="btn btn-sm min-w-34 w-34 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="shrink-0" />
Hide Sessions
{:else}
<Eye size="0.85em" class="shrink-0" />
All Sessions
{/if}
</button>
{/if}
</div>
<div class="min-w-32 flex flex-row flex-wrap items-center justify-center gap-0.5">
<!-- 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. -->
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}
class="btn btn-sm w-full text-xs transition-all"
class="btn btn-sm min-w-34 w-34 max-w-1/2 text-xs transition-all"
class:preset-tonal-tertiary={quick_display_mode === 'extend'}
class:hover:preset-filled-tertiary-500={quick_display_mode === 'extend'}
class:preset-tonal-warning={quick_display_mode === 'mirror'}
@@ -158,29 +182,55 @@ async function handle_reset_apps() {
? '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" />
<Columns2 size="0.85em" class="shrink-0" />
Display: Extend
{:else}
<Copy size="0.85em" class="m-1" />
<Copy size="0.85em" class="shrink-0" />
Display: Mirror
{/if}
</button>
{/if}
</div>
<!-- ── Always-visible controls ── -->
<div class="w-full flex flex-row flex-wrap items-center justify-center gap-0.5">
<!-- 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>
{#if $ae_loc.edit_mode}
<button
type="button"
onclick={handle_cache_cleanup}
class="btn btn-sm preset-tonal-error hover:preset-filled-error-500 w-34 max-w-1/2 text-xs transition-all"
title="Clear localStorage and IDB caches used by the Launcher. Does *not* delete cached files."
>
<Trash size="0.85em" class="shrink-0" />
{cache_status || 'Clear Cache'}
</button>
{/if}
<div class="flex w-full max-w-full flex-row items-center justify-center gap-1">
<button
type="button"
onclick={handle_reload_launcher}
class="btn btn-sm preset-tonal-error hover:preset-filled-error-500 w-34 max-w-1/2 text-xs transition-all"
title="Reload the Launcher interface."
>
<Recycle size="0.85em" class="shrink-0" />
Reload Launcher
</button>
<!-- 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-34 max-w-1/2 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="shrink-0" />
{reset_apps_status || 'Reset Apps'}
</button>
</div>
<div class="w-full flex flex-row flex-wrap items-center justify-center gap-0.5">
<!-- Font size cycler: default → larger → smaller → default -->
<button
type="button"
@@ -194,7 +244,7 @@ async function handle_reset_apps() {
$ae_loc.font_size_mode = 'default';
}
}}
class="btn btn-sm preset-tonal-tertiary hover:preset-filled-tertiary-500 group w-1/2 max-w-1/2 text-xs transition-all"
class="btn btn-sm preset-tonal-tertiary hover:preset-filled-tertiary-500 group min-w-32 max-w-1/2 text-xs transition-all"
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'}
@@ -215,7 +265,7 @@ async function handle_reset_apps() {
onclick={() => {
$ae_loc.theme_mode = $ae_loc.theme_mode === 'dark' ? 'light' : 'dark';
}}
class="btn btn-sm preset-tonal-tertiary hover:preset-filled-tertiary-500 group w-1/2 max-w-1/2 text-xs transition-all"
class="btn btn-sm preset-tonal-tertiary hover:preset-filled-tertiary-500 group min-w-32 max-w-1/2 text-xs transition-all"
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" />