from __future__ import annotations from dataclasses import dataclass, asdict from datetime import datetime from pathlib import Path import json import re from .types import PatternParameters @dataclass class PresetRecord: name: str pattern_id: str parameters: dict brightness: float palette: str created_at: str tempo_bpm: float | None = None @classmethod def create(cls, name: str, pattern_id: str, params: PatternParameters, tempo_bpm: float | None = None) -> "PresetRecord": return cls( name=name, pattern_id=pattern_id, parameters=params.to_dict(), brightness=params.brightness, palette=params.palette, created_at=datetime.utcnow().isoformat(timespec="seconds"), tempo_bpm=tempo_bpm, ) class PresetStore: def __init__(self, root: str | Path) -> None: self.root = Path(root) self.root.mkdir(parents=True, exist_ok=True) def list_presets(self) -> list[PresetRecord]: presets: list[PresetRecord] = [] for path in sorted(self.root.glob("*.json")): try: payload = json.loads(path.read_text(encoding="utf-8")) presets.append(PresetRecord(**payload)) except (OSError, json.JSONDecodeError, TypeError): continue return presets def save(self, record: PresetRecord) -> Path: path = self.root / f"{slugify(record.name)}.json" path.write_text(json.dumps(asdict(record), indent=2), encoding="utf-8") return path def load(self, name: str) -> PresetRecord: path = self.root / f"{slugify(name)}.json" payload = json.loads(path.read_text(encoding="utf-8")) return PresetRecord(**payload) def delete(self, name: str) -> None: path = self.root / f"{slugify(name)}.json" if path.exists(): path.unlink() def ensure_seed_presets(self) -> None: if any(self.root.glob("*.json")): return seeds = [ PresetRecord.create( "Afterhours Pulse", "center_pulse", PatternParameters(palette="Afterhours", color_mode="palette", fade=0.28), ), PresetRecord.create( "Laser Chase", "scan_dual", PatternParameters(palette="Laser Club", block_size=1.6, direction="left_to_right"), ), PresetRecord.create( "Heat Breathing", "breathing", PatternParameters(palette="Warehouse Heat", color_mode="palette", fade=0.7), ), ] for preset in seeds: self.save(preset) def slugify(value: str) -> str: return re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-") or "preset"