Move frame name/mode into the page header; Calendar gets its own tab

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).
This commit is contained in:
2026-07-22 21:21:00 -04:00
parent aa194be09a
commit 3a0007118c
12 changed files with 335 additions and 135 deletions
+3 -66
View File
@@ -1,13 +1,13 @@
// Configuration tab: frame settings + firmware card + take control.
// window.FRAME_API is set by the template. Checkboxes are always sent
// Frame name/mode live in the page header now (frame_header.js) and
// Calendar settings have their own tab (frame_calendar.js). window.
// FRAME_API is set by the template. Checkboxes are always sent
// explicitly as "true"/"false" -- the server treats absent fields as
// "leave unchanged", so a checkbox must never be simply omitted.
async function saveConfig() {
const minutes = parseInt(document.getElementById('refresh_interval_minutes').value, 10) || 60;
const body = new URLSearchParams({
mode: document.getElementById('frame_mode').value,
name: document.getElementById('frame_name').value || '',
order: document.getElementById('order').value,
orientation: document.getElementById('orientation').value,
refresh_interval_s: String(minutes * 60),
@@ -37,69 +37,6 @@ document.getElementById('config-form').addEventListener('submit', async (e) => {
}
});
// ---- Calendar card: mode/view toggling, its own save, self opt-in, preview ----
const calendarCard = document.getElementById('calendar-card');
if (calendarCard) {
document.getElementById('frame_mode').addEventListener('change', () => {
calendarCard.style.display = document.getElementById('frame_mode').value === 'calendar' ? 'block' : 'none';
});
const inlayRow = document.getElementById('calendar-inlay-row');
const inlayHint = document.getElementById('calendar-inlay-hint');
document.getElementById('calendar_view').addEventListener('change', () => {
const isAgenda = document.getElementById('calendar_view').value === 'agenda';
inlayRow.style.display = isAgenda ? 'flex' : 'none';
inlayHint.style.display = isAgenda ? 'block' : 'none';
});
document.getElementById('calendar-config-form').addEventListener('submit', async (e) => {
e.preventDefault();
const body = new URLSearchParams({
calendar_view: document.getElementById('calendar_view').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' });