Keep the theme on the account, not in the browser
- users.theme and users.theme_mode, added by migrate(); GET /api/me returns them and PATCH /api/me saves them, refusing anything but a plain name and light/dark/auto, since index() writes them into the page's <html> tag. - The page arrives with data-theme and data-choice already on <html> (and data-mode unless Auto), so it is drawn in the account's theme from the start. - A theme a browser kept in localStorage goes up to the account once, the first time an account with none loads the page. - Saves go one at a time, each with the choice as it stands: sent all at once, a quick run through the list could land out of order and keep a theme passed on the way. The browser test caught it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
/* ---------------- theme ---------------- */
|
||||
// A theme, and for those that come in both, light, dark or Auto, chosen in Settings and kept in
|
||||
// ipx.theme and ipx.mode. The page gets data-theme and 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.
|
||||
// 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 <html> 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<string, {name: string, modes: boolean}> = {
|
||||
modern: {name: 'Modern', modes: true},
|
||||
classic: {name: 'Classic, the 2004 Mac app', modes: false},
|
||||
@@ -13,12 +15,13 @@ const THEMES: Record<string, {name: string, modes: boolean}> = {
|
||||
nordic: {name: 'Nordic', modes: true},
|
||||
};
|
||||
const MODES: Record<string, string> = {auto: 'Auto (matches your system)', light: 'Light', dark: 'Dark'};
|
||||
// Before themes came in light and dark, ipx.theme held one of these.
|
||||
// Before themes came in light and dark, ipx.theme in localStorage held one of these.
|
||||
const OLD_THEMES: Record<string, [string, string]> = {dark: ['modern', 'dark'], light: ['modern', 'light'], auto: ['modern', 'auto']};
|
||||
const systemDark = window.matchMedia?.('(prefers-color-scheme: dark)');
|
||||
const theme = {name: 'modern', mode: 'dark'};
|
||||
|
||||
function setTheme(name = theme.name, mode = theme.mode){
|
||||
/// `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;
|
||||
@@ -30,11 +33,26 @@ function setTheme(name = theme.name, mode = 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;
|
||||
try{ localStorage.setItem('ipx.theme', theme.name); localStorage.setItem('ipx.mode', theme.mode); }catch{}
|
||||
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(); });
|
||||
try{
|
||||
let name = localStorage.getItem('ipx.theme'), mode = localStorage.getItem('ipx.mode');
|
||||
(() => {
|
||||
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);
|
||||
}catch{ setTheme(); }
|
||||
setTheme(name ?? undefined, mode ?? undefined, !!name);
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user