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

Make EC widget theme reactive - Update widget url when the theme changes #12295

Merged
merged 17 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions src/components/views/elements/AppTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import { WidgetMessagingStore } from "../../../stores/widgets/WidgetMessagingSto
import { SdkContextClass } from "../../../contexts/SDKContext";
import { ModuleRunner } from "../../../modules/ModuleRunner";
import { parseUrl } from "../../../utils/UrlUtils";
import ThemeWatcher from "../../../settings/watchers/ThemeWatcher";

interface IProps {
app: IWidget | IApp;
Expand Down Expand Up @@ -115,6 +116,7 @@ interface IState {
menuDisplayed: boolean;
requiresClient: boolean;
hasContextMenuOptions: boolean;
widgetUrl?: string;
}

export default class AppTile extends React.Component<IProps, IState> {
Expand All @@ -140,7 +142,7 @@ export default class AppTile extends React.Component<IProps, IState> {
private sgWidget: StopGapWidget | null;
private dispatcherRef?: string;
private unmounted = false;

private themeWatcher = new ThemeWatcher();
public constructor(props: IProps, context: ContextType<typeof MatrixClientContext>) {
super(props);
this.context = context; // XXX: workaround for lack of `declare` support on `public context!:` definition
Expand Down Expand Up @@ -266,6 +268,7 @@ export default class AppTile extends React.Component<IProps, IState> {
!newProps.userWidget,
newProps.onDeleteClick,
),
widgetUrl: this.sgWidget?.embedUrl,
};
}

Expand Down Expand Up @@ -352,14 +355,18 @@ export default class AppTile extends React.Component<IProps, IState> {
}

private setupSgListeners(): void {
this.themeWatcher.on("themeChange", this.onThemeChanged);
this.themeWatcher.start();
this.sgWidget?.on("preparing", this.onWidgetPreparing);
this.sgWidget?.on("error:preparing", this.updateRequiresClient);
// emits when the capabilities have been set up or changed
this.sgWidget?.on("capabilitiesNotified", this.updateRequiresClient);
}

private stopSgListeners(): void {
this.themeWatcher.stop();
if (!this.sgWidget) return;
this.themeWatcher.off("themeChange", this.onThemeChanged);
this.sgWidget.off("preparing", this.onWidgetPreparing);
this.sgWidget.off("error:preparing", this.updateRequiresClient);
this.sgWidget.off("capabilitiesNotified", this.updateRequiresClient);
Expand Down Expand Up @@ -457,6 +464,13 @@ export default class AppTile extends React.Component<IProps, IState> {
});
};

private onThemeChanged = (): void => {
// Regenerate widget url when the theme changes
// this updates the url from e.g. `theme=light` to `theme=dark`
// the widget should react to this accordingly.
this.setState({ widgetUrl: this.sgWidget?.embedUrl });
};

private onAction = (payload: ActionPayload): void => {
switch (payload.action) {
case "m.sticker":
Expand Down Expand Up @@ -549,9 +563,9 @@ export default class AppTile extends React.Component<IProps, IState> {
this.resetWidget(this.props);
this.startMessaging();

if (this.iframe && this.sgWidget) {
if (this.iframe && this.state.widgetUrl) {
// Reload iframe
this.iframe.src = this.sgWidget.embedUrl;
this.iframe.src = this.state.widgetUrl;
}
});
}
Expand Down Expand Up @@ -649,7 +663,7 @@ export default class AppTile extends React.Component<IProps, IState> {
<AppPermission
roomId={this.props.room.roomId}
creatorUserId={this.props.creatorUserId}
url={this.sgWidget.embedUrl}
url={this.state.widgetUrl}
isRoomEncrypted={isEncrypted}
onPermissionGranted={this.grantWidgetPermission}
/>
Expand Down Expand Up @@ -677,7 +691,7 @@ export default class AppTile extends React.Component<IProps, IState> {
title={widgetTitle}
allow={iframeFeatures}
ref={this.iframeRefChange}
src={this.sgWidget.embedUrl}
src={this.state.widgetUrl}
allowFullScreen={true}
sandbox={sandboxFlags}
/>
Expand Down
5 changes: 4 additions & 1 deletion src/settings/watchers/ThemeWatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ limitations under the License.
*/

import { logger } from "matrix-js-sdk/src/logger";
import EventEmitter from "events";

import SettingsStore from "../SettingsStore";
import dis from "../../dispatcher/dispatcher";
Expand All @@ -25,7 +26,7 @@ import { findHighContrastTheme, setTheme } from "../../theme";
import { ActionPayload } from "../../dispatcher/payloads";
import { SettingLevel } from "../SettingLevel";

export default class ThemeWatcher {
export default class ThemeWatcher extends EventEmitter {
toger5 marked this conversation as resolved.
Show resolved Hide resolved
private themeWatchRef: string | null;
private systemThemeWatchRef: string | null;
private dispatcherRef: string | null;
Expand All @@ -37,6 +38,7 @@ export default class ThemeWatcher {
private currentTheme: string;

public constructor() {
super();
this.themeWatchRef = null;
this.systemThemeWatchRef = null;
this.dispatcherRef = null;
Expand Down Expand Up @@ -86,6 +88,7 @@ export default class ThemeWatcher {
this.currentTheme = forceTheme === undefined ? this.getEffectiveTheme() : forceTheme;
if (oldTheme !== this.currentTheme) {
setTheme(this.currentTheme);
this.emit("themeChange", this.currentTheme);
}
}

Expand Down
10 changes: 8 additions & 2 deletions src/stores/widgets/StopGapWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,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";
Expand Down Expand Up @@ -162,6 +161,7 @@ export class StopGapWidget extends EventEmitter {
private readonly virtual: boolean;
private readUpToMap: { [roomId: string]: string } = {}; // room ID to event ID
private stickyPromise?: () => Promise<void>; // This promise will be called and needs to resolve before the widget will actually become sticky.
private themeWatcher = new ThemeWatcher();

public constructor(private appTileProps: IAppTileProps) {
super();
Expand Down Expand Up @@ -212,13 +212,19 @@ export class StopGapWidget extends EventEmitter {

private runUrlTemplate(opts = { asPopout: false }): string {
const fromCustomisation = WidgetVariableCustomisations?.provideVariables?.() ?? {};
let theme = this.themeWatcher.getEffectiveTheme();
if (theme.startsWith("custom-")) {
// simplify custom theme to only light/dark
const customTheme = getCustomTheme(theme.slice(7));
theme = customTheme.is_dark ? "dark" : "light";
}
const defaults: ITemplateParams = {
widgetRoomId: this.roomId,
currentUserId: this.client.getUserId()!,
userDisplayName: OwnProfileStore.instance.displayName ?? undefined,
userHttpAvatarUrl: OwnProfileStore.instance.getHttpAvatarUrl() ?? undefined,
clientId: ELEMENT_CLIENT_ID,
clientTheme: SettingsStore.getValue("theme"),
clientTheme: theme,
clientLanguage: getUserLanguage(),
deviceId: this.client.getDeviceId() ?? undefined,
baseUrl: this.client.baseUrl,
Expand Down
Loading