diff --git a/webapp/components/post_view/components/post.jsx b/webapp/components/post_view/components/post.jsx index ce6ce7d2a7..f052ac4ae8 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 = ( + + ); + } + if (PostUtils.isSystemMessage(post)) { profilePic = ( - ); + if (post.props && post.props.from_webhook) { + profilePic = ( + + ); + } else { + profilePic = ( + + ); + } } const profilePicContainer = (
{profilePic}
); diff --git a/webapp/components/post_view/components/post_header.jsx b/webapp/components/post_view/components/post_header.jsx index ff691c12b4..4043460a39 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 = ( + + ); } botIndicator =
  • {Constants.BOT_NAME}
  • ; diff --git a/webapp/components/profile_picture.jsx b/webapp/components/profile_picture.jsx index 8e14fa5fab..17a4ddc65f 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( - - ); - const fullname = Utils.getFullName(this.props.user); - if (fullname) { - dataContent.push( -
    -

    - {fullname} -

    -
    - ); - } - if (global.window.mm_config.ShowEmailAddress === 'true' || UserStore.isSystemAdminForCurrentUser() || this.props.user.id === UserStore.getCurrentId()) { - dataContent.push( - - ); - } return ( - {dataContent} - + } > @@ -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 0000000000..a342f312f9 --- /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 = ( + + ); + } else if (this.props.isBusy) { + webrtcMessage = ( + + ); + } else { + webrtcMessage = ( + + ); + } + + const webrtcTooltip = ( + {webrtcMessage} + ); + + webrtc = ( + + ); + } + + var dataContent = []; + dataContent.push( + + ); + + const fullname = Utils.getFullName(this.props.user); + if (fullname) { + dataContent.push( +
    +

    + {fullname} +

    +
    + ); + } + + if (this.props.user.position) { + const position = this.props.user.position.substring(0, Constants.MAX_POSITION_LENGTH); + dataContent.push( +
    +

    + {position} +

    +
    + ); + } + + 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( + + ); + } + + return ( + + {dataContent} + + ); + } +} + +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 c699fb353a..8b7642fd80 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 = ( - + ); + 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 = ( + + ); } - botIndicator =
  • {Constants.BOT_NAME}
  • ; - } else if (isSystemMessage) { + + botIndicator =
  • {'BOT'}
  • ; + } else if (PostUtils.isSystemMessage(post)) { userProfile = ( ); - if (isSystemMessage) { + if (post.props && post.props.from_webhook) { + profilePic = ( + + ); + } + + if (PostUtils.isSystemMessage(post)) { profilePic = ( - ); + if (post.props && post.props.from_webhook) { + profilePic = ( + + ); + } else { + profilePic = ( + + ); + } } const profilePicContainer = (
    {profilePic}
    ); @@ -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 4a15127a0c..95f5fc1ac7 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 = ; + let userProfile = ( + + ); let botIndicator; if (post.props && post.props.from_webhook) { @@ -260,6 +270,13 @@ export default class RhsRootPost extends React.Component { disablePopover={true} /> ); + } else { + userProfile = ( + + ); } botIndicator =
  • {'BOT'}
  • ; @@ -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 = ( + + ); + } + if (PostUtils.isSystemMessage(post)) { profilePic = ( - ); + if (post.props && post.props.from_webhook) { + profilePic = ( + + ); + } else { + profilePic = ( + + ); + } } const profilePicContainer = (
    {profilePic}
    ); @@ -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 8fd49dd25f..5040f83fc6 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} />
    {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 6396150e76..466665c2ca 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 ba172b4d3d..76681959ed 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 { ); @@ -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} /> {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 051b8d2637..807c5f023d 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
    {name}
    ; } - 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 = ( - - ); - } else if (this.props.isBusy) { - webrtcMessage = ( - - ); - } else { - webrtcMessage = ( - - ); - } - - const webrtcTooltip = ( - {webrtcMessage} - ); - - webrtc = ( - - ); - } - - var dataContent = []; - dataContent.push( - - ); - - const fullname = Utils.getFullName(this.props.user); - if (fullname) { - dataContent.push( -
    - -

    - {fullname} -

    -
    - ); - } - - dataContent.push(webrtc); - - if (this.props.user.position) { - const position = this.props.user.position.substring(0, Constants.MAX_POSITION_LENGTH); - dataContent.push( -
    -

    - {position} -

    -
    - ); - } - - if (global.window.mm_config.ShowEmailAddress === 'true' || UserStore.isSystemAdminForCurrentUser() || this.props.user === UserStore.getCurrentUser()) { - dataContent.push( - - ); - } - return ( - {dataContent} - + } >