From f34855573ef203182994eac6d9ecaa027225dd48 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 7 Feb 2019 16:24:26 +0000 Subject: [PATCH 1/6] replace ratelimitedfunc with lodash impl --- src/ratelimitedfunc.js | 60 ++++++++++++------------------------------ 1 file changed, 17 insertions(+), 43 deletions(-) diff --git a/src/ratelimitedfunc.js b/src/ratelimitedfunc.js index 20f6db79b84..1f15f11d910 100644 --- a/src/ratelimitedfunc.js +++ b/src/ratelimitedfunc.js @@ -20,54 +20,28 @@ limitations under the License. * to update the interface once for all of them. * * Note that the function must not take arguments, since the args - * could be different for each invocarion of the function. + * could be different for each invocation of the function. * * The returned function has a 'cancelPendingCall' property which can be called * on unmount or similar to cancel any pending update. */ -module.exports = function(f, minIntervalMs) { - this.lastCall = 0; - this.scheduledCall = undefined; - const self = this; - const wrapper = function() { - const now = Date.now(); - - if (self.lastCall < now - minIntervalMs) { - f.apply(this); - // get the time again now the function has finished, so if it - // took longer than the delay time to execute, it doesn't - // immediately become eligible to run again. - self.lastCall = Date.now(); - } else if (self.scheduledCall === undefined) { - self.scheduledCall = setTimeout( - () => { - self.scheduledCall = undefined; - f.apply(this); - // get time again as per above - self.lastCall = Date.now(); - }, - (self.lastCall + minIntervalMs) - now, - ); - } - }; - - // add the cancelPendingCall property - wrapper.cancelPendingCall = function() { - if (self.scheduledCall) { - clearTimeout(self.scheduledCall); - self.scheduledCall = undefined; - } +import { throttle } from "lodash"; + +export default function ratelimitedfunc(fn, time) { + const throttledFn = throttle(fn, time, { + leading: true, + trailing: true, + }); + const _bind = throttledFn.bind; + throttledFn.bind = function() { + const boundFn = _bind.apply(throttledFn, arguments); + boundFn.cancelPendingCall = throttledFn.cancelPendingCall; + return boundFn; }; - // make sure that cancelPendingCall is copied when react rebinds the - // wrapper - const _bind = wrapper.bind; - wrapper.bind = function() { - const rebound = _bind.apply(this, arguments); - rebound.cancelPendingCall = wrapper.cancelPendingCall; - return rebound; + throttledFn.cancelPendingCall = function() { + throttledFn.cancel(); }; - - return wrapper; -}; + return throttledFn; +} From d2dd1bae135830f144f962a97fa246cc3ca3b260 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 7 Feb 2019 16:31:57 +0000 Subject: [PATCH 2/6] run search after you've stopped typing for 200ms instead of every 500ms --- src/components/structures/SearchBox.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/components/structures/SearchBox.js b/src/components/structures/SearchBox.js index fbcd9a72795..2f777c11633 100644 --- a/src/components/structures/SearchBox.js +++ b/src/components/structures/SearchBox.js @@ -21,7 +21,7 @@ import { _t } from '../../languageHandler'; import { KeyCode } from '../../Keyboard'; import sdk from '../../index'; import dis from '../../dispatcher'; -import rate_limited_func from '../../ratelimitedfunc'; +import { debounce } from 'lodash'; import AccessibleButton from '../../components/views/elements/AccessibleButton'; module.exports = React.createClass({ @@ -67,12 +67,9 @@ module.exports = React.createClass({ this.onSearch(); }, - onSearch: new rate_limited_func( - function() { - this.props.onSearch(this.refs.search.value); - }, - 500, - ), + onSearch: debounce(function() { + this.props.onSearch(this.refs.search.value); + }, 200, {trailing: true}), _onKeyDown: function(ev) { switch (ev.keyCode) { From b6aa72da55e2db5d0532dd8f11edd277430d0863 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 11 Feb 2019 15:40:17 +0100 Subject: [PATCH 3/6] update RoomTile notificationCount through props so updates are consistent --- src/components/structures/RoomSubList.js | 1 + src/components/views/rooms/RoomTile.js | 11 +---------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index 4644d19f614..ca2be85b35a 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -145,6 +145,7 @@ const RoomSubList = React.createClass({ collapsed={this.props.collapsed || false} unread={Unread.doesRoomHaveUnreadMessages(room)} highlight={room.getUnreadNotificationCount('highlight') > 0 || this.props.isInvite} + notificationCount={room.getUnreadNotificationCount()} isInvite={this.props.isInvite} refreshSubList={this._updateSubListCount} incomingCall={null} diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index ed214812b53..f9e9d64b9e3 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -108,13 +108,6 @@ module.exports = React.createClass({ return statusUser._unstable_statusMessage; }, - onRoomTimeline: function(ev, room) { - if (room !== this.props.room) return; - this.setState({ - notificationCount: this.props.room.getUnreadNotificationCount(), - }); - }, - onRoomName: function(room) { if (room !== this.props.room) return; this.setState({ @@ -159,7 +152,6 @@ module.exports = React.createClass({ componentWillMount: function() { MatrixClientPeg.get().on("accountData", this.onAccountData); - MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline); MatrixClientPeg.get().on("Room.name", this.onRoomName); ActiveRoomObserver.addListener(this.props.room.roomId, this._onActiveRoomChange); this.dispatcherRef = dis.register(this.onAction); @@ -179,7 +171,6 @@ module.exports = React.createClass({ const cli = MatrixClientPeg.get(); if (cli) { MatrixClientPeg.get().removeListener("accountData", this.onAccountData); - MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline); MatrixClientPeg.get().removeListener("Room.name", this.onRoomName); } ActiveRoomObserver.removeListener(this.props.room.roomId, this._onActiveRoomChange); @@ -306,7 +297,7 @@ module.exports = React.createClass({ render: function() { const isInvite = this.props.room.getMyMembership() === "invite"; - const notificationCount = this.state.notificationCount; + const notificationCount = this.props.notificationCount; // var highlightCount = this.props.room.getUnreadNotificationCount("highlight"); const notifBadges = notificationCount > 0 && this._shouldShowNotifBadge(); From d069f4a89a4057d5107d1b2452068426d51eefc6 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 11 Feb 2019 15:41:32 +0100 Subject: [PATCH 4/6] not the cause of the bug, but this seems wrong --- src/Unread.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Unread.js b/src/Unread.js index 55e60f2a9a2..9514ec821bd 100644 --- a/src/Unread.js +++ b/src/Unread.js @@ -32,7 +32,7 @@ module.exports = { return false; } else if (ev.getType() == 'm.call.answer' || ev.getType() == 'm.call.hangup') { return false; - } else if (ev.getType == 'm.room.message' && ev.getContent().msgtype == 'm.notify') { + } else if (ev.getType() == 'm.room.message' && ev.getContent().msgtype == 'm.notify') { return false; } const EventTile = sdk.getComponent('rooms.EventTile'); From fbc0bbfb6f7e80639c9d5288df632ead62938244 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 11 Feb 2019 16:17:15 +0100 Subject: [PATCH 5/6] clear hover flag after 1000ms of not moving the mouse as a fix for #8184 --- src/components/views/rooms/RoomList.js | 30 +++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index 56eb4b713d7..1d207835bc5 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -17,6 +17,7 @@ limitations under the License. 'use strict'; import SettingsStore from "../../../settings/SettingsStore"; +import Timer from "../../../utils/Timer"; const React = require("react"); const ReactDOM = require("react-dom"); @@ -41,6 +42,7 @@ import {Resizer} from '../../../resizer'; import {Layout, Distributor} from '../../../resizer/distributors/roomsublist2'; const HIDE_CONFERENCE_CHANS = true; const STANDARD_TAGS_REGEX = /^(m\.(favourite|lowpriority|server_notice)|im\.vector\.fake\.(invite|recent|direct|archived))$/; +const HOVER_MOVE_TIMEOUT = 1000; function labelForTagName(tagName) { if (tagName.startsWith('u.')) return tagName.slice(2); @@ -73,6 +75,7 @@ module.exports = React.createClass({ getInitialState: function() { + this._hoverClearTimer = null; this._subListRefs = { // key => RoomSubList ref }; @@ -357,11 +360,32 @@ module.exports = React.createClass({ this.forceUpdate(); }, - onMouseEnter: function(ev) { - this.setState({hover: true}); + onMouseMove: async function(ev) { + if (!this._hoverClearTimer) { + this.setState({hover: true}); + this._hoverClearTimer = new Timer(HOVER_MOVE_TIMEOUT); + this._hoverClearTimer.start(); + let finished = true; + try { + await this._hoverClearTimer.finished(); + } catch (err) { + finished = false; + } + this._hoverClearTimer = null; + if (finished) { + this.setState({hover: false}); + this._delayedRefreshRoomList(); + } + } else { + this._hoverClearTimer.restart(); + } }, onMouseLeave: function(ev) { + if (this._hoverClearTimer) { + this._hoverClearTimer.abort(); + this._hoverClearTimer = null; + } this.setState({hover: false}); // Refresh the room list just in case the user missed something. @@ -774,7 +798,7 @@ module.exports = React.createClass({ return (
+ onMouseMove={this.onMouseMove} onMouseLeave={this.onMouseLeave}> { subListComponents }
); From ab6535b1353b18f7c03bc141e74aed8dd956fe3d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 11 Feb 2019 16:27:29 +0100 Subject: [PATCH 6/6] lint --- src/components/views/rooms/RoomList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index 1d207835bc5..227dd318edf 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -98,7 +98,7 @@ module.exports = React.createClass({ // update overflow indicators this._checkSubListsOverflow(); // don't store height for collapsed sublists - if(!this.collapsedState[key]) { + if (!this.collapsedState[key]) { this.subListSizes[key] = size; window.localStorage.setItem("mx_roomlist_sizes", JSON.stringify(this.subListSizes));