Dash-math_Database / layout /shell /callbacks.py
Mochi0622's picture
update files
d5d9742
Raw
History Blame Contribute Delete
6.76 kB
import dash
from dash import Dash, html, dcc
from dash import Input, Output, State, callback, clientside_callback
import dash_mantine_components as dmc
from dash_iconify import DashIconify
def generate_toggle_navbar():
@callback(
Output("appshell", "navbar"),
Input("mobile-burger", "opened"),
Input("desktop-burger", "opened"),
State("appshell", "navbar"),
)
def toggle_navbar(mobile_opened, desktop_opened, navbar):
navbar["collapsed"] = {
"mobile": not mobile_opened,
"desktop": not desktop_opened,
}
return navbar
return toggle_navbar
def switch_light_dark():
"""
前端切換 Mantine 亮/暗主題 + 修正文字/背景的自訂 inline 色彩
依據邏輯:
1) 先決定 desired('light' 或 'dark'),優先用 Switch,其次讀 localStorage
2) 套用到 <html>、<body>,並同步 localStorage
3) ★ 套用到根部的 MantineProvider(需要在 app.py: MantineProvider 加 id="mantine-provider")
4) 同步 Switch 外觀,避免與實際狀態不同步
5) 僅對「原本就有 inline color/background」或帶 data-theme-aware 的元素做對應轉色
"""
return clientside_callback(
"""
function(switchOn, pathname) {
// 0) 決定這次要套用的主題
let desired = null;
if (typeof switchOn === 'boolean') {
desired = switchOn ? 'dark' : 'light';
} else {
try { desired = localStorage.getItem('mantine-color-scheme') || 'dark'; }
catch(e) { desired = 'dark'; }
}
const isDark = (desired === 'dark');
// 1) 設定 <html>/<body> 屬性 + localStorage(Mantine 會吃到)
document.documentElement.setAttribute('data-mantine-color-scheme', desired);
if (document.body) document.body.setAttribute('data-mantine-color-scheme', desired);
try {
document.documentElement.dataset.mantineColorScheme = desired;
if (document.body) document.body.dataset.mantineColorScheme = desired;
localStorage.setItem('mantine-color-scheme', desired);
} catch (e) {}
// 2) ★ 套用到 MantineProvider 的根節點(請在 app.py 的 MantineProvider 加 id="mantine-provider")
// 這能讓 Mantine/DMC 的元件樣式(文字、背景、Border)整體跟著切換
const root = document.querySelector('#mantine-provider');
if (root) {
root.setAttribute('data-mantine-color-scheme', desired);
try { root.dataset.mantineColorScheme = desired; } catch (e) {}
}
// 3) 同步 Switch 外觀(避免 UI 與實際主題不同步)
const sw = document.getElementById('color-scheme-switch');
if (sw && typeof switchOn !== 'boolean') {
try { sw.checked = isDark; } catch (e) {}
}
// 4) 文字色的亮<->暗對照(只轉「原本就有 inline color」的元素)
const textLightToDark = {
'black': '#f2f2f2', '#000000': '#f2f2f2',
'gray': '#cccccc', '#808080': '#cccccc',
'blue': '#90caf9', 'darkblue': '#64b5f6',
'teal': '#80cbc4',
'red': '#ef9a9a',
'orange': '#ffcc80',
'#ffa6ff': '#cc66cc'
};
const textDarkToLight = {};
Object.keys(textLightToDark).forEach(k => { textDarkToLight[textLightToDark[k]] = k; });
// 5) 背景暗色 fallback(僅對「原本就有 inline 背景」的元素覆蓋)
const DARK_BG_FALLBACK = 'var(--mantine-color-body)';
const norm = (s) => (s || '').trim().toLowerCase();
// 6) 只處理三種元素:
// - 有 inline color
// - 有 inline background/background-color
// - 或者主動標記 data-theme-aware 的元素(即使沒有 inline style 也會處理)
const candidates = document.querySelectorAll(
'[style*="color:"], [style*="background:"], [style*="background-color:"], [data-theme-aware]'
);
candidates.forEach(el => {
const cs = getComputedStyle(el);
const styleAttr = el.getAttribute('style') || '';
// A) 記錄原始文字色(第一次遇到時才記)
const hadInlineColor = /(^|;)\\s*color\\s*:/.test(styleAttr) || el.hasAttribute('data-theme-aware');
if (hadInlineColor && !el.dataset.originalColor) {
// 優先記 inline,其次 computed
el.dataset.originalColor = norm(el.style.color || cs.color || '');
}
// B) 記錄原始背景(第一次遇到時才記)
const hadInlineBg = /(^|;)\\s*background(-color)?\\s*:/.test(styleAttr) || el.hasAttribute('data-theme-aware');
if (hadInlineBg && !el.dataset.hadInlineBg) {
el.dataset.hadInlineBg = '1';
el.dataset.originalInlineBg = norm(el.style.backgroundColor || cs.backgroundColor || '');
} else if (!el.dataset.hadInlineBg) {
el.dataset.hadInlineBg = '0';
}
// === 文字色反轉:僅動「原來就有 inline color」或 data-theme-aware 的元素 ===
if (el.dataset.originalColor) {
const cur = norm(el.style.color || cs.color || '');
let nextColor;
if (isDark) {
nextColor = textLightToDark[cur] || textLightToDark[el.dataset.originalColor] || cur;
} else {
nextColor = textDarkToLight[cur] || el.dataset.originalColor || cur;
}
el.style.setProperty('color', nextColor, 'important');
}
// === 背景色反轉:僅動「原來就有 inline 背景」或 data-theme-aware ===
if (el.dataset.hadInlineBg === '1') {
if (isDark) {
el.style.setProperty('background-color', DARK_BG_FALLBACK, 'important');
} else {
const restore = el.dataset.originalInlineBg || '';
if (restore) {
el.style.setProperty('background-color', restore, 'important');
} else {
el.style.removeProperty('background-color');
}
}
}
});
// 回傳字串避免 undefined(觸發 dash 更新)
return desired + ':' + Date.now();
}
""",
Output("theme-dummy", "children"),
# 兩個輸入:切換開關 + 換頁/首次載入
[Input("color-scheme-switch", "checked"),
Input("_pages_location", "pathname")]
)