Optimize message comp
Signed-off-by: Ajay Bura <ajbura@gmail.com>
This commit is contained in:
parent
a70245a3b1
commit
c291729ed6
1 changed files with 131 additions and 109 deletions
|
@ -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__avatar-container">
|
||||||
|
<button type="button" onClick={() => openProfileViewer(userId, roomId)}>
|
||||||
|
<Avatar imageSrc={avatarSrc} text={username} bgColor={colorMXID(userId)} size="small" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const MessageHeader = React.memo(({
|
||||||
|
userId, username, time,
|
||||||
|
}) => (
|
||||||
<div className="message__header">
|
<div className="message__header">
|
||||||
<div style={{ color }} className="message__profile">
|
<div style={{ color: colorMXID(userId) }} className="message__profile">
|
||||||
<Text variant="b1">{twemojify(name)}</Text>
|
<Text variant="b1">{twemojify(username)}</Text>
|
||||||
<Text variant="b1">{twemojify(userId)}</Text>
|
<Text variant="b1">{twemojify(userId)}</Text>
|
||||||
</div>
|
</div>
|
||||||
<div className="message__time">
|
<div className="message__time">
|
||||||
<Text variant="b3">{time}</Text>
|
<Text variant="b3">{time}</Text>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
|
|
Loading…
Reference in a new issue