From 3fd066c2d486eb43072333077b5365e142d33827 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Thu, 10 Mar 2016 16:44:50 +0000 Subject: [PATCH] Put direct-linked events and search clickthroughs in the middle We need two modes of operation for ScrollPanel.scrollToToken: For jump-to-read-marker, we want it 1/3 of the way down the screen. For search clickthrough, and hyperlinked events, we want put the event in the *middle* of the screen. Fixes https://github.com/vector-im/vector-web/issues/1032 --- src/components/structures/MessagePanel.js | 14 ++++++---- src/components/structures/ScrollPanel.js | 26 ++++++++++++------ src/components/structures/TimelinePanel.js | 32 ++++++++++++++++------ 3 files changed, 49 insertions(+), 23 deletions(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 503d069e48..102b07e046 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -141,13 +141,17 @@ module.exports = React.createClass({ /* jump to the given event id. * - * pixelOffset gives the number of pixels between the bottom of the node - * and the bottom of the container. If undefined, it will put the node - * 1/3 of the way down of the container. + * offsetBase gives the reference point for the pixelOffset. 0 means the + * top of the container, 1 means the bottom, and fractional values mean + * somewhere in the middle. If omitted, it defaults to 0. + * + * pixelOffset gives the number of pixels *above* the offsetBase that the + * node (specifically, the bottom of it) will be positioned. If omitted, it + * defaults to 0. */ - scrollToEvent: function(eventId, pixelOffset) { + scrollToEvent: function(eventId, pixelOffset, offsetBase) { if (this.refs.scrollPanel) { - this.refs.scrollPanel.scrollToToken(eventId, pixelOffset); + this.refs.scrollPanel.scrollToToken(eventId, pixelOffset, offsetBase); } }, diff --git a/src/components/structures/ScrollPanel.js b/src/components/structures/ScrollPanel.js index 030904cb57..a7f66d14cc 100644 --- a/src/components/structures/ScrollPanel.js +++ b/src/components/structures/ScrollPanel.js @@ -326,16 +326,24 @@ module.exports = React.createClass({ this._saveScrollState(); }, - // pixelOffset gives the number of pixels between the bottom of the node - // and the bottom of the container. If undefined, it will put the node - // 1/3 of the way down the container. - scrollToToken: function(scrollToken, pixelOffset) { - var scrollNode = this._getScrollNode(); + /* Scroll the panel to bring the DOM node with the scroll token + * `scrollToken` into view. + * + * offsetBase gives the reference point for the pixelOffset. 0 means the + * top of the container, 1 means the bottom, and fractional values mean + * somewhere in the middle. If omitted, it defaults to 0. + * + * pixelOffset gives the number of pixels *above* the offsetBase that the + * node (specifically, the bottom of it) will be positioned. If omitted, it + * defaults to 0. + */ + scrollToToken: function(scrollToken, pixelOffset, offsetBase) { + pixelOffset = pixelOffset || 0; + offsetBase = offsetBase || 0; - // default to 1/3 of the way down - if (pixelOffset === undefined) { - pixelOffset = (scrollNode.clientHeight * 2)/ 3; - } + // convert pixelOffset so that it is based on the bottom of the + // container. + pixelOffset += this._getScrollNode().clientHeight * (1-offsetBase); // save the desired scroll state. It's important we do this here rather // than as a result of the scroll event, because (a) we might not *get* diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index 46be84e3dd..2e54338b8e 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -70,7 +70,7 @@ var TimelinePanel = React.createClass({ // where to position the event given by eventId, in pixels from the // bottom of the viewport. If not given, will try to put the event - // 1/3 of the way down the viewport. + // half way down the viewport. eventPixelOffset: React.PropTypes.number, // callback which is called when the panel is scrolled. @@ -413,7 +413,8 @@ var TimelinePanel = React.createClass({ } }, - /* scroll to show the read-up-to marker + /* scroll to show the read-up-to marker. We put it 1/3 of the way down + * the container. */ jumpToReadMarker: function() { if (!this.refs.messagePanel) @@ -432,13 +433,14 @@ var TimelinePanel = React.createClass({ if (ret !== null) { // The messagepanel knows where the RM is, so we must have loaded // the relevant event. - this.refs.messagePanel.scrollToEvent(this.state.readMarkerEventId); + this.refs.messagePanel.scrollToEvent(this.state.readMarkerEventId, + 0, 1/3); } // Looks like we haven't loaded the event corresponding to the read-marker. // As with jumpToLiveTimeline, we want to reload the timeline around the // read-marker. - this._loadTimeline(this.state.readMarkerEventId); + this._loadTimeline(this.state.readMarkerEventId, 0, 1/3); }, @@ -518,7 +520,15 @@ var TimelinePanel = React.createClass({ _initTimeline: function(props) { var initialEvent = props.eventId; var pixelOffset = props.eventPixelOffset; - return this._loadTimeline(initialEvent, pixelOffset); + + // if a pixelOffset is given, it is relative to the bottom of the + // container. If not, put the event in the middle of the container. + var offsetBase = 1; + if (pixelOffset == null) { + offsetBase = 0.5; + } + + return this._loadTimeline(initialEvent, pixelOffset, offsetBase); }, /** @@ -529,12 +539,15 @@ var TimelinePanel = React.createClass({ * scroll to the bottom of the room. * * @param {number?} pixelOffset offset to position the given event at - * (pixels from the bottom of the view). If undefined, will put the - * event 1/3 of the way down the view. + * (pixels from the offsetBase). If omitted, defaults to 0. + * + * @param {number?} offsetBase the reference point for the pixelOffset. 0 + * means the top of the container, 1 means the bottom, and fractional + * values mean somewhere in the middle. If omitted, it defaults to 0. * * returns a promise which will resolve when the load completes. */ - _loadTimeline: function(eventId, pixelOffset) { + _loadTimeline: function(eventId, pixelOffset, offsetBase) { this._timelineWindow = new Matrix.TimelineWindow( MatrixClientPeg.get(), this.props.room, {windowLimit: TIMELINE_CAP}); @@ -567,7 +580,8 @@ var TimelinePanel = React.createClass({ return; } if (eventId) { - this.refs.messagePanel.scrollToEvent(eventId, pixelOffset); + this.refs.messagePanel.scrollToEvent(eventId, pixelOffset, + offsetBase); } else { this.refs.messagePanel.scrollToBottom(); }