1
0
mirror of https://github.com/matrix-org/matrix-react-sdk.git synced 2025-08-09 08:42:50 +03:00

Remove legacy consumers of the SDKContext in favour of HOCs

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski
2024-04-19 12:38:22 +01:00
parent cc7edade21
commit 0debad248b
8 changed files with 204 additions and 189 deletions

View File

@@ -58,7 +58,6 @@ import AudioFeedArrayForLegacyCall from "../views/voip/AudioFeedArrayForLegacyCa
import { OwnProfileStore } from "../../stores/OwnProfileStore"; import { OwnProfileStore } from "../../stores/OwnProfileStore";
import { UPDATE_EVENT } from "../../stores/AsyncStore"; import { UPDATE_EVENT } from "../../stores/AsyncStore";
import RoomView from "./RoomView"; import RoomView from "./RoomView";
import type { RoomView as RoomViewType } from "./RoomView";
import ToastContainer from "./ToastContainer"; import ToastContainer from "./ToastContainer";
import UserView from "./UserView"; import UserView from "./UserView";
import BackdropPanel from "./BackdropPanel"; import BackdropPanel from "./BackdropPanel";
@@ -132,7 +131,7 @@ class LoggedInView extends React.Component<IProps, IState> {
public static displayName = "LoggedInView"; public static displayName = "LoggedInView";
protected readonly _matrixClient: MatrixClient; protected readonly _matrixClient: MatrixClient;
protected readonly _roomView: React.RefObject<RoomViewType>; protected readonly _roomView: React.RefObject<React.ComponentRef<typeof RoomView>>;
protected readonly _resizeContainer: React.RefObject<HTMLDivElement>; protected readonly _resizeContainer: React.RefObject<HTMLDivElement>;
protected readonly resizeHandler: React.RefObject<HTMLDivElement>; protected readonly resizeHandler: React.RefObject<HTMLDivElement>;
protected layoutWatcherRef?: string; protected layoutWatcherRef?: string;

View File

@@ -17,7 +17,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { createRef, ReactElement, ReactNode, RefObject, useContext } from "react"; import React, { createRef, forwardRef, ReactElement, ReactNode, RefObject, useContext } from "react";
import classNames from "classnames"; import classNames from "classnames";
import { import {
IRecommendedVersion, IRecommendedVersion,
@@ -157,6 +157,7 @@ interface IRoomProps {
// Called with the credentials of a registered user (if they were a ROU that transitioned to PWLU) // Called with the credentials of a registered user (if they were a ROU that transitioned to PWLU)
onRegistered?(credentials: IMatrixClientCreds): void; onRegistered?(credentials: IMatrixClientCreds): void;
context: React.ContextType<typeof SDKContext>;
} }
// This defines the content of the mainSplit. // This defines the content of the mainSplit.
@@ -399,7 +400,7 @@ function LocalRoomCreateLoader(props: ILocalRoomCreateLoaderProps): ReactElement
); );
} }
export class RoomView extends React.Component<IRoomProps, IRoomState> { class RoomView extends React.Component<IRoomProps, IRoomState> {
private readonly askToJoinEnabled: boolean; private readonly askToJoinEnabled: boolean;
private readonly dispatcherRef: string; private readonly dispatcherRef: string;
private settingWatchers: string[]; private settingWatchers: string[];
@@ -412,19 +413,16 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private messagePanel: TimelinePanel | null = null; private messagePanel: TimelinePanel | null = null;
private roomViewBody = createRef<HTMLDivElement>(); private roomViewBody = createRef<HTMLDivElement>();
public static contextType = SDKContext; public constructor(props: IRoomProps) {
public context!: React.ContextType<typeof SDKContext>; super(props);
public constructor(props: IRoomProps, context: React.ContextType<typeof SDKContext>) {
super(props, context);
this.askToJoinEnabled = SettingsStore.getValue("feature_ask_to_join"); this.askToJoinEnabled = SettingsStore.getValue("feature_ask_to_join");
if (!context.client) { if (!props.context.client) {
throw new Error("Unable to create RoomView without MatrixClient"); throw new Error("Unable to create RoomView without MatrixClient");
} }
const llMembers = context.client.hasLazyLoadMembersEnabled(); const llMembers = props.context.client.hasLazyLoadMembersEnabled();
this.state = { this.state = {
roomId: undefined, roomId: undefined,
roomLoading: true, roomLoading: true,
@@ -458,7 +456,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
showJoinLeaves: true, showJoinLeaves: true,
showAvatarChanges: true, showAvatarChanges: true,
showDisplaynameChanges: true, showDisplaynameChanges: true,
matrixClientIsReady: context.client?.isInitialSyncComplete(), matrixClientIsReady: props.context.client?.isInitialSyncComplete(),
mainSplitContentType: MainSplitContentType.Timeline, mainSplitContentType: MainSplitContentType.Timeline,
timelineRenderingType: TimelineRenderingType.Room, timelineRenderingType: TimelineRenderingType.Room,
liveTimeline: undefined, liveTimeline: undefined,
@@ -470,25 +468,25 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}; };
this.dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
context.client.on(ClientEvent.Room, this.onRoom); props.context.client.on(ClientEvent.Room, this.onRoom);
context.client.on(RoomEvent.Timeline, this.onRoomTimeline); props.context.client.on(RoomEvent.Timeline, this.onRoomTimeline);
context.client.on(RoomEvent.TimelineReset, this.onRoomTimelineReset); props.context.client.on(RoomEvent.TimelineReset, this.onRoomTimelineReset);
context.client.on(RoomEvent.Name, this.onRoomName); props.context.client.on(RoomEvent.Name, this.onRoomName);
context.client.on(RoomStateEvent.Events, this.onRoomStateEvents); props.context.client.on(RoomStateEvent.Events, this.onRoomStateEvents);
context.client.on(RoomStateEvent.Update, this.onRoomStateUpdate); props.context.client.on(RoomStateEvent.Update, this.onRoomStateUpdate);
context.client.on(RoomEvent.MyMembership, this.onMyMembership); props.context.client.on(RoomEvent.MyMembership, this.onMyMembership);
context.client.on(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus); props.context.client.on(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus);
context.client.on(CryptoEvent.DeviceVerificationChanged, this.onDeviceVerificationChanged); props.context.client.on(CryptoEvent.DeviceVerificationChanged, this.onDeviceVerificationChanged);
context.client.on(CryptoEvent.UserTrustStatusChanged, this.onUserVerificationChanged); props.context.client.on(CryptoEvent.UserTrustStatusChanged, this.onUserVerificationChanged);
context.client.on(CryptoEvent.KeysChanged, this.onCrossSigningKeysChanged); props.context.client.on(CryptoEvent.KeysChanged, this.onCrossSigningKeysChanged);
context.client.on(MatrixEventEvent.Decrypted, this.onEventDecrypted); props.context.client.on(MatrixEventEvent.Decrypted, this.onEventDecrypted);
// Start listening for RoomViewStore updates // Start listening for RoomViewStore updates
context.roomViewStore.on(UPDATE_EVENT, this.onRoomViewStoreUpdate); props.context.roomViewStore.on(UPDATE_EVENT, this.onRoomViewStoreUpdate);
context.rightPanelStore.on(UPDATE_EVENT, this.onRightPanelStoreUpdate); props.context.rightPanelStore.on(UPDATE_EVENT, this.onRightPanelStoreUpdate);
WidgetEchoStore.on(UPDATE_EVENT, this.onWidgetEchoStoreUpdate); WidgetEchoStore.on(UPDATE_EVENT, this.onWidgetEchoStoreUpdate);
context.widgetStore.on(UPDATE_EVENT, this.onWidgetStoreUpdate); props.context.widgetStore.on(UPDATE_EVENT, this.onWidgetStoreUpdate);
CallStore.instance.on(CallStoreEvent.ActiveCalls, this.onActiveCalls); CallStore.instance.on(CallStoreEvent.ActiveCalls, this.onActiveCalls);
@@ -545,9 +543,9 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
action: "appsDrawer", action: "appsDrawer",
show: true, show: true,
}); });
if (this.context.widgetLayoutStore.hasMaximisedWidget(this.state.room)) { if (this.props.context.widgetLayoutStore.hasMaximisedWidget(this.state.room)) {
// Show chat in right panel when a widget is maximised // Show chat in right panel when a widget is maximised
this.context.rightPanelStore.setCard({ phase: RightPanelPhases.Timeline }); this.props.context.rightPanelStore.setCard({ phase: RightPanelPhases.Timeline });
} }
this.checkWidgets(this.state.room); this.checkWidgets(this.state.room);
}; };
@@ -559,15 +557,15 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
* This solves the issue if some people decide to start a conference and click the call button at the same time. * This solves the issue if some people decide to start a conference and click the call button at the same time.
*/ */
private doMaybeRemoveOwnJitsiWidget(): void { private doMaybeRemoveOwnJitsiWidget(): void {
if (!this.state.roomId || !this.state.room || !this.context.client) return; if (!this.state.roomId || !this.state.room || !this.props.context.client) return;
const apps = this.context.widgetStore.getApps(this.state.roomId); const apps = this.props.context.widgetStore.getApps(this.state.roomId);
const jitsiApps = apps.filter((app) => app.eventId && WidgetType.JITSI.matches(app.type)); const jitsiApps = apps.filter((app) => app.eventId && WidgetType.JITSI.matches(app.type));
// less than two Jitsi widgets → nothing to do // less than two Jitsi widgets → nothing to do
if (jitsiApps.length < 2) return; if (jitsiApps.length < 2) return;
const currentUserId = this.context.client.getSafeUserId(); const currentUserId = this.props.context.client.getSafeUserId();
const createdByCurrentUser = jitsiApps.find((apps) => apps.creatorUserId === currentUserId); const createdByCurrentUser = jitsiApps.find((apps) => apps.creatorUserId === currentUserId);
// no Jitsi widget from current user → nothing to do // no Jitsi widget from current user → nothing to do
@@ -598,13 +596,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
createdByCurrentUserTs - lastCreatedByOtherTs < PREVENT_MULTIPLE_JITSI_WITHIN createdByCurrentUserTs - lastCreatedByOtherTs < PREVENT_MULTIPLE_JITSI_WITHIN
) { ) {
// more than one Jitsi widget with the last one from the current user → remove it // more than one Jitsi widget with the last one from the current user → remove it
WidgetUtils.setRoomWidget(this.context.client, this.state.roomId, createdByCurrentUser.id); WidgetUtils.setRoomWidget(this.props.context.client, this.state.roomId, createdByCurrentUser.id);
} }
} }
private checkWidgets = (room: Room): void => { private checkWidgets = (room: Room): void => {
this.setState({ this.setState({
hasPinnedWidgets: this.context.widgetLayoutStore.hasPinnedWidgets(room), hasPinnedWidgets: this.props.context.widgetLayoutStore.hasPinnedWidgets(room),
mainSplitContentType: this.getMainSplitContentType(room), mainSplitContentType: this.getMainSplitContentType(room),
showApps: this.shouldShowApps(room), showApps: this.shouldShowApps(room),
}); });
@@ -612,12 +610,12 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private getMainSplitContentType = (room: Room): MainSplitContentType => { private getMainSplitContentType = (room: Room): MainSplitContentType => {
if ( if (
(SettingsStore.getValue("feature_group_calls") && this.context.roomViewStore.isViewingCall()) || (SettingsStore.getValue("feature_group_calls") && this.props.context.roomViewStore.isViewingCall()) ||
isVideoRoom(room) isVideoRoom(room)
) { ) {
return MainSplitContentType.Call; return MainSplitContentType.Call;
} }
if (this.context.widgetLayoutStore.hasMaximisedWidget(room)) { if (this.props.context.widgetLayoutStore.hasMaximisedWidget(room)) {
return MainSplitContentType.MaximisedWidget; return MainSplitContentType.MaximisedWidget;
} }
return MainSplitContentType.Timeline; return MainSplitContentType.Timeline;
@@ -628,8 +626,8 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
return; return;
} }
const roomLoadError = this.context.roomViewStore.getRoomLoadError() ?? undefined; const roomLoadError = this.props.context.roomViewStore.getRoomLoadError() ?? undefined;
if (!initial && !roomLoadError && this.state.roomId !== this.context.roomViewStore.getRoomId()) { if (!initial && !roomLoadError && this.state.roomId !== this.props.context.roomViewStore.getRoomId()) {
// RoomView explicitly does not support changing what room // RoomView explicitly does not support changing what room
// is being viewed: instead it should just be re-mounted when // is being viewed: instead it should just be re-mounted when
// switching rooms. Therefore, if the room ID changes, we // switching rooms. Therefore, if the room ID changes, we
@@ -644,51 +642,51 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
return; return;
} }
const roomId = this.context.roomViewStore.getRoomId() ?? null; const roomId = this.props.context.roomViewStore.getRoomId() ?? null;
const room = this.context.client?.getRoom(roomId ?? undefined) ?? undefined; const room = this.props.context.client?.getRoom(roomId ?? undefined) ?? undefined;
const newState: Partial<IRoomState> = { const newState: Partial<IRoomState> = {
roomId: roomId ?? undefined, roomId: roomId ?? undefined,
roomAlias: this.context.roomViewStore.getRoomAlias() ?? undefined, roomAlias: this.props.context.roomViewStore.getRoomAlias() ?? undefined,
roomLoading: this.context.roomViewStore.isRoomLoading(), roomLoading: this.props.context.roomViewStore.isRoomLoading(),
roomLoadError, roomLoadError,
joining: this.context.roomViewStore.isJoining(), joining: this.props.context.roomViewStore.isJoining(),
replyToEvent: this.context.roomViewStore.getQuotingEvent() ?? undefined, replyToEvent: this.props.context.roomViewStore.getQuotingEvent() ?? undefined,
// we should only peek once we have a ready client // we should only peek once we have a ready client
shouldPeek: this.state.matrixClientIsReady && this.context.roomViewStore.shouldPeek(), shouldPeek: this.state.matrixClientIsReady && this.props.context.roomViewStore.shouldPeek(),
showReadReceipts: SettingsStore.getValue("showReadReceipts", roomId), showReadReceipts: SettingsStore.getValue("showReadReceipts", roomId),
showRedactions: SettingsStore.getValue("showRedactions", roomId), showRedactions: SettingsStore.getValue("showRedactions", roomId),
showJoinLeaves: SettingsStore.getValue("showJoinLeaves", roomId), showJoinLeaves: SettingsStore.getValue("showJoinLeaves", roomId),
showAvatarChanges: SettingsStore.getValue("showAvatarChanges", roomId), showAvatarChanges: SettingsStore.getValue("showAvatarChanges", roomId),
showDisplaynameChanges: SettingsStore.getValue("showDisplaynameChanges", roomId), showDisplaynameChanges: SettingsStore.getValue("showDisplaynameChanges", roomId),
wasContextSwitch: this.context.roomViewStore.getWasContextSwitch(), wasContextSwitch: this.props.context.roomViewStore.getWasContextSwitch(),
mainSplitContentType: room ? this.getMainSplitContentType(room) : undefined, mainSplitContentType: room ? this.getMainSplitContentType(room) : undefined,
initialEventId: undefined, // default to clearing this, will get set later in the method if needed initialEventId: undefined, // default to clearing this, will get set later in the method if needed
showRightPanel: roomId ? this.context.rightPanelStore.isOpenForRoom(roomId) : false, showRightPanel: roomId ? this.props.context.rightPanelStore.isOpenForRoom(roomId) : false,
threadRightPanel: roomId threadRightPanel: roomId
? [RightPanelPhases.ThreadView, RightPanelPhases.ThreadPanel].includes( ? [RightPanelPhases.ThreadView, RightPanelPhases.ThreadPanel].includes(
this.context.rightPanelStore.currentCardForRoom(roomId).phase!, this.props.context.rightPanelStore.currentCardForRoom(roomId).phase!,
) )
: false, : false,
activeCall: roomId ? CallStore.instance.getActiveCall(roomId) : null, activeCall: roomId ? CallStore.instance.getActiveCall(roomId) : null,
promptAskToJoin: this.context.roomViewStore.promptAskToJoin(), promptAskToJoin: this.props.context.roomViewStore.promptAskToJoin(),
viewRoomOpts: this.context.roomViewStore.getViewRoomOpts(), viewRoomOpts: this.props.context.roomViewStore.getViewRoomOpts(),
}; };
if ( if (
this.state.mainSplitContentType !== MainSplitContentType.Timeline && this.state.mainSplitContentType !== MainSplitContentType.Timeline &&
newState.mainSplitContentType === MainSplitContentType.Timeline && newState.mainSplitContentType === MainSplitContentType.Timeline &&
this.context.rightPanelStore.isOpen && this.props.context.rightPanelStore.isOpen &&
this.context.rightPanelStore.currentCard.phase === RightPanelPhases.Timeline && this.props.context.rightPanelStore.currentCard.phase === RightPanelPhases.Timeline &&
this.context.rightPanelStore.roomPhaseHistory.some((card) => card.phase === RightPanelPhases.Timeline) this.props.context.rightPanelStore.roomPhaseHistory.some((card) => card.phase === RightPanelPhases.Timeline)
) { ) {
// We're returning to the main timeline, so hide the right panel timeline // We're returning to the main timeline, so hide the right panel timeline
this.context.rightPanelStore.setCard({ phase: RightPanelPhases.RoomSummary }); this.props.context.rightPanelStore.setCard({ phase: RightPanelPhases.RoomSummary });
this.context.rightPanelStore.togglePanel(this.state.roomId ?? null); this.props.context.rightPanelStore.togglePanel(this.state.roomId ?? null);
newState.showRightPanel = false; newState.showRightPanel = false;
} }
const initialEventId = this.context.roomViewStore.getInitialEventId() ?? this.state.initialEventId; const initialEventId = this.props.context.roomViewStore.getInitialEventId() ?? this.state.initialEventId;
if (initialEventId) { if (initialEventId) {
let initialEvent = room?.findEventById(initialEventId); let initialEvent = room?.findEventById(initialEventId);
// The event does not exist in the current sync data // The event does not exist in the current sync data
@@ -699,8 +697,9 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
// and the root event. // and the root event.
// The rest will be lost for now, until the aggregation API on the server // The rest will be lost for now, until the aggregation API on the server
// becomes available to fetch a whole thread // becomes available to fetch a whole thread
if (!initialEvent && this.context.client && roomId) { if (!initialEvent && this.props.context.client && roomId) {
initialEvent = (await fetchInitialEvent(this.context.client, roomId, initialEventId)) ?? undefined; initialEvent =
(await fetchInitialEvent(this.props.context.client, roomId, initialEventId)) ?? undefined;
} }
// If we have an initial event, we want to reset the event pixel offset to ensure it ends up visible // If we have an initial event, we want to reset the event pixel offset to ensure it ends up visible
@@ -714,13 +713,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
action: Action.ShowThread, action: Action.ShowThread,
rootEvent: thread.rootEvent, rootEvent: thread.rootEvent,
initialEvent, initialEvent,
highlighted: this.context.roomViewStore.isInitialEventHighlighted(), highlighted: this.props.context.roomViewStore.isInitialEventHighlighted(),
scroll_into_view: this.context.roomViewStore.initialEventScrollIntoView(), scroll_into_view: this.props.context.roomViewStore.initialEventScrollIntoView(),
}); });
} else { } else {
newState.initialEventId = initialEventId; newState.initialEventId = initialEventId;
newState.isInitialEventHighlighted = this.context.roomViewStore.isInitialEventHighlighted(); newState.isInitialEventHighlighted = this.props.context.roomViewStore.isInitialEventHighlighted();
newState.initialEventScrollIntoView = this.context.roomViewStore.initialEventScrollIntoView(); newState.initialEventScrollIntoView = this.props.context.roomViewStore.initialEventScrollIntoView();
} }
} }
@@ -745,7 +744,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
if (!initial && this.state.shouldPeek && !newState.shouldPeek) { if (!initial && this.state.shouldPeek && !newState.shouldPeek) {
// Stop peeking because we have joined this room now // Stop peeking because we have joined this room now
this.context.client?.stopPeeking(); this.props.context.client?.stopPeeking();
} }
// Temporary logging to diagnose https://github.com/vector-im/element-web/issues/4307 // Temporary logging to diagnose https://github.com/vector-im/element-web/issues/4307
@@ -766,7 +765,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
// NB: This does assume that the roomID will not change for the lifetime of // NB: This does assume that the roomID will not change for the lifetime of
// the RoomView instance // the RoomView instance
if (initial) { if (initial) {
newState.room = this.context.client!.getRoom(newState.roomId) || undefined; newState.room = this.props.context.client!.getRoom(newState.roomId) || undefined;
if (newState.room) { if (newState.room) {
newState.showApps = this.shouldShowApps(newState.room); newState.showApps = this.shouldShowApps(newState.room);
this.onRoomLoaded(newState.room); this.onRoomLoaded(newState.room);
@@ -897,7 +896,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
peekLoading: true, peekLoading: true,
isPeeking: true, // this will change to false if peeking fails isPeeking: true, // this will change to false if peeking fails
}); });
this.context.client this.props.context.client
?.peekInRoom(roomId) ?.peekInRoom(roomId)
.then((room) => { .then((room) => {
if (this.unmounted) { if (this.unmounted) {
@@ -934,7 +933,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}); });
} else if (room) { } else if (room) {
// Stop peeking because we have joined this room previously // Stop peeking because we have joined this room previously
this.context.client?.stopPeeking(); this.props.context.client?.stopPeeking();
this.setState({ this.setState({
isPeeking: false, isPeeking: false,
canAskToJoin: this.askToJoinEnabled && room.getJoinRule() === JoinRule.Knock, canAskToJoin: this.askToJoinEnabled && room.getJoinRule() === JoinRule.Knock,
@@ -955,7 +954,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
// Otherwise (in case the user set hideWidgetDrawer by clicking the button) follow the parameter. // Otherwise (in case the user set hideWidgetDrawer by clicking the button) follow the parameter.
const isManuallyShown = hideWidgetDrawer ? hideWidgetDrawer === "false" : true; const isManuallyShown = hideWidgetDrawer ? hideWidgetDrawer === "false" : true;
const widgets = this.context.widgetLayoutStore.getContainerWidgets(room, Container.Top); const widgets = this.props.context.widgetLayoutStore.getContainerWidgets(room, Container.Top);
return isManuallyShown && widgets.length > 0; return isManuallyShown && widgets.length > 0;
} }
@@ -968,7 +967,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
callState, callState,
}); });
this.context.legacyCallHandler.on(LegacyCallHandlerEvent.CallState, this.onCallState); this.props.context.legacyCallHandler.on(LegacyCallHandlerEvent.CallState, this.onCallState);
window.addEventListener("beforeunload", this.onPageUnload); window.addEventListener("beforeunload", this.onPageUnload);
} }
@@ -1005,7 +1004,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
// (We could use isMounted, but facebook have deprecated that.) // (We could use isMounted, but facebook have deprecated that.)
this.unmounted = true; this.unmounted = true;
this.context.legacyCallHandler.removeListener(LegacyCallHandlerEvent.CallState, this.onCallState); this.props.context.legacyCallHandler.removeListener(LegacyCallHandlerEvent.CallState, this.onCallState);
// update the scroll map before we get unmounted // update the scroll map before we get unmounted
if (this.state.roomId) { if (this.state.roomId) {
@@ -1013,47 +1012,53 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
} }
if (this.state.shouldPeek) { if (this.state.shouldPeek) {
this.context.client?.stopPeeking(); this.props.context.client?.stopPeeking();
} }
// stop tracking room changes to format permalinks // stop tracking room changes to format permalinks
this.stopAllPermalinkCreators(); this.stopAllPermalinkCreators();
dis.unregister(this.dispatcherRef); dis.unregister(this.dispatcherRef);
if (this.context.client) { if (this.props.context.client) {
this.context.client.removeListener(ClientEvent.Room, this.onRoom); this.props.context.client.removeListener(ClientEvent.Room, this.onRoom);
this.context.client.removeListener(RoomEvent.Timeline, this.onRoomTimeline); this.props.context.client.removeListener(RoomEvent.Timeline, this.onRoomTimeline);
this.context.client.removeListener(RoomEvent.TimelineReset, this.onRoomTimelineReset); this.props.context.client.removeListener(RoomEvent.TimelineReset, this.onRoomTimelineReset);
this.context.client.removeListener(RoomEvent.Name, this.onRoomName); this.props.context.client.removeListener(RoomEvent.Name, this.onRoomName);
this.context.client.removeListener(RoomStateEvent.Events, this.onRoomStateEvents); this.props.context.client.removeListener(RoomStateEvent.Events, this.onRoomStateEvents);
this.context.client.removeListener(RoomEvent.MyMembership, this.onMyMembership); this.props.context.client.removeListener(RoomEvent.MyMembership, this.onMyMembership);
this.context.client.removeListener(RoomStateEvent.Update, this.onRoomStateUpdate); this.props.context.client.removeListener(RoomStateEvent.Update, this.onRoomStateUpdate);
this.context.client.removeListener(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus); this.props.context.client.removeListener(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus);
this.context.client.removeListener(CryptoEvent.DeviceVerificationChanged, this.onDeviceVerificationChanged); this.props.context.client.removeListener(
this.context.client.removeListener(CryptoEvent.UserTrustStatusChanged, this.onUserVerificationChanged); CryptoEvent.DeviceVerificationChanged,
this.context.client.removeListener(CryptoEvent.KeysChanged, this.onCrossSigningKeysChanged); this.onDeviceVerificationChanged,
this.context.client.removeListener(MatrixEventEvent.Decrypted, this.onEventDecrypted); );
this.props.context.client.removeListener(
CryptoEvent.UserTrustStatusChanged,
this.onUserVerificationChanged,
);
this.props.context.client.removeListener(CryptoEvent.KeysChanged, this.onCrossSigningKeysChanged);
this.props.context.client.removeListener(MatrixEventEvent.Decrypted, this.onEventDecrypted);
} }
window.removeEventListener("beforeunload", this.onPageUnload); window.removeEventListener("beforeunload", this.onPageUnload);
this.context.roomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate); this.props.context.roomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate);
this.context.rightPanelStore.off(UPDATE_EVENT, this.onRightPanelStoreUpdate); this.props.context.rightPanelStore.off(UPDATE_EVENT, this.onRightPanelStoreUpdate);
WidgetEchoStore.removeListener(UPDATE_EVENT, this.onWidgetEchoStoreUpdate); WidgetEchoStore.removeListener(UPDATE_EVENT, this.onWidgetEchoStoreUpdate);
this.context.widgetStore.removeListener(UPDATE_EVENT, this.onWidgetStoreUpdate); this.props.context.widgetStore.removeListener(UPDATE_EVENT, this.onWidgetStoreUpdate);
this.props.resizeNotifier.off("isResizing", this.onIsResizing); this.props.resizeNotifier.off("isResizing", this.onIsResizing);
if (this.state.room) { if (this.state.room) {
this.context.widgetLayoutStore.off( this.props.context.widgetLayoutStore.off(
WidgetLayoutStore.emissionForRoom(this.state.room), WidgetLayoutStore.emissionForRoom(this.state.room),
this.onWidgetLayoutChange, this.onWidgetLayoutChange,
); );
} }
CallStore.instance.off(CallStoreEvent.ActiveCalls, this.onActiveCalls); CallStore.instance.off(CallStoreEvent.ActiveCalls, this.onActiveCalls);
this.context.legacyCallHandler.off(LegacyCallHandlerEvent.CallState, this.onCallState); this.props.context.legacyCallHandler.off(LegacyCallHandlerEvent.CallState, this.onCallState);
// cancel any pending calls to the throttled updated // cancel any pending calls to the throttled updated
this.updateRoomMembers.cancel(); this.updateRoomMembers.cancel();
@@ -1064,17 +1069,17 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
if (this.viewsLocalRoom && this.state.room) { if (this.viewsLocalRoom && this.state.room) {
// clean up if this was a local room // clean up if this was a local room
this.context.client?.store.removeRoom(this.state.room.roomId); this.props.context.client?.store.removeRoom(this.state.room.roomId);
} }
} }
private onRightPanelStoreUpdate = (): void => { private onRightPanelStoreUpdate = (): void => {
const { roomId } = this.state; const { roomId } = this.state;
this.setState({ this.setState({
showRightPanel: roomId ? this.context.rightPanelStore.isOpenForRoom(roomId) : false, showRightPanel: roomId ? this.props.context.rightPanelStore.isOpenForRoom(roomId) : false,
threadRightPanel: roomId threadRightPanel: roomId
? [RightPanelPhases.ThreadView, RightPanelPhases.ThreadPanel].includes( ? [RightPanelPhases.ThreadView, RightPanelPhases.ThreadPanel].includes(
this.context.rightPanelStore.currentCardForRoom(roomId).phase!, this.props.context.rightPanelStore.currentCardForRoom(roomId).phase!,
) )
: false, : false,
}); });
@@ -1131,7 +1136,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}; };
private onAction = async (payload: ActionPayload): Promise<void> => { private onAction = async (payload: ActionPayload): Promise<void> => {
if (!this.context.client) return; if (!this.props.context.client) return;
switch (payload.action) { switch (payload.action) {
case "message_sent": case "message_sent":
this.checkDesktopNotifications(); this.checkDesktopNotifications();
@@ -1151,7 +1156,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
[payload.file], [payload.file],
roomId, roomId,
undefined, undefined,
this.context.client, this.props.context.client,
); );
} }
@@ -1183,7 +1188,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
if (!this.state.matrixClientIsReady) { if (!this.state.matrixClientIsReady) {
this.setState( this.setState(
{ {
matrixClientIsReady: !!this.context.client?.isInitialSyncComplete(), matrixClientIsReady: !!this.props.context.client?.isInitialSyncComplete(),
}, },
() => { () => {
// send another "initial" RVS update to trigger peeking if needed // send another "initial" RVS update to trigger peeking if needed
@@ -1296,8 +1301,8 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}; };
private onLocalRoomEvent(roomId: string): void { private onLocalRoomEvent(roomId: string): void {
if (!this.context.client || !this.state.room || roomId !== this.state.room.roomId) return; if (!this.props.context.client || !this.state.room || roomId !== this.state.room.roomId) return;
createRoomFromLocalRoom(this.context.client, this.state.room as LocalRoom); createRoomFromLocalRoom(this.props.context.client, this.state.room as LocalRoom);
} }
private onRoomTimeline = ( private onRoomTimeline = (
@@ -1336,7 +1341,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
this.handleEffects(ev); this.handleEffects(ev);
} }
if (this.context.client && ev.getSender() !== this.context.client.getSafeUserId()) { if (this.props.context.client && ev.getSender() !== this.props.context.client.getSafeUserId()) {
// update unread count when scrolled up // update unread count when scrolled up
if (!this.state.search && this.state.atEndOfLiveTimeline) { if (!this.state.search && this.state.atEndOfLiveTimeline) {
// no change // no change
@@ -1357,7 +1362,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private handleEffects = (ev: MatrixEvent): void => { private handleEffects = (ev: MatrixEvent): void => {
if (!this.state.room) return; if (!this.state.room) return;
const notifState = this.context.roomNotificationStateStore.getRoomState(this.state.room); const notifState = this.props.context.roomNotificationStateStore.getRoomState(this.state.room);
if (!notifState.isUnread) return; if (!notifState.isUnread) return;
CHAT_EFFECTS.forEach((effect) => { CHAT_EFFECTS.forEach((effect) => {
@@ -1400,7 +1405,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private onRoomLoaded = (room: Room): void => { private onRoomLoaded = (room: Room): void => {
if (this.unmounted) return; if (this.unmounted) return;
// Attach a widget store listener only when we get a room // Attach a widget store listener only when we get a room
this.context.widgetLayoutStore.on(WidgetLayoutStore.emissionForRoom(room), this.onWidgetLayoutChange); this.props.context.widgetLayoutStore.on(WidgetLayoutStore.emissionForRoom(room), this.onWidgetLayoutChange);
this.calculatePeekRules(room); this.calculatePeekRules(room);
this.updatePreviewUrlVisibility(room); this.updatePreviewUrlVisibility(room);
@@ -1413,10 +1418,10 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
if ( if (
this.getMainSplitContentType(room) !== MainSplitContentType.Timeline && this.getMainSplitContentType(room) !== MainSplitContentType.Timeline &&
this.context.roomNotificationStateStore.getRoomState(room).isUnread this.props.context.roomNotificationStateStore.getRoomState(room).isUnread
) { ) {
// Automatically open the chat panel to make unread messages easier to discover // Automatically open the chat panel to make unread messages easier to discover
this.context.rightPanelStore.setCard({ phase: RightPanelPhases.Timeline }, true, room.roomId); this.props.context.rightPanelStore.setCard({ phase: RightPanelPhases.Timeline }, true, room.roomId);
} }
this.setState({ this.setState({
@@ -1446,7 +1451,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private async loadMembersIfJoined(room: Room): Promise<void> { private async loadMembersIfJoined(room: Room): Promise<void> {
// lazy load members if enabled // lazy load members if enabled
if (this.context.client?.hasLazyLoadMembersEnabled()) { if (this.props.context.client?.hasLazyLoadMembersEnabled()) {
if (room && room.getMyMembership() === KnownMembership.Join) { if (room && room.getMyMembership() === KnownMembership.Join) {
try { try {
await room.loadMembersIfNeeded(); await room.loadMembersIfNeeded();
@@ -1472,7 +1477,9 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private updatePreviewUrlVisibility({ roomId }: Room): void { private updatePreviewUrlVisibility({ roomId }: Room): void {
// URL Previews in E2EE rooms can be a privacy leak so use a different setting which is per-room explicit // URL Previews in E2EE rooms can be a privacy leak so use a different setting which is per-room explicit
const key = this.context.client?.isRoomEncrypted(roomId) ? "urlPreviewsEnabled_e2ee" : "urlPreviewsEnabled"; const key = this.props.context.client?.isRoomEncrypted(roomId)
? "urlPreviewsEnabled_e2ee"
: "urlPreviewsEnabled";
this.setState({ this.setState({
showUrlPreview: SettingsStore.getValue(key, roomId), showUrlPreview: SettingsStore.getValue(key, roomId),
}); });
@@ -1485,7 +1492,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
// Detach the listener if the room is changing for some reason // Detach the listener if the room is changing for some reason
if (this.state.room) { if (this.state.room) {
this.context.widgetLayoutStore.off( this.props.context.widgetLayoutStore.off(
WidgetLayoutStore.emissionForRoom(this.state.room), WidgetLayoutStore.emissionForRoom(this.state.room),
this.onWidgetLayoutChange, this.onWidgetLayoutChange,
); );
@@ -1525,15 +1532,15 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}; };
private async updateE2EStatus(room: Room): Promise<void> { private async updateE2EStatus(room: Room): Promise<void> {
if (!this.context.client?.isRoomEncrypted(room.roomId)) return; if (!this.props.context.client?.isRoomEncrypted(room.roomId)) return;
// If crypto is not currently enabled, we aren't tracking devices at all, // If crypto is not currently enabled, we aren't tracking devices at all,
// so we don't know what the answer is. Let's error on the safe side and show // so we don't know what the answer is. Let's error on the safe side and show
// a warning for this case. // a warning for this case.
let e2eStatus = E2EStatus.Warning; let e2eStatus = E2EStatus.Warning;
if (this.context.client.isCryptoEnabled()) { if (this.props.context.client.isCryptoEnabled()) {
/* At this point, the user has encryption on and cross-signing on */ /* At this point, the user has encryption on and cross-signing on */
e2eStatus = await shieldStatusForRoom(this.context.client, room); e2eStatus = await shieldStatusForRoom(this.props.context.client, room);
} }
if (this.unmounted) return; if (this.unmounted) return;
@@ -1578,8 +1585,8 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}; };
private updatePermissions(room: Room): void { private updatePermissions(room: Room): void {
if (room && this.context.client) { if (room && this.props.context.client) {
const me = this.context.client.getSafeUserId(); const me = this.props.context.client.getSafeUserId();
const canReact = const canReact =
room.getMyMembership() === KnownMembership.Join && room.getMyMembership() === KnownMembership.Join &&
room.currentState.maySendEvent(EventType.Reaction, me); room.currentState.maySendEvent(EventType.Reaction, me);
@@ -1635,7 +1642,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private onJoinButtonClicked = (): void => { private onJoinButtonClicked = (): void => {
// If the user is a ROU, allow them to transition to a PWLU // If the user is a ROU, allow them to transition to a PWLU
if (this.context.client?.isGuest()) { if (this.props.context.client?.isGuest()) {
// Join this room once the user has registered and logged in // Join this room once the user has registered and logged in
// (If we failed to peek, we may not have a valid room object.) // (If we failed to peek, we may not have a valid room object.)
dis.dispatch<DoAfterSyncPreparedPayload<ViewRoomPayload>>({ dis.dispatch<DoAfterSyncPreparedPayload<ViewRoomPayload>>({
@@ -1702,14 +1709,14 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private injectSticker(url: string, info: object, text: string, threadId: string | null): void { private injectSticker(url: string, info: object, text: string, threadId: string | null): void {
const roomId = this.getRoomId(); const roomId = this.getRoomId();
if (!this.context.client || !roomId) return; if (!this.props.context.client || !roomId) return;
if (this.context.client.isGuest()) { if (this.props.context.client.isGuest()) {
dis.dispatch({ action: "require_registration" }); dis.dispatch({ action: "require_registration" });
return; return;
} }
ContentMessages.sharedInstance() ContentMessages.sharedInstance()
.sendStickerContentToRoom(url, roomId, threadId, info, text, this.context.client) .sendStickerContentToRoom(url, roomId, threadId, info, text, this.props.context.client)
.then(undefined, (error) => { .then(undefined, (error) => {
if (error.name === "UnknownDeviceError") { if (error.name === "UnknownDeviceError") {
// Let the staus bar handle this // Let the staus bar handle this
@@ -1722,7 +1729,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
const roomId = scope === SearchScope.Room ? this.getRoomId() : undefined; const roomId = scope === SearchScope.Room ? this.getRoomId() : undefined;
debuglog("sending search request"); debuglog("sending search request");
const abortController = new AbortController(); const abortController = new AbortController();
const promise = eventSearch(this.context.client!, term, roomId, abortController.signal); const promise = eventSearch(this.props.context.client!, term, roomId, abortController.signal);
this.setState({ this.setState({
search: { search: {
@@ -1768,7 +1775,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
this.setState({ this.setState({
rejecting: true, rejecting: true,
}); });
this.context.client?.leave(roomId).then( this.props.context.client?.leave(roomId).then(
() => { () => {
dis.dispatch({ action: Action.ViewHomePage }); dis.dispatch({ action: Action.ViewHomePage });
this.setState({ this.setState({
@@ -1797,13 +1804,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}); });
try { try {
const myMember = this.state.room!.getMember(this.context.client!.getSafeUserId()); const myMember = this.state.room!.getMember(this.props.context.client!.getSafeUserId());
const inviteEvent = myMember!.events.member; const inviteEvent = myMember!.events.member;
const ignoredUsers = this.context.client!.getIgnoredUsers(); const ignoredUsers = this.props.context.client!.getIgnoredUsers();
ignoredUsers.push(inviteEvent!.getSender()!); // de-duped internally in the js-sdk ignoredUsers.push(inviteEvent!.getSender()!); // de-duped internally in the js-sdk
await this.context.client!.setIgnoredUsers(ignoredUsers); await this.props.context.client!.setIgnoredUsers(ignoredUsers);
await this.context.client!.leave(this.state.roomId!); await this.props.context.client!.leave(this.state.roomId!);
dis.dispatch({ action: Action.ViewHomePage }); dis.dispatch({ action: Action.ViewHomePage });
this.setState({ this.setState({
rejecting: false, rejecting: false,
@@ -1968,7 +1975,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
if (!this.state.room) { if (!this.state.room) {
return null; return null;
} }
return this.context.legacyCallHandler.getCallForRoom(this.state.room.roomId); return this.props.context.legacyCallHandler.getCallForRoom(this.state.room.roomId);
} }
// this has to be a proper method rather than an unnamed function, // this has to be a proper method rather than an unnamed function,
@@ -1979,7 +1986,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private getOldRoom(): Room | null { private getOldRoom(): Room | null {
const { roomId } = this.state.room?.findPredecessor(this.state.msc3946ProcessDynamicPredecessor) || {}; const { roomId } = this.state.room?.findPredecessor(this.state.msc3946ProcessDynamicPredecessor) || {};
return this.context.client?.getRoom(roomId) || null; return this.props.context.client?.getRoom(roomId) || null;
} }
public getHiddenHighlightCount(): number { public getHiddenHighlightCount(): number {
@@ -2006,12 +2013,12 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private onFileDrop = async (dataTransfer: DataTransfer): Promise<void> => { private onFileDrop = async (dataTransfer: DataTransfer): Promise<void> => {
const roomId = this.getRoomId(); const roomId = this.getRoomId();
if (!roomId || !this.context.client) return; if (!roomId || !this.props.context.client) return;
await ContentMessages.sharedInstance().sendContentListToRoom( await ContentMessages.sharedInstance().sendContentListToRoom(
Array.from(dataTransfer.files), Array.from(dataTransfer.files),
roomId, roomId,
undefined, undefined,
this.context.client, this.props.context.client,
TimelineRenderingType.Room, TimelineRenderingType.Room,
); );
}; };
@@ -2029,8 +2036,8 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
} }
private renderLocalRoomCreateLoader(localRoom: LocalRoom): ReactNode { private renderLocalRoomCreateLoader(localRoom: LocalRoom): ReactNode {
if (!this.state.room || !this.context?.client) return null; if (!this.state.room || !this.props.context?.client) return null;
const names = this.state.room.getDefaultRoomName(this.context.client.getSafeUserId()); const names = this.state.room.getDefaultRoomName(this.props.context.client.getSafeUserId());
return ( return (
<RoomContext.Provider value={this.state}> <RoomContext.Provider value={this.state}>
<LocalRoomCreateLoader localRoom={localRoom} names={names} resizeNotifier={this.props.resizeNotifier} /> <LocalRoomCreateLoader localRoom={localRoom} names={names} resizeNotifier={this.props.resizeNotifier} />
@@ -2099,7 +2106,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}; };
public render(): ReactNode { public render(): ReactNode {
if (!this.context.client) return null; if (!this.props.context.client) return null;
if (this.state.room instanceof LocalRoom) { if (this.state.room instanceof LocalRoom) {
if (this.state.room.state === LocalRoomState.CREATING) { if (this.state.room.state === LocalRoomState.CREATING) {
@@ -2209,7 +2216,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
</ErrorBoundary> </ErrorBoundary>
); );
} else { } else {
const myUserId = this.context.client.getSafeUserId(); const myUserId = this.props.context.client.getSafeUserId();
const myMember = this.state.room.getMember(myUserId); const myMember = this.state.room.getMember(myUserId);
const inviteEvent = myMember ? myMember.events.member : null; const inviteEvent = myMember ? myMember.events.member : null;
let inviterName = _t("room|inviter_unknown"); let inviterName = _t("room|inviter_unknown");
@@ -2313,7 +2320,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
const showRoomUpgradeBar = const showRoomUpgradeBar =
roomVersionRecommendation && roomVersionRecommendation &&
roomVersionRecommendation.needsUpgrade && roomVersionRecommendation.needsUpgrade &&
this.state.room.userMayUpgradeRoom(this.context.client.getSafeUserId()); this.state.room.userMayUpgradeRoom(this.props.context.client.getSafeUserId());
const hiddenHighlightCount = this.getHiddenHighlightCount(); const hiddenHighlightCount = this.getHiddenHighlightCount();
@@ -2325,7 +2332,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
searchInProgress={this.state.search?.inProgress} searchInProgress={this.state.search?.inProgress}
onCancelClick={this.onCancelSearchClick} onCancelClick={this.onCancelSearchClick}
onSearch={this.onSearch} onSearch={this.onSearch}
isRoomEncrypted={this.context.client.isRoomEncrypted(this.state.room.roomId)} isRoomEncrypted={this.props.context.client.isRoomEncrypted(this.state.room.roomId)}
/> />
); );
} else if (showRoomUpgradeBar) { } else if (showRoomUpgradeBar) {
@@ -2393,7 +2400,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
const auxPanel = ( const auxPanel = (
<AuxPanel <AuxPanel
room={this.state.room} room={this.state.room}
userId={this.context.client.getSafeUserId()} userId={this.props.context.client.getSafeUserId()}
showApps={this.state.showApps} showApps={this.state.showApps}
resizeNotifier={this.props.resizeNotifier} resizeNotifier={this.props.resizeNotifier}
> >
@@ -2546,7 +2553,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
<> <>
<AppsDrawer <AppsDrawer
room={this.state.room} room={this.state.room}
userId={this.context.client.getSafeUserId()} userId={this.props.context.client.getSafeUserId()}
resizeNotifier={this.props.resizeNotifier} resizeNotifier={this.props.resizeNotifier}
showApps={true} showApps={true}
role="main" role="main"
@@ -2563,7 +2570,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
room={this.state.room} room={this.state.room}
resizing={this.state.resizing} resizing={this.state.resizing}
waitForCall={isVideoRoom(this.state.room)} waitForCall={isVideoRoom(this.state.room)}
skipLobby={this.context.roomViewStore.skipCallLobby() ?? false} skipLobby={this.props.context.roomViewStore.skipCallLobby() ?? false}
role="main" role="main"
/> />
{previewBar} {previewBar}
@@ -2593,15 +2600,15 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
onAppsClick = null; onAppsClick = null;
onForgetClick = null; onForgetClick = null;
onSearchClick = null; onSearchClick = null;
if (this.state.room.canInvite(this.context.client.getSafeUserId())) { if (this.state.room.canInvite(this.props.context.client.getSafeUserId())) {
onInviteClick = this.onInviteClick; onInviteClick = this.onInviteClick;
} }
viewingCall = true; viewingCall = true;
} }
const myMember = this.state.room!.getMember(this.context.client!.getSafeUserId()); const myMember = this.state.room!.getMember(this.props.context.client!.getSafeUserId());
const showForgetButton = const showForgetButton =
!this.context.client.isGuest() && !this.props.context.client.isGuest() &&
(([KnownMembership.Leave, KnownMembership.Ban] as Array<string>).includes(myMembership) || (([KnownMembership.Leave, KnownMembership.Ban] as Array<string>).includes(myMembership) ||
myMember?.isKicked()); myMember?.isKicked());
@@ -2665,4 +2672,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
} }
} }
export default RoomView; export default forwardRef<RoomView, Omit<IRoomProps, "context">>((props, ref) => (
<SDKContext.Consumer>{(context) => <RoomView {...props} context={context} ref={ref} />}</SDKContext.Consumer>
));

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { createRef, ReactNode } from "react"; import React, { createRef, forwardRef, ReactNode } from "react";
import { Room } from "matrix-js-sdk/src/matrix"; import { Room } from "matrix-js-sdk/src/matrix";
import { MatrixClientPeg } from "../../MatrixClientPeg"; import { MatrixClientPeg } from "../../MatrixClientPeg";
@@ -56,6 +56,7 @@ import { shouldShowFeedback } from "../../utils/Feedback";
interface IProps { interface IProps {
isPanelCollapsed: boolean; isPanelCollapsed: boolean;
children?: ReactNode; children?: ReactNode;
context: React.ContextType<typeof SDKContext>;
} }
type PartialDOMRect = Pick<DOMRect, "width" | "left" | "top" | "height">; type PartialDOMRect = Pick<DOMRect, "width" | "left" | "top" | "height">;
@@ -84,25 +85,21 @@ const below = (rect: PartialDOMRect): MenuProps => {
}; };
}; };
export default class UserMenu extends React.Component<IProps, IState> { class UserMenu extends React.Component<IProps, IState> {
public static contextType = SDKContext;
public context!: React.ContextType<typeof SDKContext>;
private dispatcherRef?: string; private dispatcherRef?: string;
private themeWatcherRef?: string; private themeWatcherRef?: string;
private readonly dndWatcherRef?: string; private readonly dndWatcherRef?: string;
private buttonRef: React.RefObject<HTMLButtonElement> = createRef(); private buttonRef: React.RefObject<HTMLButtonElement> = createRef();
public constructor(props: IProps, context: React.ContextType<typeof SDKContext>) { public constructor(props: IProps) {
super(props, context); super(props);
this.context = context;
this.state = { this.state = {
contextMenuPosition: null, contextMenuPosition: null,
isDarkTheme: this.isUserOnDarkTheme(), isDarkTheme: this.isUserOnDarkTheme(),
isHighContrast: this.isUserOnHighContrastTheme(), isHighContrast: this.isUserOnHighContrastTheme(),
selectedSpace: SpaceStore.instance.activeSpaceRoom, selectedSpace: SpaceStore.instance.activeSpaceRoom,
showLiveAvatarAddon: this.context.voiceBroadcastRecordingsStore.hasCurrent(), showLiveAvatarAddon: this.props.context.voiceBroadcastRecordingsStore.hasCurrent(),
}; };
OwnProfileStore.instance.on(UPDATE_EVENT, this.onProfileUpdate); OwnProfileStore.instance.on(UPDATE_EVENT, this.onProfileUpdate);
@@ -110,7 +107,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
} }
private get hasHomePage(): boolean { private get hasHomePage(): boolean {
return !!getHomePageUrl(SdkConfig.get(), this.context.client!); return !!getHomePageUrl(SdkConfig.get(), this.props.context.client!);
} }
private onCurrentVoiceBroadcastRecordingChanged = (recording: VoiceBroadcastRecording | null): void => { private onCurrentVoiceBroadcastRecordingChanged = (recording: VoiceBroadcastRecording | null): void => {
@@ -120,7 +117,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
}; };
public componentDidMount(): void { public componentDidMount(): void {
this.context.voiceBroadcastRecordingsStore.on( this.props.context.voiceBroadcastRecordingsStore.on(
VoiceBroadcastRecordingsStoreEvent.CurrentChanged, VoiceBroadcastRecordingsStoreEvent.CurrentChanged,
this.onCurrentVoiceBroadcastRecordingChanged, this.onCurrentVoiceBroadcastRecordingChanged,
); );
@@ -134,7 +131,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
if (this.dispatcherRef) defaultDispatcher.unregister(this.dispatcherRef); if (this.dispatcherRef) defaultDispatcher.unregister(this.dispatcherRef);
OwnProfileStore.instance.off(UPDATE_EVENT, this.onProfileUpdate); OwnProfileStore.instance.off(UPDATE_EVENT, this.onProfileUpdate);
SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdate); SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdate);
this.context.voiceBroadcastRecordingsStore.off( this.props.context.voiceBroadcastRecordingsStore.off(
VoiceBroadcastRecordingsStoreEvent.CurrentChanged, VoiceBroadcastRecordingsStoreEvent.CurrentChanged,
this.onCurrentVoiceBroadcastRecordingChanged, this.onCurrentVoiceBroadcastRecordingChanged,
); );
@@ -496,3 +493,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
); );
} }
} }
export default forwardRef<UserMenu, Omit<IProps, "context">>((props, ref) => (
<SDKContext.Consumer>{(context) => <UserMenu {...props} context={context} ref={ref} />}</SDKContext.Consumer>
));

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { ChangeEvent, SyntheticEvent } from "react"; import React, { ChangeEvent, forwardRef, SyntheticEvent } from "react";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { Optional } from "matrix-events-sdk"; import { Optional } from "matrix-events-sdk";
import { LoginFlow, MatrixError, SSOAction, SSOFlow } from "matrix-js-sdk/src/matrix"; import { LoginFlow, MatrixError, SSOAction, SSOFlow } from "matrix-js-sdk/src/matrix";
@@ -60,6 +60,7 @@ interface IProps {
// Called when the SSO login completes // Called when the SSO login completes
onTokenLoginCompleted: () => void; onTokenLoginCompleted: () => void;
context: React.ContextType<typeof SDKContext>;
} }
interface IState { interface IState {
@@ -70,14 +71,9 @@ interface IState {
flows: LoginFlow[]; flows: LoginFlow[];
} }
export default class SoftLogout extends React.Component<IProps, IState> { class SoftLogout extends React.Component<IProps, IState> {
public static contextType = SDKContext; public constructor(props: IProps) {
public context!: React.ContextType<typeof SDKContext>; super(props);
public constructor(props: IProps, context: React.ContextType<typeof SDKContext>) {
super(props, context);
this.context = context;
this.state = { this.state = {
loginView: LoginView.Loading, loginView: LoginView.Loading,
@@ -104,7 +100,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
if (!wipeData) return; if (!wipeData) return;
logger.log("Clearing data from soft-logged-out session"); logger.log("Clearing data from soft-logged-out session");
Lifecycle.logout(this.context.oidcClientStore); Lifecycle.logout(this.props.context.oidcClientStore);
}, },
}); });
}; };
@@ -338,3 +334,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
); );
} }
} }
export default forwardRef<SoftLogout, Omit<IProps, "context">>((props, ref) => (
<SDKContext.Consumer>{(context) => <SoftLogout {...props} context={context} ref={ref} />}</SDKContext.Consumer>
));

View File

@@ -17,7 +17,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from "react"; import React, { forwardRef } from "react";
import { import {
MatrixEvent, MatrixEvent,
Room, Room,
@@ -66,6 +66,7 @@ interface IProps {
searchQuery: string; searchQuery: string;
onClose(): void; onClose(): void;
onSearchQueryChanged: (query: string) => void; onSearchQueryChanged: (query: string) => void;
context: React.ContextType<typeof SDKContext>;
} }
interface IState { interface IState {
@@ -77,18 +78,16 @@ interface IState {
truncateAtInvited: number; truncateAtInvited: number;
} }
export default class MemberList extends React.Component<IProps, IState> { class MemberList extends React.Component<IProps, IState> {
private readonly showPresence: boolean; private readonly showPresence: boolean;
private mounted = false; private mounted = false;
public static contextType = SDKContext;
public context!: React.ContextType<typeof SDKContext>;
private tiles: Map<string, MemberTile> = new Map(); private tiles: Map<string, MemberTile> = new Map();
public constructor(props: IProps, context: React.ContextType<typeof SDKContext>) { public constructor(props: IProps) {
super(props); super(props);
this.state = this.getMembersState([], []); this.state = this.getMembersState([], []);
this.showPresence = context?.memberListStore.isPresenceEnabled() ?? true; this.showPresence = props.context?.memberListStore.isPresenceEnabled() ?? true;
this.mounted = true; this.mounted = true;
this.listenForMembersChanges(); this.listenForMembersChanges();
} }
@@ -218,7 +217,7 @@ export default class MemberList extends React.Component<IProps, IState> {
if (showLoadingSpinner) { if (showLoadingSpinner) {
this.setState({ loading: true }); this.setState({ loading: true });
} }
const { joined, invited } = await this.context.memberListStore.loadMemberList( const { joined, invited } = await this.props.context.memberListStore.loadMemberList(
this.props.roomId, this.props.roomId,
this.props.searchQuery, this.props.searchQuery,
); );
@@ -449,3 +448,7 @@ export default class MemberList extends React.Component<IProps, IState> {
inviteToRoom(room); inviteToRoom(room);
}; };
} }
export default forwardRef<MemberList, Omit<IProps, "context">>((props, ref) => (
<SDKContext.Consumer>{(context) => <MemberList {...props} context={context} ref={ref} />}</SDKContext.Consumer>
));

View File

@@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { ReactNode } from "react"; import React, { forwardRef, ReactNode } from "react";
import { SERVICE_TYPES, HTTPError, IThreepid, ThreepidMedium } from "matrix-js-sdk/src/matrix"; import { SERVICE_TYPES, HTTPError, IThreepid, ThreepidMedium } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
@@ -61,6 +61,7 @@ import { SDKContext } from "../../../../../contexts/SDKContext";
interface IProps { interface IProps {
closeSettingsFn: () => void; closeSettingsFn: () => void;
context: React.ContextType<typeof SDKContext>;
} }
interface IState { interface IState {
@@ -92,17 +93,13 @@ interface IState {
canMake3pidChanges: boolean; canMake3pidChanges: boolean;
} }
export default class GeneralUserSettingsTab extends React.Component<IProps, IState> { class GeneralUserSettingsTab extends React.Component<IProps, IState> {
public static contextType = SDKContext;
public context!: React.ContextType<typeof SDKContext>;
private readonly dispatcherRef: string; private readonly dispatcherRef: string;
public constructor(props: IProps, context: React.ContextType<typeof SDKContext>) { public constructor(props: IProps) {
super(props); super(props);
this.context = context;
const cli = this.context.client!; const cli = this.props.context.client!;
this.state = { this.state = {
language: languageHandler.getCurrentLanguage(), language: languageHandler.getCurrentLanguage(),
@@ -151,7 +148,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
private onAction = (payload: ActionPayload): void => { private onAction = (payload: ActionPayload): void => {
if (payload.action === "id_server_changed") { if (payload.action === "id_server_changed") {
this.setState({ haveIdServer: Boolean(this.context.client!.getIdentityServerUrl()) }); this.setState({ haveIdServer: Boolean(this.props.context.client!.getIdentityServerUrl()) });
this.getThreepidState(); this.getThreepidState();
} }
}; };
@@ -165,7 +162,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
}; };
private async getCapabilities(): Promise<void> { private async getCapabilities(): Promise<void> {
const cli = this.context.client!; const cli = this.props.context.client!;
const capabilities = await cli.getCapabilities(); // this is cached const capabilities = await cli.getCapabilities(); // this is cached
const changePasswordCap = capabilities["m.change_password"]; const changePasswordCap = capabilities["m.change_password"];
@@ -175,8 +172,8 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
// the enabled flag value. // the enabled flag value.
const canChangePassword = !changePasswordCap || changePasswordCap["enabled"] !== false; const canChangePassword = !changePasswordCap || changePasswordCap["enabled"] !== false;
await this.context.oidcClientStore.readyPromise; // wait for the store to be ready await this.props.context.oidcClientStore.readyPromise; // wait for the store to be ready
const externalAccountManagementUrl = this.context.oidcClientStore.accountManagementEndpoint; const externalAccountManagementUrl = this.props.context.oidcClientStore.accountManagementEndpoint;
// https://spec.matrix.org/v1.7/client-server-api/#m3pid_changes-capability // https://spec.matrix.org/v1.7/client-server-api/#m3pid_changes-capability
// We support as far back as v1.1 which doesn't have m.3pid_changes // We support as far back as v1.1 which doesn't have m.3pid_changes
// so the behaviour for when it is missing has to be assume true // so the behaviour for when it is missing has to be assume true
@@ -186,7 +183,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
} }
private async getThreepidState(): Promise<void> { private async getThreepidState(): Promise<void> {
const cli = this.context.client!; const cli = this.props.context.client!;
// Check to see if terms need accepting // Check to see if terms need accepting
this.checkTerms(); this.checkTerms();
@@ -213,7 +210,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
private async checkTerms(): Promise<void> { private async checkTerms(): Promise<void> {
// By starting the terms flow we get the logic for checking which terms the user has signed // By starting the terms flow we get the logic for checking which terms the user has signed
// for free. So we might as well use that for our own purposes. // for free. So we might as well use that for our own purposes.
const idServerUrl = this.context.client!.getIdentityServerUrl(); const idServerUrl = this.props.context.client!.getIdentityServerUrl();
if (!this.state.haveIdServer || !idServerUrl) { if (!this.state.haveIdServer || !idServerUrl) {
this.setState({ idServerHasUnsignedTerms: false }); this.setState({ idServerHasUnsignedTerms: false });
return; return;
@@ -223,7 +220,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
try { try {
const idAccessToken = await authClient.getAccessToken({ check: false }); const idAccessToken = await authClient.getAccessToken({ check: false });
await startTermsFlow( await startTermsFlow(
this.context.client!, this.props.context.client!,
[new Service(SERVICE_TYPES.IS, idServerUrl, idAccessToken!)], [new Service(SERVICE_TYPES.IS, idServerUrl, idAccessToken!)],
(policiesAndServices, agreedUrls, extraClassNames) => { (policiesAndServices, agreedUrls, extraClassNames) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@@ -573,3 +570,9 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
); );
} }
} }
export default forwardRef<GeneralUserSettingsTab, Omit<IProps, "context">>((props, ref) => (
<SDKContext.Consumer>
{(context) => <GeneralUserSettingsTab {...props} context={context} ref={ref} />}
</SDKContext.Consumer>
));

View File

@@ -53,7 +53,7 @@ import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
import { Action } from "../../../src/dispatcher/actions"; import { Action } from "../../../src/dispatcher/actions";
import dis, { defaultDispatcher } from "../../../src/dispatcher/dispatcher"; import dis, { defaultDispatcher } from "../../../src/dispatcher/dispatcher";
import { ViewRoomPayload } from "../../../src/dispatcher/payloads/ViewRoomPayload"; import { ViewRoomPayload } from "../../../src/dispatcher/payloads/ViewRoomPayload";
import { RoomView as _RoomView } from "../../../src/components/structures/RoomView"; import _RoomView from "../../../src/components/structures/RoomView";
import ResizeNotifier from "../../../src/utils/ResizeNotifier"; import ResizeNotifier from "../../../src/utils/ResizeNotifier";
import SettingsStore from "../../../src/settings/SettingsStore"; import SettingsStore from "../../../src/settings/SettingsStore";
import { SettingLevel } from "../../../src/settings/SettingLevel"; import { SettingLevel } from "../../../src/settings/SettingLevel";
@@ -112,7 +112,7 @@ describe("RoomView", () => {
jest.clearAllMocks(); jest.clearAllMocks();
}); });
const mountRoomView = async (ref?: RefObject<_RoomView>): Promise<RenderResult> => { const mountRoomView = async (ref?: RefObject<React.ComponentRef<typeof _RoomView>>): Promise<RenderResult> => {
if (stores.roomViewStore.getRoomId() !== room.roomId) { if (stores.roomViewStore.getRoomId() !== room.roomId) {
const switchedRoom = new Promise<void>((resolve) => { const switchedRoom = new Promise<void>((resolve) => {
const subFn = () => { const subFn = () => {
@@ -185,8 +185,8 @@ describe("RoomView", () => {
await flushPromises(); await flushPromises();
return roomView; return roomView;
}; };
const getRoomViewInstance = async (): Promise<_RoomView> => { const getRoomViewInstance = async (): Promise<React.ComponentRef<typeof _RoomView>> => {
const ref = createRef<_RoomView>(); const ref = createRef<React.ComponentRef<typeof _RoomView>>();
await mountRoomView(ref); await mountRoomView(ref);
return ref.current!; return ref.current!;
}; };
@@ -197,7 +197,7 @@ describe("RoomView", () => {
}); });
describe("when there is an old room", () => { describe("when there is an old room", () => {
let instance: _RoomView; let instance: React.ComponentRef<typeof _RoomView>;
let oldRoom: Room; let oldRoom: Room;
beforeEach(async () => { beforeEach(async () => {
@@ -596,7 +596,7 @@ describe("RoomView", () => {
const eventMapper = (obj: Partial<IEvent>) => new MatrixEvent(obj); const eventMapper = (obj: Partial<IEvent>) => new MatrixEvent(obj);
const roomViewRef = createRef<_RoomView>(); const roomViewRef = createRef<React.ComponentRef<typeof _RoomView>>();
const { container, getByText, findByLabelText } = await mountRoomView(roomViewRef); const { container, getByText, findByLabelText } = await mountRoomView(roomViewRef);
// @ts-ignore - triggering a search organically is a lot of work // @ts-ignore - triggering a search organically is a lot of work
roomViewRef.current!.setState({ roomViewRef.current!.setState({
@@ -657,7 +657,7 @@ describe("RoomView", () => {
const eventMapper = (obj: Partial<IEvent>) => new MatrixEvent(obj); const eventMapper = (obj: Partial<IEvent>) => new MatrixEvent(obj);
const roomViewRef = createRef<_RoomView>(); const roomViewRef = createRef<React.ComponentRef<typeof _RoomView>>();
const { container, getByText, findByLabelText } = await mountRoomView(roomViewRef); const { container, getByText, findByLabelText } = await mountRoomView(roomViewRef);
// @ts-ignore - triggering a search organically is a lot of work // @ts-ignore - triggering a search organically is a lot of work
roomViewRef.current!.setState({ roomViewRef.current!.setState({

View File

@@ -59,7 +59,7 @@ describe("MemberList", () => {
let client: MatrixClient; let client: MatrixClient;
let root: RenderResult; let root: RenderResult;
let memberListRoom: Room; let memberListRoom: Room;
let memberList: MemberList; let memberList: React.ComponentRef<typeof MemberList>;
let adminUsers: RoomMember[] = []; let adminUsers: RoomMember[] = [];
let moderatorUsers: RoomMember[] = []; let moderatorUsers: RoomMember[] = [];
@@ -214,7 +214,7 @@ describe("MemberList", () => {
memberListRoom.currentState.members[member.userId] = member; memberListRoom.currentState.members[member.userId] = member;
} }
const gatherWrappedRef = (r: MemberList) => { const gatherWrappedRef = (r: React.ComponentRef<typeof MemberList>) => {
memberList = r; memberList = r;
}; };
const context = new TestSdkContext(); const context = new TestSdkContext();