From dad764088e4696edc180443e610287a20aaaba04 Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Fri, 19 Aug 2016 10:06:16 -0400 Subject: PLT-1831 Add statuses to centre channel profile pictures (#3826) * Created profile picture componenet and added statuses to pictures in center channel * PLT-3899 - Updating UI for status indicators (#3823) * PLT-3899 - Updating UI for status indicators * Updating position of timestamps for compact layout --- webapp/components/post_view/components/post.jsx | 13 ++--- .../components/post_view/components/post_list.jsx | 9 +++- .../post_view/post_focus_view_controller.jsx | 21 +++++++++ .../components/post_view/post_view_controller.jsx | 24 ++++++++++ webapp/components/profile_picture.jsx | 55 ++++++++++++++++++++++ webapp/components/user_list_row.jsx | 29 +++++++----- webapp/sass/components/_modal.scss | 48 +++++++++++-------- webapp/sass/layout/_post.scss | 4 ++ webapp/sass/responsive/_tablet.scss | 4 +- webapp/utils/utils.jsx | 6 +-- 10 files changed, 170 insertions(+), 43 deletions(-) create mode 100644 webapp/components/profile_picture.jsx diff --git a/webapp/components/post_view/components/post.jsx b/webapp/components/post_view/components/post.jsx index b372d2548..1b94f717d 100644 --- a/webapp/components/post_view/components/post.jsx +++ b/webapp/components/post_view/components/post.jsx @@ -3,6 +3,7 @@ import PostHeader from './post_header.jsx'; import PostBody from './post_body.jsx'; +import ProfilePicture from 'components/profile_picture.jsx'; import Constants from 'utils/constants.jsx'; const ActionTypes = Constants.ActionTypes; @@ -105,11 +106,11 @@ export default class Post extends React.Component { return true; } - if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) { + if (nextProps.status !== this.props.status) { return true; } - if (nextProps.emojis !== this.props.emojis) { + if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) { return true; } @@ -192,10 +193,9 @@ export default class Post extends React.Component { } let profilePic = ( - ); @@ -288,5 +288,6 @@ Post.propTypes = { isCommentMention: React.PropTypes.bool, useMilitaryTime: React.PropTypes.bool.isRequired, emojis: React.PropTypes.object.isRequired, - isFlagged: React.PropTypes.bool + isFlagged: React.PropTypes.bool, + status: React.PropTypes.string }; diff --git a/webapp/components/post_view/components/post_list.jsx b/webapp/components/post_view/components/post_list.jsx index 95b30a9d7..a1fd7a317 100644 --- a/webapp/components/post_view/components/post_list.jsx +++ b/webapp/components/post_view/components/post_list.jsx @@ -289,6 +289,11 @@ export default class PostList extends React.Component { isFlagged = this.props.flaggedPosts.get(post.id) === 'true'; } + let status = ''; + if (this.props.statuses) { + status = this.props.statuses[profile.id] || 'offline'; + } + const postCtl = ( ); @@ -579,5 +585,6 @@ PostList.propTypes = { useMilitaryTime: React.PropTypes.bool.isRequired, isFocusPost: React.PropTypes.bool, emojis: React.PropTypes.object.isRequired, - flaggedPosts: React.PropTypes.object + flaggedPosts: React.PropTypes.object, + statuses: React.PropTypes.object }; diff --git a/webapp/components/post_view/post_focus_view_controller.jsx b/webapp/components/post_view/post_focus_view_controller.jsx index a1c474184..7903087e9 100644 --- a/webapp/components/post_view/post_focus_view_controller.jsx +++ b/webapp/components/post_view/post_focus_view_controller.jsx @@ -23,6 +23,7 @@ export default class PostFocusView extends React.Component { this.onPostsChange = this.onPostsChange.bind(this); this.onUserChange = this.onUserChange.bind(this); this.onEmojiChange = this.onEmojiChange.bind(this); + this.onStatusChange = this.onStatusChange.bind(this); this.onPreferenceChange = this.onPreferenceChange.bind(this); this.onPostListScroll = this.onPostListScroll.bind(this); @@ -36,10 +37,16 @@ export default class PostFocusView extends React.Component { const joinLeaveEnabled = PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'join_leave', true); + let statuses; + if (channel && channel.type !== Constants.DM_CHANNEL) { + statuses = Object.assign({}, UserStore.getStatuses()); + } + this.state = { postList: PostStore.filterPosts(focusedPostId, joinLeaveEnabled), currentUser: UserStore.getCurrentUser(), profiles, + statuses, scrollType: ScrollTypes.POST, currentChannel: ChannelStore.getCurrentId().slice(), scrollPostId: focusedPostId, @@ -54,6 +61,7 @@ export default class PostFocusView extends React.Component { ChannelStore.addChangeListener(this.onChannelChange); PostStore.addChangeListener(this.onPostsChange); UserStore.addChangeListener(this.onUserChange); + UserStore.addStatusesChangeListener(this.onStatusChange); EmojiStore.addChangeListener(this.onEmojiChange); PreferenceStore.addChangeListener(this.onPreferenceChange); } @@ -62,7 +70,9 @@ export default class PostFocusView extends React.Component { ChannelStore.removeChangeListener(this.onChannelChange); PostStore.removeChangeListener(this.onPostsChange); UserStore.removeChangeListener(this.onUserChange); + UserStore.removeStatusesChangeListener(this.onStatusChange); EmojiStore.removeChangeListener(this.onEmojiChange); + PreferenceStore.removeChangeListener(this.onPreferenceChange); } onChannelChange() { @@ -100,6 +110,16 @@ export default class PostFocusView extends React.Component { this.setState({currentUser: UserStore.getCurrentUser(), profiles: JSON.parse(JSON.stringify(profiles))}); } + onStatusChange() { + const channel = ChannelStore.getCurrent(); + let statuses; + if (channel && channel.type !== Constants.DM_CHANNEL) { + statuses = Object.assign({}, UserStore.getStatuses()); + } + + this.setState({statuses}); + } + onEmojiChange() { this.setState({ emojis: EmojiStore.getEmojis() @@ -151,6 +171,7 @@ export default class PostFocusView extends React.Component { isFocusPost={true} emojis={this.state.emojis} flaggedPosts={this.state.flaggedPosts} + statuses={this.state.statuses} /> ); } diff --git a/webapp/components/post_view/post_view_controller.jsx b/webapp/components/post_view/post_view_controller.jsx index 2451dfc8d..7e30818fb 100644 --- a/webapp/components/post_view/post_view_controller.jsx +++ b/webapp/components/post_view/post_view_controller.jsx @@ -26,6 +26,7 @@ export default class PostViewController extends React.Component { this.onUserChange = this.onUserChange.bind(this); this.onPostsChange = this.onPostsChange.bind(this); this.onEmojisChange = this.onEmojisChange.bind(this); + this.onStatusChange = this.onStatusChange.bind(this); this.onPostsViewJumpRequest = this.onPostsViewJumpRequest.bind(this); this.onSetNewMessageIndicator = this.onSetNewMessageIndicator.bind(this); this.onPostListScroll = this.onPostListScroll.bind(this); @@ -46,11 +47,17 @@ export default class PostViewController extends React.Component { const joinLeaveEnabled = PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'join_leave', true); + let statuses; + if (channel && channel.type !== Constants.DM_CHANNEL) { + statuses = Object.assign({}, UserStore.getStatuses()); + } + this.state = { channel, postList: PostStore.filterPosts(channel.id, joinLeaveEnabled), currentUser: UserStore.getCurrentUser(), profiles, + statuses, atTop: PostStore.getVisibilityAtTop(channel.id), lastViewed, ownNewMessage: false, @@ -122,9 +129,20 @@ export default class PostViewController extends React.Component { }); } + onStatusChange() { + const channel = this.state.channel; + let statuses; + if (channel && channel.type !== Constants.DM_CHANNEL) { + statuses = Object.assign({}, UserStore.getStatuses()); + } + + this.setState({statuses}); + } + onActivate() { PreferenceStore.addChangeListener(this.onPreferenceChange); UserStore.addChangeListener(this.onUserChange); + UserStore.addStatusesChangeListener(this.onStatusChange); PostStore.addChangeListener(this.onPostsChange); PostStore.addPostsViewJumpListener(this.onPostsViewJumpRequest); EmojiStore.addChangeListener(this.onEmojisChange); @@ -134,6 +152,7 @@ export default class PostViewController extends React.Component { onDeactivate() { PreferenceStore.removeChangeListener(this.onPreferenceChange); UserStore.removeChangeListener(this.onUserChange); + UserStore.removeStatusesChangeListener(this.onStatusChange); PostStore.removeChangeListener(this.onPostsChange); PostStore.removePostsViewJumpListener(this.onPostsViewJumpRequest); EmojiStore.removeChangeListener(this.onEmojisChange); @@ -267,6 +286,10 @@ export default class PostViewController extends React.Component { return true; } + if (!Utils.areObjectsEqual(nextState.statuses, this.state.statuses)) { + return true; + } + if (!Utils.areObjectsEqual(nextState.postList, this.state.postList)) { return true; } @@ -311,6 +334,7 @@ export default class PostViewController extends React.Component { lastViewed={this.state.lastViewed} emojis={this.state.emojis} ownNewMessage={this.state.ownNewMessage} + statuses={this.state.statuses} /> ); } diff --git a/webapp/components/profile_picture.jsx b/webapp/components/profile_picture.jsx new file mode 100644 index 000000000..5443b74d6 --- /dev/null +++ b/webapp/components/profile_picture.jsx @@ -0,0 +1,55 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; + +export default class ProfilePicture extends React.Component { + shouldComponentUpdate(nextProps) { + 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; + } + + return false; + } + + render() { + let statusClass = ''; + if (this.props.status) { + statusClass = 'status-' + this.props.status; + } + + return ( + + + + ); + } +} + +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 +}; diff --git a/webapp/components/user_list_row.jsx b/webapp/components/user_list_row.jsx index d5d123ab7..9f80d4caa 100644 --- a/webapp/components/user_list_row.jsx +++ b/webapp/components/user_list_row.jsx @@ -1,11 +1,15 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import ProfilePicture from 'components/profile_picture.jsx'; + import UserStore from 'stores/user_store.jsx'; -import Constants from 'utils/constants.jsx'; import PreferenceStore from 'stores/preference_store.jsx'; + +import Constants from 'utils/constants.jsx'; import * as Utils from 'utils/utils.jsx'; import Client from 'client/web_client.jsx'; + import React from 'react'; export default function UserListRow({user, teamMember, actions, actionProps}) { @@ -32,23 +36,24 @@ export default function UserListRow({user, teamMember, actions, actionProps}) { }); } - if (!user.status) { - var status = UserStore.getStatus(user.id); - user.status = status ? 'status-' + status : ''; + let status; + if (user.status) { + status = user.status; + } else { + status = UserStore.getStatus(user.id); } + return (
- - - +
diff --git a/webapp/sass/components/_modal.scss b/webapp/sass/components/_modal.scss index dd62fc20c..a12856c63 100644 --- a/webapp/sass/components/_modal.scss +++ b/webapp/sass/components/_modal.scss @@ -483,6 +483,30 @@ } + +.status-wrapper { + display: inline-block; + margin-right: 3px; + position: relative; + + &:after { + border-radius: 100%; + bottom: 4px; + content: ''; + display: block; + height: 8px; + position: absolute; + right: 0; + width: 8px; + } + + &.status-offline { + &:after { + background: #D3D3D3; + } + } +} + .more-modal__list { display: flex; flex-direction: column; @@ -504,26 +528,12 @@ @include border-radius(60px); flex-grow: 0; flex-shrink: 0; - max-width: none; - - &-wrapper { - position: relative; - display: inline-block; - margin-right: 8px; + margin-top: 2px; + max-width: none; + .status-wrapper { &:after { - content: ""; - right: 0; - bottom: 0; - width: 25%; - height: 25%; - display: block; - position: absolute; - border-radius: 100%; - } - - &.status-offline:after { - background: #D3D3D3; + bottom: 3px; } } } @@ -586,4 +596,4 @@ flex-grow: 1; flex-shrink: 1; } -} \ No newline at end of file +} diff --git a/webapp/sass/layout/_post.scss b/webapp/sass/layout/_post.scss index d894848c9..4437e4ce3 100644 --- a/webapp/sass/layout/_post.scss +++ b/webapp/sass/layout/_post.scss @@ -703,6 +703,10 @@ body.ios { } .post__img { + .status-wrapper { + display: none; + } + img { display: none; } diff --git a/webapp/sass/responsive/_tablet.scss b/webapp/sass/responsive/_tablet.scss index a74c18660..36b6af851 100644 --- a/webapp/sass/responsive/_tablet.scss +++ b/webapp/sass/responsive/_tablet.scss @@ -134,7 +134,7 @@ } &:not(.post--thread) { - padding: 5px .5em 0 72px; + padding: 5px .5em 0 77px; .post__link { margin: 4px 0 7px; @@ -220,7 +220,7 @@ &.same--root { &.same--user { - padding-left: 72px; + padding-left: 77px; padding-top: 0; .flag-icon__container { diff --git a/webapp/utils/utils.jsx b/webapp/utils/utils.jsx index c394cdaf1..a8ad4d76a 100644 --- a/webapp/utils/utils.jsx +++ b/webapp/utils/utils.jsx @@ -525,14 +525,14 @@ export function applyTheme(theme) { changeCss('.app__body .sidebar--left .status .online--icon', 'fill:' + theme.onlineIndicator, 1); changeCss('.app__body .channel-header__info .status .online--icon', 'fill:' + theme.onlineIndicator, 1); changeCss('.app__body .navbar .status .online--icon', 'fill:' + theme.onlineIndicator, 1); - changeCss('.more-modal__list .more-modal__image-wrapper.status-online:after', 'background:' + theme.onlineIndicator, 1); + changeCss('.status-wrapper.status-online:after', 'background:' + theme.onlineIndicator, 1); } if (theme.awayIndicator) { changeCss('.app__body .sidebar--left .status .away--icon', 'fill:' + theme.awayIndicator, 1); changeCss('.app__body .channel-header__info .status .away--icon', 'fill:' + theme.awayIndicator, 1); changeCss('.app__body .navbar .status .away--icon', 'fill:' + theme.awayIndicator, 1); - changeCss('.more-modal__list .more-modal__image-wrapper.status-away:after', 'background:' + theme.awayIndicator, 1); + changeCss('.status-wrapper.status-away:after', 'background:' + theme.awayIndicator, 1); } if (theme.mentionBj) { @@ -1341,4 +1341,4 @@ export function isValidPassword(password) { export function getSiteURL() { return global.mm_config.SiteURL || window.location.origin; -} \ No newline at end of file +} -- cgit v1.2.3-1-g7c22