FEATURE: Allow themes to override color transformation variables (#7987)

Theme developers can now add any of the transformed color variables to their color scheme in about.json. For example

```
  "color_schemes": {
    "Light": {
      "primary": "333333",
      "secondary": "ffffff",
      "primary-low": "ff0000"
    }
  },
```

would override the primary-low variable when compiling SCSS for the color scheme. The primary-low variable will also be visible in administrator color palette UI.
This commit is contained in:
David Taylor 2019-08-12 11:02:38 +01:00 committed by GitHub
parent 750802bf56
commit d348368ab6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 161 additions and 109 deletions

View File

@ -3,7 +3,7 @@ import {
observes,
on
} from "ember-addons/ember-computed-decorators";
import { propertyNotEqual, i18n } from "discourse/lib/computed";
import { propertyNotEqual } from "discourse/lib/computed";
const ColorSchemeColor = Discourse.Model.extend({
@on("init")
@ -42,9 +42,23 @@ const ColorSchemeColor = Discourse.Model.extend({
}
},
translatedName: i18n("name", "admin.customize.colors.%@.name"),
@computed("name")
translatedName(name) {
if (!this.is_advanced) {
return I18n.t(`admin.customize.colors.${name}.name`);
} else {
return name;
}
},
description: i18n("name", "admin.customize.colors.%@.description"),
@computed("name")
description(name) {
if (!this.is_advanced) {
return I18n.t(`admin.customize.colors.${name}.description`);
} else {
return "";
}
},
/**
brightness returns a number between 0 (darkest) to 255 (brightest).

View File

@ -128,7 +128,8 @@ ColorScheme.reopenClass({
return ColorSchemeColor.create({
name: c.name,
hex: c.hex,
default_hex: c.default_hex
default_hex: c.default_hex,
is_advanced: c.is_advanced
});
})
})

View File

@ -0,0 +1,77 @@
// standard color transformations, use these if possible, and add any new dark-light-diffs here
// any variables defined here can be added in theme color schemes
// all variables should have the !default flag
//primary
$primary-very-low: dark-light-diff($primary, $secondary, 97%, -82%) !default;
$primary-low: dark-light-diff($primary, $secondary, 90%, -78%) !default;
$primary-low-mid: dark-light-diff($primary, $secondary, 70%, -45%) !default;
$primary-medium: dark-light-diff($primary, $secondary, 50%, -35%) !default;
$primary-high: dark-light-diff($primary, $secondary, 30%, -25%) !default;
$primary-very-high: dark-light-diff($primary, $secondary, 15%, -10%) !default;
//header_primary
$header_primary-low: dark-light-diff(
$header_primary,
$header_background,
90%,
-78%
) !default;
$header_primary-low-mid: dark-light-diff(
$header_primary,
$header_background,
70%,
-45%
) !default;
$header_primary-medium: dark-light-diff(
$header_primary,
$header_background,
50%,
-35%
) !default;
$header_primary-high: dark-light-diff(
$header_primary,
$header_background,
30%,
-25%
) !default;
$header_primary-very-high: dark-light-diff(
$header_primary,
$header_background,
15%,
-10%
) !default;
//secondary
$secondary-low: dark-light-diff($secondary, $primary, 70%, -70%) !default;
$secondary-medium: dark-light-diff($secondary, $primary, 50%, -50%) !default;
$secondary-high: dark-light-diff($secondary, $primary, 30%, -35%) !default;
$secondary-very-high: dark-light-diff($secondary, $primary, 7%, -7%) !default;
//tertiary
$tertiary-low: dark-light-diff($tertiary, $secondary, 85%, -65%) !default;
$tertiary-medium: dark-light-diff($tertiary, $secondary, 50%, -45%) !default;
$tertiary-high: dark-light-diff($tertiary, $secondary, 20%, -25%) !default;
//quaternary
$quaternary-low: dark-light-diff($quaternary, $secondary, 70%, -70%) !default;
//highlight
$highlight-low: dark-light-diff($highlight, $secondary, 70%, -80%) !default;
$highlight-medium: dark-light-diff($highlight, $secondary, 50%, -55%) !default;
$highlight-high: dark-light-diff($highlight, $secondary, -50%, -10%) !default;
//danger
$danger-low: dark-light-diff($danger, $secondary, 85%, -64%) !default;
$danger-medium: dark-light-diff($danger, $secondary, 30%, -35%) !default;
//success
$success-low: dark-light-diff($success, $secondary, 80%, -60%) !default;
$success-medium: dark-light-diff($success, $secondary, 50%, -40%) !default;
//love
$love-low: dark-light-diff($love, $secondary, 85%, -60%) !default;
//wiki
$wiki: green !default;

View File

@ -204,78 +204,4 @@ $box-shadow: (
}
}
// standard color transformations, use these if possible, and add any new dark-light-diffs here
//primary
$primary-very-low: dark-light-diff($primary, $secondary, 97%, -82%);
$primary-low: dark-light-diff($primary, $secondary, 90%, -78%);
$primary-low-mid: dark-light-diff($primary, $secondary, 70%, -45%);
$primary-medium: dark-light-diff($primary, $secondary, 50%, -35%);
$primary-high: dark-light-diff($primary, $secondary, 30%, -25%);
$primary-very-high: dark-light-diff($primary, $secondary, 15%, -10%);
//header_primary
$header_primary-low: dark-light-diff(
$header_primary,
$header_background,
90%,
-78%
);
$header_primary-low-mid: dark-light-diff(
$header_primary,
$header_background,
70%,
-45%
);
$header_primary-medium: dark-light-diff(
$header_primary,
$header_background,
50%,
-35%
);
$header_primary-high: dark-light-diff(
$header_primary,
$header_background,
30%,
-25%
);
$header_primary-very-high: dark-light-diff(
$header_primary,
$header_background,
15%,
-10%
);
//secondary
$secondary-low: dark-light-diff($secondary, $primary, 70%, -70%);
$secondary-medium: dark-light-diff($secondary, $primary, 50%, -50%);
$secondary-high: dark-light-diff($secondary, $primary, 30%, -35%);
$secondary-very-high: dark-light-diff($secondary, $primary, 7%, -7%);
//tertiary
$tertiary-low: dark-light-diff($tertiary, $secondary, 85%, -65%);
$tertiary-medium: dark-light-diff($tertiary, $secondary, 50%, -45%);
$tertiary-high: dark-light-diff($tertiary, $secondary, 20%, -25%);
//quaternary
$quaternary-low: dark-light-diff($quaternary, $secondary, 70%, -70%);
//highlight
$highlight-low: dark-light-diff($highlight, $secondary, 70%, -80%);
$highlight-medium: dark-light-diff($highlight, $secondary, 50%, -55%);
$highlight-high: dark-light-diff($highlight, $secondary, -50%, -10%);
//danger
$danger-low: dark-light-diff($danger, $secondary, 85%, -64%);
$danger-medium: dark-light-diff($danger, $secondary, 30%, -35%);
//success
$success-low: dark-light-diff($success, $secondary, 80%, -60%);
$success-medium: dark-light-diff($success, $secondary, 50%, -40%);
//love
$love-low: dark-light-diff($love, $secondary, 85%, -60%);
//wiki
$wiki: green;
@import "color_transformations";

View File

@ -140,6 +140,7 @@ class ColorScheme < ActiveRecord::Base
validates_associated :color_scheme_colors
BASE_COLORS_FILE = "#{Rails.root}/app/assets/stylesheets/common/foundation/colors.scss"
COLOR_TRANSFORMATION_FILE = "#{Rails.root}/app/assets/stylesheets/common/foundation/color_transformations.scss"
@mutex = Mutex.new
@ -157,6 +158,20 @@ class ColorScheme < ActiveRecord::Base
@base_colors
end
def self.color_transformation_variables
return @transformation_variables if @transformation_variables
@mutex.synchronize do
return @transformation_variables if @transformation_variables
transformation_variables = []
File.readlines(COLOR_TRANSFORMATION_FILE).each do |line|
matches = /\$([\w\-_]+):.*/.match(line.strip)
transformation_variables.append(matches[1]) if matches
end
@transformation_variables = transformation_variables
end
@transformation_variables
end
def self.base_color_schemes
base_color_scheme_colors.map do |hash|
scheme = new(name: I18n.t("color_schemes.#{hash[:id].downcase.gsub(' ', '_')}"), base_scheme_id: hash[:id])

View File

@ -203,24 +203,32 @@ class RemoteTheme < ActiveRecord::Base
schemes&.each do |name, colors|
missing_scheme_names.delete(name)
existing = theme.color_schemes.find_by(name: name)
if existing
existing.colors.each do |c|
override = normalize_override(colors[c.name])
if override && c.hex != override
c.hex = override
theme.notify_color_change(c)
end
end
ordered_schemes << existing
else
scheme = theme.color_schemes.build(name: name)
ColorScheme.base.colors_hashes.each do |color|
override = normalize_override(colors[color[:name]])
scheme.color_scheme_colors << ColorSchemeColor.new(name: color[:name], hex: override || color[:hex])
end
ordered_schemes << scheme
scheme = theme.color_schemes.find_by(name: name) || theme.color_schemes.build(name: name)
# Update main colors
ColorScheme.base.colors_hashes.each do |color|
override = normalize_override(colors[color[:name]])
color_scheme_color = scheme.color_scheme_colors.to_a.find { |c| c.name == color[:name] } ||
scheme.color_scheme_colors.build(name: color[:name])
color_scheme_color.hex = override || color[:hex]
theme.notify_color_change(color_scheme_color)
end
# Update advanced colors
ColorScheme.color_transformation_variables.each do |variable_name|
override = normalize_override(colors[variable_name])
color_scheme_color = scheme.color_scheme_colors.to_a.find { |c| c.name == variable_name }
if override
color_scheme_color ||= scheme.color_scheme_colors.build(name: variable_name)
color_scheme_color.hex = override
theme.notify_color_change(color_scheme_color)
elsif color_scheme_color # No longer specified in about.json, delete record
scheme.color_scheme_colors.delete(color_scheme_color)
theme.notify_color_change(nil, scheme: scheme)
end
end
ordered_schemes << scheme
end
if missing_scheme_names.length > 0

View File

@ -34,20 +34,18 @@ class Theme < ActiveRecord::Base
where('user_selectable OR id = ?', SiteSetting.default_theme_id)
}
def notify_color_change(color)
changed_colors << color
def notify_color_change(color, scheme: nil)
scheme ||= color.color_scheme
changed_colors << color if color
changed_schemes << scheme if scheme
end
after_save do
color_schemes = {}
changed_colors.each do |color|
color.save!
color_schemes[color.color_scheme_id] ||= color.color_scheme
end
color_schemes.values.each(&:save!)
changed_colors.each(&:save!)
changed_schemes.each(&:save!)
changed_colors.clear
changed_schemes.clear
changed_fields.each(&:save!)
changed_fields.clear
@ -343,6 +341,10 @@ class Theme < ActiveRecord::Base
@changed_colors ||= []
end
def changed_schemes
@changed_schemes ||= Set.new
end
def set_field(target:, name:, value: nil, type: nil, type_id: nil, upload_id: nil)
name = name.to_s

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
class ColorSchemeColorSerializer < ApplicationSerializer
attributes :name, :hex, :default_hex
attributes :name, :hex, :default_hex, :is_advanced
def hex
object.hex # otherwise something crazy is returned
@ -15,4 +15,8 @@ class ColorSchemeColorSerializer < ApplicationSerializer
object.hex
end
end
def is_advanced
!ColorScheme.base_colors.keys.include?(object.name)
end
end

View File

@ -20,7 +20,7 @@ describe RemoteTheme do
repo_dir
end
def about_json(love_color: "FAFAFA", color_scheme_name: "Amazing", about_url: "https://www.site.com/about")
def about_json(love_color: "FAFAFA", tertiary_low_color: "FFFFFF", color_scheme_name: "Amazing", about_url: "https://www.site.com/about")
<<~JSON
{
"name": "awesome theme",
@ -33,7 +33,8 @@ describe RemoteTheme do
},
"color_schemes": {
"#{color_scheme_name}": {
"love": "#{love_color}"
"love": "#{love_color}",
"tertiary-low": "#{tertiary_low_color}"
}
}
}
@ -105,6 +106,7 @@ describe RemoteTheme do
scheme = ColorScheme.find_by(theme_id: @theme.id)
expect(scheme.name).to eq("Amazing")
expect(scheme.colors.find_by(name: 'love').hex).to eq('fafafa')
expect(scheme.colors.find_by(name: 'tertiary-low').hex).to eq('ffffff')
expect(@theme.color_scheme_id).to eq(scheme.id)
@theme.update(color_scheme_id: nil)
@ -150,7 +152,7 @@ describe RemoteTheme do
expect(remote.about_url).to eq("https://newsite.com/about")
# It should be able to remove old colors as well
File.write("#{initial_repo}/about.json", about_json(love_color: "BABABA", color_scheme_name: "Amazing 2"))
File.write("#{initial_repo}/about.json", about_json(love_color: "BABABA", tertiary_low_color: "", color_scheme_name: "Amazing 2"))
`cd #{initial_repo} && git commit -am "update"`
remote.update_from_remote
@ -160,6 +162,9 @@ describe RemoteTheme do
scheme_count = ColorScheme.where(theme_id: @theme.id).count
expect(scheme_count).to eq(1)
scheme = ColorScheme.find_by(theme_id: @theme.id)
expect(scheme.colors.find_by(name: 'tertiary_low_color')).to eq(nil)
# It should detect local changes
@theme.set_field(target: :common, name: :scss, value: 'body {background-color: blue};')
@theme.save