Streamline calibration modal: inline controls, dynamic aspect ratio, offset fix
Some checks failed
Validate / validate (push) Failing after 8s
Some checks failed
Validate / validate (push) Failing after 8s
- Move total LEDs counter, direction toggle, and offset input into the screen area of the calibration preview - Remove description paragraph, standalone offset form, and total LEDs banner - Add mismatch warning (yellow + ⚠) when configured LEDs ≠ device count - Use actual display aspect ratio for calibration preview - Fix offset not updating tick labels (buildSegments now starts at offset) - Remove max-width constraint on preview, add padding for breathing room - Clean up unused i18n keys from both locale files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -980,10 +980,11 @@ function closeConfirmModal(result) {
|
||||
// Calibration functions
|
||||
async function showCalibration(deviceId) {
|
||||
try {
|
||||
// Fetch current device data
|
||||
const response = await fetch(`${API_BASE}/devices/${deviceId}`, {
|
||||
headers: getHeaders()
|
||||
});
|
||||
// Fetch device data and displays in parallel
|
||||
const [response, displaysResponse] = await Promise.all([
|
||||
fetch(`${API_BASE}/devices/${deviceId}`, { headers: getHeaders() }),
|
||||
fetch(`${API_BASE}/config/displays`, { headers: getHeaders() }),
|
||||
]);
|
||||
|
||||
if (response.status === 401) {
|
||||
handle401Error();
|
||||
@@ -998,9 +999,24 @@ async function showCalibration(deviceId) {
|
||||
const device = await response.json();
|
||||
const calibration = device.calibration;
|
||||
|
||||
// Set aspect ratio from device's display
|
||||
const preview = document.querySelector('.calibration-preview');
|
||||
if (displaysResponse.ok) {
|
||||
const displaysData = await displaysResponse.json();
|
||||
const displayIndex = device.settings?.display_index ?? 0;
|
||||
const display = (displaysData.displays || []).find(d => d.index === displayIndex);
|
||||
if (display && display.width && display.height) {
|
||||
preview.style.aspectRatio = `${display.width} / ${display.height}`;
|
||||
} else {
|
||||
preview.style.aspectRatio = '';
|
||||
}
|
||||
} else {
|
||||
preview.style.aspectRatio = '';
|
||||
}
|
||||
|
||||
// Store device ID and LED count
|
||||
document.getElementById('calibration-device-id').value = device.id;
|
||||
document.getElementById('cal-device-led-count').textContent = device.led_count;
|
||||
document.getElementById('cal-device-led-count-inline').textContent = device.led_count;
|
||||
|
||||
// Set layout
|
||||
document.getElementById('cal-start-position').value = calibration.start_position;
|
||||
@@ -1083,7 +1099,14 @@ function updateCalibrationPreview() {
|
||||
parseInt(document.getElementById('cal-right-leds').value || 0) +
|
||||
parseInt(document.getElementById('cal-bottom-leds').value || 0) +
|
||||
parseInt(document.getElementById('cal-left-leds').value || 0);
|
||||
document.getElementById('cal-total-leds').textContent = total;
|
||||
// Warning if total doesn't match device LED count
|
||||
const totalEl = document.querySelector('.preview-screen-total');
|
||||
const deviceCount = parseInt(document.getElementById('cal-device-led-count-inline').textContent || 0);
|
||||
const mismatch = total !== deviceCount;
|
||||
document.getElementById('cal-total-leds-inline').textContent = (mismatch ? '\u26A0 ' : '') + total;
|
||||
if (totalEl) {
|
||||
totalEl.classList.toggle('mismatch', mismatch);
|
||||
}
|
||||
|
||||
// Update corner dot highlights for start position
|
||||
const startPos = document.getElementById('cal-start-position').value;
|
||||
@@ -1174,11 +1197,9 @@ function renderCalibrationCanvas() {
|
||||
const segments = buildSegments(calibration);
|
||||
if (segments.length === 0) return;
|
||||
|
||||
// Edge bar geometry (matches CSS: corner zones 56px × 36px proportional)
|
||||
const cornerFracW = 56 / 500;
|
||||
const cornerFracH = 36 / 312.5;
|
||||
const cw = cornerFracW * cW;
|
||||
const ch = cornerFracH * cH;
|
||||
// Edge bar geometry (matches CSS: corner zones 56px × 36px fixed)
|
||||
const cw = 56;
|
||||
const ch = 36;
|
||||
|
||||
// Edge midlines (center of each edge bar) - in canvas coords
|
||||
const edgeGeometry = {
|
||||
@@ -1371,7 +1392,7 @@ async function clearTestMode(deviceId) {
|
||||
|
||||
async function saveCalibration() {
|
||||
const deviceId = document.getElementById('calibration-device-id').value;
|
||||
const deviceLedCount = parseInt(document.getElementById('cal-device-led-count').textContent);
|
||||
const deviceLedCount = parseInt(document.getElementById('cal-device-led-count-inline').textContent);
|
||||
const error = document.getElementById('calibration-error');
|
||||
|
||||
// Clear test mode before saving
|
||||
@@ -1476,7 +1497,7 @@ function buildSegments(calibration) {
|
||||
};
|
||||
|
||||
const segments = [];
|
||||
let ledStart = 0;
|
||||
let ledStart = calibration.offset || 0;
|
||||
|
||||
edgeOrder.forEach(edge => {
|
||||
const count = edgeCounts[edge];
|
||||
|
||||
@@ -83,19 +83,21 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="calibration-device-id">
|
||||
<p style="margin-bottom: 12px; color: var(--text-secondary);" data-i18n="calibration.description">
|
||||
Configure how your LED strip is mapped to screen edges. Click an edge to toggle test mode.
|
||||
</p>
|
||||
|
||||
<!-- Interactive Preview with integrated LED inputs and test toggles -->
|
||||
<div style="margin-bottom: 12px;">
|
||||
<div style="margin-bottom: 12px; padding: 0 24px;">
|
||||
<div class="calibration-preview">
|
||||
<!-- Screen with direction toggle -->
|
||||
<!-- Screen with direction toggle, total LEDs, and offset -->
|
||||
<div class="preview-screen">
|
||||
<span data-i18n="calibration.preview.screen">Screen</span>
|
||||
<button type="button" class="direction-toggle" onclick="toggleDirection()" title="Toggle direction">
|
||||
<span id="direction-icon">↻</span> <span id="direction-label">CW</span>
|
||||
</button>
|
||||
<div class="preview-screen-total"><span id="cal-total-leds-inline">0</span> / <span id="cal-device-led-count-inline">0</span></div>
|
||||
<div class="preview-screen-controls">
|
||||
<button type="button" class="direction-toggle" onclick="toggleDirection()" title="Toggle direction">
|
||||
<span id="direction-icon">↻</span> <span id="direction-label">CW</span>
|
||||
</button>
|
||||
<label class="offset-control" title="LED offset from LED 0 to start corner">
|
||||
<span>⊕</span>
|
||||
<input type="number" id="cal-offset" min="0" value="0" oninput="updateCalibrationPreview()">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Clickable edge bars with LED count inputs -->
|
||||
@@ -142,15 +144,6 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-bottom: 12px;">
|
||||
<label for="cal-offset" data-i18n="calibration.offset">LED Offset:</label>
|
||||
<input type="number" id="cal-offset" min="0" value="0" oninput="updateCalibrationPreview()">
|
||||
<small style="color: #aaa; display: block; margin-top: 4px;" data-i18n="calibration.offset_hint">LEDs from LED 0 to start corner (along strip)</small>
|
||||
</div>
|
||||
|
||||
<div style="padding: 8px 10px; background: rgba(255, 193, 7, 0.1); border-left: 4px solid #FFC107; border-radius: 4px; margin-bottom: 12px;">
|
||||
<strong data-i18n="calibration.total">Total LEDs:</strong> <span id="cal-total-leds">0</span> / <span id="cal-device-led-count">0</span>
|
||||
</div>
|
||||
|
||||
<div id="calibration-error" class="error-message" style="display: none;"></div>
|
||||
</div>
|
||||
|
||||
@@ -90,8 +90,6 @@
|
||||
"settings.saved": "Settings saved successfully",
|
||||
"settings.failed": "Failed to save settings",
|
||||
"calibration.title": "LED Calibration",
|
||||
"calibration.description": "Configure how your LED strip is mapped to screen edges. Click an edge to toggle test mode.",
|
||||
"calibration.preview.screen": "Screen",
|
||||
"calibration.preview.click_hint": "Click an edge to toggle test LEDs on/off",
|
||||
"calibration.start_position": "Starting Position:",
|
||||
"calibration.position.bottom_left": "Bottom Left",
|
||||
@@ -101,13 +99,10 @@
|
||||
"calibration.direction": "Direction:",
|
||||
"calibration.direction.clockwise": "Clockwise",
|
||||
"calibration.direction.counterclockwise": "Counterclockwise",
|
||||
"calibration.offset": "LED Offset:",
|
||||
"calibration.offset_hint": "LEDs from LED 0 to start corner (along strip)",
|
||||
"calibration.leds.top": "Top LEDs:",
|
||||
"calibration.leds.right": "Right LEDs:",
|
||||
"calibration.leds.bottom": "Bottom LEDs:",
|
||||
"calibration.leds.left": "Left LEDs:",
|
||||
"calibration.total": "Total LEDs:",
|
||||
"calibration.button.cancel": "Cancel",
|
||||
"calibration.button.save": "Save",
|
||||
"calibration.saved": "Calibration saved successfully",
|
||||
|
||||
@@ -90,8 +90,6 @@
|
||||
"settings.saved": "Настройки успешно сохранены",
|
||||
"settings.failed": "Не удалось сохранить настройки",
|
||||
"calibration.title": "Калибровка Светодиодов",
|
||||
"calibration.description": "Настройте как ваша светодиодная лента сопоставляется с краями экрана. Нажмите на край для теста.",
|
||||
"calibration.preview.screen": "Экран",
|
||||
"calibration.preview.click_hint": "Нажмите на край чтобы включить/выключить тест светодиодов",
|
||||
"calibration.start_position": "Начальная Позиция:",
|
||||
"calibration.position.bottom_left": "Нижний Левый",
|
||||
@@ -101,13 +99,10 @@
|
||||
"calibration.direction": "Направление:",
|
||||
"calibration.direction.clockwise": "По Часовой Стрелке",
|
||||
"calibration.direction.counterclockwise": "Против Часовой Стрелки",
|
||||
"calibration.offset": "Смещение LED:",
|
||||
"calibration.offset_hint": "Количество LED от LED 0 до начального угла (по ленте)",
|
||||
"calibration.leds.top": "Светодиодов Сверху:",
|
||||
"calibration.leds.right": "Светодиодов Справа:",
|
||||
"calibration.leds.bottom": "Светодиодов Снизу:",
|
||||
"calibration.leds.left": "Светодиодов Слева:",
|
||||
"calibration.total": "Всего Светодиодов:",
|
||||
"calibration.button.cancel": "Отмена",
|
||||
"calibration.button.save": "Сохранить",
|
||||
"calibration.saved": "Калибровка успешно сохранена",
|
||||
|
||||
@@ -827,8 +827,7 @@ input:-webkit-autofill:focus {
|
||||
.calibration-preview {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
aspect-ratio: 16 / 10;
|
||||
aspect-ratio: 16 / 9;
|
||||
margin: 20px auto;
|
||||
background: var(--card-bg);
|
||||
border: 2px solid var(--border-color);
|
||||
@@ -863,6 +862,54 @@ input:-webkit-autofill:focus {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.preview-screen-total {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
opacity: 0.9;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.preview-screen-total.mismatch {
|
||||
color: #FFC107;
|
||||
}
|
||||
|
||||
.preview-screen-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.offset-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
padding: 4px 8px;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.offset-control input {
|
||||
width: 36px;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
outline: none;
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
.offset-control input::-webkit-outer-spin-button,
|
||||
.offset-control input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.preview-edge {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user