Make EC widget theme reactive - Update widget url when the theme changes (#12295)
* update widget url when the theme changes Signed-off-by: Timo K <toger5@hotmail.de> * quick "make it EC specific" workaround proposal. Signed-off-by: Timo K <toger5@hotmail.de> * use `matches` Signed-off-by: Timo K <toger5@hotmail.de> * test coverage Signed-off-by: Timo K <toger5@hotmail.de> * more test coverage Signed-off-by: Timo K <toger5@hotmail.de> * fix jest Signed-off-by: Timo K <toger5@hotmail.de> * add tests for theme changes Signed-off-by: Timo K <toger5@hotmail.de> * update snapshots Signed-off-by: Timo K <toger5@hotmail.de> * test for theme update with non ec widget Signed-off-by: Timo K <toger5@hotmail.de> * add dark custom theme widget url Signed-off-by: Timo K <toger5@hotmail.de> * trigger conditions for theme cleanup Signed-off-by: Timo K <toger5@hotmail.de> * update tests using testId Signed-off-by: Timo K <toger5@hotmail.de> * use typed event emitter for theme watcher Signed-off-by: Timo K <toger5@hotmail.de> * simplify condition Signed-off-by: Timo K <toger5@hotmail.de> --------- Signed-off-by: Timo K <toger5@hotmail.de>
This commit is contained in:
parent
80c4c3c28c
commit
c42562ef39
7 changed files with 172 additions and 23 deletions
|
@ -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, { ThemeWatcherEvents } from "../../../settings/watchers/ThemeWatcher";
|
||||
|
||||
interface IProps {
|
||||
app: IWidget | IApp;
|
||||
|
@ -115,6 +116,7 @@ interface IState {
|
|||
menuDisplayed: boolean;
|
||||
requiresClient: boolean;
|
||||
hasContextMenuOptions: boolean;
|
||||
widgetUrl?: string;
|
||||
}
|
||||
|
||||
export default class AppTile extends React.Component<IProps, IState> {
|
||||
|
@ -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
|
||||
|
@ -267,6 +269,7 @@ export default class AppTile extends React.Component<IProps, IState> {
|
|||
!newProps.userWidget,
|
||||
newProps.onDeleteClick,
|
||||
),
|
||||
widgetUrl: this.sgWidget?.embedUrl,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -352,6 +355,8 @@ export default class AppTile extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
private setupSgListeners(): void {
|
||||
this.themeWatcher.on(ThemeWatcherEvents.ThemeChange, this.onThemeChanged);
|
||||
this.themeWatcher.start();
|
||||
this.sgWidget?.on("ready", this.onWidgetReady);
|
||||
this.sgWidget?.on("error:preparing", this.updateRequiresClient);
|
||||
// emits when the capabilities have been set up or changed
|
||||
|
@ -359,7 +364,9 @@ export default class AppTile extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
private stopSgListeners(): void {
|
||||
this.themeWatcher.stop();
|
||||
if (!this.sgWidget) return;
|
||||
this.themeWatcher.off(ThemeWatcherEvents.ThemeChange, this.onThemeChanged);
|
||||
this.sgWidget?.off("ready", this.onWidgetReady);
|
||||
this.sgWidget.off("error:preparing", this.updateRequiresClient);
|
||||
this.sgWidget.off("capabilitiesNotified", this.updateRequiresClient);
|
||||
|
@ -382,6 +389,7 @@ export default class AppTile extends React.Component<IProps, IState> {
|
|||
private startWidget(): void {
|
||||
this.sgWidget?.prepare().then(() => {
|
||||
if (this.unmounted) return;
|
||||
if (!this.state.initialising) return;
|
||||
this.setState({ initialising: false });
|
||||
});
|
||||
}
|
||||
|
@ -456,6 +464,17 @@ 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`
|
||||
// We only do this with EC widgets where the theme prop is in the hash. If the widget puts the
|
||||
// theme template variable outside the url hash this would cause a (IFrame) page reload on every theme change.
|
||||
if (WidgetType.CALL.matches(this.props.app.type)) this.setState({ widgetUrl: this.sgWidget?.embedUrl });
|
||||
|
||||
// TODO: This is a stop gap solution to responsively update the theme of the widget.
|
||||
// A new action should be introduced and the widget driver should be called here, so it informs the widget. (or connect to this by itself)
|
||||
};
|
||||
|
||||
private onAction = (payload: ActionPayload): void => {
|
||||
switch (payload.action) {
|
||||
case "m.sticker":
|
||||
|
@ -548,9 +567,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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -619,7 +638,7 @@ export default class AppTile extends React.Component<IProps, IState> {
|
|||
"mx_AppTileBody--mini": this.props.miniMode,
|
||||
"mx_AppTileBody--loading": this.state.loading,
|
||||
// We don't want mx_AppTileBody (rounded corners) for call widgets
|
||||
"mx_AppTileBody--call": this.props.app.type === WidgetType.CALL.preferred,
|
||||
"mx_AppTileBody--call": WidgetType.CALL.matches(this.props.app.type),
|
||||
});
|
||||
const appTileBodyStyles: CSSProperties = {};
|
||||
if (this.props.pointerEvents) {
|
||||
|
@ -648,7 +667,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}
|
||||
/>
|
||||
|
@ -676,7 +695,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}
|
||||
/>
|
||||
|
@ -699,7 +718,12 @@ export default class AppTile extends React.Component<IProps, IState> {
|
|||
const zIndexAboveOtherPersistentElements = 101;
|
||||
|
||||
appTileBody = (
|
||||
<div className="mx_AppTile_persistedWrapper">
|
||||
<div
|
||||
className="mx_AppTile_persistedWrapper"
|
||||
// We store the widget url to make it possible to test the value of the widgetUrl. since the iframe itself wont be here. (PersistedElement are in a different dom tree)
|
||||
data-test-widget-url={this.state.widgetUrl}
|
||||
data-testid="widget-app-tile"
|
||||
>
|
||||
<PersistedElement
|
||||
zIndex={this.props.miniMode ? zIndexAboveOtherPersistentElements : 9}
|
||||
persistKey={this.persistKey}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue