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 sdk = require('../../index');
var MatrixClientPeg = require('../../MatrixClientPeg')
/* (almost) stateless UI component which builds the event tiles in the room timeline.
*/
module.exports = React.createClass({
@ -150,7 +152,7 @@ module.exports = React.createClass({
this.refs.scrollPanel.scrollToBottom();
}
},
/**
* Page up/down.
*
@ -335,13 +337,17 @@ module.exports = React.createClass({
// Local echos have a send "status".
var scrollToken = mxEv.status ? undefined : eventId;
var readReceipts = this._getReadReceiptsForEvent(mxEv);
ret.push(
<li key={eventId}
ref={this._collectEventNode.bind(this, eventId)}
data-scroll-token={scrollToken}>
<EventTile mxEvent={mxEv} continuation={continuation}
onWidgetLoad={this._onWidgetLoad}
last={last} isSelectedEvent={highlight} />
readReceipts={readReceipts}
eventSendStatus={mxEv.status}
last={last} isSelectedEvent={highlight}/>
</li>
);
@ -359,6 +365,30 @@ module.exports = React.createClass({
!== 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) {
var hr;
if (visible) {
@ -431,7 +461,7 @@ module.exports = React.createClass({
return (
<ScrollPanel ref="scrollPanel" className="mx_RoomView_messagePanel mx_fadable"
onScroll={ this.props.onScroll }
onScroll={ this.props.onScroll }
onResize={ this.onResize }
onFillRequest={ this.props.onFillRequest }
style={ style }

View File

@ -29,6 +29,8 @@ var Velociraptor = require('../../../Velociraptor');
require('../../../VelocityBounce');
var dispatcher = require("../../../dispatcher");
var ObjectUtils = require('../../../ObjectUtils');
var bounce = false;
try {
if (global.localStorage) {
@ -107,12 +109,67 @@ module.exports = React.createClass({
/* callback called when dynamic content in events are loaded */
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() {
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() {
var actions = MatrixClientPeg.get().getPushActionsForEvent(this.props.mxEvent);
if (!actions || !actions.tweaks) { return false; }
@ -153,20 +210,6 @@ module.exports = React.createClass({
getReadAvatars: function() {
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 left = 0;
@ -176,11 +219,9 @@ module.exports = React.createClass({
easing: 'easeOut'
};
var receipts = this.props.readReceipts || [];
for (var i = 0; i < receipts.length; ++i) {
var member = room.getMember(receipts[i].userId);
if (!member) {
continue;
}
var member = receipts[i];
// Using react refs here would mean both getting Velociraptor to expose
// them and making them scoped to the whole RoomView. Not impossible, but
@ -302,9 +343,9 @@ module.exports = React.createClass({
var classes = classNames({
mx_EventTile: true,
mx_EventTile_sending: ['sending', 'queued'].indexOf(
this.props.mxEvent.status
this.props.eventSendStatus
) !== -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_selected: this.props.isSelectedEvent,
mx_EventTile_continuation: this.props.continuation,