mirror of
https://github.com/element-hq/element-web.git
synced 2024-12-01 10:27:06 +03:00
Merge pull request #921 from matrix-org/luke/new-guest-access-room-view-store
Implement a store for RoomView and join the intended room after set a mxid
This commit is contained in:
commit
454134661e
11 changed files with 403 additions and 241 deletions
|
@ -40,7 +40,6 @@ export default React.createClass({
|
|||
propTypes: {
|
||||
matrixClient: React.PropTypes.instanceOf(Matrix.MatrixClient).isRequired,
|
||||
page_type: React.PropTypes.string.isRequired,
|
||||
onRoomIdResolved: React.PropTypes.func,
|
||||
onRoomCreated: React.PropTypes.func,
|
||||
onUserSettingsClose: React.PropTypes.func,
|
||||
|
||||
|
@ -190,16 +189,14 @@ export default React.createClass({
|
|||
case PageTypes.RoomView:
|
||||
page_element = <RoomView
|
||||
ref='roomView'
|
||||
roomAddress={this.props.currentRoomAlias || this.props.currentRoomId}
|
||||
autoJoin={this.props.autoJoin}
|
||||
onRoomIdResolved={this.props.onRoomIdResolved}
|
||||
onRegistered={this.props.onRegistered}
|
||||
eventId={this.props.initialEventId}
|
||||
thirdPartyInvite={this.props.thirdPartyInvite}
|
||||
oobData={this.props.roomOobData}
|
||||
highlightedEventId={this.props.highlightedEventId}
|
||||
eventPixelOffset={this.props.initialEventPixelOffset}
|
||||
key={this.props.currentRoomAlias || this.props.currentRoomId}
|
||||
key={this.props.currentRoomId || 'roomview'}
|
||||
opacity={this.props.middleOpacity}
|
||||
collapsedRhs={this.props.collapse_rhs}
|
||||
ConferenceHandler={this.props.ConferenceHandler}
|
||||
|
|
|
@ -32,6 +32,8 @@ import sdk from '../../index';
|
|||
import * as Rooms from '../../Rooms';
|
||||
import linkifyMatrix from "../../linkify-matrix";
|
||||
import * as Lifecycle from '../../Lifecycle';
|
||||
import LifecycleStore from '../../stores/LifecycleStore';
|
||||
import RoomViewStore from '../../stores/RoomViewStore';
|
||||
import PageTypes from '../../PageTypes';
|
||||
|
||||
import createRoom from "../../createRoom";
|
||||
|
@ -99,9 +101,6 @@ module.exports = React.createClass({
|
|||
// What the LoggedInView would be showing if visible
|
||||
page_type: null,
|
||||
|
||||
// If we are viewing a room by alias, this contains the alias
|
||||
currentRoomAlias: null,
|
||||
|
||||
// The ID of the room we're viewing. This is either populated directly
|
||||
// in the case where we view a room by ID or by RoomView when it resolves
|
||||
// what ID an alias points at.
|
||||
|
@ -187,6 +186,9 @@ module.exports = React.createClass({
|
|||
componentWillMount: function() {
|
||||
SdkConfig.put(this.props.config);
|
||||
|
||||
RoomViewStore.addListener(this._onRoomViewStoreUpdated);
|
||||
this._onRoomViewStoreUpdated();
|
||||
|
||||
// Used by _viewRoom before getting state from sync
|
||||
this.firstSyncComplete = false;
|
||||
this.firstSyncPromise = q.defer();
|
||||
|
@ -529,6 +531,10 @@ module.exports = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
_onRoomViewStoreUpdated: function() {
|
||||
this.setState({ currentRoomId: RoomViewStore.getRoomId() });
|
||||
},
|
||||
|
||||
_setPage: function(pageType) {
|
||||
this.setState({
|
||||
page_type: pageType,
|
||||
|
@ -602,14 +608,9 @@ module.exports = React.createClass({
|
|||
page_type: PageTypes.RoomView,
|
||||
thirdPartyInvite: room_info.third_party_invite,
|
||||
roomOobData: room_info.oob_data,
|
||||
currentRoomAlias: room_info.room_alias,
|
||||
autoJoin: room_info.auto_join,
|
||||
};
|
||||
|
||||
if (!room_info.room_alias) {
|
||||
newState.currentRoomId = room_info.room_id;
|
||||
}
|
||||
|
||||
// if we aren't given an explicit event id, look for one in the
|
||||
// scrollStateMap.
|
||||
//
|
||||
|
@ -712,7 +713,7 @@ module.exports = React.createClass({
|
|||
|
||||
d.then(() => {
|
||||
modal.close();
|
||||
if (this.currentRoomId === roomId) {
|
||||
if (this.state.currentRoomId === roomId) {
|
||||
dis.dispatch({action: 'view_next_room'});
|
||||
}
|
||||
}, (err) => {
|
||||
|
@ -807,8 +808,12 @@ module.exports = React.createClass({
|
|||
this._teamToken = teamToken;
|
||||
dis.dispatch({action: 'view_home_page'});
|
||||
} else if (this._is_registered) {
|
||||
this._is_registered = false;
|
||||
if (this.props.config.welcomeUserId) {
|
||||
createRoom({dmUserId: this.props.config.welcomeUserId});
|
||||
createRoom({
|
||||
dmUserId: this.props.config.welcomeUserId,
|
||||
andView: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// The user has just logged in after registering
|
||||
|
@ -853,7 +858,6 @@ module.exports = React.createClass({
|
|||
ready: false,
|
||||
collapse_lhs: false,
|
||||
collapse_rhs: false,
|
||||
currentRoomAlias: null,
|
||||
currentRoomId: null,
|
||||
page_type: PageTypes.RoomDirectory,
|
||||
});
|
||||
|
@ -891,6 +895,12 @@ module.exports = React.createClass({
|
|||
});
|
||||
|
||||
cli.on('sync', function(state, prevState) {
|
||||
// LifecycleStore and others cannot directly subscribe to matrix client for
|
||||
// events because flux only allows store state changes during flux dispatches.
|
||||
// So dispatch directly from here. Ideally we'd use a SyncStateStore that
|
||||
// would do this dispatch and expose the sync state itself (by listening to
|
||||
// its own dispatch).
|
||||
dis.dispatch({action: 'sync_state', prevState, state});
|
||||
self.updateStatusIndicator(state, prevState);
|
||||
if (state === "SYNCING" && prevState === "SYNCING") {
|
||||
return;
|
||||
|
@ -1102,6 +1112,8 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
onRegistered: function(credentials, teamToken) {
|
||||
// XXX: These both should be in state or ideally store(s) because we risk not
|
||||
// rendering the most up-to-date view of state otherwise.
|
||||
// teamToken may not be truthy
|
||||
this._teamToken = teamToken;
|
||||
this._is_registered = true;
|
||||
|
@ -1163,13 +1175,6 @@ module.exports = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
onRoomIdResolved: function(roomId) {
|
||||
// It's the RoomView's resposibility to look up room aliases, but we need the
|
||||
// ID to pass into things like the Member List, so the Room View tells us when
|
||||
// its done that resolution so we can display things that take a room ID.
|
||||
this.setState({currentRoomId: roomId});
|
||||
},
|
||||
|
||||
_makeRegistrationUrl: function(params) {
|
||||
if (this.props.startingFragmentQueryParams.referrer) {
|
||||
params.referrer = this.props.startingFragmentQueryParams.referrer;
|
||||
|
@ -1211,10 +1216,10 @@ module.exports = React.createClass({
|
|||
const LoggedInView = sdk.getComponent('structures.LoggedInView');
|
||||
return (
|
||||
<LoggedInView ref="loggedInView" matrixClient={MatrixClientPeg.get()}
|
||||
onRoomIdResolved={this.onRoomIdResolved}
|
||||
onRoomCreated={this.onRoomCreated}
|
||||
onUserSettingsClose={this.onUserSettingsClose}
|
||||
onRegistered={this.onRegistered}
|
||||
currentRoomId={this.state.currentRoomId}
|
||||
teamToken={this._teamToken}
|
||||
{...this.props}
|
||||
{...this.state}
|
||||
|
|
|
@ -44,6 +44,8 @@ import KeyCode from '../../KeyCode';
|
|||
|
||||
import UserProvider from '../../autocomplete/UserProvider';
|
||||
|
||||
import RoomViewStore from '../../stores/RoomViewStore';
|
||||
|
||||
var DEBUG = false;
|
||||
|
||||
if (DEBUG) {
|
||||
|
@ -58,17 +60,6 @@ module.exports = React.createClass({
|
|||
propTypes: {
|
||||
ConferenceHandler: React.PropTypes.any,
|
||||
|
||||
// Either a room ID or room alias for the room to display.
|
||||
// If the room is being displayed as a result of the user clicking
|
||||
// on a room alias, the alias should be supplied. Otherwise, a room
|
||||
// ID should be supplied.
|
||||
roomAddress: React.PropTypes.string.isRequired,
|
||||
|
||||
// If a room alias is passed to roomAddress, a function can be
|
||||
// provided here that will be called with the ID of the room
|
||||
// once it has been resolved.
|
||||
onRoomIdResolved: React.PropTypes.func,
|
||||
|
||||
// Called with the credentials of a registered user (if they were a ROU that
|
||||
// transitioned to PWLU)
|
||||
onRegistered: React.PropTypes.func,
|
||||
|
@ -173,40 +164,27 @@ module.exports = React.createClass({
|
|||
onClickCompletes: true,
|
||||
onStateChange: (isCompleting) => {
|
||||
this.forceUpdate();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (this.props.roomAddress[0] == '#') {
|
||||
// we always look up the alias from the directory server:
|
||||
// we want the room that the given alias is pointing to
|
||||
// right now. We may have joined that alias before but there's
|
||||
// no guarantee the alias hasn't subsequently been remapped.
|
||||
MatrixClientPeg.get().getRoomIdForAlias(this.props.roomAddress).done((result) => {
|
||||
if (this.props.onRoomIdResolved) {
|
||||
this.props.onRoomIdResolved(result.room_id);
|
||||
}
|
||||
var room = MatrixClientPeg.get().getRoom(result.room_id);
|
||||
this.setState({
|
||||
room: room,
|
||||
roomId: result.room_id,
|
||||
roomLoading: !room,
|
||||
unsentMessageError: this._getUnsentMessageError(room),
|
||||
}, this._onHaveRoom);
|
||||
}, (err) => {
|
||||
this.setState({
|
||||
roomLoading: false,
|
||||
roomLoadError: err,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
var room = MatrixClientPeg.get().getRoom(this.props.roomAddress);
|
||||
this.setState({
|
||||
roomId: this.props.roomAddress,
|
||||
room: room,
|
||||
roomLoading: !room,
|
||||
unsentMessageError: this._getUnsentMessageError(room),
|
||||
}, this._onHaveRoom);
|
||||
// Start listening for RoomViewStore updates
|
||||
RoomViewStore.addListener(this._onRoomViewStoreUpdate);
|
||||
this._onRoomViewStoreUpdate(true);
|
||||
},
|
||||
|
||||
_onRoomViewStoreUpdate: function(initial) {
|
||||
if (this.unmounted) {
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
roomId: RoomViewStore.getRoomId(),
|
||||
roomAlias: RoomViewStore.getRoomAlias(),
|
||||
joining: RoomViewStore.isJoining(),
|
||||
joinError: RoomViewStore.getJoinError(),
|
||||
}, () => {
|
||||
this._onHaveRoom();
|
||||
this.onRoom(MatrixClientPeg.get().getRoom(this.state.roomId));
|
||||
});
|
||||
},
|
||||
|
||||
_onHaveRoom: function() {
|
||||
|
@ -224,17 +202,17 @@ module.exports = React.createClass({
|
|||
// NB. We peek if we are not in the room, although if we try to peek into
|
||||
// a room in which we have a member event (ie. we've left) synapse will just
|
||||
// send us the same data as we get in the sync (ie. the last events we saw).
|
||||
var user_is_in_room = null;
|
||||
if (this.state.room) {
|
||||
user_is_in_room = this.state.room.hasMembershipState(
|
||||
MatrixClientPeg.get().credentials.userId, 'join'
|
||||
const room = MatrixClientPeg.get().getRoom(this.state.roomId);
|
||||
let isUserJoined = null;
|
||||
if (room) {
|
||||
isUserJoined = room.hasMembershipState(
|
||||
MatrixClientPeg.get().credentials.userId, 'join',
|
||||
);
|
||||
|
||||
this._updateAutoComplete();
|
||||
this.tabComplete.loadEntries(this.state.room);
|
||||
this._updateAutoComplete(room);
|
||||
this.tabComplete.loadEntries(room);
|
||||
}
|
||||
|
||||
if (!user_is_in_room && this.state.roomId) {
|
||||
if (!isUserJoined && !this.state.joining && this.state.roomId) {
|
||||
if (this.props.autoJoin) {
|
||||
this.onJoinButtonClicked();
|
||||
} else if (this.state.roomId) {
|
||||
|
@ -260,9 +238,12 @@ module.exports = React.createClass({
|
|||
}
|
||||
}).done();
|
||||
}
|
||||
} else if (user_is_in_room) {
|
||||
} else if (isUserJoined) {
|
||||
MatrixClientPeg.get().stopPeeking();
|
||||
this._onRoomLoaded(this.state.room);
|
||||
this.setState({
|
||||
unsentMessageError: this._getUnsentMessageError(room),
|
||||
});
|
||||
this._onRoomLoaded(room);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -299,10 +280,6 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
componentWillReceiveProps: function(newProps) {
|
||||
if (newProps.roomAddress != this.props.roomAddress) {
|
||||
throw new Error("changing room on a RoomView is not supported");
|
||||
}
|
||||
|
||||
if (newProps.eventId != this.props.eventId) {
|
||||
// when we change focussed event id, hide the search results.
|
||||
this.setState({searchResults: null});
|
||||
|
@ -523,7 +500,7 @@ module.exports = React.createClass({
|
|||
this._updatePreviewUrlVisibility(room);
|
||||
},
|
||||
|
||||
_warnAboutEncryption: function (room) {
|
||||
_warnAboutEncryption: function(room) {
|
||||
if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) {
|
||||
return;
|
||||
}
|
||||
|
@ -604,20 +581,14 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
onRoom: function(room) {
|
||||
// This event is fired when the room is 'stored' by the JS SDK, which
|
||||
// means it's now a fully-fledged room object ready to be used, so
|
||||
// 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,
|
||||
// then join it whilst RoomView is looking at that room.
|
||||
if (!this.state.room && room.roomId == this._joiningRoomId) {
|
||||
this._joiningRoomId = undefined;
|
||||
this.setState({
|
||||
room: room,
|
||||
joining: false,
|
||||
});
|
||||
|
||||
this._onRoomLoaded(room);
|
||||
if (!room || room.roomId !== this.state.roomId) {
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
room: room,
|
||||
}, () => {
|
||||
this._onRoomLoaded(room);
|
||||
});
|
||||
},
|
||||
|
||||
updateTint: function() {
|
||||
|
@ -683,7 +654,7 @@ module.exports = React.createClass({
|
|||
|
||||
// refresh the tab complete list
|
||||
this.tabComplete.loadEntries(this.state.room);
|
||||
this._updateAutoComplete();
|
||||
this._updateAutoComplete(this.state.room);
|
||||
|
||||
// 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
|
||||
|
@ -778,37 +749,43 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
onJoinButtonClicked: function(ev) {
|
||||
var self = this;
|
||||
|
||||
var cli = MatrixClientPeg.get();
|
||||
var mxIdPromise = q();
|
||||
const cli = MatrixClientPeg.get();
|
||||
|
||||
// If the user is a ROU, allow them to transition to a PWLU
|
||||
if (cli && cli.isGuest()) {
|
||||
// Join this room once the user has registered and logged in
|
||||
dis.dispatch({
|
||||
action: 'do_after_sync_prepared',
|
||||
deferred_action: {
|
||||
action: 'join_room',
|
||||
room_id: this.state.roomId,
|
||||
},
|
||||
});
|
||||
|
||||
const SetMxIdDialog = sdk.getComponent('views.dialogs.SetMxIdDialog');
|
||||
const defered = q.defer();
|
||||
mxIdPromise = defered.promise;
|
||||
const close = Modal.createDialog(SetMxIdDialog, {
|
||||
homeserverUrl: cli.getHomeserverUrl(),
|
||||
onFinished: (submitted, credentials) => {
|
||||
if (!submitted) {
|
||||
defered.reject();
|
||||
return;
|
||||
if (submitted) {
|
||||
this.props.onRegistered(credentials);
|
||||
}
|
||||
this.props.onRegistered(credentials);
|
||||
defered.resolve();
|
||||
},
|
||||
onDifferentServerClicked: (ev) => {
|
||||
dis.dispatch({action: 'start_registration'});
|
||||
close();
|
||||
},
|
||||
}).close;
|
||||
return;
|
||||
}
|
||||
|
||||
mxIdPromise.then(() => {
|
||||
this.setState({
|
||||
joining: true
|
||||
q().then(() => {
|
||||
const signUrl = this.props.thirdPartyInvite ?
|
||||
this.props.thirdPartyInvite.inviteSignUrl : undefined;
|
||||
dis.dispatch({
|
||||
action: 'join_room',
|
||||
opts: { inviteSignUrl: signUrl },
|
||||
});
|
||||
|
||||
// if this is an invite and has the 'direct' hint set, mark it as a DM room now.
|
||||
if (this.state.room) {
|
||||
const me = this.state.room.getMember(MatrixClientPeg.get().credentials.userId);
|
||||
|
@ -820,65 +797,8 @@ module.exports = React.createClass({
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return q();
|
||||
}).then(() => {
|
||||
var sign_url = this.props.thirdPartyInvite ? this.props.thirdPartyInvite.inviteSignUrl : undefined;
|
||||
return MatrixClientPeg.get().joinRoom(this.props.roomAddress,
|
||||
{ inviteSignUrl: sign_url } );
|
||||
}).then(function(resp) {
|
||||
var roomId = resp.roomId;
|
||||
|
||||
// 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,
|
||||
// NOT when it comes down /sync. If there is no room, we'll keep the
|
||||
// joining flag set until we see it.
|
||||
|
||||
// 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,
|
||||
// once we have a useable room object.
|
||||
var room = MatrixClientPeg.get().getRoom(roomId);
|
||||
if (!room) {
|
||||
// wait for the room to turn up in onRoom.
|
||||
self._joiningRoomId = roomId;
|
||||
} else {
|
||||
// we've got a valid room, but that might also just mean that
|
||||
// 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({
|
||||
joining: false,
|
||||
joinError: error
|
||||
});
|
||||
|
||||
if (!error) return;
|
||||
|
||||
// https://matrix.org/jira/browse/SYN-659
|
||||
// Need specific error message if joining a room is refused because the user is a guest and guest access is not allowed
|
||||
if (
|
||||
error.errcode == 'M_GUEST_ACCESS_FORBIDDEN' ||
|
||||
(
|
||||
error.errcode == 'M_FORBIDDEN' &&
|
||||
MatrixClientPeg.get().isGuest()
|
||||
)
|
||||
) {
|
||||
dis.dispatch({action: 'view_set_mxid'});
|
||||
} else {
|
||||
var msg = error.message ? error.message : JSON.stringify(error);
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to join room",
|
||||
description: msg
|
||||
});
|
||||
}
|
||||
}).done();
|
||||
});
|
||||
},
|
||||
|
||||
onMessageListScroll: function(ev) {
|
||||
|
@ -1451,9 +1371,9 @@ module.exports = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
_updateAutoComplete: function() {
|
||||
_updateAutoComplete: function(room) {
|
||||
const myUserId = MatrixClientPeg.get().credentials.userId;
|
||||
const members = this.state.room.getJoinedMembers().filter(function(member) {
|
||||
const members = room.getJoinedMembers().filter(function(member) {
|
||||
if (member.userId !== myUserId) return true;
|
||||
});
|
||||
UserProvider.getInstance().setUserList(members);
|
||||
|
@ -1491,7 +1411,7 @@ module.exports = React.createClass({
|
|||
|
||||
// We have no room object for this room, only the ID.
|
||||
// We've got to this room by following a link, possibly a third party invite.
|
||||
var room_alias = this.props.roomAddress[0] == '#' ? this.props.roomAddress : null;
|
||||
var room_alias = this.state.room_alias;
|
||||
return (
|
||||
<div className="mx_RoomView">
|
||||
<RoomHeader ref="header"
|
||||
|
|
|
@ -232,6 +232,7 @@ export default React.createClass({
|
|||
!this.state.usernameBusy;
|
||||
|
||||
if (this.state.success) {
|
||||
// XXX BaseDialog needs an onFinished
|
||||
return (
|
||||
<BaseDialog className="mx_SetMxIdDialog"
|
||||
title="You have successfully picked a username!"
|
||||
|
|
|
@ -57,6 +57,11 @@ function createRoom(opts) {
|
|||
createOpts.is_direct = true;
|
||||
}
|
||||
|
||||
// By default, view the room after creating it
|
||||
if (opts.andView === undefined) {
|
||||
opts.andView = true;
|
||||
}
|
||||
|
||||
// Allow guests by default since the room is private and they'd
|
||||
// need an invite. This means clicking on a 3pid invite email can
|
||||
// actually drop you right in to a chat.
|
||||
|
@ -90,10 +95,12 @@ function createRoom(opts) {
|
|||
// room has been created, so we race here with the client knowing that
|
||||
// the room exists, causing things like
|
||||
// https://github.com/vector-im/vector-web/issues/1813
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: roomId
|
||||
});
|
||||
if (opts.andView) {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: roomId,
|
||||
});
|
||||
}
|
||||
return roomId;
|
||||
}, function(err) {
|
||||
console.error("Failed to create room " + roomId + " " + err);
|
||||
|
|
72
src/stores/LifecycleStore.js
Normal file
72
src/stores/LifecycleStore.js
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
import dis from '../dispatcher';
|
||||
import {Store} from 'flux/utils';
|
||||
|
||||
/**
|
||||
* A class for storing application state to do with login/registration. This is a simple
|
||||
* flux store that listens for actions and updates its state accordingly, informing any
|
||||
* listeners (views) of state changes.
|
||||
*
|
||||
* Usage:
|
||||
* ```
|
||||
* lifecycleStore.addListener(() => {
|
||||
* this.setState({ cachedPassword: lifecycleStore.getCachedPassword() })
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
class LifecycleStore extends Store {
|
||||
constructor() {
|
||||
super(dis);
|
||||
|
||||
// Initialise state
|
||||
this._state = {
|
||||
deferred_action: null,
|
||||
};
|
||||
}
|
||||
|
||||
_setState(newState) {
|
||||
this._state = Object.assign(this._state, newState);
|
||||
this.__emitChange();
|
||||
}
|
||||
|
||||
__onDispatch(payload) {
|
||||
switch (payload.action) {
|
||||
case 'do_after_sync_prepared':
|
||||
this._setState({
|
||||
deferred_action: payload.deferred_action,
|
||||
});
|
||||
break;
|
||||
case 'sync_state':
|
||||
if (payload.state !== 'PREPARED') {
|
||||
break;
|
||||
}
|
||||
if (!this._state.deferred_action) break;
|
||||
const deferredAction = Object.assign({}, this._state.deferred_action);
|
||||
this._setState({
|
||||
deferred_action: null,
|
||||
});
|
||||
dis.dispatch(deferredAction);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let singletonLifecycleStore = null;
|
||||
if (!singletonLifecycleStore) {
|
||||
singletonLifecycleStore = new LifecycleStore();
|
||||
}
|
||||
module.exports = singletonLifecycleStore;
|
145
src/stores/RoomViewStore.js
Normal file
145
src/stores/RoomViewStore.js
Normal file
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
import dis from '../dispatcher';
|
||||
import {Store} from 'flux/utils';
|
||||
import MatrixClientPeg from '../MatrixClientPeg';
|
||||
|
||||
const INITIAL_STATE = {
|
||||
// Whether we're joining the currently viewed room
|
||||
joining: false,
|
||||
// Any error occurred during joining
|
||||
joinError: null,
|
||||
// The room ID of the room
|
||||
roomId: null,
|
||||
// The room alias of the room (or null if not originally specified in view_room)
|
||||
roomAlias: null,
|
||||
// Whether the current room is loading
|
||||
roomLoading: false,
|
||||
// Any error that has occurred during loading
|
||||
roomLoadError: null,
|
||||
};
|
||||
|
||||
/**
|
||||
* A class for storing application state for RoomView. This is the RoomView's interface
|
||||
* with a subset of the js-sdk.
|
||||
* ```
|
||||
*/
|
||||
class RoomViewStore extends Store {
|
||||
constructor() {
|
||||
super(dis);
|
||||
|
||||
// Initialise state
|
||||
this._state = INITIAL_STATE;
|
||||
}
|
||||
|
||||
_setState(newState) {
|
||||
this._state = Object.assign(this._state, newState);
|
||||
this.__emitChange();
|
||||
}
|
||||
|
||||
__onDispatch(payload) {
|
||||
switch (payload.action) {
|
||||
// view_room:
|
||||
// - room_alias: '#somealias:matrix.org'
|
||||
// - room_id: '!roomid123:matrix.org'
|
||||
case 'view_room':
|
||||
this._viewRoom(payload);
|
||||
break;
|
||||
|
||||
// join_room:
|
||||
// - opts: options for joinRoom
|
||||
case 'join_room':
|
||||
this._joinRoom(payload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_viewRoom(payload) {
|
||||
const address = payload.room_alias || payload.room_id;
|
||||
if (address[0] == '#') {
|
||||
this._setState({
|
||||
roomLoading: true,
|
||||
});
|
||||
MatrixClientPeg.get().getRoomIdForAlias(address).then(
|
||||
(result) => {
|
||||
this._setState({
|
||||
roomId: result.room_id,
|
||||
roomAlias: address,
|
||||
roomLoading: false,
|
||||
roomLoadError: null,
|
||||
});
|
||||
}, (err) => {
|
||||
console.error(err);
|
||||
this._setState({
|
||||
roomLoading: false,
|
||||
roomLoadError: err,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this._setState({
|
||||
roomId: address,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_joinRoom(payload) {
|
||||
this._setState({
|
||||
joining: true,
|
||||
});
|
||||
MatrixClientPeg.get().joinRoom(this._state.roomId, payload.opts).then(
|
||||
() => {
|
||||
this._setState({
|
||||
joining: false,
|
||||
});
|
||||
}, (err) => {
|
||||
this._setState({
|
||||
joining: false,
|
||||
joinError: err,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
reset() {
|
||||
this._state = Object.assign({}, INITIAL_STATE);
|
||||
}
|
||||
|
||||
getRoomId() {
|
||||
return this._state.roomId;
|
||||
}
|
||||
|
||||
getRoomAlias() {
|
||||
return this._state.roomAlias;
|
||||
}
|
||||
|
||||
isRoomLoading() {
|
||||
return this._state.roomLoading;
|
||||
}
|
||||
|
||||
isJoining() {
|
||||
return this._state.joining;
|
||||
}
|
||||
|
||||
getJoinError() {
|
||||
return this._state.joinError;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let singletonRoomViewStore = null;
|
||||
if (!singletonRoomViewStore) {
|
||||
singletonRoomViewStore = new RoomViewStore();
|
||||
}
|
||||
module.exports = singletonRoomViewStore;
|
|
@ -1,3 +1,18 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
import dis from '../dispatcher';
|
||||
import {Store} from 'flux/utils';
|
||||
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
var React = require('react');
|
||||
var expect = require('expect');
|
||||
var sinon = require('sinon');
|
||||
var ReactDOM = require("react-dom");
|
||||
|
||||
var sdk = require('matrix-react-sdk');
|
||||
var RoomView = sdk.getComponent('structures.RoomView');
|
||||
var peg = require('../../../src/MatrixClientPeg');
|
||||
|
||||
var test_utils = require('../../test-utils');
|
||||
var q = require('q');
|
||||
|
||||
var Skinner = require("../../../src/Skinner");
|
||||
var stubComponent = require('../../components/stub-component.js');
|
||||
|
||||
describe('RoomView', function () {
|
||||
var sandbox;
|
||||
var parentDiv;
|
||||
|
||||
beforeEach(function() {
|
||||
test_utils.beforeEach(this);
|
||||
sandbox = test_utils.stubClient();
|
||||
parentDiv = document.createElement('div');
|
||||
|
||||
this.oldTimelinePanel = Skinner.getComponent('structures.TimelinePanel');
|
||||
this.oldRoomHeader = Skinner.getComponent('views.rooms.RoomHeader');
|
||||
Skinner.addComponent('structures.TimelinePanel', stubComponent());
|
||||
Skinner.addComponent('views.rooms.RoomHeader', stubComponent());
|
||||
|
||||
peg.get().credentials = { userId: "@test:example.com" };
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
sandbox.restore();
|
||||
|
||||
ReactDOM.unmountComponentAtNode(parentDiv);
|
||||
|
||||
Skinner.addComponent('structures.TimelinePanel', this.oldTimelinePanel);
|
||||
Skinner.addComponent('views.rooms.RoomHeader', this.oldRoomHeader);
|
||||
});
|
||||
|
||||
it('resolves a room alias to a room id', function (done) {
|
||||
peg.get().getRoomIdForAlias.returns(q({room_id: "!randomcharacters:aser.ver"}));
|
||||
|
||||
function onRoomIdResolved(room_id) {
|
||||
expect(room_id).toEqual("!randomcharacters:aser.ver");
|
||||
done();
|
||||
}
|
||||
|
||||
ReactDOM.render(<RoomView roomAddress="#alias:ser.ver" onRoomIdResolved={onRoomIdResolved} />, parentDiv);
|
||||
});
|
||||
|
||||
it('joins by alias if given an alias', function (done) {
|
||||
peg.get().getRoomIdForAlias.returns(q({room_id: "!randomcharacters:aser.ver"}));
|
||||
peg.get().getProfileInfo.returns(q({displayname: "foo"}));
|
||||
var roomView = ReactDOM.render(<RoomView roomAddress="#alias:ser.ver" />, parentDiv);
|
||||
|
||||
peg.get().joinRoom = function(x) {
|
||||
expect(x).toEqual('#alias:ser.ver');
|
||||
done();
|
||||
};
|
||||
|
||||
process.nextTick(function() {
|
||||
roomView.onJoinButtonClicked();
|
||||
});
|
||||
});
|
||||
});
|
56
test/stores/RoomViewStore-test.js
Normal file
56
test/stores/RoomViewStore-test.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
import expect from 'expect';
|
||||
|
||||
import dis from '../../src/dispatcher';
|
||||
import RoomViewStore from '../../src/stores/RoomViewStore';
|
||||
|
||||
|
||||
import peg from '../../src/MatrixClientPeg';
|
||||
|
||||
import * as testUtils from '../test-utils';
|
||||
import q from 'q';
|
||||
|
||||
const dispatch = testUtils.getDispatchForStore(RoomViewStore);
|
||||
|
||||
describe('RoomViewStore', function() {
|
||||
let sandbox;
|
||||
|
||||
beforeEach(function() {
|
||||
testUtils.beforeEach(this);
|
||||
sandbox = testUtils.stubClient();
|
||||
peg.get().credentials = { userId: "@test:example.com" };
|
||||
|
||||
// Reset the state of the store
|
||||
RoomViewStore.reset();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('can be used to view a room by ID and join', function(done) {
|
||||
peg.get().joinRoom = (roomId) => {
|
||||
expect(roomId).toBe("!randomcharacters:aser.ver");
|
||||
done();
|
||||
};
|
||||
|
||||
dispatch({ action: 'view_room', room_id: '!randomcharacters:aser.ver' });
|
||||
dispatch({ action: 'join_room' });
|
||||
expect(RoomViewStore.isJoining()).toBe(true);
|
||||
});
|
||||
|
||||
it('can be used to view a room by alias and join', function(done) {
|
||||
peg.get().getRoomIdForAlias.returns(q({room_id: "!randomcharacters:aser.ver"}));
|
||||
peg.get().joinRoom = (roomId) => {
|
||||
expect(roomId).toBe("!randomcharacters:aser.ver");
|
||||
done();
|
||||
};
|
||||
|
||||
dispatch({ action: 'view_room', room_alias: '#somealias2:aser.ver' });
|
||||
|
||||
// Wait for the next event loop to allow for room alias resolution
|
||||
setTimeout(() => {
|
||||
dispatch({ action: 'join_room' });
|
||||
expect(RoomViewStore.isJoining()).toBe(true);
|
||||
}, 0);
|
||||
});
|
||||
});
|
|
@ -4,7 +4,8 @@ import sinon from 'sinon';
|
|||
import q from 'q';
|
||||
import ReactTestUtils from 'react-addons-test-utils';
|
||||
|
||||
import peg from '../src/MatrixClientPeg.js';
|
||||
import peg from '../src/MatrixClientPeg';
|
||||
import dis from '../src/dispatcher';
|
||||
import jssdk from 'matrix-js-sdk';
|
||||
const MatrixEvent = jssdk.MatrixEvent;
|
||||
|
||||
|
@ -290,3 +291,13 @@ export function mkStubRoom(roomId = null) {
|
|||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function getDispatchForStore(store) {
|
||||
// Mock the dispatcher by gut-wrenching. Stores can only __emitChange whilst a
|
||||
// dispatcher `_isDispatching` is true.
|
||||
return (payload) => {
|
||||
dis._isDispatching = true;
|
||||
dis._callbacks[store._dispatchToken](payload);
|
||||
dis._isDispatching = false;
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue