Class: ColorScheme

Inherits:
ActiveRecord::Base
  • Object
show all
Defined in:
app/models/color_scheme.rb

Constant Summary collapse

CUSTOM_SCHEMES =
{
  Dark: {
    "primary" => "dddddd",
    "secondary" => "222222",
    "tertiary" => "099dd7",
    "quaternary" => "c14924",
    "header_background" => "111111",
    "header_primary" => "dddddd",
    "highlight" => "a87137",
    "selected" => "052e3d",
    "hover" => "313131",
    "danger" => "e45735",
    "success" => "1ca551",
    "love" => "fa6c8d",
  },
  # By @itsbhanusharma
  Neutral: {
    "primary" => "000000",
    "secondary" => "ffffff",
    "tertiary" => "51839b",
    "quaternary" => "b85e48",
    "header_background" => "333333",
    "header_primary" => "f3f3f3",
    "highlight" => "ecec70",
    "selected" => "e6e6e6",
    "hover" => "f0f0f0",
    "danger" => "b85e48",
    "success" => "518751",
    "love" => "fa6c8d",
  },
  # By @Flower_Child
  "Grey Amber": {
    "primary" => "d9d9d9",
    "secondary" => "3d4147",
    "tertiary" => "fdd459",
    "quaternary" => "fdd459",
    "header_background" => "36393e",
    "header_primary" => "d9d9d9",
    "highlight" => "fdd459",
    "selected" => "272727",
    "hover" => "2F2F30",
    "danger" => "e45735",
    "success" => "fdd459",
    "love" => "fdd459",
  },
  # By @rafafotes
  "Shades of Blue": {
    "primary" => "203243",
    "secondary" => "eef4f7",
    "tertiary" => "416376",
    "quaternary" => "5e99b9",
    "header_background" => "86bddb",
    "header_primary" => "203243",
    "highlight" => "86bddb",
    "selected" => "bee0f2",
    "hover" => "d2efff",
    "danger" => "bf3c3c",
    "success" => "70db82",
    "love" => "fc94cb",
  },
  # By @mikechristopher
  Latte: {
    "primary" => "f2e5d7",
    "secondary" => "262322",
    "tertiary" => "f7f2ed",
    "quaternary" => "d7c9aa",
    "header_background" => "d7c9aa",
    "header_primary" => "262322",
    "highlight" => "d7c9aa",
    "selected" => "3e2a14",
    "hover" => "4c3319",
    "danger" => "db9584",
    "success" => "78be78",
    "love" => "8f6201",
  },
  # By @Flower_Child
  Summer: {
    "primary" => "874342",
    "secondary" => "fffff4",
    "tertiary" => "fe9896",
    "quaternary" => "fcc9d0",
    "header_background" => "96ccbf",
    "header_primary" => "fff1e7",
    "highlight" => "f3c07f",
    "selected" => "f5eaea",
    "hover" => "f9f3f3",
    "danger" => "cfebdc",
    "success" => "fcb4b5",
    "love" => "f3c07f",
  },
  # By @Flower_Child
  "Dark Rose": {
    "primary" => "ca9cb2",
    "secondary" => "3a2a37",
    "tertiary" => "fdd459",
    "quaternary" => "7e566a",
    "header_background" => "a97189",
    "header_primary" => "d9b2bb",
    "highlight" => "bd36a3",
    "selected" => "2a1620",
    "hover" => "331b27",
    "danger" => "6c3e63",
    "success" => "d9b2bb",
    "love" => "d9b2bb",
  },
  WCAG: {
    "primary" => "000000",
    "primary-medium" => "696969",
    "primary-low-mid" => "909090",
    "secondary" => "ffffff",
    "tertiary" => "0033CC",
    "quaternary" => "3369FF",
    "header_background" => "ffffff",
    "header_primary" => "000000",
    "highlight" => "ffff00",
    "highlight-high" => "0036E6",
    "highlight-medium" => "e0e9ff",
    "highlight-low" => "e0e9ff",
    "selected" => "E2E9FE",
    "hover" => "F0F4FE",
    "danger" => "BB1122",
    "success" => "3d854d",
    "love" => "9D256B",
  },
  "WCAG Dark": {
    "primary" => "ffffff",
    "primary-medium" => "999999",
    "primary-low-mid" => "888888",
    "secondary" => "0c0c0c",
    "tertiary" => "759AFF",
    "quaternary" => "759AFF",
    "header_background" => "000000",
    "header_primary" => "ffffff",
    "highlight" => "3369FF",
    "selected" => "0d2569",
    "hover" => "002382",
    "danger" => "FF697A",
    "success" => "70B880",
    "love" => "9D256B",
  },
  # By @zenorocha
  Dracula: {
    "primary_very_low" => "373A47",
    "primary_low" => "414350",
    "primary_low_mid" => "8C8D94",
    "primary_medium" => "A3A4AA",
    "primary_high" => "CCCCCF",
    "primary" => "f2f2f2",
    "primary-50" => "3F414E",
    "primary-100" => "535460",
    "primary-200" => "666972",
    "primary-300" => "7A7C84",
    "primary-400" => "8D8F96",
    "primary-500" => "A2A3A9",
    "primary-600" => "B6B7BC",
    "primary-700" => "C7C7C7",
    "primary-800" => "DEDFE0",
    "primary-900" => "F5F5F5",
    "secondary_low" => "CCCCCF",
    "secondary_medium" => "91939A",
    "secondary_high" => "6A6C76",
    "secondary_very_high" => "3D404C",
    "secondary" => "2d303e",
    "tertiary_low" => "4A4463",
    "tertiary_medium" => "6E5D92",
    "tertiary" => "bd93f9",
    "tertiary_high" => "9275C1",
    "quaternary_low" => "6AA8BA",
    "quaternary" => "8be9fd",
    "header_background" => "373A47",
    "header_primary" => "f2f2f2",
    "highlight_low" => "686D55",
    "highlight_medium" => "52592B",
    "highlight_high" => "C0C879",
    "selected" => "4A4463",
    "hover" => "61597f",
    "danger_low" => "957279",
    "danger" => "ff5555",
    "success_low" => "386D50",
    "success_medium" => "44B366",
    "success" => "50fa7b",
    "love_low" => "6C4667",
    "love" => "ff79c6",
  },
  # By @altercation
  "Solarized Light": {
    "primary_very_low" => "F0ECD7",
    "primary_low" => "D6D8C7",
    "primary_low_mid" => "A4AFA5",
    "primary_medium" => "7E918C",
    "primary_high" => "4C6869",
    "primary" => "002B36",
    "primary-50" => "F0EBDA",
    "primary-100" => "DAD8CA",
    "primary-200" => "B2B9B3",
    "primary-300" => "839496",
    "primary-400" => "76898C",
    "primary-500" => "697F83",
    "primary-600" => "627A7E",
    "primary-700" => "556F74",
    "primary-800" => "415F66",
    "primary-900" => "21454E",
    "secondary_low" => "325458",
    "secondary_medium" => "6C8280",
    "secondary_high" => "97A59D",
    "secondary_very_high" => "E8E6D3",
    "secondary" => "FCF6E1",
    "tertiary_low" => "D6E6DE",
    "tertiary_medium" => "7EBFD7",
    "tertiary" => "0088cc",
    "tertiary_high" => "329ED0",
    "quaternary" => "e45735",
    "header_background" => "FCF6E1",
    "header_primary" => "002B36",
    "highlight_low" => "FDF9AD",
    "highlight_medium" => "E3D0A3",
    "highlight" => "F2F481",
    "highlight_high" => "BCAA7F",
    "selected" => "E8E6D3",
    "hover" => "F0EBDA",
    "danger_low" => "F8D9C2",
    "danger" => "e45735",
    "success_low" => "CFE5B9",
    "success_medium" => "4CB544",
    "success" => "009900",
    "love_low" => "FCDDD2",
    "love" => "fa6c8d",
  },
  # By @altercation
  "Solarized Dark": {
    "primary_very_low" => "0D353F",
    "primary_low" => "193F47",
    "primary_low_mid" => "798C88",
    "primary_medium" => "97A59D",
    "primary_high" => "B5BDB1",
    "primary" => "FCF6E1",
    "primary-50" => "21454E",
    "primary-100" => "415F66",
    "primary-200" => "556F74",
    "primary-300" => "627A7E",
    "primary-400" => "697F83",
    "primary-500" => "76898C",
    "primary-600" => "839496",
    "primary-700" => "B2B9B3",
    "primary-800" => "DAD8CA",
    "primary-900" => "F0EBDA",
    "secondary_low" => "B5BDB1",
    "secondary_medium" => "81938D",
    "secondary_high" => "4E6A6B",
    "secondary_very_high" => "143B44",
    "secondary" => "002B36",
    "tertiary_low" => "003E54",
    "tertiary_medium" => "00557A",
    "tertiary" => "1a97d5",
    "tertiary_high" => "006C9F",
    "quaternary_low" => "944835",
    "quaternary" => "e45735",
    "header_background" => "002B36",
    "header_primary" => "FCF6E1",
    "highlight_low" => "4D6B3D",
    "highlight_medium" => "464C33",
    "highlight" => "F2F481",
    "highlight_high" => "BFCA47",
    "selected" => "143B44",
    "hover" => "21454E",
    "danger_low" => "443836",
    "danger_medium" => "944835",
    "danger" => "e45735",
    "success_low" => "004C26",
    "success_medium" => "007313",
    "success" => "009900",
    "love_low" => "4B3F50",
    "love" => "fa6c8d",
  },
}
LIGHT_THEME_ID =
"Light"
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"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#is_baseObject

