// Page-header controls shared by every per-frame page (Layout/ // Configuration/Stats): the frame-name pencil-edit, living outside the // tab structure since it applies regardless of which tab is open. // Depends on window.FRAME_BASE_API (a stable frame-level base set by // every page -- unlike window.FRAME_API, which the Layout page's // widget dialogs repoint to a widget-scoped base while one is open) and // common.js's showStatus/apiError. (function () { var view = document.getElementById('frame-name-view'); var editRow = document.getElementById('frame-name-edit-row'); var pencil = document.getElementById('frame-name-pencil'); var input = document.getElementById('frame-name-input'); var textEl = document.getElementById('frame-name-text'); var saveBtn = document.getElementById('frame-name-save'); var cancelBtn = document.getElementById('frame-name-cancel'); if (!view || !window.FRAME_BASE_API) return; function openEdit() { input.value = textEl.textContent.trim(); view.style.display = 'none'; editRow.style.display = 'inline-flex'; input.focus(); input.select(); } function closeEdit() { editRow.style.display = 'none'; view.style.display = 'inline-flex'; } pencil.addEventListener('click', openEdit); cancelBtn.addEventListener('click', closeEdit); async function save() { var name = input.value.trim(); if (!name || name === textEl.textContent.trim()) { closeEdit(); return; } try { const resp = await fetch(`${window.FRAME_BASE_API}/config`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ name }), }); if (!resp.ok) throw new Error(await apiError(resp)); textEl.textContent = name; closeEdit(); showStatus(true, 'Renamed.'); } catch (e) { showStatus(false, e.message); } } saveBtn.addEventListener('click', save); input.addEventListener('keydown', function (e) { if (e.key === 'Enter') save(); if (e.key === 'Escape') closeEdit(); }); })(); // Live "how it's displaying" thumbnail. A real composite render (same // pipeline /frame/image uses), not a cached snapshot, so it's on a slow // poll rather than something tighter like the 10s device-status poll -- // no need to hit Immich/calendar/whiteboard sources that often just for // a header thumbnail. Click enlarges it in a dialog (which also fetches // a fresh render); clicking the enlarged image refreshes it again. (function () { var thumb = document.getElementById('frame-preview-thumb'); var dialog = document.getElementById('frame-preview-dialog'); var bigImg = document.getElementById('frame-preview-dialog-img'); var closeBtn = document.getElementById('frame-preview-dialog-close'); if (!thumb || !window.FRAME_BASE_API) return; function previewUrl() { return `${window.FRAME_BASE_API}/preview?t=${Date.now()}`; } function refreshThumb() { thumb.src = previewUrl(); } // Opening the dialog (or clicking the big image inside it) fetches a // fresh render and keeps the header thumb in sync, so this single path // covers both "enlarge" and the old click-to-refresh behavior. function refreshBig() { var url = previewUrl(); bigImg.src = url; thumb.src = url; } thumb.addEventListener('click', function () { if (!dialog) { refreshThumb(); return; } refreshBig(); dialog.showModal(); }); refreshThumb(); setInterval(refreshThumb, 60000); if (dialog && bigImg && closeBtn) { bigImg.addEventListener('click', refreshBig); closeBtn.addEventListener('click', function () { dialog.close(); }); // Same backdrop-click-to-close trick as #widget-dialog: a click that // lands on the dialog element itself (not its content box) means the // backdrop was hit. dialog.addEventListener('click', function (e) { if (e.target !== dialog) return; var rect = dialog.getBoundingClientRect(); var inside = e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom; if (!inside) dialog.close(); }); } })();