feat(launcher): Oral/Poster Kiosk mode preset toggle + ae_mode WS command

Adds a two-button Session Mode Preset toggle in Display & App Modes cfg:
- 'Oral / Default' restores all menus/headers/iframe off
- 'Poster Kiosk'   sets iframe=true + hides menu, header, footer

When WS is connected (local_push or remote controller), tapping a preset
sends ae_mode:poster / ae_mode:oral to all connected devices so an operator
can reconfigure the whole room from one device.

ae_mode:{poster|oral} command handler added to handle_ws_recv() in
+layout.svelte — receives and applies the same preset on remote devices.
This commit is contained in:
Scott Idem
2026-03-13 13:58:37 -04:00
parent 1417fafcd3
commit ce0c8b03c9
2 changed files with 91 additions and 1 deletions

View File

@@ -1,12 +1,55 @@
<script lang="ts">
import { ae_loc } from '$lib/stores/ae_stores';
import { events_loc } from '$lib/stores/ae_events_stores';
import { events_loc, events_sess } from '$lib/stores/ae_events_stores';
import Launcher_Cfg_Section from './launcher_cfg_section.svelte';
interface Props {
on_expand?: () => void;
}
let { on_expand }: Props = $props();
// Poster Kiosk mode presets:
// iframe=true hides global site chrome
// hide__launcher_menu/header/footer removes all launcher panels
// Oral/Default restores everything.
// WHY: A single tap lets event staff reconfigure a device (or all WS-connected
// remote devices) for a session type without touching individual toggles.
const POSTER_PRESET = {
iframe: true,
hide__launcher_menu: true,
hide__launcher_header: true,
hide__launcher_footer: true
};
const ORAL_PRESET = {
iframe: false,
hide__launcher_menu: false,
hide__launcher_header: false,
hide__launcher_footer: false
};
// Detect current mode: if both iframe AND hide_menu are on, we're in poster mode.
// Individual overrides are still possible via the checkboxes below.
let is_poster_mode = $derived(
$ae_loc.iframe === true && $events_loc.launcher.hide__launcher_menu === true
);
function apply_mode(mode: 'poster' | 'oral') {
const preset = mode === 'poster' ? POSTER_PRESET : ORAL_PRESET;
$ae_loc.iframe = preset.iframe;
$events_loc.launcher.hide__launcher_menu = preset.hide__launcher_menu;
$events_loc.launcher.hide__launcher_header = preset.hide__launcher_header;
$events_loc.launcher.hide__launcher_footer = preset.hide__launcher_footer;
// Push to WS-connected remote devices when we're acting as a controller.
// 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_sess.launcher.controller_cmd = `ae_mode:${mode}`;
$events_sess.launcher.controller_trigger_send = 'trigger';
}
}
</script>
<Launcher_Cfg_Section
@@ -18,6 +61,38 @@
>
<!-- 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">
<button
type="button"
onclick={() => apply_mode('oral')}
class="btn btn-xs font-bold text-[10px]"
class:preset-filled-secondary={!is_poster_mode}
class:preset-tonal-surface={is_poster_mode}
title="Standard oral/presentation layout — menus and headers visible"
>
<span class="fas fa-chalkboard-teacher mr-1 opacity-70"></span>
Oral / Default
</button>
<button
type="button"
onclick={() => apply_mode('poster')}
class="btn btn-xs font-bold text-[10px]"
class:preset-filled-primary={is_poster_mode}
class:preset-tonal-surface={!is_poster_mode}
title="Digital Poster kiosk — hides site chrome, menu, header & footer"
>
<span class="fas fa-id-badge mr-1 opacity-70"></span>
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>
{/if}
</div>
<!-- 1. App Mode Selection -->
<div class="flex flex-col gap-1">
<p class="text-[9px] font-bold uppercase opacity-50 ml-1"

View File

@@ -415,6 +415,21 @@
else if (zoom_target === 'zoom') modal_zoom_fit = false;
} else if (cmd.startsWith('ae_refresh:')) {
if (cmd.split(':')[1] == 'now') location.reload();
} else if (cmd.startsWith('ae_mode:')) {
// WHY: Allows a controller to remotely push a display mode preset
// (e.g. switch all poster kiosks into kiosk mode without touching each device).
const mode_target = cmd.split(':')[1];
if (mode_target === 'poster') {
$ae_loc.iframe = true;
$events_loc.launcher.hide__launcher_menu = true;
$events_loc.launcher.hide__launcher_header = true;
$events_loc.launcher.hide__launcher_footer = true;
} else if (mode_target === 'oral') {
$ae_loc.iframe = false;
$events_loc.launcher.hide__launcher_menu = false;
$events_loc.launcher.hide__launcher_header = false;
$events_loc.launcher.hide__launcher_footer = false;
}
}
}
}