Make the left panel more friendly to new users

https://github.com/vector-im/riot-web/issues/3609

Conflicts:
	src/components/views/rooms/RoomList.js

cherry-picking commit f5f35e3.
This commit is contained in:
David Baker 2017-04-26 18:59:16 +01:00 committed by Luke Barnard
parent f3fc459875
commit 5531f27435

View file

@ -29,7 +29,14 @@ 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 HIDE_CONFERENCE_CHANS = true; const HIDE_CONFERENCE_CHANS = true;
const VERBS = {
'm.favourite': 'favourite',
'im.vector.fake.direct': 'tag direct chat',
'im.vector.fake.recent': 'restore',
'm.lowpriority': 'demote',
};
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'RoomList', displayName: 'RoomList',
@ -44,6 +51,7 @@ module.exports = React.createClass({
getInitialState: function() { getInitialState: function() {
return { return {
isLoadingLeftRooms: false, isLoadingLeftRooms: false,
totalRoomCount: null,
lists: {}, lists: {},
incomingCall: null, incomingCall: null,
}; };
@ -63,8 +71,17 @@ module.exports = React.createClass({
cli.on("RoomMember.name", this.onRoomMemberName); cli.on("RoomMember.name", this.onRoomMemberName);
cli.on("accountData", this.onAccountData); cli.on("accountData", this.onAccountData);
var s = this.getRoomLists(); // lookup for which lists a given roomId is currently in.
this.setState(s); this.listsForRoomId = {};
this.refreshRoomList();
// order of the sublists
//this.listOrder = [];
// loop count to stop a stack overflow if the user keeps waggling the
// mouse for >30s in a row, or if running under mocha
this._delayedRefreshRoomListLoopCount = 0
}, },
componentDidMount: function() { componentDidMount: function() {
@ -202,31 +219,33 @@ module.exports = React.createClass({
}, 500), }, 500),
refreshRoomList: function() { refreshRoomList: function() {
// console.log("DEBUG: Refresh room list delta=%s ms", // TODO: ideally we'd calculate this once at start, and then maintain
// (!this._lastRefreshRoomListTs ? "-" : (Date.now() - this._lastRefreshRoomListTs)) // any changes to it incrementally, updating the appropriate sublists
// ); // as needed.
// Alternatively we'd do something magical with Immutable.js or similar.
// TODO: rather than bluntly regenerating and re-sorting everything const lists = this.getRoomLists();
// every time we see any kind of room change from the JS SDK let totalRooms = 0;
// we could do incremental updates on our copy of the state for (const l of Object.values(lists)) {
// based on the room which has actually changed. This would stop totalRooms += l.length;
// us re-rendering all the sublists every time anything changes anywhere }
// in the state of the client. this.setState({
this.setState(this.getRoomLists()); lists: this.getRoomLists(),
totalRoomCount: totalRooms,
});
// this._lastRefreshRoomListTs = Date.now(); // this._lastRefreshRoomListTs = Date.now();
}, },
getRoomLists: function() { getRoomLists: function() {
var self = this; var self = this;
var s = { lists: {} }; const lists = {};
s.lists["im.vector.fake.invite"] = []; lists["im.vector.fake.invite"] = [];
s.lists["m.favourite"] = []; lists["m.favourite"] = [];
s.lists["im.vector.fake.recent"] = []; lists["im.vector.fake.recent"] = [];
s.lists["im.vector.fake.direct"] = []; lists["im.vector.fake.direct"] = [];
s.lists["m.lowpriority"] = []; lists["m.lowpriority"] = [];
s.lists["im.vector.fake.archived"] = []; lists["im.vector.fake.archived"] = [];
const dmRoomMap = new DMRoomMap(MatrixClientPeg.get()); const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
@ -240,7 +259,8 @@ module.exports = React.createClass({
// ", prevMembership = " + me.events.member.getPrevContent().membership); // ", prevMembership = " + me.events.member.getPrevContent().membership);
if (me.membership == "invite") { if (me.membership == "invite") {
s.lists["im.vector.fake.invite"].push(room); self.listsForRoomId[room.roomId].push("im.vector.fake.invite");
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)) {
// skip past this room & don't put it in any lists // skip past this room & don't put it in any lists
@ -254,48 +274,55 @@ module.exports = React.createClass({
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] || []; lists[tagName] = lists[tagName] || [];
s.lists[tagNames[i]].push(room); 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)
s.lists["im.vector.fake.direct"].push(room); self.listsForRoomId[room.roomId].push("im.vector.fake.direct");
lists["im.vector.fake.direct"].push(room);
} }
else { else {
s.lists["im.vector.fake.recent"].push(room); self.listsForRoomId[room.roomId].push("im.vector.fake.recent");
lists["im.vector.fake.recent"].push(room);
} }
} }
else if (me.membership === "leave") { else if (me.membership === "leave") {
s.lists["im.vector.fake.archived"].push(room); self.listsForRoomId[room.roomId].push("im.vector.fake.archived");
lists["im.vector.fake.archived"].push(room);
} }
else { else {
console.error("unrecognised membership: " + me.membership + " - this should never happen"); console.error("unrecognised membership: " + me.membership + " - this should never happen");
} }
}); });
if (s.lists["im.vector.fake.direct"].length == 0 && if (lists["im.vector.fake.direct"].length == 0 &&
MatrixClientPeg.get().getAccountData('m.direct') === undefined && MatrixClientPeg.get().getAccountData('m.direct') === undefined &&
!MatrixClientPeg.get().isGuest()) !MatrixClientPeg.get().isGuest())
{ {
// scan through the 'recents' list for any rooms which look like DM rooms // scan through the 'recents' list for any rooms which look like DM rooms
// and make them DM rooms // and make them DM rooms
const oldRecents = s.lists["im.vector.fake.recent"]; const oldRecents = lists["im.vector.fake.recent"];
s.lists["im.vector.fake.recent"] = []; lists["im.vector.fake.recent"] = [];
for (const room of oldRecents) { for (const room of oldRecents) {
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)) {
s.lists["im.vector.fake.direct"].push(room); self.listsForRoomId[room.roomId].push("im.vector.fake.direct");
lists["im.vector.fake.direct"].push(room);
} else { } else {
s.lists["im.vector.fake.recent"].push(room); self.listsForRoomId[room.roomId].push("im.vector.fake.recent");
lists["im.vector.fake.recent"].push(room);
} }
} }
// save these new guessed DM rooms into the account data // save these new guessed DM rooms into the account data
const newMDirectEvent = {}; const newMDirectEvent = {};
for (const room of s.lists["im.vector.fake.direct"]) { for (const room of lists["im.vector.fake.direct"]) {
const me = room.getMember(MatrixClientPeg.get().credentials.userId); const me = room.getMember(MatrixClientPeg.get().credentials.userId);
const otherPerson = Rooms.getOnlyOtherMember(room, me); const otherPerson = Rooms.getOnlyOtherMember(room, me);
if (!otherPerson) continue; if (!otherPerson) continue;
@ -313,7 +340,22 @@ 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.
return s; // 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 lists;
}, },
_getScrollNode: function() { _getScrollNode: function() {
@ -467,6 +509,49 @@ module.exports = React.createClass({
this.refs.gemscroll.forceUpdate(); this.refs.gemscroll.forceUpdate();
}, },
_getEmptyContent: function(section) {
let greyed = false;
if (this.state.totalRoomCount === 0) {
const TintableSvg = sdk.getComponent('elements.TintableSvg');
switch (section) {
case 'm.favourite':
case 'm.lowpriority':
greyed = true;
break;
case 'im.vector.fake.direct':
return <div className="mx_RoomList_emptySubListTip">
<div className="mx_RoomList_butonPreview">
<TintableSvg src="img/icons-people.svg" width="25" height="25" />
</div>
Use the button below to chat with someone!
</div>;
case 'im.vector.fake.recent':
return <div className="mx_RoomList_emptySubListTip">
<div className="mx_RoomList_butonPreview">
<TintableSvg src="img/icons-directory.svg" width="25" height="25" />
</div>
Use the button below to browse the room directory
<br /><br />
<div className="mx_RoomList_butonPreview">
<TintableSvg src="img/icons-create-room.svg" width="25" height="25" />
</div>
or this button to start a new one!
</div>;
}
}
const RoomDropTarget = sdk.getComponent('rooms.RoomDropTarget');
const labelText = 'Drop here to ' + (VERBS[section] || 'tag ' + section);
let label;
if (greyed) {
label = <span className="mx_RoomList_greyedSubListLabel">{labelText}</span>;
} else {
label = labelText;
}
return <RoomDropTarget label={label} />;
},
render: function() { render: function() {
var RoomSubList = sdk.getComponent('structures.RoomSubList'); var RoomSubList = sdk.getComponent('structures.RoomSubList');
var self = this; var self = this;
@ -489,7 +574,7 @@ module.exports = React.createClass({
<RoomSubList list={ self.state.lists['m.favourite'] } <RoomSubList list={ self.state.lists['m.favourite'] }
label="Favourites" label="Favourites"
tagName="m.favourite" tagName="m.favourite"
verb="favourite" emptyContent={this._getEmptyContent('m.favourite')}
editable={ true } editable={ true }
order="manual" order="manual"
selectedRoom={ self.props.selectedRoom } selectedRoom={ self.props.selectedRoom }
@ -502,7 +587,7 @@ module.exports = React.createClass({
<RoomSubList list={ self.state.lists['im.vector.fake.direct'] } <RoomSubList list={ self.state.lists['im.vector.fake.direct'] }
label="People" label="People"
tagName="im.vector.fake.direct" tagName="im.vector.fake.direct"
verb="tag direct chat" emptyContent={this._getEmptyContent('im.vector.fake.direct')}
editable={ true } editable={ true }
order="recent" order="recent"
selectedRoom={ self.props.selectedRoom } selectedRoom={ self.props.selectedRoom }
@ -516,7 +601,7 @@ module.exports = React.createClass({
<RoomSubList list={ self.state.lists['im.vector.fake.recent'] } <RoomSubList list={ self.state.lists['im.vector.fake.recent'] }
label="Rooms" label="Rooms"
editable={ true } editable={ true }
verb="restore" emptyContent={this._getEmptyContent('im.vector.fake.recent')}
order="recent" order="recent"
selectedRoom={ self.props.selectedRoom } selectedRoom={ self.props.selectedRoom }
incomingCall={ self.state.incomingCall } incomingCall={ self.state.incomingCall }
@ -531,7 +616,7 @@ module.exports = React.createClass({
key={ tagName } key={ tagName }
label={ tagName } label={ tagName }
tagName={ tagName } tagName={ tagName }
verb={ "tag as " + tagName } emptyContent={this._getEmptyContent(tagName)}
editable={ true } editable={ true }
order="manual" order="manual"
selectedRoom={ self.props.selectedRoom } selectedRoom={ self.props.selectedRoom }
@ -547,7 +632,7 @@ module.exports = React.createClass({
<RoomSubList list={ self.state.lists['m.lowpriority'] } <RoomSubList list={ self.state.lists['m.lowpriority'] }
label="Low priority" label="Low priority"
tagName="m.lowpriority" tagName="m.lowpriority"
verb="demote" emptyContent={this._getEmptyContent('m.lowpriority')}
editable={ true } editable={ true }
order="recent" order="recent"
selectedRoom={ self.props.selectedRoom } selectedRoom={ self.props.selectedRoom }