Optimize message comp

Signed-off-by: Ajay Bura <ajbura@gmail.com>
This commit is contained in:
Ajay Bura 2021-12-09 11:59:17 +05:30
parent a70245a3b1
commit c291729ed6

View file

@ -1,5 +1,5 @@
/* eslint-disable react/prop-types */ /* eslint-disable react/prop-types */
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useCallback, useRef } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './Message.scss'; import './Message.scss';
@ -52,25 +52,35 @@ function PlaceholderMessage() {
); );
} }
function MessageHeader({ const MessageAvatar = React.memo(({
userId, name, color, time, roomId, mEvent, userId, username,
}) { }) => {
const avatarSrc = mEvent.sender.getAvatarUrl(initMatrix.matrixClient.baseUrl, 36, 36, 'crop');
return ( return (
<div className="message__header"> <div className="message__avatar-container">
<div style={{ color }} className="message__profile"> <button type="button" onClick={() => openProfileViewer(userId, roomId)}>
<Text variant="b1">{twemojify(name)}</Text> <Avatar imageSrc={avatarSrc} text={username} bgColor={colorMXID(userId)} size="small" />
<Text variant="b1">{twemojify(userId)}</Text> </button>
</div>
<div className="message__time">
<Text variant="b3">{time}</Text>
</div>
</div> </div>
); );
} });
const MessageHeader = React.memo(({
userId, username, time,
}) => (
<div className="message__header">
<div style={{ color: colorMXID(userId) }} className="message__profile">
<Text variant="b1">{twemojify(username)}</Text>
<Text variant="b1">{twemojify(userId)}</Text>
</div>
<div className="message__time">
<Text variant="b3">{time}</Text>
</div>
</div>
));
MessageHeader.propTypes = { MessageHeader.propTypes = {
userId: PropTypes.string.isRequired, userId: PropTypes.string.isRequired,
name: PropTypes.string.isRequired, username: PropTypes.string.isRequired,
color: PropTypes.string.isRequired,
time: PropTypes.string.isRequired, time: PropTypes.string.isRequired,
}; };
@ -93,7 +103,7 @@ MessageReply.propTypes = {
body: PropTypes.string.isRequired, body: PropTypes.string.isRequired,
}; };
function MessageReplyWrapper({ roomTimeline, eventId }) { const MessageReplyWrapper = React.memo(({ roomTimeline, eventId }) => {
const [reply, setReply] = useState(null); const [reply, setReply] = useState(null);
const isMountedRef = useRef(true); const isMountedRef = useRef(true);
@ -141,19 +151,19 @@ function MessageReplyWrapper({ roomTimeline, eventId }) {
{reply !== null && <MessageReply name={reply.to} color={reply.color} body={reply.body} />} {reply !== null && <MessageReply name={reply.to} color={reply.color} body={reply.body} />}
</div> </div>
); );
} });
MessageReplyWrapper.propTypes = { MessageReplyWrapper.propTypes = {
roomTimeline: PropTypes.shape({}).isRequired, roomTimeline: PropTypes.shape({}).isRequired,
eventId: PropTypes.string.isRequired, eventId: PropTypes.string.isRequired,
}; };
function MessageBody({ const MessageBody = React.memo(({
senderName, senderName,
body, body,
isCustomHTML, isCustomHTML,
isEdited, isEdited,
msgType, msgType,
}) { }) => {
// if body is not string it is a React element. // if body is not string it is a React element.
if (typeof body !== 'string') return <div className="message__body">{body}</div>; if (typeof body !== 'string') return <div className="message__body">{body}</div>;
@ -176,7 +186,7 @@ function MessageBody({
{ isEdited && <Text className="message__body-edited" variant="b3">(edited)</Text>} { isEdited && <Text className="message__body-edited" variant="b3">(edited)</Text>}
</div> </div>
); );
} });
MessageBody.defaultProps = { MessageBody.defaultProps = {
isCustomHTML: false, isCustomHTML: false,
isEdited: false, isEdited: false,
@ -379,17 +389,6 @@ MessageReactionGroup.propTypes = {
mEvent: PropTypes.shape({}).isRequired, mEvent: PropTypes.shape({}).isRequired,
}; };
function MessageOptions({ children }) {
return (
<div className="message__options">
{children}
</div>
);
}
MessageOptions.propTypes = {
children: PropTypes.node.isRequired,
};
function isMedia(mE) { function isMedia(mE) {
return ( return (
mE.getContent()?.msgtype === 'm.file' mE.getContent()?.msgtype === 'm.file'
@ -400,6 +399,86 @@ function isMedia(mE) {
); );
} }
const MessageOptions = React.memo(({
roomTimeline, mEvent, edit, reply,
}) => {
const { roomId, room } = roomTimeline;
const mx = initMatrix.matrixClient;
const eventId = mEvent.getId();
const senderId = mEvent.getSender();
const myPowerlevel = room.getMember(mx.getUserId())?.powerLevel;
const canIRedact = room.currentState.hasSufficientPowerLevelFor('redact', myPowerlevel);
return (
<div className="message__options">
<IconButton
onClick={(e) => pickEmoji(e, roomId, eventId, roomTimeline)}
src={EmojiAddIC}
size="extra-small"
tooltip="Add reaction"
/>
<IconButton
onClick={() => reply()}
src={ReplyArrowIC}
size="extra-small"
tooltip="Reply"
/>
{(senderId === mx.getUserId() && !isMedia(mEvent)) && (
<IconButton
onClick={() => edit(true)}
src={PencilIC}
size="extra-small"
tooltip="Edit"
/>
)}
<ContextMenu
content={() => (
<>
<MenuHeader>Options</MenuHeader>
<MenuItem
iconSrc={TickMarkIC}
onClick={() => openReadReceipts(roomId, roomTimeline.getEventReaders(eventId))}
>
Read receipts
</MenuItem>
{(canIRedact || senderId === mx.getUserId()) && (
<>
<MenuBorder />
<MenuItem
variant="danger"
iconSrc={BinIC}
onClick={() => {
if (window.confirm('Are you sure you want to delete this event')) {
redactEvent(roomId, eventId);
}
}}
>
Delete
</MenuItem>
</>
)}
</>
)}
render={(toggleMenu) => (
<IconButton
onClick={toggleMenu}
src={VerticalMenuIC}
size="extra-small"
tooltip="Options"
/>
)}
/>
</div>
);
});
MessageOptions.propTypes = {
roomTimeline: PropTypes.shape({}).isRequired,
mEvent: PropTypes.shape({}).isRequired,
edit: PropTypes.func.isRequired,
reply: PropTypes.func.isRequired,
};
function genMediaContent(mE) { function genMediaContent(mE) {
const mx = initMatrix.matrixClient; const mx = initMatrix.matrixClient;
const mContent = mE.getContent(); const mContent = mE.getContent();
@ -481,14 +560,10 @@ function getEditedBody(editedMEvent) {
} }
function Message({ function Message({
mEvent, isBodyOnly, roomTimeline, focus, time mEvent, isBodyOnly, roomTimeline, focus, time,
}) { }) {
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
const { roomId, editedTimeline, reactionTimeline } = roomTimeline;
const mx = initMatrix.matrixClient;
const {
room, roomId, editedTimeline, reactionTimeline,
} = roomTimeline;
const className = ['message', (isBodyOnly ? 'message--body-only' : 'message--full')]; const className = ['message', (isBodyOnly ? 'message--body-only' : 'message--full')];
if (focus) className.push('message--focus'); if (focus) className.push('message--focus');
@ -496,17 +571,12 @@ function Message({
const eventId = mEvent.getId(); const eventId = mEvent.getId();
const msgType = content?.msgtype; const msgType = content?.msgtype;
const senderId = mEvent.getSender(); const senderId = mEvent.getSender();
const mxidColor = colorMXID(senderId);
let { body } = content; let { body } = content;
const avatarSrc = mEvent.sender.getAvatarUrl(initMatrix.matrixClient.baseUrl, 36, 36, 'crop');
const username = getUsernameOfRoomMember(mEvent.sender); const username = getUsernameOfRoomMember(mEvent.sender);
if (typeof body === 'undefined') return null; if (typeof body === 'undefined') return null;
if (msgType === 'm.emote') className.push('message--type-emote'); if (msgType === 'm.emote') className.push('message--type-emote');
const myPowerlevel = room.getMember(mx.getUserId())?.powerLevel;
const canIRedact = room.currentState.hasSufficientPowerLevelFor('redact', myPowerlevel);
let isCustomHTML = content.format === 'org.matrix.custom.html'; let isCustomHTML = content.format === 'org.matrix.custom.html';
const isEdited = editedTimeline.has(eventId); const isEdited = editedTimeline.has(eventId);
const haveReactions = reactionTimeline.has(eventId) || !!mEvent.getServerAggregatedRelation('m.annotation'); const haveReactions = reactionTimeline.has(eventId) || !!mEvent.getServerAggregatedRelation('m.annotation');
@ -524,18 +594,23 @@ function Message({
body = parseReply(body)?.body ?? body; body = parseReply(body)?.body ?? body;
} }
const edit = useCallback(() => {
setIsEditing(true);
}, []);
const reply = useCallback(() => {
replyTo(senderId, eventId, body);
}, [body]);
return ( return (
<div className={className.join(' ')}> <div className={className.join(' ')}>
<div className="message__avatar-container"> {
{!isBodyOnly && ( isBodyOnly
<button type="button" onClick={() => openProfileViewer(senderId, roomId)}> ? <div className="message__avatar-container" />
<Avatar imageSrc={avatarSrc} text={username} bgColor={mxidColor} size="small" /> : <MessageAvatar roomId={roomId} mEvent={mEvent} userId={senderId} username={username} />
</button> }
)}
</div>
<div className="message__main-container"> <div className="message__main-container">
{!isBodyOnly && ( {!isBodyOnly && (
<MessageHeader userId={senderId} name={username} color={mxidColor} time={time} /> <MessageHeader userId={senderId} username={username} time={time} />
)} )}
{isReply && ( {isReply && (
<MessageReplyWrapper <MessageReplyWrapper
@ -568,65 +643,12 @@ function Message({
<MessageReactionGroup roomTimeline={roomTimeline} mEvent={mEvent} /> <MessageReactionGroup roomTimeline={roomTimeline} mEvent={mEvent} />
)} )}
{!isEditing && ( {!isEditing && (
<MessageOptions> <MessageOptions
<IconButton roomTimeline={roomTimeline}
onClick={(e) => pickEmoji(e, roomId, eventId, roomTimeline)} mEvent={mEvent}
src={EmojiAddIC} edit={edit}
size="extra-small" reply={reply}
tooltip="Add reaction" />
/>
<IconButton
onClick={() => replyTo(senderId, eventId, body)}
src={ReplyArrowIC}
size="extra-small"
tooltip="Reply"
/>
{(senderId === mx.getUserId() && !isMedia(mEvent)) && (
<IconButton
onClick={() => setIsEditing(true)}
src={PencilIC}
size="extra-small"
tooltip="Edit"
/>
)}
<ContextMenu
content={() => (
<>
<MenuHeader>Options</MenuHeader>
<MenuItem
iconSrc={TickMarkIC}
onClick={() => openReadReceipts(roomId, roomTimeline.getEventReaders(eventId))}
>
Read receipts
</MenuItem>
{(canIRedact || senderId === mx.getUserId()) && (
<>
<MenuBorder />
<MenuItem
variant="danger"
iconSrc={BinIC}
onClick={() => {
if (window.confirm('Are you sure you want to delete this event')) {
redactEvent(roomId, eventId);
}
}}
>
Delete
</MenuItem>
</>
)}
</>
)}
render={(toggleMenu) => (
<IconButton
onClick={toggleMenu}
src={VerticalMenuIC}
size="extra-small"
tooltip="Options"
/>
)}
/>
</MessageOptions>
)} )}
</div> </div>
</div> </div>