From b18cda98b72f56a0338e1a7c99aebbf12f99929d Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Thu, 12 Mar 2026 19:57:33 -0400 Subject: [PATCH] feat(launcher): poster modal zoom sync, clean close buttons - Add modal_zoom_fit state (default: fit); resets on every new poster open - Zoom/Fit toggle button + image double-tap on controller tablet - Both zoom triggers send ae_zoom:fit/zoom over WS to remote display (local_push) - ae_zoom: handler added to handle_ws_recv() for remote device - Replace 3 scattered close buttons with single bottom control bar: - [Zoom/Fit] always visible on controller - [Close Both] sends ae_close WS + clears local modal (local_push only) - [Back to List] clears local modal only; remote keeps showing current poster - Bottom bar hidden on controller=remote (kiosk display-screen mode) - native pinch-to-zoom via touch-action: pinch-zoom on img (no JS library needed) - pb-14 on modal bodyClass prevents buttons from overlapping poster content --- .../(launcher)/launcher/+layout.svelte | 221 +++++++++++------- 1 file changed, 136 insertions(+), 85 deletions(-) diff --git a/src/routes/events/[event_id]/(launcher)/launcher/+layout.svelte b/src/routes/events/[event_id]/(launcher)/launcher/+layout.svelte index f7875893..278cdc93 100644 --- a/src/routes/events/[event_id]/(launcher)/launcher/+layout.svelte +++ b/src/routes/events/[event_id]/(launcher)/launcher/+layout.svelte @@ -371,6 +371,13 @@ clearInterval(idle_timer_interval); saver_looping = false; restartCountdown(); + } else if (cmd.startsWith('ae_zoom:')) { + // WHY: Controller can push zoom state to the remote display so both + // devices stay in sync (e.g. operator zooms to show detail to an attendee + // while the wall screen also zooms in for the room to see). + const zoom_target = cmd.split(':')[1]; + if (zoom_target === 'fit') modal_zoom_fit = true; + else if (zoom_target === 'zoom') modal_zoom_fit = false; } else if (cmd.startsWith('ae_refresh:')) { if (cmd.split(':')[1] == 'now') location.reload(); } @@ -417,6 +424,9 @@ let idle_timer_interval: any = $state(); let saver_looping: boolean = $state(false); + // Tracks fit vs. zoom mode for the poster modal. + // Reset to fit whenever a new poster opens so every poster starts clean. + let modal_zoom_fit: boolean = $state(true); function handle_idle_client() { if ( @@ -473,6 +483,13 @@ saver_looping = false; } }); + + // Reset to fit-mode whenever a different poster is opened. + $effect(() => { + if ($events_sess.launcher.modal__open_event_file_id) { + modal_zoom_fit = true; + } + }); @@ -908,7 +925,7 @@ {$events_loc.launcher.controller == 'remote' ? 'min-h-full' : ''} min-w-full " - bodyClass="p-0 space-y-0 overflow-y-auto flex flex-col gap-1 items-center justify-center" + 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" > @@ -932,96 +949,130 @@ {/snippet} - + {#if $events_sess.launcher.modal__event_file_obj?.hosted_file_id} + + Poster: {$events_sess.launcher.modal__title} { + 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') { + $events_sess.launcher.controller_cmd = `ae_zoom:${modal_zoom_fit ? 'fit' : 'zoom'}`; + $events_sess.launcher.controller_trigger_send = true; + } + }} + title="Double-tap to toggle zoom / fit" + class="block transition-[max-height,max-width,width] duration-200" + class:max-h-[85dvh]={modal_zoom_fit} + class:max-w-full={modal_zoom_fit} + class:w-auto={modal_zoom_fit} + 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;" + /> + {:else} +
+ + No image selected +
+ {/if} + - {#if $events_sess.launcher.modal__event_file_obj?.hosted_file_id} - - Poster - {:else} -
- - No image selected -
- {/if} - - + + - + + + + + + {#if $events_loc.launcher.controller_group_code && $events_loc.launcher.ws_connect}