mirror of https://github.com/keeweb/keeweb
fix #1584: automatically switching between dark and light theme
parent
f99ff95a57
commit
677f1ce75f
|
@ -9,6 +9,7 @@ import { UsbListener } from 'comp/app/usb-listener';
|
|||
import { FeatureTester } from 'comp/browser/feature-tester';
|
||||
import { FocusDetector } from 'comp/browser/focus-detector';
|
||||
import { IdleTracker } from 'comp/browser/idle-tracker';
|
||||
import { ThemeWatcher } from 'comp/browser/theme-watcher';
|
||||
import { KeyHandler } from 'comp/browser/key-handler';
|
||||
import { PopupNotifier } from 'comp/browser/popup-notifier';
|
||||
import { Launcher } from 'comp/launcher';
|
||||
|
@ -91,6 +92,8 @@ ready(() => {
|
|||
KdbxwebInit.init();
|
||||
FocusDetector.init();
|
||||
AutoType.init();
|
||||
ThemeWatcher.init();
|
||||
SettingsManager.init();
|
||||
window.kw = ExportApi;
|
||||
return PluginManager.init().then(() => {
|
||||
StartProfiler.milestone('initializing modules');
|
||||
|
@ -111,13 +114,13 @@ ready(() => {
|
|||
function loadRemoteConfig() {
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
SettingsManager.setBySettings(appModel.settings);
|
||||
SettingsManager.setBySettings();
|
||||
const configParam = getConfigParam();
|
||||
if (configParam) {
|
||||
return appModel
|
||||
.loadConfig(configParam)
|
||||
.then(() => {
|
||||
SettingsManager.setBySettings(appModel.settings);
|
||||
SettingsManager.setBySettings();
|
||||
})
|
||||
.catch((e) => {
|
||||
if (!appModel.settings.cacheConfigSettings) {
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import { Events } from 'framework/events';
|
||||
|
||||
const ThemeWatcher = {
|
||||
dark: false,
|
||||
|
||||
init() {
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
mediaQuery.addEventListener('change', (e) => {
|
||||
const dark = e.matches;
|
||||
this.dark = dark;
|
||||
Events.emit('dark-mode-changed', { dark });
|
||||
});
|
||||
this.dark = mediaQuery.matches;
|
||||
}
|
||||
};
|
||||
|
||||
export { ThemeWatcher };
|
|
@ -1,6 +1,11 @@
|
|||
import { Events } from 'framework/events';
|
||||
import { Features } from 'util/features';
|
||||
import { Locale } from 'util/locale';
|
||||
import { ThemeWatcher } from 'comp/browser/theme-watcher';
|
||||
import { AppSettingsModel } from 'models/app-settings-model';
|
||||
import { Logger } from 'util/logger';
|
||||
|
||||
const logger = new Logger('settings-manager');
|
||||
|
||||
const SettingsManager = {
|
||||
neutralLocale: null,
|
||||
|
@ -16,23 +21,40 @@ const SettingsManager = {
|
|||
allThemes: {
|
||||
dark: 'setGenThemeDark',
|
||||
light: 'setGenThemeLight',
|
||||
fb: 'setGenThemeFb',
|
||||
db: 'setGenThemeDb',
|
||||
sd: 'setGenThemeSd',
|
||||
sl: 'setGenThemeSl',
|
||||
fb: 'setGenThemeFb',
|
||||
db: 'setGenThemeDb',
|
||||
te: 'setGenThemeTe',
|
||||
hc: 'setGenThemeHc'
|
||||
},
|
||||
|
||||
autoSwitchedThemes: [
|
||||
{
|
||||
name: 'setGenThemeDefault',
|
||||
dark: 'dark',
|
||||
light: 'light'
|
||||
},
|
||||
{
|
||||
name: 'setGenThemeSol',
|
||||
dark: 'sd',
|
||||
light: 'sl'
|
||||
}
|
||||
],
|
||||
|
||||
customLocales: {},
|
||||
|
||||
setBySettings(settings) {
|
||||
this.setTheme(settings.theme);
|
||||
this.setFontSize(settings.fontSize);
|
||||
const locale = settings.locale;
|
||||
init() {
|
||||
Events.on('dark-mode-changed', () => this.darkModeChanged());
|
||||
},
|
||||
|
||||
setBySettings() {
|
||||
this.setTheme(AppSettingsModel.theme);
|
||||
this.setFontSize(AppSettingsModel.fontSize);
|
||||
const locale = AppSettingsModel.locale;
|
||||
try {
|
||||
if (locale) {
|
||||
this.setLocale(settings.locale);
|
||||
this.setLocale(AppSettingsModel.locale);
|
||||
} else {
|
||||
this.setLocale(this.getBrowserLocale());
|
||||
}
|
||||
|
@ -55,18 +77,45 @@ const SettingsManager = {
|
|||
document.body.classList.remove(cls);
|
||||
}
|
||||
}
|
||||
if (AppSettingsModel.autoSwitchTheme) {
|
||||
theme = this.selectDarkOrLightTheme(theme);
|
||||
}
|
||||
document.body.classList.add(this.getThemeClass(theme));
|
||||
const metaThemeColor = document.head.querySelector('meta[name=theme-color]');
|
||||
if (metaThemeColor) {
|
||||
metaThemeColor.content = window.getComputedStyle(document.body).backgroundColor;
|
||||
}
|
||||
this.activeTheme = theme;
|
||||
logger.debug('Theme changed', theme);
|
||||
Events.emit('theme-applied');
|
||||
},
|
||||
|
||||
getThemeClass(theme) {
|
||||
return 'th-' + theme;
|
||||
},
|
||||
|
||||
selectDarkOrLightTheme(theme) {
|
||||
for (const config of this.autoSwitchedThemes) {
|
||||
if (config.light === theme || config.dark === theme) {
|
||||
return ThemeWatcher.dark ? config.dark : config.light;
|
||||
}
|
||||
}
|
||||
return theme;
|
||||
},
|
||||
|
||||
darkModeChanged() {
|
||||
if (AppSettingsModel.autoSwitchTheme) {
|
||||
for (const config of this.autoSwitchedThemes) {
|
||||
if (config.light === this.activeTheme || config.dark === this.activeTheme) {
|
||||
const newTheme = ThemeWatcher.dark ? config.dark : config.light;
|
||||
logger.debug('Setting theme triggered by system settings change', newTheme);
|
||||
this.setTheme(newTheme);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setFontSize(fontSize) {
|
||||
const defaultFontSize = Features.isMobile ? 14 : 12;
|
||||
document.documentElement.style.fontSize = defaultFontSize + (fontSize || 0) * 2 + 'px';
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const DefaultAppSettings = {
|
||||
theme: null, // UI theme
|
||||
autoSwitchTheme: false, // automatically switch between light and dark theme
|
||||
locale: null, // user interface language
|
||||
expandGroups: true, // show entries from all subgroups
|
||||
listViewWidth: null, // width of the entry list representation
|
||||
|
|
|
@ -371,15 +371,18 @@
|
|||
"setGenDownloadAndRestart": "Download update and restart",
|
||||
"setGenAppearance": "Appearance",
|
||||
"setGenTheme": "Theme",
|
||||
"setGenThemeDefault": "Default",
|
||||
"setGenThemeDark": "Dark",
|
||||
"setGenThemeLight": "Light",
|
||||
"setGenThemeFb": "Flat blue",
|
||||
"setGenThemeDb": "Dark brown",
|
||||
"setGenThemeTe": "Terminal",
|
||||
"setGenThemeHc": "High contrast",
|
||||
"setGenThemeSol": "Solarized",
|
||||
"setGenThemeSd": "Solarized dark",
|
||||
"setGenThemeSl": "Solarized light",
|
||||
"setGenMoreThemes": "More themes",
|
||||
"setGenAutoSwitchTheme": "Automatically switch between light and dark theme when possible",
|
||||
"setGenLocale": "Language",
|
||||
"setGenLocOther": "other languages are available as plugins",
|
||||
"setGenFontSize": "Font size",
|
||||
|
|
|
@ -17,6 +17,7 @@ import { Locale } from 'util/locale';
|
|||
import { SettingsLogsView } from 'views/settings/settings-logs-view';
|
||||
import { SettingsPrvView } from 'views/settings/settings-prv-view';
|
||||
import { mapObject } from 'util/fn';
|
||||
import { ThemeWatcher } from 'comp/browser/theme-watcher';
|
||||
import template from 'templates/settings/settings-general.hbs';
|
||||
|
||||
class SettingsGeneralView extends View {
|
||||
|
@ -24,6 +25,7 @@ class SettingsGeneralView extends View {
|
|||
|
||||
events = {
|
||||
'click .settings__general-theme': 'changeTheme',
|
||||
'click .settings__general-auto-switch-theme': 'changeAuthSwitchTheme',
|
||||
'change .settings__general-locale': 'changeLocale',
|
||||
'change .settings__general-font-size': 'changeFontSize',
|
||||
'change .settings__general-expand': 'changeExpandGroups',
|
||||
|
@ -63,6 +65,7 @@ class SettingsGeneralView extends View {
|
|||
super(model, options);
|
||||
this.listenTo(UpdateModel, 'change:status', this.render);
|
||||
this.listenTo(UpdateModel, 'change:updateStatus', this.render);
|
||||
this.listenTo(Events, 'theme-applied', this.render);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -72,7 +75,8 @@ class SettingsGeneralView extends View {
|
|||
const storageProviders = this.getStorageProviders();
|
||||
|
||||
super.render({
|
||||
themes: mapObject(SettingsManager.allThemes, (theme) => Locale[theme]),
|
||||
themes: this.getAllThemes(),
|
||||
autoSwitchTheme: AppSettingsModel.autoSwitchTheme,
|
||||
activeTheme: SettingsManager.activeTheme,
|
||||
locales: SettingsManager.allLocales,
|
||||
activeLocale: SettingsManager.activeLocale,
|
||||
|
@ -204,16 +208,49 @@ class SettingsGeneralView extends View {
|
|||
}));
|
||||
}
|
||||
|
||||
getAllThemes() {
|
||||
const { autoSwitchTheme } = AppSettingsModel;
|
||||
if (autoSwitchTheme) {
|
||||
const themes = {};
|
||||
const ignoredThemes = {};
|
||||
for (const config of SettingsManager.autoSwitchedThemes) {
|
||||
ignoredThemes[config.dark] = true;
|
||||
ignoredThemes[config.light] = true;
|
||||
const activeTheme = ThemeWatcher.dark ? config.dark : config.light;
|
||||
themes[activeTheme] = Locale[config.name];
|
||||
}
|
||||
for (const [th, name] of Object.entries(SettingsManager.allThemes)) {
|
||||
if (!ignoredThemes[th]) {
|
||||
themes[th] = Locale[name];
|
||||
}
|
||||
}
|
||||
return themes;
|
||||
} else {
|
||||
return mapObject(SettingsManager.allThemes, (theme) => Locale[theme]);
|
||||
}
|
||||
}
|
||||
|
||||
changeTheme(e) {
|
||||
const theme = e.target.closest('.settings__general-theme').dataset.theme;
|
||||
if (theme === '...') {
|
||||
this.goToPlugins();
|
||||
} else {
|
||||
AppSettingsModel.theme = theme;
|
||||
this.render();
|
||||
const changedInSettings = AppSettingsModel.theme !== theme;
|
||||
if (changedInSettings) {
|
||||
AppSettingsModel.theme = theme;
|
||||
} else {
|
||||
SettingsManager.setTheme(theme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changeAuthSwitchTheme(e) {
|
||||
const autoSwitchTheme = e.target.checked;
|
||||
AppSettingsModel.autoSwitchTheme = autoSwitchTheme;
|
||||
SettingsManager.darkModeChanged();
|
||||
this.render();
|
||||
}
|
||||
|
||||
changeLocale(e) {
|
||||
const locale = e.target.value;
|
||||
if (locale === '...') {
|
||||
|
|
|
@ -66,6 +66,10 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" class="settings__input input-base settings__general-auto-switch-theme" id="settings__general-auto-switch-theme" {{#if autoSwitchTheme}}checked{{/if}} />
|
||||
<label for="settings__general-auto-switch-theme">{{res 'setGenAutoSwitchTheme'}}</label>
|
||||
</div>
|
||||
<div>
|
||||
<label for="settings__general-font-size">{{res 'setGenFontSize'}}:</label>
|
||||
<select class="settings__general-font-size settings__select input-base" id="settings__general-font-size">
|
||||
|
|
|
@ -66,6 +66,10 @@ const themeBgColors = {
|
|||
sd: '#002b36',
|
||||
sl: '#fdf6e3'
|
||||
};
|
||||
const darkLightThemes = {
|
||||
dark: 'light',
|
||||
sd: 'sl'
|
||||
};
|
||||
const defaultBgColor = '#282C34';
|
||||
|
||||
logProgress('defining args');
|
||||
|
@ -102,7 +106,6 @@ app.on('ready', () => {
|
|||
|
||||
settingsPromise
|
||||
.then(() => {
|
||||
setSystemAppearance();
|
||||
createMainWindow();
|
||||
setGlobalShortcuts(appSettings);
|
||||
subscribePowerEvents();
|
||||
|
@ -214,15 +217,6 @@ function logStartupMessage(msg) {
|
|||
}
|
||||
}
|
||||
|
||||
function setSystemAppearance() {
|
||||
if (process.platform === 'darwin') {
|
||||
if (electron.nativeTheme.shouldUseDarkColors) {
|
||||
electron.systemPreferences.appLevelAppearance = 'dark';
|
||||
}
|
||||
}
|
||||
logProgress('setting system appearance');
|
||||
}
|
||||
|
||||
function checkSettingsTheme(theme) {
|
||||
// old settings migration
|
||||
if (theme === 'macdark') {
|
||||
|
@ -235,14 +229,24 @@ function checkSettingsTheme(theme) {
|
|||
}
|
||||
|
||||
function getDefaultTheme() {
|
||||
if (process.platform === 'darwin' && !electron.nativeTheme.shouldUseDarkColors) {
|
||||
return 'light';
|
||||
}
|
||||
return 'dark';
|
||||
}
|
||||
|
||||
function selectDarkOrLightTheme(theme) {
|
||||
const dark = electron.nativeTheme.shouldUseDarkColors;
|
||||
for (const [darkTheme, lightTheme] of Object.entries(darkLightThemes)) {
|
||||
if (darkTheme === theme || lightTheme === theme) {
|
||||
return dark ? darkTheme : lightTheme;
|
||||
}
|
||||
}
|
||||
return theme;
|
||||
}
|
||||
|
||||
function createMainWindow() {
|
||||
const theme = checkSettingsTheme(appSettings.theme) || getDefaultTheme();
|
||||
let theme = checkSettingsTheme(appSettings.theme) || getDefaultTheme();
|
||||
if (appSettings.autoSwitchTheme) {
|
||||
theme = selectDarkOrLightTheme(theme);
|
||||
}
|
||||
const bgColor = themeBgColors[theme] || defaultBgColor;
|
||||
const windowOptions = {
|
||||
show: false,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
Release notes
|
||||
-------------
|
||||
##### v1.17.0 (TBD)
|
||||
`+` automatically switching between dark and light theme
|
||||
`+` clear searchbox button
|
||||
|
||||
##### v1.16.5 (2020-12-18)
|
||||
|
|
Loading…
Reference in New Issue