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:
@@ -1,4 +1,10 @@
|
||||
"""Dependency injection for API routes."""
|
||||
"""Dependency injection for API routes.
|
||||
|
||||
Uses a registry dict instead of individual module-level globals.
|
||||
All getter function signatures remain unchanged for FastAPI Depends() compatibility.
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, Type, TypeVar
|
||||
|
||||
from wled_controller.core.processing.processor_manager import ProcessorManager
|
||||
from wled_controller.storage import DeviceStore
|
||||
@@ -14,147 +20,101 @@ from wled_controller.storage.value_source_store import ValueSourceStore
|
||||
from wled_controller.storage.automation_store import AutomationStore
|
||||
from wled_controller.storage.scene_preset_store import ScenePresetStore
|
||||
from wled_controller.storage.sync_clock_store import SyncClockStore
|
||||
from wled_controller.storage.color_strip_processing_template_store import ColorStripProcessingTemplateStore
|
||||
from wled_controller.core.automations.automation_engine import AutomationEngine
|
||||
from wled_controller.core.backup.auto_backup import AutoBackupEngine
|
||||
from wled_controller.core.processing.sync_clock_manager import SyncClockManager
|
||||
|
||||
# Global instances (initialized in main.py)
|
||||
_auto_backup_engine: AutoBackupEngine | None = None
|
||||
_device_store: DeviceStore | None = None
|
||||
_template_store: TemplateStore | None = None
|
||||
_pp_template_store: PostprocessingTemplateStore | None = None
|
||||
_pattern_template_store: PatternTemplateStore | None = None
|
||||
_picture_source_store: PictureSourceStore | None = None
|
||||
_output_target_store: OutputTargetStore | None = None
|
||||
_color_strip_store: ColorStripStore | None = None
|
||||
_audio_source_store: AudioSourceStore | None = None
|
||||
_audio_template_store: AudioTemplateStore | None = None
|
||||
_value_source_store: ValueSourceStore | None = None
|
||||
_processor_manager: ProcessorManager | None = None
|
||||
_automation_store: AutomationStore | None = None
|
||||
_scene_preset_store: ScenePresetStore | None = None
|
||||
_automation_engine: AutomationEngine | None = None
|
||||
_sync_clock_store: SyncClockStore | None = None
|
||||
_sync_clock_manager: SyncClockManager | None = None
|
||||
T = TypeVar("T")
|
||||
|
||||
# Central dependency registry — keyed by type or string label
|
||||
_deps: Dict[str, Any] = {}
|
||||
|
||||
|
||||
def _get(key: str, label: str) -> Any:
|
||||
"""Get a dependency by key, raising RuntimeError if not initialized."""
|
||||
dep = _deps.get(key)
|
||||
if dep is None:
|
||||
raise RuntimeError(f"{label} not initialized")
|
||||
return dep
|
||||
|
||||
|
||||
# ── Typed getters (unchanged signatures for FastAPI Depends()) ──────────
|
||||
|
||||
|
||||
def get_device_store() -> DeviceStore:
|
||||
"""Get device store dependency."""
|
||||
if _device_store is None:
|
||||
raise RuntimeError("Device store not initialized")
|
||||
return _device_store
|
||||
return _get("device_store", "Device store")
|
||||
|
||||
|
||||
def get_template_store() -> TemplateStore:
|
||||
"""Get template store dependency."""
|
||||
if _template_store is None:
|
||||
raise RuntimeError("Template store not initialized")
|
||||
return _template_store
|
||||
return _get("template_store", "Template store")
|
||||
|
||||
|
||||
def get_pp_template_store() -> PostprocessingTemplateStore:
|
||||
"""Get postprocessing template store dependency."""
|
||||
if _pp_template_store is None:
|
||||
raise RuntimeError("Postprocessing template store not initialized")
|
||||
return _pp_template_store
|
||||
return _get("pp_template_store", "Postprocessing template store")
|
||||
|
||||
|
||||
def get_pattern_template_store() -> PatternTemplateStore:
|
||||
"""Get pattern template store dependency."""
|
||||
if _pattern_template_store is None:
|
||||
raise RuntimeError("Pattern template store not initialized")
|
||||
return _pattern_template_store
|
||||
return _get("pattern_template_store", "Pattern template store")
|
||||
|
||||
|
||||
def get_picture_source_store() -> PictureSourceStore:
|
||||
"""Get picture source store dependency."""
|
||||
if _picture_source_store is None:
|
||||
raise RuntimeError("Picture source store not initialized")
|
||||
return _picture_source_store
|
||||
return _get("picture_source_store", "Picture source store")
|
||||
|
||||
|
||||
def get_output_target_store() -> OutputTargetStore:
|
||||
"""Get output target store dependency."""
|
||||
if _output_target_store is None:
|
||||
raise RuntimeError("Picture target store not initialized")
|
||||
return _output_target_store
|
||||
return _get("output_target_store", "Output target store")
|
||||
|
||||
|
||||
def get_color_strip_store() -> ColorStripStore:
|
||||
"""Get color strip store dependency."""
|
||||
if _color_strip_store is None:
|
||||
raise RuntimeError("Color strip store not initialized")
|
||||
return _color_strip_store
|
||||
return _get("color_strip_store", "Color strip store")
|
||||
|
||||
|
||||
def get_audio_source_store() -> AudioSourceStore:
|
||||
"""Get audio source store dependency."""
|
||||
if _audio_source_store is None:
|
||||
raise RuntimeError("Audio source store not initialized")
|
||||
return _audio_source_store
|
||||
return _get("audio_source_store", "Audio source store")
|
||||
|
||||
|
||||
def get_audio_template_store() -> AudioTemplateStore:
|
||||
"""Get audio template store dependency."""
|
||||
if _audio_template_store is None:
|
||||
raise RuntimeError("Audio template store not initialized")
|
||||
return _audio_template_store
|
||||
return _get("audio_template_store", "Audio template store")
|
||||
|
||||
|
||||
def get_value_source_store() -> ValueSourceStore:
|
||||
"""Get value source store dependency."""
|
||||
if _value_source_store is None:
|
||||
raise RuntimeError("Value source store not initialized")
|
||||
return _value_source_store
|
||||
return _get("value_source_store", "Value source store")
|
||||
|
||||
|
||||
def get_processor_manager() -> ProcessorManager:
|
||||
"""Get processor manager dependency."""
|
||||
if _processor_manager is None:
|
||||
raise RuntimeError("Processor manager not initialized")
|
||||
return _processor_manager
|
||||
return _get("processor_manager", "Processor manager")
|
||||
|
||||
|
||||
def get_automation_store() -> AutomationStore:
|
||||
"""Get automation store dependency."""
|
||||
if _automation_store is None:
|
||||
raise RuntimeError("Automation store not initialized")
|
||||
return _automation_store
|
||||
return _get("automation_store", "Automation store")
|
||||
|
||||
|
||||
def get_scene_preset_store() -> ScenePresetStore:
|
||||
"""Get scene preset store dependency."""
|
||||
if _scene_preset_store is None:
|
||||
raise RuntimeError("Scene preset store not initialized")
|
||||
return _scene_preset_store
|
||||
return _get("scene_preset_store", "Scene preset store")
|
||||
|
||||
|
||||
def get_automation_engine() -> AutomationEngine:
|
||||
"""Get automation engine dependency."""
|
||||
if _automation_engine is None:
|
||||
raise RuntimeError("Automation engine not initialized")
|
||||
return _automation_engine
|
||||
return _get("automation_engine", "Automation engine")
|
||||
|
||||
|
||||
def get_auto_backup_engine() -> AutoBackupEngine:
|
||||
"""Get auto-backup engine dependency."""
|
||||
if _auto_backup_engine is None:
|
||||
raise RuntimeError("Auto-backup engine not initialized")
|
||||
return _auto_backup_engine
|
||||
return _get("auto_backup_engine", "Auto-backup engine")
|
||||
|
||||
|
||||
def get_sync_clock_store() -> SyncClockStore:
|
||||
"""Get sync clock store dependency."""
|
||||
if _sync_clock_store is None:
|
||||
raise RuntimeError("Sync clock store not initialized")
|
||||
return _sync_clock_store
|
||||
return _get("sync_clock_store", "Sync clock store")
|
||||
|
||||
|
||||
def get_sync_clock_manager() -> SyncClockManager:
|
||||
"""Get sync clock manager dependency."""
|
||||
if _sync_clock_manager is None:
|
||||
raise RuntimeError("Sync clock manager not initialized")
|
||||
return _sync_clock_manager
|
||||
return _get("sync_clock_manager", "Sync clock manager")
|
||||
|
||||
|
||||
def get_cspt_store() -> ColorStripProcessingTemplateStore:
|
||||
return _get("cspt_store", "Color strip processing template store")
|
||||
|
||||
|
||||
# ── Event helper ────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def fire_entity_event(entity_type: str, action: str, entity_id: str) -> None:
|
||||
@@ -165,8 +125,9 @@ def fire_entity_event(entity_type: str, action: str, entity_id: str) -> None:
|
||||
action: "created", "updated", or "deleted"
|
||||
entity_id: The entity's unique ID
|
||||
"""
|
||||
if _processor_manager is not None:
|
||||
_processor_manager.fire_event({
|
||||
pm = _deps.get("processor_manager")
|
||||
if pm is not None:
|
||||
pm.fire_event({
|
||||
"type": "entity_changed",
|
||||
"entity_type": entity_type,
|
||||
"action": action,
|
||||
@@ -174,6 +135,9 @@ def fire_entity_event(entity_type: str, action: str, entity_id: str) -> None:
|
||||
})
|
||||
|
||||
|
||||
# ── Initialization ──────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def init_dependencies(
|
||||
device_store: DeviceStore,
|
||||
template_store: TemplateStore,
|
||||
@@ -192,27 +156,26 @@ def init_dependencies(
|
||||
auto_backup_engine: AutoBackupEngine | None = None,
|
||||
sync_clock_store: SyncClockStore | None = None,
|
||||
sync_clock_manager: SyncClockManager | None = None,
|
||||
cspt_store: ColorStripProcessingTemplateStore | None = None,
|
||||
):
|
||||
"""Initialize global dependencies."""
|
||||
global _device_store, _template_store, _processor_manager
|
||||
global _pp_template_store, _pattern_template_store, _picture_source_store, _output_target_store
|
||||
global _color_strip_store, _audio_source_store, _audio_template_store
|
||||
global _value_source_store, _automation_store, _scene_preset_store, _automation_engine, _auto_backup_engine
|
||||
global _sync_clock_store, _sync_clock_manager
|
||||
_device_store = device_store
|
||||
_template_store = template_store
|
||||
_processor_manager = processor_manager
|
||||
_pp_template_store = pp_template_store
|
||||
_pattern_template_store = pattern_template_store
|
||||
_picture_source_store = picture_source_store
|
||||
_output_target_store = output_target_store
|
||||
_color_strip_store = color_strip_store
|
||||
_audio_source_store = audio_source_store
|
||||
_audio_template_store = audio_template_store
|
||||
_value_source_store = value_source_store
|
||||
_automation_store = automation_store
|
||||
_scene_preset_store = scene_preset_store
|
||||
_automation_engine = automation_engine
|
||||
_auto_backup_engine = auto_backup_engine
|
||||
_sync_clock_store = sync_clock_store
|
||||
_sync_clock_manager = sync_clock_manager
|
||||
_deps.update({
|
||||
"device_store": device_store,
|
||||
"template_store": template_store,
|
||||
"processor_manager": processor_manager,
|
||||
"pp_template_store": pp_template_store,
|
||||
"pattern_template_store": pattern_template_store,
|
||||
"picture_source_store": picture_source_store,
|
||||
"output_target_store": output_target_store,
|
||||
"color_strip_store": color_strip_store,
|
||||
"audio_source_store": audio_source_store,
|
||||
"audio_template_store": audio_template_store,
|
||||
"value_source_store": value_source_store,
|
||||
"automation_store": automation_store,
|
||||
"scene_preset_store": scene_preset_store,
|
||||
"automation_engine": automation_engine,
|
||||
"auto_backup_engine": auto_backup_engine,
|
||||
"sync_clock_store": sync_clock_store,
|
||||
"sync_clock_manager": sync_clock_manager,
|
||||
"cspt_store": cspt_store,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user