|
/**
|
|
* @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 };
|