diff --git a/Gemfile b/Gemfile index 0cd4b934..a54ddcb1 100644 --- a/Gemfile +++ b/Gemfile @@ -40,6 +40,7 @@ gem 'fog-aws' gem 'momentjs-rails', '>= 2.9.0' gem 'bootstrap3-datetimepicker-rails', '~> 4.7.14' gem 'tiny-color-rails' +gem 'jquery-minicolors-rails' gem 'twemoji-rails' diff --git a/Gemfile.lock b/Gemfile.lock index 23e26eb8..57c816c4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -231,6 +231,9 @@ GEM jbuilder (2.2.13) activesupport (>= 3.0.0, < 5) multi_json (~> 1.2) + jquery-minicolors-rails (2.1.4.0) + jquery-rails + rails (>= 3.2.8) jquery-rails (4.0.3) rails-dom-testing (~> 1.0) railties (>= 4.2.0) @@ -513,6 +516,7 @@ DEPENDENCIES haml i18n-js jbuilder (~> 2.2.4) + jquery-minicolors-rails jquery-rails jquery-turbolinks letter_opener diff --git a/app/assets/javascripts/application.js.erb.coffee b/app/assets/javascripts/application.js.erb.coffee index 79549378..fdf02bd8 100644 --- a/app/assets/javascripts/application.js.erb.coffee +++ b/app/assets/javascripts/application.js.erb.coffee @@ -15,6 +15,7 @@ #= require i18n #= require i18n/translations #= require tinycolor-min +#= require jquery.minicolors # local requires to be seen by everyone: #= require_tree ./answerbox #= require_tree ./questionbox diff --git a/app/assets/javascripts/settings.coffee b/app/assets/javascripts/settings.coffee index 595511ba..d7ed181b 100644 --- a/app/assets/javascripts/settings.coffee +++ b/app/assets/javascripts/settings.coffee @@ -99,3 +99,57 @@ if window.URL? or window.webkitURL? ($ '#profile-header-crop-controls').slideDown() cropper.attr 'src', src + +# theming + +previewStyle = document.createElement 'style' +document.head.appendChild previewStyle + +previewTimeout = null + +previewTheme = -> + payload = {} + + $('#update_theme').find('.color').each -> + n = this.name.substr 6, this.name.length - 7 + payload[n] = parseInt this.value.substr(1, 6), 16 + + $.post '/settings/theme/preview.css', payload, (data) -> + previewStyle.innerHTML = data + , 'text' + + null + +themePresets = { + rs: [0x5E35B1, 0xFFFFFF, 0xFF0039, 0xFFFFFF, 0x3FB618, 0xFFFFFF, 0xFF7518, 0xFFFFFF, 0x9954BB, 0xFFFFFF, 0x222222, 0xEEEEEE, 0xF9F9F9, 0x151515, 0x5E35B1, 0xFFFFFF, 0x222222, 0xbbbbbb], + dc: [0x222222, 0xeeeeee, 0x222222, 0xeeeeee, 0x222222, 0xeeeeee, 0x222222, 0xeeeeee, 0x222222, 0xeeeeee, 0x222222, 0xeeeeee, 0x222222, 0xeeeeee, 0x111111, 0x555555, 0xeeeeee, 0xbbbbbb], + lc: [0xdddddd, 0x111111, 0xdddddd, 0x111111, 0xdddddd, 0x111111, 0xdddddd, 0x111111, 0xdddddd, 0x111111, 0xdddddd, 0x111111, 0xdddddd, 0x111111, 0xeeeeee, 0xaaaaaa, 0x111111, 0x444444] +} + +$(document).on 'submit', '#update_theme', (event) -> + $this = $ this + $this.find('.color').each -> + this.value = parseInt this.value.substr(1, 6), 16 + true + +$(document).ready -> + $('#update_theme .color').each -> + $this = $ this + this.value = '#' + ('000000' + parseInt(this.value).toString(16)).substr(-6, 6) + + $this.minicolors + control: 'hue' + defaultValue: this.value + letterCase: 'lowercase' + position: 'bottom left' + theme: 'bootstrap' + inline: false + change: -> + clearTimeout previewTimeout + previewTimeout = setTimeout(previewTheme, 1000) + true + +$(document).on 'click', 'a.theme_preset', (event) -> + preset = [].concat themePresets[this.dataset.preset] + $('#update_theme .color').each -> + $(this).minicolors 'value', '#' + ('000000' + parseInt(preset.shift()).toString(16)).substr(-6, 6) diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss index 90fe114c..a1791eb8 100644 --- a/app/assets/stylesheets/application.css.scss +++ b/app/assets/stylesheets/application.css.scss @@ -3,6 +3,7 @@ *= require growl *= require jquery.guillotine *= require sweet-alert + *= require jquery.minicolors *= require flags *= require_self */ @@ -63,3 +64,11 @@ body { padding-top: $navbar-height; } $nprogress-color: lighten($navbar-inverse-bg, 25%); @import 'nprogress'; @import 'nprogress-bootstrap'; + +.minicolors-theme-bootstrap .minicolors-swatch { + top: 0; + left: 0; + width: 42px; + height: 42px; + border-radius: 0; +} diff --git a/app/controllers/user_controller.rb b/app/controllers/user_controller.rb index 7af373b1..d2cfe93c 100644 --- a/app/controllers/user_controller.rb +++ b/app/controllers/user_controller.rb @@ -27,7 +27,7 @@ class UserController < ApplicationController def update user_attributes = params.require(:user).permit(:display_name, :profile_picture, :profile_header, :motivation_header, :website, - :location, :bio, :crop_x, :crop_y, :crop_w, :crop_h, :crop_h_x, :crop_h_y, :crop_h_w, :crop_h_h) + :location, :bio, :crop_x, :crop_y, :crop_w, :crop_h, :crop_h_x, :crop_h_y, :crop_h_w, :crop_h_h, :show_foreign_themes) if current_user.update_attributes(user_attributes) text = t('flash.user.update.text') text += t('flash.user.update.avatar') if user_attributes[:profile_picture] @@ -97,6 +97,27 @@ class UserController < ApplicationController def edit_theme end + # NOTE: Yes, I am storing and transmitting values as 3 byte numbers because false sense of security. + def preview_theme + attrib = params.permit([ + :primary_color, :primary_text, + :danger_color, :danger_text, + :success_color, :success_text, + :warning_color, :warning_text, + :info_color, :info_text, + :default_color, :default_text, + :panel_color, :panel_text, + :link_color, :background_color, + :background_text, :background_muted + ]) + + attrib.each do |k ,v| + attrib[k] = v.to_i + end + + render plain: render_theme_with_context(attrib) + end + def update_theme update_attributes = params.require(:theme).permit([ :primary_color, :primary_text, @@ -115,14 +136,14 @@ class UserController < ApplicationController current_user.theme.user_id = current_user.id if current_user.theme.save - flash[:success] = t('flash.user.update_theme.success') + flash[:success] = 'Theme saved.' else - flash[:error] = t('flash.user.update_theme.error') + flash[:error] = 'Theme saving failed.' end - elsif current_user.theme.update_attributes(user_attributes) - flash[:success] = t('flash.user.update_theme.success') + elsif current_user.theme.update_attributes(update_attributes) + flash[:success] = 'Theme saved.' else - flash[:error] = t('flash.user.update_theme.error') + flash[:error] = 'Theme saving failed.' end redirect_to edit_user_theme_path end diff --git a/app/helpers/theme_helper.rb b/app/helpers/theme_helper.rb index dcaa0576..019c13d1 100644 --- a/app/helpers/theme_helper.rb +++ b/app/helpers/theme_helper.rb @@ -15,13 +15,13 @@ module ThemeHelper x.each do |v| next if hash[v].nil? - self.instance_variable_set "@#{v}", hash[v].to_s(16)[-6, 6] + self.instance_variable_set "@#{v}", ('#' + ('0000000' + hash[v].to_s(16))[-6, 6]) end elsif hash.is_a? Hash hash.each do |k, v| next unless v.is_a? Fixnum - self.instance_variable_set "@#{k}", v.to_s(16)[-6, 6] + self.instance_variable_set "@#{k}", ('#' + ('0000000' + hash[k].to_s(16))[-6, 6]) end end end diff --git a/app/models/theme.rb b/app/models/theme.rb index 4a05d480..ffbaa745 100644 --- a/app/models/theme.rb +++ b/app/models/theme.rb @@ -15,14 +15,19 @@ class Theme < ActiveRecord::Base greater_than_or_equal_to: 0, less_than_or_equal_to: 0xFFFFFF, allow_nil: true, only_integer: true + has_attached_file :css, use_timestamp: false + validates_attachment_content_type :css, content_type: 'text/x-c' + before_save do - style = StringIO.new(render_theme_with_context(self).render) - + self.css = nil + + style = StringIO.new(render_theme_with_context(self)) + style.class.class_eval { attr_accessor :original_filename, :content_type } - style.content_type = 'text/stylesheet' + style.content_type = 'text/x-c' style.original_filename = 'theme.css' self.css = style diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index c2bd4ed5..161851fa 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -12,10 +12,10 @@ %title= yield(:title) = javascript_include_tag 'i18n', 'data-turbolinks-track' => true = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true - - if user_signed_in? and current_user.theme.present? - %link{rel: 'stylesheet', href: current_user.theme.css.url, media: :all, 'data-turbolinks-track' => true} - - elsif @user.present? and @user.theme.present? + - if @user.present? and @user.theme.present? and (user_signed_in? and current_user.show_foreign_themes?) %link{rel: 'stylesheet', href: @user.theme.css.url, media: :all, 'data-turbolinks-track' => true} + - elsif user_signed_in? and current_user.theme.present? + %link{rel: 'stylesheet', href: current_user.theme.css.url, media: :all, 'data-turbolinks-track' => true} - else = javascript_include_tag 'application', 'data-turbolinks-track' => true - if user_signed_in? diff --git a/app/views/user/_settings_tabs.html.haml b/app/views/user/_settings_tabs.html.haml index 0f30d0fa..676dfa81 100644 --- a/app/views/user/_settings_tabs.html.haml +++ b/app/views/user/_settings_tabs.html.haml @@ -5,6 +5,7 @@ = list_group_item t('views.settings.tabs.profile'), edit_user_profile_path = list_group_item t('views.settings.tabs.privacy'), edit_user_privacy_path = list_group_item t('views.settings.tabs.sharing'), services_path + = list_group_item 'Theme', edit_user_theme_path = list_group_item "Your Data", user_data_path - + .hidden-xs= render "shared/links" diff --git a/app/views/user/edit.html.haml b/app/views/user/edit.html.haml index d04ee81e..0120ecf6 100644 --- a/app/views/user/edit.html.haml +++ b/app/views/user/edit.html.haml @@ -51,6 +51,8 @@ = f.text_area :bio, label: t('views.settings.profile.bio'), placeholder: t('views.settings.profile.placeholder.bio') + = f.check_box :show_foreign_themes, label: 'Render other user themes when visiting their profile' + - for attrib in %i(crop_x crop_y crop_w crop_h) = f.hidden_field attrib, id: attrib diff --git a/app/views/user/edit_theme.html.haml b/app/views/user/edit_theme.html.haml new file mode 100644 index 00000000..d22fa9d5 --- /dev/null +++ b/app/views/user/edit_theme.html.haml @@ -0,0 +1,59 @@ +- provide(:title, generate_title("Theme Settings")) +.container.j2-page + = render 'settings_tabs' + .col-md-9.col-xs-12.col-sm-9 + = render 'layouts/messages' + .panel.panel-default + .panel-body + %b Presets: + %a{href: '#', class: 'theme_preset', data: {preset: 'rs'}} Retrospring Purple, + %a{href: '#', class: 'theme_preset', data: {preset: 'dc'}} Dark Copycat, + %a{href: '#', class: 'theme_preset', data: {preset: 'lc'}} Light Copycat + = bootstrap_form_for(current_user.theme || Theme.new, url: {action: "update_theme"}, html: {id: 'update_theme'}, method: "patch") do |f| + .row + .col-md-6 + = f.text_field :primary_color, class: 'color', data: {default: 0x5E35B1} + .col-md-6 + = f.text_field :primary_text, class: 'color', data: {default: 0xFFFFFF} + .row + .col-md-6 + = f.text_field :danger_color, class: 'color', data: {default: 0xFF0039} + .col-md-6 + = f.text_field :danger_text, class: 'color', data: {default: 0xFFFFFF} + .row + .col-md-6 + = f.text_field :success_color, class: 'color', data: {default: 0x3FB618} + .col-md-6 + = f.text_field :success_text, class: 'color', data: {default: 0xFFFFFF} + .row + .col-md-6 + = f.text_field :warning_color, class: 'color', data: {default: 0xFF7518} + .col-md-6 + = f.text_field :warning_text, class: 'color', data: {default: 0xFFFFFF} + .row + .col-md-6 + = f.text_field :info_color, class: 'color', data: {default: 0x9954BB} + .col-md-6 + = f.text_field :info_text, class: 'color', data: {default: 0xFFFFFF} + .row + .col-md-6 + = f.text_field :default_color, class: 'color', data: {default: 0x222222} + .col-md-6 + = f.text_field :default_text, class: 'color', data: {default: 0xEEEEEE} + .row + .col-md-6 + = f.text_field :panel_color, class: 'color', data: {default: 0xF9F9F9} + .col-md-6 + = f.text_field :panel_text, class: 'color', data: {default: 0x151515} + .row + .col-md-6 + = f.text_field :link_color, class: 'color', data: {default: 0x5E35B1} + .col-md-6 + = f.text_field :background_color, class: 'color', data: {default: 0xFFFFFF} + .row + .col-md-6 + = f.text_field :background_text, class: 'color', data: {default: 0x222222} + .col-md-6 + = f.text_field :background_muted, class: 'color', data: {default: 0xBBBBBB} + + = f.submit t('views.actions.save'), class: 'btn btn-primary' diff --git a/config/routes.rb b/config/routes.rb index 3f9f2cad..87be1877 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -56,6 +56,7 @@ Rails.application.routes.draw do match '/settings/theme', to: 'user#edit_theme', via: 'get', as: :edit_user_theme match '/settings/theme', to: 'user#update_theme', via: 'patch', as: :update_user_theme + match '/settings/theme/preview.css', to: 'user#preview_theme', via: 'post', as: :preview_user_theme # resources :services, only: [:index, :destroy] match '/settings/services', to: 'services#index', via: 'get', as: :services diff --git a/db/migrate/20150825180139_add_show_foreign_themes_to_users.rb b/db/migrate/20150825180139_add_show_foreign_themes_to_users.rb new file mode 100644 index 00000000..4b7abd69 --- /dev/null +++ b/db/migrate/20150825180139_add_show_foreign_themes_to_users.rb @@ -0,0 +1,5 @@ +class AddShowForeignThemesToUsers < ActiveRecord::Migration + def change + add_column :users, :show_foreign_themes, :boolean, default: true, null: false + end +end