Merge 4d91e97f0f
into ebef0d353e
commit
2524b29912
|
@ -50,7 +50,7 @@ import ThemeController from "../../settings/controllers/ThemeController";
|
|||
import { startAnyRegistrationFlow } from "../../Registration";
|
||||
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||
import AutoDiscoveryUtils from "../../utils/AutoDiscoveryUtils";
|
||||
import ThemeWatcher from "../../settings/watchers/ThemeWatcher";
|
||||
import ThemeWatcher, { ThemeWatcherEvent } from "../../settings/watchers/ThemeWatcher";
|
||||
import { FontWatcher } from "../../settings/watchers/FontWatcher";
|
||||
import { storeRoomAliasInCache } from "../../RoomAliasCache";
|
||||
import ToastStore from "../../stores/ToastStore";
|
||||
|
@ -133,6 +133,7 @@ import { ConfirmSessionLockTheftView } from "./auth/ConfirmSessionLockTheftView"
|
|||
import { LoginSplashView } from "./auth/LoginSplashView";
|
||||
import { cleanUpDraftsIfRequired } from "../../DraftCleaner";
|
||||
import { InitialCryptoSetupStore } from "../../stores/InitialCryptoSetupStore";
|
||||
import { setTheme } from "../../theme";
|
||||
|
||||
// legacy export
|
||||
export { default as Views } from "../../Views";
|
||||
|
@ -465,6 +466,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
this.themeWatcher = new ThemeWatcher();
|
||||
this.fontWatcher = new FontWatcher();
|
||||
this.themeWatcher.start();
|
||||
this.themeWatcher.on(ThemeWatcherEvent.Change, setTheme);
|
||||
this.fontWatcher.start();
|
||||
|
||||
initSentry(SdkConfig.get("sentry"));
|
||||
|
@ -497,6 +499,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
public componentWillUnmount(): void {
|
||||
Lifecycle.stopMatrixClient();
|
||||
dis.unregister(this.dispatcherRef);
|
||||
this.themeWatcher?.off(ThemeWatcherEvent.Change, setTheme);
|
||||
this.themeWatcher?.stop();
|
||||
this.fontWatcher?.stop();
|
||||
UIStore.destroy();
|
||||
|
|
|
@ -33,7 +33,7 @@ import { OwnProfileStore } from "../../../stores/OwnProfileStore";
|
|||
import { arrayFastClone } from "../../../utils/arrays";
|
||||
import { ElementWidget } from "../../../stores/widgets/StopGapWidget";
|
||||
import { ELEMENT_CLIENT_ID } from "../../../identifiers";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import ThemeWatcher, { ThemeWatcherEvent } from "../../../settings/watchers/ThemeWatcher";
|
||||
|
||||
interface IProps {
|
||||
widgetDefinition: IModalWidgetOpenRequestData;
|
||||
|
@ -54,6 +54,7 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
|
|||
private readonly widget: Widget;
|
||||
private readonly possibleButtons: ModalButtonID[];
|
||||
private appFrame: React.RefObject<HTMLIFrameElement> = React.createRef();
|
||||
private readonly themeWatcher = new ThemeWatcher();
|
||||
|
||||
public state: IState = {
|
||||
disabledButtonIds: (this.props.widgetDefinition.buttons || []).filter((b) => b.disabled).map((b) => b.id),
|
||||
|
@ -77,6 +78,8 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
|
|||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
this.themeWatcher.off(ThemeWatcherEvent.Change, this.onThemeChange);
|
||||
this.themeWatcher.stop();
|
||||
if (!this.state.messaging) return;
|
||||
this.state.messaging.off("ready", this.onReady);
|
||||
this.state.messaging.off(`action:${WidgetApiFromWidgetAction.CloseModalWidget}`, this.onWidgetClose);
|
||||
|
@ -84,6 +87,10 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
|
|||
}
|
||||
|
||||
private onReady = (): void => {
|
||||
this.themeWatcher.start();
|
||||
this.themeWatcher.on(ThemeWatcherEvent.Change, this.onThemeChange);
|
||||
// Theme may have changed while messaging was starting
|
||||
this.onThemeChange(this.themeWatcher.getEffectiveTheme());
|
||||
this.state.messaging?.sendWidgetConfig(this.props.widgetDefinition);
|
||||
};
|
||||
|
||||
|
@ -94,6 +101,10 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
|
|||
this.state.messaging.on(`action:${WidgetApiFromWidgetAction.SetModalButtonEnabled}`, this.onButtonEnableToggle);
|
||||
};
|
||||
|
||||
private onThemeChange = (theme: string): void => {
|
||||
this.state.messaging?.updateTheme({ name: theme });
|
||||
};
|
||||
|
||||
private onWidgetClose = (ev: CustomEvent<IModalWidgetCloseRequest>): void => {
|
||||
this.props.onFinished(true, ev.detail.data);
|
||||
};
|
||||
|
@ -127,7 +138,7 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
|
|||
userDisplayName: OwnProfileStore.instance.displayName ?? undefined,
|
||||
userHttpAvatarUrl: OwnProfileStore.instance.getHttpAvatarUrl() ?? undefined,
|
||||
clientId: ELEMENT_CLIENT_ID,
|
||||
clientTheme: SettingsStore.getValue("theme"),
|
||||
clientTheme: this.themeWatcher.getEffectiveTheme(),
|
||||
clientLanguage: getUserLanguage(),
|
||||
baseUrl: MatrixClientPeg.safeGet().baseUrl,
|
||||
});
|
||||
|
|
|
@ -8,16 +8,25 @@ Please see LICENSE files in the repository root for full details.
|
|||
*/
|
||||
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { TypedEventEmitter } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import SettingsStore from "../SettingsStore";
|
||||
import dis from "../../dispatcher/dispatcher";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import ThemeController from "../controllers/ThemeController";
|
||||
import { findHighContrastTheme, setTheme } from "../../theme";
|
||||
import { findHighContrastTheme } from "../../theme";
|
||||
import { ActionPayload } from "../../dispatcher/payloads";
|
||||
import { SettingLevel } from "../SettingLevel";
|
||||
|
||||
export default class ThemeWatcher {
|
||||
export enum ThemeWatcherEvent {
|
||||
Change = "change",
|
||||
}
|
||||
|
||||
interface ThemeWatcherEventHandlerMap {
|
||||
[ThemeWatcherEvent.Change]: (theme: string) => void;
|
||||
}
|
||||
|
||||
export default class ThemeWatcher extends TypedEventEmitter<ThemeWatcherEvent, ThemeWatcherEventHandlerMap> {
|
||||
private themeWatchRef?: string;
|
||||
private systemThemeWatchRef?: string;
|
||||
private dispatcherRef?: string;
|
||||
|
@ -29,6 +38,7 @@ export default class ThemeWatcher {
|
|||
private currentTheme: string;
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
// 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 = (<any>global).matchMedia("(prefers-color-scheme: dark)");
|
||||
|
@ -72,9 +82,7 @@ export default class ThemeWatcher {
|
|||
public recheck(forceTheme?: string): void {
|
||||
const oldTheme = this.currentTheme;
|
||||
this.currentTheme = forceTheme === undefined ? this.getEffectiveTheme() : forceTheme;
|
||||
if (oldTheme !== this.currentTheme) {
|
||||
setTheme(this.currentTheme);
|
||||
}
|
||||
if (oldTheme !== this.currentTheme) this.emit(ThemeWatcherEvent.Change, this.currentTheme);
|
||||
}
|
||||
|
||||
public getEffectiveTheme(): string {
|
||||
|
|
|
@ -37,7 +37,6 @@ import { MatrixClientPeg } from "../../MatrixClientPeg";
|
|||
import { OwnProfileStore } from "../OwnProfileStore";
|
||||
import WidgetUtils from "../../utils/WidgetUtils";
|
||||
import { IntegrationManagers } from "../../integrations/IntegrationManagers";
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import { WidgetType } from "../../widgets/WidgetType";
|
||||
import ActiveWidgetStore from "../ActiveWidgetStore";
|
||||
import { objectShallowClone } from "../../utils/objects";
|
||||
|
@ -46,7 +45,7 @@ import { Action } from "../../dispatcher/actions";
|
|||
import { ElementWidgetActions, IHangupCallApiRequest, IViewRoomApiRequest } from "./ElementWidgetActions";
|
||||
import { ModalWidgetStore } from "../ModalWidgetStore";
|
||||
import { IApp, isAppWidget } from "../WidgetStore";
|
||||
import ThemeWatcher from "../../settings/watchers/ThemeWatcher";
|
||||
import ThemeWatcher, { ThemeWatcherEvent } from "../../settings/watchers/ThemeWatcher";
|
||||
import { getCustomTheme } from "../../theme";
|
||||
import { ElementWidgetCapabilities } from "./ElementWidgetCapabilities";
|
||||
import { ELEMENT_CLIENT_ID } from "../../identifiers";
|
||||
|
@ -153,6 +152,7 @@ export class StopGapWidget extends EventEmitter {
|
|||
private roomId?: string;
|
||||
private kind: WidgetKind;
|
||||
private readonly virtual: boolean;
|
||||
private readonly themeWatcher = new ThemeWatcher();
|
||||
private readUpToMap: { [roomId: string]: string } = {}; // room ID to event ID
|
||||
// This promise will be called and needs to resolve before the widget will actually become sticky.
|
||||
private stickyPromise?: () => Promise<void>;
|
||||
|
@ -214,7 +214,7 @@ export class StopGapWidget extends EventEmitter {
|
|||
userDisplayName: OwnProfileStore.instance.displayName ?? undefined,
|
||||
userHttpAvatarUrl: OwnProfileStore.instance.getHttpAvatarUrl() ?? undefined,
|
||||
clientId: ELEMENT_CLIENT_ID,
|
||||
clientTheme: SettingsStore.getValue("theme"),
|
||||
clientTheme: this.themeWatcher.getEffectiveTheme(),
|
||||
clientLanguage: getUserLanguage(),
|
||||
deviceId: this.client.getDeviceId() ?? undefined,
|
||||
baseUrl: this.client.baseUrl,
|
||||
|
@ -246,6 +246,10 @@ export class StopGapWidget extends EventEmitter {
|
|||
return !!this.messaging;
|
||||
}
|
||||
|
||||
private onThemeChange = (theme: string): void => {
|
||||
this.messaging?.updateTheme({ name: theme });
|
||||
};
|
||||
|
||||
private onOpenModal = async (ev: CustomEvent<IModalWidgetOpenRequest>): Promise<void> => {
|
||||
ev.preventDefault();
|
||||
if (ModalWidgetStore.instance.canOpenModalWidget()) {
|
||||
|
@ -278,9 +282,14 @@ export class StopGapWidget extends EventEmitter {
|
|||
this.messaging = new ClientWidgetApi(this.mockWidget, iframe, driver);
|
||||
this.messaging.on("preparing", () => this.emit("preparing"));
|
||||
this.messaging.on("error:preparing", (err: unknown) => this.emit("error:preparing", err));
|
||||
this.messaging.on("ready", () => {
|
||||
this.messaging.once("ready", () => {
|
||||
WidgetMessagingStore.instance.storeMessaging(this.mockWidget, this.roomId, this.messaging!);
|
||||
this.emit("ready");
|
||||
|
||||
this.themeWatcher.start();
|
||||
this.themeWatcher.on(ThemeWatcherEvent.Change, this.onThemeChange);
|
||||
// Theme may have changed while messaging was starting
|
||||
this.onThemeChange(this.themeWatcher.getEffectiveTheme());
|
||||
});
|
||||
this.messaging.on("capabilitiesNotified", () => this.emit("capabilitiesNotified"));
|
||||
this.messaging.on(`action:${WidgetApiFromWidgetAction.OpenModalWidget}`, this.onOpenModal);
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { fireEvent, render } from "jest-matrix-react";
|
||||
import { ClientWidgetApi, MatrixWidgetType } from "matrix-widget-api";
|
||||
import React from "react";
|
||||
import { TooltipProvider } from "@vector-im/compound-web";
|
||||
import { mocked } from "jest-mock";
|
||||
import { findLast, last } from "lodash";
|
||||
|
||||
import ModalWidgetDialog from "../../../../src/components/views/dialogs/ModalWidgetDialog";
|
||||
import { stubClient } from "../../../test-utils";
|
||||
import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
|
||||
import { Action } from "../../../../src/dispatcher/actions";
|
||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||
|
||||
jest.mock("matrix-widget-api", () => ({
|
||||
...jest.requireActual("matrix-widget-api"),
|
||||
ClientWidgetApi: (jest.createMockFromModule("matrix-widget-api") as any).ClientWidgetApi,
|
||||
}));
|
||||
|
||||
describe("ModalWidgetDialog", () => {
|
||||
it("informs the widget of theme changes", () => {
|
||||
stubClient();
|
||||
let theme = "light";
|
||||
const settingsSpy = jest
|
||||
.spyOn(SettingsStore, "getValue")
|
||||
.mockImplementation((name) => (name === "theme" ? theme : undefined));
|
||||
try {
|
||||
render(
|
||||
<TooltipProvider>
|
||||
<ModalWidgetDialog
|
||||
widgetDefinition={{ type: MatrixWidgetType.Custom, url: "https://example.org" }}
|
||||
sourceWidgetId=""
|
||||
onFinished={() => {}}
|
||||
/>
|
||||
</TooltipProvider>,
|
||||
);
|
||||
// Indicate that the widget is loaded and ready
|
||||
fireEvent.load(document.getElementsByTagName("iframe").item(0)!);
|
||||
const messaging = mocked(last(mocked(ClientWidgetApi).mock.instances)!);
|
||||
findLast(messaging.once.mock.calls, ([eventName]) => eventName === "ready")![1]();
|
||||
|
||||
// Now change the theme
|
||||
theme = "dark";
|
||||
defaultDispatcher.dispatch({ action: Action.RecheckTheme }, true);
|
||||
expect(messaging.updateTheme).toHaveBeenLastCalledWith({ name: "dark" });
|
||||
} finally {
|
||||
settingsSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
|
@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
*/
|
||||
|
||||
import { mocked, MockedObject } from "jest-mock";
|
||||
import { last } from "lodash";
|
||||
import { findLast, last } from "lodash";
|
||||
import {
|
||||
MatrixEvent,
|
||||
MatrixClient,
|
||||
|
@ -24,8 +24,13 @@ import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
|||
import { StopGapWidget } from "../../../../src/stores/widgets/StopGapWidget";
|
||||
import ActiveWidgetStore from "../../../../src/stores/ActiveWidgetStore";
|
||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||
import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
|
||||
import { Action } from "../../../../src/dispatcher/actions";
|
||||
|
||||
jest.mock("matrix-widget-api/lib/ClientWidgetApi");
|
||||
jest.mock("matrix-widget-api", () => ({
|
||||
...jest.requireActual("matrix-widget-api"),
|
||||
ClientWidgetApi: (jest.createMockFromModule("matrix-widget-api") as any).ClientWidgetApi,
|
||||
}));
|
||||
|
||||
describe("StopGapWidget", () => {
|
||||
let client: MockedObject<MatrixClient>;
|
||||
|
@ -84,6 +89,25 @@ describe("StopGapWidget", () => {
|
|||
expect(messaging.feedToDevice).toHaveBeenCalledWith(event.getEffectiveEvent(), false);
|
||||
});
|
||||
|
||||
it("informs widget of theme changes", () => {
|
||||
let theme = "light";
|
||||
const settingsSpy = jest
|
||||
.spyOn(SettingsStore, "getValue")
|
||||
.mockImplementation((name) => (name === "theme" ? theme : undefined));
|
||||
try {
|
||||
// Indicate that the widget is ready
|
||||
findLast(messaging.once.mock.calls, ([eventName]) => eventName === "ready")![1]();
|
||||
|
||||
// Now change the theme
|
||||
theme = "dark";
|
||||
defaultDispatcher.dispatch({ action: Action.RecheckTheme }, true);
|
||||
expect(messaging.updateTheme).toHaveBeenLastCalledWith({ name: "dark" });
|
||||
} finally {
|
||||
console.log("TEST OVER");
|
||||
settingsSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
describe("feed event", () => {
|
||||
let event1: MatrixEvent;
|
||||
let event2: MatrixEvent;
|
||||
|
|
|
@ -8678,9 +8678,9 @@ matrix-web-i18n@^3.2.1:
|
|||
walk "^2.3.15"
|
||||
|
||||
matrix-widget-api@^1.10.0:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-1.10.0.tgz#d31ea073a5871a1fb1a511ef900b0c125a37bf55"
|
||||
integrity sha512-rkAJ29briYV7TJnfBVLVSKtpeBrBju15JZFSDP6wj8YdbCu1bdmlplJayQ+vYaw1x4fzI49Q+Nz3E85s46sRDw==
|
||||
version "1.11.0"
|
||||
resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-1.11.0.tgz#2f548b11a7c0df789d5d4fdb5cc9ef7af8aef3da"
|
||||
integrity sha512-ED/9hrJqDWVLeED0g1uJnYRhINh3ZTquwurdM+Hc8wLVJIQ8G/r7A7z74NC+8bBIHQ1Jo7i1Uq5CoJp/TzFYrA==
|
||||
dependencies:
|
||||
"@types/events" "^3.0.0"
|
||||
events "^3.2.0"
|
||||
|
|
Loading…
Reference in New Issue