Returns the value of attribute is_base.



302
303
304
# File 'app/models/color_scheme.rb', line 302

def is_base
  @is_base
end

#skip_publishObject

Returns the value of attribute skip_publish.



303
304
305
# File 'app/models/color_scheme.rb', line 303

def skip_publish
  @skip_publish
end

Class Method Details

.baseObject



368
369
370
371
372
373
374
# File 'app/models/color_scheme.rb', line 368

def self.base
  return @base_color_scheme if @base_color_scheme
  @base_color_scheme = new(name: I18n.t("color_schemes.base_theme_name"))
  @base_color_scheme.colors = base_colors.map { |name, hex| { name: name, hex: hex } }
  @base_color_scheme.is_base = true
  @base_color_scheme
end

.base_color_scheme_colorsObject



282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'app/models/color_scheme.rb', line 282

def self.base_color_scheme_colors
  base_with_hash = []

  base_colors.each { |name, color| base_with_hash << { name: name, hex: "#{color}" } }

  list = [{ id: LIGHT_THEME_ID, colors: base_with_hash }]

  CUSTOM_SCHEMES.each do |k, v|
    colors = []
    v.each { |name, color| colors << { name: name, hex: "#{color}" } }
    list.push(id: k.to_s, colors: colors)
  end

  list
