from __future__ import annotations import json import uuid from dataclasses import dataclass, field, asdict from datetime import datetime from pathlib import Path from typing import Any from utils.logger import get_logger logger = get_logger(__name__) DATA_DIR = Path(__file__).resolve().parent.parent / "data" SCENES_DIR = DATA_DIR / "scenes" @dataclass class DashboardPlacement: dashboard_id: str = "" x: float = 0.0 y: float = 0.0 width: int = 400 height: int = 300 z_index: int = 0 scale_x: float = 1.0 scale_y: float = 1.0 aspect_ratio: str = "auto" render_mode: str = "contain" custom_config: dict[str, Any] = field(default_factory=dict) @dataclass class SceneCanvas: width: int = 1920 height: int = 1080 label: str = "16:9" placements: list[DashboardPlacement] = field(default_factory=list) @dataclass class Scene: id: str = field(default_factory=lambda: str(uuid.uuid4())) name: str = "New Scene" game_id: str = "" description: str = "" canvases: list[SceneCanvas] = field(default_factory=list) created_at: str = field(default_factory=lambda: datetime.now().isoformat()) updated_at: str = field(default_factory=lambda: datetime.now().isoformat()) def to_dict(self) -> dict[str, Any]: d = asdict(self) d["canvases"] = [asdict(c) for c in self.canvases] return d @classmethod def from_dict(cls, d: dict[str, Any]) -> Scene: canvases = [] for cd in d.get("canvases", []): placements = [DashboardPlacement(**p) for p in cd.get("placements", [])] canvases.append(SceneCanvas( width=cd.get("width", 1920), height=cd.get("height", 1080), label=cd.get("label", ""), placements=placements, )) return cls( id=d.get("id", str(uuid.uuid4())), name=d.get("name", "New Scene"), game_id=d.get("game_id", ""), description=d.get("description", ""), canvases=canvases, created_at=d.get("created_at", datetime.now().isoformat()), updated_at=d.get("updated_at", datetime.now().isoformat()), ) class SceneManager: def __init__(self): SCENES_DIR.mkdir(parents=True, exist_ok=True) def list_all(self, game_id: str | None = None) -> list[dict[str, Any]]: scenes = [] for f in SCENES_DIR.glob("*.json"): try: with open(f, "r", encoding="utf-8") as fp: data = json.load(fp) scene = Scene.from_dict(data) if game_id is None or scene.game_id == game_id: scenes.append(scene.to_dict()) except Exception as e: logger.error("Failed to load scene %s: %s", f.name, e) return scenes def get(self, scene_id: str) -> dict[str, Any] | None: filepath = SCENES_DIR / f"{scene_id}.json" if not filepath.exists(): return None try: with open(filepath, "r", encoding="utf-8") as f: data = json.load(f) return Scene.from_dict(data).to_dict() except Exception as e: logger.error("Failed to read scene %s: %s", scene_id, e) return None def save(self, scene: Scene) -> bool: scene.updated_at = datetime.now().isoformat() filepath = SCENES_DIR / f"{scene.id}.json" try: with open(filepath, "w", encoding="utf-8") as f: json.dump(scene.to_dict(), f, indent=2, ensure_ascii=False) logger.info("Scene saved: %s", scene.id) return True except Exception as e: logger.error("Failed to save scene %s: %s", scene.id, e) return False def delete(self, scene_id: str) -> bool: filepath = SCENES_DIR / f"{scene_id}.json" if filepath.exists(): filepath.unlink() logger.info("Scene deleted: %s", scene_id) return True return False def export_scene(self, scene_id: str) -> dict[str, Any] | None: scene = self.get(scene_id) if not scene: return None return { "type": "scene", "version": "1.0", "data": scene, } def import_scene(self, data: dict[str, Any]) -> bool: if data.get("type") != "scene": return False scene_data = data.get("data", {}) if "id" not in scene_data: scene_data["id"] = str(uuid.uuid4()) scene = Scene.from_dict(scene_data) return self.save(scene) scene_manager = SceneManager()