Live-update whiteboard/tasks source display; add firmware CI build check
Whiteboard and tasks-source save/clear used to just tell the user to reload the page to see the change. Both endpoints always assign a successful "set" to the calling user, so the new state is fully known client-side already -- rewrite the "Currently using/showing ..." block in place instead, no server round trip or reload needed. Also adds .gitea/workflows/firmware-build-check.yml: builds both board variants (devkit, xiao) on every push touching firmware/**, unlike firmware-release-build.yml which only builds on a version.txt bump. Verified both builds succeed locally against the actual ESP-IDF toolchain before wiring this in.
This commit is contained in:
@@ -0,0 +1,51 @@
|
|||||||
|
name: Firmware build check
|
||||||
|
|
||||||
|
# Fires on every push touching firmware source, unlike
|
||||||
|
# firmware-release-build.yml (which only builds+publishes when
|
||||||
|
# firmware/version.txt itself is bumped -- the "cut a release" signal).
|
||||||
|
# This just verifies both board variants still compile; nothing else in
|
||||||
|
# CI catches a firmware/** push that breaks the build until someone
|
||||||
|
# happens to bump the version next.
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- "firmware/**"
|
||||||
|
- ".gitea/workflows/firmware-build-check.yml"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-check:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Same docker create/cp/start pattern as firmware-release-build.yml
|
||||||
|
# (see that file's own comment for why -- the runner's job
|
||||||
|
# workspace lives in a named Docker volume, not a real host path,
|
||||||
|
# so a nested `docker run -v "$PWD:..."` bind-mounts nothing
|
||||||
|
# useful). No release/artifact step here -- this only needs to
|
||||||
|
# prove `idf.py build` still succeeds for each board.
|
||||||
|
- name: Build (devkit -- ESP32-C6-DevKitC-1)
|
||||||
|
run: |
|
||||||
|
cid=$(docker create -w /workspace/firmware espressif/idf:release-v6.0 bash -c '
|
||||||
|
git config --global --add safe.directory /workspace &&
|
||||||
|
. "$IDF_PATH/export.sh" &&
|
||||||
|
./build_for_board.sh devkit set-target esp32c6 &&
|
||||||
|
./build_for_board.sh devkit build
|
||||||
|
')
|
||||||
|
docker cp "$PWD/." "$cid:/workspace"
|
||||||
|
docker start -a "$cid"
|
||||||
|
docker rm "$cid"
|
||||||
|
|
||||||
|
- name: Build (xiao -- Seeed XIAO ESP32-C6)
|
||||||
|
run: |
|
||||||
|
cid=$(docker create -w /workspace/firmware espressif/idf:release-v6.0 bash -c '
|
||||||
|
git config --global --add safe.directory /workspace &&
|
||||||
|
. "$IDF_PATH/export.sh" &&
|
||||||
|
./build_for_board.sh xiao set-target esp32c6 &&
|
||||||
|
./build_for_board.sh xiao build
|
||||||
|
')
|
||||||
|
docker cp "$PWD/." "$cid:/workspace"
|
||||||
|
docker start -a "$cid"
|
||||||
|
docker rm "$cid"
|
||||||
@@ -202,6 +202,31 @@ document.getElementById('tasks_enabled').addEventListener('change', async (e) =>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Rewrites #tasks-current-source in place instead of telling the user
|
||||||
|
// to reload -- the API always assigns a successful "set" to the caller
|
||||||
|
// (see api_tasks_source), so after either action we already know
|
||||||
|
// exactly what the new state is without asking the server again.
|
||||||
|
function renderTasksCurrentSource(label) {
|
||||||
|
const container = document.getElementById('tasks-current-source');
|
||||||
|
container.innerHTML = '';
|
||||||
|
if (!label) return; // matches the template's no-tasks_source branch: nothing rendered
|
||||||
|
const p = document.createElement('p');
|
||||||
|
p.className = 'sub';
|
||||||
|
p.style.marginTop = '10px';
|
||||||
|
p.append('Currently using your ');
|
||||||
|
const labelEl = document.createElement('strong');
|
||||||
|
labelEl.textContent = label;
|
||||||
|
p.append(labelEl, ' list. ');
|
||||||
|
const clearBtn = document.createElement('button');
|
||||||
|
clearBtn.type = 'button';
|
||||||
|
clearBtn.className = 'btn-inline secondary';
|
||||||
|
clearBtn.id = 'tasks-source-clear';
|
||||||
|
clearBtn.textContent = 'Clear';
|
||||||
|
clearBtn.addEventListener('click', clearTasksSource);
|
||||||
|
p.append(clearBtn);
|
||||||
|
container.append(p);
|
||||||
|
}
|
||||||
|
|
||||||
// Choosing one of your own CalDAV task lists as this frame's source --
|
// 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
|
// owner-only (see api_frames.py's api_tasks_source), so these radios
|
||||||
// only ever render for the viewer's own calendars anyway.
|
// only ever render for the viewer's own calendars anyway.
|
||||||
@@ -214,7 +239,9 @@ document.querySelectorAll('.tasks-source-choice').forEach((el) => {
|
|||||||
body: JSON.stringify({ calendar_key: el.dataset.key }),
|
body: JSON.stringify({ calendar_key: el.dataset.key }),
|
||||||
});
|
});
|
||||||
if (!resp.ok) throw new Error(await apiError(resp));
|
if (!resp.ok) throw new Error(await apiError(resp));
|
||||||
showStatus(true, 'Task list saved. Reload to see the updated source.');
|
showStatus(true, 'Task list saved.');
|
||||||
|
const labelEl = el.closest('li').querySelector('label');
|
||||||
|
renderTasksCurrentSource(labelEl ? labelEl.textContent.trim() : '');
|
||||||
loadCalendarPreview();
|
loadCalendarPreview();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showStatus(false, e.message);
|
showStatus(false, e.message);
|
||||||
@@ -222,9 +249,7 @@ document.querySelectorAll('.tasks-source-choice').forEach((el) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const tasksSourceClear = document.getElementById('tasks-source-clear');
|
async function clearTasksSource() {
|
||||||
if (tasksSourceClear) {
|
|
||||||
tasksSourceClear.addEventListener('click', async () => {
|
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(`${window.FRAME_API}/tasks-source`, {
|
const resp = await fetch(`${window.FRAME_API}/tasks-source`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -232,12 +257,18 @@ if (tasksSourceClear) {
|
|||||||
body: JSON.stringify({ calendar_key: null }),
|
body: JSON.stringify({ calendar_key: null }),
|
||||||
});
|
});
|
||||||
if (!resp.ok) throw new Error(await apiError(resp));
|
if (!resp.ok) throw new Error(await apiError(resp));
|
||||||
showStatus(true, 'Task list cleared. Reload to see the change.');
|
showStatus(true, 'Task list cleared.');
|
||||||
|
renderTasksCurrentSource(null);
|
||||||
|
document.querySelectorAll('.tasks-source-choice').forEach((r) => { r.checked = false; });
|
||||||
loadCalendarPreview();
|
loadCalendarPreview();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showStatus(false, e.message);
|
showStatus(false, e.message);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
const tasksSourceClear = document.getElementById('tasks-source-clear');
|
||||||
|
if (tasksSourceClear) {
|
||||||
|
tasksSourceClear.addEventListener('click', clearTasksSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadCalendarPreview() {
|
function loadCalendarPreview() {
|
||||||
|
|||||||
@@ -2,6 +2,39 @@
|
|||||||
// api_whiteboard_source), preview, and take control. window.FRAME_API is
|
// api_whiteboard_source), preview, and take control. window.FRAME_API is
|
||||||
// set by the template.
|
// set by the template.
|
||||||
|
|
||||||
|
// Rewrites #whiteboard-current-source in place instead of telling the
|
||||||
|
// user to reload -- the API always assigns a successful "set" to the
|
||||||
|
// caller (see api_whiteboard_source), so after either action we already
|
||||||
|
// know exactly what the new state is without asking the server again.
|
||||||
|
function renderWhiteboardCurrentSource(url) {
|
||||||
|
const container = document.getElementById('whiteboard-current-source');
|
||||||
|
container.innerHTML = '';
|
||||||
|
const p = document.createElement('p');
|
||||||
|
p.className = 'sub';
|
||||||
|
p.style.marginTop = '10px';
|
||||||
|
if (url) {
|
||||||
|
p.append('Currently showing ');
|
||||||
|
const urlEl = document.createElement('strong');
|
||||||
|
urlEl.textContent = url;
|
||||||
|
p.append(urlEl, ' using your WebDAV account. ');
|
||||||
|
const clearBtn = document.createElement('button');
|
||||||
|
clearBtn.type = 'button';
|
||||||
|
clearBtn.className = 'btn-inline secondary';
|
||||||
|
clearBtn.id = 'whiteboard-source-clear';
|
||||||
|
clearBtn.textContent = 'Clear';
|
||||||
|
clearBtn.addEventListener('click', clearWhiteboardSource);
|
||||||
|
p.append(clearBtn);
|
||||||
|
} else {
|
||||||
|
p.textContent = 'No whiteboard configured yet.';
|
||||||
|
}
|
||||||
|
container.append(p);
|
||||||
|
|
||||||
|
const label = document.getElementById('whiteboard-source-form-label');
|
||||||
|
if (label) {
|
||||||
|
label.textContent = url ? 'Change to one of your own files' : 'Use one of your own files';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const whiteboardForm = document.getElementById('whiteboard-source-form');
|
const whiteboardForm = document.getElementById('whiteboard-source-form');
|
||||||
if (whiteboardForm) {
|
if (whiteboardForm) {
|
||||||
whiteboardForm.addEventListener('submit', async (e) => {
|
whiteboardForm.addEventListener('submit', async (e) => {
|
||||||
@@ -15,7 +48,8 @@ if (whiteboardForm) {
|
|||||||
body: JSON.stringify({ url }),
|
body: JSON.stringify({ url }),
|
||||||
});
|
});
|
||||||
if (!resp.ok) throw new Error(await apiError(resp));
|
if (!resp.ok) throw new Error(await apiError(resp));
|
||||||
showStatus(true, 'Saved. Reload to see the updated source.');
|
showStatus(true, 'Saved.');
|
||||||
|
renderWhiteboardCurrentSource(url);
|
||||||
loadWhiteboardPreview();
|
loadWhiteboardPreview();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showStatus(false, e.message);
|
showStatus(false, e.message);
|
||||||
@@ -23,9 +57,7 @@ if (whiteboardForm) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const whiteboardClearBtn = document.getElementById('whiteboard-source-clear');
|
async function clearWhiteboardSource() {
|
||||||
if (whiteboardClearBtn) {
|
|
||||||
whiteboardClearBtn.addEventListener('click', async () => {
|
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(`${window.FRAME_API}/whiteboard-source`, {
|
const resp = await fetch(`${window.FRAME_API}/whiteboard-source`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -33,12 +65,19 @@ if (whiteboardClearBtn) {
|
|||||||
body: JSON.stringify({ url: null }),
|
body: JSON.stringify({ url: null }),
|
||||||
});
|
});
|
||||||
if (!resp.ok) throw new Error(await apiError(resp));
|
if (!resp.ok) throw new Error(await apiError(resp));
|
||||||
showStatus(true, 'Cleared. Reload to see the change.');
|
showStatus(true, 'Cleared.');
|
||||||
|
renderWhiteboardCurrentSource(null);
|
||||||
|
const urlInput = document.getElementById('whiteboard-url-input');
|
||||||
|
if (urlInput) urlInput.value = '';
|
||||||
loadWhiteboardPreview();
|
loadWhiteboardPreview();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showStatus(false, e.message);
|
showStatus(false, e.message);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
const whiteboardClearBtn = document.getElementById('whiteboard-source-clear');
|
||||||
|
if (whiteboardClearBtn) {
|
||||||
|
whiteboardClearBtn.addEventListener('click', clearWhiteboardSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadWhiteboardPreview(force) {
|
function loadWhiteboardPreview(force) {
|
||||||
|
|||||||
@@ -163,6 +163,7 @@
|
|||||||
<label for="tasks_enabled">Show a task list</label>
|
<label for="tasks_enabled">Show a task list</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="tasks-current-source">
|
||||||
{% if tasks_source %}
|
{% if tasks_source %}
|
||||||
<p class="sub" style="margin-top: 10px;">
|
<p class="sub" style="margin-top: 10px;">
|
||||||
Currently using <strong>{{ tasks_source.display_name }}</strong>'s
|
Currently using <strong>{{ tasks_source.display_name }}</strong>'s
|
||||||
@@ -170,6 +171,7 @@
|
|||||||
<button type="button" class="btn-inline secondary" id="tasks-source-clear">Clear</button>
|
<button type="button" class="btn-inline secondary" id="tasks-source-clear">Clear</button>
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if viewer_task_calendars %}
|
{% if viewer_task_calendars %}
|
||||||
<p class="sub" style="margin-top: 10px;">Use one of your own CalDAV task lists:</p>
|
<p class="sub" style="margin-top: 10px;">Use one of your own CalDAV task lists:</p>
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
scene) fetched over WebDAV -- credentials set up in
|
scene) fetched over WebDAV -- credentials set up in
|
||||||
<a href="/settings">Settings</a>.</p>
|
<a href="/settings">Settings</a>.</p>
|
||||||
|
|
||||||
|
<div id="whiteboard-current-source">
|
||||||
{% if whiteboard_source %}
|
{% if whiteboard_source %}
|
||||||
<p class="sub" style="margin-top: 10px;">
|
<p class="sub" style="margin-top: 10px;">
|
||||||
Currently showing <strong>{{ whiteboard_source.url }}</strong>
|
Currently showing <strong>{{ whiteboard_source.url }}</strong>
|
||||||
@@ -37,10 +38,11 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<p class="sub" style="margin-top: 10px;">No whiteboard configured yet.</p>
|
<p class="sub" style="margin-top: 10px;">No whiteboard configured yet.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if viewer_has_webdav_creds %}
|
{% if viewer_has_webdav_creds %}
|
||||||
<form id="whiteboard-source-form" style="margin-top: 16px;">
|
<form id="whiteboard-source-form" style="margin-top: 16px;">
|
||||||
<label>{{ "Change to one of your own files" if whiteboard_source else "Use one of your own files" }}
|
<label><span id="whiteboard-source-form-label">{{ "Change to one of your own files" if whiteboard_source else "Use one of your own files" }}</span>
|
||||||
<input type="text" id="whiteboard-url-input"
|
<input type="text" id="whiteboard-url-input"
|
||||||
placeholder="https://cloud.example.com/remote.php/dav/files/you/Boards/family.whiteboard"
|
placeholder="https://cloud.example.com/remote.php/dav/files/you/Boards/family.whiteboard"
|
||||||
value="{{ whiteboard_source.url if whiteboard_source and whiteboard_source.user_id == user.id else '' }}">
|
value="{{ whiteboard_source.url if whiteboard_source and whiteboard_source.user_id == user.id else '' }}">
|
||||||
|
|||||||
Reference in New Issue
Block a user