mirror of
https://github.com/element-hq/element-web.git
synced 2024-12-03 20:36:57 +03:00
Merge pull request #1196 from matrix-org/dbkr/groups_better_groupview
Add more features to Group View
This commit is contained in:
commit
af3b6484cd
4 changed files with 259 additions and 9 deletions
|
@ -18,9 +18,154 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import MatrixClientPeg from '../../MatrixClientPeg';
|
import MatrixClientPeg from '../../MatrixClientPeg';
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
|
import dis from '../../dispatcher';
|
||||||
import { sanitizedHtmlNode } from '../../HtmlUtils';
|
import { sanitizedHtmlNode } from '../../HtmlUtils';
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
|
import AccessibleButton from '../views/elements/AccessibleButton';
|
||||||
|
|
||||||
|
const RoomSummaryType = PropTypes.shape({
|
||||||
|
room_id: PropTypes.string.isRequired,
|
||||||
|
profile: PropTypes.shape({
|
||||||
|
name: PropTypes.string,
|
||||||
|
avatar_url: PropTypes.string,
|
||||||
|
canonical_alias: PropTypes.string,
|
||||||
|
}).isRequired,
|
||||||
|
});
|
||||||
|
|
||||||
|
const UserSummaryType = PropTypes.shape({
|
||||||
|
summaryInfo: PropTypes.shape({
|
||||||
|
user_id: PropTypes.string.isRequired,
|
||||||
|
}).isRequired,
|
||||||
|
});
|
||||||
|
|
||||||
|
const CategoryRoomList = React.createClass({
|
||||||
|
displayName: 'CategoryRoomList',
|
||||||
|
|
||||||
|
props: {
|
||||||
|
rooms: PropTypes.arrayOf(RoomSummaryType).isRequired,
|
||||||
|
category: PropTypes.shape({
|
||||||
|
profile: PropTypes.shape({
|
||||||
|
name: PropTypes.string,
|
||||||
|
}).isRequired,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
const roomNodes = this.props.rooms.map((r) => {
|
||||||
|
return <FeaturedRoom key={r.room_id} summaryInfo={r} />;
|
||||||
|
});
|
||||||
|
let catHeader = null;
|
||||||
|
if (this.props.category && this.props.category.profile) {
|
||||||
|
catHeader = <div className="mx_GroupView_featuredThings_category">{this.props.category.profile.name}</div>;
|
||||||
|
}
|
||||||
|
return <div>
|
||||||
|
{catHeader}
|
||||||
|
{roomNodes}
|
||||||
|
</div>;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const FeaturedRoom = React.createClass({
|
||||||
|
displayName: 'FeaturedRoom',
|
||||||
|
|
||||||
|
props: {
|
||||||
|
summaryInfo: RoomSummaryType.isRequired,
|
||||||
|
},
|
||||||
|
|
||||||
|
onClick: function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'view_room',
|
||||||
|
room_alias: this.props.summaryInfo.profile.canonical_alias,
|
||||||
|
room_id: this.props.summaryInfo.room_id,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
const RoomAvatar = sdk.getComponent("avatars.RoomAvatar");
|
||||||
|
|
||||||
|
const oobData = {
|
||||||
|
roomId: this.props.summaryInfo.room_id,
|
||||||
|
avatarUrl: this.props.summaryInfo.profile.avatar_url,
|
||||||
|
name: this.props.summaryInfo.profile.name,
|
||||||
|
};
|
||||||
|
let permalink = null;
|
||||||
|
if (this.props.summaryInfo.profile && this.props.summaryInfo.profile.canonical_alias) {
|
||||||
|
permalink = 'https://matrix.to/#/' + this.props.summaryInfo.profile.canonical_alias;
|
||||||
|
}
|
||||||
|
let roomNameNode = null;
|
||||||
|
if (permalink) {
|
||||||
|
roomNameNode = <a href={permalink} onClick={this.onClick} >{this.props.summaryInfo.profile.name}</a>;
|
||||||
|
} else {
|
||||||
|
roomNameNode = <span>{this.props.summaryInfo.profile.name}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <AccessibleButton className="mx_GroupView_featuredThing" onClick={this.onClick}>
|
||||||
|
<RoomAvatar oobData={oobData} width={64} height={64} />
|
||||||
|
<div className="mx_GroupView_featuredThing_name">{roomNameNode}</div>
|
||||||
|
</AccessibleButton>;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const RoleUserList = React.createClass({
|
||||||
|
displayName: 'RoleUserList',
|
||||||
|
|
||||||
|
props: {
|
||||||
|
users: PropTypes.arrayOf(UserSummaryType).isRequired,
|
||||||
|
role: PropTypes.shape({
|
||||||
|
profile: PropTypes.shape({
|
||||||
|
name: PropTypes.string,
|
||||||
|
}).isRequired,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
const userNodes = this.props.users.map((u) => {
|
||||||
|
return <FeaturedUser key={u.user_id} summaryInfo={u} />;
|
||||||
|
});
|
||||||
|
let roleHeader = null;
|
||||||
|
if (this.props.role && this.props.role.profile) {
|
||||||
|
roleHeader = <div className="mx_GroupView_featuredThings_category">{this.props.role.profile.name}</div>;
|
||||||
|
}
|
||||||
|
return <div>
|
||||||
|
{roleHeader}
|
||||||
|
{userNodes}
|
||||||
|
</div>;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const FeaturedUser = React.createClass({
|
||||||
|
displayName: 'FeaturedUser',
|
||||||
|
|
||||||
|
props: {
|
||||||
|
summaryInfo: UserSummaryType.isRequired,
|
||||||
|
},
|
||||||
|
|
||||||
|
onClick: function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'view_start_chat_or_reuse',
|
||||||
|
user_id: this.props.summaryInfo.user_id,
|
||||||
|
go_home_on_cancel: false,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
// Add avatar once we get profile info inline in the summary response
|
||||||
|
//const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
|
||||||
|
|
||||||
|
const permalink = 'https://matrix.to/#/' + this.props.summaryInfo.user_id;
|
||||||
|
const userNameNode = <a href={permalink} onClick={this.onClick} >{this.props.summaryInfo.user_id}</a>;
|
||||||
|
|
||||||
|
return <AccessibleButton className="mx_GroupView_featuredThing" onClick={this.onClick}>
|
||||||
|
<div className="mx_GroupView_featuredThing_name">{userNameNode}</div>
|
||||||
|
</AccessibleButton>;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export default React.createClass({
|
export default React.createClass({
|
||||||
displayName: 'GroupView',
|
displayName: 'GroupView',
|
||||||
|
@ -33,6 +178,7 @@ export default React.createClass({
|
||||||
return {
|
return {
|
||||||
summary: null,
|
summary: null,
|
||||||
error: null,
|
error: null,
|
||||||
|
editing: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -65,12 +211,95 @@ export default React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_onSettingsClick: function() {
|
||||||
|
this.setState({editing: true});
|
||||||
|
},
|
||||||
|
|
||||||
|
_getFeaturedRoomsNode() {
|
||||||
|
const summary = this.state.summary;
|
||||||
|
|
||||||
|
if (summary.rooms_section.rooms.length == 0) return null;
|
||||||
|
|
||||||
|
const defaultCategoryRooms = [];
|
||||||
|
const categoryRooms = {};
|
||||||
|
summary.rooms_section.rooms.forEach((r) => {
|
||||||
|
if (r.category_id === null) {
|
||||||
|
defaultCategoryRooms.push(r);
|
||||||
|
} else {
|
||||||
|
let list = categoryRooms[r.category_id];
|
||||||
|
if (list === undefined) {
|
||||||
|
list = [];
|
||||||
|
categoryRooms[r.category_id] = list;
|
||||||
|
}
|
||||||
|
list.push(r);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let defaultCategoryNode = null;
|
||||||
|
if (defaultCategoryRooms.length > 0) {
|
||||||
|
defaultCategoryNode = <CategoryRoomList rooms={defaultCategoryRooms} />;
|
||||||
|
}
|
||||||
|
const categoryRoomNodes = Object.keys(categoryRooms).map((catId) => {
|
||||||
|
const cat = summary.rooms_section.categories[catId];
|
||||||
|
return <CategoryRoomList key={catId} rooms={categoryRooms[catId]} category={cat} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
return <div className="mx_GroupView_featuredThings">
|
||||||
|
<div className="mx_GroupView_featuredThings_header">
|
||||||
|
{_t('Featured Rooms:')}
|
||||||
|
</div>
|
||||||
|
{defaultCategoryNode}
|
||||||
|
{categoryRoomNodes}
|
||||||
|
</div>;
|
||||||
|
},
|
||||||
|
|
||||||
|
_getFeaturedUsersNode() {
|
||||||
|
const summary = this.state.summary;
|
||||||
|
|
||||||
|
if (summary.users_section.users.length == 0) return null;
|
||||||
|
|
||||||
|
const noRoleUsers = [];
|
||||||
|
const roleUsers = {};
|
||||||
|
summary.users_section.users.forEach((u) => {
|
||||||
|
if (u.role_id === null) {
|
||||||
|
noRoleUsers.push(u);
|
||||||
|
} else {
|
||||||
|
let list = roleUsers[u.role_id];
|
||||||
|
if (list === undefined) {
|
||||||
|
list = [];
|
||||||
|
roleUsers[u.role_id] = list;
|
||||||
|
}
|
||||||
|
list.push(u);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let noRoleNode = null;
|
||||||
|
if (noRoleUsers.length > 0) {
|
||||||
|
noRoleNode = <RoleUserList users={noRoleUsers} />;
|
||||||
|
}
|
||||||
|
const roleUserNodes = Object.keys(roleUsers).map((roleId) => {
|
||||||
|
const role = summary.users_section.roles[roleId];
|
||||||
|
return <RoleUserList key={roleId} users={roleUsers[roleId]} role={role} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
return <div className="mx_GroupView_featuredThings">
|
||||||
|
<div className="mx_GroupView_featuredThings_header">
|
||||||
|
{_t('Featured Users:')}
|
||||||
|
</div>
|
||||||
|
{noRoleNode}
|
||||||
|
{roleUserNodes}
|
||||||
|
</div>;
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const GroupAvatar = sdk.getComponent("avatars.GroupAvatar");
|
const GroupAvatar = sdk.getComponent("avatars.GroupAvatar");
|
||||||
const Loader = sdk.getComponent("elements.Spinner");
|
const Loader = sdk.getComponent("elements.Spinner");
|
||||||
|
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||||
|
|
||||||
if (this.state.summary === null && this.state.error === null) {
|
if (this.state.summary === null && this.state.error === null) {
|
||||||
return <Loader />;
|
return <Loader />;
|
||||||
|
} else if (this.state.editing) {
|
||||||
|
return <div />;
|
||||||
} else if (this.state.summary) {
|
} else if (this.state.summary) {
|
||||||
const summary = this.state.summary;
|
const summary = this.state.summary;
|
||||||
let description = null;
|
let description = null;
|
||||||
|
@ -78,6 +307,12 @@ export default React.createClass({
|
||||||
description = sanitizedHtmlNode(summary.profile.long_description);
|
description = sanitizedHtmlNode(summary.profile.long_description);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const roomBody = <div>
|
||||||
|
<div className="mx_GroupView_groupDesc">{description}</div>
|
||||||
|
{this._getFeaturedRoomsNode()}
|
||||||
|
{this._getFeaturedUsersNode()}
|
||||||
|
</div>;
|
||||||
|
|
||||||
let nameNode;
|
let nameNode;
|
||||||
if (summary.profile && summary.profile.name) {
|
if (summary.profile && summary.profile.name) {
|
||||||
nameNode = <div className="mx_RoomHeader_name">
|
nameNode = <div className="mx_RoomHeader_name">
|
||||||
|
@ -94,6 +329,7 @@ export default React.createClass({
|
||||||
|
|
||||||
const groupAvatarUrl = summary.profile ? summary.profile.avatar_url : null;
|
const groupAvatarUrl = summary.profile ? summary.profile.avatar_url : null;
|
||||||
|
|
||||||
|
// settings button is display: none until settings is wired up
|
||||||
return (
|
return (
|
||||||
<div className="mx_GroupView">
|
<div className="mx_GroupView">
|
||||||
<div className="mx_RoomHeader">
|
<div className="mx_RoomHeader">
|
||||||
|
@ -111,9 +347,12 @@ export default React.createClass({
|
||||||
{summary.profile.short_description}
|
{summary.profile.short_description}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<AccessibleButton className="mx_RoomHeader_button" onClick={this._onSettingsClick} title={_t("Settings")} style={{display: 'none'}}>
|
||||||
|
<TintableSvg src="img/icons-settings-room.svg" width="16" height="16"/>
|
||||||
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{description}
|
{roomBody}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (this.state.error) {
|
} else if (this.state.error) {
|
||||||
|
|
|
@ -506,7 +506,7 @@ module.exports = React.createClass({
|
||||||
this._setMxId(payload);
|
this._setMxId(payload);
|
||||||
break;
|
break;
|
||||||
case 'view_start_chat_or_reuse':
|
case 'view_start_chat_or_reuse':
|
||||||
this._chatCreateOrReuse(payload.user_id);
|
this._chatCreateOrReuse(payload.user_id, payload.go_home_on_cancel);
|
||||||
break;
|
break;
|
||||||
case 'view_create_chat':
|
case 'view_create_chat':
|
||||||
this._createChat();
|
this._createChat();
|
||||||
|
@ -801,7 +801,9 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
_chatCreateOrReuse: function(userId) {
|
_chatCreateOrReuse: function(userId, goHomeOnCancel) {
|
||||||
|
if (goHomeOnCancel === undefined) goHomeOnCancel = true;
|
||||||
|
|
||||||
const ChatCreateOrReuseDialog = sdk.getComponent(
|
const ChatCreateOrReuseDialog = sdk.getComponent(
|
||||||
'views.dialogs.ChatCreateOrReuseDialog',
|
'views.dialogs.ChatCreateOrReuseDialog',
|
||||||
);
|
);
|
||||||
|
@ -832,7 +834,7 @@ module.exports = React.createClass({
|
||||||
const close = Modal.createDialog(ChatCreateOrReuseDialog, {
|
const close = Modal.createDialog(ChatCreateOrReuseDialog, {
|
||||||
userId: userId,
|
userId: userId,
|
||||||
onFinished: (success) => {
|
onFinished: (success) => {
|
||||||
if (!success) {
|
if (!success && goHomeOnCancel) {
|
||||||
// Dialog cancelled, default to home
|
// Dialog cancelled, default to home
|
||||||
dis.dispatch({ action: 'view_home_page' });
|
dis.dispatch({ action: 'view_home_page' });
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,7 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
getRoomAvatarUrl: function(props) {
|
getRoomAvatarUrl: function(props) {
|
||||||
if (!this.props.room) return null;
|
if (!props.room) return null;
|
||||||
|
|
||||||
return props.room.getAvatarUrl(
|
return props.room.getAvatarUrl(
|
||||||
MatrixClientPeg.get().getHomeserverUrl(),
|
MatrixClientPeg.get().getHomeserverUrl(),
|
||||||
|
@ -84,7 +84,7 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
getOneToOneAvatar: function(props) {
|
getOneToOneAvatar: function(props) {
|
||||||
if (!this.props.room) return null;
|
if (!props.room) return null;
|
||||||
|
|
||||||
var mlist = props.room.currentState.members;
|
var mlist = props.room.currentState.members;
|
||||||
var userIds = [];
|
var userIds = [];
|
||||||
|
@ -126,9 +126,16 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
getFallbackAvatar: function(props) {
|
getFallbackAvatar: function(props) {
|
||||||
if (!this.props.room) return null;
|
let roomId = null;
|
||||||
|
if (props.oobData && props.oobData.roomId) {
|
||||||
|
roomId = this.props.oobData.roomId;
|
||||||
|
} else if (props.room) {
|
||||||
|
roomId = props.room.roomId;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return Avatar.defaultAvatarUrlForString(props.room.roomId);
|
return Avatar.defaultAvatarUrlForString(roomId);
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
|
|
|
@ -954,5 +954,7 @@
|
||||||
"Create a group to represent your community! Define a set of rooms and your own custom homepage to mark out your space in the Matrix universe.": "Create a group to represent your community! Define a set of rooms and your own custom homepage to mark out your space in the Matrix universe.",
|
"Create a group to represent your community! Define a set of rooms and your own custom homepage to mark out your space in the Matrix universe.": "Create a group to represent your community! Define a set of rooms and your own custom homepage to mark out your space in the Matrix universe.",
|
||||||
"Join an existing group": "Join an existing group",
|
"Join an existing group": "Join an existing group",
|
||||||
"To join an exisitng group you'll have to know its group identifier; this will look something like <i>+example:matrix.org</i>.": "To join an exisitng group you'll have to know its group identifier; this will look something like <i>+example:matrix.org</i>.",
|
"To join an exisitng group you'll have to know its group identifier; this will look something like <i>+example:matrix.org</i>.": "To join an exisitng group you'll have to know its group identifier; this will look something like <i>+example:matrix.org</i>.",
|
||||||
"Error whilst fetching joined groups": "Error whilst fetching joined groups"
|
"Featured Rooms:": "Featured Rooms:",
|
||||||
|
"Error whilst fetching joined groups": "Error whilst fetching joined groups",
|
||||||
|
"Featured Users:": "Featured Users:"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue