CSS: add led_count field; calibration dialog improvements; color corrections collapsible section

- Add explicit led_count to PictureColorStripSource (0 = auto from calibration)
- Stream pads with black or truncates to match led_count exactly
- Calibration dialog: show led_count input above visual editor in CSS mode
- Calibration dialog: pre-populate led_count with effective count (cal sum) when stored value is 0
- Calibration dialog: sync preview label live as led_count input changes
- CSS editor: group brightness/saturation/gamma into collapsible "Color Corrections" section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-20 16:42:32 +03:00
parent 7de3546b14
commit a3aeafef13
14 changed files with 173 additions and 47 deletions

View File

@@ -30,6 +30,7 @@ class CalibrationModal extends Modal {
skip_start: this.$('cal-skip-start').value,
skip_end: this.$('cal-skip-end').value,
border_width: this.$('cal-border-width').value,
led_count: this.$('cal-css-led-count').value,
};
}
@@ -112,6 +113,7 @@ export async function showCalibration(deviceId) {
document.getElementById('calibration-device-id').value = device.id;
document.getElementById('cal-device-led-count-inline').textContent = device.led_count;
document.getElementById('cal-css-led-count-group').style.display = 'none';
document.getElementById('cal-start-position').value = calibration.start_position;
document.getElementById('cal-layout').value = calibration.layout;
@@ -216,6 +218,11 @@ export async function showCSSCalibration(cssId) {
const preview = document.querySelector('.calibration-preview');
preview.style.aspectRatio = '';
document.getElementById('cal-device-led-count-inline').textContent = '—';
const ledCountGroup = document.getElementById('cal-css-led-count-group');
ledCountGroup.style.display = '';
const calLeds = (calibration.leds_top || 0) + (calibration.leds_right || 0) +
(calibration.leds_bottom || 0) + (calibration.leds_left || 0);
document.getElementById('cal-css-led-count').value = source.led_count || calLeds || 0;
document.getElementById('cal-start-position').value = calibration.start_position || 'bottom_left';
document.getElementById('cal-layout').value = calibration.layout || 'clockwise';
@@ -286,8 +293,17 @@ export function updateCalibrationPreview() {
parseInt(document.getElementById('cal-left-leds').value || 0);
const totalEl = document.querySelector('.preview-screen-total');
const inCSS = _isCSS();
const deviceCount = inCSS ? null : parseInt(document.getElementById('cal-device-led-count-inline').textContent || 0);
const mismatch = !inCSS && total !== deviceCount;
const declaredCount = inCSS
? parseInt(document.getElementById('cal-css-led-count').value || 0)
: parseInt(document.getElementById('cal-device-led-count-inline').textContent || 0);
if (inCSS) {
document.getElementById('cal-device-led-count-inline').textContent = declaredCount || '—';
}
// In device mode: calibration total must exactly equal device LED count
// In CSS mode: warn only if calibrated LEDs exceed the declared total (padding handles the rest)
const mismatch = inCSS
? (declaredCount > 0 && total > declaredCount)
: (total !== declaredCount);
document.getElementById('cal-total-leds-inline').textContent = (mismatch ? '\u26A0 ' : '') + total;
if (totalEl) totalEl.classList.toggle('mismatch', mismatch);
@@ -831,10 +847,18 @@ export async function saveCalibration() {
const leftLeds = parseInt(document.getElementById('cal-left-leds').value || 0);
const total = topLeds + rightLeds + bottomLeds + leftLeds;
const declaredLedCount = cssMode
? parseInt(document.getElementById('cal-css-led-count').value) || 0
: parseInt(document.getElementById('cal-device-led-count-inline').textContent) || 0;
if (!cssMode) {
const deviceLedCount = parseInt(document.getElementById('cal-device-led-count-inline').textContent);
if (total !== deviceLedCount) {
error.textContent = `Total LEDs (${total}) must equal device LED count (${deviceLedCount})`;
if (total !== declaredLedCount) {
error.textContent = `Total LEDs (${total}) must equal device LED count (${declaredLedCount})`;
error.style.display = 'block';
return;
}
} else {
if (declaredLedCount > 0 && total > declaredLedCount) {
error.textContent = `Calibrated LEDs (${total}) exceed total LED count (${declaredLedCount})`;
error.style.display = 'block';
return;
}
@@ -862,7 +886,7 @@ export async function saveCalibration() {
if (cssMode) {
response = await fetchWithAuth(`/color-strip-sources/${cssId}`, {
method: 'PUT',
body: JSON.stringify({ calibration }),
body: JSON.stringify({ calibration, led_count: declaredLedCount }),
});
} else {
response = await fetchWithAuth(`/devices/${deviceId}/calibration`, {

View File

@@ -22,6 +22,7 @@ class CSSEditorModal extends Modal {
brightness: document.getElementById('css-editor-brightness').value,
saturation: document.getElementById('css-editor-saturation').value,
gamma: document.getElementById('css-editor-gamma').value,
led_count: document.getElementById('css-editor-led-count').value,
};
}
}
@@ -35,7 +36,8 @@ export function createColorStripCard(source, pictureSourceMap) {
? pictureSourceMap[source.picture_source_id].name
: source.picture_source_id || '—';
const cal = source.calibration || {};
const ledCount = (cal.leds_top || 0) + (cal.leds_right || 0) + (cal.leds_bottom || 0) + (cal.leds_left || 0);
const calLeds = (cal.leds_top || 0) + (cal.leds_right || 0) + (cal.leds_bottom || 0) + (cal.leds_left || 0);
const ledCount = (source.led_count > 0) ? source.led_count : calLeds;
return `
<div class="card" data-css-id="${source.id}">
@@ -107,6 +109,8 @@ export async function showCSSEditor(cssId = null) {
document.getElementById('css-editor-gamma').value = gamma;
document.getElementById('css-editor-gamma-value').textContent = parseFloat(gamma).toFixed(2);
document.getElementById('css-editor-led-count').value = css.led_count ?? 0;
document.getElementById('css-editor-title').textContent = t('color_strip.edit');
} else {
document.getElementById('css-editor-id').value = '';
@@ -122,6 +126,7 @@ export async function showCSSEditor(cssId = null) {
document.getElementById('css-editor-saturation-value').textContent = '1.00';
document.getElementById('css-editor-gamma').value = 1.0;
document.getElementById('css-editor-gamma-value').textContent = '1.00';
document.getElementById('css-editor-led-count').value = 0;
document.getElementById('css-editor-title').textContent = t('color_strip.add');
}
@@ -160,6 +165,7 @@ export async function saveCSSEditor() {
brightness: parseFloat(document.getElementById('css-editor-brightness').value),
saturation: parseFloat(document.getElementById('css-editor-saturation').value),
gamma: parseFloat(document.getElementById('css-editor-gamma').value),
led_count: parseInt(document.getElementById('css-editor-led-count').value) || 0,
};
try {

View File

@@ -100,9 +100,7 @@ export function createDeviceCard(device) {
<button class="btn btn-icon btn-secondary" onclick="showSettings('${device.id}')" title="${t('device.button.settings')}">
⚙️
</button>
<button class="btn btn-icon btn-secondary" onclick="showCalibration('${device.id}')" title="${t('device.button.calibrate')}">
📐
</button>
</div>
</div>
`;