Merge pull request #62 from matrix-org/kegan/archived-rooms

Show archived rooms and implement forgetting
This commit is contained in:
Kegsay 2015-12-18 17:36:36 +00:00
commit e57d0601b6
3 changed files with 119 additions and 17 deletions

View file

@ -875,7 +875,19 @@ module.exports = React.createClass({
action: 'leave_room', action: 'leave_room',
room_id: this.props.roomId, room_id: this.props.roomId,
}); });
this.props.onFinished(); },
onForgetClick: function() {
MatrixClientPeg.get().forget(this.props.roomId).done(function() {
dis.dispatch({ action: 'view_next_room' });
}, function(err) {
var errCode = err.errcode || "unknown error code";
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Error",
description: `Failed to forget room (${errCode})`
});
});
}, },
onRejectButtonClicked: function(ev) { onRejectButtonClicked: function(ev) {
@ -1267,16 +1279,23 @@ module.exports = React.createClass({
} }
var messageComposer, searchInfo; var messageComposer, searchInfo;
if (!this.state.searchResults) { var canSpeak = (
// joined and not showing search results
myMember && (myMember.membership == 'join') && !this.state.searchResults
);
if (canSpeak) {
messageComposer = messageComposer =
<MessageComposer room={this.state.room} roomView={this} uploadFile={this.uploadFile} callState={this.state.callState} /> <MessageComposer room={this.state.room} roomView={this} uploadFile={this.uploadFile} callState={this.state.callState} />
} }
else {
// TODO: Why aren't we storing the term/scope/count in this format
// in this.state if this is what RoomHeader desires?
if (this.state.searchResults) {
searchInfo = { searchInfo = {
searchTerm : this.state.searchTerm, searchTerm : this.state.searchTerm,
searchScope : this.state.searchScope, searchScope : this.state.searchScope,
searchCount : this.state.searchCount, searchCount : this.state.searchCount,
} };
} }
var call = CallHandler.getCallForRoom(this.props.roomId); var call = CallHandler.getCallForRoom(this.props.roomId);
@ -1323,8 +1342,18 @@ module.exports = React.createClass({
return ( return (
<div className={ "mx_RoomView" + (inCall ? " mx_RoomView_inCall" : "") }> <div className={ "mx_RoomView" + (inCall ? " mx_RoomView_inCall" : "") }>
<RoomHeader ref="header" room={this.state.room} searchInfo={searchInfo} editing={this.state.editingRoomSettings} onSearchClick={this.onSearchClick} <RoomHeader ref="header" room={this.state.room} searchInfo={searchInfo}
onSettingsClick={this.onSettingsClick} onSaveClick={this.onSaveClick} onCancelClick={this.onCancelClick} onLeaveClick={this.onLeaveClick} /> editing={this.state.editingRoomSettings}
onSearchClick={this.onSearchClick}
onSettingsClick={this.onSettingsClick}
onSaveClick={this.onSaveClick}
onCancelClick={this.onCancelClick}
onForgetClick={
(myMember && myMember.membership === "leave") ? this.onForgetClick : null
}
onLeaveClick={
(myMember && myMember.membership === "join") ? this.onLeaveClick : null
} />
{ fileDropTarget } { fileDropTarget }
<div className="mx_RoomView_auxPanel"> <div className="mx_RoomView_auxPanel">
<CallView ref="callView" room={this.state.room} ConferenceHandler={this.props.ConferenceHandler}/> <CallView ref="callView" room={this.state.room} ConferenceHandler={this.props.ConferenceHandler}/>

View file

@ -131,7 +131,17 @@ module.exports = React.createClass({
if (this.props.onLeaveClick) { if (this.props.onLeaveClick) {
leave_button = leave_button =
<div className="mx_RoomHeader_button mx_RoomHeader_leaveButton"> <div className="mx_RoomHeader_button mx_RoomHeader_leaveButton">
<img src="img/leave.svg" title="Leave room" alt="Leave room" width="26" height="20" onClick={this.props.onLeaveClick}/> <img src="img/leave.svg" title="Leave room" alt="Leave room"
width="26" height="20" onClick={this.props.onLeaveClick}/>
</div>;
}
var forget_button;
if (this.props.onForgetClick) {
forget_button =
<div className="mx_RoomHeader_button mx_RoomHeader_leaveButton">
<img src="img/leave.svg" title="Forget room" alt="Forget room"
width="26" height="20" onClick={this.props.onForgetClick}/>
</div>; </div>;
} }
@ -149,6 +159,7 @@ module.exports = React.createClass({
{cancel_button} {cancel_button}
{save_button} {save_button}
<div className="mx_RoomHeader_rightRow"> <div className="mx_RoomHeader_rightRow">
{ forget_button }
{ leave_button } { leave_button }
<div className="mx_RoomHeader_button"> <div className="mx_RoomHeader_button">
<img src="img/search.svg" title="Search" alt="Search" width="21" height="19" onClick={this.props.onSearchClick}/> <img src="img/search.svg" title="Search" alt="Search" width="21" height="19" onClick={this.props.onSearchClick}/>

View file

@ -39,6 +39,7 @@ module.exports = React.createClass({
getInitialState: function() { getInitialState: function() {
return { return {
activityMap: null, activityMap: null,
isLoadingLeftRooms: false,
lists: {}, lists: {},
incomingCall: null, incomingCall: null,
} }
@ -47,6 +48,7 @@ module.exports = React.createClass({
componentWillMount: function() { componentWillMount: function() {
var cli = MatrixClientPeg.get(); var cli = MatrixClientPeg.get();
cli.on("Room", this.onRoom); cli.on("Room", this.onRoom);
cli.on("deleteRoom", this.onDeleteRoom);
cli.on("Room.timeline", this.onRoomTimeline); cli.on("Room.timeline", this.onRoomTimeline);
cli.on("Room.name", this.onRoomName); cli.on("Room.name", this.onRoomName);
cli.on("Room.tags", this.onRoomTags); cli.on("Room.tags", this.onRoomTags);
@ -104,7 +106,26 @@ module.exports = React.createClass({
}, },
onRoom: function(room) { onRoom: function(room) {
this.refreshRoomList(); this._delayedRefreshRoomList();
},
onDeleteRoom: function(roomId) {
this._delayedRefreshRoomList();
},
onArchivedHeaderClick: function(isHidden) {
if (!isHidden) {
var self = this;
this.setState({ isLoadingLeftRooms: true });
// we don't care about the response since it comes down via "Room"
// events.
MatrixClientPeg.get().syncLeftRooms().catch(function(err) {
console.error("Failed to sync left rooms: %s", err);
console.error(err);
}).finally(function() {
self.setState({ isLoadingLeftRooms: false });
});
}
}, },
onRoomTimeline: function(ev, room, toStartOfTimeline) { onRoomTimeline: function(ev, room, toStartOfTimeline) {
@ -143,22 +164,57 @@ module.exports = React.createClass({
}, },
onRoomName: function(room) { onRoomName: function(room) {
this.refreshRoomList(); this._delayedRefreshRoomList();
}, },
onRoomTags: function(event, room) { onRoomTags: function(event, room) {
this.refreshRoomList(); this._delayedRefreshRoomList();
}, },
onRoomStateEvents: function(ev, state) { onRoomStateEvents: function(ev, state) {
setTimeout(this.refreshRoomList, 0); this._delayedRefreshRoomList();
}, },
onRoomMemberName: function(ev, member) { onRoomMemberName: function(ev, member) {
setTimeout(this.refreshRoomList, 0); this._delayedRefreshRoomList();
},
_delayedRefreshRoomList: function() {
// There can be 1000s of JS SDK events when rooms are initially synced;
// we don't want to do lots of work rendering until things have settled.
// Therefore, keep a 1s refresh buffer which will refresh the room list
// at MOST once every 1s to prevent thrashing.
var MAX_REFRESH_INTERVAL_MS = 1000;
var self = this;
if (!self._lastRefreshRoomListTs) {
self.refreshRoomList(); // first refresh evar
}
else {
var timeWaitedMs = Date.now() - self._lastRefreshRoomListTs;
if (timeWaitedMs > MAX_REFRESH_INTERVAL_MS) {
clearTimeout(self._refreshRoomListTimerId);
self._refreshRoomListTimerId = null;
self.refreshRoomList(); // refreshed more than MAX_REFRESH_INTERVAL_MS ago
}
else {
// refreshed less than MAX_REFRESH_INTERVAL_MS ago, wait the difference
// if we aren't already waiting. If we are waiting then NOP, it will
// fire soon, promise!
if (!self._refreshRoomListTimerId) {
self._refreshRoomListTimerId = setTimeout(function() {
self.refreshRoomList();
}, 10 + MAX_REFRESH_INTERVAL_MS - timeWaitedMs); // 10 is a buffer amount
}
}
}
}, },
refreshRoomList: function() { refreshRoomList: function() {
// console.log("DEBUG: Refresh room list delta=%s ms",
// (!this._lastRefreshRoomListTs ? "-" : (Date.now() - this._lastRefreshRoomListTs))
// );
// TODO: rather than bluntly regenerating and re-sorting everything // TODO: rather than bluntly regenerating and re-sorting everything
// every time we see any kind of room change from the JS SDK // every time we see any kind of room change from the JS SDK
// we could do incremental updates on our copy of the state // we could do incremental updates on our copy of the state
@ -166,6 +222,7 @@ module.exports = React.createClass({
// us re-rendering all the sublists every time anything changes anywhere // us re-rendering all the sublists every time anything changes anywhere
// in the state of the client. // in the state of the client.
this.setState(this.getRoomLists()); this.setState(this.getRoomLists());
this._lastRefreshRoomListTs = Date.now();
}, },
getRoomLists: function() { getRoomLists: function() {
@ -184,9 +241,12 @@ module.exports = React.createClass({
if (me && me.membership == "invite") { if (me && me.membership == "invite") {
s.lists["im.vector.fake.invite"].push(room); s.lists["im.vector.fake.invite"].push(room);
} }
else if (me && me.membership === "leave") {
s.lists["im.vector.fake.archived"].push(room);
}
else { else {
var shouldShowRoom = ( var shouldShowRoom = (
me && (me.membership == "join") me && (me.membership == "join" || me.membership === "ban")
); );
// hiding conf rooms only ever toggles shouldShowRoom to false // hiding conf rooms only ever toggles shouldShowRoom to false
@ -355,7 +415,6 @@ module.exports = React.createClass({
verb="demote" verb="demote"
editable={ true } editable={ true }
order="recent" order="recent"
bottommost={ self.state.lists['im.vector.fake.archived'].length === 0 }
activityMap={ self.state.activityMap } activityMap={ self.state.activityMap }
selectedRoom={ self.props.selectedRoom } selectedRoom={ self.props.selectedRoom }
incomingCall={ self.state.incomingCall } incomingCall={ self.state.incomingCall }
@ -365,11 +424,14 @@ module.exports = React.createClass({
label="Historical" label="Historical"
editable={ false } editable={ false }
order="recent" order="recent"
bottommost={ true }
activityMap={ self.state.activityMap } activityMap={ self.state.activityMap }
selectedRoom={ self.props.selectedRoom } selectedRoom={ self.props.selectedRoom }
incomingCall={ self.state.incomingCall } collapsed={ self.props.collapsed }
collapsed={ self.props.collapsed } /> alwaysShowHeader={ true }
startAsHidden={ true }
showSpinner={ self.state.isLoadingLeftRooms }
onHeaderClick= { self.onArchivedHeaderClick }
incomingCall={ self.state.incomingCall } />
</div> </div>
</GeminiScrollbar> </GeminiScrollbar>
); );