Files
bookhoard/web/src/theme.ts
T
john-okeefe 2075077bb7 refactor: remove unnecessary DOMContentLoaded wrappers
Since main.js has 'defer' attribute, the DOM is guaranteed to be
ready when modules execute. These wrappers are unnecessary.

dashboard.ts:
- Removed DOMContentLoaded wrapper, code runs directly
- Event delegation setup runs immediately

custom-section-builder.ts:
- Removed DOMContentLoaded wrapper
- initCustomSectionBuilder() called directly

toast.ts:
- Removed DOMContentLoaded wrapper
- initializeToastSystem() called directly at top level
- Removed dead Alpine.data registration (unused)

search.ts:
- Removed DOMContentLoaded wrapper
- initializeSearch exported for use in header

theme.ts:
- Removed DOMContentLoaded wrapper
- Functions now exported for use in header Alpine component
2026-03-13 12:51:16 -04:00

227 lines
5.7 KiB
TypeScript

import { Alpine } from "./alpine";
// Theme management functionality
type ThemeType =
| "tokyo-night"
| "dracula"
| "nord"
| "solarized-dark"
| "monokai"
| "one-dark-pro"
| "material-dark"
| "catppuccin-mocha"
| "catppuccin-macchiato"
| "catppuccin-frappe"
| "catppuccin-latte";
const DEFAULT_THEME: ThemeType = "tokyo-night";
const THEME_STORAGE_KEY = "theme";
const TOKEN_STORAGE_KEY = "token";
// Apply theme to document body
const applyTheme = (theme: string): void => {
// Apply regular theme only
document.body.className = `theme-${theme}`;
document.body.style.background = "";
document.body.style.backgroundSize = "";
document.body.style.backgroundAttachment = "";
localStorage.setItem(THEME_STORAGE_KEY, theme);
};
// Load theme from localStorage or use default
const loadTheme = (): void => {
const storedTheme = localStorage.getItem(
THEME_STORAGE_KEY,
) as ThemeType | null;
const theme = storedTheme || DEFAULT_THEME;
applyTheme(theme);
};
// Change: Remove DOM element lookup, accept theme as parameter
const changeTheme = async (theme: string): Promise<void> => {
applyTheme(theme);
// Save to server if logged in
const token = localStorage.getItem(TOKEN_STORAGE_KEY);
if (!token) return;
try {
const response = await fetch("/api/auth/theme", {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ theme }),
});
if (!response.ok) {
console.log("Theme save failed");
}
} catch (error) {
console.log("Theme save failed", error);
}
};
// Load user's theme from server if logged in
const loadUserTheme = async (): Promise<void> => {
const token = localStorage.getItem(TOKEN_STORAGE_KEY);
if (!token) return;
try {
const response = await fetch("/api/auth/profile", {
headers: { Authorization: `Bearer ${token}` },
});
if (response.ok) {
const data = await response.json();
if (data.theme) {
applyTheme(data.theme as string);
}
}
} catch {
// Silently fail - user will get default theme
}
};
// Initialize theme system
const initializeTheme = (): void => {
loadTheme();
loadUserTheme();
// Set theme select value to current theme
const themeSelect = document.getElementById(
"theme-select",
) as HTMLSelectElement;
if (themeSelect) {
const currentTheme =
localStorage.getItem(THEME_STORAGE_KEY) || DEFAULT_THEME;
themeSelect.value = currentTheme;
}
// Setup smooth scroll for anchor links
setupSmoothScroll();
};
// Setup smooth scrolling for anchor links
const setupSmoothScroll = (): void => {
document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
anchor.addEventListener("click", (e) => {
e.preventDefault();
const href = anchor.getAttribute("href");
if (!href) return;
const target = document.querySelector(href);
if (target) {
target.scrollIntoView({
behavior: "smooth",
block: "start",
});
}
});
});
};
// Wood paneling management functionality
type WoodPanelingType = "none" | "wood-light" | "wood-dark" | "wood-mahogany";
const WOOD_STORAGE_KEY = "wood-paneling";
// Apply wood paneling to collections container
const applyWoodPaneling = (paneling: WoodPanelingType): void => {
const container = document.getElementById("collections-container");
const body = document.body;
if (!container || !body) return;
// Remove all wood background classes from container
container.classList.remove(
"bg-wood-light",
"bg-wood-dark",
"bg-wood-mahogany",
);
container.removeAttribute("data-wood");
// Remove from body
body.classList.remove("bg-wood-light", "bg-wood-dark", "bg-wood-mahogany");
if (paneling !== "none") {
// Add selected wood background class to both container and body
const woodClass = `bg-${paneling}`;
container.classList.add(woodClass);
container.setAttribute("data-wood", paneling);
// Add to body for overscroll area
body.classList.add(woodClass);
}
// Save to localStorage
localStorage.setItem(WOOD_STORAGE_KEY, paneling);
};
// Load wood paneling from localStorage on page load
const loadWoodPaneling = (): void => {
const stored = localStorage.getItem(
WOOD_STORAGE_KEY,
) as WoodPanelingType | null;
if (stored) {
applyWoodPaneling(stored);
} else {
// Default to none
applyWoodPaneling("none");
}
};
// Change wood paneling (called from theme dropdown)
const changeWoodPaneling = (paneling: WoodPanelingType): void => {
applyWoodPaneling(paneling);
// Update active indicators
updateWoodPanelingIndicators();
// Alpine closes dropdown automatically via template state
};
// Update visual indicators for wood paneling buttons
const updateWoodPanelingIndicators = (): void => {
const currentWood = localStorage.getItem(WOOD_STORAGE_KEY) || "none";
// Update wood paneling buttons
document.querySelectorAll(".wood-paneling-btn").forEach((btn) => {
const wood = btn.getAttribute("data-wood");
if (wood === currentWood) {
// Active state - use CSS class instead of inline style
btn.classList.add("bg-wood-active");
btn.classList.remove("bg-wood-inactive");
} else {
// Inactive state
btn.classList.remove("bg-wood-active");
btn.classList.add("bg-wood-inactive");
}
});
};
Alpine.data("theme", () => ({
applyTheme,
changeTheme,
changeWoodPaneling,
initializeTheme,
loadTheme,
loadUserTheme,
loadWoodPaneling,
updateWoodPanelingIndicators,
}));
export {
applyTheme,
changeTheme,
changeWoodPaneling,
initializeTheme,
loadTheme,
loadUserTheme,
loadWoodPaneling,
updateWoodPanelingIndicators,
};
export type { ThemeType };