157 lines
4.0 KiB
JavaScript
Raw Normal View History

2025-12-04 20:29:35 +01:00
/**
* @fileoverview Theme Service for Rantii
* @author retoor <retoor@molodetz.nl>
* @description Manages application themes and visual appearance
* @keywords theme, dark mode, light mode, appearance, styling
*/
const THEMES = {
dark: {
name: 'Dark',
class: 'theme-dark'
},
light: {
name: 'Light',
class: 'theme-light'
},
black: {
name: 'Black',
class: 'theme-black'
},
white: {
name: 'White',
class: 'theme-white'
},
ocean: {
name: 'Ocean',
class: 'theme-ocean'
},
forest: {
name: 'Forest',
class: 'theme-forest'
},
sunset: {
name: 'Sunset',
class: 'theme-sunset'
}
};
class ThemeService {
constructor(storageService) {
this.storage = storageService;
this.currentTheme = 'dark';
this.onThemeChange = null;
}
init() {
const savedTheme = this.storage.getTheme();
if (savedTheme && THEMES[savedTheme]) {
this.applyTheme(savedTheme);
} else {
this.applyTheme(this.detectPreferredTheme());
}
this.listenToSystemPreference();
}
detectPreferredTheme() {
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark';
}
return 'light';
}
listenToSystemPreference() {
if (window.matchMedia) {
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
if (!this.storage.has('theme')) {
this.applyTheme(e.matches ? 'dark' : 'light');
}
});
}
}
applyTheme(themeName) {
const theme = THEMES[themeName];
if (!theme) {
return false;
}
Object.values(THEMES).forEach(t => {
document.documentElement.classList.remove(t.class);
document.body.classList.remove(t.class);
});
document.documentElement.classList.add(theme.class);
document.body.classList.add(theme.class);
this.currentTheme = themeName;
this.storage.setTheme(themeName);
const metaThemeColor = document.querySelector('meta[name="theme-color"]');
if (metaThemeColor) {
const colors = {
dark: '#1a1a2e',
light: '#f5f5f7',
black: '#000000',
white: '#ffffff',
ocean: '#0a1929',
forest: '#0d1f0d',
sunset: '#1f1410'
};
metaThemeColor.setAttribute('content', colors[themeName] || '#1a1a2e');
}
if (this.onThemeChange) {
this.onThemeChange(themeName, theme);
}
window.dispatchEvent(new CustomEvent('rantii:theme-change', {
detail: { theme: themeName, themeData: theme }
}));
return true;
}
setTheme(themeName) {
return this.applyTheme(themeName);
}
getTheme() {
return this.currentTheme;
}
getThemeData() {
return THEMES[this.currentTheme];
}
getAvailableThemes() {
return Object.entries(THEMES).map(([key, value]) => ({
id: key,
name: value.name
}));
}
toggle() {
const currentIndex = Object.keys(THEMES).indexOf(this.currentTheme);
const nextIndex = (currentIndex + 1) % Object.keys(THEMES).length;
const nextTheme = Object.keys(THEMES)[nextIndex];
return this.applyTheme(nextTheme);
}
toggleDarkLight() {
if (this.currentTheme === 'dark' || this.currentTheme === 'black') {
return this.applyTheme('light');
}
return this.applyTheme('dark');
}
setThemeChangeCallback(callback) {
this.onThemeChange = callback;
}
isDark() {
return ['dark', 'black', 'ocean', 'forest', 'sunset'].includes(this.currentTheme);
}
}
export { ThemeService, THEMES };