- Create web/src/ for TypeScript source files - Create web/static/ for compiled assets and runtime files - Move input.css and style.css to web/static/ - Add toast.ts - Functional toast notification system - Add theme.ts - Functional theme management system - All code uses functional programming (no classes, no OOP) - TypeScript provides full type safety Separates frontend code from backend for better organization.
130 lines
3.6 KiB
TypeScript
130 lines
3.6 KiB
TypeScript
// 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: ThemeType): void => {
|
|
document.body.className = `theme-${theme}`;
|
|
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);
|
|
};
|
|
|
|
// Handle theme change from user selection
|
|
const changeTheme = async (): Promise<void> => {
|
|
const themeSelect = document.getElementById('theme-select') as HTMLSelectElement;
|
|
if (!themeSelect) return;
|
|
|
|
const theme = themeSelect.value as ThemeType;
|
|
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);
|
|
}
|
|
}
|
|
} 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'
|
|
});
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
// Auto-initialize when DOM is ready
|
|
if (typeof document !== 'undefined') {
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', initializeTheme);
|
|
} else {
|
|
initializeTheme();
|
|
}
|
|
}
|
|
|
|
// Make changeTheme available globally for HTML onchange attribute
|
|
(window as any).changeTheme = changeTheme;
|