From 7a3c5410770ec0752f72ce13f87bef6601c4aa0d Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Tue, 24 Jul 2018 09:00:20 -0400 Subject: [PATCH] UX: Preview multiple color schemes in wizard (#6151) It was a dropdown to provide choices of color schemes, and only one scheme could be shown. With this commit, multiple color scheme previews can be displayed on one page at the same time, making admins choose color schemes more easily. Theme preview windows are shrinked. Imported default color schemes. Co-Authored-By: Misaka 0x4e21 --- .../wizard/components/theme-preview.js.es6 | 63 ++++++++----- .../wizard/components/theme-previews.js.es6 | 7 ++ .../javascripts/wizard/lib/preview.js.es6 | 45 +++++----- .../javascripts/wizard/models/wizard.js.es6 | 6 +- .../templates/components/theme-previews.hbs | 13 +++ app/assets/stylesheets/wizard.scss | 35 +++++++- app/models/color_scheme.rb | 88 +++++++++++++++++-- config/locales/client.en.yml | 5 ++ config/locales/server.en.yml | 18 +++- db/fixtures/600_themes.rb | 6 +- lib/wizard/builder.rb | 32 +++---- spec/components/wizard/step_updater_spec.rb | 12 +-- 12 files changed, 243 insertions(+), 87 deletions(-) create mode 100644 app/assets/javascripts/wizard/components/theme-previews.js.es6 create mode 100644 app/assets/javascripts/wizard/templates/components/theme-previews.hbs diff --git a/app/assets/javascripts/wizard/components/theme-preview.js.es6 b/app/assets/javascripts/wizard/components/theme-preview.js.es6 index 23045e5675a..738be14f695 100644 --- a/app/assets/javascripts/wizard/components/theme-preview.js.es6 +++ b/app/assets/javascripts/wizard/components/theme-preview.js.es6 @@ -3,14 +3,18 @@ import { observes } from "ember-addons/ember-computed-decorators"; import { createPreviewComponent, darkLightDiff, - chooseBrighter, + chooseDarker, LOREM } from "wizard/lib/preview"; -export default createPreviewComponent(659, 320, { +export default createPreviewComponent(225, 120, { logo: null, avatar: null, + click() { + this.sendAction("onChange", this.get("colorsId")); + }, + @observes("step.fieldsById.base_scheme_id.value") themeChanged() { this.triggerRepaint(); @@ -24,19 +28,19 @@ export default createPreviewComponent(659, 320, { }, paint(ctx, colors, width, height) { - const headerHeight = height * 0.15; + const headerHeight = height * 0.3; this.drawFullHeader(colors); - const margin = width * 0.02; - const avatarSize = height * 0.1; - const lineHeight = height / 19.0; + const margin = width * 0.04; + const avatarSize = height * 0.2; + const lineHeight = height / 9.5; // Draw a fake topic this.scaleImage( this.avatar, margin, - headerHeight + height * 0.17, + headerHeight + height * 0.085, avatarSize, avatarSize ); @@ -46,33 +50,48 @@ export default createPreviewComponent(659, 320, { ctx.beginPath(); ctx.fillStyle = colors.primary; ctx.font = `bold ${titleFontSize}em 'Arial'`; - ctx.fillText("Welcome to Discourse", margin, height * 0.25); + ctx.fillText(I18n.t("wizard.previews.topic_title"), margin, height * 0.3); - const bodyFontSize = height / 440.0; + const bodyFontSize = height / 220.0; ctx.font = `${bodyFontSize}em 'Arial'`; let line = 0; const lines = LOREM.split("\n"); - for (let i = 0; i < 10; i++) { - line = height * 0.3 + i * lineHeight; + for (let i = 0; i < 4; i++) { + line = height * 0.35 + i * lineHeight; ctx.fillText(lines[i], margin + avatarSize + margin, line); } + // Share Button + ctx.beginPath(); + ctx.rect(margin, line + lineHeight, width * 0.14, height * 0.14); + ctx.fillStyle = darkLightDiff(colors.primary, colors.secondary, 90, 65); + ctx.fill(); + ctx.fillStyle = chooseDarker(colors.primary, colors.secondary); + ctx.font = `${bodyFontSize}em 'Arial'`; + ctx.fillText( + I18n.t("wizard.previews.share_button"), + margin + width / 55, + line + lineHeight * 1.85 + ); + // Reply Button ctx.beginPath(); - ctx.rect(width * 0.57, line + lineHeight, width * 0.1, height * 0.07); + ctx.rect( + margin * 2 + width * 0.14, + line + lineHeight, + width * 0.14, + height * 0.14 + ); ctx.fillStyle = colors.tertiary; ctx.fill(); - ctx.fillStyle = chooseBrighter(colors.primary, colors.secondary); + ctx.fillStyle = colors.secondary; ctx.font = `${bodyFontSize}em 'Arial'`; - ctx.fillText("Reply", width * 0.595, line + lineHeight * 1.85); - - // Icons - ctx.font = `${bodyFontSize}em FontAwesome`; - ctx.fillStyle = colors.love; - ctx.fillText("\uf004", width * 0.48, line + lineHeight * 1.8); - ctx.fillStyle = darkLightDiff(colors.primary, colors.secondary, 65, 55); - ctx.fillText("\uf040", width * 0.525, line + lineHeight * 1.8); + ctx.fillText( + I18n.t("wizard.previews.reply_button"), + margin * 2 + width * 0.14 + width / 55, + line + lineHeight * 1.85 + ); // Draw Timeline const timelineX = width * 0.8; @@ -80,7 +99,7 @@ export default createPreviewComponent(659, 320, { ctx.strokeStyle = colors.tertiary; ctx.lineWidth = 0.5; ctx.moveTo(timelineX, height * 0.3); - ctx.lineTo(timelineX, height * 0.6); + ctx.lineTo(timelineX, height * 0.7); ctx.stroke(); // Timeline diff --git a/app/assets/javascripts/wizard/components/theme-previews.js.es6 b/app/assets/javascripts/wizard/components/theme-previews.js.es6 new file mode 100644 index 00000000000..57c74ccab27 --- /dev/null +++ b/app/assets/javascripts/wizard/components/theme-previews.js.es6 @@ -0,0 +1,7 @@ +export default Ember.Component.extend({ + actions: { + changed(value) { + this.set("field.value", value); + } + } +}); diff --git a/app/assets/javascripts/wizard/lib/preview.js.es6 b/app/assets/javascripts/wizard/lib/preview.js.es6 index f3aee2439ac..887a72ba186 100644 --- a/app/assets/javascripts/wizard/lib/preview.js.es6 +++ b/app/assets/javascripts/wizard/lib/preview.js.es6 @@ -2,17 +2,14 @@ import getUrl from "discourse-common/lib/get-url"; export const LOREM = ` -Lorem ipsum dolor sit amet, consectetur adipiscing elit. -Nullam eget sem non elit tincidunt rhoncus. Fusce velit nisl, -porttitor sed nisl ac, consectetur interdum metus. Fusce in -consequat augue, vel facilisis felis. Nunc tellus elit, and -semper vitae orci nec, blandit pharetra enim. Aenean a ebus -posuere nunc. Maecenas ultrices viverra enim ac commodo -Vestibulum nec quam sit amet libero ultricies sollicitudin. -Nulla quis scelerisque sem, eget volutpat velit. Fusce eget -accumsan sapien, nec feugiat quam. Quisque non risus. -placerat lacus vitae, lacinia nisi. Sed metus arcu, iaculis -sit amet cursus nec, sodales at eros.`; +Lorem ipsum dolor sit amet, +consectetur adipiscing elit. +Nullam eget sem non elit +tincidunt rhoncus. Fusce +velit nisl, porttitor sed +nisl ac, consectetur interdum +metus. Fusce in consequat +augue, vel facilisis felis.`; const scaled = {}; @@ -75,7 +72,9 @@ export function createPreviewComponent(width, height, obj) { return false; } - const colors = this.get("wizard").getCurrentColors(); + const colors = this.get("wizard").getCurrentColors( + this.get("colorsId") + ); if (!colors) { return; } @@ -137,16 +136,10 @@ export function createPreviewComponent(width, height, obj) { const headerMargin = headerHeight * 0.2; const logoHeight = headerHeight - headerMargin * 2; - if (this.logo) { - const logoWidth = (logoHeight / this.logo.height) * this.logo.width; - this.scaleImage( - this.logo, - headerMargin, - headerMargin, - logoWidth, - logoHeight - ); - } + ctx.beginPath(); + ctx.fillStyle = colors.header_primary; + ctx.font = `bold ${logoHeight}px 'Arial'`; + ctx.fillText("Discourse", headerMargin, headerHeight - headerMargin); // Top right menu this.scaleImage( @@ -370,6 +363,14 @@ export function chooseBrighter(primary, secondary) { : primary; } +export function chooseDarker(primary, secondary) { + if (chooseBrighter(primary, secondary) === primary) { + return secondary; + } else { + return primary; + } +} + export function darkLightDiff(adjusted, comparison, lightness, darkness) { const adjustedCol = parseColor(adjusted); const comparisonCol = parseColor(comparison); diff --git a/app/assets/javascripts/wizard/models/wizard.js.es6 b/app/assets/javascripts/wizard/models/wizard.js.es6 index 28dc9f02973..95fa48bfb03 100644 --- a/app/assets/javascripts/wizard/models/wizard.js.es6 +++ b/app/assets/javascripts/wizard/models/wizard.js.es6 @@ -23,18 +23,18 @@ const Wizard = Ember.Object.extend({ }, // A bit clunky, but get the current colors from the appropriate step - getCurrentColors() { + getCurrentColors(schemeId) { const colorStep = this.get("steps").findBy("id", "colors"); if (!colorStep) { return; } - const themeChoice = colorStep.get("fieldsById.base_scheme_id"); + const themeChoice = colorStep.get("fieldsById.theme_previews"); if (!themeChoice) { return; } - const themeId = themeChoice.get("value"); + const themeId = schemeId ? schemeId : themeChoice.get("value"); if (!themeId) { return; } diff --git a/app/assets/javascripts/wizard/templates/components/theme-previews.hbs b/app/assets/javascripts/wizard/templates/components/theme-previews.hbs new file mode 100644 index 00000000000..34e5ea8e32f --- /dev/null +++ b/app/assets/javascripts/wizard/templates/components/theme-previews.hbs @@ -0,0 +1,13 @@ + diff --git a/app/assets/stylesheets/wizard.scss b/app/assets/stylesheets/wizard.scss index ef006c48d1a..ce145bb11b3 100644 --- a/app/assets/stylesheets/wizard.scss +++ b/app/assets/stylesheets/wizard.scss @@ -40,7 +40,9 @@ body.wizard { } .wizard-warning { - font-family: sans-serif, p { + font-family: sans-serif; + + p { margin-top: 0; } @@ -71,6 +73,11 @@ body.wizard { font-weight: bold; } +.wizard-step-form { + max-height: 500px; + overflow-y: auto; +} + .wizard-step-emoji { .radio-area { display: flex; @@ -106,6 +113,29 @@ body.wizard { } } +.wizard-step-colors { + .grid { + margin: 0 auto; + list-style-type: none; + text-align: center; + + li { + display: inline-block; + vertical-align: top; + margin: 15px; + .radio-area { + text-align: left; + font-size: 14px; + font-weight: bold; + & > * { + position: relative; + right: 7px; + } + } + } + } +} + .wizard-column { position: relative; z-index: 11; @@ -483,6 +513,9 @@ body.wizard { .wizard-column { margin: auto !important; } + .wizard-step-form { + max-height: auto; + } .wizard-step-contents { min-height: auto !important; } diff --git a/app/models/color_scheme.rb b/app/models/color_scheme.rb index d03b865c7ea..22dcd2c92fe 100644 --- a/app/models/color_scheme.rb +++ b/app/models/color_scheme.rb @@ -5,7 +5,7 @@ require_dependency 'distributed_cache' class ColorScheme < ActiveRecord::Base CUSTOM_SCHEMES = { - dark: { + 'Dark': { "primary" => 'dddddd', "secondary" => '222222', "tertiary" => '0f82af', @@ -16,6 +16,84 @@ class ColorScheme < ActiveRecord::Base "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', + "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', + "danger" => 'e45735', + "success" => 'fdd459', + "love" => 'fdd459' + }, + # By @awesomerobot + 'Shades of Blue': { + "primary" => '203243', + "secondary" => 'eef4f7', + "tertiary" => '416376', + "quaternary" => '5e99b9', + "header_background" => '86bddb', + "header_primary" => 'ffffff', + "highlight" => '86bddb', + "danger" => 'bf3c3c', + "success" => '70db82', + "love" => 'fc94cb' + }, + # By @mikechristopher + 'Latte': { + "primary" => 'f2e507', + "secondary" => '262322', + "tertiary" => 'f7f2ed', + "quaternary" => 'd7c9aa', + "header_background" => 'd7c9aa', + "header_primary" => '262322', + "highlight" => 'd7c9aa', + "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', + "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" => '6c3e63', + "danger" => '6c3e63', + "success" => 'd9b2bb', + "love" => 'd9b2bb' } } @@ -26,7 +104,7 @@ class ColorScheme < ActiveRecord::Base end list = [ - { id: 'default', colors: base_with_hash } + { id: 'Light', colors: base_with_hash } ] CUSTOM_SCHEMES.each do |k, v| @@ -71,7 +149,7 @@ class ColorScheme < ActiveRecord::Base def self.base_color_schemes base_color_scheme_colors.map do |hash| - scheme = new(name: I18n.t("color_schemes.#{hash[:id]}"), base_scheme_id: hash[:id]) + scheme = new(name: I18n.t("color_schemes.#{hash[:id].downcase.gsub(' ', '_')}"), base_scheme_id: hash[:id]) scheme.colors = hash[:colors].map { |k, v| { name: k.to_s, hex: v.sub("#", "") } } scheme.is_base = true scheme @@ -140,7 +218,7 @@ class ColorScheme < ActiveRecord::Base def base_colors colors = nil - if base_scheme_id && base_scheme_id != "default" + if base_scheme_id && base_scheme_id != "Light" colors = CUSTOM_SCHEMES[base_scheme_id.to_sym] end colors || ColorScheme.base_colors @@ -148,7 +226,7 @@ class ColorScheme < ActiveRecord::Base def resolved_colors resolved = ColorScheme.base_colors.dup - if base_scheme_id && base_scheme_id != "default" + if base_scheme_id && base_scheme_id != "Light" if scheme = CUSTOM_SCHEMES[base_scheme_id.to_sym] scheme.each do |name, value| resolved[name] = value diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 111713f7735..edb33eda8f0 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -3995,3 +3995,8 @@ en: admin: "Admin" moderator: "Moderator" regular: "Regular User" + + previews: + topic_title: "Discussion topic" + share_button: "Share" + reply_button: "Reply" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 2e07af6bd18..7ec50e083a7 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -3185,11 +3185,23 @@ en: color_schemes: base_theme_name: "Base" - default: "Light Scheme" + light: "Light Scheme" dark: "Dark Scheme" - default_theme_name: "Default" - dark_theme_name: "Dark" + neutral: "Neutral Scheme" + grey_amber: "Grey Amber Scheme" + shades_of_blue: "Shades of Blue Scheme" + latte: "Latte Scheme" + summer: "Summer Scheme" + dark_rose: "Dark Rose Scheme" + default_theme_name: "Light" light_theme_name: "Light" + dark_theme_name: "Dark" + neutral_theme_name: "Neutral" + grey_amber_theme_name: "Grey Amber" + shades_of_blue_theme_name: "Shades of Blue" + latte_theme_name: "Latte" + summer_theme_name: "Summer" + dark_rose_theme_name: "Dark Rose" about: "About" guidelines: "Guidelines" diff --git a/db/fixtures/600_themes.rb b/db/fixtures/600_themes.rb index 0575e1ea29d..be8ee222a84 100644 --- a/db/fixtures/600_themes.rb +++ b/db/fixtures/600_themes.rb @@ -2,9 +2,9 @@ if !Theme.exists? STDERR.puts "> Seeding dark and light themes" - name = I18n.t("wizard.step.colors.fields.theme_id.choices.dark.label") - dark_scheme = ColorScheme.find_by(base_scheme_id: "dark") - dark_scheme ||= ColorScheme.create_from_base(name: name, via_wizard: true, base_scheme_id: "dark") + name = I18n.t("color_schemes.dark_theme_name") + dark_scheme = ColorScheme.find_by(base_scheme_id: "Dark") + dark_scheme ||= ColorScheme.create_from_base(name: name, via_wizard: true, base_scheme_id: "Dark") name = I18n.t('color_schemes.dark_theme_name') _dark_theme = Theme.create(name: name, user_id: -1, diff --git a/lib/wizard/builder.rb b/lib/wizard/builder.rb index eb5f7d04b1e..1d7a2e5a4b0 100644 --- a/lib/wizard/builder.rb +++ b/lib/wizard/builder.rb @@ -118,39 +118,27 @@ class Wizard @wizard.append_step('colors') do |step| default_theme = Theme.find_by(id: SiteSetting.default_theme_id) - scheme_id = default_theme&.color_scheme&.base_scheme_id || 'default' + scheme_id = default_theme&.color_scheme&.base_scheme_id || 'Light' - themes = step.add_field(id: 'base_scheme_id', type: 'dropdown', required: true, value: scheme_id) + themes = step.add_field(id: 'theme_previews', type: 'component', required: true, value: scheme_id) ColorScheme.base_color_scheme_colors.each do |t| with_hash = t[:colors].dup with_hash.map { |k, v| with_hash[k] = "##{v}" } themes.add_choice(t[:id], data: { colors: with_hash }) end - step.add_field(id: 'theme_preview', type: 'component') step.on_update do |updater| - scheme_name = updater.fields[:base_scheme_id] + scheme_name = updater.fields[:theme_previews] || 'Light' + name = I18n.t("color_schemes.#{scheme_name.downcase.gsub(' ', '_')}_theme_name") theme = nil + scheme = ColorScheme.find_by(base_scheme_id: scheme_name, via_wizard: true) + scheme ||= ColorScheme.create_from_base(name: name, via_wizard: true, base_scheme_id: scheme_name) + themes = Theme.where(color_scheme_id: scheme.id).order(:id).to_a + theme = themes.find(&:default?) + theme ||= themes.first - if scheme_name == "dark" - scheme = ColorScheme.find_by(base_scheme_id: 'dark', via_wizard: true) - - name = I18n.t("wizard.step.colors.fields.theme_id.choices.dark.label") - scheme ||= ColorScheme.create_from_base(name: name, via_wizard: true, base_scheme_id: "dark") - - theme = Theme.find_by(color_scheme_id: scheme.id) - name = I18n.t('color_schemes.dark_theme_name') - theme ||= Theme.create(name: name, color_scheme_id: scheme.id, user_id: @wizard.user.id) - else - themes = Theme.where(color_scheme_id: nil).order(:id).to_a - theme = themes.find(&:default?) - theme ||= themes.first - - name = I18n.t('color_schemes.light_theme_name') - theme ||= Theme.create(name: name, user_id: @wizard.user.id) - end - + theme ||= Theme.create(name: name, user_id: @wizard.user.id, color_scheme_id: scheme.id) theme.set_default! end end diff --git a/spec/components/wizard/step_updater_spec.rb b/spec/components/wizard/step_updater_spec.rb index d10c2120e9e..0a858c8b888 100644 --- a/spec/components/wizard/step_updater_spec.rb +++ b/spec/components/wizard/step_updater_spec.rb @@ -151,12 +151,12 @@ describe Wizard::StepUpdater do let!(:color_scheme) { Fabricate(:color_scheme, name: 'existing', via_wizard: true) } it "updates the scheme" do - updater = wizard.create_updater('colors', base_scheme_id: 'dark') + updater = wizard.create_updater('colors', theme_previews: 'Dark') updater.update expect(updater.success?).to eq(true) expect(wizard.completed_steps?('colors')).to eq(true) theme = Theme.find_by(id: SiteSetting.default_theme_id) - expect(theme.color_scheme.base_scheme_id).to eq('dark') + expect(theme.color_scheme.base_scheme_id).to eq('Dark') end end @@ -167,14 +167,14 @@ describe Wizard::StepUpdater do context 'dark theme' do it "creates the theme" do - updater = wizard.create_updater('colors', base_scheme_id: 'dark', allow_dark_light_selection: true) + updater = wizard.create_updater('colors', theme_previews: 'Dark', allow_dark_light_selection: true) expect { updater.update }.to change { Theme.count }.by(1) theme = Theme.last expect(theme.user_id).to eq(wizard.user.id) - expect(theme.color_scheme.base_scheme_id).to eq('dark') + expect(theme.color_scheme.base_scheme_id).to eq('Dark') end end @@ -187,14 +187,14 @@ describe Wizard::StepUpdater do theme = Theme.last expect(theme.user_id).to eq(wizard.user.id) - expect(theme.color_scheme).to eq(nil) + expect(theme.color_scheme).to eq(ColorScheme.find_by(name: 'Light')) end end end context "without an existing scheme" do it "creates the scheme" do - updater = wizard.create_updater('colors', base_scheme_id: 'dark', allow_dark_light_selection: true) + updater = wizard.create_updater('colors', theme_previews: 'Dark', allow_dark_light_selection: true) updater.update expect(updater.success?).to eq(true) expect(wizard.completed_steps?('colors')).to eq(true)