Fix settings persistence, streaming stability, and UI polish

- Fix device settings partial update using model_fields_set for true merge
- Add missing interpolation_mode and smoothing to all API responses
- Fix send_pixels race condition when wled_client is None during stop
- Allow LED segments to exceed edge pixel count (float stepping)
- Fix modal scroll lock using position:fixed to prevent layout shift
- Show loading state for brightness slider until real value is fetched
- Remove stream description from stream selector dialog

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-12 02:59:34 +03:00
parent aa02a5f372
commit 66eecdb3c9
5 changed files with 58 additions and 28 deletions

View File

@@ -47,16 +47,18 @@ const EDGE_TEST_COLORS = {
left: [255, 255, 0]
};
// Modal body lock helpers - prevent layout jump when scrollbar disappears
// Modal body lock helpers — uses position:fixed to freeze scroll without removing scrollbar
function lockBody() {
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
document.body.style.paddingRight = scrollbarWidth + 'px';
const scrollY = window.scrollY;
document.body.style.top = `-${scrollY}px`;
document.body.classList.add('modal-open');
}
function unlockBody() {
const scrollY = parseInt(document.body.style.top || '0', 10) * -1;
document.body.classList.remove('modal-open');
document.body.style.paddingRight = '';
document.body.style.top = '';
window.scrollTo(0, scrollY);
}
// Image lightbox
@@ -764,12 +766,13 @@ function createDeviceCard(device) {
</div>
` : ''}
</div>
<div class="brightness-control">
<div class="brightness-control${_deviceBrightnessCache[device.id] == null ? ' brightness-loading' : ''}" data-brightness-wrap="${device.id}">
<input type="range" class="brightness-slider" min="0" max="255"
value="${_deviceBrightnessCache[device.id] ?? 128}" data-device-brightness="${device.id}"
value="${_deviceBrightnessCache[device.id] ?? 0}" data-device-brightness="${device.id}"
oninput="updateBrightnessLabel('${device.id}', this.value)"
onchange="saveCardBrightness('${device.id}', this.value)"
title="${_deviceBrightnessCache[device.id] != null ? Math.round(_deviceBrightnessCache[device.id] / 255 * 100) + '%' : '...'}">
title="${_deviceBrightnessCache[device.id] != null ? Math.round(_deviceBrightnessCache[device.id] / 255 * 100) + '%' : '...'}"
${_deviceBrightnessCache[device.id] == null ? 'disabled' : ''}>
</div>
<div class="card-actions">
${isProcessing ? `
@@ -1231,7 +1234,10 @@ async function fetchDeviceBrightness(deviceId) {
if (slider) {
slider.value = data.brightness;
slider.title = Math.round(data.brightness / 255 * 100) + '%';
slider.disabled = false;
}
const wrap = document.querySelector(`[data-brightness-wrap="${deviceId}"]`);
if (wrap) wrap.classList.remove('brightness-loading');
} catch (err) {
// Silently fail — device may be offline
}
@@ -4279,7 +4285,6 @@ async function updateStreamSelectorInfo(streamId) {
<span class="stream-card-prop" title="${t('streams.type')}">${typeIcon} ${typeName}</span>
${propsHtml}
</div>
${stream.description ? `<div class="template-config" style="opacity:0.7;">${escapeHtml(stream.description)}</div>` : ''}
`;
infoPanel.style.display = '';
} catch {