/* ---------------- theme ---------------- */ // A theme, and for those that come in both, light, dark or Auto, chosen in Settings and kept on // the account, so it follows you to another browser or computer. The server writes it onto the // page's tag (data-theme, data-choice) so the page is drawn in it from the start. The // page gets data-mode, light or dark, which is all the CSS reads: Auto is worked out here, from // the system, so no palette is written twice. const THEMES: Record = { modern: {name: 'Modern', modes: true}, classic: {name: 'Classic', modes: false}, dracula: {name: 'Dracula', modes: true}, material: {name: 'Material', modes: true}, adwaita: {name: 'Adwaita', modes: true}, flatremix: {name: 'Flat Remix', modes: true}, paper: {name: 'Paper', modes: false}, nordic: {name: 'Nordic', modes: true}, }; const MODES: Record = {auto: 'Auto (matches your system)', light: 'Light', dark: 'Dark'}; // Before themes came in light and dark, ipx.theme in localStorage held one of these. const OLD_THEMES: Record = {dark: ['modern', 'dark'], light: ['modern', 'light'], auto: ['modern', 'auto']}; const systemDark = window.matchMedia?.('(prefers-color-scheme: dark)'); const theme = {name: 'modern', mode: 'dark'}; /// `save` for a choice made in Settings, which goes to the account; not for applying one. function setTheme(name = theme.name, mode = theme.mode, save = false){ theme.name = THEMES[name] ? name : 'modern'; theme.mode = MODES[mode] ? mode : 'dark'; const both = THEMES[theme.name].modes; const root = document.documentElement; root.dataset.theme = theme.name; // A theme with one palette has it whatever the mode; both of those are light. root.dataset.mode = !both ? 'light' : theme.mode === 'auto' ? (systemDark && !systemDark.matches ? 'light' : 'dark') : theme.mode; const sel = $('#stheme'); if(sel) sel.value = theme.name; const ms = $('#smode'); if(ms) ms.value = theme.mode; const mf = $('#smodefield'); if(mf) mf.hidden = !both; if(save) saveTheme(); } /// One save at a time, each sending the choice as it stands when it goes. Sent as they came, /// several at once, a quick run through the list could reach the server out of order and /// leave the account on a theme passed on the way. let themeSaving = Promise.resolve(); function saveTheme(){ themeSaving = themeSaving .then(() => api('/api/me', {method: 'PATCH', body: JSON.stringify({theme: theme.name, mode: theme.mode})})) .catch(e => toast(`Your theme was not saved: ${e.message}`, true)); } systemDark?.addEventListener?.('change', () => { if(theme.mode === 'auto') setTheme(); }); (() => { const root = document.documentElement; if(root.dataset.choice) return setTheme(root.dataset.theme, root.dataset.choice); // Nothing on the account yet. A theme this browser kept, from before themes were kept on the // account, goes up to it once, so nobody has to choose again. let name: string | null = null, mode: string | null = null; try{ name = localStorage.getItem('ipx.theme'); mode = localStorage.getItem('ipx.mode'); }catch{} if(OLD_THEMES[name]) [name, mode] = OLD_THEMES[name]; setTheme(name ?? undefined, mode ?? undefined, !!name); })();