From e4a15076f458be1416de25b2c45578975b914de5 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Wed, 23 Sep 2015 10:12:40 -0400 Subject: Implement UI theme colors. --- api/user.go | 3 - model/user.go | 5 + store/sql_user_store.go | 2 + web/react/components/channel_loader.jsx | 24 +- web/react/components/popover_list_members.jsx | 2 +- web/react/components/register_app_modal.jsx | 2 +- web/react/components/rhs_header_post.jsx | 1 + web/react/components/search_results_header.jsx | 1 + .../user_settings/custom_theme_chooser.jsx | 108 +++++++++ .../user_settings/import_theme_modal.jsx | 179 ++++++++++++++ .../user_settings/premade_theme_chooser.jsx | 55 +++++ .../user_settings/user_settings_appearance.jsx | 261 +++++++++++++-------- .../user_settings/user_settings_developer.jsx | 2 +- .../user_settings/user_settings_modal.jsx | 2 +- web/react/package.json | 18 +- web/react/pages/channel.jsx | 6 + web/react/stores/user_store.jsx | 16 ++ web/react/utils/constants.jsx | 139 ++++++++++- web/react/utils/utils.jsx | 188 +++++++++++---- web/sass-files/sass/partials/_base.scss | 7 +- web/sass-files/sass/partials/_colorpicker.scss | 251 ++++++++++++++++++++ web/sass-files/sass/partials/_headers.scss | 4 + web/sass-files/sass/partials/_modal.scss | 10 + web/sass-files/sass/partials/_post.scss | 2 +- web/sass-files/sass/partials/_search.scss | 1 + web/sass-files/sass/partials/_settings.scss | 30 +++ web/sass-files/sass/partials/_sidebar--left.scss | 6 + web/sass-files/sass/partials/_sidebar--right.scss | 11 +- web/sass-files/sass/styles.scss | 1 + web/static/css/bootstrap-colorpicker.min.css | 9 + .../bootstrap-colorpicker/alpha-horizontal.png | Bin 0 -> 3635 bytes web/static/images/bootstrap-colorpicker/alpha.png | Bin 0 -> 3271 bytes .../bootstrap-colorpicker/hue-horizontal.png | Bin 0 -> 2837 bytes web/static/images/bootstrap-colorpicker/hue.png | Bin 0 -> 2972 bytes .../images/bootstrap-colorpicker/saturation.png | Bin 0 -> 8817 bytes web/static/images/themes/dark.png | Bin 0 -> 75371 bytes web/static/images/themes/mattermost.png | Bin 0 -> 66828 bytes web/static/images/themes/slack.png | Bin 0 -> 68603 bytes web/static/js/bootstrap-colorpicker.min.js | 1 + web/templates/channel.html | 1 + web/templates/head.html | 2 + 41 files changed, 1161 insertions(+), 189 deletions(-) create mode 100644 web/react/components/user_settings/custom_theme_chooser.jsx create mode 100644 web/react/components/user_settings/import_theme_modal.jsx create mode 100644 web/react/components/user_settings/premade_theme_chooser.jsx create mode 100644 web/sass-files/sass/partials/_colorpicker.scss create mode 100755 web/static/css/bootstrap-colorpicker.min.css create mode 100755 web/static/images/bootstrap-colorpicker/alpha-horizontal.png create mode 100755 web/static/images/bootstrap-colorpicker/alpha.png create mode 100755 web/static/images/bootstrap-colorpicker/hue-horizontal.png create mode 100755 web/static/images/bootstrap-colorpicker/hue.png create mode 100755 web/static/images/bootstrap-colorpicker/saturation.png create mode 100644 web/static/images/themes/dark.png create mode 100644 web/static/images/themes/mattermost.png create mode 100644 web/static/images/themes/slack.png create mode 100755 web/static/js/bootstrap-colorpicker.min.js 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}