4in1_ws_web/htdocs/js/common/35-theme-switcher.js
2023-12-30 23:29:31 +00:00

214 lines
6.3 KiB
JavaScript

var ThemeSwitcher = (function() {
/**
* @type {string[]}
*/
var modes = ['auto', 'dark', 'light'];
/**
* @type {number}
*/
var currentModeIndex = -1;
/**
* @type {boolean|null}
*/
var systemState = null;
/**
* @type {function[]}
*/
var changeListeners = [];
/**
* @returns {boolean}
*/
function isSystemModeSupported() {
try {
// crashes on:
// Mozilla/5.0 (Windows NT 6.2; ARM; Trident/7.0; Touch; rv:11.0; WPDesktop; Lumia 630 Dual SIM) like Gecko
// Mozilla/5.0 (iPhone; CPU iPhone OS 13_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Mobile/15E148 Safari/604.1
// Mozilla/5.0 (iPad; CPU OS 12_5_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.2 Mobile/15E148 Safari/604.1
//
// error examples:
// - window.matchMedia("(prefers-color-scheme: dark)").addEventListener is not a function. (In 'window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",this.onSystemSettingChange.bind(this))', 'window.matchMedia("(prefers-color-scheme: dark)").addEventListener' is undefined)
// - Object [object MediaQueryList] has no method 'addEventListener'
return !!window['matchMedia']
&& typeof window.matchMedia("(prefers-color-scheme: dark)").addEventListener === 'function';
} catch (e) {
return false
}
}
/**
* @returns {boolean}
*/
function isDarkModeApplied() {
var st = StaticManager.loadedStyles;
for (var i = 0; i < st.length; i++) {
var name = st[i];
if (ge('style_'+name+'_dark'))
return true;
}
return false;
}
/**
* @returns {string}
*/
function getSavedMode() {
var val = getCookie('theme');
if (!val)
return modes[0];
if (modes.indexOf(val) === -1) {
console.error('[ThemeSwitcher getSavedMode] invalid cookie value')
unsetCookie('theme')
return modes[0]
}
return val
}
/**
* @param {boolean} dark
*/
function changeTheme(dark) {
addClass(document.body, 'theme-changing');
var onDone = function() {
window.requestAnimationFrame(function() {
removeClass(document.body, 'theme-changing');
changeListeners.forEach(function(f) {
try {
f(dark)
} catch (e) {
console.error('[ThemeSwitcher->changeTheme->onDone] error while calling user callback:', e)
}
})
})
};
window.requestAnimationFrame(function() {
if (dark)
enableDark(onDone);
else
disableDark(onDone);
})
}
/**
* @param {function} callback
*/
function enableDark(callback) {
var names = [];
StaticManager.loadedStyles.forEach(function(name) {
var el = ge('style_'+name+'_dark');
if (el)
return;
names.push(name);
});
var left = names.length;
names.forEach(function(name) {
StaticManager.loadStyle(name, 'dark', once(function(e) {
left--;
if (left === 0)
callback();
}));
})
}
/**
* @param {function} callback
*/
function disableDark(callback) {
StaticManager.loadedStyles.forEach(function(name) {
var el = ge('style_'+name+'_dark');
if (el)
el.remove();
})
callback();
}
/**
* @param {string} mode
*/
function setLabel(mode) {
var labelEl = ge('theme-switcher-label');
labelEl.innerHTML = escape(lang('theme_'+mode));
}
return {
init: function() {
var cur = getSavedMode();
currentModeIndex = modes.indexOf(cur);
var systemSupported = isSystemModeSupported();
if (!systemSupported) {
if (currentModeIndex === 0) {
modes.shift(); // remove 'auto' from the list
currentModeIndex = 1; // set to 'light'
if (isDarkModeApplied())
disableDark();
}
} else {
/**
* @param {boolean} dark
*/
var onSystemChange = function(dark) {
var prevSystemState = systemState;
systemState = dark;
if (modes[currentModeIndex] !== 'auto')
return;
if (systemState !== prevSystemState)
changeTheme(systemState);
};
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) {
onSystemChange(e.matches === true)
});
onSystemChange(window.matchMedia('(prefers-color-scheme: dark)').matches === true);
}
setLabel(modes[currentModeIndex]);
},
next: function(e) {
if (hasClass(document.body, 'theme-changing')) {
console.log('next: theme changing is in progress, ignoring...')
return;
}
currentModeIndex = (currentModeIndex + 1) % modes.length;
switch (modes[currentModeIndex]) {
case 'auto':
if (systemState !== null && systemState !== isDarkModeApplied())
changeTheme(systemState);
break;
case 'light':
if (isDarkModeApplied())
changeTheme(false);
break;
case 'dark':
if (!isDarkModeApplied())
changeTheme(true);
break;
}
setLabel(modes[currentModeIndex]);
setCookie('theme', modes[currentModeIndex]);
return cancelEvent(e);
},
/**
* @param {function} f
*/
addOnChangeListener: function(f) {
changeListeners.push(f);
}
};
})();