diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index deb71fa678..ccf89adc42 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -56,7 +56,7 @@ $left-gutter: 64px; font-size: $font-14px; position: relative; - &[data-shape=thread_list][data-notification]::before { + &[data-shape=ThreadsList][data-notification]::before { content: ""; position: absolute; width: 8px; @@ -67,11 +67,11 @@ $left-gutter: 64px; left: auto; } - &[data-shape=thread_list][data-notification=total]::before { + &[data-shape=ThreadsList][data-notification=total]::before { background-color: $roomtile-default-badge-bg-color; } - &[data-shape=thread_list][data-notification=highlight]::before { + &[data-shape=ThreadsList][data-notification=highlight]::before { background-color: $alert; } @@ -797,7 +797,7 @@ $left-gutter: 64px; white-space: nowrap; } -.mx_EventTile[data-shape=thread_list] { +.mx_EventTile[data-shape=ThreadsList] { --topOffset: 20px; --leftOffset: 46px; diff --git a/src/components/structures/FilePanel.tsx b/src/components/structures/FilePanel.tsx index 4062182266..6a7db3dc0b 100644 --- a/src/components/structures/FilePanel.tsx +++ b/src/components/structures/FilePanel.tsx @@ -33,7 +33,6 @@ import { replaceableComponent } from "../../utils/replaceableComponent"; import ResizeNotifier from '../../utils/ResizeNotifier'; import TimelinePanel from "./TimelinePanel"; import Spinner from "../views/elements/Spinner"; -import { TileShape } from '../views/rooms/EventTile'; import { Layout } from "../../settings/enums/Layout"; import RoomContext, { TimelineRenderingType } from '../../contexts/RoomContext'; @@ -270,7 +269,6 @@ class FilePanel extends React.Component { timelineSet={this.state.timelineSet} showUrlPreview={false} onPaginationRequest={this.onPaginationRequest} - tileShape={TileShape.FileGrid} resizeNotifier={this.props.resizeNotifier} empty={emptyState} layout={Layout.Group} diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx index 689d02155f..938e01b8e7 100644 --- a/src/components/structures/MessagePanel.tsx +++ b/src/components/structures/MessagePanel.tsx @@ -29,7 +29,7 @@ import SettingsStore from '../../settings/SettingsStore'; import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext"; import { Layout } from "../../settings/enums/Layout"; import { _t } from "../../languageHandler"; -import EventTile, { haveTileForEvent, IReadReceiptProps, TileShape } from "../views/rooms/EventTile"; +import EventTile, { haveTileForEvent, IReadReceiptProps } from "../views/rooms/EventTile"; import { hasText } from "../../TextForEvent"; import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer"; import DMRoomMap from "../../utils/DMRoomMap"; @@ -144,9 +144,6 @@ interface IProps { // className for the panel className: string; - // shape parameter to be passed to EventTiles - tileShape?: TileShape; - // show twelve hour timestamps isTwelveHour?: boolean; @@ -802,7 +799,6 @@ export default class MessagePanel extends React.Component { showUrlPreview={this.props.showUrlPreview} checkUnmounting={this.isUnmounting} eventSendStatus={mxEv.getAssociatedStatus()} - tileShape={this.props.tileShape} isTwelveHour={this.props.isTwelveHour} permalinkCreator={this.props.permalinkCreator} last={last} @@ -994,7 +990,10 @@ export default class MessagePanel extends React.Component { const style = this.props.hidden ? { display: 'none' } : {}; let whoIsTyping; - if (this.props.room && !this.props.tileShape && this.state.showTypingNotifications) { + if (this.props.room && + this.state.showTypingNotifications && + this.context.timelineRenderingType === TimelineRenderingType.Room + ) { whoIsTyping = ( { manageReadMarkers={false} timelineSet={timelineSet} showUrlPreview={false} - tileShape={TileShape.Notif} empty={emptyState} alwaysShowTimestamps={true} layout={Layout.Group} diff --git a/src/components/structures/ThreadPanel.tsx b/src/components/structures/ThreadPanel.tsx index 1148ee4402..151af8377a 100644 --- a/src/components/structures/ThreadPanel.tsx +++ b/src/components/structures/ThreadPanel.tsx @@ -36,7 +36,6 @@ import ContextMenu, { ChevronFace, MenuItemRadio, useContextMenu } from './Conte import RoomContext, { TimelineRenderingType } from '../../contexts/RoomContext'; import TimelinePanel from './TimelinePanel'; import { Layout } from '../../settings/enums/Layout'; -import { TileShape } from '../views/rooms/EventTile'; import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks'; import { useEventEmitter } from '../../hooks/useEventEmitter'; @@ -302,7 +301,6 @@ const ThreadPanel: React.FC = ({ roomId, onClose, permalinkCreator }) => className="mx_RoomView_messagePanel mx_GroupLayout" membersLoaded={true} permalinkCreator={permalinkCreator} - tileShape={TileShape.ThreadPanel} disableGrouping={true} /> ) } diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx index da8d49aaeb..c26c64428c 100644 --- a/src/components/structures/ThreadView.tsx +++ b/src/components/structures/ThreadView.tsx @@ -28,7 +28,6 @@ import BaseCard from "../views/right_panel/BaseCard"; import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases"; import { replaceableComponent } from "../../utils/replaceableComponent"; import ResizeNotifier from '../../utils/ResizeNotifier'; -import { TileShape } from '../views/rooms/EventTile'; import MessageComposer from '../views/rooms/MessageComposer'; import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks'; import { Layout } from '../../settings/enums/Layout'; @@ -284,7 +283,6 @@ export default class ThreadView extends React.Component { sendReadReceiptOnLoad={true} timelineSet={this.state?.thread?.timelineSet} showUrlPreview={true} - tileShape={TileShape.Thread} // ThreadView doesn't support IRC layout at this time layout={this.state.layout === Layout.Bubble ? Layout.Bubble : Layout.Group} hideThreadedMessages={false} diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx index a5cb4add5a..32ed88103e 100644 --- a/src/components/structures/TimelinePanel.tsx +++ b/src/components/structures/TimelinePanel.tsx @@ -39,7 +39,7 @@ import { Action } from '../../dispatcher/actions'; import { Key } from '../../Keyboard'; import Timer from '../../utils/Timer'; import shouldHideEvent from '../../shouldHideEvent'; -import { haveTileForEvent, TileShape } from "../views/rooms/EventTile"; +import { haveTileForEvent } from "../views/rooms/EventTile"; import { UIFeature } from "../../settings/UIFeature"; import { replaceableComponent } from "../../utils/replaceableComponent"; import { arrayFastClone } from "../../utils/arrays"; @@ -103,9 +103,6 @@ interface IProps { // classname to use for the messagepanel className?: string; - // shape property to be passed to EventTiles - tileShape?: TileShape; - // placeholder to use if the timeline is empty empty?: ReactNode; @@ -1644,7 +1641,6 @@ class TimelinePanel extends React.Component { this.state.alwaysShowTimestamps } className={this.props.className} - tileShape={this.props.tileShape} resizeNotifier={this.props.resizeNotifier} getRelationsForEvent={this.getRelationsForEvent} editState={this.props.editState} diff --git a/src/components/views/audio_messages/AudioPlayerBase.tsx b/src/components/views/audio_messages/AudioPlayerBase.tsx index f645c53387..321fdd6ecf 100644 --- a/src/components/views/audio_messages/AudioPlayerBase.tsx +++ b/src/components/views/audio_messages/AudioPlayerBase.tsx @@ -18,7 +18,6 @@ import React, { ReactNode } from "react"; import { logger } from "matrix-js-sdk/src/logger"; import { Playback, PlaybackState } from "../../../audio/Playback"; -import { TileShape } from "../rooms/EventTile"; import { UPDATE_EVENT } from "../../../stores/AsyncStore"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { _t } from "../../../languageHandler"; @@ -29,7 +28,6 @@ interface IProps { playback: Playback; mediaName?: string; - tileShape?: TileShape; } interface IState { diff --git a/src/components/views/audio_messages/RecordingPlayback.tsx b/src/components/views/audio_messages/RecordingPlayback.tsx index 04c61affd9..a80751fd61 100644 --- a/src/components/views/audio_messages/RecordingPlayback.tsx +++ b/src/components/views/audio_messages/RecordingPlayback.tsx @@ -19,16 +19,19 @@ import React, { ReactNode } from "react"; import PlayPauseButton from "./PlayPauseButton"; import PlaybackClock from "./PlaybackClock"; import { replaceableComponent } from "../../../utils/replaceableComponent"; -import { TileShape } from "../rooms/EventTile"; import PlaybackWaveform from "./PlaybackWaveform"; import AudioPlayerBase from "./AudioPlayerBase"; +import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; @replaceableComponent("views.audio_messages.RecordingPlayback") export default class RecordingPlayback extends AudioPlayerBase { + static contextType = RoomContext; + public context!: React.ContextType; + private get isWaveformable(): boolean { - return this.props.tileShape !== TileShape.Notif - && this.props.tileShape !== TileShape.FileGrid - && this.props.tileShape !== TileShape.Pinned; + return this.context.timelineRenderingType !== TimelineRenderingType.Notification + && this.context.timelineRenderingType !== TimelineRenderingType.File + && this.context.timelineRenderingType !== TimelineRenderingType.Pinned; } protected renderComponent(): ReactNode { diff --git a/src/components/views/messages/IBodyProps.ts b/src/components/views/messages/IBodyProps.ts index c39dfa4798..76c823b185 100644 --- a/src/components/views/messages/IBodyProps.ts +++ b/src/components/views/messages/IBodyProps.ts @@ -17,7 +17,6 @@ limitations under the License. import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { Relations } from "matrix-js-sdk/src/models/relations"; -import { TileShape } from "../rooms/EventTile"; import { MediaEventHelper } from "../../../utils/MediaEventHelper"; import EditorStateTransfer from "../../../utils/EditorStateTransfer"; import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; @@ -36,7 +35,6 @@ export interface IBodyProps { showUrlPreview?: boolean; forExport?: boolean; - tileShape: TileShape; maxImageHeight?: number; replacingEventId?: string; editState?: EditorStateTransfer; diff --git a/src/components/views/messages/MAudioBody.tsx b/src/components/views/messages/MAudioBody.tsx index d3bc2fb107..4af3821596 100644 --- a/src/components/views/messages/MAudioBody.tsx +++ b/src/components/views/messages/MAudioBody.tsx @@ -28,6 +28,7 @@ import { IBodyProps } from "./IBodyProps"; import { PlaybackManager } from "../../../audio/PlaybackManager"; import { isVoiceMessage } from "../../../utils/EventUtils"; import { PlaybackQueue } from "../../../audio/PlaybackQueue"; +import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; interface IState { error?: Error; @@ -36,6 +37,9 @@ interface IState { @replaceableComponent("views.messages.MAudioBody") export default class MAudioBody extends React.PureComponent { + static contextType = RoomContext; + public context!: React.ContextType; + constructor(props: IBodyProps) { super(props); @@ -82,6 +86,12 @@ export default class MAudioBody extends React.PureComponent this.state.playback?.destroy(); } + protected get showFileBody(): boolean { + return this.context.timelineRenderingType !== TimelineRenderingType.Room && + this.context.timelineRenderingType !== TimelineRenderingType.Pinned && + this.context.timelineRenderingType !== TimelineRenderingType.Search; + } + public render() { if (this.state.error) { return ( @@ -115,7 +125,7 @@ export default class MAudioBody extends React.PureComponent return ( - { this.props.tileShape && } + { this.showFileBody && } ); } diff --git a/src/components/views/messages/MFileBody.tsx b/src/components/views/messages/MFileBody.tsx index 3df84b5101..ce99ddc688 100644 --- a/src/components/views/messages/MFileBody.tsx +++ b/src/components/views/messages/MFileBody.tsx @@ -24,12 +24,12 @@ import AccessibleButton from "../elements/AccessibleButton"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { mediaFromContent } from "../../../customisations/Media"; import ErrorDialog from "../dialogs/ErrorDialog"; -import { TileShape } from "../rooms/EventTile"; import { presentableTextForFile } from "../../../utils/FileUtils"; import { IMediaEventContent } from "../../../customisations/models/IMediaEventContent"; import { IBodyProps } from "./IBodyProps"; import { FileDownloader } from "../../../utils/FileDownloader"; import TextWithTooltip from "../elements/TextWithTooltip"; +import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; export let DOWNLOAD_ICON_URL; // cached copy of the download.svg asset for the sandboxed iframe later on @@ -108,6 +108,9 @@ interface IState { @replaceableComponent("views.messages.MFileBody") export default class MFileBody extends React.Component { + static contextType = RoomContext; + public context!: React.ContextType; + static defaultProps = { showGenericPlaceholder: true, }; @@ -223,8 +226,15 @@ export default class MFileBody extends React.Component { ; } - const showDownloadLink = (this.props.tileShape || !this.props.showGenericPlaceholder) && - this.props.tileShape !== TileShape.Thread; + let showDownloadLink = !this.props.showGenericPlaceholder || ( + this.context.timelineRenderingType !== TimelineRenderingType.Room && + this.context.timelineRenderingType !== TimelineRenderingType.Search && + this.context.timelineRenderingType !== TimelineRenderingType.Pinned + ); + + if (this.context.timelineRenderingType === TimelineRenderingType.Thread) { + showDownloadLink = false; + } if (isEncrypted) { if (!this.state.decryptedBlob) { @@ -334,9 +344,11 @@ export default class MFileBody extends React.Component { { _t("Download %(text)s", { text: this.linkText }) } - { this.props.tileShape === TileShape.FileGrid &&
- { this.content.info && this.content.info.size ? filesize(this.content.info.size) : "" } -
} + { this.context.timelineRenderingType === TimelineRenderingType.File && ( +
+ { this.content.info && this.content.info.size ? filesize(this.content.info.size) : "" } +
+ ) } }
); diff --git a/src/components/views/messages/MImageBody.tsx b/src/components/views/messages/MImageBody.tsx index 809b44d06b..1b0aa1444d 100644 --- a/src/components/views/messages/MImageBody.tsx +++ b/src/components/views/messages/MImageBody.tsx @@ -26,7 +26,6 @@ import MFileBody from './MFileBody'; import Modal from '../../../Modal'; import { _t } from '../../../languageHandler'; import SettingsStore from "../../../settings/SettingsStore"; -import MatrixClientContext from "../../../contexts/MatrixClientContext"; import InlineSpinner from '../elements/InlineSpinner'; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { Media, mediaFromContent } from "../../../customisations/Media"; @@ -34,8 +33,9 @@ import { BLURHASH_FIELD } from "../../../ContentMessages"; import { IMediaEventContent } from '../../../customisations/models/IMediaEventContent'; import ImageView from '../elements/ImageView'; import { IBodyProps } from "./IBodyProps"; -import { TileShape } from '../rooms/EventTile'; import { ImageSize, suggestedSize as suggestedImageSize } from "../../../settings/enums/ImageSize"; +import { MatrixClientPeg } from '../../../MatrixClientPeg'; +import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; interface IState { decryptedUrl?: string; @@ -55,7 +55,9 @@ interface IState { @replaceableComponent("views.messages.MImageBody") export default class MImageBody extends React.Component { - static contextType = MatrixClientContext; + static contextType = RoomContext; + public context!: React.ContextType; + private unmounted = true; private image = createRef(); private timeout?: number; @@ -297,7 +299,7 @@ export default class MImageBody extends React.Component { componentDidMount() { this.unmounted = false; - this.context.on('sync', this.onClientSync); + MatrixClientPeg.get().on('sync', this.onClientSync); const showImage = this.state.showImage || localStorage.getItem("mx_ShowImage_" + this.props.mxEvent.getId()) === "true"; @@ -327,7 +329,7 @@ export default class MImageBody extends React.Component { componentWillUnmount() { this.unmounted = true; - this.context.removeListener('sync', this.onClientSync); + MatrixClientPeg.get().removeListener('sync', this.onClientSync); this.clearBlurhashTimeout(); SettingsStore.unwatchSetting(this.sizeWatcher); } @@ -524,9 +526,11 @@ export default class MImageBody extends React.Component { * In the room timeline or the thread context we don't need the download * link as the message action bar will fullfil that */ - const hasMessageActionBar = !this.props.tileShape - || this.props.tileShape === TileShape.Thread - || this.props.tileShape === TileShape.ThreadPanel; + const hasMessageActionBar = this.context.timelineRenderingType === TimelineRenderingType.Room + || this.context.timelineRenderingType === TimelineRenderingType.Pinned + || this.context.timelineRenderingType === TimelineRenderingType.Search + || this.context.timelineRenderingType === TimelineRenderingType.Thread + || this.context.timelineRenderingType === TimelineRenderingType.ThreadsList; if (!hasMessageActionBar) { return ; } diff --git a/src/components/views/messages/MVideoBody.tsx b/src/components/views/messages/MVideoBody.tsx index 202829a619..22d5399d90 100644 --- a/src/components/views/messages/MVideoBody.tsx +++ b/src/components/views/messages/MVideoBody.tsx @@ -28,6 +28,7 @@ import { IMediaEventContent } from "../../../customisations/models/IMediaEventCo import { IBodyProps } from "./IBodyProps"; import MFileBody from "./MFileBody"; import { ImageSize, suggestedSize as suggestedVideoSize } from "../../../settings/enums/ImageSize"; +import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; interface IState { decryptedUrl?: string; @@ -41,6 +42,9 @@ interface IState { @replaceableComponent("views.messages.MVideoBody") export default class MVideoBody extends React.PureComponent { + static contextType = RoomContext; + public context!: React.ContextType; + private videoRef = React.createRef(); private sizeWatcher: string; @@ -235,9 +239,15 @@ export default class MVideoBody extends React.PureComponent this.props.onHeightChanged(); }; + protected get showFileBody(): boolean { + return this.context.timelineRenderingType !== TimelineRenderingType.Room && + this.context.timelineRenderingType !== TimelineRenderingType.Pinned && + this.context.timelineRenderingType !== TimelineRenderingType.Search; + } + private getFileBody = () => { if (this.props.forExport) return null; - return this.props.tileShape && ; + return this.showFileBody && ; }; render() { diff --git a/src/components/views/messages/MVoiceMessageBody.tsx b/src/components/views/messages/MVoiceMessageBody.tsx index 5a8a633855..bc42ff5551 100644 --- a/src/components/views/messages/MVoiceMessageBody.tsx +++ b/src/components/views/messages/MVoiceMessageBody.tsx @@ -47,8 +47,8 @@ export default class MVoiceMessageBody extends MAudioBody { // At this point we should have a playable state return ( - - { this.props.tileShape && } + + { this.showFileBody && } ); } diff --git a/src/components/views/messages/MessageEvent.tsx b/src/components/views/messages/MessageEvent.tsx index ec310fef0c..578b887420 100644 --- a/src/components/views/messages/MessageEvent.tsx +++ b/src/components/views/messages/MessageEvent.tsx @@ -161,7 +161,6 @@ export default class MessageEvent extends React.Component implements IMe highlights={this.props.highlights} highlightLink={this.props.highlightLink} showUrlPreview={this.props.showUrlPreview} - tileShape={this.props.tileShape} forExport={this.props.forExport} maxImageHeight={this.props.maxImageHeight} replacingEventId={this.props.replacingEventId} diff --git a/src/components/views/messages/SenderProfile.tsx b/src/components/views/messages/SenderProfile.tsx index d363bdef94..7f30c81793 100644 --- a/src/components/views/messages/SenderProfile.tsx +++ b/src/components/views/messages/SenderProfile.tsx @@ -24,7 +24,7 @@ import { getUserNameColorClass } from '../../../utils/FormattingUtils'; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import UserIdentifier from '../../../customisations/UserIdentifier'; -import { TileShape } from '../rooms/EventTile'; +import RoomContext, { TimelineRenderingType } from '../../../contexts/RoomContext'; import SettingsStore from "../../../settings/SettingsStore"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; @@ -32,7 +32,6 @@ interface IProps { mxEvent: MatrixEvent; onClick?(): void; enableFlair: boolean; - tileShape?: TileShape; } interface IState { @@ -121,41 +120,44 @@ export default class SenderProfile extends React.Component { const displayName = member?.rawDisplayName || mxEvent.getSender() || ""; const mxid = member?.userId || mxEvent.getSender() || ""; - if (msgtype === MsgType.Emote && this.props.tileShape !== TileShape.ThreadPanel) { - return null; // emote message must include the name so don't duplicate it - } + return + { roomContext => { + if (msgtype === MsgType.Emote && + roomContext.timelineRenderingType !== TimelineRenderingType.ThreadsList + ) { + return null; // emote message must include the name so don't duplicate it + } - let mxidElement; - if (disambiguate) { - mxidElement = ( - - { UserIdentifier.getDisplayUserIdentifier( - mxid, { withDisplayName: true, roomId: mxEvent.getRoomId() }, - ) } - - ); - } + let mxidElement; + if (disambiguate) { + mxidElement = ( + + { UserIdentifier.getDisplayUserIdentifier( + mxid, { withDisplayName: true, roomId: mxEvent.getRoomId() }, + ) } + + ); + } - let flair; - if (this.props.enableFlair) { - const displayedGroups = this.getDisplayedGroups( - this.state.userGroups, this.state.relatedGroups, - ); + let flair; + if (this.props.enableFlair) { + const displayedGroups = this.getDisplayedGroups( + this.state.userGroups, this.state.relatedGroups, + ); - flair = ; - } + flair = ; + } - return ( -
- - { displayName } - - { mxidElement } - { flair } -
- ); + return ( +
+ + { displayName } + + { mxidElement } + { flair } +
+ ); + } } +
; } } diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx index 44c6b2c922..c5ba91c275 100644 --- a/src/components/views/right_panel/PinnedMessagesCard.tsx +++ b/src/components/views/right_panel/PinnedMessagesCard.tsx @@ -29,6 +29,7 @@ import PinningUtils from "../../../utils/PinningUtils"; import { useAsyncMemo } from "../../../hooks/useAsyncMemo"; import PinnedEventTile from "../rooms/PinnedEventTile"; import { useRoomState } from "../../../hooks/useRoomState"; +import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; interface IProps { room: Room; @@ -78,6 +79,7 @@ export const useReadPinnedEvents = (room: Room): Set => { const PinnedMessagesCard = ({ room, onClose }: IProps) => { const cli = useContext(MatrixClientContext); + const roomContext = useContext(RoomContext); const canUnpin = useRoomState(room, state => state.mayClientSendStateEvent(EventType.RoomPinnedEvents, cli)); const pinnedEventIds = usePinnedEvents(room); const readPinnedEvents = useReadPinnedEvents(room); @@ -166,7 +168,12 @@ const PinnedMessagesCard = ({ room, onClose }: IProps) => { className="mx_PinnedMessagesCard" onClose={onClose} > - { content } + + { content } + ; }; diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 2b95c946bd..13218d1297 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -64,7 +64,7 @@ import SettingsStore from "../../../settings/SettingsStore"; import MKeyVerificationConclusion from "../messages/MKeyVerificationConclusion"; import { showThread } from '../../../dispatcher/dispatch-actions/threads'; import { MessagePreviewStore } from '../../../stores/room-list/MessagePreviewStore'; -import { TimelineRenderingType } from "../../../contexts/RoomContext"; +import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; import { MediaEventHelper } from "../../../utils/MediaEventHelper"; import Toolbar from '../../../accessibility/Toolbar'; import { RovingAccessibleTooltipButton } from '../../../accessibility/roving/RovingAccessibleTooltipButton'; @@ -213,14 +213,6 @@ export interface IReadReceiptProps { ts: number; } -export enum TileShape { - Notif = "notif", - FileGrid = "file_grid", - Pinned = "pinned", - Thread = "thread", - ThreadPanel = "thread_list" -} - interface IProps { // the MatrixEvent to show mxEvent: MatrixEvent; @@ -281,14 +273,6 @@ interface IProps { // that we can tell when it changes. eventSendStatus?: string; - // the shape of the tile. by default, the layout is intended for the - // normal room timeline. alternative values are: "file_list", "file_grid" - // and "notif". This could be done by CSS, but it'd be horribly inefficient. - // It could also be done by subclassing EventTile, but that'd be quite - // boiilerplatey. So just make the necessary render decisions conditional - // for now. - tileShape?: TileShape; - forExport?: boolean; // show twelve hour timestamps @@ -367,6 +351,7 @@ interface IState { threadNotification?: NotificationCountType; } +// MUST be rendered within a RoomContext with a set timelineRenderingType @replaceableComponent("views.rooms.EventTile") export default class EventTile extends React.Component { private suppressReadReceiptAnimation: boolean; @@ -385,13 +370,12 @@ export default class EventTile extends React.Component { layout: Layout.Group, }; - static contextType = MatrixClientContext; - public context!: React.ContextType; + static contextType = RoomContext; + public context!: React.ContextType; constructor(props: IProps, context: React.ContextType) { super(props, context); - this.context = context; const thread = this.thread; this.state = { @@ -437,12 +421,12 @@ export default class EventTile extends React.Component { if (!this.props.mxEvent) return false; // Sanity check (should never happen, but we shouldn't explode if it does) - const room = this.context.getRoom(this.props.mxEvent.getRoomId()); + const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); if (!room) return false; // Quickly check to see if the event was sent by us. If it wasn't, it won't qualify for // special read receipts. - const myUserId = this.context.getUserId(); + const myUserId = MatrixClientPeg.get().getUserId(); if (this.props.mxEvent.getSender() !== myUserId) return false; // Finally, determine if the type is relevant to the user. This notably excludes state @@ -473,7 +457,7 @@ export default class EventTile extends React.Component { // If anyone has read the event besides us, we don't want to show a sent receipt. const receipts = this.props.readReceipts || []; - const myUserId = this.context.getUserId(); + const myUserId = MatrixClientPeg.get().getUserId(); if (receipts.some(r => r.userId !== myUserId)) return false; // Finally, we should show a receipt. @@ -501,7 +485,7 @@ export default class EventTile extends React.Component { componentDidMount() { this.suppressReadReceiptAnimation = false; - const client = this.context; + const client = MatrixClientPeg.get(); if (!this.props.forExport) { client.on("deviceVerificationChanged", this.onDeviceVerificationChanged); client.on("userTrustStatusChanged", this.onUserVerificationChanged); @@ -526,12 +510,12 @@ export default class EventTile extends React.Component { } } - const room = this.context.getRoom(this.props.mxEvent.getRoomId()); + const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); room?.on(ThreadEvent.New, this.onNewThread); } private setupNotificationListener = (thread: Thread): void => { - const room = this.context.getRoom(this.props.mxEvent.getRoomId()); + const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); const notifications = RoomNotificationStateStore.instance.getThreadsRoomState(room); this.threadState = notifications.getThreadRoomState(thread); @@ -592,7 +576,7 @@ export default class EventTile extends React.Component { } componentWillUnmount() { - const client = this.context; + const client = MatrixClientPeg.get(); client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged); client.removeListener("userTrustStatusChanged", this.onUserVerificationChanged); client.removeListener("Room.receipt", this.onRoomReceipt); @@ -606,7 +590,7 @@ export default class EventTile extends React.Component { this.props.mxEvent.off(ThreadEvent.Update, this.updateThread); } - const room = this.context.getRoom(this.props.mxEvent.getRoomId()); + const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); room?.off(ThreadEvent.New, this.onNewThread); if (this.threadState) { this.threadState.off(NotificationStateEvents.Update, this.onThreadStateUpdate); @@ -616,7 +600,7 @@ export default class EventTile extends React.Component { componentDidUpdate(prevProps: IProps, prevState: IState, snapshot) { // If we're not listening for receipts and expect to be, register a listener. if (!this.isListeningForReceipts && (this.shouldShowSentReceipt || this.shouldShowSendingReceipt)) { - this.context.on("Room.receipt", this.onRoomReceipt); + MatrixClientPeg.get().on("Room.receipt", this.onRoomReceipt); this.isListeningForReceipts = true; } } @@ -624,7 +608,7 @@ export default class EventTile extends React.Component { private onNewThread = (thread: Thread) => { if (thread.id === this.props.mxEvent.getId()) { this.updateThread(thread); - const room = this.context.getRoom(this.props.mxEvent.getRoomId()); + const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); room.off(ThreadEvent.New, this.onNewThread); } }; @@ -734,7 +718,7 @@ export default class EventTile extends React.Component { private onRoomReceipt = (ev: MatrixEvent, room: Room): void => { // ignore events for other rooms - const tileRoom = this.context.getRoom(this.props.mxEvent.getRoomId()); + const tileRoom = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); if (room !== tileRoom) return; if (!this.shouldShowSentReceipt && !this.shouldShowSendingReceipt && !this.isListeningForReceipts) { @@ -746,7 +730,7 @@ export default class EventTile extends React.Component { this.forceUpdate(() => { // Per elsewhere in this file, we can remove the listener once we will have no further purpose for it. if (!this.shouldShowSentReceipt && !this.shouldShowSendingReceipt) { - this.context.removeListener("Room.receipt", this.onRoomReceipt); + MatrixClientPeg.get().removeListener("Room.receipt", this.onRoomReceipt); this.isListeningForReceipts = false; } }); @@ -779,9 +763,9 @@ export default class EventTile extends React.Component { return; } - const encryptionInfo = this.context.getEventEncryptionInfo(mxEvent); + const encryptionInfo = MatrixClientPeg.get().getEventEncryptionInfo(mxEvent); const senderId = mxEvent.getSender(); - const userTrust = this.context.checkUserTrust(senderId); + const userTrust = MatrixClientPeg.get().checkUserTrust(senderId); if (encryptionInfo.mismatchedSender) { // something definitely wrong is going on here @@ -799,7 +783,7 @@ export default class EventTile extends React.Component { return; } - const eventSenderTrust = encryptionInfo.sender && this.context.checkDeviceTrust( + const eventSenderTrust = encryptionInfo.sender && MatrixClientPeg.get().checkDeviceTrust( senderId, encryptionInfo.sender.deviceId, ); if (!eventSenderTrust) { @@ -878,14 +862,16 @@ export default class EventTile extends React.Component { private shouldHighlight(): boolean { if (this.props.forExport) return false; - if (this.props.tileShape === TileShape.Notif) return false; - if (this.props.tileShape === TileShape.ThreadPanel) return false; + if (this.context.timelineRenderingType === TimelineRenderingType.Notification) return false; + if (this.context.timelineRenderingType === TimelineRenderingType.ThreadsList) return false; - const actions = this.context.getPushActionsForEvent(this.props.mxEvent.replacingEvent() || this.props.mxEvent); + const actions = MatrixClientPeg.get().getPushActionsForEvent( + this.props.mxEvent.replacingEvent() || this.props.mxEvent, + ); if (!actions || !actions.tweaks) { return false; } // don't show self-highlights from another of our clients - if (this.props.mxEvent.getSender() === this.context.credentials.userId) { + if (this.props.mxEvent.getSender() === MatrixClientPeg.get().credentials.userId) { return false; } @@ -1013,7 +999,7 @@ export default class EventTile extends React.Component { // Cancel any outgoing key request for this event and resend it. If a response // is received for the request with the required keys, the event could be // decrypted successfully. - this.context.cancelAndResendEventRoomKeyRequest(this.props.mxEvent); + MatrixClientPeg.get().cancelAndResendEventRoomKeyRequest(this.props.mxEvent); }; private onPermalinkClicked = e => { @@ -1054,7 +1040,7 @@ export default class EventTile extends React.Component { } } - if (this.context.isRoomEncrypted(ev.getRoomId())) { + if (MatrixClientPeg.get().isRoomEncrypted(ev.getRoomId())) { // else if room is encrypted // and event is being encrypted or is not_sent (Unknown Devices/Network Error) if (ev.status === EventStatus.ENCRYPTING) { @@ -1169,7 +1155,10 @@ export default class EventTile extends React.Component { const isEncryptionFailure = this.props.mxEvent.isDecryptionFailure(); let isContinuation = this.props.continuation; - if (this.props.tileShape && this.props.layout !== Layout.Bubble) { + if (this.context.timelineRenderingType !== TimelineRenderingType.Room && + this.context.timelineRenderingType !== TimelineRenderingType.Search && + this.props.layout !== Layout.Bubble + ) { isContinuation = false; } @@ -1196,7 +1185,7 @@ export default class EventTile extends React.Component { mx_EventTile_bad: isEncryptionFailure, mx_EventTile_emote: msgtype === MsgType.Emote, mx_EventTile_noSender: this.props.hideSender, - mx_EventTile_clamp: this.props.tileShape === TileShape.ThreadPanel, + mx_EventTile_clamp: this.context.timelineRenderingType === TimelineRenderingType.ThreadsList, mx_EventTile_noBubble: noBubbleEvent, }); @@ -1219,7 +1208,9 @@ export default class EventTile extends React.Component { let avatarSize; let needsSenderProfile; - if (this.props.tileShape === TileShape.Notif || this.props.tileShape === TileShape.ThreadPanel) { + if (this.context.timelineRenderingType === TimelineRenderingType.Notification || + this.context.timelineRenderingType === TimelineRenderingType.ThreadsList + ) { avatarSize = 24; needsSenderProfile = true; } else if (tileHandler === 'messages.RoomCreate' || isBubbleMessage) { @@ -1234,7 +1225,7 @@ export default class EventTile extends React.Component { avatarSize = 14; needsSenderProfile = true; } else if ( - (this.props.continuation && this.props.tileShape !== TileShape.FileGrid) || + (this.props.continuation && this.context.timelineRenderingType !== TimelineRenderingType.File) || eventType === EventType.CallInvite ) { // no avatar or sender profile for continuation messages and call tiles @@ -1269,17 +1260,20 @@ export default class EventTile extends React.Component { } if (needsSenderProfile && this.props.hideSender !== true) { - if (!this.props.tileShape || this.props.tileShape === TileShape.Thread) { - sender = ; } else { sender = ; } } @@ -1303,16 +1297,16 @@ export default class EventTile extends React.Component { || this.state.hover || this.state.actionBarFocused); - const room = this.context.getRoom(this.props.mxEvent.getRoomId()); + const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); const thread = room?.findThreadForEvent?.(this.props.mxEvent); // Thread panel shows the timestamp of the last reply in that thread - const ts = this.props.tileShape !== TileShape.ThreadPanel + const ts = this.context.timelineRenderingType !== TimelineRenderingType.ThreadsList ? this.props.mxEvent.getTs() : thread?.replyToEvent.getTs(); const messageTimestamp = ; @@ -1392,7 +1386,7 @@ export default class EventTile extends React.Component { msgOption = readAvatars; } - const renderTarget = this.props.tileShape === TileShape.Thread + const renderTarget = this.context.timelineRenderingType === TimelineRenderingType.Thread ? RelationType.Thread : undefined; @@ -1412,11 +1406,11 @@ export default class EventTile extends React.Component { /> : null; - const isOwnEvent = this.props.mxEvent?.sender?.userId === this.context.getUserId(); + const isOwnEvent = this.props.mxEvent?.sender?.userId === MatrixClientPeg.get().getUserId(); - switch (this.props.tileShape) { - case TileShape.Notif: { - const room = this.context.getRoom(this.props.mxEvent.getRoomId()); + switch (this.context.timelineRenderingType) { + case TimelineRenderingType.Notification: { + const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); return React.createElement(this.props.as || "li", { "className": classes, "aria-live": ariaLive, @@ -1443,7 +1437,6 @@ export default class EventTile extends React.Component { highlightLink={this.props.highlightLink} showUrlPreview={this.props.showUrlPreview} onHeightChanged={this.props.onHeightChanged} - tileShape={this.props.tileShape} editState={this.props.editState} getRelationsForEvent={this.props.getRelationsForEvent} isSeeingThroughMessageHiddenForModeration={isSeeingThroughMessageHiddenForModeration} @@ -1451,8 +1444,8 @@ export default class EventTile extends React.Component { , ]); } - case TileShape.Thread: { - const room = this.context.getRoom(this.props.mxEvent.getRoomId()); + case TimelineRenderingType.Thread: { + const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); return React.createElement(this.props.as || "li", { "ref": this.ref, "className": classes, @@ -1485,7 +1478,6 @@ export default class EventTile extends React.Component { highlightLink={this.props.highlightLink} showUrlPreview={this.props.showUrlPreview} onHeightChanged={this.props.onHeightChanged} - tileShape={this.props.tileShape} editState={this.props.editState} replacingEventId={this.props.replacingEventId} getRelationsForEvent={this.props.getRelationsForEvent} @@ -1497,7 +1489,7 @@ export default class EventTile extends React.Component { reactionsRow, ]); } - case TileShape.ThreadPanel: { + case TimelineRenderingType.ThreadsList: { // tab-index=-1 to allow it to be focusable but do not add tab stop for it, primarily for screen readers return ( React.createElement(this.props.as || "li", { @@ -1508,7 +1500,7 @@ export default class EventTile extends React.Component { "aria-atomic": "true", "data-scroll-tokens": scrollToken, "data-layout": this.props.layout, - "data-shape": this.props.tileShape, + "data-shape": this.context.timelineRenderingType, "data-self": isOwnEvent, "data-has-reply": !!replyChain, "data-notification": this.state.threadNotification, @@ -1550,7 +1542,7 @@ export default class EventTile extends React.Component { ) ); } - case TileShape.FileGrid: { + case TimelineRenderingType.File: { return React.createElement(this.props.as || "li", { "className": classes, "aria-live": ariaLive, @@ -1563,7 +1555,6 @@ export default class EventTile extends React.Component { highlights={this.props.highlights} highlightLink={this.props.highlightLink} showUrlPreview={this.props.showUrlPreview} - tileShape={this.props.tileShape} onHeightChanged={this.props.onHeightChanged} editState={this.props.editState} getRelationsForEvent={this.props.getRelationsForEvent} @@ -1584,7 +1575,7 @@ export default class EventTile extends React.Component { ]); } - default: { + default: { // Pinned, Room, Search // tab-index=-1 to allow it to be focusable but do not add tab stop for it, primarily for screen readers return ( React.createElement(this.props.as || "li", { diff --git a/src/components/views/rooms/PinnedEventTile.tsx b/src/components/views/rooms/PinnedEventTile.tsx index 8f45039cf2..91ad97236a 100644 --- a/src/components/views/rooms/PinnedEventTile.tsx +++ b/src/components/views/rooms/PinnedEventTile.tsx @@ -30,7 +30,6 @@ import { replaceableComponent } from "../../../utils/replaceableComponent"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { getUserNameColorClass } from "../../../utils/FormattingUtils"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; -import { TileShape } from "./EventTile"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; interface IProps { @@ -92,7 +91,6 @@ export default class PinnedEventTile extends React.Component { className="mx_PinnedEventTile_body" maxImageHeight={150} onHeightChanged={() => {}} // we need to give this, apparently - tileShape={TileShape.Pinned} /> diff --git a/src/contexts/RoomContext.ts b/src/contexts/RoomContext.ts index 7e838d1ac9..e20ae0848b 100644 --- a/src/contexts/RoomContext.ts +++ b/src/contexts/RoomContext.ts @@ -25,7 +25,8 @@ export enum TimelineRenderingType { ThreadsList = "ThreadsList", File = "File", Notification = "Notification", - Search = "Search" + Search = "Search", + Pinned = "Pinned", } const RoomContext = createContext({