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
This commit is contained in:
Richard van der Hoff 2016-03-10 16:44:50 +00:00
parent 7660276b54
commit 3fd066c2d4
3 changed files with 49 additions and 23 deletions

View File

@ -141,13 +141,17 @@ module.exports = React.createClass({
/* jump to the given event id. /* jump to the given event id.
* *
* pixelOffset gives the number of pixels between the bottom of the node * offsetBase gives the reference point for the pixelOffset. 0 means the
* and the bottom of the container. If undefined, it will put the node * top of the container, 1 means the bottom, and fractional values mean
* 1/3 of the way down of the container. * 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) { if (this.refs.scrollPanel) {
this.refs.scrollPanel.scrollToToken(eventId, pixelOffset); this.refs.scrollPanel.scrollToToken(eventId, pixelOffset, offsetBase);
} }
}, },

View File

@ -326,16 +326,24 @@ module.exports = React.createClass({
this._saveScrollState(); this._saveScrollState();
}, },
// pixelOffset gives the number of pixels between the bottom of the node /* Scroll the panel to bring the DOM node with the scroll token
// and the bottom of the container. If undefined, it will put the node * `scrollToken` into view.
// 1/3 of the way down the container. *
scrollToToken: function(scrollToken, pixelOffset) { * offsetBase gives the reference point for the pixelOffset. 0 means the
var scrollNode = this._getScrollNode(); * 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 // convert pixelOffset so that it is based on the bottom of the
if (pixelOffset === undefined) { // container.
pixelOffset = (scrollNode.clientHeight * 2)/ 3; pixelOffset += this._getScrollNode().clientHeight * (1-offsetBase);
}
// save the desired scroll state. It's important we do this here rather // 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* // than as a result of the scroll event, because (a) we might not *get*

View File

@ -70,7 +70,7 @@ var TimelinePanel = React.createClass({
// where to position the event given by eventId, in pixels from the // 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 // 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, eventPixelOffset: React.PropTypes.number,
// callback which is called when the panel is scrolled. // 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() { jumpToReadMarker: function() {
if (!this.refs.messagePanel) if (!this.refs.messagePanel)
@ -432,13 +433,14 @@ var TimelinePanel = React.createClass({
if (ret !== null) { if (ret !== null) {
// The messagepanel knows where the RM is, so we must have loaded // The messagepanel knows where the RM is, so we must have loaded
// the relevant event. // 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. // Looks like we haven't loaded the event corresponding to the read-marker.
// As with jumpToLiveTimeline, we want to reload the timeline around the // As with jumpToLiveTimeline, we want to reload the timeline around the
// read-marker. // 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) { _initTimeline: function(props) {
var initialEvent = props.eventId; var initialEvent = props.eventId;
var pixelOffset = props.eventPixelOffset; 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. * scroll to the bottom of the room.
* *
* @param {number?} pixelOffset offset to position the given event at * @param {number?} pixelOffset offset to position the given event at
* (pixels from the bottom of the view). If undefined, will put the * (pixels from the offsetBase). If omitted, defaults to 0.
* event 1/3 of the way down the view. *
* @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. * returns a promise which will resolve when the load completes.
*/ */
_loadTimeline: function(eventId, pixelOffset) { _loadTimeline: function(eventId, pixelOffset, offsetBase) {
this._timelineWindow = new Matrix.TimelineWindow( this._timelineWindow = new Matrix.TimelineWindow(
MatrixClientPeg.get(), this.props.room, MatrixClientPeg.get(), this.props.room,
{windowLimit: TIMELINE_CAP}); {windowLimit: TIMELINE_CAP});
@ -567,7 +580,8 @@ var TimelinePanel = React.createClass({
return; return;
} }
if (eventId) { if (eventId) {
this.refs.messagePanel.scrollToEvent(eventId, pixelOffset); this.refs.messagePanel.scrollToEvent(eventId, pixelOffset,
offsetBase);
} else { } else {
this.refs.messagePanel.scrollToBottom(); this.refs.messagePanel.scrollToBottom();
} }