Implement adding rooms to the group summary

This includes making UserPickerDialog more generic such that it can also pick rooms from groups.
This commit is contained in:
Luke Barnard 2017-09-21 16:53:10 +01:00
parent 5940b11fd2
commit 5f840a3031
5 changed files with 131 additions and 31 deletions

View file

@ -20,8 +20,8 @@ import MultiInviter from './utils/MultiInviter';
import { _t } from './languageHandler'; import { _t } from './languageHandler';
export function showGroupInviteDialog(groupId) { export function showGroupInviteDialog(groupId) {
const UserPickerDialog = sdk.getComponent("dialogs.UserPickerDialog"); const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
Modal.createTrackedDialog('Group Invite', '', UserPickerDialog, { Modal.createTrackedDialog('Group Invite', '', AddressPickerDialog, {
title: _t('Invite new group members'), title: _t('Invite new group members'),
description: _t("Who would you like to add to this group?"), description: _t("Who would you like to add to this group?"),
placeholder: _t("Name or matrix ID"), placeholder: _t("Name or matrix ID"),

View file

@ -50,8 +50,8 @@ export function inviteMultipleToRoom(roomId, addrs) {
} }
export function showStartChatInviteDialog() { export function showStartChatInviteDialog() {
const UserPickerDialog = sdk.getComponent("dialogs.UserPickerDialog"); const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
Modal.createTrackedDialog('Start a chat', '', UserPickerDialog, { Modal.createTrackedDialog('Start a chat', '', AddressPickerDialog, {
title: _t('Start a chat'), title: _t('Start a chat'),
description: _t("Who would you like to communicate with?"), description: _t("Who would you like to communicate with?"),
placeholder: _t("Email, name or matrix ID"), placeholder: _t("Email, name or matrix ID"),
@ -61,8 +61,8 @@ export function showStartChatInviteDialog() {
} }
export function showRoomInviteDialog(roomId) { export function showRoomInviteDialog(roomId) {
const UserPickerDialog = sdk.getComponent("dialogs.UserPickerDialog"); const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
Modal.createTrackedDialog('Chat Invite', '', UserPickerDialog, { Modal.createTrackedDialog('Chat Invite', '', AddressPickerDialog, {
title: _t('Invite new room members'), title: _t('Invite new room members'),
description: _t('Who would you like to add to this room?'), description: _t('Who would you like to add to this room?'),
button: _t('Send Invites'), button: _t('Send Invites'),

View file

@ -55,32 +55,74 @@ const CategoryRoomList = React.createClass({
name: PropTypes.string, name: PropTypes.string,
}).isRequired, }).isRequired,
}), }),
groupId: PropTypes.string.isRequired,
// Whether the list should be editable // Whether the list should be editable
editing: PropTypes.bool.isRequired, editing: PropTypes.bool.isRequired,
}, },
onAddRoomsClicked: function(ev) {
ev.preventDefault();
const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
Modal.createTrackedDialog('Add Rooms to Group Summary', '', AddressPickerDialog, {
title: _t('Add rooms to the group summary'),
description: _t("Which rooms would you like to add to this summary?"),
placeholder: _t("Room name or alias"),
button: _t("Add to summary"),
pickerType: 'room',
validAddressTypes: ['mx'],
groupId: this.props.groupId,
onFinished: (success, addrs) => {
if (!success) return;
const errorList = [];
Promise.all(addrs.map((addr) => {
return MatrixClientPeg.get()
.addRoomToGroupSummary(this.props.groupId, addr.address)
.catch(() => { errorList.push(addr.address); })
.reflect();
})).then(() => {
if (errorList.length === 0) {
return;
}
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog(
'Failed to add the following room to the group summary',
'', ErrorDialog,
{
title: _t(
"Failed to add the following rooms to the summary of %(groupId)s:",
{groupId: this.props.groupId},
),
description: errorList.join(", "),
});
});
},
});
},
render: function() { render: function() {
const TintableSvg = sdk.getComponent("elements.TintableSvg");
const addButton = this.props.editing ?
(<AccessibleButton className="mx_GroupView_featuredThings_addButton" onClick={this.onAddRoomsClicked}>
<TintableSvg src="img/icons-create-room.svg" width="64" height="64"/>
<div className="mx_GroupView_featuredThings_addButton_label">
{_t('Add a Room')}
</div>
</AccessibleButton>) : <div />;
const roomNodes = this.props.rooms.map((r) => { const roomNodes = this.props.rooms.map((r) => {
return <FeaturedRoom key={r.room_id} summaryInfo={r} />; return <FeaturedRoom key={r.room_id} summaryInfo={r} />;
}); });
let catHeader = null; let catHeader = <div />;
if (this.props.category && this.props.category.profile) { if (this.props.category && this.props.category.profile) {
catHeader = <div className="mx_GroupView_featuredThings_category">{this.props.category.profile.name}</div>; catHeader = <div className="mx_GroupView_featuredThings_category">{this.props.category.profile.name}</div>;
} }
return <div className="mx_GroupView_featuredThings_container"> return <div className="mx_GroupView_featuredThings_container">
{catHeader} {catHeader}
{roomNodes} {roomNodes}
{addButton}
</div>; </div>;
// TODO: Modify UserPickerDialog to allow picking of rooms, and then use it here
// const TintableSvg = sdk.getComponent("elements.TintableSvg");
// <AccessibleButton className="mx_GroupView_featuredThings_addButton">
// <TintableSvg src="img/icons-create-room.svg" width="64" height="64"/>
// <div className="mx_GroupView_featuredThings_addButton_label">
// {_t('Add a Room')}
// </div>
// </AccessibleButton>
}, },
}); });
@ -146,8 +188,8 @@ const RoleUserList = React.createClass({
onAddUsersClicked: function(ev) { onAddUsersClicked: function(ev) {
ev.preventDefault(); ev.preventDefault();
const UserPickerDialog = sdk.getComponent("dialogs.UserPickerDialog"); const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
Modal.createTrackedDialog('Add Users to Group Summary', '', UserPickerDialog, { Modal.createTrackedDialog('Add Users to Group Summary', '', AddressPickerDialog, {
title: _t('Add users to the group summary'), title: _t('Add users to the group summary'),
description: _t("Who would you like to add to this summary?"), description: _t("Who would you like to add to this summary?"),
placeholder: _t("Name or matrix ID"), placeholder: _t("Name or matrix ID"),
@ -190,11 +232,11 @@ const RoleUserList = React.createClass({
<div className="mx_GroupView_featuredThings_addButton_label"> <div className="mx_GroupView_featuredThings_addButton_label">
{_t('Add a User')} {_t('Add a User')}
</div> </div>
</AccessibleButton>) : null; </AccessibleButton>) : <div />;
const userNodes = this.props.users.map((u) => { const userNodes = this.props.users.map((u) => {
return <FeaturedUser key={u.user_id} summaryInfo={u} />; return <FeaturedUser key={u.user_id} summaryInfo={u} />;
}); });
let roleHeader = null; let roleHeader = <div />;
if (this.props.role && this.props.role.profile) { if (this.props.role && this.props.role.profile) {
roleHeader = <div className="mx_GroupView_featuredThings_category">{this.props.role.profile.name}</div>; roleHeader = <div className="mx_GroupView_featuredThings_category">{this.props.role.profile.name}</div>;
} }
@ -456,6 +498,7 @@ export default React.createClass({
const defaultCategoryNode = <CategoryRoomList const defaultCategoryNode = <CategoryRoomList
rooms={defaultCategoryRooms} rooms={defaultCategoryRooms}
groupId={this.props.groupId}
editing={this.state.editing}/>; editing={this.state.editing}/>;
const categoryRoomNodes = Object.keys(categoryRooms).map((catId) => { const categoryRoomNodes = Object.keys(categoryRooms).map((catId) => {
const cat = summary.rooms_section.categories[catId]; const cat = summary.rooms_section.categories[catId];
@ -463,6 +506,7 @@ export default React.createClass({
key={catId} key={catId}
rooms={categoryRooms[catId]} rooms={categoryRooms[catId]}
category={cat} category={cat}
groupId={this.props.groupId}
editing={this.state.editing}/>; editing={this.state.editing}/>;
}); });

View file

@ -28,7 +28,7 @@ const TRUNCATE_QUERY_LIST = 40;
const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200; const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200;
module.exports = React.createClass({ module.exports = React.createClass({
displayName: "UserPickerDialog", displayName: "AddressPickerDialog",
propTypes: { propTypes: {
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
@ -41,6 +41,7 @@ module.exports = React.createClass({
validAddressTypes: PropTypes.arrayOf(PropTypes.oneOf(addressTypes)), validAddressTypes: PropTypes.arrayOf(PropTypes.oneOf(addressTypes)),
onFinished: PropTypes.func.isRequired, onFinished: PropTypes.func.isRequired,
groupId: PropTypes.string, groupId: PropTypes.string,
pickerType: PropTypes.oneOf(['user', 'room']),
}, },
getDefaultProps: function() { getDefaultProps: function() {
@ -48,6 +49,7 @@ module.exports = React.createClass({
value: "", value: "",
focus: true, focus: true,
validAddressTypes: addressTypes, validAddressTypes: addressTypes,
pickerType: 'user',
}; };
}, },
@ -141,12 +143,22 @@ module.exports = React.createClass({
// Only do search if there is something to search // Only do search if there is something to search
if (query.length > 0 && query != '@' && query.length >= 2) { if (query.length > 0 && query != '@' && query.length >= 2) {
this.queryChangedDebouncer = setTimeout(() => { this.queryChangedDebouncer = setTimeout(() => {
if (this.props.groupId) { if (this.props.pickerType === 'user') {
this._doNaiveGroupSearch(query); if (this.props.groupId) {
} else if (this.state.serverSupportsUserDirectory) { this._doNaiveGroupSearch(query);
this._doUserDirectorySearch(query); } else if (this.state.serverSupportsUserDirectory) {
this._doUserDirectorySearch(query);
} else {
this._doLocalSearch(query);
}
} else if (this.props.pickerType === 'room') {
if (this.props.groupId) {
this._doNaiveGroupRoomSearch(query);
} else {
console.error('Room searching only implemented for groups');
}
} else { } else {
this._doLocalSearch(query); console.error('Unknown pickerType', this.props.pickerType);
} }
}, QUERY_USER_DIRECTORY_DEBOUNCE_MS); }, QUERY_USER_DIRECTORY_DEBOUNCE_MS);
} else { } else {
@ -210,6 +222,36 @@ module.exports = React.createClass({
}); });
}); });
this._processResults(results, query); this._processResults(results, query);
}).catch((err) => {
console.error('Error whilst searching group rooms: ', err);
this.setState({
searchError: err.errcode ? err.message : _t('Something went wrong!'),
});
}).done(() => {
this.setState({
busy: false,
});
});
},
_doNaiveGroupRoomSearch: function(query) {
const lowerCaseQuery = query.toLowerCase();
MatrixClientPeg.get().getGroupRooms(this.props.groupId).then((resp) => {
const results = [];
resp.chunk.forEach((r) => {
const nameMatch = (r.name || '').toLowerCase().includes(lowerCaseQuery);
const topicMatch = (r.topic || '').toLowerCase().includes(lowerCaseQuery);
const aliasMatch = (r.canonical_alias || '').toLowerCase().includes(lowerCaseQuery);
if (!(nameMatch || topicMatch || aliasMatch)) {
return;
}
results.push({
room_id: r.room_id,
avatar_url: r.avatar_url,
name: r.name,
});
});
this._processResults(results, query);
}).catch((err) => { }).catch((err) => {
console.error('Error whilst searching group users: ', err); console.error('Error whilst searching group users: ', err);
this.setState({ this.setState({
@ -282,17 +324,28 @@ module.exports = React.createClass({
_processResults: function(results, query) { _processResults: function(results, query) {
const queryList = []; const queryList = [];
results.forEach((user) => { results.forEach((result) => {
if (user.user_id === MatrixClientPeg.get().credentials.userId) { if (result.room_id) {
queryList.push({
addressType: 'mx',
address: result.room_id,
displayName: result.name,
avatarMxc: result.avatar_url,
isKnown: true,
});
return; return;
} }
if (result.user_id === MatrixClientPeg.get().credentials.userId) {
return;
}
// Return objects, structure of which is defined // Return objects, structure of which is defined
// by UserAddressType // by UserAddressType
queryList.push({ queryList.push({
addressType: 'mx', addressType: 'mx',
address: user.user_id, address: result.user_id,
displayName: user.display_name, displayName: result.display_name,
avatarMxc: user.avatar_url, avatarMxc: result.avatar_url,
isKnown: true, isKnown: true,
}); });
}); });

View file

@ -892,5 +892,8 @@
"Add users to the group summary": "Add users to the group summary", "Add users to the group summary": "Add users to the group summary",
"Who would you like to add to this summary?": "Who would you like to add to this summary?", "Who would you like to add to this summary?": "Who would you like to add to this summary?",
"Add to summary": "Add to summary", "Add to summary": "Add to summary",
"Failed to add the following users to the summary of %(groupId)s:": "Failed to add the following users to the summary of %(groupId)s:" "Failed to add the following users to the summary of %(groupId)s:": "Failed to add the following users to the summary of %(groupId)s:",
"Add rooms to the group summary": "Add rooms to the group summary",
"Which rooms would you like to add to this summary?": "Which rooms would you like to add to this summary?",
"Room name or alias": "Room name or alias"
} }