PLT-946 Add status icon to the left of the username in DM channel (#3258)
Add a StatusIcon component to be able to display a status icon from anywhere
Этот коммит содержится в:
коммит произвёл
Christopher Speller
родитель
dbcf8572e5
Коммит
d6fdd93679
@@ -15,6 +15,7 @@ import ChannelNotificationsModal from './channel_notifications_modal.jsx';
|
|||||||
import DeleteChannelModal from './delete_channel_modal.jsx';
|
import DeleteChannelModal from './delete_channel_modal.jsx';
|
||||||
import RenameChannelModal from './rename_channel_modal.jsx';
|
import RenameChannelModal from './rename_channel_modal.jsx';
|
||||||
import ToggleModalButton from './toggle_modal_button.jsx';
|
import ToggleModalButton from './toggle_modal_button.jsx';
|
||||||
|
import StatusIcon from './status_icon.jsx';
|
||||||
|
|
||||||
import ChannelStore from 'stores/channel_store.jsx';
|
import ChannelStore from 'stores/channel_store.jsx';
|
||||||
import UserStore from 'stores/user_store.jsx';
|
import UserStore from 'stores/user_store.jsx';
|
||||||
@@ -82,6 +83,7 @@ export default class ChannelHeader extends React.Component {
|
|||||||
SearchStore.addSearchChangeListener(this.onListenerChange);
|
SearchStore.addSearchChangeListener(this.onListenerChange);
|
||||||
PreferenceStore.addChangeListener(this.onListenerChange);
|
PreferenceStore.addChangeListener(this.onListenerChange);
|
||||||
UserStore.addChangeListener(this.onListenerChange);
|
UserStore.addChangeListener(this.onListenerChange);
|
||||||
|
UserStore.addStatusesChangeListener(this.onListenerChange);
|
||||||
$('.sidebar--left .dropdown-menu').perfectScrollbar();
|
$('.sidebar--left .dropdown-menu').perfectScrollbar();
|
||||||
document.addEventListener('keydown', this.openRecentMentions);
|
document.addEventListener('keydown', this.openRecentMentions);
|
||||||
}
|
}
|
||||||
@@ -91,6 +93,7 @@ export default class ChannelHeader extends React.Component {
|
|||||||
SearchStore.removeSearchChangeListener(this.onListenerChange);
|
SearchStore.removeSearchChangeListener(this.onListenerChange);
|
||||||
PreferenceStore.removeChangeListener(this.onListenerChange);
|
PreferenceStore.removeChangeListener(this.onListenerChange);
|
||||||
UserStore.removeChangeListener(this.onListenerChange);
|
UserStore.removeChangeListener(this.onListenerChange);
|
||||||
|
UserStore.removeStatusesChangeListener(this.onListenerChange);
|
||||||
document.removeEventListener('keydown', this.openRecentMentions);
|
document.removeEventListener('keydown', this.openRecentMentions);
|
||||||
}
|
}
|
||||||
onListenerChange() {
|
onListenerChange() {
|
||||||
@@ -157,6 +160,21 @@ export default class ChannelHeader extends React.Component {
|
|||||||
showRenameChannelModal: false
|
showRenameChannelModal: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTeammateStatus() {
|
||||||
|
const channel = this.state.channel;
|
||||||
|
|
||||||
|
// get status for direct message channels
|
||||||
|
if (channel.type === 'D') {
|
||||||
|
const currentUserId = this.state.currentUser.id;
|
||||||
|
const teammate = this.state.users.find((user) => user.id !== currentUserId);
|
||||||
|
if (teammate) {
|
||||||
|
return UserStore.getStatus(teammate.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (!this.validState()) {
|
if (!this.validState()) {
|
||||||
return null;
|
return null;
|
||||||
@@ -173,7 +191,7 @@ export default class ChannelHeader extends React.Component {
|
|||||||
);
|
);
|
||||||
const popoverContent = (
|
const popoverContent = (
|
||||||
<Popover
|
<Popover
|
||||||
id='hader-popover'
|
id='header-popover'
|
||||||
bStyle='info'
|
bStyle='info'
|
||||||
bSize='large'
|
bSize='large'
|
||||||
placement='bottom'
|
placement='bottom'
|
||||||
@@ -459,7 +477,7 @@ export default class ChannelHeader extends React.Component {
|
|||||||
data-toggle='dropdown'
|
data-toggle='dropdown'
|
||||||
aria-expanded='true'
|
aria-expanded='true'
|
||||||
>
|
>
|
||||||
<strong className='heading'>{channelTitle} </strong>
|
<strong className='heading'><StatusIcon status={this.getTeammateStatus()}/>{channelTitle} </strong>
|
||||||
<span className='glyphicon glyphicon-chevron-down header-dropdown__icon'/>
|
<span className='glyphicon glyphicon-chevron-down header-dropdown__icon'/>
|
||||||
</a>
|
</a>
|
||||||
<ul
|
<ul
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import ChannelNotificationsModal from './channel_notifications_modal.jsx';
|
|||||||
import DeleteChannelModal from './delete_channel_modal.jsx';
|
import DeleteChannelModal from './delete_channel_modal.jsx';
|
||||||
import RenameChannelModal from './rename_channel_modal.jsx';
|
import RenameChannelModal from './rename_channel_modal.jsx';
|
||||||
import ToggleModalButton from './toggle_modal_button.jsx';
|
import ToggleModalButton from './toggle_modal_button.jsx';
|
||||||
|
import StatusIcon from './status_icon.jsx';
|
||||||
|
|
||||||
import UserStore from 'stores/user_store.jsx';
|
import UserStore from 'stores/user_store.jsx';
|
||||||
import ChannelStore from 'stores/channel_store.jsx';
|
import ChannelStore from 'stores/channel_store.jsx';
|
||||||
@@ -77,12 +78,14 @@ export default class Navbar extends React.Component {
|
|||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
ChannelStore.addChangeListener(this.onChange);
|
ChannelStore.addChangeListener(this.onChange);
|
||||||
ChannelStore.addExtraInfoChangeListener(this.onChange);
|
ChannelStore.addExtraInfoChangeListener(this.onChange);
|
||||||
|
UserStore.addStatusesChangeListener(this.onChange);
|
||||||
$('.inner-wrap').click(this.hideSidebars);
|
$('.inner-wrap').click(this.hideSidebars);
|
||||||
document.addEventListener('keydown', this.showChannelSwitchModal);
|
document.addEventListener('keydown', this.showChannelSwitchModal);
|
||||||
}
|
}
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
ChannelStore.removeChangeListener(this.onChange);
|
ChannelStore.removeChangeListener(this.onChange);
|
||||||
ChannelStore.removeExtraInfoChangeListener(this.onChange);
|
ChannelStore.removeExtraInfoChangeListener(this.onChange);
|
||||||
|
UserStore.removeStatusesChangeListener(this.onChange);
|
||||||
document.removeEventListener('keydown', this.showChannelSwitchModal);
|
document.removeEventListener('keydown', this.showChannelSwitchModal);
|
||||||
}
|
}
|
||||||
handleSubmit(e) {
|
handleSubmit(e) {
|
||||||
@@ -352,7 +355,7 @@ export default class Navbar extends React.Component {
|
|||||||
data-toggle='dropdown'
|
data-toggle='dropdown'
|
||||||
aria-expanded='true'
|
aria-expanded='true'
|
||||||
>
|
>
|
||||||
<span className='heading'>{channelTitle} </span>
|
<span className='heading'><StatusIcon status={this.getTeammateStatus()}/>{channelTitle} </span>
|
||||||
<span className='glyphicon glyphicon-chevron-down header-dropdown__icon'></span>
|
<span className='glyphicon glyphicon-chevron-down header-dropdown__icon'></span>
|
||||||
</a>
|
</a>
|
||||||
<ul
|
<ul
|
||||||
@@ -446,6 +449,21 @@ export default class Navbar extends React.Component {
|
|||||||
|
|
||||||
return buttons;
|
return buttons;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTeammateStatus() {
|
||||||
|
const channel = this.state.channel;
|
||||||
|
|
||||||
|
// get status for direct message channels
|
||||||
|
if (channel.type === 'D') {
|
||||||
|
const currentUserId = this.state.currentUser.id;
|
||||||
|
const teammate = this.state.users.find((user) => user.id !== currentUserId);
|
||||||
|
if (teammate) {
|
||||||
|
return UserStore.getStatus(teammate.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (!this.isStateValid()) {
|
if (!this.isStateValid()) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import MoreDirectChannels from './more_direct_channels.jsx';
|
|||||||
import SidebarHeader from './sidebar_header.jsx';
|
import SidebarHeader from './sidebar_header.jsx';
|
||||||
import UnreadChannelIndicator from './unread_channel_indicator.jsx';
|
import UnreadChannelIndicator from './unread_channel_indicator.jsx';
|
||||||
import TutorialTip from './tutorial/tutorial_tip.jsx';
|
import TutorialTip from './tutorial/tutorial_tip.jsx';
|
||||||
|
import StatusIcon from './status_icon.jsx';
|
||||||
|
|
||||||
import ChannelStore from 'stores/channel_store.jsx';
|
import ChannelStore from 'stores/channel_store.jsx';
|
||||||
import UserStore from 'stores/user_store.jsx';
|
import UserStore from 'stores/user_store.jsx';
|
||||||
@@ -503,30 +504,14 @@ export default class Sidebar extends React.Component {
|
|||||||
rowClass += ' has-badge';
|
rowClass += ' has-badge';
|
||||||
}
|
}
|
||||||
|
|
||||||
// set up status icon for direct message channels
|
|
||||||
var status = null;
|
|
||||||
if (channel.type === 'D') {
|
|
||||||
var statusIcon = '';
|
|
||||||
if (channel.status === 'online') {
|
|
||||||
statusIcon = Constants.ONLINE_ICON_SVG;
|
|
||||||
} else if (channel.status === 'away') {
|
|
||||||
statusIcon = Constants.AWAY_ICON_SVG;
|
|
||||||
} else {
|
|
||||||
statusIcon = Constants.OFFLINE_ICON_SVG;
|
|
||||||
}
|
|
||||||
status = (
|
|
||||||
<span
|
|
||||||
className='status'
|
|
||||||
dangerouslySetInnerHTML={{__html: statusIcon}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
var icon = null;
|
var icon = null;
|
||||||
if (channel.type === 'O') {
|
if (channel.type === 'O') {
|
||||||
icon = <div className='status'><i className='fa fa-globe'></i></div>;
|
icon = <div className='status'><i className='fa fa-globe'></i></div>;
|
||||||
} else if (channel.type === 'P') {
|
} else if (channel.type === 'P') {
|
||||||
icon = <div className='status'><i className='fa fa-lock'></i></div>;
|
icon = <div className='status'><i className='fa fa-lock'></i></div>;
|
||||||
|
} else {
|
||||||
|
// set up status icon for direct message channels (status is null for other channel types)
|
||||||
|
icon = <StatusIcon status={channel.status}/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
let closeButton = null;
|
let closeButton = null;
|
||||||
@@ -581,7 +566,6 @@ export default class Sidebar extends React.Component {
|
|||||||
className={rowClass}
|
className={rowClass}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
{status}
|
|
||||||
{channel.display_name}
|
{channel.display_name}
|
||||||
{badge}
|
{badge}
|
||||||
{closeButton}
|
{closeButton}
|
||||||
|
|||||||
37
webapp/components/status_icon.jsx
Обычный файл
37
webapp/components/status_icon.jsx
Обычный файл
@@ -0,0 +1,37 @@
|
|||||||
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import Constants from 'utils/constants.jsx';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default class StatusIcon extends React.Component {
|
||||||
|
render() {
|
||||||
|
const status = this.props.status;
|
||||||
|
|
||||||
|
if (!status) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let statusIcon = '';
|
||||||
|
if (status === 'online') {
|
||||||
|
statusIcon = Constants.ONLINE_ICON_SVG;
|
||||||
|
} else if (status === 'away') {
|
||||||
|
statusIcon = Constants.AWAY_ICON_SVG;
|
||||||
|
} else {
|
||||||
|
statusIcon = Constants.OFFLINE_ICON_SVG;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className='status'
|
||||||
|
dangerouslySetInnerHTML={{__html: statusIcon}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusIcon.propTypes = {
|
||||||
|
status: React.PropTypes.string.isRequired
|
||||||
|
};
|
||||||
33
webapp/sass/components/_status-icon.scss
Обычный файл
33
webapp/sass/components/_status-icon.scss
Обычный файл
@@ -0,0 +1,33 @@
|
|||||||
|
@charset 'UTF-8';
|
||||||
|
|
||||||
|
.status {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 6px;
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
width: 12px;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
max-height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
i,
|
||||||
|
path,
|
||||||
|
ellipse {
|
||||||
|
@include opacity(.5);
|
||||||
|
|
||||||
|
&.online--icon,
|
||||||
|
&.away--icon {
|
||||||
|
@include opacity(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-lock {
|
||||||
|
margin-left: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-globe {
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -144,6 +144,8 @@
|
|||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@import "../components/status-icon";
|
||||||
}
|
}
|
||||||
|
|
||||||
.channel-intro {
|
.channel-intro {
|
||||||
|
|||||||
@@ -60,38 +60,6 @@
|
|||||||
top: 5px;
|
top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status {
|
|
||||||
display: inline-block;
|
|
||||||
margin-right: 6px;
|
|
||||||
position: relative;
|
|
||||||
top: 1px;
|
|
||||||
width: 12px;
|
|
||||||
|
|
||||||
svg {
|
|
||||||
max-height: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
i,
|
|
||||||
path,
|
|
||||||
ellipse {
|
|
||||||
@include opacity(.5);
|
|
||||||
|
|
||||||
&.online--icon,
|
|
||||||
&.away--icon {
|
|
||||||
@include opacity(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fa-lock {
|
|
||||||
margin-left: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fa-globe {
|
|
||||||
position: relative;
|
|
||||||
top: -1px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-pills__container {
|
.nav-pills__container {
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
height: calc(100% - 80px);
|
height: calc(100% - 80px);
|
||||||
@@ -234,6 +202,8 @@
|
|||||||
color: #666666;
|
color: #666666;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@import "../components/status-icon";
|
||||||
}
|
}
|
||||||
|
|
||||||
.channel-loading-gif {
|
.channel-loading-gif {
|
||||||
|
|||||||
@@ -590,6 +590,8 @@
|
|||||||
|
|
||||||
.navbar-brand {
|
.navbar-brand {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
|
@import "../components/status-icon";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -521,7 +521,7 @@ export function applyTheme(theme) {
|
|||||||
changeCss('@media(max-width: 768px){.app__body .modal .settings-modal .settings-table .nav>li>a', 'color:' + theme.sidebarText, 1);
|
changeCss('@media(max-width: 768px){.app__body .modal .settings-modal .settings-table .nav>li>a', 'color:' + theme.sidebarText, 1);
|
||||||
changeCss('.app__body .sidebar--left .nav-pills__container li>h4, .app__body .sidebar--left .add-channel-btn', 'color:' + changeOpacity(theme.sidebarText, 0.6), 1);
|
changeCss('.app__body .sidebar--left .nav-pills__container li>h4, .app__body .sidebar--left .add-channel-btn', 'color:' + changeOpacity(theme.sidebarText, 0.6), 1);
|
||||||
changeCss('.app__body .sidebar--left .add-channel-btn:hover, .app__body .sidebar--left .add-channel-btn:focus', 'color:' + theme.sidebarText, 1);
|
changeCss('.app__body .sidebar--left .add-channel-btn:hover, .app__body .sidebar--left .add-channel-btn:focus', 'color:' + theme.sidebarText, 1);
|
||||||
changeCss('.app__body .sidebar--left .status .offline--icon, .app__body .sidebar--left .status .offline--icon', 'fill:' + theme.sidebarText, 1);
|
changeCss('.app__body .sidebar--left .status .offline--icon', 'fill:' + theme.sidebarText, 1);
|
||||||
changeCss('@media(max-width: 768px){.app__body .modal .settings-modal .settings-table .nav>li>a', 'border-color:' + changeOpacity(theme.sidebarText, 0.2), 2);
|
changeCss('@media(max-width: 768px){.app__body .modal .settings-modal .settings-table .nav>li>a', 'border-color:' + changeOpacity(theme.sidebarText, 0.2), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -566,10 +566,14 @@ export function applyTheme(theme) {
|
|||||||
|
|
||||||
if (theme.onlineIndicator) {
|
if (theme.onlineIndicator) {
|
||||||
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 .navbar .status .online--icon', 'fill:' + 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 .navbar .status .away--icon', 'fill:' + theme.awayIndicator, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (theme.mentionBj) {
|
if (theme.mentionBj) {
|
||||||
@@ -659,6 +663,8 @@ export function applyTheme(theme) {
|
|||||||
changeCss('.app__body .post.post--comment .post__body', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
|
changeCss('.app__body .post.post--comment .post__body', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
|
||||||
changeCss('@media(min-width: 768px){.app__body .post.post--compact.same--root.post--comment .post__content', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
|
changeCss('@media(min-width: 768px){.app__body .post.post--compact.same--root.post--comment .post__content', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
|
||||||
changeCss('.app__body .post.post--comment.current--user .post__body', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
|
changeCss('.app__body .post.post--comment.current--user .post__body', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
|
||||||
|
changeCss('.app__body .channel-header__info .status .offline--icon', 'fill:' + theme.centerChannelColor, 1);
|
||||||
|
changeCss('.app__body .navbar .status .offline--icon', 'fill:' + theme.centerChannelColor, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (theme.newMessageSeparator) {
|
if (theme.newMessageSeparator) {
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user