From 316c746936480a801b9876a44b4ddb7a103ed8c1 Mon Sep 17 00:00:00 2001 From: Ricardo Guzman Date: Wed, 23 Sep 2026 05:12:51 -0300 Subject: [PATCH] WebUI fixes --- README.md | 2 ++ common_settings.ini | 2 +- data_embed/script.js | 73 ++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9c8a998..da381dd 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,8 @@ ____________________________________________________
# Timeline (Versions): +- 2026-09-23 APRSPacketLib V1.0.7 update and many code cleaning with this. +- 2026-09-17 Real APRS Symbols added to map and TNC2 output as a posibility in TNC mode. - 2026-08-16 New UI (Web) and also Live View over a Map to see Rx stations. - 2026-08-10 CAD (Channel Activity Detection) and DIFS (Distributed Inter-Frame Space) added. - 2026-03-25 More Boards, SDK update, OTA fix, GPS process update. diff --git a/common_settings.ini b/common_settings.ini index cff00c3..6092504 100644 --- a/common_settings.ini +++ b/common_settings.ini @@ -38,7 +38,7 @@ lib_deps = ESP32Async/AsyncTCP @ 3.4.10 ESP32Async/ESPAsyncWebServer @ 3.11.1 mikalhart/TinyGPSPlus @ 1.0.3 - richonguzman/APRSPacketLib @ 1.0.6 + richonguzman/APRSPacketLib @ 1.0.7 display_libs = adafruit/Adafruit GFX Library @ 1.12.6 adafruit/Adafruit SSD1306 @ 2.5.17 diff --git a/data_embed/script.js b/data_embed/script.js index 848c9c6..5cf1b09 100644 --- a/data_embed/script.js +++ b/data_embed/script.js @@ -96,7 +96,7 @@ function loadSettings(settings) { networksContainer.appendChild(networkElement); networkCount++; }); - document.getElementById("startupDelay").value = settings.startupDelay; + document.getElementById("startupDelay").value = settings.other.startupDelay; // APRS-IS document.getElementById("aprs_is.active").checked = settings.aprs_is.active; @@ -277,6 +277,8 @@ function loadSettings(settings) { document.getElementById("ntp.server").value = settings.ntp.server; document.getElementById("ntp.gmtCorrection").value = settings.ntp.gmtCorrection; + clampOutOfRangeInputs(); + updateImage(); } @@ -1102,4 +1104,71 @@ window.showMap = function () { fetchMapStations(); } }, 15000); -} \ No newline at end of file +} + + +/* ------------------------------------------------- keeping the form submittable + * A control that fails HTML5 validation blocks the whole form, and when it sits in + * a panel that is not the visible one the browser cannot show its bubble either: + * pressing Save then does nothing at all -- no message, no request, no clue. Two + * guards against that: pull stored values back inside the allowed range on load, + * and, if validation still fails, open the section holding the offending control + * and show what is wrong. + */ +function clampOutOfRangeInputs() { + const fixed = []; + + document.querySelectorAll("#configuration input[type=number]").forEach((el) => { + if (el.disabled || el.value === "") return; + + const value = parseFloat(el.value); + if (isNaN(value)) return; + + const min = el.min !== "" ? parseFloat(el.min) : null; + const max = el.max !== "" ? parseFloat(el.max) : null; + + let clamped = value; + if (max !== null && value > max) clamped = max; + if (min !== null && value < min) clamped = min; + + if (clamped !== value) { + el.value = clamped; + fixed.push((el.name || el.id) + ": " + value + " → " + clamped); + } + }); + + if (fixed.length) { + showToast("Stored values outside the allowed range, adjusted:
" + fixed.join("
") + + "
Save to keep them."); + } +} + +let invalidFieldReported = false; + +form.addEventListener("invalid", function (event) { + const el = event.target; + + if (!invalidFieldReported) { // only the first one: the rest would pile up toasts + invalidFieldReported = true; + setTimeout(() => { invalidFieldReported = false; }, 500); + + const section = el.closest(".panel-section"); + if (section) { + const link = document.querySelector('.side-link[data-target="' + section.id + '"]'); + if (link) { + link.click(); // reuses the sidebar switcher (also shows the config view) + } else { + document.querySelectorAll(".panel-section").forEach((s) => s.classList.remove("active")); + section.classList.add("active"); + document.getElementById("configuration").classList.remove("d-none"); + } + } + + showToast("" + (el.name || el.id) + ": " + (el.validationMessage || "invalid value")); + + setTimeout(() => { + el.focus(); + el.scrollIntoView({ block: "center", behavior: "smooth" }); + }, 0); + } +}, true); \ No newline at end of file