Mochi0622's picture
Update app.py
8defc51 verified
Raw
History Blame Contribute Delete
4.16 kB
"""
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)