summaryrefslogtreecommitdiffstats
path: root/web
diff options
context:
space:
mode:
Diffstat (limited to 'web')
-rw-r--r--web/react/components/channel_members_modal.jsx94
-rw-r--r--web/react/components/filtered_user_list.jsx129
-rw-r--r--web/react/components/more_direct_channels.jsx215
-rw-r--r--web/react/components/user_list.jsx53
-rw-r--r--web/react/components/user_list_row.jsx79
-rw-r--r--web/sass-files/sass/partials/_modal.scss22
6 files changed, 349 insertions, 243 deletions
diff --git a/web/react/components/channel_members_modal.jsx b/web/react/components/channel_members_modal.jsx
index fd452f206..44de0266b 100644
--- a/web/react/components/channel_members_modal.jsx
+++ b/web/react/components/channel_members_modal.jsx
@@ -1,8 +1,8 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import FilteredUserList from './filtered_user_list.jsx';
import LoadingScreen from './loading_screen.jsx';
-import MemberList from './member_list.jsx';
import ChannelInviteModal from './channel_invite_modal.jsx';
import UserStore from '../stores/user_store.jsx';
@@ -24,6 +24,8 @@ export default class ChannelMembersModal extends React.Component {
this.onChange = this.onChange.bind(this);
this.handleRemove = this.handleRemove.bind(this);
+ this.createRemoveMemberButton = this.createRemoveMemberButton.bind(this);
+
// the rest of the state gets populated when the modal is shown
this.state = {
showInviteModal: false
@@ -51,24 +53,10 @@ export default class ChannelMembersModal extends React.Component {
};
}
- const users = UserStore.getActiveOnlyProfiles();
- const memberList = extraInfo.members;
-
- const nonmemberList = [];
- for (const id in users) {
- if (users.hasOwnProperty(id)) {
- let found = false;
- for (let i = 0; i < memberList.length; i++) {
- if (memberList[i].id === id) {
- found = true;
- break;
- }
- }
- if (!found) {
- nonmemberList.push(users[id]);
- }
- }
- }
+ // clone the member list since we mutate it later on
+ const memberList = extraInfo.members.map((member) => {
+ return Object.assign({}, member);
+ });
function compareByUsername(a, b) {
if (a.username < b.username) {
@@ -81,15 +69,14 @@ export default class ChannelMembersModal extends React.Component {
}
memberList.sort(compareByUsername);
- nonmemberList.sort(compareByUsername);
return {
- nonmemberList,
memberList,
loading: false
};
}
onShow() {
+ // TODO ugh
if ($(window).width() > 768) {
$(ReactDOM.findDOMNode(this.refs.modalBody)).perfectScrollbar();
}
@@ -116,41 +103,25 @@ export default class ChannelMembersModal extends React.Component {
this.setState(newState);
}
}
- handleRemove(userId) {
- // Make sure the user is a member of the channel
- const memberList = this.state.memberList;
- let found = false;
- for (let i = 0; i < memberList.length; i++) {
- if (memberList[i].id === userId) {
- found = true;
- break;
- }
- }
-
- if (!found) {
- return;
- }
+ handleRemove(user) {
+ const userId = user.id;
const data = {};
data.user_id = userId;
- Client.removeChannelMember(ChannelStore.getCurrentId(), data,
+ Client.removeChannelMember(
+ ChannelStore.getCurrentId(),
+ data,
() => {
- let oldMember;
+ const memberList = this.state.memberList.slice();
for (let i = 0; i < memberList.length; i++) {
if (userId === memberList[i].id) {
- oldMember = memberList[i];
memberList.splice(i, 1);
break;
}
}
- const nonmemberList = this.state.nonmemberList;
- if (oldMember) {
- nonmemberList.push(oldMember);
- }
-
- this.setState({memberList, nonmemberList});
+ this.setState({memberList});
AsyncClient.getChannelExtraInfo();
},
(err) => {
@@ -158,30 +129,39 @@ export default class ChannelMembersModal extends React.Component {
}
);
}
+ createRemoveMemberButton({user}) {
+ if (user.id === UserStore.getCurrentId()) {
+ return null;
+ }
+
+ return (
+ <button
+ type='button'
+ className='btn btn-primary btn-message'
+ onClick={this.handleRemove.bind(this, user)}
+ >
+ <FormattedMessage
+ id='member_item.removeMember'
+ defaultMessage='Remove Member'
+ />
+ </button>
+ );
+ }
render() {
var maxHeight = 1000;
if (Utils.windowHeight() <= 1200) {
maxHeight = Utils.windowHeight() - 300;
}
- const currentMember = ChannelStore.getCurrentMember();
- let isAdmin = false;
- if (currentMember) {
- isAdmin = Utils.isAdmin(currentMember.roles) || Utils.isAdmin(UserStore.getCurrentUser().roles);
- }
-
let content;
if (this.state.loading) {
content = (<LoadingScreen/>);
} else {
content = (
- <div className='team-member-list'>
- <MemberList
- memberList={this.state.memberList}
- isAdmin={isAdmin}
- handleRemove={this.handleRemove}
- />
- </div>
+ <FilteredUserList
+ users={this.state.memberList}
+ actions={[this.createRemoveMemberButton]}
+ />
);
}
diff --git a/web/react/components/filtered_user_list.jsx b/web/react/components/filtered_user_list.jsx
new file mode 100644
index 000000000..dc8abd4c1
--- /dev/null
+++ b/web/react/components/filtered_user_list.jsx
@@ -0,0 +1,129 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import UserList from './user_list.jsx';
+
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ member: {
+ id: 'more_direct_channels.member',
+ defaultMessage: 'Member'
+ },
+ search: {
+ id: 'more_direct_channels.search',
+ defaultMessage: 'Search members'
+ }
+});
+
+class FilteredUserList extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleFilterChange = this.handleFilterChange.bind(this);
+
+ this.state = {
+ filter: ''
+ };
+ }
+
+ componentDidUpdate(prevProps, prevState) {
+ if (prevState.filter !== this.state.filter) {
+ $(ReactDOM.findDOMNode(this.refs.userList)).scrollTop(0);
+ }
+ }
+
+ handleFilterChange(e) {
+ this.setState({
+ filter: e.target.value
+ });
+ }
+
+ render() {
+ const {formatMessage} = this.props.intl;
+
+ let users = this.props.users;
+
+ if (this.state.filter) {
+ const filter = this.state.filter.toLowerCase();
+
+ users = users.filter((user) => {
+ return user.username.toLowerCase().indexOf(filter) !== -1 ||
+ (user.first_name && user.first_name.toLowerCase().indexOf(filter) !== -1) ||
+ (user.last_name && user.last_name.toLowerCase().indexOf(filter) !== -1) ||
+ (user.nickname && user.nickname.toLowerCase().indexOf(filter) !== -1);
+ });
+ }
+
+ let memberString = formatMessage(holders.member);
+ if (users.length !== 1) {
+ memberString += 's';
+ }
+
+ let count;
+ if (users.length === this.props.users.length) {
+ count = (
+ <FormattedMessage
+ id='more_direct_channels.count'
+ defaultMessage='{count} {member}'
+ values={{
+ count: users.length,
+ member: memberString
+ }}
+ />
+ );
+ } else {
+ count = (
+ <FormattedMessage
+ id='more_direct_channels.countTotal'
+ defaultMessage='{count} {member} of {total} Total'
+ values={{
+ count: users.length,
+ member: memberString,
+ total: this.props.users.length
+ }}
+ />
+ );
+ }
+
+ return (
+ <div>
+ <div className='filter-row'>
+ <div className='col-sm-6'>
+ <input
+ ref='filter'
+ className='form-control filter-textbox'
+ placeholder={formatMessage(holders.search)}
+ onInput={this.handleFilterChange}
+ />
+ </div>
+ <div className='col-sm-6'>
+ <span className='member-count'>{count}</span>
+ </div>
+ </div>
+ <div
+ ref='userList'
+ className='user-list'
+ >
+ <UserList
+ users={users}
+ actions={this.props.actions}
+ />
+ </div>
+ </div>
+ );
+ }
+}
+
+FilteredUserList.defaultProps = {
+ users: [],
+ actions: []
+};
+
+FilteredUserList.propTypes = {
+ intl: intlShape.isRequired,
+ users: React.PropTypes.arrayOf(React.PropTypes.object),
+ actions: React.PropTypes.arrayOf(React.PropTypes.func)
+};
+
+export default injectIntl(FilteredUserList);
diff --git a/web/react/components/more_direct_channels.jsx b/web/react/components/more_direct_channels.jsx
index 3b72b251c..c85b5e9c5 100644
--- a/web/react/components/more_direct_channels.jsx
+++ b/web/react/components/more_direct_channels.jsx
@@ -2,36 +2,24 @@
// See License.txt for license information.
const Modal = ReactBootstrap.Modal;
+import FilteredUserList from './filtered_user_list.jsx';
import UserStore from '../stores/user_store.jsx';
import * as Utils from '../utils/utils.jsx';
-import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+import {FormattedMessage} from 'mm-intl';
-const holders = defineMessages({
- member: {
- id: 'more_direct_channels.member',
- defaultMessage: 'Member'
- },
- search: {
- id: 'more_direct_channels.search',
- defaultMessage: 'Search members'
- }
-});
-
-class MoreDirectChannels extends React.Component {
+export default class MoreDirectChannels extends React.Component {
constructor(props) {
super(props);
- this.handleFilterChange = this.handleFilterChange.bind(this);
this.handleHide = this.handleHide.bind(this);
this.handleShowDirectChannel = this.handleShowDirectChannel.bind(this);
this.handleUserChange = this.handleUserChange.bind(this);
- this.createRowForUser = this.createRowForUser.bind(this);
+ this.createJoinDirectChannelButton = this.createJoinDirectChannelButton.bind(this);
this.state = {
users: this.getUsersFromStore(),
- filter: '',
loadingDMChannel: -1
};
}
@@ -67,32 +55,20 @@ class MoreDirectChannels extends React.Component {
}
onShow() {
- if (Utils.isMobile()) {
- $(ReactDOM.findDOMNode(this.refs.userList)).css('max-height', $(window).height() - 250);
+ // TODO ugh
+ /*if (Utils.isMobile()) {
+ $(ReactDOM.findDOMNode(this.refs.modal)).css('max-height', $(window).height() - 250);
} else {
- $(ReactDOM.findDOMNode(this.refs.userList)).perfectScrollbar();
- $(ReactDOM.findDOMNode(this.refs.userList)).css('max-height', $(window).height() - 300);
- }
- }
-
- handleFilterChange() {
- const filter = ReactDOM.findDOMNode(this.refs.filter).value;
-
- if ($(window).width() > 768) {
- $(ReactDOM.findDOMNode(this.refs.userList)).scrollTop(0);
- }
-
- if (filter !== this.state.filter) {
- this.setState({filter});
- }
+ console.log(ReactDOM.findDOMNode(this.refs.modal));
+ console.log($(ReactDOM.findDOMNode(this.refs.modal)));
+ $(ReactDOM.findDOMNode(this.refs.modal)).css('max-height', $(window).height() - 300);
+ }*/
}
handleHide() {
if (this.props.onModalDismissed) {
this.props.onModalDismissed();
}
-
- this.setState({filter: ''});
}
handleShowDirectChannel(teammate, e) {
@@ -120,145 +96,34 @@ class MoreDirectChannels extends React.Component {
this.setState({users: this.getUsersFromStore()});
}
- createRowForUser(user) {
- const details = [];
-
- const fullName = Utils.getFullName(user);
- if (fullName) {
- details.push(
- <span
- key={`${user.id}__full-name`}
- className='full-name'
- >
- {fullName}
- </span>
- );
- }
-
- if (user.nickname) {
- const separator = fullName ? ' - ' : '';
- details.push(
- <span
- key={`${user.nickname}__nickname`}
- >
- {separator + user.nickname}
- </span>
- );
- }
-
- let joinButton;
+ createJoinDirectChannelButton({user}) {
if (this.state.loadingDMChannel === user.id) {
- joinButton = (
+ return (
<img
className='channel-loading-gif'
src='/static/images/load.gif'
/>
);
- } else {
- joinButton = (
- <button
- type='button'
- className='btn btn-primary btn-message'
- onClick={this.handleShowDirectChannel.bind(this, user)}
- >
- <FormattedMessage
- id='more_direct_channels.message'
- defaultMessage='Message'
- />
- </button>
- );
}
return (
- <tr key={'direct-channel-row-user' + user.id}>
- <td
- key={user.id}
- className='direct-channel'
- >
- <img
- className='profile-img pull-left'
- width='38'
- height='38'
- src={`/api/v1/users/${user.id}/image?time=${user.update_at}&${Utils.getSessionIndex()}`}
- />
- <div className='more-name'>
- {user.username}
- </div>
- <div className='more-description'>
- {details}
- </div>
- </td>
- <td className='td--action lg'>
- {joinButton}
- </td>
- </tr>
+ <button
+ type='button'
+ className='btn btn-primary btn-message'
+ onClick={this.handleShowDirectChannel.bind(this, user)}
+ >
+ <FormattedMessage
+ id='more_direct_channels.message'
+ defaultMessage='Message'
+ />
+ </button>
);
}
render() {
- const {formatMessage} = this.props.intl;
- if (!this.props.show) {
- return null;
- }
-
- let users = this.state.users;
- if (this.state.filter) {
- const filter = this.state.filter.toLowerCase();
-
- users = users.filter((user) => {
- return user.username.toLowerCase().indexOf(filter) !== -1 ||
- user.first_name.toLowerCase().indexOf(filter) !== -1 ||
- user.last_name.toLowerCase().indexOf(filter) !== -1 ||
- user.nickname.toLowerCase().indexOf(filter) !== -1;
- });
- }
-
- const userEntries = users.map(this.createRowForUser);
-
- if (userEntries.length === 0) {
- userEntries.push(
- <tr key='no-users-found'><td>
- <FormattedMessage
- id='more_direct_channels.notFound'
- defaultMessage='No users found :('
- />
- </td></tr>);
- }
-
- let memberString = formatMessage(holders.member);
- if (users.length !== 1) {
- memberString += 's';
- }
-
- let count;
- if (users.length === this.state.users.length) {
- count = (
- <FormattedMessage
- id='more_direct_channels.count'
- defaultMessage='{count} {member}'
- values={{
- count: users.length,
- member: memberString
- }}
- />
- );
- } else {
- count = (
- <FormattedMessage
- id='more_direct_channels.countTotal'
- defaultMessage='{count} {member} of {total} Total'
- values={{
- count: users.length,
- member: memberString,
- total: this.state.users.length
- }}
- />
- );
- }
-
return (
<Modal
- dialogClassName='more-modal'
+ dialogClassName='more-modal more-direct-channels'
show={this.props.show}
onHide={this.handleHide}
>
@@ -270,30 +135,11 @@ class MoreDirectChannels extends React.Component {
/>
</Modal.Title>
</Modal.Header>
- <Modal.Body ref='modalBody'>
- <div className='filter-row'>
- <div className='col-sm-6'>
- <input
- ref='filter'
- className='form-control filter-textbox'
- placeholder={formatMessage(holders.search)}
- onInput={this.handleFilterChange}
- />
- </div>
- <div className='col-sm-6'>
- <span className='member-count'>{count}</span>
- </div>
- </div>
- <div
- ref='userList'
- className='user-list'
- >
- <table className='more-table table'>
- <tbody>
- {userEntries}
- </tbody>
- </table>
- </div>
+ <Modal.Body>
+ <FilteredUserList
+ users={this.state.users}
+ actions={[this.createJoinDirectChannelButton]}
+ />
</Modal.Body>
<Modal.Footer>
<button
@@ -313,9 +159,6 @@ class MoreDirectChannels extends React.Component {
}
MoreDirectChannels.propTypes = {
- intl: intlShape.isRequired,
show: React.PropTypes.bool.isRequired,
onModalDismissed: React.PropTypes.func
};
-
-export default injectIntl(MoreDirectChannels);
diff --git a/web/react/components/user_list.jsx b/web/react/components/user_list.jsx
new file mode 100644
index 000000000..e3c2ad08d
--- /dev/null
+++ b/web/react/components/user_list.jsx
@@ -0,0 +1,53 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {FormattedMessage} from 'mm-intl';
+import UserListRow from './user_list_row.jsx';
+
+export default class UserList extends React.Component {
+ render() {
+ const users = this.props.users;
+
+ let content;
+ if (users.length > 0) {
+ content = users.map((user) => {
+ return (
+ <UserListRow
+ key={user.id}
+ user={user}
+ actions={this.props.actions}
+ />
+ );
+ });
+ } else {
+ content = (
+ <tr key='no-users-found'>
+ <td>
+ <FormattedMessage
+ id='more_direct_channels.notFound'
+ defaultMessage='No users found :('
+ />
+ </td>
+ </tr>
+ );
+ }
+
+ return (
+ <table className='more-table table'>
+ <tbody>
+ {content}
+ </tbody>
+ </table>
+ );
+ }
+}
+
+UserList.defaultProps = {
+ users: [],
+ actions: []
+};
+
+UserList.propTypes = {
+ users: React.PropTypes.arrayOf(React.PropTypes.object),
+ actions: React.PropTypes.arrayOf(React.PropTypes.func)
+};
diff --git a/web/react/components/user_list_row.jsx b/web/react/components/user_list_row.jsx
new file mode 100644
index 000000000..cf2e2668c
--- /dev/null
+++ b/web/react/components/user_list_row.jsx
@@ -0,0 +1,79 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import * as Utils from '../utils/utils.jsx';
+
+export default function UserListRow({user, actions}) {
+ const details = [];
+
+ const fullName = Utils.getFullName(user);
+ if (fullName) {
+ details.push(
+ <span
+ key={`${user.id}__full-name`}
+ className='full-name'
+ >
+ {fullName}
+ </span>
+ );
+ }
+
+ if (user.nickname) {
+ const separator = fullName ? ' - ' : '';
+ details.push(
+ <span
+ key={`${user.nickname}__nickname`}
+ >
+ {separator + user.nickname}
+ </span>
+ );
+ }
+
+ const buttons = actions.map((Action, index) => {
+ return (
+ <Action
+ key={index.toString()}
+ user={user}
+ />
+ );
+ });
+
+ return (
+ <tr>
+ <td
+ key={user.id}
+ className='direct-channel'
+ style={{display: 'flex'}}
+ >
+ <img
+ className='profile-img'
+ src={`/api/v1/users/${user.id}/image?time=${user.update_at}&${Utils.getSessionIndex()}`}
+ />
+ <div
+ className='user-list-item__details'
+ >
+ <div className='more-name'>
+ {user.username}
+ </div>
+ <div className='more-description'>
+ {details}
+ </div>
+ </div>
+ <div
+ className='user-list-item__actions'
+ >
+ {buttons}
+ </div>
+ </td>
+ </tr>
+ );
+}
+
+UserListRow.defaultProps = {
+ actions: []
+};
+
+UserListRow.propTypes = {
+ user: React.PropTypes.object.isRequired,
+ actions: React.PropTypes.arrayOf(React.PropTypes.func)
+};
diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss
index 09c366c77..6144dc611 100644
--- a/web/sass-files/sass/partials/_modal.scss
+++ b/web/sass-files/sass/partials/_modal.scss
@@ -434,3 +434,25 @@
max-height: 150px;
}
}
+
+.user-list {
+ display: flex;
+ flex-direction: column;
+
+ .profile-img {
+ width: 38px;
+ height: 38px;
+ flex-grow: 0;
+ flex-shrink: 0;
+ }
+
+ .user-list-item__details {
+ flex-grow: 1;
+ flex-shrink: 1;
+ }
+
+ .user-list-item__actions {
+ flex-grow: 0;
+ flex-shrink: 0;
+ }
+}