Dash-math_Database / layout /page /page_layout.py
Mochi0622's picture
initial deploy
e66cfb4
Raw
History Blame Contribute Delete
5.95 kB
"""
layout/page/page_layout.py
---------------------------
頁面主體 layout 組裝。
只負責把各子模組的產出組合成最終的 dmc.Container,
不含資料邏輯,也不直接定義 callback。
公開 API:
make_test_page(json_source, *, top_offset, show_src) → dmc.Container
"""
from __future__ import annotations
from typing import Any
import traceback
from dash import html, dcc
import dash_mantine_components as dmc
from ..renderer.renderer import render_doc
try:
from ..renderer.renderer import install_callbacks_once
except Exception:
install_callbacks_once = None
from ..toc import make_toc
from .loader import load_json, build_toc_titles
from .sections import (
split_blocks_into_sections,
get_section_blocks,
make_section_control,
)
from .page_callbacks import register_page_callbacks
# ---------------------------------------------------------------------------
# 回到頂端按鈕樣式(獨立常數,方便日後調整)
# ---------------------------------------------------------------------------
_BACK_TO_TOP_STYLE: dict[str, Any] = {
"position": "fixed",
"right": "24px",
"bottom": "24px",
"width": "46px",
"height": "46px",
"borderRadius": "999px",
"border": "none",
"textDecoration": "none",
"display": "flex",
"alignItems": "center",
"justifyContent": "center",
"background": "rgba(0, 0, 0, 0.35)",
"color": "white",
"fontSize": "22px",
"cursor": "pointer",
"zIndex": 999,
"backdropFilter": "blur(4px)",
"boxShadow": "0 4px 12px rgba(0,0,0,0.18)",
}
# TOC 欄固定定位樣式
def _toc_fixed_style(top_offset: int) -> dict[str, Any]:
return {
"position": "sticky",
"top": f"{top_offset + 12}px",
"maxHeight": f"calc(100vh - {top_offset + 32}px)",
"overflowY": "auto",
"overflowX": "hidden",
"paddingRight": "4px",
"alignSelf": "flex-start",
}
# ---------------------------------------------------------------------------
# 主要組裝函式
# ---------------------------------------------------------------------------
def make_test_page(
json_source: Any,
*,
top_offset: int = 90,
show_src: bool = True,
) -> dmc.Container:
"""
組裝完整的頁面 Container。
json_source:可傳入檔案路徑(str/Path)、已解析的 dict 或 list。
top_offset: 頁首高度(px),用於 TOC 固定定位與 scroll offset。
show_src: 保留參數(尚未使用),預留供未來顯示原始 JSON 用。
"""
# 1. 確保 dashTest callbacks 已掛載
if callable(install_callbacks_once):
try:
install_callbacks_once()
except Exception:
pass
# 2. 確保頁面 callbacks 已掛載
register_page_callbacks()
# 3. 載入資料
data = load_json(json_source)
blocks = data.get("blocks", [])
sections = data.get("sections", []) or []
raw_toc = data.get("toc", []) or []
# 4. 若 JSON 未提供 sections,自動按 H2 切割
if not sections:
sections = split_blocks_into_sections(blocks)
# 5. 決定初始 section
default_section_id = sections[0].get("id") if sections else None
current_blocks = get_section_blocks(sections, default_section_id) if sections else blocks
# 6. 渲染初始內容
try:
content = render_doc(current_blocks)
except Exception as e:
content = dmc.Alert(
f"render_doc 發生錯誤:{e}\n\n{traceback.format_exc(limit=2)}",
title="渲染失敗",
color="red",
variant="filled",
)
# 7. Section 控制列
section_control = make_section_control(sections, default_section_id)
# 8. 左欄:Store + 控制列 + 內容
left_children: list[Any] = [
dcc.Store(id="page-sections-store", data=sections),
dcc.Store(id="page-raw-toc-store", data=raw_toc),
dcc.Store(id="page-active-section-store", data=default_section_id),
]
if section_control is not None:
left_children.append(section_control)
left_children.append(html.Div(content, id="section-content-wrapper"))
left_area = dmc.Stack(left_children, gap="md", style={"minWidth": 0})
# 9. TOC(僅當有標題時才產生)
toc_titles = build_toc_titles(current_blocks, raw_toc)
has_toc = bool(toc_titles)
toc_component = (
make_toc(
titles=toc_titles,
top_offset=top_offset,
col_span={"xs": 0, "sm": 0, "md": 3, "lg": 3, "xl": 3},
visible_from="md",
)
if has_toc
else None
)
# 10. Grid 欄位配置
main_span = {"base": 12, "md": 9 if has_toc else 12}
toc_span = {"base": 12, "md": 3} if has_toc else {"base": 12, "md": 0}
toc_style = _toc_fixed_style(top_offset) if has_toc else {}
cols = [
dmc.GridCol(left_area, id="main-content-col", span=main_span),
dmc.GridCol(
html.Div(
id="section-toc-wrapper",
children=toc_component,
style=toc_style,
),
id="toc-col",
span=toc_span,
className="toc-col-wrapper",
style={"display": "block" if has_toc else "none"},
),
]
# compat:空 Grid 讓 Dash 的 pattern-matching callback 不出錯
grid = dmc.Grid(cols, gutter="xl")
compat = dmc.Grid(id="Grid", style={"display": "none"})
return dmc.Container(
[
html.Div(id="page-top-anchor"),
dmc.Stack([grid, compat], gap="md"),
html.A(
"↑",
href="#page-top-anchor",
title="回到最上方",
id="back-to-top-btn",
style=_BACK_TO_TOP_STYLE,
),
],
fluid=True,
style={"overflow": "visible"},
)