// Tasks widget dialog: per-user included-task-list checkboxes + color // pins (same shape as the calendar widget's "Included calendars"), the // name/recently-completed settings form, and the rendered preview. Not // a page-load script -- frame_layout.js fetches this widget's dialog // HTML fragment, injects it into the shared , points // window.FRAME_API at this specific widget // (/api/frames/{id}/widgets/{widget_id}), then calls initTasksDialog(). function loadTasksPreview() { document.getElementById('tasks-preview').src = `${window.FRAME_API}/preview/tasks?_=${Date.now()}`; } function initTasksDialog() { // Each task list's own include/mute toggle -- auto-saves on change, // not batched into the form below, since it's a data-sharing choice // (see api_widget_task_list_select), not a widget-wide setting. Works // the same element for your own lists (full add/remove) and other // people's (mute only) -- the server enforces which direction is // allowed and this just reverts the checkbox with an error message if // rejected. document.querySelectorAll('.task-list-toggle').forEach((el) => { el.addEventListener('change', async () => { try { const resp = await fetch(`${window.FRAME_API}/task-list-select`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: Number(el.dataset.userId), calendar_key: el.dataset.key, calendar_label: el.dataset.label, included: el.checked, }), }); if (!resp.ok) throw new Error(await apiError(resp)); showStatus(true, el.checked ? 'Task list included on this widget.' : 'Task list removed from this widget.'); loadTasksPreview(); } catch (e) { el.checked = !el.checked; showStatus(false, e.message); } }); }); // Per-task-list color pin -- owner-only (the server enforces it; // these buttons only ever render for the viewer's own lists anyway). document.querySelectorAll('.task-list-color-picker').forEach((picker) => { const key = picker.dataset.key; picker.querySelectorAll('.color-swatch').forEach((btn) => { btn.addEventListener('click', async () => { const colorIndex = btn.dataset.index === '' ? null : Number(btn.dataset.index); try { const resp = await fetch(`${window.FRAME_API}/task-list-color`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ calendar_key: key, color_index: colorIndex }), }); if (!resp.ok) throw new Error(await apiError(resp)); picker.querySelectorAll('.color-swatch').forEach((el) => el.classList.remove('selected')); btn.classList.add('selected'); showStatus(true, 'Color saved.'); loadTasksPreview(); } catch (e) { showStatus(false, e.message); } }); }); }); document.getElementById('tasks-config-form').addEventListener('submit', async (e) => { e.preventDefault(); const body = new URLSearchParams({ tasks_name: document.getElementById('tasks_name').value, tasks_show_completed: String(document.getElementById('tasks_show_completed').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.'); loadTasksPreview(); } catch (e) { showStatus(false, e.message); } }); document.getElementById('tasks-preview-refresh').addEventListener('click', loadTasksPreview); loadTasksPreview(); } function closeTasksDialog() { // Nothing to tear down -- no poll interval, unlike the photos dialog. }