{ room={this.state.room} resizing={this.state.resizing} waitForCall={isVideoRoom(this.state.room)} - skipLobby={this.context.roomViewStore.skipCallLobby() ?? false} + skipLobby={this.props.context.roomViewStore.skipCallLobby() ?? false} role="main" /> {previewBar} @@ -2593,15 +2600,15 @@ export class RoomView extends React.Component { onAppsClick = null; onForgetClick = 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; } 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 = - !this.context.client.isGuest() && + !this.props.context.client.isGuest() && (([KnownMembership.Leave, KnownMembership.Ban] as Array).includes(myMembership) || myMember?.isKicked()); @@ -2665,4 +2672,6 @@ export class RoomView extends React.Component { } } -export default RoomView; +export default forwardRef>((props, ref) => ( + {(context) => } +)); diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index edc857edaf0..5fe4d7d8baf 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -19,7 +19,7 @@ import { KnownMembership } from "matrix-js-sdk/src/types"; import { logger } from "matrix-js-sdk/src/logger"; import React, { useCallback, useContext, useRef, useState } from "react"; -import MatrixClientContext from "../../contexts/MatrixClientContext"; +import MatrixClientContext, { MatrixClientProps, withMatrixClientHOC } from "../../contexts/MatrixClientContext"; import createRoom, { IOpts } from "../../createRoom"; import { shouldShowComponent } from "../../customisations/helpers/UIComponents"; import { Action } from "../../dispatcher/actions"; @@ -76,7 +76,7 @@ import RightPanel from "./RightPanel"; import SpaceHierarchy, { showRoom } from "./SpaceHierarchy"; import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks"; -interface IProps { +interface IProps extends MatrixClientProps { space: Room; justCreatedOpts?: IOpts; resizeNotifier: ResizeNotifier; @@ -603,19 +603,16 @@ const SpaceSetupPrivateInvite: React.FC<{ ); }; -export default class SpaceRoomView extends React.PureComponent { - public static contextType = MatrixClientContext; - public context!: React.ContextType; - +class SpaceRoomView extends React.PureComponent { private readonly dispatcherRef: string; - public constructor(props: IProps, context: React.ContextType) { - super(props, context); + public constructor(props: IProps) { + super(props); let phase = Phase.Landing; const creator = this.props.space.currentState.getStateEvents(EventType.RoomCreate, "")?.getSender(); - const showSetup = this.props.justCreatedOpts && context.getSafeUserId() === creator; + const showSetup = this.props.justCreatedOpts && props.mxClient.getSafeUserId() === creator; if (showSetup) { phase = @@ -635,13 +632,13 @@ export default class SpaceRoomView extends React.PureComponent { } public componentDidMount(): void { - this.context.on(RoomEvent.MyMembership, this.onMyMembership); + this.props.mxClient.on(RoomEvent.MyMembership, this.onMyMembership); } public componentWillUnmount(): void { defaultDispatcher.unregister(this.dispatcherRef); RightPanelStore.instance.off(UPDATE_EVENT, this.onRightPanelStoreUpdate); - this.context.off(RoomEvent.MyMembership, this.onMyMembership); + this.props.mxClient.off(RoomEvent.MyMembership, this.onMyMembership); } private onMyMembership = (room: Room, myMembership: string): void => { @@ -780,3 +777,5 @@ export default class SpaceRoomView extends React.PureComponent { ); } } + +export default withMatrixClientHOC(SpaceRoomView); diff --git a/src/components/structures/ThreadPanel.tsx b/src/components/structures/ThreadPanel.tsx index d1e83601747..7dab4d68967 100644 --- a/src/components/structures/ThreadPanel.tsx +++ b/src/components/structures/ThreadPanel.tsx @@ -230,7 +230,7 @@ const EmptyThread: React.FC = ({ hasThreads, filterOption, sh const ThreadPanel: React.FC = ({ roomId, onClose, permalinkCreator }) => { const mxClient = useContext(MatrixClientContext); const roomContext = useContext(RoomContext); - const timelinePanel = useRef(null); + const timelinePanel = useRef | null>(null); const card = useRef(null); const closeButonRef = useRef(null); diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx index a345faf9f74..57c77e1ed8e 100644 --- a/src/components/structures/ThreadView.tsx +++ b/src/components/structures/ThreadView.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { createRef, KeyboardEvent } from "react"; +import React, { createRef, forwardRef, KeyboardEvent } from "react"; import { Thread, THREAD_RELATION_TYPE, @@ -70,6 +70,7 @@ interface IProps { initialEvent?: MatrixEvent; isInitialEventHighlighted?: boolean; initialEventScrollIntoView?: boolean; + context: React.ContextType; } interface IState { @@ -81,13 +82,10 @@ interface IState { narrow: boolean; } -export default class ThreadView extends React.Component { - public static contextType = RoomContext; - public context!: React.ContextType; - +class ThreadView extends React.Component { private dispatcherRef: string | null = null; private readonly layoutWatcherRef: string; - private timelinePanel = createRef(); + private timelinePanel = createRef>(); private card = createRef(); // Set by setEventId in ctor. @@ -398,12 +396,12 @@ export default class ThreadView extends React.Component { { return ( { ); } } + +export default forwardRef>((props, ref) => ( + {(context) => } +)); diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx index 288c65972f1..f8dbccf28c3 100644 --- a/src/components/structures/TimelinePanel.tsx +++ b/src/components/structures/TimelinePanel.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { createRef, ReactNode } from "react"; +import React, { createRef, forwardRef, ReactNode } from "react"; import ReactDOM from "react-dom"; import { Room, @@ -166,6 +166,7 @@ interface IProps { hideThreadedMessages?: boolean; disableGrouping?: boolean; + context: React.ContextType; } interface IState { @@ -243,10 +244,7 @@ interface IEventIndexOpts { * * Also responsible for handling and sending read receipts. */ -class TimelinePanel extends React.Component { - public static contextType = RoomContext; - public context!: React.ContextType; - +export class TimelinePanel extends React.Component { // a map from room id to read marker event timestamp public static roomReadMarkerTsMap: Record = {}; @@ -263,7 +261,7 @@ class TimelinePanel extends React.Component { private lastRRSentEventId: string | null | undefined = undefined; private lastRMSentEventId: string | null | undefined = undefined; - private readonly messagePanel = createRef(); + private readonly messagePanel = createRef>(); private readonly dispatcherRef: string; private timelineWindow?: TimelineWindow; private overlayTimelineWindow?: TimelineWindow; @@ -275,9 +273,8 @@ class TimelinePanel extends React.Component { private callEventGroupers = new Map(); private initialReadMarkerId: string | null = null; - public constructor(props: IProps, context: React.ContextType) { - super(props, context); - this.context = context; + public constructor(props: IProps) { + super(props); debuglog("mounting"); @@ -499,7 +496,7 @@ class TimelinePanel extends React.Component { } logger.debug( - `TimelinePanel(${this.context.timelineRenderingType}): Debugging info for ${room?.roomId}\n` + + `TimelinePanel(${this.props.context.timelineRenderingType}): Debugging info for ${room?.roomId}\n` + `\tevents(${eventIdList.length})=${JSON.stringify(eventIdList)}\n` + `\trenderedEventIds(${renderedEventIds?.length ?? 0})=` + `${JSON.stringify(renderedEventIds)}\n` + @@ -735,7 +732,7 @@ class TimelinePanel extends React.Component { return; } - if (!Thread.hasServerSideSupport && this.context.timelineRenderingType === TimelineRenderingType.Thread) { + if (!Thread.hasServerSideSupport && this.props.context.timelineRenderingType === TimelineRenderingType.Thread) { if (toStartOfTimeline && !this.state.canBackPaginate) { this.setState({ canBackPaginate: true, @@ -997,8 +994,8 @@ class TimelinePanel extends React.Component { private readMarkerTimeout(readMarkerPosition: number | null): number { return readMarkerPosition === 0 - ? this.context?.readMarkerInViewThresholdMs ?? this.state.readMarkerInViewThresholdMs - : this.context?.readMarkerOutOfViewThresholdMs ?? this.state.readMarkerOutOfViewThresholdMs; + ? this.props.context?.readMarkerInViewThresholdMs ?? this.state.readMarkerInViewThresholdMs + : this.props.context?.readMarkerOutOfViewThresholdMs ?? this.state.readMarkerOutOfViewThresholdMs; } private async updateReadMarkerOnUserActivity(): Promise { @@ -1775,8 +1772,8 @@ class TimelinePanel extends React.Component { pendingEvents, ); - if (this.context.timelineRenderingType === TimelineRenderingType.Thread) { - return threadId === this.context.threadId; + if (this.props.context.timelineRenderingType === TimelineRenderingType.Thread) { + return threadId === this.props.context.threadId; } { return shouldLiveInRoom; @@ -1807,7 +1804,7 @@ class TimelinePanel extends React.Component { const room = this.props.timelineSet.room; const isThreadTimeline = [TimelineRenderingType.Thread, TimelineRenderingType.ThreadsList].includes( - this.context.timelineRenderingType, + this.props.context.timelineRenderingType, ); if (events.length === 0 || !room || !cli.isRoomEncrypted(room.roomId) || isThreadTimeline) { logger.debug("checkForPreJoinUISI: showing all messages, skipping check"); @@ -1876,7 +1873,7 @@ class TimelinePanel extends React.Component { /* Threads do not have server side support for read receipts and the concept is very tied to the main room timeline, we are forcing the timeline to send read receipts for threaded events */ - if (this.context.timelineRenderingType === TimelineRenderingType.Thread) { + if (this.props.context.timelineRenderingType === TimelineRenderingType.Thread) { return 0; } const index = this.state.events.findIndex((ev) => ev.getId() === evId); @@ -1976,7 +1973,7 @@ class TimelinePanel extends React.Component { !!ev.status || // local echo (ignoreOwn && ev.getSender() === myUserId); // own message const isWithoutTile = - !haveRendererForEvent(ev, MatrixClientPeg.safeGet(), this.context?.showHiddenEvents) || + !haveRendererForEvent(ev, MatrixClientPeg.safeGet(), this.props.context?.showHiddenEvents) || shouldHideEvent(ev, this.context); if (isWithoutTile || !node) { @@ -2142,10 +2139,10 @@ class TimelinePanel extends React.Component { onScroll={this.onMessageListScroll} onFillRequest={this.onMessageListFillRequest} onUnfillRequest={this.onMessageListUnfillRequest} - isTwelveHour={this.context?.showTwelveHourTimestamps ?? this.state.isTwelveHour} + isTwelveHour={this.props.context?.showTwelveHourTimestamps ?? this.state.isTwelveHour} alwaysShowTimestamps={ this.props.alwaysShowTimestamps ?? - this.context?.alwaysShowTimestamps ?? + this.props.context?.alwaysShowTimestamps ?? this.state.alwaysShowTimestamps } className={this.props.className} @@ -2188,4 +2185,6 @@ function serializeEventIdsFromTimelineSets(timelineSets: EventTimelineSet[]): { return serializedEventIdsInTimelineSet; } -export default TimelinePanel; +export default forwardRef>((props, ref) => ( + {(context) => } +)); diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index f24fa57d7d8..1a65e70b4c6 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and 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 { MatrixClientPeg } from "../../MatrixClientPeg"; @@ -56,6 +56,7 @@ import { shouldShowFeedback } from "../../utils/Feedback"; interface IProps { isPanelCollapsed: boolean; children?: ReactNode; + context: React.ContextType; } type PartialDOMRect = Pick; @@ -84,25 +85,21 @@ const below = (rect: PartialDOMRect): MenuProps => { }; }; -export default class UserMenu extends React.Component { - public static contextType = SDKContext; - public context!: React.ContextType; - +class UserMenu extends React.Component { private dispatcherRef?: string; private themeWatcherRef?: string; private readonly dndWatcherRef?: string; private buttonRef: React.RefObject = createRef(); - public constructor(props: IProps, context: React.ContextType) { - super(props, context); + public constructor(props: IProps) { + super(props); - this.context = context; this.state = { contextMenuPosition: null, isDarkTheme: this.isUserOnDarkTheme(), isHighContrast: this.isUserOnHighContrastTheme(), selectedSpace: SpaceStore.instance.activeSpaceRoom, - showLiveAvatarAddon: this.context.voiceBroadcastRecordingsStore.hasCurrent(), + showLiveAvatarAddon: this.props.context.voiceBroadcastRecordingsStore.hasCurrent(), }; OwnProfileStore.instance.on(UPDATE_EVENT, this.onProfileUpdate); @@ -110,7 +107,7 @@ export default class UserMenu extends React.Component { } 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 => { @@ -120,7 +117,7 @@ export default class UserMenu extends React.Component { }; public componentDidMount(): void { - this.context.voiceBroadcastRecordingsStore.on( + this.props.context.voiceBroadcastRecordingsStore.on( VoiceBroadcastRecordingsStoreEvent.CurrentChanged, this.onCurrentVoiceBroadcastRecordingChanged, ); @@ -134,7 +131,7 @@ export default class UserMenu extends React.Component { if (this.dispatcherRef) defaultDispatcher.unregister(this.dispatcherRef); OwnProfileStore.instance.off(UPDATE_EVENT, this.onProfileUpdate); SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdate); - this.context.voiceBroadcastRecordingsStore.off( + this.props.context.voiceBroadcastRecordingsStore.off( VoiceBroadcastRecordingsStoreEvent.CurrentChanged, this.onCurrentVoiceBroadcastRecordingChanged, ); @@ -496,3 +493,7 @@ export default class UserMenu extends React.Component { ); } } + +export default forwardRef>((props, ref) => ( + {(context) => } +)); diff --git a/src/components/structures/UserView.tsx b/src/components/structures/UserView.tsx index 4d5dd258e01..846dcc2b11c 100644 --- a/src/components/structures/UserView.tsx +++ b/src/components/structures/UserView.tsx @@ -27,9 +27,9 @@ import Spinner from "../views/elements/Spinner"; import ResizeNotifier from "../../utils/ResizeNotifier"; import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases"; import { UserOnboardingPage } from "../views/user-onboarding/UserOnboardingPage"; -import MatrixClientContext from "../../contexts/MatrixClientContext"; +import { MatrixClientProps, withMatrixClientHOC } from "../../contexts/MatrixClientContext"; -interface IProps { +interface IProps extends MatrixClientProps { userId: string; resizeNotifier: ResizeNotifier; } @@ -39,10 +39,7 @@ interface IState { member?: RoomMember; } -export default class UserView extends React.Component { - public static contextType = MatrixClientContext; - public context!: React.ContextType; - +class UserView extends React.Component { public constructor(props: IProps) { super(props); this.state = { @@ -69,7 +66,7 @@ export default class UserView extends React.Component { this.setState({ loading: true }); let profileInfo: Awaited>; try { - profileInfo = await this.context.getProfileInfo(this.props.userId); + profileInfo = await this.props.mxClient.getProfileInfo(this.props.userId); } catch (err) { Modal.createDialog(ErrorDialog, { title: _t("error_dialog|error_loading_user_profile"), @@ -105,3 +102,5 @@ export default class UserView extends React.Component { } } } + +export default withMatrixClientHOC(UserView); diff --git a/src/components/structures/WaitingForThirdPartyRoomView.tsx b/src/components/structures/WaitingForThirdPartyRoomView.tsx index 1b61abb3bb8..2b16642718f 100644 --- a/src/components/structures/WaitingForThirdPartyRoomView.tsx +++ b/src/components/structures/WaitingForThirdPartyRoomView.tsx @@ -26,7 +26,7 @@ import RoomHeader from "../views/rooms/RoomHeader"; import ScrollPanel from "./ScrollPanel"; import EventTileBubble from "../views/messages/EventTileBubble"; import NewRoomIntro from "../views/rooms/NewRoomIntro"; -import { UnwrappedEventTile } from "../views/rooms/EventTile"; +import EventTile from "../views/rooms/EventTile"; import { _t } from "../../languageHandler"; import SdkConfig from "../../SdkConfig"; import SettingsStore from "../../settings/SettingsStore"; @@ -53,7 +53,7 @@ export const WaitingForThirdPartyRoomView: React.FC = ({ roomView, resize ) : ( = ({ roomView, resize subtitle={_t("room|waiting_for_join_subtitle", { brand })} /> - +