import {BLOCKS_CUSTOM, Theme} from '.'; import {customThemeManager, CustomTheme} from './custom-themes.js'; const matchMedia = query => (window.matchMedia ? window.matchMedia(query) : null); const PREFERS_HIGH_CONTRAST_QUERY = matchMedia('(prefers-contrast: more)'); const PREFERS_DARK_QUERY = matchMedia('(prefers-color-scheme: dark)'); const STORAGE_KEY = 'tw:theme'; /** * @returns {Theme} detected theme */ const systemPreferencesTheme = () => { if (PREFERS_HIGH_CONTRAST_QUERY && PREFERS_HIGH_CONTRAST_QUERY.matches) { return Theme.highContrast; } if (PREFERS_DARK_QUERY && PREFERS_DARK_QUERY.matches) { return Theme.dark; } return Theme.light; }; /** * @param {function} onChange callback; no guarantees about arguments * @returns {function} call to remove event listeners to prevent memory leak */ const onSystemPreferenceChange = onChange => { if ( !PREFERS_HIGH_CONTRAST_QUERY || !PREFERS_DARK_QUERY || // Some old browsers don't support addEventListener on media queries !PREFERS_HIGH_CONTRAST_QUERY.addEventListener || !PREFERS_DARK_QUERY.addEventListener ) { return () => {}; } PREFERS_HIGH_CONTRAST_QUERY.addEventListener('change', onChange); PREFERS_DARK_QUERY.addEventListener('change', onChange); return () => { PREFERS_HIGH_CONTRAST_QUERY.removeEventListener('change', onChange); PREFERS_DARK_QUERY.removeEventListener('change', onChange); }; }; /** * @returns {Theme} the theme */ const detectTheme = () => { const systemPreferences = systemPreferencesTheme(); try { const local = localStorage.getItem(STORAGE_KEY); // Migrate legacy preferences if (local === 'dark') { return Theme.dark; } if (local === 'light') { return Theme.light; } const parsed = JSON.parse(local); // Check if this is a custom theme if (parsed.isCustom && parsed.customThemeUuid) { const customTheme = customThemeManager.getTheme(parsed.customThemeUuid); if (customTheme) { return customTheme; } // Fall back to system preferences if custom theme not found console.warn(`Custom theme ${parsed.customThemeUuid} not found, falling back to system preferences`); } // Any invalid values in storage will be handled by Theme itself const wallpaper = parsed.wallpaper || { url: '', opacity: 0.3, darkness: 0, gridVisible: true, history: [] }; // Add backward compatibility for gridVisible if (typeof wallpaper.gridVisible === 'undefined') { wallpaper.gridVisible = true; } return new Theme( parsed.accent || systemPreferences.accent, parsed.gui || systemPreferences.gui, parsed.blocks || systemPreferences.blocks, parsed.menuBarAlign || systemPreferences.menuBarAlign, wallpaper, parsed.fonts || { system: [], google: [], history: [] } ); } catch (e) { // ignore } return systemPreferences; }; /** * @param {Theme} theme the theme */ const persistTheme = theme => { const systemPreferences = systemPreferencesTheme(); const nonDefaultSettings = {}; // Handle custom themes differently if (theme instanceof CustomTheme) { nonDefaultSettings.customThemeUuid = theme.uuid; nonDefaultSettings.isCustom = true; } else { if (theme.accent !== systemPreferences.accent) { nonDefaultSettings.accent = theme.accent; } if (theme.gui !== systemPreferences.gui) { nonDefaultSettings.gui = theme.gui; } // custom blocks are managed by addon at runtime, don't save here if (theme.blocks !== systemPreferences.blocks && theme.blocks !== BLOCKS_CUSTOM) { nonDefaultSettings.blocks = theme.blocks; } if (theme.menuBarAlign !== systemPreferences.menuBarAlign) { nonDefaultSettings.menuBarAlign = theme.menuBarAlign; } // Always save wallpaper settings if they exist if (theme.wallpaper && (theme.wallpaper.url || theme.wallpaper.history.length > 0)) { nonDefaultSettings.wallpaper = theme.wallpaper; } // Always save fonts settings if they exist if (theme.fonts && (theme.fonts.system.length > 0 || theme.fonts.google.length > 0 || theme.fonts.history.length > 0)) { nonDefaultSettings.fonts = theme.fonts; } } if (Object.keys(nonDefaultSettings).length === 0) { try { localStorage.removeItem(STORAGE_KEY); } catch (e) { // ignore } } else { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(nonDefaultSettings)); } catch (e) { // ignore } } }; export { onSystemPreferenceChange, detectTheme, persistTheme };