// Photos widget dialog: now-displaying, album picker, order/display-mode // settings, and the upcoming grid (rendering/drag logic in queue.js). // Not a page-load script -- frame_layout.js fetches this widget's dialog // HTML fragment, injects it into the shared , points // window.FRAME_API at this specific widget (/api/frames/{id}/widgets/ // {widget_id}), then calls initPhotosDialog(). closePhotosDialog() stops // the poll interval when the dialog closes, same "expects window. // FRAME_API + a global loadQueue()" contract queue.js has always had. let photosPollTimer = null; let photoLocked = false; let photoHasCurrent = false; function renderLockButton() { const btn = document.getElementById('lock-photo-btn'); if (!btn) return; btn.textContent = photoLocked ? 'Unlock this photo' : 'Lock this photo'; btn.classList.toggle('active', photoLocked); btn.disabled = !photoHasCurrent && !photoLocked; // nothing displayed yet to lock } async function toggleLock() { const next = !photoLocked; try { const resp = await fetch(`${window.FRAME_API}/lock`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ locked: next }), }); if (!resp.ok) throw new Error(await apiError(resp)); photoLocked = next; renderLockButton(); showStatus(true, photoLocked ? 'Locked -- this photo will stay put.' : 'Unlocked.'); } catch (e) { showStatus(false, e.message); } } async function loadQueue() { if (dragState) { return; // don't yank the grid out from under an in-progress drag } const currentEl = document.getElementById('current-thumb'); if (!currentEl) return; // dialog closed mid-flight try { const resp = await fetch(`${window.FRAME_API}/queue`); if (!resp.ok) { currentEl.innerHTML = '

Not available yet -- the owner needs to connect Immich (Settings) and pick an album.

'; renderUpcoming([]); photoHasCurrent = false; renderLockButton(); return; } const data = await resp.json(); photoLocked = !!data.locked; photoHasCurrent = !!data.current; renderLockButton(); currentEl.innerHTML = ''; if (data.current) { const wrap = document.createElement('div'); wrap.className = 'thumb-wrap'; const img = document.createElement('img'); img.className = 'thumb'; img.src = data.current.thumbnail_url; img.alt = ''; wrap.appendChild(img); const removeBtn = document.createElement('button'); removeBtn.type = 'button'; removeBtn.className = 'remove-btn'; removeBtn.title = 'Remove from rotation'; removeBtn.textContent = '×'; removeBtn.addEventListener('click', () => removeAsset(data.current.id)); wrap.appendChild(removeBtn); currentEl.appendChild(wrap); } else { currentEl.innerHTML = '

Nothing displayed yet.

'; } renderUpcoming(data.upcoming); } catch (e) { currentEl.innerHTML = '

Could not load.

'; } } async function savePhotoSettings() { const body = new URLSearchParams({ album_id: document.getElementById('album_id').value || '', queue_target_len: document.getElementById('queue_target_len').value, order: document.getElementById('order').value, display_mode: document.getElementById('display_mode').value, }); const resp = await fetch(`${window.FRAME_API}/config`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body, }); if (!resp.ok) { throw new Error(await apiError(resp)); } } function initPhotosDialog() { document.getElementById('load-albums').addEventListener('click', async () => { try { // /albums is frame-level (routers/api_frames.py) -- it lists the // frame owner's whole Immich library, not something scoped to // this one photo widget -- so it uses window.FRAME_BASE_API (the // stable frame-level base), not window.FRAME_API (repointed to // this widget's own API base while the dialog is open). const resp = await fetch(`${window.FRAME_BASE_API}/albums`); if (!resp.ok) { throw new Error(await apiError(resp)); } const albums = await resp.json(); const select = document.getElementById('album_id'); select.innerHTML = ''; for (const a of albums) { const opt = document.createElement('option'); opt.value = a.id; opt.textContent = `${a.name} (${a.count})`; select.appendChild(opt); } showStatus(true, `Loaded ${albums.length} album(s) -- pick one and click Save.`); } catch (e) { showStatus(false, e.message); } }); document.getElementById('photos-form').addEventListener('submit', async (e) => { e.preventDefault(); try { await savePhotoSettings(); showStatus(true, 'Saved.'); loadQueue(); } catch (e) { showStatus(false, e.message); } }); document.getElementById('lock-photo-btn').addEventListener('click', toggleLock); loadQueue(); // Slow poll: picks up real changes (new photo displayed, queue edited // from elsewhere) without a manual refresh. Skipped mid-drag. photosPollTimer = setInterval(loadQueue, 10000); initBorderFields(); initButtonActionFields(); } function closePhotosDialog() { clearInterval(photosPollTimer); photosPollTimer = null; }