Add CSPT entity, processed CSS source type, reverse filter, and UI improvements

- Add Color Strip Processing Template (CSPT) entity: reusable filter chains
  for 1D LED strip postprocessing (backend, storage, API, frontend CRUD)
- Add "processed" color strip source type that wraps another CSS source and
  applies a CSPT filter chain (dataclass, stream, schema, modal, cards)
- Add Reverse filter for strip LED order reversal
- Add CSPT and processed CSS nodes/edges to visual graph editor
- Add CSPT test preview WS endpoint with input source selection
- Add device settings CSPT template selector (add + edit modals with hints)
- Use icon grids for palette quantization preset selector in filter lists
- Use EntitySelect for template references and test modal source selectors
- Fix filters.css_filter_template.desc missing localization
- Fix icon grid cell height inequality (grid-auto-rows: 1fr)
- Rename "Processed" subtab to "Processing Templates"
- Localize all new strings (en/ru/zh)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-15 02:16:59 +03:00
parent 7e78323c9c
commit 294d704eb0
72 changed files with 2992 additions and 1416 deletions

View File

@@ -4,8 +4,6 @@ import pytest
from pathlib import Path
from wled_controller.storage.device_store import Device, DeviceStore
from wled_controller.core.processing.processing_settings import ProcessingSettings
from wled_controller.core.capture.calibration import create_default_calibration
@pytest.fixture
@@ -51,8 +49,6 @@ def test_device_to_dict():
assert data["name"] == "Test Device"
assert data["url"] == "http://192.168.1.100"
assert data["led_count"] == 150
assert "settings" in data
assert "calibration" in data
def test_device_from_dict():
@@ -63,11 +59,6 @@ def test_device_from_dict():
"url": "http://192.168.1.100",
"led_count": 150,
"enabled": True,
"settings": {
"display_index": 0,
"fps": 30,
"border_width": 10,
},
}
device = Device.from_dict(data)
@@ -130,9 +121,9 @@ def test_get_device(device_store):
def test_get_device_not_found(device_store):
"""Test retrieving non-existent device."""
device = device_store.get_device("nonexistent")
assert device is None
"""Test retrieving non-existent device raises ValueError."""
with pytest.raises(ValueError, match="not found"):
device_store.get_device("nonexistent")
def test_get_all_devices(device_store):
@@ -165,41 +156,17 @@ def test_update_device(device_store):
assert updated.enabled is False
def test_update_device_settings(device_store):
"""Test updating device settings."""
def test_update_device_led_count(device_store):
"""Test updating device LED count."""
device = device_store.create_device(
name="Test WLED",
url="http://192.168.1.100",
led_count=150,
)
new_settings = ProcessingSettings(fps=60, border_width=20)
updated = device_store.update_device(device.id, led_count=200)
updated = device_store.update_device(
device.id,
settings=new_settings,
)
assert updated.settings.fps == 60
assert updated.settings.border_width == 20
def test_update_device_calibration(device_store):
"""Test updating device calibration."""
device = device_store.create_device(
name="Test WLED",
url="http://192.168.1.100",
led_count=150,
)
new_calibration = create_default_calibration(150)
updated = device_store.update_device(
device.id,
calibration=new_calibration,
)
assert updated.calibration is not None
assert updated.led_count == 200
def test_update_device_not_found(device_store):
@@ -219,7 +186,8 @@ def test_delete_device(device_store):
device_store.delete_device(device.id)
assert device_store.count() == 0
assert device_store.get_device(device.id) is None
with pytest.raises(ValueError, match="not found"):
device_store.get_device(device.id)
def test_delete_device_not_found(device_store):
@@ -273,33 +241,28 @@ def test_clear(device_store):
assert device_store.count() == 0
def test_update_led_count_resets_calibration(device_store):
"""Test that updating LED count resets calibration."""
def test_update_device_ignores_none_values(device_store):
"""Test that update_device ignores None kwargs."""
device = device_store.create_device(
name="Test WLED",
url="http://192.168.1.100",
led_count=150,
)
original_calibration = device.calibration
updated = device_store.update_device(device.id, name=None, led_count=200)
# Update LED count
updated = device_store.update_device(device.id, led_count=200)
# Calibration should be reset for new LED count
assert updated.calibration.get_total_leds() == 200
assert updated.calibration != original_calibration
assert updated.name == "Test WLED" # unchanged
assert updated.led_count == 200
def test_update_calibration_led_count_mismatch(device_store):
"""Test updating calibration with mismatched LED count fails."""
def test_update_device_ignores_unknown_fields(device_store):
"""Test that update_device silently ignores unknown kwargs."""
device = device_store.create_device(
name="Test WLED",
url="http://192.168.1.100",
led_count=150,
)
wrong_calibration = create_default_calibration(100)
with pytest.raises(ValueError, match="does not match"):
device_store.update_device(device.id, calibration=wrong_calibration)
# Should not raise even with an unknown kwarg
updated = device_store.update_device(device.id, nonexistent_field="foo")
assert updated.name == "Test WLED"