Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Merge upstream
Browse files Browse the repository at this point in the history
Signed-off-by: Elsie Hupp <[email protected]>
  • Loading branch information
elsiehupp committed Nov 10, 2021
2 parents 74376f9 + 0db7d1e commit 73581ed
Show file tree
Hide file tree
Showing 9 changed files with 484 additions and 432 deletions.
5 changes: 0 additions & 5 deletions src/components/structures/MatrixChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -891,7 +891,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}

this.setStateForNewView(newState);
ThemeController.isLogin = true;
this.themeWatcher.recheck();
this.notifyNewScreen('register');
}
Expand Down Expand Up @@ -1020,7 +1019,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
view: Views.WELCOME,
});
this.notifyNewScreen('welcome');
ThemeController.isLogin = true;
this.themeWatcher.recheck();
}

Expand All @@ -1030,7 +1028,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
...otherState,
});
this.notifyNewScreen('login');
ThemeController.isLogin = true;
this.themeWatcher.recheck();
}

Expand All @@ -1043,7 +1040,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
});
this.setPage(PageType.HomePage);
this.notifyNewScreen('home');
ThemeController.isLogin = false;
this.themeWatcher.recheck();
}

Expand Down Expand Up @@ -1301,7 +1297,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
* Called when a new logged in session has started
*/
private async onLoggedIn() {
ThemeController.isLogin = false;
this.themeWatcher.recheck();
this.setStateForNewView({ view: Views.LOGGED_IN });
// If a specific screen is set to be shown after login, show that above
Expand Down
39 changes: 8 additions & 31 deletions src/components/structures/UserMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ import FeedbackDialog from "../views/dialogs/FeedbackDialog";
import Modal from "../../Modal";
import LogoutDialog from "../views/dialogs/LogoutDialog";
import SettingsStore from "../../settings/SettingsStore";
import { findHighContrastTheme, getCustomTheme, isHighContrastTheme } from "../../theme";
import { Theme } from '../../Theme';
import ThemeWatcher from "../../settings/watchers/ThemeWatcher";
import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
import SdkConfig from "../../SdkConfig";
import { getHomePageUrl } from "../../utils/pages";
Expand Down Expand Up @@ -87,8 +88,8 @@ export default class UserMenu extends React.Component<IProps, IState> {

this.state = {
contextMenuPosition: null,
isDarkTheme: this.isUserOnDarkTheme(),
isHighContrast: this.isUserOnHighContrastTheme(),
isDarkTheme: ThemeWatcher.isDarkTheme(),
isHighContrast: ThemeWatcher.isHighContrast(),
pendingRoomJoin: new Set<string>(),
};

Expand Down Expand Up @@ -132,30 +133,6 @@ export default class UserMenu extends React.Component<IProps, IState> {
this.forceUpdate(); // we don't have anything useful in state to update
};

private isUserOnDarkTheme(): boolean {
if (SettingsStore.getValue("use_system_theme")) {
return window.matchMedia("(prefers-color-scheme: dark)").matches;
} else {
const theme = SettingsStore.getValue("theme");
if (theme.startsWith("custom-")) {
return getCustomTheme(theme.substring("custom-".length)).is_dark;
}
return theme === "dark";
}
}

private isUserOnHighContrastTheme(): boolean {
if (SettingsStore.getValue("use_system_theme")) {
return window.matchMedia("(prefers-contrast: more)").matches;
} else {
const theme = SettingsStore.getValue("theme");
if (theme.startsWith("custom-")) {
return false;
}
return isHighContrastTheme(theme);
}
}

private onProfileUpdate = async () => {
// the store triggered an update, so force a layout update. We don't
// have any state to store here for that to magically happen.
Expand All @@ -169,8 +146,8 @@ export default class UserMenu extends React.Component<IProps, IState> {
private onThemeChanged = () => {
this.setState(
{
isDarkTheme: this.isUserOnDarkTheme(),
isHighContrast: this.isUserOnHighContrastTheme(),
isDarkTheme: ThemeWatcher.isDarkTheme(),
isHighContrast: ThemeWatcher.isHighContrast(),
});
};

Expand Down Expand Up @@ -239,9 +216,9 @@ export default class UserMenu extends React.Component<IProps, IState> {
// Disable system theme matching if the user hits this button
SettingsStore.setValue("use_system_theme", null, SettingLevel.DEVICE, false);

let newTheme = this.state.isDarkTheme ? "light" : "dark";
let newTheme = this.state.isDarkTheme ? SettingsStore.getValue("light_theme") : SettingsStore.getValue("dark_theme");
if (this.state.isHighContrast) {
const hcTheme = findHighContrastTheme(newTheme);
const hcTheme = new Theme(newTheme).highContrast;
if (hcTheme) {
newTheme = hcTheme;
}
Expand Down
18 changes: 9 additions & 9 deletions src/components/views/settings/ThemeChoicePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ limitations under the License.
import React from 'react';
import { _t } from "../../../languageHandler";
import SettingsStore from "../../../settings/SettingsStore";
import { enumerateThemes, findHighContrastTheme, findNonHighContrastTheme, isHighContrastTheme } from "../../../theme";
import { Theme } from "../../../theme";
import ThemeWatcher from "../../../settings/watchers/ThemeWatcher";
import AccessibleButton from "../elements/AccessibleButton";
import dis from "../../../dispatcher/dispatcher";
Expand Down Expand Up @@ -162,13 +162,13 @@ export default class ThemeChoicePanel extends React.Component<IProps, IState> {
private renderHighContrastCheckbox(): React.ReactElement<HTMLDivElement> {
if (
!this.state.useSystemTheme && (
findHighContrastTheme(this.state.theme) ||
isHighContrastTheme(this.state.theme)
// Theme.findHighContrast(this.state.theme) ||
ThemeWatcher.isHighContrast()
)
) {
return <div>
<StyledCheckbox
checked={isHighContrastTheme(this.state.theme)}
checked={ThemeWatcher.isHighContrast()}
onChange={(e) => this.highContrastThemeChanged(e.target.checked)}
>
{ _t( "Use high contrast" ) }
Expand All @@ -178,21 +178,21 @@ export default class ThemeChoicePanel extends React.Component<IProps, IState> {
}

private highContrastThemeChanged(checked: boolean): void {
const theme = new Theme(this.state.theme)
let newTheme: string;
if (checked) {
newTheme = findHighContrastTheme(this.state.theme);
newTheme = theme.highContrast;
} else {
newTheme = findNonHighContrastTheme(this.state.theme);
newTheme = theme.nonHighContrast;
}
if (newTheme) {
this.onThemeChange(newTheme);
}
}

public render(): React.ReactElement<HTMLDivElement> {
const themeWatcher = new ThemeWatcher();
let systemThemeSection: JSX.Element;
if (themeWatcher.isSystemThemeSupported()) {
if (ThemeWatcher.supportsSystemTheme()) {
systemThemeSection = <div>
<StyledCheckbox
checked={this.state.useSystemTheme}
Expand Down Expand Up @@ -241,7 +241,7 @@ export default class ThemeChoicePanel extends React.Component<IProps, IState> {
// XXX: replace any type here
const themes = Object.entries<any>(enumerateThemes())
.map(p => ({ id: p[0], name: p[1] })) // convert pairs to objects for code readability
.filter(p => !isHighContrastTheme(p.id));
.filter(p => !ThemeWatcher.isHighContrast());
const builtInThemes = themes.filter(p => !p.id.startsWith("custom-"));
const customThemes = themes.filter(p => !builtInThemes.includes(p))
.sort((a, b) => compare(a.name, b.name));
Expand Down
8 changes: 8 additions & 0 deletions src/settings/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,14 @@ export const SETTINGS: {[setting: string]: ISetting} = {
default: "light",
controller: new ThemeController(),
},
"dark_theme": {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
default: "dark", // Must be "dark" for compatibility with, e.g., Jitsi
},
"light_theme": {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
default: "light", // Must be "light" for compatibility with, e.g., Jitsi
},
"custom_themes": {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
default: [],
Expand Down
112 changes: 107 additions & 5 deletions src/settings/controllers/ThemeController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { CustomTheme } from "../../Theme"
import SettingController from "./SettingController";
import { DEFAULT_THEME, enumerateThemes } from "../../theme";
import { SettingLevel } from "../SettingLevel";
import ThemeWatcher from "../watchers/ThemeWatcher"

export default class ThemeController extends SettingController {
public static isLogin = false;

public constructor() {
super()
}

public getValueOverride(
level: SettingLevel,
Expand All @@ -30,14 +34,112 @@ export default class ThemeController extends SettingController {
): any {
if (!calculatedValue) return null; // Don't override null themes

if (ThemeController.isLogin) return 'light';
const themes = ThemeWatcher.availableThemes;

const themes = enumerateThemes();
// Override in case some no longer supported theme is stored here
if (!themes[calculatedValue]) {
return DEFAULT_THEME;
return ThemeWatcher.currentTheme();
}

return null; // no override
}

/**
* Called whenever someone changes the theme
* Async function that returns once the theme has been set
* (ie. the CSS has been loaded)
*
* @param {string} theme new theme
*/
public static async setTheme(theme?: string): Promise<void> {
if (!theme) {
theme = ThemeWatcher.currentTheme();
}
ThemeController.clearCustomTheme();
let stylesheetName = theme;
if (theme.startsWith("custom-")) {
stylesheetName = new CustomTheme(theme.substr(7)).is_dark ? "dark-custom" : "light-custom";
}

// look for the stylesheet elements.
// styleElements is a map from style name to HTMLLinkElement.
const styleElements = Object.create(null);
const themes = Array.from(document.querySelectorAll('[data-mx-theme]'));
themes.forEach(theme => {
styleElements[theme.attributes['data-mx-theme'].value.toLowerCase()] = theme;
});

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;

return new Promise((resolve) => {
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/element-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: HTMLStyleElement) => {
if (a == styleElements[stylesheetName]) return;
a.disabled = true;
});
const bodyStyles = global.getComputedStyle(document.body);
if (bodyStyles.backgroundColor) {
const metaElement: HTMLMetaElement = document.querySelector('meta[name="theme-color"]');
metaElement.content = bodyStyles.backgroundColor;
}
resolve();
};

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

private static clearCustomTheme(): void {
// remove all css variables, we assume these are there because of the custom theme
const inlineStyleProps = Object.values(document.body.style);
for (const prop of inlineStyleProps) {
if (prop.startsWith("--")) {
document.body.style.removeProperty(prop);
}
}
const customFontFaceStyle = document.querySelector("head > style[title='custom-theme-font-faces']");
if (customFontFaceStyle) {
customFontFaceStyle.remove();
}
}
}
Loading

0 comments on commit 73581ed

Please sign in to comment.