File size: 5,069 Bytes
8fd7a1d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
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
};