refactor: establish standalone application boundary
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
const THEME_STORAGE_KEY = "xiaobaiTheme";
|
||||
let activeThemeTransition = null;
|
||||
let themeSwitchSequence = 0;
|
||||
|
||||
|
||||
function syncThemeControl() {
|
||||
const theme = document.documentElement.dataset.theme === "dark" ? "dark" : "light";
|
||||
const button = document.querySelector("#themeToggle");
|
||||
if (!button) return;
|
||||
const dark = theme === "dark";
|
||||
const label = dark ? "切换到日间模式" : "切换到夜间模式";
|
||||
button.title = label;
|
||||
button.setAttribute("aria-label", label);
|
||||
button.setAttribute("aria-pressed", String(dark));
|
||||
button.querySelector("i")?.setAttribute("data-lucide", dark ? "sun" : "moon");
|
||||
}
|
||||
|
||||
function clearThemeTransitionEffects() {
|
||||
document.querySelectorAll(".row-enter, .row-pending, .view-entering").forEach((element) => {
|
||||
element.classList.remove("row-enter", "row-pending", "view-entering");
|
||||
element.style.removeProperty("--row-delay");
|
||||
});
|
||||
}
|
||||
|
||||
function redrawThemeSensitiveVisuals() {
|
||||
if (!elements.stockPreview.hidden && state.stockPreviewPayload) {
|
||||
selectStockPreviewChart(state.stockPreviewChart);
|
||||
}
|
||||
if (elements.stockDialog.open) {
|
||||
if (state.stockDetailChartMode === "intraday" && state.stockDetailIntraday?.points?.length) {
|
||||
drawIntradayCanvas(
|
||||
elements.priceChart,
|
||||
state.stockDetailIntraday.points,
|
||||
[],
|
||||
state.stockDetailIntraday.meta?.previous_close,
|
||||
);
|
||||
} else if (state.stockDetail?.prices) drawPriceChart(state.stockDetail.prices);
|
||||
}
|
||||
if (elements.entityDetailDialog.open) {
|
||||
if (state.entityDetailChartMode === "intraday" && state.entityDetailIntraday?.points?.length) {
|
||||
drawIntradayCanvas(
|
||||
elements.entityDetailChart,
|
||||
state.entityDetailIntraday.points,
|
||||
[],
|
||||
state.entityDetailIntraday.meta?.previous_close,
|
||||
);
|
||||
} else if (state.entityDetailPayload?.series) {
|
||||
drawEntityDetailChart(state.entityDetailPayload.series);
|
||||
}
|
||||
}
|
||||
if (state.activeView === "sentimentCycleView" && state.sentimentHistory) {
|
||||
drawSentimentTrendChart(state.sentimentHistory.rows || []);
|
||||
}
|
||||
if (state.activeView === "heavenView") {
|
||||
if (state.heavenPanel === "fortune" && state.heavenSetup?.field) {
|
||||
renderQiFieldCanvas(state.heavenSetup.field.balance || [], { intro: false });
|
||||
drawQiUseConnections(false);
|
||||
}
|
||||
if (state.heavenPanel === "heart") startHeartDust();
|
||||
}
|
||||
}
|
||||
|
||||
function commitTheme(normalized, persist) {
|
||||
document.documentElement.dataset.theme = normalized;
|
||||
document.documentElement.style.colorScheme = normalized;
|
||||
if (persist) {
|
||||
try {
|
||||
localStorage.setItem(THEME_STORAGE_KEY, normalized);
|
||||
} catch (_error) {
|
||||
// The selected theme still applies for the current page when storage is unavailable.
|
||||
}
|
||||
}
|
||||
syncThemeControl();
|
||||
refreshIcons();
|
||||
redrawThemeSensitiveVisuals();
|
||||
}
|
||||
|
||||
function applyTheme(theme, persist = true) {
|
||||
const normalized = theme === "dark" ? "dark" : "light";
|
||||
const root = document.documentElement;
|
||||
if (root.dataset.theme === normalized) {
|
||||
commitTheme(normalized, persist);
|
||||
return;
|
||||
}
|
||||
const sequence = ++themeSwitchSequence;
|
||||
activeThemeTransition?.skipTransition?.();
|
||||
clearThemeTransitionEffects();
|
||||
root.classList.add("theme-switching");
|
||||
|
||||
const update = () => commitTheme(normalized, persist);
|
||||
const finish = () => {
|
||||
if (sequence !== themeSwitchSequence) return;
|
||||
clearThemeTransitionEffects();
|
||||
root.classList.remove("theme-switching");
|
||||
activeThemeTransition = null;
|
||||
};
|
||||
const reducedMotion = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
||||
if (!reducedMotion && typeof document.startViewTransition === "function") {
|
||||
activeThemeTransition = document.startViewTransition(update);
|
||||
activeThemeTransition.finished.then(finish, finish);
|
||||
return;
|
||||
}
|
||||
update();
|
||||
requestAnimationFrame(() => requestAnimationFrame(finish));
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
applyTheme(document.documentElement.dataset.theme === "dark" ? "light" : "dark");
|
||||
}
|
||||
|
||||
|
||||
function bindThemeEvents() {
|
||||
document.querySelector("#themeToggle").addEventListener("click", toggleTheme);
|
||||
window.addEventListener("resize", redrawThemeSensitiveVisuals);
|
||||
}
|
||||
Reference in New Issue
Block a user