diff --git a/app/assets/javascripts/select-kit/addon/components/color-palettes/color-palettes-row.js b/app/assets/javascripts/select-kit/addon/components/color-palettes/color-palettes-row.js index 4819db65d06..df52f73cb17 100644 --- a/app/assets/javascripts/select-kit/addon/components/color-palettes/color-palettes-row.js +++ b/app/assets/javascripts/select-kit/addon/components/color-palettes/color-palettes-row.js @@ -1,6 +1,5 @@ import SelectKitRowComponent from "select-kit/components/select-kit/select-kit-row"; import { computed } from "@ember/object"; -import { escapeExpression } from "discourse/lib/utilities"; import layout from "select-kit/templates/components/color-palettes/color-palettes-row"; export default SelectKitRowComponent.extend({ @@ -10,7 +9,7 @@ export default SelectKitRowComponent.extend({ palettes: computed("item.colors.[]", function () { return (this.item.colors || []) .filter((color) => color.name !== "secondary") - .map((color) => `#${escapeExpression(color.hex)}`) + .map((color) => `#${escape(color.hex)}`) .map( (hex) => `` ) @@ -22,7 +21,7 @@ export default SelectKitRowComponent.extend({ const secondary = (this.item.colors || []).findBy("name", "secondary"); if (secondary && secondary.hex) { - return `background-color:#${escapeExpression(secondary.hex)}`.htmlSafe(); + return `background-color:#${escape(secondary.hex)}`.htmlSafe(); } else { return ""; } diff --git a/app/assets/javascripts/wizard/components/homepage-preview.js b/app/assets/javascripts/wizard/components/homepage-preview.js index 0e61f60f3aa..b0800076ab9 100644 --- a/app/assets/javascripts/wizard/components/homepage-preview.js +++ b/app/assets/javascripts/wizard/components/homepage-preview.js @@ -3,14 +3,14 @@ import { createPreviewComponent, darkLightDiff, } from "wizard/lib/preview"; -import { observes } from "discourse-common/utils/decorators"; export default createPreviewComponent(659, 320, { logo: null, avatar: null, - @observes("step.fieldsById.homepage_style.value") - styleChanged() { + didUpdateAttrs() { + this._super(...arguments); + this.triggerRepaint(); }, @@ -22,7 +22,9 @@ export default createPreviewComponent(659, 320, { }, paint({ ctx, colors, font, width, height }) { - this.drawFullHeader(colors, font, this.logo); + if (this.logo) { + this.drawFullHeader(colors, font, this.logo); + } if (this.get("step.fieldsById.homepage_style.value") === "latest") { this.drawPills(colors, font, height * 0.15); diff --git a/app/assets/javascripts/wizard/components/font-preview.js b/app/assets/javascripts/wizard/components/styling-preview.js similarity index 65% rename from app/assets/javascripts/wizard/components/font-preview.js rename to app/assets/javascripts/wizard/components/styling-preview.js index ef2f73f216b..a45d1ef168e 100644 --- a/app/assets/javascripts/wizard/components/font-preview.js +++ b/app/assets/javascripts/wizard/components/styling-preview.js @@ -15,13 +15,66 @@ metus. Fusce in consequat augue, vel facilisis felis.`; export default createPreviewComponent(659, 320, { logo: null, avatar: null, + previewTopic: true, + draggingActive: false, + startX: 0, + scrollLeft: 0, + + mouseDown(e) { + const slider = this.element.querySelector(".previews"); + this.setProperties({ + draggingActive: true, + startX: e.pageX - slider.offsetLeft, + scrollLeft: slider.scrollLeft, + }); + }, + + mouseLeave() { + this.set("draggingActive", false); + }, + + mouseUp() { + this.set("draggingActive", false); + }, + + mouseMove(e) { + if (!this.draggingActive) { + return; + } + e.preventDefault(); + + const slider = this.element.querySelector(".previews"), + x = e.pageX - slider.offsetLeft, + walk = (x - this.startX) * 1.5; + + slider.scrollLeft = this.scrollLeft - walk; + + if (slider.scrollLeft < 50) { + this.set("previewTopic", true); + } + if (slider.scrollLeft > slider.offsetWidth) { + this.set("previewTopic", false); + } + }, + + didUpdateAttrs() { + this._super(...arguments); - @observes( - "step.fieldsById.body_font.value", - "step.fieldsById.heading_font.value" - ) - fontChanged() { this.triggerRepaint(); + + if (this.stylingDropdown?.id === "homepage_style") { + this.set("previewTopic", false); + } + }, + + @observes("previewTopic") + scrollPreviewArea() { + const el = this.element.querySelector(".previews"); + el.scrollTo({ + top: 0, + left: this.previewTopic ? 0 : el.scrollWidth - el.offsetWidth, + behavior: "smooth", + }); }, images() { @@ -39,14 +92,14 @@ export default createPreviewComponent(659, 320, { } const margin = 20; - const avatarSize = height * 0.2; - const lineHeight = height / 11; + const avatarSize = height * 0.15; + const lineHeight = height / 14; // Draw a fake topic this.scaleImage( this.avatar, margin, - headerHeight + height * 0.11, + headerHeight + height * 0.09, avatarSize, avatarSize ); @@ -80,7 +133,7 @@ export default createPreviewComponent(659, 320, { ctx.fillText( I18n.t("wizard.previews.share_button"), margin + 10, - line + lineHeight * 1.7 + line + lineHeight * 1.9 ); // Reply Button @@ -100,7 +153,7 @@ export default createPreviewComponent(659, 320, { ctx.fillText( I18n.t("wizard.previews.reply_button"), shareButtonWidth + margin + 20, - line + lineHeight * 1.7 + line + lineHeight * 1.9 ); // Draw Timeline @@ -124,4 +177,14 @@ export default createPreviewComponent(659, 320, { ctx.fillStyle = colors.primary; ctx.fillText("1 / 20", timelineX + margin, height * 0.3 + margin * 1.5); }, + + actions: { + setPreviewHomepage() { + this.set("previewTopic", false); + }, + + setPreviewTopic() { + this.set("previewTopic", true); + }, + }, }); diff --git a/app/assets/javascripts/wizard/components/theme-previews.js b/app/assets/javascripts/wizard/components/theme-previews.js deleted file mode 100644 index 65c8d2f4c7a..00000000000 --- a/app/assets/javascripts/wizard/components/theme-previews.js +++ /dev/null @@ -1,8 +0,0 @@ -import Component from "@ember/component"; -export default Component.extend({ - actions: { - changed(value) { - this.set("field.value", value); - }, - }, -}); diff --git a/app/assets/javascripts/wizard/components/wizard-field-dropdown.js b/app/assets/javascripts/wizard/components/wizard-field-dropdown.js index 3e49cb3561f..f0c510ba34a 100644 --- a/app/assets/javascripts/wizard/components/wizard-field-dropdown.js +++ b/app/assets/javascripts/wizard/components/wizard-field-dropdown.js @@ -1,7 +1,36 @@ import Component from "@ember/component"; +import discourseComputed from "discourse-common/utils/decorators"; +import { set } from "@ember/object"; export default Component.extend({ + init(...args) { + this._super(...args); + + if (this.field.id === "color_scheme") { + for (let choice of this.field.choices) { + if (choice?.data?.colors) { + set(choice, "colors", choice.data.colors); + } + } + } + }, + + @discourseComputed("field.id") + componentName(id) { + if (id === "color_scheme") { + return "color-palettes"; + } + return "combo-box"; + }, + keyPress(e) { e.stopPropagation(); }, + + actions: { + onChangeValue(value) { + this.set("field.value", value); + this.stylingDropdownChanged(this.field.id, value); + }, + }, }); diff --git a/app/assets/javascripts/wizard/components/wizard-field.js b/app/assets/javascripts/wizard/components/wizard-field.js index 3a39cb46d5d..443b589f6e7 100644 --- a/app/assets/javascripts/wizard/components/wizard-field.js +++ b/app/assets/javascripts/wizard/components/wizard-field.js @@ -3,10 +3,11 @@ import { dasherize } from "@ember/string"; import discourseComputed from "discourse-common/utils/decorators"; export default Component.extend({ - classNameBindings: [":wizard-field", "typeClass", "field.invalid"], + classNameBindings: [":wizard-field", "typeClasses", "field.invalid"], - @discourseComputed("field.type") - typeClass: (type) => `${dasherize(type)}-field`, + @discourseComputed("field.type", "field.id") + typeClasses: (type, id) => + `${dasherize(type)}-field ${dasherize(type)}-${dasherize(id)}`, @discourseComputed("field.id") fieldClass: (id) => `field-${dasherize(id)} wizard-focusable`, diff --git a/app/assets/javascripts/wizard/components/wizard-step.js b/app/assets/javascripts/wizard/components/wizard-step.js index 80b41ec1307..1d66e86cca9 100644 --- a/app/assets/javascripts/wizard/components/wizard-step.js +++ b/app/assets/javascripts/wizard/components/wizard-step.js @@ -27,6 +27,11 @@ export default Component.extend({ classNames: ["wizard-step"], saving: null, + init() { + this._super(...arguments); + this.set("stylingDropdown", {}); + }, + didInsertElement() { this._super(...arguments); this.autoFocus(); @@ -96,6 +101,11 @@ export default Component.extend({ return htmlSafe(`width: ${ratio * 200}px`); }, + @discourseComputed("step.fields") + includeSidebar(fields) { + return !!fields.findBy("show_in_sidebar"); + }, + autoFocus() { schedule("afterRender", () => { const $invalid = $( @@ -130,6 +140,10 @@ export default Component.extend({ document.location = getUrl("/"); }, + stylingDropdownChanged(id, value) { + this.set("stylingDropdown", { id, value }); + }, + exitEarly() { const step = this.step; step.validate(); diff --git a/app/assets/javascripts/wizard/controllers/application.js b/app/assets/javascripts/wizard/controllers/application.js index a9f67bbe49e..d0c86664b39 100644 --- a/app/assets/javascripts/wizard/controllers/application.js +++ b/app/assets/javascripts/wizard/controllers/application.js @@ -12,7 +12,7 @@ export default Controller.extend({ @discourseComputed("model") fontClasses(model) { - const fontsStep = model.steps.findBy("id", "fonts"); + const fontsStep = model.steps.findBy("id", "styling"); if (!fontsStep) { return []; } diff --git a/app/assets/javascripts/wizard/lib/preview.js b/app/assets/javascripts/wizard/lib/preview.js index db9553d0354..3ed162ccad3 100644 --- a/app/assets/javascripts/wizard/lib/preview.js +++ b/app/assets/javascripts/wizard/lib/preview.js @@ -2,6 +2,7 @@ import Component from "@ember/component"; import { Promise } from "rsvp"; /*eslint no-bitwise:0 */ import getUrl from "discourse-common/lib/get-url"; +import { htmlSafe } from "@ember/template"; import { scheduleOnce } from "@ember/runloop"; export const LOREM = ` @@ -41,7 +42,7 @@ export function createPreviewComponent(width, height, obj) { height, elementWidth: width * scale, elementHeight: height * scale, - canvasStyle: `width:${width}px;height:${height}px`, + canvasStyle: htmlSafe(`width:${width}px;height:${height}px`), ctx: null, loaded: false, @@ -87,11 +88,17 @@ export function createPreviewComponent(width, height, obj) { return false; } - const colors = this.wizard.getCurrentColors(this.colorsId); - if (!colors) { + const colorsArray = this.wizard.getCurrentColors(this.colorsId); + if (!colorsArray) { return; } + let colors = {}; + colorsArray.forEach(function (c) { + const name = c.name; + colors[name] = `#${c.hex}`; + }); + const font = this.wizard.getCurrentFont(this.fontId); const headingFont = this.wizard.getCurrentFont( this.fontId, @@ -115,12 +122,6 @@ export function createPreviewComponent(width, height, obj) { height: this.height, }; this.paint(options); - - // draw border - ctx.beginPath(); - ctx.strokeStyle = "rgba(0, 0, 0, 0.2)"; - ctx.rect(0, 0, width, height); - ctx.stroke(); }, categories() { diff --git a/app/assets/javascripts/wizard/models/wizard.js b/app/assets/javascripts/wizard/models/wizard.js index b0bb132c399..f6b83ede7bd 100644 --- a/app/assets/javascripts/wizard/models/wizard.js +++ b/app/assets/javascripts/wizard/models/wizard.js @@ -26,12 +26,12 @@ const Wizard = EmberObject.extend({ // A bit clunky, but get the current colors from the appropriate step getCurrentColors(schemeId) { - const colorStep = this.steps.findBy("id", "colors"); + const colorStep = this.steps.findBy("id", "styling"); if (!colorStep) { return this.current_color_scheme; } - const themeChoice = colorStep.get("fieldsById.theme_previews"); + const themeChoice = colorStep.get("fieldsById.color_scheme"); if (!themeChoice) { return; } @@ -55,7 +55,7 @@ const Wizard = EmberObject.extend({ }, getCurrentFont(fontId, type = "body_font") { - const fontsStep = this.steps.findBy("id", "fonts"); + const fontsStep = this.steps.findBy("id", "styling"); if (!fontsStep) { return; } diff --git a/app/assets/javascripts/wizard/templates/components/font-preview.hbs b/app/assets/javascripts/wizard/templates/components/font-preview.hbs deleted file mode 100644 index 2201f2a1a90..00000000000 --- a/app/assets/javascripts/wizard/templates/components/font-preview.hbs +++ /dev/null @@ -1,8 +0,0 @@ -
- - -
diff --git a/app/assets/javascripts/wizard/templates/components/styling-preview.hbs b/app/assets/javascripts/wizard/templates/components/styling-preview.hbs new file mode 100644 index 00000000000..9dde43e5d1b --- /dev/null +++ b/app/assets/javascripts/wizard/templates/components/styling-preview.hbs @@ -0,0 +1,22 @@ +
+
+ + +
+
+ {{homepage-preview + wizard=wizard + step=step + stylingDropdown=stylingDropdown + }} +
+
+ +
+ + {{i18n "wizard.previews.topic_preview"}} + + + {{i18n "wizard.previews.homepage_preview"}} + +
diff --git a/app/assets/javascripts/wizard/templates/components/theme-previews.hbs b/app/assets/javascripts/wizard/templates/components/theme-previews.hbs deleted file mode 100644 index f267e499525..00000000000 --- a/app/assets/javascripts/wizard/templates/components/theme-previews.hbs +++ /dev/null @@ -1,14 +0,0 @@ - diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs b/app/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs index e311b0183e9..2f7077aef21 100644 --- a/app/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs +++ b/app/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs @@ -1,9 +1,12 @@ -{{combo-box - id=field.id +{{component + componentName class=fieldClass value=field.value content=field.choices nameProperty="label" tabindex="9" - onChange=(action (mut field.value)) + onChange=(action "onChangeValue") + options=(hash + translatedNone=false + ) }} diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field.hbs b/app/assets/javascripts/wizard/templates/components/wizard-field.hbs index e347fb80b3f..d1d9fce684c 100644 --- a/app/assets/javascripts/wizard/templates/components/wizard-field.hbs +++ b/app/assets/javascripts/wizard/templates/components/wizard-field.hbs @@ -13,7 +13,14 @@
- {{component inputComponentName field=field step=step fieldClass=fieldClass wizard=wizard}} + {{component + inputComponentName + field=field step=step + fieldClass=fieldClass + wizard=wizard + stylingDropdownChanged=stylingDropdownChanged + stylingDropdown=stylingDropdown + }}
{{#if field.errorDescription}} diff --git a/app/assets/javascripts/wizard/templates/components/wizard-step.hbs b/app/assets/javascripts/wizard/templates/components/wizard-step.hbs index 01650e3ca0b..e7ae567047d 100644 --- a/app/assets/javascripts/wizard/templates/components/wizard-step.hbs +++ b/app/assets/javascripts/wizard/templates/components/wizard-step.hbs @@ -14,9 +14,32 @@ {{#wizard-step-form step=step}} - {{#each step.fields as |field|}} - {{wizard-field field=field step=step wizard=wizard}} - {{/each}} + {{#if includeSidebar}} +
+ {{#each step.fields as |field|}} + {{#if field.show_in_sidebar}} + {{wizard-field + field=field + step=step + wizard=wizard + stylingDropdownChanged=(action "stylingDropdownChanged") + }} + {{/if}} + {{/each}} +
+ {{/if}} +
+ {{#each step.fields as |field|}} + {{#unless field.show_in_sidebar}} + {{wizard-field + field=field + step=step + wizard=wizard + stylingDropdown=stylingDropdown + }} + {{/unless}} + {{/each}} +
{{/wizard-step-form}} diff --git a/app/assets/stylesheets/wizard.scss b/app/assets/stylesheets/wizard.scss index 648a4d736f2..9b2407987ce 100644 --- a/app/assets/stylesheets/wizard.scss +++ b/app/assets/stylesheets/wizard.scss @@ -820,66 +820,78 @@ body.wizard { .wizard-step-form { max-height: 500px; -} + display: flex; -.wizard-step-homepage { - .field-homepage-style { - width: 280px; + .wizard-fields-main { + width: 100%; } -} -.wizard-step-colors { - max-height: 465px; - overflow-y: auto; - .grid { + .wizard-fields-sidebar { + width: 170px; box-sizing: border-box; - display: flex; - flex-wrap: wrap; - justify-content: space-around; - padding: 0; - margin: 0 auto; - list-style-type: none; - text-align: center; - - li { - display: inline-block; - vertical-align: top; - margin: 0 5px 25px 5px; - label:checked + div { - display: none; - } - .is-selected { - box-shadow: 0 0 0 5px var(--tertiary); - } - div { - display: flex; - flex: 1 1 auto; - } - .radio-area { - display: none; - & > * { - position: relative; - right: 7px; - } - } - canvas { - transition: box-shadow 0.25s; - &:hover { - box-shadow: shadow("card"); - cursor: pointer; - } - } + padding: 30px 0px 15px 15px; + background: var(--primary-very-low); + + .wizard-fields-main { + padding: 15px; + padding-top: 30px; + background: var(--primary-very-low); + width: calc(100% - 170px); } } } -.wizard-step-fonts { - .dropdown-field { - float: left; - margin-right: 1.5em; +.wizard-step-styling { + .preview-nav { + display: flex; + justify-content: flex-end; + position: relative; + margin-top: -1px; + padding-right: 10px; + .preview-nav-button { + text-align: center; + padding: 10px 15px; + cursor: pointer; + margin-left: 10px; + font-size: 14px; + font-weight: bold; + color: var(--primary-high); + &.active { + background: var(--secondary); + border-bottom-left-radius: 10px; + border-bottom-right-radius: 10px; + color: var(--tertiary); + border: 1px dashed var(--tertiary-low); + border-top: none; + } + } } - .component-field { - clear: both; + + .previews { + position: relative; + height: 320px; + width: 100%; + overflow: hidden; + background: var(--secondary); + border: 1px dashed var(--tertiary-low); + border-radius: 10px; + cursor: grab; + user-select: none; + &.dragging { + cursor: grabbing; + } + .topic-preview { + position: absolute; + left: 0px; + top: 0px; + transform: scale(0.85) translateX(-45px); + } + .homepage-preview { + position: absolute; + left: calc(100% + 25px); + top: 0px; + transform: scale(0.85); + padding-right: 20px; + } } } @@ -891,7 +903,7 @@ body.wizard { box-sizing: border-box; margin: 1.5em auto; padding: 0; - max-width: 700px; + max-width: 820px; min-width: 280px; width: 100%; border: 1px solid var(--primary-low-mid); @@ -924,7 +936,7 @@ body.wizard { } .wizard-step-banner { margin-bottom: 2em; - width: 620px; + width: 100%; display: block; } @@ -1178,6 +1190,10 @@ body.wizard { margin-bottom: 2em; } + + .wizard-image-row canvas { + border: 1px solid rgba(0, 0, 0, 0.2); + } } .textarea-field { diff --git a/app/models/color_scheme.rb b/app/models/color_scheme.rb index 22682be18dd..bcd7d7c02fe 100644 --- a/app/models/color_scheme.rb +++ b/app/models/color_scheme.rb @@ -133,10 +133,10 @@ class ColorScheme < ActiveRecord::Base LIGHT_THEME_ID = 'Light' def self.base_color_scheme_colors - base_with_hash = {} + base_with_hash = [] base_colors.each do |name, color| - base_with_hash[name] = "#{color}" + base_with_hash << { name: name, hex: "#{color}" } end list = [ @@ -144,7 +144,11 @@ class ColorScheme < ActiveRecord::Base ] CUSTOM_SCHEMES.each do |k, v| - list.push(id: k.to_s, colors: v) + colors = [] + v.each do |name, color| + colors << { name: name, hex: "#{color}" } + end + list.push(id: k.to_s, colors: colors) end list @@ -205,7 +209,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].downcase.gsub(' ', '_')}"), base_scheme_id: hash[:id]) - scheme.colors = hash[:colors].map { |k, v| { name: k.to_s, hex: v.sub("#", "") } } + scheme.colors = hash[:colors].map { |k| { name: k[:name], hex: k[:hex] } } scheme.is_base = true scheme end diff --git a/app/serializers/wizard_field_serializer.rb b/app/serializers/wizard_field_serializer.rb index 6d9617fd65f..0b9124fb7a1 100644 --- a/app/serializers/wizard_field_serializer.rb +++ b/app/serializers/wizard_field_serializer.rb @@ -2,7 +2,7 @@ class WizardFieldSerializer < ApplicationSerializer - attributes :id, :type, :required, :value, :label, :placeholder, :description, :extra_description + attributes :id, :type, :required, :value, :label, :placeholder, :description, :extra_description, :show_in_sidebar has_many :choices, serializer: WizardFieldChoiceSerializer, embed: :objects def id @@ -68,4 +68,12 @@ class WizardFieldSerializer < ApplicationSerializer extra_description.present? end + def show_in_sidebar + object.show_in_sidebar + end + + def include_show_in_sidebar? + object.show_in_sidebar.present? + end + end diff --git a/app/serializers/wizard_serializer.rb b/app/serializers/wizard_serializer.rb index 4476c4ff4e3..12bcbe802b5 100644 --- a/app/serializers/wizard_serializer.rb +++ b/app/serializers/wizard_serializer.rb @@ -15,11 +15,6 @@ class WizardSerializer < ApplicationSerializer def current_color_scheme color_scheme = Theme.where(id: SiteSetting.default_theme_id).first&.color_scheme - colors = color_scheme ? color_scheme.colors : ColorScheme.base.colors - - # The frontend expects the color hexs to start with '#' - colors_with_hash = {} - colors.each { |color| colors_with_hash[color.name] = color.hex_with_hash } - colors_with_hash + color_scheme ? color_scheme.colors_hashes : ColorScheme.base.colors_hashes end end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index f88b8605738..fd0ff5eb290 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -5395,7 +5395,8 @@ en: regular: "Regular User" previews: - topic_title: "Discussion topic" - font_title: "%{font} Font" + topic_title: "A discussion topic heading" share_button: "Share" reply_button: "Reply" + topic_preview: "Topic preview" + homepage_preview: "Homepage preview" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index c19136c70cf..3e555899a85 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -4863,18 +4863,34 @@ en: label: "City for Disputes" placeholder: "San Francisco, California" - colors: - title: "Colors" - - fonts: - title: "Fonts" + styling: + title: "Styling" fields: + color_scheme: + label: "Color scheme" body_font: label: "Body font" heading_font: label: "Heading font" - font_preview: + styling_preview: label: "Preview" + homepage_style: + label: "Homepage style" + choices: + latest: + label: "Latest Topics" + categories_only: + label: "Categories Only" + categories_with_featured_topics: + label: "Categories with Featured Topics" + categories_and_latest_topics: + label: "Categories and Latest Topics" + categories_and_top_topics: + label: "Categories and Top Topics" + categories_boxes: + label: "Categories boxes" + categories_boxes_with_topics: + label: "Categories boxes with Topics" logos: title: "Logos" @@ -4896,28 +4912,6 @@ en: label: "Large Icon" description: "Icon image used to represent your site on modern devices that looks good at larger sizes. Ideally larger than 512 × 512. We'll use the square logo by default." - homepage: - description: "We recommend showing the latest topics on your homepage, but you can also show categories (groups of topics) on the homepage if you prefer." - title: "Homepage" - - fields: - homepage_style: - choices: - latest: - label: "Latest Topics" - categories_only: - label: "Categories Only" - categories_with_featured_topics: - label: "Categories with Featured Topics" - categories_and_latest_topics: - label: "Categories and Latest Topics" - categories_and_top_topics: - label: "Categories and Top Topics" - categories_boxes: - label: "Categories boxes" - categories_boxes_with_topics: - label: "Categories boxes with Topics" - invites: title: "Invite Staff" description: "You’re almost done! Let’s invite some people to help seed your discussions with interesting topics and replies to get your community started." diff --git a/lib/wizard/builder.rb b/lib/wizard/builder.rb index 4dfee9af9c8..d6006682a12 100644 --- a/lib/wizard/builder.rb +++ b/lib/wizard/builder.rb @@ -141,7 +141,7 @@ class Wizard end end - @wizard.append_step('colors') do |step| + @wizard.append_step('styling') do |step| default_theme = Theme.find_by(id: SiteSetting.default_theme_id) default_theme_override = SiteSetting.exists?(name: "default_theme_id") @@ -151,29 +151,60 @@ class Wizard scheme_id = default_theme_override ? (base_scheme || color_scheme_name) : ColorScheme::LIGHT_THEME_ID themes = step.add_field( - id: 'theme_previews', - type: 'component', + id: 'color_scheme', + type: 'dropdown', required: !default_theme_override, - value: scheme_id || ColorScheme::LIGHT_THEME_ID + value: scheme_id || ColorScheme::LIGHT_THEME_ID, + show_in_sidebar: true ) # fix for the case when base_scheme is nil if scheme_id && default_theme_override && base_scheme.nil? scheme = default_theme.color_scheme - default_colors = scheme.colors.select(:name, :hex) - choice_hash = default_colors.reduce({}) { |choice, color| choice[color.name] = "##{color.hex}"; choice } - themes.add_choice(scheme_id, data: { colors: choice_hash }) + themes.add_choice(scheme_id, data: { colors: scheme.colors_hashes }) end 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 }) + themes.add_choice(t[:id], data: { colors: t[:colors] }) end + body_font = step.add_field( + id: 'body_font', + type: 'dropdown', + value: SiteSetting.base_font, + show_in_sidebar: true + ) + + heading_font = step.add_field( + id: 'heading_font', + type: 'dropdown', + value: SiteSetting.heading_font, + show_in_sidebar: true + ) + + DiscourseFonts.fonts.each do |font| + body_font.add_choice(font[:key], label: font[:name]) + heading_font.add_choice(font[:key], label: font[:name]) + end + + current = SiteSetting.top_menu.starts_with?("categories") ? SiteSetting.desktop_category_page_style : "latest" + style = step.add_field(id: 'homepage_style', type: 'dropdown', required: true, value: current, show_in_sidebar: true) + style.add_choice('latest') + CategoryPageStyle.values.each do |page| + style.add_choice(page[:value]) + end + + step.add_field( + id: 'styling_preview', + type: 'component' + ) + step.on_update do |updater| + updater.update_setting(:base_font, updater.fields[:body_font]) + updater.update_setting(:heading_font, updater.fields[:heading_font]) + scheme_name = ( - (updater.fields[:theme_previews] || "") || + (updater.fields[:color_scheme] || "") || ColorScheme::LIGHT_THEME_ID ) @@ -189,33 +220,21 @@ class Wizard default_theme.save! else theme = Theme.create!( - name: name, + name: I18n.t("color_schemes.default_theme_name"), user_id: @wizard.user.id, color_scheme_id: scheme.id ) theme.set_default! end - end - end - @wizard.append_step('fonts') do |step| - body_font = step.add_field(id: 'body_font', type: 'dropdown', value: SiteSetting.base_font) - heading_font = step.add_field(id: 'heading_font', type: 'dropdown', value: SiteSetting.heading_font) - - DiscourseFonts.fonts.each do |font| - body_font.add_choice(font[:key], label: font[:name]) - heading_font.add_choice(font[:key], label: font[:name]) - end - - step.add_field( - id: 'font_preview', - type: 'component' - ) - - step.on_update do |updater| - updater.update_setting(:base_font, updater.fields[:body_font]) - updater.update_setting(:heading_font, updater.fields[:heading_font]) + if updater.fields[:homepage_style] == 'latest' + top_menu = "latest|new|unread|top|categories" + else + top_menu = "categories|latest|new|unread|top" + updater.update_setting(:desktop_category_page_style, updater.fields[:homepage_style]) + end + updater.update_setting(:top_menu, top_menu) end end @@ -242,29 +261,6 @@ class Wizard end end - @wizard.append_step('homepage') do |step| - - current = SiteSetting.top_menu.starts_with?("categories") ? SiteSetting.desktop_category_page_style : "latest" - - style = step.add_field(id: 'homepage_style', type: 'dropdown', required: true, value: current) - style.add_choice('latest') - CategoryPageStyle.values.each do |page| - style.add_choice(page[:value]) - end - - step.add_field(id: 'homepage_preview', type: 'component') - - step.on_update do |updater| - if updater.fields[:homepage_style] == 'latest' - top_menu = "latest|new|unread|top|categories" - else - top_menu = "categories|latest|new|unread|top" - updater.update_setting(:desktop_category_page_style, updater.fields[:homepage_style]) - end - updater.update_setting(:top_menu, top_menu) - end - end - @wizard.append_step('invites') do |step| if SiteSetting.enable_local_logins staff_count = User.staff.human_users.where('username_lower not in (?)', reserved_usernames).count diff --git a/lib/wizard/field.rb b/lib/wizard/field.rb index 7c15ba09564..d085f959877 100644 --- a/lib/wizard/field.rb +++ b/lib/wizard/field.rb @@ -16,7 +16,7 @@ class Wizard end class Field - attr_reader :id, :type, :required, :value, :choices + attr_reader :id, :type, :required, :value, :choices, :show_in_sidebar attr_accessor :step def initialize(attrs) @@ -27,6 +27,7 @@ class Wizard @required = !!attrs[:required] @value = attrs[:value] @choices = [] + @show_in_sidebar = attrs[:show_in_sidebar] end def add_choice(id, opts = nil) diff --git a/spec/components/wizard/step_updater_spec.rb b/spec/components/wizard/step_updater_spec.rb index 1f508507794..d0d3f38865b 100644 --- a/spec/components/wizard/step_updater_spec.rb +++ b/spec/components/wizard/step_updater_spec.rb @@ -167,105 +167,145 @@ describe Wizard::StepUpdater do end end - context "fonts step" do + context "styling step" do it "updates fonts" do - updater = wizard.create_updater('fonts', body_font: 'open_sans', heading_font: 'oswald') + updater = wizard.create_updater('styling', body_font: 'open_sans', heading_font: 'oswald') updater.update expect(updater.success?).to eq(true) - expect(wizard.completed_steps?('fonts')).to eq(true) + expect(wizard.completed_steps?('styling')).to eq(true) expect(SiteSetting.base_font).to eq('open_sans') expect(SiteSetting.heading_font).to eq('oswald') end - end - context "colors step" do - context "with an existing color scheme" do - fab!(:color_scheme) { Fabricate(:color_scheme, name: 'existing', via_wizard: true) } + context "colors" do + context "with an existing color scheme" do + fab!(:color_scheme) { Fabricate(:color_scheme, name: 'existing', via_wizard: true) } - it "updates the scheme" do - 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') - end - end - - context "with an existing default theme" do - fab!(:theme) { Fabricate(:theme) } - - before do - theme.set_default! - end - - it "should not update the default theme when no option has been selected" do - expect do - wizard.create_updater('colors', {}).update - end.to_not change { SiteSetting.default_theme_id } - end - - it "should update the color scheme of the default theme" do - updater = wizard.create_updater('colors', theme_previews: 'Neutral') - expect { updater.update }.not_to change { Theme.count } - theme.reload - expect(theme.color_scheme.base_scheme_id).to eq('Neutral') - end - end - - context "without an existing theme" do - before do - Theme.delete_all - end - - context 'dark theme' do - it "creates the theme" do - updater = wizard.create_updater('colors', theme_previews: 'Dark') - - expect { updater.update }.to change { Theme.count }.by(1) - - theme = Theme.last - - expect(theme.user_id).to eq(wizard.user.id) + it "updates the scheme" do + updater = wizard.create_updater('styling', color_scheme: 'Dark', body_font: 'arial', heading_font: 'arial', homepage_style: 'latest') + updater.update + expect(updater.success?).to eq(true) + expect(wizard.completed_steps?('styling')).to eq(true) + theme = Theme.find_by(id: SiteSetting.default_theme_id) expect(theme.color_scheme.base_scheme_id).to eq('Dark') end end - context 'light theme' do - it "creates the theme" do - updater = wizard.create_updater('colors', - theme_previews: ColorScheme::LIGHT_THEME_ID + context "with an existing default theme" do + fab!(:theme) { Fabricate(:theme) } + + before do + theme.set_default! + end + + it "should update the color scheme of the default theme" do + updater = wizard.create_updater('styling', + color_scheme: 'Neutral', + body_font: 'arial', + heading_font: 'arial', + homepage_style: 'latest' ) + expect { updater.update }.not_to change { Theme.count } + theme.reload + expect(theme.color_scheme.base_scheme_id).to eq('Neutral') + end + end - expect { updater.update }.to change { Theme.count }.by(1) + context "without an existing theme" do + before do + Theme.delete_all + end - theme = Theme.last + context 'dark theme' do + it "creates the theme" do + updater = wizard.create_updater('styling', + color_scheme: 'Dark', + body_font: 'arial', + heading_font: 'arial', + homepage_style: 'latest' + ) - expect(theme.user_id).to eq(wizard.user.id) + expect { updater.update }.to change { Theme.count }.by(1) - expect(theme.color_scheme).to eq(ColorScheme.find_by(name: - ColorScheme::LIGHT_THEME_ID - )) + theme = Theme.last + + expect(theme.user_id).to eq(wizard.user.id) + expect(theme.color_scheme.base_scheme_id).to eq('Dark') + end + end + + context 'light theme' do + it "creates the theme" do + updater = wizard.create_updater('styling', + color_scheme: ColorScheme::LIGHT_THEME_ID, + body_font: 'arial', + heading_font: 'arial', + homepage_style: 'latest' + ) + + 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).to eq(ColorScheme.find_by(name: + ColorScheme::LIGHT_THEME_ID + )) + end + end + end + + context "without an existing scheme" do + it "creates the scheme" do + ColorScheme.destroy_all + updater = wizard.create_updater('styling', + color_scheme: 'Dark', + body_font: 'arial', + heading_font: 'arial', + homepage_style: 'latest' + ) + updater.update + expect(updater.success?).to eq(true) + expect(wizard.completed_steps?('styling')).to eq(true) + + color_scheme = ColorScheme.where(via_wizard: true).first + expect(color_scheme).to be_present + expect(color_scheme.colors).to be_present + + theme = Theme.find_by(id: SiteSetting.default_theme_id) + expect(theme.color_scheme_id).to eq(color_scheme.id) end end end - context "without an existing scheme" do - it "creates the scheme" do - ColorScheme.destroy_all - updater = wizard.create_updater('colors', theme_previews: 'Dark') + context "homepage style" do + it "updates the fields correctly" do + updater = wizard.create_updater('styling', + color_scheme: 'Dark', + body_font: 'arial', + heading_font: 'arial', + homepage_style: "categories_and_top_topics" + ) updater.update - expect(updater.success?).to eq(true) - expect(wizard.completed_steps?('colors')).to eq(true) - color_scheme = ColorScheme.where(via_wizard: true).first - expect(color_scheme).to be_present - expect(color_scheme.colors).to be_present + expect(updater).to be_success + expect(wizard.completed_steps?('styling')).to eq(true) + expect(SiteSetting.top_menu).to eq('categories|latest|new|unread|top') + expect(SiteSetting.desktop_category_page_style).to eq('categories_and_top_topics') - theme = Theme.find_by(id: SiteSetting.default_theme_id) - expect(theme.color_scheme_id).to eq(color_scheme.id) + updater = wizard.create_updater('styling', + color_scheme: 'Dark', + body_font: 'arial', + heading_font: 'arial', + homepage_style: "latest" + ) + updater.update + expect(updater).to be_success + expect(SiteSetting.top_menu).to eq('latest|new|unread|top|categories') end end + end context "logos step" do @@ -307,23 +347,6 @@ describe Wizard::StepUpdater do end end - context "homepage step" do - it "updates the fields correctly" do - updater = wizard.create_updater('homepage', homepage_style: "categories_and_top_topics") - updater.update - - expect(updater).to be_success - expect(wizard.completed_steps?('homepage')).to eq(true) - expect(SiteSetting.top_menu).to eq('categories|latest|new|unread|top') - expect(SiteSetting.desktop_category_page_style).to eq('categories_and_top_topics') - - updater = wizard.create_updater('homepage', homepage_style: "latest") - updater.update - expect(updater).to be_success - expect(SiteSetting.top_menu).to eq('latest|new|unread|top|categories') - end - end - context "invites step" do let(:invites) { return [{ email: 'regular@example.com', role: 'regular' }, diff --git a/spec/components/wizard/wizard_builder_spec.rb b/spec/components/wizard/wizard_builder_spec.rb index c1ea65136e3..b2d32e4ed35 100644 --- a/spec/components/wizard/wizard_builder_spec.rb +++ b/spec/components/wizard/wizard_builder_spec.rb @@ -41,12 +41,64 @@ describe Wizard::Builder do expect(invites_step.disabled).to be_truthy end - context 'fonts step' do - let(:fonts_step) { wizard.steps.find { |s| s.id == 'fonts' } } - let(:field) { fonts_step.fields.first } + context 'styling step' do + let(:styling_step) { wizard.steps.find { |s| s.id == 'styling' } } + let(:font_field) { styling_step.fields[1] } + fab!(:theme) { Fabricate(:theme) } + let(:colors_field) { styling_step.fields.first } - it 'should set the right font' do - expect(field.choices.size).to eq(DiscourseFonts.fonts.size) + it 'has the full list of available fonts' do + expect(font_field.choices.size).to eq(DiscourseFonts.fonts.size) + end + + context "colors" do + describe "when the default theme has not been override" do + before do + SiteSetting.find_by(name: "default_theme_id").destroy! + end + + it 'should set the right default values' do + expect(colors_field.required).to eq(true) + expect(colors_field.value).to eq(ColorScheme::LIGHT_THEME_ID) + end + end + + describe "when the default theme has been override and the color scheme doesn't have a base scheme" do + let(:color_scheme) { Fabricate(:color_scheme, base_scheme_id: nil) } + + before do + SiteSetting.default_theme_id = theme.id + theme.update(color_scheme: color_scheme) + end + + it 'fallbacks to the color scheme name' do + expect(colors_field.required).to eq(false) + expect(colors_field.value).to eq(color_scheme.name) + end + end + + describe "when the default theme has been overridden by a theme without a color scheme" do + before do + theme.set_default! + end + + it 'should set the right default values' do + expect(colors_field.required).to eq(false) + expect(colors_field.value).to eq("Light") + end + end + + describe "when the default theme has been overridden by a theme with a color scheme" do + before do + theme.update(color_scheme_id: ColorScheme.find_by_name("Dark").id) + theme.set_default! + end + + it 'should set the right default values' do + expect(colors_field.required).to eq(false) + expect(colors_field.value).to eq("Dark") + end + end end end @@ -155,57 +207,4 @@ describe Wizard::Builder do end end - context "colors step" do - fab!(:theme) { Fabricate(:theme) } - let(:colors_step) { wizard.steps.find { |s| s.id == 'colors' } } - let(:field) { colors_step.fields.first } - - describe "when the default theme has not been override" do - before do - SiteSetting.find_by(name: "default_theme_id").destroy! - end - - it 'should set the right default values' do - expect(field.required).to eq(true) - expect(field.value).to eq(ColorScheme::LIGHT_THEME_ID) - end - end - - describe "when the default theme has been override and the color scheme doesn't have a base scheme" do - let(:color_scheme) { Fabricate(:color_scheme, base_scheme_id: nil) } - - before do - SiteSetting.default_theme_id = theme.id - theme.update(color_scheme: color_scheme) - end - - it 'fallbacks to the color scheme name' do - expect(field.required).to eq(false) - expect(field.value).to eq(color_scheme.name) - end - end - - describe "when the default theme has been overridden by a theme without a color scheme" do - before do - theme.set_default! - end - - it 'should set the right default values' do - expect(field.required).to eq(false) - expect(field.value).to eq("Light") - end - end - - describe "when the default theme has been overridden by a theme with a color scheme" do - before do - theme.update(color_scheme_id: ColorScheme.find_by_name("Dark").id) - theme.set_default! - end - - it 'should set the right default values' do - expect(field.required).to eq(false) - expect(field.value).to eq("Dark") - end - end - end end diff --git a/spec/requests/admin/color_schemes_controller_spec.rb b/spec/requests/admin/color_schemes_controller_spec.rb index 782d9ee5c2b..ae3905e612b 100644 --- a/spec/requests/admin/color_schemes_controller_spec.rb +++ b/spec/requests/admin/color_schemes_controller_spec.rb @@ -28,8 +28,13 @@ describe Admin::ColorSchemesController do get "/admin/color_schemes.json" expect(response.status).to eq(200) - schemes = response.parsed_body.map { |scheme| scheme["name"] } - expect(schemes).to include(scheme_name) + scheme_names = response.parsed_body.map { |scheme| scheme["name"] } + scheme_colors = response.parsed_body[0]["colors"] + base_scheme_colors = ColorScheme.base.colors + + expect(scheme_names).to include(scheme_name) + expect(scheme_colors[0]["name"]).to eq(base_scheme_colors[0].name) + expect(scheme_colors[0]["hex"]).to eq(base_scheme_colors[0].hex) end end diff --git a/spec/serializers/wizard_serializer_spec.rb b/spec/serializers/wizard_serializer_spec.rb index cd66038dafd..ff9406f21b1 100644 --- a/spec/serializers/wizard_serializer_spec.rb +++ b/spec/serializers/wizard_serializer_spec.rb @@ -18,7 +18,8 @@ describe WizardSerializer do json = MultiJson.load(MultiJson.dump(serializer.as_json)) wjson = json['wizard'] - expect(wjson['current_color_scheme']['primary']).to eq('#222222') + expect(wjson['current_color_scheme'][0]['name']).to eq('primary') + expect(wjson['current_color_scheme'][0]['hex']).to eq('222222') end it "should provide custom colors correctly" do @@ -34,7 +35,7 @@ describe WizardSerializer do json = MultiJson.load(MultiJson.dump(serializer.as_json)) wjson = json['wizard'] - expect(wjson['current_color_scheme']['header_background']).to eq('#00FF00') + expect(wjson['current_color_scheme'].to_s).to include('{"name"=>"header_background", "hex"=>"00FF00"}') end end