Week view flexibility: configurable day count, layout, and a CalDAV task list
Build and push server image / build-and-push (push) Successful in 52s

- Day count (2-10, was fixed at 7) -- 5 days trims the weekend clutter
  without losing the grid format.
- Layout choice: days side by side (original behavior) or stacked
  vertically as full agenda-style sections (reuses _draw_agenda_day,
  same approach _build_today_tomorrow already used for a fixed 2 days).
- Optional task list (CalDAV VTODO collections only -- a plain ICS
  subscription doesn't meaningfully have one) that takes the space of
  one day slot instead of adding an extra one. Same owner-controls-
  their-own-data permission split as calendar sources: only the
  calendar's owner can point a frame's task list at it, but anyone
  linked to the frame can clear it.

Browse-offset paging now moves by N days (was hardcoded to weeks),
identical to the old behavior when days=7. Changing the day count
resets the browse offset, same reasoning as changing views already did.
This commit is contained in:
2026-07-23 07:58:13 -04:00
parent 01b9e9f1d0
commit ce8525bee8
10 changed files with 436 additions and 25 deletions
+57
View File
@@ -8,6 +8,8 @@ document.getElementById('calendar-config-form').addEventListener('submit', async
const body = new URLSearchParams({
calendar_view: document.getElementById('calendar_view').value,
calendar_week_start: document.getElementById('calendar_week_start').value,
calendar_week_days: document.getElementById('calendar_week_days').value,
calendar_week_layout: document.getElementById('calendar_week_layout').value,
calendar_photo_inlay: String(document.getElementById('calendar_photo_inlay').checked),
});
try {
@@ -162,6 +164,61 @@ document.getElementById('weather-city-add').addEventListener('click', async () =
}
});
document.getElementById('tasks_enabled').addEventListener('change', async (e) => {
const el = e.target;
try {
const resp = await fetch(`${window.FRAME_API}/config`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ calendar_tasks_enabled: String(el.checked) }),
});
if (!resp.ok) throw new Error(await apiError(resp));
showStatus(true, 'Saved.');
loadCalendarPreview();
} catch (e) {
el.checked = !el.checked;
showStatus(false, e.message);
}
});
// Choosing one of your own CalDAV task lists as this frame's source --
// owner-only (see api_frames.py's api_tasks_source), so these radios
// only ever render for the viewer's own calendars anyway.
document.querySelectorAll('.tasks-source-choice').forEach((el) => {
el.addEventListener('change', async () => {
try {
const resp = await fetch(`${window.FRAME_API}/tasks-source`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ calendar_key: el.dataset.key }),
});
if (!resp.ok) throw new Error(await apiError(resp));
showStatus(true, 'Task list saved. Reload to see the updated source.');
loadCalendarPreview();
} catch (e) {
showStatus(false, e.message);
}
});
});
const tasksSourceClear = document.getElementById('tasks-source-clear');
if (tasksSourceClear) {
tasksSourceClear.addEventListener('click', async () => {
try {
const resp = await fetch(`${window.FRAME_API}/tasks-source`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ calendar_key: null }),
});
if (!resp.ok) throw new Error(await apiError(resp));
showStatus(true, 'Task list cleared. Reload to see the change.');
loadCalendarPreview();
} catch (e) {
showStatus(false, e.message);
}
});
}
function loadCalendarPreview() {
document.getElementById('calendar-preview').src = `${window.FRAME_API}/preview/calendar?_=${Date.now()}`;
}