feat(settings): server global brightness and Wi-Fi driver resync
- Serve GET /settings as JSON by removing duplicate HTML route (use /settings/page for the standalone UI). - Save global_brightness via PUT; broadcast to connected drivers; push saved level when outbound WS connects. - Zones UI loads brightness from GET /settings only (no localStorage). - Bump led-driver submodule for settings.save on brightness with save flag. - Extend API doc and endpoint tests for global_brightness. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,32 +2,12 @@
|
||||
let currentZoneId = null;
|
||||
let brightnessSendTimeout = null;
|
||||
|
||||
const UI_BRIGHTNESS_STORAGE_KEY = "led_controller_ui_brightness";
|
||||
|
||||
function clamp255(n) {
|
||||
const v = parseInt(n, 10);
|
||||
if (Number.isNaN(v)) return null;
|
||||
return Math.max(0, Math.min(255, v));
|
||||
}
|
||||
|
||||
function loadSavedUiBrightness() {
|
||||
try {
|
||||
const raw = localStorage.getItem(UI_BRIGHTNESS_STORAGE_KEY);
|
||||
if (raw == null) return null;
|
||||
return clamp255(raw);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function persistUiBrightness(value) {
|
||||
const v = clamp255(value);
|
||||
if (v === null) return;
|
||||
try {
|
||||
localStorage.setItem(UI_BRIGHTNESS_STORAGE_KEY, String(v));
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
function applyBrightnessSliders(val) {
|
||||
const v = clamp255(val);
|
||||
if (v === null) return;
|
||||
@@ -37,9 +17,25 @@ function applyBrightnessSliders(val) {
|
||||
if (menuSlider) menuSlider.value = String(v);
|
||||
}
|
||||
|
||||
async function saveGlobalBrightnessToServer(val) {
|
||||
try {
|
||||
const res = await fetch("/settings/settings", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
||||
credentials: "same-origin",
|
||||
body: JSON.stringify({ global_brightness: val }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({}));
|
||||
console.warn("global_brightness save failed:", err.error || res.status);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("global_brightness save failed:", e);
|
||||
}
|
||||
}
|
||||
|
||||
function sendZoneBrightness(value) {
|
||||
const val = Math.max(0, Math.min(255, parseInt(value, 10) || 0));
|
||||
persistUiBrightness(val);
|
||||
const headerSlider = document.getElementById('header-brightness-slider');
|
||||
const menuSlider = document.getElementById('menu-brightness-slider');
|
||||
if (headerSlider && String(headerSlider.value) !== String(val)) {
|
||||
@@ -54,6 +50,7 @@ function sendZoneBrightness(value) {
|
||||
brightnessSendTimeout = setTimeout(() => {
|
||||
(async () => {
|
||||
try {
|
||||
await saveGlobalBrightnessToServer(val);
|
||||
const section = document.querySelector('.presets-section[data-zone-id]');
|
||||
const names = typeof window.parseTabDeviceNames === 'function'
|
||||
? window.parseTabDeviceNames(section)
|
||||
@@ -1027,22 +1024,41 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
const menuBrightnessSlider = document.getElementById('menu-brightness-slider');
|
||||
const headerBrightnessSlider = document.getElementById('header-brightness-slider');
|
||||
const savedBr = loadSavedUiBrightness();
|
||||
if (savedBr !== null) {
|
||||
applyBrightnessSliders(savedBr);
|
||||
}
|
||||
if (menuBrightnessSlider) {
|
||||
menuBrightnessSlider.addEventListener('input', (e) => {
|
||||
sendZoneBrightness(e.target.value);
|
||||
});
|
||||
}
|
||||
if (headerBrightnessSlider) {
|
||||
headerBrightnessSlider.addEventListener('input', (e) => {
|
||||
sendZoneBrightness(e.target.value);
|
||||
});
|
||||
// Apply saved (or default) level to devices once the page is ready.
|
||||
sendZoneBrightness(headerBrightnessSlider.value);
|
||||
}
|
||||
(async () => {
|
||||
let fromServer = null;
|
||||
try {
|
||||
const res = await fetch('/settings', {
|
||||
headers: { Accept: 'application/json' },
|
||||
credentials: 'same-origin',
|
||||
});
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
const g = data.global_brightness;
|
||||
if (typeof g === 'number' && g >= 0 && g <= 255) {
|
||||
fromServer = Math.round(g);
|
||||
} else if (g != null && g !== '') {
|
||||
const n = parseInt(String(g), 10);
|
||||
if (!Number.isNaN(n) && n >= 0 && n <= 255) {
|
||||
fromServer = n;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
if (fromServer !== null) {
|
||||
applyBrightnessSliders(fromServer);
|
||||
}
|
||||
if (menuBrightnessSlider) {
|
||||
menuBrightnessSlider.addEventListener('input', (e) => {
|
||||
sendZoneBrightness(e.target.value);
|
||||
});
|
||||
}
|
||||
if (headerBrightnessSlider) {
|
||||
headerBrightnessSlider.addEventListener('input', (e) => {
|
||||
sendZoneBrightness(e.target.value);
|
||||
});
|
||||
sendZoneBrightness(headerBrightnessSlider.value);
|
||||
}
|
||||
})();
|
||||
|
||||
// When run/edit mode toggles, refresh tabs UI so edit actions show/hide immediately.
|
||||
document.querySelectorAll('.ui-mode-toggle').forEach((btn) => {
|
||||
|
||||
Reference in New Issue
Block a user