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. * 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 { ae_loc } from '$lib/stores/ae_stores';
import { events_loc } from '$lib/stores/ae_events_stores'; import { events_loc } from '$lib/stores/ae_events_stores';
@@ -69,6 +69,7 @@ const DEFAULT_KILL_LIST = [
]; ];
let reset_apps_status = $state(''); let reset_apps_status = $state('');
let cache_status = $state('');
async function handle_reset_apps() { async function handle_reset_apps() {
const native_device = ($ae_loc as any).native_device ?? null; const native_device = ($ae_loc as any).native_device ?? null;
@@ -79,77 +80,100 @@ async function handle_reset_apps() {
reset_apps_status = 'Done'; reset_apps_status = 'Done';
setTimeout(() => (reset_apps_status = ''), 3000); 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> </script>
<div class="flex w-full max-w-full flex-col items-center justify-center gap-1"> <div class="flex flex-row flex-wrap w-full max-w-full items-center justify-center gap-0.5">
<!-- ── 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>
<button <!-- All Files / All Sessions toggles.
type="button" Warning color when showing hidden content — signals non-default state to operators. -->
onclick={() => { <div class="min-w-32 flex flex-row flex-wrap items-center justify-center gap-0.5">
$events_loc.launcher.show_content__hidden_sessions = <button
!$events_loc.launcher.show_content__hidden_sessions; type="button"
}} onclick={() => {
class="btn btn-sm w-1/2 max-w-1/2 text-xs transition-all" if ($events_loc.launcher.show_content__hidden_files) {
class:preset-tonal-warning={$events_loc.launcher.show_content__hidden_sessions} $events_loc.launcher.show_content__hidden_files = false;
class:hover:preset-filled-warning-500={$events_loc.launcher $events_loc.launcher.show_content__internal_files = false;
.show_content__hidden_sessions} } else {
class:preset-tonal-tertiary={!$events_loc.launcher.show_content__hidden_sessions} $events_loc.launcher.show_content__hidden_files = true;
class:hover:preset-filled-tertiary-500={!$events_loc.launcher $events_loc.launcher.show_content__internal_files = true;
.show_content__hidden_sessions} }
title={$events_loc.launcher.show_content__hidden_sessions }}
? 'Showing all sessions including cancelled and hidden. Tap to hide them again.' class="btn btn-sm min-w-34 w-34 max-w-1/2 text-xs transition-all"
: 'Showing only active sessions. Tap to show all sessions including hidden and cancelled.'}> class:preset-tonal-warning={$events_loc.launcher.show_content__hidden_files}
{#if $events_loc.launcher.show_content__hidden_sessions} class:hover:preset-filled-warning-500={$events_loc.launcher
<EyeOff size="0.85em" class="m-1" /> .show_content__hidden_files}
Hide Sessions class:preset-tonal-tertiary={!$events_loc.launcher.show_content__hidden_files}
{:else} class:hover:preset-filled-tertiary-500={!$events_loc.launcher
<Eye size="0.85em" class="m-1" /> .show_content__hidden_files}
All Sessions title={$events_loc.launcher.show_content__hidden_files
{/if} ? 'Showing all files including hidden and draft. Tap to hide them again.'
</button> : 'Showing only public files. Tap to show all files including hidden and draft.'}>
</div> {#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). <!-- Display mode toggle — warning color when mirroring (non-default operator setting).
Tooltip describes the CURRENT state and what pressing will do, so operators know Tooltip describes the CURRENT state and what pressing will do, so operators know
which way they are switching before they tap. --> which way they are switching before they tap. -->
<button <button
type="button" type="button"
onclick={toggle_display_mode} 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:preset-tonal-tertiary={quick_display_mode === 'extend'}
class:hover:preset-filled-tertiary-500={quick_display_mode === 'extend'} class:hover:preset-filled-tertiary-500={quick_display_mode === 'extend'}
class:preset-tonal-warning={quick_display_mode === 'mirror'} 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 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.'}> : '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'} {#if quick_display_mode === 'extend'}
<Columns2 size="0.85em" class="m-1" /> <Columns2 size="0.85em" class="shrink-0" />
Display: Extend Display: Extend
{:else} {:else}
<Copy size="0.85em" class="m-1" /> <Copy size="0.85em" class="shrink-0" />
Display: Mirror Display: Mirror
{/if} {/if}
</button> </button>
{/if} </div>
<!-- ── Always-visible controls ── --> <!-- ── 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. {#if $ae_loc.edit_mode}
Essential recovery tool for presenters stuck on stage with a frozen app. --> <button
<button type="button"
type="button" onclick={handle_cache_cleanup}
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"
class="btn btn-sm preset-tonal-error hover:preset-filled-error-500 w-full text-xs transition-all" title="Clear localStorage and IDB caches used by the Launcher. Does *not* delete cached files."
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" /> <Trash size="0.85em" class="shrink-0" />
{reset_apps_status || 'Reset Apps'} {cache_status || 'Clear Cache'}
</button> </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 --> <!-- Font size cycler: default → larger → smaller → default -->
<button <button
type="button" type="button"
@@ -194,7 +244,7 @@ async function handle_reset_apps() {
$ae_loc.font_size_mode = 'default'; $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 ?? title="Cycle font size (default → larger → smaller). Current: {$ae_loc.font_size_mode ??
'default'}"> 'default'}">
{#if !$ae_loc.font_size_mode || $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={() => { 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 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'}"> title="Toggle light/dark display mode. Current: {$ae_loc.theme_mode ?? 'light'}">
{#if $ae_loc.theme_mode === 'dark'} {#if $ae_loc.theme_mode === 'dark'}
<Moon class="m-1 inline-block" size="1em" /> <Moon class="m-1 inline-block" size="1em" />