Merge pull request #2766 from matrix-org/bwindels/scrolling

Scroll investigation changes
This commit is contained in:
Bruno Windels 2019-03-11 09:57:13 +00:00 committed by GitHub
commit 99f82a3de9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 60 additions and 72 deletions

View file

@ -387,7 +387,7 @@ module.exports = React.createClass({
ret.push(<MemberEventListSummary key={key}
events={summarisedEvents}
onToggle={this._onWidgetLoad} // Update scroll state
onToggle={this._onHeightChanged} // Update scroll state
startExpanded={highlightInMels}
>
{ eventTiles }
@ -517,7 +517,7 @@ module.exports = React.createClass({
data-scroll-tokens={scrollToken}>
<EventTile mxEvent={mxEv} continuation={continuation}
isRedacted={mxEv.isRedacted()}
onWidgetLoad={this._onWidgetLoad}
onHeightChanged={this._onHeightChanged}
readReceipts={readReceipts}
readReceiptMap={this._readReceiptMap}
showUrlPreview={this.props.showUrlPreview}
@ -625,7 +625,7 @@ module.exports = React.createClass({
// once dynamic content in the events load, make the scrollPanel check the
// scroll offsets.
_onWidgetLoad: function() {
_onHeightChanged: function() {
const scrollPanel = this.refs.scrollPanel;
if (scrollPanel) {
scrollPanel.forceUpdate();

View file

@ -1186,7 +1186,7 @@ module.exports = React.createClass({
// once dynamic content in the search results load, make the scrollPanel check
// the scroll offsets.
const onWidgetLoad = () => {
const onHeightChanged = () => {
const scrollPanel = this.refs.searchResultsPanel;
if (scrollPanel) {
scrollPanel.checkScroll();
@ -1231,7 +1231,7 @@ module.exports = React.createClass({
searchHighlights={this.state.searchHighlights}
resultLink={resultLink}
permalinkCreator={this.state.permalinkCreator}
onWidgetLoad={onWidgetLoad} />);
onHeightChanged={onHeightChanged} />);
}
return ret;
},

View file

@ -79,26 +79,6 @@ if (DEBUG_SCROLL) {
* offset as normal.
*/
function createTimelineResizeDetector(scrollNode, itemlist, callback) {
if (typeof ResizeObserver !== "undefined") {
const ro = new ResizeObserver(callback);
ro.observe(itemlist);
return ro;
} else if (typeof IntersectionObserver !== "undefined") {
const threshold = [];
for (let i = 0; i <= 1000; ++i) {
threshold.push(i / 1000);
}
const io = new IntersectionObserver(
callback,
{root: scrollNode, threshold},
);
io.observe(itemlist);
return io;
}
}
module.exports = React.createClass({
displayName: 'ScrollPanel',
@ -181,12 +161,6 @@ module.exports = React.createClass({
componentDidMount: function() {
this.checkScroll();
this._timelineSizeObserver = createTimelineResizeDetector(
this._getScrollNode(),
this.refs.itemlist,
() => { this._restoreSavedScrollState(); },
);
},
componentDidUpdate: function() {
@ -204,10 +178,6 @@ module.exports = React.createClass({
//
// (We could use isMounted(), but facebook have deprecated that.)
this.unmounted = true;
if (this._timelineSizeObserver) {
this._timelineSizeObserver.disconnect();
this._timelineSizeObserver = null;
}
},
onScroll: function(ev) {
@ -601,16 +571,17 @@ module.exports = React.createClass({
}
const scrollNode = this._getScrollNode();
const scrollBottom = scrollNode.scrollTop + scrollNode.clientHeight;
const scrollTop = scrollNode.scrollTop;
const viewportBottom = scrollTop + scrollNode.clientHeight;
const nodeBottom = node.offsetTop + node.clientHeight;
const scrollDelta = nodeBottom + pixelOffset - scrollBottom;
const intendedViewportBottom = nodeBottom + pixelOffset;
const scrollDelta = intendedViewportBottom - viewportBottom;
debuglog("ScrollPanel: scrolling to token '" + scrollToken + "'+" +
pixelOffset + " (delta: "+scrollDelta+")");
if (scrollDelta != 0) {
this._setScrollTop(scrollNode.scrollTop + scrollDelta);
if (scrollDelta !== 0) {
this._setScrollTop(scrollTop + scrollDelta);
}
},
@ -622,7 +593,7 @@ module.exports = React.createClass({
}
const scrollNode = this._getScrollNode();
const scrollBottom = scrollNode.scrollTop + scrollNode.clientHeight;
const viewportBottom = scrollNode.scrollTop + scrollNode.clientHeight;
const itemlist = this.refs.itemlist;
const messages = itemlist.children;
@ -636,7 +607,7 @@ module.exports = React.createClass({
node = messages[i];
// break at the first message (coming from the bottom)
// that has it's offsetTop above the bottom of the viewport.
if (node.offsetTop < scrollBottom) {
if (node.offsetTop < viewportBottom) {
// Use this node as the scrollToken
break;
}
@ -652,7 +623,7 @@ module.exports = React.createClass({
this.scrollState = {
stuckAtBottom: false,
trackedScrollToken: node.dataset.scrollTokens.split(',')[0],
pixelOffset: scrollBottom - nodeBottom,
pixelOffset: viewportBottom - nodeBottom,
};
},

View file

@ -31,7 +31,7 @@ export default class ReplyThread extends React.Component {
// the latest event in this chain of replies
parentEv: PropTypes.instanceOf(MatrixEvent),
// called when the ReplyThread contents has changed, including EventTiles thereof
onWidgetLoad: PropTypes.func.isRequired,
onHeightChanged: PropTypes.func.isRequired,
permalinkCreator: PropTypes.instanceOf(RoomPermalinkCreator).isRequired,
};
@ -160,11 +160,11 @@ export default class ReplyThread extends React.Component {
};
}
static makeThread(parentEv, onWidgetLoad, permalinkCreator, ref) {
static makeThread(parentEv, onHeightChanged, permalinkCreator, ref) {
if (!ReplyThread.getParentEventId(parentEv)) {
return <div />;
}
return <ReplyThread parentEv={parentEv} onWidgetLoad={onWidgetLoad}
return <ReplyThread parentEv={parentEv} onHeightChanged={onHeightChanged}
ref={ref} permalinkCreator={permalinkCreator} />;
}
@ -175,7 +175,7 @@ export default class ReplyThread extends React.Component {
}
componentDidUpdate() {
this.props.onWidgetLoad();
this.props.onHeightChanged();
}
componentWillUnmount() {
@ -295,7 +295,7 @@ export default class ReplyThread extends React.Component {
{ dateSep }
<EventTile mxEvent={ev}
tileShape="reply"
onWidgetLoad={this.props.onWidgetLoad}
onHeightChanged={this.props.onHeightChanged}
permalinkCreator={this.props.permalinkCreator}
isTwelveHour={SettingsStore.getValue("showTwelveHourTimestamps")} />
</blockquote>;

View file

@ -203,6 +203,17 @@ module.exports = React.createClass({
};
},
propTypes: {
/* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired,
/* already decrypted blob */
decryptedBlob: PropTypes.object,
/* called when the download link iframe is shown */
onHeightChanged: PropTypes.func,
/* the shape of the tile, used */
tileShape: PropTypes.string,
},
contextTypes: {
appConfig: PropTypes.object,
},
@ -248,6 +259,12 @@ module.exports = React.createClass({
this.tint();
},
componentDidUpdate: function(prevProps, prevState) {
if (this.props.onHeightChanged && !prevState.decryptedBlob && this.state.decryptedBlob) {
this.props.onHeightChanged();
}
},
componentWillUnmount: function() {
// Remove this from the list of mounted components
delete mounts[this.id];

View file

@ -34,7 +34,7 @@ export default class MImageBody extends React.Component {
mxEvent: PropTypes.object.isRequired,
/* called when the image has loaded */
onWidgetLoad: PropTypes.func.isRequired,
onHeightChanged: PropTypes.func.isRequired,
/* the maximum image height to use */
maxImageHeight: PropTypes.number,
@ -144,7 +144,7 @@ export default class MImageBody extends React.Component {
}
onImageLoad() {
this.props.onWidgetLoad();
this.props.onHeightChanged();
let loadedImageDimensions;

View file

@ -33,7 +33,7 @@ module.exports = React.createClass({
mxEvent: PropTypes.object.isRequired,
/* called when the video has loaded */
onWidgetLoad: PropTypes.func.isRequired,
onHeightChanged: PropTypes.func.isRequired,
},
getInitialState: function() {
@ -108,7 +108,7 @@ module.exports = React.createClass({
decryptedThumbnailUrl: thumbnailUrl,
decryptedBlob: decryptedBlob,
});
this.props.onWidgetLoad();
this.props.onHeightChanged();
});
}).catch((err) => {
console.warn("Unable to decrypt attachment: ", err);

View file

@ -37,7 +37,7 @@ module.exports = React.createClass({
showUrlPreview: PropTypes.bool,
/* callback called when dynamic content in events are loaded */
onWidgetLoad: PropTypes.func,
onHeightChanged: PropTypes.func,
/* the shape of the tile, used */
tileShape: PropTypes.string,
@ -89,6 +89,6 @@ module.exports = React.createClass({
showUrlPreview={this.props.showUrlPreview}
tileShape={this.props.tileShape}
maxImageHeight={this.props.maxImageHeight}
onWidgetLoad={this.props.onWidgetLoad} />;
onHeightChanged={this.props.onHeightChanged} />;
},
});

View file

@ -52,7 +52,7 @@ module.exports = React.createClass({
showUrlPreview: PropTypes.bool,
/* callback for when our widget has loaded */
onWidgetLoad: PropTypes.func,
onHeightChanged: PropTypes.func,
/* the shape of the tile, used */
tileShape: PropTypes.string,
@ -451,7 +451,7 @@ module.exports = React.createClass({
link={link}
mxEvent={this.props.mxEvent}
onCancelClick={this.onCancelClick}
onWidgetLoad={this.props.onWidgetLoad} />;
onHeightChanged={this.props.onHeightChanged} />;
});
}

View file

@ -129,7 +129,7 @@ module.exports = withMatrixClient(React.createClass({
isSelectedEvent: PropTypes.bool,
/* callback called when dynamic content in events are loaded */
onWidgetLoad: PropTypes.func,
onHeightChanged: PropTypes.func,
/* a list of read-receipts we should show. Each object has a 'roomMember' and 'ts'. */
readReceipts: PropTypes.arrayOf(React.PropTypes.object),
@ -165,8 +165,8 @@ module.exports = withMatrixClient(React.createClass({
getDefaultProps: function() {
return {
// no-op function because onWidgetLoad is optional yet some sub-components assume its existence
onWidgetLoad: function() {},
// no-op function because onHeightChanged is optional yet some sub-components assume its existence
onHeightChanged: function() {},
};
},
@ -223,7 +223,7 @@ module.exports = withMatrixClient(React.createClass({
*/
_onDecrypted: function() {
// we need to re-verify the sending device.
// (we call onWidgetLoad in _verifyEvent to handle the case where decryption
// (we call onHeightChanged in _verifyEvent to handle the case where decryption
// has caused a change in size of the event tile)
this._verifyEvent(this.props.mxEvent);
this.forceUpdate();
@ -245,7 +245,7 @@ module.exports = withMatrixClient(React.createClass({
verified: verified,
}, () => {
// Decryption may have caused a change in size
this.props.onWidgetLoad();
this.props.onHeightChanged();
});
},
@ -667,7 +667,7 @@ module.exports = withMatrixClient(React.createClass({
highlights={this.props.highlights}
highlightLink={this.props.highlightLink}
showUrlPreview={this.props.showUrlPreview}
onWidgetLoad={this.props.onWidgetLoad} />
onHeightChanged={this.props.onHeightChanged} />
</div>
</div>
);
@ -682,7 +682,7 @@ module.exports = withMatrixClient(React.createClass({
highlightLink={this.props.highlightLink}
showUrlPreview={this.props.showUrlPreview}
tileShape={this.props.tileShape}
onWidgetLoad={this.props.onWidgetLoad} />
onHeightChanged={this.props.onHeightChanged} />
</div>
<a
className="mx_EventTile_senderDetailsLink"
@ -704,7 +704,7 @@ module.exports = withMatrixClient(React.createClass({
if (this.props.tileShape === 'reply_preview') {
thread = ReplyThread.makeThread(
this.props.mxEvent,
this.props.onWidgetLoad,
this.props.onHeightChanged,
this.props.permalinkCreator,
'replyThread',
);
@ -723,7 +723,7 @@ module.exports = withMatrixClient(React.createClass({
mxEvent={this.props.mxEvent}
highlights={this.props.highlights}
highlightLink={this.props.highlightLink}
onWidgetLoad={this.props.onWidgetLoad}
onHeightChanged={this.props.onHeightChanged}
showUrlPreview={false} />
</div>
</div>
@ -732,7 +732,7 @@ module.exports = withMatrixClient(React.createClass({
default: {
const thread = ReplyThread.makeThread(
this.props.mxEvent,
this.props.onWidgetLoad,
this.props.onHeightChanged,
this.props.permalinkCreator,
'replyThread',
);
@ -753,7 +753,7 @@ module.exports = withMatrixClient(React.createClass({
highlights={this.props.highlights}
highlightLink={this.props.highlightLink}
showUrlPreview={this.props.showUrlPreview}
onWidgetLoad={this.props.onWidgetLoad} />
onHeightChanged={this.props.onHeightChanged} />
{ keyRequestInfo }
{ editButton }
</div>

View file

@ -32,7 +32,7 @@ module.exports = React.createClass({
link: PropTypes.string.isRequired, // the URL being previewed
mxEvent: PropTypes.object.isRequired, // the Event associated with the preview
onCancelClick: PropTypes.func, // called when the preview's cancel ('hide') button is clicked
onWidgetLoad: PropTypes.func, // called when the preview's contents has loaded
onHeightChanged: PropTypes.func, // called when the preview's contents has loaded
},
getInitialState: function() {
@ -49,7 +49,7 @@ module.exports = React.createClass({
}
this.setState(
{ preview: res },
this.props.onWidgetLoad,
this.props.onHeightChanged,
);
}, (error)=>{
console.error("Failed to get preview for " + this.props.link + " " + error);

View file

@ -92,7 +92,7 @@ module.exports = React.createClass({
</span>
<div className="mx_PinnedEventTile_message">
<MessageEvent mxEvent={this.props.mxEvent} className="mx_PinnedEventTile_body" maxImageHeight={150}
onWidgetLoad={() => {}} // we need to give this, apparently
onHeightChanged={() => {}} // we need to give this, apparently
/>
</div>
</div>

View file

@ -33,7 +33,7 @@ module.exports = React.createClass({
// href for the highlights in this result
resultLink: PropTypes.string,
onWidgetLoad: PropTypes.func,
onHeightChanged: PropTypes.func,
},
render: function() {
@ -58,7 +58,7 @@ module.exports = React.createClass({
ret.push(<EventTile key={eventId+"+"+j} mxEvent={ev} contextual={contextual} highlights={highlights}
permalinkCreator={this.props.permalinkCreator}
highlightLink={this.props.resultLink}
onWidgetLoad={this.props.onWidgetLoad} />);
onHeightChanged={this.props.onHeightChanged} />);
}
}
return (