Files
wled-screen-controller-mixed/server/src/wled_controller/storage/template_store.py
alexei.dolgolyov 30fa107ef7 Add tags to all entity types with chip-based input and autocomplete
- Add `tags: List[str]` field to all 13 entity types (devices, output targets,
  CSS sources, picture sources, audio sources, value sources, sync clocks,
  automations, scene presets, capture/audio/PP/pattern templates)
- Update all stores, schemas, and route handlers for tag CRUD
- Add GET /api/v1/tags endpoint aggregating unique tags across all stores
- Create TagInput component with chip display, autocomplete dropdown,
  keyboard navigation, and API-backed suggestions
- Display tag chips on all entity cards (searchable via existing text filter)
- Add tag input to all 14 editor modals with dirty check support
- Add CSS styles and i18n keys (en/ru/zh) for tag UI
- Also includes code review fixes: thread safety, perf, store dedup

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 22:20:19 +03:00

121 lines
3.8 KiB
Python

"""Template storage using JSON files."""
import uuid
from datetime import datetime, timezone
from typing import Dict, List, Optional
from wled_controller.core.capture_engines.factory import EngineRegistry
from wled_controller.storage.base_store import BaseJsonStore
from wled_controller.storage.template import CaptureTemplate
from wled_controller.utils import get_logger
logger = get_logger(__name__)
class TemplateStore(BaseJsonStore[CaptureTemplate]):
"""Storage for capture templates.
All templates are persisted to the JSON file.
On startup, if no templates exist, one is auto-created using the
highest-priority available engine.
"""
_json_key = "templates"
_entity_name = "Capture template"
def __init__(self, file_path: str):
super().__init__(file_path, CaptureTemplate.from_dict)
self._ensure_initial_template()
# Backward-compatible aliases
get_all_templates = BaseJsonStore.get_all
get_template = BaseJsonStore.get
delete_template = BaseJsonStore.delete
def _ensure_initial_template(self) -> None:
"""Auto-create a template if none exist, using the best available engine."""
if self._items:
return
best_engine = EngineRegistry.get_best_available_engine()
if not best_engine:
logger.warning("No capture engines available, cannot create initial template")
return
engine_class = EngineRegistry.get_engine(best_engine)
default_config = engine_class.get_default_config()
now = datetime.now(timezone.utc)
template_id = f"tpl_{uuid.uuid4().hex[:8]}"
template = CaptureTemplate(
id=template_id,
name="Default",
engine_type=best_engine,
engine_config=default_config,
created_at=now,
updated_at=now,
description=f"Default capture template using {best_engine.upper()} engine",
)
self._items[template_id] = template
self._save()
logger.info(f"Auto-created initial template: {template.name} ({template_id}, engine={best_engine})")
def create_template(
self,
name: str,
engine_type: str,
engine_config: Dict[str, any],
description: Optional[str] = None,
tags: Optional[List[str]] = None,
) -> CaptureTemplate:
self._check_name_unique(name)
template_id = f"tpl_{uuid.uuid4().hex[:8]}"
now = datetime.now(timezone.utc)
template = CaptureTemplate(
id=template_id,
name=name,
engine_type=engine_type,
engine_config=engine_config,
created_at=now,
updated_at=now,
description=description,
tags=tags or [],
)
self._items[template_id] = template
self._save()
logger.info(f"Created template: {name} ({template_id})")
return template
def update_template(
self,
template_id: str,
name: Optional[str] = None,
engine_type: Optional[str] = None,
engine_config: Optional[Dict[str, any]] = None,
description: Optional[str] = None,
tags: Optional[List[str]] = None,
) -> CaptureTemplate:
template = self.get(template_id)
if name is not None:
self._check_name_unique(name, exclude_id=template_id)
template.name = name
if engine_type is not None:
template.engine_type = engine_type
if engine_config is not None:
template.engine_config = engine_config
if description is not None:
template.description = description
if tags is not None:
template.tags = tags
template.updated_at = datetime.now(timezone.utc)
self._save()
logger.info(f"Updated template: {template_id}")
return template