summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoramWilander <jwawilander@gmail.com>2015-09-23 10:12:40 -0400
committerJoramWilander <jwawilander@gmail.com>2015-09-23 10:12:40 -0400
commite4a15076f458be1416de25b2c45578975b914de5 (patch)
tree0c8a91d17ee2546ee9de969ede2e979c5a4f5a52
parent0391e5fe96d922f1e4a6ca2418a871ba1e88c9d2 (diff)
downloadchat-e4a15076f458be1416de25b2c45578975b914de5.tar.gz
chat-e4a15076f458be1416de25b2c45578975b914de5.tar.bz2
chat-e4a15076f458be1416de25b2c45578975b914de5.zip
Implement UI theme colors.
-rw-r--r--api/user.go3
-rw-r--r--model/user.go5
-rw-r--r--store/sql_user_store.go2
-rw-r--r--web/react/components/channel_loader.jsx24
-rw-r--r--web/react/components/popover_list_members.jsx2
-rw-r--r--web/react/components/register_app_modal.jsx2
-rw-r--r--web/react/components/rhs_header_post.jsx1
-rw-r--r--web/react/components/search_results_header.jsx1
-rw-r--r--web/react/components/user_settings/custom_theme_chooser.jsx108
-rw-r--r--web/react/components/user_settings/import_theme_modal.jsx179
-rw-r--r--web/react/components/user_settings/premade_theme_chooser.jsx55
-rw-r--r--web/react/components/user_settings/user_settings_appearance.jsx261
-rw-r--r--web/react/components/user_settings/user_settings_developer.jsx2
-rw-r--r--web/react/components/user_settings/user_settings_modal.jsx2
-rw-r--r--web/react/package.json18
-rw-r--r--web/react/pages/channel.jsx6
-rw-r--r--web/react/stores/user_store.jsx16
-rw-r--r--web/react/utils/constants.jsx139
-rw-r--r--web/react/utils/utils.jsx188
-rw-r--r--web/sass-files/sass/partials/_base.scss7
-rw-r--r--web/sass-files/sass/partials/_colorpicker.scss251
-rw-r--r--web/sass-files/sass/partials/_headers.scss4
-rw-r--r--web/sass-files/sass/partials/_modal.scss10
-rw-r--r--web/sass-files/sass/partials/_post.scss2
-rw-r--r--web/sass-files/sass/partials/_search.scss1
-rw-r--r--web/sass-files/sass/partials/_settings.scss30
-rw-r--r--web/sass-files/sass/partials/_sidebar--left.scss6
-rw-r--r--web/sass-files/sass/partials/_sidebar--right.scss11
-rw-r--r--web/sass-files/sass/styles.scss1
-rwxr-xr-xweb/static/css/bootstrap-colorpicker.min.css9
-rwxr-xr-xweb/static/images/bootstrap-colorpicker/alpha-horizontal.pngbin0 -> 3635 bytes
-rwxr-xr-xweb/static/images/bootstrap-colorpicker/alpha.pngbin0 -> 3271 bytes
-rwxr-xr-xweb/static/images/bootstrap-colorpicker/hue-horizontal.pngbin0 -> 2837 bytes
-rwxr-xr-xweb/static/images/bootstrap-colorpicker/hue.pngbin0 -> 2972 bytes
-rwxr-xr-xweb/static/images/bootstrap-colorpicker/saturation.pngbin0 -> 8817 bytes
-rw-r--r--web/static/images/themes/dark.pngbin0 -> 75371 bytes
-rw-r--r--web/static/images/themes/mattermost.pngbin0 -> 66828 bytes
-rw-r--r--web/static/images/themes/slack.pngbin0 -> 68603 bytes
-rwxr-xr-xweb/static/js/bootstrap-colorpicker.min.js1
-rw-r--r--web/templates/channel.html1
-rw-r--r--web/templates/head.html2
41 files changed, 1161 insertions, 189 deletions
diff --git a/api/user.go b/api/user.go
index 0a54b6a5d..2740696e1 100644
--- a/api/user.go
+++ b/api/user.go
@@ -177,9 +177,6 @@ func CreateUser(c *Context, team *model.Team, user *model.User) *model.User {
}
user.MakeNonNil()
- if len(user.Props["theme"]) == 0 {
- user.AddProp("theme", utils.Cfg.TeamSettings.DefaultThemeColor)
- }
if result := <-Srv.Store.User().Save(user); result.Err != nil {
c.Err = result.Err
diff --git a/model/user.go b/model/user.go
index fdc519b99..3a2c9d56c 100644
--- a/model/user.go
+++ b/model/user.go
@@ -47,6 +47,7 @@ type User struct {
AllowMarketing bool `json:"allow_marketing"`
Props StringMap `json:"props"`
NotifyProps StringMap `json:"notify_props"`
+ ThemeProps StringMap `json:"theme_props"`
LastPasswordUpdate int64 `json:"last_password_update"`
LastPictureUpdate int64 `json:"last_picture_update"`
FailedAttempts int `json:"failed_attempts"`
@@ -108,6 +109,10 @@ func (u *User) IsValid() *AppError {
return NewAppError("User.IsValid", "Invalid user, password and auth data cannot both be set", "user_id="+u.Id)
}
+ if len(u.ThemeProps) > 2000 {
+ return NewAppError("User.IsValid", "Invalid theme", "user_id="+u.Id)
+ }
+
return nil
}
diff --git a/store/sql_user_store.go b/store/sql_user_store.go
index 778df367e..3fd1c82b5 100644
--- a/store/sql_user_store.go
+++ b/store/sql_user_store.go
@@ -32,6 +32,7 @@ func NewSqlUserStore(sqlStore *SqlStore) UserStore {
table.ColMap("Roles").SetMaxSize(64)
table.ColMap("Props").SetMaxSize(4000)
table.ColMap("NotifyProps").SetMaxSize(2000)
+ table.ColMap("ThemeProps").SetMaxSize(2000)
table.SetUniqueTogether("Email", "TeamId")
table.SetUniqueTogether("Username", "TeamId")
}
@@ -40,6 +41,7 @@ func NewSqlUserStore(sqlStore *SqlStore) UserStore {
}
func (us SqlUserStore) UpgradeSchemaIfNeeded() {
+ us.CreateColumnIfNotExists("Users", "ThemeProps", "varchar(2000)", "character varying(2000)", "{}")
}
func (us SqlUserStore) CreateIndexesIfNotExists() {
diff --git a/web/react/components/channel_loader.jsx b/web/react/components/channel_loader.jsx
index ce6f60f87..1261b957b 100644
--- a/web/react/components/channel_loader.jsx
+++ b/web/react/components/channel_loader.jsx
@@ -12,6 +12,7 @@ var PostStore = require('../stores/post_store.jsx');
var UserStore = require('../stores/user_store.jsx');
var Utils = require('../utils/utils.jsx');
+var Constants = require('../utils/constants.jsx');
export default class ChannelLoader extends React.Component {
constructor(props) {
@@ -68,25 +69,10 @@ export default class ChannelLoader extends React.Component {
/* Update CSS classes to match user theme */
var user = UserStore.getCurrentUser();
- if (user.props && user.props.theme) {
- Utils.changeCss('div.theme', 'background-color:' + user.props.theme + ';');
- Utils.changeCss('.btn.btn-primary', 'background: ' + user.props.theme + ';');
- Utils.changeCss('.modal .modal-header', 'background: ' + user.props.theme + ';');
- Utils.changeCss('.mention', 'background: ' + user.props.theme + ';');
- Utils.changeCss('.mention-link', 'color: ' + user.props.theme + ';');
- Utils.changeCss('@media(max-width: 768px){.search-bar__container', 'background: ' + user.props.theme + ';}');
- Utils.changeCss('.search-item-container:hover', 'background: ' + Utils.changeOpacity(user.props.theme, 0.05) + ';');
- }
-
- if (user.props.theme !== '#000000' && user.props.theme !== '#585858') {
- Utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + Utils.changeColor(user.props.theme, -10) + ';');
- Utils.changeCss('a.theme', 'color:' + user.props.theme + '; fill:' + user.props.theme + '!important;');
- } else if (user.props.theme === '#000000') {
- Utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + Utils.changeColor(user.props.theme, +50) + ';');
- $('.team__header').addClass('theme--black');
- } else if (user.props.theme === '#585858') {
- Utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + Utils.changeColor(user.props.theme, +10) + ';');
- $('.team__header').addClass('theme--gray');
+ if ($.isPlainObject(user.theme_props) && !$.isEmptyObject(user.theme_props)) {
+ Utils.applyTheme(user.theme_props);
+ } else {
+ Utils.applyTheme(Constants.THEMES.default);
}
/* Setup global mouse events */
diff --git a/web/react/components/popover_list_members.jsx b/web/react/components/popover_list_members.jsx
index ec873dd00..a2ca8b00f 100644
--- a/web/react/components/popover_list_members.jsx
+++ b/web/react/components/popover_list_members.jsx
@@ -65,7 +65,7 @@ export default class PopoverListMembers extends React.Component {
>
{count}
<span
- className='glyphicon glyphicon-user'
+ className='fa fa-user'
aria-hidden='true'
/>
</div>
diff --git a/web/react/components/register_app_modal.jsx b/web/react/components/register_app_modal.jsx
index 3dd5c094e..473ff3f91 100644
--- a/web/react/components/register_app_modal.jsx
+++ b/web/react/components/register_app_modal.jsx
@@ -228,7 +228,7 @@ export default class RegisterAppModal extends React.Component {
data-dismiss='modal'
aria-label='Close'
>
- <span aria-hidden='true'>{'x'}</span>
+ <span aria-hidden='true'>{'×'}</span>
</button>
<h4
className='modal-title'
diff --git a/web/react/components/rhs_header_post.jsx b/web/react/components/rhs_header_post.jsx
index 5156ec4d7..f55c4095e 100644
--- a/web/react/components/rhs_header_post.jsx
+++ b/web/react/components/rhs_header_post.jsx
@@ -65,6 +65,7 @@ export default class RhsHeaderPost extends React.Component {
aria-label='Close'
onClick={this.handleClose}
>
+ <i className='fa fa-sign-out'/>
</button>
</div>
);
diff --git a/web/react/components/search_results_header.jsx b/web/react/components/search_results_header.jsx
index 694f0c55d..4e8a3ef10 100644
--- a/web/react/components/search_results_header.jsx
+++ b/web/react/components/search_results_header.jsx
@@ -50,6 +50,7 @@ export default class SearchResultsHeader extends React.Component {
title='Close'
onClick={this.handleClose}
>
+ <i className='fa fa-sign-out'/>
</button>
</div>
);
diff --git a/web/react/components/user_settings/custom_theme_chooser.jsx b/web/react/components/user_settings/custom_theme_chooser.jsx
new file mode 100644
index 000000000..44630a318
--- /dev/null
+++ b/web/react/components/user_settings/custom_theme_chooser.jsx
@@ -0,0 +1,108 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var Constants = require('../../utils/constants.jsx');
+
+export default class CustomThemeChooser extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.onPickerChange = this.onPickerChange.bind(this);
+ this.onInputChange = this.onInputChange.bind(this);
+ this.pasteBoxChange = this.pasteBoxChange.bind(this);
+
+ this.state = {};
+ }
+ componentDidMount() {
+ $('.color-picker').colorpicker().on('changeColor', this.onPickerChange);
+ }
+ onPickerChange(e) {
+ const theme = this.props.theme;
+ theme[e.target.id] = e.color.toHex();
+ theme.type = 'custom';
+ this.props.updateTheme(theme);
+ }
+ onInputChange(e) {
+ const theme = this.props.theme;
+ theme[e.target.parentNode.id] = e.target.value;
+ theme.type = 'custom';
+ this.props.updateTheme(theme);
+ }
+ pasteBoxChange(e) {
+ const text = e.target.value;
+
+ if (text.length === 0) {
+ return;
+ }
+
+ const colors = text.split(',');
+
+ const theme = {type: 'custom'};
+ let index = 0;
+ Constants.THEME_ELEMENTS.forEach((element) => {
+ if (index < colors.length) {
+ theme[element.id] = colors[index];
+ }
+ index++;
+ });
+
+ this.props.updateTheme(theme);
+ }
+ render() {
+ const theme = this.props.theme;
+
+ const elements = [];
+ let colors = '';
+ Constants.THEME_ELEMENTS.forEach((element) => {
+ elements.push(
+ <div className='col-sm-4 form-group'>
+ <label className='custom-label'>{element.uiName}</label>
+ <div
+ className='input-group color-picker'
+ id={element.id}
+ >
+ <input
+ className='form-control'
+ type='text'
+ defaultValue={theme[element.id]}
+ onChange={this.onInputChange}
+ />
+ <span className='input-group-addon'><i></i></span>
+ </div>
+ </div>
+ );
+
+ colors += theme[element.id] + ',';
+ });
+
+ const pasteBox = (
+ <div className='col-sm-12'>
+ <label className='custom-label'>
+ {'Copy and paste to share theme colors:'}
+ </label>
+ <input
+ type='text'
+ className='form-control'
+ value={colors}
+ onChange={this.pasteBoxChange}
+ />
+ </div>
+ );
+
+ return (
+ <div>
+ <div className='row form-group'>
+ {elements}
+ </div>
+ <div className='row'>
+ {pasteBox}
+ </div>
+ </div>
+ );
+ }
+}
+
+CustomThemeChooser.propTypes = {
+ theme: React.PropTypes.object.isRequired,
+ updateTheme: React.PropTypes.func.isRequired
+};
diff --git a/web/react/components/user_settings/import_theme_modal.jsx b/web/react/components/user_settings/import_theme_modal.jsx
new file mode 100644
index 000000000..48be83afe
--- /dev/null
+++ b/web/react/components/user_settings/import_theme_modal.jsx
@@ -0,0 +1,179 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+const UserStore = require('../../stores/user_store.jsx');
+const Utils = require('../../utils/utils.jsx');
+const Client = require('../../utils/client.jsx');
+const Modal = ReactBootstrap.Modal;
+
+const AppDispatcher = require('../../dispatcher/app_dispatcher.jsx');
+const Constants = require('../../utils/constants.jsx');
+const ActionTypes = Constants.ActionTypes;
+
+export default class ImportThemeModal extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.updateShow = this.updateShow.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+
+ this.state = {
+ inputError: '',
+ show: false
+ };
+ }
+ componentDidMount() {
+ UserStore.addImportModalListener(this.updateShow);
+ }
+ componentWillUnmount() {
+ UserStore.removeImportModalListener(this.updateShow);
+ }
+ updateShow(show) {
+ this.setState({show});
+ }
+ handleSubmit(e) {
+ e.preventDefault();
+
+ const text = React.findDOMNode(this.refs.input).value;
+
+ if (!this.isInputValid(text)) {
+ this.setState({inputError: 'Invalid format, please try copying and pasting in again.'});
+ return;
+ }
+
+ const colors = text.split(',');
+ const theme = {type: 'custom'};
+
+ theme.sidebarBg = colors[0];
+ theme.sidebarText = colors[5];
+ theme.sidebarUnreadText = colors[5];
+ theme.sidebarTextHoverBg = colors[4];
+ theme.sidebarTextHoverColor = colors[5];
+ theme.sidebarTextActiveBg = colors[2];
+ theme.sidebarTextActiveColor = colors[3];
+ theme.sidebarHeaderBg = colors[1];
+ theme.sidebarHeaderTextColor = colors[5];
+ theme.onlineIndicator = colors[6];
+ theme.mentionBj = colors[7];
+ theme.mentionColor = '#ffffff';
+ theme.centerChannelBg = '#ffffff';
+ theme.centerChannelColor = '#333333';
+ theme.linkColor = '#2389d7';
+ theme.buttonBg = '#26a970';
+ theme.buttonColor = '#ffffff';
+
+ let user = UserStore.getCurrentUser();
+ user.theme_props = theme;
+
+ Client.updateUser(user,
+ (data) => {
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECIEVED_ME,
+ me: data
+ });
+
+ this.setState({show: false});
+ Utils.applyTheme(theme);
+ $('#user_settings').modal('show');
+ },
+ (err) => {
+ var state = this.getStateFromStores();
+ state.serverError = err;
+ this.setState(state);
+ }
+ );
+ }
+ isInputValid(text) {
+ if (text.length === 0) {
+ return false;
+ }
+
+ if (text.indexOf(' ') !== -1) {
+ return false;
+ }
+
+ if (text.length > 0 && text.indexOf(',') === -1) {
+ return false;
+ }
+
+ if (text.length > 0) {
+ const colors = text.split(',');
+
+ if (colors.length !== 8) {
+ return false;
+ }
+
+ for (let i = 0; i < colors.length; i++) {
+ if (colors[i].length !== 7 && colors[i].length !== 4) {
+ return false;
+ }
+
+ if (colors[i].charAt(0) !== '#') {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+ handleChange(e) {
+ if (this.isInputValid(e.target.value)) {
+ this.setState({inputError: null});
+ } else {
+ this.setState({inputError: 'Invalid format, please try copying and pasting in again.'});
+ }
+ }
+ render() {
+ return (
+ <span>
+ <Modal
+ show={this.state.show}
+ onHide={() => this.setState({show: false})}
+ >
+ <Modal.Header closeButton={true}>
+ <Modal.Title>{'Import Slack Theme'}</Modal.Title>
+ </Modal.Header>
+ <form
+ role='form'
+ className='form-horizontal'
+ >
+ <Modal.Body>
+ <p>
+ {'To import a theme, go to a Slack team and look for “”Preferences” -> Sidebar Theme”. Open the custom theme option, copy the theme color values and paste them here:'}
+ </p>
+ <div className='form-group less'>
+ <div className='col-sm-9'>
+ <input
+ ref='input'
+ type='text'
+ className='form-control'
+ onChange={this.handleChange}
+ />
+ {this.state.inputError}
+ </div>
+ </div>
+ </Modal.Body>
+ <Modal.Footer>
+ <button
+ type='button'
+ className='btn btn-default'
+ onClick={() => this.setState({show: false})}
+ >
+ {'Cancel'}
+ </button>
+ <button
+ onClick={this.handleSubmit}
+ type='submit'
+ className='btn btn-primary'
+ tabIndex='3'
+ >
+ {'Submit'}
+ </button>
+ </Modal.Footer>
+ </form>
+ </Modal>
+ </span>
+ );
+ }
+}
diff --git a/web/react/components/user_settings/premade_theme_chooser.jsx b/web/react/components/user_settings/premade_theme_chooser.jsx
new file mode 100644
index 000000000..e36503053
--- /dev/null
+++ b/web/react/components/user_settings/premade_theme_chooser.jsx
@@ -0,0 +1,55 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var Utils = require('../../utils/utils.jsx');
+var Constants = require('../../utils/constants.jsx');
+
+export default class PremadeThemeChooser extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {};
+ }
+ render() {
+ const theme = this.props.theme;
+
+ const premadeThemes = [];
+ for (const k in Constants.THEMES) {
+ if (Constants.THEMES.hasOwnProperty(k)) {
+ const premadeTheme = $.extend(true, {}, Constants.THEMES[k]);
+
+ let activeClass = '';
+ if (premadeTheme.type === theme.type) {
+ activeClass = 'active';
+ }
+
+ premadeThemes.push(
+ <div className='col-sm-3 premade-themes'>
+ <div
+ className={activeClass}
+ onClick={() => this.props.updateTheme(premadeTheme)}
+ >
+ <label>
+ <img
+ className='img-responsive'
+ src={'/static/images/themes/' + premadeTheme.type + '.png'}
+ />
+ <div className='theme-label'>{Utils.toTitleCase(premadeTheme.type)}</div>
+ </label>
+ </div>
+ </div>
+ );
+ }
+ }
+
+ return (
+ <div className='row'>
+ {premadeThemes}
+ </div>
+ );
+ }
+}
+
+PremadeThemeChooser.propTypes = {
+ theme: React.PropTypes.object.isRequired,
+ updateTheme: React.PropTypes.func.isRequired
+};
diff --git a/web/react/components/user_settings/user_settings_appearance.jsx b/web/react/components/user_settings/user_settings_appearance.jsx
index aec3b319d..7617f04d1 100644
--- a/web/react/components/user_settings/user_settings_appearance.jsx
+++ b/web/react/components/user_settings/user_settings_appearance.jsx
@@ -2,78 +2,119 @@
// See License.txt for license information.
var UserStore = require('../../stores/user_store.jsx');
-var SettingItemMin = require('../setting_item_min.jsx');
-var SettingItemMax = require('../setting_item_max.jsx');
var Client = require('../../utils/client.jsx');
var Utils = require('../../utils/utils.jsx');
-var ThemeColors = ['#2389d7', '#008a17', '#dc4fad', '#ac193d', '#0072c6', '#d24726', '#ff8f32', '#82ba00', '#03b3b2', '#008299', '#4617b4', '#8c0095', '#004b8b', '#004b8b', '#570000', '#380000', '#585858', '#000000'];
+const CustomThemeChooser = require('./custom_theme_chooser.jsx');
+const PremadeThemeChooser = require('./premade_theme_chooser.jsx');
+const AppDispatcher = require('../../dispatcher/app_dispatcher.jsx');
+const Constants = require('../../utils/constants.jsx');
+const ActionTypes = Constants.ActionTypes;
export default class UserSettingsAppearance extends React.Component {
constructor(props) {
super(props);
+ this.onChange = this.onChange.bind(this);
this.submitTheme = this.submitTheme.bind(this);
this.updateTheme = this.updateTheme.bind(this);
this.handleClose = this.handleClose.bind(this);
+ this.handleImportModal = this.handleImportModal.bind(this);
this.state = this.getStateFromStores();
+
+ this.originalTheme = this.state.theme;
+ }
+ componentDidMount() {
+ UserStore.addChangeListener(this.onChange);
+
+ if (this.props.activeSection === 'theme') {
+ $(React.findDOMNode(this.refs[this.state.theme])).addClass('active-border');
+ }
+ $('#user_settings').on('hidden.bs.modal', this.handleClose);
+ }
+ componentDidUpdate() {
+ if (this.props.activeSection === 'theme') {
+ $('.color-btn').removeClass('active-border');
+ $(React.findDOMNode(this.refs[this.state.theme])).addClass('active-border');
+ }
+ }
+ componentWillUnmount() {
+ UserStore.removeChangeListener(this.onChange);
+ $('#user_settings').off('hidden.bs.modal', this.handleClose);
}
getStateFromStores() {
- var user = UserStore.getCurrentUser();
- var theme = '#2389d7';
- if (ThemeColors != null) {
- theme = ThemeColors[0];
+ const user = UserStore.getCurrentUser();
+ let theme = null;
+
+ if ($.isPlainObject(user.theme_props) && !$.isEmptyObject(user.theme_props)) {
+ theme = user.theme_props;
+ } else {
+ theme = $.extend(true, {}, Constants.THEMES.default);
}
- if (user.props && user.props.theme) {
- theme = user.props.theme;
+
+ let type = 'premade';
+ if (theme.type === 'custom') {
+ type = 'custom';
}
- return {theme: theme.toLowerCase()};
+ return {theme, type};
+ }
+ onChange() {
+ const newState = this.getStateFromStores();
+
+ if (!Utils.areStatesEqual(this.state, newState)) {
+ this.setState(newState);
+ }
}
submitTheme(e) {
e.preventDefault();
var user = UserStore.getCurrentUser();
- if (!user.props) {
- user.props = {};
- }
- user.props.theme = this.state.theme;
+ user.theme_props = this.state.theme;
Client.updateUser(user,
- function success() {
- this.props.updateSection('');
- window.location.reload();
- }.bind(this),
- function fail(err) {
+ (data) => {
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECIEVED_ME,
+ me: data
+ });
+
+ $('#user_settings').off('hidden.bs.modal', this.handleClose);
+ this.props.updateTab('general');
+ $('#user_settings').modal('hide');
+ },
+ (err) => {
var state = this.getStateFromStores();
state.serverError = err;
this.setState(state);
- }.bind(this)
+ }
);
}
- updateTheme(e) {
- var hex = Utils.rgb2hex(e.target.style.backgroundColor);
- this.setState({theme: hex.toLowerCase()});
+ updateTheme(theme) {
+ this.setState({theme});
+ Utils.applyTheme(theme);
}
- handleClose() {
- this.setState({serverError: null});
- this.props.updateTab('general');
+ updateType(type) {
+ this.setState({type});
}
- componentDidMount() {
- if (this.props.activeSection === 'theme') {
- $(React.findDOMNode(this.refs[this.state.theme])).addClass('active-border');
- }
- $('#user_settings').on('hidden.bs.modal', this.handleClose);
- }
- componentDidUpdate() {
- if (this.props.activeSection === 'theme') {
- $('.color-btn').removeClass('active-border');
- $(React.findDOMNode(this.refs[this.state.theme])).addClass('active-border');
- }
+ handleClose() {
+ const state = this.getStateFromStores();
+ state.serverError = null;
+
+ Utils.applyTheme(state.theme);
+
+ this.setState(state);
+
+ $('.ps-container.modal-body').scrollTop(0);
+ $('.ps-container.modal-body').perfectScrollbar('update');
+ $('#user_settings').modal('hide');
}
- componentWillUnmount() {
- $('#user_settings').off('hidden.bs.modal', this.handleClose);
- this.props.updateSection('');
+ handleImportModal() {
+ $('#user_settings').modal('hide');
+ AppDispatcher.handleViewAction({
+ type: ActionTypes.TOGGLE_IMPORT_THEME_MODAL,
+ value: true
+ });
}
render() {
var serverError;
@@ -81,67 +122,73 @@ export default class UserSettingsAppearance extends React.Component {
serverError = this.state.serverError;
}
- var themeSection;
- var self = this;
-
- if (ThemeColors != null) {
- if (this.props.activeSection === 'theme') {
- var themeButtons = [];
-
- for (var i = 0; i < ThemeColors.length; i++) {
- themeButtons.push(
- <button
- key={ThemeColors[i] + 'key' + i}
- ref={ThemeColors[i]}
- type='button'
- className='btn btn-lg color-btn'
- style={{backgroundColor: ThemeColors[i]}}
- onClick={this.updateTheme}
- />
- );
- }
-
- var inputs = [];
-
- inputs.push(
- <li
- key='themeColorSetting'
- className='setting-list-item'
- >
- <div
- className='btn-group'
- data-toggle='buttons-radio'
- >
- {themeButtons}
- </div>
- </li>
- );
-
- themeSection = (
- <SettingItemMax
- title='Theme Color'
- inputs={inputs}
- submit={this.submitTheme}
- serverError={serverError}
- updateSection={function updateSection(e) {
- self.props.updateSection('');
- e.preventDefault();
- }}
- />
- );
- } else {
- themeSection = (
- <SettingItemMin
- title='Theme Color'
- describe={this.state.theme}
- updateSection={function updateSection() {
- self.props.updateSection('theme');
- }}
- />
- );
- }
+ const displayCustom = this.state.type === 'custom';
+
+ let custom;
+ let premade;
+ if (displayCustom) {
+ custom = (
+ <CustomThemeChooser
+ theme={this.state.theme}
+ updateTheme={this.updateTheme}
+ />
+ );
+ } else {
+ premade = (
+ <PremadeThemeChooser
+ theme={this.state.theme}
+ updateTheme={this.updateTheme}
+ />
+ );
}
+ const themeUI = (
+ <div className='section-max appearance-section'>
+ <div className='col-sm-12'>
+ <div className='radio'>
+ <label>
+ <input type='radio'
+ checked={!displayCustom}
+ onChange={this.updateType.bind(this, 'premade')}
+ >
+ {'Theme Colors'}
+ </input>
+ </label>
+ <br/>
+ </div>
+ {premade}
+ <div className='radio'>
+ <label>
+ <input type='radio'
+ checked={displayCustom}
+ onChange={this.updateType.bind(this, 'custom')}
+ >
+ {'Custom Theme'}
+ </input>
+ </label>
+ <br/>
+ </div>
+ {custom}
+ <hr />
+ {serverError}
+ <a
+ className='btn btn-sm btn-primary'
+ href='#'
+ onClick={this.submitTheme}
+ >
+ {'Submit'}
+ </a>
+ <a
+ className='btn btn-sm theme'
+ href='#'
+ onClick={this.handleClose}
+ >
+ {'Cancel'}
+ </a>
+ </div>
+ </div>
+ );
+
return (
<div>
<div className='modal-header'>
@@ -151,21 +198,28 @@ export default class UserSettingsAppearance extends React.Component {
data-dismiss='modal'
aria-label='Close'
>
- <span aria-hidden='true'>&times;</span>
+ <span aria-hidden='true'>{'×'}</span>
</button>
<h4
className='modal-title'
ref='title'
>
- <i className='modal-back'></i>Appearance Settings
+ <i className='modal-back'></i>{'Appearance Settings'}
</h4>
</div>
<div className='user-settings'>
- <h3 className='tab-header'>Appearance Settings</h3>
+ <h3 className='tab-header'>{'Appearance Settings'}</h3>
<div className='divider-dark first'/>
- {themeSection}
+ {themeUI}
<div className='divider-dark'/>
</div>
+ <br/>
+ <a
+ className='theme'
+ onClick={this.handleImportModal}
+ >
+ {'Import from Slack'}
+ </a>
</div>
);
}
@@ -176,6 +230,5 @@ UserSettingsAppearance.defaultProps = {
};
UserSettingsAppearance.propTypes = {
activeSection: React.PropTypes.string,
- updateSection: React.PropTypes.func,
updateTab: React.PropTypes.func
};
diff --git a/web/react/components/user_settings/user_settings_developer.jsx b/web/react/components/user_settings/user_settings_developer.jsx
index 1694aaa79..d9fb43902 100644
--- a/web/react/components/user_settings/user_settings_developer.jsx
+++ b/web/react/components/user_settings/user_settings_developer.jsx
@@ -64,7 +64,7 @@ export default class DeveloperTab extends React.Component {
data-dismiss='modal'
aria-label='Close'
>
- <span aria-hidden='true'>{'x'}</span>
+ <span aria-hidden='true'>{'×'}</span>
</button>
<h4
className='modal-title'
diff --git a/web/react/components/user_settings/user_settings_modal.jsx b/web/react/components/user_settings/user_settings_modal.jsx
index 1b22e6045..430a7ec7c 100644
--- a/web/react/components/user_settings/user_settings_modal.jsx
+++ b/web/react/components/user_settings/user_settings_modal.jsx
@@ -60,7 +60,7 @@ export default class UserSettingsModal extends React.Component {
data-dismiss='modal'
aria-label='Close'
>
- <span aria-hidden='true'>{'x'}</span>
+ <span aria-hidden='true'>{'×'}</span>
</button>
<h4
className='modal-title'
diff --git a/web/react/package.json b/web/react/package.json
index dd7d45f8a..a9eba6c6c 100644
--- a/web/react/package.json
+++ b/web/react/package.json
@@ -4,13 +4,14 @@
"private": true,
"dependencies": {
"autolinker": "0.18.1",
+ "babel-runtime": "5.8.24",
+ "bootstrap-colorpicker": "2.2.0",
"flux": "2.1.1",
"keymirror": "0.1.1",
+ "marked": "0.3.5",
"object-assign": "3.0.0",
"react-zeroclipboard-mixin": "0.1.0",
- "twemoji": "1.4.1",
- "babel-runtime": "5.8.24",
- "marked": "0.3.5"
+ "twemoji": "1.4.1"
},
"devDependencies": {
"browserify": "11.0.1",
@@ -28,8 +29,15 @@
},
"browserify": {
"transform": [
- ["babelify", { "optional": ["runtime"] }],
- "envify"
+ [
+ "babelify",
+ {
+ "optional": [
+ "runtime"
+ ]
+ }
+ ],
+ "envify"
]
},
"jest": {
diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx
index d24fe0b98..07207c556 100644
--- a/web/react/pages/channel.jsx
+++ b/web/react/pages/channel.jsx
@@ -34,6 +34,7 @@ var ActivityLogModal = require('../components/activity_log_modal.jsx');
var RemovedFromChannelModal = require('../components/removed_from_channel_modal.jsx');
var FileUploadOverlay = require('../components/file_upload_overlay.jsx');
var RegisterAppModal = require('../components/register_app_modal.jsx');
+var ImportThemeModal = require('../components/user_settings/import_theme_modal.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
@@ -85,6 +86,11 @@ function setupChannelPage(props) {
);
React.render(
+ <ImportThemeModal />,
+ document.getElementById('import_theme_modal')
+ );
+
+ React.render(
<TeamSettingsModal teamDisplayName={props.TeamDisplayName} />,
document.getElementById('team_settings_modal')
);
diff --git a/web/react/stores/user_store.jsx b/web/react/stores/user_store.jsx
index f75c1d4c3..8842263fa 100644
--- a/web/react/stores/user_store.jsx
+++ b/web/react/stores/user_store.jsx
@@ -14,6 +14,7 @@ var CHANGE_EVENT_SESSIONS = 'change_sessions';
var CHANGE_EVENT_AUDITS = 'change_audits';
var CHANGE_EVENT_TEAMS = 'change_teams';
var CHANGE_EVENT_STATUSES = 'change_statuses';
+var TOGGLE_IMPORT_MODAL_EVENT = 'toggle_import_modal';
class UserStoreClass extends EventEmitter {
constructor() {
@@ -34,6 +35,9 @@ class UserStoreClass extends EventEmitter {
this.emitStatusesChange = this.emitStatusesChange.bind(this);
this.addStatusesChangeListener = this.addStatusesChangeListener.bind(this);
this.removeStatusesChangeListener = this.removeStatusesChangeListener.bind(this);
+ this.emitToggleImportModal = this.emitToggleImportModal.bind(this);
+ this.addImportModalListener = this.addImportModalListener.bind(this);
+ this.removeImportModalListener = this.removeImportModalListener.bind(this);
this.setCurrentId = this.setCurrentId.bind(this);
this.getCurrentId = this.getCurrentId.bind(this);
this.getCurrentUser = this.getCurrentUser.bind(this);
@@ -114,6 +118,15 @@ class UserStoreClass extends EventEmitter {
removeStatusesChangeListener(callback) {
this.removeListener(CHANGE_EVENT_STATUSES, callback);
}
+ emitToggleImportModal(value) {
+ this.emit(TOGGLE_IMPORT_MODAL_EVENT, value);
+ }
+ addImportModalListener(callback) {
+ this.on(TOGGLE_IMPORT_MODAL_EVENT, callback);
+ }
+ removeImportModalListener(callback) {
+ this.removeListener(TOGGLE_IMPORT_MODAL_EVENT, callback);
+ }
setCurrentId(id) {
this.gCurrentId = id;
if (id == null) {
@@ -321,6 +334,9 @@ UserStore.dispatchToken = AppDispatcher.register(function registry(payload) {
UserStore.pSetStatuses(action.statuses);
UserStore.emitStatusesChange();
break;
+ case ActionTypes.TOGGLE_IMPORT_THEME_MODAL:
+ UserStore.emitToggleImportModal(action.value);
+ break;
default:
}
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 03e4635b5..41e9e9ca6 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -36,7 +36,9 @@ module.exports = {
RECIEVED_CONFIG: null,
- RECIEVED_LOGS: null
+ RECIEVED_LOGS: null,
+
+ TOGGLE_IMPORT_THEME_MODAL: null
}),
PayloadSources: keyMirror({
@@ -110,5 +112,138 @@ module.exports = {
ONLINE_ICON_SVG: "<svg version='1.1' id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:cc='http://creativecommons.org/ns#' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' sodipodi:docname='TRASH_1_4.svg' inkscape:version='0.48.4 r9939' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='12px' height='12px' viewBox='0 0 12 12' enable-background='new 0 0 12 12' xml:space='preserve'><sodipodi:namedview inkscape:cy='139.7898' inkscape:cx='26.358185' inkscape:zoom='1.18' showguides='true' showgrid='false' id='namedview6' guidetolerance='10' gridtolerance='10' objecttolerance='10' borderopacity='1' bordercolor='#666666' pagecolor='#ffffff' inkscape:current-layer='Layer_1' inkscape:window-maximized='1' inkscape:window-y='-8' inkscape:window-x='-8' inkscape:window-height='705' inkscape:window-width='1366' inkscape:guide-bbox='true' inkscape:pageshadow='2' inkscape:pageopacity='0'><sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide><sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide></sodipodi:namedview><g><g><path class='online--icon' d='M6,5.487c1.371,0,2.482-1.116,2.482-2.493c0-1.378-1.111-2.495-2.482-2.495S3.518,1.616,3.518,2.994C3.518,4.371,4.629,5.487,6,5.487z M10.452,8.545c-0.101-0.829-0.36-1.968-0.726-2.541C9.475,5.606,8.5,5.5,8.5,5.5S8.43,7.521,6,7.521C3.507,7.521,3.5,5.5,3.5,5.5S2.527,5.606,2.273,6.004C1.908,6.577,1.648,7.716,1.547,8.545C1.521,8.688,1.49,9.082,1.498,9.142c0.161,1.295,2.238,2.322,4.375,2.358C5.916,11.501,5.958,11.501,6,11.501c0.043,0,0.084,0,0.127-0.001c2.076-0.026,4.214-1.063,4.375-2.358C10.509,9.082,10.471,8.696,10.452,8.545z'/></g></g></svg>",
OFFLINE_ICON_SVG: "<svg version='1.1' id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:cc='http://creativecommons.org/ns#' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' sodipodi:docname='TRASH_1_4.svg' inkscape:version='0.48.4 r9939' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='12px' height='12px' viewBox='0 0 12 12' enable-background='new 0 0 12 12' xml:space='preserve'><sodipodi:namedview inkscape:cy='139.7898' inkscape:cx='26.358185' inkscape:zoom='1.18' showguides='true' showgrid='false' id='namedview6' guidetolerance='10' gridtolerance='10' objecttolerance='10' borderopacity='1' bordercolor='#666666' pagecolor='#ffffff' inkscape:current-layer='Layer_1' inkscape:window-maximized='1' inkscape:window-y='-8' inkscape:window-x='-8' inkscape:window-height='705' inkscape:window-width='1366' inkscape:guide-bbox='true' inkscape:pageshadow='2' inkscape:pageopacity='0'><sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide><sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide></sodipodi:namedview><g><g><path fill='#cccccc' d='M6.002,7.143C5.645,7.363,5.167,7.52,4.502,7.52c-2.493,0-2.5-2.02-2.5-2.02S1.029,5.607,0.775,6.004C0.41,6.577,0.15,7.716,0.049,8.545c-0.025,0.145-0.057,0.537-0.05,0.598c0.162,1.295,2.237,2.321,4.375,2.357c0.043,0.001,0.085,0.001,0.127,0.001c0.043,0,0.084,0,0.127-0.001c1.879-0.023,3.793-0.879,4.263-2h-2.89L6.002,7.143L6.002,7.143z M4.501,5.488c1.372,0,2.483-1.117,2.483-2.494c0-1.378-1.111-2.495-2.483-2.495c-1.371,0-2.481,1.117-2.481,2.495C2.02,4.371,3.13,5.488,4.501,5.488z M7.002,6.5v2h5v-2H7.002z'/></g></g></svg>",
MENU_ICON: "<svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='4px' height='16px' viewBox='0 0 8 32' enable-background='new 0 0 8 32' xml:space='preserve'> <g> <circle cx='4' cy='4.062' r='4'/> <circle cx='4' cy='16' r='4'/> <circle cx='4' cy='28' r='4'/> </g> </svg>",
- COMMENT_ICON: "<svg version='1.1' id='Layer_2' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='15px' height='15px' viewBox='1 1.5 15 15' enable-background='new 1 1.5 15 15' xml:space='preserve'> <g> <g> <path fill='#211B1B' d='M14,1.5H3c-1.104,0-2,0.896-2,2v8c0,1.104,0.896,2,2,2h1.628l1.884,3l1.866-3H14c1.104,0,2-0.896,2-2v-8 C16,2.396,15.104,1.5,14,1.5z M15,11.5c0,0.553-0.447,1-1,1H8l-1.493,2l-1.504-1.991L5,12.5H3c-0.552,0-1-0.447-1-1v-8 c0-0.552,0.448-1,1-1h11c0.553,0,1,0.448,1,1V11.5z'/> </g> </g> </svg>"
+ COMMENT_ICON: "<svg version='1.1' id='Layer_2' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='15px' height='15px' viewBox='1 1.5 15 15' enable-background='new 1 1.5 15 15' xml:space='preserve'> <g> <g> <path fill='#211B1B' d='M14,1.5H3c-1.104,0-2,0.896-2,2v8c0,1.104,0.896,2,2,2h1.628l1.884,3l1.866-3H14c1.104,0,2-0.896,2-2v-8 C16,2.396,15.104,1.5,14,1.5z M15,11.5c0,0.553-0.447,1-1,1H8l-1.493,2l-1.504-1.991L5,12.5H3c-0.552,0-1-0.447-1-1v-8 c0-0.552,0.448-1,1-1h11c0.553,0,1,0.448,1,1V11.5z'/> </g> </g> </svg>",
+ THEMES: {
+ default: {
+ type: 'Mattermost',
+ sidebarBg: '#fafafa',
+ sidebarText: '#999999',
+ sidebarUnreadText: '#333333',
+ sidebarTextHoverBg: '#e6f2fa',
+ sidebarTextHoverColor: '#999999',
+ sidebarTextActiveBg: '#e1e1e1',
+ sidebarTextActiveColor: '#111111',
+ sidebarHeaderBg: '#2389d7',
+ sidebarHeaderTextColor: '#ffffff',
+ onlineIndicator: '#7DBE00',
+ mentionBj: '#2389d7',
+ mentionColor: '#ffffff',
+ centerChannelBg: '#ffffff',
+ centerChannelColor: '#333333',
+ linkColor: '#2389d7',
+ buttonBg: '#2389d7',
+ buttonColor: '#FFFFFF'
+ },
+ slack: {
+ type: 'Slack',
+ sidebarBg: '#4D394B',
+ sidebarText: '#ab9ba9',
+ sidebarUnreadText: '#FFFFFF',
+ sidebarTextHoverBg: '#3e313c',
+ sidebarTextHoverColor: '#ab9ba9',
+ sidebarTextActiveBg: '#4c9689',
+ sidebarTextActiveColor: '#FFFFFF',
+ sidebarHeaderBg: '#4D394B',
+ sidebarHeaderTextColor: '#FFFFFF',
+ onlineIndicator: '#4c9689',
+ mentionBj: '#eb4d5c',
+ mentionColor: '#FFFFFF',
+ centerChannelBg: '#FFFFFF',
+ centerChannelColor: '#333333',
+ linkColor: '#2389d7',
+ buttonBg: '#26a970',
+ buttonColor: '#FFFFFF'
+ },
+ dark: {
+ type: 'Dark',
+ sidebarBg: '#1B2C3E',
+ sidebarText: '#bbbbbb',
+ sidebarUnreadText: '#fff',
+ sidebarTextHoverBg: '#4A5664',
+ sidebarTextHoverColor: '#bbbbbb',
+ sidebarTextActiveBg: '#39769C',
+ sidebarTextActiveColor: '#FFFFFF',
+ sidebarHeaderBg: '#1B2C3E',
+ sidebarHeaderTextColor: '#FFFFFF',
+ onlineIndicator: '#4c9689',
+ mentionBj: '#B74A4A',
+ mentionColor: '#FFFFFF',
+ centerChannelBg: '#2F3E4E',
+ centerChannelColor: '#DDDDDD',
+ linkColor: '#A4FFEB',
+ buttonBg: '#2B9C99',
+ buttonColor: '#FFFFFF'
+ }
+ },
+ THEME_ELEMENTS: [
+ {
+ id: 'sidebarBg',
+ uiName: 'Sidebar BG'
+ },
+ {
+ id: 'sidebarText',
+ uiName: 'Sidebar text color'
+ },
+ {
+ id: 'sidebarHeaderBg',
+ uiName: 'Sidebar Header BG'
+ },
+ {
+ id: 'sidebarHeaderTextColor',
+ uiName: 'Sidebar Header text color'
+ },
+ {
+ id: 'sidebarUnreadText',
+ uiName: 'Sidebar unread text color'
+ },
+ {
+ id: 'sidebarTextHoverBg',
+ uiName: 'Sidebar text hover BG'
+ },
+ {
+ id: 'sidebarTextHoverColor',
+ uiName: 'Sidebar text hover color'
+ },
+ {
+ id: 'sidebarTextActiveBg',
+ uiName: 'Sidebar text active BG'
+ },
+ {
+ id: 'sidebarTextActiveColor',
+ uiName: 'Sidebar text active color'
+ },
+ {
+ id: 'onlineIndicator',
+ uiName: 'Online indicator'
+ },
+ {
+ id: 'mentionBj',
+ uiName: 'Mention jewel BG'
+ },
+ {
+ id: 'mentionColor',
+ uiName: 'Mention jewel text color'
+ },
+ {
+ id: 'centerChannelBg',
+ uiName: 'Center channel BG'
+ },
+ {
+ id: 'centerChannelColor',
+ uiName: 'Center channel text color'
+ },
+ {
+ id: 'linkColor',
+ uiName: 'Link color'
+ },
+ {
+ id: 'buttonBg',
+ uiName: 'Button BG'
+ },
+
+ {
+ id: 'buttonColor',
+ uiName: 'Button Color'
+ }
+ ]
};
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 074591489..a8860eb94 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -542,7 +542,119 @@ export function toTitleCase(str) {
return str.replace(/\w\S*/g, doTitleCase);
}
-export function changeCss(className, classValue) {
+export function applyTheme(theme) {
+ if (theme.sidebarBg) {
+ changeCss('.sidebar--left', 'background:' + theme.sidebarBg, 1);
+ changeCss('@media(max-width: 768px){.search-bar__container', 'background:' + theme.sidebarBg, 1);
+ }
+
+ if (theme.sidebarText) {
+ changeCss('.sidebar--left .nav li>a, .sidebar--right', 'color:' + theme.sidebarText, 1);
+ changeCss('.sidebar--left .nav li>h4, .sidebar--left .add-channel-btn', 'color:' + changeOpacity(theme.sidebarText, 0.8), 1);
+ changeCss('.sidebar--left .add-channel-btn:hover, .sidebar--left .add-channel-btn:focus', 'color:' + theme.sidebarText, 1);
+ changeCss('.sidebar--left, .sidebar--right .sidebar--right__header', 'border-color:' + changeOpacity(theme.sidebarText, 0.2), 1);
+ changeCss('.sidebar--right .sidebar-right__body', 'border-color:' + changeOpacity(theme.sidebarText, 0.2), 2);
+ changeCss('.sidebar--left .status path', 'fill:' + changeOpacity(theme.sidebarText, 0.5), 1);
+ }
+
+ if (theme.sidebarUnreadText) {
+ changeCss('.sidebar--left .nav li>a.unread-title', 'color:' + theme.sidebarUnreadText + '!important;', 1);
+ }
+
+ if (theme.sidebarTextHoverBg) {
+ changeCss('.sidebar--left .nav li>a:hover, .sidebar--left .nav li>a:focus', 'background:' + theme.sidebarTextHoverBg, 1);
+ }
+
+ if (theme.sidebarTextHoverColor) {
+ changeCss('.sidebar--left .nav li>a:hover, .sidebar--left .nav li>a:focus', 'color:' + theme.sidebarTextHoverColor, 2);
+ }
+
+ if (theme.sidebarTextActiveBg) {
+ changeCss('.sidebar--left .nav li.active a, .sidebar--left .nav li.active a:hover, .sidebar--left .nav li.active a:focus', 'background:' + theme.sidebarTextActiveBg, 1);
+ }
+
+ if (theme.sidebarTextActiveColor) {
+ changeCss('.sidebar--left .nav li.active a, .sidebar--left .nav li.active a:hover, .sidebar--left .nav li.active a:focus', 'color:' + theme.sidebarTextActiveColor, 2);
+ }
+
+ if (theme.sidebarHeaderBg) {
+ changeCss('.sidebar--left .team__header, .sidebar--menu .team__header', 'background:' + theme.sidebarHeaderBg, 1);
+ changeCss('.modal .modal-header', 'background:' + theme.sidebarHeaderBg, 1);
+ changeCss('#navbar .navbar-default', 'background:' + theme.sidebarHeaderBg, 1);
+ }
+
+ if (theme.sidebarHeaderTextColor) {
+ changeCss('.sidebar--left .team__header .header__info, .sidebar--menu .team__header .header__info', 'color:' + theme.sidebarHeaderTextColor, 1);
+ changeCss('.sidebar--left .team__header .user__name, .sidebar--menu .team__header .user__name', 'color:' + changeOpacity(theme.sidebarHeaderTextColor, 0.8), 1);
+ changeCss('.sidebar--left .team__header:hover .user__name, .sidebar--menu .team__header:hover .user__name', 'color:' + theme.sidebarHeaderTextColor, 1);
+ changeCss('.modal .modal-header .modal-title, .modal .modal-header .modal-title .name, .modal .modal-header button.close', 'color:' + theme.sidebarHeaderTextColor, 1);
+ changeCss('#navbar .navbar-default .navbar-brand .heading, ', 'color:' + theme.sidebarHeaderTextColor, 1);
+ changeCss('#navbar .navbar-default .navbar-toggle .icon-bar, ', 'background:' + theme.sidebarHeaderTextColor, 1);
+ }
+
+ if (theme.onlineIndicator) {
+ changeCss('.sidebar--left .status .online--icon', 'fill:' + theme.onlineIndicator, 1);
+ }
+
+ if (theme.mentionBj) {
+ changeCss('.sidebar--left .nav-pills__unread-indicator', 'background:' + theme.mentionBj, 1);
+ changeCss('.sidebar--left .badge', 'background:' + theme.mentionBj, 1);
+ }
+
+ if (theme.mentionColor) {
+ changeCss('.sidebar--left .nav-pills__unread-indicator', 'color:' + theme.mentionColor, 2);
+ changeCss('.sidebar--left .badge', 'color:' + theme.mentionColor, 2);
+ }
+
+ if (theme.centerChannelBg) {
+ changeCss('.app__content', 'background:' + theme.centerChannelBg, 1);
+ changeCss('#post-list .post-list-holder-by-time', 'background:' + theme.centerChannelBg, 1);
+ changeCss('#post-create', 'background:' + theme.centerChannelBg, 1);
+ changeCss('.search-bar__container .search__form .search-bar', 'background:' + theme.centerChannelBg, 1);
+ changeCss('.date-separator .separator__text, .new-separator .separator__text', 'background:' + theme.centerChannelBg, 1);
+ changeCss('.sidebar--right', 'background:' + theme.centerChannelBg, 1);
+ }
+
+ if (theme.centerChannelColor) {
+ changeCss('.app__content', 'color:' + theme.centerChannelColor, 2);
+ changeCss('#post-create', 'color:' + theme.centerChannelColor, 2);
+ changeCss('.channel-header .heading', 'color:' + theme.centerChannelColor, 1);
+ changeCss('.channel-header__info>div.dropdown .header-dropdown__icon', 'color:' + changeOpacity(theme.centerChannelColor, 0.8), 1);
+ changeCss('.channel-header #member_popover', 'color:' + changeOpacity(theme.centerChannelColor, 0.8), 1);
+ changeCss('.custom-textarea', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2) + '!important; color: ' + theme.centerChannelColor, 1);
+ changeCss('.search-bar__container .search__form .search-bar', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2) + '; color: ' + theme.centerChannelColor, 2);
+ changeCss('.search-bar__container .search__form', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
+ changeCss('.channel-intro .channel-intro__content', 'background:' + changeOpacity(theme.centerChannelColor, 0.05), 1);
+ changeCss('.date-separator .separator__text, .new-separator .separator__text', 'color:' + theme.centerChannelColor, 2);
+ changeCss('.date-separator .separator__hr, .new-separator .separator__hr, .post-right__container .post.post--root hr, .search-item-container', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
+ changeCss('.channel-intro', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
+ changeCss('.post.current--user .post-body, .post.post--comment.other--root.current--user .post-comment', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
+ changeCss('.post.current--user .post-body, .post.post--comment.other--root.current--user .post-comment', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2);
+ changeCss('@media(max-width: 1440px){.post.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2);
+ changeCss('@media(max-width: 1800px){.inner__wrap.move--left .post.post--comment.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2);
+ changeCss('.post:hover, .sidebar--right .sidebar--right__header', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
+ changeCss('.date-separator.hovered--before:after, .new-separator.hovered--before:after', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
+ changeCss('.date-separator.hovered--after:before, .new-separator.hovered--after:before', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
+ changeCss('.post.current--user:hover .post-body ', 'background: none;', 1);
+ changeCss('.sidebar--right', 'color:' + theme.centerChannelColor, 2);
+ }
+
+ if (theme.linkColor) {
+ changeCss('a, a:focus, a:hover', 'color:' + theme.linkColor, 1);
+ changeCss('.post .comment-icon__container', 'fill:' + theme.linkColor, 1);
+ }
+
+ if (theme.buttonBg) {
+ changeCss('.btn.btn-primary', 'background:' + theme.buttonBg, 1);
+ changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background:' + changeColor(theme.buttonBg, -0.25), 1);
+ }
+
+ if (theme.buttonColor) {
+ changeCss('.btn.btn-primary', 'color:' + theme.buttonColor, 2);
+ }
+}
+
+export function changeCss(className, classValue, classRepeat) {
// we need invisible container to store additional css definitions
var cssMainContainer = $('#css-modifier-container');
if (cssMainContainer.length === 0) {
@@ -552,9 +664,9 @@ export function changeCss(className, classValue) {
}
// and we need one div for each class
- var classContainer = cssMainContainer.find('div[data-class="' + className + '"]');
+ var classContainer = cssMainContainer.find('div[data-class="' + className + classRepeat + '"]');
if (classContainer.length === 0) {
- classContainer = $('<div data-class="' + className + '"></div>');
+ classContainer = $('<div data-class="' + className + classRepeat + '"></div>');
classContainer.appendTo(cssMainContainer);
}
@@ -760,57 +872,47 @@ Image.prototype.load = function imageLoad(url, progressCallback) {
Image.prototype.completedPercentage = 0;
export function changeColor(colourIn, amt) {
- var usePound = false;
- var col = colourIn;
+ var hex = colourIn;
+ var lum = amt;
- if (col[0] === '#') {
- col = col.slice(1);
- usePound = true;
+ // validate hex string
+ hex = String(hex).replace(/[^0-9a-f]/gi, '');
+ if (hex.length < 6) {
+ hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
+ lum = lum || 0;
- var num = parseInt(col, 16);
-
- var r = (num >> 16) + amt;
-
- if (r > 255) {
- r = 255;
- } else if (r < 0) {
- r = 0;
- }
-
- var b = ((num >> 8) & 0x00FF) + amt;
-
- if (b > 255) {
- b = 255;
- } else if (b < 0) {
- b = 0;
+ // convert to decimal and change luminosity
+ var rgb = '#';
+ var c;
+ var i;
+ for (i = 0; i < 3; i++) {
+ c = parseInt(hex.substr(i * 2, 2), 16);
+ c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
+ rgb += ('00' + c).substr(c.length);
}
- var g = (num & 0x0000FF) + amt;
-
- if (g > 255) {
- g = 255;
- } else if (g < 0) {
- g = 0;
- }
+ return rgb;
+}
- var pound = '#';
- if (!usePound) {
- pound = '';
+export function changeOpacity(oldColor, opacity) {
+ var color = oldColor;
+ if (color[0] === '#') {
+ color = color.slice(1);
}
- return pound + String('000000' + (g | (b << 8) | (r << 16)).toString(16)).slice(-6);
-}
+ if (color.length === 3) {
+ const tempColor = color;
+ color = '';
-export function changeOpacity(oldColor, opacity) {
- var col = oldColor;
- if (col[0] === '#') {
- col = col.slice(1);
+ color += tempColor[0] + tempColor[0];
+ color += tempColor[1] + tempColor[1];
+ color += tempColor[2] + tempColor[2];
}
- var r = parseInt(col.substring(0, 2), 16);
- var g = parseInt(col.substring(2, 4), 16);
- var b = parseInt(col.substring(4, 6), 16);
+ var r = parseInt(color.substring(0, 2), 16);
+ var g = parseInt(color.substring(2, 4), 16);
+ var b = parseInt(color.substring(4, 6), 16);
return 'rgba(' + r + ',' + g + ',' + b + ',' + opacity + ')';
}
diff --git a/web/sass-files/sass/partials/_base.scss b/web/sass-files/sass/partials/_base.scss
index cdc2152e9..592d5e62e 100644
--- a/web/sass-files/sass/partials/_base.scss
+++ b/web/sass-files/sass/partials/_base.scss
@@ -40,14 +40,11 @@ b, strong {
a {
word-break: break-word;
-}
-
-a.theme {
color: $primary-color;
}
-div.theme {
- background-color: $primary-color;
+a:focus, a:hover {
+ color: $primary-color;
}
.tooltip {
diff --git a/web/sass-files/sass/partials/_colorpicker.scss b/web/sass-files/sass/partials/_colorpicker.scss
new file mode 100644
index 000000000..431f9d8d0
--- /dev/null
+++ b/web/sass-files/sass/partials/_colorpicker.scss
@@ -0,0 +1,251 @@
+/*!
+ * Bootstrap Colorpicker
+ * http://mjolnic.github.io/bootstrap-colorpicker/
+ *
+ * Originally written by (c) 2012 Stefan Petre
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0.txt
+ *
+ */
+
+.colorpicker-saturation {
+ float: left;
+ width: 100px;
+ height: 100px;
+ cursor: crosshair;
+ background-image: url("../images/bootstrap-colorpicker/saturation.png");
+}
+
+.colorpicker-saturation i {
+ position: absolute;
+ top: 0;
+ left: 0;
+ display: block;
+ width: 5px;
+ height: 5px;
+ margin: -4px 0 0 -4px;
+ border: 1px solid #000;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+}
+
+.colorpicker-saturation i b {
+ display: block;
+ width: 5px;
+ height: 5px;
+ border: 1px solid #fff;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+}
+
+.colorpicker-hue,
+.colorpicker-alpha {
+ float: left;
+ width: 15px;
+ height: 100px;
+ margin-bottom: 4px;
+ margin-left: 4px;
+ cursor: row-resize;
+}
+
+.colorpicker-hue i,
+.colorpicker-alpha i {
+ position: absolute;
+ top: 0;
+ left: 0;
+ display: block;
+ width: 100%;
+ height: 1px;
+ margin-top: -1px;
+ background: #000;
+ border-top: 1px solid #fff;
+}
+
+.colorpicker-hue {
+ background-image: url("../images/bootstrap-colorpicker/hue.png");
+}
+
+.colorpicker-alpha {
+ display: none;
+ background-image: url("../images/bootstrap-colorpicker/alpha.png");
+}
+
+.colorpicker-saturation,
+.colorpicker-hue,
+.colorpicker-alpha {
+ background-size: contain;
+}
+
+.colorpicker {
+ top: 0;
+ left: 0;
+ z-index: 2500;
+ min-width: 130px;
+ padding: 4px;
+ margin-top: 1px;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ *zoom: 1;
+}
+
+.colorpicker:before,
+.colorpicker:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.colorpicker:after {
+ clear: both;
+}
+
+.colorpicker:before {
+ position: absolute;
+ top: -7px;
+ left: 6px;
+ display: inline-block;
+ border-right: 7px solid transparent;
+ border-bottom: 7px solid #ccc;
+ border-left: 7px solid transparent;
+ border-bottom-color: rgba(0, 0, 0, 0.2);
+ content: '';
+}
+
+.colorpicker:after {
+ position: absolute;
+ top: -6px;
+ left: 7px;
+ display: inline-block;
+ border-right: 6px solid transparent;
+ border-bottom: 6px solid #ffffff;
+ border-left: 6px solid transparent;
+ content: '';
+}
+
+.colorpicker div {
+ position: relative;
+}
+
+.colorpicker.colorpicker-with-alpha {
+ min-width: 140px;
+}
+
+.colorpicker.colorpicker-with-alpha .colorpicker-alpha {
+ display: block;
+}
+
+.colorpicker-color {
+ height: 10px;
+ margin-top: 5px;
+ clear: both;
+ background-image: url("../images/bootstrap-colorpicker/alpha.png");
+ background-position: 0 100%;
+}
+
+.colorpicker-color div {
+ height: 10px;
+}
+
+.colorpicker-selectors {
+ display: none;
+ height: 10px;
+ margin-top: 5px;
+ clear: both;
+}
+
+.colorpicker-selectors i {
+ float: left;
+ width: 10px;
+ height: 10px;
+ cursor: pointer;
+}
+
+.colorpicker-selectors i + i {
+ margin-left: 3px;
+}
+
+.colorpicker-element .input-group-addon i,
+.colorpicker-element .add-on i {
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+ vertical-align: text-top;
+ cursor: pointer;
+}
+
+.colorpicker.colorpicker-inline {
+ position: relative;
+ z-index: auto;
+ display: inline-block;
+ float: none;
+}
+
+.colorpicker.colorpicker-horizontal {
+ width: 110px;
+ height: auto;
+ min-width: 110px;
+}
+
+.colorpicker.colorpicker-horizontal .colorpicker-saturation {
+ margin-bottom: 4px;
+}
+
+.colorpicker.colorpicker-horizontal .colorpicker-color {
+ width: 100px;
+}
+
+.colorpicker.colorpicker-horizontal .colorpicker-hue,
+.colorpicker.colorpicker-horizontal .colorpicker-alpha {
+ float: left;
+ width: 100px;
+ height: 15px;
+ margin-bottom: 4px;
+ margin-left: 0;
+ cursor: col-resize;
+}
+
+.colorpicker.colorpicker-horizontal .colorpicker-hue i,
+.colorpicker.colorpicker-horizontal .colorpicker-alpha i {
+ position: absolute;
+ top: 0;
+ left: 0;
+ display: block;
+ width: 1px;
+ height: 15px;
+ margin-top: 0;
+ background: #ffffff;
+ border: none;
+}
+
+.colorpicker.colorpicker-horizontal .colorpicker-hue {
+ background-image: url("../images/bootstrap-colorpicker/hue-horizontal.png");
+}
+
+.colorpicker.colorpicker-horizontal .colorpicker-alpha {
+ background-image: url("../images/bootstrap-colorpicker/alpha-horizontal.png");
+}
+
+.colorpicker.colorpicker-hidden {
+ display: none;
+}
+
+.colorpicker.colorpicker-visible {
+ display: block;
+}
+
+.colorpicker-inline.colorpicker-visible {
+ display: inline-block;
+}
+
+.colorpicker-right:before {
+ right: 6px;
+ left: auto;
+}
+
+.colorpicker-right:after {
+ right: 7px;
+ left: auto;
+} \ No newline at end of file
diff --git a/web/sass-files/sass/partials/_headers.scss b/web/sass-files/sass/partials/_headers.scss
index 702f0fd60..a20d1f48b 100644
--- a/web/sass-files/sass/partials/_headers.scss
+++ b/web/sass-files/sass/partials/_headers.scss
@@ -231,6 +231,10 @@
width: 45px;
color: #999;
cursor: pointer;
+ .fa {
+ margin-left: 3px;
+ font-size: 16px;
+ }
}
&.alt {
margin: 0;
diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss
index a046cd904..38e9b4174 100644
--- a/web/sass-files/sass/partials/_modal.scss
+++ b/web/sass-files/sass/partials/_modal.scss
@@ -8,6 +8,16 @@
@include opacity(0.7);
}
}
+ a, a:focus, a:hover {
+ color: #2389D7;
+ }
+ .btn.btn-primary {
+ background: #4285f4;
+ &:hover, &:focus, &:active {
+ background: $primary-color--hover;
+ color: #fff;
+ }
+ }
.info__label {
font-weight: 600;
text-align: right;
diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss
index 6940cf2fb..d4f02cf4b 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -275,7 +275,7 @@ body.ios {
&.current--user {
.post-body {
@include border-radius(4px);
- background: #f5f5f5;
+ background: rgba(#000, 0.05);
}
}
&.post--comment {
diff --git a/web/sass-files/sass/partials/_search.scss b/web/sass-files/sass/partials/_search.scss
index 9ae41ebb0..9abdd40da 100644
--- a/web/sass-files/sass/partials/_search.scss
+++ b/web/sass-files/sass/partials/_search.scss
@@ -107,4 +107,5 @@
.search-highlight.theme, .search-highlight {
background-color: #FFF2BB;
+ color: #333;
}
diff --git a/web/sass-files/sass/partials/_settings.scss b/web/sass-files/sass/partials/_settings.scss
index 2b59a943b..8dcd8f35c 100644
--- a/web/sass-files/sass/partials/_settings.scss
+++ b/web/sass-files/sass/partials/_settings.scss
@@ -79,6 +79,36 @@
}
}
+ .appearance-section {
+ .premade-themes {
+ .theme-label {
+ font-weight: 400;
+ margin-top: 5px;
+ }
+ img {
+ border: 3px solid transparent;
+ }
+ .active {
+ img {
+ border-color: $primary-color;
+ }
+ }
+ }
+ .custom-label {
+ font-weight: normal;
+ font-size: 13px;
+ width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ margin-bottom: 0;
+ }
+ .radio {
+ label {
+ font-weight: 600;
+ }
+ }
+ }
+
.section-title {
margin-bottom: 5px;
font-weight: 600;
diff --git a/web/sass-files/sass/partials/_sidebar--left.scss b/web/sass-files/sass/partials/_sidebar--left.scss
index 94583b153..6a418e270 100644
--- a/web/sass-files/sass/partials/_sidebar--left.scss
+++ b/web/sass-files/sass/partials/_sidebar--left.scss
@@ -16,6 +16,12 @@
overflow-y: auto;
max-width: 200px;
width: 200px;
+ a {
+ color: #262626 !important;
+ &:hover, &:focus {
+ background: #f5f5f5 !important;
+ }
+ }
}
.search__form {
margin: 0;
diff --git a/web/sass-files/sass/partials/_sidebar--right.scss b/web/sass-files/sass/partials/_sidebar--right.scss
index d02a92448..b37dbf421 100644
--- a/web/sass-files/sass/partials/_sidebar--right.scss
+++ b/web/sass-files/sass/partials/_sidebar--right.scss
@@ -51,9 +51,10 @@
margin: 11px 0 0 0;
width: 22px;
height: 22px;
- background: url("../images/closeSidebar.png");
- @include background-size(100% 100%);
opacity: 0.5;
+ font-size: 22px;
+ line-height: 0;
+ background: none;
float: right;
outline: none;
border: none;
@@ -61,11 +62,15 @@
&:hover, &:active {
opacity: 0.8;
}
+ i {
+ position: relative;
+ top: -2px;
+ }
}
.sidebar--right__header {
font-size: 1em;
text-transform: uppercase;
- color: #444;
+ color: inherit;
height: 44px;
padding: 0 1em;
line-height: 44px;
diff --git a/web/sass-files/sass/styles.scss b/web/sass-files/sass/styles.scss
index de1db57e8..923a6e99b 100644
--- a/web/sass-files/sass/styles.scss
+++ b/web/sass-files/sass/styles.scss
@@ -10,6 +10,7 @@
@import "partials/perfect-scrollbar";
@import "partials/font-awesome";
@import "partials/base";
+@import "partials/colorpicker";
// Channel Css
@import "partials/headers";
diff --git a/web/static/css/bootstrap-colorpicker.min.css b/web/static/css/bootstrap-colorpicker.min.css
new file mode 100755
index 000000000..5656e768f
--- /dev/null
+++ b/web/static/css/bootstrap-colorpicker.min.css
@@ -0,0 +1,9 @@
+/*!
+ * Bootstrap Colorpicker
+ * http://mjolnic.github.io/bootstrap-colorpicker/
+ *
+ * Originally written by (c) 2012 Stefan Petre
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0.txt
+ *
+ */.colorpicker-saturation{float:left;width:100px;height:100px;cursor:crosshair;background-image:url("../images/bootstrap-colorpicker/saturation.png")}.colorpicker-saturation i{position:absolute;top:0;left:0;display:block;width:5px;height:5px;margin:-4px 0 0 -4px;border:1px solid #000;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.colorpicker-saturation i b{display:block;width:5px;height:5px;border:1px solid #fff;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.colorpicker-hue,.colorpicker-alpha{float:left;width:15px;height:100px;margin-bottom:4px;margin-left:4px;cursor:row-resize}.colorpicker-hue i,.colorpicker-alpha i{position:absolute;top:0;left:0;display:block;width:100%;height:1px;margin-top:-1px;background:#000;border-top:1px solid #fff}.colorpicker-hue{background-image:url("../images/bootstrap-colorpicker/hue.png")}.colorpicker-alpha{display:none;background-image:url("../images/bootstrap-colorpicker/alpha.png")}.colorpicker-saturation,.colorpicker-hue,.colorpicker-alpha{background-size:contain}.colorpicker{top:0;left:0;z-index:2500;min-width:130px;padding:4px;margin-top:1px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*zoom:1}.colorpicker:before,.colorpicker:after{display:table;line-height:0;content:""}.colorpicker:after{clear:both}.colorpicker:before{position:absolute;top:-7px;left:6px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.colorpicker:after{position:absolute;top:-6px;left:7px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.colorpicker div{position:relative}.colorpicker.colorpicker-with-alpha{min-width:140px}.colorpicker.colorpicker-with-alpha .colorpicker-alpha{display:block}.colorpicker-color{height:10px;margin-top:5px;clear:both;background-image:url("../images/bootstrap-colorpicker/alpha.png");background-position:0 100%}.colorpicker-color div{height:10px}.colorpicker-selectors{display:none;height:10px;margin-top:5px;clear:both}.colorpicker-selectors i{float:left;width:10px;height:10px;cursor:pointer}.colorpicker-selectors i+i{margin-left:3px}.colorpicker-element .input-group-addon i,.colorpicker-element .add-on i{display:inline-block;width:16px;height:16px;vertical-align:text-top;cursor:pointer}.colorpicker.colorpicker-inline{position:relative;z-index:auto;display:inline-block;float:none}.colorpicker.colorpicker-horizontal{width:110px;height:auto;min-width:110px}.colorpicker.colorpicker-horizontal .colorpicker-saturation{margin-bottom:4px}.colorpicker.colorpicker-horizontal .colorpicker-color{width:100px}.colorpicker.colorpicker-horizontal .colorpicker-hue,.colorpicker.colorpicker-horizontal .colorpicker-alpha{float:left;width:100px;height:15px;margin-bottom:4px;margin-left:0;cursor:col-resize}.colorpicker.colorpicker-horizontal .colorpicker-hue i,.colorpicker.colorpicker-horizontal .colorpicker-alpha i{position:absolute;top:0;left:0;display:block;width:1px;height:15px;margin-top:0;background:#fff;border:0}.colorpicker.colorpicker-horizontal .colorpicker-hue{background-image:url("../images/bootstrap-colorpicker/hue-horizontal.png")}.colorpicker.colorpicker-horizontal .colorpicker-alpha{background-image:url("../images/bootstrap-colorpicker/alpha-horizontal.png")}.colorpicker.colorpicker-hidden{display:none}.colorpicker.colorpicker-visible{display:block}.colorpicker-inline.colorpicker-visible{display:inline-block}.colorpicker-right:before{right:6px;left:auto}.colorpicker-right:after{right:7px;left:auto}
diff --git a/web/static/images/bootstrap-colorpicker/alpha-horizontal.png b/web/static/images/bootstrap-colorpicker/alpha-horizontal.png
new file mode 100755
index 000000000..d0a65c08b
--- /dev/null
+++ b/web/static/images/bootstrap-colorpicker/alpha-horizontal.png
Binary files differ
diff --git a/web/static/images/bootstrap-colorpicker/alpha.png b/web/static/images/bootstrap-colorpicker/alpha.png
new file mode 100755
index 000000000..38043f1c8
--- /dev/null
+++ b/web/static/images/bootstrap-colorpicker/alpha.png
Binary files differ
diff --git a/web/static/images/bootstrap-colorpicker/hue-horizontal.png b/web/static/images/bootstrap-colorpicker/hue-horizontal.png
new file mode 100755
index 000000000..a0d9add8e
--- /dev/null
+++ b/web/static/images/bootstrap-colorpicker/hue-horizontal.png
Binary files differ
diff --git a/web/static/images/bootstrap-colorpicker/hue.png b/web/static/images/bootstrap-colorpicker/hue.png
new file mode 100755
index 000000000..d89560e99
--- /dev/null
+++ b/web/static/images/bootstrap-colorpicker/hue.png
Binary files differ
diff --git a/web/static/images/bootstrap-colorpicker/saturation.png b/web/static/images/bootstrap-colorpicker/saturation.png
new file mode 100755
index 000000000..594ae50ed
--- /dev/null
+++ b/web/static/images/bootstrap-colorpicker/saturation.png
Binary files differ
diff --git a/web/static/images/themes/dark.png b/web/static/images/themes/dark.png
new file mode 100644
index 000000000..832f64d2e
--- /dev/null
+++ b/web/static/images/themes/dark.png
Binary files differ
diff --git a/web/static/images/themes/mattermost.png b/web/static/images/themes/mattermost.png
new file mode 100644
index 000000000..4a321adcb
--- /dev/null
+++ b/web/static/images/themes/mattermost.png
Binary files differ
diff --git a/web/static/images/themes/slack.png b/web/static/images/themes/slack.png
new file mode 100644
index 000000000..dc70c7dc2
--- /dev/null
+++ b/web/static/images/themes/slack.png
Binary files differ
diff --git a/web/static/js/bootstrap-colorpicker.min.js b/web/static/js/bootstrap-colorpicker.min.js
new file mode 100755
index 000000000..05c0e0744
--- /dev/null
+++ b/web/static/js/bootstrap-colorpicker.min.js
@@ -0,0 +1 @@
+!function(a){"use strict";"object"==typeof exports?module.exports=a(window.jQuery):"function"==typeof define&&define.amd?define(["jquery"],a):window.jQuery&&!window.jQuery.fn.colorpicker&&a(window.jQuery)}(function(a){"use strict";var b=function(b,c){this.value={h:0,s:0,b:0,a:1},this.origFormat=null,c&&a.extend(this.colors,c),b&&(void 0!==b.toLowerCase?(b+="",this.setColor(b)):void 0!==b.h&&(this.value=b))};b.prototype={constructor:b,colors:{aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#008000",greenyellow:"#adff2f",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgrey:"#d3d3d3",lightgreen:"#90ee90",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370d8",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#d87093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32",transparent:"transparent"},_sanitizeNumber:function(a){return"number"==typeof a?a:isNaN(a)||null===a||""===a||void 0===a?1:void 0!==a.toLowerCase?parseFloat(a):1},isTransparent:function(a){return a?(a=a.toLowerCase().trim(),"transparent"===a||a.match(/#?00000000/)||a.match(/(rgba|hsla)\(0,0,0,0?\.?0\)/)):!1},rgbaIsTransparent:function(a){return 0===a.r&&0===a.g&&0===a.b&&0===a.a},setColor:function(a){a=a.toLowerCase().trim(),a&&(this.isTransparent(a)?this.value={h:0,s:0,b:0,a:0}:this.value=this.stringToHSB(a)||{h:0,s:0,b:0,a:1})},stringToHSB:function(b){b=b.toLowerCase();var c;"undefined"!=typeof this.colors[b]&&(b=this.colors[b],c="alias");var d=this,e=!1;return a.each(this.stringParsers,function(a,f){var g=f.re.exec(b),h=g&&f.parse.apply(d,[g]),i=c||f.format||"rgba";return h?(e=i.match(/hsla?/)?d.RGBtoHSB.apply(d,d.HSLtoRGB.apply(d,h)):d.RGBtoHSB.apply(d,h),d.origFormat=i,!1):!0}),e},setHue:function(a){this.value.h=1-a},setSaturation:function(a){this.value.s=a},setBrightness:function(a){this.value.b=1-a},setAlpha:function(a){this.value.a=parseInt(100*(1-a),10)/100},toRGB:function(a,b,c,d){a||(a=this.value.h,b=this.value.s,c=this.value.b),a*=360;var e,f,g,h,i;return a=a%360/60,i=c*b,h=i*(1-Math.abs(a%2-1)),e=f=g=c-i,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a],{r:Math.round(255*e),g:Math.round(255*f),b:Math.round(255*g),a:d||this.value.a}},toHex:function(a,b,c,d){var e=this.toRGB(a,b,c,d);return this.rgbaIsTransparent(e)?"transparent":"#"+(1<<24|parseInt(e.r)<<16|parseInt(e.g)<<8|parseInt(e.b)).toString(16).substr(1)},toHSL:function(a,b,c,d){a=a||this.value.h,b=b||this.value.s,c=c||this.value.b,d=d||this.value.a;var e=a,f=(2-b)*c,g=b*c;return g/=f>0&&1>=f?f:2-f,f/=2,g>1&&(g=1),{h:isNaN(e)?0:e,s:isNaN(g)?0:g,l:isNaN(f)?0:f,a:isNaN(d)?0:d}},toAlias:function(a,b,c,d){var e=this.toHex(a,b,c,d);for(var f in this.colors)if(this.colors[f]===e)return f;return!1},RGBtoHSB:function(a,b,c,d){a/=255,b/=255,c/=255;var e,f,g,h;return g=Math.max(a,b,c),h=g-Math.min(a,b,c),e=0===h?null:g===a?(b-c)/h:g===b?(c-a)/h+2:(a-b)/h+4,e=(e+360)%6*60/360,f=0===h?0:h/g,{h:this._sanitizeNumber(e),s:f,b:g,a:this._sanitizeNumber(d)}},HueToRGB:function(a,b,c){return 0>c?c+=1:c>1&&(c-=1),1>6*c?a+(b-a)*c*6:1>2*c?b:2>3*c?a+(b-a)*(2/3-c)*6:a},HSLtoRGB:function(a,b,c,d){0>b&&(b=0);var e;e=.5>=c?c*(1+b):c+b-c*b;var f=2*c-e,g=a+1/3,h=a,i=a-1/3,j=Math.round(255*this.HueToRGB(f,e,g)),k=Math.round(255*this.HueToRGB(f,e,h)),l=Math.round(255*this.HueToRGB(f,e,i));return[j,k,l,this._sanitizeNumber(d)]},toString:function(a){a=a||"rgba";var b=!1;switch(a){case"rgb":return b=this.toRGB(),this.rgbaIsTransparent(b)?"transparent":"rgb("+b.r+","+b.g+","+b.b+")";case"rgba":return b=this.toRGB(),"rgba("+b.r+","+b.g+","+b.b+","+b.a+")";case"hsl":return b=this.toHSL(),"hsl("+Math.round(360*b.h)+","+Math.round(100*b.s)+"%,"+Math.round(100*b.l)+"%)";case"hsla":return b=this.toHSL(),"hsla("+Math.round(360*b.h)+","+Math.round(100*b.s)+"%,"+Math.round(100*b.l)+"%,"+b.a+")";case"hex":return this.toHex();case"alias":return this.toAlias()||this.toHex();default:return b}},stringParsers:[{re:/rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*?\)/,format:"rgb",parse:function(a){return[a[1],a[2],a[3],1]}},{re:/rgb\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*?\)/,format:"rgb",parse:function(a){return[2.55*a[1],2.55*a[2],2.55*a[3],1]}},{re:/rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,format:"rgba",parse:function(a){return[a[1],a[2],a[3],a[4]]}},{re:/rgba\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,format:"rgba",parse:function(a){return[2.55*a[1],2.55*a[2],2.55*a[3],a[4]]}},{re:/hsl\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*?\)/,format:"hsl",parse:function(a){return[a[1]/360,a[2]/100,a[3]/100,a[4]]}},{re:/hsla\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,format:"hsla",parse:function(a){return[a[1]/360,a[2]/100,a[3]/100,a[4]]}},{re:/#?([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/,format:"hex",parse:function(a){return[parseInt(a[1],16),parseInt(a[2],16),parseInt(a[3],16),1]}},{re:/#?([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/,format:"hex",parse:function(a){return[parseInt(a[1]+a[1],16),parseInt(a[2]+a[2],16),parseInt(a[3]+a[3],16),1]}}],colorNameToHex:function(a){return"undefined"!=typeof this.colors[a.toLowerCase()]?this.colors[a.toLowerCase()]:!1}};var c={horizontal:!1,inline:!1,color:!1,format:!1,input:"input",container:!1,component:".add-on, .input-group-addon",sliders:{saturation:{maxLeft:100,maxTop:100,callLeft:"setSaturation",callTop:"setBrightness"},hue:{maxLeft:0,maxTop:100,callLeft:!1,callTop:"setHue"},alpha:{maxLeft:0,maxTop:100,callLeft:!1,callTop:"setAlpha"}},slidersHorz:{saturation:{maxLeft:100,maxTop:100,callLeft:"setSaturation",callTop:"setBrightness"},hue:{maxLeft:100,maxTop:0,callLeft:"setHue",callTop:!1},alpha:{maxLeft:100,maxTop:0,callLeft:"setAlpha",callTop:!1}},template:'<div class="colorpicker dropdown-menu"><div class="colorpicker-saturation"><i><b></b></i></div><div class="colorpicker-hue"><i></i></div><div class="colorpicker-alpha"><i></i></div><div class="colorpicker-color"><div /></div><div class="colorpicker-selectors"></div></div>',align:"right",customClass:null,colorSelectors:null},d=function(d,e){if(this.element=a(d).addClass("colorpicker-element"),this.options=a.extend(!0,{},c,this.element.data(),e),this.component=this.options.component,this.component=this.component!==!1?this.element.find(this.component):!1,this.component&&0===this.component.length&&(this.component=!1),this.container=this.options.container===!0?this.element:this.options.container,this.container=this.container!==!1?a(this.container):!1,this.input=this.element.is("input")?this.element:this.options.input?this.element.find(this.options.input):!1,this.input&&0===this.input.length&&(this.input=!1),this.color=new b(this.options.color!==!1?this.options.color:this.getValue(),this.options.colorSelectors),this.format=this.options.format!==!1?this.options.format:this.color.origFormat,this.picker=a(this.options.template),this.options.customClass&&this.picker.addClass(this.options.customClass),this.options.inline?this.picker.addClass("colorpicker-inline colorpicker-visible"):this.picker.addClass("colorpicker-hidden"),this.options.horizontal&&this.picker.addClass("colorpicker-horizontal"),("rgba"===this.format||"hsla"===this.format||this.options.format===!1)&&this.picker.addClass("colorpicker-with-alpha"),"right"===this.options.align&&this.picker.addClass("colorpicker-right"),this.options.colorSelectors){var f=this;a.each(this.options.colorSelectors,function(b,c){var d=a("<i />").css("background-color",c).data("class",b);d.click(function(){f.setValue(a(this).css("background-color"))}),f.picker.find(".colorpicker-selectors").append(d)}),this.picker.find(".colorpicker-selectors").show()}this.picker.on("mousedown.colorpicker touchstart.colorpicker",a.proxy(this.mousedown,this)),this.picker.appendTo(this.container?this.container:a("body")),this.input!==!1&&(this.input.on({"keyup.colorpicker":a.proxy(this.keyup,this)}),this.input.on({"change.colorpicker":a.proxy(this.change,this)}),this.component===!1&&this.element.on({"focus.colorpicker":a.proxy(this.show,this)}),this.options.inline===!1&&this.element.on({"focusout.colorpicker":a.proxy(this.hide,this)})),this.component!==!1&&this.component.on({"click.colorpicker":a.proxy(this.show,this)}),this.input===!1&&this.component===!1&&this.element.on({"click.colorpicker":a.proxy(this.show,this)}),this.input!==!1&&this.component!==!1&&"color"===this.input.attr("type")&&this.input.on({"click.colorpicker":a.proxy(this.show,this),"focus.colorpicker":a.proxy(this.show,this)}),this.update(),a(a.proxy(function(){this.element.trigger("create")},this))};d.Color=b,d.prototype={constructor:d,destroy:function(){this.picker.remove(),this.element.removeData("colorpicker").off(".colorpicker"),this.input!==!1&&this.input.off(".colorpicker"),this.component!==!1&&this.component.off(".colorpicker"),this.element.removeClass("colorpicker-element"),this.element.trigger({type:"destroy"})},reposition:function(){if(this.options.inline!==!1||this.options.container)return!1;var a=this.container&&this.container[0]!==document.body?"position":"offset",b=this.component||this.element,c=b[a]();"right"===this.options.align&&(c.left-=this.picker.outerWidth()-b.outerWidth()),this.picker.css({top:c.top+b.outerHeight(),left:c.left})},show:function(b){return this.isDisabled()?!1:(this.picker.addClass("colorpicker-visible").removeClass("colorpicker-hidden"),this.reposition(),a(window).on("resize.colorpicker",a.proxy(this.reposition,this)),!b||this.hasInput()&&"color"!==this.input.attr("type")||b.stopPropagation&&b.preventDefault&&(b.stopPropagation(),b.preventDefault()),this.options.inline===!1&&a(window.document).on({"mousedown.colorpicker":a.proxy(this.hide,this)}),void this.element.trigger({type:"showPicker",color:this.color}))},hide:function(){this.picker.addClass("colorpicker-hidden").removeClass("colorpicker-visible"),a(window).off("resize.colorpicker",this.reposition),a(document).off({"mousedown.colorpicker":this.hide}),this.update(),this.element.trigger({type:"hidePicker",color:this.color})},updateData:function(a){return a=a||this.color.toString(this.format),this.element.data("color",a),a},updateInput:function(a){if(a=a||this.color.toString(this.format),this.input!==!1){if(this.options.colorSelectors){var c=new b(a,this.options.colorSelectors),d=c.toAlias();"undefined"!=typeof this.options.colorSelectors[d]&&(a=d)}this.input.prop("value",a)}return a},updatePicker:function(a){void 0!==a&&(this.color=new b(a,this.options.colorSelectors));var c=this.options.horizontal===!1?this.options.sliders:this.options.slidersHorz,d=this.picker.find("i");return 0!==d.length?(this.options.horizontal===!1?(c=this.options.sliders,d.eq(1).css("top",c.hue.maxTop*(1-this.color.value.h)).end().eq(2).css("top",c.alpha.maxTop*(1-this.color.value.a))):(c=this.options.slidersHorz,d.eq(1).css("left",c.hue.maxLeft*(1-this.color.value.h)).end().eq(2).css("left",c.alpha.maxLeft*(1-this.color.value.a))),d.eq(0).css({top:c.saturation.maxTop-this.color.value.b*c.saturation.maxTop,left:this.color.value.s*c.saturation.maxLeft}),this.picker.find(".colorpicker-saturation").css("backgroundColor",this.color.toHex(this.color.value.h,1,1,1)),this.picker.find(".colorpicker-alpha").css("backgroundColor",this.color.toHex()),this.picker.find(".colorpicker-color, .colorpicker-color div").css("backgroundColor",this.color.toString(this.format)),a):void 0},updateComponent:function(a){if(a=a||this.color.toString(this.format),this.component!==!1){var b=this.component.find("i").eq(0);b.length>0?b.css({backgroundColor:a}):this.component.css({backgroundColor:a})}return a},update:function(a){var b;return(this.getValue(!1)!==!1||a===!0)&&(b=this.updateComponent(),this.updateInput(b),this.updateData(b),this.updatePicker()),b},setValue:function(a){this.color=new b(a,this.options.colorSelectors),this.update(!0),this.element.trigger({type:"changeColor",color:this.color,value:a})},getValue:function(a){a=void 0===a?"#000000":a;var b;return b=this.hasInput()?this.input.val():this.element.data("color"),(void 0===b||""===b||null===b)&&(b=a),b},hasInput:function(){return this.input!==!1},isDisabled:function(){return this.hasInput()?this.input.prop("disabled")===!0:!1},disable:function(){return this.hasInput()?(this.input.prop("disabled",!0),this.element.trigger({type:"disable",color:this.color,value:this.getValue()}),!0):!1},enable:function(){return this.hasInput()?(this.input.prop("disabled",!1),this.element.trigger({type:"enable",color:this.color,value:this.getValue()}),!0):!1},currentSlider:null,mousePointer:{left:0,top:0},mousedown:function(b){b.pageX||b.pageY||!b.originalEvent||(b.pageX=b.originalEvent.touches[0].pageX,b.pageY=b.originalEvent.touches[0].pageY),b.stopPropagation(),b.preventDefault();var c=a(b.target),d=c.closest("div"),e=this.options.horizontal?this.options.slidersHorz:this.options.sliders;if(!d.is(".colorpicker")){if(d.is(".colorpicker-saturation"))this.currentSlider=a.extend({},e.saturation);else if(d.is(".colorpicker-hue"))this.currentSlider=a.extend({},e.hue);else{if(!d.is(".colorpicker-alpha"))return!1;this.currentSlider=a.extend({},e.alpha)}var f=d.offset();this.currentSlider.guide=d.find("i")[0].style,this.currentSlider.left=b.pageX-f.left,this.currentSlider.top=b.pageY-f.top,this.mousePointer={left:b.pageX,top:b.pageY},a(document).on({"mousemove.colorpicker":a.proxy(this.mousemove,this),"touchmove.colorpicker":a.proxy(this.mousemove,this),"mouseup.colorpicker":a.proxy(this.mouseup,this),"touchend.colorpicker":a.proxy(this.mouseup,this)}).trigger("mousemove")}return!1},mousemove:function(a){a.pageX||a.pageY||!a.originalEvent||(a.pageX=a.originalEvent.touches[0].pageX,a.pageY=a.originalEvent.touches[0].pageY),a.stopPropagation(),a.preventDefault();var b=Math.max(0,Math.min(this.currentSlider.maxLeft,this.currentSlider.left+((a.pageX||this.mousePointer.left)-this.mousePointer.left))),c=Math.max(0,Math.min(this.currentSlider.maxTop,this.currentSlider.top+((a.pageY||this.mousePointer.top)-this.mousePointer.top)));return this.currentSlider.guide.left=b+"px",this.currentSlider.guide.top=c+"px",this.currentSlider.callLeft&&this.color[this.currentSlider.callLeft].call(this.color,b/this.currentSlider.maxLeft),this.currentSlider.callTop&&this.color[this.currentSlider.callTop].call(this.color,c/this.currentSlider.maxTop),"setAlpha"===this.currentSlider.callTop&&this.options.format===!1&&(1!==this.color.value.a?(this.format="rgba",this.color.origFormat="rgba"):(this.format="hex",this.color.origFormat="hex")),this.update(!0),this.element.trigger({type:"changeColor",color:this.color}),!1},mouseup:function(b){return b.stopPropagation(),b.preventDefault(),a(document).off({"mousemove.colorpicker":this.mousemove,"touchmove.colorpicker":this.mousemove,"mouseup.colorpicker":this.mouseup,"touchend.colorpicker":this.mouseup}),!1},change:function(a){this.keyup(a)},keyup:function(a){38===a.keyCode?(this.color.value.a<1&&(this.color.value.a=Math.round(100*(this.color.value.a+.01))/100),this.update(!0)):40===a.keyCode?(this.color.value.a>0&&(this.color.value.a=Math.round(100*(this.color.value.a-.01))/100),this.update(!0)):(this.color=new b(this.input.val(),this.options.colorSelectors),this.color.origFormat&&this.options.format===!1&&(this.format=this.color.origFormat),this.getValue(!1)!==!1&&(this.updateData(),this.updateComponent(),this.updatePicker())),this.element.trigger({type:"changeColor",color:this.color,value:this.input.val()})}},a.colorpicker=d,a.fn.colorpicker=function(b){var c,e=arguments,f=this.each(function(){var f=a(this),g=f.data("colorpicker"),h="object"==typeof b?b:{};g||"string"==typeof b?"string"==typeof b&&(c=g[b].apply(g,Array.prototype.slice.call(e,1))):f.data("colorpicker",new d(this,h))});return"getValue"===b?c:f},a.fn.colorpicker.constructor=d}); \ No newline at end of file
diff --git a/web/templates/channel.html b/web/templates/channel.html
index 92aaaf02f..2af94e415 100644
--- a/web/templates/channel.html
+++ b/web/templates/channel.html
@@ -29,6 +29,7 @@
<div id="edit_mention_tab"></div>
<div id="get_link_modal"></div>
<div id="user_settings_modal"></div>
+ <div id="import_theme_modal"></div>
<div id="team_settings_modal"></div>
<div id="invite_member_modal"></div>
<div id="edit_channel_modal"></div>
diff --git a/web/templates/head.html b/web/templates/head.html
index af5c86bba..36af56a21 100644
--- a/web/templates/head.html
+++ b/web/templates/head.html
@@ -25,10 +25,12 @@
<link rel="stylesheet" href="/static/css/bootstrap-3.3.5.min.css">
<link rel="stylesheet" href="/static/css/jasny-bootstrap.min.css" rel="stylesheet">
+ <link rel="stylesheet" href="/static/css/bootstrap-colorpicker.min.css" rel="stylesheet">
<script src="/static/js/react-with-addons-0.13.3.js"></script>
<script src="/static/js/jquery-1.11.1.js"></script>
<script src="/static/js/bootstrap-3.3.5.js"></script>
+ <script src="/static/js/bootstrap-colorpicker.min.js"></script>
<script src="/static/js/react-bootstrap-0.25.1.js"></script>
<link id="favicon" rel="icon" href="/static/images/favicon.ico" type="image/x-icon">