summaryrefslogtreecommitdiffstats
path: root/webapp/components
diff options
context:
space:
mode:
Diffstat (limited to 'webapp/components')
-rw-r--r--webapp/components/admin_console/admin_console.jsx9
-rw-r--r--webapp/components/admin_console/color_setting.jsx119
-rw-r--r--webapp/components/admin_console/policy_settings.jsx92
-rw-r--r--webapp/components/announcement_bar/announcement_bar.jsx (renamed from webapp/components/error_bar.jsx)126
-rw-r--r--webapp/components/announcement_bar/index.js16
-rw-r--r--webapp/components/backstage/backstage_controller.jsx4
-rw-r--r--webapp/components/create_team/create_team_controller.jsx4
-rw-r--r--webapp/components/login/login_controller.jsx4
-rw-r--r--webapp/components/needs_team/needs_team.jsx4
-rw-r--r--webapp/components/select_team/select_team.jsx4
-rw-r--r--webapp/components/signup/signup_controller.jsx4
11 files changed, 343 insertions, 43 deletions
diff --git a/webapp/components/admin_console/admin_console.jsx b/webapp/components/admin_console/admin_console.jsx
index 80d9bfed9..b8250bab2 100644
--- a/webapp/components/admin_console/admin_console.jsx
+++ b/webapp/components/admin_console/admin_console.jsx
@@ -1,12 +1,11 @@
-import PropTypes from 'prop-types';
-
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
+import PropTypes from 'prop-types';
import 'bootstrap';
-import ErrorBar from 'components/error_bar.jsx';
+import AnnouncementBar from 'components/announcement_bar';
import AdminStore from 'stores/admin_store.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
@@ -52,7 +51,7 @@ export default class AdminConsole extends React.Component {
if (config && Object.keys(config).length === 0 && config.constructor === 'Object') {
return (
<div className='admin-console__wrapper'>
- <ErrorBar/>
+ <AnnouncementBar/>
<div className='admin-console'/>
</div>
);
@@ -64,7 +63,7 @@ export default class AdminConsole extends React.Component {
});
return (
<div className='admin-console__wrapper'>
- <ErrorBar/>
+ <AnnouncementBar/>
<div className='admin-console'>
<AdminSidebar/>
{children}
diff --git a/webapp/components/admin_console/color_setting.jsx b/webapp/components/admin_console/color_setting.jsx
new file mode 100644
index 000000000..483b585ee
--- /dev/null
+++ b/webapp/components/admin_console/color_setting.jsx
@@ -0,0 +1,119 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import Setting from './setting.jsx';
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import {ChromePicker} from 'react-color';
+
+export default class ColorSetting extends React.PureComponent {
+ static propTypes = {
+
+ /*
+ * The unique identifer for the admin console setting
+ */
+ id: PropTypes.string.isRequired,
+
+ /*
+ * The text/jsx display name for the setting
+ */
+ label: PropTypes.node.isRequired,
+
+ /*
+ * The text/jsx help text to display underneath the setting
+ */
+ helpText: PropTypes.node,
+
+ /*
+ * The hex color value
+ */
+ value: PropTypes.string.isRequired,
+
+ /*
+ * Function called when the input changes
+ */
+ onChange: PropTypes.func,
+
+ /*
+ * Set to disable the setting
+ */
+ disabled: PropTypes.bool
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ showPicker: false
+ };
+ }
+
+ componentDidMount() {
+ document.addEventListener('click', this.closePicker);
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener('click', this.closePicker);
+ }
+
+ handleChange = (color) => {
+ this.props.onChange(this.props.id, color.hex);
+ }
+
+ togglePicker = () => {
+ if (this.props.disabled) {
+ this.setState({showPicker: false});
+ }
+ this.setState({showPicker: !this.state.showPicker});
+ }
+
+ closePicker = (e) => {
+ if (!e.target.closest('.picker-' + this.props.id)) {
+ this.setState({showPicker: false});
+ }
+ }
+
+ onTextInput = (e) => {
+ this.props.onChange(this.props.id, e.target.value);
+ }
+
+ render() {
+ let picker;
+ if (this.state.showPicker) {
+ picker = (
+ <div className={'color-picker__popover picker-' + this.props.id}>
+ <ChromePicker
+ color={this.props.value}
+ onChange={this.handleChange}
+ />
+ </div>
+ );
+ }
+
+ return (
+ <Setting
+ label={this.props.label}
+ helpText={this.props.helpText}
+ inputId={this.props.id}
+ >
+ <div className='input-group color-picker colorpicker-element'>
+ <input
+ type='text'
+ className='form-control'
+ value={this.props.value}
+ onChange={this.onTextInput}
+ disabled={this.props.disabled}
+ />
+ <span
+ className={'input-group-addon picker-' + this.props.id}
+ onClick={this.togglePicker}
+ >
+ <i style={{backgroundColor: this.props.value}}/>
+ </span>
+ {picker}
+ </div>
+ </Setting>
+ );
+ }
+}
diff --git a/webapp/components/admin_console/policy_settings.jsx b/webapp/components/admin_console/policy_settings.jsx
index 7d2985001..f689efd82 100644
--- a/webapp/components/admin_console/policy_settings.jsx
+++ b/webapp/components/admin_console/policy_settings.jsx
@@ -8,6 +8,9 @@ import SettingsGroup from './settings_group.jsx';
import DropdownSetting from './dropdown_setting.jsx';
import RadioSetting from './radio_setting.jsx';
import PostEditSetting from './post_edit_setting.jsx';
+import BooleanSetting from './boolean_setting.jsx';
+import TextSetting from './text_setting.jsx';
+import ColorSetting from './color_setting.jsx';
import Constants from 'utils/constants.jsx';
import * as Utils from 'utils/utils.jsx';
@@ -35,6 +38,11 @@ export default class PolicySettings extends AdminSettings {
config.TeamSettings.RestrictPublicChannelDeletion = this.state.restrictPublicChannelDeletion;
config.TeamSettings.RestrictPrivateChannelDeletion = this.state.restrictPrivateChannelDeletion;
config.TeamSettings.RestrictPrivateChannelManageMembers = this.state.restrictPrivateChannelManageMembers;
+ config.AnnouncementSettings.EnableBanner = this.state.enableBanner;
+ config.AnnouncementSettings.BannerText = this.state.bannerText;
+ config.AnnouncementSettings.BannerColor = this.state.bannerColor;
+ config.AnnouncementSettings.BannerTextColor = this.state.bannerTextColor;
+ config.AnnouncementSettings.AllowBannerDismissal = this.state.allowBannerDismissal;
return config;
}
@@ -51,7 +59,12 @@ export default class PolicySettings extends AdminSettings {
restrictPrivateChannelManagement: config.TeamSettings.RestrictPrivateChannelManagement,
restrictPublicChannelDeletion: config.TeamSettings.RestrictPublicChannelDeletion,
restrictPrivateChannelDeletion: config.TeamSettings.RestrictPrivateChannelDeletion,
- restrictPrivateChannelManageMembers: config.TeamSettings.RestrictPrivateChannelManageMembers
+ restrictPrivateChannelManageMembers: config.TeamSettings.RestrictPrivateChannelManageMembers,
+ enableBanner: config.AnnouncementSettings.EnableBanner,
+ bannerText: config.AnnouncementSettings.BannerText,
+ bannerColor: config.AnnouncementSettings.BannerColor,
+ bannerTextColor: config.AnnouncementSettings.BannerTextColor,
+ allowBannerDismissal: config.AnnouncementSettings.AllowBannerDismissal
};
}
@@ -317,6 +330,83 @@ export default class PolicySettings extends AdminSettings {
/>
}
/>
+ <BooleanSetting
+ id='enableBanner'
+ label={
+ <FormattedMessage
+ id='admin.general.policy.enableBannerTitle'
+ defaultMessage='Enable Announcement Banner:'
+ />
+ }
+ helpText={
+ <FormattedMessage
+ id='admin.general.policy.enableBannerDesc'
+ defaultMessage='Enable an announcement banner across all teams.'
+ />
+ }
+ value={this.state.enableBanner}
+ onChange={this.handleChange}
+ />
+ <TextSetting
+ id='bannerText'
+ label={
+ <FormattedMessage
+ id='admin.general.policy.bannerTextTitle'
+ defaultMessage='Banner Text:'
+ />
+ }
+ helpText={
+ <FormattedMessage
+ id='admin.general.policy.bannerTextDesc'
+ defaultMessage='Text that will appear in the announcement banner.'
+ />
+ }
+ value={this.state.bannerText}
+ onChange={this.handleChange}
+ disabled={!this.state.enableBanner}
+ />
+ <ColorSetting
+ id='bannerColor'
+ label={
+ <FormattedMessage
+ id='admin.general.policy.bannerColorTitle'
+ defaultMessage='Banner Color:'
+ />
+ }
+ value={this.state.bannerColor}
+ onChange={this.handleChange}
+ disabled={!this.state.enableBanner}
+ />
+ <ColorSetting
+ id='bannerTextColor'
+ label={
+ <FormattedMessage
+ id='admin.general.policy.bannerTextColorTitle'
+ defaultMessage='Banner Text Color:'
+ />
+ }
+ value={this.state.bannerTextColor}
+ onChange={this.handleChange}
+ disabled={!this.state.enableBanner}
+ />
+ <BooleanSetting
+ id='allowBannerDismissal'
+ label={
+ <FormattedMessage
+ id='admin.general.policy.allowBannerDismissalTitle'
+ defaultMessage='Allow Banner Dismissal:'
+ />
+ }
+ helpText={
+ <FormattedMessage
+ id='admin.general.policy.allowBannerDismissalDesc'
+ defaultMessage='When true, users can dismiss the banner until its next update. When false, the banner is permanently visible until it is turned off by the System Admin.'
+ />
+ }
+ value={this.state.allowBannerDismissal}
+ onChange={this.handleChange}
+ disabled={!this.state.enableBanner}
+ />
</SettingsGroup>
);
}
diff --git a/webapp/components/error_bar.jsx b/webapp/components/announcement_bar/announcement_bar.jsx
index 97fbbdca0..ed097c436 100644
--- a/webapp/components/error_bar.jsx
+++ b/webapp/components/announcement_bar/announcement_bar.jsx
@@ -18,8 +18,19 @@ const RENEWAL_LINK = 'https://licensing.mattermost.com/renew';
const BAR_DEVELOPER_TYPE = 'developer';
const BAR_CRITICAL_TYPE = 'critical';
+const BAR_ANNOUNCEMENT_TYPE = 'announcement';
+
+const ANNOUNCEMENT_KEY = 'announcement--';
+
+export default class AnnouncementBar extends React.PureComponent {
+ static propTypes = {
+
+ /*
+ * Set if the user is logged in
+ */
+ isLoggedIn: React.PropTypes.bool.isRequired
+ }
-export default class ErrorBar extends React.Component {
constructor() {
super();
@@ -31,7 +42,7 @@ export default class ErrorBar extends React.Component {
this.setInitialError();
- this.state = ErrorStore.getLastError() || {};
+ this.state = this.getState();
}
setInitialError() {
@@ -62,11 +73,38 @@ export default class ErrorBar extends React.Component {
} else if (isLicenseExpired() && isSystemAdmin) {
ErrorStore.storeLastError({notification: true, message: ErrorBarTypes.LICENSE_EXPIRED, type: BAR_CRITICAL_TYPE});
} else if (isLicenseExpiring() && isSystemAdmin) {
- ErrorStore.storeLastError({notification: true, message: ErrorBarTypes.LICENSE_EXPIRING});
+ ErrorStore.storeLastError({notification: true, message: ErrorBarTypes.LICENSE_EXPIRING, type: BAR_CRITICAL_TYPE});
}
}
- isValidError(s) {
+ getState() {
+ const error = ErrorStore.getLastError();
+ if (error) {
+ return {message: error.message, color: null, textColor: null, type: error.type, allowDismissal: true};
+ }
+
+ const bannerText = global.window.mm_config.BannerText || '';
+ const allowDismissal = global.window.mm_config.AllowBannerDismissal === 'true';
+ const bannerDismissed = localStorage.getItem(ANNOUNCEMENT_KEY + global.window.mm_config.BannerText);
+
+ if (global.window.mm_config.EnableBanner === 'true' &&
+ bannerText.length > 0 &&
+ (!bannerDismissed || !allowDismissal)) {
+ // Remove old local storage items
+ Utils.removePrefixFromLocalStorage(ANNOUNCEMENT_KEY);
+ return {
+ message: bannerText,
+ color: global.window.mm_config.BannerColor,
+ textColor: global.window.mm_config.BannerTextColor,
+ type: BAR_ANNOUNCEMENT_TYPE,
+ allowDismissal
+ };
+ }
+
+ return {message: null, color: null, colorText: null, textColor: null, type: null, allowDismissal: true};
+ }
+
+ isValidState(s) {
if (!s) {
return false;
}
@@ -83,28 +121,40 @@ export default class ErrorBar extends React.Component {
}
componentDidMount() {
+ if (this.props.isLoggedIn && !this.state.allowDismissal) {
+ document.body.classList.add('error-bar--fixed');
+ }
+
ErrorStore.addChangeListener(this.onErrorChange);
AnalyticsStore.addChangeListener(this.onAnalyticsChange);
}
componentWillUnmount() {
+ document.body.classList.remove('error-bar--fixed');
ErrorStore.removeChangeListener(this.onErrorChange);
AnalyticsStore.removeChangeListener(this.onAnalyticsChange);
}
- onErrorChange() {
- var newState = ErrorStore.getLastError();
+ componentDidUpdate(prevProps, prevState) {
+ if (!this.props.isLoggedIn) {
+ return;
+ }
- if (newState) {
- if (newState.message === ErrorBarTypes.LICENSE_EXPIRING && !this.state.totalUsers) {
- AsyncClient.getStandardAnalytics();
- }
- this.setState(newState);
- } else {
- this.setState({message: null});
+ if (!prevState.allowDismissal && this.state.allowDismissal) {
+ document.body.classList.remove('error-bar--fixed');
+ } else if (prevState.allowDismissal && !this.state.allowDismissal) {
+ document.body.classList.add('error-bar--fixed');
}
}
+ onErrorChange() {
+ const newState = this.getState();
+ if (newState.message === ErrorBarTypes.LICENSE_EXPIRING && !this.state.totalUsers) {
+ AsyncClient.getStandardAnalytics();
+ }
+ this.setState(newState);
+ }
+
onAnalyticsChange() {
const stats = AnalyticsStore.getAllSystem();
this.setState({totalUsers: stats[StatTypes.TOTAL_USERS]});
@@ -115,28 +165,57 @@ export default class ErrorBar extends React.Component {
e.preventDefault();
}
+ if (this.state.type === BAR_ANNOUNCEMENT_TYPE) {
+ localStorage.setItem(ANNOUNCEMENT_KEY + this.state.message, true);
+ }
+
if (ErrorStore.getLastError() && ErrorStore.getLastError().notification) {
ErrorStore.clearNotificationError();
} else {
ErrorStore.clearLastError();
}
- this.setState({message: null});
+ this.setState(this.getState());
}
render() {
- if (!this.isValidError(this.state)) {
+ if (!this.isValidState(this.state)) {
return <div/>;
}
- var errClass = 'error-bar';
+ if (!this.props.isLoggedIn && this.state.type === BAR_ANNOUNCEMENT_TYPE) {
+ return <div/>;
+ }
- if (this.state.type === BAR_DEVELOPER_TYPE) {
+ let errClass = 'error-bar';
+ let dismissClass = ' error-bar--fixed';
+ const barStyle = {};
+ const linkStyle = {};
+ if (this.state.color && this.state.textColor) {
+ barStyle.backgroundColor = this.state.color;
+ barStyle.color = this.state.textColor;
+ linkStyle.color = this.state.textColor;
+ } else if (this.state.type === BAR_DEVELOPER_TYPE) {
errClass = 'error-bar-developer';
} else if (this.state.type === BAR_CRITICAL_TYPE) {
errClass = 'error-bar-critical';
}
+ let closeButton;
+ if (this.state.allowDismissal) {
+ dismissClass = '';
+ closeButton = (
+ <a
+ href='#'
+ className='error-bar__close'
+ style={linkStyle}
+ onClick={this.handleClose}
+ >
+ {'×'}
+ </a>
+ );
+ }
+
const renewalLink = RENEWAL_LINK + '?id=' + global.window.mm_license.Id + '&user_count=' + this.state.totalUsers;
let message = this.state.message;
@@ -217,15 +296,12 @@ export default class ErrorBar extends React.Component {
}
return (
- <div className={errClass}>
+ <div
+ className={errClass + dismissClass}
+ style={barStyle}
+ >
<span>{message}</span>
- <a
- href='#'
- className='error-bar__close'
- onClick={this.handleClose}
- >
- {'×'}
- </a>
+ {closeButton}
</div>
);
}
diff --git a/webapp/components/announcement_bar/index.js b/webapp/components/announcement_bar/index.js
new file mode 100644
index 000000000..8fe4f56b4
--- /dev/null
+++ b/webapp/components/announcement_bar/index.js
@@ -0,0 +1,16 @@
+// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {connect} from 'react-redux';
+import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
+
+import AnnouncementBar from './announcement_bar.jsx';
+
+function mapStateToProps(state, ownProps) {
+ return {
+ ...ownProps,
+ isLoggedIn: Boolean(getCurrentUserId(state))
+ };
+}
+
+export default connect(mapStateToProps)(AnnouncementBar);
diff --git a/webapp/components/backstage/backstage_controller.jsx b/webapp/components/backstage/backstage_controller.jsx
index 4619641b1..795fb0e95 100644
--- a/webapp/components/backstage/backstage_controller.jsx
+++ b/webapp/components/backstage/backstage_controller.jsx
@@ -10,7 +10,7 @@ import UserStore from 'stores/user_store.jsx';
import BackstageSidebar from './components/backstage_sidebar.jsx';
import BackstageNavbar from './components/backstage_navbar.jsx';
-import ErrorBar from 'components/error_bar.jsx';
+import AnnouncementBar from 'components/announcement_bar';
export default class BackstageController extends React.Component {
static get propTypes() {
@@ -55,7 +55,7 @@ export default class BackstageController extends React.Component {
render() {
return (
<div className='backstage'>
- <ErrorBar/>
+ <AnnouncementBar/>
<BackstageNavbar team={this.state.team}/>
<div className='backstage-body'>
<BackstageSidebar
diff --git a/webapp/components/create_team/create_team_controller.jsx b/webapp/components/create_team/create_team_controller.jsx
index e06ea9465..1e2e7dde1 100644
--- a/webapp/components/create_team/create_team_controller.jsx
+++ b/webapp/components/create_team/create_team_controller.jsx
@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import ErrorBar from 'components/error_bar.jsx';
+import AnnouncementBar from 'components/announcement_bar';
import ChannelStore from 'stores/channel_store.jsx';
import TeamStore from 'stores/team_store.jsx';
@@ -63,7 +63,7 @@ export default class CreateTeamController extends React.Component {
return (
<div>
- <ErrorBar/>
+ <AnnouncementBar/>
<div className='signup-header'>
<Link to={url}>
<span className='fa fa-chevron-left'/>
diff --git a/webapp/components/login/login_controller.jsx b/webapp/components/login/login_controller.jsx
index cef7fe435..212a09bf2 100644
--- a/webapp/components/login/login_controller.jsx
+++ b/webapp/components/login/login_controller.jsx
@@ -2,7 +2,7 @@
// See License.txt for license information.
import LoginMfa from './components/login_mfa.jsx';
-import ErrorBar from 'components/error_bar.jsx';
+import AnnouncementBar from 'components/announcement_bar';
import FormError from 'components/form_error.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
@@ -621,7 +621,7 @@ export default class LoginController extends React.Component {
return (
<div>
- <ErrorBar/>
+ <AnnouncementBar/>
<div className='col-sm-12'>
<div className={'signup-team__container ' + customClass}>
<div className='signup__markdown'>
diff --git a/webapp/components/needs_team/needs_team.jsx b/webapp/components/needs_team/needs_team.jsx
index aa3ea46e8..75ec40653 100644
--- a/webapp/components/needs_team/needs_team.jsx
+++ b/webapp/components/needs_team/needs_team.jsx
@@ -23,7 +23,7 @@ import Constants from 'utils/constants.jsx';
const TutorialSteps = Constants.TutorialSteps;
const Preferences = Constants.Preferences;
-import ErrorBar from 'components/error_bar.jsx';
+import AnnouncementBar from 'components/announcement_bar';
import SidebarRight from 'components/sidebar_right.jsx';
import SidebarRightMenu from 'components/sidebar_right_menu.jsx';
import Navbar from 'components/navbar.jsx';
@@ -211,7 +211,7 @@ export default class NeedsTeam extends React.Component {
return (
<div className='channel-view'>
- <ErrorBar/>
+ <AnnouncementBar/>
<WebrtcNotification/>
<div className='container-fluid'>
<SidebarRight channel={channel}/>
diff --git a/webapp/components/select_team/select_team.jsx b/webapp/components/select_team/select_team.jsx
index 858329bd0..fe706af0f 100644
--- a/webapp/components/select_team/select_team.jsx
+++ b/webapp/components/select_team/select_team.jsx
@@ -5,7 +5,7 @@ import UserStore from 'stores/user_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import * as UserAgent from 'utils/user_agent.jsx';
import * as Utils from 'utils/utils.jsx';
-import ErrorBar from 'components/error_bar.jsx';
+import AnnouncementBar from 'components/announcement_bar';
import LoadingScreen from 'components/loading_screen.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import SelectTeamItem from './components/select_team_item.jsx';
@@ -218,7 +218,7 @@ export default class SelectTeam extends React.Component {
}
return (
<div>
- <ErrorBar/>
+ <AnnouncementBar/>
<div className='signup-header'>
{headerButton}
</div>
diff --git a/webapp/components/signup/signup_controller.jsx b/webapp/components/signup/signup_controller.jsx
index 5a9e535a8..e9024a389 100644
--- a/webapp/components/signup/signup_controller.jsx
+++ b/webapp/components/signup/signup_controller.jsx
@@ -18,7 +18,7 @@ import {addUserToTeamFromInvite, getInviteInfo} from 'actions/team_actions.jsx';
import {loadMe} from 'actions/user_actions.jsx';
import logoImage from 'images/logo.png';
-import ErrorBar from 'components/error_bar.jsx';
+import AnnouncementBar from 'components/announcement_bar';
import {FormattedMessage} from 'react-intl';
import {browserHistory, Link} from 'react-router/es6';
@@ -319,7 +319,7 @@ export default class SignupController extends React.Component {
return (
<div>
- <ErrorBar/>
+ <AnnouncementBar/>
<div className='signup-header'>
<Link to='/'>
<span className='fa fa-chevron-left'/>