from __future__ import annotations import json, os, uuid from typing import List, Dict from flask import current_app from .models import Actor, Preset, PresetGroup, CONDITION_ICON_FILES, CURATED_ICON_SLUGS PLACEHOLDER_BG = '#334155' PLACEHOLDER_FG = '#e2e8f0' GAME_ICONS_BASE = 'https://raw.githubusercontent.com/game-icons/icons/master' def paths(): return dict( STATE=current_app.config["STATE_PATH"], PRESETS=current_app.config["PRESETS_PATH"], GROUPS=current_app.config["PRESET_GROUPS_PATH"], AVATARS=current_app.config["AVATAR_DIR"], ICONS=current_app.config["ICON_DIR"], DATA=current_app.config["DATA_DIR"], ) def save_state(state) -> None: p = paths() tmp = { 'actors': [a.__dict__ for a in state.actors], 'turn_idx': state.turn_idx, 'round': state.round, 'visible': state.visible, 'dead_mode': state.dead_mode, } with open(p["STATE"], 'w', encoding='utf-8') as f: json.dump(tmp, f, ensure_ascii=False, indent=2) def load_state(state) -> None: p = paths() if not os.path.exists(p["STATE"]): return try: with open(p["STATE"], 'r', encoding='utf-8') as f: data = json.load(f) state.actors = [Actor(**a) for a in data.get('actors', [])] state.turn_idx = int(data.get('turn_idx', 0)) state.round = int(data.get('round', 1)) state.visible = bool(data.get('visible', True)) state.dead_mode = str(data.get('dead_mode', 'normal')) state.normalize() state.touch() except Exception: pass def save_presets(items: List[Preset]) -> None: p = paths() with open(p["PRESETS"], 'w', encoding='utf-8') as f: json.dump([x.__dict__ for x in items], f, ensure_ascii=False, indent=2) def load_presets() -> List[Preset]: p = paths() if not os.path.exists(p["PRESETS"]): return [] try: with open(p["PRESETS"], 'r', encoding='utf-8') as f: arr = json.load(f) return [Preset(**x) for x in arr] except Exception: return [] def save_groups(items: List[PresetGroup]) -> None: p = paths() with open(p["GROUPS"], 'w', encoding='utf-8') as f: json.dump([x.__dict__ for x in items], f, ensure_ascii=False, indent=2) def load_groups() -> List[PresetGroup]: p = paths() if not os.path.exists(p["GROUPS"]): return [] try: with open(p["GROUPS"], 'r', encoding='utf-8') as f: arr = json.load(f) return [PresetGroup(**x) for x in arr] except Exception: return [] def ensure_default_icons() -> None: p = paths() os.makedirs(p["ICONS"], exist_ok=True) for c, fn in CONDITION_ICON_FILES.items(): dest = os.path.join(p["ICONS"], fn) if os.path.exists(dest): continue letter = (c[:1] or '?').upper() svg = f""" {letter} """ try: with open(dest, 'w', encoding='utf-8') as f: f.write(svg) except Exception: pass def seed_curated_icons(overwrite: bool = True) -> list[dict]: ensure_default_icons() out = [] try: from urllib.request import urlopen from urllib.error import URLError, HTTPError p = paths() for cond, (author, slug) in CURATED_ICON_SLUGS.items(): dest = os.path.join(p["ICONS"], f'{cond}.svg') url = f"https://raw.githubusercontent.com/game-icons/icons/master/{author}/{slug}.svg" try: with urlopen(url, timeout=10) as r: svg = r.read() if (not os.path.exists(dest)) or overwrite: with open(dest, 'wb') as f: f.write(svg) out.append({'condition': cond, 'status': 'downloaded'}) else: out.append({'condition': cond, 'status': 'exists'}) except (URLError, HTTPError) as e: out.append({'condition': cond, 'status': f'fallback ({e.__class__.__name__})'}) finally: return out