Merge pull request #278 from matrix-org/rav/optimise_eventtile_update

Avoid rerendering EventTiles when not necessary
pull/21833/head
Richard van der Hoff 2016-04-19 20:30:54 +01:00
commit b0eba8aea8
2 changed files with 94 additions and 23 deletions

View File

@ -19,6 +19,8 @@ var ReactDOM = require("react-dom");
var dis = require("../../dispatcher"); var dis = require("../../dispatcher");
var sdk = require('../../index'); var sdk = require('../../index');
var MatrixClientPeg = require('../../MatrixClientPeg')
/* (almost) stateless UI component which builds the event tiles in the room timeline. /* (almost) stateless UI component which builds the event tiles in the room timeline.
*/ */
module.exports = React.createClass({ module.exports = React.createClass({
@ -150,7 +152,7 @@ module.exports = React.createClass({
this.refs.scrollPanel.scrollToBottom(); this.refs.scrollPanel.scrollToBottom();
} }
}, },
/** /**
* Page up/down. * Page up/down.
* *
@ -335,13 +337,17 @@ module.exports = React.createClass({
// Local echos have a send "status". // Local echos have a send "status".
var scrollToken = mxEv.status ? undefined : eventId; var scrollToken = mxEv.status ? undefined : eventId;
var readReceipts = this._getReadReceiptsForEvent(mxEv);
ret.push( ret.push(
<li key={eventId} <li key={eventId}
ref={this._collectEventNode.bind(this, eventId)} ref={this._collectEventNode.bind(this, eventId)}
data-scroll-token={scrollToken}> data-scroll-token={scrollToken}>
<EventTile mxEvent={mxEv} continuation={continuation} <EventTile mxEvent={mxEv} continuation={continuation}
onWidgetLoad={this._onWidgetLoad} onWidgetLoad={this._onWidgetLoad}
last={last} isSelectedEvent={highlight} /> readReceipts={readReceipts}
eventSendStatus={mxEv.status}
last={last} isSelectedEvent={highlight}/>
</li> </li>
); );
@ -359,6 +365,30 @@ module.exports = React.createClass({
!== new Date(nextEventTs).toDateString()); !== new Date(nextEventTs).toDateString());
}, },
// get a list of the userids whose read receipts should
// be shown next to this event
_getReadReceiptsForEvent: function(event) {
var myUserId = MatrixClientPeg.get().credentials.userId;
// get list of read receipts, sorted most recent first
var room = MatrixClientPeg.get().getRoom(event.getRoomId());
if (!room) {
// huh.
return null;
}
return room.getReceiptsForEvent(event).filter(function(r) {
return r.type === "m.read" && r.userId != myUserId;
}).sort(function(r1, r2) {
return r2.data.ts - r1.data.ts;
}).map(function(r) {
return room.getMember(r.userId);
}).filter(function(m) {
// check that the user is a known room member
return m;
});
},
_getReadMarkerTile: function(visible) { _getReadMarkerTile: function(visible) {
var hr; var hr;
if (visible) { if (visible) {
@ -431,7 +461,7 @@ module.exports = React.createClass({
return ( return (
<ScrollPanel ref="scrollPanel" className="mx_RoomView_messagePanel mx_fadable" <ScrollPanel ref="scrollPanel" className="mx_RoomView_messagePanel mx_fadable"
onScroll={ this.props.onScroll } onScroll={ this.props.onScroll }
onResize={ this.onResize } onResize={ this.onResize }
onFillRequest={ this.props.onFillRequest } onFillRequest={ this.props.onFillRequest }
style={ style } style={ style }

View File

@ -29,6 +29,8 @@ var Velociraptor = require('../../../Velociraptor');
require('../../../VelocityBounce'); require('../../../VelocityBounce');
var dispatcher = require("../../../dispatcher"); var dispatcher = require("../../../dispatcher");
var ObjectUtils = require('../../../ObjectUtils');
var bounce = false; var bounce = false;
try { try {
if (global.localStorage) { if (global.localStorage) {
@ -107,12 +109,67 @@ module.exports = React.createClass({
/* callback called when dynamic content in events are loaded */ /* callback called when dynamic content in events are loaded */
onWidgetLoad: React.PropTypes.func, onWidgetLoad: React.PropTypes.func,
/* a list of Room Members whose read-receipts we should show */
readReceipts: React.PropTypes.arrayOf(React.PropTypes.object),
/* the status of this event - ie, mxEvent.status. Denormalised to here so
* that we can tell when it changes. */
eventSendStatus: React.PropTypes.string,
}, },
getInitialState: function() { getInitialState: function() {
return {menu: false, allReadAvatars: false}; return {menu: false, allReadAvatars: false};
}, },
shouldComponentUpdate: function (nextProps, nextState) {
if (!ObjectUtils.shallowEqual(this.state, nextState)) {
return true;
}
if (!this._propsEqual(this.props, nextProps)) {
return true;
}
return false;
},
_propsEqual: function(objA, objB) {
var keysA = Object.keys(objA);
var keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
for (var i = 0; i < keysA.length; i++) {
var key = keysA[i];
if (!objB.hasOwnProperty(key)) {
return false;
}
// need to deep-compare readReceipts
if (key == 'readReceipts') {
var rA = objA[key];
var rB = objB[key];
if (rA.length !== rB.length) {
return false;
}
for (var j = 0; j < rA.length; j++) {
if (rA[j].userId !== rB[j].userId) {
return false;
}
}
} else {
if (objA[key] !== objB[key]) {
return false;
}
}
}
return true;
},
shouldHighlight: function() { shouldHighlight: function() {
var actions = MatrixClientPeg.get().getPushActionsForEvent(this.props.mxEvent); var actions = MatrixClientPeg.get().getPushActionsForEvent(this.props.mxEvent);
if (!actions || !actions.tweaks) { return false; } if (!actions || !actions.tweaks) { return false; }
@ -153,20 +210,6 @@ module.exports = React.createClass({
getReadAvatars: function() { getReadAvatars: function() {
var avatars = []; var avatars = [];
var room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
if (!room) return [];
var myUserId = MatrixClientPeg.get().credentials.userId;
// get list of read receipts, sorted most recent first
var receipts = room.getReceiptsForEvent(this.props.mxEvent).filter(function(r) {
return r.type === "m.read" && r.userId != myUserId;
}).sort(function(r1, r2) {
return r2.data.ts - r1.data.ts;
});
var MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); var MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
var left = 0; var left = 0;
@ -176,11 +219,9 @@ module.exports = React.createClass({
easing: 'easeOut' easing: 'easeOut'
}; };
var receipts = this.props.readReceipts || [];
for (var i = 0; i < receipts.length; ++i) { for (var i = 0; i < receipts.length; ++i) {
var member = room.getMember(receipts[i].userId); var member = receipts[i];
if (!member) {
continue;
}
// Using react refs here would mean both getting Velociraptor to expose // Using react refs here would mean both getting Velociraptor to expose
// them and making them scoped to the whole RoomView. Not impossible, but // them and making them scoped to the whole RoomView. Not impossible, but
@ -302,9 +343,9 @@ module.exports = React.createClass({
var classes = classNames({ var classes = classNames({
mx_EventTile: true, mx_EventTile: true,
mx_EventTile_sending: ['sending', 'queued'].indexOf( mx_EventTile_sending: ['sending', 'queued'].indexOf(
this.props.mxEvent.status this.props.eventSendStatus
) !== -1, ) !== -1,
mx_EventTile_notSent: this.props.mxEvent.status == 'not_sent', mx_EventTile_notSent: this.props.eventSendStatus == 'not_sent',
mx_EventTile_highlight: this.shouldHighlight(), mx_EventTile_highlight: this.shouldHighlight(),
mx_EventTile_selected: this.props.isSelectedEvent, mx_EventTile_selected: this.props.isSelectedEvent,
mx_EventTile_continuation: this.props.continuation, mx_EventTile_continuation: this.props.continuation,