Merge pull request #270 from matrix-org/rav/issue_1314_federated_rooms

RoomView: Handle joining federated rooms
This commit is contained in:
Richard van der Hoff 2016-04-13 16:36:46 +01:00
commit 85277a237a
2 changed files with 106 additions and 82 deletions

View file

@ -628,10 +628,13 @@ module.exports = React.createClass({
if (!this.refs.roomView) { if (!this.refs.roomView) {
return; return;
} }
var roomview = this.refs.roomView; var roomview = this.refs.roomView;
var roomId = this.refs.roomView.getRoomId();
if (!roomId) {
return;
}
var state = roomview.getScrollState(); var state = roomview.getScrollState();
this.scrollStateMap[roomview.props.roomId] = state; this.scrollStateMap[roomId] = state;
}, },
onLoggedIn: function(credentials) { onLoggedIn: function(credentials) {
@ -1066,8 +1069,7 @@ module.exports = React.createClass({
page_element = ( page_element = (
<RoomView <RoomView
ref="roomView" ref="roomView"
roomId={this.state.currentRoom} roomAddress={this.state.currentRoom || this.state.currentRoomAlias}
roomAlias={this.state.currentRoomAlias}
eventId={this.state.initialEventId} eventId={this.state.initialEventId}
thirdPartyInvite={this.state.thirdPartyInvite} thirdPartyInvite={this.state.thirdPartyInvite}
oobData={this.state.roomOobData} oobData={this.state.roomOobData}

View file

@ -54,11 +54,15 @@ module.exports = React.createClass({
propTypes: { propTypes: {
ConferenceHandler: React.PropTypes.any, ConferenceHandler: React.PropTypes.any,
roomId: React.PropTypes.string.isRequired, // the ID for this room (or, if we don't know it, an alias for it)
//
// if we are referring to this room by a given alias (e.g. in the URL), track it. // XXX: if this is an alias, we will display a 'join' dialogue,
// useful for joining rooms by alias correctly (and fixing https://github.com/vector-im/vector-web/issues/819) // regardless of whether we are already a member, or if the room is
roomAlias: React.PropTypes.string, // peekable. Currently there is a big mess, where at least four
// different components (RoomView, MatrixChat, RoomDirectory,
// SlashCommands) have logic for turning aliases into rooms, and each
// of them do it differently and have different edge cases.
roomAddress: React.PropTypes.string.isRequired,
// An object representing a third party invite to join this room // An object representing a third party invite to join this room
// Fields: // Fields:
@ -93,7 +97,7 @@ module.exports = React.createClass({
}, },
getInitialState: function() { getInitialState: function() {
var room = this.props.roomId ? MatrixClientPeg.get().getRoom(this.props.roomId) : null; var room = MatrixClientPeg.get().getRoom(this.props.roomAddress);
return { return {
room: room, room: room,
roomLoading: !room, roomLoading: !room,
@ -123,7 +127,6 @@ module.exports = React.createClass({
this.dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
MatrixClientPeg.get().on("Room", this.onRoom); MatrixClientPeg.get().on("Room", this.onRoom);
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline); MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
MatrixClientPeg.get().on("Room.name", this.onRoomName);
MatrixClientPeg.get().on("Room.accountData", this.onRoomAccountData); MatrixClientPeg.get().on("Room.accountData", this.onRoomAccountData);
MatrixClientPeg.get().on("RoomState.members", this.onRoomStateMember); MatrixClientPeg.get().on("RoomState.members", this.onRoomStateMember);
// xchat-style tab complete, add a colon if tab // xchat-style tab complete, add a colon if tab
@ -146,9 +149,9 @@ module.exports = React.createClass({
// We can /peek though. If it fails then we present the join UI. If it // We can /peek though. If it fails then we present the join UI. If it
// succeeds then great, show the preview (but we still may be able to /join!). // succeeds then great, show the preview (but we still may be able to /join!).
if (!this.state.room) { if (!this.state.room) {
console.log("Attempting to peek into room %s", this.props.roomId); console.log("Attempting to peek into room %s", this.props.roomAddress);
MatrixClientPeg.get().peekInRoom(this.props.roomId).then((room) => { MatrixClientPeg.get().peekInRoom(this.props.roomAddress).then((room) => {
this.setState({ this.setState({
room: room, room: room,
roomLoading: false, roomLoading: false,
@ -200,7 +203,6 @@ module.exports = React.createClass({
if (MatrixClientPeg.get()) { if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("Room", this.onRoom); MatrixClientPeg.get().removeListener("Room", this.onRoom);
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline); MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
MatrixClientPeg.get().removeListener("Room.accountData", this.onRoomAccountData); MatrixClientPeg.get().removeListener("Room.accountData", this.onRoomAccountData);
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember); MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
} }
@ -233,7 +235,7 @@ module.exports = React.createClass({
return; return;
} }
var call = CallHandler.getCallForRoom(payload.room_id); var call = this._getCallForRoom();
var callState; var callState;
if (call) { if (call) {
@ -256,7 +258,7 @@ module.exports = React.createClass({
}, },
componentWillReceiveProps: function(newProps) { componentWillReceiveProps: function(newProps) {
if (newProps.roomId != this.props.roomId) { if (newProps.roomAddress != this.props.roomAddress) {
throw new Error("changing room on a RoomView is not supported"); throw new Error("changing room on a RoomView is not supported");
} }
@ -270,7 +272,7 @@ module.exports = React.createClass({
if (this.unmounted) return; if (this.unmounted) return;
// ignore events for other rooms // ignore events for other rooms
if (room.roomId != this.props.roomId) return; if (!this.state.room || room.roomId != this.state.room.roomId) return;
// ignore anything but real-time updates at the end of the room: // ignore anything but real-time updates at the end of the room:
// updates from pagination will happen when the paginate completes. // updates from pagination will happen when the paginate completes.
@ -321,30 +323,18 @@ module.exports = React.createClass({
// set it in our state and start using it (ie. init the timeline) // set it in our state and start using it (ie. init the timeline)
// This will happen if we start off viewing a room we're not joined, // This will happen if we start off viewing a room we're not joined,
// then join it whilst RoomView is looking at that room. // then join it whilst RoomView is looking at that room.
if (room.roomId == this.props.roomId && !this.state.room) { if (!this.state.room && room.roomId == this._joiningRoomId) {
this._joiningRoomId = undefined;
this.setState({ this.setState({
room: room room: room,
joining: false,
}); });
this._onRoomLoaded(room); this._onRoomLoaded(room);
} }
}, },
onRoomName: function(room) {
// NB don't set state.room here.
//
// When peeking, this event lands *before* the timeline is correctly
// synced; if we set state.room here, the TimelinePanel will be
// instantiated, and it will initialise its scroll state, with *no
// events*. In short, the scroll state will be all messed up.
//
// There's no need to set state.room here anyway.
if (room.roomId == this.props.roomId) {
this.forceUpdate();
}
},
updateTint: function() { updateTint: function() {
var room = MatrixClientPeg.get().getRoom(this.props.roomId); var room = this.state.room;
if (!room) return; if (!room) return;
var color_scheme_event = room.getAccountData("org.matrix.room.color_scheme"); var color_scheme_event = room.getAccountData("org.matrix.room.color_scheme");
@ -367,28 +357,33 @@ module.exports = React.createClass({
}, },
onRoomStateMember: function(ev, state, member) { onRoomStateMember: function(ev, state, member) {
if (member.roomId === this.props.roomId) { // ignore if we don't have a room yet
// a member state changed in this room, refresh the tab complete list if (!this.state.room) {
this._updateTabCompleteList();
var room = MatrixClientPeg.get().getRoom(this.props.roomId);
if (!room) return;
var me = MatrixClientPeg.get().credentials.userId;
if (this.state.joining && room.hasMembershipState(me, "join")) {
this.setState({
joining: false
});
}
}
if (!this.props.ConferenceHandler) {
return; return;
} }
if (member.roomId !== this.props.roomId ||
member.userId !== this.props.ConferenceHandler.getConferenceUserIdForRoom(member.roomId)) { // ignore members in other rooms
if (member.roomId !== this.state.room.roomId) {
return; return;
} }
this._updateConfCallNotification();
// a member state changed in this room, refresh the tab complete list
this._updateTabCompleteList();
// if we are now a member of the room, where we were not before, that
// means we have finished joining a room we were previously peeking
// into.
var me = MatrixClientPeg.get().credentials.userId;
if (this.state.joining && this.state.room.hasMembershipState(me, "join")) {
this.setState({
joining: false
});
}
if (this.props.ConferenceHandler &&
member.userId === this.props.ConferenceHandler.getConferenceUserIdForRoom(member.roomId)) {
this._updateConfCallNotification();
}
}, },
_hasUnsentMessages: function(room) { _hasUnsentMessages: function(room) {
@ -403,12 +398,12 @@ module.exports = React.createClass({
}, },
_updateConfCallNotification: function() { _updateConfCallNotification: function() {
var room = MatrixClientPeg.get().getRoom(this.props.roomId); var room = this.state.room;
if (!room || !this.props.ConferenceHandler) { if (!room || !this.props.ConferenceHandler) {
return; return;
} }
var confMember = room.getMember( var confMember = room.getMember(
this.props.ConferenceHandler.getConferenceUserIdForRoom(this.props.roomId) this.props.ConferenceHandler.getConferenceUserIdForRoom(room.roomId)
); );
if (!confMember) { if (!confMember) {
@ -427,7 +422,7 @@ module.exports = React.createClass({
}, },
componentDidMount: function() { componentDidMount: function() {
var call = CallHandler.getCallForRoom(this.props.roomId); var call = this._getCallForRoom();
var callState = call ? call.call_state : "ended"; var callState = call ? call.call_state : "ended";
this.setState({ this.setState({
callState: callState callState: callState
@ -559,25 +554,35 @@ module.exports = React.createClass({
display_name_promise.then(() => { display_name_promise.then(() => {
var sign_url = this.props.thirdPartyInvite ? this.props.thirdPartyInvite.inviteSignUrl : undefined; var sign_url = this.props.thirdPartyInvite ? this.props.thirdPartyInvite.inviteSignUrl : undefined;
return MatrixClientPeg.get().joinRoom(this.props.roomAlias || this.props.roomId, return MatrixClientPeg.get().joinRoom(this.props.roomAddress,
{ inviteSignUrl: sign_url } ) { inviteSignUrl: sign_url } )
}).done(function() { }).then(function(resp) {
var roomId = resp.roomId;
// It is possible that there is no Room yet if state hasn't come down // It is possible that there is no Room yet if state hasn't come down
// from /sync - joinRoom will resolve when the HTTP request to join succeeds, // from /sync - joinRoom will resolve when the HTTP request to join succeeds,
// NOT when it comes down /sync. If there is no room, we'll keep the // NOT when it comes down /sync. If there is no room, we'll keep the
// joining flag set until we see it. Likewise, if our state is not // joining flag set until we see it.
// "join" we'll keep this flag set until it comes down /sync.
// We'll need to initialise the timeline when joining, but due to // We'll need to initialise the timeline when joining, but due to
// the above, we can't do it here: we do it in onRoom instead, // the above, we can't do it here: we do it in onRoom instead,
// once we have a useable room object. // once we have a useable room object.
var room = MatrixClientPeg.get().getRoom(self.props.roomId); var room = MatrixClientPeg.get().getRoom(roomId);
var me = MatrixClientPeg.get().credentials.userId; if (!room) {
self.setState({ // wait for the room to turn up in onRoom.
joining: room ? !room.hasMembershipState(me, "join") : true, self._joiningRoomId = roomId;
room: room } else {
}); // we've got a valid room, but that might also just mean that
}, function(error) { // it was peekable (so we had one before anyway). If we are
// not yet a member of the room, we will need to wait for that
// to happen, in onRoomStateMember.
var me = MatrixClientPeg.get().credentials.userId;
self.setState({
joining: !room.hasMembershipState(me, "join"),
room: room
});
}
}).catch(function(error) {
self.setState({ self.setState({
joining: false, joining: false,
joinError: error joinError: error
@ -612,7 +617,8 @@ module.exports = React.createClass({
description: msg description: msg
}); });
} }
}); }).done();
this.setState({ this.setState({
joining: true joining: true
}); });
@ -667,7 +673,7 @@ module.exports = React.createClass({
uploadFile: function(file) { uploadFile: function(file) {
var self = this; var self = this;
ContentMessages.sendContentToRoom( ContentMessages.sendContentToRoom(
file, this.props.roomId, MatrixClientPeg.get() file, this.state.room.roomId, MatrixClientPeg.get()
).done(undefined, function(error) { ).done(undefined, function(error) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
@ -702,7 +708,7 @@ module.exports = React.createClass({
filter = { filter = {
// XXX: it's unintuitive that the filter for searching doesn't have the same shape as the v2 filter API :( // XXX: it's unintuitive that the filter for searching doesn't have the same shape as the v2 filter API :(
rooms: [ rooms: [
this.props.roomId this.state.room.roomId
] ]
}; };
} }
@ -910,12 +916,12 @@ module.exports = React.createClass({
onLeaveClick: function() { onLeaveClick: function() {
dis.dispatch({ dis.dispatch({
action: 'leave_room', action: 'leave_room',
room_id: this.props.roomId, room_id: this.state.room.roomId,
}); });
}, },
onForgetClick: function() { onForgetClick: function() {
MatrixClientPeg.get().forget(this.props.roomId).done(function() { MatrixClientPeg.get().forget(this.state.room.roomId).done(function() {
dis.dispatch({ action: 'view_next_room' }); dis.dispatch({ action: 'view_next_room' });
}, function(err) { }, function(err) {
var errCode = err.errcode || "unknown error code"; var errCode = err.errcode || "unknown error code";
@ -932,7 +938,7 @@ module.exports = React.createClass({
this.setState({ this.setState({
rejecting: true rejecting: true
}); });
MatrixClientPeg.get().leave(this.props.roomId).done(function() { MatrixClientPeg.get().leave(this.props.roomAddress).done(function() {
dis.dispatch({ action: 'view_next_room' }); dis.dispatch({ action: 'view_next_room' });
self.setState({ self.setState({
rejecting: false rejecting: false
@ -1090,7 +1096,7 @@ module.exports = React.createClass({
}, },
onMuteAudioClick: function() { onMuteAudioClick: function() {
var call = CallHandler.getCallForRoom(this.props.roomId); var call = this._getCallForRoom();
if (!call) { if (!call) {
return; return;
} }
@ -1102,7 +1108,7 @@ module.exports = React.createClass({
}, },
onMuteVideoClick: function() { onMuteVideoClick: function() {
var call = CallHandler.getCallForRoom(this.props.roomId); var call = this._getCallForRoom();
if (!call) { if (!call) {
return; return;
} }
@ -1142,6 +1148,29 @@ module.exports = React.createClass({
} }
}, },
/**
* Get the ID of the displayed room
*
* Returns null if the RoomView was instantiated on a room alias and
* we haven't yet joined the room.
*/
getRoomId: function() {
if (!this.state.room) {
return null;
}
return this.state.room.roomId;
},
/**
* get any current call for this room
*/
_getCallForRoom: function() {
if (!this.state.room) {
return null;
}
return CallHandler.getCallForRoom(this.state.room.roomId);
},
// this has to be a proper method rather than an unnamed function, // this has to be a proper method rather than an unnamed function,
// otherwise react calls it with null on each update. // otherwise react calls it with null on each update.
_gatherTimelinePanelRef: function(r) { _gatherTimelinePanelRef: function(r) {
@ -1164,7 +1193,6 @@ module.exports = React.createClass({
var TimelinePanel = sdk.getComponent("structures.TimelinePanel"); var TimelinePanel = sdk.getComponent("structures.TimelinePanel");
if (!this.state.room) { if (!this.state.room) {
if (this.props.roomId) {
if (this.state.roomLoading) { if (this.state.roomLoading) {
return ( return (
<div className="mx_RoomView"> <div className="mx_RoomView">
@ -1201,12 +1229,6 @@ module.exports = React.createClass({
</div> </div>
); );
} }
}
else {
return (
<div />
);
}
} }
var myUserId = MatrixClientPeg.get().credentials.userId; var myUserId = MatrixClientPeg.get().credentials.userId;
@ -1248,7 +1270,7 @@ module.exports = React.createClass({
// We have successfully loaded this room, and are not previewing. // We have successfully loaded this room, and are not previewing.
// Display the "normal" room view. // Display the "normal" room view.
var call = CallHandler.getCallForRoom(this.props.roomId); var call = this._getCallForRoom();
var inCall = false; var inCall = false;
if (call && (this.state.callState !== 'ended' && this.state.callState !== 'ringing')) { if (call && (this.state.callState !== 'ended' && this.state.callState !== 'ringing')) {
inCall = true; inCall = true;