diff --git a/web_interface/static/v3/app.css b/web_interface/static/v3/app.css index 26b7ac4a..27416ede 100644 --- a/web_interface/static/v3/app.css +++ b/web_interface/static/v3/app.css @@ -1368,6 +1368,11 @@ button.bg-white { border-radius: 0.5rem; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); overflow: hidden; + /* Desktop: draggable resize handle (bottom-left visually, since the + panel is anchored to the right). Touch devices use the size button. */ + resize: both; + min-width: 160px; + max-width: 90vw; } .floating-preview img { background-color: #000; @@ -1387,7 +1392,8 @@ button.bg-white { justify-content: center; } @media (max-width: 640px) { - .floating-preview img { - width: 192px !important; + /* Whatever size is chosen, never wider than the phone viewport */ + .floating-preview { + max-width: calc(100vw - 2rem); } } diff --git a/web_interface/static/v3/app.js b/web_interface/static/v3/app.js index c84cb4b0..82b2a9e5 100644 --- a/web_interface/static/v3/app.js +++ b/web_interface/static/v3/app.js @@ -389,6 +389,29 @@ window.toggleFloatingPreview = function(open) { window.updateFloatingPreviewVisibility(); }; +// Preset widths the size button cycles through (px). Desktop users can also +// drag the panel's native resize handle (CSS resize: both). +const FLOATING_PREVIEW_SIZES = [192, 256, 384, 512]; + +window.applyFloatingPreviewSize = function() { + const panel = document.getElementById('floating-preview'); + if (!panel) return; + let size = 256; + try { size = parseInt(localStorage.getItem('ledmatrix-floating-preview-size'), 10) || 256; } catch { /* no-op */ } + panel.style.width = size + 'px'; + // Clear any manual drag-resize height so the image's aspect ratio rules + panel.style.height = ''; +}; + +window.cycleFloatingPreviewSize = function() { + let size = 256; + try { size = parseInt(localStorage.getItem('ledmatrix-floating-preview-size'), 10) || 256; } catch { /* no-op */ } + const idx = FLOATING_PREVIEW_SIZES.indexOf(size); + const next = FLOATING_PREVIEW_SIZES[(idx + 1) % FLOATING_PREVIEW_SIZES.length]; + try { localStorage.setItem('ledmatrix-floating-preview-size', String(next)); } catch { /* no-op */ } + window.applyFloatingPreviewSize(); +}; + window.updateFloatingPreviewVisibility = function(tab) { const panel = document.getElementById('floating-preview'); const toggle = document.getElementById('floating-preview-toggle'); @@ -402,8 +425,19 @@ window.updateFloatingPreviewVisibility = function(tab) { const onOverview = active === 'overview'; let open = false; try { open = localStorage.getItem('ledmatrix-floating-preview') === '1'; } catch { /* no-op */ } - panel.style.display = (!onOverview && open) ? 'block' : 'none'; + const showPanel = !onOverview && open; + panel.style.display = showPanel ? 'block' : 'none'; toggle.style.display = (!onOverview && !open) ? 'flex' : 'none'; + if (showPanel) { + window.applyFloatingPreviewSize(); + // Show the last cached frame immediately — SSE only pushes on + // display changes, so a freshly opened panel would otherwise stay + // empty until the next change. + const img = document.getElementById('floating-preview-img'); + if (img && !img.src && window._lastPreviewFrame) { + img.src = 'data:image/png;base64,' + window._lastPreviewFrame; + } + } }; document.addEventListener('DOMContentLoaded', function() { diff --git a/web_interface/static/v3/js/app-shell.js b/web_interface/static/v3/js/app-shell.js index 0abfada8..bfe6a25f 100644 --- a/web_interface/static/v3/js/app-shell.js +++ b/web_interface/static/v3/js/app-shell.js @@ -1878,6 +1878,13 @@ const ledCanvas = document.getElementById('ledCanvas'); const placeholder = document.getElementById('displayPlaceholder'); + // Always cache the latest frame so the floating preview can show + // something the moment it's opened — SSE only sends frames when + // the display CHANGES, so a late subscriber would otherwise stare + // at an empty panel until the next change. + if (data.image) { + window._lastPreviewFrame = data.image; + } // Feed the floating mini preview (lives in base.html, present on // every tab) before the overview-only guard below. const floatImg = document.getElementById('floating-preview-img'); diff --git a/web_interface/templates/v3/base.html b/web_interface/templates/v3/base.html index d54d8cc1..2223106c 100644 --- a/web_interface/templates/v3/base.html +++ b/web_interface/templates/v3/base.html @@ -563,13 +563,20 @@