diff options
Diffstat (limited to 'webapp/components/profile_popover')
4 files changed, 606 insertions, 0 deletions
diff --git a/webapp/components/profile_popover/atmention_profile_popover.jsx b/webapp/components/profile_popover/atmention_profile_popover.jsx new file mode 100644 index 000000000..47c625f64 --- /dev/null +++ b/webapp/components/profile_popover/atmention_profile_popover.jsx @@ -0,0 +1,95 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import ProfilePopover from './profile_popover.jsx'; +import * as Utils from 'utils/utils.jsx'; +import Client from 'client/web_client.jsx'; + +import {OverlayTrigger} from 'react-bootstrap'; + +import React from 'react'; + +export default class AtMentionProfile extends React.Component { + constructor(props) { + super(props); + + this.hideProfilePopover = this.hideProfilePopover.bind(this); + } + + shouldComponentUpdate(nextProps) { + if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) { + return true; + } + + if (nextProps.overwriteImage !== this.props.overwriteImage) { + return true; + } + + if (nextProps.disablePopover !== this.props.disablePopover) { + return true; + } + + if (nextProps.displayNameType !== this.props.displayNameType) { + return true; + } + + if (nextProps.status !== this.props.status) { + return true; + } + + if (nextProps.isBusy !== this.props.isBusy) { + return true; + } + + return false; + } + + hideProfilePopover() { + this.refs.overlay.hide(); + } + + render() { + let profileImg = ''; + if (this.props.user) { + profileImg = Client.getUsersRoute() + '/' + this.props.user.id + '/image?time=' + this.props.user.last_picture_update; + } + + if (this.props.disablePopover) { + return <a className='mention-link'>{'@' + this.props.username}</a>; + } + + return ( + <OverlayTrigger + ref='overlay' + trigger='click' + placement='right' + rootClose={true} + overlay={ + <ProfilePopover + user={this.props.user} + src={profileImg} + status={this.props.status} + isBusy={this.props.isBusy} + hide={this.hideProfilePopover} + /> + } + > + <a className='mention-link'>{'@' + this.props.username}</a> + </OverlayTrigger> + ); + } +} + +AtMentionProfile.defaultProps = { + overwriteImage: '', + disablePopover: false +}; +AtMentionProfile.propTypes = { + user: React.PropTypes.object.isRequired, + username: React.PropTypes.string.isRequired, + overwriteImage: React.PropTypes.string, + disablePopover: React.PropTypes.bool, + displayNameType: React.PropTypes.string, + status: React.PropTypes.string, + isBusy: React.PropTypes.bool +}; diff --git a/webapp/components/profile_popover/picture_profile_popover.jsx b/webapp/components/profile_popover/picture_profile_popover.jsx new file mode 100644 index 000000000..2c2b91b25 --- /dev/null +++ b/webapp/components/profile_popover/picture_profile_popover.jsx @@ -0,0 +1,104 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import ProfilePopover from './profile_popover.jsx'; +import * as Utils from 'utils/utils.jsx'; + +import React from 'react'; +import StatusIcon from 'components/status_icon.jsx'; +import {OverlayTrigger} from 'react-bootstrap'; + +export default class ProfilePicture extends React.Component { + constructor(props) { + super(props); + + this.hideProfilePopover = this.hideProfilePopover.bind(this); + } + shouldComponentUpdate(nextProps) { + if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) { + return true; + } + + if (nextProps.src !== this.props.src) { + return true; + } + + if (nextProps.status !== this.props.status) { + return true; + } + + if (nextProps.width !== this.props.width) { + return true; + } + + if (nextProps.height !== this.props.height) { + return true; + } + + if (nextProps.isBusy !== this.props.isBusy) { + return true; + } + + return false; + } + + hideProfilePopover() { + this.refs.overlay.hide(); + } + + render() { + if (this.props.user) { + return ( + <OverlayTrigger + ref='overlay' + trigger='click' + placement='right' + rootClose={true} + overlay={ + <ProfilePopover + user={this.props.user} + src={this.props.src} + status={this.props.status} + isBusy={this.props.isBusy} + hide={this.hideProfilePopover} + /> + } + > + <span className='status-wrapper'> + <img + className='more-modal__image' + width={this.props.width} + height={this.props.width} + src={this.props.src} + /> + <StatusIcon status={this.props.status}/> + </span> + </OverlayTrigger> + ); + } + return ( + <span className='status-wrapper'> + <img + className='more-modal__image' + width={this.props.width} + height={this.props.width} + src={this.props.src} + /> + <StatusIcon status={this.props.status}/> + </span> + ); + } +} + +ProfilePicture.defaultProps = { + width: '36', + height: '36' +}; +ProfilePicture.propTypes = { + src: React.PropTypes.string.isRequired, + status: React.PropTypes.string, + width: React.PropTypes.string, + height: React.PropTypes.string, + user: React.PropTypes.object, + isBusy: React.PropTypes.bool +}; diff --git a/webapp/components/profile_popover/profile_popover.jsx b/webapp/components/profile_popover/profile_popover.jsx new file mode 100644 index 000000000..a32b7904b --- /dev/null +++ b/webapp/components/profile_popover/profile_popover.jsx @@ -0,0 +1,296 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import * as Utils from 'utils/utils.jsx'; +import UserStore from 'stores/user_store.jsx'; +import WebrtcStore from 'stores/webrtc_store.jsx'; +import TeamStore from 'stores/team_store.jsx'; +import * as GlobalActions from 'actions/global_actions.jsx'; +import * as WebrtcActions from 'actions/webrtc_actions.jsx'; +import {openDirectChannelToUser} from 'actions/channel_actions.jsx'; +import Constants from 'utils/constants.jsx'; +const UserStatuses = Constants.UserStatuses; +const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES; + +import {Popover, OverlayTrigger, Tooltip} from 'react-bootstrap'; +import {FormattedMessage} from 'react-intl'; +import {browserHistory} from 'react-router/es6'; +import React from 'react'; + +export default class ProfilePopover extends React.Component { + constructor(props) { + super(props); + + this.initWebrtc = this.initWebrtc.bind(this); + this.handleShowDirectChannel = this.handleShowDirectChannel.bind(this); + this.generateImage = this.generateImage.bind(this); + this.generateFullname = this.generateFullname.bind(this); + this.generatePosition = this.generatePosition.bind(this); + this.generateWebrtc = this.generateWebrtc.bind(this); + this.generateEmail = this.generateEmail.bind(this); + this.generateDirectMessage = this.generateDirectMessage.bind(this); + + this.state = { + currentUserId: UserStore.getCurrentId(), + loadingDMChannel: -1 + }; + } + + shouldComponentUpdate(nextProps) { + if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) { + return true; + } + + if (nextProps.src !== this.props.src) { + return true; + } + + if (nextProps.status !== this.props.status) { + return true; + } + + if (nextProps.isBusy !== this.props.isBusy) { + return true; + } + + // React-Bootstrap Forwarded Props from OverlayTrigger to Popover + if (nextProps.arrowOffsetLeft !== this.props.arrowOffsetLeft) { + return true; + } + + if (nextProps.arrowOffsetTop !== this.props.arrowOffsetTop) { + return true; + } + + if (nextProps.positionLeft !== this.props.positionLeft) { + return true; + } + + if (nextProps.positionTop !== this.props.positionTop) { + return true; + } + + return false; + } + + handleShowDirectChannel(e) { + e.preventDefault(); + + if (!this.props.user) { + return; + } + + const user = this.props.user; + + if (this.state.loadingDMChannel !== -1) { + return; + } + + this.setState({loadingDMChannel: user.id}); + + openDirectChannelToUser( + user.id, + (channel) => { + if (Utils.isMobile()) { + GlobalActions.emitCloseRightHandSide(); + } + this.setState({loadingDMChannel: -1}); + if (this.props.hide) { + this.props.hide(); + } + browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + channel.name); + } + ); + } + + initWebrtc() { + if (this.props.status !== UserStatuses.OFFLINE && !WebrtcStore.isBusy()) { + GlobalActions.emitCloseRightHandSide(); + WebrtcActions.initWebrtc(this.props.user.id, true); + } + } + + generateImage(src) { + return ( + <img + className='user-popover__image' + src={src} + height='128' + width='128' + key='user-popover-image' + /> + ); + } + + generateFullname() { + const fullname = Utils.getFullName(this.props.user); + if (fullname) { + return ( + <OverlayTrigger + delayShow={Constants.WEBRTC_TIME_DELAY} + placement='top' + overlay={<Tooltip id='fullNameTooltip'>{fullname}</Tooltip>} + > + <div + className='overflow--ellipsis text-nowrap padding-bottom' + > + {fullname} + </div> + </OverlayTrigger> + ); + } + + return ''; + } + + generatePosition() { + if (this.props.user.hasOwnProperty('position')) { + const position = this.props.user.position.substring(0, Constants.MAX_POSITION_LENGTH); + return ( + <OverlayTrigger + delayShow={Constants.WEBRTC_TIME_DELAY} + placement='top' + overlay={<Tooltip id='positionTooltip'>{position}</Tooltip>} + > + <div + className='overflow--ellipsis text-nowrap padding-bottom' + > + {position} + </div> + </OverlayTrigger> + ); + } + + return ''; + } + + generateWebrtc() { + const userMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia; + const webrtcEnabled = global.mm_config.EnableWebrtc === 'true' && userMedia && Utils.isFeatureEnabled(PreReleaseFeatures.WEBRTC_PREVIEW); + if (webrtcEnabled && this.props.user.id !== this.state.currentUserId) { + const isOnline = this.props.status !== UserStatuses.OFFLINE; + let webrtcMessage; + if (isOnline && !this.props.isBusy) { + webrtcMessage = ( + <FormattedMessage + id='user_profile.webrtc.call' + defaultMessage='Start Video Call' + /> + ); + } else if (this.props.isBusy) { + webrtcMessage = ( + <FormattedMessage + id='user_profile.webrtc.unavailable' + defaultMessage='New call unavailable until your existing call ends' + /> + ); + } else { + webrtcMessage = ( + <FormattedMessage + id='user_profile.webrtc.offline' + defaultMessage='The user is offline' + /> + ); + } + + return ( + <div + data-toggle='tooltip' + key='makeCall' + className='popover__row' + > + <a + href='#' + className='text-nowrap user-popover__email' + onClick={() => this.initWebrtc()} + disabled={!isOnline} + > + <i className='fa fa-video-camera'/> + {webrtcMessage} + </a> + </div> + ); + } + + return ''; + } + + generateEmail() { + const email = this.props.user.hasOwnProperty('email') ? this.props.user.email : ''; + const showEmail = (global.window.mm_config.ShowEmailAddress === 'true' || UserStore.isSystemAdminForCurrentUser() || this.props.user === UserStore.getCurrentUser()); + + if (email !== '' && showEmail) { + return ( + <div + data-toggle='tooltip' + title={email} + key='user-popover-email' + > + <a + href={'mailto:' + email} + className='text-nowrap text-lowercase user-popover__email' + > + {email} + </a> + </div> + ); + } + + return ''; + } + + generateDirectMessage() { + if (this.props.user.id !== UserStore.getCurrentId()) { + return ( + <div + data-toggle='tooltip' + key='user-popover-dm' + className='popover__row first' + > + <a + href='#' + className='text-nowrap text-lowercase user-popover__email' + onClick={this.handleShowDirectChannel} + > + <i className='fa fa-paper-plane'/> + <FormattedMessage + id='user_profile.send.dm' + defaultMessage='Send Message' + /> + </a> + </div> + ); + } + + return ''; + } + + render() { + return ( + <Popover + arrowOffsetLeft={this.props.arrowOffsetLeft} + arrowOffsetTop={this.props.arrowOffsetTop} + positionLeft={this.props.positionLeft} + positionTop={this.props.positionTop} + title={'@' + this.props.user.username} + id='user-profile-popover' + > + {this.generateImage(this.props.src)} + {this.generateFullname()} + {this.generatePosition()} + {this.generateEmail()} + {this.generateDirectMessage()} + {this.generateWebrtc()} + </Popover> + ); + } +} + +ProfilePopover.propTypes = Object.assign({ + src: React.PropTypes.string.isRequired, + user: React.PropTypes.object.isRequired, + status: React.PropTypes.string, + isBusy: React.PropTypes.bool, + hide: React.PropTypes.func +}, Popover.propTypes); +delete ProfilePopover.propTypes.id; diff --git a/webapp/components/profile_popover/username_profile_popover.jsx b/webapp/components/profile_popover/username_profile_popover.jsx new file mode 100644 index 000000000..37993094b --- /dev/null +++ b/webapp/components/profile_popover/username_profile_popover.jsx @@ -0,0 +1,111 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import ProfilePopover from './profile_popover.jsx'; +import * as Utils from 'utils/utils.jsx'; +import Client from 'client/web_client.jsx'; + +import {OverlayTrigger} from 'react-bootstrap'; + +import React from 'react'; + +export default class UserProfile extends React.Component { + constructor(props) { + super(props); + + this.hideProfilePopover = this.hideProfilePopover.bind(this); + } + shouldComponentUpdate(nextProps) { + if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) { + return true; + } + + if (nextProps.overwriteName !== this.props.overwriteName) { + return true; + } + + if (nextProps.overwriteImage !== this.props.overwriteImage) { + return true; + } + + if (nextProps.disablePopover !== this.props.disablePopover) { + return true; + } + + if (nextProps.displayNameType !== this.props.displayNameType) { + return true; + } + + if (nextProps.status !== this.props.status) { + return true; + } + + if (nextProps.isBusy !== this.props.isBusy) { + return true; + } + + return false; + } + + hideProfilePopover() { + this.refs.overlay.hide(); + } + + render() { + let name = '...'; + let profileImg = ''; + if (this.props.user) { + name = Utils.displayUsername(this.props.user.id); + profileImg = Client.getUsersRoute() + '/' + this.props.user.id + '/image?time=' + this.props.user.last_picture_update; + } + + if (this.props.overwriteName) { + name = this.props.overwriteName; + } + + if (this.props.disablePopover) { + return <div className='user-popover'>{name}</div>; + } + + return ( + <OverlayTrigger + ref='overlay' + trigger='click' + placement='right' + rootClose={true} + overlay={ + <ProfilePopover + user={this.props.user} + src={profileImg} + status={this.props.status} + isBusy={this.props.isBusy} + hide={this.hideProfilePopover} + /> + } + > + <div + className='user-popover' + id={'profile_' + this.uniqueId} + > + {name} + </div> + </OverlayTrigger> + ); + } +} + +UserProfile.defaultProps = { + user: {}, + overwriteName: '', + overwriteImage: '', + disablePopover: false +}; +UserProfile.propTypes = { + user: React.PropTypes.object, + overwriteName: React.PropTypes.node, + overwriteImage: React.PropTypes.string, + disablePopover: React.PropTypes.bool, + displayNameType: React.PropTypes.string, + status: React.PropTypes.string, + isBusy: React.PropTypes.bool +}; |