From 66cfdc96dac8fa0eacdf4743b0bef3a18344429c Mon Sep 17 00:00:00 2001 From: Chad Pilkey Date: Tue, 21 Jan 2020 21:08:48 +0000 Subject: [PATCH] virtualized chat tweaks for rendering and performance --- .../imports/ui/components/chat/container.jsx | 32 ++-- .../chat/message-list/component.jsx | 180 ++++++------------ .../message-list-item/component.jsx | 40 ++-- .../message-list-item/container.jsx | 22 +++ .../message-list-item/styles.scss | 19 +- .../imports/ui/components/chat/service.js | 13 +- 6 files changed, 139 insertions(+), 167 deletions(-) create mode 100644 bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/container.jsx diff --git a/bigbluebutton-html5/imports/ui/components/chat/container.jsx b/bigbluebutton-html5/imports/ui/components/chat/container.jsx index a790298ddc..7dfe11426d 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/container.jsx @@ -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, diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx index d17355b9fe..2fb1352e41 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx @@ -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 - 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 ( - { - ({ measure }) => ( - - - - ) - } + + + ); } @@ -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 ( -
-
+ + {({ 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) : ''} - > - - {({ height, width }) => ( + + return ( { - 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" /> - )} - -
- {this.renderUnreadNotification()} -
+ ); + }} + + , + this.renderUnreadNotification()] ); } } diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx index c3afcca86c..3b84e86d07 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx @@ -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 (
-
{ this.item = ref; }}> -
- - {messages.map(message => ( - message.text !== '' - ? ( - - ) : null - ))} - -
+
+ {messages.map(message => ( + message.text !== '' + ? ( + + ) : null + ))}
- ); } @@ -117,7 +115,7 @@ class MessageListItem extends Component { return (
-
{ this.item = ref; }}> +
+ ); + } +} + +export default withTracker(({ message }) => { + const mappedMessage = ChatService.mapGroupMessage(message); + + return { + messages: mappedMessage.content, + user: mappedMessage.sender, + time: mappedMessage.time, + }; +})(MessageListItemContainer); diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/styles.scss b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/styles.scss index 579b6d4883..492fd1cc65 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/styles.scss @@ -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 { diff --git a/bigbluebutton-html5/imports/ui/components/chat/service.js b/bigbluebutton-html5/imports/ui/components/chat/service.js index 64cb0ea531..e4f3f5c0a1 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/service.js +++ b/bigbluebutton-html5/imports/ui/components/chat/service.js @@ -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,