From 9fe036ae1d37595e76a0b46b81e9f53f17b99c78 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 19 Apr 2024 14:31:48 +0100 Subject: [PATCH] Remove legacy consumers of the RoomContext in favour of HOCs Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/FilePanel.tsx | 13 +-- src/components/structures/MessagePanel.tsx | 63 ++++++++------ .../structures/NotificationPanel.tsx | 15 ++-- src/components/structures/RoomView.tsx | 8 +- src/components/structures/ThreadPanel.tsx | 2 +- src/components/structures/ThreadView.tsx | 18 ++-- src/components/structures/TimelinePanel.tsx | 31 ++++--- .../WaitingForThirdPartyRoomView.tsx | 6 +- .../structures/grouper/BaseGrouper.ts | 4 +- .../structures/grouper/CreationGrouper.tsx | 5 +- .../structures/grouper/MainGrouper.tsx | 7 +- .../context_menus/MessageContextMenu.tsx | 20 +++-- .../views/elements/EventListSummary.tsx | 18 ++-- src/components/views/elements/ReplyChain.tsx | 8 +- .../views/emojipicker/ReactionPicker.tsx | 24 +++--- src/components/views/messages/CallEvent.tsx | 2 +- src/components/views/messages/IBodyProps.ts | 3 - src/components/views/messages/MAudioBody.tsx | 23 +++-- src/components/views/messages/MFileBody.tsx | 24 +++--- src/components/views/messages/MImageBody.tsx | 31 ++++--- .../views/messages/MImageReplyBody.tsx | 18 +++- .../views/messages/MStickerBody.tsx | 16 +++- src/components/views/messages/MVideoBody.tsx | 23 +++-- .../views/messages/MVoiceMessageBody.tsx | 18 +++- .../views/messages/MessageActionBar.tsx | 25 +++--- .../views/messages/MessageEvent.tsx | 22 ++--- .../views/messages/ReactionsRow.tsx | 27 +++--- src/components/views/messages/TextualBody.tsx | 19 +++-- .../views/messages/TextualEvent.tsx | 13 +-- .../views/right_panel/TimelineCard.tsx | 17 ++-- src/components/views/rooms/Autocomplete.tsx | 13 +-- .../views/rooms/BasicMessageComposer.tsx | 2 +- .../views/rooms/EditMessageComposer.tsx | 39 +++++---- src/components/views/rooms/EventTile.tsx | 83 ++++++++++--------- .../views/rooms/LegacyRoomHeader.tsx | 23 +++-- .../views/rooms/MessageComposer.tsx | 40 ++++----- src/components/views/rooms/ReplyPreview.tsx | 13 +-- src/components/views/rooms/ReplyTile.tsx | 6 +- .../views/rooms/SearchResultTile.tsx | 24 +++--- .../views/rooms/SendMessageComposer.tsx | 49 ++++++----- .../views/rooms/VoiceRecordComposerTile.tsx | 15 ++-- .../components/WysiwygAutocomplete.tsx | 2 +- .../components/WysiwygComposer.tsx | 2 +- .../hooks/useInputEventProcessor.ts | 4 +- .../hooks/usePlainTextListeners.ts | 4 +- .../rooms/wysiwyg_composer/hooks/utils.ts | 2 +- src/editor/autocomplete.ts | 2 +- src/events/EventTileFactory.tsx | 7 +- .../structures/TimelinePanel-test.tsx | 10 +-- .../views/rooms/LegacyRoomHeader-test.tsx | 9 +- .../rooms/VoiceRecordComposerTile-test.tsx | 2 +- .../components/WysiwygAutocomplete-test.tsx | 2 +- 52 files changed, 509 insertions(+), 367 deletions(-) diff --git a/src/components/structures/FilePanel.tsx b/src/components/structures/FilePanel.tsx index 3836863431..a1d4808b02 100644 --- a/src/components/structures/FilePanel.tsx +++ b/src/components/structures/FilePanel.tsx @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { createRef } from "react"; +import React, { createRef, forwardRef } from "react"; import { Filter, EventTimelineSet, @@ -45,6 +45,7 @@ interface IProps { roomId: string; onClose: () => void; resizeNotifier: ResizeNotifier; + context: React.ContextType; } interface IState { @@ -56,8 +57,6 @@ interface IState { * Component which shows the filtered file using a TimelinePanel */ class FilePanel extends React.Component { - public static contextType = RoomContext; - // This is used to track if a decrypted event was a live event and should be // added to the timeline. private decryptingEvents = new Set(); @@ -267,7 +266,7 @@ class FilePanel extends React.Component { return ( { return ( @@ -312,4 +311,6 @@ class FilePanel extends React.Component { } } -export default FilePanel; +export default forwardRef>((props, ref) => ( + {(context) => } +)); diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx index 3a97f27b38..121577855c 100644 --- a/src/components/structures/MessagePanel.tsx +++ b/src/components/structures/MessagePanel.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { createRef, ReactNode, TransitionEvent } from "react"; +import React, { createRef, forwardRef, ReactNode, TransitionEvent } from "react"; import ReactDOM from "react-dom"; import classNames from "classnames"; import { Room, MatrixClient, RoomStateEvent, EventStatus, MatrixEvent, EventType } from "matrix-js-sdk/src/matrix"; @@ -31,7 +31,6 @@ import EventTile, { GetRelationsForEvent, IReadReceiptProps, isEligibleForSpecialReceipt, - UnwrappedEventTile, } from "../views/rooms/EventTile"; import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer"; import defaultDispatcher from "../../dispatcher/dispatcher"; @@ -188,6 +187,7 @@ interface IProps { disableGrouping?: boolean; callEventGroupers: Map; + context: React.ContextType; } interface IState { @@ -203,10 +203,7 @@ interface IReadReceiptForUser { /* (almost) stateless UI component which builds the event tiles in the room timeline. */ -export default class MessagePanel extends React.Component { - public static contextType = RoomContext; - public context!: React.ContextType; - +class MessagePanel extends React.Component { public static defaultProps = { disableGrouping: false, }; @@ -256,13 +253,13 @@ export default class MessagePanel extends React.Component { private scrollPanel = createRef(); private readonly showTypingNotificationsWatcherRef: string; - private eventTiles: Record = {}; + private eventTiles: Record> = {}; // A map to allow groupers to maintain consistent keys even if their first event is uprooted due to back-pagination. public grouperKeyMap = new WeakMap(); - public constructor(props: IProps, context: React.ContextType) { - super(props, context); + public constructor(props: IProps) { + super(props); this.state = { // previous positions the read marker has been in, so we can @@ -320,7 +317,7 @@ export default class MessagePanel extends React.Component { defaultDispatcher.dispatch({ action: Action.EditEvent, event: !event?.isRedacted() ? event : null, - timelineRenderingType: this.context.timelineRenderingType, + timelineRenderingType: this.props.context.timelineRenderingType, }); } } @@ -354,7 +351,7 @@ export default class MessagePanel extends React.Component { return this.eventTiles[eventId]?.ref?.current ?? undefined; } - public getTileForEventId(eventId?: string): UnwrappedEventTile | undefined { + public getTileForEventId(eventId?: string): React.ComponentRef | undefined { if (!this.eventTiles || !eventId) { return undefined; } @@ -454,7 +451,7 @@ export default class MessagePanel extends React.Component { }; public get showHiddenEvents(): boolean { - return this.context?.showHiddenEvents ?? this._showHiddenEvents; + return this.props.context?.showHiddenEvents ?? this._showHiddenEvents; } // TODO: Implement granular (per-room) hide options @@ -481,7 +478,7 @@ export default class MessagePanel extends React.Component { // Always show highlighted event if (this.props.highlightedEventId === mxEv.getId()) return true; - return !shouldHideEvent(mxEv, this.context); + return !shouldHideEvent(mxEv, this.props.context); } public readMarkerForEvent(eventId: string, isLastEvent: boolean): ReactNode { @@ -592,7 +589,9 @@ export default class MessagePanel extends React.Component { } try { - return localStorage.getItem(editorRoomKey(this.props.room.roomId, this.context.timelineRenderingType)); + return localStorage.getItem( + editorRoomKey(this.props.room.roomId, this.props.context.timelineRenderingType), + ); } catch (err) { logger.error(err); return null; @@ -775,13 +774,25 @@ export default class MessagePanel extends React.Component { willWantSeparator === SeparatorKind.Date || mxEv.getSender() !== nextEv.getSender() || getEventDisplayInfo(cli, nextEv, this.showHiddenEvents).isInfoMessage || - !shouldFormContinuation(mxEv, nextEv, cli, this.showHiddenEvents, this.context.timelineRenderingType); + !shouldFormContinuation( + mxEv, + nextEv, + cli, + this.showHiddenEvents, + this.props.context.timelineRenderingType, + ); } // is this a continuation of the previous message? const continuation = wantsSeparator === SeparatorKind.None && - shouldFormContinuation(prevEvent, mxEv, cli, this.showHiddenEvents, this.context.timelineRenderingType); + shouldFormContinuation( + prevEvent, + mxEv, + cli, + this.showHiddenEvents, + this.props.context.timelineRenderingType, + ); const eventId = mxEv.getId()!; const highlight = eventId === this.props.highlightedEventId; @@ -826,7 +837,7 @@ export default class MessagePanel extends React.Component { } public wantsSeparator(prevEvent: MatrixEvent | null, mxEvent: MatrixEvent): SeparatorKind { - if (this.context.timelineRenderingType === TimelineRenderingType.ThreadsList) { + if (this.props.context.timelineRenderingType === TimelineRenderingType.ThreadsList) { return SeparatorKind.None; } @@ -863,13 +874,13 @@ export default class MessagePanel extends React.Component { return null; } - const receiptDestination = this.context.threadId ? room.getThread(this.context.threadId) : room; + const receiptDestination = this.props.context.threadId ? room.getThread(this.props.context.threadId) : room; const receipts: IReadReceiptProps[] = []; if (!receiptDestination) { logger.debug( - "Discarding request, could not find the receiptDestination for event: " + this.context.threadId, + "Discarding request, could not find the receiptDestination for event: " + this.props.context.threadId, ); return receipts; } @@ -950,7 +961,7 @@ export default class MessagePanel extends React.Component { return receiptsByEvent; } - private collectEventTile = (eventId: string, node: UnwrappedEventTile): void => { + private collectEventTile = (eventId: string, node: React.ComponentRef): void => { this.eventTiles[eventId] = node; }; @@ -1033,7 +1044,7 @@ export default class MessagePanel extends React.Component { if ( this.props.room && this.state.showTypingNotifications && - this.context.timelineRenderingType === TimelineRenderingType.Room + this.props.context.timelineRenderingType === TimelineRenderingType.Room ) { whoIsTyping = ( { } const classes = classNames(this.props.className, { - mx_MessagePanel_narrow: this.context.narrow, + mx_MessagePanel_narrow: this.props.context.narrow, }); return ( @@ -1079,10 +1090,14 @@ export default class MessagePanel extends React.Component { } } +export default forwardRef>((props, ref) => ( + {(context) => } +)); + /** - * Holds on to an event, caching the information about it in the context of the current messages list. + * Holds on to an event, caching the information about it in the props.context. of the current messages list. * Avoids calling shouldShowEvent more times than we need to. - * Simplifies threading of event context like whether it's the last successful event we sent which cannot be determined + * Simplifies threading of event props.context. like whether it's the last successful event we sent which cannot be determined * by a consumer from the event alone, so has to be done by the event list processing code earlier. */ export interface WrappedEvent { diff --git a/src/components/structures/NotificationPanel.tsx b/src/components/structures/NotificationPanel.tsx index 0da27a19b1..082e01d9f9 100644 --- a/src/components/structures/NotificationPanel.tsx +++ b/src/components/structures/NotificationPanel.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { forwardRef } from "react"; import { logger } from "matrix-js-sdk/src/logger"; import { _t } from "../../languageHandler"; @@ -29,6 +29,7 @@ import Heading from "../views/typography/Heading"; interface IProps { onClose(): void; + context: React.ContextType; } interface IState { @@ -38,9 +39,7 @@ interface IState { /* * Component which shows the global notification list using a TimelinePanel */ -export default class NotificationPanel extends React.PureComponent { - public static contextType = RoomContext; - +class NotificationPanel extends React.PureComponent { private card = React.createRef(); public constructor(props: IProps) { @@ -86,7 +85,7 @@ export default class NotificationPanel extends React.PureComponent>((props, ref) => ( + + {(context) => } + +)); diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index d23eafc625..1e171162ed 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -323,7 +323,7 @@ function LocalRoomView(props: LocalRoomViewProps): ReactElement { ) : ( { private roomView = createRef(); private searchResultsPanel = createRef(); - private messagePanel: TimelinePanel | null = null; + private messagePanel: React.ComponentRef | null = null; private roomViewBody = createRef(); public constructor(props: IRoomProps) { @@ -1958,7 +1958,7 @@ class RoomView extends React.Component { * We pass it down to the scroll panel. */ public handleScrollKey = (ev: React.KeyboardEvent | KeyboardEvent): void => { - let panel: ScrollPanel | TimelinePanel | undefined; + let panel: ScrollPanel | React.ComponentRef | undefined; if (this.searchResultsPanel.current) { panel = this.searchResultsPanel.current; } else if (this.messagePanel) { @@ -1980,7 +1980,7 @@ class RoomView extends React.Component { // this has to be a proper method rather than an unnamed function, // otherwise react calls it with null on each update. - private gatherTimelinePanelRef = (r: TimelinePanel | null): void => { + private gatherTimelinePanelRef = (r: React.ComponentRef | null): void => { this.messagePanel = r; }; diff --git a/src/components/structures/ThreadPanel.tsx b/src/components/structures/ThreadPanel.tsx index d1e8360174..7dab4d6896 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 a345faf9f7..1a29fd61c8 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 { { ); } } + +export default forwardRef>((props, ref) => ( + {(context) => } +)); diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx index ba3c4d203b..8010304ac6 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, @@ -167,6 +167,7 @@ interface IProps { hideThreadedMessages?: boolean; disableGrouping?: boolean; + context: React.ContextType; } interface IState { @@ -244,10 +245,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 = {}; @@ -264,7 +262,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; @@ -276,9 +274,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"); @@ -500,7 +497,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` + @@ -736,7 +733,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, @@ -1780,8 +1777,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; @@ -1812,7 +1809,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"); @@ -1881,7 +1878,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); @@ -2193,4 +2190,6 @@ function serializeEventIdsFromTimelineSets(timelineSets: EventTimelineSet[]): { return serializedEventIdsInTimelineSet; } -export default TimelinePanel; +export default forwardRef>((props, ref) => ( + {(context) => } +)); diff --git a/src/components/structures/WaitingForThirdPartyRoomView.tsx b/src/components/structures/WaitingForThirdPartyRoomView.tsx index 1b61abb3bb..2b16642718 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 })} /> - + diff --git a/src/components/structures/grouper/BaseGrouper.ts b/src/components/structures/grouper/BaseGrouper.ts index 5821797ad5..57b07e4f2b 100644 --- a/src/components/structures/grouper/BaseGrouper.ts +++ b/src/components/structures/grouper/BaseGrouper.ts @@ -31,7 +31,7 @@ import MessagePanel, { WrappedEvent } from "../MessagePanel"; * when determining things such as whether a date separator is necessary */ export abstract class BaseGrouper { - public static canStartGroup = (_panel: MessagePanel, _ev: WrappedEvent): boolean => true; + public static canStartGroup = (_panel: React.ComponentRef, _ev: WrappedEvent): boolean => true; public events: WrappedEvent[] = []; // events that we include in the group but then eject out and place above the group. @@ -39,7 +39,7 @@ export abstract class BaseGrouper { public readMarker: ReactNode; public constructor( - public readonly panel: MessagePanel, + public readonly panel: React.ComponentRef, public readonly firstEventAndShouldShow: WrappedEvent, public readonly prevEvent: MatrixEvent | null, public readonly lastShownEvent: MatrixEvent | undefined, diff --git a/src/components/structures/grouper/CreationGrouper.tsx b/src/components/structures/grouper/CreationGrouper.tsx index fa91a1bd90..bcb5552968 100644 --- a/src/components/structures/grouper/CreationGrouper.tsx +++ b/src/components/structures/grouper/CreationGrouper.tsx @@ -33,7 +33,10 @@ import { SeparatorKind } from "../../views/messages/TimelineSeparator"; // the first non-state event, beacon_info event or membership event which is not regarding the sender of the `m.room.create` event export class CreationGrouper extends BaseGrouper { - public static canStartGroup = function (_panel: MessagePanel, { event }: WrappedEvent): boolean { + public static canStartGroup = function ( + _panel: React.ComponentRef, + { event }: WrappedEvent, + ): boolean { return event.getType() === EventType.RoomCreate; }; diff --git a/src/components/structures/grouper/MainGrouper.tsx b/src/components/structures/grouper/MainGrouper.tsx index 28a62d7ac9..721bc93c9c 100644 --- a/src/components/structures/grouper/MainGrouper.tsx +++ b/src/components/structures/grouper/MainGrouper.tsx @@ -36,7 +36,10 @@ const groupedStateEvents = [ // Wrap consecutive grouped events in a ListSummary export class MainGrouper extends BaseGrouper { - public static canStartGroup = function (panel: MessagePanel, { event: ev, shouldShow }: WrappedEvent): boolean { + public static canStartGroup = function ( + panel: React.ComponentRef, + { event: ev, shouldShow }: WrappedEvent, + ): boolean { if (!shouldShow) return false; if (ev.isState() && groupedStateEvents.includes(ev.getType() as EventType)) { @@ -55,7 +58,7 @@ export class MainGrouper extends BaseGrouper { }; public constructor( - public readonly panel: MessagePanel, + public readonly panel: React.ComponentRef, public readonly firstEventAndShouldShow: WrappedEvent, public readonly prevEvent: MatrixEvent | null, public readonly lastShownEvent: MatrixEvent | undefined, diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index e0fca0a4c0..9c87d73894 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { createRef, useContext } from "react"; +import React, { createRef, forwardRef, useContext } from "react"; import { EventStatus, MatrixEvent, @@ -123,6 +123,7 @@ interface IProps extends MenuProps { link?: string; getRelationsForEvent?: GetRelationsForEvent; + context: React.ContextType; } interface IState { @@ -131,11 +132,8 @@ interface IState { reactionPickerDisplayed: boolean; } -export default class MessageContextMenu extends React.Component { - public static contextType = RoomContext; - public context!: React.ContextType; - - private reactButtonRef = createRef(); // XXX Ref to a functional component +class MessageContextMenu extends React.Component { + private reactButtonRef = createRef(); public constructor(props: IProps) { super(props); @@ -315,7 +313,7 @@ export default class MessageContextMenu extends React.Component editEvent( MatrixClientPeg.safeGet(), this.props.mxEvent, - this.context.timelineRenderingType, + this.props.context.timelineRenderingType, this.props.getRelationsForEvent, ); this.closeMenu(); @@ -325,7 +323,7 @@ export default class MessageContextMenu extends React.Component dis.dispatch({ action: "reply_to_event", event: this.props.mxEvent, - context: this.context.timelineRenderingType, + context: this.props.context.timelineRenderingType, }); this.closeMenu(); }; @@ -733,3 +731,9 @@ export default class MessageContextMenu extends React.Component ); } } + +export default forwardRef>((props, ref) => ( + + {(context) => } + +)); diff --git a/src/components/views/elements/EventListSummary.tsx b/src/components/views/elements/EventListSummary.tsx index a1270427cc..932f9d992a 100644 --- a/src/components/views/elements/EventListSummary.tsx +++ b/src/components/views/elements/EventListSummary.tsx @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ComponentProps, ReactNode } from "react"; +import React, { ComponentProps, forwardRef, ReactNode } from "react"; import { MatrixEvent, RoomMember, EventType } from "matrix-js-sdk/src/matrix"; import { KnownMembership } from "matrix-js-sdk/src/types"; @@ -43,7 +43,8 @@ interface IProps extends Omit, "s // The maximum number of avatars to display in the summary avatarsMaxLength?: number; // The currently selected layout - layout: Layout; + layout?: Layout; + context: React.ContextType; } interface IUserEvents { @@ -77,12 +78,9 @@ enum TransitionType { const SEP = ","; -export default class EventListSummary extends React.Component< +class EventListSummary extends React.Component< IProps & Required> > { - public static contextType = RoomContext; - public context!: React.ContextType; - public static defaultProps = { summaryLength: 1, threshold: 3, @@ -527,7 +525,7 @@ export default class EventListSummary extends React.Component< let displayName = userKey; if (e.isRedacted()) { - const sender = this.context?.room?.getMember(userKey); + const sender = this.props.context?.room?.getMember(userKey); if (sender) { displayName = sender.name; latestUserAvatarMember.set(userKey, sender); @@ -569,3 +567,9 @@ export default class EventListSummary extends React.Component< ); } } + +export default forwardRef>((props, ref) => ( + + {(context) => } + +)); diff --git a/src/components/views/elements/ReplyChain.tsx b/src/components/views/elements/ReplyChain.tsx index b7e833a629..ae3d31a035 100644 --- a/src/components/views/elements/ReplyChain.tsx +++ b/src/components/views/elements/ReplyChain.tsx @@ -31,7 +31,6 @@ import ReplyTile from "../rooms/ReplyTile"; import { Pill, PillType } from "./Pill"; import AccessibleButton from "./AccessibleButton"; import { getParentEventId, shouldDisplayReply } from "../../../utils/Reply"; -import RoomContext from "../../../contexts/RoomContext"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { GetRelationsForEvent } from "../rooms/EventTile"; @@ -72,15 +71,12 @@ interface IState { // craft event_id's, using a homeserver that generates predictable event IDs; even then the impact would // be low as each event being loaded (after the first) is triggered by an explicit user action. export default class ReplyChain extends React.Component { - public static contextType = RoomContext; - public context!: React.ContextType; - private unmounted = false; private room: Room; private blockquoteRef = React.createRef(); - public constructor(props: IProps, context: React.ContextType) { - super(props, context); + public constructor(props: IProps) { + super(props); this.state = { events: [], diff --git a/src/components/views/emojipicker/ReactionPicker.tsx b/src/components/views/emojipicker/ReactionPicker.tsx index 075a6e6cee..4a671a8b12 100644 --- a/src/components/views/emojipicker/ReactionPicker.tsx +++ b/src/components/views/emojipicker/ReactionPicker.tsx @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { forwardRef } from "react"; import { MatrixEvent, EventType, RelationType, Relations, RelationsEvent } from "matrix-js-sdk/src/matrix"; import EmojiPicker from "./EmojiPicker"; @@ -29,6 +29,7 @@ interface IProps { mxEvent: MatrixEvent; reactions?: Relations | null | undefined; onFinished(): void; + context: React.ContextType; } interface IState { @@ -36,11 +37,8 @@ interface IState { } class ReactionPicker extends React.Component { - public static contextType = RoomContext; - public context!: React.ContextType; - - public constructor(props: IProps, context: React.ContextType) { - super(props, context); + public constructor(props: IProps) { + super(props); this.state = { selectedEmojis: new Set(Object.keys(this.getReactions())), @@ -95,12 +93,12 @@ class ReactionPicker extends React.Component { this.props.onFinished(); const myReactions = this.getReactions(); if (myReactions.hasOwnProperty(reaction)) { - if (this.props.mxEvent.isRedacted() || !this.context.canSelfRedact) return false; + if (this.props.mxEvent.isRedacted() || !this.props.context.canSelfRedact) return false; MatrixClientPeg.safeGet().redactEvent(this.props.mxEvent.getRoomId()!, myReactions[reaction]); dis.dispatch({ action: Action.FocusAComposer, - context: this.context.timelineRenderingType, + context: this.props.context.timelineRenderingType, }); // Tell the emoji picker not to bump this in the more frequently used list. return false; @@ -115,7 +113,7 @@ class ReactionPicker extends React.Component { dis.dispatch({ action: "message_sent" }); dis.dispatch({ action: Action.FocusAComposer, - context: this.context.timelineRenderingType, + context: this.props.context.timelineRenderingType, }); return true; } @@ -123,7 +121,7 @@ class ReactionPicker extends React.Component { private isEmojiDisabled = (unicode: string): boolean => { if (!this.getReactions()[unicode]) return false; - if (this.context.canSelfRedact) return false; + if (this.props.context.canSelfRedact) return false; return true; }; @@ -140,4 +138,8 @@ class ReactionPicker extends React.Component { } } -export default ReactionPicker; +export default forwardRef>((props, ref) => ( + + {(context) => } + +)); diff --git a/src/components/views/messages/CallEvent.tsx b/src/components/views/messages/CallEvent.tsx index e37217c422..ff3190f308 100644 --- a/src/components/views/messages/CallEvent.tsx +++ b/src/components/views/messages/CallEvent.tsx @@ -163,7 +163,7 @@ interface CallEventProps { /** * An event tile representing an active or historical Element call. */ -export const CallEvent = forwardRef(({ mxEvent }, ref) => { +export const CallEvent = forwardRef(({ mxEvent }, ref) => { const client = useContext(MatrixClientContext); const call = useCall(mxEvent.getRoomId()!); const latestEvent = client diff --git a/src/components/views/messages/IBodyProps.ts b/src/components/views/messages/IBodyProps.ts index fcc204dae3..54631f538b 100644 --- a/src/components/views/messages/IBodyProps.ts +++ b/src/components/views/messages/IBodyProps.ts @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { LegacyRef } from "react"; import { MatrixEvent } from "matrix-js-sdk/src/matrix"; import { MediaEventHelper } from "../../../utils/MediaEventHelper"; @@ -54,8 +53,6 @@ export interface IBodyProps { // helper function to access relations for this event getRelationsForEvent?: GetRelationsForEvent; - ref?: React.RefObject | LegacyRef; - // Set to `true` to disable interactions (e.g. video controls) and to remove controls from the tab order. // This may be useful when displaying a preview of the event. inhibitInteraction?: boolean; diff --git a/src/components/views/messages/MAudioBody.tsx b/src/components/views/messages/MAudioBody.tsx index de30b65f72..9bd613585f 100644 --- a/src/components/views/messages/MAudioBody.tsx +++ b/src/components/views/messages/MAudioBody.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { forwardRef } from "react"; import { logger } from "matrix-js-sdk/src/logger"; import { IContent } from "matrix-js-sdk/src/matrix"; import { MediaEventContent } from "matrix-js-sdk/src/types"; @@ -31,16 +31,17 @@ import { PlaybackQueue } from "../../../audio/PlaybackQueue"; import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; import MediaProcessingError from "./shared/MediaProcessingError"; +interface Props extends IBodyProps { + context: React.ContextType; +} + interface IState { error?: boolean; playback?: Playback; } -export default class MAudioBody extends React.PureComponent { - public static contextType = RoomContext; - public context!: React.ContextType; - - public constructor(props: IBodyProps) { +export class MAudioBody extends React.PureComponent { + public constructor(props: Props) { super(props); this.state = {}; @@ -88,9 +89,9 @@ export default class MAudioBody extends React.PureComponent protected get showFileBody(): boolean { return ( - this.context.timelineRenderingType !== TimelineRenderingType.Room && - this.context.timelineRenderingType !== TimelineRenderingType.Pinned && - this.context.timelineRenderingType !== TimelineRenderingType.Search + this.props.context.timelineRenderingType !== TimelineRenderingType.Room && + this.props.context.timelineRenderingType !== TimelineRenderingType.Pinned && + this.props.context.timelineRenderingType !== TimelineRenderingType.Search ); } @@ -131,3 +132,7 @@ export default class MAudioBody extends React.PureComponent ); } } + +export default forwardRef>((props, ref) => ( + {(context) => } +)); diff --git a/src/components/views/messages/MFileBody.tsx b/src/components/views/messages/MFileBody.tsx index 12d4c80416..be11392658 100644 --- a/src/components/views/messages/MFileBody.tsx +++ b/src/components/views/messages/MFileBody.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { AllHTMLAttributes, createRef } from "react"; +import React, { AllHTMLAttributes, createRef, forwardRef } from "react"; import { logger } from "matrix-js-sdk/src/logger"; import { MediaEventContent } from "matrix-js-sdk/src/types"; @@ -97,17 +97,15 @@ export function computedStyle(element: HTMLElement | null): string { interface IProps extends IBodyProps { /* whether or not to show the default placeholder for the file. Defaults to true. */ - showGenericPlaceholder: boolean; + showGenericPlaceholder?: boolean; + context: React.ContextType; } interface IState { decryptedBlob?: Blob; } -export default class MFileBody extends React.Component { - public static contextType = RoomContext; - public context!: React.ContextType; - +class MFileBody extends React.Component { public static defaultProps = { showGenericPlaceholder: true, }; @@ -226,11 +224,11 @@ export default class MFileBody extends React.Component { let showDownloadLink = !this.props.showGenericPlaceholder || - (this.context.timelineRenderingType !== TimelineRenderingType.Room && - this.context.timelineRenderingType !== TimelineRenderingType.Search && - this.context.timelineRenderingType !== TimelineRenderingType.Pinned); + (this.props.context.timelineRenderingType !== TimelineRenderingType.Room && + this.props.context.timelineRenderingType !== TimelineRenderingType.Search && + this.props.context.timelineRenderingType !== TimelineRenderingType.Pinned); - if (this.context.timelineRenderingType === TimelineRenderingType.Thread) { + if (this.props.context.timelineRenderingType === TimelineRenderingType.Thread) { showDownloadLink = false; } @@ -348,7 +346,7 @@ export default class MFileBody extends React.Component { {_t("timeline|m.file|download_label", { text: this.linkText })} - {this.context.timelineRenderingType === TimelineRenderingType.File && ( + {this.props.context.timelineRenderingType === TimelineRenderingType.File && (
{this.content.info?.size ? fileSize(this.content.info.size) : ""}
@@ -368,3 +366,7 @@ export default class MFileBody extends React.Component { } } } + +export default forwardRef>((props, ref) => ( + {(context) => } +)); diff --git a/src/components/views/messages/MImageBody.tsx b/src/components/views/messages/MImageBody.tsx index 36f3a85168..2191a2cea4 100644 --- a/src/components/views/messages/MImageBody.tsx +++ b/src/components/views/messages/MImageBody.tsx @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ComponentProps, createRef, ReactNode } from "react"; +import React, { ComponentProps, createRef, forwardRef, ReactNode } from "react"; import { Blurhash } from "react-blurhash"; import classNames from "classnames"; import { CSSTransition, SwitchTransition } from "react-transition-group"; @@ -47,6 +47,10 @@ enum Placeholder { Blurhash, } +interface Props extends IBodyProps { + context: React.ContextType; +} + interface IState { contentUrl: string | null; thumbUrl: string | null; @@ -63,17 +67,14 @@ interface IState { placeholder: Placeholder; } -export default class MImageBody extends React.Component { - public static contextType = RoomContext; - public context!: React.ContextType; - +export class MImageBody extends React.Component { private unmounted = true; private image = createRef(); private timeout?: number; private sizeWatcher?: string; private reconnectedListener: ClientEventHandlerMap[ClientEvent.Sync]; - public constructor(props: IBodyProps) { + public constructor(props: Props) { super(props); this.reconnectedListener = createReconnectedListener(this.clearError); @@ -391,7 +392,9 @@ export default class MImageBody extends React.Component { protected getBanner(content: ImageContent): ReactNode { // Hide it for the threads list & the file panel where we show it as text anyway. if ( - [TimelineRenderingType.ThreadsList, TimelineRenderingType.File].includes(this.context.timelineRenderingType) + [TimelineRenderingType.ThreadsList, TimelineRenderingType.File].includes( + this.props.context.timelineRenderingType, + ) ) { return null; } @@ -601,11 +604,11 @@ export default class MImageBody extends React.Component { * link as the message action bar will fulfill that */ const hasMessageActionBar = - this.context.timelineRenderingType === TimelineRenderingType.Room || - this.context.timelineRenderingType === TimelineRenderingType.Pinned || - this.context.timelineRenderingType === TimelineRenderingType.Search || - this.context.timelineRenderingType === TimelineRenderingType.Thread || - this.context.timelineRenderingType === TimelineRenderingType.ThreadsList; + this.props.context.timelineRenderingType === TimelineRenderingType.Room || + this.props.context.timelineRenderingType === TimelineRenderingType.Pinned || + this.props.context.timelineRenderingType === TimelineRenderingType.Search || + this.props.context.timelineRenderingType === TimelineRenderingType.Thread || + this.props.context.timelineRenderingType === TimelineRenderingType.ThreadsList; if (!hasMessageActionBar) { return ; } @@ -648,6 +651,10 @@ export default class MImageBody extends React.Component { } } +export default forwardRef>((props, ref) => ( + {(context) => } +)); + interface PlaceholderIProps { hover?: boolean; maxWidth?: number; diff --git a/src/components/views/messages/MImageReplyBody.tsx b/src/components/views/messages/MImageReplyBody.tsx index ded0d374a8..bccb717819 100644 --- a/src/components/views/messages/MImageReplyBody.tsx +++ b/src/components/views/messages/MImageReplyBody.tsx @@ -14,14 +14,20 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { forwardRef } from "react"; import { ImageContent } from "matrix-js-sdk/src/types"; -import MImageBody from "./MImageBody"; +import { MImageBody } from "./MImageBody"; +import { IBodyProps } from "./IBodyProps"; +import RoomContext from "../../../contexts/RoomContext"; const FORCED_IMAGE_HEIGHT = 44; -export default class MImageReplyBody extends MImageBody { +interface Props extends IBodyProps { + context: React.ContextType; +} + +class MImageReplyBody extends MImageBody { public onClick = (ev: React.MouseEvent): void => { ev.preventDefault(); }; @@ -43,3 +49,9 @@ export default class MImageReplyBody extends MImageBody { return
{thumbnail}
; } } + +export default forwardRef>((props, ref) => ( + + {(context) => } + +)); diff --git a/src/components/views/messages/MStickerBody.tsx b/src/components/views/messages/MStickerBody.tsx index 073da22b2a..f5d46f8037 100644 --- a/src/components/views/messages/MStickerBody.tsx +++ b/src/components/views/messages/MStickerBody.tsx @@ -14,14 +14,20 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ComponentProps, ReactNode } from "react"; +import React, { ComponentProps, forwardRef, ReactNode } from "react"; import { Tooltip } from "@vector-im/compound-web"; import { MediaEventContent } from "matrix-js-sdk/src/types"; -import MImageBody from "./MImageBody"; +import { MImageBody } from "./MImageBody"; import { BLURHASH_FIELD } from "../../../utils/image-media"; +import RoomContext from "../../../contexts/RoomContext"; +import { IBodyProps } from "./IBodyProps"; -export default class MStickerBody extends MImageBody { +interface Props extends IBodyProps { + context: React.ContextType; +} + +class MStickerBody extends MImageBody { // Mostly empty to prevent default behaviour of MImageBody protected onClick = (ev: React.MouseEvent): void => { ev.preventDefault(); @@ -83,3 +89,7 @@ export default class MStickerBody extends MImageBody { return null; // we don't need a banner, we have a tooltip } } + +export default forwardRef>((props, ref) => ( + {(context) => } +)); diff --git a/src/components/views/messages/MVideoBody.tsx b/src/components/views/messages/MVideoBody.tsx index be6ae4442c..a68e3b1907 100644 --- a/src/components/views/messages/MVideoBody.tsx +++ b/src/components/views/messages/MVideoBody.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ReactNode } from "react"; +import React, { forwardRef, ReactNode } from "react"; import { decode } from "blurhash"; import { MediaEventContent } from "matrix-js-sdk/src/types"; import { logger } from "matrix-js-sdk/src/logger"; @@ -30,6 +30,10 @@ import { ImageSize, suggestedSize as suggestedVideoSize } from "../../../setting import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; import MediaProcessingError from "./shared/MediaProcessingError"; +interface Props extends IBodyProps { + context: React.ContextType; +} + interface IState { decryptedUrl: string | null; decryptedThumbnailUrl: string | null; @@ -40,14 +44,11 @@ interface IState { blurhashUrl: string | null; } -export default class MVideoBody extends React.PureComponent { - public static contextType = RoomContext; - public context!: React.ContextType; - +class MVideoBody extends React.PureComponent { private videoRef = React.createRef(); private sizeWatcher?: string; - public constructor(props: IBodyProps) { + public constructor(props: Props) { super(props); this.state = { @@ -221,9 +222,9 @@ export default class MVideoBody extends React.PureComponent protected get showFileBody(): boolean { return ( - this.context.timelineRenderingType !== TimelineRenderingType.Room && - this.context.timelineRenderingType !== TimelineRenderingType.Pinned && - this.context.timelineRenderingType !== TimelineRenderingType.Search + this.props.context.timelineRenderingType !== TimelineRenderingType.Room && + this.props.context.timelineRenderingType !== TimelineRenderingType.Pinned && + this.props.context.timelineRenderingType !== TimelineRenderingType.Search ); } @@ -306,3 +307,7 @@ export default class MVideoBody extends React.PureComponent ); } } + +export default forwardRef>((props, ref) => ( + {(context) => } +)); diff --git a/src/components/views/messages/MVoiceMessageBody.tsx b/src/components/views/messages/MVoiceMessageBody.tsx index 096824e4b2..10fb5c810c 100644 --- a/src/components/views/messages/MVoiceMessageBody.tsx +++ b/src/components/views/messages/MVoiceMessageBody.tsx @@ -14,16 +14,22 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { forwardRef } from "react"; import InlineSpinner from "../elements/InlineSpinner"; import { _t } from "../../../languageHandler"; import RecordingPlayback from "../audio_messages/RecordingPlayback"; -import MAudioBody from "./MAudioBody"; +import { MAudioBody } from "./MAudioBody"; import MFileBody from "./MFileBody"; import MediaProcessingError from "./shared/MediaProcessingError"; +import { IBodyProps } from "./IBodyProps"; +import RoomContext from "../../../contexts/RoomContext"; -export default class MVoiceMessageBody extends MAudioBody { +interface Props extends IBodyProps { + context: React.ContextType; +} + +class MVoiceMessageBody extends MAudioBody { // A voice message is an audio file but rendered in a special way. public render(): React.ReactNode { if (this.state.error) { @@ -51,3 +57,9 @@ export default class MVoiceMessageBody extends MAudioBody { ); } } + +export default forwardRef>((props, ref) => ( + + {(context) => } + +)); diff --git a/src/components/views/messages/MessageActionBar.tsx b/src/components/views/messages/MessageActionBar.tsx index 9cf3592208..fa3df0ca03 100644 --- a/src/components/views/messages/MessageActionBar.tsx +++ b/src/components/views/messages/MessageActionBar.tsx @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ReactElement, useCallback, useContext, useEffect } from "react"; +import React, { forwardRef, ReactElement, useCallback, useContext, useEffect } from "react"; import { EventStatus, MatrixEvent, @@ -261,11 +261,10 @@ interface IMessageActionBarProps { toggleThreadExpanded: () => void; isQuoteExpanded?: boolean; getRelationsForEvent?: GetRelationsForEvent; + context: React.ContextType; } -export default class MessageActionBar extends React.PureComponent { - public static contextType = RoomContext; - +class MessageActionBar extends React.PureComponent { public componentDidMount(): void { if (this.props.mxEvent.status && this.props.mxEvent.status !== EventStatus.SENT) { this.props.mxEvent.on(MatrixEventEvent.Status, this.onSent); @@ -314,7 +313,7 @@ export default class MessageActionBar extends React.PureComponent>((props, ref) => ( + + {(context) => } + +)); diff --git a/src/components/views/messages/MessageEvent.tsx b/src/components/views/messages/MessageEvent.tsx index db0016de7b..853fd7f9a9 100644 --- a/src/components/views/messages/MessageEvent.tsx +++ b/src/components/views/messages/MessageEvent.tsx @@ -51,8 +51,8 @@ import { VoiceBroadcastBody, VoiceBroadcastInfoEventType, VoiceBroadcastInfoStat // onMessageAllowed is handled internally interface IProps extends Omit { /* overrides for the msgtype-specific components, used by ReplyTile to override file rendering */ - overrideBodyTypes?: Record; - overrideEventTypes?: Record; + overrideBodyTypes?: Record; + overrideEventTypes?: Record; // helper function to access relations for this event getRelationsForEvent?: GetRelationsForEvent; @@ -64,7 +64,9 @@ export interface IOperableEventTile { getEventTileOps(): IEventTileOps | null; } -const baseBodyTypes = new Map([ +type ComponentType = React.ComponentType>; + +const baseBodyTypes = new Map([ [MsgType.Text, TextualBody], [MsgType.Notice, TextualBody], [MsgType.Emote, TextualBody], @@ -73,7 +75,7 @@ const baseBodyTypes = new Map([ [MsgType.Audio, MVoiceOrAudioBody], [MsgType.Video, MVideoBody], ]); -const baseEvTypes = new Map>([ +const baseEvTypes = new Map([ [EventType.Sticker, MStickerBody], [M_POLL_START.name, MPollBody], [M_POLL_START.altName, MPollBody], @@ -84,10 +86,10 @@ const baseEvTypes = new Map>([ ]); export default class MessageEvent extends React.Component implements IMediaBody, IOperableEventTile { - private body: React.RefObject = createRef(); + private body: React.RefObject = createRef(); private mediaHelper?: MediaEventHelper; - private bodyTypes = new Map(baseBodyTypes.entries()); - private evTypes = new Map>(baseEvTypes.entries()); + private bodyTypes = new Map(baseBodyTypes.entries()); + private evTypes = new Map(baseEvTypes.entries()); public static contextType = MatrixClientContext; public context!: React.ContextType; @@ -121,12 +123,12 @@ export default class MessageEvent extends React.Component implements IMe } private updateComponentMaps(): void { - this.bodyTypes = new Map(baseBodyTypes.entries()); + this.bodyTypes = new Map(baseBodyTypes.entries()); for (const [bodyType, bodyComponent] of Object.entries(this.props.overrideBodyTypes ?? {})) { this.bodyTypes.set(bodyType, bodyComponent); } - this.evTypes = new Map>(baseEvTypes.entries()); + this.evTypes = new Map(baseEvTypes.entries()); for (const [evType, evComponent] of Object.entries(this.props.overrideEventTypes ?? {})) { this.evTypes.set(evType, evComponent); } @@ -156,7 +158,7 @@ export default class MessageEvent extends React.Component implements IMe const content = this.props.mxEvent.getContent(); const type = this.props.mxEvent.getType(); const msgtype = content.msgtype; - let BodyType: React.ComponentType = RedactedBody; + let BodyType: ComponentType = RedactedBody; if (!this.props.mxEvent.isRedacted()) { // only resolve BodyType if event is not redacted if (this.props.mxEvent.isDecryptionFailure()) { diff --git a/src/components/views/messages/ReactionsRow.tsx b/src/components/views/messages/ReactionsRow.tsx index e57326edd7..fcf748bd6a 100644 --- a/src/components/views/messages/ReactionsRow.tsx +++ b/src/components/views/messages/ReactionsRow.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { SyntheticEvent } from "react"; +import React, { forwardRef, SyntheticEvent } from "react"; import classNames from "classnames"; import { MatrixEvent, MatrixEventEvent, Relations, RelationsEvent } from "matrix-js-sdk/src/matrix"; import { uniqBy } from "lodash"; @@ -35,7 +35,7 @@ const MAX_ITEMS_WHEN_LIMITED = 8; export const REACTION_SHORTCODE_KEY = new UnstableValue("shortcode", "com.beeper.reaction.shortcode"); -const ReactButton: React.FC = ({ mxEvent, reactions }) => { +const ReactButton: React.FC> = ({ mxEvent, reactions }) => { const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); let contextMenu: JSX.Element | undefined; @@ -74,6 +74,7 @@ interface IProps { mxEvent: MatrixEvent; // The Relations model from the JS SDK for reactions to `mxEvent` reactions?: Relations | null | undefined; + context: React.ContextType; } interface IState { @@ -81,13 +82,9 @@ interface IState { showAll: boolean; } -export default class ReactionsRow extends React.PureComponent { - public static contextType = RoomContext; - public context!: React.ContextType; - - public constructor(props: IProps, context: React.ContextType) { - super(props, context); - this.context = context; +class ReactionsRow extends React.PureComponent { + public constructor(props: IProps) { + super(props); this.state = { myReactions: this.getMyReactions(), @@ -151,7 +148,7 @@ export default class ReactionsRow extends React.PureComponent { if (!reactions) { return null; } - const userId = this.context.room?.client.getUserId(); + const userId = this.props.context.room?.client.getUserId(); if (!userId) return null; const myReactions = reactions.getAnnotationsBySender()?.[userId]; if (!myReactions) { @@ -202,8 +199,8 @@ export default class ReactionsRow extends React.PureComponent { myReactionEvent={myReactionEvent} customReactionImagesEnabled={customReactionImagesEnabled} disabled={ - !this.context.canReact || - (myReactionEvent && !myReactionEvent.isRedacted() && !this.context.canSelfRedact) + !this.props.context.canReact || + (myReactionEvent && !myReactionEvent.isRedacted() && !this.props.context.canSelfRedact) } /> ); @@ -226,7 +223,7 @@ export default class ReactionsRow extends React.PureComponent { } let addReactionButton: JSX.Element | undefined; - if (this.context.canReact) { + if (this.props.context.canReact) { addReactionButton = ; } @@ -239,3 +236,7 @@ export default class ReactionsRow extends React.PureComponent { ); } } + +export default forwardRef>((props, ref) => ( + {(context) => } +)); diff --git a/src/components/views/messages/TextualBody.tsx b/src/components/views/messages/TextualBody.tsx index f0b5c70f17..69ee45c91b 100644 --- a/src/components/views/messages/TextualBody.tsx +++ b/src/components/views/messages/TextualBody.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { createRef, SyntheticEvent, MouseEvent } from "react"; +import React, { createRef, SyntheticEvent, MouseEvent, forwardRef } from "react"; import ReactDOM from "react-dom"; import { MsgType } from "matrix-js-sdk/src/matrix"; @@ -51,6 +51,10 @@ import { MatrixClientPeg } from "../../../MatrixClientPeg"; const MAX_HIGHLIGHT_LENGTH = 4096; +interface Props extends IBodyProps { + context: React.ContextType; +} + interface IState { // the URLs (if any) to be previewed with a LinkPreviewWidget inside this TextualBody. links: string[]; @@ -59,17 +63,14 @@ interface IState { widgetHidden: boolean; } -export default class TextualBody extends React.Component { +class TextualBody extends React.Component { private readonly contentRef = createRef(); private unmounted = false; private pills: Element[] = []; private tooltips: Element[] = []; - public static contextType = RoomContext; - public context!: React.ContextType; - - public constructor(props: IBodyProps) { + public constructor(props: Props) { super(props); this.state = { @@ -429,7 +430,7 @@ export default class TextualBody extends React.Component { dis.dispatch({ action: Action.ComposerInsert, userId: mxEvent.getSender(), - timelineRenderingType: this.context.timelineRenderingType, + timelineRenderingType: this.props.context.timelineRenderingType, }); }; @@ -659,3 +660,7 @@ export default class TextualBody extends React.Component { ); } } + +export default forwardRef>((props, ref) => ( + {(context) => } +)); diff --git a/src/components/views/messages/TextualEvent.tsx b/src/components/views/messages/TextualEvent.tsx index ae94fd31f9..b49f64a475 100644 --- a/src/components/views/messages/TextualEvent.tsx +++ b/src/components/views/messages/TextualEvent.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { forwardRef } from "react"; import { MatrixEvent } from "matrix-js-sdk/src/matrix"; import RoomContext from "../../../contexts/RoomContext"; @@ -23,19 +23,22 @@ import { MatrixClientPeg } from "../../../MatrixClientPeg"; interface IProps { mxEvent: MatrixEvent; + context: React.ContextType; } -export default class TextualEvent extends React.Component { - public static contextType = RoomContext; - +class TextualEvent extends React.Component { public render(): React.ReactNode { const text = TextForEvent.textForEvent( this.props.mxEvent, MatrixClientPeg.safeGet(), true, - this.context?.showHiddenEvents, + this.props.context?.showHiddenEvents, ); if (!text) return null; return
{text}
; } } + +export default forwardRef>((props, ref) => ( + {(context) => } +)); diff --git a/src/components/views/right_panel/TimelineCard.tsx b/src/components/views/right_panel/TimelineCard.tsx index 7dfcd63307..6e21b5895c 100644 --- a/src/components/views/right_panel/TimelineCard.tsx +++ b/src/components/views/right_panel/TimelineCard.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { forwardRef } from "react"; import { IEventRelation, MatrixEvent, @@ -59,6 +59,7 @@ interface IProps { timelineRenderingType?: TimelineRenderingType; showComposer?: boolean; composerRelation?: IEventRelation; + context: React.ContextType; } interface IState { @@ -75,12 +76,10 @@ interface IState { showReadReceipts?: boolean; } -export default class TimelineCard extends React.Component { - public static contextType = RoomContext; - +class TimelineCard extends React.Component { private dispatcherRef?: string; private layoutWatcherRef?: string; - private timelinePanel = React.createRef(); + private timelinePanel = React.createRef>(); private card = React.createRef(); private readReceiptsSettingWatcher: string | undefined; @@ -224,7 +223,7 @@ export default class TimelineCard extends React.Component { { manageReadMarkers={false} // No RM support in the TimelineCard sendReadReceiptOnLoad={true} timelineSet={this.props.timelineSet} - showUrlPreview={this.context.showUrlPreview} + showUrlPreview={this.props.context.showUrlPreview} // The right panel timeline (and therefore threads) don't support IRC layout at this time layout={this.state.layout === Layout.Bubble ? Layout.Bubble : Layout.Group} hideThreadedMessages={false} @@ -281,3 +280,7 @@ export default class TimelineCard extends React.Component { ); } } + +export default forwardRef>((props, ref) => ( + {(context) => } +)); diff --git a/src/components/views/rooms/Autocomplete.tsx b/src/components/views/rooms/Autocomplete.tsx index 8eedf0867e..1dde894624 100644 --- a/src/components/views/rooms/Autocomplete.tsx +++ b/src/components/views/rooms/Autocomplete.tsx @@ -15,7 +15,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 classNames from "classnames"; import { flatMap } from "lodash"; import { Room } from "matrix-js-sdk/src/matrix"; @@ -38,6 +38,7 @@ interface IProps { selection: ISelectionRange; // The room in which we're autocompleting room: Room; + context: React.ContextType; } interface IState { @@ -49,14 +50,12 @@ interface IState { forceComplete: boolean; } -export default class Autocomplete extends React.PureComponent { +export class Autocomplete extends React.PureComponent { public autocompleter?: Autocompleter; public queryRequested?: string; public debounceCompletionsRequest?: number; private containerRef = createRef(); - public static contextType = RoomContext; - public constructor(props: IProps) { super(props); @@ -80,7 +79,7 @@ export default class Autocomplete extends React.PureComponent { } public componentDidMount(): void { - this.autocompleter = new Autocompleter(this.props.room, this.context.timelineRenderingType); + this.autocompleter = new Autocompleter(this.props.room, this.props.context.timelineRenderingType); this.applyNewProps(); } @@ -320,3 +319,7 @@ export default class Autocomplete extends React.PureComponent { ) : null; } } + +export default forwardRef>((props, ref) => ( + {(context) => } +)); diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index 061dfe2703..50933ad82d 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -123,7 +123,7 @@ interface IState { export default class BasicMessageEditor extends React.Component { public readonly editorRef = createRef(); - private autocompleteRef = createRef(); + private autocompleteRef = createRef>(); private formatBarRef = createRef(); private modifiedFlag = false; diff --git a/src/components/views/rooms/EditMessageComposer.tsx b/src/components/views/rooms/EditMessageComposer.tsx index fabca13a1c..1f13aa48e1 100644 --- a/src/components/views/rooms/EditMessageComposer.tsx +++ b/src/components/views/rooms/EditMessageComposer.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 classNames from "classnames"; import { EventStatus, MatrixEvent, Room, MsgType } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; @@ -122,28 +122,25 @@ export function createEditContent( interface IEditMessageComposerProps extends MatrixClientProps { editState: EditorStateTransfer; className?: string; + context: React.ContextType; } interface IState { saveDisabled: boolean; } class EditMessageComposer extends React.Component { - public static contextType = RoomContext; - public context!: React.ContextType; - private readonly editorRef = createRef(); private readonly dispatcherRef: string; private readonly replyToEvent?: MatrixEvent; private model!: EditorModel; - public constructor(props: IEditMessageComposerProps, context: React.ContextType) { + public constructor(props: IEditMessageComposerProps) { super(props); - this.context = context; // otherwise React will only set it prior to render due to type def above const isRestored = this.createEditorModel(); const ev = this.props.editState.getEvent(); - this.replyToEvent = ev.replyEventId ? this.context.room?.findEventById(ev.replyEventId) : undefined; + this.replyToEvent = ev.replyEventId ? this.props.context.room?.findEventById(ev.replyEventId) : undefined; const editContent = createEditContent(this.model, ev, this.replyToEvent); this.state = { @@ -155,10 +152,10 @@ class EditMessageComposer extends React.Component { @@ -191,7 +188,7 @@ class EditMessageComposer extends React.Component>( + (props, ref) => ( + + {(context) => } + + ), +); diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 108e0d9d93..32ff6c2b4c 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -266,6 +266,10 @@ interface IState { threadNotification?: NotificationCountType; } +interface Props extends EventTileProps { + context: React.ContextType; +} + /** * When true, the tile qualifies for some sort of special read receipt. * This could be a 'sending' or 'sent' receipt, for example. @@ -282,7 +286,7 @@ export function isEligibleForSpecialReceipt(event: MatrixEvent): boolean { } // MUST be rendered within a RoomContext with a set timelineRenderingType -export class UnwrappedEventTile extends React.Component { +class UnwrappedEventTile extends React.Component { private suppressReadReceiptAnimation: boolean; private isListeningForReceipts: boolean; private tile = createRef(); @@ -297,13 +301,10 @@ export class UnwrappedEventTile extends React.Component layout: Layout.Group, }; - public static contextType = RoomContext; - public context!: React.ContextType; - private unmounted = false; - public constructor(props: EventTileProps, context: React.ContextType) { - super(props, context); + public constructor(props: Props) { + super(props); const thread = this.thread; @@ -508,7 +509,10 @@ export class UnwrappedEventTile extends React.Component ); } - if (this.context.timelineRenderingType === TimelineRenderingType.Search && this.props.mxEvent.threadRootId) { + if ( + this.props.context.timelineRenderingType === TimelineRenderingType.Search && + this.props.mxEvent.threadRootId + ) { if (this.props.highlightLink) { return ( @@ -676,8 +680,8 @@ export class UnwrappedEventTile extends React.Component */ private shouldHighlight(): boolean { if (this.props.forExport) return false; - if (this.context.timelineRenderingType === TimelineRenderingType.Notification) return false; - if (this.context.timelineRenderingType === TimelineRenderingType.ThreadsList) return false; + if (this.props.context.timelineRenderingType === TimelineRenderingType.Notification) return false; + if (this.props.context.timelineRenderingType === TimelineRenderingType.ThreadsList) return false; const cli = MatrixClientPeg.safeGet(); const actions = cli.getPushActionsForEvent(this.props.mxEvent.replacingEvent() || this.props.mxEvent); @@ -701,7 +705,7 @@ export class UnwrappedEventTile extends React.Component dis.dispatch({ action: Action.ComposerInsert, userId: this.props.mxEvent.getSender()!, - timelineRenderingType: this.context.timelineRenderingType, + timelineRenderingType: this.props.context.timelineRenderingType, }); }; @@ -715,7 +719,7 @@ export class UnwrappedEventTile extends React.Component highlighted: true, room_id: this.props.mxEvent.getRoomId(), metricsTrigger: - this.context.timelineRenderingType === TimelineRenderingType.Search ? "MessageSearch" : undefined, + this.props.context.timelineRenderingType === TimelineRenderingType.Search ? "MessageSearch" : undefined, }); }; @@ -923,7 +927,7 @@ export class UnwrappedEventTile extends React.Component } = getEventDisplayInfo( MatrixClientPeg.safeGet(), this.props.mxEvent, - this.context.showHiddenEvents, + this.props.context.showHiddenEvents, this.shouldHideEvent(), ); const { isQuoteExpanded } = this.state; @@ -958,15 +962,15 @@ export class UnwrappedEventTile extends React.Component let isContinuation = this.props.continuation; if ( - this.context.timelineRenderingType !== TimelineRenderingType.Room && - this.context.timelineRenderingType !== TimelineRenderingType.Search && - this.context.timelineRenderingType !== TimelineRenderingType.Thread && + this.props.context.timelineRenderingType !== TimelineRenderingType.Room && + this.props.context.timelineRenderingType !== TimelineRenderingType.Search && + this.props.context.timelineRenderingType !== TimelineRenderingType.Thread && this.props.layout !== Layout.Bubble ) { isContinuation = false; } - const isRenderingNotification = this.context.timelineRenderingType === TimelineRenderingType.Notification; + const isRenderingNotification = this.props.context.timelineRenderingType === TimelineRenderingType.Notification; const isEditing = !!this.props.editState; const classes = classNames({ @@ -990,7 +994,8 @@ export class UnwrappedEventTile extends React.Component mx_EventTile_emote: msgtype === MsgType.Emote, mx_EventTile_noSender: this.props.hideSender, mx_EventTile_clamp: - this.context.timelineRenderingType === TimelineRenderingType.ThreadsList || isRenderingNotification, + this.props.context.timelineRenderingType === TimelineRenderingType.ThreadsList || + isRenderingNotification, mx_EventTile_noBubble: noBubbleEvent, }); @@ -1020,8 +1025,8 @@ export class UnwrappedEventTile extends React.Component avatarSize = "14px"; needsSenderProfile = false; } else if ( - this.context.timelineRenderingType === TimelineRenderingType.ThreadsList || - (this.context.timelineRenderingType === TimelineRenderingType.Thread && !this.props.continuation) + this.props.context.timelineRenderingType === TimelineRenderingType.ThreadsList || + (this.props.context.timelineRenderingType === TimelineRenderingType.Thread && !this.props.continuation) ) { avatarSize = "32px"; needsSenderProfile = true; @@ -1032,7 +1037,7 @@ export class UnwrappedEventTile extends React.Component avatarSize = "14px"; needsSenderProfile = true; } else if ( - (this.props.continuation && this.context.timelineRenderingType !== TimelineRenderingType.File) || + (this.props.continuation && this.props.context.timelineRenderingType !== TimelineRenderingType.File) || eventType === EventType.CallInvite || ElementCall.CALL_EVENT_TYPE.matches(eventType) ) { @@ -1058,7 +1063,7 @@ export class UnwrappedEventTile extends React.Component const viewUserOnClick = !this.props.inhibitInteraction && ![TimelineRenderingType.ThreadsList, TimelineRenderingType.Notification].includes( - this.context.timelineRenderingType, + this.props.context.timelineRenderingType, ); avatar = (
@@ -1074,13 +1079,13 @@ export class UnwrappedEventTile extends React.Component if (needsSenderProfile && this.props.hideSender !== true) { if ( - this.context.timelineRenderingType === TimelineRenderingType.Room || - this.context.timelineRenderingType === TimelineRenderingType.Search || - this.context.timelineRenderingType === TimelineRenderingType.Pinned || - this.context.timelineRenderingType === TimelineRenderingType.Thread + this.props.context.timelineRenderingType === TimelineRenderingType.Room || + this.props.context.timelineRenderingType === TimelineRenderingType.Search || + this.props.context.timelineRenderingType === TimelineRenderingType.Pinned || + this.props.context.timelineRenderingType === TimelineRenderingType.Thread ) { sender = ; - } else if (this.context.timelineRenderingType === TimelineRenderingType.ThreadsList) { + } else if (this.props.context.timelineRenderingType === TimelineRenderingType.ThreadsList) { sender = ; } else { sender = ; @@ -1113,7 +1118,7 @@ export class UnwrappedEventTile extends React.Component // Thread panel shows the timestamp of the last reply in that thread let ts = - this.context.timelineRenderingType !== TimelineRenderingType.ThreadsList + this.props.context.timelineRenderingType !== TimelineRenderingType.ThreadsList ? this.props.mxEvent.getTs() : this.state.thread?.replyToEvent?.getTs(); if (typeof ts !== "number") { @@ -1123,7 +1128,7 @@ export class UnwrappedEventTile extends React.Component const messageTimestamp = ( let replyChain: JSX.Element | undefined; if ( - haveRendererForEvent(this.props.mxEvent, MatrixClientPeg.safeGet(), this.context.showHiddenEvents) && + haveRendererForEvent(this.props.mxEvent, MatrixClientPeg.safeGet(), this.props.context.showHiddenEvents) && shouldDisplayReply(this.props.mxEvent) ) { replyChain = ( @@ -1202,7 +1207,7 @@ export class UnwrappedEventTile extends React.Component // Use `getSender()` because searched events might not have a proper `sender`. const isOwnEvent = this.props.mxEvent?.getSender() === MatrixClientPeg.safeGet().getUserId(); - switch (this.context.timelineRenderingType) { + switch (this.props.context.timelineRenderingType) { case TimelineRenderingType.Thread: { return React.createElement( this.props.as || "li", @@ -1242,7 +1247,7 @@ export class UnwrappedEventTile extends React.Component onHeightChanged: () => this.props.onHeightChanged, permalinkCreator: this.props.permalinkCreator!, }, - this.context.showHiddenEvents, + this.props.context.showHiddenEvents, )} {actionBar} @@ -1268,7 +1273,7 @@ export class UnwrappedEventTile extends React.Component "aria-atomic": "true", "data-scroll-tokens": scrollToken, "data-layout": this.props.layout, - "data-shape": this.context.timelineRenderingType, + "data-shape": this.props.context.timelineRenderingType, "data-self": isOwnEvent, "data-has-reply": !!replyChain, "onMouseEnter": () => this.setState({ hover: true }), @@ -1277,7 +1282,7 @@ export class UnwrappedEventTile extends React.Component const target = ev.currentTarget as HTMLElement; let index = -1; if (target.parentElement) index = Array.from(target.parentElement.children).indexOf(target); - switch (this.context.timelineRenderingType) { + switch (this.props.context.timelineRenderingType) { case TimelineRenderingType.Notification: this.viewInRoom(ev); break; @@ -1333,7 +1338,7 @@ export class UnwrappedEventTile extends React.Component
{this.renderThreadPanelSummary()} - {this.context.timelineRenderingType === TimelineRenderingType.ThreadsList && ( + {this.props.context.timelineRenderingType === TimelineRenderingType.ThreadsList && ( onHeightChanged: this.props.onHeightChanged, permalinkCreator: this.props.permalinkCreator, }, - this.context.showHiddenEvents, + this.props.context.showHiddenEvents, )} , {groupPadlock} {replyChain} {renderTile( - this.context.timelineRenderingType, + this.props.context.timelineRenderingType, { ...this.props, @@ -1434,7 +1439,7 @@ export class UnwrappedEventTile extends React.Component onHeightChanged: this.props.onHeightChanged, permalinkCreator: this.props.permalinkCreator, }, - this.context.showHiddenEvents, + this.props.context.showHiddenEvents, )} {actionBar} {this.props.layout === Layout.IRC && ( @@ -1463,7 +1468,9 @@ const SafeEventTile = forwardRef((props, ref return ( <> - + + {(context) => } + ); diff --git a/src/components/views/rooms/LegacyRoomHeader.tsx b/src/components/views/rooms/LegacyRoomHeader.tsx index 5fab692046..fd9458fe77 100644 --- a/src/components/views/rooms/LegacyRoomHeader.tsx +++ b/src/components/views/rooms/LegacyRoomHeader.tsx @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { FC, useState, useMemo, useCallback } from "react"; +import React, { FC, useState, useMemo, useCallback, forwardRef } from "react"; import classNames from "classnames"; import { throttle } from "lodash"; import { RoomStateEvent, ISearchResults } from "matrix-js-sdk/src/matrix"; @@ -470,7 +470,7 @@ export interface ISearchInfo { count?: number; } -export interface IProps { +interface IProps { room: Room; oobData?: IOOBData; inRoom: boolean; @@ -478,7 +478,7 @@ export interface IProps { onInviteClick: (() => void) | null; onForgetClick: (() => void) | null; onAppsClick: (() => void) | null; - e2eStatus: E2EStatus; + e2eStatus?: E2EStatus; appsShown: boolean; searchInfo?: ISearchInfo; excludedRightPanelPhaseButtons?: Array; @@ -487,6 +487,7 @@ export interface IProps { viewingCall: boolean; activeCall: Call | null; additionalButtons?: ViewRoomOpts["buttons"]; + context: React.ContextType; } interface IState { @@ -498,7 +499,7 @@ interface IState { /** * @deprecated use `src/components/views/rooms/RoomHeader.tsx` instead */ -export default class RoomHeader extends React.Component { +class LegacyRoomHeader extends React.Component { public static defaultProps: Partial = { inRoom: false, excludedRightPanelPhaseButtons: [], @@ -506,13 +507,11 @@ export default class RoomHeader extends React.Component { enableRoomOptionsMenu: true, }; - public static contextType = RoomContext; - public context!: React.ContextType; private readonly client = this.props.room.client; private readonly featureAskToJoinWatcher: string; - public constructor(props: IProps, context: IState) { - super(props, context); + public constructor(props: IProps) { + super(props); const notiStore = RoomNotificationStateStore.instance.getRoomState(props.room); notiStore.on(NotificationStateEvents.Update, this.onNotificationUpdate); this.state = { @@ -590,7 +589,7 @@ export default class RoomHeader extends React.Component { private renderButtons(isVideoRoom: boolean): React.ReactNode { const startButtons: JSX.Element[] = []; - if (!this.props.viewingCall && this.props.inRoom && !this.context.tombstone) { + if (!this.props.viewingCall && this.props.inRoom && !this.props.context.tombstone) { startButtons.push(); } @@ -866,3 +865,9 @@ export default class RoomHeader extends React.Component { ); } } + +export default forwardRef>((props, ref) => ( + + {(context) => } + +)); diff --git a/src/components/views/rooms/MessageComposer.tsx b/src/components/views/rooms/MessageComposer.tsx index f9aae433fe..4911a216a8 100644 --- a/src/components/views/rooms/MessageComposer.tsx +++ b/src/components/views/rooms/MessageComposer.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 classNames from "classnames"; import { IEventRelation, @@ -44,7 +44,7 @@ import { RecordingState } from "../../../audio/VoiceRecording"; import Tooltip, { Alignment } from "../elements/Tooltip"; import ResizeNotifier from "../../../utils/ResizeNotifier"; import { E2EStatus } from "../../../utils/ShieldUtils"; -import SendMessageComposer, { SendMessageComposer as SendMessageComposerClass } from "./SendMessageComposer"; +import SendMessageComposer from "./SendMessageComposer"; import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload"; import { Action } from "../../../dispatcher/actions"; import EditorModel from "../../../editor/model"; @@ -92,6 +92,7 @@ interface IProps extends MatrixClientProps { relation?: IEventRelation; e2eStatus?: E2EStatus; compact?: boolean; + context: React.ContextType; } interface IState { @@ -113,16 +114,13 @@ interface IState { export class MessageComposer extends React.Component { private tooltipId = `mx_MessageComposer_${Math.random()}`; private dispatcherRef?: string; - private messageComposerInput = createRef(); - private voiceRecordingButton = createRef(); + private messageComposerInput = createRef>(); + private voiceRecordingButton = createRef>(); private ref: React.RefObject = createRef(); private instanceId: number; private _voiceRecording: Optional; - public static contextType = RoomContext; - public context!: React.ContextType; - public static defaultProps = { compact: false, showVoiceBroadcastButton: false, @@ -189,7 +187,7 @@ export class MessageComposer extends React.Component { private onResize = (type: UI_EVENTS, entry: ResizeObserverEntry): void => { if (type === UI_EVENTS.Resize) { - const { narrow } = this.context; + const { narrow } = this.props.context; this.setState({ isMenuOpen: !narrow ? false : this.state.isMenuOpen, isStickerPickerOpen: false, @@ -200,7 +198,7 @@ export class MessageComposer extends React.Component { private onAction = (payload: ActionPayload): void => { switch (payload.action) { case "reply_to_event": - if (payload.context === this.context.timelineRenderingType) { + if (payload.context === this.props.context.timelineRenderingType) { // add a timeout for the reply preview to be rendered, so // that the ScrollPanel listening to the resizeNotifier can // correctly measure it's new height and scroll down to keep @@ -274,7 +272,7 @@ export class MessageComposer extends React.Component { private onTombstoneClick = (ev: ButtonEvent): void => { ev.preventDefault(); - const replacementRoomId = this.context.tombstone?.getContent()["replacement_room"]; + const replacementRoomId = this.props.context.tombstone?.getContent()["replacement_room"]; const replacementRoom = MatrixClientPeg.safeGet().getRoom(replacementRoomId); let createEventId: string | undefined; if (replacementRoom) { @@ -282,7 +280,7 @@ export class MessageComposer extends React.Component { if (createEvent?.getId()) createEventId = createEvent.getId(); } - const sender = this.context.tombstone?.getSender(); + const sender = this.props.context.tombstone?.getSender(); const viaServers = sender ? [sender.split(":").slice(1).join(":")] : undefined; dis.dispatch({ @@ -324,7 +322,7 @@ export class MessageComposer extends React.Component { dis.dispatch({ action: Action.ComposerInsert, text: emoji, - timelineRenderingType: this.context.timelineRenderingType, + timelineRenderingType: this.props.context.timelineRenderingType, }); return true; }; @@ -345,11 +343,11 @@ export class MessageComposer extends React.Component { this.setState({ composerContent: "", initialComposerContent: "" }); dis.dispatch({ action: Action.ClearAndFocusSendMessageComposer, - timelineRenderingType: this.context.timelineRenderingType, + timelineRenderingType: this.props.context.timelineRenderingType, }); await sendMessage(composerContent, this.state.isRichTextEnabled, { mxClient: this.props.mxClient, - roomContext: this.context, + roomContext: this.props.context, permalinkCreator, relation, replyToEvent, @@ -467,7 +465,7 @@ export class MessageComposer extends React.Component { this.voiceRecordingButton.current?.onRecordStartEndClick(); } - if (this.context.narrow) { + if (this.props.context.narrow) { this.toggleButtonMenu(); } }; @@ -483,7 +481,7 @@ export class MessageComposer extends React.Component { const controls: ReactNode[] = []; const menuPosition = this.getMenuPosition(); - const canSendMessages = this.context.canSendMessages && !this.context.tombstone; + const canSendMessages = this.props.context.canSendMessages && !this.props.context.tombstone; let composer: ReactNode; if (canSendMessages) { if (this.state.isWysiwygLabEnabled && menuPosition) { @@ -528,8 +526,8 @@ export class MessageComposer extends React.Component { replyToEvent={this.props.replyToEvent} />, ); - } else if (this.context.tombstone) { - const replacementRoomId = this.context.tombstone.getContent()["replacement_room"]; + } else if (this.props.context.tombstone) { + const replacementRoomId = this.props.context.tombstone.getContent()["replacement_room"]; const continuesLink = replacementRoomId ? ( { } const MessageComposerWithMatrixClient = withMatrixClientHOC(MessageComposer); -export default MessageComposerWithMatrixClient; +export default forwardRef>((props, ref) => ( + + {(context) => } + +)); diff --git a/src/components/views/rooms/ReplyPreview.tsx b/src/components/views/rooms/ReplyPreview.tsx index 50c625c428..20b085755d 100644 --- a/src/components/views/rooms/ReplyPreview.tsx +++ b/src/components/views/rooms/ReplyPreview.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { forwardRef } from "react"; import { MatrixEvent } from "matrix-js-sdk/src/matrix"; import dis from "../../../dispatcher/dispatcher"; @@ -35,11 +35,10 @@ function cancelQuoting(context: TimelineRenderingType): void { interface IProps { permalinkCreator?: RoomPermalinkCreator; replyToEvent?: MatrixEvent; + context: React.ContextType; } -export default class ReplyPreview extends React.Component { - public static contextType = RoomContext; - +class ReplyPreview extends React.Component { public render(): JSX.Element | null { if (!this.props.replyToEvent) return null; @@ -50,7 +49,7 @@ export default class ReplyPreview extends React.Component { {_t("composer|replying_title")} cancelQuoting(this.context.timelineRenderingType)} + onClick={() => cancelQuoting(this.props.context.timelineRenderingType)} /> @@ -59,3 +58,7 @@ export default class ReplyPreview extends React.Component { ); } } + +export default forwardRef>((props, ref) => ( + {(context) => } +)); diff --git a/src/components/views/rooms/ReplyTile.tsx b/src/components/views/rooms/ReplyTile.tsx index 56ca6ade60..986bb8d09c 100644 --- a/src/components/views/rooms/ReplyTile.tsx +++ b/src/components/views/rooms/ReplyTile.tsx @@ -31,7 +31,7 @@ import MFileBody from "../messages/MFileBody"; import MemberAvatar from "../avatars/MemberAvatar"; import MVoiceMessageBody from "../messages/MVoiceMessageBody"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; -import { renderReplyTile } from "../../../events/EventTileFactory"; +import { EventTileTypeProps, renderReplyTile } from "../../../events/EventTileFactory"; import { GetRelationsForEvent } from "../rooms/EventTile"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; @@ -147,13 +147,13 @@ export default class ReplyTile extends React.PureComponent { ); } - const msgtypeOverrides: Record = { + const msgtypeOverrides: Record> = { [MsgType.Image]: MImageReplyBody, // Override audio and video body with file body. We also hide the download/decrypt button using CSS [MsgType.Audio]: isVoiceMessage(mxEvent) ? MVoiceMessageBody : MFileBody, [MsgType.Video]: MFileBody, }; - const evOverrides: Record = { + const evOverrides: Record> = { // Use MImageReplyBody so that the sticker isn't taking up a lot of space [EventType.Sticker]: MImageReplyBody, }; diff --git a/src/components/views/rooms/SearchResultTile.tsx b/src/components/views/rooms/SearchResultTile.tsx index 99b5f0805c..2efca1731b 100644 --- a/src/components/views/rooms/SearchResultTile.tsx +++ b/src/components/views/rooms/SearchResultTile.tsx @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { forwardRef } from "react"; import { MatrixEvent } from "matrix-js-sdk/src/matrix"; import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; @@ -40,17 +40,15 @@ interface IProps { ourEventsIndexes: number[]; onHeightChanged?: () => void; permalinkCreator?: RoomPermalinkCreator; + context: React.ContextType; } -export default class SearchResultTile extends React.Component { - public static contextType = RoomContext; - public context!: React.ContextType; - +class SearchResultTile extends React.Component { // A map of private callEventGroupers = new Map(); - public constructor(props: IProps, context: React.ContextType) { - super(props, context); + public constructor(props: IProps) { + super(props); this.buildLegacyCallEventGroupers(this.props.timeline); } @@ -79,7 +77,7 @@ export default class SearchResultTile extends React.Component { highlights = this.props.searchHighlights; } - if (haveRendererForEvent(mxEv, cli, this.context?.showHiddenEvents)) { + if (haveRendererForEvent(mxEv, cli, this.props.context?.showHiddenEvents)) { // do we need a date separator since the last event? const prevEv = timeline[j - 1]; // is this a continuation of the previous message? @@ -90,7 +88,7 @@ export default class SearchResultTile extends React.Component { prevEv, mxEv, cli, - this.context?.showHiddenEvents, + this.props.context?.showHiddenEvents, TimelineRenderingType.Search, ); @@ -108,7 +106,7 @@ export default class SearchResultTile extends React.Component { mxEv, nextEv, cli, - this.context?.showHiddenEvents, + this.props.context?.showHiddenEvents, TimelineRenderingType.Search, ); } @@ -140,3 +138,9 @@ export default class SearchResultTile extends React.Component { ); } } + +export default forwardRef>((props, ref) => ( + + {(context) => } + +)); diff --git a/src/components/views/rooms/SendMessageComposer.tsx b/src/components/views/rooms/SendMessageComposer.tsx index 0ea0bdf94c..113b5ef2b7 100644 --- a/src/components/views/rooms/SendMessageComposer.tsx +++ b/src/components/views/rooms/SendMessageComposer.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { createRef, KeyboardEvent, SyntheticEvent } from "react"; +import React, { createRef, forwardRef, KeyboardEvent, SyntheticEvent } from "react"; import EMOJI_REGEX from "emojibase-regex"; import { IContent, @@ -250,12 +250,10 @@ interface ISendMessageComposerProps extends MatrixClientProps { onChange?(model: EditorModel): void; includeReplyLegacyFallback?: boolean; toggleStickerPickerOpen: () => void; + context: React.ContextType; } -export class SendMessageComposer extends React.Component { - public static contextType = RoomContext; - public context!: React.ContextType; - +class SendMessageComposer extends React.Component { private readonly prepareToEncrypt?: DebouncedFunc<() => void>; private readonly editorRef = createRef(); private model: EditorModel; @@ -267,9 +265,8 @@ export class SendMessageComposer extends React.Component) { - super(props, context); - this.context = context; // otherwise React will only set it prior to render due to type def above + public constructor(props: ISendMessageComposerProps) { + super(props); if (this.props.mxClient.isCryptoEnabled() && this.props.mxClient.isRoomEncrypted(this.props.room.roomId)) { this.prepareToEncrypt = throttle( @@ -336,7 +333,7 @@ export class SendMessageComposer extends React.Component { @@ -789,4 +786,10 @@ export class SendMessageComposer extends React.Component>( + (props, ref) => ( + + {(context) => } + + ), +); diff --git a/src/components/views/rooms/VoiceRecordComposerTile.tsx b/src/components/views/rooms/VoiceRecordComposerTile.tsx index 70cabb474c..4c8e838513 100644 --- a/src/components/views/rooms/VoiceRecordComposerTile.tsx +++ b/src/components/views/rooms/VoiceRecordComposerTile.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ReactNode } from "react"; +import React, { forwardRef, ReactNode } from "react"; import { Room, IEventRelation, MatrixEvent } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; import { Optional } from "matrix-events-sdk"; @@ -50,6 +50,7 @@ interface IProps { permalinkCreator?: RoomPermalinkCreator; relation?: IEventRelation; replyToEvent?: MatrixEvent; + context: React.ContextType; } interface IState { @@ -61,9 +62,7 @@ interface IState { /** * Container tile for rendering the voice message recorder in the composer. */ -export default class VoiceRecordComposerTile extends React.PureComponent { - public static contextType = RoomContext; - public context!: React.ContextType; +class VoiceRecordComposerTile extends React.PureComponent { private voiceRecordingId: string; public constructor(props: IProps) { @@ -141,7 +140,7 @@ export default class VoiceRecordComposerTile extends React.PureComponent>((props, ref) => ( + + {(context) => } + +)); diff --git a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx index 1e1cb0eb60..7d4bcec712 100644 --- a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx +++ b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx @@ -59,7 +59,7 @@ interface WysiwygAutocompleteProps { const WysiwygAutocomplete = forwardRef( ( { suggestion, handleMention, handleCommand, handleAtRoomMention }: WysiwygAutocompleteProps, - ref: ForwardedRef, + ref: ForwardedRef>, ): JSX.Element | null => { const { room } = useRoomContext(); const client = useMatrixClientContext(); diff --git a/src/components/views/rooms/wysiwyg_composer/components/WysiwygComposer.tsx b/src/components/views/rooms/wysiwyg_composer/components/WysiwygComposer.tsx index ba0dad529b..d22e8e270d 100644 --- a/src/components/views/rooms/wysiwyg_composer/components/WysiwygComposer.tsx +++ b/src/components/views/rooms/wysiwyg_composer/components/WysiwygComposer.tsx @@ -58,7 +58,7 @@ export const WysiwygComposer = memo(function WysiwygComposer({ eventRelation, }: WysiwygComposerProps) { const { room } = useRoomContext(); - const autocompleteRef = useRef(null); + const autocompleteRef = useRef | null>(null); const inputEventProcessor = useInputEventProcessor(onSend, autocompleteRef, initialContent, eventRelation); const { ref, isWysiwygReady, content, actionStates, wysiwyg, suggestion, messageContent } = useWysiwyg({ diff --git a/src/components/views/rooms/wysiwyg_composer/hooks/useInputEventProcessor.ts b/src/components/views/rooms/wysiwyg_composer/hooks/useInputEventProcessor.ts index a9cfa2966e..aec01df7bd 100644 --- a/src/components/views/rooms/wysiwyg_composer/hooks/useInputEventProcessor.ts +++ b/src/components/views/rooms/wysiwyg_composer/hooks/useInputEventProcessor.ts @@ -37,7 +37,7 @@ import { handleClipboardEvent, handleEventWithAutocomplete, isEventToHandleAsCli export function useInputEventProcessor( onSend: () => void, - autocompleteRef: React.RefObject, + autocompleteRef: React.RefObject>, initialContent?: string, eventRelation?: IEventRelation, ): (event: WysiwygEvent, composer: Wysiwyg, editor: HTMLElement) => WysiwygEvent | null { @@ -105,7 +105,7 @@ function handleKeyboardEvent( roomContext: IRoomState, composerContext: ComposerContextState, mxClient: MatrixClient | undefined, - autocompleteRef: React.RefObject, + autocompleteRef: React.RefObject>, ): KeyboardEvent | null { const { editorStateTransfer } = composerContext; const isEditing = Boolean(editorStateTransfer); diff --git a/src/components/views/rooms/wysiwyg_composer/hooks/usePlainTextListeners.ts b/src/components/views/rooms/wysiwyg_composer/hooks/usePlainTextListeners.ts index 6121f0c877..22eb0a358f 100644 --- a/src/components/views/rooms/wysiwyg_composer/hooks/usePlainTextListeners.ts +++ b/src/components/views/rooms/wysiwyg_composer/hooks/usePlainTextListeners.ts @@ -55,7 +55,7 @@ export function usePlainTextListeners( eventRelation?: IEventRelation, ): { ref: RefObject; - autocompleteRef: React.RefObject; + autocompleteRef: React.RefObject>; content?: string; onBeforeInput(event: SyntheticEvent): void; onInput(event: SyntheticEvent): void; @@ -72,7 +72,7 @@ export function usePlainTextListeners( const mxClient = useMatrixClientContext(); const ref = useRef(null); - const autocompleteRef = useRef(null); + const autocompleteRef = useRef | null>(null); const [content, setContent] = useState(initialContent); const send = useCallback(() => { diff --git a/src/components/views/rooms/wysiwyg_composer/hooks/utils.ts b/src/components/views/rooms/wysiwyg_composer/hooks/utils.ts index f95405c3bf..8d4500314f 100644 --- a/src/components/views/rooms/wysiwyg_composer/hooks/utils.ts +++ b/src/components/views/rooms/wysiwyg_composer/hooks/utils.ts @@ -70,7 +70,7 @@ export function setCursorPositionAtTheEnd(element: HTMLElement): void { * @returns boolean - whether or not the autocomplete has handled the event */ export function handleEventWithAutocomplete( - autocompleteRef: RefObject, + autocompleteRef: RefObject>, // we get a React Keyboard event from plain text composer, a Keyboard Event from the rich text composer event: KeyboardEvent | React.KeyboardEvent, ): boolean { diff --git a/src/editor/autocomplete.ts b/src/editor/autocomplete.ts index 3aed32d91f..6e92196faf 100644 --- a/src/editor/autocomplete.ts +++ b/src/editor/autocomplete.ts @@ -28,7 +28,7 @@ export interface ICallback { } export type UpdateCallback = (data: ICallback) => void; -export type GetAutocompleterComponent = () => Autocomplete | null; +export type GetAutocompleterComponent = () => React.ComponentRef | null; export type UpdateQuery = (test: string) => Promise; export default class AutocompleteWrapperModel { diff --git a/src/events/EventTileFactory.tsx b/src/events/EventTileFactory.tsx index 88ebb0a560..d5c427a0e9 100644 --- a/src/events/EventTileFactory.tsx +++ b/src/events/EventTileFactory.tsx @@ -56,6 +56,7 @@ import { shouldDisplayAsVoiceBroadcastStoppedText, VoiceBroadcastChunkEventType, } from "../voice-broadcast"; +import { IBodyProps } from "../components/views/messages/IBodyProps"; // Subset of EventTile's IProps plus some mixins export interface EventTileTypeProps @@ -78,11 +79,11 @@ export interface EventTileTypeProps ref?: React.RefObject; // `any` because it's effectively impossible to convince TS of a reasonable type timestamp?: JSX.Element; maxImageHeight?: number; // pixels - overrideBodyTypes?: Record; - overrideEventTypes?: Record; + overrideBodyTypes?: Record>; + overrideEventTypes?: Record>; } -type FactoryProps = Omit; +type FactoryProps = EventTileTypeProps; type Factory = (ref: Optional>, props: X) => JSX.Element; export const MessageEventFactory: Factory = (ref, props) => ; diff --git a/test/components/structures/TimelinePanel-test.tsx b/test/components/structures/TimelinePanel-test.tsx index 0396ea68a4..9992c772e8 100644 --- a/test/components/structures/TimelinePanel-test.tsx +++ b/test/components/structures/TimelinePanel-test.tsx @@ -40,7 +40,7 @@ import React, { createRef } from "react"; import { Mocked, mocked } from "jest-mock"; import { forEachRight } from "lodash"; -import TimelinePanel from "../../../src/components/structures/TimelinePanel"; +import TimelinePanel, { TimelinePanel as TimelinePanelClass } from "../../../src/components/structures/TimelinePanel"; import MatrixClientContext from "../../../src/contexts/MatrixClientContext"; import { MatrixClientPeg } from "../../../src/MatrixClientPeg"; import { isCallEvent } from "../../../src/components/structures/LegacyCallEventGrouper"; @@ -77,7 +77,7 @@ const mkTimeline = (room: Room, events: MatrixEvent[]): [EventTimeline, EventTim return [timeline, timelineSet]; }; -const getProps = (room: Room, events: MatrixEvent[]): TimelinePanel["props"] => { +const getProps = (room: Room, events: MatrixEvent[]): React.ComponentProps => { const [, timelineSet] = mkTimeline(room, events); return { @@ -184,7 +184,7 @@ describe("TimelinePanel", () => { const roomId = "#room:example.com"; let room: Room; let timelineSet: EventTimelineSet; - let timelinePanel: TimelinePanel; + let timelinePanel: React.ComponentRef; const ev1 = new MatrixEvent({ event_id: "ev1", @@ -203,7 +203,7 @@ describe("TimelinePanel", () => { }); const renderTimelinePanel = async (): Promise => { - const ref = createRef(); + const ref = createRef>(); render( { }); afterEach(() => { - TimelinePanel.roomReadMarkerTsMap = {}; + TimelinePanelClass.roomReadMarkerTsMap = {}; }); it("when there is no event, it should not send any receipt", async () => { diff --git a/test/components/views/rooms/LegacyRoomHeader-test.tsx b/test/components/views/rooms/LegacyRoomHeader-test.tsx index c01d749eea..dd648c9f5f 100644 --- a/test/components/views/rooms/LegacyRoomHeader-test.tsx +++ b/test/components/views/rooms/LegacyRoomHeader-test.tsx @@ -50,7 +50,7 @@ import { } from "../../../test-utils"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; import DMRoomMap from "../../../../src/utils/DMRoomMap"; -import RoomHeader, { IProps as RoomHeaderProps } from "../../../../src/components/views/rooms/LegacyRoomHeader"; +import RoomHeader from "../../../../src/components/views/rooms/LegacyRoomHeader"; import { SearchScope } from "../../../../src/components/views/rooms/SearchBar"; import { E2EStatus } from "../../../../src/utils/ShieldUtils"; import { IRoomState } from "../../../../src/components/structures/RoomView"; @@ -201,7 +201,10 @@ describe("LegacyRoomHeader", () => { WidgetMessagingStore.instance.stopMessaging(widget, call.roomId); }; - const renderHeader = (props: Partial = {}, roomContext: Partial = {}) => { + const renderHeader = ( + props: Partial> = {}, + roomContext: Partial = {}, + ) => { render( ): RenderResult { - const props: RoomHeaderProps = { + const props: React.ComponentProps = { room, inRoom: true, onSearchClick: () => {}, diff --git a/test/components/views/rooms/VoiceRecordComposerTile-test.tsx b/test/components/views/rooms/VoiceRecordComposerTile-test.tsx index 72456f3c19..e5ac821b6c 100644 --- a/test/components/views/rooms/VoiceRecordComposerTile-test.tsx +++ b/test/components/views/rooms/VoiceRecordComposerTile-test.tsx @@ -43,7 +43,7 @@ jest.mock("../../../../src/stores/VoiceRecordingStore", () => ({ })); describe("", () => { - let voiceRecordComposerTile: RefObject; + let voiceRecordComposerTile: RefObject>; let mockRecorder: VoiceMessageRecording; let mockUpload: IUpload; let mockClient: MatrixClient; diff --git a/test/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete-test.tsx b/test/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete-test.tsx index 0b20cc61b2..310b0d547c 100644 --- a/test/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete-test.tsx +++ b/test/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete-test.tsx @@ -60,7 +60,7 @@ describe("WysiwygAutocomplete", () => { jest.restoreAllMocks(); }); - const autocompleteRef = createRef(); + const autocompleteRef = createRef>(); const getCompletionsSpy = jest.spyOn(Autocompleter.prototype, "getCompletions").mockResolvedValue([ { completions: mockCompletion,