Preserve scroll position when backfilling

Save the scroll state whenever the user does a scroll operation, and use that
to update the scroll after a backfill completes.
pull/21833/head
Richard van der Hoff 2015-12-10 21:34:10 +00:00
parent a1d88722aa
commit 1a3fb9aca9
1 changed files with 25 additions and 27 deletions

View File

@ -70,7 +70,7 @@ module.exports = React.createClass({
MatrixClientPeg.get().on("RoomMember.typing", this.onRoomMemberTyping); MatrixClientPeg.get().on("RoomMember.typing", this.onRoomMemberTyping);
MatrixClientPeg.get().on("RoomState.members", this.onRoomStateMember); MatrixClientPeg.get().on("RoomState.members", this.onRoomStateMember);
MatrixClientPeg.get().on("sync", this.onSyncStateChange); MatrixClientPeg.get().on("sync", this.onSyncStateChange);
this.atBottom = true; this.savedScrollState = {atBottom: true};
}, },
componentWillUnmount: function() { componentWillUnmount: function() {
@ -173,7 +173,7 @@ module.exports = React.createClass({
if (!toStartOfTimeline && if (!toStartOfTimeline &&
(ev.getSender() !== MatrixClientPeg.get().credentials.userId)) { (ev.getSender() !== MatrixClientPeg.get().credentials.userId)) {
// update unread count when scrolled up // update unread count when scrolled up
if (this.atBottom) { if (this.savedScrollState.atBottom) {
currentUnread = 0; currentUnread = 0;
} }
else { else {
@ -181,15 +181,10 @@ module.exports = React.createClass({
} }
} }
this.setState({ this.setState({
room: MatrixClientPeg.get().getRoom(this.props.roomId), room: MatrixClientPeg.get().getRoom(this.props.roomId),
numUnreadMessages: currentUnread numUnreadMessages: currentUnread
}); });
if (toStartOfTimeline && !this.state.paginating) {
this.fillSpace();
}
}, },
onRoomName: function(room) { onRoomName: function(room) {
@ -282,17 +277,15 @@ module.exports = React.createClass({
componentDidUpdate: function() { componentDidUpdate: function() {
if (!this.refs.messagePanel) return; if (!this.refs.messagePanel) return;
var messageWrapperScroll = this._getScrollNode(); var scrollState = this.savedScrollState;
if (scrollState.atBottom) {
if (this.state.paginating && !this.waiting_for_paginate) { this.scrollToBottom();
var heightGained = messageWrapperScroll.scrollHeight - this.oldScrollHeight;
messageWrapperScroll.scrollTop += heightGained;
this.oldScrollHeight = undefined;
} else if (this.atBottom) {
messageWrapperScroll.scrollTop = messageWrapperScroll.scrollHeight;
if (this.state.numUnreadMessages !== 0) { if (this.state.numUnreadMessages !== 0) {
this.setState({numUnreadMessages: 0}); this.setState({numUnreadMessages: 0});
} }
} else if (scrollState.lastDisplayedEvent) {
this.scrollToEvent(scrollState.lastDisplayedEvent,
scrollState.pixelOffset);
} }
}, },
@ -365,12 +358,9 @@ module.exports = React.createClass({
onMessageListScroll: function(ev) { onMessageListScroll: function(ev) {
if (this.refs.messagePanel) { if (this.refs.messagePanel) {
var messageWrapperScroll = this._getScrollNode(); this.savedScrollState = this._calculateScrollState();
var wasAtBottom = this.atBottom; if (this.savedScrollState.atBottom && this.state.numUnreadMessages != 0) {
// + 1 here to avoid fractional pixel rounding errors this.setState({numUnreadMessages: 0});
this.atBottom = messageWrapperScroll.scrollHeight - messageWrapperScroll.scrollTop <= messageWrapperScroll.clientHeight + 1;
if (this.atBottom && !wasAtBottom) {
this.forceUpdate(); // remove unread msg count
} }
} }
if (!this.state.paginating) this.fillSpace(); if (!this.state.paginating) this.fillSpace();
@ -852,9 +842,7 @@ module.exports = React.createClass({
scrollNode.scrollTop += boundingRect.bottom + pixel_offset - wrapperRect.bottom; scrollNode.scrollTop += boundingRect.bottom + pixel_offset - wrapperRect.bottom;
}, },
// get the current scroll position of the room, so that it can be _calculateScrollState: function() {
// restored when we switch back to it
getScrollState: function() {
// we don't save the absolute scroll offset, because that // we don't save the absolute scroll offset, because that
// would be affected by window width, zoom level, amount of scrollback, // would be affected by window width, zoom level, amount of scrollback,
// etc. // etc.
@ -869,6 +857,10 @@ module.exports = React.createClass({
if (messageWrapper === undefined) return null; if (messageWrapper === undefined) return null;
var wrapperRect = ReactDOM.findDOMNode(messageWrapper).getBoundingClientRect(); var wrapperRect = ReactDOM.findDOMNode(messageWrapper).getBoundingClientRect();
var messageWrapperScroll = this._getScrollNode();
// + 1 here to avoid fractional pixel rounding errors
var atBottom = messageWrapperScroll.scrollHeight - messageWrapperScroll.scrollTop <= messageWrapperScroll.clientHeight + 1;
for (var i = this.state.room.timeline.length-1; i >= 0; --i) { for (var i = this.state.room.timeline.length-1; i >= 0; --i) {
var ev = this.state.room.timeline[i]; var ev = this.state.room.timeline[i];
var node = this.eventNodes[ev.getId()]; var node = this.eventNodes[ev.getId()];
@ -877,7 +869,7 @@ module.exports = React.createClass({
var boundingRect = node.getBoundingClientRect(); var boundingRect = node.getBoundingClientRect();
if (boundingRect.bottom < wrapperRect.bottom) { if (boundingRect.bottom < wrapperRect.bottom) {
return { return {
atBottom: this.atBottom, atBottom: atBottom,
lastDisplayedEvent: ev.getId(), lastDisplayedEvent: ev.getId(),
pixelOffset: wrapperRect.bottom - boundingRect.bottom, pixelOffset: wrapperRect.bottom - boundingRect.bottom,
} }
@ -885,14 +877,20 @@ module.exports = React.createClass({
} }
// apparently the entire timeline is below the viewport. Give up. // apparently the entire timeline is below the viewport. Give up.
return null; return { atBottom: true };
},
// get the current scroll position of the room, so that it can be
// restored when we switch back to it
getScrollState: function() {
return this.savedScrollState;
}, },
restoreScrollState: function(scrollState) { restoreScrollState: function(scrollState) {
if(scrollState.atBottom) { if(scrollState.atBottom) {
// we were at the bottom before. Ideally we'd scroll to the // we were at the bottom before. Ideally we'd scroll to the
// 'read-up-to' mark here. // 'read-up-to' mark here.
} else if (scrollState.lastDisplayed) { } else if (scrollState.lastDisplayedEvent) {
this.scrollToEvent(scrollState.lastDisplayedEvent, this.scrollToEvent(scrollState.lastDisplayedEvent,
scrollState.pixelOffset); scrollState.pixelOffset);
} }