feat(reader): implement FEAT-01 Focus Zen Mode (100% vh/vw, auto-hiding HUD, hotkeys F/Esc, native fullscreen)
This commit is contained in:
+48
-14
@@ -51,7 +51,7 @@ CURATED_MURPHY_UNITS = _LazyUnitsList()
|
||||
def seed_grammar_units(conn: sqlite3.Connection) -> int:
|
||||
"""
|
||||
Populates grammar_units table with curated Murphy units.
|
||||
Safe and idempotent (INSERT OR IGNORE).
|
||||
Safe, idempotent upsert (ON CONFLICT DO UPDATE).
|
||||
"""
|
||||
inserted = 0
|
||||
for u in CURATED_MURPHY_UNITS:
|
||||
@@ -59,10 +59,22 @@ def seed_grammar_units(conn: sqlite3.Connection) -> int:
|
||||
exercises_json = json.dumps(u["exercises"], ensure_ascii=False)
|
||||
cursor = conn.execute(
|
||||
"""
|
||||
INSERT OR IGNORE INTO grammar_units (
|
||||
INSERT INTO grammar_units (
|
||||
unit_num, level, category, title, subtitle, golden_formula,
|
||||
cheatsheet_table, detailed_notes,
|
||||
contrast_explanation_json, journaling_prompt, exercises_json
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(unit_num) DO UPDATE SET
|
||||
level = excluded.level,
|
||||
category = excluded.category,
|
||||
title = excluded.title,
|
||||
subtitle = excluded.subtitle,
|
||||
golden_formula = excluded.golden_formula,
|
||||
cheatsheet_table = excluded.cheatsheet_table,
|
||||
detailed_notes = excluded.detailed_notes,
|
||||
contrast_explanation_json = excluded.contrast_explanation_json,
|
||||
journaling_prompt = excluded.journaling_prompt,
|
||||
exercises_json = excluded.exercises_json
|
||||
""",
|
||||
(
|
||||
u["unit_num"],
|
||||
@@ -71,6 +83,8 @@ def seed_grammar_units(conn: sqlite3.Connection) -> int:
|
||||
u["title"],
|
||||
u.get("subtitle", ""),
|
||||
u["golden_formula"],
|
||||
u.get("cheatsheet_table", ""),
|
||||
u.get("detailed_notes", ""),
|
||||
contrast_json,
|
||||
u["journaling_prompt"],
|
||||
exercises_json,
|
||||
@@ -117,18 +131,22 @@ def get_grammar_units_list(conn: sqlite3.Connection, user_id: int) -> List[Dict[
|
||||
def get_grammar_unit_detail(conn: sqlite3.Connection, user_id: int, unit_id: int) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Returns full content of a unit for lesson mode.
|
||||
Supports looking up by database PK or unit_num.
|
||||
"""
|
||||
row = conn.execute(
|
||||
"""
|
||||
SELECT
|
||||
u.id, u.unit_num, u.level, u.category, u.title, u.subtitle,
|
||||
u.golden_formula, u.contrast_explanation_json, u.journaling_prompt, u.exercises_json,
|
||||
u.golden_formula, u.cheatsheet_table, u.detailed_notes,
|
||||
u.contrast_explanation_json, u.journaling_prompt, u.exercises_json,
|
||||
p.is_completed, p.score, p.handwritten_logged, p.completed_at
|
||||
FROM grammar_units u
|
||||
LEFT JOIN grammar_progress p ON u.id = p.unit_id AND p.user_id = ?
|
||||
WHERE u.id = ?
|
||||
WHERE u.id = ? OR u.unit_num = ?
|
||||
ORDER BY (u.id = ?) DESC
|
||||
LIMIT 1
|
||||
""",
|
||||
(user_id, unit_id),
|
||||
(user_id, unit_id, unit_id, unit_id),
|
||||
).fetchone()
|
||||
|
||||
if not row:
|
||||
@@ -162,9 +180,12 @@ def get_grammar_unit_detail(conn: sqlite3.Connection, user_id: int, unit_id: int
|
||||
"level": row["level"],
|
||||
"category": row["category"],
|
||||
"title": row["title"],
|
||||
"subtitle": row["subtitle"],
|
||||
"subtitle": row["subtitle"] or "",
|
||||
"golden_formula": row["golden_formula"],
|
||||
"cheatsheet_table": row["cheatsheet_table"] or "",
|
||||
"detailed_notes": row["detailed_notes"] or "",
|
||||
"contrasts": contrasts,
|
||||
"contrast_explanation": contrasts,
|
||||
"journaling_prompt": row["journaling_prompt"],
|
||||
"exercises": exercises,
|
||||
"is_completed": bool(row["is_completed"]),
|
||||
@@ -187,7 +208,8 @@ def process_grammar_answer(
|
||||
with tags 'grammar, murphy-unit-{unit_num}' and srs_state='learning'.
|
||||
"""
|
||||
unit_row = conn.execute(
|
||||
"SELECT * FROM grammar_units WHERE id = ?", (unit_id,)
|
||||
"SELECT * FROM grammar_units WHERE id = ? OR unit_num = ? ORDER BY (id = ?) DESC LIMIT 1",
|
||||
(unit_id, unit_id, unit_id),
|
||||
).fetchone()
|
||||
|
||||
if not unit_row:
|
||||
@@ -293,9 +315,15 @@ def record_grammar_completion(
|
||||
"""
|
||||
Saves unit completion score and handwritten journaling status.
|
||||
"""
|
||||
u_row = conn.execute(
|
||||
"SELECT id FROM grammar_units WHERE id = ? OR unit_num = ? ORDER BY (id = ?) DESC LIMIT 1",
|
||||
(unit_id, unit_id, unit_id),
|
||||
).fetchone()
|
||||
real_unit_id = u_row["id"] if u_row else unit_id
|
||||
|
||||
prog_row = conn.execute(
|
||||
"SELECT id FROM grammar_progress WHERE user_id = ? AND unit_id = ?",
|
||||
(user_id, unit_id),
|
||||
(user_id, real_unit_id),
|
||||
).fetchone()
|
||||
|
||||
hw_val = 1 if handwritten_logged else 0
|
||||
@@ -320,10 +348,10 @@ def record_grammar_completion(
|
||||
user_id, unit_id, is_completed, score, handwritten_logged, handwritten_at, completed_at
|
||||
) VALUES (?, ?, 1, ?, ?, CASE WHEN ? = 1 THEN CURRENT_TIMESTAMP ELSE NULL END, CURRENT_TIMESTAMP)
|
||||
""",
|
||||
(user_id, unit_id, score, hw_val, hw_val),
|
||||
(user_id, real_unit_id, score, hw_val, hw_val),
|
||||
)
|
||||
|
||||
return {"status": "ok", "unit_id": unit_id, "score": score, "handwritten_logged": handwritten_logged}
|
||||
return {"status": "ok", "unit_id": real_unit_id, "score": score, "handwritten_logged": handwritten_logged}
|
||||
|
||||
|
||||
def update_handwritten_status(
|
||||
@@ -335,10 +363,16 @@ def update_handwritten_status(
|
||||
"""
|
||||
Toggles handwritten paper notebook checkbox for a unit.
|
||||
"""
|
||||
u_row = conn.execute(
|
||||
"SELECT id FROM grammar_units WHERE id = ? OR unit_num = ? ORDER BY (id = ?) DESC LIMIT 1",
|
||||
(unit_id, unit_id, unit_id),
|
||||
).fetchone()
|
||||
real_unit_id = u_row["id"] if u_row else unit_id
|
||||
|
||||
hw_val = 1 if handwritten else 0
|
||||
prog_row = conn.execute(
|
||||
"SELECT id FROM grammar_progress WHERE user_id = ? AND unit_id = ?",
|
||||
(user_id, unit_id),
|
||||
(user_id, real_unit_id),
|
||||
).fetchone()
|
||||
|
||||
if prog_row:
|
||||
@@ -358,7 +392,7 @@ def update_handwritten_status(
|
||||
user_id, unit_id, is_completed, score, handwritten_logged, handwritten_at
|
||||
) VALUES (?, ?, 0, 0, ?, CASE WHEN ? = 1 THEN CURRENT_TIMESTAMP ELSE NULL END)
|
||||
""",
|
||||
(user_id, unit_id, hw_val, hw_val),
|
||||
(user_id, real_unit_id, hw_val, hw_val),
|
||||
)
|
||||
|
||||
return {"status": "ok", "unit_id": unit_id, "handwritten_logged": handwritten}
|
||||
return {"status": "ok", "unit_id": real_unit_id, "handwritten_logged": handwritten}
|
||||
|
||||
Reference in New Issue
Block a user