From 8cf2607415ad7bdb837fa0de15dc9c28a353bae8 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 12 Mar 2019 16:30:06 +0100 Subject: [PATCH 01/10] use AutoHideScrollbar in ScrollPanel --- res/css/structures/_RoomView.scss | 2 + .../structures/AutoHideScrollbar.js | 1 + src/components/structures/ScrollPanel.js | 40 +++++++++---------- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index f15552e484a..1b639928e02 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -84,6 +84,7 @@ limitations under the License. display: flex; flex-direction: column; flex: 1; + min-width: 0; } .mx_RoomView_body .mx_RoomView_timeline { @@ -111,6 +112,7 @@ limitations under the License. .mx_RoomView_messagePanel { width: 100%; overflow-y: auto; + flex: 1 1 0; } .mx_RoomView_messagePanelSearchSpinner { diff --git a/src/components/structures/AutoHideScrollbar.js b/src/components/structures/AutoHideScrollbar.js index 0f93f204078..72d48a20841 100644 --- a/src/components/structures/AutoHideScrollbar.js +++ b/src/components/structures/AutoHideScrollbar.js @@ -121,6 +121,7 @@ export default class AutoHideScrollbar extends React.Component { render() { return (
diff --git a/src/components/structures/ScrollPanel.js b/src/components/structures/ScrollPanel.js index ee4045c91e4..cdb79686adc 100644 --- a/src/components/structures/ScrollPanel.js +++ b/src/components/structures/ScrollPanel.js @@ -15,14 +15,13 @@ limitations under the License. */ const React = require("react"); -const ReactDOM = require("react-dom"); import PropTypes from 'prop-types'; import Promise from 'bluebird'; import { KeyCode } from '../../Keyboard'; -import sdk from '../../index.js'; +import AutoHideScrollbar from "./AutoHideScrollbar"; const DEBUG_SCROLL = false; -// var DEBUG_SCROLL = true; +// const DEBUG_SCROLL = true; // The amount of extra scroll distance to allow prior to unfilling. // See _getExcessHeight. @@ -129,11 +128,6 @@ module.exports = React.createClass({ */ onScroll: PropTypes.func, - /* onResize: a callback which is called whenever the Gemini scroll - * panel is resized - */ - onResize: PropTypes.func, - /* className: classnames to add to the top-level div */ className: PropTypes.string, @@ -150,7 +144,6 @@ module.exports = React.createClass({ onFillRequest: function(backwards) { return Promise.resolve(false); }, onUnfillRequest: function(backwards, scrollToken) {}, onScroll: function() {}, - onResize: function() {}, }; }, @@ -185,6 +178,16 @@ module.exports = React.createClass({ debuglog("Scroll event: offset now:", sn.scrollTop, "_lastSetScroll:", this._lastSetScroll); + // ignore scroll events where scrollTop hasn't changed, + // appears to happen when the layout changes outside + // of the scroll container, like resizing the right panel. + if (sn.scrollTop === this._lastEventScroll) { + debuglog("ignore scroll event with same scrollTop as before"); + return; + } + + this._lastEventScroll = sn.scrollTop; + // Sometimes we see attempts to write to scrollTop essentially being // ignored. (Or rather, it is successfully written, but on the next // scroll event, it's been reset again). @@ -225,9 +228,7 @@ module.exports = React.createClass({ onResize: function() { this.clearBlockShrinking(); - this.props.onResize(); this.checkScroll(); - if (this._gemScroll) this._gemScroll.forceUpdate(); }, // after an update to the contents of the panel, check that the scroll is @@ -672,17 +673,17 @@ module.exports = React.createClass({ throw new Error("ScrollPanel._getScrollNode called when unmounted"); } - if (!this._gemScroll) { + if (!this._divScroll) { // Likewise, we should have the ref by this point, but if not // turn the NPE into something meaningful. throw new Error("ScrollPanel._getScrollNode called before gemini ref collected"); } - return this._gemScroll.scrollbar.getViewElement(); + return this._divScroll; }, - _collectGeminiScroll: function(gemScroll) { - this._gemScroll = gemScroll; + _collectScroll: function(divScroll) { + this._divScroll = divScroll; }, /** @@ -724,19 +725,18 @@ module.exports = React.createClass({ }, render: function() { - const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper"); // TODO: the classnames on the div and ol could do with being updated to // reflect the fact that we don't necessarily contain a list of messages. // it's not obvious why we have a separate div and ol anyway. - return (
    { this.props.children }
-
- ); + + ); }, }); From 27070b314960ed3cfc0e4415e96534accff821a3 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 12 Mar 2019 16:33:05 +0100 Subject: [PATCH 02/10] remove onChildResize in RoomView as it's unused --- src/components/structures/RoomView.js | 15 ++------------- src/components/views/rooms/MessageComposer.js | 5 ----- .../views/rooms/MessageComposerInput.js | 4 ---- 3 files changed, 2 insertions(+), 22 deletions(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 2a1c7fe79e1..d7488a2558f 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -1355,8 +1355,7 @@ module.exports = React.createClass({ const showBar = this.refs.messagePanel.canJumpToReadMarker(); if (this.state.showTopUnreadMessagesBar != showBar) { - this.setState({showTopUnreadMessagesBar: showBar}, - this.onChildResize); + this.setState({showTopUnreadMessagesBar: showBar}); } }, @@ -1399,7 +1398,7 @@ module.exports = React.createClass({ }; }, - onResize: function(e) { + onResize: function() { // It seems flexbox doesn't give us a way to constrain the auxPanel height to have // a minimum of the height of the video element, whilst also capping it from pushing out the page // so we have to do it via JS instead. In this implementation we cap the height by putting @@ -1417,9 +1416,6 @@ module.exports = React.createClass({ if (auxPanelMaxHeight < 50) auxPanelMaxHeight = 50; this.setState({auxPanelMaxHeight: auxPanelMaxHeight}); - - // changing the maxHeight on the auxpanel will trigger a callback go - // onChildResize, so no need to worry about that here. }, onFullscreenClick: function() { @@ -1449,10 +1445,6 @@ module.exports = React.createClass({ this.forceUpdate(); // TODO: just update the voip buttons }, - onChildResize: function() { - // no longer anything to do here - }, - onStatusBarVisible: function() { if (this.unmounted) return; this.setState({ @@ -1645,7 +1637,6 @@ module.exports = React.createClass({ isPeeking={myMembership !== "join"} onInviteClick={this.onInviteButtonClick} onStopWarningClick={this.onStopAloneWarningClick} - onResize={this.onChildResize} onVisible={this.onStatusBarVisible} onHidden={this.onStatusBarHidden} />; @@ -1714,7 +1705,6 @@ module.exports = React.createClass({ draggingFile={this.state.draggingFile} displayConfCallNotification={this.state.displayConfCallNotification} maxHeight={this.state.auxPanelMaxHeight} - onResize={this.onChildResize} showApps={this.state.showApps} hideAppsDrawer={false} > { aux } @@ -1730,7 +1720,6 @@ module.exports = React.createClass({ messageComposer = this.messageComposerInput = c} key="controls_input" - onResize={this.props.onResize} room={this.props.room} placeholder={placeholderText} onFilesPasted={this.uploadFiles} @@ -505,10 +504,6 @@ export default class MessageComposer extends React.Component { } MessageComposer.propTypes = { - // a callback which is called when the height of the composer is - // changed due to a change in content. - onResize: PropTypes.func, - // js-sdk Room object room: PropTypes.object.isRequired, diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index 6b80902c8f8..cbea2bccb9a 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -135,10 +135,6 @@ function rangeEquals(a: Range, b: Range): boolean { */ export default class MessageComposerInput extends React.Component { static propTypes = { - // a callback which is called when the height of the composer is - // changed due to a change in content. - onResize: PropTypes.func, - // js-sdk Room object room: PropTypes.object.isRequired, From 735b4f6fcfbff30e0064568282f17e4dc8b765bc Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 12 Mar 2019 16:36:12 +0100 Subject: [PATCH 03/10] create ResizeNotifier to derive which areas of the app resize and emit --- src/components/structures/FilePanel.js | 1 + src/components/structures/LoggedInView.js | 2 + src/components/structures/MainSplit.js | 1 + src/components/structures/MatrixChat.js | 3 ++ src/components/structures/MessagePanel.js | 3 +- src/components/structures/RightPanel.js | 2 +- src/components/structures/RoomView.js | 13 ++++-- src/components/structures/ScrollPanel.js | 3 ++ src/components/structures/TimelinePanel.js | 1 + src/utils/ResizeNotifier.js | 52 ++++++++++++++++++++++ 10 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 src/utils/ResizeNotifier.js diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 927449750c9..e35a39a1073 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -123,6 +123,7 @@ const FilePanel = React.createClass({ timelineSet={this.state.timelineSet} showUrlPreview = {false} tileShape="file_grid" + resizeNotifier={this.props.resizeNotifier} empty={_t('There are no visible files in this room')} /> ); diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index c6c1be67ec5..5267dba715d 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -173,6 +173,7 @@ const LoggedInView = React.createClass({ }, onResized: (size) => { window.localStorage.setItem("mx_lhs_size", '' + size); + this.props.resizeNotifier.notifyLeftHandleResized(); }, }; const resizer = new Resizer( @@ -448,6 +449,7 @@ const LoggedInView = React.createClass({ disabled={this.props.middleDisabled} collapsedRhs={this.props.collapsedRhs} ConferenceHandler={this.props.ConferenceHandler} + resizeNotifier={this.props.resizeNotifier} />; break; diff --git a/src/components/structures/MainSplit.js b/src/components/structures/MainSplit.js index 0427130eeac..c0bf74d02c7 100644 --- a/src/components/structures/MainSplit.js +++ b/src/components/structures/MainSplit.js @@ -27,6 +27,7 @@ export default class MainSplit extends React.Component { _onResized(size) { window.localStorage.setItem("mx_rhs_size", size); + this.props.resizeNotifier.notifyRightHandleResized(); } _createResizer() { diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 2622a6bf93b..a9b34c9058a 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -48,6 +48,7 @@ import { _t, getCurrentLanguage } from '../../languageHandler'; import SettingsStore, {SettingLevel} from "../../settings/SettingsStore"; import { startAnyRegistrationFlow } from "../../Registration.js"; import { messageForSyncError } from '../../utils/ErrorUtils'; +import ResizeNotifier from "../../utils/ResizeNotifier"; const AutoDiscovery = Matrix.AutoDiscovery; @@ -194,6 +195,7 @@ export default React.createClass({ hideToSRUsers: false, syncError: null, // If the current syncing status is ERROR, the error object, otherwise null. + resizeNotifier: new ResizeNotifier(), }; return s; }, @@ -1661,6 +1663,7 @@ export default React.createClass({ dis.dispatch({ action: 'show_right_panel' }); } + this.state.resizeNotifier.notifyWindowResized(); this._windowWidth = window.innerWidth; }, diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index b1f88a62212..aec2f8cbe17 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -703,7 +703,8 @@ module.exports = React.createClass({ onFillRequest={this.props.onFillRequest} onUnfillRequest={this.props.onUnfillRequest} style={style} - stickyBottom={this.props.stickyBottom}> + stickyBottom={this.props.stickyBottom} + resizeNotifier={this.props.resizeNotifier}> { topSpinner } { this._getEventTiles() } { whoIsTyping } diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js index 5c745b04cc2..93cbff32330 100644 --- a/src/components/structures/RightPanel.js +++ b/src/components/structures/RightPanel.js @@ -193,7 +193,7 @@ export default class RightPanel extends React.Component { } else if (this.state.phase === RightPanel.Phase.NotificationPanel) { panel = ; } else if (this.state.phase === RightPanel.Phase.FilePanel) { - panel = ; + panel = ; } const classes = classNames("mx_RightPanel", "mx_fadable", { diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index d7488a2558f..5a914cbd1cb 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -392,7 +392,7 @@ module.exports = React.createClass({ this._updateConfCallNotification(); window.addEventListener('beforeunload', this.onPageUnload); - window.addEventListener('resize', this.onResize); + this.props.resizeNotifier.on("middlePanelResized", this.onResize); this.onResize(); document.addEventListener("keydown", this.onKeyDown); @@ -472,7 +472,7 @@ module.exports = React.createClass({ } window.removeEventListener('beforeunload', this.onPageUnload); - window.removeEventListener('resize', this.onResize); + this.props.resizeNotifier.removeListener("middlePanelResized", this.onResize); document.removeEventListener("keydown", this.onKeyDown); @@ -1829,6 +1829,7 @@ module.exports = React.createClass({ className="mx_RoomView_messagePanel" membersLoaded={this.state.membersLoaded} permalinkCreator={this.state.permalinkCreator} + resizeNotifier={this.props.resizeNotifier} />); let topUnreadMessagesBar = null; @@ -1861,7 +1862,7 @@ module.exports = React.createClass({ }, ); - const rightPanel = this.state.room ? : undefined; + const rightPanel = this.state.room ? : undefined; return (
@@ -1877,7 +1878,11 @@ module.exports = React.createClass({ onLeaveClick={(myMembership === "join") ? this.onLeaveClick : null} e2eStatus={this.state.e2eStatus} /> - +
{ auxPanel }
diff --git a/src/components/structures/ScrollPanel.js b/src/components/structures/ScrollPanel.js index cdb79686adc..799c88140e2 100644 --- a/src/components/structures/ScrollPanel.js +++ b/src/components/structures/ScrollPanel.js @@ -149,6 +149,8 @@ module.exports = React.createClass({ componentWillMount: function() { this._pendingFillRequests = {b: null, f: null}; + this.props.resizeNotifier.on("middlePanelResized", this.onResize); + this.resetScrollState(); }, @@ -171,6 +173,7 @@ module.exports = React.createClass({ // // (We could use isMounted(), but facebook have deprecated that.) this.unmounted = true; + this.props.resizeNotifier.removeListener("middlePanelResized", this.onResize); }, onScroll: function(ev) { diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index 911499e3149..537862ab1e2 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -1227,6 +1227,7 @@ var TimelinePanel = React.createClass({ alwaysShowTimestamps={this.state.alwaysShowTimestamps} className={this.props.className} tileShape={this.props.tileShape} + resizeNotifier={this.props.resizeNotifier} /> ); }, diff --git a/src/utils/ResizeNotifier.js b/src/utils/ResizeNotifier.js new file mode 100644 index 00000000000..43578ebcaa8 --- /dev/null +++ b/src/utils/ResizeNotifier.js @@ -0,0 +1,52 @@ +/* +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/** + * Fires when the middle panel has been resized. + * @event module:utils~ResizeNotifier#"middlePanelResized" + */ +import { EventEmitter } from "events"; +import { throttle } from "lodash"; + +export default class ResizeNotifier extends EventEmitter { + + constructor() { + super(); + // with default options, will call fn once at first call, and then every x ms + // if there was another call in that timespan + this._throttledMiddlePanel = throttle(() => this.emit("middlePanelResized"), 200); + } + + notifyBannersChanged() { + this.emit("middlePanelResized"); + } + + // can be called in quick succession + notifyLeftHandleResized() { + this._throttledMiddlePanel(); + } + + // can be called in quick succession + notifyRightHandleResized() { + this._throttledMiddlePanel(); + } + + // can be called in quick succession + notifyWindowResized() { + this._throttledMiddlePanel(); + } +} + From 56aeb5194a5e584c30b28e25abff3ab7b4fce33c Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 12 Mar 2019 16:37:20 +0100 Subject: [PATCH 04/10] emit timeline_resize in MatrixChat based on ResizeNotifier as it's used in PersistentElement which could be used at various places --- src/components/structures/MatrixChat.js | 8 ++++++++ src/components/structures/MessagePanel.js | 5 ----- src/components/structures/RoomView.js | 5 ----- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index a9b34c9058a..6e05138e2d3 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -318,6 +318,9 @@ export default React.createClass({ // N.B. we don't call the whole of setTheme() here as we may be // racing with the theme CSS download finishing from index.js Tinter.tint(); + + // For PersistentElement + this.state.resizeNotifier.on("middlePanelResized", this._dispatchTimelineResize); }, componentDidMount: function() { @@ -400,6 +403,7 @@ export default React.createClass({ dis.unregister(this.dispatcherRef); window.removeEventListener("focus", this.onFocus); window.removeEventListener('resize', this.handleResize); + this.state.resizeNotifier.removeListener("middlePanelResized", this._dispatchTimelineResize); }, componentWillUpdate: function(props, state) { @@ -1667,6 +1671,10 @@ export default React.createClass({ this._windowWidth = window.innerWidth; }, + _dispatchTimelineResize() { + dis.dispatch({ action: 'timeline_resize' }, true); + }, + onRoomCreated: function(roomId) { dis.dispatch({ action: "view_room", diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index aec2f8cbe17..fecf5f1ad7d 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -21,7 +21,6 @@ import PropTypes from 'prop-types'; import classNames from 'classnames'; import shouldHideEvent from '../../shouldHideEvent'; import {wantsDateSeparator} from '../../DateUtils'; -import dis from "../../dispatcher"; import sdk from '../../index'; import MatrixClientPeg from '../../MatrixClientPeg'; @@ -665,10 +664,6 @@ module.exports = React.createClass({ } }, - onResize: function() { - dis.dispatch({ action: 'timeline_resize' }, true); - }, - render: function() { const ScrollPanel = sdk.getComponent("structures.ScrollPanel"); const WhoIsTypingTile = sdk.getComponent("rooms.WhoIsTypingTile"); diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 5a914cbd1cb..507fbf6979d 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -856,10 +856,6 @@ module.exports = React.createClass({ } }, - onSearchResultsResize: function() { - dis.dispatch({ action: 'timeline_resize' }, true); - }, - onSearchResultsFillRequest: function(backwards) { if (!backwards) { return Promise.resolve(false); @@ -1794,7 +1790,6 @@ module.exports = React.createClass({
  • { this.getSearchResultTiles() } From c8123ec66536c0e10e07c607b7a116d4a444ab6d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 12 Mar 2019 16:38:30 +0100 Subject: [PATCH 05/10] use AutoHideScrollbar in memberlist --- res/css/views/rooms/_MemberList.scss | 5 +++++ src/components/views/rooms/MemberList.js | 7 ++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/res/css/views/rooms/_MemberList.scss b/res/css/views/rooms/_MemberList.scss index 9f2b5da930f..cac97cb60d1 100644 --- a/res/css/views/rooms/_MemberList.scss +++ b/res/css/views/rooms/_MemberList.scss @@ -20,6 +20,7 @@ limitations under the License. flex: 1; display: flex; flex-direction: column; + min-height: 0; .mx_Spinner { flex: 1 0 auto; @@ -35,6 +36,10 @@ limitations under the License. margin-top: 8px; margin-bottom: 4px; } + + .mx_AutoHideScrollbar { + flex: 1 1 0; + } } .mx_MemberList_chevron { diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index f9ce672c167..29f49f1691c 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -20,6 +20,8 @@ import React from 'react'; import { _t } from '../../../languageHandler'; import SdkConfig from '../../../SdkConfig'; import dis from '../../../dispatcher'; +import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; + const MatrixClientPeg = require("../../../MatrixClientPeg"); const sdk = require('../../../index'); const rate_limited_func = require('../../../ratelimitedfunc'); @@ -439,7 +441,6 @@ module.exports = React.createClass({ const SearchBox = sdk.getComponent('structures.SearchBox'); const TruncatedList = sdk.getComponent("elements.TruncatedList"); - const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper"); const cli = MatrixClientPeg.get(); const room = cli.getRoom(this.props.roomId); @@ -466,7 +467,7 @@ module.exports = React.createClass({ return (
    { inviteButton } - +
    - + Date: Tue, 12 Mar 2019 16:38:42 +0100 Subject: [PATCH 06/10] use AutoHideScrollbar in member info panel --- src/components/views/rooms/MemberInfo.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index 3ada730ec84..35161dedf7d 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -44,6 +44,7 @@ import SdkConfig from '../../../SdkConfig'; import MultiInviter from "../../../utils/MultiInviter"; import SettingsStore from "../../../settings/SettingsStore"; import E2EIcon from "./E2EIcon"; +import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; module.exports = withMatrixClient(React.createClass({ displayName: 'MemberInfo', @@ -1003,7 +1004,7 @@ module.exports = withMatrixClient(React.createClass({ { roomMemberDetails }
    - +
    { this._renderUserOptions() } @@ -1015,7 +1016,7 @@ module.exports = withMatrixClient(React.createClass({ { spinner }
    -
    +
    ); }, From 58f26ee9b070b1eca2943bdca5e14a96ca390a84 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 12 Mar 2019 17:29:16 +0100 Subject: [PATCH 07/10] emit resize event when banners are shown/hidden to restore scroll pos --- src/Notifier.js | 12 +++++++++++- src/components/structures/LoggedInView.js | 19 +++++++++++++------ src/components/structures/MatrixChat.js | 13 ++++++++++--- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/Notifier.js b/src/Notifier.js index 80e8be10846..ab8009c4579 100644 --- a/src/Notifier.js +++ b/src/Notifier.js @@ -220,7 +220,17 @@ const Notifier = { } }, - isToolbarHidden: function() { + shouldShowToolbar: function() { + const client = MatrixClientPeg.get(); + if (!client) { + return false; + } + const isGuest = client.isGuest(); + return !isGuest && Notifier.supportsDesktopNotifications() && + !Notifier.isEnabled() && !Notifier._isToolbarHidden(); + }, + + _isToolbarHidden: function() { // Check localStorage for any such meta data if (global.localStorage) { return global.localStorage.getItem("notifications_hidden") === "true"; diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index 5267dba715d..c22c217e5fb 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -22,7 +22,6 @@ import PropTypes from 'prop-types'; import { DragDropContext } from 'react-beautiful-dnd'; import { KeyCode, isOnlyCtrlOrCmdKeyEvent } from '../../Keyboard'; -import Notifier from '../../Notifier'; import PageTypes from '../../PageTypes'; import CallMediaHandler from '../../CallMediaHandler'; import sdk from '../../index'; @@ -121,6 +120,18 @@ const LoggedInView = React.createClass({ this._matrixClient.on("RoomState.events", this.onRoomStateEvents); }, + componentDidUpdate(prevProps) { + // attempt to guess when a banner was opened or closed + if ( + (prevProps.showCookieBar !== this.props.showCookieBar) || + (prevProps.hasNewVersion !== this.props.hasNewVersion) || + (prevProps.userHasGeneratedPassword !== this.props.userHasGeneratedPassword) || + (prevProps.showNotifierToolbar !== this.props.showNotifierToolbar) + ) { + this.props.resizeNotifier.notifyBannersChanged(); + } + }, + componentWillUnmount: function() { document.removeEventListener('keydown', this._onKeyDown); this._matrixClient.removeListener("accountData", this.onAccountData); @@ -491,7 +502,6 @@ const LoggedInView = React.createClass({ }); let topBar; - const isGuest = this.props.matrixClient.isGuest(); if (this.state.syncErrorData && this.state.syncErrorData.error.errcode === 'M_RESOURCE_LIMIT_EXCEEDED') { topBar = ; } else if (this.state.userHasGeneratedPassword) { topBar = ; - } else if ( - !isGuest && Notifier.supportsDesktopNotifications() && - !Notifier.isEnabled() && !Notifier.isToolbarHidden() - ) { + } else if (this.props.showNotifierToolbar) { topBar = ; } diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 6e05138e2d3..a7192b96cbf 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -29,6 +29,7 @@ import PlatformPeg from "../../PlatformPeg"; import SdkConfig from "../../SdkConfig"; import * as RoomListSorter from "../../RoomListSorter"; import dis from "../../dispatcher"; +import Notifier from '../../Notifier'; import Modal from "../../Modal"; import Tinter from "../../Tinter"; @@ -196,6 +197,7 @@ export default React.createClass({ syncError: null, // If the current syncing status is ERROR, the error object, otherwise null. resizeNotifier: new ResizeNotifier(), + showNotifierToolbar: Notifier.shouldShowToolbar(), }; return s; }, @@ -644,8 +646,9 @@ export default React.createClass({ case 'view_invite': showRoomInviteDialog(payload.roomId); break; - case 'notifier_enabled': - this.forceUpdate(); + case 'notifier_enabled': { + this.setState({showNotifierToolbar: Notifier.shouldShowToolbar()}); + } break; case 'hide_left_panel': this.setState({ @@ -1180,6 +1183,7 @@ export default React.createClass({ */ _onLoggedIn: async function() { this.setStateForNewView({view: VIEWS.LOGGED_IN}); + this.setState({showNotifierToolbar: Notifier.shouldShowToolbar()}); if (this._is_registered) { this._is_registered = false; @@ -1672,7 +1676,10 @@ export default React.createClass({ }, _dispatchTimelineResize() { - dis.dispatch({ action: 'timeline_resize' }, true); + // prevent dispatch from within dispatch error + setTimeout(() => { + dis.dispatch({ action: 'timeline_resize' }, true); + }, 0); }, onRoomCreated: function(roomId) { From 9541cc175f357fc0cdeab5299239eac199d58b9a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 12 Mar 2019 18:00:05 +0100 Subject: [PATCH 08/10] use ResizeNotifier as well to relayout room list --- src/components/structures/LeftPanel.js | 2 +- src/components/structures/LoggedInView.js | 2 +- src/components/views/rooms/RoomList.js | 10 ++++++---- src/utils/ResizeNotifier.js | 8 ++++++++ 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/components/structures/LeftPanel.js b/src/components/structures/LeftPanel.js index 21438c597cd..95b57a0ca57 100644 --- a/src/components/structures/LeftPanel.js +++ b/src/components/structures/LeftPanel.js @@ -234,7 +234,7 @@ const LeftPanel = React.createClass({ diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index c22c217e5fb..4771c6f4872 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -543,7 +543,7 @@ const LoggedInView = React.createClass({
    diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index 227dd318edf..2de9918e6e3 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -212,7 +212,7 @@ module.exports = React.createClass({ this._checkSubListsOverflow(); this.resizer.attach(); - window.addEventListener("resize", this.onWindowResize); + this.props.resizeNotifier.on("leftPanelResized", this.onResize); this.mounted = true; }, @@ -260,7 +260,6 @@ module.exports = React.createClass({ componentWillUnmount: function() { this.mounted = false; - window.removeEventListener("resize", this.onWindowResize); dis.unregister(this.dispatcherRef); if (MatrixClientPeg.get()) { MatrixClientPeg.get().removeListener("Room", this.onRoom); @@ -272,6 +271,8 @@ module.exports = React.createClass({ MatrixClientPeg.get().removeListener("Group.myMembership", this._onGroupMyMembership); MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvents); } + this.props.resizeNotifier.removeListener("leftPanelResized", this.onResize); + if (this._tagStoreToken) { this._tagStoreToken.remove(); @@ -293,13 +294,14 @@ module.exports = React.createClass({ this._delayedRefreshRoomList.cancelPendingCall(); }, - onWindowResize: function() { + + onResize: function() { if (this.mounted && this._layout && this.resizeContainer && Array.isArray(this._layoutSections) ) { this._layout.update( this._layoutSections, - this.resizeContainer.offsetHeight + this.resizeContainer.offsetHeight, ); } }, diff --git a/src/utils/ResizeNotifier.js b/src/utils/ResizeNotifier.js index 43578ebcaa8..ff4b79091be 100644 --- a/src/utils/ResizeNotifier.js +++ b/src/utils/ResizeNotifier.js @@ -31,11 +31,13 @@ export default class ResizeNotifier extends EventEmitter { } notifyBannersChanged() { + this.emit("leftPanelResized"); this.emit("middlePanelResized"); } // can be called in quick succession notifyLeftHandleResized() { + // don't emit event for own region this._throttledMiddlePanel(); } @@ -46,6 +48,12 @@ export default class ResizeNotifier extends EventEmitter { // can be called in quick succession notifyWindowResized() { + // no need to throttle this one, + // also it could make scrollbars appear for + // a split second when the room list manual layout is now + // taller than the available space + this.emit("leftPanelResized"); + this._throttledMiddlePanel(); } } From 955ec14db98816dba3fe8a53de769cf11848df1e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 14 Mar 2019 15:04:09 +0100 Subject: [PATCH 09/10] chrome apparently anchors the scroll position, which fights against our restore position logic. Disable it like this. --- res/css/structures/_RoomView.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 1b639928e02..ced0b9eab36 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -113,6 +113,7 @@ limitations under the License. width: 100%; overflow-y: auto; flex: 1 1 0; + overflow-anchor: none; } .mx_RoomView_messagePanelSearchSpinner { From 0b8196343f008d9fd2e22cd54c2c3be85f7c5c8c Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 15 Mar 2019 09:57:26 +0100 Subject: [PATCH 10/10] fix some tests --- test/components/structures/MessagePanel-test.js | 9 ++++++++- test/components/structures/ScrollPanel-test.js | 5 ++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/test/components/structures/MessagePanel-test.js b/test/components/structures/MessagePanel-test.js index 0a51cb89186..c7287f15236 100644 --- a/test/components/structures/MessagePanel-test.js +++ b/test/components/structures/MessagePanel-test.js @@ -21,6 +21,7 @@ const ReactDOM = require("react-dom"); const TestUtils = require('react-addons-test-utils'); const expect = require('expect'); import sinon from 'sinon'; +import { EventEmitter } from "events"; const sdk = require('matrix-react-sdk'); @@ -46,8 +47,14 @@ const WrappedMessagePanel = React.createClass({ }; }, + getInitialState: function() { + return { + resizeNotifier: new EventEmitter(), + }; + }, + render: function() { - return ; + return ; }, }); diff --git a/test/components/structures/ScrollPanel-test.js b/test/components/structures/ScrollPanel-test.js index 0e091cdddf6..41d0f4469b1 100644 --- a/test/components/structures/ScrollPanel-test.js +++ b/test/components/structures/ScrollPanel-test.js @@ -19,6 +19,7 @@ const ReactDOM = require("react-dom"); const ReactTestUtils = require('react-addons-test-utils'); const expect = require('expect'); import Promise from 'bluebird'; +import { EventEmitter } from "events"; const sdk = require('matrix-react-sdk'); @@ -29,6 +30,7 @@ const Tester = React.createClass({ getInitialState: function() { return { tileKeys: [], + resizeNotifier: new EventEmitter(), }; }, @@ -130,7 +132,8 @@ const Tester = React.createClass({ return ( + onFillRequest={this._onFillRequest} + resizeNotifier={this.state.resizeNotifier}> { tiles } );