Merge pull request #807 from matrix-org/matthew/quick-search
Improve RoomList performance via side-stepping React
This commit is contained in:
commit
0ad1d8caf3
7 changed files with 268 additions and 60 deletions
|
@ -67,7 +67,7 @@
|
||||||
"react": "^15.4.0",
|
"react": "^15.4.0",
|
||||||
"react-addons-css-transition-group": "15.3.2",
|
"react-addons-css-transition-group": "15.3.2",
|
||||||
"react-dom": "^15.4.0",
|
"react-dom": "^15.4.0",
|
||||||
"react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#5e97aef",
|
"react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#39d858c",
|
||||||
"sanitize-html": "^1.11.1",
|
"sanitize-html": "^1.11.1",
|
||||||
"text-encoding-utf-8": "^1.0.1",
|
"text-encoding-utf-8": "^1.0.1",
|
||||||
"velocity-vector": "vector-im/velocity#059e3b2",
|
"velocity-vector": "vector-im/velocity#059e3b2",
|
||||||
|
|
62
src/ConstantTimeDispatcher.js
Normal file
62
src/ConstantTimeDispatcher.js
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 Vector Creations 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// singleton which dispatches invocations of a given type & argument
|
||||||
|
// rather than just a type (as per EventEmitter and Flux's dispatcher etc)
|
||||||
|
//
|
||||||
|
// This means you can have a single point which listens for an EventEmitter event
|
||||||
|
// and then dispatches out to one of thousands of RoomTiles (for instance) rather than
|
||||||
|
// having each RoomTile register for the EventEmitter event and having to
|
||||||
|
// iterate over all of them.
|
||||||
|
class ConstantTimeDispatcher {
|
||||||
|
constructor() {
|
||||||
|
// type -> arg -> [ listener(arg, params) ]
|
||||||
|
this.listeners = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
register(type, arg, listener) {
|
||||||
|
if (!this.listeners[type]) this.listeners[type] = {};
|
||||||
|
if (!this.listeners[type][arg]) this.listeners[type][arg] = [];
|
||||||
|
this.listeners[type][arg].push(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
unregister(type, arg, listener) {
|
||||||
|
if (this.listeners[type] && this.listeners[type][arg]) {
|
||||||
|
var i = this.listeners[type][arg].indexOf(listener);
|
||||||
|
if (i > -1) {
|
||||||
|
this.listeners[type][arg].splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.warn("Unregistering unrecognised listener (type=" + type + ", arg=" + arg + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(type, arg, params) {
|
||||||
|
if (!this.listeners[type] || !this.listeners[type][arg]) {
|
||||||
|
console.warn("No registered listeners for dispatch (type=" + type + ", arg=" + arg + ")");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.listeners[type][arg].forEach(listener=>{
|
||||||
|
listener.call(arg, params);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!global.constantTimeDispatcher) {
|
||||||
|
global.constantTimeDispatcher = new ConstantTimeDispatcher();
|
||||||
|
}
|
||||||
|
module.exports = global.constantTimeDispatcher;
|
|
@ -32,4 +32,5 @@ module.exports = {
|
||||||
DELETE: 46,
|
DELETE: 46,
|
||||||
KEY_D: 68,
|
KEY_D: 68,
|
||||||
KEY_E: 69,
|
KEY_E: 69,
|
||||||
|
KEY_K: 75,
|
||||||
};
|
};
|
||||||
|
|
|
@ -523,6 +523,7 @@ var TimelinePanel = React.createClass({
|
||||||
this.props.timelineSet.room.setUnreadNotificationCount('highlight', 0);
|
this.props.timelineSet.room.setUnreadNotificationCount('highlight', 0);
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'on_room_read',
|
action: 'on_room_read',
|
||||||
|
room: this.props.timelineSet.room,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,12 @@ export default React.createClass({
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
if (this.props.focus) {
|
||||||
|
this.refs.button.focus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
const cancelButton = this.props.hasCancelButton ? (
|
const cancelButton = this.props.hasCancelButton ? (
|
||||||
|
@ -63,7 +69,7 @@ export default React.createClass({
|
||||||
{this.props.description}
|
{this.props.description}
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons">
|
||||||
<button className="mx_Dialog_primary" onClick={this.onOk} autoFocus={this.props.focus}>
|
<button ref="button" className="mx_Dialog_primary" onClick={this.onOk}>
|
||||||
{this.props.button}
|
{this.props.button}
|
||||||
</button>
|
</button>
|
||||||
{this.props.extraButtons}
|
{this.props.extraButtons}
|
||||||
|
|
|
@ -21,13 +21,13 @@ var GeminiScrollbar = require('react-gemini-scrollbar');
|
||||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||||
var CallHandler = require('../../../CallHandler');
|
var CallHandler = require('../../../CallHandler');
|
||||||
var RoomListSorter = require("../../../RoomListSorter");
|
var RoomListSorter = require("../../../RoomListSorter");
|
||||||
var Unread = require('../../../Unread');
|
|
||||||
var dis = require("../../../dispatcher");
|
var dis = require("../../../dispatcher");
|
||||||
var sdk = require('../../../index');
|
var sdk = require('../../../index');
|
||||||
var rate_limited_func = require('../../../ratelimitedfunc');
|
var rate_limited_func = require('../../../ratelimitedfunc');
|
||||||
var Rooms = require('../../../Rooms');
|
var Rooms = require('../../../Rooms');
|
||||||
import DMRoomMap from '../../../utils/DMRoomMap';
|
import DMRoomMap from '../../../utils/DMRoomMap';
|
||||||
var Receipt = require('../../../utils/Receipt');
|
var Receipt = require('../../../utils/Receipt');
|
||||||
|
var constantTimeDispatcher = require('../../../ConstantTimeDispatcher');
|
||||||
|
|
||||||
var HIDE_CONFERENCE_CHANS = true;
|
var HIDE_CONFERENCE_CHANS = true;
|
||||||
|
|
||||||
|
@ -37,10 +37,16 @@ module.exports = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
ConferenceHandler: React.PropTypes.any,
|
ConferenceHandler: React.PropTypes.any,
|
||||||
collapsed: React.PropTypes.bool.isRequired,
|
collapsed: React.PropTypes.bool.isRequired,
|
||||||
currentRoom: React.PropTypes.string,
|
selectedRoom: React.PropTypes.string,
|
||||||
searchFilter: React.PropTypes.string,
|
searchFilter: React.PropTypes.string,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
shouldComponentUpdate: function(nextProps, nextState) {
|
||||||
|
if (nextProps.collapsed !== this.props.collapsed) return true;
|
||||||
|
if (nextProps.searchFilter !== this.props.searchFilter) return true;
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
isLoadingLeftRooms: false,
|
isLoadingLeftRooms: false,
|
||||||
|
@ -57,12 +63,18 @@ module.exports = React.createClass({
|
||||||
cli.on("Room.name", this.onRoomName);
|
cli.on("Room.name", this.onRoomName);
|
||||||
cli.on("Room.tags", this.onRoomTags);
|
cli.on("Room.tags", this.onRoomTags);
|
||||||
cli.on("Room.receipt", this.onRoomReceipt);
|
cli.on("Room.receipt", this.onRoomReceipt);
|
||||||
cli.on("RoomState.events", this.onRoomStateEvents);
|
cli.on("RoomState.members", this.onRoomStateMember);
|
||||||
cli.on("RoomMember.name", this.onRoomMemberName);
|
cli.on("RoomMember.name", this.onRoomMemberName);
|
||||||
cli.on("accountData", this.onAccountData);
|
cli.on("accountData", this.onAccountData);
|
||||||
|
|
||||||
|
// lookup for which lists a given roomId is currently in.
|
||||||
|
this.listsForRoomId = {};
|
||||||
|
|
||||||
var s = this.getRoomLists();
|
var s = this.getRoomLists();
|
||||||
this.setState(s);
|
this.setState(s);
|
||||||
|
|
||||||
|
// order of the sublists
|
||||||
|
this.listOrder = [];
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
|
@ -71,7 +83,22 @@ module.exports = React.createClass({
|
||||||
this._updateStickyHeaders(true);
|
this._updateStickyHeaders(true);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidUpdate: function() {
|
componentWillReceiveProps: function(nextProps) {
|
||||||
|
// short-circuit react when the room changes
|
||||||
|
// to avoid rerendering all the sublists everywhere
|
||||||
|
if (nextProps.selectedRoom !== this.props.selectedRoom) {
|
||||||
|
if (this.props.selectedRoom) {
|
||||||
|
constantTimeDispatcher.dispatch(
|
||||||
|
"RoomTile.select", this.props.selectedRoom, {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
constantTimeDispatcher.dispatch(
|
||||||
|
"RoomTile.select", nextProps.selectedRoom, { selected: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidUpdate: function(prevProps, prevState) {
|
||||||
// Reinitialise the stickyHeaders when the component is updated
|
// Reinitialise the stickyHeaders when the component is updated
|
||||||
this._updateStickyHeaders(true);
|
this._updateStickyHeaders(true);
|
||||||
this._repositionIncomingCallBox(undefined, false);
|
this._repositionIncomingCallBox(undefined, false);
|
||||||
|
@ -97,10 +124,24 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'on_room_read':
|
case 'on_room_read':
|
||||||
// Force an update because the notif count state is too deep to cause
|
// poke the right RoomTile to refresh, using the constantTimeDispatcher
|
||||||
// an update. This forces the local echo of reading notifs to be
|
// to avoid each and every RoomTile registering to the 'on_room_read' event
|
||||||
// reflected by the RoomTiles.
|
// XXX: if we like the constantTimeDispatcher we might want to dispatch
|
||||||
this.forceUpdate();
|
// directly from TimelinePanel rather than needlessly bouncing via here.
|
||||||
|
constantTimeDispatcher.dispatch(
|
||||||
|
"RoomTile.refresh", payload.room.roomId, {}
|
||||||
|
);
|
||||||
|
|
||||||
|
// also have to poke the right list(s)
|
||||||
|
var lists = this.listsForRoomId[payload.room.roomId];
|
||||||
|
if (lists) {
|
||||||
|
lists.forEach(list=>{
|
||||||
|
constantTimeDispatcher.dispatch(
|
||||||
|
"RoomSubList.refreshHeader", list, { room: payload.room }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -114,7 +155,7 @@ module.exports = React.createClass({
|
||||||
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
|
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
|
||||||
MatrixClientPeg.get().removeListener("Room.tags", this.onRoomTags);
|
MatrixClientPeg.get().removeListener("Room.tags", this.onRoomTags);
|
||||||
MatrixClientPeg.get().removeListener("Room.receipt", this.onRoomReceipt);
|
MatrixClientPeg.get().removeListener("Room.receipt", this.onRoomReceipt);
|
||||||
MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvents);
|
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
|
||||||
MatrixClientPeg.get().removeListener("RoomMember.name", this.onRoomMemberName);
|
MatrixClientPeg.get().removeListener("RoomMember.name", this.onRoomMemberName);
|
||||||
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
||||||
}
|
}
|
||||||
|
@ -123,10 +164,14 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoom: function(room) {
|
onRoom: function(room) {
|
||||||
|
// XXX: this happens rarely; ideally we should only update the correct
|
||||||
|
// sublists when it does (e.g. via a constantTimeDispatch to the right sublist)
|
||||||
this._delayedRefreshRoomList();
|
this._delayedRefreshRoomList();
|
||||||
},
|
},
|
||||||
|
|
||||||
onDeleteRoom: function(roomId) {
|
onDeleteRoom: function(roomId) {
|
||||||
|
// XXX: this happens rarely; ideally we should only update the correct
|
||||||
|
// sublists when it does (e.g. via a constantTimeDispatch to the right sublist)
|
||||||
this._delayedRefreshRoomList();
|
this._delayedRefreshRoomList();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -149,6 +194,10 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_onMouseOver: function(ev) {
|
||||||
|
this._lastMouseOverTs = Date.now();
|
||||||
|
},
|
||||||
|
|
||||||
onSubListHeaderClick: function(isHidden, scrollToPosition) {
|
onSubListHeaderClick: function(isHidden, scrollToPosition) {
|
||||||
// The scroll area has expanded or contracted, so re-calculate sticky headers positions
|
// The scroll area has expanded or contracted, so re-calculate sticky headers positions
|
||||||
this._updateStickyHeaders(true, scrollToPosition);
|
this._updateStickyHeaders(true, scrollToPosition);
|
||||||
|
@ -158,41 +207,89 @@ module.exports = React.createClass({
|
||||||
if (toStartOfTimeline) return;
|
if (toStartOfTimeline) return;
|
||||||
if (!room) return;
|
if (!room) return;
|
||||||
if (data.timeline.getTimelineSet() !== room.getUnfilteredTimelineSet()) return;
|
if (data.timeline.getTimelineSet() !== room.getUnfilteredTimelineSet()) return;
|
||||||
this._delayedRefreshRoomList();
|
|
||||||
|
// rather than regenerate our full roomlists, which is very heavy, we poke the
|
||||||
|
// correct sublists to just re-sort themselves. This isn't enormously reacty,
|
||||||
|
// but is much faster than the default react reconciler, or having to do voodoo
|
||||||
|
// with shouldComponentUpdate and a pleaseRefresh property or similar.
|
||||||
|
var lists = this.listsForRoomId[room.roomId];
|
||||||
|
if (lists) {
|
||||||
|
lists.forEach(list=>{
|
||||||
|
constantTimeDispatcher.dispatch("RoomSubList.sort", list, { room: room });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// we have to explicitly hit the roomtile which just changed
|
||||||
|
constantTimeDispatcher.dispatch(
|
||||||
|
"RoomTile.refresh", room.roomId, {}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoomReceipt: function(receiptEvent, room) {
|
onRoomReceipt: function(receiptEvent, room) {
|
||||||
// because if we read a notification, it will affect notification count
|
// because if we read a notification, it will affect notification count
|
||||||
// only bother updating if there's a receipt from us
|
// only bother updating if there's a receipt from us
|
||||||
if (Receipt.findReadReceiptFromUserId(receiptEvent, MatrixClientPeg.get().credentials.userId)) {
|
if (Receipt.findReadReceiptFromUserId(receiptEvent, MatrixClientPeg.get().credentials.userId)) {
|
||||||
this._delayedRefreshRoomList();
|
var lists = this.listsForRoomId[room.roomId];
|
||||||
|
if (lists) {
|
||||||
|
lists.forEach(list=>{
|
||||||
|
constantTimeDispatcher.dispatch(
|
||||||
|
"RoomSubList.refreshHeader", list, { room: room }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// we have to explicitly hit the roomtile which just changed
|
||||||
|
constantTimeDispatcher.dispatch(
|
||||||
|
"RoomTile.refresh", room.roomId, {}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoomName: function(room) {
|
onRoomName: function(room) {
|
||||||
this._delayedRefreshRoomList();
|
constantTimeDispatcher.dispatch(
|
||||||
|
"RoomTile.refresh", room.roomId, {}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoomTags: function(event, room) {
|
onRoomTags: function(event, room) {
|
||||||
|
// XXX: this happens rarely; ideally we should only update the correct
|
||||||
|
// sublists when it does (e.g. via a constantTimeDispatch to the right sublist)
|
||||||
this._delayedRefreshRoomList();
|
this._delayedRefreshRoomList();
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoomStateEvents: function(ev, state) {
|
onRoomStateMember: function(ev, state, member) {
|
||||||
this._delayedRefreshRoomList();
|
constantTimeDispatcher.dispatch(
|
||||||
|
"RoomTile.refresh", member.roomId, {}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoomMemberName: function(ev, member) {
|
onRoomMemberName: function(ev, member) {
|
||||||
this._delayedRefreshRoomList();
|
constantTimeDispatcher.dispatch(
|
||||||
|
"RoomTile.refresh", member.roomId, {}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
onAccountData: function(ev) {
|
onAccountData: function(ev) {
|
||||||
if (ev.getType() == 'm.direct') {
|
if (ev.getType() == 'm.direct') {
|
||||||
|
// XXX: this happens rarely; ideally we should only update the correct
|
||||||
|
// sublists when it does (e.g. via a constantTimeDispatch to the right sublist)
|
||||||
|
this._delayedRefreshRoomList();
|
||||||
|
}
|
||||||
|
else if (ev.getType() == 'm.push_rules') {
|
||||||
this._delayedRefreshRoomList();
|
this._delayedRefreshRoomList();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_delayedRefreshRoomList: new rate_limited_func(function() {
|
_delayedRefreshRoomList: new rate_limited_func(function() {
|
||||||
this.refreshRoomList();
|
// if the mouse has been moving over the RoomList in the last 500ms
|
||||||
|
// then delay the refresh further to avoid bouncing around under the
|
||||||
|
// cursor
|
||||||
|
if (Date.now() - this._lastMouseOverTs > 500) {
|
||||||
|
this.refreshRoomList();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this._delayedRefreshRoomList();
|
||||||
|
}
|
||||||
}, 500),
|
}, 500),
|
||||||
|
|
||||||
refreshRoomList: function() {
|
refreshRoomList: function() {
|
||||||
|
@ -200,14 +297,13 @@ module.exports = React.createClass({
|
||||||
// (!this._lastRefreshRoomListTs ? "-" : (Date.now() - this._lastRefreshRoomListTs))
|
// (!this._lastRefreshRoomListTs ? "-" : (Date.now() - this._lastRefreshRoomListTs))
|
||||||
// );
|
// );
|
||||||
|
|
||||||
// TODO: rather than bluntly regenerating and re-sorting everything
|
// TODO: ideally we'd calculate this once at start, and then maintain
|
||||||
// every time we see any kind of room change from the JS SDK
|
// any changes to it incrementally, updating the appropriate sublists
|
||||||
// we could do incremental updates on our copy of the state
|
// as needed.
|
||||||
// based on the room which has actually changed. This would stop
|
// Alternatively we'd do something magical with Immutable.js or similar.
|
||||||
// us re-rendering all the sublists every time anything changes anywhere
|
|
||||||
// in the state of the client.
|
|
||||||
this.setState(this.getRoomLists());
|
this.setState(this.getRoomLists());
|
||||||
this._lastRefreshRoomListTs = Date.now();
|
|
||||||
|
// this._lastRefreshRoomListTs = Date.now();
|
||||||
},
|
},
|
||||||
|
|
||||||
getRoomLists: function() {
|
getRoomLists: function() {
|
||||||
|
@ -221,6 +317,9 @@ module.exports = React.createClass({
|
||||||
s.lists["m.lowpriority"] = [];
|
s.lists["m.lowpriority"] = [];
|
||||||
s.lists["im.vector.fake.archived"] = [];
|
s.lists["im.vector.fake.archived"] = [];
|
||||||
|
|
||||||
|
this.listsForRoomId = {};
|
||||||
|
var otherTagNames = {};
|
||||||
|
|
||||||
const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
|
const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
|
||||||
|
|
||||||
MatrixClientPeg.get().getRooms().forEach(function(room) {
|
MatrixClientPeg.get().getRooms().forEach(function(room) {
|
||||||
|
@ -232,7 +331,12 @@ module.exports = React.createClass({
|
||||||
// ", target = " + me.events.member.getStateKey() +
|
// ", target = " + me.events.member.getStateKey() +
|
||||||
// ", prevMembership = " + me.events.member.getPrevContent().membership);
|
// ", prevMembership = " + me.events.member.getPrevContent().membership);
|
||||||
|
|
||||||
|
if (!self.listsForRoomId[room.roomId]) {
|
||||||
|
self.listsForRoomId[room.roomId] = [];
|
||||||
|
}
|
||||||
|
|
||||||
if (me.membership == "invite") {
|
if (me.membership == "invite") {
|
||||||
|
self.listsForRoomId[room.roomId].push("im.vector.fake.invite");
|
||||||
s.lists["im.vector.fake.invite"].push(room);
|
s.lists["im.vector.fake.invite"].push(room);
|
||||||
}
|
}
|
||||||
else if (HIDE_CONFERENCE_CHANS && Rooms.isConfCallRoom(room, me, self.props.ConferenceHandler)) {
|
else if (HIDE_CONFERENCE_CHANS && Rooms.isConfCallRoom(room, me, self.props.ConferenceHandler)) {
|
||||||
|
@ -243,23 +347,27 @@ module.exports = React.createClass({
|
||||||
{
|
{
|
||||||
// Used to split rooms via tags
|
// Used to split rooms via tags
|
||||||
var tagNames = Object.keys(room.tags);
|
var tagNames = Object.keys(room.tags);
|
||||||
|
|
||||||
if (tagNames.length) {
|
if (tagNames.length) {
|
||||||
for (var i = 0; i < tagNames.length; i++) {
|
for (var i = 0; i < tagNames.length; i++) {
|
||||||
var tagName = tagNames[i];
|
var tagName = tagNames[i];
|
||||||
s.lists[tagName] = s.lists[tagName] || [];
|
s.lists[tagName] = s.lists[tagName] || [];
|
||||||
s.lists[tagNames[i]].push(room);
|
s.lists[tagName].push(room);
|
||||||
|
self.listsForRoomId[room.roomId].push(tagName);
|
||||||
|
otherTagNames[tagName] = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (dmRoomMap.getUserIdForRoomId(room.roomId)) {
|
else if (dmRoomMap.getUserIdForRoomId(room.roomId)) {
|
||||||
// "Direct Message" rooms (that we're still in and that aren't otherwise tagged)
|
// "Direct Message" rooms (that we're still in and that aren't otherwise tagged)
|
||||||
|
self.listsForRoomId[room.roomId].push("im.vector.fake.direct");
|
||||||
s.lists["im.vector.fake.direct"].push(room);
|
s.lists["im.vector.fake.direct"].push(room);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
self.listsForRoomId[room.roomId].push("im.vector.fake.recent");
|
||||||
s.lists["im.vector.fake.recent"].push(room);
|
s.lists["im.vector.fake.recent"].push(room);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (me.membership === "leave") {
|
else if (me.membership === "leave") {
|
||||||
|
self.listsForRoomId[room.roomId].push("im.vector.fake.archived");
|
||||||
s.lists["im.vector.fake.archived"].push(room);
|
s.lists["im.vector.fake.archived"].push(room);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -280,8 +388,10 @@ module.exports = React.createClass({
|
||||||
const me = room.getMember(MatrixClientPeg.get().credentials.userId);
|
const me = room.getMember(MatrixClientPeg.get().credentials.userId);
|
||||||
|
|
||||||
if (me && Rooms.looksLikeDirectMessageRoom(room, me)) {
|
if (me && Rooms.looksLikeDirectMessageRoom(room, me)) {
|
||||||
|
self.listsForRoomId[room.roomId].push("im.vector.fake.direct");
|
||||||
s.lists["im.vector.fake.direct"].push(room);
|
s.lists["im.vector.fake.direct"].push(room);
|
||||||
} else {
|
} else {
|
||||||
|
self.listsForRoomId[room.roomId].push("im.vector.fake.recent");
|
||||||
s.lists["im.vector.fake.recent"].push(room);
|
s.lists["im.vector.fake.recent"].push(room);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -298,6 +408,8 @@ module.exports = React.createClass({
|
||||||
newMDirectEvent[otherPerson.userId] = roomList;
|
newMDirectEvent[otherPerson.userId] = roomList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.warn("Resetting room DM state to be " + JSON.stringify(newMDirectEvent));
|
||||||
|
|
||||||
// if this fails, fine, we'll just do the same thing next time we get the room lists
|
// if this fails, fine, we'll just do the same thing next time we get the room lists
|
||||||
MatrixClientPeg.get().setAccountData('m.direct', newMDirectEvent).done();
|
MatrixClientPeg.get().setAccountData('m.direct', newMDirectEvent).done();
|
||||||
}
|
}
|
||||||
|
@ -306,6 +418,21 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
// we actually apply the sorting to this when receiving the prop in RoomSubLists.
|
// we actually apply the sorting to this when receiving the prop in RoomSubLists.
|
||||||
|
|
||||||
|
// we'll need this when we get to iterating through lists programatically - e.g. ctrl-shift-up/down
|
||||||
|
/*
|
||||||
|
this.listOrder = [
|
||||||
|
"im.vector.fake.invite",
|
||||||
|
"m.favourite",
|
||||||
|
"im.vector.fake.recent",
|
||||||
|
"im.vector.fake.direct",
|
||||||
|
Object.keys(otherTagNames).filter(tagName=>{
|
||||||
|
return (!tagName.match(/^m\.(favourite|lowpriority)$/));
|
||||||
|
}).sort(),
|
||||||
|
"m.lowpriority",
|
||||||
|
"im.vector.fake.archived"
|
||||||
|
];
|
||||||
|
*/
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -464,14 +591,14 @@ module.exports = React.createClass({
|
||||||
return (
|
return (
|
||||||
<GeminiScrollbar className="mx_RoomList_scrollbar"
|
<GeminiScrollbar className="mx_RoomList_scrollbar"
|
||||||
autoshow={true} onScroll={ self._whenScrolling } ref="gemscroll">
|
autoshow={true} onScroll={ self._whenScrolling } ref="gemscroll">
|
||||||
<div className="mx_RoomList">
|
<div className="mx_RoomList" onMouseOver={ this._onMouseOver }>
|
||||||
<RoomSubList list={ self.state.lists['im.vector.fake.invite'] }
|
<RoomSubList list={ self.state.lists['im.vector.fake.invite'] }
|
||||||
label="Invites"
|
label="Invites"
|
||||||
editable={ false }
|
editable={ false }
|
||||||
order="recent"
|
order="recent"
|
||||||
selectedRoom={ self.props.selectedRoom }
|
|
||||||
incomingCall={ self.state.incomingCall }
|
incomingCall={ self.state.incomingCall }
|
||||||
collapsed={ self.props.collapsed }
|
collapsed={ self.props.collapsed }
|
||||||
|
selectedRoom={ self.props.selectedRoom }
|
||||||
searchFilter={ self.props.searchFilter }
|
searchFilter={ self.props.searchFilter }
|
||||||
onHeaderClick={ self.onSubListHeaderClick }
|
onHeaderClick={ self.onSubListHeaderClick }
|
||||||
onShowMoreRooms={ self.onShowMoreRooms } />
|
onShowMoreRooms={ self.onShowMoreRooms } />
|
||||||
|
@ -482,9 +609,9 @@ module.exports = React.createClass({
|
||||||
verb="favourite"
|
verb="favourite"
|
||||||
editable={ true }
|
editable={ true }
|
||||||
order="manual"
|
order="manual"
|
||||||
selectedRoom={ self.props.selectedRoom }
|
|
||||||
incomingCall={ self.state.incomingCall }
|
incomingCall={ self.state.incomingCall }
|
||||||
collapsed={ self.props.collapsed }
|
collapsed={ self.props.collapsed }
|
||||||
|
selectedRoom={ self.props.selectedRoom }
|
||||||
searchFilter={ self.props.searchFilter }
|
searchFilter={ self.props.searchFilter }
|
||||||
onHeaderClick={ self.onSubListHeaderClick }
|
onHeaderClick={ self.onSubListHeaderClick }
|
||||||
onShowMoreRooms={ self.onShowMoreRooms } />
|
onShowMoreRooms={ self.onShowMoreRooms } />
|
||||||
|
@ -495,9 +622,9 @@ module.exports = React.createClass({
|
||||||
verb="tag direct chat"
|
verb="tag direct chat"
|
||||||
editable={ true }
|
editable={ true }
|
||||||
order="recent"
|
order="recent"
|
||||||
selectedRoom={ self.props.selectedRoom }
|
|
||||||
incomingCall={ self.state.incomingCall }
|
incomingCall={ self.state.incomingCall }
|
||||||
collapsed={ self.props.collapsed }
|
collapsed={ self.props.collapsed }
|
||||||
|
selectedRoom={ self.props.selectedRoom }
|
||||||
alwaysShowHeader={ true }
|
alwaysShowHeader={ true }
|
||||||
searchFilter={ self.props.searchFilter }
|
searchFilter={ self.props.searchFilter }
|
||||||
onHeaderClick={ self.onSubListHeaderClick }
|
onHeaderClick={ self.onSubListHeaderClick }
|
||||||
|
@ -508,14 +635,14 @@ module.exports = React.createClass({
|
||||||
editable={ true }
|
editable={ true }
|
||||||
verb="restore"
|
verb="restore"
|
||||||
order="recent"
|
order="recent"
|
||||||
selectedRoom={ self.props.selectedRoom }
|
|
||||||
incomingCall={ self.state.incomingCall }
|
incomingCall={ self.state.incomingCall }
|
||||||
collapsed={ self.props.collapsed }
|
collapsed={ self.props.collapsed }
|
||||||
|
selectedRoom={ self.props.selectedRoom }
|
||||||
searchFilter={ self.props.searchFilter }
|
searchFilter={ self.props.searchFilter }
|
||||||
onHeaderClick={ self.onSubListHeaderClick }
|
onHeaderClick={ self.onSubListHeaderClick }
|
||||||
onShowMoreRooms={ self.onShowMoreRooms } />
|
onShowMoreRooms={ self.onShowMoreRooms } />
|
||||||
|
|
||||||
{ Object.keys(self.state.lists).map(function(tagName) {
|
{ Object.keys(self.state.lists).sort().map(function(tagName) {
|
||||||
if (!tagName.match(/^(m\.(favourite|lowpriority)|im\.vector\.fake\.(invite|recent|direct|archived))$/)) {
|
if (!tagName.match(/^(m\.(favourite|lowpriority)|im\.vector\.fake\.(invite|recent|direct|archived))$/)) {
|
||||||
return <RoomSubList list={ self.state.lists[tagName] }
|
return <RoomSubList list={ self.state.lists[tagName] }
|
||||||
key={ tagName }
|
key={ tagName }
|
||||||
|
@ -524,9 +651,9 @@ module.exports = React.createClass({
|
||||||
verb={ "tag as " + tagName }
|
verb={ "tag as " + tagName }
|
||||||
editable={ true }
|
editable={ true }
|
||||||
order="manual"
|
order="manual"
|
||||||
selectedRoom={ self.props.selectedRoom }
|
|
||||||
incomingCall={ self.state.incomingCall }
|
incomingCall={ self.state.incomingCall }
|
||||||
collapsed={ self.props.collapsed }
|
collapsed={ self.props.collapsed }
|
||||||
|
selectedRoom={ self.props.selectedRoom }
|
||||||
searchFilter={ self.props.searchFilter }
|
searchFilter={ self.props.searchFilter }
|
||||||
onHeaderClick={ self.onSubListHeaderClick }
|
onHeaderClick={ self.onSubListHeaderClick }
|
||||||
onShowMoreRooms={ self.onShowMoreRooms } />;
|
onShowMoreRooms={ self.onShowMoreRooms } />;
|
||||||
|
@ -540,9 +667,9 @@ module.exports = React.createClass({
|
||||||
verb="demote"
|
verb="demote"
|
||||||
editable={ true }
|
editable={ true }
|
||||||
order="recent"
|
order="recent"
|
||||||
selectedRoom={ self.props.selectedRoom }
|
|
||||||
incomingCall={ self.state.incomingCall }
|
incomingCall={ self.state.incomingCall }
|
||||||
collapsed={ self.props.collapsed }
|
collapsed={ self.props.collapsed }
|
||||||
|
selectedRoom={ self.props.selectedRoom }
|
||||||
searchFilter={ self.props.searchFilter }
|
searchFilter={ self.props.searchFilter }
|
||||||
onHeaderClick={ self.onSubListHeaderClick }
|
onHeaderClick={ self.onSubListHeaderClick }
|
||||||
onShowMoreRooms={ self.onShowMoreRooms } />
|
onShowMoreRooms={ self.onShowMoreRooms } />
|
||||||
|
@ -551,8 +678,8 @@ module.exports = React.createClass({
|
||||||
label="Historical"
|
label="Historical"
|
||||||
editable={ false }
|
editable={ false }
|
||||||
order="recent"
|
order="recent"
|
||||||
selectedRoom={ self.props.selectedRoom }
|
|
||||||
collapsed={ self.props.collapsed }
|
collapsed={ self.props.collapsed }
|
||||||
|
selectedRoom={ self.props.selectedRoom }
|
||||||
alwaysShowHeader={ true }
|
alwaysShowHeader={ true }
|
||||||
startAsHidden={ true }
|
startAsHidden={ true }
|
||||||
showSpinner={ self.state.isLoadingLeftRooms }
|
showSpinner={ self.state.isLoadingLeftRooms }
|
||||||
|
|
|
@ -27,6 +27,8 @@ var RoomNotifs = require('../../../RoomNotifs');
|
||||||
var FormattingUtils = require('../../../utils/FormattingUtils');
|
var FormattingUtils = require('../../../utils/FormattingUtils');
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
var UserSettingsStore = require('../../../UserSettingsStore');
|
var UserSettingsStore = require('../../../UserSettingsStore');
|
||||||
|
var constantTimeDispatcher = require('../../../ConstantTimeDispatcher');
|
||||||
|
var Unread = require('../../../Unread');
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'RoomTile',
|
displayName: 'RoomTile',
|
||||||
|
@ -36,12 +38,10 @@ module.exports = React.createClass({
|
||||||
connectDropTarget: React.PropTypes.func,
|
connectDropTarget: React.PropTypes.func,
|
||||||
onClick: React.PropTypes.func,
|
onClick: React.PropTypes.func,
|
||||||
isDragging: React.PropTypes.bool,
|
isDragging: React.PropTypes.bool,
|
||||||
|
selectedRoom: React.PropTypes.string,
|
||||||
|
|
||||||
room: React.PropTypes.object.isRequired,
|
room: React.PropTypes.object.isRequired,
|
||||||
collapsed: React.PropTypes.bool.isRequired,
|
collapsed: React.PropTypes.bool.isRequired,
|
||||||
selected: React.PropTypes.bool.isRequired,
|
|
||||||
unread: React.PropTypes.bool.isRequired,
|
|
||||||
highlight: React.PropTypes.bool.isRequired,
|
|
||||||
isInvite: React.PropTypes.bool.isRequired,
|
isInvite: React.PropTypes.bool.isRequired,
|
||||||
incomingCall: React.PropTypes.object,
|
incomingCall: React.PropTypes.object,
|
||||||
},
|
},
|
||||||
|
@ -54,10 +54,11 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return({
|
return({
|
||||||
hover : false,
|
hover: false,
|
||||||
badgeHover : false,
|
badgeHover: false,
|
||||||
menuDisplayed: false,
|
menuDisplayed: false,
|
||||||
notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId),
|
notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId),
|
||||||
|
selected: this.props.room ? (this.props.selectedRoom === this.props.room.roomId) : false,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -79,23 +80,32 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onAccountData: function(accountDataEvent) {
|
|
||||||
if (accountDataEvent.getType() == 'm.push_rules') {
|
|
||||||
this.setState({
|
|
||||||
notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
MatrixClientPeg.get().on("accountData", this.onAccountData);
|
constantTimeDispatcher.register("RoomTile.refresh", this.props.room.roomId, this.onRefresh);
|
||||||
|
constantTimeDispatcher.register("RoomTile.select", this.props.room.roomId, this.onSelect);
|
||||||
|
this.onRefresh();
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
var cli = MatrixClientPeg.get();
|
constantTimeDispatcher.unregister("RoomTile.refresh", this.props.room.roomId, this.onRefresh);
|
||||||
if (cli) {
|
constantTimeDispatcher.unregister("RoomTile.select", this.props.room.roomId, this.onSelect);
|
||||||
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
},
|
||||||
}
|
|
||||||
|
componentWillReceiveProps: function(nextProps) {
|
||||||
|
this.onRefresh();
|
||||||
|
},
|
||||||
|
|
||||||
|
onRefresh: function(params) {
|
||||||
|
this.setState({
|
||||||
|
unread: Unread.doesRoomHaveUnreadMessages(this.props.room),
|
||||||
|
highlight: this.props.room.getUnreadNotificationCount('highlight') > 0 || this.props.isInvite,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onSelect: function(params) {
|
||||||
|
this.setState({
|
||||||
|
selected: params.selected,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onClick: function() {
|
onClick: function() {
|
||||||
|
@ -169,13 +179,13 @@ module.exports = React.createClass({
|
||||||
// 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();
|
||||||
const mentionBadges = this.props.highlight && this._shouldShowMentionBadge();
|
const mentionBadges = this.state.highlight && this._shouldShowMentionBadge();
|
||||||
const badges = notifBadges || mentionBadges;
|
const badges = notifBadges || mentionBadges;
|
||||||
|
|
||||||
var classes = classNames({
|
var classes = classNames({
|
||||||
'mx_RoomTile': true,
|
'mx_RoomTile': true,
|
||||||
'mx_RoomTile_selected': this.props.selected,
|
'mx_RoomTile_selected': this.state.selected,
|
||||||
'mx_RoomTile_unread': this.props.unread,
|
'mx_RoomTile_unread': this.state.unread,
|
||||||
'mx_RoomTile_unreadNotify': notifBadges,
|
'mx_RoomTile_unreadNotify': notifBadges,
|
||||||
'mx_RoomTile_highlight': mentionBadges,
|
'mx_RoomTile_highlight': mentionBadges,
|
||||||
'mx_RoomTile_invited': (me && me.membership == 'invite'),
|
'mx_RoomTile_invited': (me && me.membership == 'invite'),
|
||||||
|
@ -221,7 +231,7 @@ module.exports = React.createClass({
|
||||||
'mx_RoomTile_badgeShown': badges || this.state.badgeHover || this.state.menuDisplayed,
|
'mx_RoomTile_badgeShown': badges || this.state.badgeHover || this.state.menuDisplayed,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.props.selected) {
|
if (this.state.selected) {
|
||||||
let nameSelected = <EmojiText>{name}</EmojiText>;
|
let nameSelected = <EmojiText>{name}</EmojiText>;
|
||||||
|
|
||||||
label = <div title={ name } className={ nameClasses }>{ nameSelected }</div>;
|
label = <div title={ name } className={ nameClasses }>{ nameSelected }</div>;
|
||||||
|
@ -255,7 +265,8 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
let ret = (
|
let ret = (
|
||||||
<div> { /* Only native elements can be wrapped in a DnD object. */}
|
<div> { /* Only native elements can be wrapped in a DnD object. */}
|
||||||
<AccessibleButton className={classes} tabIndex="0" onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
<AccessibleButton className={classes} tabIndex="0" onClick={this.onClick}
|
||||||
|
onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||||
<div className={avatarClasses}>
|
<div className={avatarClasses}>
|
||||||
<div className="mx_RoomTile_avatar_container">
|
<div className="mx_RoomTile_avatar_container">
|
||||||
<RoomAvatar room={this.props.room} width={24} height={24} />
|
<RoomAvatar room={this.props.room} width={24} height={24} />
|
||||||
|
|
Loading…
Reference in a new issue