Merge pull request #1006 from matrix-org/luke/new-guest-access-user-action-chat

Implement /user/@userid:domain?action=chat
pull/21833/head
Luke Barnard 2017-06-02 12:03:11 +01:00 committed by GitHub
commit 953a573f81
6 changed files with 206 additions and 46 deletions

View File

@ -378,6 +378,11 @@ module.exports = React.createClass({
}); });
this.notifyNewScreen('forgot_password'); this.notifyNewScreen('forgot_password');
break; break;
case 'start_chat':
createRoom({
dmUserId: payload.user_id,
});
break;
case 'leave_room': case 'leave_room':
this._leaveRoom(payload.room_id); this._leaveRoom(payload.room_id);
break; break;
@ -474,6 +479,9 @@ module.exports = React.createClass({
case 'view_set_mxid': case 'view_set_mxid':
this._setMxId(); this._setMxId();
break; break;
case 'view_start_chat_or_reuse':
this._chatCreateOrReuse(payload.user_id);
break;
case 'view_create_chat': case 'view_create_chat':
this._createChat(); this._createChat();
break; break;
@ -707,6 +715,51 @@ module.exports = React.createClass({
}); });
}, },
_chatCreateOrReuse: function(userId) {
const ChatCreateOrReuseDialog = sdk.getComponent(
'views.dialogs.ChatCreateOrReuseDialog',
);
// Use a deferred action to reshow the dialog once the user has registered
if (MatrixClientPeg.get().isGuest()) {
dis.dispatch({
action: 'do_after_sync_prepared',
deferred_action: {
action: 'view_start_chat_or_reuse',
user_id: userId,
},
});
dis.dispatch({
action: 'view_set_mxid',
});
return;
}
const close = Modal.createDialog(ChatCreateOrReuseDialog, {
userId: userId,
onFinished: (success) => {
if (!success) {
// Dialog cancelled, default to home
dis.dispatch({ action: 'view_home_page' });
}
},
onNewDMClick: () => {
dis.dispatch({
action: 'start_chat',
user_id: userId,
});
// Close the dialog, indicate success (calls onFinished(true))
close(true);
},
onExistingRoomSelected: (roomId) => {
dis.dispatch({
action: 'view_room',
room_id: roomId,
});
close(true);
},
}).close;
},
_invite: function(roomId) { _invite: function(roomId) {
const ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog"); const ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog");
Modal.createDialog(ChatInviteDialog, { Modal.createDialog(ChatInviteDialog, {
@ -1043,6 +1096,12 @@ module.exports = React.createClass({
} }
} else if (screen.indexOf('user/') == 0) { } else if (screen.indexOf('user/') == 0) {
const userId = screen.substring(5); const userId = screen.substring(5);
if (params.action === 'chat') {
this._chatCreateOrReuse(userId);
return;
}
this.setState({ viewUserId: userId }); this.setState({ viewUserId: userId });
this._setPage(PageTypes.UserView); this._setPage(PageTypes.UserView);
this.notifyNewScreen('user/' + userId); this.notifyNewScreen('user/' + userId);

View File

@ -32,6 +32,7 @@ module.exports = React.createClass({
urls: React.PropTypes.array, // [highest_priority, ... , lowest_priority] urls: React.PropTypes.array, // [highest_priority, ... , lowest_priority]
width: React.PropTypes.number, width: React.PropTypes.number,
height: React.PropTypes.number, height: React.PropTypes.number,
// XXX resizeMethod not actually used.
resizeMethod: React.PropTypes.string, resizeMethod: React.PropTypes.string,
defaultToInitialLetter: React.PropTypes.bool // true to add default url defaultToInitialLetter: React.PropTypes.bool // true to add default url
}, },

View File

@ -18,34 +18,31 @@ import React from 'react';
import sdk from '../../../index'; import sdk from '../../../index';
import dis from '../../../dispatcher'; import dis from '../../../dispatcher';
import MatrixClientPeg from '../../../MatrixClientPeg'; import MatrixClientPeg from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler';
import DMRoomMap from '../../../utils/DMRoomMap'; import DMRoomMap from '../../../utils/DMRoomMap';
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';
import Unread from '../../../Unread'; import Unread from '../../../Unread';
import classNames from 'classnames'; import classNames from 'classnames';
import createRoom from '../../../createRoom'; import createRoom from '../../../createRoom';
import { RoomMember } from "matrix-js-sdk";
export default class ChatCreateOrReuseDialog extends React.Component { export default class ChatCreateOrReuseDialog extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.onNewDMClick = this.onNewDMClick.bind(this);
this.onRoomTileClick = this.onRoomTileClick.bind(this); this.onRoomTileClick = this.onRoomTileClick.bind(this);
this.state = {
tiles: [],
profile: {
displayName: null,
avatarUrl: null,
},
profileError: null,
};
} }
onNewDMClick() { componentWillMount() {
createRoom({dmUserId: this.props.userId});
this.props.onFinished(true);
}
onRoomTileClick(roomId) {
dis.dispatch({
action: 'view_room',
room_id: roomId,
});
this.props.onFinished(true);
}
render() {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const dmRoomMap = new DMRoomMap(client); const dmRoomMap = new DMRoomMap(client);
@ -70,40 +67,123 @@ export default class ChatCreateOrReuseDialog extends React.Component {
highlight={highlight} highlight={highlight}
isInvite={me.membership == "invite"} isInvite={me.membership == "invite"}
onClick={this.onRoomTileClick} onClick={this.onRoomTileClick}
/> />,
); );
} }
} }
const labelClasses = classNames({ this.setState({
mx_MemberInfo_createRoom_label: true, tiles: tiles,
mx_RoomTile_name: true,
}); });
const startNewChat = <AccessibleButton
className="mx_MemberInfo_createRoom" if (tiles.length === 0) {
onClick={this.onNewDMClick} this.setState({
> busyProfile: true,
<div className="mx_RoomTile_avatar"> });
<img src="img/create-big.svg" width="26" height="26" /> MatrixClientPeg.get().getProfileInfo(this.props.userId).done((resp) => {
</div> const profile = {
<div className={labelClasses}><i>{_("Start new chat")}</i></div> displayName: resp.displayname,
</AccessibleButton>; avatarUrl: null,
};
if (resp.avatar_url) {
profile.avatarUrl = MatrixClientPeg.get().mxcUrlToHttp(
resp.avatar_url, 48, 48, "crop",
);
}
this.setState({
busyProfile: false,
profile: profile,
});
}, (err) => {
console.error(
'Unable to get profile for user ' + this.props.userId + ':',
err,
);
this.setState({
busyProfile: false,
profileError: err,
});
});
}
}
onRoomTileClick(roomId) {
this.props.onExistingRoomSelected(roomId);
}
render() {
let title = '';
let content = null;
if (this.state.tiles.length > 0) {
// Show the existing rooms with a "+" to add a new dm
title = _t('Create a new chat or reuse an existing one');
const labelClasses = classNames({
mx_MemberInfo_createRoom_label: true,
mx_RoomTile_name: true,
});
const startNewChat = <AccessibleButton
className="mx_MemberInfo_createRoom"
onClick={this.props.onNewDMClick}
>
<div className="mx_RoomTile_avatar">
<img src="img/create-big.svg" width="26" height="26" />
</div>
<div className={labelClasses}><i>{ _t("Start new chat") }</i></div>
</AccessibleButton>;
content = <div className="mx_Dialog_content">
{ _t('You already have existing direct chats with this user:') }
<div className="mx_ChatCreateOrReuseDialog_tiles">
{ this.state.tiles }
{ startNewChat }
</div>
</div>;
} else {
// Show the avatar, name and a button to confirm that a new chat is requested
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const Spinner = sdk.getComponent('elements.Spinner');
title = _t('Start chatting');
let profile = null;
if (this.state.busyProfile) {
profile = <Spinner />;
} else if (this.state.profileError) {
profile = <div className="error">
Unable to load profile information for { this.props.userId }
</div>;
} else {
profile = <div className="mx_ChatCreateOrReuseDialog_profile">
<BaseAvatar
name={this.state.profile.displayName || this.props.userId}
url={this.state.profile.avatarUrl}
width={48} height={48}
/>
<div className="mx_ChatCreateOrReuseDialog_profile_name">
{this.state.profile.displayName || this.props.userId}
</div>
</div>;
}
content = <div>
<div className="mx_Dialog_content">
<p>
{ _t('Click on the button below to start chatting!') }
</p>
{ profile }
</div>
<div className="mx_Dialog_buttons">
<button className="mx_Dialog_primary" onClick={this.props.onNewDMClick}>
{ _t('Start Chatting') }
</button>
</div>
</div>;
}
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return ( return (
<BaseDialog className='mx_ChatCreateOrReuseDialog' <BaseDialog className='mx_ChatCreateOrReuseDialog'
onFinished={() => { onFinished={ this.props.onFinished.bind(false) }
this.props.onFinished(false) title={title}
}}
title='Create a new chat or reuse an existing one'
> >
<div className="mx_Dialog_content"> { content }
You already have existing direct chats with this user:
<div className="mx_ChatCreateOrReuseDialog_tiles">
{tiles}
{startNewChat}
</div>
</div>
</BaseDialog> </BaseDialog>
); );
} }
@ -111,5 +191,8 @@ export default class ChatCreateOrReuseDialog extends React.Component {
ChatCreateOrReuseDialog.propTyps = { ChatCreateOrReuseDialog.propTyps = {
userId: React.PropTypes.string.isRequired, userId: React.PropTypes.string.isRequired,
// Called when clicking outside of the dialog
onFinished: React.PropTypes.func.isRequired, onFinished: React.PropTypes.func.isRequired,
onNewDMClick: React.PropTypes.func.isRequired,
onExistingRoomSelected: React.PropTypes.func.isRequired,
}; };

View File

@ -95,16 +95,25 @@ module.exports = React.createClass({
// A Direct Message room already exists for this user, so select a // A Direct Message room already exists for this user, so select a
// room from a list that is similar to the one in MemberInfo panel // room from a list that is similar to the one in MemberInfo panel
const ChatCreateOrReuseDialog = sdk.getComponent( const ChatCreateOrReuseDialog = sdk.getComponent(
"views.dialogs.ChatCreateOrReuseDialog" "views.dialogs.ChatCreateOrReuseDialog",
); );
Modal.createDialog(ChatCreateOrReuseDialog, { Modal.createDialog(ChatCreateOrReuseDialog, {
userId: userId, userId: userId,
onFinished: (success) => { onFinished: (success) => {
if (success) { this.props.onFinished(success);
this.props.onFinished(true, inviteList[0]); },
} onNewDMClick: () => {
// else show this ChatInviteDialog again dis.dispatch({
} action: 'start_chat',
user_id: userId,
});
},
onExistingRoomSelected: (roomId) => {
dis.dispatch({
action: 'view_room',
user_id: roomId,
});
},
}); });
} else { } else {
this._startChat(inviteList); this._startChat(inviteList);

View File

@ -771,5 +771,11 @@
"Idle": "Idle", "Idle": "Idle",
"Offline": "Offline", "Offline": "Offline",
"disabled": "disabled", "disabled": "disabled",
"enabled": "enabled" "enabled": "enabled",
"Start chatting": "Start chatting",
"Start Chatting": "Start Chatting",
"Click on the button below to start chatting!": "Click on the button below to start chatting!",
"Create a new chat or reuse an existing one": "Create a new chat or reuse an existing one",
"You already have existing direct chats with this user:": "You already have existing direct chats with this user:",
"Start new chat": "Start new chat"
} }

View File

@ -41,6 +41,7 @@ class LifecycleStore extends Store {
__onDispatch(payload) { __onDispatch(payload) {
switch (payload.action) { switch (payload.action) {
case 'do_after_sync_prepared': case 'do_after_sync_prepared':
console.info('Will do after sync', payload.deferred_action);
this._setState({ this._setState({
deferred_action: payload.deferred_action, deferred_action: payload.deferred_action,
}); });
@ -49,6 +50,7 @@ class LifecycleStore extends Store {
if (payload.state !== 'PREPARED') { if (payload.state !== 'PREPARED') {
break; break;
} }
console.info('Doing', payload.deferred_action);
if (!this._state.deferred_action) break; if (!this._state.deferred_action) break;
const deferredAction = Object.assign({}, this._state.deferred_action); const deferredAction = Object.assign({}, this._state.deferred_action);
this._setState({ this._setState({