From d41ba0bd8a40db39ae3ec1068f662ccdfda320da Mon Sep 17 00:00:00 2001 From: cottongin Date: Mon, 3 Aug 2026 21:14:19 -0400 Subject: [PATCH] feat: add 2m, 10m presets and custom duration for slideshow interval MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Presets are now [1, 2, 5, 10, 15, 30, 60] minutes. A 'Custom' button reveals a number input clamped to 1–60 minutes. Backend also clamps the received value with constrain(v, 1, 60) for safety. Co-authored-by: Cursor --- data/app.js | 20 +++++++++++++++++--- src/web_server.cpp | 5 ++++- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/data/app.js b/data/app.js index b6a78eb..f15188e 100644 --- a/data/app.js +++ b/data/app.js @@ -86,14 +86,20 @@ async function renderAlbums(el) { async function renderSlideshow(el) { const settings = await api('/api/settings'); - const intervals = [1, 5, 15, 30, 60]; + const presets = [1, 2, 5, 10, 15, 30, 60]; + const isCustom = !presets.includes(settings.interval_min); const modes = ['Random', 'Chronological', 'Reverse Chrono', 'Favorites Weighted', 'Weighted Recent', 'Weighted Oldest']; el.innerHTML = `

Interval

- ${intervals.map(i => ``).join('')} + ${presets.map(i => ``).join('')} + +
+
+ +
@@ -109,10 +115,18 @@ async function renderSlideshow(el) { btn.onclick = () => { el.querySelectorAll('[data-interval]').forEach(b => b.classList.remove('active')); btn.classList.add('active'); + document.getElementById('customIntervalRow').style.display = + btn.dataset.interval === 'custom' ? 'flex' : 'none'; }; }); document.getElementById('saveSlideshow').onclick = async () => { - const interval = parseInt(el.querySelector('[data-interval].active')?.dataset.interval || '5'); + const activeBtn = el.querySelector('[data-interval].active'); + let interval; + if (activeBtn && activeBtn.dataset.interval === 'custom') { + interval = Math.min(60, Math.max(1, parseInt(document.getElementById('customInterval').value) || 5)); + } else { + interval = parseInt(activeBtn?.dataset.interval || '5'); + } const mode = parseInt(document.getElementById('cycleMode').value); await api('/api/settings', { method: 'POST', body: JSON.stringify({ interval_min: interval, cycle_mode: mode }) }); toast('Slideshow settings saved'); diff --git a/src/web_server.cpp b/src/web_server.cpp index 3868d04..462678d 100644 --- a/src/web_server.cpp +++ b/src/web_server.cpp @@ -310,7 +310,10 @@ void AppWebServer::handlePostSettings(AsyncWebServerRequest* request, Settings s = _settings->get(); - if (!doc["interval_min"].isNull()) s.interval_min = doc["interval_min"]; + if (!doc["interval_min"].isNull()) { + uint8_t v = doc["interval_min"]; + s.interval_min = constrain(v, 1, 60); + } if (!doc["cycle_mode"].isNull()) s.cycle_mode = static_cast((uint8_t)doc["cycle_mode"]); if (!doc["img_quality"].isNull()) s.img_quality = static_cast((uint8_t)doc["img_quality"]); if (!doc["meta_flags"].isNull()) s.meta_flags = doc["meta_flags"];