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:
@@ -83,6 +83,7 @@
|
||||
color: #FFC107;
|
||||
}
|
||||
|
||||
|
||||
.inputs-dimmed .edge-led-input {
|
||||
opacity: 0.2;
|
||||
pointer-events: none;
|
||||
|
||||
@@ -221,6 +221,47 @@
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.form-collapse {
|
||||
margin-bottom: 12px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.form-collapse > summary {
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary, #888);
|
||||
padding: 4px 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.form-collapse > summary::-webkit-details-marker { display: none; }
|
||||
|
||||
.form-collapse > summary::before {
|
||||
content: '▶';
|
||||
font-size: 0.6rem;
|
||||
opacity: 0.6;
|
||||
transition: transform 0.15s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.form-collapse[open] > summary::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.form-collapse > summary:hover {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.form-collapse-body {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background: rgba(244, 67, 54, 0.1);
|
||||
border: 1px solid var(--danger-color);
|
||||
|
||||
@@ -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`, {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
|
||||
@@ -552,6 +552,7 @@
|
||||
"color_strip.interpolation.dominant": "Dominant",
|
||||
"color_strip.smoothing": "Smoothing:",
|
||||
"color_strip.smoothing.hint": "Temporal blending between frames (0=none, 1=full). Reduces flicker.",
|
||||
"color_strip.color_corrections": "Color Corrections",
|
||||
"color_strip.brightness": "Brightness:",
|
||||
"color_strip.brightness.hint": "Output brightness multiplier (0=off, 1=unchanged, 2=double). Applied after color extraction.",
|
||||
"color_strip.saturation": "Saturation:",
|
||||
@@ -561,6 +562,8 @@
|
||||
"color_strip.test_device": "Test on Device:",
|
||||
"color_strip.test_device.hint": "Select a device to send test pixels to when clicking edge toggles",
|
||||
"color_strip.leds": "LED count",
|
||||
"color_strip.led_count": "LED Count:",
|
||||
"color_strip.led_count.hint": "Total number of LEDs on the physical strip. Set to 0 to use the sum from calibration. Useful when the strip has LEDs behind the TV that are not mapped to screen edges — those LEDs will be sent black.",
|
||||
"color_strip.created": "Color strip source created",
|
||||
"color_strip.updated": "Color strip source updated",
|
||||
"color_strip.deleted": "Color strip source deleted",
|
||||
|
||||
@@ -552,6 +552,7 @@
|
||||
"color_strip.interpolation.dominant": "Доминирующий",
|
||||
"color_strip.smoothing": "Сглаживание:",
|
||||
"color_strip.smoothing.hint": "Временное смешивание кадров (0=без смешивания, 1=полное). Уменьшает мерцание.",
|
||||
"color_strip.color_corrections": "Цветокоррекция",
|
||||
"color_strip.brightness": "Яркость:",
|
||||
"color_strip.brightness.hint": "Множитель яркости (0=выкл, 1=без изменений, 2=двойная). Применяется после извлечения цвета.",
|
||||
"color_strip.saturation": "Насыщенность:",
|
||||
@@ -561,6 +562,8 @@
|
||||
"color_strip.test_device": "Тестировать на устройстве:",
|
||||
"color_strip.test_device.hint": "Выберите устройство для отправки тестовых пикселей при нажатии на рамку",
|
||||
"color_strip.leds": "Количество светодиодов",
|
||||
"color_strip.led_count": "Количество LED:",
|
||||
"color_strip.led_count.hint": "Общее число светодиодов на физической полосе. 0 = взять из калибровки. Укажите явно, если на полосе есть светодиоды за телевизором, не привязанные к краям экрана — им будет отправлен чёрный цвет.",
|
||||
"color_strip.created": "Источник цветовой полосы создан",
|
||||
"color_strip.updated": "Источник цветовой полосы обновлён",
|
||||
"color_strip.deleted": "Источник цветовой полосы удалён",
|
||||
|
||||
Reference in New Issue
Block a user