mirror of
https://github.com/element-hq/element-web
synced 2024-11-23 09:46:09 +03:00
Avoid rerendering EventTiles when not necessary
Each individual eventtile isn't particularly expensive, but when you have 500 of them, they start adding up. Shuffle some of the stuff into MessagePanel, so that we can shouldComponentUpdate EventTiles properly.
This commit is contained in:
parent
8bb13db7d4
commit
568e7aef8b
2 changed files with 94 additions and 23 deletions
|
@ -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 }
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue