feat: scaffold personal progress monitor MVP
This commit is contained in:
78
app/layouts.py
Normal file
78
app/layouts.py
Normal file
@@ -0,0 +1,78 @@
|
||||
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",
|
||||
}
|
||||
|
||||
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 items in regions.values():
|
||||
if not isinstance(items, list):
|
||||
return False
|
||||
for item in items:
|
||||
if not isinstance(item, dict) or set(item) - {"component", "options"}:
|
||||
return False
|
||||
if item.get("component") not in ALLOWED_COMPONENTS:
|
||||
return False
|
||||
if "options" in item and not isinstance(item["options"], dict):
|
||||
return False
|
||||
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])
|
||||
|
||||
Reference in New Issue
Block a user