- 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>
121 lines
3.8 KiB
Python
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
|