From 36c1acb362bee5aa84c24c621a94068b981d414e Mon Sep 17 00:00:00 2001 From: Elia Schito Date: Mon, 13 Nov 2023 17:08:47 +0100 Subject: [PATCH] Add a theme selector to the backend UI with dark and dimmed variants - Save the user preference alongside the system preference so that when the system switches the UI will follow along. - Use the session to store preferences so that we start the page with the correct theme(s). - Keep the current theme in the select tag up to date at page load and whenever the system changes. Add dark and dimmed theme variants dark: Full black background with no alteration on images. dimmed: Dark gray background with dimmed images, easier on the eyes but less accurate colors. Co-Authored-By: piyushswain Co-Authored-By: Elia Schito Co-Authored-By: Massimiliano Lattanzio --- .../assets/config/solidus_backend_manifest.js | 4 +-- .../spree/backend/components/_navigation.scss | 16 ++++++++++ .../components/_navigation_solidus_admin.scss | 14 ++++++++ .../backend/themes/classic_dark.css.scss | 16 ++++++++++ .../backend/themes/classic_dimmed.css.scss | 16 ++++++++++ .../backend/themes/solidus_admin.css.scss | 1 + .../backend/themes/solidus_admin/_tables.scss | 2 ++ .../themes/solidus_admin_dark.css.scss | 16 ++++++++++ .../themes/solidus_admin_dimmed.css.scss | 16 ++++++++++ .../spree/admin/theme_controller.rb | 30 +++++++++++++++++ .../views/spree/admin/shared/_head.html.erb | 3 +- .../spree/admin/shared/_navigation.html.erb | 1 + .../shared/_navigation_solidus_admin.html.erb | 1 + .../admin/shared/_theme_selection.html.erb | 21 ++++++++++++ .../_theme_selection_solidus_admin.html.erb | 25 +++++++++++++++ backend/config/routes.rb | 1 + backend/lib/spree/backend_configuration.rb | 15 +++++++-- .../spree/admin/theme_controller_spec.rb | 32 +++++++++++++++++++ .../lib/spree/backend_configuration_spec.rb | 12 +++---- core/config/locales/en.yml | 2 ++ 20 files changed, 231 insertions(+), 13 deletions(-) create mode 100644 backend/app/assets/stylesheets/spree/backend/themes/classic_dark.css.scss create mode 100644 backend/app/assets/stylesheets/spree/backend/themes/classic_dimmed.css.scss create mode 100644 backend/app/assets/stylesheets/spree/backend/themes/solidus_admin_dark.css.scss create mode 100644 backend/app/assets/stylesheets/spree/backend/themes/solidus_admin_dimmed.css.scss create mode 100644 backend/app/controllers/spree/admin/theme_controller.rb create mode 100644 backend/app/views/spree/admin/shared/_theme_selection.html.erb create mode 100644 backend/app/views/spree/admin/shared/_theme_selection_solidus_admin.html.erb create mode 100644 backend/spec/controllers/spree/admin/theme_controller_spec.rb diff --git a/backend/app/assets/config/solidus_backend_manifest.js b/backend/app/assets/config/solidus_backend_manifest.js index 49d989282a6..71a745f44f1 100644 --- a/backend/app/assets/config/solidus_backend_manifest.js +++ b/backend/app/assets/config/solidus_backend_manifest.js @@ -1,9 +1,7 @@ //= link_tree ../images +//= link_tree "../stylesheets/spree/backend/themes" .css //= link spree/backend/all.js //= link spree/backend/all.css //= link solidus_admin/select2_locales - -//= link spree/backend/themes/classic.css -//= link spree/backend/themes/solidus_admin.css diff --git a/backend/app/assets/stylesheets/spree/backend/components/_navigation.scss b/backend/app/assets/stylesheets/spree/backend/components/_navigation.scss index 05b2c4ff5a7..a10809a5c21 100644 --- a/backend/app/assets/stylesheets/spree/backend/components/_navigation.scss +++ b/backend/app/assets/stylesheets/spree/backend/components/_navigation.scss @@ -122,6 +122,22 @@ nav.menu { @media print { display: none } + + + .dark-only { + display: none; + @media (prefers-color-scheme: dark) { + display: block; + } + } + + .light-only { + display: block; + @media (prefers-color-scheme: dark) { + display: none; + } + } + } .admin-nav-header { diff --git a/backend/app/assets/stylesheets/spree/backend/components/_navigation_solidus_admin.scss b/backend/app/assets/stylesheets/spree/backend/components/_navigation_solidus_admin.scss index ee2d567cbd3..ae54248ec34 100644 --- a/backend/app/assets/stylesheets/spree/backend/components/_navigation_solidus_admin.scss +++ b/backend/app/assets/stylesheets/spree/backend/components/_navigation_solidus_admin.scss @@ -19,6 +19,20 @@ $color-navbar-hover: $color-navbar !default; min-height: 100vh; border-right: $color-light-accent 1px solid; + .dark-only { + display: none; + @media (prefers-color-scheme: dark) { + display: block; + } + } + + .light-only { + display: none; + @media (prefers-color-scheme: light) { + display: block; + } + } + &--wrapper { position: absolute; top: 0; diff --git a/backend/app/assets/stylesheets/spree/backend/themes/classic_dark.css.scss b/backend/app/assets/stylesheets/spree/backend/themes/classic_dark.css.scss new file mode 100644 index 00000000000..5549b4c636f --- /dev/null +++ b/backend/app/assets/stylesheets/spree/backend/themes/classic_dark.css.scss @@ -0,0 +1,16 @@ +@import 'spree/backend/themes/classic'; + +html { + background-color: #fff; + color: #fff; + -webkit-filter: invert(100%); + filter: invert(100%) hue-rotate(180deg); + + img { + filter: invert(100%) hue-rotate(-180deg); + } + + .brand-link img { + filter: invert(0%) hue-rotate(0deg); + } +} diff --git a/backend/app/assets/stylesheets/spree/backend/themes/classic_dimmed.css.scss b/backend/app/assets/stylesheets/spree/backend/themes/classic_dimmed.css.scss new file mode 100644 index 00000000000..8dfc79471f8 --- /dev/null +++ b/backend/app/assets/stylesheets/spree/backend/themes/classic_dimmed.css.scss @@ -0,0 +1,16 @@ +@import 'spree/backend/themes/classic'; + +html { + background-color: #fff; + color: #fff; + -webkit-filter: invert(85%); + filter: invert(85%) hue-rotate(180deg); + + img { + filter: invert(100%) hue-rotate(-180deg); + } + + .brand-link img { + filter: invert(0%) hue-rotate(0deg); + } +} diff --git a/backend/app/assets/stylesheets/spree/backend/themes/solidus_admin.css.scss b/backend/app/assets/stylesheets/spree/backend/themes/solidus_admin.css.scss index 17955943be8..636633c325b 100644 --- a/backend/app/assets/stylesheets/spree/backend/themes/solidus_admin.css.scss +++ b/backend/app/assets/stylesheets/spree/backend/themes/solidus_admin.css.scss @@ -1,6 +1,7 @@ @import "spree/backend/globals"; @import "./solidus_admin/variables"; +@import "./solidus_admin/colors"; @import "spree/backend/vendor"; @import "spree/backend/shared"; diff --git a/backend/app/assets/stylesheets/spree/backend/themes/solidus_admin/_tables.scss b/backend/app/assets/stylesheets/spree/backend/themes/solidus_admin/_tables.scss index 9309eb426d7..618155a12f0 100644 --- a/backend/app/assets/stylesheets/spree/backend/themes/solidus_admin/_tables.scss +++ b/backend/app/assets/stylesheets/spree/backend/themes/solidus_admin/_tables.scss @@ -1,3 +1,5 @@ +@import "./colors"; + table.index { // Borders border-collapse: separate; diff --git a/backend/app/assets/stylesheets/spree/backend/themes/solidus_admin_dark.css.scss b/backend/app/assets/stylesheets/spree/backend/themes/solidus_admin_dark.css.scss new file mode 100644 index 00000000000..4728b02308d --- /dev/null +++ b/backend/app/assets/stylesheets/spree/backend/themes/solidus_admin_dark.css.scss @@ -0,0 +1,16 @@ +@import "./solidus_admin"; + +html { + background-color: #fff; + color: #fff; + -webkit-filter: invert(100%); + filter: invert(100%) hue-rotate(180deg); + + img { + filter: invert(100%) hue-rotate(-180deg); + } + + .brand-link img { + filter: invert(0%) hue-rotate(0deg); + } +} diff --git a/backend/app/assets/stylesheets/spree/backend/themes/solidus_admin_dimmed.css.scss b/backend/app/assets/stylesheets/spree/backend/themes/solidus_admin_dimmed.css.scss new file mode 100644 index 00000000000..34b20be1d4e --- /dev/null +++ b/backend/app/assets/stylesheets/spree/backend/themes/solidus_admin_dimmed.css.scss @@ -0,0 +1,16 @@ +@import "./solidus_admin"; + +html { + background-color: #fff; + color: #fff; + -webkit-filter: invert(85%); + filter: invert(85%) hue-rotate(180deg); + + img { + filter: invert(100%) hue-rotate(-180deg); + } + + .brand-link img { + filter: invert(0%) hue-rotate(0deg); + } +} diff --git a/backend/app/controllers/spree/admin/theme_controller.rb b/backend/app/controllers/spree/admin/theme_controller.rb new file mode 100644 index 00000000000..b00852afe91 --- /dev/null +++ b/backend/app/controllers/spree/admin/theme_controller.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Spree + module Admin + class ThemeController < Spree::Admin::BaseController + skip_before_action :authorize_admin, only: [:set] + + def set + requested_theme = params[:switch_to_theme].presence + + # Avoid interpolating user content into the session key + system_theme = params[:system_theme].presence == "dark" ? "dark" : "light" + session_key = :"admin_#{system_theme}_theme" + + if theme_is_available?(requested_theme) + session[session_key] = requested_theme + redirect_back_or_to spree.admin_url, notice: t('spree.theme_changed') + else + redirect_back_or_to spree.admin_url, error: t('spree.error') + end + end + + private + + def theme_is_available?(theme) + theme && Spree::Backend::Config.themes.key?(theme.to_sym) + end + end + end +end diff --git a/backend/app/views/spree/admin/shared/_head.html.erb b/backend/app/views/spree/admin/shared/_head.html.erb index 1287add73a8..4bcfd5a455b 100644 --- a/backend/app/views/spree/admin/shared/_head.html.erb +++ b/backend/app/views/spree/admin/shared/_head.html.erb @@ -6,7 +6,8 @@ <%= admin_page_title %> <%= favicon_link_tag 'favicon.ico' %> -<%= stylesheet_link_tag Spree::Backend::Config.theme_path, media: 'all', data: {turbolinks_track: 'reload'} %> +<%= stylesheet_link_tag Spree::Backend::Config.theme_path(session[:admin_light_theme]), media: '(prefers-color-scheme: light)', data: {turbolinks_track: 'reload'} %> +<%= stylesheet_link_tag Spree::Backend::Config.theme_path(session[:admin_dark_theme]), media: '(prefers-color-scheme: dark)', data: {turbolinks_track: 'reload'} %> <%= javascript_include_tag 'spree/backend/all', data: {turbolinks_track: 'reload'} %> <%- if Rails.env.test? %> diff --git a/backend/app/views/spree/admin/shared/_navigation.html.erb b/backend/app/views/spree/admin/shared/_navigation.html.erb index 9f15ae2861c..36bb36e72e3 100644 --- a/backend/app/views/spree/admin/shared/_navigation.html.erb +++ b/backend/app/views/spree/admin/shared/_navigation.html.erb @@ -7,6 +7,7 @@ <%= t('spree.minimize_menu') %> <% end %> <%= render partial: 'spree/admin/shared/locale_selection' %> + <%= render partial: 'spree/admin/shared/theme_selection' %> <% if lookup_context.exists?('spree/admin/shared/_navigation_footer') %> <%= render partial: 'spree/admin/shared/navigation_footer' %> <% else %> diff --git a/backend/app/views/spree/admin/shared/_navigation_solidus_admin.html.erb b/backend/app/views/spree/admin/shared/_navigation_solidus_admin.html.erb index 6355b5ec8b5..44af1123fff 100644 --- a/backend/app/views/spree/admin/shared/_navigation_solidus_admin.html.erb +++ b/backend/app/views/spree/admin/shared/_navigation_solidus_admin.html.erb @@ -50,6 +50,7 @@
    <%= render 'spree/admin/shared/locale_selection_solidus_admin' %> + <%= render 'spree/admin/shared/theme_selection_solidus_admin' %> <% if can?(:admin, spree_current_user) %>
  • diff --git a/backend/app/views/spree/admin/shared/_theme_selection.html.erb b/backend/app/views/spree/admin/shared/_theme_selection.html.erb new file mode 100644 index 00000000000..ad4d7a2027e --- /dev/null +++ b/backend/app/views/spree/admin/shared/_theme_selection.html.erb @@ -0,0 +1,21 @@ +<% theme_options_for_select = Spree::Backend::Config.themes.keys.map { |theme| [theme.to_s.humanize, theme] }.sort %> + +<%= form_tag(admin_set_theme_path(format: :html), method: :put, style: "width: 100%;", class: "light-only") do %> + <%= hidden_field_tag :system_theme, :light %> + +<% end %> + +<%= form_tag(admin_set_theme_path(format: :html), method: :put, style: "width: 100%;", class: "dark-only") do %> + <%= hidden_field_tag :system_theme, :dark %> + +<% end %> diff --git a/backend/app/views/spree/admin/shared/_theme_selection_solidus_admin.html.erb b/backend/app/views/spree/admin/shared/_theme_selection_solidus_admin.html.erb new file mode 100644 index 00000000000..d2309396d41 --- /dev/null +++ b/backend/app/views/spree/admin/shared/_theme_selection_solidus_admin.html.erb @@ -0,0 +1,25 @@ +<% theme_options_for_select = Spree::Backend::Config.themes.keys.map { |theme| [theme.to_s.humanize, theme] }.sort %> + +
  • + <%= form_tag(admin_set_theme_path(format: :html), method: :put, style: "width: 100%;", class: "light-only") do %> + <%= hidden_field_tag :system_theme, :light %> + + <% end %> + + <%= form_tag(admin_set_theme_path(format: :html), method: :put, style: "width: 100%;", class: "dark-only") do %> + <%= hidden_field_tag :system_theme, :dark %> + + <% end %> +
  • diff --git a/backend/config/routes.rb b/backend/config/routes.rb index f8c70640e61..343d1bc028e 100644 --- a/backend/config/routes.rb +++ b/backend/config/routes.rb @@ -6,6 +6,7 @@ get '/search/products', to: "search#products", as: :search_products put '/locale/set', to: 'locale#set', defaults: { format: :json }, as: :set_locale + put '/theme/set', to: 'theme#set', defaults: { format: :json }, as: :set_theme resources :dashboards, only: [] do collection do diff --git a/backend/lib/spree/backend_configuration.rb b/backend/lib/spree/backend_configuration.rb index 9291730b0d1..2e801149e93 100644 --- a/backend/lib/spree/backend_configuration.rb +++ b/backend/lib/spree/backend_configuration.rb @@ -11,15 +11,24 @@ class BackendConfiguration < Preferences::Configuration # @return [Hash] A hash containing the themes that are available for the admin panel preference :themes, :hash, default: { classic: 'spree/backend/all', + classic_dark: 'spree/backend/themes/classic_dark', + classic_dark_dimmed: 'spree/backend/themes/classic_dimmed', + solidus: 'spree/backend/themes/solidus_admin', + solidus_dark: 'spree/backend/themes/solidus_admin_dark', + solidus_dimmed: 'spree/backend/themes/solidus_admin_dimmed', solidus_admin: 'spree/backend/themes/solidus_admin' } # @!attribute [rw] theme # @return [String] Default admin theme name - versioned_preference :theme, :string, initial_value: 'classic', boundaries: { "4.2.0" => "solidus_admin" } + versioned_preference :theme, :string, initial_value: 'classic', boundaries: { "4.2.0" => "solidus_admin", "4.4.0" => "solidus" } - def theme_path(user_theme = nil) - user_theme ? themes.fetch(user_theme.to_sym) : themes.fetch(theme.to_sym) + # @!attribute [rw] dark_theme + # @return [String] Dark admin theme name + versioned_preference :dark_theme, :string, initial_value: 'classic', boundaries: { "4.2.0" => "solidus_admin", "4.4.0" => 'solidus_dark' } + + def theme_path(user_theme) + themes.fetch(user_theme&.to_sym, themes.fetch(theme.to_sym)) end # @!attribute [rw] admin_updated_navbar diff --git a/backend/spec/controllers/spree/admin/theme_controller_spec.rb b/backend/spec/controllers/spree/admin/theme_controller_spec.rb new file mode 100644 index 00000000000..6ba9984f93b --- /dev/null +++ b/backend/spec/controllers/spree/admin/theme_controller_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Spree::Admin::ThemeController, type: :controller do + stub_authorization! + + it 'sets the theme in a different session key for each system theme' do + stub_spree_preferences(Spree::Backend::Config, themes: { foo: 'foo-path', bar: 'bar-path' }) + + get :set, params: { switch_to_theme: 'foo', system_theme: 'light', format: :json } + + expect(session[:admin_light_theme]).to eq('foo') + expect(session[:admin_dark_theme]).to eq(nil) + expect(response).to have_http_status(:redirect) + + get :set, params: { switch_to_theme: 'bar', system_theme: 'dark', format: :json } + expect(session[:admin_light_theme]).to eq('foo') + expect(session[:admin_dark_theme]).to eq('bar') + expect(response).to have_http_status(:redirect) + end + + it 'responds with "not found" for a missing theme' do + stub_spree_preferences(Spree::Backend::Config, themes: { foo: 'foo-path' }) + + get :set, params: { switch_to_theme: 'bar', system_theme: 'dark', format: :json } + + expect(session[:admin_light_theme]).to eq(nil) + expect(session[:admin_dark_theme]).to eq(nil) + expect(response).to have_http_status(:redirect) + end +end diff --git a/backend/spec/lib/spree/backend_configuration_spec.rb b/backend/spec/lib/spree/backend_configuration_spec.rb index ce716412003..75abd233c42 100644 --- a/backend/spec/lib/spree/backend_configuration_spec.rb +++ b/backend/spec/lib/spree/backend_configuration_spec.rb @@ -79,25 +79,25 @@ def product_path(product) end describe '#theme_path' do - it 'returns the default theme path' do + it 'returns the default theme path if the user theme is not set' do subject.themes = { foo: 'foo-theme-path' } subject.theme = :foo - expect(subject.theme_path).to eq('foo-theme-path') + expect(subject.theme_path(nil)).to eq('foo-theme-path') end it 'returns the default theme path when the theme is a string' do subject.themes = { foo: 'foo-theme-path' } subject.theme = 'foo' - expect(subject.theme_path).to eq('foo-theme-path') + expect(subject.theme_path(nil)).to eq('foo-theme-path') end it 'returns the fallback theme path when the default theme is missing' do subject.themes = { foo: 'foo-theme-path', classic: 'classic-theme-path' } subject.theme = :bar - expect{ subject.theme_path }.to raise_error(KeyError) + expect{ subject.theme_path(:baz) }.to raise_error(KeyError) end it 'gives priority to the user defined theme' do @@ -107,11 +107,11 @@ def product_path(product) expect(subject.theme_path(:user)).to eq('user-theme-path') end - it 'raises an error if the user theme is missing' do + it 'falls back to the configure theme if the user theme is missing' do subject.themes = { foo: 'foo-theme-path', classic: 'classic-theme-path' } subject.theme = :foo - expect{ subject.theme_path(:bar) }.to raise_error(KeyError) + expect(subject.theme_path(:bar)).to eq('foo-theme-path') end end diff --git a/core/config/locales/en.yml b/core/config/locales/en.yml index 1327ebc708d..8c0cd525e3a 100644 --- a/core/config/locales/en.yml +++ b/core/config/locales/en.yml @@ -1141,6 +1141,7 @@ en: choose_a_taxon_to_sort_products_for: Choose a taxon to sort products for choose_currency: Choose Currency choose_dashboard_locale: Choose Dashboard Locale + choose_dashboard_theme: Choose Dashboard Theme choose_location: Choose Location choose_promotion_action: Choose Action choose_promotion_rule: Choose Rule @@ -2347,6 +2348,7 @@ en: subject: Test Mail test_mode: Test Mode thank_you_for_your_order: Thank you for your business. Please print out a copy of this confirmation page for your records. + theme_changed: Theme Changed there_are_no_items_for_this_order: There are no items for this order. Please add an item to the order to continue. there_were_problems_with_the_following_fields: There were problems with the following fields this_order_has_already_received_a_refund: This order has already received a refund