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
Этот коммит содержится в:
Joram Wilander
2016-08-19 10:06:16 -04:00
коммит произвёл GitHub
родитель 8c2ea22892
Коммит dad764088e
10 изменённых файлов: 170 добавлений и 43 удалений

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

@@ -3,6 +3,7 @@
import PostHeader from './post_header.jsx'; import PostHeader from './post_header.jsx';
import PostBody from './post_body.jsx'; import PostBody from './post_body.jsx';
import ProfilePicture from 'components/profile_picture.jsx';
import Constants from 'utils/constants.jsx'; import Constants from 'utils/constants.jsx';
const ActionTypes = Constants.ActionTypes; const ActionTypes = Constants.ActionTypes;
@@ -105,11 +106,11 @@ export default class Post extends React.Component {
return true; return true;
} }
if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) { if (nextProps.status !== this.props.status) {
return true; return true;
} }
if (nextProps.emojis !== this.props.emojis) { if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) {
return true; return true;
} }
@@ -192,10 +193,9 @@ export default class Post extends React.Component {
} }
let profilePic = ( let profilePic = (
<img <ProfilePicture
src={PostUtils.getProfilePicSrcForPost(post, timestamp)} src={PostUtils.getProfilePicSrcForPost(post, timestamp)}
height='36' status={this.props.status}
width='36'
/> />
); );
@@ -288,5 +288,6 @@ Post.propTypes = {
isCommentMention: React.PropTypes.bool, isCommentMention: React.PropTypes.bool,
useMilitaryTime: React.PropTypes.bool.isRequired, useMilitaryTime: React.PropTypes.bool.isRequired,
emojis: React.PropTypes.object.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'; isFlagged = this.props.flaggedPosts.get(post.id) === 'true';
} }
let status = '';
if (this.props.statuses) {
status = this.props.statuses[profile.id] || 'offline';
}
const postCtl = ( const postCtl = (
<Post <Post
key={keyPrefix + 'postKey'} key={keyPrefix + 'postKey'}
@@ -311,6 +316,7 @@ export default class PostList extends React.Component {
useMilitaryTime={this.props.useMilitaryTime} useMilitaryTime={this.props.useMilitaryTime}
emojis={this.props.emojis} emojis={this.props.emojis}
isFlagged={isFlagged} isFlagged={isFlagged}
status={status}
/> />
); );
@@ -579,5 +585,6 @@ PostList.propTypes = {
useMilitaryTime: React.PropTypes.bool.isRequired, useMilitaryTime: React.PropTypes.bool.isRequired,
isFocusPost: React.PropTypes.bool, isFocusPost: React.PropTypes.bool,
emojis: React.PropTypes.object.isRequired, 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.onPostsChange = this.onPostsChange.bind(this);
this.onUserChange = this.onUserChange.bind(this); this.onUserChange = this.onUserChange.bind(this);
this.onEmojiChange = this.onEmojiChange.bind(this); this.onEmojiChange = this.onEmojiChange.bind(this);
this.onStatusChange = this.onStatusChange.bind(this);
this.onPreferenceChange = this.onPreferenceChange.bind(this); this.onPreferenceChange = this.onPreferenceChange.bind(this);
this.onPostListScroll = this.onPostListScroll.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); 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 = { this.state = {
postList: PostStore.filterPosts(focusedPostId, joinLeaveEnabled), postList: PostStore.filterPosts(focusedPostId, joinLeaveEnabled),
currentUser: UserStore.getCurrentUser(), currentUser: UserStore.getCurrentUser(),
profiles, profiles,
statuses,
scrollType: ScrollTypes.POST, scrollType: ScrollTypes.POST,
currentChannel: ChannelStore.getCurrentId().slice(), currentChannel: ChannelStore.getCurrentId().slice(),
scrollPostId: focusedPostId, scrollPostId: focusedPostId,
@@ -54,6 +61,7 @@ export default class PostFocusView extends React.Component {
ChannelStore.addChangeListener(this.onChannelChange); ChannelStore.addChangeListener(this.onChannelChange);
PostStore.addChangeListener(this.onPostsChange); PostStore.addChangeListener(this.onPostsChange);
UserStore.addChangeListener(this.onUserChange); UserStore.addChangeListener(this.onUserChange);
UserStore.addStatusesChangeListener(this.onStatusChange);
EmojiStore.addChangeListener(this.onEmojiChange); EmojiStore.addChangeListener(this.onEmojiChange);
PreferenceStore.addChangeListener(this.onPreferenceChange); PreferenceStore.addChangeListener(this.onPreferenceChange);
} }
@@ -62,7 +70,9 @@ export default class PostFocusView extends React.Component {
ChannelStore.removeChangeListener(this.onChannelChange); ChannelStore.removeChangeListener(this.onChannelChange);
PostStore.removeChangeListener(this.onPostsChange); PostStore.removeChangeListener(this.onPostsChange);
UserStore.removeChangeListener(this.onUserChange); UserStore.removeChangeListener(this.onUserChange);
UserStore.removeStatusesChangeListener(this.onStatusChange);
EmojiStore.removeChangeListener(this.onEmojiChange); EmojiStore.removeChangeListener(this.onEmojiChange);
PreferenceStore.removeChangeListener(this.onPreferenceChange);
} }
onChannelChange() { onChannelChange() {
@@ -100,6 +110,16 @@ export default class PostFocusView extends React.Component {
this.setState({currentUser: UserStore.getCurrentUser(), profiles: JSON.parse(JSON.stringify(profiles))}); 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() { onEmojiChange() {
this.setState({ this.setState({
emojis: EmojiStore.getEmojis() emojis: EmojiStore.getEmojis()
@@ -151,6 +171,7 @@ export default class PostFocusView extends React.Component {
isFocusPost={true} isFocusPost={true}
emojis={this.state.emojis} emojis={this.state.emojis}
flaggedPosts={this.state.flaggedPosts} 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.onUserChange = this.onUserChange.bind(this);
this.onPostsChange = this.onPostsChange.bind(this); this.onPostsChange = this.onPostsChange.bind(this);
this.onEmojisChange = this.onEmojisChange.bind(this); this.onEmojisChange = this.onEmojisChange.bind(this);
this.onStatusChange = this.onStatusChange.bind(this);
this.onPostsViewJumpRequest = this.onPostsViewJumpRequest.bind(this); this.onPostsViewJumpRequest = this.onPostsViewJumpRequest.bind(this);
this.onSetNewMessageIndicator = this.onSetNewMessageIndicator.bind(this); this.onSetNewMessageIndicator = this.onSetNewMessageIndicator.bind(this);
this.onPostListScroll = this.onPostListScroll.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); 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 = { this.state = {
channel, channel,
postList: PostStore.filterPosts(channel.id, joinLeaveEnabled), postList: PostStore.filterPosts(channel.id, joinLeaveEnabled),
currentUser: UserStore.getCurrentUser(), currentUser: UserStore.getCurrentUser(),
profiles, profiles,
statuses,
atTop: PostStore.getVisibilityAtTop(channel.id), atTop: PostStore.getVisibilityAtTop(channel.id),
lastViewed, lastViewed,
ownNewMessage: false, 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() { onActivate() {
PreferenceStore.addChangeListener(this.onPreferenceChange); PreferenceStore.addChangeListener(this.onPreferenceChange);
UserStore.addChangeListener(this.onUserChange); UserStore.addChangeListener(this.onUserChange);
UserStore.addStatusesChangeListener(this.onStatusChange);
PostStore.addChangeListener(this.onPostsChange); PostStore.addChangeListener(this.onPostsChange);
PostStore.addPostsViewJumpListener(this.onPostsViewJumpRequest); PostStore.addPostsViewJumpListener(this.onPostsViewJumpRequest);
EmojiStore.addChangeListener(this.onEmojisChange); EmojiStore.addChangeListener(this.onEmojisChange);
@@ -134,6 +152,7 @@ export default class PostViewController extends React.Component {
onDeactivate() { onDeactivate() {
PreferenceStore.removeChangeListener(this.onPreferenceChange); PreferenceStore.removeChangeListener(this.onPreferenceChange);
UserStore.removeChangeListener(this.onUserChange); UserStore.removeChangeListener(this.onUserChange);
UserStore.removeStatusesChangeListener(this.onStatusChange);
PostStore.removeChangeListener(this.onPostsChange); PostStore.removeChangeListener(this.onPostsChange);
PostStore.removePostsViewJumpListener(this.onPostsViewJumpRequest); PostStore.removePostsViewJumpListener(this.onPostsViewJumpRequest);
EmojiStore.removeChangeListener(this.onEmojisChange); EmojiStore.removeChangeListener(this.onEmojisChange);
@@ -267,6 +286,10 @@ export default class PostViewController extends React.Component {
return true; return true;
} }
if (!Utils.areObjectsEqual(nextState.statuses, this.state.statuses)) {
return true;
}
if (!Utils.areObjectsEqual(nextState.postList, this.state.postList)) { if (!Utils.areObjectsEqual(nextState.postList, this.state.postList)) {
return true; return true;
} }
@@ -311,6 +334,7 @@ export default class PostViewController extends React.Component {
lastViewed={this.state.lastViewed} lastViewed={this.state.lastViewed}
emojis={this.state.emojis} emojis={this.state.emojis}
ownNewMessage={this.state.ownNewMessage} ownNewMessage={this.state.ownNewMessage}
statuses={this.state.statuses}
/> />
); );
} }

55
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 (
<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. // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
import ProfilePicture from 'components/profile_picture.jsx';
import UserStore from 'stores/user_store.jsx'; import UserStore from 'stores/user_store.jsx';
import Constants from 'utils/constants.jsx';
import PreferenceStore from 'stores/preference_store.jsx'; import PreferenceStore from 'stores/preference_store.jsx';
import Constants from 'utils/constants.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 React from 'react'; import React from 'react';
export default function UserListRow({user, teamMember, actions, actionProps}) { export default function UserListRow({user, teamMember, actions, actionProps}) {
@@ -32,23 +36,24 @@ export default function UserListRow({user, teamMember, actions, actionProps}) {
}); });
} }
if (!user.status) { let status;
var status = UserStore.getStatus(user.id); if (user.status) {
user.status = status ? 'status-' + status : ''; status = user.status;
} else {
status = UserStore.getStatus(user.id);
} }
return ( return (
<div <div
key={user.id} key={user.id}
className='more-modal__row' className='more-modal__row'
> >
<span className={`more-modal__image-wrapper ${user.status}`}> <ProfilePicture
<img src={`${Client.getUsersRoute()}/${user.id}/image?time=${user.update_at}`}
className='more-modal__image' status={status}
width='38' width='32'
height='38' height='32'
src={`${Client.getUsersRoute()}/${user.id}/image?time=${user.update_at}`} />
/>
</span>
<div <div
className='more-modal__details' 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 { .more-modal__list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -504,26 +528,12 @@
@include border-radius(60px); @include border-radius(60px);
flex-grow: 0; flex-grow: 0;
flex-shrink: 0; flex-shrink: 0;
max-width: none; margin-top: 2px;
max-width: none;
&-wrapper {
position: relative;
display: inline-block;
margin-right: 8px;
.status-wrapper {
&:after { &:after {
content: ""; bottom: 3px;
right: 0;
bottom: 0;
width: 25%;
height: 25%;
display: block;
position: absolute;
border-radius: 100%;
}
&.status-offline:after {
background: #D3D3D3;
} }
} }
} }

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

@@ -703,6 +703,10 @@ body.ios {
} }
.post__img { .post__img {
.status-wrapper {
display: none;
}
img { img {
display: none; display: none;
} }

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

@@ -134,7 +134,7 @@
} }
&:not(.post--thread) { &:not(.post--thread) {
padding: 5px .5em 0 72px; padding: 5px .5em 0 77px;
.post__link { .post__link {
margin: 4px 0 7px; margin: 4px 0 7px;
@@ -220,7 +220,7 @@
&.same--root { &.same--root {
&.same--user { &.same--user {
padding-left: 72px; padding-left: 77px;
padding-top: 0; padding-top: 0;
.flag-icon__container { .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 .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 .channel-header__info .status .online--icon', 'fill:' + theme.onlineIndicator, 1);
changeCss('.app__body .navbar .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) { if (theme.awayIndicator) {
changeCss('.app__body .sidebar--left .status .away--icon', 'fill:' + theme.awayIndicator, 1); 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 .channel-header__info .status .away--icon', 'fill:' + theme.awayIndicator, 1);
changeCss('.app__body .navbar .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) { if (theme.mentionBj) {