mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
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:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,8 +0,0 @@
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
actions: {
|
||||
changed(value) {
|
||||
this.set("field.value", value);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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`,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 [];
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<div class="preview-area">
|
||||
<canvas
|
||||
width={{elementWidth}}
|
||||
height={{elementHeight}}
|
||||
style={{canvasStyle}}
|
||||
>
|
||||
</canvas>
|
||||
</div>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
|
||||
)
|
||||
}}
|
||||
|
||||
@@ -13,7 +13,14 @@
|
||||
</label>
|
||||
|
||||
<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>
|
||||
|
||||
{{#if field.errorDescription}}
|
||||
|
||||
@@ -14,9 +14,32 @@
|
||||
</div>
|
||||
|
||||
{{#wizard-step-form step=step}}
|
||||
{{#each step.fields as |field|}}
|
||||
{{wizard-field field=field step=step wizard=wizard}}
|
||||
{{/each}}
|
||||
{{#if includeSidebar}}
|
||||
<div class="wizard-fields-sidebar">
|
||||
{{#each step.fields as |field|}}
|
||||
{{#if field.show_in_sidebar}}
|
||||
{{wizard-field
|
||||
field=field
|
||||
step=step
|
||||
wizard=wizard
|
||||
stylingDropdownChanged=(action "stylingDropdownChanged")
|
||||
}}
|
||||
{{/if}}
|
||||
{{/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}}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user