Design thread list tiles according to mockups (#7078)

This commit is contained in:
Germain 2021-11-03 18:05:01 +00:00 committed by GitHub
parent 2a20d9a7df
commit 38750202ee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 332 additions and 181 deletions

View file

@ -64,6 +64,9 @@ import { MessagePreviewStore } from '../../../stores/room-list/MessagePreviewSto
import { logger } from "matrix-js-sdk/src/logger";
import { TimelineRenderingType } from "../../../contexts/RoomContext";
import { MediaEventHelper } from "../../../utils/MediaEventHelper";
import Toolbar from '../../../accessibility/Toolbar';
import { RovingAccessibleTooltipButton } from '../../../accessibility/roving/RovingAccessibleTooltipButton';
import { ThreadListContextMenu } from '../context_menus/ThreadListContextMenu';
const eventTileTypes = {
[EventType.RoomMessage]: 'messages.MessageEvent',
@ -547,6 +550,43 @@ export default class EventTile extends React.Component<IProps, IState> {
}
};
private renderThreadLastMessagePreview(): JSX.Element | null {
if (!SettingsStore.getValue("feature_thread")) {
return null;
}
/**
* Accessing the threads value through the room due to a race condition
* that will be solved when there are proper backend support for threads
* We currently have no reliable way to discover than an event is a thread
* when we are at the sync stage
*/
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
const thread = room?.threads.get(this.props.mxEvent.getId());
if (!thread || thread.length === 0) {
return null;
}
const [lastEvent] = thread.events
.filter(event => event.isThreadRelation)
.slice(-1);
const threadMessagePreview = MessagePreviewStore.instance.generatePreviewForEvent(lastEvent);
if (!threadMessagePreview || !lastEvent.sender) {
return null;
}
return <>
<MemberAvatar member={lastEvent.sender} style={{ float: "left", width: "24px", height: "24px" }} />
<div className="mx_ThreadInfo_content">
<span className="mx_ThreadInfo_message-preview">
{ threadMessagePreview }
</span>
</div>
</>;
}
private renderThreadInfo(): React.ReactNode {
if (!SettingsStore.getValue("feature_thread")) {
return null;
@ -569,11 +609,6 @@ export default class EventTile extends React.Component<IProps, IState> {
return null;
}
const [lastEvent] = thread.events
.filter(event => event.isThreadRelation)
.slice(-1);
const threadMessagePreview = MessagePreviewStore.instance.generatePreviewForEvent(lastEvent);
return (
<div
className="mx_ThreadInfo"
@ -589,14 +624,7 @@ export default class EventTile extends React.Component<IProps, IState> {
count: thread.length,
}) }
</span>
{ (threadMessagePreview && lastEvent.sender) && <>
<MemberAvatar member={lastEvent.sender} width={24} height={24} />
<div className="mx_ThreadInfo_content">
<span className="mx_ThreadInfo_message-preview">
{ threadMessagePreview }
</span>
</div>
</> }
{ this.renderThreadLastMessagePreview() }
</div>
);
}
@ -1199,6 +1227,20 @@ export default class EventTile extends React.Component<IProps, IState> {
msgOption = readAvatars;
}
const replyChain = haveTileForEvent(this.props.mxEvent) &&
ReplyChain.hasReply(this.props.mxEvent) ? (
<ReplyChain
parentEv={this.props.mxEvent}
onHeightChanged={this.props.onHeightChanged}
ref={this.replyChain}
forExport={this.props.forExport}
permalinkCreator={this.props.permalinkCreator}
layout={this.props.layout}
alwaysShowTimestamps={this.props.alwaysShowTimestamps || this.state.hover}
isQuoteExpanded={isQuoteExpanded}
setQuoteExpanded={this.setQuoteExpanded}
/>) : null;
switch (this.props.tileShape) {
case TileShape.Notif: {
const room = this.context.getRoom(this.props.mxEvent.getRoomId());
@ -1235,19 +1277,6 @@ export default class EventTile extends React.Component<IProps, IState> {
]);
}
case TileShape.Thread: {
const replyChain = haveTileForEvent(this.props.mxEvent) &&
ReplyChain.hasReply(this.props.mxEvent) ? (
<ReplyChain
parentEv={this.props.mxEvent}
onHeightChanged={this.props.onHeightChanged}
ref={this.replyChain}
forExport={this.props.forExport}
permalinkCreator={this.props.permalinkCreator}
layout={this.props.layout}
alwaysShowTimestamps={this.props.alwaysShowTimestamps || this.state.hover}
isQuoteExpanded={isQuoteExpanded}
setQuoteExpanded={this.setQuoteExpanded}
/>) : null;
const room = this.context.getRoom(this.props.mxEvent.getRoomId());
return React.createElement(this.props.as || "li", {
"className": classes,
@ -1288,6 +1317,63 @@ export default class EventTile extends React.Component<IProps, IState> {
reactionsRow,
]);
}
case TileShape.ThreadPanel: {
const isOwnEvent = this.props.mxEvent?.sender?.userId === MatrixClientPeg.get().getUserId();
// 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", {
"ref": this.ref,
"className": classes,
"tabIndex": -1,
"aria-live": ariaLive,
"aria-atomic": "true",
"data-scroll-tokens": scrollToken,
"data-layout": this.props.layout,
"data-shape": this.props.tileShape,
"data-self": isOwnEvent,
"data-has-reply": !!replyChain,
"onMouseEnter": () => this.setState({ hover: true }),
"onMouseLeave": () => this.setState({ hover: false }),
"onClick": () => dispatchShowThreadEvent(this.props.mxEvent),
}, <>
{ sender }
{ avatar }
<div className={lineClasses} key="mx_EventTile_line">
{ linkedTimestamp }
{ this.renderE2EPadlock() }
{ replyChain }
<EventTileType ref={this.tile}
mxEvent={this.props.mxEvent}
forExport={this.props.forExport}
replacingEventId={this.props.replacingEventId}
editState={this.props.editState}
highlights={this.props.highlights}
highlightLink={this.props.highlightLink}
showUrlPreview={this.props.showUrlPreview}
permalinkCreator={this.props.permalinkCreator}
onHeightChanged={this.props.onHeightChanged}
callEventGrouper={this.props.callEventGrouper}
tileShape={this.props.tileShape}
/>
{ keyRequestInfo }
<Toolbar className="mx_MessageActionBar" aria-label={_t("Message Actions")} aria-live="off">
<RovingAccessibleTooltipButton
className="mx_MessageActionBar_maskButton mx_MessageActionBar_threadButton"
title={_t("Thread")}
onClick={() => dispatchShowThreadEvent(this.props.mxEvent)}
key="thread"
/>
<ThreadListContextMenu
mxEvent={this.props.mxEvent}
permalinkCreator={this.props.permalinkCreator} />
</Toolbar>
{ this.renderThreadLastMessagePreview() }
</div>
{ msgOption }
</>)
);
}
case TileShape.FileGrid: {
return React.createElement(this.props.as || "li", {
"className": classes,
@ -1321,19 +1407,6 @@ export default class EventTile extends React.Component<IProps, IState> {
}
default: {
const replyChain = haveTileForEvent(this.props.mxEvent) &&
ReplyChain.hasReply(this.props.mxEvent) ? (
<ReplyChain
parentEv={this.props.mxEvent}
onHeightChanged={this.props.onHeightChanged}
ref={this.replyChain}
forExport={this.props.forExport}
permalinkCreator={this.props.permalinkCreator}
layout={this.props.layout}
alwaysShowTimestamps={this.props.alwaysShowTimestamps || this.state.hover}
isQuoteExpanded={isQuoteExpanded}
setQuoteExpanded={this.setQuoteExpanded}
/>) : null;
const isOwnEvent = this.props.mxEvent?.sender?.userId === MatrixClientPeg.get().getUserId();
// tab-index=-1 to allow it to be focusable but do not add tab stop for it, primarily for screen readers