Add target FPS slider to Capture Settings and remove unused HACS workflow

- Add Target FPS slider (range 10-90) to Capture Settings dialog
- Fix settings PUT to merge with current values instead of resetting defaults
- Update FPS validation range to 10-90 in schema and config
- Remove irrelevant .github/workflows/validate.yml (HACS leftover)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-10 16:15:18 +03:00
parent e3208e0ca2
commit 74d87fd0ab
8 changed files with 57 additions and 25 deletions

View File

@@ -871,11 +871,12 @@ let captureSettingsInitialValues = {};
async function showCaptureSettings(deviceId) {
try {
// Fetch device data, displays, and templates in parallel
const [deviceResponse, displaysResponse, templatesResponse] = await Promise.all([
// Fetch device data, displays, templates, and settings in parallel
const [deviceResponse, displaysResponse, templatesResponse, settingsResponse] = await Promise.all([
fetch(`${API_BASE}/devices/${deviceId}`, { headers: getHeaders() }),
fetch(`${API_BASE}/config/displays`, { headers: getHeaders() }),
fetchWithAuth('/capture-templates'),
fetch(`${API_BASE}/devices/${deviceId}/settings`, { headers: getHeaders() }),
]);
if (deviceResponse.status === 401) {
@@ -889,6 +890,7 @@ async function showCaptureSettings(deviceId) {
}
const device = await deviceResponse.json();
const currentSettings = settingsResponse.ok ? await settingsResponse.json() : {};
// Populate display index select
const displaySelect = document.getElementById('capture-settings-display-index');
@@ -910,6 +912,11 @@ async function showCaptureSettings(deviceId) {
}
displaySelect.value = String(device.settings.display_index ?? 0);
// Populate FPS slider
const fpsValue = Math.max(10, Math.min(90, currentSettings.fps ?? 30));
document.getElementById('capture-settings-fps').value = fpsValue;
document.getElementById('capture-settings-fps-value').textContent = fpsValue;
// Populate capture template select
const templateSelect = document.getElementById('capture-settings-template');
templateSelect.innerHTML = '';
@@ -931,11 +938,13 @@ async function showCaptureSettings(deviceId) {
}
templateSelect.value = device.capture_template_id || 'tpl_mss_default';
// Store device ID and snapshot initial values
// Store device ID, current settings snapshot, and initial values for dirty check
document.getElementById('capture-settings-device-id').value = device.id;
captureSettingsInitialValues = {
display_index: String(device.settings.display_index ?? 0),
fps: String(currentSettings.fps ?? 30),
capture_template_id: device.capture_template_id || 'tpl_mss_default',
_currentSettings: currentSettings,
};
// Show modal
@@ -952,6 +961,7 @@ async function showCaptureSettings(deviceId) {
function isCaptureSettingsDirty() {
return (
document.getElementById('capture-settings-display-index').value !== captureSettingsInitialValues.display_index ||
document.getElementById('capture-settings-fps').value !== captureSettingsInitialValues.fps ||
document.getElementById('capture-settings-template').value !== captureSettingsInitialValues.capture_template_id
);
}
@@ -976,6 +986,7 @@ async function closeCaptureSettingsModal() {
async function saveCaptureSettings() {
const deviceId = document.getElementById('capture-settings-device-id').value;
const display_index = parseInt(document.getElementById('capture-settings-display-index').value) || 0;
const fps = parseInt(document.getElementById('capture-settings-fps').value) || 30;
const capture_template_id = document.getElementById('capture-settings-template').value;
const error = document.getElementById('capture-settings-error');
@@ -999,11 +1010,18 @@ async function saveCaptureSettings() {
return;
}
// Update display index in settings
// Merge changed fields with current settings to avoid resetting other values
const mergedSettings = {
...(captureSettingsInitialValues._currentSettings || {}),
display_index,
fps,
};
// Update settings
const settingsResponse = await fetch(`${API_BASE}/devices/${deviceId}/settings`, {
method: 'PUT',
headers: getHeaders(),
body: JSON.stringify({ display_index })
body: JSON.stringify(mergedSettings)
});
if (settingsResponse.status === 401) {

View File

@@ -261,6 +261,15 @@
<small class="input-hint" data-i18n="settings.display_index.hint">Which screen to capture for this device</small>
</div>
<div class="form-group">
<label for="capture-settings-fps" data-i18n="settings.fps">Target FPS:</label>
<div class="slider-row">
<input type="range" id="capture-settings-fps" min="10" max="90" value="30" oninput="document.getElementById('capture-settings-fps-value').textContent = this.value">
<span id="capture-settings-fps-value" class="slider-value">30</span>
</div>
<small class="input-hint" data-i18n="settings.fps.hint">Target frames per second (10-90)</small>
</div>
<div class="form-group">
<label for="capture-settings-template" data-i18n="settings.capture_template">Capture Template:</label>
<select id="capture-settings-template"></select>

View File

@@ -152,6 +152,8 @@
"settings.url.hint": "IP address or hostname of your WLED device",
"settings.display_index": "Display:",
"settings.display_index.hint": "Which screen to capture for this device",
"settings.fps": "Target FPS:",
"settings.fps.hint": "Target frames per second (10-90)",
"settings.capture_template": "Capture Template:",
"settings.capture_template.hint": "Screen capture engine and configuration for this device",
"settings.button.cancel": "Cancel",

View File

@@ -152,6 +152,8 @@
"settings.url.hint": "IP адрес или имя хоста вашего WLED устройства",
"settings.display_index": "Дисплей:",
"settings.display_index.hint": "Какой экран захватывать для этого устройства",
"settings.fps": "Целевой FPS:",
"settings.fps.hint": "Целевая частота кадров (10-90)",
"settings.capture_template": "Шаблон Захвата:",
"settings.capture_template.hint": "Движок захвата экрана и конфигурация для этого устройства",
"settings.button.cancel": "Отмена",

View File

@@ -997,6 +997,24 @@ input:-webkit-autofill:focus {
font-size: 0.85rem;
}
.slider-row {
display: flex;
align-items: center;
gap: 12px;
}
.slider-row input[type="range"] {
flex: 1;
}
.slider-value {
min-width: 28px;
text-align: center;
font-weight: 600;
font-size: 0.95rem;
color: var(--text-primary);
}
.error-message {
background: rgba(244, 67, 54, 0.1);
border: 1px solid var(--danger-color);