virtualized chat tweaks for rendering and performance
This commit is contained in:
parent
e3f33ab613
commit
66cfdc96da
@ -9,6 +9,7 @@ import ChatService from './service';
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const PUBLIC_CHAT_KEY = CHAT_CONFIG.public_id;
|
||||
const CHAT_CLEAR = CHAT_CONFIG.system_messages_keys.chat_clear;
|
||||
const SYSTEM_CHAT_TYPE = CHAT_CONFIG.type_system;
|
||||
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
|
||||
const CONNECTION_STATUS = 'online';
|
||||
|
||||
@ -78,30 +79,33 @@ export default injectIntl(withTracker(({ intl }) => {
|
||||
sender: null,
|
||||
};
|
||||
|
||||
const moderatorTime = time + 1;
|
||||
const moderatorId = `moderator-msg-${moderatorTime}`;
|
||||
let moderatorMsg;
|
||||
if (amIModerator && welcomeProp.modOnlyMessage) {
|
||||
const moderatorTime = time + 1;
|
||||
const moderatorId = `moderator-msg-${moderatorTime}`;
|
||||
|
||||
const moderatorMsg = {
|
||||
id: moderatorId,
|
||||
content: [{
|
||||
moderatorMsg = {
|
||||
id: moderatorId,
|
||||
text: welcomeProp.modOnlyMessage,
|
||||
content: [{
|
||||
id: moderatorId,
|
||||
text: welcomeProp.modOnlyMessage,
|
||||
time: moderatorTime,
|
||||
}],
|
||||
time: moderatorTime,
|
||||
}],
|
||||
time: moderatorTime,
|
||||
sender: null,
|
||||
};
|
||||
sender: null,
|
||||
};
|
||||
}
|
||||
|
||||
const messagesBeforeWelcomeMsg = ChatService.reduceAndMapGroupMessages(
|
||||
const messagesBeforeWelcomeMsg = ChatService.reduceAndDontMapGroupMessages(
|
||||
messages.filter(message => message.timestamp < time),
|
||||
);
|
||||
const messagesAfterWelcomeMsg = ChatService.reduceAndMapGroupMessages(
|
||||
const messagesAfterWelcomeMsg = ChatService.reduceAndDontMapGroupMessages(
|
||||
messages.filter(message => message.timestamp >= time),
|
||||
);
|
||||
|
||||
const messagesFormated = messagesBeforeWelcomeMsg
|
||||
.concat(welcomeMsg)
|
||||
.concat(amIModerator ? moderatorMsg : [])
|
||||
.concat(moderatorMsg || [])
|
||||
.concat(messagesAfterWelcomeMsg);
|
||||
|
||||
messages = messagesFormated.sort((a, b) => (a.time - b.time));
|
||||
@ -134,7 +138,7 @@ export default injectIntl(withTracker(({ intl }) => {
|
||||
}
|
||||
|
||||
messages = messages.map((message) => {
|
||||
if (message.sender) return message;
|
||||
if (message.sender && message.sender !== SYSTEM_CHAT_TYPE) return message;
|
||||
|
||||
return {
|
||||
...message,
|
||||
|
@ -8,14 +8,13 @@ import {
|
||||
List, AutoSizer, CellMeasurer, CellMeasurerCache,
|
||||
} from 'react-virtualized';
|
||||
import { styles } from './styles';
|
||||
import MessageListItem from './message-list-item/component';
|
||||
import MessageListItemContainer from './message-list-item/container';
|
||||
|
||||
const propTypes = {
|
||||
messages: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
scrollPosition: PropTypes.number,
|
||||
chatId: PropTypes.string.isRequired,
|
||||
hasUnreadMessages: PropTypes.bool.isRequired,
|
||||
partnerIsLoggedOut: PropTypes.bool.isRequired,
|
||||
handleScrollUpdate: PropTypes.func.isRequired,
|
||||
intl: PropTypes.shape({
|
||||
formatMessage: PropTypes.func.isRequired,
|
||||
@ -42,22 +41,11 @@ const intlMessages = defineMessages({
|
||||
});
|
||||
|
||||
class MessageList extends Component {
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
const { messages: propMessages } = props;
|
||||
const { messages: stateMessages } = state;
|
||||
|
||||
if (propMessages.length !== 3 && propMessages.length < stateMessages.length) return null;
|
||||
|
||||
return {
|
||||
messages: propMessages,
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.cache = new CellMeasurerCache({
|
||||
fixedWidth: true,
|
||||
minWidth: 75,
|
||||
minHeight: 18,
|
||||
});
|
||||
|
||||
this.shouldScrollBottom = false;
|
||||
@ -74,10 +62,11 @@ class MessageList extends Component {
|
||||
shouldScrollToBottom: true,
|
||||
shouldScrollToPosition: false,
|
||||
scrollPosition: 0,
|
||||
messages: [],
|
||||
};
|
||||
|
||||
this.listRef = null;
|
||||
|
||||
this.lastWidth = 0;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -87,52 +76,16 @@ class MessageList extends Component {
|
||||
this.scrollTo(scrollPosition);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {
|
||||
chatId,
|
||||
} = this.props;
|
||||
|
||||
if (chatId !== nextProps.chatId) {
|
||||
const { scrollArea } = this.state;
|
||||
this.handleScrollUpdate(scrollArea.scrollTop, scrollArea);
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const {
|
||||
chatId,
|
||||
hasUnreadMessages,
|
||||
partnerIsLoggedOut,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
scrollArea,
|
||||
} = this.state;
|
||||
|
||||
if (!scrollArea && nextState.scrollArea) return true;
|
||||
|
||||
const switchingCorrespondent = chatId !== nextProps.chatId;
|
||||
const hasNewUnreadMessages = hasUnreadMessages !== nextProps.hasUnreadMessages;
|
||||
|
||||
// check if the messages include <user has left the meeting>
|
||||
const lastMessage = nextProps.messages[nextProps.messages.length - 1];
|
||||
if (lastMessage) {
|
||||
const userLeftIsDisplayed = lastMessage.id.includes('partner-disconnected');
|
||||
if (!(partnerIsLoggedOut && userLeftIsDisplayed)) return true;
|
||||
}
|
||||
|
||||
if (switchingCorrespondent || hasNewUnreadMessages) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
scrollPosition,
|
||||
chatId,
|
||||
messages,
|
||||
} = this.props;
|
||||
const {
|
||||
scrollPosition: prevScrollPosition,
|
||||
messages: prevMessages,
|
||||
chatId: prevChatId,
|
||||
} = prevProps;
|
||||
|
||||
const {
|
||||
@ -140,35 +93,36 @@ class MessageList extends Component {
|
||||
shouldScrollToPosition,
|
||||
scrollPosition: scrollPositionState,
|
||||
shouldScrollToBottom,
|
||||
messages,
|
||||
} = this.state;
|
||||
const { messages: prevMessages } = prevState;
|
||||
const compareChatId = prevProps.chatId !== chatId;
|
||||
|
||||
if (compareChatId) {
|
||||
if (prevChatId !== chatId) {
|
||||
this.cache.clearAll();
|
||||
setTimeout(() => this.scrollTo(scrollPosition), 300);
|
||||
} else if (prevMessages && messages) {
|
||||
if (prevMessages.length > messages.length) {
|
||||
// the chat has been cleared
|
||||
this.cache.clearAll();
|
||||
} else {
|
||||
prevMessages.forEach((prevMessage, index) => {
|
||||
const newMessage = messages[index];
|
||||
if (newMessage.content.length > prevMessage.content.length
|
||||
|| newMessage.id !== prevMessage.id) {
|
||||
this.resizeRow(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldScrollToBottom && !scrollPosition && prevScrollPosition) {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
|
||||
const prevLength = prevProps.messages && !!prevProps.messages.length
|
||||
&& prevProps.messages[prevProps.messages.length - 1].content.length;
|
||||
|
||||
const currentLength = messages && !!messages.length
|
||||
&& messages[messages.length - 1].content.length;
|
||||
|
||||
if (!compareChatId && (prevLength !== currentLength && currentLength > prevLength)) {
|
||||
this.resizeRow(messages.length - 1);
|
||||
}
|
||||
|
||||
if (shouldScrollToPosition && scrollArea.scrollTop === scrollPositionState) {
|
||||
this.setState({ shouldScrollToPosition: false });
|
||||
}
|
||||
|
||||
if (prevMessages.length < messages.length) {
|
||||
this.resizeRow(prevMessages.length - 1);
|
||||
// this.resizeRow(prevMessages.length - 1);
|
||||
// messages.forEach((i, idx) => this.resizeRow(idx));
|
||||
}
|
||||
}
|
||||
@ -217,7 +171,7 @@ class MessageList extends Component {
|
||||
this.cache.clear(idx);
|
||||
if (this.listRef) {
|
||||
this.listRef.recomputeRowHeights(idx);
|
||||
this.listRef.forceUpdate();
|
||||
// this.listRef.forceUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@ -242,6 +196,7 @@ class MessageList extends Component {
|
||||
} = this.props;
|
||||
const { scrollArea } = this.state;
|
||||
const message = messages[index];
|
||||
|
||||
return (
|
||||
<CellMeasurer
|
||||
key={key}
|
||||
@ -250,28 +205,21 @@ class MessageList extends Component {
|
||||
parent={parent}
|
||||
rowIndex={index}
|
||||
>
|
||||
{
|
||||
({ measure }) => (
|
||||
<span
|
||||
style={style}
|
||||
onLoad={measure}
|
||||
key={key}
|
||||
>
|
||||
<MessageListItem
|
||||
style={style}
|
||||
handleReadMessage={handleReadMessage}
|
||||
key={message.id}
|
||||
messages={message.content}
|
||||
user={message.sender}
|
||||
time={message.time}
|
||||
chatAreaId={id}
|
||||
lastReadMessageTime={lastReadMessageTime}
|
||||
deferredMeasurementCache={this.cache}
|
||||
scrollArea={scrollArea}
|
||||
/>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
<span
|
||||
style={style}
|
||||
key={key}
|
||||
>
|
||||
<MessageListItemContainer
|
||||
style={style}
|
||||
handleReadMessage={handleReadMessage}
|
||||
key={key}
|
||||
message={message}
|
||||
messageId={message.id}
|
||||
chatAreaId={id}
|
||||
lastReadMessageTime={lastReadMessageTime}
|
||||
scrollArea={scrollArea}
|
||||
/>
|
||||
</span>
|
||||
</CellMeasurer>
|
||||
);
|
||||
}
|
||||
@ -294,6 +242,7 @@ class MessageList extends Component {
|
||||
className={styles.unreadButton}
|
||||
color="primary"
|
||||
size="sm"
|
||||
key="unread-messages"
|
||||
label={intl.formatMessage(intlMessages.moreMessages)}
|
||||
onClick={this.scrollToBottom}
|
||||
/>
|
||||
@ -305,39 +254,28 @@ class MessageList extends Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
intl,
|
||||
id,
|
||||
messages,
|
||||
} = this.props;
|
||||
const {
|
||||
scrollArea,
|
||||
shouldScrollToBottom,
|
||||
shouldScrollToPosition,
|
||||
scrollPosition,
|
||||
messages,
|
||||
} = this.state;
|
||||
|
||||
const isEmpty = messages.length === 0;
|
||||
return (
|
||||
<div className={styles.messageListWrapper}>
|
||||
<div
|
||||
style={
|
||||
{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
[<div className={styles.messageListWrapper} key="chat-list">
|
||||
<AutoSizer>
|
||||
{({ height, width }) => {
|
||||
if (width !== this.lastWidth) {
|
||||
this.lastWidth = width;
|
||||
this.cache.clearAll();
|
||||
}
|
||||
}
|
||||
role="log"
|
||||
id={id}
|
||||
aria-live="polite"
|
||||
aria-atomic="false"
|
||||
aria-relevant="additions"
|
||||
aria-label={isEmpty ? intl.formatMessage(intlMessages.emptyLogLabel) : ''}
|
||||
>
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
|
||||
return (
|
||||
<List
|
||||
ref={(ref) => {
|
||||
if (ref != null) {
|
||||
if (ref !== null) {
|
||||
this.listRef = ref;
|
||||
|
||||
if (!scrollArea) {
|
||||
@ -351,7 +289,7 @@ class MessageList extends Component {
|
||||
rowCount={messages.length}
|
||||
height={height}
|
||||
width={width}
|
||||
overscanRowCount={15}
|
||||
overscanRowCount={5}
|
||||
deferredMeasurementCache={this.cache}
|
||||
onScroll={this.handleScrollChange}
|
||||
scrollToIndex={shouldScrollToBottom ? messages.length - 1 : undefined}
|
||||
@ -360,13 +298,13 @@ class MessageList extends Component {
|
||||
&& (scrollArea && scrollArea.scrollHeight >= scrollPosition)
|
||||
? scrollPosition : undefined
|
||||
}
|
||||
scrollToAlignment="start"
|
||||
scrollToAlignment="end"
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
{this.renderUnreadNotification()}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</AutoSizer>
|
||||
</div>,
|
||||
this.renderUnreadNotification()]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -45,21 +45,24 @@ class MessageListItem extends Component {
|
||||
scrollArea,
|
||||
messages,
|
||||
user,
|
||||
messageId,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
scrollArea: nextScrollArea,
|
||||
messages: nextMessages,
|
||||
user: nextUser,
|
||||
messageId: nextMessageId,
|
||||
} = nextProps;
|
||||
|
||||
if (!scrollArea && nextScrollArea) return true;
|
||||
|
||||
const hasNewMessage = messages.length !== nextMessages.length;
|
||||
const hasIdChanged = messageId !== nextMessageId;
|
||||
const hasUserChanged = user && nextUser
|
||||
&& (user.isModerator !== nextUser.isModerator || user.isOnline !== nextUser.isOnline);
|
||||
|
||||
return hasNewMessage || hasUserChanged;
|
||||
return hasNewMessage || hasIdChanged || hasUserChanged;
|
||||
}
|
||||
|
||||
renderSystemMessage() {
|
||||
@ -71,27 +74,22 @@ class MessageListItem extends Component {
|
||||
|
||||
return (
|
||||
<div className={styles.item}>
|
||||
<div className={styles.wrapper} ref={(ref) => { this.item = ref; }}>
|
||||
<div className={styles.messages}>
|
||||
<span>
|
||||
{messages.map(message => (
|
||||
message.text !== ''
|
||||
? (
|
||||
<Message
|
||||
className={(message.id ? styles.systemMessage : null)}
|
||||
key={_.uniqueId('id-')}
|
||||
text={message.text}
|
||||
time={message.time}
|
||||
chatAreaId={chatAreaId}
|
||||
handleReadMessage={handleReadMessage}
|
||||
/>
|
||||
) : null
|
||||
))}
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.messages}>
|
||||
{messages.map(message => (
|
||||
message.text !== ''
|
||||
? (
|
||||
<Message
|
||||
className={(message.id ? styles.systemMessage : styles.systemMessageNoBorder)}
|
||||
key={message.id ? message.id : _.uniqueId('id-')}
|
||||
text={message.text}
|
||||
time={message.time}
|
||||
chatAreaId={chatAreaId}
|
||||
handleReadMessage={handleReadMessage}
|
||||
/>
|
||||
) : null
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
@ -117,7 +115,7 @@ class MessageListItem extends Component {
|
||||
|
||||
return (
|
||||
<div className={styles.item}>
|
||||
<div className={styles.wrapper} ref={(ref) => { this.item = ref; }}>
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.avatarWrapper}>
|
||||
<UserAvatar
|
||||
className={styles.avatar}
|
||||
|
@ -0,0 +1,22 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import MessageListItem from './component';
|
||||
import ChatService from '../../service';
|
||||
|
||||
class MessageListItemContainer extends PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<MessageListItem {...this.props} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTracker(({ message }) => {
|
||||
const mappedMessage = ChatService.mapGroupMessage(message);
|
||||
|
||||
return {
|
||||
messages: mappedMessage.content,
|
||||
user: mappedMessage.sender,
|
||||
time: mappedMessage.time,
|
||||
};
|
||||
})(MessageListItemContainer);
|
@ -3,16 +3,12 @@
|
||||
:root {
|
||||
--systemMessage-background-color: #F9FBFC;
|
||||
--systemMessage-border-color: #C5CDD4;
|
||||
--systemMessage-font-color: var(--color-dark-grey);
|
||||
}
|
||||
|
||||
.item {
|
||||
margin: 1rem 0 1rem 0;
|
||||
padding: calc(var(--line-height-computed) / 4) 0 calc(var(--line-height-computed) / 2) 0;
|
||||
font-size: var(--font-size-base);
|
||||
margin-bottom: var(--line-height-computed);
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
@ -33,7 +29,16 @@
|
||||
border-radius: var(--border-radius);
|
||||
font-weight: var(--btn-font-weight);
|
||||
padding: var(--font-size-base);
|
||||
margin-bottom: var(--line-height-computed);
|
||||
//margin-bottom: var(--line-height-computed);
|
||||
color: var(--systemMessage-font-color);
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.systemMessageNoBorder {
|
||||
color: var(--systemMessage-font-color);
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.avatarWrapper {
|
||||
|
@ -37,13 +37,13 @@ const getWelcomeProp = () => Meetings.findOne({ meetingId: Auth.meetingID },
|
||||
|
||||
const mapGroupMessage = (message) => {
|
||||
const mappedMessage = {
|
||||
id: message._id,
|
||||
id: message._id || message.id,
|
||||
content: message.content,
|
||||
time: message.timestamp,
|
||||
time: message.timestamp || message.time,
|
||||
sender: null,
|
||||
};
|
||||
|
||||
if (message.sender !== SYSTEM_CHAT_TYPE) {
|
||||
if (message.sender && message.sender !== SYSTEM_CHAT_TYPE) {
|
||||
const sender = Users.findOne({ userId: message.sender },
|
||||
{
|
||||
fields: {
|
||||
@ -97,6 +97,9 @@ const reduceGroupMessages = (previous, current) => {
|
||||
const reduceAndMapGroupMessages = messages => (messages
|
||||
.reduce(reduceGroupMessages, []).map(mapGroupMessage));
|
||||
|
||||
const reduceAndDontMapGroupMessages = messages => (messages
|
||||
.reduce(reduceGroupMessages, []));
|
||||
|
||||
const getPublicGroupMessages = () => {
|
||||
const publicGroupMessages = GroupChatMsg.find({
|
||||
meetingId: Auth.meetingID,
|
||||
@ -128,7 +131,7 @@ const getPrivateGroupMessages = () => {
|
||||
}, { sort: ['timestamp'] }).fetch();
|
||||
}
|
||||
|
||||
return reduceAndMapGroupMessages(messages, []);
|
||||
return reduceAndDontMapGroupMessages(messages, []);
|
||||
};
|
||||
|
||||
const isChatLocked = (receiverID) => {
|
||||
@ -322,7 +325,9 @@ const getLastMessageTimestampFromChatList = activeChats => activeChats
|
||||
.reduce(maxNumberReducer, 0);
|
||||
|
||||
export default {
|
||||
mapGroupMessage,
|
||||
reduceAndMapGroupMessages,
|
||||
reduceAndDontMapGroupMessages,
|
||||
getPublicGroupMessages,
|
||||
getPrivateGroupMessages,
|
||||
getUser,
|
||||
|
Loading…
Reference in New Issue
Block a user