84 lines
3.0 KiB
Python
84 lines
3.0 KiB
Python
"""Persisted user settings: which quads are enabled, per-module feature toggles,
|
|
and quad ordering. Single JSON file, read at startup, written on every change.
|
|
|
|
Modules never import this directly for their own feature flags; they receive a
|
|
scoped view via ctx.feature(...) so the persistence format stays centralised.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from typing import Callable
|
|
|
|
from paths import CONFIG_DIR, SETTINGS_FILE, ensure_dirs
|
|
|
|
|
|
class Settings:
|
|
def __init__(self) -> None:
|
|
self._data: dict = {"quads": {}, "features": {}, "order": None, "favorites": []}
|
|
self._listeners: list[Callable[[], None]] = []
|
|
self.load()
|
|
|
|
# -- persistence -------------------------------------------------------
|
|
def load(self) -> None:
|
|
path = SETTINGS_FILE
|
|
if not path.exists():
|
|
# one-time migration from the old CONFIG_DIR location (pre state-dir)
|
|
legacy = CONFIG_DIR / "settings.json"
|
|
if legacy.exists():
|
|
path = legacy
|
|
try:
|
|
self._data.update(json.loads(path.read_text()))
|
|
except (FileNotFoundError, json.JSONDecodeError):
|
|
pass
|
|
|
|
def save(self) -> None:
|
|
ensure_dirs()
|
|
SETTINGS_FILE.write_text(json.dumps(self._data, indent=2))
|
|
for cb in list(self._listeners):
|
|
cb()
|
|
|
|
def subscribe(self, cb: Callable[[], None]) -> None:
|
|
self._listeners.append(cb)
|
|
|
|
# -- quad enable/disable ----------------------------------------------
|
|
def quad_enabled(self, module_id: str, default: bool = True) -> bool:
|
|
return bool(self._data["quads"].get(module_id, default))
|
|
|
|
def set_quad_enabled(self, module_id: str, value: bool) -> None:
|
|
self._data["quads"][module_id] = bool(value)
|
|
self.save()
|
|
|
|
# -- per-module feature toggles ---------------------------------------
|
|
def feature(self, module_id: str, feature_id: str, default: bool = True) -> bool:
|
|
return bool(self._data["features"].get(module_id, {}).get(feature_id, default))
|
|
|
|
def set_feature(self, module_id: str, feature_id: str, value: bool) -> None:
|
|
self._data["features"].setdefault(module_id, {})[feature_id] = bool(value)
|
|
self.save()
|
|
|
|
# -- favorites ---------------------------------------------------------
|
|
def favorites(self) -> list[str]:
|
|
return list(self._data.get("favorites", []))
|
|
|
|
def is_favorite(self, entry: str) -> bool:
|
|
return entry in self._data.get("favorites", [])
|
|
|
|
def toggle_favorite(self, entry: str) -> None:
|
|
if not entry:
|
|
return
|
|
favs = self._data.setdefault("favorites", [])
|
|
if entry in favs:
|
|
favs.remove(entry)
|
|
else:
|
|
favs.append(entry)
|
|
self.save()
|
|
|
|
# -- ordering ----------------------------------------------------------
|
|
def order(self) -> list[str] | None:
|
|
return self._data.get("order")
|
|
|
|
def set_order(self, order: list[str]) -> None:
|
|
self._data["order"] = order
|
|
self.save()
|