Class: ColorScheme

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

Constant Summary collapse

CUSTOM_SCHEMES =

rubocop:disable Layout/HashAlignment

{
  Dark: {
    "primary" => "dddddd",
    "secondary" => "222222",
    "tertiary" => "099dd7",
    "quaternary" => "c14924",
    "header_background" => "111111",
    "header_primary" => "dddddd",
    "highlight" => "a87137",
    "selected" => "2c2c2c",
    "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 =

rubocop:enable Layout/HashAlignment

"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.



306
307
308
# File 'app/models/color_scheme.rb', line 306

def is_base
  @is_base
end

#skip_publishObject

Returns the value of attribute skip_publish.



307
308
309
# File 'app/models/color_scheme.rb', line 307

def skip_publish
  @skip_publish
end

Class Method Details

.baseObject



372
373
374
375
376
377
378
# File 'app/models/color_scheme.rb', line 372

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



286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'app/models/color_scheme.rb', line 286

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



359
360
361
362
363
364
365
366
367
368
369
370
# File 'app/models/color_scheme.rb', line 359

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



327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# File 'app/models/color_scheme.rb', line 327

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



343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
# File 'app/models/color_scheme.rb', line 343

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.



385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# File 'app/models/color_scheme.rb', line 385

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



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

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

.hex_for_name(name, scheme_id = nil) ⇒ Object



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

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)


380
381
382
# File 'app/models/color_scheme.rb', line 380

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



409
410
411
412
413
# File 'app/models/color_scheme.rb', line 409

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



480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
# File 'app/models/color_scheme.rb', line 480

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



444
445
446
447
448
# File 'app/models/color_scheme.rb', line 444

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



506
507
508
# File 'app/models/color_scheme.rb', line 506

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

#clear_colors_cacheObject



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

def clear_colors_cache
  @colors_by_name = nil
end

#colors=(arr) ⇒ Object



421
422
423
424
# File 'app/models/color_scheme.rb', line 421

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



426
427
428
429
430
431
432
433
434
# File 'app/models/color_scheme.rb', line 426

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

#colors_hashesObject



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

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

#dump_cachesObject



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

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

#is_dark?Boolean

Returns:

  • (Boolean)


510
511
512
513
514
515
516
517
# File 'app/models/color_scheme.rb', line 510

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)


519
520
521
# File 'app/models/color_scheme.rb', line 519

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

#publish_discourse_stylesheetObject



476
477
478
# File 'app/models/color_scheme.rb', line 476

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

#resolved_colorsObject



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

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