1
0
mirror of https://github.com/matrix-org/matrix-react-sdk.git synced 2025-08-07 21:23:00 +03:00

Remove legacy consumers of the RoomContext in favour of HOCs

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski
2024-04-19 14:31:48 +01:00
parent f4296b4893
commit 9fe036ae1d
52 changed files with 509 additions and 367 deletions

View File

@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { createRef } from "react"; import React, { createRef, forwardRef } from "react";
import { import {
Filter, Filter,
EventTimelineSet, EventTimelineSet,
@@ -45,6 +45,7 @@ interface IProps {
roomId: string; roomId: string;
onClose: () => void; onClose: () => void;
resizeNotifier: ResizeNotifier; resizeNotifier: ResizeNotifier;
context: React.ContextType<typeof RoomContext>;
} }
interface IState { interface IState {
@@ -56,8 +57,6 @@ interface IState {
* Component which shows the filtered file using a TimelinePanel * Component which shows the filtered file using a TimelinePanel
*/ */
class FilePanel extends React.Component<IProps, IState> { class FilePanel extends React.Component<IProps, IState> {
public static contextType = RoomContext;
// This is used to track if a decrypted event was a live event and should be // This is used to track if a decrypted event was a live event and should be
// added to the timeline. // added to the timeline.
private decryptingEvents = new Set<string>(); private decryptingEvents = new Set<string>();
@@ -267,7 +266,7 @@ class FilePanel extends React.Component<IProps, IState> {
return ( return (
<RoomContext.Provider <RoomContext.Provider
value={{ value={{
...this.context, ...this.props.context,
timelineRenderingType: TimelineRenderingType.File, timelineRenderingType: TimelineRenderingType.File,
narrow: this.state.narrow, narrow: this.state.narrow,
}} }}
@@ -299,7 +298,7 @@ class FilePanel extends React.Component<IProps, IState> {
return ( return (
<RoomContext.Provider <RoomContext.Provider
value={{ value={{
...this.context, ...this.props.context,
timelineRenderingType: TimelineRenderingType.File, timelineRenderingType: TimelineRenderingType.File,
}} }}
> >
@@ -312,4 +311,6 @@ class FilePanel extends React.Component<IProps, IState> {
} }
} }
export default FilePanel; export default forwardRef<FilePanel, Omit<IProps, "context">>((props, ref) => (
<RoomContext.Consumer>{(context) => <FilePanel {...props} context={context} ref={ref} />}</RoomContext.Consumer>
));

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { createRef, ReactNode, TransitionEvent } from "react"; import React, { createRef, forwardRef, ReactNode, TransitionEvent } from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import classNames from "classnames"; import classNames from "classnames";
import { Room, MatrixClient, RoomStateEvent, EventStatus, MatrixEvent, EventType } from "matrix-js-sdk/src/matrix"; import { Room, MatrixClient, RoomStateEvent, EventStatus, MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
@@ -31,7 +31,6 @@ import EventTile, {
GetRelationsForEvent, GetRelationsForEvent,
IReadReceiptProps, IReadReceiptProps,
isEligibleForSpecialReceipt, isEligibleForSpecialReceipt,
UnwrappedEventTile,
} from "../views/rooms/EventTile"; } from "../views/rooms/EventTile";
import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer"; import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer";
import defaultDispatcher from "../../dispatcher/dispatcher"; import defaultDispatcher from "../../dispatcher/dispatcher";
@@ -188,6 +187,7 @@ interface IProps {
disableGrouping?: boolean; disableGrouping?: boolean;
callEventGroupers: Map<string, LegacyCallEventGrouper>; callEventGroupers: Map<string, LegacyCallEventGrouper>;
context: React.ContextType<typeof RoomContext>;
} }
interface IState { interface IState {
@@ -203,10 +203,7 @@ interface IReadReceiptForUser {
/* (almost) stateless UI component which builds the event tiles in the room timeline. /* (almost) stateless UI component which builds the event tiles in the room timeline.
*/ */
export default class MessagePanel extends React.Component<IProps, IState> { class MessagePanel extends React.Component<IProps, IState> {
public static contextType = RoomContext;
public context!: React.ContextType<typeof RoomContext>;
public static defaultProps = { public static defaultProps = {
disableGrouping: false, disableGrouping: false,
}; };
@@ -256,13 +253,13 @@ export default class MessagePanel extends React.Component<IProps, IState> {
private scrollPanel = createRef<ScrollPanel>(); private scrollPanel = createRef<ScrollPanel>();
private readonly showTypingNotificationsWatcherRef: string; private readonly showTypingNotificationsWatcherRef: string;
private eventTiles: Record<string, UnwrappedEventTile> = {}; private eventTiles: Record<string, React.ComponentRef<typeof EventTile>> = {};
// A map to allow groupers to maintain consistent keys even if their first event is uprooted due to back-pagination. // A map to allow groupers to maintain consistent keys even if their first event is uprooted due to back-pagination.
public grouperKeyMap = new WeakMap<MatrixEvent, string>(); public grouperKeyMap = new WeakMap<MatrixEvent, string>();
public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) { public constructor(props: IProps) {
super(props, context); super(props);
this.state = { this.state = {
// previous positions the read marker has been in, so we can // previous positions the read marker has been in, so we can
@@ -320,7 +317,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
defaultDispatcher.dispatch({ defaultDispatcher.dispatch({
action: Action.EditEvent, action: Action.EditEvent,
event: !event?.isRedacted() ? event : null, 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<IProps, IState> {
return this.eventTiles[eventId]?.ref?.current ?? undefined; return this.eventTiles[eventId]?.ref?.current ?? undefined;
} }
public getTileForEventId(eventId?: string): UnwrappedEventTile | undefined { public getTileForEventId(eventId?: string): React.ComponentRef<typeof EventTile> | undefined {
if (!this.eventTiles || !eventId) { if (!this.eventTiles || !eventId) {
return undefined; return undefined;
} }
@@ -454,7 +451,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
}; };
public get showHiddenEvents(): boolean { public get showHiddenEvents(): boolean {
return this.context?.showHiddenEvents ?? this._showHiddenEvents; return this.props.context?.showHiddenEvents ?? this._showHiddenEvents;
} }
// TODO: Implement granular (per-room) hide options // TODO: Implement granular (per-room) hide options
@@ -481,7 +478,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
// Always show highlighted event // Always show highlighted event
if (this.props.highlightedEventId === mxEv.getId()) return true; 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 { public readMarkerForEvent(eventId: string, isLastEvent: boolean): ReactNode {
@@ -592,7 +589,9 @@ export default class MessagePanel extends React.Component<IProps, IState> {
} }
try { 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) { } catch (err) {
logger.error(err); logger.error(err);
return null; return null;
@@ -775,13 +774,25 @@ export default class MessagePanel extends React.Component<IProps, IState> {
willWantSeparator === SeparatorKind.Date || willWantSeparator === SeparatorKind.Date ||
mxEv.getSender() !== nextEv.getSender() || mxEv.getSender() !== nextEv.getSender() ||
getEventDisplayInfo(cli, nextEv, this.showHiddenEvents).isInfoMessage || 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? // is this a continuation of the previous message?
const continuation = const continuation =
wantsSeparator === SeparatorKind.None && 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 eventId = mxEv.getId()!;
const highlight = eventId === this.props.highlightedEventId; const highlight = eventId === this.props.highlightedEventId;
@@ -826,7 +837,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
} }
public wantsSeparator(prevEvent: MatrixEvent | null, mxEvent: MatrixEvent): SeparatorKind { public wantsSeparator(prevEvent: MatrixEvent | null, mxEvent: MatrixEvent): SeparatorKind {
if (this.context.timelineRenderingType === TimelineRenderingType.ThreadsList) { if (this.props.context.timelineRenderingType === TimelineRenderingType.ThreadsList) {
return SeparatorKind.None; return SeparatorKind.None;
} }
@@ -863,13 +874,13 @@ export default class MessagePanel extends React.Component<IProps, IState> {
return null; 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[] = []; const receipts: IReadReceiptProps[] = [];
if (!receiptDestination) { if (!receiptDestination) {
logger.debug( 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; return receipts;
} }
@@ -950,7 +961,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
return receiptsByEvent; return receiptsByEvent;
} }
private collectEventTile = (eventId: string, node: UnwrappedEventTile): void => { private collectEventTile = (eventId: string, node: React.ComponentRef<typeof EventTile>): void => {
this.eventTiles[eventId] = node; this.eventTiles[eventId] = node;
}; };
@@ -1033,7 +1044,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
if ( if (
this.props.room && this.props.room &&
this.state.showTypingNotifications && this.state.showTypingNotifications &&
this.context.timelineRenderingType === TimelineRenderingType.Room this.props.context.timelineRenderingType === TimelineRenderingType.Room
) { ) {
whoIsTyping = ( whoIsTyping = (
<WhoIsTypingTile <WhoIsTypingTile
@@ -1053,7 +1064,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
} }
const classes = classNames(this.props.className, { const classes = classNames(this.props.className, {
mx_MessagePanel_narrow: this.context.narrow, mx_MessagePanel_narrow: this.props.context.narrow,
}); });
return ( return (
@@ -1079,10 +1090,14 @@ export default class MessagePanel extends React.Component<IProps, IState> {
} }
} }
export default forwardRef<MessagePanel, Omit<IProps, "context">>((props, ref) => (
<RoomContext.Consumer>{(context) => <MessagePanel {...props} context={context} ref={ref} />}</RoomContext.Consumer>
));
/** /**
* 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. * 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. * by a consumer from the event alone, so has to be done by the event list processing code earlier.
*/ */
export interface WrappedEvent { export interface WrappedEvent {

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from "react"; import React, { forwardRef } from "react";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { _t } from "../../languageHandler"; import { _t } from "../../languageHandler";
@@ -29,6 +29,7 @@ import Heading from "../views/typography/Heading";
interface IProps { interface IProps {
onClose(): void; onClose(): void;
context: React.ContextType<typeof RoomContext>;
} }
interface IState { interface IState {
@@ -38,9 +39,7 @@ interface IState {
/* /*
* Component which shows the global notification list using a TimelinePanel * Component which shows the global notification list using a TimelinePanel
*/ */
export default class NotificationPanel extends React.PureComponent<IProps, IState> { class NotificationPanel extends React.PureComponent<IProps, IState> {
public static contextType = RoomContext;
private card = React.createRef<HTMLDivElement>(); private card = React.createRef<HTMLDivElement>();
public constructor(props: IProps) { public constructor(props: IProps) {
@@ -86,7 +85,7 @@ export default class NotificationPanel extends React.PureComponent<IProps, IStat
return ( return (
<RoomContext.Provider <RoomContext.Provider
value={{ value={{
...this.context, ...this.props.context,
timelineRenderingType: TimelineRenderingType.Notification, timelineRenderingType: TimelineRenderingType.Notification,
narrow: this.state.narrow, narrow: this.state.narrow,
}} }}
@@ -114,3 +113,9 @@ export default class NotificationPanel extends React.PureComponent<IProps, IStat
); );
} }
} }
export default forwardRef<NotificationPanel, Omit<IProps, "context">>((props, ref) => (
<RoomContext.Consumer>
{(context) => <NotificationPanel {...props} context={context} ref={ref} />}
</RoomContext.Consumer>
));

View File

@@ -323,7 +323,7 @@ function LocalRoomView(props: LocalRoomViewProps): ReactElement {
<RoomHeader room={room} /> <RoomHeader room={room} />
) : ( ) : (
<LegacyRoomHeader <LegacyRoomHeader
room={context.room} room={context.room!}
searchInfo={undefined} searchInfo={undefined}
inRoom={true} inRoom={true}
onSearchClick={null} onSearchClick={null}
@@ -410,7 +410,7 @@ class RoomView extends React.Component<IRoomProps, IRoomState> {
private roomView = createRef<HTMLDivElement>(); private roomView = createRef<HTMLDivElement>();
private searchResultsPanel = createRef<ScrollPanel>(); private searchResultsPanel = createRef<ScrollPanel>();
private messagePanel: TimelinePanel | null = null; private messagePanel: React.ComponentRef<typeof TimelinePanel> | null = null;
private roomViewBody = createRef<HTMLDivElement>(); private roomViewBody = createRef<HTMLDivElement>();
public constructor(props: IRoomProps) { public constructor(props: IRoomProps) {
@@ -1958,7 +1958,7 @@ class RoomView extends React.Component<IRoomProps, IRoomState> {
* We pass it down to the scroll panel. * We pass it down to the scroll panel.
*/ */
public handleScrollKey = (ev: React.KeyboardEvent | KeyboardEvent): void => { public handleScrollKey = (ev: React.KeyboardEvent | KeyboardEvent): void => {
let panel: ScrollPanel | TimelinePanel | undefined; let panel: ScrollPanel | React.ComponentRef<typeof TimelinePanel> | undefined;
if (this.searchResultsPanel.current) { if (this.searchResultsPanel.current) {
panel = this.searchResultsPanel.current; panel = this.searchResultsPanel.current;
} else if (this.messagePanel) { } else if (this.messagePanel) {
@@ -1980,7 +1980,7 @@ class RoomView extends React.Component<IRoomProps, IRoomState> {
// this has to be a proper method rather than an unnamed function, // this has to be a proper method rather than an unnamed function,
// otherwise react calls it with null on each update. // otherwise react calls it with null on each update.
private gatherTimelinePanelRef = (r: TimelinePanel | null): void => { private gatherTimelinePanelRef = (r: React.ComponentRef<typeof TimelinePanel> | null): void => {
this.messagePanel = r; this.messagePanel = r;
}; };

View File

@@ -230,7 +230,7 @@ const EmptyThread: React.FC<EmptyThreadIProps> = ({ hasThreads, filterOption, sh
const ThreadPanel: React.FC<IProps> = ({ roomId, onClose, permalinkCreator }) => { const ThreadPanel: React.FC<IProps> = ({ roomId, onClose, permalinkCreator }) => {
const mxClient = useContext(MatrixClientContext); const mxClient = useContext(MatrixClientContext);
const roomContext = useContext(RoomContext); const roomContext = useContext(RoomContext);
const timelinePanel = useRef<TimelinePanel | null>(null); const timelinePanel = useRef<React.ComponentRef<typeof TimelinePanel> | null>(null);
const card = useRef<HTMLDivElement | null>(null); const card = useRef<HTMLDivElement | null>(null);
const closeButonRef = useRef<HTMLDivElement | null>(null); const closeButonRef = useRef<HTMLDivElement | null>(null);

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { createRef, KeyboardEvent } from "react"; import React, { createRef, forwardRef, KeyboardEvent } from "react";
import { import {
Thread, Thread,
THREAD_RELATION_TYPE, THREAD_RELATION_TYPE,
@@ -70,6 +70,7 @@ interface IProps {
initialEvent?: MatrixEvent; initialEvent?: MatrixEvent;
isInitialEventHighlighted?: boolean; isInitialEventHighlighted?: boolean;
initialEventScrollIntoView?: boolean; initialEventScrollIntoView?: boolean;
context: React.ContextType<typeof RoomContext>;
} }
interface IState { interface IState {
@@ -81,13 +82,10 @@ interface IState {
narrow: boolean; narrow: boolean;
} }
export default class ThreadView extends React.Component<IProps, IState> { class ThreadView extends React.Component<IProps, IState> {
public static contextType = RoomContext;
public context!: React.ContextType<typeof RoomContext>;
private dispatcherRef: string | null = null; private dispatcherRef: string | null = null;
private readonly layoutWatcherRef: string; private readonly layoutWatcherRef: string;
private timelinePanel = createRef<TimelinePanel>(); private timelinePanel = createRef<React.ComponentRef<typeof TimelinePanel>>();
private card = createRef<HTMLDivElement>(); private card = createRef<HTMLDivElement>();
// Set by setEventId in ctor. // Set by setEventId in ctor.
@@ -398,12 +396,12 @@ export default class ThreadView extends React.Component<IProps, IState> {
<TimelinePanel <TimelinePanel
key={this.state.thread.id} key={this.state.thread.id}
ref={this.timelinePanel} ref={this.timelinePanel}
showReadReceipts={this.context.showReadReceipts} showReadReceipts={this.props.context.showReadReceipts}
manageReadReceipts={true} manageReadReceipts={true}
manageReadMarkers={true} manageReadMarkers={true}
sendReadReceiptOnLoad={true} sendReadReceiptOnLoad={true}
timelineSet={this.state.thread.timelineSet} timelineSet={this.state.thread.timelineSet}
showUrlPreview={this.context.showUrlPreview} showUrlPreview={this.props.context.showUrlPreview}
// ThreadView doesn't support IRC layout at this time // ThreadView doesn't support IRC layout at this time
layout={this.state.layout === Layout.Bubble ? Layout.Bubble : Layout.Group} layout={this.state.layout === Layout.Bubble ? Layout.Bubble : Layout.Group}
hideThreadedMessages={false} hideThreadedMessages={false}
@@ -474,3 +472,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
); );
} }
} }
export default forwardRef<ThreadView, Omit<IProps, "context">>((props, ref) => (
<RoomContext.Consumer>{(context) => <ThreadView {...props} context={context} ref={ref} />}</RoomContext.Consumer>
));

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { createRef, ReactNode } from "react"; import React, { createRef, forwardRef, ReactNode } from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import { import {
Room, Room,
@@ -167,6 +167,7 @@ interface IProps {
hideThreadedMessages?: boolean; hideThreadedMessages?: boolean;
disableGrouping?: boolean; disableGrouping?: boolean;
context: React.ContextType<typeof RoomContext>;
} }
interface IState { interface IState {
@@ -244,10 +245,7 @@ interface IEventIndexOpts {
* *
* Also responsible for handling and sending read receipts. * Also responsible for handling and sending read receipts.
*/ */
class TimelinePanel extends React.Component<IProps, IState> { export class TimelinePanel extends React.Component<IProps, IState> {
public static contextType = RoomContext;
public context!: React.ContextType<typeof RoomContext>;
// a map from room id to read marker event timestamp // a map from room id to read marker event timestamp
public static roomReadMarkerTsMap: Record<string, number> = {}; public static roomReadMarkerTsMap: Record<string, number> = {};
@@ -264,7 +262,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
private lastRRSentEventId: string | null | undefined = undefined; private lastRRSentEventId: string | null | undefined = undefined;
private lastRMSentEventId: string | null | undefined = undefined; private lastRMSentEventId: string | null | undefined = undefined;
private readonly messagePanel = createRef<MessagePanel>(); private readonly messagePanel = createRef<React.ComponentRef<typeof MessagePanel>>();
private readonly dispatcherRef: string; private readonly dispatcherRef: string;
private timelineWindow?: TimelineWindow; private timelineWindow?: TimelineWindow;
private overlayTimelineWindow?: TimelineWindow; private overlayTimelineWindow?: TimelineWindow;
@@ -276,9 +274,8 @@ class TimelinePanel extends React.Component<IProps, IState> {
private callEventGroupers = new Map<string, LegacyCallEventGrouper>(); private callEventGroupers = new Map<string, LegacyCallEventGrouper>();
private initialReadMarkerId: string | null = null; private initialReadMarkerId: string | null = null;
public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) { public constructor(props: IProps) {
super(props, context); super(props);
this.context = context;
debuglog("mounting"); debuglog("mounting");
@@ -500,7 +497,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
} }
logger.debug( 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` + `\tevents(${eventIdList.length})=${JSON.stringify(eventIdList)}\n` +
`\trenderedEventIds(${renderedEventIds?.length ?? 0})=` + `\trenderedEventIds(${renderedEventIds?.length ?? 0})=` +
`${JSON.stringify(renderedEventIds)}\n` + `${JSON.stringify(renderedEventIds)}\n` +
@@ -736,7 +733,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
return; return;
} }
if (!Thread.hasServerSideSupport && this.context.timelineRenderingType === TimelineRenderingType.Thread) { if (!Thread.hasServerSideSupport && this.props.context.timelineRenderingType === TimelineRenderingType.Thread) {
if (toStartOfTimeline && !this.state.canBackPaginate) { if (toStartOfTimeline && !this.state.canBackPaginate) {
this.setState({ this.setState({
canBackPaginate: true, canBackPaginate: true,
@@ -1780,8 +1777,8 @@ class TimelinePanel extends React.Component<IProps, IState> {
pendingEvents, pendingEvents,
); );
if (this.context.timelineRenderingType === TimelineRenderingType.Thread) { if (this.props.context.timelineRenderingType === TimelineRenderingType.Thread) {
return threadId === this.context.threadId; return threadId === this.props.context.threadId;
} }
{ {
return shouldLiveInRoom; return shouldLiveInRoom;
@@ -1812,7 +1809,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
const room = this.props.timelineSet.room; const room = this.props.timelineSet.room;
const isThreadTimeline = [TimelineRenderingType.Thread, TimelineRenderingType.ThreadsList].includes( const isThreadTimeline = [TimelineRenderingType.Thread, TimelineRenderingType.ThreadsList].includes(
this.context.timelineRenderingType, this.props.context.timelineRenderingType,
); );
if (events.length === 0 || !room || !cli.isRoomEncrypted(room.roomId) || isThreadTimeline) { if (events.length === 0 || !room || !cli.isRoomEncrypted(room.roomId) || isThreadTimeline) {
logger.debug("checkForPreJoinUISI: showing all messages, skipping check"); logger.debug("checkForPreJoinUISI: showing all messages, skipping check");
@@ -1881,7 +1878,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
/* Threads do not have server side support for read receipts and the concept /* 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 is very tied to the main room timeline, we are forcing the timeline to
send read receipts for threaded events */ send read receipts for threaded events */
if (this.context.timelineRenderingType === TimelineRenderingType.Thread) { if (this.props.context.timelineRenderingType === TimelineRenderingType.Thread) {
return 0; return 0;
} }
const index = this.state.events.findIndex((ev) => ev.getId() === evId); const index = this.state.events.findIndex((ev) => ev.getId() === evId);
@@ -2193,4 +2190,6 @@ function serializeEventIdsFromTimelineSets(timelineSets: EventTimelineSet[]): {
return serializedEventIdsInTimelineSet; return serializedEventIdsInTimelineSet;
} }
export default TimelinePanel; export default forwardRef<TimelinePanel, Omit<IProps, "context">>((props, ref) => (
<RoomContext.Consumer>{(context) => <TimelinePanel {...props} context={context} ref={ref} />}</RoomContext.Consumer>
));

View File

@@ -26,7 +26,7 @@ import RoomHeader from "../views/rooms/RoomHeader";
import ScrollPanel from "./ScrollPanel"; import ScrollPanel from "./ScrollPanel";
import EventTileBubble from "../views/messages/EventTileBubble"; import EventTileBubble from "../views/messages/EventTileBubble";
import NewRoomIntro from "../views/rooms/NewRoomIntro"; import NewRoomIntro from "../views/rooms/NewRoomIntro";
import { UnwrappedEventTile } from "../views/rooms/EventTile"; import EventTile from "../views/rooms/EventTile";
import { _t } from "../../languageHandler"; import { _t } from "../../languageHandler";
import SdkConfig from "../../SdkConfig"; import SdkConfig from "../../SdkConfig";
import SettingsStore from "../../settings/SettingsStore"; import SettingsStore from "../../settings/SettingsStore";
@@ -53,7 +53,7 @@ export const WaitingForThirdPartyRoomView: React.FC<Props> = ({ roomView, resize
<RoomHeader room={context.room!} /> <RoomHeader room={context.room!} />
) : ( ) : (
<LegacyRoomHeader <LegacyRoomHeader
room={context.room} room={context.room!}
inRoom={true} inRoom={true}
onSearchClick={null} onSearchClick={null}
onInviteClick={null} onInviteClick={null}
@@ -77,7 +77,7 @@ export const WaitingForThirdPartyRoomView: React.FC<Props> = ({ roomView, resize
subtitle={_t("room|waiting_for_join_subtitle", { brand })} subtitle={_t("room|waiting_for_join_subtitle", { brand })}
/> />
<NewRoomIntro /> <NewRoomIntro />
<UnwrappedEventTile mxEvent={inviteEvent} /> <EventTile mxEvent={inviteEvent} />
</ScrollPanel> </ScrollPanel>
</div> </div>
</main> </main>

View File

@@ -31,7 +31,7 @@ import MessagePanel, { WrappedEvent } from "../MessagePanel";
* when determining things such as whether a date separator is necessary * when determining things such as whether a date separator is necessary
*/ */
export abstract class BaseGrouper { export abstract class BaseGrouper {
public static canStartGroup = (_panel: MessagePanel, _ev: WrappedEvent): boolean => true; public static canStartGroup = (_panel: React.ComponentRef<typeof MessagePanel>, _ev: WrappedEvent): boolean => true;
public events: WrappedEvent[] = []; public events: WrappedEvent[] = [];
// events that we include in the group but then eject out and place above the group. // 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 readMarker: ReactNode;
public constructor( public constructor(
public readonly panel: MessagePanel, public readonly panel: React.ComponentRef<typeof MessagePanel>,
public readonly firstEventAndShouldShow: WrappedEvent, public readonly firstEventAndShouldShow: WrappedEvent,
public readonly prevEvent: MatrixEvent | null, public readonly prevEvent: MatrixEvent | null,
public readonly lastShownEvent: MatrixEvent | undefined, public readonly lastShownEvent: MatrixEvent | undefined,

View File

@@ -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 // 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 { export class CreationGrouper extends BaseGrouper {
public static canStartGroup = function (_panel: MessagePanel, { event }: WrappedEvent): boolean { public static canStartGroup = function (
_panel: React.ComponentRef<typeof MessagePanel>,
{ event }: WrappedEvent,
): boolean {
return event.getType() === EventType.RoomCreate; return event.getType() === EventType.RoomCreate;
}; };

View File

@@ -36,7 +36,10 @@ const groupedStateEvents = [
// Wrap consecutive grouped events in a ListSummary // Wrap consecutive grouped events in a ListSummary
export class MainGrouper extends BaseGrouper { export class MainGrouper extends BaseGrouper {
public static canStartGroup = function (panel: MessagePanel, { event: ev, shouldShow }: WrappedEvent): boolean { public static canStartGroup = function (
panel: React.ComponentRef<typeof MessagePanel>,
{ event: ev, shouldShow }: WrappedEvent,
): boolean {
if (!shouldShow) return false; if (!shouldShow) return false;
if (ev.isState() && groupedStateEvents.includes(ev.getType() as EventType)) { if (ev.isState() && groupedStateEvents.includes(ev.getType() as EventType)) {
@@ -55,7 +58,7 @@ export class MainGrouper extends BaseGrouper {
}; };
public constructor( public constructor(
public readonly panel: MessagePanel, public readonly panel: React.ComponentRef<typeof MessagePanel>,
public readonly firstEventAndShouldShow: WrappedEvent, public readonly firstEventAndShouldShow: WrappedEvent,
public readonly prevEvent: MatrixEvent | null, public readonly prevEvent: MatrixEvent | null,
public readonly lastShownEvent: MatrixEvent | undefined, public readonly lastShownEvent: MatrixEvent | undefined,

View File

@@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { createRef, useContext } from "react"; import React, { createRef, forwardRef, useContext } from "react";
import { import {
EventStatus, EventStatus,
MatrixEvent, MatrixEvent,
@@ -123,6 +123,7 @@ interface IProps extends MenuProps {
link?: string; link?: string;
getRelationsForEvent?: GetRelationsForEvent; getRelationsForEvent?: GetRelationsForEvent;
context: React.ContextType<typeof RoomContext>;
} }
interface IState { interface IState {
@@ -131,11 +132,8 @@ interface IState {
reactionPickerDisplayed: boolean; reactionPickerDisplayed: boolean;
} }
export default class MessageContextMenu extends React.Component<IProps, IState> { class MessageContextMenu extends React.Component<IProps, IState> {
public static contextType = RoomContext; private reactButtonRef = createRef<HTMLElement>();
public context!: React.ContextType<typeof RoomContext>;
private reactButtonRef = createRef<any>(); // XXX Ref to a functional component
public constructor(props: IProps) { public constructor(props: IProps) {
super(props); super(props);
@@ -315,7 +313,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
editEvent( editEvent(
MatrixClientPeg.safeGet(), MatrixClientPeg.safeGet(),
this.props.mxEvent, this.props.mxEvent,
this.context.timelineRenderingType, this.props.context.timelineRenderingType,
this.props.getRelationsForEvent, this.props.getRelationsForEvent,
); );
this.closeMenu(); this.closeMenu();
@@ -325,7 +323,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
dis.dispatch({ dis.dispatch({
action: "reply_to_event", action: "reply_to_event",
event: this.props.mxEvent, event: this.props.mxEvent,
context: this.context.timelineRenderingType, context: this.props.context.timelineRenderingType,
}); });
this.closeMenu(); this.closeMenu();
}; };
@@ -733,3 +731,9 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
); );
} }
} }
export default forwardRef<MessageContextMenu, Omit<IProps, "context">>((props, ref) => (
<RoomContext.Consumer>
{(context) => <MessageContextMenu {...props} context={context} ref={ref} />}
</RoomContext.Consumer>
));

View File

@@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { ComponentProps, ReactNode } from "react"; import React, { ComponentProps, forwardRef, ReactNode } from "react";
import { MatrixEvent, RoomMember, EventType } from "matrix-js-sdk/src/matrix"; import { MatrixEvent, RoomMember, EventType } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types"; import { KnownMembership } from "matrix-js-sdk/src/types";
@@ -43,7 +43,8 @@ interface IProps extends Omit<ComponentProps<typeof GenericEventListSummary>, "s
// The maximum number of avatars to display in the summary // The maximum number of avatars to display in the summary
avatarsMaxLength?: number; avatarsMaxLength?: number;
// The currently selected layout // The currently selected layout
layout: Layout; layout?: Layout;
context: React.ContextType<typeof RoomContext>;
} }
interface IUserEvents { interface IUserEvents {
@@ -77,12 +78,9 @@ enum TransitionType {
const SEP = ","; const SEP = ",";
export default class EventListSummary extends React.Component< class EventListSummary extends React.Component<
IProps & Required<Pick<IProps, "summaryLength" | "threshold" | "avatarsMaxLength" | "layout">> IProps & Required<Pick<IProps, "summaryLength" | "threshold" | "avatarsMaxLength" | "layout">>
> { > {
public static contextType = RoomContext;
public context!: React.ContextType<typeof RoomContext>;
public static defaultProps = { public static defaultProps = {
summaryLength: 1, summaryLength: 1,
threshold: 3, threshold: 3,
@@ -527,7 +525,7 @@ export default class EventListSummary extends React.Component<
let displayName = userKey; let displayName = userKey;
if (e.isRedacted()) { if (e.isRedacted()) {
const sender = this.context?.room?.getMember(userKey); const sender = this.props.context?.room?.getMember(userKey);
if (sender) { if (sender) {
displayName = sender.name; displayName = sender.name;
latestUserAvatarMember.set(userKey, sender); latestUserAvatarMember.set(userKey, sender);
@@ -569,3 +567,9 @@ export default class EventListSummary extends React.Component<
); );
} }
} }
export default forwardRef<EventListSummary, Omit<IProps, "context">>((props, ref) => (
<RoomContext.Consumer>
{(context) => <EventListSummary {...props} context={context} ref={ref} />}
</RoomContext.Consumer>
));

View File

@@ -31,7 +31,6 @@ import ReplyTile from "../rooms/ReplyTile";
import { Pill, PillType } from "./Pill"; import { Pill, PillType } from "./Pill";
import AccessibleButton from "./AccessibleButton"; import AccessibleButton from "./AccessibleButton";
import { getParentEventId, shouldDisplayReply } from "../../../utils/Reply"; import { getParentEventId, shouldDisplayReply } from "../../../utils/Reply";
import RoomContext from "../../../contexts/RoomContext";
import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { GetRelationsForEvent } from "../rooms/EventTile"; 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 // 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. // be low as each event being loaded (after the first) is triggered by an explicit user action.
export default class ReplyChain extends React.Component<IProps, IState> { export default class ReplyChain extends React.Component<IProps, IState> {
public static contextType = RoomContext;
public context!: React.ContextType<typeof RoomContext>;
private unmounted = false; private unmounted = false;
private room: Room; private room: Room;
private blockquoteRef = React.createRef<HTMLQuoteElement>(); private blockquoteRef = React.createRef<HTMLQuoteElement>();
public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) { public constructor(props: IProps) {
super(props, context); super(props);
this.state = { this.state = {
events: [], events: [],

View File

@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from "react"; import React, { forwardRef } from "react";
import { MatrixEvent, EventType, RelationType, Relations, RelationsEvent } from "matrix-js-sdk/src/matrix"; import { MatrixEvent, EventType, RelationType, Relations, RelationsEvent } from "matrix-js-sdk/src/matrix";
import EmojiPicker from "./EmojiPicker"; import EmojiPicker from "./EmojiPicker";
@@ -29,6 +29,7 @@ interface IProps {
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
reactions?: Relations | null | undefined; reactions?: Relations | null | undefined;
onFinished(): void; onFinished(): void;
context: React.ContextType<typeof RoomContext>;
} }
interface IState { interface IState {
@@ -36,11 +37,8 @@ interface IState {
} }
class ReactionPicker extends React.Component<IProps, IState> { class ReactionPicker extends React.Component<IProps, IState> {
public static contextType = RoomContext; public constructor(props: IProps) {
public context!: React.ContextType<typeof RoomContext>; super(props);
public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) {
super(props, context);
this.state = { this.state = {
selectedEmojis: new Set(Object.keys(this.getReactions())), selectedEmojis: new Set(Object.keys(this.getReactions())),
@@ -95,12 +93,12 @@ class ReactionPicker extends React.Component<IProps, IState> {
this.props.onFinished(); this.props.onFinished();
const myReactions = this.getReactions(); const myReactions = this.getReactions();
if (myReactions.hasOwnProperty(reaction)) { 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]); MatrixClientPeg.safeGet().redactEvent(this.props.mxEvent.getRoomId()!, myReactions[reaction]);
dis.dispatch<FocusComposerPayload>({ dis.dispatch<FocusComposerPayload>({
action: Action.FocusAComposer, 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. // Tell the emoji picker not to bump this in the more frequently used list.
return false; return false;
@@ -115,7 +113,7 @@ class ReactionPicker extends React.Component<IProps, IState> {
dis.dispatch({ action: "message_sent" }); dis.dispatch({ action: "message_sent" });
dis.dispatch<FocusComposerPayload>({ dis.dispatch<FocusComposerPayload>({
action: Action.FocusAComposer, action: Action.FocusAComposer,
context: this.context.timelineRenderingType, context: this.props.context.timelineRenderingType,
}); });
return true; return true;
} }
@@ -123,7 +121,7 @@ class ReactionPicker extends React.Component<IProps, IState> {
private isEmojiDisabled = (unicode: string): boolean => { private isEmojiDisabled = (unicode: string): boolean => {
if (!this.getReactions()[unicode]) return false; if (!this.getReactions()[unicode]) return false;
if (this.context.canSelfRedact) return false; if (this.props.context.canSelfRedact) return false;
return true; return true;
}; };
@@ -140,4 +138,8 @@ class ReactionPicker extends React.Component<IProps, IState> {
} }
} }
export default ReactionPicker; export default forwardRef<ReactionPicker, Omit<IProps, "context">>((props, ref) => (
<RoomContext.Consumer>
{(context) => <ReactionPicker {...props} context={context} ref={ref} />}
</RoomContext.Consumer>
));

View File

@@ -163,7 +163,7 @@ interface CallEventProps {
/** /**
* An event tile representing an active or historical Element call. * An event tile representing an active or historical Element call.
*/ */
export const CallEvent = forwardRef<any, CallEventProps>(({ mxEvent }, ref) => { export const CallEvent = forwardRef<HTMLDivElement, CallEventProps>(({ mxEvent }, ref) => {
const client = useContext(MatrixClientContext); const client = useContext(MatrixClientContext);
const call = useCall(mxEvent.getRoomId()!); const call = useCall(mxEvent.getRoomId()!);
const latestEvent = client const latestEvent = client

View File

@@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { LegacyRef } from "react";
import { MatrixEvent } from "matrix-js-sdk/src/matrix"; import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import { MediaEventHelper } from "../../../utils/MediaEventHelper"; import { MediaEventHelper } from "../../../utils/MediaEventHelper";
@@ -54,8 +53,6 @@ export interface IBodyProps {
// helper function to access relations for this event // helper function to access relations for this event
getRelationsForEvent?: GetRelationsForEvent; getRelationsForEvent?: GetRelationsForEvent;
ref?: React.RefObject<any> | LegacyRef<any>;
// Set to `true` to disable interactions (e.g. video controls) and to remove controls from the tab order. // 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. // This may be useful when displaying a preview of the event.
inhibitInteraction?: boolean; inhibitInteraction?: boolean;

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from "react"; import React, { forwardRef } from "react";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { IContent } from "matrix-js-sdk/src/matrix"; import { IContent } from "matrix-js-sdk/src/matrix";
import { MediaEventContent } from "matrix-js-sdk/src/types"; import { MediaEventContent } from "matrix-js-sdk/src/types";
@@ -31,16 +31,17 @@ import { PlaybackQueue } from "../../../audio/PlaybackQueue";
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
import MediaProcessingError from "./shared/MediaProcessingError"; import MediaProcessingError from "./shared/MediaProcessingError";
interface Props extends IBodyProps {
context: React.ContextType<typeof RoomContext>;
}
interface IState { interface IState {
error?: boolean; error?: boolean;
playback?: Playback; playback?: Playback;
} }
export default class MAudioBody extends React.PureComponent<IBodyProps, IState> { export class MAudioBody extends React.PureComponent<Props, IState> {
public static contextType = RoomContext; public constructor(props: Props) {
public context!: React.ContextType<typeof RoomContext>;
public constructor(props: IBodyProps) {
super(props); super(props);
this.state = {}; this.state = {};
@@ -88,9 +89,9 @@ export default class MAudioBody extends React.PureComponent<IBodyProps, IState>
protected get showFileBody(): boolean { protected get showFileBody(): boolean {
return ( return (
this.context.timelineRenderingType !== TimelineRenderingType.Room && this.props.context.timelineRenderingType !== TimelineRenderingType.Room &&
this.context.timelineRenderingType !== TimelineRenderingType.Pinned && this.props.context.timelineRenderingType !== TimelineRenderingType.Pinned &&
this.context.timelineRenderingType !== TimelineRenderingType.Search this.props.context.timelineRenderingType !== TimelineRenderingType.Search
); );
} }
@@ -131,3 +132,7 @@ export default class MAudioBody extends React.PureComponent<IBodyProps, IState>
); );
} }
} }
export default forwardRef<MAudioBody, Omit<Props, "context">>((props, ref) => (
<RoomContext.Consumer>{(context) => <MAudioBody {...props} context={context} ref={ref} />}</RoomContext.Consumer>
));

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { AllHTMLAttributes, createRef } from "react"; import React, { AllHTMLAttributes, createRef, forwardRef } from "react";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { MediaEventContent } from "matrix-js-sdk/src/types"; import { MediaEventContent } from "matrix-js-sdk/src/types";
@@ -97,17 +97,15 @@ export function computedStyle(element: HTMLElement | null): string {
interface IProps extends IBodyProps { interface IProps extends IBodyProps {
/* whether or not to show the default placeholder for the file. Defaults to true. */ /* whether or not to show the default placeholder for the file. Defaults to true. */
showGenericPlaceholder: boolean; showGenericPlaceholder?: boolean;
context: React.ContextType<typeof RoomContext>;
} }
interface IState { interface IState {
decryptedBlob?: Blob; decryptedBlob?: Blob;
} }
export default class MFileBody extends React.Component<IProps, IState> { class MFileBody extends React.Component<IProps, IState> {
public static contextType = RoomContext;
public context!: React.ContextType<typeof RoomContext>;
public static defaultProps = { public static defaultProps = {
showGenericPlaceholder: true, showGenericPlaceholder: true,
}; };
@@ -226,11 +224,11 @@ export default class MFileBody extends React.Component<IProps, IState> {
let showDownloadLink = let showDownloadLink =
!this.props.showGenericPlaceholder || !this.props.showGenericPlaceholder ||
(this.context.timelineRenderingType !== TimelineRenderingType.Room && (this.props.context.timelineRenderingType !== TimelineRenderingType.Room &&
this.context.timelineRenderingType !== TimelineRenderingType.Search && this.props.context.timelineRenderingType !== TimelineRenderingType.Search &&
this.context.timelineRenderingType !== TimelineRenderingType.Pinned); this.props.context.timelineRenderingType !== TimelineRenderingType.Pinned);
if (this.context.timelineRenderingType === TimelineRenderingType.Thread) { if (this.props.context.timelineRenderingType === TimelineRenderingType.Thread) {
showDownloadLink = false; showDownloadLink = false;
} }
@@ -348,7 +346,7 @@ export default class MFileBody extends React.Component<IProps, IState> {
<span className="mx_MFileBody_download_icon" /> <span className="mx_MFileBody_download_icon" />
{_t("timeline|m.file|download_label", { text: this.linkText })} {_t("timeline|m.file|download_label", { text: this.linkText })}
</a> </a>
{this.context.timelineRenderingType === TimelineRenderingType.File && ( {this.props.context.timelineRenderingType === TimelineRenderingType.File && (
<div className="mx_MImageBody_size"> <div className="mx_MImageBody_size">
{this.content.info?.size ? fileSize(this.content.info.size) : ""} {this.content.info?.size ? fileSize(this.content.info.size) : ""}
</div> </div>
@@ -368,3 +366,7 @@ export default class MFileBody extends React.Component<IProps, IState> {
} }
} }
} }
export default forwardRef<MFileBody, Omit<IProps, "context">>((props, ref) => (
<RoomContext.Consumer>{(context) => <MFileBody {...props} context={context} ref={ref} />}</RoomContext.Consumer>
));

View File

@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License. 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 { Blurhash } from "react-blurhash";
import classNames from "classnames"; import classNames from "classnames";
import { CSSTransition, SwitchTransition } from "react-transition-group"; import { CSSTransition, SwitchTransition } from "react-transition-group";
@@ -47,6 +47,10 @@ enum Placeholder {
Blurhash, Blurhash,
} }
interface Props extends IBodyProps {
context: React.ContextType<typeof RoomContext>;
}
interface IState { interface IState {
contentUrl: string | null; contentUrl: string | null;
thumbUrl: string | null; thumbUrl: string | null;
@@ -63,17 +67,14 @@ interface IState {
placeholder: Placeholder; placeholder: Placeholder;
} }
export default class MImageBody extends React.Component<IBodyProps, IState> { export class MImageBody extends React.Component<Props, IState> {
public static contextType = RoomContext;
public context!: React.ContextType<typeof RoomContext>;
private unmounted = true; private unmounted = true;
private image = createRef<HTMLImageElement>(); private image = createRef<HTMLImageElement>();
private timeout?: number; private timeout?: number;
private sizeWatcher?: string; private sizeWatcher?: string;
private reconnectedListener: ClientEventHandlerMap[ClientEvent.Sync]; private reconnectedListener: ClientEventHandlerMap[ClientEvent.Sync];
public constructor(props: IBodyProps) { public constructor(props: Props) {
super(props); super(props);
this.reconnectedListener = createReconnectedListener(this.clearError); this.reconnectedListener = createReconnectedListener(this.clearError);
@@ -391,7 +392,9 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
protected getBanner(content: ImageContent): ReactNode { protected getBanner(content: ImageContent): ReactNode {
// Hide it for the threads list & the file panel where we show it as text anyway. // Hide it for the threads list & the file panel where we show it as text anyway.
if ( if (
[TimelineRenderingType.ThreadsList, TimelineRenderingType.File].includes(this.context.timelineRenderingType) [TimelineRenderingType.ThreadsList, TimelineRenderingType.File].includes(
this.props.context.timelineRenderingType,
)
) { ) {
return null; return null;
} }
@@ -601,11 +604,11 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
* link as the message action bar will fulfill that * link as the message action bar will fulfill that
*/ */
const hasMessageActionBar = const hasMessageActionBar =
this.context.timelineRenderingType === TimelineRenderingType.Room || this.props.context.timelineRenderingType === TimelineRenderingType.Room ||
this.context.timelineRenderingType === TimelineRenderingType.Pinned || this.props.context.timelineRenderingType === TimelineRenderingType.Pinned ||
this.context.timelineRenderingType === TimelineRenderingType.Search || this.props.context.timelineRenderingType === TimelineRenderingType.Search ||
this.context.timelineRenderingType === TimelineRenderingType.Thread || this.props.context.timelineRenderingType === TimelineRenderingType.Thread ||
this.context.timelineRenderingType === TimelineRenderingType.ThreadsList; this.props.context.timelineRenderingType === TimelineRenderingType.ThreadsList;
if (!hasMessageActionBar) { if (!hasMessageActionBar) {
return <MFileBody {...this.props} showGenericPlaceholder={false} />; return <MFileBody {...this.props} showGenericPlaceholder={false} />;
} }
@@ -648,6 +651,10 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
} }
} }
export default forwardRef<MImageBody, Omit<Props, "context">>((props, ref) => (
<RoomContext.Consumer>{(context) => <MImageBody {...props} context={context} ref={ref} />}</RoomContext.Consumer>
));
interface PlaceholderIProps { interface PlaceholderIProps {
hover?: boolean; hover?: boolean;
maxWidth?: number; maxWidth?: number;

View File

@@ -14,14 +14,20 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from "react"; import React, { forwardRef } from "react";
import { ImageContent } from "matrix-js-sdk/src/types"; 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; const FORCED_IMAGE_HEIGHT = 44;
export default class MImageReplyBody extends MImageBody { interface Props extends IBodyProps {
context: React.ContextType<typeof RoomContext>;
}
class MImageReplyBody extends MImageBody {
public onClick = (ev: React.MouseEvent): void => { public onClick = (ev: React.MouseEvent): void => {
ev.preventDefault(); ev.preventDefault();
}; };
@@ -43,3 +49,9 @@ export default class MImageReplyBody extends MImageBody {
return <div className="mx_MImageReplyBody">{thumbnail}</div>; return <div className="mx_MImageReplyBody">{thumbnail}</div>;
} }
} }
export default forwardRef<MImageReplyBody, Omit<Props, "context">>((props, ref) => (
<RoomContext.Consumer>
{(context) => <MImageReplyBody {...props} context={context} ref={ref} />}
</RoomContext.Consumer>
));

View File

@@ -14,14 +14,20 @@ See the License for the specific language governing permissions and
limitations under the License. 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 { Tooltip } from "@vector-im/compound-web";
import { MediaEventContent } from "matrix-js-sdk/src/types"; import { MediaEventContent } from "matrix-js-sdk/src/types";
import MImageBody from "./MImageBody"; import { MImageBody } from "./MImageBody";
import { BLURHASH_FIELD } from "../../../utils/image-media"; 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<typeof RoomContext>;
}
class MStickerBody extends MImageBody {
// Mostly empty to prevent default behaviour of MImageBody // Mostly empty to prevent default behaviour of MImageBody
protected onClick = (ev: React.MouseEvent): void => { protected onClick = (ev: React.MouseEvent): void => {
ev.preventDefault(); ev.preventDefault();
@@ -83,3 +89,7 @@ export default class MStickerBody extends MImageBody {
return null; // we don't need a banner, we have a tooltip return null; // we don't need a banner, we have a tooltip
} }
} }
export default forwardRef<MStickerBody, Omit<Props, "context">>((props, ref) => (
<RoomContext.Consumer>{(context) => <MStickerBody {...props} context={context} ref={ref} />}</RoomContext.Consumer>
));

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { ReactNode } from "react"; import React, { forwardRef, ReactNode } from "react";
import { decode } from "blurhash"; import { decode } from "blurhash";
import { MediaEventContent } from "matrix-js-sdk/src/types"; import { MediaEventContent } from "matrix-js-sdk/src/types";
import { logger } from "matrix-js-sdk/src/logger"; 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 RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
import MediaProcessingError from "./shared/MediaProcessingError"; import MediaProcessingError from "./shared/MediaProcessingError";
interface Props extends IBodyProps {
context: React.ContextType<typeof RoomContext>;
}
interface IState { interface IState {
decryptedUrl: string | null; decryptedUrl: string | null;
decryptedThumbnailUrl: string | null; decryptedThumbnailUrl: string | null;
@@ -40,14 +44,11 @@ interface IState {
blurhashUrl: string | null; blurhashUrl: string | null;
} }
export default class MVideoBody extends React.PureComponent<IBodyProps, IState> { class MVideoBody extends React.PureComponent<Props, IState> {
public static contextType = RoomContext;
public context!: React.ContextType<typeof RoomContext>;
private videoRef = React.createRef<HTMLVideoElement>(); private videoRef = React.createRef<HTMLVideoElement>();
private sizeWatcher?: string; private sizeWatcher?: string;
public constructor(props: IBodyProps) { public constructor(props: Props) {
super(props); super(props);
this.state = { this.state = {
@@ -221,9 +222,9 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
protected get showFileBody(): boolean { protected get showFileBody(): boolean {
return ( return (
this.context.timelineRenderingType !== TimelineRenderingType.Room && this.props.context.timelineRenderingType !== TimelineRenderingType.Room &&
this.context.timelineRenderingType !== TimelineRenderingType.Pinned && this.props.context.timelineRenderingType !== TimelineRenderingType.Pinned &&
this.context.timelineRenderingType !== TimelineRenderingType.Search this.props.context.timelineRenderingType !== TimelineRenderingType.Search
); );
} }
@@ -306,3 +307,7 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
); );
} }
} }
export default forwardRef<MVideoBody, Omit<Props, "context">>((props, ref) => (
<RoomContext.Consumer>{(context) => <MVideoBody {...props} context={context} ref={ref} />}</RoomContext.Consumer>
));

View File

@@ -14,16 +14,22 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from "react"; import React, { forwardRef } from "react";
import InlineSpinner from "../elements/InlineSpinner"; import InlineSpinner from "../elements/InlineSpinner";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import RecordingPlayback from "../audio_messages/RecordingPlayback"; import RecordingPlayback from "../audio_messages/RecordingPlayback";
import MAudioBody from "./MAudioBody"; import { MAudioBody } from "./MAudioBody";
import MFileBody from "./MFileBody"; import MFileBody from "./MFileBody";
import MediaProcessingError from "./shared/MediaProcessingError"; 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<typeof RoomContext>;
}
class MVoiceMessageBody extends MAudioBody {
// A voice message is an audio file but rendered in a special way. // A voice message is an audio file but rendered in a special way.
public render(): React.ReactNode { public render(): React.ReactNode {
if (this.state.error) { if (this.state.error) {
@@ -51,3 +57,9 @@ export default class MVoiceMessageBody extends MAudioBody {
); );
} }
} }
export default forwardRef<MVoiceMessageBody, Omit<Props, "context">>((props, ref) => (
<RoomContext.Consumer>
{(context) => <MVoiceMessageBody {...props} context={context} ref={ref} />}
</RoomContext.Consumer>
));

View File

@@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { ReactElement, useCallback, useContext, useEffect } from "react"; import React, { forwardRef, ReactElement, useCallback, useContext, useEffect } from "react";
import { import {
EventStatus, EventStatus,
MatrixEvent, MatrixEvent,
@@ -261,11 +261,10 @@ interface IMessageActionBarProps {
toggleThreadExpanded: () => void; toggleThreadExpanded: () => void;
isQuoteExpanded?: boolean; isQuoteExpanded?: boolean;
getRelationsForEvent?: GetRelationsForEvent; getRelationsForEvent?: GetRelationsForEvent;
context: React.ContextType<typeof RoomContext>;
} }
export default class MessageActionBar extends React.PureComponent<IMessageActionBarProps> { class MessageActionBar extends React.PureComponent<IMessageActionBarProps> {
public static contextType = RoomContext;
public componentDidMount(): void { public componentDidMount(): void {
if (this.props.mxEvent.status && this.props.mxEvent.status !== EventStatus.SENT) { if (this.props.mxEvent.status && this.props.mxEvent.status !== EventStatus.SENT) {
this.props.mxEvent.on(MatrixEventEvent.Status, this.onSent); this.props.mxEvent.on(MatrixEventEvent.Status, this.onSent);
@@ -314,7 +313,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
dis.dispatch({ dis.dispatch({
action: "reply_to_event", action: "reply_to_event",
event: this.props.mxEvent, event: this.props.mxEvent,
context: this.context.timelineRenderingType, context: this.props.context.timelineRenderingType,
}); });
}; };
@@ -326,7 +325,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
editEvent( editEvent(
MatrixClientPeg.safeGet(), MatrixClientPeg.safeGet(),
this.props.mxEvent, this.props.mxEvent,
this.context.timelineRenderingType, this.props.context.timelineRenderingType,
this.props.getRelationsForEvent, this.props.getRelationsForEvent,
); );
}; };
@@ -334,7 +333,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
private readonly forbiddenThreadHeadMsgType = [MsgType.KeyVerificationRequest]; private readonly forbiddenThreadHeadMsgType = [MsgType.KeyVerificationRequest];
private get showReplyInThreadAction(): boolean { private get showReplyInThreadAction(): boolean {
const inNotThreadTimeline = this.context.timelineRenderingType !== TimelineRenderingType.Thread; const inNotThreadTimeline = this.props.context.timelineRenderingType !== TimelineRenderingType.Thread;
const isAllowedMessageType = const isAllowedMessageType =
!this.forbiddenThreadHeadMsgType.includes(this.props.mxEvent.getContent().msgtype as MsgType) && !this.forbiddenThreadHeadMsgType.includes(this.props.mxEvent.getContent().msgtype as MsgType) &&
@@ -448,7 +447,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
// The only catch is we do the reply button first so that we can make sure the react // The only catch is we do the reply button first so that we can make sure the react
// button is the very first button without having to do length checks for `splice()`. // button is the very first button without having to do length checks for `splice()`.
if (this.context.canSendMessages) { if (this.props.context.canSendMessages) {
if (this.showReplyInThreadAction) { if (this.showReplyInThreadAction) {
toolbarOpts.splice(0, 0, threadTooltipButton); toolbarOpts.splice(0, 0, threadTooltipButton);
} }
@@ -467,7 +466,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
); );
} }
// We hide the react button in search results as we don't show reactions in results // We hide the react button in search results as we don't show reactions in results
if (this.context.canReact && !this.context.search) { if (this.props.context.canReact && !this.props.context.search) {
toolbarOpts.splice( toolbarOpts.splice(
0, 0,
0, 0,
@@ -494,7 +493,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
} }
} else if ( } else if (
// Show thread icon even for deleted messages, but only within main timeline // Show thread icon even for deleted messages, but only within main timeline
this.context.timelineRenderingType === TimelineRenderingType.Room && this.props.context.timelineRenderingType === TimelineRenderingType.Room &&
this.props.mxEvent.getThread() this.props.mxEvent.getThread()
) { ) {
toolbarOpts.unshift(threadTooltipButton); toolbarOpts.unshift(threadTooltipButton);
@@ -560,3 +559,9 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
); );
} }
} }
export default forwardRef<MessageActionBar, Omit<IMessageActionBarProps, "context">>((props, ref) => (
<RoomContext.Consumer>
{(context) => <MessageActionBar {...props} context={context} ref={ref} />}
</RoomContext.Consumer>
));

View File

@@ -51,8 +51,8 @@ import { VoiceBroadcastBody, VoiceBroadcastInfoEventType, VoiceBroadcastInfoStat
// onMessageAllowed is handled internally // onMessageAllowed is handled internally
interface IProps extends Omit<IBodyProps, "onMessageAllowed" | "mediaEventHelper"> { interface IProps extends Omit<IBodyProps, "onMessageAllowed" | "mediaEventHelper"> {
/* overrides for the msgtype-specific components, used by ReplyTile to override file rendering */ /* overrides for the msgtype-specific components, used by ReplyTile to override file rendering */
overrideBodyTypes?: Record<string, typeof React.Component>; overrideBodyTypes?: Record<string, ComponentType>;
overrideEventTypes?: Record<string, typeof React.Component>; overrideEventTypes?: Record<string, ComponentType>;
// helper function to access relations for this event // helper function to access relations for this event
getRelationsForEvent?: GetRelationsForEvent; getRelationsForEvent?: GetRelationsForEvent;
@@ -64,7 +64,9 @@ export interface IOperableEventTile {
getEventTileOps(): IEventTileOps | null; getEventTileOps(): IEventTileOps | null;
} }
const baseBodyTypes = new Map<string, typeof React.Component>([ type ComponentType = React.ComponentType<IBodyProps & React.RefAttributes<any>>;
const baseBodyTypes = new Map<string, ComponentType>([
[MsgType.Text, TextualBody], [MsgType.Text, TextualBody],
[MsgType.Notice, TextualBody], [MsgType.Notice, TextualBody],
[MsgType.Emote, TextualBody], [MsgType.Emote, TextualBody],
@@ -73,7 +75,7 @@ const baseBodyTypes = new Map<string, typeof React.Component>([
[MsgType.Audio, MVoiceOrAudioBody], [MsgType.Audio, MVoiceOrAudioBody],
[MsgType.Video, MVideoBody], [MsgType.Video, MVideoBody],
]); ]);
const baseEvTypes = new Map<string, React.ComponentType<IBodyProps>>([ const baseEvTypes = new Map<string, ComponentType>([
[EventType.Sticker, MStickerBody], [EventType.Sticker, MStickerBody],
[M_POLL_START.name, MPollBody], [M_POLL_START.name, MPollBody],
[M_POLL_START.altName, MPollBody], [M_POLL_START.altName, MPollBody],
@@ -84,10 +86,10 @@ const baseEvTypes = new Map<string, React.ComponentType<IBodyProps>>([
]); ]);
export default class MessageEvent extends React.Component<IProps> implements IMediaBody, IOperableEventTile { export default class MessageEvent extends React.Component<IProps> implements IMediaBody, IOperableEventTile {
private body: React.RefObject<React.Component | IOperableEventTile> = createRef(); private body: React.RefObject<ComponentType | IOperableEventTile> = createRef();
private mediaHelper?: MediaEventHelper; private mediaHelper?: MediaEventHelper;
private bodyTypes = new Map<string, typeof React.Component>(baseBodyTypes.entries()); private bodyTypes = new Map<string, ComponentType>(baseBodyTypes.entries());
private evTypes = new Map<string, React.ComponentType<IBodyProps>>(baseEvTypes.entries()); private evTypes = new Map<string, ComponentType>(baseEvTypes.entries());
public static contextType = MatrixClientContext; public static contextType = MatrixClientContext;
public context!: React.ContextType<typeof MatrixClientContext>; public context!: React.ContextType<typeof MatrixClientContext>;
@@ -121,12 +123,12 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
} }
private updateComponentMaps(): void { private updateComponentMaps(): void {
this.bodyTypes = new Map<string, typeof React.Component>(baseBodyTypes.entries()); this.bodyTypes = new Map<string, ComponentType>(baseBodyTypes.entries());
for (const [bodyType, bodyComponent] of Object.entries(this.props.overrideBodyTypes ?? {})) { for (const [bodyType, bodyComponent] of Object.entries(this.props.overrideBodyTypes ?? {})) {
this.bodyTypes.set(bodyType, bodyComponent); this.bodyTypes.set(bodyType, bodyComponent);
} }
this.evTypes = new Map<string, React.ComponentType<IBodyProps>>(baseEvTypes.entries()); this.evTypes = new Map<string, ComponentType>(baseEvTypes.entries());
for (const [evType, evComponent] of Object.entries(this.props.overrideEventTypes ?? {})) { for (const [evType, evComponent] of Object.entries(this.props.overrideEventTypes ?? {})) {
this.evTypes.set(evType, evComponent); this.evTypes.set(evType, evComponent);
} }
@@ -156,7 +158,7 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
const content = this.props.mxEvent.getContent(); const content = this.props.mxEvent.getContent();
const type = this.props.mxEvent.getType(); const type = this.props.mxEvent.getType();
const msgtype = content.msgtype; const msgtype = content.msgtype;
let BodyType: React.ComponentType<IBodyProps> = RedactedBody; let BodyType: ComponentType = RedactedBody;
if (!this.props.mxEvent.isRedacted()) { if (!this.props.mxEvent.isRedacted()) {
// only resolve BodyType if event is not redacted // only resolve BodyType if event is not redacted
if (this.props.mxEvent.isDecryptionFailure()) { if (this.props.mxEvent.isDecryptionFailure()) {

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { SyntheticEvent } from "react"; import React, { forwardRef, SyntheticEvent } from "react";
import classNames from "classnames"; import classNames from "classnames";
import { MatrixEvent, MatrixEventEvent, Relations, RelationsEvent } from "matrix-js-sdk/src/matrix"; import { MatrixEvent, MatrixEventEvent, Relations, RelationsEvent } from "matrix-js-sdk/src/matrix";
import { uniqBy } from "lodash"; 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"); export const REACTION_SHORTCODE_KEY = new UnstableValue("shortcode", "com.beeper.reaction.shortcode");
const ReactButton: React.FC<IProps> = ({ mxEvent, reactions }) => { const ReactButton: React.FC<Omit<IProps, "context">> = ({ mxEvent, reactions }) => {
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
let contextMenu: JSX.Element | undefined; let contextMenu: JSX.Element | undefined;
@@ -74,6 +74,7 @@ interface IProps {
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
// The Relations model from the JS SDK for reactions to `mxEvent` // The Relations model from the JS SDK for reactions to `mxEvent`
reactions?: Relations | null | undefined; reactions?: Relations | null | undefined;
context: React.ContextType<typeof RoomContext>;
} }
interface IState { interface IState {
@@ -81,13 +82,9 @@ interface IState {
showAll: boolean; showAll: boolean;
} }
export default class ReactionsRow extends React.PureComponent<IProps, IState> { class ReactionsRow extends React.PureComponent<IProps, IState> {
public static contextType = RoomContext; public constructor(props: IProps) {
public context!: React.ContextType<typeof RoomContext>; super(props);
public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) {
super(props, context);
this.context = context;
this.state = { this.state = {
myReactions: this.getMyReactions(), myReactions: this.getMyReactions(),
@@ -151,7 +148,7 @@ export default class ReactionsRow extends React.PureComponent<IProps, IState> {
if (!reactions) { if (!reactions) {
return null; return null;
} }
const userId = this.context.room?.client.getUserId(); const userId = this.props.context.room?.client.getUserId();
if (!userId) return null; if (!userId) return null;
const myReactions = reactions.getAnnotationsBySender()?.[userId]; const myReactions = reactions.getAnnotationsBySender()?.[userId];
if (!myReactions) { if (!myReactions) {
@@ -202,8 +199,8 @@ export default class ReactionsRow extends React.PureComponent<IProps, IState> {
myReactionEvent={myReactionEvent} myReactionEvent={myReactionEvent}
customReactionImagesEnabled={customReactionImagesEnabled} customReactionImagesEnabled={customReactionImagesEnabled}
disabled={ disabled={
!this.context.canReact || !this.props.context.canReact ||
(myReactionEvent && !myReactionEvent.isRedacted() && !this.context.canSelfRedact) (myReactionEvent && !myReactionEvent.isRedacted() && !this.props.context.canSelfRedact)
} }
/> />
); );
@@ -226,7 +223,7 @@ export default class ReactionsRow extends React.PureComponent<IProps, IState> {
} }
let addReactionButton: JSX.Element | undefined; let addReactionButton: JSX.Element | undefined;
if (this.context.canReact) { if (this.props.context.canReact) {
addReactionButton = <ReactButton mxEvent={mxEvent} reactions={reactions} />; addReactionButton = <ReactButton mxEvent={mxEvent} reactions={reactions} />;
} }
@@ -239,3 +236,7 @@ export default class ReactionsRow extends React.PureComponent<IProps, IState> {
); );
} }
} }
export default forwardRef<ReactionsRow, Omit<IProps, "context">>((props, ref) => (
<RoomContext.Consumer>{(context) => <ReactionsRow {...props} context={context} ref={ref} />}</RoomContext.Consumer>
));

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { createRef, SyntheticEvent, MouseEvent } from "react"; import React, { createRef, SyntheticEvent, MouseEvent, forwardRef } from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import { MsgType } from "matrix-js-sdk/src/matrix"; import { MsgType } from "matrix-js-sdk/src/matrix";
@@ -51,6 +51,10 @@ import { MatrixClientPeg } from "../../../MatrixClientPeg";
const MAX_HIGHLIGHT_LENGTH = 4096; const MAX_HIGHLIGHT_LENGTH = 4096;
interface Props extends IBodyProps {
context: React.ContextType<typeof RoomContext>;
}
interface IState { interface IState {
// the URLs (if any) to be previewed with a LinkPreviewWidget inside this TextualBody. // the URLs (if any) to be previewed with a LinkPreviewWidget inside this TextualBody.
links: string[]; links: string[];
@@ -59,17 +63,14 @@ interface IState {
widgetHidden: boolean; widgetHidden: boolean;
} }
export default class TextualBody extends React.Component<IBodyProps, IState> { class TextualBody extends React.Component<Props, IState> {
private readonly contentRef = createRef<HTMLSpanElement>(); private readonly contentRef = createRef<HTMLSpanElement>();
private unmounted = false; private unmounted = false;
private pills: Element[] = []; private pills: Element[] = [];
private tooltips: Element[] = []; private tooltips: Element[] = [];
public static contextType = RoomContext; public constructor(props: Props) {
public context!: React.ContextType<typeof RoomContext>;
public constructor(props: IBodyProps) {
super(props); super(props);
this.state = { this.state = {
@@ -429,7 +430,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
dis.dispatch({ dis.dispatch({
action: Action.ComposerInsert, action: Action.ComposerInsert,
userId: mxEvent.getSender(), userId: mxEvent.getSender(),
timelineRenderingType: this.context.timelineRenderingType, timelineRenderingType: this.props.context.timelineRenderingType,
}); });
}; };
@@ -659,3 +660,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
); );
} }
} }
export default forwardRef<TextualBody, Omit<Props, "context">>((props, ref) => (
<RoomContext.Consumer>{(context) => <TextualBody {...props} context={context} ref={ref} />}</RoomContext.Consumer>
));

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from "react"; import React, { forwardRef } from "react";
import { MatrixEvent } from "matrix-js-sdk/src/matrix"; import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import RoomContext from "../../../contexts/RoomContext"; import RoomContext from "../../../contexts/RoomContext";
@@ -23,19 +23,22 @@ import { MatrixClientPeg } from "../../../MatrixClientPeg";
interface IProps { interface IProps {
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
context: React.ContextType<typeof RoomContext>;
} }
export default class TextualEvent extends React.Component<IProps> { class TextualEvent extends React.Component<IProps> {
public static contextType = RoomContext;
public render(): React.ReactNode { public render(): React.ReactNode {
const text = TextForEvent.textForEvent( const text = TextForEvent.textForEvent(
this.props.mxEvent, this.props.mxEvent,
MatrixClientPeg.safeGet(), MatrixClientPeg.safeGet(),
true, true,
this.context?.showHiddenEvents, this.props.context?.showHiddenEvents,
); );
if (!text) return null; if (!text) return null;
return <div className="mx_TextualEvent">{text}</div>; return <div className="mx_TextualEvent">{text}</div>;
} }
} }
export default forwardRef<TextualEvent, Omit<IProps, "context">>((props, ref) => (
<RoomContext.Consumer>{(context) => <TextualEvent {...props} context={context} ref={ref} />}</RoomContext.Consumer>
));

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from "react"; import React, { forwardRef } from "react";
import { import {
IEventRelation, IEventRelation,
MatrixEvent, MatrixEvent,
@@ -59,6 +59,7 @@ interface IProps {
timelineRenderingType?: TimelineRenderingType; timelineRenderingType?: TimelineRenderingType;
showComposer?: boolean; showComposer?: boolean;
composerRelation?: IEventRelation; composerRelation?: IEventRelation;
context: React.ContextType<typeof RoomContext>;
} }
interface IState { interface IState {
@@ -75,12 +76,10 @@ interface IState {
showReadReceipts?: boolean; showReadReceipts?: boolean;
} }
export default class TimelineCard extends React.Component<IProps, IState> { class TimelineCard extends React.Component<IProps, IState> {
public static contextType = RoomContext;
private dispatcherRef?: string; private dispatcherRef?: string;
private layoutWatcherRef?: string; private layoutWatcherRef?: string;
private timelinePanel = React.createRef<TimelinePanel>(); private timelinePanel = React.createRef<React.ComponentRef<typeof TimelinePanel>>();
private card = React.createRef<HTMLDivElement>(); private card = React.createRef<HTMLDivElement>();
private readReceiptsSettingWatcher: string | undefined; private readReceiptsSettingWatcher: string | undefined;
@@ -224,7 +223,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
<RoomContext.Provider <RoomContext.Provider
value={{ value={{
...this.context, ...this.context,
timelineRenderingType: this.props.timelineRenderingType ?? this.context.timelineRenderingType, timelineRenderingType: this.props.timelineRenderingType ?? this.props.context.timelineRenderingType,
liveTimeline: this.props.timelineSet?.getLiveTimeline(), liveTimeline: this.props.timelineSet?.getLiveTimeline(),
narrow: this.state.narrow, narrow: this.state.narrow,
}} }}
@@ -246,7 +245,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
manageReadMarkers={false} // No RM support in the TimelineCard manageReadMarkers={false} // No RM support in the TimelineCard
sendReadReceiptOnLoad={true} sendReadReceiptOnLoad={true}
timelineSet={this.props.timelineSet} 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 // 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} layout={this.state.layout === Layout.Bubble ? Layout.Bubble : Layout.Group}
hideThreadedMessages={false} hideThreadedMessages={false}
@@ -281,3 +280,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
); );
} }
} }
export default forwardRef<TimelineCard, Omit<IProps, "context">>((props, ref) => (
<RoomContext.Consumer>{(context) => <TimelineCard {...props} context={context} ref={ref} />}</RoomContext.Consumer>
));

View File

@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { createRef, KeyboardEvent } from "react"; import React, { createRef, forwardRef, KeyboardEvent } from "react";
import classNames from "classnames"; import classNames from "classnames";
import { flatMap } from "lodash"; import { flatMap } from "lodash";
import { Room } from "matrix-js-sdk/src/matrix"; import { Room } from "matrix-js-sdk/src/matrix";
@@ -38,6 +38,7 @@ interface IProps {
selection: ISelectionRange; selection: ISelectionRange;
// The room in which we're autocompleting // The room in which we're autocompleting
room: Room; room: Room;
context: React.ContextType<typeof RoomContext>;
} }
interface IState { interface IState {
@@ -49,14 +50,12 @@ interface IState {
forceComplete: boolean; forceComplete: boolean;
} }
export default class Autocomplete extends React.PureComponent<IProps, IState> { export class Autocomplete extends React.PureComponent<IProps, IState> {
public autocompleter?: Autocompleter; public autocompleter?: Autocompleter;
public queryRequested?: string; public queryRequested?: string;
public debounceCompletionsRequest?: number; public debounceCompletionsRequest?: number;
private containerRef = createRef<HTMLDivElement>(); private containerRef = createRef<HTMLDivElement>();
public static contextType = RoomContext;
public constructor(props: IProps) { public constructor(props: IProps) {
super(props); super(props);
@@ -80,7 +79,7 @@ export default class Autocomplete extends React.PureComponent<IProps, IState> {
} }
public componentDidMount(): void { 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(); this.applyNewProps();
} }
@@ -320,3 +319,7 @@ export default class Autocomplete extends React.PureComponent<IProps, IState> {
) : null; ) : null;
} }
} }
export default forwardRef<Autocomplete, Omit<IProps, "context">>((props, ref) => (
<RoomContext.Consumer>{(context) => <Autocomplete {...props} context={context} ref={ref} />}</RoomContext.Consumer>
));

View File

@@ -123,7 +123,7 @@ interface IState {
export default class BasicMessageEditor extends React.Component<IProps, IState> { export default class BasicMessageEditor extends React.Component<IProps, IState> {
public readonly editorRef = createRef<HTMLDivElement>(); public readonly editorRef = createRef<HTMLDivElement>();
private autocompleteRef = createRef<Autocomplete>(); private autocompleteRef = createRef<React.ComponentRef<typeof Autocomplete>>();
private formatBarRef = createRef<MessageComposerFormatBar>(); private formatBarRef = createRef<MessageComposerFormatBar>();
private modifiedFlag = false; private modifiedFlag = false;

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { createRef, KeyboardEvent } from "react"; import React, { createRef, forwardRef, KeyboardEvent } from "react";
import classNames from "classnames"; import classNames from "classnames";
import { EventStatus, MatrixEvent, Room, MsgType } from "matrix-js-sdk/src/matrix"; import { EventStatus, MatrixEvent, Room, MsgType } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
@@ -122,28 +122,25 @@ export function createEditContent(
interface IEditMessageComposerProps extends MatrixClientProps { interface IEditMessageComposerProps extends MatrixClientProps {
editState: EditorStateTransfer; editState: EditorStateTransfer;
className?: string; className?: string;
context: React.ContextType<typeof RoomContext>;
} }
interface IState { interface IState {
saveDisabled: boolean; saveDisabled: boolean;
} }
class EditMessageComposer extends React.Component<IEditMessageComposerProps, IState> { class EditMessageComposer extends React.Component<IEditMessageComposerProps, IState> {
public static contextType = RoomContext;
public context!: React.ContextType<typeof RoomContext>;
private readonly editorRef = createRef<BasicMessageComposer>(); private readonly editorRef = createRef<BasicMessageComposer>();
private readonly dispatcherRef: string; private readonly dispatcherRef: string;
private readonly replyToEvent?: MatrixEvent; private readonly replyToEvent?: MatrixEvent;
private model!: EditorModel; private model!: EditorModel;
public constructor(props: IEditMessageComposerProps, context: React.ContextType<typeof RoomContext>) { public constructor(props: IEditMessageComposerProps) {
super(props); super(props);
this.context = context; // otherwise React will only set it prior to render due to type def above
const isRestored = this.createEditorModel(); const isRestored = this.createEditorModel();
const ev = this.props.editState.getEvent(); 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); const editContent = createEditContent(this.model, ev, this.replyToEvent);
this.state = { this.state = {
@@ -155,10 +152,10 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
} }
private getRoom(): Room { private getRoom(): Room {
if (!this.context.room) { if (!this.props.context.room) {
throw new Error(`Cannot render without room`); throw new Error(`Cannot render without room`);
} }
return this.context.room; return this.props.context.room;
} }
private onKeyDown = (event: KeyboardEvent): void => { private onKeyDown = (event: KeyboardEvent): void => {
@@ -191,7 +188,7 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
dis.dispatch({ dis.dispatch({
action: Action.EditEvent, action: Action.EditEvent,
event: previousEvent, event: previousEvent,
timelineRenderingType: this.context.timelineRenderingType, timelineRenderingType: this.props.context.timelineRenderingType,
}); });
event.preventDefault(); event.preventDefault();
} }
@@ -211,7 +208,7 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
dis.dispatch({ dis.dispatch({
action: Action.EditEvent, action: Action.EditEvent,
event: nextEvent, event: nextEvent,
timelineRenderingType: this.context.timelineRenderingType, timelineRenderingType: this.props.context.timelineRenderingType,
}); });
} else { } else {
this.cancelEdit(); this.cancelEdit();
@@ -230,16 +227,16 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
dis.dispatch({ dis.dispatch({
action: Action.EditEvent, action: Action.EditEvent,
event: null, event: null,
timelineRenderingType: this.context.timelineRenderingType, timelineRenderingType: this.props.context.timelineRenderingType,
}); });
dis.dispatch({ dis.dispatch({
action: Action.FocusSendMessageComposer, action: Action.FocusSendMessageComposer,
context: this.context.timelineRenderingType, context: this.props.context.timelineRenderingType,
}); });
} }
private get editorRoomKey(): string { private get editorRoomKey(): string {
return editorRoomKey(this.props.editState.getEvent().getRoomId()!, this.context.timelineRenderingType); return editorRoomKey(this.props.editState.getEvent().getRoomId()!, this.props.context.timelineRenderingType);
} }
private get editorStateKey(): string { private get editorStateKey(): string {
@@ -247,7 +244,7 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
} }
private get events(): MatrixEvent[] { private get events(): MatrixEvent[] {
const liveTimelineEvents = this.context.liveTimeline?.getEvents(); const liveTimelineEvents = this.props.context.liveTimeline?.getEvents();
const room = this.getRoom(); const room = this.getRoom();
if (!liveTimelineEvents || !room) return []; if (!liveTimelineEvents || !room) return [];
const pendingEvents = room.getPendingEvents(); const pendingEvents = room.getPendingEvents();
@@ -368,7 +365,7 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
// re-focus the composer after QuestionDialog is closed // re-focus the composer after QuestionDialog is closed
dis.dispatch({ dis.dispatch({
action: Action.FocusAComposer, action: Action.FocusAComposer,
context: this.context.timelineRenderingType, context: this.props.context.timelineRenderingType,
}); });
// if !sendAnyway bail to let the user edit the composer and try again // if !sendAnyway bail to let the user edit the composer and try again
if (!sendAnyway) return; if (!sendAnyway) return;
@@ -462,7 +459,7 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
if (!this.editorRef.current) return; if (!this.editorRef.current) return;
if (payload.action === Action.ComposerInsert) { if (payload.action === Action.ComposerInsert) {
if (payload.timelineRenderingType !== this.context.timelineRenderingType) return; if (payload.timelineRenderingType !== this.props.context.timelineRenderingType) return;
if (payload.composerType !== ComposerType.Edit) return; if (payload.composerType !== ComposerType.Edit) return;
if (payload.userId) { if (payload.userId) {
@@ -506,4 +503,10 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
} }
const EditMessageComposerWithMatrixClient = withMatrixClientHOC(EditMessageComposer); const EditMessageComposerWithMatrixClient = withMatrixClientHOC(EditMessageComposer);
export default EditMessageComposerWithMatrixClient; export default forwardRef<EditMessageComposer, Omit<IEditMessageComposerProps, "context" | "mxClient">>(
(props, ref) => (
<RoomContext.Consumer>
{(context) => <EditMessageComposerWithMatrixClient {...props} context={context} ref={ref} />}
</RoomContext.Consumer>
),
);

View File

@@ -266,6 +266,10 @@ interface IState {
threadNotification?: NotificationCountType; threadNotification?: NotificationCountType;
} }
interface Props extends EventTileProps {
context: React.ContextType<typeof RoomContext>;
}
/** /**
* When true, the tile qualifies for some sort of special read receipt. * When true, the tile qualifies for some sort of special read receipt.
* This could be a 'sending' or 'sent' receipt, for example. * 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 // MUST be rendered within a RoomContext with a set timelineRenderingType
export class UnwrappedEventTile extends React.Component<EventTileProps, IState> { class UnwrappedEventTile extends React.Component<Props, IState> {
private suppressReadReceiptAnimation: boolean; private suppressReadReceiptAnimation: boolean;
private isListeningForReceipts: boolean; private isListeningForReceipts: boolean;
private tile = createRef<IEventTileType>(); private tile = createRef<IEventTileType>();
@@ -297,13 +301,10 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
layout: Layout.Group, layout: Layout.Group,
}; };
public static contextType = RoomContext;
public context!: React.ContextType<typeof RoomContext>;
private unmounted = false; private unmounted = false;
public constructor(props: EventTileProps, context: React.ContextType<typeof RoomContext>) { public constructor(props: Props) {
super(props, context); super(props);
const thread = this.thread; const thread = this.thread;
@@ -508,7 +509,10 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
); );
} }
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) { if (this.props.highlightLink) {
return ( return (
<a className="mx_ThreadSummary_icon" href={this.props.highlightLink}> <a className="mx_ThreadSummary_icon" href={this.props.highlightLink}>
@@ -676,8 +680,8 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
*/ */
private shouldHighlight(): boolean { private shouldHighlight(): boolean {
if (this.props.forExport) return false; if (this.props.forExport) return false;
if (this.context.timelineRenderingType === TimelineRenderingType.Notification) return false; if (this.props.context.timelineRenderingType === TimelineRenderingType.Notification) return false;
if (this.context.timelineRenderingType === TimelineRenderingType.ThreadsList) return false; if (this.props.context.timelineRenderingType === TimelineRenderingType.ThreadsList) return false;
const cli = MatrixClientPeg.safeGet(); const cli = MatrixClientPeg.safeGet();
const actions = cli.getPushActionsForEvent(this.props.mxEvent.replacingEvent() || this.props.mxEvent); const actions = cli.getPushActionsForEvent(this.props.mxEvent.replacingEvent() || this.props.mxEvent);
@@ -701,7 +705,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
dis.dispatch<ComposerInsertPayload>({ dis.dispatch<ComposerInsertPayload>({
action: Action.ComposerInsert, action: Action.ComposerInsert,
userId: this.props.mxEvent.getSender()!, userId: this.props.mxEvent.getSender()!,
timelineRenderingType: this.context.timelineRenderingType, timelineRenderingType: this.props.context.timelineRenderingType,
}); });
}; };
@@ -715,7 +719,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
highlighted: true, highlighted: true,
room_id: this.props.mxEvent.getRoomId(), room_id: this.props.mxEvent.getRoomId(),
metricsTrigger: 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<EventTileProps, IState>
} = getEventDisplayInfo( } = getEventDisplayInfo(
MatrixClientPeg.safeGet(), MatrixClientPeg.safeGet(),
this.props.mxEvent, this.props.mxEvent,
this.context.showHiddenEvents, this.props.context.showHiddenEvents,
this.shouldHideEvent(), this.shouldHideEvent(),
); );
const { isQuoteExpanded } = this.state; const { isQuoteExpanded } = this.state;
@@ -958,15 +962,15 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
let isContinuation = this.props.continuation; let isContinuation = this.props.continuation;
if ( if (
this.context.timelineRenderingType !== TimelineRenderingType.Room && this.props.context.timelineRenderingType !== TimelineRenderingType.Room &&
this.context.timelineRenderingType !== TimelineRenderingType.Search && this.props.context.timelineRenderingType !== TimelineRenderingType.Search &&
this.context.timelineRenderingType !== TimelineRenderingType.Thread && this.props.context.timelineRenderingType !== TimelineRenderingType.Thread &&
this.props.layout !== Layout.Bubble this.props.layout !== Layout.Bubble
) { ) {
isContinuation = false; isContinuation = false;
} }
const isRenderingNotification = this.context.timelineRenderingType === TimelineRenderingType.Notification; const isRenderingNotification = this.props.context.timelineRenderingType === TimelineRenderingType.Notification;
const isEditing = !!this.props.editState; const isEditing = !!this.props.editState;
const classes = classNames({ const classes = classNames({
@@ -990,7 +994,8 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
mx_EventTile_emote: msgtype === MsgType.Emote, mx_EventTile_emote: msgtype === MsgType.Emote,
mx_EventTile_noSender: this.props.hideSender, mx_EventTile_noSender: this.props.hideSender,
mx_EventTile_clamp: mx_EventTile_clamp:
this.context.timelineRenderingType === TimelineRenderingType.ThreadsList || isRenderingNotification, this.props.context.timelineRenderingType === TimelineRenderingType.ThreadsList ||
isRenderingNotification,
mx_EventTile_noBubble: noBubbleEvent, mx_EventTile_noBubble: noBubbleEvent,
}); });
@@ -1020,8 +1025,8 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
avatarSize = "14px"; avatarSize = "14px";
needsSenderProfile = false; needsSenderProfile = false;
} else if ( } else if (
this.context.timelineRenderingType === TimelineRenderingType.ThreadsList || this.props.context.timelineRenderingType === TimelineRenderingType.ThreadsList ||
(this.context.timelineRenderingType === TimelineRenderingType.Thread && !this.props.continuation) (this.props.context.timelineRenderingType === TimelineRenderingType.Thread && !this.props.continuation)
) { ) {
avatarSize = "32px"; avatarSize = "32px";
needsSenderProfile = true; needsSenderProfile = true;
@@ -1032,7 +1037,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
avatarSize = "14px"; avatarSize = "14px";
needsSenderProfile = true; needsSenderProfile = true;
} else if ( } else if (
(this.props.continuation && this.context.timelineRenderingType !== TimelineRenderingType.File) || (this.props.continuation && this.props.context.timelineRenderingType !== TimelineRenderingType.File) ||
eventType === EventType.CallInvite || eventType === EventType.CallInvite ||
ElementCall.CALL_EVENT_TYPE.matches(eventType) ElementCall.CALL_EVENT_TYPE.matches(eventType)
) { ) {
@@ -1058,7 +1063,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
const viewUserOnClick = const viewUserOnClick =
!this.props.inhibitInteraction && !this.props.inhibitInteraction &&
![TimelineRenderingType.ThreadsList, TimelineRenderingType.Notification].includes( ![TimelineRenderingType.ThreadsList, TimelineRenderingType.Notification].includes(
this.context.timelineRenderingType, this.props.context.timelineRenderingType,
); );
avatar = ( avatar = (
<div className="mx_EventTile_avatar"> <div className="mx_EventTile_avatar">
@@ -1074,13 +1079,13 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
if (needsSenderProfile && this.props.hideSender !== true) { if (needsSenderProfile && this.props.hideSender !== true) {
if ( if (
this.context.timelineRenderingType === TimelineRenderingType.Room || this.props.context.timelineRenderingType === TimelineRenderingType.Room ||
this.context.timelineRenderingType === TimelineRenderingType.Search || this.props.context.timelineRenderingType === TimelineRenderingType.Search ||
this.context.timelineRenderingType === TimelineRenderingType.Pinned || this.props.context.timelineRenderingType === TimelineRenderingType.Pinned ||
this.context.timelineRenderingType === TimelineRenderingType.Thread this.props.context.timelineRenderingType === TimelineRenderingType.Thread
) { ) {
sender = <SenderProfile onClick={this.onSenderProfileClick} mxEvent={this.props.mxEvent} />; sender = <SenderProfile onClick={this.onSenderProfileClick} mxEvent={this.props.mxEvent} />;
} else if (this.context.timelineRenderingType === TimelineRenderingType.ThreadsList) { } else if (this.props.context.timelineRenderingType === TimelineRenderingType.ThreadsList) {
sender = <SenderProfile mxEvent={this.props.mxEvent} withTooltip />; sender = <SenderProfile mxEvent={this.props.mxEvent} withTooltip />;
} else { } else {
sender = <SenderProfile mxEvent={this.props.mxEvent} />; sender = <SenderProfile mxEvent={this.props.mxEvent} />;
@@ -1113,7 +1118,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
// Thread panel shows the timestamp of the last reply in that thread // Thread panel shows the timestamp of the last reply in that thread
let ts = let ts =
this.context.timelineRenderingType !== TimelineRenderingType.ThreadsList this.props.context.timelineRenderingType !== TimelineRenderingType.ThreadsList
? this.props.mxEvent.getTs() ? this.props.mxEvent.getTs()
: this.state.thread?.replyToEvent?.getTs(); : this.state.thread?.replyToEvent?.getTs();
if (typeof ts !== "number") { if (typeof ts !== "number") {
@@ -1123,7 +1128,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
const messageTimestamp = ( const messageTimestamp = (
<MessageTimestamp <MessageTimestamp
showRelative={this.context.timelineRenderingType === TimelineRenderingType.ThreadsList} showRelative={this.props.context.timelineRenderingType === TimelineRenderingType.ThreadsList}
showTwelveHour={this.props.isTwelveHour} showTwelveHour={this.props.isTwelveHour}
ts={ts} ts={ts}
receivedTs={getLateEventInfo(this.props.mxEvent)?.received_ts} receivedTs={getLateEventInfo(this.props.mxEvent)?.received_ts}
@@ -1180,7 +1185,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
let replyChain: JSX.Element | undefined; let replyChain: JSX.Element | undefined;
if ( if (
haveRendererForEvent(this.props.mxEvent, MatrixClientPeg.safeGet(), this.context.showHiddenEvents) && haveRendererForEvent(this.props.mxEvent, MatrixClientPeg.safeGet(), this.props.context.showHiddenEvents) &&
shouldDisplayReply(this.props.mxEvent) shouldDisplayReply(this.props.mxEvent)
) { ) {
replyChain = ( replyChain = (
@@ -1202,7 +1207,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
// Use `getSender()` because searched events might not have a proper `sender`. // Use `getSender()` because searched events might not have a proper `sender`.
const isOwnEvent = this.props.mxEvent?.getSender() === MatrixClientPeg.safeGet().getUserId(); const isOwnEvent = this.props.mxEvent?.getSender() === MatrixClientPeg.safeGet().getUserId();
switch (this.context.timelineRenderingType) { switch (this.props.context.timelineRenderingType) {
case TimelineRenderingType.Thread: { case TimelineRenderingType.Thread: {
return React.createElement( return React.createElement(
this.props.as || "li", this.props.as || "li",
@@ -1242,7 +1247,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
onHeightChanged: () => this.props.onHeightChanged, onHeightChanged: () => this.props.onHeightChanged,
permalinkCreator: this.props.permalinkCreator!, permalinkCreator: this.props.permalinkCreator!,
}, },
this.context.showHiddenEvents, this.props.context.showHiddenEvents,
)} )}
{actionBar} {actionBar}
<a href={permalink} onClick={this.onPermalinkClicked}> <a href={permalink} onClick={this.onPermalinkClicked}>
@@ -1268,7 +1273,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
"aria-atomic": "true", "aria-atomic": "true",
"data-scroll-tokens": scrollToken, "data-scroll-tokens": scrollToken,
"data-layout": this.props.layout, "data-layout": this.props.layout,
"data-shape": this.context.timelineRenderingType, "data-shape": this.props.context.timelineRenderingType,
"data-self": isOwnEvent, "data-self": isOwnEvent,
"data-has-reply": !!replyChain, "data-has-reply": !!replyChain,
"onMouseEnter": () => this.setState({ hover: true }), "onMouseEnter": () => this.setState({ hover: true }),
@@ -1277,7 +1282,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
const target = ev.currentTarget as HTMLElement; const target = ev.currentTarget as HTMLElement;
let index = -1; let index = -1;
if (target.parentElement) index = Array.from(target.parentElement.children).indexOf(target); if (target.parentElement) index = Array.from(target.parentElement.children).indexOf(target);
switch (this.context.timelineRenderingType) { switch (this.props.context.timelineRenderingType) {
case TimelineRenderingType.Notification: case TimelineRenderingType.Notification:
this.viewInRoom(ev); this.viewInRoom(ev);
break; break;
@@ -1333,7 +1338,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
</div> </div>
{this.renderThreadPanelSummary()} {this.renderThreadPanelSummary()}
</div> </div>
{this.context.timelineRenderingType === TimelineRenderingType.ThreadsList && ( {this.props.context.timelineRenderingType === TimelineRenderingType.ThreadsList && (
<EventTileThreadToolbar <EventTileThreadToolbar
viewInRoom={this.viewInRoom} viewInRoom={this.viewInRoom}
copyLinkToThread={this.copyLinkToThread} copyLinkToThread={this.copyLinkToThread}
@@ -1371,7 +1376,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
onHeightChanged: this.props.onHeightChanged, onHeightChanged: this.props.onHeightChanged,
permalinkCreator: this.props.permalinkCreator, permalinkCreator: this.props.permalinkCreator,
}, },
this.context.showHiddenEvents, this.props.context.showHiddenEvents,
)} )}
</div>, </div>,
<a <a
@@ -1419,7 +1424,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
{groupPadlock} {groupPadlock}
{replyChain} {replyChain}
{renderTile( {renderTile(
this.context.timelineRenderingType, this.props.context.timelineRenderingType,
{ {
...this.props, ...this.props,
@@ -1434,7 +1439,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
onHeightChanged: this.props.onHeightChanged, onHeightChanged: this.props.onHeightChanged,
permalinkCreator: this.props.permalinkCreator, permalinkCreator: this.props.permalinkCreator,
}, },
this.context.showHiddenEvents, this.props.context.showHiddenEvents,
)} )}
{actionBar} {actionBar}
{this.props.layout === Layout.IRC && ( {this.props.layout === Layout.IRC && (
@@ -1463,7 +1468,9 @@ const SafeEventTile = forwardRef<UnwrappedEventTile, EventTileProps>((props, ref
return ( return (
<> <>
<TileErrorBoundary mxEvent={props.mxEvent} layout={props.layout ?? Layout.Group}> <TileErrorBoundary mxEvent={props.mxEvent} layout={props.layout ?? Layout.Group}>
<UnwrappedEventTile ref={ref} {...props} /> <RoomContext.Consumer>
{(context) => <UnwrappedEventTile {...props} ref={ref} context={context} />}
</RoomContext.Consumer>
</TileErrorBoundary> </TileErrorBoundary>
</> </>
); );

View File

@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License. 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 classNames from "classnames";
import { throttle } from "lodash"; import { throttle } from "lodash";
import { RoomStateEvent, ISearchResults } from "matrix-js-sdk/src/matrix"; import { RoomStateEvent, ISearchResults } from "matrix-js-sdk/src/matrix";
@@ -470,7 +470,7 @@ export interface ISearchInfo {
count?: number; count?: number;
} }
export interface IProps { interface IProps {
room: Room; room: Room;
oobData?: IOOBData; oobData?: IOOBData;
inRoom: boolean; inRoom: boolean;
@@ -478,7 +478,7 @@ export interface IProps {
onInviteClick: (() => void) | null; onInviteClick: (() => void) | null;
onForgetClick: (() => void) | null; onForgetClick: (() => void) | null;
onAppsClick: (() => void) | null; onAppsClick: (() => void) | null;
e2eStatus: E2EStatus; e2eStatus?: E2EStatus;
appsShown: boolean; appsShown: boolean;
searchInfo?: ISearchInfo; searchInfo?: ISearchInfo;
excludedRightPanelPhaseButtons?: Array<RightPanelPhases>; excludedRightPanelPhaseButtons?: Array<RightPanelPhases>;
@@ -487,6 +487,7 @@ export interface IProps {
viewingCall: boolean; viewingCall: boolean;
activeCall: Call | null; activeCall: Call | null;
additionalButtons?: ViewRoomOpts["buttons"]; additionalButtons?: ViewRoomOpts["buttons"];
context: React.ContextType<typeof RoomContext>;
} }
interface IState { interface IState {
@@ -498,7 +499,7 @@ interface IState {
/** /**
* @deprecated use `src/components/views/rooms/RoomHeader.tsx` instead * @deprecated use `src/components/views/rooms/RoomHeader.tsx` instead
*/ */
export default class RoomHeader extends React.Component<IProps, IState> { class LegacyRoomHeader extends React.Component<IProps, IState> {
public static defaultProps: Partial<IProps> = { public static defaultProps: Partial<IProps> = {
inRoom: false, inRoom: false,
excludedRightPanelPhaseButtons: [], excludedRightPanelPhaseButtons: [],
@@ -506,13 +507,11 @@ export default class RoomHeader extends React.Component<IProps, IState> {
enableRoomOptionsMenu: true, enableRoomOptionsMenu: true,
}; };
public static contextType = RoomContext;
public context!: React.ContextType<typeof RoomContext>;
private readonly client = this.props.room.client; private readonly client = this.props.room.client;
private readonly featureAskToJoinWatcher: string; private readonly featureAskToJoinWatcher: string;
public constructor(props: IProps, context: IState) { public constructor(props: IProps) {
super(props, context); super(props);
const notiStore = RoomNotificationStateStore.instance.getRoomState(props.room); const notiStore = RoomNotificationStateStore.instance.getRoomState(props.room);
notiStore.on(NotificationStateEvents.Update, this.onNotificationUpdate); notiStore.on(NotificationStateEvents.Update, this.onNotificationUpdate);
this.state = { this.state = {
@@ -590,7 +589,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
private renderButtons(isVideoRoom: boolean): React.ReactNode { private renderButtons(isVideoRoom: boolean): React.ReactNode {
const startButtons: JSX.Element[] = []; 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(<CallButtons key="calls" room={this.props.room} />); startButtons.push(<CallButtons key="calls" room={this.props.room} />);
} }
@@ -866,3 +865,9 @@ export default class RoomHeader extends React.Component<IProps, IState> {
); );
} }
} }
export default forwardRef<LegacyRoomHeader, Omit<IProps, "context">>((props, ref) => (
<RoomContext.Consumer>
{(context) => <LegacyRoomHeader {...props} context={context} ref={ref} />}
</RoomContext.Consumer>
));

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { createRef, ReactNode } from "react"; import React, { createRef, forwardRef, ReactNode } from "react";
import classNames from "classnames"; import classNames from "classnames";
import { import {
IEventRelation, IEventRelation,
@@ -44,7 +44,7 @@ import { RecordingState } from "../../../audio/VoiceRecording";
import Tooltip, { Alignment } from "../elements/Tooltip"; import Tooltip, { Alignment } from "../elements/Tooltip";
import ResizeNotifier from "../../../utils/ResizeNotifier"; import ResizeNotifier from "../../../utils/ResizeNotifier";
import { E2EStatus } from "../../../utils/ShieldUtils"; import { E2EStatus } from "../../../utils/ShieldUtils";
import SendMessageComposer, { SendMessageComposer as SendMessageComposerClass } from "./SendMessageComposer"; import SendMessageComposer from "./SendMessageComposer";
import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload"; import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
import { Action } from "../../../dispatcher/actions"; import { Action } from "../../../dispatcher/actions";
import EditorModel from "../../../editor/model"; import EditorModel from "../../../editor/model";
@@ -92,6 +92,7 @@ interface IProps extends MatrixClientProps {
relation?: IEventRelation; relation?: IEventRelation;
e2eStatus?: E2EStatus; e2eStatus?: E2EStatus;
compact?: boolean; compact?: boolean;
context: React.ContextType<typeof RoomContext>;
} }
interface IState { interface IState {
@@ -113,16 +114,13 @@ interface IState {
export class MessageComposer extends React.Component<IProps, IState> { export class MessageComposer extends React.Component<IProps, IState> {
private tooltipId = `mx_MessageComposer_${Math.random()}`; private tooltipId = `mx_MessageComposer_${Math.random()}`;
private dispatcherRef?: string; private dispatcherRef?: string;
private messageComposerInput = createRef<SendMessageComposerClass>(); private messageComposerInput = createRef<React.ComponentRef<typeof SendMessageComposer>>();
private voiceRecordingButton = createRef<VoiceRecordComposerTile>(); private voiceRecordingButton = createRef<React.ComponentRef<typeof VoiceRecordComposerTile>>();
private ref: React.RefObject<HTMLDivElement> = createRef(); private ref: React.RefObject<HTMLDivElement> = createRef();
private instanceId: number; private instanceId: number;
private _voiceRecording: Optional<VoiceMessageRecording>; private _voiceRecording: Optional<VoiceMessageRecording>;
public static contextType = RoomContext;
public context!: React.ContextType<typeof RoomContext>;
public static defaultProps = { public static defaultProps = {
compact: false, compact: false,
showVoiceBroadcastButton: false, showVoiceBroadcastButton: false,
@@ -189,7 +187,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
private onResize = (type: UI_EVENTS, entry: ResizeObserverEntry): void => { private onResize = (type: UI_EVENTS, entry: ResizeObserverEntry): void => {
if (type === UI_EVENTS.Resize) { if (type === UI_EVENTS.Resize) {
const { narrow } = this.context; const { narrow } = this.props.context;
this.setState({ this.setState({
isMenuOpen: !narrow ? false : this.state.isMenuOpen, isMenuOpen: !narrow ? false : this.state.isMenuOpen,
isStickerPickerOpen: false, isStickerPickerOpen: false,
@@ -200,7 +198,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
private onAction = (payload: ActionPayload): void => { private onAction = (payload: ActionPayload): void => {
switch (payload.action) { switch (payload.action) {
case "reply_to_event": 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 // add a timeout for the reply preview to be rendered, so
// that the ScrollPanel listening to the resizeNotifier can // that the ScrollPanel listening to the resizeNotifier can
// correctly measure it's new height and scroll down to keep // correctly measure it's new height and scroll down to keep
@@ -274,7 +272,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
private onTombstoneClick = (ev: ButtonEvent): void => { private onTombstoneClick = (ev: ButtonEvent): void => {
ev.preventDefault(); 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); const replacementRoom = MatrixClientPeg.safeGet().getRoom(replacementRoomId);
let createEventId: string | undefined; let createEventId: string | undefined;
if (replacementRoom) { if (replacementRoom) {
@@ -282,7 +280,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
if (createEvent?.getId()) createEventId = createEvent.getId(); 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; const viaServers = sender ? [sender.split(":").slice(1).join(":")] : undefined;
dis.dispatch<ViewRoomPayload>({ dis.dispatch<ViewRoomPayload>({
@@ -324,7 +322,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
dis.dispatch<ComposerInsertPayload>({ dis.dispatch<ComposerInsertPayload>({
action: Action.ComposerInsert, action: Action.ComposerInsert,
text: emoji, text: emoji,
timelineRenderingType: this.context.timelineRenderingType, timelineRenderingType: this.props.context.timelineRenderingType,
}); });
return true; return true;
}; };
@@ -345,11 +343,11 @@ export class MessageComposer extends React.Component<IProps, IState> {
this.setState({ composerContent: "", initialComposerContent: "" }); this.setState({ composerContent: "", initialComposerContent: "" });
dis.dispatch({ dis.dispatch({
action: Action.ClearAndFocusSendMessageComposer, action: Action.ClearAndFocusSendMessageComposer,
timelineRenderingType: this.context.timelineRenderingType, timelineRenderingType: this.props.context.timelineRenderingType,
}); });
await sendMessage(composerContent, this.state.isRichTextEnabled, { await sendMessage(composerContent, this.state.isRichTextEnabled, {
mxClient: this.props.mxClient, mxClient: this.props.mxClient,
roomContext: this.context, roomContext: this.props.context,
permalinkCreator, permalinkCreator,
relation, relation,
replyToEvent, replyToEvent,
@@ -467,7 +465,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
this.voiceRecordingButton.current?.onRecordStartEndClick(); this.voiceRecordingButton.current?.onRecordStartEndClick();
} }
if (this.context.narrow) { if (this.props.context.narrow) {
this.toggleButtonMenu(); this.toggleButtonMenu();
} }
}; };
@@ -483,7 +481,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
const controls: ReactNode[] = []; const controls: ReactNode[] = [];
const menuPosition = this.getMenuPosition(); 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; let composer: ReactNode;
if (canSendMessages) { if (canSendMessages) {
if (this.state.isWysiwygLabEnabled && menuPosition) { if (this.state.isWysiwygLabEnabled && menuPosition) {
@@ -528,8 +526,8 @@ export class MessageComposer extends React.Component<IProps, IState> {
replyToEvent={this.props.replyToEvent} replyToEvent={this.props.replyToEvent}
/>, />,
); );
} else if (this.context.tombstone) { } else if (this.props.context.tombstone) {
const replacementRoomId = this.context.tombstone.getContent()["replacement_room"]; const replacementRoomId = this.props.context.tombstone.getContent()["replacement_room"];
const continuesLink = replacementRoomId ? ( const continuesLink = replacementRoomId ? (
<a <a
@@ -667,4 +665,8 @@ export class MessageComposer extends React.Component<IProps, IState> {
} }
const MessageComposerWithMatrixClient = withMatrixClientHOC(MessageComposer); const MessageComposerWithMatrixClient = withMatrixClientHOC(MessageComposer);
export default MessageComposerWithMatrixClient; export default forwardRef<MessageComposer, Omit<IProps, "context" | "mxClient">>((props, ref) => (
<RoomContext.Consumer>
{(context) => <MessageComposerWithMatrixClient {...props} context={context} ref={ref} />}
</RoomContext.Consumer>
));

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from "react"; import React, { forwardRef } from "react";
import { MatrixEvent } from "matrix-js-sdk/src/matrix"; import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
@@ -35,11 +35,10 @@ function cancelQuoting(context: TimelineRenderingType): void {
interface IProps { interface IProps {
permalinkCreator?: RoomPermalinkCreator; permalinkCreator?: RoomPermalinkCreator;
replyToEvent?: MatrixEvent; replyToEvent?: MatrixEvent;
context: React.ContextType<typeof RoomContext>;
} }
export default class ReplyPreview extends React.Component<IProps> { class ReplyPreview extends React.Component<IProps> {
public static contextType = RoomContext;
public render(): JSX.Element | null { public render(): JSX.Element | null {
if (!this.props.replyToEvent) return null; if (!this.props.replyToEvent) return null;
@@ -50,7 +49,7 @@ export default class ReplyPreview extends React.Component<IProps> {
<span>{_t("composer|replying_title")}</span> <span>{_t("composer|replying_title")}</span>
<AccessibleButton <AccessibleButton
className="mx_ReplyPreview_header_cancel" className="mx_ReplyPreview_header_cancel"
onClick={() => cancelQuoting(this.context.timelineRenderingType)} onClick={() => cancelQuoting(this.props.context.timelineRenderingType)}
/> />
</div> </div>
<ReplyTile mxEvent={this.props.replyToEvent} permalinkCreator={this.props.permalinkCreator} /> <ReplyTile mxEvent={this.props.replyToEvent} permalinkCreator={this.props.permalinkCreator} />
@@ -59,3 +58,7 @@ export default class ReplyPreview extends React.Component<IProps> {
); );
} }
} }
export default forwardRef<ReplyPreview, Omit<IProps, "context">>((props, ref) => (
<RoomContext.Consumer>{(context) => <ReplyPreview {...props} context={context} ref={ref} />}</RoomContext.Consumer>
));

View File

@@ -31,7 +31,7 @@ import MFileBody from "../messages/MFileBody";
import MemberAvatar from "../avatars/MemberAvatar"; import MemberAvatar from "../avatars/MemberAvatar";
import MVoiceMessageBody from "../messages/MVoiceMessageBody"; import MVoiceMessageBody from "../messages/MVoiceMessageBody";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { renderReplyTile } from "../../../events/EventTileFactory"; import { EventTileTypeProps, renderReplyTile } from "../../../events/EventTileFactory";
import { GetRelationsForEvent } from "../rooms/EventTile"; import { GetRelationsForEvent } from "../rooms/EventTile";
import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../MatrixClientPeg";
@@ -147,13 +147,13 @@ export default class ReplyTile extends React.PureComponent<IProps> {
); );
} }
const msgtypeOverrides: Record<string, typeof React.Component> = { const msgtypeOverrides: Record<string, React.ComponentType<EventTileTypeProps>> = {
[MsgType.Image]: MImageReplyBody, [MsgType.Image]: MImageReplyBody,
// Override audio and video body with file body. We also hide the download/decrypt button using CSS // Override audio and video body with file body. We also hide the download/decrypt button using CSS
[MsgType.Audio]: isVoiceMessage(mxEvent) ? MVoiceMessageBody : MFileBody, [MsgType.Audio]: isVoiceMessage(mxEvent) ? MVoiceMessageBody : MFileBody,
[MsgType.Video]: MFileBody, [MsgType.Video]: MFileBody,
}; };
const evOverrides: Record<string, typeof React.Component> = { const evOverrides: Record<string, React.ComponentType<EventTileTypeProps>> = {
// Use MImageReplyBody so that the sticker isn't taking up a lot of space // Use MImageReplyBody so that the sticker isn't taking up a lot of space
[EventType.Sticker]: MImageReplyBody, [EventType.Sticker]: MImageReplyBody,
}; };

View File

@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from "react"; import React, { forwardRef } from "react";
import { MatrixEvent } from "matrix-js-sdk/src/matrix"; import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
@@ -40,17 +40,15 @@ interface IProps {
ourEventsIndexes: number[]; ourEventsIndexes: number[];
onHeightChanged?: () => void; onHeightChanged?: () => void;
permalinkCreator?: RoomPermalinkCreator; permalinkCreator?: RoomPermalinkCreator;
context: React.ContextType<typeof RoomContext>;
} }
export default class SearchResultTile extends React.Component<IProps> { class SearchResultTile extends React.Component<IProps> {
public static contextType = RoomContext;
public context!: React.ContextType<typeof RoomContext>;
// A map of <callId, LegacyCallEventGrouper> // A map of <callId, LegacyCallEventGrouper>
private callEventGroupers = new Map<string, LegacyCallEventGrouper>(); private callEventGroupers = new Map<string, LegacyCallEventGrouper>();
public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) { public constructor(props: IProps) {
super(props, context); super(props);
this.buildLegacyCallEventGroupers(this.props.timeline); this.buildLegacyCallEventGroupers(this.props.timeline);
} }
@@ -79,7 +77,7 @@ export default class SearchResultTile extends React.Component<IProps> {
highlights = this.props.searchHighlights; 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? // do we need a date separator since the last event?
const prevEv = timeline[j - 1]; const prevEv = timeline[j - 1];
// is this a continuation of the previous message? // is this a continuation of the previous message?
@@ -90,7 +88,7 @@ export default class SearchResultTile extends React.Component<IProps> {
prevEv, prevEv,
mxEv, mxEv,
cli, cli,
this.context?.showHiddenEvents, this.props.context?.showHiddenEvents,
TimelineRenderingType.Search, TimelineRenderingType.Search,
); );
@@ -108,7 +106,7 @@ export default class SearchResultTile extends React.Component<IProps> {
mxEv, mxEv,
nextEv, nextEv,
cli, cli,
this.context?.showHiddenEvents, this.props.context?.showHiddenEvents,
TimelineRenderingType.Search, TimelineRenderingType.Search,
); );
} }
@@ -140,3 +138,9 @@ export default class SearchResultTile extends React.Component<IProps> {
); );
} }
} }
export default forwardRef<SearchResultTile, Omit<IProps, "context">>((props, ref) => (
<RoomContext.Consumer>
{(context) => <SearchResultTile {...props} context={context} ref={ref} />}
</RoomContext.Consumer>
));

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { createRef, KeyboardEvent, SyntheticEvent } from "react"; import React, { createRef, forwardRef, KeyboardEvent, SyntheticEvent } from "react";
import EMOJI_REGEX from "emojibase-regex"; import EMOJI_REGEX from "emojibase-regex";
import { import {
IContent, IContent,
@@ -250,12 +250,10 @@ interface ISendMessageComposerProps extends MatrixClientProps {
onChange?(model: EditorModel): void; onChange?(model: EditorModel): void;
includeReplyLegacyFallback?: boolean; includeReplyLegacyFallback?: boolean;
toggleStickerPickerOpen: () => void; toggleStickerPickerOpen: () => void;
context: React.ContextType<typeof RoomContext>;
} }
export class SendMessageComposer extends React.Component<ISendMessageComposerProps> { class SendMessageComposer extends React.Component<ISendMessageComposerProps> {
public static contextType = RoomContext;
public context!: React.ContextType<typeof RoomContext>;
private readonly prepareToEncrypt?: DebouncedFunc<() => void>; private readonly prepareToEncrypt?: DebouncedFunc<() => void>;
private readonly editorRef = createRef<BasicMessageComposer>(); private readonly editorRef = createRef<BasicMessageComposer>();
private model: EditorModel; private model: EditorModel;
@@ -267,9 +265,8 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
includeReplyLegacyFallback: true, includeReplyLegacyFallback: true,
}; };
public constructor(props: ISendMessageComposerProps, context: React.ContextType<typeof RoomContext>) { public constructor(props: ISendMessageComposerProps) {
super(props, context); super(props);
this.context = context; // otherwise React will only set it prior to render due to type def above
if (this.props.mxClient.isCryptoEnabled() && this.props.mxClient.isRoomEncrypted(this.props.room.roomId)) { if (this.props.mxClient.isCryptoEnabled() && this.props.mxClient.isRoomEncrypted(this.props.room.roomId)) {
this.prepareToEncrypt = throttle( this.prepareToEncrypt = throttle(
@@ -336,7 +333,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
case KeyBindingAction.EditPrevMessage: case KeyBindingAction.EditPrevMessage:
// selection must be collapsed and caret at start // selection must be collapsed and caret at start
if (this.editorRef.current?.isSelectionCollapsed() && this.editorRef.current?.isCaretAtStart()) { if (this.editorRef.current?.isSelectionCollapsed() && this.editorRef.current?.isCaretAtStart()) {
const events = this.context.liveTimeline const events = this.props.context.liveTimeline
?.getEvents() ?.getEvents()
.concat(replyingToThread ? [] : this.props.room.getPendingEvents()); .concat(replyingToThread ? [] : this.props.room.getPendingEvents());
const editEvent = events const editEvent = events
@@ -352,17 +349,17 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
dis.dispatch({ dis.dispatch({
action: Action.EditEvent, action: Action.EditEvent,
event: editEvent, event: editEvent,
timelineRenderingType: this.context.timelineRenderingType, timelineRenderingType: this.props.context.timelineRenderingType,
}); });
} }
} }
break; break;
case KeyBindingAction.CancelReplyOrEdit: case KeyBindingAction.CancelReplyOrEdit:
if (!!this.context.replyToEvent) { if (!!this.props.context.replyToEvent) {
dis.dispatch({ dis.dispatch({
action: "reply_to_event", action: "reply_to_event",
event: null, event: null,
context: this.context.timelineRenderingType, context: this.props.context.timelineRenderingType,
}); });
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
@@ -395,7 +392,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
dis.dispatch({ dis.dispatch({
action: "reply_to_event", action: "reply_to_event",
event: replyEventId ? this.props.room.findEventById(replyEventId) : null, event: replyEventId ? this.props.room.findEventById(replyEventId) : null,
context: this.context.timelineRenderingType, context: this.props.context.timelineRenderingType,
}); });
if (parts) { if (parts) {
this.model.reset(parts); this.model.reset(parts);
@@ -405,7 +402,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
} }
private sendQuickReaction(): void { private sendQuickReaction(): void {
const timeline = this.context.liveTimeline; const timeline = this.props.context.liveTimeline;
if (!timeline) return; if (!timeline) return;
const events = timeline.getEvents(); const events = timeline.getEvents();
const reaction = this.model.parts[1].text; const reaction = this.model.parts[1].text;
@@ -518,7 +515,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
// re-focus the composer after QuestionDialog is closed // re-focus the composer after QuestionDialog is closed
dis.dispatch({ dis.dispatch({
action: Action.FocusAComposer, action: Action.FocusAComposer,
context: this.context.timelineRenderingType, context: this.props.context.timelineRenderingType,
}); });
// if !sendAnyway bail to let the user edit the composer and try again // if !sendAnyway bail to let the user edit the composer and try again
if (!sendAnyway) return; if (!sendAnyway) return;
@@ -563,7 +560,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
dis.dispatch({ dis.dispatch({
action: "reply_to_event", action: "reply_to_event",
event: null, event: null,
context: this.context.timelineRenderingType, context: this.props.context.timelineRenderingType,
}); });
} }
dis.dispatch({ action: "message_sent" }); dis.dispatch({ action: "message_sent" });
@@ -593,7 +590,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
if (shouldSend && SettingsStore.getValue("scrollToBottomOnMessageSent")) { if (shouldSend && SettingsStore.getValue("scrollToBottomOnMessageSent")) {
dis.dispatch({ dis.dispatch({
action: "scroll_to_bottom", action: "scroll_to_bottom",
timelineRenderingType: this.context.timelineRenderingType, timelineRenderingType: this.props.context.timelineRenderingType,
}); });
} }
} }
@@ -631,7 +628,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
dis.dispatch({ dis.dispatch({
action: "reply_to_event", action: "reply_to_event",
event: this.props.room.findEventById(replyEventId), event: this.props.room.findEventById(replyEventId),
context: this.context.timelineRenderingType, context: this.props.context.timelineRenderingType,
}); });
} }
return parts; return parts;
@@ -665,12 +662,12 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
switch (payload.action) { switch (payload.action) {
case "reply_to_event": case "reply_to_event":
case Action.FocusSendMessageComposer: case Action.FocusSendMessageComposer:
if ((payload.context ?? TimelineRenderingType.Room) === this.context.timelineRenderingType) { if ((payload.context ?? TimelineRenderingType.Room) === this.props.context.timelineRenderingType) {
this.editorRef.current?.focus(); this.editorRef.current?.focus();
} }
break; break;
case Action.ComposerInsert: case Action.ComposerInsert:
if (payload.timelineRenderingType !== this.context.timelineRenderingType) break; if (payload.timelineRenderingType !== this.props.context.timelineRenderingType) break;
if (payload.composerType !== ComposerType.Send) break; if (payload.composerType !== ComposerType.Send) break;
if (payload.userId) { if (payload.userId) {
@@ -695,7 +692,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
this.props.room.roomId, this.props.room.roomId,
this.props.relation, this.props.relation,
this.props.mxClient, this.props.mxClient,
this.context.timelineRenderingType, this.props.context.timelineRenderingType,
); );
return true; // to skip internal onPaste handler return true; // to skip internal onPaste handler
} }
@@ -734,7 +731,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
this.props.room.roomId, this.props.room.roomId,
this.props.relation, this.props.relation,
this.props.mxClient, this.props.mxClient,
this.context.replyToEvent, this.props.context.replyToEvent,
); );
}, },
(error) => { (error) => {
@@ -789,4 +786,10 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
} }
const SendMessageComposerWithMatrixClient = withMatrixClientHOC(SendMessageComposer); const SendMessageComposerWithMatrixClient = withMatrixClientHOC(SendMessageComposer);
export default SendMessageComposerWithMatrixClient; export default forwardRef<SendMessageComposer, Omit<ISendMessageComposerProps, "context" | "mxClient">>(
(props, ref) => (
<RoomContext.Consumer>
{(context) => <SendMessageComposerWithMatrixClient {...props} context={context} ref={ref} />}
</RoomContext.Consumer>
),
);

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { ReactNode } from "react"; import React, { forwardRef, ReactNode } from "react";
import { Room, IEventRelation, MatrixEvent } from "matrix-js-sdk/src/matrix"; import { Room, IEventRelation, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { Optional } from "matrix-events-sdk"; import { Optional } from "matrix-events-sdk";
@@ -50,6 +50,7 @@ interface IProps {
permalinkCreator?: RoomPermalinkCreator; permalinkCreator?: RoomPermalinkCreator;
relation?: IEventRelation; relation?: IEventRelation;
replyToEvent?: MatrixEvent; replyToEvent?: MatrixEvent;
context: React.ContextType<typeof RoomContext>;
} }
interface IState { interface IState {
@@ -61,9 +62,7 @@ interface IState {
/** /**
* Container tile for rendering the voice message recorder in the composer. * Container tile for rendering the voice message recorder in the composer.
*/ */
export default class VoiceRecordComposerTile extends React.PureComponent<IProps, IState> { class VoiceRecordComposerTile extends React.PureComponent<IProps, IState> {
public static contextType = RoomContext;
public context!: React.ContextType<typeof RoomContext>;
private voiceRecordingId: string; private voiceRecordingId: string;
public constructor(props: IProps) { public constructor(props: IProps) {
@@ -141,7 +140,7 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
defaultDispatcher.dispatch({ defaultDispatcher.dispatch({
action: "reply_to_event", action: "reply_to_event",
event: null, event: null,
context: this.context.timelineRenderingType, context: this.props.context.timelineRenderingType,
}); });
} }
@@ -323,3 +322,9 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
); );
} }
} }
export default forwardRef<VoiceRecordComposerTile, Omit<IProps, "context">>((props, ref) => (
<RoomContext.Consumer>
{(context) => <VoiceRecordComposerTile {...props} context={context} ref={ref} />}
</RoomContext.Consumer>
));

View File

@@ -59,7 +59,7 @@ interface WysiwygAutocompleteProps {
const WysiwygAutocomplete = forwardRef( const WysiwygAutocomplete = forwardRef(
( (
{ suggestion, handleMention, handleCommand, handleAtRoomMention }: WysiwygAutocompleteProps, { suggestion, handleMention, handleCommand, handleAtRoomMention }: WysiwygAutocompleteProps,
ref: ForwardedRef<Autocomplete>, ref: ForwardedRef<React.ComponentRef<typeof Autocomplete>>,
): JSX.Element | null => { ): JSX.Element | null => {
const { room } = useRoomContext(); const { room } = useRoomContext();
const client = useMatrixClientContext(); const client = useMatrixClientContext();

View File

@@ -58,7 +58,7 @@ export const WysiwygComposer = memo(function WysiwygComposer({
eventRelation, eventRelation,
}: WysiwygComposerProps) { }: WysiwygComposerProps) {
const { room } = useRoomContext(); const { room } = useRoomContext();
const autocompleteRef = useRef<Autocomplete | null>(null); const autocompleteRef = useRef<React.ComponentRef<typeof Autocomplete> | null>(null);
const inputEventProcessor = useInputEventProcessor(onSend, autocompleteRef, initialContent, eventRelation); const inputEventProcessor = useInputEventProcessor(onSend, autocompleteRef, initialContent, eventRelation);
const { ref, isWysiwygReady, content, actionStates, wysiwyg, suggestion, messageContent } = useWysiwyg({ const { ref, isWysiwygReady, content, actionStates, wysiwyg, suggestion, messageContent } = useWysiwyg({

View File

@@ -37,7 +37,7 @@ import { handleClipboardEvent, handleEventWithAutocomplete, isEventToHandleAsCli
export function useInputEventProcessor( export function useInputEventProcessor(
onSend: () => void, onSend: () => void,
autocompleteRef: React.RefObject<Autocomplete>, autocompleteRef: React.RefObject<React.ComponentRef<typeof Autocomplete>>,
initialContent?: string, initialContent?: string,
eventRelation?: IEventRelation, eventRelation?: IEventRelation,
): (event: WysiwygEvent, composer: Wysiwyg, editor: HTMLElement) => WysiwygEvent | null { ): (event: WysiwygEvent, composer: Wysiwyg, editor: HTMLElement) => WysiwygEvent | null {
@@ -105,7 +105,7 @@ function handleKeyboardEvent(
roomContext: IRoomState, roomContext: IRoomState,
composerContext: ComposerContextState, composerContext: ComposerContextState,
mxClient: MatrixClient | undefined, mxClient: MatrixClient | undefined,
autocompleteRef: React.RefObject<Autocomplete>, autocompleteRef: React.RefObject<React.ComponentRef<typeof Autocomplete>>,
): KeyboardEvent | null { ): KeyboardEvent | null {
const { editorStateTransfer } = composerContext; const { editorStateTransfer } = composerContext;
const isEditing = Boolean(editorStateTransfer); const isEditing = Boolean(editorStateTransfer);

View File

@@ -55,7 +55,7 @@ export function usePlainTextListeners(
eventRelation?: IEventRelation, eventRelation?: IEventRelation,
): { ): {
ref: RefObject<HTMLDivElement>; ref: RefObject<HTMLDivElement>;
autocompleteRef: React.RefObject<Autocomplete>; autocompleteRef: React.RefObject<React.ComponentRef<typeof Autocomplete>>;
content?: string; content?: string;
onBeforeInput(event: SyntheticEvent<HTMLDivElement, InputEvent | ClipboardEvent>): void; onBeforeInput(event: SyntheticEvent<HTMLDivElement, InputEvent | ClipboardEvent>): void;
onInput(event: SyntheticEvent<HTMLDivElement, InputEvent | ClipboardEvent>): void; onInput(event: SyntheticEvent<HTMLDivElement, InputEvent | ClipboardEvent>): void;
@@ -72,7 +72,7 @@ export function usePlainTextListeners(
const mxClient = useMatrixClientContext(); const mxClient = useMatrixClientContext();
const ref = useRef<HTMLDivElement | null>(null); const ref = useRef<HTMLDivElement | null>(null);
const autocompleteRef = useRef<Autocomplete | null>(null); const autocompleteRef = useRef<React.ComponentRef<typeof Autocomplete> | null>(null);
const [content, setContent] = useState<string | undefined>(initialContent); const [content, setContent] = useState<string | undefined>(initialContent);
const send = useCallback(() => { const send = useCallback(() => {

View File

@@ -70,7 +70,7 @@ export function setCursorPositionAtTheEnd(element: HTMLElement): void {
* @returns boolean - whether or not the autocomplete has handled the event * @returns boolean - whether or not the autocomplete has handled the event
*/ */
export function handleEventWithAutocomplete( export function handleEventWithAutocomplete(
autocompleteRef: RefObject<Autocomplete>, autocompleteRef: RefObject<React.ComponentRef<typeof Autocomplete>>,
// we get a React Keyboard event from plain text composer, a Keyboard Event from the rich text composer // we get a React Keyboard event from plain text composer, a Keyboard Event from the rich text composer
event: KeyboardEvent | React.KeyboardEvent<HTMLDivElement>, event: KeyboardEvent | React.KeyboardEvent<HTMLDivElement>,
): boolean { ): boolean {

View File

@@ -28,7 +28,7 @@ export interface ICallback {
} }
export type UpdateCallback = (data: ICallback) => void; export type UpdateCallback = (data: ICallback) => void;
export type GetAutocompleterComponent = () => Autocomplete | null; export type GetAutocompleterComponent = () => React.ComponentRef<typeof Autocomplete> | null;
export type UpdateQuery = (test: string) => Promise<void>; export type UpdateQuery = (test: string) => Promise<void>;
export default class AutocompleteWrapperModel { export default class AutocompleteWrapperModel {

View File

@@ -56,6 +56,7 @@ import {
shouldDisplayAsVoiceBroadcastStoppedText, shouldDisplayAsVoiceBroadcastStoppedText,
VoiceBroadcastChunkEventType, VoiceBroadcastChunkEventType,
} from "../voice-broadcast"; } from "../voice-broadcast";
import { IBodyProps } from "../components/views/messages/IBodyProps";
// Subset of EventTile's IProps plus some mixins // Subset of EventTile's IProps plus some mixins
export interface EventTileTypeProps export interface EventTileTypeProps
@@ -78,11 +79,11 @@ export interface EventTileTypeProps
ref?: React.RefObject<any>; // `any` because it's effectively impossible to convince TS of a reasonable type ref?: React.RefObject<any>; // `any` because it's effectively impossible to convince TS of a reasonable type
timestamp?: JSX.Element; timestamp?: JSX.Element;
maxImageHeight?: number; // pixels maxImageHeight?: number; // pixels
overrideBodyTypes?: Record<string, typeof React.Component>; overrideBodyTypes?: Record<string, React.ComponentType<IBodyProps>>;
overrideEventTypes?: Record<string, typeof React.Component>; overrideEventTypes?: Record<string, React.ComponentType<IBodyProps>>;
} }
type FactoryProps = Omit<EventTileTypeProps, "ref">; type FactoryProps = EventTileTypeProps;
type Factory<X = FactoryProps> = (ref: Optional<React.RefObject<any>>, props: X) => JSX.Element; type Factory<X = FactoryProps> = (ref: Optional<React.RefObject<any>>, props: X) => JSX.Element;
export const MessageEventFactory: Factory = (ref, props) => <MessageEvent ref={ref} {...props} />; export const MessageEventFactory: Factory = (ref, props) => <MessageEvent ref={ref} {...props} />;

View File

@@ -40,7 +40,7 @@ import React, { createRef } from "react";
import { Mocked, mocked } from "jest-mock"; import { Mocked, mocked } from "jest-mock";
import { forEachRight } from "lodash"; 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 MatrixClientContext from "../../../src/contexts/MatrixClientContext";
import { MatrixClientPeg } from "../../../src/MatrixClientPeg"; import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
import { isCallEvent } from "../../../src/components/structures/LegacyCallEventGrouper"; import { isCallEvent } from "../../../src/components/structures/LegacyCallEventGrouper";
@@ -77,7 +77,7 @@ const mkTimeline = (room: Room, events: MatrixEvent[]): [EventTimeline, EventTim
return [timeline, timelineSet]; return [timeline, timelineSet];
}; };
const getProps = (room: Room, events: MatrixEvent[]): TimelinePanel["props"] => { const getProps = (room: Room, events: MatrixEvent[]): React.ComponentProps<typeof TimelinePanel> => {
const [, timelineSet] = mkTimeline(room, events); const [, timelineSet] = mkTimeline(room, events);
return { return {
@@ -184,7 +184,7 @@ describe("TimelinePanel", () => {
const roomId = "#room:example.com"; const roomId = "#room:example.com";
let room: Room; let room: Room;
let timelineSet: EventTimelineSet; let timelineSet: EventTimelineSet;
let timelinePanel: TimelinePanel; let timelinePanel: React.ComponentRef<typeof TimelinePanel>;
const ev1 = new MatrixEvent({ const ev1 = new MatrixEvent({
event_id: "ev1", event_id: "ev1",
@@ -203,7 +203,7 @@ describe("TimelinePanel", () => {
}); });
const renderTimelinePanel = async (): Promise<void> => { const renderTimelinePanel = async (): Promise<void> => {
const ref = createRef<TimelinePanel>(); const ref = createRef<React.ComponentRef<typeof TimelinePanel>>();
render( render(
<TimelinePanel <TimelinePanel
timelineSet={timelineSet} timelineSet={timelineSet}
@@ -239,7 +239,7 @@ describe("TimelinePanel", () => {
}); });
afterEach(() => { afterEach(() => {
TimelinePanel.roomReadMarkerTsMap = {}; TimelinePanelClass.roomReadMarkerTsMap = {};
}); });
it("when there is no event, it should not send any receipt", async () => { it("when there is no event, it should not send any receipt", async () => {

View File

@@ -50,7 +50,7 @@ import {
} from "../../../test-utils"; } from "../../../test-utils";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import DMRoomMap from "../../../../src/utils/DMRoomMap"; 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 { SearchScope } from "../../../../src/components/views/rooms/SearchBar";
import { E2EStatus } from "../../../../src/utils/ShieldUtils"; import { E2EStatus } from "../../../../src/utils/ShieldUtils";
import { IRoomState } from "../../../../src/components/structures/RoomView"; import { IRoomState } from "../../../../src/components/structures/RoomView";
@@ -201,7 +201,10 @@ describe("LegacyRoomHeader", () => {
WidgetMessagingStore.instance.stopMessaging(widget, call.roomId); WidgetMessagingStore.instance.stopMessaging(widget, call.roomId);
}; };
const renderHeader = (props: Partial<RoomHeaderProps> = {}, roomContext: Partial<IRoomState> = {}) => { const renderHeader = (
props: Partial<React.ComponentProps<typeof RoomHeader>> = {},
roomContext: Partial<IRoomState> = {},
) => {
render( render(
<RoomContext.Provider value={{ ...roomContext, room } as IRoomState}> <RoomContext.Provider value={{ ...roomContext, room } as IRoomState}>
<RoomHeader <RoomHeader
@@ -843,7 +846,7 @@ function createRoom(info: IRoomCreationInfo) {
} }
function mountHeader(room: Room, propsOverride = {}, roomContext?: Partial<IRoomState>): RenderResult { function mountHeader(room: Room, propsOverride = {}, roomContext?: Partial<IRoomState>): RenderResult {
const props: RoomHeaderProps = { const props: React.ComponentProps<typeof RoomHeader> = {
room, room,
inRoom: true, inRoom: true,
onSearchClick: () => {}, onSearchClick: () => {},

View File

@@ -43,7 +43,7 @@ jest.mock("../../../../src/stores/VoiceRecordingStore", () => ({
})); }));
describe("<VoiceRecordComposerTile/>", () => { describe("<VoiceRecordComposerTile/>", () => {
let voiceRecordComposerTile: RefObject<VoiceRecordComposerTile>; let voiceRecordComposerTile: RefObject<React.ComponentRef<typeof VoiceRecordComposerTile>>;
let mockRecorder: VoiceMessageRecording; let mockRecorder: VoiceMessageRecording;
let mockUpload: IUpload; let mockUpload: IUpload;
let mockClient: MatrixClient; let mockClient: MatrixClient;

View File

@@ -60,7 +60,7 @@ describe("WysiwygAutocomplete", () => {
jest.restoreAllMocks(); jest.restoreAllMocks();
}); });
const autocompleteRef = createRef<Autocomplete>(); const autocompleteRef = createRef<React.ComponentRef<typeof Autocomplete>>();
const getCompletionsSpy = jest.spyOn(Autocompleter.prototype, "getCompletions").mockResolvedValue([ const getCompletionsSpy = jest.spyOn(Autocompleter.prototype, "getCompletions").mockResolvedValue([
{ {
completions: mockCompletion, completions: mockCompletion,