File size: 4,155 Bytes
e66cfb4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2e32dbb
 
 
e66cfb4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3160eea
e66cfb4
8defc51
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
"""
app.py
------
Dash app 進入點。

職責(僅此而已):
    1. 建立 Dash app 實例
    2. 組裝 AppShell layout
    3. 掛載全域 callback(navbar、主題、MathJax)
    4. 啟動 dev server

Build pipeline(MD → JSON)已移至 build.py。
  - 若希望每次啟動都重建:取消下方 build_if_needed 的 force=True 呼叫
  - 若只想手動重建:直接執行 python build.py --force
"""

import dash
from dash import Dash, html, dcc
from dash import Input, Output, clientside_callback
import dash_mantine_components as dmc
from pathlib import Path

from layout.shell.navbar    import generate_navbar
from layout.shell.header    import generate_header
from layout.shell.main      import generate_main
from layout.shell.footer    import generate_footer
from layout.shell.callbacks import generate_toggle_navbar, switch_light_dark

# 確保頁面 section 切換 callback 在啟動時就被註冊
import layout.page.page_callbacks  # noqa: F401

# 學生登入元件
from student_login import create_student_store, create_login_modal, create_student_badge

# Firebase quiz hooks(答題記錄)
from firebase_quiz_hooks import create_dummy_stores
import firebase_quiz_hooks  # noqa: F401 ← 掛載 callback

# Session 狀態保存(重整後恢復 section、答題、解法)由 assets/session_state.js 處理
from session_state import create_session_stores

# Build pipeline:只在啟動時呼叫,不影響 app 本體邏輯
from build import build_if_needed

BASE_DIR    = Path(__file__).resolve().parent
CONFIG_PATH = BASE_DIR / "paths.json"

import os
if not os.getenv("SPACE_ID"):  # 不在 HF 上才 build
    build_if_needed(CONFIG_PATH, force=False)

# ---------------------------------------------------------------------------
# App 實例
# ---------------------------------------------------------------------------
app = Dash(
    __name__,
    use_pages=True,
    external_stylesheets=dmc.styles.ALL,
    suppress_callback_exceptions=True,
)

# ---------------------------------------------------------------------------
# Callbacks
# ---------------------------------------------------------------------------
generate_toggle_navbar()
switch_light_dark()

# ---------------------------------------------------------------------------
# Layout 元件
# ---------------------------------------------------------------------------
header = generate_header()
navbar = generate_navbar()
main   = generate_main()
footer = generate_footer()

layout = dmc.AppShell(
    id="appshell",
    children=[
        dcc.Store(id="noop-store"),
        html.Div(id="theme-dummy", style={"display": "none"}),

        # ── 學生登入相關 ──
        create_student_store(),       # localStorage 學號 Store
        create_login_modal(),         # 學號輸入 Modal
        *create_dummy_stores(),       # Firebase 虛擬 Store
        *create_session_stores(),     # Session 狀態 Store

        header, navbar, main, footer,
    ],
    header={"height": "4rem"},
    navbar={
        "width": {"xs": 200, "sm": 250, "md": 300, "lg": 300},
        "breakpoint": "sm",
        "collapsed": {"mobile": True, "desktop": False},
    },
    footer={"height": "2rem"},
    padding="lg",
)

app.layout = dmc.MantineProvider(
    id="mantine-provider",
    children=layout,
    defaultColorScheme="light",
)

# ---------------------------------------------------------------------------
# MathJax:每次換頁都重新 typeset
# ---------------------------------------------------------------------------
clientside_callback(
    """
    function(pathname) {
        if (window.MathJax) {
            window.MathJax.typeset();
        }
        return "";
    }
    """,
    Output("noop-store", "data"),
    Input("_pages_location", "pathname"),
)

# ---------------------------------------------------------------------------
# 啟動
# ---------------------------------------------------------------------------
server = app.server  # 給 gunicorn 用:gunicorn app:server

if __name__ == "__main__":
    # app.run(debug=True)
    # 部署時改用:
    app.run(host="0.0.0.0", port=7860, debug=False)