aboutsummaryrefslogtreecommitdiff
path: root/htdocs/js
diff options
context:
space:
mode:
authorEvgeny Zinoviev <me@ch1p.io>2022-07-10 01:30:05 +0300
committerEvgeny Zinoviev <me@ch1p.io>2022-07-10 23:53:02 +0300
commit1c524efbf7da91cb99bb4516feb514071e938495 (patch)
treee1fced0104749014db154f5d3b29881e705bfafc /htdocs/js
parent8979719a1af4bc0712407db7f95704f645f261a3 (diff)
dark theme support
Diffstat (limited to 'htdocs/js')
-rw-r--r--htdocs/js/common.js293
1 files changed, 290 insertions, 3 deletions
diff --git a/htdocs/js/common.js b/htdocs/js/common.js
index 4e4199c..959b672 100644
--- a/htdocs/js/common.js
+++ b/htdocs/js/common.js
@@ -14,6 +14,39 @@ if (!String.prototype.endsWith) {
};
}
+if (!Object.assign) {
+ Object.defineProperty(Object, 'assign', {
+ enumerable: false,
+ configurable: true,
+ writable: true,
+ value: function(target, firstSource) {
+ 'use strict';
+ if (target === undefined || target === null) {
+ throw new TypeError('Cannot convert first argument to object');
+ }
+
+ var to = Object(target);
+ for (var i = 1; i < arguments.length; i++) {
+ var nextSource = arguments[i];
+ if (nextSource === undefined || nextSource === null) {
+ continue;
+ }
+
+ var keysArray = Object.keys(Object(nextSource));
+ for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
+ var nextKey = keysArray[nextIndex];
+ var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
+ if (desc !== undefined && desc.enumerable) {
+ to[nextKey] = nextSource[nextKey];
+ }
+ }
+ }
+ return to;
+ }
+ });
+}
+
+
//
// AJAX
//
@@ -87,7 +120,7 @@ if (!String.prototype.endsWith) {
break;
}
- opts = extend({}, defaultOpts, opts);
+ opts = Object.assign({}, defaultOpts, opts);
var xhr = createXMLHttpRequest();
xhr.open(method, url);
@@ -244,11 +277,11 @@ function setCookie(name, value, days) {
date.setTime(date.getTime() + (days*24*60*60*1000));
expires = "; expires=" + date.toUTCString();
}
- document.cookie = name + "=" + (value || "") + expires + "; path=/";
+ document.cookie = name + "=" + (value || "") + expires + "; domain=" + window.appConfig.cookieHost + "; path=/";
}
function unsetCookie(name) {
- document.cookie = name+'=; Max-Age=-99999999;';
+ document.cookie = name + '=; Max-Age=-99999999; domain=' + window.appConfig.cookieHost + "; path=/";
}
function getCookie(name) {
@@ -312,6 +345,34 @@ function escape(str) {
return pre.innerHTML;
}
+function parseUrl(uri) {
+ var parser = document.createElement('a');
+ parser.href = uri;
+
+ return {
+ protocol: parser.protocol, // => "http:"
+ host: parser.host, // => "example.com:3000"
+ hostname: parser.hostname, // => "example.com"
+ port: parser.port, // => "3000"
+ pathname: parser.pathname, // => "/pathname/"
+ hash: parser.hash, // => "#hash"
+ search: parser.search, // => "?search=test"
+ origin: parser.origin, // => "http://example.com:3000"
+ path: (parser.pathname || '') + (parser.search || '')
+ }
+}
+
+function once(fn, context) {
+ var result;
+ return function() {
+ if (fn) {
+ result = fn.apply(context || this, arguments);
+ fn = null;
+ }
+ return result;
+ };
+}
+
//
//
@@ -390,3 +451,229 @@ window.__lang = {};
unsetCookie('is_retina');
}
})();
+
+var StaticManager = {
+ loadedStyles: [],
+ versions: {},
+
+ setStyles: function(list, versions) {
+ this.loadedStyles = list;
+ this.versions = versions;
+ },
+
+ loadStyle: function(name, theme, callback) {
+ var url;
+ if (!window.appConfig.devMode) {
+ var filename = name + (theme === 'dark' ? '_dark' : '') + '.css';
+ url = '/css/'+filename+'?'+this.versions[filename];
+ } else {
+ url = '/sass.php?name='+name+'&theme='+theme;
+ }
+
+ var el = document.createElement('link');
+ el.onerror = callback
+ el.onload = callback
+ el.setAttribute('rel', 'stylesheet');
+ el.setAttribute('type', 'text/css');
+ el.setAttribute('id', 'style_'+name+'_dark');
+ el.setAttribute('href', url);
+
+ document.getElementsByTagName('head')[0].appendChild(el);
+ }
+};
+
+var ThemeSwitcher = (function() {
+ /**
+ * @type {string[]}
+ */
+ var modes = ['auto', 'dark', 'light'];
+
+ /**
+ * @type {number}
+ */
+ var currentModeIndex = -1;
+
+ /**
+ * @type {boolean|null}
+ */
+ var systemState = null;
+
+ /**
+ * @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');
+ })
+ };
+
+ 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)
+ 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);
+ }
+ };
+})(); \ No newline at end of file