diff options
Diffstat (limited to 'web')
-rw-r--r-- | web/react/components/more_direct_channels.jsx | 350 | ||||
-rw-r--r-- | web/react/components/sidebar.jsx | 69 | ||||
-rw-r--r-- | web/react/pages/channel.jsx | 6 | ||||
-rw-r--r-- | web/react/utils/utils.jsx | 12 | ||||
-rw-r--r-- | web/sass-files/sass/partials/_modal.scss | 46 |
5 files changed, 346 insertions, 137 deletions
diff --git a/web/react/components/more_direct_channels.jsx b/web/react/components/more_direct_channels.jsx index bc610cd60..99babe714 100644 --- a/web/react/components/more_direct_channels.jsx +++ b/web/react/components/more_direct_channels.jsx @@ -1,133 +1,273 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -var TeamStore = require('../stores/team_store.jsx'); -var Client = require('../utils/client.jsx'); -var Constants = require('../utils/constants.jsx'); -var AsyncClient = require('../utils/async_client.jsx'); -var PreferenceStore = require('../stores/preference_store.jsx'); -var utils = require('../utils/utils.jsx'); +const AsyncClient = require('../utils/async_client.jsx'); +const ChannelStore = require('../stores/channel_store.jsx'); +const Constants = require('../utils/constants.jsx'); +const Client = require('../utils/client.jsx'); +const Modal = ReactBootstrap.Modal; +const PreferenceStore = require('../stores/preference_store.jsx'); +const TeamStore = require('../stores/team_store.jsx'); +const UserStore = require('../stores/user_store.jsx'); +const Utils = require('../utils/utils.jsx'); export default class MoreDirectChannels extends React.Component { constructor(props) { super(props); - this.state = {channels: [], loadingDMChannel: -1}; + 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.state = { + users: this.getUsersFromStore(), + filter: '', + loadingDMChannel: -1 + }; + } + + getUsersFromStore() { + const currentId = UserStore.getCurrentId(); + const profiles = UserStore.getProfiles(); + const users = []; + + for (const id in profiles) { + if (id !== currentId) { + users.push(profiles[id]); + } + } + + users.sort((a, b) => a.username.localeCompare(b.username)); + + return users; } componentDidMount() { - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', (e) => { - var button = e.relatedTarget; - this.setState({channels: $(button).data('channels')}); // eslint-disable-line react/no-did-mount-set-state - }); + UserStore.addChangeListener(this.handleUserChange); } - handleJoinDirectChannel(channel) { - const preference = PreferenceStore.setPreference(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, channel.teammate_id, 'true'); - AsyncClient.savePreferences([preference]); + componentWillUnmount() { + UserStore.addChangeListener(this.handleUserChange); } - render() { - var directMessageItems = this.state.channels.map((channel, index) => { - var badge = ''; - var titleClass = ''; - var handleClick = null; - - if (channel.fake) { - // It's a direct message channel that doesn't exist yet so let's create it now - var otherUserId = utils.getUserIdFromChannelName(channel); - - if (this.state.loadingDMChannel === index) { - badge = ( - <img - className='channel-loading-gif pull-right' - src='/static/images/load.gif' - /> - ); - } + handleFilterChange() { + const filter = React.findDOMNode(this.refs.filter).value; - if (this.state.loadingDMChannel === -1) { - handleClick = (e) => { - e.preventDefault(); - this.setState({loadingDMChannel: index}); - this.handleJoinDirectChannel(channel); - - Client.createDirectChannel(channel, otherUserId, - (data) => { - $(React.findDOMNode(this.refs.modal)).modal('hide'); - this.setState({loadingDMChannel: -1}); - AsyncClient.getChannel(data.id); - utils.switchChannel(data); - }, - () => { - this.setState({loadingDMChannel: -1}); - window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name; - } - ); - }; - } - } else { - if (channel.unread) { - badge = <span className='badge pull-right small'>{channel.unread}</span>; - titleClass = 'unread-title'; + if (filter !== this.state.filter) { + this.setState({filter}); + } + } + + handleHide() { + if (this.props.onModalDismissed) { + this.props.onModalDismissed(); + } + + this.setState({filter: ''}); + } + + handleShowDirectChannel(teammate, e) { + if (this.state.loadingDMChannel !== -1) { + return; + } + + e.preventDefault(); + + const channelName = Utils.getDirectChannelName(UserStore.getCurrentId(), teammate.id); + let channel = ChannelStore.getByName(channelName); + + const preference = PreferenceStore.setPreference(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, teammate.id, 'true'); + AsyncClient.savePreferences([preference]); + + if (channel) { + Utils.switchChannel(channel); + + this.handleHide(); + } else { + this.setState({loadingDMChannel: teammate.id}); + + channel = { + name: channelName, + last_post_at: 0, + total_msg_count: 0, + type: 'D', + display_name: teammate.username, + teammate_id: teammate.id, + status: UserStore.getStatus(teammate.id) + }; + + Client.createDirectChannel( + channel, + teammate.id, + (data) => { + this.setState({loadingDMChannel: -1}); + + AsyncClient.getChannel(data.id); + Utils.switchChannel(data); + + this.handleHide(); + }, + () => { + this.setState({loadingDMChannel: -1}); + window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/' + channelName; } + ); + } + } - handleClick = (e) => { - e.preventDefault(); - this.handleJoinDirectChannel(channel); - utils.switchChannel(channel); - $(React.findDOMNode(this.refs.modal)).modal('hide'); - }; - } + handleUserChange() { + 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> + ); + } - return ( - <li key={channel.name}> - <a - className={'sidebar-channel ' + titleClass} - href='#' - onClick={handleClick} - >{badge}{channel.display_name}</a> - </li> + if (user.nickname) { + const separator = fullName ? ' - ' : ''; + details.push( + <span + key={`${user.nickname}__nickname`} + className='nickname' + > + {separator + user.nickname} + </span> ); - }); + } + + let joinButton; + if (this.state.loadingDMChannel === user.id) { + joinButton = ( + <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)} + > + {'Message'} + </button> + ); + } return ( - <div - className='modal fade' - id='more_direct_channels' - ref='modal' - tabIndex='-1' - role='dialog' - aria-hidden='true' + <li + key={user.id} + className='direct-channel' > - <div className='modal-dialog'> - <div className='modal-content'> - <div className='modal-header'> - <button - type='button' - className='close' - data-dismiss='modal' - > - <span aria-hidden='true'>{'×'}</span> - <span className='sr-only'>{'Close'}</span> - </button> - <h4 className='modal-title'>{'More Direct Messages'}</h4> - </div> - <div className='modal-body'> - <ul className='nav nav-pills nav-stacked'> - {directMessageItems} - </ul> - </div> - <div className='modal-footer'> - <button - type='button' - className='btn btn-default' - data-dismiss='modal' - >{'Close'}</button> - </div> + <div className='col-xs-1 image-div'> + <img + className='profile-image' + src={`/api/v1/users/${user.id}/image?time=${user.update_at}`} + /> + </div> + <div className='col-xs-9'> + <div className='username'> + {user.username} </div> + <div> + {details} + </div> + </div> + <div className='col-xs-2 btn-div'> + {joinButton} </div> - </div> + </li> + ); + } + + render() { + if (!this.props.show) { + return null; + } + + let users = this.state.users; + if (this.state.filter !== '') { + users = users.filter((user) => { + return user.username.indexOf(this.state.filter) !== -1 || + user.first_name.indexOf(this.state.filter) !== -1 || + user.last_name.indexOf(this.state.filter) !== -1 || + user.nickname.indexOf(this.state.filter) !== -1; + }); + } + + const userEntries = users.map(this.createRowForUser); + + if (userEntries.length === 0) { + userEntries.push(<li key='no-users-found'>{'No users found :('}</li>); + } + + let memberString = 'Member'; + if (users.length !== 1) { + memberString += 's'; + } + + let count; + if (users.length === this.state.users.length) { + count = `${users.length} ${memberString}`; + } else { + count = `${users.length} ${memberString} of ${this.state.users.length} Total`; + } + + return ( + <Modal + className='modal-direct-channels' + show={this.props.show} + bsSize='large' + onHide={this.handleHide} + > + <Modal.Header closeButton={true}> + <Modal.Title>{'More Direct Messages'}</Modal.Title> + </Modal.Header> + <Modal.Body> + <div> + <input + ref='filter' + className='form-control filter-textbox' + placeholder='Search members' + onInput={this.handleFilterChange} + style={{width: '200px', display: 'inline'}} + /> + <span className='member-count pull-right'>{count}</span> + </div> + <ul className='user-list'> + {userEntries} + </ul> + </Modal.Body> + <Modal.Footer> + <button + type='button' + className='btn btn-default' + onClick={this.handleHide} + > + {'Close'} + </button> + </Modal.Footer> + </Modal> ); } } + +MoreDirectChannels.propTypes = { + show: React.PropTypes.bool.isRequired, + onModalDismissed: React.PropTypes.func +}; diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index 4911f17ef..1308165a9 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -8,6 +8,7 @@ const Client = require('../utils/client.jsx'); const Constants = require('../utils/constants.jsx'); const PreferenceStore = require('../stores/preference_store.jsx'); const NewChannelFlow = require('./new_channel_flow.jsx'); +const MoreDirectChannels = require('./more_direct_channels.jsx'); const SearchBox = require('./search_bar.jsx'); const SidebarHeader = require('./sidebar_header.jsx'); const SocketStore = require('../stores/socket_store.jsx'); @@ -33,12 +34,19 @@ export default class Sidebar extends React.Component { this.onResize = this.onResize.bind(this); this.updateUnreadIndicators = this.updateUnreadIndicators.bind(this); this.handleLeaveDirectChannel = this.handleLeaveDirectChannel.bind(this); + + this.showNewChannelModal = this.showNewChannelModal.bind(this); + this.hideNewChannelModal = this.hideNewChannelModal.bind(this); + this.showMoreDirectChannelsModal = this.showMoreDirectChannelsModal.bind(this); + this.hideMoreDirectChannelsModal = this.hideMoreDirectChannelsModal.bind(this); + this.createChannelElement = this.createChannelElement.bind(this); this.isLeaving = new Map(); const state = this.getStateFromStores(); - state.modal = ''; + state.newChannelModalType = ''; + state.showMoreDirectChannelsModal = false; state.loadingDMChannel = -1; this.state = state; @@ -47,10 +55,11 @@ export default class Sidebar extends React.Component { const members = ChannelStore.getAllMembers(); var teamMemberMap = UserStore.getActiveOnlyProfiles(); var currentId = ChannelStore.getCurrentId(); + const currentUserId = UserStore.getCurrentId(); var teammates = []; for (var id in teamMemberMap) { - if (id === UserStore.getCurrentId()) { + if (id === currentUserId) { continue; } teammates.push(teamMemberMap[id]); @@ -58,22 +67,16 @@ export default class Sidebar extends React.Component { const preferences = PreferenceStore.getPreferences(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW); - // Create lists of all read and unread direct channels var visibleDirectChannels = []; - var hiddenDirectChannels = []; + var hiddenDirectChannelCount = 0; for (var i = 0; i < teammates.length; i++) { const teammate = teammates[i]; - if (teammate.id === UserStore.getCurrentId()) { + if (teammate.id === currentUserId) { continue; } - var channelName = ''; - if (teammate.id > UserStore.getCurrentId()) { - channelName = UserStore.getCurrentId() + '__' + teammate.id; - } else { - channelName = teammate.id + '__' + UserStore.getCurrentId(); - } + const channelName = Utils.getDirectChannelName(currentUserId, teammate.id); let forceShow = false; let channel = ChannelStore.getByName(channelName); @@ -106,19 +109,18 @@ export default class Sidebar extends React.Component { visibleDirectChannels.push(channel); } else { - hiddenDirectChannels.push(channel); + hiddenDirectChannelCount += 1; } } visibleDirectChannels.sort(this.sortChannelsByDisplayName); - hiddenDirectChannels.sort(this.sortChannelsByDisplayName); return { activeId: currentId, channels: ChannelStore.getAll(), members, visibleDirectChannels, - hiddenDirectChannels + hiddenDirectChannelCount }; } @@ -336,6 +338,20 @@ export default class Sidebar extends React.Component { return a.display_name.localeCompare(b.display_name); } + showNewChannelModal(type) { + this.setState({newChannelModalType: type}); + } + hideNewChannelModal() { + this.setState({newChannelModalType: ''}); + } + + showMoreDirectChannelsModal() { + this.setState({showDirectChannelsModal: true}); + } + hideMoreDirectChannelsModal() { + this.setState({showDirectChannelsModal: false}); + } + createChannelElement(channel, index, arr, handleClose) { var members = this.state.members; var activeId = this.state.activeId; @@ -532,25 +548,21 @@ export default class Sidebar extends React.Component { head.appendChild(link); var directMessageMore = null; - if (this.state.hiddenDirectChannels.length > 0) { + if (this.state.hiddenDirectChannelCount > 0) { directMessageMore = ( <li key='more'> <a - key={`more${this.state.hiddenDirectChannels.length}`} href='#' - data-toggle='modal' - className='nav-more' - data-target='#more_direct_channels' - data-channels={JSON.stringify(this.state.hiddenDirectChannels)} + onClick={this.showMoreDirectChannelsModal} > - {'More (' + this.state.hiddenDirectChannels.length + ')'} + {'More (' + this.state.hiddenDirectChannelCount + ')'} </a> </li> ); } let showChannelModal = false; - if (this.state.modal !== '') { + if (this.state.newChannelModalType !== '') { showChannelModal = true; } @@ -561,9 +573,14 @@ export default class Sidebar extends React.Component { <div> <NewChannelFlow show={showChannelModal} - channelType={this.state.modal} - onModalDismissed={() => this.setState({modal: ''})} + channelType={this.state.newChannelModalType} + onModalDismissed={this.hideNewChannelModal} + /> + <MoreDirectChannels + show={this.state.showDirectChannelsModal} + onModalDismissed={this.hideMoreDirectChannelsModal} /> + <SidebarHeader teamDisplayName={this.props.teamDisplayName} teamName={this.props.teamName} @@ -599,7 +616,7 @@ export default class Sidebar extends React.Component { <a className='add-channel-btn' href='#' - onClick={() => this.setState({modal: 'O'})} + onClick={this.showNewChannelModal.bind(this, 'O')} > {'+'} </a> @@ -632,7 +649,7 @@ export default class Sidebar extends React.Component { <a className='add-channel-btn' href='#' - onClick={() => this.setState({modal: 'P'})} + onClick={this.showNewChannelModal.bind(this, 'P')} > {'+'} </a> diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx index 4d6d3fd15..698213eef 100644 --- a/web/react/pages/channel.jsx +++ b/web/react/pages/channel.jsx @@ -24,7 +24,6 @@ var TeamSettingsModal = require('../components/team_settings_modal.jsx'); var ChannelMembersModal = require('../components/channel_members.jsx'); var ChannelInviteModal = require('../components/channel_invite_modal.jsx'); var TeamMembersModal = require('../components/team_members.jsx'); -var DirectChannelModal = require('../components/more_direct_channels.jsx'); var ErrorBar = require('../components/error_bar.jsx'); var ErrorStore = require('../stores/error_store.jsx'); var ChannelLoader = require('../components/channel_loader.jsx'); @@ -156,11 +155,6 @@ function setupChannelPage(props) { ); React.render( - <DirectChannelModal />, - document.getElementById('direct_channel_modal') - ); - - React.render( <PostListContainer />, document.getElementById('post-list') ); diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 8f697a9c5..f93fe02e4 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -911,6 +911,18 @@ export function isBrowserEdge() { return window.naviagtor && navigator.userAgent && navigator.userAgent.toLowerCase().indexOf('edge') > -1; } +export function getDirectChannelName(id, otherId) { + let handle; + + if (otherId > id) { + handle = id + '__' + otherId; + } else { + handle = otherId + '__' + id; + } + + return handle; +} + // Used to get the id of the other user from a DM channel export function getUserIdFromChannelName(channel) { var ids = channel.name.split('__'); diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss index 90ea8ce2c..b942a5a40 100644 --- a/web/sass-files/sass/partials/_modal.scss +++ b/web/sass-files/sass/partials/_modal.scss @@ -329,3 +329,49 @@ } } } + +.modal-direct-channels { + .user-list { + list-style-type: none; + margin: 15px 0px 0px; + max-height: 600px; + padding: 0px; + overflow: auto; + + li { + border-bottom: 1px solid #ddd; + height: 60px; + padding: 10px 0px; + + .image-div { + padding: 0px; + + .profile-image { + width: 40px; + height: 40px; + @include border-radius(20px); + } + } + + .username { + font-weight: bold; + } + + .nickname { + color: #888; + } + + .btn-div { + padding: 0px; + .btn-message { + position: relative; + top: 5px; + } + } + + &:last-child { + border-bottom: 0px; + } + } + } +} |