Files
espresso_frame/server/app/static/widget_dialog_photos.js
T
tfaour cb11ffdd2d
Build and push server image / test (push) Successful in 21s
Build and push server image / build-and-push (push) Successful in 2m4s
Build and push server image / deploy (push) Successful in 56s
Fix Load Albums button in the photos widget dialog
The dialog calls /albums via window.FRAME_API, but that's repointed to
this widget's own API base while a dialog is open (see frame_layout.js)
-- /albums is frame-level (lists the owner's whole Immich library, not
scoped to one photo widget), so it needs window.FRAME_BASE_API instead.
The button silently 404'd since the resulting URL never existed.
2026-07-24 14:39:01 -04:00

122 lines
4.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 <dialog>, 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;
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 =
'<p class="sub">Not available yet -- the owner needs to connect Immich (Settings) and pick an album.</p>';
renderUpcoming([]);
return;
}
const data = await resp.json();
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 = '<p class="sub">Nothing displayed yet.</p>';
}
renderUpcoming(data.upcoming);
} catch (e) {
currentEl.innerHTML = '<p class="sub">Could not load.</p>';
}
}
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);
}
});
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);
}
function closePhotosDialog() {
clearInterval(photosPollTimer);
photosPollTimer = null;
}