mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
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
This commit is contained in:
@@ -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 = (
|
||||
<img
|
||||
<ProfilePicture
|
||||
src={PostUtils.getProfilePicSrcForPost(post, timestamp)}
|
||||
height='36'
|
||||
width='36'
|
||||
status={this.props.status}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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 = (
|
||||
<Post
|
||||
key={keyPrefix + 'postKey'}
|
||||
@@ -311,6 +316,7 @@ export default class PostList extends React.Component {
|
||||
useMilitaryTime={this.props.useMilitaryTime}
|
||||
emojis={this.props.emojis}
|
||||
isFlagged={isFlagged}
|
||||
status={status}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
55
webapp/components/profile_picture.jsx
Normal file
55
webapp/components/profile_picture.jsx
Normal file
@@ -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 (
|
||||
<span className={`status-wrapper ${statusClass}`}>
|
||||
<img
|
||||
className='more-modal__image'
|
||||
width={this.props.width}
|
||||
height={this.props.width}
|
||||
src={this.props.src}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ProfilePicture.defaultProps = {
|
||||
width: '36',
|
||||
height: '36'
|
||||
};
|
||||
ProfilePicture.propTypes = {
|
||||
src: React.PropTypes.string.isRequired,
|
||||
status: React.PropTypes.string,
|
||||
width: React.PropTypes.string,
|
||||
height: React.PropTypes.string
|
||||
};
|
||||
@@ -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 (
|
||||
<div
|
||||
key={user.id}
|
||||
className='more-modal__row'
|
||||
>
|
||||
<span className={`more-modal__image-wrapper ${user.status}`}>
|
||||
<img
|
||||
className='more-modal__image'
|
||||
width='38'
|
||||
height='38'
|
||||
src={`${Client.getUsersRoute()}/${user.id}/image?time=${user.update_at}`}
|
||||
/>
|
||||
</span>
|
||||
<ProfilePicture
|
||||
src={`${Client.getUsersRoute()}/${user.id}/image?time=${user.update_at}`}
|
||||
status={status}
|
||||
width='32'
|
||||
height='32'
|
||||
/>
|
||||
<div
|
||||
className='more-modal__details'
|
||||
>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -703,6 +703,10 @@ body.ios {
|
||||
}
|
||||
|
||||
.post__img {
|
||||
.status-wrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
img {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user