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); } }; })();