summaryrefslogtreecommitdiffstats
path: root/webapp/components/profile_popover
diff options
context:
space:
mode:
Diffstat (limited to 'webapp/components/profile_popover')
-rw-r--r--webapp/components/profile_popover/atmention_profile_popover.jsx95
-rw-r--r--webapp/components/profile_popover/picture_profile_popover.jsx104
-rw-r--r--webapp/components/profile_popover/profile_popover.jsx296
-rw-r--r--webapp/components/profile_popover/username_profile_popover.jsx111
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
+};