summaryrefslogtreecommitdiffstats
path: root/webapp/components
diff options
context:
space:
mode:
authorGeorge Goldberg <george@gberg.me>2016-12-22 17:19:19 +0000
committerenahum <nahumhbl@gmail.com>2016-12-22 14:19:19 -0300
commita857cf18f4809ab5fbff4956b42430e5eeddb54e (patch)
treefc6dee02b37c9ca1ae457ce0d98724abae91e32d /webapp/components
parent52c4538817c310977474dd94c8e828f6489dadab (diff)
downloadchat-a857cf18f4809ab5fbff4956b42430e5eeddb54e.tar.gz
chat-a857cf18f4809ab5fbff4956b42430e5eeddb54e.tar.bz2
chat-a857cf18f4809ab5fbff4956b42430e5eeddb54e.zip
PLT-4860 Make ProfilePopover into it's own component and use it consistently everywhere (#4701)
* PLT-4860 - Use same User Popover everywhere. Refactor out the ProfilePopover into it's own component and give it the union of all the features of the previous two implementations, and make sure all the necessary data for it to work consistently everywhere is provided through the props wherever it is used. * Don't show popover for webhook posts in main view. * No popover on RHS when it's a webhook post. * Fix style. * Don't send in user when it's a system message. * Fix some duplication of code.
Diffstat (limited to 'webapp/components')
-rw-r--r--webapp/components/post_view/components/post.jsx32
-rw-r--r--webapp/components/post_view/components/post_header.jsx8
-rw-r--r--webapp/components/profile_picture.jsx72
-rw-r--r--webapp/components/profile_popover.jsx224
-rw-r--r--webapp/components/rhs_comment.jsx72
-rw-r--r--webapp/components/rhs_root_post.jsx56
-rw-r--r--webapp/components/rhs_thread.jsx15
-rw-r--r--webapp/components/search_results.jsx32
-rw-r--r--webapp/components/search_results_item.jsx8
-rw-r--r--webapp/components/user_profile.jsx174
10 files changed, 440 insertions, 253 deletions
diff --git a/webapp/components/post_view/components/post.jsx b/webapp/components/post_view/components/post.jsx
index ce6ce7d2a..f052ac4ae 100644
--- a/webapp/components/post_view/components/post.jsx
+++ b/webapp/components/post_view/components/post.jsx
@@ -194,9 +194,18 @@ export default class Post extends React.Component {
src={PostUtils.getProfilePicSrcForPost(post, timestamp)}
status={status}
user={this.props.user}
+ isBusy={this.props.isBusy}
/>
);
+ if (post.props && post.props.from_webhook) {
+ profilePic = (
+ <ProfilePicture
+ src={PostUtils.getProfilePicSrcForPost(post, timestamp)}
+ />
+ );
+ }
+
if (PostUtils.isSystemMessage(post)) {
profilePic = (
<span
@@ -215,13 +224,22 @@ export default class Post extends React.Component {
if (this.props.compactDisplay) {
compactClass = 'post--compact';
- profilePic = (
- <ProfilePicture
- src=''
- status={status}
- user={this.props.user}
- />
- );
+ if (post.props && post.props.from_webhook) {
+ profilePic = (
+ <ProfilePicture
+ src=''
+ status={status}
+ isBusy={this.props.isBusy}
+ user={this.props.user}
+ />
+ );
+ } else {
+ profilePic = (
+ <ProfilePicture
+ src=''
+ />
+ );
+ }
}
const profilePicContainer = (<div className='post__img'>{profilePic}</div>);
diff --git a/webapp/components/post_view/components/post_header.jsx b/webapp/components/post_view/components/post_header.jsx
index ff691c12b..4043460a3 100644
--- a/webapp/components/post_view/components/post_header.jsx
+++ b/webapp/components/post_view/components/post_header.jsx
@@ -40,6 +40,14 @@ export default class PostHeader extends React.Component {
disablePopover={true}
/>
);
+ } else {
+ userProfile = (
+ <UserProfile
+ user={this.props.user}
+ displayNameType={this.props.displayNameType}
+ disablePopover={true}
+ />
+ );
}
botIndicator = <li className='bot-indicator'>{Constants.BOT_NAME}</li>;
diff --git a/webapp/components/profile_picture.jsx b/webapp/components/profile_picture.jsx
index 8e14fa5fa..17a4ddc65 100644
--- a/webapp/components/profile_picture.jsx
+++ b/webapp/components/profile_picture.jsx
@@ -1,12 +1,17 @@
// Copyright (c) 2016 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 UserStore from 'stores/user_store.jsx';
+
import React from 'react';
-import {Popover, OverlayTrigger} from 'react-bootstrap';
+import {OverlayTrigger} from 'react-bootstrap';
export default class ProfilePicture extends React.Component {
shouldComponentUpdate(nextProps) {
+ if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) {
+ return true;
+ }
+
if (nextProps.src !== this.props.src) {
return true;
}
@@ -23,71 +28,31 @@ export default class ProfilePicture extends React.Component {
return true;
}
+ if (nextProps.isBusy !== this.props.isBusy) {
+ return true;
+ }
+
return false;
}
render() {
- let email = '';
let statusClass = '';
if (this.props.status) {
statusClass = 'status-' + this.props.status;
}
if (this.props.user) {
- email = this.props.user.email;
- var dataContent = [];
- dataContent.push(
- <img
- className='user-popover__image'
- src={this.props.src}
- height='128'
- width='128'
- key='user-popover-image'
- />
- );
- const fullname = Utils.getFullName(this.props.user);
- if (fullname) {
- dataContent.push(
- <div
- data-toggle='tooltip'
- title={fullname}
- key='user-popover-fullname'
- >
- <p
- className='text-nowrap'
- >
- {fullname}
- </p>
- </div>
- );
- }
- if (global.window.mm_config.ShowEmailAddress === 'true' || UserStore.isSystemAdminForCurrentUser() || this.props.user.id === UserStore.getCurrentId()) {
- dataContent.push(
- <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 (
<OverlayTrigger
trigger='click'
placement='right'
rootClose={true}
overlay={
- <Popover
- title={'@' + this.props.user.username}
- id='user-profile-popover'
- >
- {dataContent}
- </Popover>
+ <ProfilePopover
+ user={this.props.user}
+ src={this.props.src}
+ status={this.props.status}
+ isBusy={this.props.isBusy}
+ />
}
>
<span className={`status-wrapper ${statusClass}`}>
@@ -123,5 +88,6 @@ ProfilePicture.propTypes = {
status: React.PropTypes.string,
width: React.PropTypes.string,
height: React.PropTypes.string,
- user: React.PropTypes.object
+ user: React.PropTypes.object,
+ isBusy: React.PropTypes.bool
};
diff --git a/webapp/components/profile_popover.jsx b/webapp/components/profile_popover.jsx
new file mode 100644
index 000000000..a342f312f
--- /dev/null
+++ b/webapp/components/profile_popover.jsx
@@ -0,0 +1,224 @@
+// Copyright (c) 2016 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 * as GlobalActions from 'actions/global_actions.jsx';
+import * as WebrtcActions from 'actions/webrtc_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 React from 'react';
+
+export default class ProfilePopover extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.initWebrtc = this.initWebrtc.bind(this);
+ this.state = {
+ currentUserId: UserStore.getCurrentId()
+ };
+ }
+ 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;
+ }
+
+ initWebrtc() {
+ if (this.props.status !== UserStatuses.OFFLINE && !WebrtcStore.isBusy()) {
+ GlobalActions.emitCloseRightHandSide();
+ WebrtcActions.initWebrtc(this.props.user.id, true);
+ }
+ }
+
+ render() {
+ const popoverProps = Object.assign({}, this.props);
+ delete popoverProps.user;
+ delete popoverProps.src;
+ delete popoverProps.status;
+ delete popoverProps.isBusy;
+
+ let webrtc;
+ 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;
+ let circleClass = 'offline';
+ if (isOnline && !this.props.isBusy) {
+ circleClass = '';
+ 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'
+ />
+ );
+ }
+
+ const webrtcTooltip = (
+ <Tooltip id='webrtcTooltip'>{webrtcMessage}</Tooltip>
+ );
+
+ webrtc = (
+ <div
+ className='webrtc__user-profile'
+ key='makeCall'
+ >
+ <a
+ href='#'
+ onClick={() => this.initWebrtc()}
+ disabled={!isOnline}
+ >
+ <OverlayTrigger
+ delayShow={Constants.WEBRTC_TIME_DELAY}
+ placement='top'
+ overlay={webrtcTooltip}
+ >
+ <div
+ id='webrtc-btn'
+ className={'webrtc__button ' + circleClass}
+ >
+ <span dangerouslySetInnerHTML={{__html: Constants.VIDEO_ICON}}/>
+ </div>
+ </OverlayTrigger>
+ </a>
+ </div>
+ );
+ }
+
+ var dataContent = [];
+ dataContent.push(
+ <img
+ className='user-popover__image'
+ src={this.props.src}
+ height='128'
+ width='128'
+ key='user-popover-image'
+ />
+ );
+
+ const fullname = Utils.getFullName(this.props.user);
+ if (fullname) {
+ dataContent.push(
+ <div
+ data-toggle='tooltip'
+ title={fullname}
+ key='user-popover-fullname'
+ >
+ <p
+ className='text-nowrap'
+ >
+ {fullname}
+ </p>
+ </div>
+ );
+ }
+
+ if (this.props.user.position) {
+ const position = this.props.user.position.substring(0, Constants.MAX_POSITION_LENGTH);
+ dataContent.push(
+ <div
+ data-toggle='tooltip'
+ title={position}
+ key='user-popover-position'
+ >
+ <p
+ className='text-nowrap'
+ >
+ {position}
+ </p>
+ </div>
+ );
+ }
+
+ dataContent.push(webrtc);
+
+ const email = this.props.user.email;
+ if (global.window.mm_config.ShowEmailAddress === 'true' || UserStore.isSystemAdminForCurrentUser() || this.props.user === UserStore.getCurrentUser()) {
+ dataContent.push(
+ <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 (
+ <Popover
+ {...popoverProps}
+ title={'@' + this.props.user.username}
+ id='user-profile-popover'
+ >
+ {dataContent}
+ </Popover>
+ );
+ }
+}
+
+ProfilePopover.propTypes = Object.assign({
+ src: React.PropTypes.string.isRequired,
+ user: React.PropTypes.object.isRequired,
+ status: React.PropTypes.string.isRequired,
+ isBusy: React.PropTypes.bool.isRequired
+}, Popover.propTypes);
+delete ProfilePopover.propTypes.id;
diff --git a/webapp/components/rhs_comment.jsx b/webapp/components/rhs_comment.jsx
index c699fb353..8b7642fd8 100644
--- a/webapp/components/rhs_comment.jsx
+++ b/webapp/components/rhs_comment.jsx
@@ -66,6 +66,10 @@ export default class RhsComment extends React.Component {
return true;
}
+ if (nextProps.isBusy !== this.props.isBusy) {
+ return true;
+ }
+
if (nextProps.compactDisplay !== this.props.compactDisplay) {
return true;
}
@@ -237,10 +241,20 @@ export default class RhsComment extends React.Component {
var timestamp = this.props.currentUser.update_at;
+ let status = this.props.status;
+ if (post.props && post.props.from_webhook === 'true') {
+ status = null;
+ }
+
let botIndicator;
let userProfile = (
- <UserProfile user={this.props.user}/>
+ <UserProfile
+ user={this.props.user}
+ status={status}
+ isBusy={this.props.isBusy}
+ />
);
+
if (post.props && post.props.from_webhook) {
if (post.props.override_username && global.window.mm_config.EnablePostUsernameOverride === 'true') {
userProfile = (
@@ -250,9 +264,17 @@ export default class RhsComment extends React.Component {
disablePopover={true}
/>
);
+ } else {
+ userProfile = (
+ <UserProfile
+ user={this.props.user}
+ disablePopover={true}
+ />
+ );
}
- botIndicator = <li className='bot-indicator'>{Constants.BOT_NAME}</li>;
- } else if (isSystemMessage) {
+
+ botIndicator = <li className='col col__name bot-indicator'>{'BOT'}</li>;
+ } else if (PostUtils.isSystemMessage(post)) {
userProfile = (
<UserProfile
user={{}}
@@ -292,11 +314,6 @@ export default class RhsComment extends React.Component {
systemMessageClass = 'post--system';
}
- let status = this.props.status;
- if (post.props && post.props.from_webhook === 'true') {
- status = null;
- }
-
let profilePic = (
<ProfilePicture
src={PostUtils.getProfilePicSrcForPost(post, timestamp)}
@@ -304,10 +321,21 @@ export default class RhsComment extends React.Component {
width='36'
height='36'
user={this.props.user}
+ isBusy={this.props.isBusy}
/>
);
- if (isSystemMessage) {
+ if (post.props && post.props.from_webhook) {
+ profilePic = (
+ <ProfilePicture
+ src={PostUtils.getProfilePicSrcForPost(post, timestamp)}
+ width='36'
+ height='36'
+ />
+ );
+ }
+
+ if (PostUtils.isSystemMessage(post)) {
profilePic = (
<span
className='icon'
@@ -320,13 +348,22 @@ export default class RhsComment extends React.Component {
if (this.props.compactDisplay) {
compactClass = 'post--compact';
- profilePic = (
- <ProfilePicture
- src=''
- status={status}
- user={this.props.user}
- />
- );
+ if (post.props && post.props.from_webhook) {
+ profilePic = (
+ <ProfilePicture
+ src=''
+ />
+ );
+ } else {
+ profilePic = (
+ <ProfilePicture
+ src=''
+ status={status}
+ user={this.props.user}
+ isBusy={this.props.isBusy}
+ />
+ );
+ }
}
const profilePicContainer = (<div className='post__img'>{profilePic}</div>);
@@ -466,5 +503,6 @@ RhsComment.propTypes = {
compactDisplay: React.PropTypes.bool,
useMilitaryTime: React.PropTypes.bool.isRequired,
isFlagged: React.PropTypes.bool,
- status: React.PropTypes.string
+ status: React.PropTypes.string,
+ isBusy: React.PropTypes.bool
};
diff --git a/webapp/components/rhs_root_post.jsx b/webapp/components/rhs_root_post.jsx
index 4a15127a0..95f5fc1ac 100644
--- a/webapp/components/rhs_root_post.jsx
+++ b/webapp/components/rhs_root_post.jsx
@@ -47,6 +47,10 @@ export default class RhsRootPost extends React.Component {
return true;
}
+ if (nextProps.isBusy !== this.props.isBusy) {
+ return true;
+ }
+
if (nextProps.compactDisplay !== this.props.compactDisplay) {
return true;
}
@@ -248,7 +252,13 @@ export default class RhsRootPost extends React.Component {
);
}
- let userProfile = <UserProfile user={user}/>;
+ let userProfile = (
+ <UserProfile
+ user={user}
+ status={this.props.status}
+ isBusy={this.props.isBusy}
+ />
+ );
let botIndicator;
if (post.props && post.props.from_webhook) {
@@ -260,6 +270,13 @@ export default class RhsRootPost extends React.Component {
disablePopover={true}
/>
);
+ } else {
+ userProfile = (
+ <UserProfile
+ user={user}
+ disablePopover={true}
+ />
+ );
}
botIndicator = <li className='col col__name bot-indicator'>{'BOT'}</li>;
@@ -286,9 +303,20 @@ export default class RhsRootPost extends React.Component {
width='36'
height='36'
user={this.props.user}
+ isBusy={this.props.isBusy}
/>
);
+ if (post.props && post.props.from_webhook) {
+ profilePic = (
+ <ProfilePicture
+ src={PostUtils.getProfilePicSrcForPost(post, timestamp)}
+ width='36'
+ height='36'
+ />
+ );
+ }
+
if (PostUtils.isSystemMessage(post)) {
profilePic = (
<span
@@ -302,13 +330,22 @@ export default class RhsRootPost extends React.Component {
if (this.props.compactDisplay) {
compactClass = 'post--compact';
- profilePic = (
- <ProfilePicture
- src=''
- status={status}
- user={this.props.user}
- />
- );
+ if (post.props && post.props.from_webhook) {
+ profilePic = (
+ <ProfilePicture
+ src=''
+ />
+ );
+ } else {
+ profilePic = (
+ <ProfilePicture
+ src=''
+ status={status}
+ user={this.props.user}
+ isBusy={this.props.isBusy}
+ />
+ );
+ }
}
const profilePicContainer = (<div className='post__img'>{profilePic}</div>);
@@ -424,5 +461,6 @@ RhsRootPost.propTypes = {
useMilitaryTime: React.PropTypes.bool.isRequired,
isFlagged: React.PropTypes.bool,
status: React.PropTypes.string,
- previewCollapsed: React.PropTypes.string
+ previewCollapsed: React.PropTypes.string,
+ isBusy: React.PropTypes.bool
};
diff --git a/webapp/components/rhs_thread.jsx b/webapp/components/rhs_thread.jsx
index 8fd49dd25..5040f83fc 100644
--- a/webapp/components/rhs_thread.jsx
+++ b/webapp/components/rhs_thread.jsx
@@ -11,6 +11,7 @@ import FileUploadOverlay from './file_upload_overlay.jsx';
import PostStore from 'stores/post_store.jsx';
import UserStore from 'stores/user_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
+import WebrtcStore from 'stores/webrtc_store.jsx';
import * as Utils from 'utils/utils.jsx';
@@ -56,6 +57,7 @@ export default class RhsThread extends React.Component {
this.forceUpdateInfo = this.forceUpdateInfo.bind(this);
this.onPreferenceChange = this.onPreferenceChange.bind(this);
this.onStatusChange = this.onStatusChange.bind(this);
+ this.onBusy = this.onBusy.bind(this);
this.handleResize = this.handleResize.bind(this);
const state = this.getPosts();
@@ -66,6 +68,7 @@ export default class RhsThread extends React.Component {
state.flaggedPosts = PreferenceStore.getCategory(Constants.Preferences.CATEGORY_FLAGGED_POST);
state.statuses = Object.assign({}, UserStore.getStatuses());
state.previewsCollapsed = PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.COLLAPSE_DISPLAY, 'false');
+ state.isBusy = WebrtcStore.isBusy();
this.state = state;
}
@@ -76,6 +79,7 @@ export default class RhsThread extends React.Component {
PreferenceStore.addChangeListener(this.onPreferenceChange);
UserStore.addChangeListener(this.onUserChange);
UserStore.addStatusesChangeListener(this.onStatusChange);
+ WebrtcStore.addBusyListener(this.onBusy);
this.scrollToBottom();
window.addEventListener('resize', this.handleResize);
@@ -89,6 +93,7 @@ export default class RhsThread extends React.Component {
PreferenceStore.removeChangeListener(this.onPreferenceChange);
UserStore.removeChangeListener(this.onUserChange);
UserStore.removeStatusesChangeListener(this.onStatusChange);
+ WebrtcStore.removeBusyListener(this.onBusy);
window.removeEventListener('resize', this.handleResize);
@@ -147,6 +152,10 @@ export default class RhsThread extends React.Component {
return true;
}
+ if (nextState.isBusy !== this.state.isBusy) {
+ return true;
+ }
+
return false;
}
@@ -191,6 +200,10 @@ export default class RhsThread extends React.Component {
this.setState({statuses: Object.assign({}, UserStore.getStatuses())});
}
+ onBusy(isBusy) {
+ this.setState({isBusy});
+ }
+
getPosts() {
const selected = PostStore.getSelectedPost();
const posts = PostStore.getSelectedPostThread();
@@ -312,6 +325,7 @@ export default class RhsThread extends React.Component {
isFlagged={isRootFlagged}
status={rootStatus}
previewCollapsed={this.state.previewsCollapsed}
+ isBusy={this.state.isBusy}
/>
<div className='post-right-comments-container'>
{postsArray.map((comPost) => {
@@ -345,6 +359,7 @@ export default class RhsThread extends React.Component {
useMilitaryTime={this.props.useMilitaryTime}
isFlagged={isFlagged}
status={status}
+ isBusy={this.state.isBusy}
/>
);
})}
diff --git a/webapp/components/search_results.jsx b/webapp/components/search_results.jsx
index 6396150e7..466665c2c 100644
--- a/webapp/components/search_results.jsx
+++ b/webapp/components/search_results.jsx
@@ -9,6 +9,7 @@ import ChannelStore from 'stores/channel_store.jsx';
import SearchStore from 'stores/search_store.jsx';
import UserStore from 'stores/user_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
+import WebrtcStore from 'stores/webrtc_store.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
@@ -51,8 +52,10 @@ export default class SearchResults extends React.Component {
this.onChange = this.onChange.bind(this);
this.onUserChange = this.onUserChange.bind(this);
this.onPreferenceChange = this.onPreferenceChange.bind(this);
+ this.onBusy = this.onBusy.bind(this);
this.resize = this.resize.bind(this);
this.onPreferenceChange = this.onPreferenceChange.bind(this);
+ this.onStatusChange = this.onStatusChange.bind(this);
this.handleResize = this.handleResize.bind(this);
const state = getStateFromStores();
@@ -60,6 +63,8 @@ export default class SearchResults extends React.Component {
state.windowHeight = Utils.windowHeight();
state.profiles = JSON.parse(JSON.stringify(UserStore.getProfiles()));
state.compactDisplay = PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT) === Preferences.MESSAGE_DISPLAY_COMPACT;
+ state.isBusy = WebrtcStore.isBusy();
+ state.statuses = Object.assign({}, UserStore.getStatuses());
this.state = state;
}
@@ -70,7 +75,9 @@ export default class SearchResults extends React.Component {
ChannelStore.addChangeListener(this.onChange);
PreferenceStore.addChangeListener(this.onPreferenceChange);
UserStore.addChangeListener(this.onUserChange);
+ UserStore.addStatusesChangeListener(this.onStatusChange);
PreferenceStore.addChangeListener(this.onPreferenceChange);
+ WebrtcStore.addBusyListener(this.onBusy);
this.resize();
window.addEventListener('resize', this.handleResize);
@@ -80,6 +87,10 @@ export default class SearchResults extends React.Component {
}
shouldComponentUpdate(nextProps, nextState) {
+ if (!Utils.areObjectsEqual(nextState.statuses, this.state.statuses)) {
+ return true;
+ }
+
if (!Utils.areObjectsEqual(this.props, nextProps)) {
return true;
}
@@ -92,6 +103,10 @@ export default class SearchResults extends React.Component {
return true;
}
+ if (nextState.isBusy !== this.state.isBusy) {
+ return true;
+ }
+
return false;
}
@@ -106,7 +121,9 @@ export default class SearchResults extends React.Component {
ChannelStore.removeChangeListener(this.onChange);
PreferenceStore.removeChangeListener(this.onPreferenceChange);
UserStore.removeChangeListener(this.onUserChange);
+ UserStore.removeStatusesChangeListener(this.onStatusChange);
PreferenceStore.removeChangeListener(this.onPreferenceChange);
+ WebrtcStore.removeBusyListener(this.onBusy);
window.removeEventListener('resize', this.handleResize);
}
@@ -135,6 +152,14 @@ export default class SearchResults extends React.Component {
this.setState({profiles: JSON.parse(JSON.stringify(UserStore.getProfiles()))});
}
+ onBusy(isBusy) {
+ this.setState({isBusy});
+ }
+
+ onStatusChange() {
+ this.setState({statuses: Object.assign({}, UserStore.getStatuses())});
+ }
+
resize() {
$('#search-items-container').scrollTop(0);
}
@@ -224,6 +249,11 @@ export default class SearchResults extends React.Component {
profile = profiles[post.user_id];
}
+ let status = 'offline';
+ if (this.state.statuses) {
+ status = this.state.statuses[post.user_id] || 'offline';
+ }
+
let isFlagged = false;
if (this.state.flaggedPosts) {
isFlagged = this.state.flaggedPosts.get(post.id) === 'true';
@@ -241,6 +271,8 @@ export default class SearchResults extends React.Component {
useMilitaryTime={this.props.useMilitaryTime}
shrink={this.props.shrink}
isFlagged={isFlagged}
+ isBusy={this.state.isBusy}
+ status={status}
/>
);
}, this);
diff --git a/webapp/components/search_results_item.jsx b/webapp/components/search_results_item.jsx
index ba172b4d3..76681959e 100644
--- a/webapp/components/search_results_item.jsx
+++ b/webapp/components/search_results_item.jsx
@@ -101,6 +101,8 @@ export default class SearchResultsItem extends React.Component {
<ProfilePicture
src={PostUtils.getProfilePicSrcForPost(post, timestamp)}
user={this.props.user}
+ status={this.props.status}
+ isBusy={this.props.isBusy}
/>
);
@@ -265,6 +267,8 @@ export default class SearchResultsItem extends React.Component {
user={user}
overwriteName={overrideUsername}
disablePopover={disableProfilePopover}
+ status={this.props.status}
+ isBusy={this.props.isBusy}
/>
</strong></li>
{botIndicator}
@@ -302,5 +306,7 @@ SearchResultsItem.propTypes = {
term: React.PropTypes.string,
useMilitaryTime: React.PropTypes.bool.isRequired,
shrink: React.PropTypes.func,
- isFlagged: React.PropTypes.bool
+ isFlagged: React.PropTypes.bool,
+ isBusy: React.PropTypes.bool,
+ status: React.PropTypes.string
};
diff --git a/webapp/components/user_profile.jsx b/webapp/components/user_profile.jsx
index 051b8d263..807c5f023 100644
--- a/webapp/components/user_profile.jsx
+++ b/webapp/components/user_profile.jsx
@@ -1,38 +1,15 @@
// Copyright (c) 2015 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 UserStore from 'stores/user_store.jsx';
-import WebrtcStore from 'stores/webrtc_store.jsx';
-import * as GlobalActions from 'actions/global_actions.jsx';
-import * as WebrtcActions from 'actions/webrtc_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 {OverlayTrigger} from 'react-bootstrap';
import React from 'react';
export default class UserProfile extends React.Component {
- constructor(props) {
- super(props);
-
- this.initWebrtc = this.initWebrtc.bind(this);
- this.state = {
- currentUserId: UserStore.getCurrentId()
- };
- }
-
- initWebrtc() {
- if (this.props.status !== UserStatuses.OFFLINE && !WebrtcStore.isBusy()) {
- GlobalActions.emitCloseRightHandSide();
- WebrtcActions.initWebrtc(this.props.user.id, true);
- }
- }
-
shouldComponentUpdate(nextProps) {
if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) {
return true;
@@ -67,11 +44,9 @@ export default class UserProfile extends React.Component {
render() {
let name = '...';
- let email = '';
let profileImg = '';
if (this.props.user) {
name = Utils.displayUsername(this.props.user.id);
- email = this.props.user.email;
profileImg = Client.getUsersRoute() + '/' + this.props.user.id + '/image?time=' + this.props.user.update_at;
}
@@ -79,155 +54,22 @@ export default class UserProfile extends React.Component {
name = this.props.overwriteName;
}
- if (this.props.overwriteImage) {
- profileImg = this.props.overwriteImage;
- }
-
if (this.props.disablePopover) {
return <div className='user-popover'>{name}</div>;
}
- let webrtc;
- 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;
- let circleClass = 'offline';
- if (isOnline && !this.props.isBusy) {
- circleClass = '';
- 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'
- />
- );
- }
-
- const webrtcTooltip = (
- <Tooltip id='webrtcTooltip'>{webrtcMessage}</Tooltip>
- );
-
- webrtc = (
- <div
- className='webrtc__user-profile'
- key='makeCall'
- >
- <a
- href='#'
- onClick={() => this.initWebrtc()}
- disabled={!isOnline}
- >
- <OverlayTrigger
- delayShow={Constants.WEBRTC_TIME_DELAY}
- placement='top'
- overlay={webrtcTooltip}
- >
- <div
- id='webrtc-btn'
- className={'webrtc__button ' + circleClass}
- >
- <span dangerouslySetInnerHTML={{__html: Constants.VIDEO_ICON}}/>
- </div>
- </OverlayTrigger>
- </a>
- </div>
- );
- }
-
- var dataContent = [];
- dataContent.push(
- <img
- className='user-popover__image'
- src={profileImg}
- height='128'
- width='128'
- key='user-popover-image'
- />
- );
-
- const fullname = Utils.getFullName(this.props.user);
- if (fullname) {
- dataContent.push(
- <div
- data-toggle='tooltip'
- title={fullname}
- key='user-popover-fullname'
- >
-
- <p
- className='text-nowrap'
- >
- {fullname}
- </p>
- </div>
- );
- }
-
- dataContent.push(webrtc);
-
- if (this.props.user.position) {
- const position = this.props.user.position.substring(0, Constants.MAX_POSITION_LENGTH);
- dataContent.push(
- <div
- data-toggle='tooltip'
- title={position}
- key='user-popover-position'
- >
- <p
- className='text-nowrap'
- >
- {position}
- </p>
- </div>
- );
- }
-
- if (global.window.mm_config.ShowEmailAddress === 'true' || UserStore.isSystemAdminForCurrentUser() || this.props.user === UserStore.getCurrentUser()) {
- dataContent.push(
- <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 (
<OverlayTrigger
trigger='click'
placement='right'
rootClose={true}
overlay={
- <Popover
- title={'@' + this.props.user.username}
- id='user-profile-popover'
- >
- {dataContent}
- </Popover>
+ <ProfilePopover
+ user={this.props.user}
+ src={profileImg}
+ status={this.props.status}
+ isBusy={this.props.isBusy}
+ />
}
>
<div