mirror of
https://github.com/element-hq/element-web
synced 2024-11-27 03:36:07 +03:00
Merge pull request #3637 from matrix-org/dbkr/system_dark_mode
Get theme automatically from system setting
This commit is contained in:
commit
00241a8d0c
5 changed files with 106 additions and 18 deletions
|
@ -59,7 +59,7 @@ import { ValidatedServerConfig } from "../../utils/AutoDiscoveryUtils";
|
||||||
import AutoDiscoveryUtils from "../../utils/AutoDiscoveryUtils";
|
import AutoDiscoveryUtils from "../../utils/AutoDiscoveryUtils";
|
||||||
import DMRoomMap from '../../utils/DMRoomMap';
|
import DMRoomMap from '../../utils/DMRoomMap';
|
||||||
import { countRoomsWithNotif } from '../../RoomNotifs';
|
import { countRoomsWithNotif } from '../../RoomNotifs';
|
||||||
import { setTheme } from "../../theme";
|
import { ThemeWatcher } from "../../theme";
|
||||||
import { storeRoomAliasInCache } from '../../RoomAliasCache';
|
import { storeRoomAliasInCache } from '../../RoomAliasCache';
|
||||||
import { defer } from "../../utils/promise";
|
import { defer } from "../../utils/promise";
|
||||||
|
|
||||||
|
@ -274,7 +274,8 @@ export default createReactClass({
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
this._themeWatchRef = SettingsStore.watchSetting("theme", null, this._onThemeChanged);
|
this._themeWatcher = new ThemeWatcher();
|
||||||
|
this._themeWatcher.start();
|
||||||
|
|
||||||
this.focusComposer = false;
|
this.focusComposer = false;
|
||||||
|
|
||||||
|
@ -361,7 +362,7 @@ export default createReactClass({
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
Lifecycle.stopMatrixClient();
|
Lifecycle.stopMatrixClient();
|
||||||
dis.unregister(this.dispatcherRef);
|
dis.unregister(this.dispatcherRef);
|
||||||
SettingsStore.unwatchSetting(this._themeWatchRef);
|
this._themeWatcher.stop();
|
||||||
window.removeEventListener("focus", this.onFocus);
|
window.removeEventListener("focus", this.onFocus);
|
||||||
window.removeEventListener('resize', this.handleResize);
|
window.removeEventListener('resize', this.handleResize);
|
||||||
this.state.resizeNotifier.removeListener("middlePanelResized", this._dispatchTimelineResize);
|
this.state.resizeNotifier.removeListener("middlePanelResized", this._dispatchTimelineResize);
|
||||||
|
@ -384,13 +385,6 @@ export default createReactClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_onThemeChanged: function(settingName, roomId, atLevel, newValue) {
|
|
||||||
dis.dispatch({
|
|
||||||
action: 'set_theme',
|
|
||||||
value: newValue,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
startPageChangeTimer() {
|
startPageChangeTimer() {
|
||||||
// Tor doesn't support performance
|
// Tor doesn't support performance
|
||||||
if (!performance || !performance.mark) return null;
|
if (!performance || !performance.mark) return null;
|
||||||
|
@ -672,9 +666,6 @@ export default createReactClass({
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'set_theme':
|
|
||||||
setTheme(payload.value);
|
|
||||||
break;
|
|
||||||
case 'on_logging_in':
|
case 'on_logging_in':
|
||||||
// We are now logging in, so set the state to reflect that
|
// We are now logging in, so set the state to reflect that
|
||||||
// NB. This does not touch 'ready' since if our dispatches
|
// NB. This does not touch 'ready' since if our dispatches
|
||||||
|
|
|
@ -27,7 +27,7 @@ import LanguageDropdown from "../../../elements/LanguageDropdown";
|
||||||
import AccessibleButton from "../../../elements/AccessibleButton";
|
import AccessibleButton from "../../../elements/AccessibleButton";
|
||||||
import DeactivateAccountDialog from "../../../dialogs/DeactivateAccountDialog";
|
import DeactivateAccountDialog from "../../../dialogs/DeactivateAccountDialog";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import {enumerateThemes} from "../../../../../theme";
|
import {enumerateThemes, ThemeWatcher} from "../../../../../theme";
|
||||||
import PlatformPeg from "../../../../../PlatformPeg";
|
import PlatformPeg from "../../../../../PlatformPeg";
|
||||||
import MatrixClientPeg from "../../../../../MatrixClientPeg";
|
import MatrixClientPeg from "../../../../../MatrixClientPeg";
|
||||||
import sdk from "../../../../..";
|
import sdk from "../../../../..";
|
||||||
|
@ -50,6 +50,7 @@ export default class GeneralUserSettingsTab extends React.Component {
|
||||||
this.state = {
|
this.state = {
|
||||||
language: languageHandler.getCurrentLanguage(),
|
language: languageHandler.getCurrentLanguage(),
|
||||||
theme: SettingsStore.getValueAt(SettingLevel.ACCOUNT, "theme"),
|
theme: SettingsStore.getValueAt(SettingLevel.ACCOUNT, "theme"),
|
||||||
|
useSystemTheme: SettingsStore.getValueAt(SettingLevel.DEVICE, "use_system_theme"),
|
||||||
haveIdServer: Boolean(MatrixClientPeg.get().getIdentityServerUrl()),
|
haveIdServer: Boolean(MatrixClientPeg.get().getIdentityServerUrl()),
|
||||||
serverSupportsSeparateAddAndBind: null,
|
serverSupportsSeparateAddAndBind: null,
|
||||||
idServerHasUnsignedTerms: false,
|
idServerHasUnsignedTerms: false,
|
||||||
|
@ -177,16 +178,25 @@ export default class GeneralUserSettingsTab extends React.Component {
|
||||||
// so remember what the value was before we tried to set it so we can revert
|
// so remember what the value was before we tried to set it so we can revert
|
||||||
const oldTheme = SettingsStore.getValue('theme');
|
const oldTheme = SettingsStore.getValue('theme');
|
||||||
SettingsStore.setValue("theme", null, SettingLevel.ACCOUNT, newTheme).catch(() => {
|
SettingsStore.setValue("theme", null, SettingLevel.ACCOUNT, newTheme).catch(() => {
|
||||||
dis.dispatch({action: 'set_theme', value: oldTheme});
|
dis.dispatch({action: 'recheck_theme'});
|
||||||
this.setState({theme: oldTheme});
|
this.setState({theme: oldTheme});
|
||||||
});
|
});
|
||||||
this.setState({theme: newTheme});
|
this.setState({theme: newTheme});
|
||||||
// The settings watcher doesn't fire until the echo comes back from the
|
// The settings watcher doesn't fire until the echo comes back from the
|
||||||
// server, so to make the theme change immediately we need to manually
|
// server, so to make the theme change immediately we need to manually
|
||||||
// do the dispatch now
|
// do the dispatch now
|
||||||
dis.dispatch({action: 'set_theme', value: newTheme});
|
// XXX: The local echoed value appears to be unreliable, in particular
|
||||||
|
// when settings custom themes(!) so adding forceTheme to override
|
||||||
|
// the value from settings.
|
||||||
|
dis.dispatch({action: 'recheck_theme', forceTheme: newTheme});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_onUseSystemThemeChanged = (checked) => {
|
||||||
|
this.setState({useSystemTheme: checked});
|
||||||
|
dis.dispatch({action: 'recheck_theme'});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
_onPasswordChangeError = (err) => {
|
_onPasswordChangeError = (err) => {
|
||||||
// TODO: Figure out a design that doesn't involve replacing the current dialog
|
// TODO: Figure out a design that doesn't involve replacing the current dialog
|
||||||
let errMsg = err.error || "";
|
let errMsg = err.error || "";
|
||||||
|
@ -297,11 +307,24 @@ export default class GeneralUserSettingsTab extends React.Component {
|
||||||
|
|
||||||
_renderThemeSection() {
|
_renderThemeSection() {
|
||||||
const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag");
|
const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag");
|
||||||
|
|
||||||
|
const themeWatcher = new ThemeWatcher();
|
||||||
|
let systemThemeSection;
|
||||||
|
if (themeWatcher.isSystemThemeSupported()) {
|
||||||
|
systemThemeSection = <div>
|
||||||
|
<SettingsFlag name="use_system_theme" level={SettingLevel.DEVICE}
|
||||||
|
onChange={this._onUseSystemThemeChanged}
|
||||||
|
/>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className="mx_SettingsTab_section mx_GeneralUserSettingsTab_themeSection">
|
<div className="mx_SettingsTab_section mx_GeneralUserSettingsTab_themeSection">
|
||||||
<span className="mx_SettingsTab_subheading">{_t("Theme")}</span>
|
<span className="mx_SettingsTab_subheading">{_t("Theme")}</span>
|
||||||
|
{systemThemeSection}
|
||||||
<Field id="theme" label={_t("Theme")} element="select"
|
<Field id="theme" label={_t("Theme")} element="select"
|
||||||
value={this.state.theme} onChange={this._onThemeChange}>
|
value={this.state.theme} onChange={this._onThemeChange}
|
||||||
|
disabled={this.state.useSystemTheme}
|
||||||
|
>
|
||||||
{Object.entries(enumerateThemes()).map(([theme, text]) => {
|
{Object.entries(enumerateThemes()).map(([theme, text]) => {
|
||||||
return <option key={theme} value={theme}>{text}</option>;
|
return <option key={theme} value={theme}>{text}</option>;
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -364,6 +364,7 @@
|
||||||
"Automatically replace plain text Emoji": "Automatically replace plain text Emoji",
|
"Automatically replace plain text Emoji": "Automatically replace plain text Emoji",
|
||||||
"Mirror local video feed": "Mirror local video feed",
|
"Mirror local video feed": "Mirror local video feed",
|
||||||
"Enable Community Filter Panel": "Enable Community Filter Panel",
|
"Enable Community Filter Panel": "Enable Community Filter Panel",
|
||||||
|
"Match system dark mode setting": "Match system dark mode setting",
|
||||||
"Allow Peer-to-Peer for 1:1 calls": "Allow Peer-to-Peer for 1:1 calls",
|
"Allow Peer-to-Peer for 1:1 calls": "Allow Peer-to-Peer for 1:1 calls",
|
||||||
"Send analytics data": "Send analytics data",
|
"Send analytics data": "Send analytics data",
|
||||||
"Never send encrypted messages to unverified devices from this device": "Never send encrypted messages to unverified devices from this device",
|
"Never send encrypted messages to unverified devices from this device": "Never send encrypted messages to unverified devices from this device",
|
||||||
|
|
|
@ -281,6 +281,11 @@ export const SETTINGS = {
|
||||||
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||||
default: [],
|
default: [],
|
||||||
},
|
},
|
||||||
|
"use_system_theme": {
|
||||||
|
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
|
||||||
|
default: true,
|
||||||
|
displayName: _td("Match system dark mode setting"),
|
||||||
|
},
|
||||||
"webRtcAllowPeerToPeer": {
|
"webRtcAllowPeerToPeer": {
|
||||||
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG,
|
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG,
|
||||||
displayName: _td('Allow Peer-to-Peer for 1:1 calls'),
|
displayName: _td('Allow Peer-to-Peer for 1:1 calls'),
|
||||||
|
|
70
src/theme.js
70
src/theme.js
|
@ -19,8 +19,75 @@ import {_t} from "./languageHandler";
|
||||||
|
|
||||||
export const DEFAULT_THEME = "light";
|
export const DEFAULT_THEME = "light";
|
||||||
import Tinter from "./Tinter";
|
import Tinter from "./Tinter";
|
||||||
|
import dis from "./dispatcher";
|
||||||
import SettingsStore from "./settings/SettingsStore";
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
|
|
||||||
|
export class ThemeWatcher {
|
||||||
|
static _instance = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this._themeWatchRef = null;
|
||||||
|
this._systemThemeWatchRef = null;
|
||||||
|
this._dispatcherRef = null;
|
||||||
|
|
||||||
|
// we have both here as each may either match or not match, so by having both
|
||||||
|
// we can get the tristate of dark/light/unsupported
|
||||||
|
this._preferDark = global.matchMedia("(prefers-color-scheme: dark)");
|
||||||
|
this._preferLight = global.matchMedia("(prefers-color-scheme: light)");
|
||||||
|
|
||||||
|
this._currentTheme = this.getEffectiveTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this._themeWatchRef = SettingsStore.watchSetting("theme", null, this._onChange);
|
||||||
|
this._systemThemeWatchRef = SettingsStore.watchSetting("use_system_theme", null, this._onChange);
|
||||||
|
this._preferDark.addEventListener('change', this._onChange);
|
||||||
|
this._preferLight.addEventListener('change', this._onChange);
|
||||||
|
this._dispatcherRef = dis.register(this._onAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
this._preferDark.removeEventListener('change', this._onChange);
|
||||||
|
this._preferLight.removeEventListener('change', this._onChange);
|
||||||
|
SettingsStore.unwatchSetting(this._systemThemeWatchRef);
|
||||||
|
SettingsStore.unwatchSetting(this._themeWatchRef);
|
||||||
|
dis.unregister(this._dispatcherRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onChange = () => {
|
||||||
|
this.recheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onAction = (payload) => {
|
||||||
|
if (payload.action === 'recheck_theme') {
|
||||||
|
// XXX forceTheme
|
||||||
|
this.recheck(payload.forceTheme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX: forceTheme param aded here as local echo appears to be unreliable
|
||||||
|
// https://github.com/vector-im/riot-web/issues/11443
|
||||||
|
recheck(forceTheme) {
|
||||||
|
const oldTheme = this._currentTheme;
|
||||||
|
this._currentTheme = forceTheme === undefined ? this.getEffectiveTheme() : forceTheme;
|
||||||
|
if (oldTheme !== this._currentTheme) {
|
||||||
|
setTheme(this._currentTheme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getEffectiveTheme() {
|
||||||
|
if (SettingsStore.getValue('use_system_theme')) {
|
||||||
|
if (this._preferDark.matches) return 'dark';
|
||||||
|
if (this._preferLight.matches) return 'light';
|
||||||
|
}
|
||||||
|
return SettingsStore.getValue('theme');
|
||||||
|
}
|
||||||
|
|
||||||
|
isSystemThemeSupported() {
|
||||||
|
return this._preferDark.matches || this._preferLight.matches;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function enumerateThemes() {
|
export function enumerateThemes() {
|
||||||
const BUILTIN_THEMES = {
|
const BUILTIN_THEMES = {
|
||||||
"light": _t("Light theme"),
|
"light": _t("Light theme"),
|
||||||
|
@ -83,7 +150,8 @@ export function getBaseTheme(theme) {
|
||||||
*/
|
*/
|
||||||
export function setTheme(theme) {
|
export function setTheme(theme) {
|
||||||
if (!theme) {
|
if (!theme) {
|
||||||
theme = SettingsStore.getValue("theme");
|
const themeWatcher = new ThemeWatcher();
|
||||||
|
theme = themeWatcher.getEffectiveTheme();
|
||||||
}
|
}
|
||||||
let stylesheetName = theme;
|
let stylesheetName = theme;
|
||||||
if (theme.startsWith("custom-")) {
|
if (theme.startsWith("custom-")) {
|
||||||
|
|
Loading…
Reference in a new issue