Merge pull request #2766 from matrix-org/bwindels/scrolling
Scroll investigation changes
This commit is contained in:
commit
99f82a3de9
13 changed files with 60 additions and 72 deletions
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
},
|
||||
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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} />;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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} />;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 (
|
||||
|
|
Loading…
Reference in a new issue