mirror of https://github.com/vector-im/riot-web
support custom themes from setting
also move theme setting code from MatrixChat to own file.pull/21833/head
parent
79d4434c9f
commit
558f8daeeb
|
@ -56,6 +56,7 @@ import { ValidatedServerConfig } from "../../utils/AutoDiscoveryUtils";
|
|||
import AutoDiscoveryUtils from "../../utils/AutoDiscoveryUtils";
|
||||
import DMRoomMap from '../../utils/DMRoomMap';
|
||||
import { countRoomsWithNotif } from '../../RoomNotifs';
|
||||
import { setTheme } from "../../theme";
|
||||
|
||||
// Disable warnings for now: we use deprecated bluebird functions
|
||||
// and need to migrate, but they spam the console with warnings.
|
||||
|
@ -658,7 +659,7 @@ export default createReactClass({
|
|||
break;
|
||||
}
|
||||
case 'set_theme':
|
||||
this._onSetTheme(payload.value);
|
||||
setTheme(payload.value);
|
||||
break;
|
||||
case 'on_logging_in':
|
||||
// We are now logging in, so set the state to reflect that
|
||||
|
@ -1102,82 +1103,6 @@ export default createReactClass({
|
|||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Called whenever someone changes the theme
|
||||
*
|
||||
* @param {string} theme new theme
|
||||
*/
|
||||
_onSetTheme: function(theme) {
|
||||
if (!theme) {
|
||||
theme = SettingsStore.getValue("theme");
|
||||
}
|
||||
|
||||
// look for the stylesheet elements.
|
||||
// styleElements is a map from style name to HTMLLinkElement.
|
||||
const styleElements = Object.create(null);
|
||||
let a;
|
||||
for (let i = 0; (a = document.getElementsByTagName("link")[i]); i++) {
|
||||
const href = a.getAttribute("href");
|
||||
// shouldn't we be using the 'title' tag rather than the href?
|
||||
const match = href.match(/^bundles\/.*\/theme-(.*)\.css$/);
|
||||
if (match) {
|
||||
styleElements[match[1]] = a;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(theme in styleElements)) {
|
||||
throw new Error("Unknown theme " + theme);
|
||||
}
|
||||
|
||||
// disable all of them first, then enable the one we want. Chrome only
|
||||
// bothers to do an update on a true->false transition, so this ensures
|
||||
// that we get exactly one update, at the right time.
|
||||
//
|
||||
// ^ This comment was true when we used to use alternative stylesheets
|
||||
// for the CSS. Nowadays we just set them all as disabled in index.html
|
||||
// and enable them as needed. It might be cleaner to disable them all
|
||||
// at the same time to prevent loading two themes simultaneously and
|
||||
// having them interact badly... but this causes a flash of unstyled app
|
||||
// which is even uglier. So we don't.
|
||||
|
||||
styleElements[theme].disabled = false;
|
||||
|
||||
const switchTheme = function() {
|
||||
// we re-enable our theme here just in case we raced with another
|
||||
// theme set request as per https://github.com/vector-im/riot-web/issues/5601.
|
||||
// We could alternatively lock or similar to stop the race, but
|
||||
// this is probably good enough for now.
|
||||
styleElements[theme].disabled = false;
|
||||
Object.values(styleElements).forEach((a) => {
|
||||
if (a == styleElements[theme]) return;
|
||||
a.disabled = true;
|
||||
});
|
||||
Tinter.setTheme(theme);
|
||||
};
|
||||
|
||||
// turns out that Firefox preloads the CSS for link elements with
|
||||
// the disabled attribute, but Chrome doesn't.
|
||||
|
||||
let cssLoaded = false;
|
||||
|
||||
styleElements[theme].onload = () => {
|
||||
switchTheme();
|
||||
};
|
||||
|
||||
for (let i = 0; i < document.styleSheets.length; i++) {
|
||||
const ss = document.styleSheets[i];
|
||||
if (ss && ss.href === styleElements[theme].href) {
|
||||
cssLoaded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cssLoaded) {
|
||||
styleElements[theme].onload = undefined;
|
||||
switchTheme();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts a chat with the welcome user, if the user doesn't already have one
|
||||
* @returns {string} The room ID of the new room, or null if no room was created
|
||||
|
|
|
@ -27,7 +27,7 @@ import LanguageDropdown from "../../../elements/LanguageDropdown";
|
|||
import AccessibleButton from "../../../elements/AccessibleButton";
|
||||
import DeactivateAccountDialog from "../../../dialogs/DeactivateAccountDialog";
|
||||
import PropTypes from "prop-types";
|
||||
import {THEMES} from "../../../../../themes";
|
||||
import {enumerateThemes} from "../../../../../theme";
|
||||
import PlatformPeg from "../../../../../PlatformPeg";
|
||||
import MatrixClientPeg from "../../../../../MatrixClientPeg";
|
||||
import sdk from "../../../../..";
|
||||
|
@ -275,8 +275,8 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
<span className="mx_SettingsTab_subheading">{_t("Theme")}</span>
|
||||
<Field id="theme" label={_t("Theme")} element="select"
|
||||
value={this.state.theme} onChange={this._onThemeChange}>
|
||||
{Object.entries(THEMES).map(([theme, text]) => {
|
||||
return <option key={theme} value={theme}>{_t(text)}</option>;
|
||||
{Object.entries(enumerateThemes()).map(([theme, text]) => {
|
||||
return <option key={theme} value={theme}>{text}</option>;
|
||||
})}
|
||||
</Field>
|
||||
<SettingsFlag name="useCompactLayout" level={SettingLevel.ACCOUNT} />
|
||||
|
|
|
@ -245,6 +245,10 @@ export const SETTINGS = {
|
|||
default: "light",
|
||||
controller: new ThemeController(),
|
||||
},
|
||||
"custom_themes": {
|
||||
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||
default: [],
|
||||
},
|
||||
"webRtcAllowPeerToPeer": {
|
||||
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG,
|
||||
displayName: _td('Allow Peer-to-Peer for 1:1 calls'),
|
||||
|
|
|
@ -16,12 +16,13 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import SettingController from "./SettingController";
|
||||
import {DEFAULT_THEME, THEMES} from "../../themes";
|
||||
import {DEFAULT_THEME, enumerateThemes} from "../../theme";
|
||||
|
||||
export default class ThemeController extends SettingController {
|
||||
getValueOverride(level, roomId, calculatedValue, calculatedAtLevel) {
|
||||
const themes = enumerateThemes();
|
||||
// Override in case some no longer supported theme is stored here
|
||||
if (!THEMES[calculatedValue]) {
|
||||
if (!themes[calculatedValue]) {
|
||||
return DEFAULT_THEME;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {_t} from "./languageHandler";
|
||||
|
||||
export const DEFAULT_THEME = "light";
|
||||
import Tinter from "./Tinter";
|
||||
import SettingsStore from "./settings/SettingsStore";
|
||||
|
||||
export function enumerateThemes() {
|
||||
const BUILTIN_THEMES = {
|
||||
"light": _t("Light theme"),
|
||||
"dark": _t("Dark theme"),
|
||||
};
|
||||
const customThemes = SettingsStore.getValue("custom_themes");
|
||||
const customThemeNames = {};
|
||||
for (const {name} of customThemes) {
|
||||
customThemeNames[`custom-${name}`] = name;
|
||||
}
|
||||
console.log("customThemeNames", customThemeNames);
|
||||
return Object.assign({}, customThemeNames, BUILTIN_THEMES);
|
||||
}
|
||||
|
||||
function setCustomThemeVars(themeName) {
|
||||
// set css variables
|
||||
const customThemes = SettingsStore.getValue("custom_themes");
|
||||
if (!customThemes) {
|
||||
throw new Error(`No custom themes set, can't set custom theme "${themeName}"`);
|
||||
}
|
||||
const customTheme = customThemes.find(t => t.name === themeName);
|
||||
if (!customTheme) {
|
||||
const knownNames = customThemes.map(t => t.name).join(", ");
|
||||
throw new Error(`Can't find custom theme "${themeName}", only know ${knownNames}`);
|
||||
}
|
||||
const {style} = document.body;
|
||||
if (customTheme.colors) {
|
||||
for (const [name, hexColor] of Object.entries(customTheme.colors)) {
|
||||
style.setProperty(`--${name}`, hexColor);
|
||||
// uses #rrggbbaa to define the color with alpha values at 0% and 50%
|
||||
style.setProperty(`--${name}-0pct`, hexColor + "00");
|
||||
style.setProperty(`--${name}-50pct`, hexColor + "7F");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever someone changes the theme
|
||||
*
|
||||
* @param {string} theme new theme
|
||||
*/
|
||||
export function setTheme(theme) {
|
||||
if (!theme) {
|
||||
theme = SettingsStore.getValue("theme");
|
||||
}
|
||||
let stylesheetName = theme;
|
||||
if (theme.startsWith("custom-")) {
|
||||
stylesheetName = "light-custom";
|
||||
const themeName = theme.substr(7);
|
||||
setCustomThemeVars(themeName);
|
||||
}
|
||||
|
||||
// look for the stylesheet elements.
|
||||
// styleElements is a map from style name to HTMLLinkElement.
|
||||
const styleElements = Object.create(null);
|
||||
let a;
|
||||
for (let i = 0; (a = document.getElementsByTagName("link")[i]); i++) {
|
||||
const href = a.getAttribute("href");
|
||||
// shouldn't we be using the 'title' tag rather than the href?
|
||||
const match = href.match(/^bundles\/.*\/theme-(.*)\.css$/);
|
||||
if (match) {
|
||||
styleElements[match[1]] = a;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(stylesheetName in styleElements)) {
|
||||
throw new Error("Unknown theme " + stylesheetName);
|
||||
}
|
||||
|
||||
// disable all of them first, then enable the one we want. Chrome only
|
||||
// bothers to do an update on a true->false transition, so this ensures
|
||||
// that we get exactly one update, at the right time.
|
||||
//
|
||||
// ^ This comment was true when we used to use alternative stylesheets
|
||||
// for the CSS. Nowadays we just set them all as disabled in index.html
|
||||
// and enable them as needed. It might be cleaner to disable them all
|
||||
// at the same time to prevent loading two themes simultaneously and
|
||||
// having them interact badly... but this causes a flash of unstyled app
|
||||
// which is even uglier. So we don't.
|
||||
|
||||
styleElements[stylesheetName].disabled = false;
|
||||
|
||||
const switchTheme = function() {
|
||||
// we re-enable our theme here just in case we raced with another
|
||||
// theme set request as per https://github.com/vector-im/riot-web/issues/5601.
|
||||
// We could alternatively lock or similar to stop the race, but
|
||||
// this is probably good enough for now.
|
||||
styleElements[stylesheetName].disabled = false;
|
||||
Object.values(styleElements).forEach((a) => {
|
||||
if (a == styleElements[stylesheetName]) return;
|
||||
a.disabled = true;
|
||||
});
|
||||
Tinter.setTheme(theme);
|
||||
};
|
||||
|
||||
// turns out that Firefox preloads the CSS for link elements with
|
||||
// the disabled attribute, but Chrome doesn't.
|
||||
|
||||
let cssLoaded = false;
|
||||
|
||||
styleElements[stylesheetName].onload = () => {
|
||||
switchTheme();
|
||||
};
|
||||
|
||||
for (let i = 0; i < document.styleSheets.length; i++) {
|
||||
const ss = document.styleSheets[i];
|
||||
if (ss && ss.href === styleElements[stylesheetName].href) {
|
||||
cssLoaded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cssLoaded) {
|
||||
styleElements[stylesheetName].onload = undefined;
|
||||
switchTheme();
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {_td} from "./languageHandler";
|
||||
|
||||
export const DEFAULT_THEME = "light";
|
||||
|
||||
export const THEMES = {
|
||||
"light": _td("Light theme"),
|
||||
"dark": _td("Dark theme"),
|
||||
"light-custom": _td("Custom theme (light)"),
|
||||
};
|
Loading…
Reference in New Issue