Modify GroupView UI
- Remove featured users/rooms - Add "Rooms" section to show all rooms in the group in a room-directory-esque list. This has a "+" button in "edit" mode. - Make the group view body scrollable
This commit is contained in:
parent
047daec587
commit
77418f535e
4 changed files with 179 additions and 18 deletions
|
@ -29,6 +29,8 @@ import classnames from 'classnames';
|
|||
|
||||
import GroupStoreCache from '../../stores/GroupStoreCache';
|
||||
import GroupStore from '../../stores/GroupStore';
|
||||
import { showGroupAddRoomDialog } from '../../GroupAddressPicker';
|
||||
|
||||
|
||||
const RoomSummaryType = PropTypes.shape({
|
||||
room_id: PropTypes.string.isRequired,
|
||||
|
@ -64,7 +66,7 @@ const CategoryRoomList = React.createClass({
|
|||
editing: PropTypes.bool.isRequired,
|
||||
},
|
||||
|
||||
onAddRoomsClicked: function(ev) {
|
||||
onAddRoomsToSummaryClicked: function(ev) {
|
||||
ev.preventDefault();
|
||||
const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
|
||||
Modal.createTrackedDialog('Add Rooms to Group Summary', '', AddressPickerDialog, {
|
||||
|
@ -106,7 +108,7 @@ const CategoryRoomList = React.createClass({
|
|||
render: function() {
|
||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||
const addButton = this.props.editing ?
|
||||
(<AccessibleButton className="mx_GroupView_featuredThings_addButton" onClick={this.onAddRoomsClicked}>
|
||||
(<AccessibleButton className="mx_GroupView_featuredThings_addButton" onClick={this.onAddRoomsToSummaryClicked}>
|
||||
<TintableSvg src="img/icons-create-room.svg" width="64" height="64" />
|
||||
<div className="mx_GroupView_featuredThings_addButton_label">
|
||||
{ _t('Add a Room') }
|
||||
|
@ -450,6 +452,7 @@ export default React.createClass({
|
|||
});
|
||||
});
|
||||
this._groupStore.on('error', (err) => {
|
||||
console.error(err);
|
||||
this.setState({
|
||||
summary: null,
|
||||
error: err,
|
||||
|
@ -601,6 +604,10 @@ export default React.createClass({
|
|||
this._setPublicity(true);
|
||||
},
|
||||
|
||||
_onAddRoomsClick: function() {
|
||||
showGroupAddRoomDialog(this.props.groupId);
|
||||
},
|
||||
|
||||
_setPublicity: function(publicity) {
|
||||
this.setState({
|
||||
publicityBusy: true,
|
||||
|
@ -612,6 +619,28 @@ export default React.createClass({
|
|||
});
|
||||
},
|
||||
|
||||
_getRoomsNode: function() {
|
||||
const RoomDetailList = sdk.getComponent('rooms.RoomDetailList');
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
const TintableSvg = sdk.getComponent('elements.TintableSvg');
|
||||
const addButton = this.state.editing ?
|
||||
(<AccessibleButton onClick={this._onAddRoomsClick} >
|
||||
<div className="mx_GroupView_rooms_header_addButton" >
|
||||
<TintableSvg src="img/icons-room-add.svg" width="24" height="24" />
|
||||
</div>
|
||||
<div className="mx_GroupView_rooms_header_addButton_label">
|
||||
{ _t('Add rooms to this group') }
|
||||
</div>
|
||||
</AccessibleButton>) : <div />;
|
||||
return <div className="mx_GroupView_rooms">
|
||||
<div className="mx_GroupView_rooms_header">
|
||||
<h3>Rooms</h3>
|
||||
{ addButton }
|
||||
</div>
|
||||
<RoomDetailList rooms={this._groupStore.getGroupRooms()} />
|
||||
</div>;
|
||||
},
|
||||
|
||||
_getFeaturedRoomsNode: function() {
|
||||
const summary = this.state.summary;
|
||||
|
||||
|
@ -790,6 +819,7 @@ export default React.createClass({
|
|||
const GroupAvatar = sdk.getComponent("avatars.GroupAvatar");
|
||||
const Loader = sdk.getComponent("elements.Spinner");
|
||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||
const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
|
||||
|
||||
if (this.state.summary === null && this.state.error === null || this.state.saving) {
|
||||
return <Loader />;
|
||||
|
@ -799,7 +829,7 @@ export default React.createClass({
|
|||
let avatarNode;
|
||||
let nameNode;
|
||||
let shortDescNode;
|
||||
let roomBody;
|
||||
let bodyNodes = [];
|
||||
const rightButtons = [];
|
||||
const headerClasses = {
|
||||
mx_GroupView_header: true,
|
||||
|
@ -856,14 +886,15 @@ export default React.createClass({
|
|||
width="18" height="18" alt={_t("Cancel")} />
|
||||
</AccessibleButton>,
|
||||
);
|
||||
roomBody = <div>
|
||||
<textarea className="mx_GroupView_editLongDesc" value={this.state.profileForm.long_description}
|
||||
bodyNodes = [
|
||||
<textarea className="mx_GroupView_editLongDesc"
|
||||
value={this.state.profileForm.long_description}
|
||||
onChange={this._onLongDescChange}
|
||||
tabIndex="3"
|
||||
/>
|
||||
{ this._getFeaturedRoomsNode() }
|
||||
{ this._getFeaturedUsersNode() }
|
||||
</div>;
|
||||
key="editLongDesc"
|
||||
/>,
|
||||
this._getRoomsNode(),
|
||||
];
|
||||
} else {
|
||||
const groupAvatarUrl = summary.profile ? summary.profile.avatar_url : null;
|
||||
avatarNode = <GroupAvatar
|
||||
|
@ -881,18 +912,19 @@ export default React.createClass({
|
|||
} else {
|
||||
nameNode = <span>{ this.props.groupId }</span>;
|
||||
}
|
||||
if (summary.profile && summary.profile.short_description) {
|
||||
shortDescNode = <span>{ summary.profile.short_description }</span>;
|
||||
}
|
||||
|
||||
let description = null;
|
||||
if (summary.profile && summary.profile.long_description) {
|
||||
description = sanitizedHtmlNode(summary.profile.long_description);
|
||||
}
|
||||
roomBody = <div>
|
||||
{ this._getMembershipSection() }
|
||||
<div className="mx_GroupView_groupDesc">{ description }</div>
|
||||
{ this._getFeaturedRoomsNode() }
|
||||
{ this._getFeaturedUsersNode() }
|
||||
</div>;
|
||||
bodyNodes = [
|
||||
this._getMembershipSection(),
|
||||
<div key="groupDesc" className="mx_GroupView_groupDesc">{ description }</div>,
|
||||
this._getRoomsNode(),
|
||||
];
|
||||
if (summary.user && summary.user.is_privileged) {
|
||||
rightButtons.push(
|
||||
<AccessibleButton className="mx_GroupHeader_button"
|
||||
|
@ -935,7 +967,12 @@ export default React.createClass({
|
|||
{ rightButtons }
|
||||
</div>
|
||||
</div>
|
||||
{ roomBody }
|
||||
<ScrollPanel className="mx_GroupView_body"
|
||||
stickyBottom={false}
|
||||
startAtBottom={false}
|
||||
>
|
||||
{ bodyNodes }
|
||||
</ScrollPanel>
|
||||
</div>
|
||||
);
|
||||
} else if (this.state.error) {
|
||||
|
|
|
@ -148,6 +148,7 @@ module.exports = React.createClass({
|
|||
onFillRequest: function(backwards) { return Promise.resolve(false); },
|
||||
onUnfillRequest: function(backwards, scrollToken) {},
|
||||
onScroll: function() {},
|
||||
onResize: function() {},
|
||||
};
|
||||
},
|
||||
|
||||
|
|
122
src/components/views/rooms/RoomDetailList.js
Normal file
122
src/components/views/rooms/RoomDetailList.js
Normal file
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
Copyright 2017 New Vector 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 sdk from '../../../index';
|
||||
import dis from '../../../dispatcher';
|
||||
import React from 'react';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import linkifyString from 'linkifyjs/string';
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
import { ContentRepo } from 'matrix-js-sdk';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
function getDisplayAliasForRoom(room) {
|
||||
return room.canonical_alias || (room.aliases ? room.aliases[0] : "");
|
||||
}
|
||||
|
||||
const RoomDetailRow = React.createClass({
|
||||
onClick: function(ev) {
|
||||
ev.preventDefault();
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: this.props.room.room_id,
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||
|
||||
const room = this.props.room;
|
||||
const name = room.name || getDisplayAliasForRoom(room) || _t('Unnamed room');
|
||||
const topic = linkifyString(sanitizeHtml(room.topic || ''));
|
||||
|
||||
const guestRead = room.world_readable ? (
|
||||
<div className="mx_RoomDirectory_perm">{ _t('World readable') }</div>
|
||||
) : <div />;
|
||||
const guestJoin = room.guest_can_join ? (
|
||||
<div className="mx_RoomDirectory_perm">{ _t('Guests can join') }</div>
|
||||
) : <div />;
|
||||
|
||||
const perms = (guestRead || guestJoin) ? (<div className="mx_RoomDirectory_perms">
|
||||
{ guestRead }
|
||||
{ guestJoin }
|
||||
</div>) : <div />;
|
||||
|
||||
return <tr key={room.room_id} onClick={this.onClick}>
|
||||
<td className="mx_RoomDirectory_roomAvatar">
|
||||
<BaseAvatar width={24} height={24} resizeMethod='crop'
|
||||
name={name} idName={name}
|
||||
url={ContentRepo.getHttpUriForMxc(
|
||||
MatrixClientPeg.get().getHomeserverUrl(),
|
||||
room.avatar_url, 24, 24, "crop")} />
|
||||
</td>
|
||||
<td className="mx_RoomDirectory_roomDescription">
|
||||
<div className="mx_RoomDirectory_name">{ name }</div>
|
||||
{ perms }
|
||||
<div className="mx_RoomDirectory_topic"
|
||||
onClick={function(e) { e.stopPropagation(); }}
|
||||
dangerouslySetInnerHTML={{ __html: topic }} />
|
||||
<div className="mx_RoomDirectory_alias">{ getDisplayAliasForRoom(room) }</div>
|
||||
</td>
|
||||
<td className="mx_RoomDirectory_roomMemberCount">
|
||||
{ room.num_joined_members }
|
||||
</td>
|
||||
</tr>;
|
||||
},
|
||||
});
|
||||
|
||||
export default React.createClass({
|
||||
displayName: 'RoomDetailList',
|
||||
|
||||
propTypes: {
|
||||
rooms: PropTypes.arrayOf(PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
topic: PropTypes.string,
|
||||
room_id: PropTypes.string,
|
||||
num_joined_members: PropTypes.number,
|
||||
canonical_alias: PropTypes.string,
|
||||
aliases: PropTypes.arrayOf(PropTypes.string),
|
||||
|
||||
world_readable: PropTypes.bool,
|
||||
guest_can_join: PropTypes.bool,
|
||||
})),
|
||||
},
|
||||
|
||||
getRows: function() {
|
||||
if (!this.props.rooms) return [];
|
||||
return this.props.rooms.map((room, index) => {
|
||||
return <RoomDetailRow key={index} room={room} />;
|
||||
});
|
||||
},
|
||||
|
||||
render() {
|
||||
const rows = this.getRows();
|
||||
let rooms;
|
||||
if (rows.length == 0) {
|
||||
rooms = <i>{ _t('No rooms to show') }</i>;
|
||||
} else {
|
||||
rooms = <table ref="directory_table" className="mx_RoomDirectory_table">
|
||||
<tbody>
|
||||
{ this.getRows() }
|
||||
</tbody>
|
||||
</table>;
|
||||
}
|
||||
return <div className="mx_RoomDetailList">
|
||||
{ rooms }
|
||||
</div>;
|
||||
},
|
||||
});
|
|
@ -917,5 +917,6 @@
|
|||
"Related groups for this room:": "Related groups for this room:",
|
||||
"This room has no related groups": "This room has no related groups",
|
||||
"New group ID (e.g. +foo:%(localDomain)s)": "New group ID (e.g. +foo:%(localDomain)s)",
|
||||
"%(serverName)s Matrix ID": "%(serverName)s Matrix ID"
|
||||
"%(serverName)s Matrix ID": "%(serverName)s Matrix ID",
|
||||
"Add rooms to this group": "Add rooms to this group"
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue