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.
Этот коммит содержится в:
George Goldberg
2016-12-22 17:19:19 +00:00
коммит произвёл enahum
родитель 52c4538817
Коммит a857cf18f4
10 изменённых файлов: 440 добавлений и 253 удалений

Просмотреть файл

@@ -194,9 +194,18 @@ export default class Post extends React.Component {
src={PostUtils.getProfilePicSrcForPost(post, timestamp)} src={PostUtils.getProfilePicSrcForPost(post, timestamp)}
status={status} status={status}
user={this.props.user} 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)) { if (PostUtils.isSystemMessage(post)) {
profilePic = ( profilePic = (
<span <span
@@ -215,13 +224,22 @@ export default class Post extends React.Component {
if (this.props.compactDisplay) { if (this.props.compactDisplay) {
compactClass = 'post--compact'; compactClass = 'post--compact';
profilePic = ( if (post.props && post.props.from_webhook) {
<ProfilePicture profilePic = (
src='' <ProfilePicture
status={status} src=''
user={this.props.user} status={status}
/> isBusy={this.props.isBusy}
); user={this.props.user}
/>
);
} else {
profilePic = (
<ProfilePicture
src=''
/>
);
}
} }
const profilePicContainer = (<div className='post__img'>{profilePic}</div>); const profilePicContainer = (<div className='post__img'>{profilePic}</div>);

Просмотреть файл

@@ -40,6 +40,14 @@ export default class PostHeader extends React.Component {
disablePopover={true} disablePopover={true}
/> />
); );
} else {
userProfile = (
<UserProfile
user={this.props.user}
displayNameType={this.props.displayNameType}
disablePopover={true}
/>
);
} }
botIndicator = <li className='bot-indicator'>{Constants.BOT_NAME}</li>; botIndicator = <li className='bot-indicator'>{Constants.BOT_NAME}</li>;

Просмотреть файл

@@ -1,12 +1,17 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
import ProfilePopover from './profile_popover.jsx';
import * as Utils from 'utils/utils.jsx'; import * as Utils from 'utils/utils.jsx';
import UserStore from 'stores/user_store.jsx';
import React from 'react'; import React from 'react';
import {Popover, OverlayTrigger} from 'react-bootstrap'; import {OverlayTrigger} from 'react-bootstrap';
export default class ProfilePicture extends React.Component { export default class ProfilePicture extends React.Component {
shouldComponentUpdate(nextProps) { shouldComponentUpdate(nextProps) {
if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) {
return true;
}
if (nextProps.src !== this.props.src) { if (nextProps.src !== this.props.src) {
return true; return true;
} }
@@ -23,71 +28,31 @@ export default class ProfilePicture extends React.Component {
return true; return true;
} }
if (nextProps.isBusy !== this.props.isBusy) {
return true;
}
return false; return false;
} }
render() { render() {
let email = '';
let statusClass = ''; let statusClass = '';
if (this.props.status) { if (this.props.status) {
statusClass = 'status-' + this.props.status; statusClass = 'status-' + this.props.status;
} }
if (this.props.user) { 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 ( return (
<OverlayTrigger <OverlayTrigger
trigger='click' trigger='click'
placement='right' placement='right'
rootClose={true} rootClose={true}
overlay={ overlay={
<Popover <ProfilePopover
title={'@' + this.props.user.username} user={this.props.user}
id='user-profile-popover' src={this.props.src}
> status={this.props.status}
{dataContent} isBusy={this.props.isBusy}
</Popover> />
} }
> >
<span className={`status-wrapper ${statusClass}`}> <span className={`status-wrapper ${statusClass}`}>
@@ -123,5 +88,6 @@ ProfilePicture.propTypes = {
status: React.PropTypes.string, status: React.PropTypes.string,
width: React.PropTypes.string, width: React.PropTypes.string,
height: React.PropTypes.string, height: React.PropTypes.string,
user: React.PropTypes.object user: React.PropTypes.object,
isBusy: React.PropTypes.bool
}; };

224
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;

Просмотреть файл

@@ -66,6 +66,10 @@ export default class RhsComment extends React.Component {
return true; return true;
} }
if (nextProps.isBusy !== this.props.isBusy) {
return true;
}
if (nextProps.compactDisplay !== this.props.compactDisplay) { if (nextProps.compactDisplay !== this.props.compactDisplay) {
return true; return true;
} }
@@ -237,10 +241,20 @@ export default class RhsComment extends React.Component {
var timestamp = this.props.currentUser.update_at; 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 botIndicator;
let userProfile = ( 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 && post.props.from_webhook) {
if (post.props.override_username && global.window.mm_config.EnablePostUsernameOverride === 'true') { if (post.props.override_username && global.window.mm_config.EnablePostUsernameOverride === 'true') {
userProfile = ( userProfile = (
@@ -250,9 +264,17 @@ export default class RhsComment extends React.Component {
disablePopover={true} 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 = (
<UserProfile <UserProfile
user={{}} user={{}}
@@ -292,11 +314,6 @@ export default class RhsComment extends React.Component {
systemMessageClass = 'post--system'; systemMessageClass = 'post--system';
} }
let status = this.props.status;
if (post.props && post.props.from_webhook === 'true') {
status = null;
}
let profilePic = ( let profilePic = (
<ProfilePicture <ProfilePicture
src={PostUtils.getProfilePicSrcForPost(post, timestamp)} src={PostUtils.getProfilePicSrcForPost(post, timestamp)}
@@ -304,10 +321,21 @@ export default class RhsComment extends React.Component {
width='36' width='36'
height='36' height='36'
user={this.props.user} 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 = ( profilePic = (
<span <span
className='icon' className='icon'
@@ -320,13 +348,22 @@ export default class RhsComment extends React.Component {
if (this.props.compactDisplay) { if (this.props.compactDisplay) {
compactClass = 'post--compact'; compactClass = 'post--compact';
profilePic = ( if (post.props && post.props.from_webhook) {
<ProfilePicture profilePic = (
src='' <ProfilePicture
status={status} src=''
user={this.props.user} />
/> );
); } else {
profilePic = (
<ProfilePicture
src=''
status={status}
user={this.props.user}
isBusy={this.props.isBusy}
/>
);
}
} }
const profilePicContainer = (<div className='post__img'>{profilePic}</div>); const profilePicContainer = (<div className='post__img'>{profilePic}</div>);
@@ -466,5 +503,6 @@ RhsComment.propTypes = {
compactDisplay: React.PropTypes.bool, compactDisplay: React.PropTypes.bool,
useMilitaryTime: React.PropTypes.bool.isRequired, useMilitaryTime: React.PropTypes.bool.isRequired,
isFlagged: React.PropTypes.bool, isFlagged: React.PropTypes.bool,
status: React.PropTypes.string status: React.PropTypes.string,
isBusy: React.PropTypes.bool
}; };

Просмотреть файл

@@ -47,6 +47,10 @@ export default class RhsRootPost extends React.Component {
return true; return true;
} }
if (nextProps.isBusy !== this.props.isBusy) {
return true;
}
if (nextProps.compactDisplay !== this.props.compactDisplay) { if (nextProps.compactDisplay !== this.props.compactDisplay) {
return true; 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; let botIndicator;
if (post.props && post.props.from_webhook) { if (post.props && post.props.from_webhook) {
@@ -260,6 +270,13 @@ export default class RhsRootPost extends React.Component {
disablePopover={true} disablePopover={true}
/> />
); );
} else {
userProfile = (
<UserProfile
user={user}
disablePopover={true}
/>
);
} }
botIndicator = <li className='col col__name bot-indicator'>{'BOT'}</li>; botIndicator = <li className='col col__name bot-indicator'>{'BOT'}</li>;
@@ -286,9 +303,20 @@ export default class RhsRootPost extends React.Component {
width='36' width='36'
height='36' height='36'
user={this.props.user} 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)) { if (PostUtils.isSystemMessage(post)) {
profilePic = ( profilePic = (
<span <span
@@ -302,13 +330,22 @@ export default class RhsRootPost extends React.Component {
if (this.props.compactDisplay) { if (this.props.compactDisplay) {
compactClass = 'post--compact'; compactClass = 'post--compact';
profilePic = ( if (post.props && post.props.from_webhook) {
<ProfilePicture profilePic = (
src='' <ProfilePicture
status={status} src=''
user={this.props.user} />
/> );
); } else {
profilePic = (
<ProfilePicture
src=''
status={status}
user={this.props.user}
isBusy={this.props.isBusy}
/>
);
}
} }
const profilePicContainer = (<div className='post__img'>{profilePic}</div>); const profilePicContainer = (<div className='post__img'>{profilePic}</div>);
@@ -424,5 +461,6 @@ RhsRootPost.propTypes = {
useMilitaryTime: React.PropTypes.bool.isRequired, useMilitaryTime: React.PropTypes.bool.isRequired,
isFlagged: React.PropTypes.bool, isFlagged: React.PropTypes.bool,
status: React.PropTypes.string, status: React.PropTypes.string,
previewCollapsed: React.PropTypes.string previewCollapsed: React.PropTypes.string,
isBusy: React.PropTypes.bool
}; };

Просмотреть файл

@@ -11,6 +11,7 @@ import FileUploadOverlay from './file_upload_overlay.jsx';
import PostStore from 'stores/post_store.jsx'; import PostStore from 'stores/post_store.jsx';
import UserStore from 'stores/user_store.jsx'; import UserStore from 'stores/user_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx'; import PreferenceStore from 'stores/preference_store.jsx';
import WebrtcStore from 'stores/webrtc_store.jsx';
import * as Utils from 'utils/utils.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.forceUpdateInfo = this.forceUpdateInfo.bind(this);
this.onPreferenceChange = this.onPreferenceChange.bind(this); this.onPreferenceChange = this.onPreferenceChange.bind(this);
this.onStatusChange = this.onStatusChange.bind(this); this.onStatusChange = this.onStatusChange.bind(this);
this.onBusy = this.onBusy.bind(this);
this.handleResize = this.handleResize.bind(this); this.handleResize = this.handleResize.bind(this);
const state = this.getPosts(); 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.flaggedPosts = PreferenceStore.getCategory(Constants.Preferences.CATEGORY_FLAGGED_POST);
state.statuses = Object.assign({}, UserStore.getStatuses()); state.statuses = Object.assign({}, UserStore.getStatuses());
state.previewsCollapsed = PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.COLLAPSE_DISPLAY, 'false'); state.previewsCollapsed = PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.COLLAPSE_DISPLAY, 'false');
state.isBusy = WebrtcStore.isBusy();
this.state = state; this.state = state;
} }
@@ -76,6 +79,7 @@ export default class RhsThread extends React.Component {
PreferenceStore.addChangeListener(this.onPreferenceChange); PreferenceStore.addChangeListener(this.onPreferenceChange);
UserStore.addChangeListener(this.onUserChange); UserStore.addChangeListener(this.onUserChange);
UserStore.addStatusesChangeListener(this.onStatusChange); UserStore.addStatusesChangeListener(this.onStatusChange);
WebrtcStore.addBusyListener(this.onBusy);
this.scrollToBottom(); this.scrollToBottom();
window.addEventListener('resize', this.handleResize); window.addEventListener('resize', this.handleResize);
@@ -89,6 +93,7 @@ export default class RhsThread extends React.Component {
PreferenceStore.removeChangeListener(this.onPreferenceChange); PreferenceStore.removeChangeListener(this.onPreferenceChange);
UserStore.removeChangeListener(this.onUserChange); UserStore.removeChangeListener(this.onUserChange);
UserStore.removeStatusesChangeListener(this.onStatusChange); UserStore.removeStatusesChangeListener(this.onStatusChange);
WebrtcStore.removeBusyListener(this.onBusy);
window.removeEventListener('resize', this.handleResize); window.removeEventListener('resize', this.handleResize);
@@ -147,6 +152,10 @@ export default class RhsThread extends React.Component {
return true; return true;
} }
if (nextState.isBusy !== this.state.isBusy) {
return true;
}
return false; return false;
} }
@@ -191,6 +200,10 @@ export default class RhsThread extends React.Component {
this.setState({statuses: Object.assign({}, UserStore.getStatuses())}); this.setState({statuses: Object.assign({}, UserStore.getStatuses())});
} }
onBusy(isBusy) {
this.setState({isBusy});
}
getPosts() { getPosts() {
const selected = PostStore.getSelectedPost(); const selected = PostStore.getSelectedPost();
const posts = PostStore.getSelectedPostThread(); const posts = PostStore.getSelectedPostThread();
@@ -312,6 +325,7 @@ export default class RhsThread extends React.Component {
isFlagged={isRootFlagged} isFlagged={isRootFlagged}
status={rootStatus} status={rootStatus}
previewCollapsed={this.state.previewsCollapsed} previewCollapsed={this.state.previewsCollapsed}
isBusy={this.state.isBusy}
/> />
<div className='post-right-comments-container'> <div className='post-right-comments-container'>
{postsArray.map((comPost) => { {postsArray.map((comPost) => {
@@ -345,6 +359,7 @@ export default class RhsThread extends React.Component {
useMilitaryTime={this.props.useMilitaryTime} useMilitaryTime={this.props.useMilitaryTime}
isFlagged={isFlagged} isFlagged={isFlagged}
status={status} status={status}
isBusy={this.state.isBusy}
/> />
); );
})} })}

Просмотреть файл

@@ -9,6 +9,7 @@ import ChannelStore from 'stores/channel_store.jsx';
import SearchStore from 'stores/search_store.jsx'; import SearchStore from 'stores/search_store.jsx';
import UserStore from 'stores/user_store.jsx'; import UserStore from 'stores/user_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx'; import PreferenceStore from 'stores/preference_store.jsx';
import WebrtcStore from 'stores/webrtc_store.jsx';
import * as Utils from 'utils/utils.jsx'; import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.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.onChange = this.onChange.bind(this);
this.onUserChange = this.onUserChange.bind(this); this.onUserChange = this.onUserChange.bind(this);
this.onPreferenceChange = this.onPreferenceChange.bind(this); this.onPreferenceChange = this.onPreferenceChange.bind(this);
this.onBusy = this.onBusy.bind(this);
this.resize = this.resize.bind(this); this.resize = this.resize.bind(this);
this.onPreferenceChange = this.onPreferenceChange.bind(this); this.onPreferenceChange = this.onPreferenceChange.bind(this);
this.onStatusChange = this.onStatusChange.bind(this);
this.handleResize = this.handleResize.bind(this); this.handleResize = this.handleResize.bind(this);
const state = getStateFromStores(); const state = getStateFromStores();
@@ -60,6 +63,8 @@ export default class SearchResults extends React.Component {
state.windowHeight = Utils.windowHeight(); state.windowHeight = Utils.windowHeight();
state.profiles = JSON.parse(JSON.stringify(UserStore.getProfiles())); 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.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; this.state = state;
} }
@@ -70,7 +75,9 @@ export default class SearchResults extends React.Component {
ChannelStore.addChangeListener(this.onChange); ChannelStore.addChangeListener(this.onChange);
PreferenceStore.addChangeListener(this.onPreferenceChange); PreferenceStore.addChangeListener(this.onPreferenceChange);
UserStore.addChangeListener(this.onUserChange); UserStore.addChangeListener(this.onUserChange);
UserStore.addStatusesChangeListener(this.onStatusChange);
PreferenceStore.addChangeListener(this.onPreferenceChange); PreferenceStore.addChangeListener(this.onPreferenceChange);
WebrtcStore.addBusyListener(this.onBusy);
this.resize(); this.resize();
window.addEventListener('resize', this.handleResize); window.addEventListener('resize', this.handleResize);
@@ -80,6 +87,10 @@ export default class SearchResults extends React.Component {
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
if (!Utils.areObjectsEqual(nextState.statuses, this.state.statuses)) {
return true;
}
if (!Utils.areObjectsEqual(this.props, nextProps)) { if (!Utils.areObjectsEqual(this.props, nextProps)) {
return true; return true;
} }
@@ -92,6 +103,10 @@ export default class SearchResults extends React.Component {
return true; return true;
} }
if (nextState.isBusy !== this.state.isBusy) {
return true;
}
return false; return false;
} }
@@ -106,7 +121,9 @@ export default class SearchResults extends React.Component {
ChannelStore.removeChangeListener(this.onChange); ChannelStore.removeChangeListener(this.onChange);
PreferenceStore.removeChangeListener(this.onPreferenceChange); PreferenceStore.removeChangeListener(this.onPreferenceChange);
UserStore.removeChangeListener(this.onUserChange); UserStore.removeChangeListener(this.onUserChange);
UserStore.removeStatusesChangeListener(this.onStatusChange);
PreferenceStore.removeChangeListener(this.onPreferenceChange); PreferenceStore.removeChangeListener(this.onPreferenceChange);
WebrtcStore.removeBusyListener(this.onBusy);
window.removeEventListener('resize', this.handleResize); 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()))}); this.setState({profiles: JSON.parse(JSON.stringify(UserStore.getProfiles()))});
} }
onBusy(isBusy) {
this.setState({isBusy});
}
onStatusChange() {
this.setState({statuses: Object.assign({}, UserStore.getStatuses())});
}
resize() { resize() {
$('#search-items-container').scrollTop(0); $('#search-items-container').scrollTop(0);
} }
@@ -224,6 +249,11 @@ export default class SearchResults extends React.Component {
profile = profiles[post.user_id]; profile = profiles[post.user_id];
} }
let status = 'offline';
if (this.state.statuses) {
status = this.state.statuses[post.user_id] || 'offline';
}
let isFlagged = false; let isFlagged = false;
if (this.state.flaggedPosts) { if (this.state.flaggedPosts) {
isFlagged = this.state.flaggedPosts.get(post.id) === 'true'; isFlagged = this.state.flaggedPosts.get(post.id) === 'true';
@@ -241,6 +271,8 @@ export default class SearchResults extends React.Component {
useMilitaryTime={this.props.useMilitaryTime} useMilitaryTime={this.props.useMilitaryTime}
shrink={this.props.shrink} shrink={this.props.shrink}
isFlagged={isFlagged} isFlagged={isFlagged}
isBusy={this.state.isBusy}
status={status}
/> />
); );
}, this); }, this);

Просмотреть файл

@@ -101,6 +101,8 @@ export default class SearchResultsItem extends React.Component {
<ProfilePicture <ProfilePicture
src={PostUtils.getProfilePicSrcForPost(post, timestamp)} src={PostUtils.getProfilePicSrcForPost(post, timestamp)}
user={this.props.user} 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} user={user}
overwriteName={overrideUsername} overwriteName={overrideUsername}
disablePopover={disableProfilePopover} disablePopover={disableProfilePopover}
status={this.props.status}
isBusy={this.props.isBusy}
/> />
</strong></li> </strong></li>
{botIndicator} {botIndicator}
@@ -302,5 +306,7 @@ SearchResultsItem.propTypes = {
term: React.PropTypes.string, term: React.PropTypes.string,
useMilitaryTime: React.PropTypes.bool.isRequired, useMilitaryTime: React.PropTypes.bool.isRequired,
shrink: React.PropTypes.func, shrink: React.PropTypes.func,
isFlagged: React.PropTypes.bool isFlagged: React.PropTypes.bool,
isBusy: React.PropTypes.bool,
status: React.PropTypes.string
}; };

Просмотреть файл

@@ -1,38 +1,15 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
import ProfilePopover from './profile_popover.jsx';
import * as Utils from 'utils/utils.jsx'; import * as Utils from 'utils/utils.jsx';
import Client from 'client/web_client.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 {OverlayTrigger} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
import React from 'react'; import React from 'react';
export default class UserProfile extends React.Component { 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) { shouldComponentUpdate(nextProps) {
if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) { if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) {
return true; return true;
@@ -67,11 +44,9 @@ export default class UserProfile extends React.Component {
render() { render() {
let name = '...'; let name = '...';
let email = '';
let profileImg = ''; let profileImg = '';
if (this.props.user) { if (this.props.user) {
name = Utils.displayUsername(this.props.user.id); 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; 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; name = this.props.overwriteName;
} }
if (this.props.overwriteImage) {
profileImg = this.props.overwriteImage;
}
if (this.props.disablePopover) { if (this.props.disablePopover) {
return <div className='user-popover'>{name}</div>; 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 ( return (
<OverlayTrigger <OverlayTrigger
trigger='click' trigger='click'
placement='right' placement='right'
rootClose={true} rootClose={true}
overlay={ overlay={
<Popover <ProfilePopover
title={'@' + this.props.user.username} user={this.props.user}
id='user-profile-popover' src={profileImg}
> status={this.props.status}
{dataContent} isBusy={this.props.isBusy}
</Popover> />
} }
> >
<div <div