Frame name (pencil-icon inline edit) and the Photos/Calendar mode
selector now live in the page header, shared across all four per-frame
pages instead of being buried in the Configuration form -- so renaming
a frame or flipping its mode no longer requires navigating to a
specific tab first.
Calendar settings move out of a conditionally-hidden card on the
Configuration tab into their own dedicated tab (new /frames/{id}/
calendar route), visually greyed out when the frame is in Photos mode
but still fully usable so calendar settings can be configured ahead of
switching modes. Also wires the week-start setting into the UI for the
first time (the column/backend support landed earlier but had no
control anywhere).
83 lines
3.1 KiB
JavaScript
83 lines
3.1 KiB
JavaScript
// Calendar tab: view/week-start/photo-inlay settings, per-user opt-in,
|
|
// and the rendered preview. Extracted from frame_config.js when the
|
|
// Calendar card became its own tab (window.FRAME_API is set by the
|
|
// template; checkboxes are always sent explicitly as "true"/"false").
|
|
|
|
document.getElementById('calendar-config-form').addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
const body = new URLSearchParams({
|
|
calendar_view: document.getElementById('calendar_view').value,
|
|
calendar_week_start: document.getElementById('calendar_week_start').value,
|
|
calendar_photo_inlay: String(document.getElementById('calendar_photo_inlay').checked),
|
|
});
|
|
try {
|
|
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));
|
|
showStatus(true, 'Saved.');
|
|
loadCalendarPreview();
|
|
} catch (e) {
|
|
showStatus(false, e.message);
|
|
}
|
|
});
|
|
|
|
// Each person's own opt-in -- auto-saves on toggle, not batched into the
|
|
// form above, since it's the toggling user's own preference (see
|
|
// api_frames.py's /calendar-included), not a frame-wide setting.
|
|
document.querySelectorAll('.calendar-self-toggle').forEach((el) => {
|
|
el.addEventListener('change', async () => {
|
|
try {
|
|
const resp = await fetch(`${window.FRAME_API}/calendar-included`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ included: el.checked }),
|
|
});
|
|
if (!resp.ok) throw new Error(await apiError(resp));
|
|
showStatus(true, el.checked ? 'Your calendar is included on this frame.' : 'Your calendar removed from this frame.');
|
|
} catch (e) {
|
|
el.checked = !el.checked;
|
|
showStatus(false, e.message);
|
|
}
|
|
});
|
|
});
|
|
|
|
function loadCalendarPreview() {
|
|
document.getElementById('calendar-preview').src = `${window.FRAME_API}/preview/calendar?_=${Date.now()}`;
|
|
}
|
|
document.getElementById('calendar-preview-refresh').addEventListener('click', loadCalendarPreview);
|
|
loadCalendarPreview();
|
|
|
|
async function takeControl() {
|
|
try {
|
|
const resp = await fetch(`${window.FRAME_API}/take-control`, { method: 'POST' });
|
|
if (!resp.ok) throw new Error(await apiError(resp));
|
|
showStatus(true, 'You have control now.');
|
|
loadControl();
|
|
} catch (e) {
|
|
showStatus(false, e.message);
|
|
}
|
|
}
|
|
|
|
async function loadControl() {
|
|
const banner = document.getElementById('control-banner');
|
|
try {
|
|
const resp = await fetch(`${window.FRAME_API}/queue`);
|
|
if (!resp.ok) return; // unconfigured frame: control still works via 409s
|
|
const data = await resp.json();
|
|
if (data.control && !data.control.you) {
|
|
banner.style.display = 'flex';
|
|
document.getElementById('control-holder').textContent = data.control.controller
|
|
? `${data.control.controller} currently has control of this frame.`
|
|
: 'Nobody has control of this frame yet.';
|
|
} else {
|
|
banner.style.display = 'none';
|
|
}
|
|
} catch (e) { /* banner is best-effort */ }
|
|
}
|
|
|
|
document.getElementById('take-control').addEventListener('click', takeControl);
|
|
loadControl();
|