Files
espresso_frame/server/app/static/widget_dialog_photos.js
T
tfaour b15747a604
Build and push server image / test (push) Has been cancelled
Build and push server image / build-and-push (push) Has been cancelled
Build and push server image / deploy (push) Has been cancelled
Add per-widget border option (style, thickness, palette color)
A Widget-level property (border_style/border_thickness/border_color_index),
not a per-type config field, since every widget type can have one -- drawn
once centrally in device.py's _render_widgets before compositing, using
an exact panel palette color so it never dithers. Styles: solid, dashed,
dotted, and a fancy double-line picture-frame-mat look. Configurable from
a shared "Border" card in every widget's gear-icon dialog.
2026-07-27 19:51:04 +00:00

123 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);
initBorderFields();
}
function closePhotosDialog() {
clearInterval(photosPollTimer);
photosPollTimer = null;
}