Merge pull request #3867 from oswaldoacauan/chat-performance-improvement

[HTML5] Chat performance improvement
This commit is contained in:
Anton Georgiev 2017-04-28 15:40:08 -04:00 committed by GitHub
commit 8363d76f98
4 changed files with 104 additions and 35 deletions

View File

@ -107,9 +107,8 @@ export default injectIntl(createContainer(({ params, intl }) => {
handleClosePrivateChat: chatID => ChatService.closePrivateChat(chatID),
handleSendMessage: message => {
ChatService.updateScrollPosition(chatID, null);
let sentMessage = ChatService.sendMessage(chatID, message);
ChatService.updateScrollPosition(chatID, null); //null so its scrolls to bottom
// ChatService.updateUnreadMessage(chatID, sentMessage.from_time);
},
handleScrollUpdate: position => ChatService.updateScrollPosition(chatID, position),

View File

@ -18,7 +18,7 @@ const intlMessages = defineMessages({
emptyLogLabel: {
id: 'app.chat.emptyLogLabel',
description: 'aria-label used when chat log is empty',
}
},
});
class MessageList extends Component {
@ -73,7 +73,6 @@ class MessageList extends Component {
}
componentWillUpdate(nextProps) {
if (this.props.chatId !== nextProps.chatId) {
this.shouldScrollBottom = false;
return;
@ -86,7 +85,8 @@ class MessageList extends Component {
//Compare with <1 to account for the chance scrollArea.scrollTop is a float
//value in some browsers.
this.shouldScrollBottom = position === scrollArea.scrollHeight ||
(scrollArea.scrollHeight - position < 1);
(scrollArea.scrollHeight - position < 1) ||
nextProps.scrollPosition === null;
}
componentDidUpdate(prevProps) {

View File

@ -1,6 +1,7 @@
import React, { Component, PropTypes } from 'react';
import { FormattedTime } from 'react-intl';
import cx from 'classnames';
import _ from 'lodash';
import UserAvatar from '/imports/ui/components/user-avatar/component';
import Message from './message/component';
@ -16,10 +17,74 @@ const propTypes = {
const defaultProps = {
};
const eventsToBeBound = [
'scroll',
'resize',
];
const isElementInViewport = (el) => {
const rect = el.getBoundingClientRect();
const clientHeight = window.innerHeight || document.documentElement.clientHeight;
const prefetchHeight = 125;
return (
rect.top >= -(prefetchHeight) &&
rect.bottom <= clientHeight + prefetchHeight
);
};
export default class MessageListItem extends Component {
constructor(props) {
super(props);
this.state = {
pendingChanges: false,
preventRender: true,
};
this.handleMessageInViewport = _.debounce(this.handleMessageInViewport.bind(this), 50);
}
handleMessageInViewport() {
window.requestAnimationFrame(() => {
const node = this.refs.item;
this.setState({ preventRender: !isElementInViewport(node) });
});
}
componentDidMount() {
const scrollArea = document.getElementById(this.props.chatAreaId);
eventsToBeBound.forEach(
e => scrollArea.addEventListener(e, this.handleMessageInViewport, false)
);
this.handleMessageInViewport();
}
componentWillUnmount() {
const scrollArea = document.getElementById(this.props.chatAreaId);
eventsToBeBound.forEach(
e => scrollArea.removeEventListener(e, this.handleMessageInViewport, false)
);
}
componentDidUpdate(prevProps, prevState) {
if (prevState.preventRender && !this.state.preventRender && this.state.pendingChanges) {
this.setState({ pendingChanges: false });
}
}
componentWillReceiveProps(nextProps) {
if (this.state.pendingChanges) return;
const hasNewMessage = this.props.messages.length !== nextProps.messages.length;
const hasUserChanged = !_.isEqual(this.props.user, nextProps.user);
this.setState({ pendingChanges: hasNewMessage || hasUserChanged });
}
shouldComponentUpdate(nextProps, nextState) {
return !nextState.preventRender && nextState.pendingChanges;
}
render() {
@ -36,32 +101,34 @@ export default class MessageListItem extends Component {
}
return (
<div className={styles.item}>
<div className={styles.avatar}>
<UserAvatar user={user} />
</div>
<div className={styles.content}>
<div className={styles.meta}>
<div className={user.isOnline ? styles.name : styles.logout} aria-label={user.name}>
<span>{user.name}</span>
{user.isOnline ? null : <span className={styles.offline}>(offline)</span>}
</div>
<time className={styles.time} dateTime={dateTime}>
<FormattedTime value={dateTime}/>
</time>
<div className={styles.item}>
<div className={styles.wrapper} ref="item">
<div className={styles.avatar}>
<UserAvatar user={user} />
</div>
<div className={styles.messages}>
{messages.map((message, i) => (
<Message
className={styles.message}
key={message.id}
text={message.text}
time={message.time}
chatAreaId={this.props.chatAreaId}
lastReadMessageTime={this.props.lastReadMessageTime}
handleReadMessage={this.props.handleReadMessage}
/>
))}
<div className={styles.content}>
<div className={styles.meta}>
<div className={!user.isOnline ? styles.name : styles.logout}>
<span>{user.name}</span>
{user.isOnline ? null : <span className={styles.offline}>(offline)</span>}
</div>
<time className={styles.time} dateTime={dateTime}>
<FormattedTime value={dateTime}/>
</time>
</div>
<div className={styles.messages}>
{messages.map((message, i) => (
<Message
className={styles.message}
key={message.id}
text={message.text}
time={message.time}
chatAreaId={this.props.chatAreaId}
lastReadMessageTime={this.props.lastReadMessageTime}
handleReadMessage={this.props.handleReadMessage}
/>
))}
</div>
</div>
</div>
</div>
@ -75,7 +142,7 @@ export default class MessageListItem extends Component {
return (
<div className={cx(styles.item, styles.systemMessage)}>
<div className={styles.content}>
<div className={styles.content} ref="item">
<div className={styles.messages}>
{messages.map((message, i) => (
<Message

View File

@ -1,17 +1,20 @@
@import "../../../../stylesheets/variables/_all";
.item {
display: flex;
flex-flow: row;
flex: 1;
margin-bottom: $line-height-computed;
font-size: $font-size-base * .90;
margin-bottom: $line-height-computed;
&:last-child {
margin-bottom: 0 !important;
}
}
.wrapper {
display: flex;
flex-flow: row;
flex: 1;
}
.systemMessage {
.item + &,