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.
*
* 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);
}
},

View File

@ -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*

View File

@ -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();
}