from __future__ import annotations import json from copy import deepcopy from pathlib import Path from typing import Any ALLOWED_COMPONENTS = { "task-list", "overview-summary", "task-progress-grid", "task-header", "today-record", "metric-progress", "activity-heatmap", "task-settings", } # 每个组件允许出现的页面与区域。组件在各自区域内必须单例,避免重复 ID # 与重复事件绑定;跨页/跨区错放(例如把 today-record 放进首页)视为非法布局。 COMPONENT_SCOPES: dict[str, tuple[tuple[str, ...], str]] = { "task-list": (("home", "task-detail"), "sidebar"), "overview-summary": (("home",), "main"), "task-progress-grid": (("home",), "main"), "task-header": (("task-detail",), "main"), "today-record": (("task-detail",), "main"), "metric-progress": (("task-detail",), "main"), "activity-heatmap": (("task-detail",), "main"), "task-settings": (("task-detail",), "main"), } FALLBACKS = { "home": { "schemaVersion": 1, "page": "home", "regions": { "sidebar": [{"component": "task-list"}], "main": [{"component": "overview-summary"}, {"component": "task-progress-grid"}], }, }, "task-detail": { "schemaVersion": 1, "page": "task-detail", "regions": { "sidebar": [{"component": "task-list"}], "main": [ {"component": "task-header"}, {"component": "today-record"}, {"component": "metric-progress"}, {"component": "activity-heatmap"}, {"component": "task-settings"}, ], }, }, } def validate_layout(layout: Any, expected_page: str) -> bool: if not isinstance(layout, dict): return False if layout.get("schemaVersion") != 1 or layout.get("page") != expected_page: return False regions = layout.get("regions") if not isinstance(regions, dict) or set(regions) - {"sidebar", "main"}: return False for region, items in regions.items(): if not isinstance(items, list): return False seen: set[str] = set() for item in items: if not isinstance(item, dict) or set(item) - {"component", "options"}: return False component = item.get("component") if component not in ALLOWED_COMPONENTS: return False if "options" in item and not isinstance(item["options"], dict): return False pages, expected_region = COMPONENT_SCOPES[component] if expected_page not in pages or region != expected_region: return False if component in seen: return False seen.add(component) return True def load_layout(layout_dir: Path, page: str) -> dict[str, Any]: if page not in FALLBACKS: raise KeyError(page) try: with (layout_dir / f"{page}.json").open("r", encoding="utf-8") as handle: layout = json.load(handle) if validate_layout(layout, page): return layout except (OSError, json.JSONDecodeError): pass return deepcopy(FALLBACKS[page])