end

.base_color_schemesObject



355
356
357
358
359
360
361
362
363
364
365
366
# File 'app/models/color_scheme.rb', line 355

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],
      )
    scheme.colors = hash[:colors].map { |k| { name: k[:name], hex: k[:hex] } }
    scheme.is_base = true
    scheme
  end
end

.base_colorsObject



323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
# File 'app/models/color_scheme.rb', line 323

def self.base_colors
  return @base_colors if @base_colors
  @mutex.synchronize do
    return @base_colors if @base_colors
    base_colors = {}
    File
      .readlines(BASE_COLORS_FILE)
      .each do |line|
        matches = /\$([\w]+):\s*#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})(?:[;]|\s)/.match(line.strip)
        base_colors[matches[1]] = matches[2] if matches
      end
    @base_colors = base_colors
  end
  @base_colors
end

.color_transformation_variablesObject



339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'app/models/color_scheme.rb', line 339

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

.create_from_base(params) ⇒ Object

create_from_base will create a new ColorScheme that overrides Discourse’s base color scheme with the given colors.



381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
# File 'app/models/color_scheme.rb', line 381

def self.create_from_base(params)
  new_color_scheme = new(name: params[:name])
  new_color_scheme.via_wizard = true if params[:via_wizard]
  new_color_scheme.base_scheme_id = params[:base_scheme_id]
  new_color_scheme.user_selectable = true

  colors =
    CUSTOM_SCHEMES[params[:base_scheme_id].to_sym]&.map do |name, hex|
      { name: name, hex: hex }
    end if params[:base_scheme_id]
  colors ||= base.colors_hashes

  # Override base values
  params[:colors].each do |name, hex|
    c = colors.find { |x| x[:name].to_s == name.to_s }
    c[:hex] = hex
  end if params[:colors]

  new_color_scheme.colors = colors
  new_color_scheme.skip_publish if params[:skip_publish]
  new_color_scheme.save
  new_color_scheme
end

.hex_cacheObject



298
299
300
# File 'app/models/color_scheme.rb', line 298

def self.hex_cache
  @hex_cache ||= DistributedCache.new("scheme_hex_for_name")
end

.hex_for_name(name, scheme_id = nil) ⇒ Object



411
412
413
414
415
# File 'app/models/color_scheme.rb', line 411

def self.hex_for_name(name, scheme_id = nil)
  hex_cache.defer_get_set(scheme_id ? name + "_#{scheme_id}" : name) do
    lookup_hex_for_name(name, scheme_id)
  end
end

.is_base?(scheme_name) ⇒ Boolean

Returns:

  • (Boolean)


376
377
378
# File 'app/models/color_scheme.rb', line 376

