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

@@ -1,17 +0,0 @@
name: Validate
on:
push:
pull_request:
schedule:
- cron: "0 0 * * *"
jobs:
validate:
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v3"
- name: HACS validation
uses: "hacs/action@main"
with:
category: "integration"

View File

@@ -79,7 +79,7 @@ class ProcessingSettings(BaseModel):
"""Processing settings for a device."""
display_index: int = Field(default=0, description="Display to capture", ge=0)
fps: int = Field(default=30, description="Target frames per second", ge=1, le=60)
fps: int = Field(default=30, description="Target frames per second", ge=10, le=90)
border_width: int = Field(default=10, description="Border width in pixels", ge=1, le=100)
brightness: float = Field(default=1.0, description="Global brightness (0.0-1.0)", ge=0.0, le=1.0)
state_check_interval: int = Field(

View File

@@ -28,8 +28,8 @@ class ProcessingConfig(BaseSettings):
"""Processing configuration."""
default_fps: int = 30
max_fps: int = 60
min_fps: int = 1
max_fps: int = 90
min_fps: int = 10
border_width: int = 10
interpolation_mode: Literal["average", "median", "dominant"] = "average"

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);