|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ThemeManager { |
|
|
constructor() { |
|
|
this.storageKey = 'crypto_monitor_theme'; |
|
|
this.currentTheme = 'light'; |
|
|
this.listeners = []; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
init() { |
|
|
|
|
|
this.currentTheme = this.getSavedTheme() || this.getSystemPreference(); |
|
|
|
|
|
|
|
|
this.applyTheme(this.currentTheme, false); |
|
|
|
|
|
|
|
|
this.setupToggleButton(); |
|
|
|
|
|
|
|
|
this.listenToSystemChanges(); |
|
|
|
|
|
console.log(`[ThemeManager] Initialized with theme: ${this.currentTheme}`); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getSavedTheme() { |
|
|
try { |
|
|
return localStorage.getItem(this.storageKey); |
|
|
} catch (error) { |
|
|
console.warn('[ThemeManager] localStorage not available:', error); |
|
|
return null; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
saveTheme(theme) { |
|
|
try { |
|
|
localStorage.setItem(this.storageKey, theme); |
|
|
} catch (error) { |
|
|
console.warn('[ThemeManager] Could not save theme:', error); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getSystemPreference() { |
|
|
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { |
|
|
return 'dark'; |
|
|
} |
|
|
return 'light'; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
applyTheme(theme, save = true) { |
|
|
const body = document.body; |
|
|
|
|
|
|
|
|
body.classList.remove('theme-light', 'theme-dark'); |
|
|
|
|
|
|
|
|
body.classList.add(`theme-${theme}`); |
|
|
|
|
|
|
|
|
this.currentTheme = theme; |
|
|
|
|
|
|
|
|
if (save) { |
|
|
this.saveTheme(theme); |
|
|
} |
|
|
|
|
|
|
|
|
this.updateToggleButton(theme); |
|
|
|
|
|
|
|
|
this.notifyListeners(theme); |
|
|
|
|
|
|
|
|
this.announceThemeChange(theme); |
|
|
|
|
|
console.log(`[ThemeManager] Applied theme: ${theme}`); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
toggleTheme() { |
|
|
const newTheme = this.currentTheme === 'light' ? 'dark' : 'light'; |
|
|
this.applyTheme(newTheme); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setTheme(theme) { |
|
|
if (theme !== 'light' && theme !== 'dark') { |
|
|
console.warn(`[ThemeManager] Invalid theme: ${theme}`); |
|
|
return; |
|
|
} |
|
|
this.applyTheme(theme); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getTheme() { |
|
|
return this.currentTheme; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setupToggleButton() { |
|
|
const toggleBtn = document.getElementById('theme-toggle'); |
|
|
if (toggleBtn) { |
|
|
toggleBtn.addEventListener('click', () => { |
|
|
this.toggleTheme(); |
|
|
}); |
|
|
|
|
|
|
|
|
toggleBtn.addEventListener('keydown', (e) => { |
|
|
if (e.key === 'Enter' || e.key === ' ') { |
|
|
e.preventDefault(); |
|
|
this.toggleTheme(); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
this.updateToggleButton(this.currentTheme); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
updateToggleButton(theme) { |
|
|
const toggleBtn = document.getElementById('theme-toggle'); |
|
|
const toggleIcon = document.getElementById('theme-toggle-icon'); |
|
|
|
|
|
if (toggleBtn && toggleIcon) { |
|
|
if (theme === 'dark') { |
|
|
toggleIcon.textContent = '☀️'; |
|
|
toggleBtn.setAttribute('aria-label', 'Switch to light mode'); |
|
|
toggleBtn.setAttribute('title', 'Light Mode'); |
|
|
} else { |
|
|
toggleIcon.textContent = '🌙'; |
|
|
toggleBtn.setAttribute('aria-label', 'Switch to dark mode'); |
|
|
toggleBtn.setAttribute('title', 'Dark Mode'); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
listenToSystemChanges() { |
|
|
if (window.matchMedia) { |
|
|
const darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)'); |
|
|
|
|
|
|
|
|
if (darkModeQuery.addEventListener) { |
|
|
darkModeQuery.addEventListener('change', (e) => { |
|
|
|
|
|
if (!this.getSavedTheme()) { |
|
|
const newTheme = e.matches ? 'dark' : 'light'; |
|
|
this.applyTheme(newTheme, false); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
else if (darkModeQuery.addListener) { |
|
|
darkModeQuery.addListener((e) => { |
|
|
if (!this.getSavedTheme()) { |
|
|
const newTheme = e.matches ? 'dark' : 'light'; |
|
|
this.applyTheme(newTheme, false); |
|
|
} |
|
|
}); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
onChange(callback) { |
|
|
this.listeners.push(callback); |
|
|
return () => { |
|
|
const index = this.listeners.indexOf(callback); |
|
|
if (index > -1) { |
|
|
this.listeners.splice(index, 1); |
|
|
} |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
notifyListeners(theme) { |
|
|
this.listeners.forEach(callback => { |
|
|
try { |
|
|
callback(theme); |
|
|
} catch (error) { |
|
|
console.error('[ThemeManager] Error in listener:', error); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
announceThemeChange(theme) { |
|
|
const liveRegion = document.getElementById('sr-live-region'); |
|
|
if (liveRegion) { |
|
|
liveRegion.textContent = `Theme changed to ${theme} mode`; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
resetToSystem() { |
|
|
try { |
|
|
localStorage.removeItem(this.storageKey); |
|
|
} catch (error) { |
|
|
console.warn('[ThemeManager] Could not remove saved theme:', error); |
|
|
} |
|
|
|
|
|
const systemTheme = this.getSystemPreference(); |
|
|
this.applyTheme(systemTheme, false); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
window.themeManager = new ThemeManager(); |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
window.themeManager.init(); |
|
|
}); |
|
|
|
|
|
console.log('[ThemeManager] Module loaded'); |
|
|
|