def self.is_base?(scheme_name)
  base_color_scheme_colors.map { |c| c[:id] }.include?(scheme_name)
end

.lookup_hex_for_name(name, scheme_id = nil) ⇒ Object



405
406
407
408
409
# File 'app/models/color_scheme.rb', line 405

def self.lookup_hex_for_name(name, scheme_id = nil)
  enabled_color_scheme = find_by(id: scheme_id) if scheme_id
  enabled_color_scheme ||= Theme.where(id: SiteSetting.default_theme_id).first&.color_scheme
  (enabled_color_scheme || base).colors.find { |c| c.name == name }.try(:hex)
end

.publish_discourse_stylesheets!(id = nil) ⇒ Object



476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
# File 'app/models/color_scheme.rb', line 476

def self.publish_discourse_stylesheets!(id = nil)
  Stylesheet::Manager.clear_color_scheme_cache!

  theme_ids = []
  if id
    theme_ids = Theme.where(color_scheme_id: id).pluck(:id)
  else
    theme_ids = Theme.all.pluck(:id)
  end
  if theme_ids.present?
    Stylesheet::Manager.cache.clear

    Theme.notify_theme_change(
      theme_ids,
      with_scheme: true,
      clear_manager_cache: false,
      all_themes: true,
    )
  end
end

Instance Method Details

#base_colorsObject



440
441
442
443
444
# File 'app/models/color_scheme.rb', line 440

def base_colors
  colors = nil
  colors = CUSTOM_SCHEMES[base_scheme_id.to_sym] if base_scheme_id && base_scheme_id != "Light"
  colors || ColorScheme.base_colors
end

#bump_versionObject



502
503
504
# File 'app/models/color_scheme.rb', line 502

def bump_version
  self.version += 1 if self.id
end

#clear_colors_cacheObject



432
433
434
# File 'app/models/color_scheme.rb', line 432

def clear_colors_cache
  @colors_by_name = nil
end

#colors=(arr) ⇒ Object



417
418
419
420
# File 'app/models/color_scheme.rb', line 417

def colors=(arr)
  @colors_by_name = nil
  arr.each { |c| self.color_scheme_colors << ColorSchemeColor.new(name: c[:name], hex: c[:hex]) }
end

#colors_by_nameObject



422
423
424
425
426
427
428
429
430
# File 'app/models/color_scheme.rb', line 422

def colors_by_name
  @colors_by_name ||=
    self
      .colors
      .inject({}) do |sum, c|
        sum[c.name] = c
        sum
      end
end

#colors_hashesObject



436
437
438
# File 'app/models/color_scheme.rb', line 436

def colors_hashes
  color_scheme_colors.map { |c| { name: c.name, hex: c.hex } }
end

#dump_cachesObject



497
498
499
500
# File 'app/models/color_scheme.rb', line 497

def dump_caches
  self.class.hex_cache.clear
  ApplicationSerializer.expire_cache_fragment!("user_color_schemes")
end

#is_dark?Boolean

Returns:

  • (Boolean)


506
507
508
509
510
511
512
513
# File 'app/models/color_scheme.rb', line 506

def is_dark?
  return if colors.to_a.empty?

  primary_b = ColorMath.brightness(resolved_colors["primary"])
  secondary_b = ColorMath.brightness(resolved_colors["secondary"])

  primary_b > secondary_b
end

#is_wcag?Boolean

Returns:

  • (Boolean)


515
516
517
# File 'app/models/color_scheme.rb', line 515

def is_wcag?
  base_scheme_id&.start_with?("WCAG")
end

#publish_discourse_stylesheetObject



472
473
474
# File 'app/models/color_scheme.rb', line 472

def publish_discourse_stylesheet
  self.class.publish_discourse_stylesheets!(self.id) if self.id
end

#resolved_colorsObject



446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
# File 'app/models/color_scheme.rb', line 446

def resolved_colors
  from_base = ColorScheme.base_colors
  from_custom_scheme = base_colors
  from_db = colors.map { |c| [c.name, c.hex] }.to_h

  resolved = from_base.merge(from_custom_scheme).except("hover", "selected").merge(from_db)

  # Equivalent to primary-100 in light mode, or primary-low in dark mode
  resolved["hover"] ||= ColorMath.dark_light_diff(
    resolved["primary"],
    resolved["secondary"],
    0.94,
    -0.78,
  )

  # Equivalent to primary-low in light mode, or primary-100 in dark mode
  resolved["selected"] ||= ColorMath.dark_light_diff(
    resolved["primary"],
    resolved["secondary"],
    0.9,
    -0.8,
  )

  resolved
end