UX: Add Styling step to wizard (#14132)

Refactors three wizard steps (colors, fonts, homepage style) into one new step called Styling.
This commit is contained in:
Penar Musaraj 2021-08-25 17:10:12 -04:00 committed by GitHub
parent cfbf69848a
commit 85b8fea262
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 548 additions and 371 deletions

View File

@ -1,6 +1,5 @@
import SelectKitRowComponent from "select-kit/components/select-kit/select-kit-row"; import SelectKitRowComponent from "select-kit/components/select-kit/select-kit-row";
import { computed } from "@ember/object"; import { computed } from "@ember/object";
import { escapeExpression } from "discourse/lib/utilities";
import layout from "select-kit/templates/components/color-palettes/color-palettes-row"; import layout from "select-kit/templates/components/color-palettes/color-palettes-row";
export default SelectKitRowComponent.extend({ export default SelectKitRowComponent.extend({
@ -10,7 +9,7 @@ export default SelectKitRowComponent.extend({
palettes: computed("item.colors.[]", function () { palettes: computed("item.colors.[]", function () {
return (this.item.colors || []) return (this.item.colors || [])
.filter((color) => color.name !== "secondary") .filter((color) => color.name !== "secondary")
.map((color) => `#${escapeExpression(color.hex)}`) .map((color) => `#${escape(color.hex)}`)
.map( .map(
(hex) => `<span class="palette" style="background-color:${hex}"></span>` (hex) => `<span class="palette" style="background-color:${hex}"></span>`
) )
@ -22,7 +21,7 @@ export default SelectKitRowComponent.extend({
const secondary = (this.item.colors || []).findBy("name", "secondary"); const secondary = (this.item.colors || []).findBy("name", "secondary");
if (secondary && secondary.hex) { if (secondary && secondary.hex) {
return `background-color:#${escapeExpression(secondary.hex)}`.htmlSafe(); return `background-color:#${escape(secondary.hex)}`.htmlSafe();
} else { } else {
return ""; return "";
} }

View File

@ -3,14 +3,14 @@ import {
createPreviewComponent, createPreviewComponent,
darkLightDiff, darkLightDiff,
} from "wizard/lib/preview"; } from "wizard/lib/preview";
import { observes } from "discourse-common/utils/decorators";
export default createPreviewComponent(659, 320, { export default createPreviewComponent(659, 320, {
logo: null, logo: null,
avatar: null, avatar: null,
@observes("step.fieldsById.homepage_style.value") didUpdateAttrs() {
styleChanged() { this._super(...arguments);
this.triggerRepaint(); this.triggerRepaint();
}, },
@ -22,7 +22,9 @@ export default createPreviewComponent(659, 320, {
}, },
paint({ ctx, colors, font, width, height }) { paint({ ctx, colors, font, width, height }) {
if (this.logo) {
this.drawFullHeader(colors, font, this.logo); this.drawFullHeader(colors, font, this.logo);
}
if (this.get("step.fieldsById.homepage_style.value") === "latest") { if (this.get("step.fieldsById.homepage_style.value") === "latest") {
this.drawPills(colors, font, height * 0.15); this.drawPills(colors, font, height * 0.15);

View File

@ -15,13 +15,66 @@ metus. Fusce in consequat augue, vel facilisis felis.`;
export default createPreviewComponent(659, 320, { export default createPreviewComponent(659, 320, {
logo: null, logo: null,
avatar: 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(); 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() { images() {
@ -39,14 +92,14 @@ export default createPreviewComponent(659, 320, {
} }
const margin = 20; const margin = 20;
const avatarSize = height * 0.2; const avatarSize = height * 0.15;
const lineHeight = height / 11; const lineHeight = height / 14;
// Draw a fake topic // Draw a fake topic
this.scaleImage( this.scaleImage(
this.avatar, this.avatar,
margin, margin,
headerHeight + height * 0.11, headerHeight + height * 0.09,
avatarSize, avatarSize,
avatarSize avatarSize
); );
@ -80,7 +133,7 @@ export default createPreviewComponent(659, 320, {
ctx.fillText( ctx.fillText(
I18n.t("wizard.previews.share_button"), I18n.t("wizard.previews.share_button"),
margin + 10, margin + 10,
line + lineHeight * 1.7 line + lineHeight * 1.9
); );
// Reply Button // Reply Button
@ -100,7 +153,7 @@ export default createPreviewComponent(659, 320, {
ctx.fillText( ctx.fillText(
I18n.t("wizard.previews.reply_button"), I18n.t("wizard.previews.reply_button"),
shareButtonWidth + margin + 20, shareButtonWidth + margin + 20,
line + lineHeight * 1.7 line + lineHeight * 1.9
); );
// Draw Timeline // Draw Timeline
@ -124,4 +177,14 @@ export default createPreviewComponent(659, 320, {
ctx.fillStyle = colors.primary; ctx.fillStyle = colors.primary;
ctx.fillText("1 / 20", timelineX + margin, height * 0.3 + margin * 1.5); ctx.fillText("1 / 20", timelineX + margin, height * 0.3 + margin * 1.5);
}, },
actions: {
setPreviewHomepage() {
this.set("previewTopic", false);
},
setPreviewTopic() {
this.set("previewTopic", true);
},
},
}); });

View File

@ -1,8 +0,0 @@
import Component from "@ember/component";
export default Component.extend({
actions: {
changed(value) {
this.set("field.value", value);
},
},
});

View File

@ -1,7 +1,36 @@
import Component from "@ember/component"; import Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators";
import { set } from "@ember/object";
export default Component.extend({ 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) { keyPress(e) {
e.stopPropagation(); e.stopPropagation();
}, },
actions: {
onChangeValue(value) {
this.set("field.value", value);
this.stylingDropdownChanged(this.field.id, value);
},
},
}); });

View File

@ -3,10 +3,11 @@ import { dasherize } from "@ember/string";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({ export default Component.extend({
classNameBindings: [":wizard-field", "typeClass", "field.invalid"], classNameBindings: [":wizard-field", "typeClasses", "field.invalid"],
@discourseComputed("field.type") @discourseComputed("field.type", "field.id")
typeClass: (type) => `${dasherize(type)}-field`, typeClasses: (type, id) =>
`${dasherize(type)}-field ${dasherize(type)}-${dasherize(id)}`,
@discourseComputed("field.id") @discourseComputed("field.id")
fieldClass: (id) => `field-${dasherize(id)} wizard-focusable`, fieldClass: (id) => `field-${dasherize(id)} wizard-focusable`,

View File

@ -27,6 +27,11 @@ export default Component.extend({
classNames: ["wizard-step"], classNames: ["wizard-step"],
saving: null, saving: null,
init() {
this._super(...arguments);
this.set("stylingDropdown", {});
},
didInsertElement() { didInsertElement() {
this._super(...arguments); this._super(...arguments);
this.autoFocus(); this.autoFocus();
@ -96,6 +101,11 @@ export default Component.extend({
return htmlSafe(`width: ${ratio * 200}px`); return htmlSafe(`width: ${ratio * 200}px`);
}, },
@discourseComputed("step.fields")
includeSidebar(fields) {
return !!fields.findBy("show_in_sidebar");
},
autoFocus() { autoFocus() {
schedule("afterRender", () => { schedule("afterRender", () => {
const $invalid = $( const $invalid = $(
@ -130,6 +140,10 @@ export default Component.extend({
document.location = getUrl("/"); document.location = getUrl("/");
}, },
stylingDropdownChanged(id, value) {
this.set("stylingDropdown", { id, value });
},
exitEarly() { exitEarly() {
const step = this.step; const step = this.step;
step.validate(); step.validate();

View File

@ -12,7 +12,7 @@ export default Controller.extend({
@discourseComputed("model") @discourseComputed("model")
fontClasses(model) { fontClasses(model) {
const fontsStep = model.steps.findBy("id", "fonts"); const fontsStep = model.steps.findBy("id", "styling");
if (!fontsStep) { if (!fontsStep) {
return []; return [];
} }

View File

@ -2,6 +2,7 @@ import Component from "@ember/component";
import { Promise } from "rsvp"; import { Promise } from "rsvp";
/*eslint no-bitwise:0 */ /*eslint no-bitwise:0 */
import getUrl from "discourse-common/lib/get-url"; import getUrl from "discourse-common/lib/get-url";
import { htmlSafe } from "@ember/template";
import { scheduleOnce } from "@ember/runloop"; import { scheduleOnce } from "@ember/runloop";
export const LOREM = ` export const LOREM = `
@ -41,7 +42,7 @@ export function createPreviewComponent(width, height, obj) {
height, height,
elementWidth: width * scale, elementWidth: width * scale,
elementHeight: height * scale, elementHeight: height * scale,
canvasStyle: `width:${width}px;height:${height}px`, canvasStyle: htmlSafe(`width:${width}px;height:${height}px`),
ctx: null, ctx: null,
loaded: false, loaded: false,
@ -87,11 +88,17 @@ export function createPreviewComponent(width, height, obj) {
return false; return false;
} }
const colors = this.wizard.getCurrentColors(this.colorsId); const colorsArray = this.wizard.getCurrentColors(this.colorsId);
if (!colors) { if (!colorsArray) {
return; return;
} }
let colors = {};
colorsArray.forEach(function (c) {
const name = c.name;
colors[name] = `#${c.hex}`;
});
const font = this.wizard.getCurrentFont(this.fontId); const font = this.wizard.getCurrentFont(this.fontId);
const headingFont = this.wizard.getCurrentFont( const headingFont = this.wizard.getCurrentFont(
this.fontId, this.fontId,
@ -115,12 +122,6 @@ export function createPreviewComponent(width, height, obj) {
height: this.height, height: this.height,
}; };
this.paint(options); this.paint(options);
// draw border
ctx.beginPath();
ctx.strokeStyle = "rgba(0, 0, 0, 0.2)";
ctx.rect(0, 0, width, height);
ctx.stroke();
}, },
categories() { categories() {

View File

@ -26,12 +26,12 @@ const Wizard = EmberObject.extend({
// A bit clunky, but get the current colors from the appropriate step // A bit clunky, but get the current colors from the appropriate step
getCurrentColors(schemeId) { getCurrentColors(schemeId) {
const colorStep = this.steps.findBy("id", "colors"); const colorStep = this.steps.findBy("id", "styling");
if (!colorStep) { if (!colorStep) {
return this.current_color_scheme; return this.current_color_scheme;
} }
const themeChoice = colorStep.get("fieldsById.theme_previews"); const themeChoice = colorStep.get("fieldsById.color_scheme");
if (!themeChoice) { if (!themeChoice) {
return; return;
} }
@ -55,7 +55,7 @@ const Wizard = EmberObject.extend({
}, },
getCurrentFont(fontId, type = "body_font") { getCurrentFont(fontId, type = "body_font") {
const fontsStep = this.steps.findBy("id", "fonts"); const fontsStep = this.steps.findBy("id", "styling");
if (!fontsStep) { if (!fontsStep) {
return; return;
} }

View File

@ -1,8 +0,0 @@
<div class="preview-area">
<canvas
width={{elementWidth}}
height={{elementHeight}}
style={{canvasStyle}}
>
</canvas>
</div>

View File

@ -0,0 +1,22 @@
<div class="previews {{if draggingActive "dragging"}}">
<div class="preview-area topic-preview">
<canvas width={{elementWidth}} height={{elementHeight}} style={{canvasStyle}}>
</canvas>
</div>
<div class="preview-area homepage-preview">
{{homepage-preview
wizard=wizard
step=step
stylingDropdown=stylingDropdown
}}
</div>
</div>
<div class="preview-nav">
<a href class="preview-nav-button {{if previewTopic "active"}}" {{action "setPreviewTopic"}}>
{{i18n "wizard.previews.topic_preview"}}
</a>
<a href class="preview-nav-button {{unless previewTopic "active"}}" {{action "setPreviewHomepage"}}>
{{i18n "wizard.previews.homepage_preview"}}
</a>
</div>

View File

@ -1,14 +0,0 @@
<ul class="grid">
{{#each field.choices as |choice|}}
<li>
{{theme-preview colorsId=choice.id
wizard=wizard
selectedId=field.value
onChange=(action "changed")}}
{{radio-button radioValue=choice.id
label=choice.id
value=field.value
onChange=(action "changed")}}
</li>
{{/each}}
</ul>

View File

@ -1,9 +1,12 @@
{{combo-box {{component
id=field.id componentName
class=fieldClass class=fieldClass
value=field.value value=field.value
content=field.choices content=field.choices
nameProperty="label" nameProperty="label"
tabindex="9" tabindex="9"
onChange=(action (mut field.value)) onChange=(action "onChangeValue")
options=(hash
translatedNone=false
)
}} }}

View File

@ -13,7 +13,14 @@
</label> </label>
<div class="input-area"> <div class="input-area">
{{component inputComponentName field=field step=step fieldClass=fieldClass wizard=wizard}} {{component
inputComponentName
field=field step=step
fieldClass=fieldClass
wizard=wizard
stylingDropdownChanged=stylingDropdownChanged
stylingDropdown=stylingDropdown
}}
</div> </div>
{{#if field.errorDescription}} {{#if field.errorDescription}}

View File

@ -14,9 +14,32 @@
</div> </div>
{{#wizard-step-form step=step}} {{#wizard-step-form step=step}}
{{#if includeSidebar}}
<div class="wizard-fields-sidebar">
{{#each step.fields as |field|}} {{#each step.fields as |field|}}
{{wizard-field field=field step=step wizard=wizard}} {{#if field.show_in_sidebar}}
{{wizard-field
field=field
step=step
wizard=wizard
stylingDropdownChanged=(action "stylingDropdownChanged")
}}
{{/if}}
{{/each}} {{/each}}
</div>
{{/if}}
<div class="wizard-fields-main">
{{#each step.fields as |field|}}
{{#unless field.show_in_sidebar}}
{{wizard-field
field=field
step=step
wizard=wizard
stylingDropdown=stylingDropdown
}}
{{/unless}}
{{/each}}
</div>
{{/wizard-step-form}} {{/wizard-step-form}}
</div> </div>

View File

@ -820,66 +820,78 @@ body.wizard {
.wizard-step-form { .wizard-step-form {
max-height: 500px; max-height: 500px;
display: flex;
.wizard-fields-main {
width: 100%;
} }
.wizard-step-homepage { .wizard-fields-sidebar {
.field-homepage-style { width: 170px;
width: 280px;
}
}
.wizard-step-colors {
max-height: 465px;
overflow-y: auto;
.grid {
box-sizing: border-box; box-sizing: border-box;
display: flex; padding: 30px 0px 15px 15px;
flex-wrap: wrap; background: var(--primary-very-low);
justify-content: space-around; + .wizard-fields-main {
padding: 0; padding: 15px;
margin: 0 auto; padding-top: 30px;
list-style-type: none; background: var(--primary-very-low);
text-align: center; width: calc(100% - 170px);
}
}
}
li { .wizard-step-styling {
display: inline-block; .preview-nav {
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; display: flex;
flex: 1 1 auto; justify-content: flex-end;
}
.radio-area {
display: none;
& > * {
position: relative; position: relative;
right: 7px; margin-top: -1px;
} padding-right: 10px;
} .preview-nav-button {
canvas { text-align: center;
transition: box-shadow 0.25s; padding: 10px 15px;
&:hover {
box-shadow: shadow("card");
cursor: pointer; 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;
} }
} }
} }
.wizard-step-fonts { .previews {
.dropdown-field { position: relative;
float: left; height: 320px;
margin-right: 1.5em; 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;
} }
.component-field {
clear: both;
} }
} }
@ -891,7 +903,7 @@ body.wizard {
box-sizing: border-box; box-sizing: border-box;
margin: 1.5em auto; margin: 1.5em auto;
padding: 0; padding: 0;
max-width: 700px; max-width: 820px;
min-width: 280px; min-width: 280px;
width: 100%; width: 100%;
border: 1px solid var(--primary-low-mid); border: 1px solid var(--primary-low-mid);
@ -924,7 +936,7 @@ body.wizard {
} }
.wizard-step-banner { .wizard-step-banner {
margin-bottom: 2em; margin-bottom: 2em;
width: 620px; width: 100%;
display: block; display: block;
} }
@ -1178,6 +1190,10 @@ body.wizard {
margin-bottom: 2em; margin-bottom: 2em;
} }
.wizard-image-row canvas {
border: 1px solid rgba(0, 0, 0, 0.2);
}
} }
.textarea-field { .textarea-field {

View File

@ -133,10 +133,10 @@ class ColorScheme < ActiveRecord::Base
LIGHT_THEME_ID = 'Light' LIGHT_THEME_ID = 'Light'
def self.base_color_scheme_colors def self.base_color_scheme_colors
base_with_hash = {} base_with_hash = []
base_colors.each do |name, color| base_colors.each do |name, color|
base_with_hash[name] = "#{color}" base_with_hash << { name: name, hex: "#{color}" }
end end
list = [ list = [
@ -144,7 +144,11 @@ class ColorScheme < ActiveRecord::Base
] ]
CUSTOM_SCHEMES.each do |k, v| 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 end
list list
@ -205,7 +209,7 @@ class ColorScheme < ActiveRecord::Base
def self.base_color_schemes def self.base_color_schemes
base_color_scheme_colors.map do |hash| base_color_scheme_colors.map do |hash|
scheme = new(name: I18n.t("color_schemes.#{hash[:id].downcase.gsub(' ', '_')}"), 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.colors = hash[:colors].map { |k| { name: k[:name], hex: k[:hex] } }
scheme.is_base = true scheme.is_base = true
scheme scheme
end end

View File

@ -2,7 +2,7 @@
class WizardFieldSerializer < ApplicationSerializer 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 has_many :choices, serializer: WizardFieldChoiceSerializer, embed: :objects
def id def id
@ -68,4 +68,12 @@ class WizardFieldSerializer < ApplicationSerializer
extra_description.present? extra_description.present?
end end
def show_in_sidebar
object.show_in_sidebar
end
def include_show_in_sidebar?
object.show_in_sidebar.present?
end
end end

View File

@ -15,11 +15,6 @@ class WizardSerializer < ApplicationSerializer
def current_color_scheme def current_color_scheme
color_scheme = Theme.where(id: SiteSetting.default_theme_id).first&.color_scheme color_scheme = Theme.where(id: SiteSetting.default_theme_id).first&.color_scheme
colors = color_scheme ? color_scheme.colors : ColorScheme.base.colors color_scheme ? color_scheme.colors_hashes : ColorScheme.base.colors_hashes
# 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
end end
end end

View File

@ -5395,7 +5395,8 @@ en:
regular: "Regular User" regular: "Regular User"
previews: previews:
topic_title: "Discussion topic" topic_title: "A discussion topic heading"
font_title: "%{font} Font"
share_button: "Share" share_button: "Share"
reply_button: "Reply" reply_button: "Reply"
topic_preview: "Topic preview"
homepage_preview: "Homepage preview"

View File

@ -4863,18 +4863,34 @@ en:
label: "City for Disputes" label: "City for Disputes"
placeholder: "San Francisco, California" placeholder: "San Francisco, California"
colors: styling:
title: "Colors" title: "Styling"
fonts:
title: "Fonts"
fields: fields:
color_scheme:
label: "Color scheme"
body_font: body_font:
label: "Body font" label: "Body font"
heading_font: heading_font:
label: "Heading font" label: "Heading font"
font_preview: styling_preview:
label: "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: logos:
title: "Logos" title: "Logos"
@ -4896,28 +4912,6 @@ en:
label: "Large Icon" 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." 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: invites:
title: "Invite Staff" title: "Invite Staff"
description: "Youre almost done! Lets invite some people to help <a href='https://blog.discourse.org/2014/08/building-a-discourse-community/' target='blank'>seed your discussions</a> with interesting topics and replies to get your community started." description: "Youre almost done! Lets invite some people to help <a href='https://blog.discourse.org/2014/08/building-a-discourse-community/' target='blank'>seed your discussions</a> with interesting topics and replies to get your community started."

View File

@ -141,7 +141,7 @@ class Wizard
end end
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 = Theme.find_by(id: SiteSetting.default_theme_id)
default_theme_override = SiteSetting.exists?(name: "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 scheme_id = default_theme_override ? (base_scheme || color_scheme_name) : ColorScheme::LIGHT_THEME_ID
themes = step.add_field( themes = step.add_field(
id: 'theme_previews', id: 'color_scheme',
type: 'component', type: 'dropdown',
required: !default_theme_override, 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 # fix for the case when base_scheme is nil
if scheme_id && default_theme_override && base_scheme.nil? if scheme_id && default_theme_override && base_scheme.nil?
scheme = default_theme.color_scheme scheme = default_theme.color_scheme
default_colors = scheme.colors.select(:name, :hex) themes.add_choice(scheme_id, data: { colors: scheme.colors_hashes })
choice_hash = default_colors.reduce({}) { |choice, color| choice[color.name] = "##{color.hex}"; choice }
themes.add_choice(scheme_id, data: { colors: choice_hash })
end end
ColorScheme.base_color_scheme_colors.each do |t| ColorScheme.base_color_scheme_colors.each do |t|
with_hash = t[:colors].dup themes.add_choice(t[:id], data: { colors: t[:colors] })
with_hash.map { |k, v| with_hash[k] = "##{v}" }
themes.add_choice(t[:id], data: { colors: with_hash })
end 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| 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 = ( scheme_name = (
(updater.fields[:theme_previews] || "") || (updater.fields[:color_scheme] || "") ||
ColorScheme::LIGHT_THEME_ID ColorScheme::LIGHT_THEME_ID
) )
@ -189,33 +220,21 @@ class Wizard
default_theme.save! default_theme.save!
else else
theme = Theme.create!( theme = Theme.create!(
name: name, name: I18n.t("color_schemes.default_theme_name"),
user_id: @wizard.user.id, user_id: @wizard.user.id,
color_scheme_id: scheme.id color_scheme_id: scheme.id
) )
theme.set_default! theme.set_default!
end end
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 end
end updater.update_setting(:top_menu, top_menu)
@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])
end end
end end
@ -242,29 +261,6 @@ class Wizard
end end
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| @wizard.append_step('invites') do |step|
if SiteSetting.enable_local_logins if SiteSetting.enable_local_logins
staff_count = User.staff.human_users.where('username_lower not in (?)', reserved_usernames).count staff_count = User.staff.human_users.where('username_lower not in (?)', reserved_usernames).count

View File

@ -16,7 +16,7 @@ class Wizard
end end
class Field class Field
attr_reader :id, :type, :required, :value, :choices attr_reader :id, :type, :required, :value, :choices, :show_in_sidebar
attr_accessor :step attr_accessor :step
def initialize(attrs) def initialize(attrs)
@ -27,6 +27,7 @@ class Wizard
@required = !!attrs[:required] @required = !!attrs[:required]
@value = attrs[:value] @value = attrs[:value]
@choices = [] @choices = []
@show_in_sidebar = attrs[:show_in_sidebar]
end end
def add_choice(id, opts = nil) def add_choice(id, opts = nil)

View File

@ -167,26 +167,25 @@ describe Wizard::StepUpdater do
end end
end end
context "fonts step" do context "styling step" do
it "updates fonts" 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 updater.update
expect(updater.success?).to eq(true) 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.base_font).to eq('open_sans')
expect(SiteSetting.heading_font).to eq('oswald') expect(SiteSetting.heading_font).to eq('oswald')
end end
end
context "colors step" do context "colors" do
context "with an existing color scheme" do context "with an existing color scheme" do
fab!(:color_scheme) { Fabricate(:color_scheme, name: 'existing', via_wizard: true) } fab!(:color_scheme) { Fabricate(:color_scheme, name: 'existing', via_wizard: true) }
it "updates the scheme" do it "updates the scheme" do
updater = wizard.create_updater('colors', theme_previews: 'Dark') updater = wizard.create_updater('styling', color_scheme: 'Dark', body_font: 'arial', heading_font: 'arial', homepage_style: 'latest')
updater.update updater.update
expect(updater.success?).to eq(true) expect(updater.success?).to eq(true)
expect(wizard.completed_steps?('colors')).to eq(true) expect(wizard.completed_steps?('styling')).to eq(true)
theme = Theme.find_by(id: SiteSetting.default_theme_id) 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
@ -199,14 +198,13 @@ describe Wizard::StepUpdater do
theme.set_default! theme.set_default!
end 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 it "should update the color scheme of the default theme" do
updater = wizard.create_updater('colors', theme_previews: 'Neutral') 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 } expect { updater.update }.not_to change { Theme.count }
theme.reload theme.reload
expect(theme.color_scheme.base_scheme_id).to eq('Neutral') expect(theme.color_scheme.base_scheme_id).to eq('Neutral')
@ -220,7 +218,12 @@ describe Wizard::StepUpdater do
context 'dark theme' do context 'dark theme' do
it "creates the theme" do it "creates the theme" do
updater = wizard.create_updater('colors', theme_previews: 'Dark') updater = wizard.create_updater('styling',
color_scheme: 'Dark',
body_font: 'arial',
heading_font: 'arial',
homepage_style: 'latest'
)
expect { updater.update }.to change { Theme.count }.by(1) expect { updater.update }.to change { Theme.count }.by(1)
@ -233,8 +236,11 @@ describe Wizard::StepUpdater do
context 'light theme' do context 'light theme' do
it "creates the theme" do it "creates the theme" do
updater = wizard.create_updater('colors', updater = wizard.create_updater('styling',
theme_previews: ColorScheme::LIGHT_THEME_ID color_scheme: ColorScheme::LIGHT_THEME_ID,
body_font: 'arial',
heading_font: 'arial',
homepage_style: 'latest'
) )
expect { updater.update }.to change { Theme.count }.by(1) expect { updater.update }.to change { Theme.count }.by(1)
@ -253,10 +259,15 @@ describe Wizard::StepUpdater do
context "without an existing scheme" do context "without an existing scheme" do
it "creates the scheme" do it "creates the scheme" do
ColorScheme.destroy_all ColorScheme.destroy_all
updater = wizard.create_updater('colors', theme_previews: 'Dark') updater = wizard.create_updater('styling',
color_scheme: 'Dark',
body_font: 'arial',
heading_font: 'arial',
homepage_style: 'latest'
)
updater.update updater.update
expect(updater.success?).to eq(true) expect(updater.success?).to eq(true)
expect(wizard.completed_steps?('colors')).to eq(true) expect(wizard.completed_steps?('styling')).to eq(true)
color_scheme = ColorScheme.where(via_wizard: true).first color_scheme = ColorScheme.where(via_wizard: true).first
expect(color_scheme).to be_present expect(color_scheme).to be_present
@ -268,6 +279,35 @@ describe Wizard::StepUpdater do
end end
end end
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).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')
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 context "logos step" do
it "updates the fields correctly" do it "updates the fields correctly" do
upload = Fabricate(:upload) upload = Fabricate(:upload)
@ -307,23 +347,6 @@ describe Wizard::StepUpdater do
end end
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 context "invites step" do
let(:invites) { let(:invites) {
return [{ email: 'regular@example.com', role: 'regular' }, return [{ email: 'regular@example.com', role: 'regular' },

View File

@ -41,12 +41,64 @@ describe Wizard::Builder do
expect(invites_step.disabled).to be_truthy expect(invites_step.disabled).to be_truthy
end end
context 'fonts step' do context 'styling step' do
let(:fonts_step) { wizard.steps.find { |s| s.id == 'fonts' } } let(:styling_step) { wizard.steps.find { |s| s.id == 'styling' } }
let(:field) { fonts_step.fields.first } 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 it 'has the full list of available fonts' do
expect(field.choices.size).to eq(DiscourseFonts.fonts.size) 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
end end
@ -155,57 +207,4 @@ describe Wizard::Builder do
end end
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 end

View File

@ -28,8 +28,13 @@ describe Admin::ColorSchemesController do
get "/admin/color_schemes.json" get "/admin/color_schemes.json"
expect(response.status).to eq(200) expect(response.status).to eq(200)
schemes = response.parsed_body.map { |scheme| scheme["name"] } scheme_names = response.parsed_body.map { |scheme| scheme["name"] }
expect(schemes).to include(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
end end

View File

@ -18,7 +18,8 @@ describe WizardSerializer do
json = MultiJson.load(MultiJson.dump(serializer.as_json)) json = MultiJson.load(MultiJson.dump(serializer.as_json))
wjson = json['wizard'] 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 end
it "should provide custom colors correctly" do it "should provide custom colors correctly" do
@ -34,7 +35,7 @@ describe WizardSerializer do
json = MultiJson.load(MultiJson.dump(serializer.as_json)) json = MultiJson.load(MultiJson.dump(serializer.as_json))
wjson = json['wizard'] 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
end end