Merge pull request #2612 from matrix-org/bwindels/roomlistlag

Fix: roomlist reordering lags
This commit is contained in:
Bruno Windels 2019-02-13 09:15:46 +01:00 committed by GitHub
commit 240dc3c1cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 51 additions and 64 deletions

View file

@ -32,7 +32,7 @@ module.exports = {
return false; return false;
} else if (ev.getType() == 'm.call.answer' || ev.getType() == 'm.call.hangup') { } else if (ev.getType() == 'm.call.answer' || ev.getType() == 'm.call.hangup') {
return false; 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; return false;
} }
const EventTile = sdk.getComponent('rooms.EventTile'); const EventTile = sdk.getComponent('rooms.EventTile');

View file

@ -145,6 +145,7 @@ const RoomSubList = React.createClass({
collapsed={this.props.collapsed || false} collapsed={this.props.collapsed || false}
unread={Unread.doesRoomHaveUnreadMessages(room)} unread={Unread.doesRoomHaveUnreadMessages(room)}
highlight={room.getUnreadNotificationCount('highlight') > 0 || this.props.isInvite} highlight={room.getUnreadNotificationCount('highlight') > 0 || this.props.isInvite}
notificationCount={room.getUnreadNotificationCount()}
isInvite={this.props.isInvite} isInvite={this.props.isInvite}
refreshSubList={this._updateSubListCount} refreshSubList={this._updateSubListCount}
incomingCall={null} incomingCall={null}

View file

@ -21,7 +21,7 @@ import { _t } from '../../languageHandler';
import { KeyCode } from '../../Keyboard'; import { KeyCode } from '../../Keyboard';
import sdk from '../../index'; import sdk from '../../index';
import dis from '../../dispatcher'; import dis from '../../dispatcher';
import rate_limited_func from '../../ratelimitedfunc'; import { debounce } from 'lodash';
import AccessibleButton from '../../components/views/elements/AccessibleButton'; import AccessibleButton from '../../components/views/elements/AccessibleButton';
module.exports = React.createClass({ module.exports = React.createClass({
@ -67,12 +67,9 @@ module.exports = React.createClass({
this.onSearch(); this.onSearch();
}, },
onSearch: new rate_limited_func( onSearch: debounce(function() {
function() {
this.props.onSearch(this.refs.search.value); this.props.onSearch(this.refs.search.value);
}, }, 200, {trailing: true}),
500,
),
_onKeyDown: function(ev) { _onKeyDown: function(ev) {
switch (ev.keyCode) { switch (ev.keyCode) {

View file

@ -17,6 +17,7 @@ limitations under the License.
'use strict'; 'use strict';
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import Timer from "../../../utils/Timer";
const React = require("react"); const React = require("react");
const ReactDOM = require("react-dom"); const ReactDOM = require("react-dom");
@ -41,6 +42,7 @@ import {Resizer} from '../../../resizer';
import {Layout, Distributor} from '../../../resizer/distributors/roomsublist2'; import {Layout, Distributor} from '../../../resizer/distributors/roomsublist2';
const HIDE_CONFERENCE_CHANS = true; const HIDE_CONFERENCE_CHANS = true;
const STANDARD_TAGS_REGEX = /^(m\.(favourite|lowpriority|server_notice)|im\.vector\.fake\.(invite|recent|direct|archived))$/; const STANDARD_TAGS_REGEX = /^(m\.(favourite|lowpriority|server_notice)|im\.vector\.fake\.(invite|recent|direct|archived))$/;
const HOVER_MOVE_TIMEOUT = 1000;
function labelForTagName(tagName) { function labelForTagName(tagName) {
if (tagName.startsWith('u.')) return tagName.slice(2); if (tagName.startsWith('u.')) return tagName.slice(2);
@ -73,6 +75,7 @@ module.exports = React.createClass({
getInitialState: function() { getInitialState: function() {
this._hoverClearTimer = null;
this._subListRefs = { this._subListRefs = {
// key => RoomSubList ref // key => RoomSubList ref
}; };
@ -357,11 +360,32 @@ module.exports = React.createClass({
this.forceUpdate(); this.forceUpdate();
}, },
onMouseEnter: function(ev) { onMouseMove: async function(ev) {
if (!this._hoverClearTimer) {
this.setState({hover: true}); 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) { onMouseLeave: function(ev) {
if (this._hoverClearTimer) {
this._hoverClearTimer.abort();
this._hoverClearTimer = null;
}
this.setState({hover: false}); this.setState({hover: false});
// Refresh the room list just in case the user missed something. // Refresh the room list just in case the user missed something.
@ -774,7 +798,7 @@ module.exports = React.createClass({
return ( return (
<div ref={this._collectResizeContainer} className="mx_RoomList" <div ref={this._collectResizeContainer} className="mx_RoomList"
onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}> onMouseMove={this.onMouseMove} onMouseLeave={this.onMouseLeave}>
{ subListComponents } { subListComponents }
</div> </div>
); );

View file

@ -108,13 +108,6 @@ module.exports = React.createClass({
return statusUser._unstable_statusMessage; return statusUser._unstable_statusMessage;
}, },
onRoomTimeline: function(ev, room) {
if (room !== this.props.room) return;
this.setState({
notificationCount: this.props.room.getUnreadNotificationCount(),
});
},
onRoomName: function(room) { onRoomName: function(room) {
if (room !== this.props.room) return; if (room !== this.props.room) return;
this.setState({ this.setState({
@ -159,7 +152,6 @@ module.exports = React.createClass({
componentWillMount: function() { componentWillMount: function() {
MatrixClientPeg.get().on("accountData", this.onAccountData); MatrixClientPeg.get().on("accountData", this.onAccountData);
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
MatrixClientPeg.get().on("Room.name", this.onRoomName); MatrixClientPeg.get().on("Room.name", this.onRoomName);
ActiveRoomObserver.addListener(this.props.room.roomId, this._onActiveRoomChange); ActiveRoomObserver.addListener(this.props.room.roomId, this._onActiveRoomChange);
this.dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
@ -179,7 +171,6 @@ module.exports = React.createClass({
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
if (cli) { if (cli) {
MatrixClientPeg.get().removeListener("accountData", this.onAccountData); MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName); MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
} }
ActiveRoomObserver.removeListener(this.props.room.roomId, this._onActiveRoomChange); ActiveRoomObserver.removeListener(this.props.room.roomId, this._onActiveRoomChange);
@ -306,7 +297,7 @@ module.exports = React.createClass({
render: function() { render: function() {
const isInvite = this.props.room.getMyMembership() === "invite"; const isInvite = this.props.room.getMyMembership() === "invite";
const notificationCount = this.state.notificationCount; const notificationCount = this.props.notificationCount;
// var highlightCount = this.props.room.getUnreadNotificationCount("highlight"); // var highlightCount = this.props.room.getUnreadNotificationCount("highlight");
const notifBadges = notificationCount > 0 && this._shouldShowNotifBadge(); const notifBadges = notificationCount > 0 && this._shouldShowNotifBadge();

View file

@ -20,54 +20,28 @@ limitations under the License.
* to update the interface once for all of them. * to update the interface once for all of them.
* *
* Note that the function must not take arguments, since the args * 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 * The returned function has a 'cancelPendingCall' property which can be called
* on unmount or similar to cancel any pending update. * on unmount or similar to cancel any pending update.
*/ */
module.exports = function(f, minIntervalMs) {
this.lastCall = 0;
this.scheduledCall = undefined;
const self = this; import { throttle } from "lodash";
const wrapper = function() {
const now = Date.now();
if (self.lastCall < now - minIntervalMs) { export default function ratelimitedfunc(fn, time) {
f.apply(this); const throttledFn = throttle(fn, time, {
// get the time again now the function has finished, so if it leading: true,
// took longer than the delay time to execute, it doesn't trailing: true,
// immediately become eligible to run again. });
self.lastCall = Date.now(); const _bind = throttledFn.bind;
} else if (self.scheduledCall === undefined) { throttledFn.bind = function() {
self.scheduledCall = setTimeout( const boundFn = _bind.apply(throttledFn, arguments);
() => { boundFn.cancelPendingCall = throttledFn.cancelPendingCall;
self.scheduledCall = undefined; return boundFn;
f.apply(this); };
// get time again as per above
self.lastCall = Date.now(); throttledFn.cancelPendingCall = function() {
}, throttledFn.cancel();
(self.lastCall + minIntervalMs) - now, };
); return throttledFn;
} }
};
// add the cancelPendingCall property
wrapper.cancelPendingCall = function() {
if (self.scheduledCall) {
clearTimeout(self.scheduledCall);
self.scheduledCall = undefined;
}
};
// 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;
};
return wrapper;
};