diff options
Diffstat (limited to 'webapp')
23 files changed, 594 insertions, 56 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'/> diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json index a73068360..e3bc3e027 100755 --- a/webapp/i18n/en.json +++ b/webapp/i18n/en.json @@ -319,6 +319,14 @@ "admin.general.localization.serverLocaleTitle": "Default Server Language:", "admin.general.log": "Logging", "admin.general.policy": "Policy", + "admin.general.policy.enableBannerTitle": "Enable Announcement Banner:", + "admin.general.policy.enableBannerDesc": "Enable an announcement banner across all teams.", + "admin.general.policy.bannerTextTitle": "Banner Text:", + "admin.general.policy.bannerTextDesc": "Text that will appear in the announcement banner.", + "admin.general.policy.bannerColorTitle": "Banner Color:", + "admin.general.policy.bannerTextColorTitle": "Banner Text Color:", + "admin.general.policy.allowBannerDismissalTitle": "Allow Banner Dismissal:", + "admin.general.policy.allowBannerDismissalDesc": "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.", "admin.general.policy.allowEditPostAlways": "Any time", "admin.general.policy.allowEditPostDescription": "Set policy on the length of time authors have to edit their messages after posting.", "admin.general.policy.allowEditPostNever": "Never", diff --git a/webapp/package.json b/webapp/package.json index 3b2e8792a..dc32b6d2e 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -30,6 +30,7 @@ "react": "15.5.4", "react-addons-pure-render-mixin": "15.5.2", "react-bootstrap": "0.31.0", + "react-color": "2.11.7", "react-custom-scrollbars": "4.1.2", "react-dom": "15.5.4", "react-intl": "2.3.0", diff --git a/webapp/root.jsx b/webapp/root.jsx index 6c7643f17..161eff48e 100644 --- a/webapp/root.jsx +++ b/webapp/root.jsx @@ -25,9 +25,11 @@ import 'katex/dist/katex.min.css'; import store from 'stores/redux_store.jsx'; const dispatch = store.dispatch; const getState = store.getState; +import EventEmitter from 'mattermost-redux/utils/event_emitter'; import {viewChannel} from 'mattermost-redux/actions/channels'; -import {getClientConfig, getLicenseConfig, setUrl} from 'mattermost-redux/actions/general'; +import {getClientConfig, getLicenseConfig, setUrl, setServerVersion as setServerVersionRedux} from 'mattermost-redux/actions/general'; +import {General} from 'mattermost-redux/constants'; // Import the root of our routing tree import rRoot from 'routes/route_root.jsx'; @@ -128,15 +130,19 @@ function renderRootComponent() { let serverVersion = ''; -store.subscribe(() => { - const newServerVersion = getState().entities.general.serverVersion; +EventEmitter.on(General.CONFIG_CHANGED, setServerVersion); + +function setServerVersion(newServerVersion) { if (serverVersion && serverVersion !== newServerVersion) { console.log('Detected version update refreshing the page'); //eslint-disable-line no-console window.location.reload(true); } - serverVersion = newServerVersion; -}); + if (serverVersion !== newServerVersion) { + serverVersion = newServerVersion; + setServerVersionRedux(newServerVersion)(dispatch, getState); + } +} global.window.setup_root = () => { // Do the pre-render setup and call renderRootComponent when done diff --git a/webapp/sass/base/_structure.scss b/webapp/sass/base/_structure.scss index 60673f9a2..df7d75329 100644 --- a/webapp/sass/base/_structure.scss +++ b/webapp/sass/base/_structure.scss @@ -10,6 +10,10 @@ body { height: 100%; position: relative; width: 100%; + + &.error-bar--fixed { + padding-top: 22px; + } } .sticky { diff --git a/webapp/sass/components/_error-bar.scss b/webapp/sass/components/_error-bar.scss index c2ad2402a..765b61ba8 100644 --- a/webapp/sass/components/_error-bar.scss +++ b/webapp/sass/components/_error-bar.scss @@ -4,16 +4,33 @@ background-color: darken($primary-color, 5%); color: $white; padding: 5px 30px; - position: absolute; + position: fixed; text-align: center; top: 0; width: 100%; z-index: 9999; + &.error-bar--fixed { + overflow: hidden; + padding: 1px 30px; + text-overflow: ellipsis; + white-space: nowrap; + } + a { - color: $white !important; + color: $white; text-decoration: underline; + .app__body & { + color: $white; + + &:hover, + &:active, + &:focus { + color: $white; + } + } + &.error-bar__close { color: $white; font-family: 'Open Sans', sans-serif; diff --git a/webapp/sass/layout/_sidebar-left.scss b/webapp/sass/layout/_sidebar-left.scss index d08a9ef45..abe49b9c6 100644 --- a/webapp/sass/layout/_sidebar-left.scss +++ b/webapp/sass/layout/_sidebar-left.scss @@ -8,6 +8,10 @@ width: 220px; z-index: 5; + .error-bar--fixed & { + height: calc(100% - 22px); + } + &.sidebar--padded { padding-top: 44px; } diff --git a/webapp/sass/layout/_sidebar-right.scss b/webapp/sass/layout/_sidebar-right.scss index a1698b3a1..ab73e075b 100644 --- a/webapp/sass/layout/_sidebar-right.scss +++ b/webapp/sass/layout/_sidebar-right.scss @@ -10,6 +10,10 @@ width: 400px; z-index: 8; + .error-bar--fixed & { + height: calc(100% - 22px); + } + &.webrtc { z-index: 5; } @@ -85,8 +89,8 @@ @include opacity(.55); position: absolute; top: -25px; - } - } + } + } } .help__format-text { diff --git a/webapp/sass/routes/_admin-console.scss b/webapp/sass/routes/_admin-console.scss index aece76b28..26e93ec3d 100644 --- a/webapp/sass/routes/_admin-console.scss +++ b/webapp/sass/routes/_admin-console.scss @@ -12,6 +12,13 @@ overflow: auto; padding: 0 20px; + .color-picker__popover { + position: absolute; + right: 0; + top: 40px; + z-index: 5; + } + .dropdown-menu { .divider { @include opacity(1); diff --git a/webapp/tests/components/admin_console/__snapshots__/color_setting.test.jsx.snap b/webapp/tests/components/admin_console/__snapshots__/color_setting.test.jsx.snap new file mode 100644 index 000000000..7b8c934ce --- /dev/null +++ b/webapp/tests/components/admin_console/__snapshots__/color_setting.test.jsx.snap @@ -0,0 +1,95 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`components/ColorSetting should match snapshot, all 1`] = ` +<Settings + helpText="helptext" + inputId="id" + label="label" +> + <div + className="input-group color-picker colorpicker-element" + > + <input + className="form-control" + disabled={false} + onChange={[Function]} + type="text" + value="#fff" + /> + <span + className="input-group-addon picker-id" + onClick={[Function]} + > + <i + style={ + Object { + "backgroundColor": "#fff", + } + } + /> + </span> + </div> +</Settings> +`; + +exports[`components/ColorSetting should match snapshot, disabled 1`] = ` +<Settings + inputId="id" + label="label" +> + <div + className="input-group color-picker colorpicker-element" + > + <input + className="form-control" + disabled={true} + onChange={[Function]} + type="text" + value="#fff" + /> + <span + className="input-group-addon picker-id" + onClick={[Function]} + > + <i + style={ + Object { + "backgroundColor": "#fff", + } + } + /> + </span> + </div> +</Settings> +`; + +exports[`components/ColorSetting should match snapshot, no help text 1`] = ` +<Settings + inputId="id" + label="label" +> + <div + className="input-group color-picker colorpicker-element" + > + <input + className="form-control" + disabled={false} + onChange={[Function]} + type="text" + value="#fff" + /> + <span + className="input-group-addon picker-id" + onClick={[Function]} + > + <i + style={ + Object { + "backgroundColor": "#fff", + } + } + /> + </span> + </div> +</Settings> +`; diff --git a/webapp/tests/components/admin_console/color_setting.test.jsx b/webapp/tests/components/admin_console/color_setting.test.jsx new file mode 100644 index 000000000..a1c44a037 --- /dev/null +++ b/webapp/tests/components/admin_console/color_setting.test.jsx @@ -0,0 +1,55 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; +import {shallow} from 'enzyme'; + +import ColorSetting from 'components/admin_console/color_setting.jsx'; + +describe('components/ColorSetting', () => { + test('should match snapshot, all', () => { + function emptyFunction() {} //eslint-disable-line no-empty-function + + const wrapper = shallow( + <ColorSetting + id='id' + label='label' + helpText='helptext' + value='#fff' + onChange={emptyFunction} + disabled={false} + /> + ); + expect(wrapper).toMatchSnapshot(); + }); + + test('should match snapshot, no help text', () => { + function emptyFunction() {} //eslint-disable-line no-empty-function + + const wrapper = shallow( + <ColorSetting + id='id' + label='label' + value='#fff' + onChange={emptyFunction} + disabled={false} + /> + ); + expect(wrapper).toMatchSnapshot(); + }); + + test('should match snapshot, disabled', () => { + function emptyFunction() {} //eslint-disable-line no-empty-function + + const wrapper = shallow( + <ColorSetting + id='id' + label='label' + value='#fff' + onChange={emptyFunction} + disabled={true} + /> + ); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/webapp/utils/utils.jsx b/webapp/utils/utils.jsx index e4aa48a59..469f793c8 100644 --- a/webapp/utils/utils.jsx +++ b/webapp/utils/utils.jsx @@ -1348,3 +1348,16 @@ export function isEmptyObject(object) { export function updateWindowDimensions(component) { component.setState({width: window.innerWidth, height: window.innerHeight}); } + +export function removePrefixFromLocalStorage(prefix) { + const keys = []; + for (let i = 0; i < localStorage.length; i++) { + if (localStorage.key(i).startsWith(prefix)) { + keys.push(localStorage.key(i)); + } + } + + for (let i = 0; i < keys.length; i++) { + localStorage.removeItem(keys[i]); + } +} diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 703622450..8660bed5e 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -4792,7 +4792,7 @@ lodash.unset@^4.5.2: version "4.5.2" resolved "https://registry.yarnpkg.com/lodash.unset/-/lodash.unset-4.5.2.tgz#370d1d3e85b72a7e1b0cdf2d272121306f23e4ed" -lodash@^4.0.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.8.0: +lodash@^4.0.0, lodash@^4.0.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.8.0: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" @@ -4874,13 +4874,17 @@ match-at@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/match-at/-/match-at-0.1.0.tgz#f561e7709ff9a105b85cc62c6b8ee7c15bf24f31" +material-colors@^1.2.1: + version "1.2.5" + resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.5.tgz#5292593e6754cb1bcc2b98030e4e0d6a3afc9ea1" + math-expression-evaluator@^1.2.14: version "1.2.16" resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.16.tgz#b357fa1ca9faefb8e48d10c14ef2bcb2d9f0a7c9" mattermost-redux@mattermost/mattermost-redux#webapp-master: version "0.0.1" - resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/d1652dc7b636aae658d0d109919b6a74762a186d" + resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/dfa584b61ed9d04c167be53fe16ac6432892ece1" dependencies: deep-equal "1.0.1" harmony-reflect "1.5.1" @@ -6044,6 +6048,16 @@ react-bootstrap@0.31.0: uncontrollable "^4.1.0" warning "^3.0.0" +react-color@^2.11.7: + version "2.11.7" + resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.11.7.tgz#746465b75feda63c2567607dfbcb276fc954a5b7" + dependencies: + lodash "^4.0.1" + material-colors "^1.2.1" + prop-types "^15.5.4" + reactcss "^1.2.0" + tinycolor2 "^1.1.2" + react-custom-scrollbars@4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/react-custom-scrollbars/-/react-custom-scrollbars-4.1.2.tgz#0e60c4a46c4a61f9e4994a7663e2b9cbbc5187a3" @@ -6137,6 +6151,12 @@ react@15.5.4: object-assign "^4.1.0" prop-types "^15.5.7" +reactcss@^1.2.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/reactcss/-/reactcss-1.2.2.tgz#41b0ef43e01d54880357c34b11ac1531209350ef" + dependencies: + lodash "^4.0.1" + read-all-stream@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/read-all-stream/-/read-all-stream-3.1.0.tgz#35c3e177f2078ef789ee4bfafa4373074eaef4fa" @@ -6280,7 +6300,7 @@ redux-persist-transform-filter@0.0.9: lodash.set "^4.3.2" lodash.unset "^4.5.2" -redux-persist@4.6.0, redux-persist@^4.5.0: +redux-persist@4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-4.6.0.tgz#3994793d5f2f38bf02591c9e693e16bf8eae2728" dependencies: @@ -6288,7 +6308,7 @@ redux-persist@4.6.0, redux-persist@^4.5.0: lodash "^4.17.4" lodash-es "^4.17.4" -redux-persist@4.8.0: +redux-persist@4.8.0, redux-persist@^4.5.0: version "4.8.0" resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-4.8.0.tgz#17fd998949bdeef9275e4cf60ad5bbe1c73675fc" dependencies: @@ -7257,6 +7277,10 @@ timers-browserify@^2.0.2: dependencies: setimmediate "^1.0.4" +tinycolor2@^1.1.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.1.tgz#f4fad333447bc0b07d4dc8e9209d8f39a8ac77e8" + tmp@^0.0.29: version "0.0.29" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.29.tgz#f25125ff0dd9da3ccb0c2dd371ee1288bb9128c0" |