mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 11:23:25 +08:00
DEV: Refactor Wizard components (#24770)
This commit refactors the Wizard component code in preparation for moving it to the 'static' directory for Embroider route-splitting. It also includes a number of general improvements and simplifications. Extracted from https://github.com/discourse/discourse/pull/23678 Co-authored-by: Godfrey Chan <godfreykfc@gmail.com>
This commit is contained in:
parent
0139481188
commit
e4c373194d
|
@ -6,62 +6,133 @@ import {
|
|||
visit,
|
||||
} from "@ember/test-helpers";
|
||||
import { test } from "qunit";
|
||||
import { acceptance, exists } from "discourse/tests/helpers/qunit-helpers";
|
||||
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
|
||||
|
||||
acceptance("Wizard", function (needs) {
|
||||
needs.user();
|
||||
|
||||
test("Wizard starts", async function (assert) {
|
||||
await visit("/wizard");
|
||||
assert.ok(exists(".wizard-container"));
|
||||
assert.notOk(
|
||||
exists(".d-header-wrap"),
|
||||
"header is not rendered on wizard pages"
|
||||
);
|
||||
assert.dom(".wizard-container").exists();
|
||||
assert
|
||||
.dom(".d-header-wrap")
|
||||
.doesNotExist("header is not rendered on wizard pages");
|
||||
assert.strictEqual(currentRouteName(), "wizard.step");
|
||||
});
|
||||
|
||||
test("Going back and forth in steps", async function (assert) {
|
||||
await visit("/wizard/steps/hello-world");
|
||||
assert.ok(exists(".wizard-container__step"));
|
||||
assert.ok(
|
||||
exists(".wizard-container__step.hello-world"),
|
||||
"it adds a class for the step id"
|
||||
);
|
||||
assert.ok(
|
||||
!exists(".wizard-container__button.finish"),
|
||||
"cannot finish on first step"
|
||||
);
|
||||
assert.ok(exists(".wizard-container__step-progress"));
|
||||
assert.ok(exists(".wizard-container__step-title"));
|
||||
assert.ok(exists(".wizard-container__step-description"));
|
||||
assert.ok(
|
||||
!exists(".invalid #full_name"),
|
||||
"don't show it as invalid until the user does something"
|
||||
);
|
||||
assert.ok(!exists(".wizard-container__button.btn-back"));
|
||||
assert.ok(!exists(".wizard-container__field .error"));
|
||||
assert.dom(".wizard-container__step").exists();
|
||||
assert
|
||||
.dom(".wizard-container__step.hello-world")
|
||||
.exists("it adds a class for the step id");
|
||||
assert.dom(".wizard-container__step-title").exists();
|
||||
assert.dom(".wizard-container__step-description").exists();
|
||||
assert
|
||||
.dom(".invalid #full_name")
|
||||
.doesNotExist("don't show it as invalid until the user does something");
|
||||
assert.dom(".wizard-container__field .error").doesNotExist();
|
||||
|
||||
// First step: only next button
|
||||
assert.dom(".wizard-canvas").doesNotExist("First step: no confetti");
|
||||
assert
|
||||
.dom(".wizard-container__button.back")
|
||||
.doesNotExist("First step: no back button");
|
||||
assert
|
||||
.dom(".wizard-container__button.next")
|
||||
.exists("First step: next button");
|
||||
assert
|
||||
.dom(".wizard-container__button.jump-in")
|
||||
.doesNotExist("First step: no jump-in button");
|
||||
assert
|
||||
.dom(".wizard-container__button.configure-more")
|
||||
.doesNotExist("First step: no configure-more button");
|
||||
assert
|
||||
.dom(".wizard-container__button.finish")
|
||||
.doesNotExist("First step: no finish button");
|
||||
|
||||
// invalid data
|
||||
await click(".wizard-container__button.next");
|
||||
assert.ok(exists(".invalid #full_name"));
|
||||
assert.dom(".invalid #full_name").exists();
|
||||
|
||||
// server validation fail
|
||||
await fillIn("input#full_name", "Server Fail");
|
||||
await click(".wizard-container__button.next");
|
||||
assert.ok(exists(".invalid #full_name"));
|
||||
assert.ok(exists(".wizard-container__field .error"));
|
||||
assert.dom(".invalid #full_name").exists();
|
||||
assert.dom(".wizard-container__field .error").exists();
|
||||
|
||||
// server validation ok
|
||||
await fillIn("input#full_name", "Evil Trout");
|
||||
await click(".wizard-container__button.next");
|
||||
assert.ok(!exists(".wizard-container__field .error"));
|
||||
assert.ok(!exists(".wizard-container__step-description"));
|
||||
assert.ok(
|
||||
exists(".wizard-container__button.finish"),
|
||||
"shows finish on an intermediate step"
|
||||
);
|
||||
assert
|
||||
.dom(".wizard-container__step.hello-again")
|
||||
.exists("step: hello-again");
|
||||
assert.dom(".wizard-container__field .error").doesNotExist();
|
||||
assert.dom(".wizard-container__step-description").doesNotExist();
|
||||
|
||||
// Pre-ready: back and next buttons
|
||||
assert.dom(".wizard-canvas").doesNotExist("Pre-ready step: no confetti");
|
||||
assert
|
||||
.dom(".wizard-container__button.back")
|
||||
.exists("Pre-ready step: back button");
|
||||
assert
|
||||
.dom(".wizard-container__button.next")
|
||||
.exists("Pre-ready step: next button");
|
||||
assert
|
||||
.dom(".wizard-container__button.jump-in")
|
||||
.doesNotExist("Pre-ready step: no jump-in button");
|
||||
assert
|
||||
.dom(".wizard-container__button.configure-more")
|
||||
.doesNotExist("Pre-ready step: no configure-more button");
|
||||
assert
|
||||
.dom(".wizard-container__button.finish")
|
||||
.doesNotExist("Pre-ready step: no finish button");
|
||||
|
||||
// ok to skip an optional field
|
||||
await click(".wizard-container__button.next");
|
||||
assert.dom(".wizard-container__step.ready").exists("step: ready");
|
||||
|
||||
// Ready: back, configure-more and jump-in buttons
|
||||
assert.dom(".wizard-canvas").exists("Ready step: confetti");
|
||||
assert
|
||||
.dom(".wizard-container__button.back")
|
||||
.exists("Ready step: back button");
|
||||
assert
|
||||
.dom(".wizard-container__button.next")
|
||||
.doesNotExist("Ready step: no next button");
|
||||
assert
|
||||
.dom(".wizard-container__button.jump-in")
|
||||
.exists("Ready step: jump-in button");
|
||||
assert
|
||||
.dom(".wizard-container__button.configure-more")
|
||||
.exists("Ready step: configure-more button");
|
||||
assert
|
||||
.dom(".wizard-container__button.finish")
|
||||
.doesNotExist("Ready step: no finish button");
|
||||
|
||||
// continue on to optional steps
|
||||
await click(".wizard-container__button.configure-more");
|
||||
assert.dom(".wizard-container__step.optional").exists("step: optional");
|
||||
|
||||
// Post-ready: back, next and finish buttons
|
||||
assert.dom(".wizard-canvas").doesNotExist("Post-ready step: no confetti");
|
||||
assert
|
||||
.dom(".wizard-container__button.back")
|
||||
.exists("Post-ready step: back button");
|
||||
assert
|
||||
.dom(".wizard-container__button.next")
|
||||
.exists("Post-ready step: next button");
|
||||
assert
|
||||
.dom(".wizard-container__button.jump-in")
|
||||
.doesNotExist("Post-ready step: no jump-in button");
|
||||
assert
|
||||
.dom(".wizard-container__button.configure-more")
|
||||
.doesNotExist("Post-ready step: no configure-more button");
|
||||
assert
|
||||
.dom(".wizard-container__button.finish")
|
||||
.exists("Post-ready step: finish button");
|
||||
|
||||
// finish early, does not save/validate
|
||||
await click(".wizard-container__button.finish");
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
|
@ -69,51 +140,107 @@ acceptance("Wizard", function (needs) {
|
|||
"it should transition to the homepage"
|
||||
);
|
||||
|
||||
await visit("/wizard/steps/styling");
|
||||
await visit("/wizard/steps/optional");
|
||||
assert.dom(".wizard-container__step.optional").exists("step: optional");
|
||||
|
||||
// Post-ready: back, next and finish buttons
|
||||
assert.dom(".wizard-canvas").doesNotExist("Post-ready step: no confetti");
|
||||
assert
|
||||
.dom(".wizard-container__button.back")
|
||||
.exists("Post-ready step: back button");
|
||||
assert
|
||||
.dom(".wizard-container__button.next")
|
||||
.exists("Post-ready step: next button");
|
||||
assert
|
||||
.dom(".wizard-container__button.jump-in")
|
||||
.doesNotExist("Post-ready step: no jump-in button");
|
||||
assert
|
||||
.dom(".wizard-container__button.configure-more")
|
||||
.doesNotExist("Post-ready step: no configure-more button");
|
||||
assert
|
||||
.dom(".wizard-container__button.finish")
|
||||
.exists("Post-ready step: finish button");
|
||||
|
||||
await click(".wizard-container__button.primary.next");
|
||||
assert.ok(
|
||||
exists(".wizard-container__text-input#company_name"),
|
||||
"went to the next step"
|
||||
);
|
||||
assert.ok(
|
||||
exists(".wizard-container__preview"),
|
||||
"renders the component field"
|
||||
);
|
||||
assert.ok(
|
||||
exists(".wizard-container__button.jump-in"),
|
||||
"last step shows a jump in button"
|
||||
);
|
||||
assert.ok(
|
||||
exists(".wizard-container__button.btn-back"),
|
||||
"shows the back button"
|
||||
);
|
||||
assert.ok(!exists(".wizard-container__step-title"));
|
||||
assert.ok(
|
||||
!exists(".wizard-container__button.next"),
|
||||
"does not show next button"
|
||||
);
|
||||
assert.ok(
|
||||
!exists(".wizard-container__button.finish"),
|
||||
"cannot finish on last step"
|
||||
);
|
||||
assert.dom(".wizard-container__step.corporate").exists("step: corporate");
|
||||
|
||||
// Final step: back and jump-in buttons
|
||||
assert.dom(".wizard-canvas").doesNotExist("Finish step: no confetti");
|
||||
assert
|
||||
.dom(".wizard-container__button.back")
|
||||
.exists("Finish step: back button");
|
||||
assert
|
||||
.dom(".wizard-container__button.next")
|
||||
.doesNotExist("Finish step: no next button");
|
||||
assert
|
||||
.dom(".wizard-container__button.jump-in")
|
||||
.exists("Finish step: jump-in button");
|
||||
assert
|
||||
.dom(".wizard-container__button.configure-more")
|
||||
.doesNotExist("Finish step: no configure-more button");
|
||||
assert
|
||||
.dom(".wizard-container__button.finish")
|
||||
.doesNotExist("Finish step: no finish button");
|
||||
|
||||
assert
|
||||
.dom(".wizard-container__text-input#company_name")
|
||||
.exists("went to the next step");
|
||||
assert
|
||||
.dom(".wizard-container__preview")
|
||||
.exists("renders the component field");
|
||||
assert.dom(".wizard-container__step-title").doesNotExist();
|
||||
|
||||
await click(".wizard-container__button.back");
|
||||
assert.dom(".wizard-container__step.optional").exists("step: optional");
|
||||
|
||||
// Post-ready: back, next and finish buttons
|
||||
assert.dom(".wizard-canvas").doesNotExist("Post-ready step: no confetti");
|
||||
assert
|
||||
.dom(".wizard-container__button.back")
|
||||
.exists("Post-ready step: back button");
|
||||
assert
|
||||
.dom(".wizard-container__button.next")
|
||||
.exists("Post-ready step: next button");
|
||||
assert
|
||||
.dom(".wizard-container__button.jump-in")
|
||||
.doesNotExist("Post-ready step: no jump-in button");
|
||||
assert
|
||||
.dom(".wizard-container__button.configure-more")
|
||||
.doesNotExist("Post-ready step: no configure-more button");
|
||||
assert
|
||||
.dom(".wizard-container__button.finish")
|
||||
.exists("Post-ready step: finish button");
|
||||
|
||||
assert.dom(".wizard-container__step-title").exists("shows the step title");
|
||||
|
||||
await click(".wizard-container__button.btn-back");
|
||||
assert.ok(exists(".wizard-container__step-title"), "shows the step title");
|
||||
assert.ok(
|
||||
exists(".wizard-container__button.next"),
|
||||
"shows the next button"
|
||||
);
|
||||
await click(".wizard-container__button.next");
|
||||
assert.dom(".wizard-container__step.corporate").exists("step: optional");
|
||||
|
||||
// Final step: back and jump-in buttons
|
||||
assert.dom(".wizard-canvas").doesNotExist("Finish step: no confetti");
|
||||
assert
|
||||
.dom(".wizard-container__button.back")
|
||||
.exists("Finish step: back button");
|
||||
assert
|
||||
.dom(".wizard-container__button.next")
|
||||
.doesNotExist("Finish step: no next button");
|
||||
assert
|
||||
.dom(".wizard-container__button.jump-in")
|
||||
.exists("Finish step: jump-in button");
|
||||
assert
|
||||
.dom(".wizard-container__button.configure-more")
|
||||
.doesNotExist("Finish step: no configure-more button");
|
||||
assert
|
||||
.dom(".wizard-container__button.finish")
|
||||
.doesNotExist("Finish step: no finish button");
|
||||
|
||||
// server validation fail
|
||||
await fillIn("input#company_name", "Server Fail");
|
||||
await click(".wizard-container__button.jump-in");
|
||||
assert.ok(
|
||||
exists(".invalid #company_name"),
|
||||
"highlights the field with error"
|
||||
);
|
||||
assert.ok(exists(".wizard-container__field .error"), "shows the error");
|
||||
assert
|
||||
.dom(".invalid #company_name")
|
||||
.exists("highlights the field with error");
|
||||
assert.dom(".wizard-container__field .error").exists("shows the error");
|
||||
|
||||
await fillIn("input#company_name", "Foo Bar");
|
||||
await click(".wizard-container__button.jump-in");
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{{component
|
||||
this.componentName
|
||||
this.component
|
||||
class="wizard-container__dropdown"
|
||||
value=this.field.value
|
||||
content=this.field.choices
|
|
@ -1,6 +1,8 @@
|
|||
import Component from "@ember/component";
|
||||
import { action, set } from "@ember/object";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import ColorPalettes from "select-kit/components/color-palettes";
|
||||
import ComboBox from "select-kit/components/combo-box";
|
||||
|
||||
export default Component.extend({
|
||||
init() {
|
||||
|
@ -16,8 +18,8 @@ export default Component.extend({
|
|||
},
|
||||
|
||||
@discourseComputed("field.id")
|
||||
componentName(id) {
|
||||
return id === "color_scheme" ? "color-palettes" : "combo-box";
|
||||
component(id) {
|
||||
return id === "color_scheme" ? ColorPalettes : ComboBox;
|
||||
},
|
||||
|
||||
keyPress(e) {
|
|
@ -0,0 +1,9 @@
|
|||
import Generic from "./generic";
|
||||
import Logo from "./logo";
|
||||
import LogoSmall from "./logo-small";
|
||||
|
||||
export default {
|
||||
generic: Generic,
|
||||
logo: Logo,
|
||||
"logo-small": LogoSmall,
|
||||
};
|
|
@ -1,8 +1,8 @@
|
|||
import { action } from "@ember/object";
|
||||
import { drawHeader, LOREM } from "wizard/lib/preview";
|
||||
import WizardPreviewBaseComponent from "./wizard-preview-base";
|
||||
import { drawHeader, LOREM } from "../../../lib/preview";
|
||||
import PreviewBaseComponent from "../styling-preview/-preview-base";
|
||||
|
||||
export default WizardPreviewBaseComponent.extend({
|
||||
export default PreviewBaseComponent.extend({
|
||||
width: 375,
|
||||
height: 100,
|
||||
image: null,
|
|
@ -1,8 +1,8 @@
|
|||
import { action } from "@ember/object";
|
||||
import { drawHeader } from "wizard/lib/preview";
|
||||
import WizardPreviewBaseComponent from "./wizard-preview-base";
|
||||
import { drawHeader } from "../../../lib/preview";
|
||||
import PreviewBaseComponent from "../styling-preview/-preview-base";
|
||||
|
||||
export default WizardPreviewBaseComponent.extend({
|
||||
export default PreviewBaseComponent.extend({
|
||||
width: 400,
|
||||
height: 100,
|
||||
image: null,
|
|
@ -5,10 +5,10 @@ import { dasherize } from "@ember/string";
|
|||
import Uppy from "@uppy/core";
|
||||
import DropTarget from "@uppy/drop-target";
|
||||
import XHRUpload from "@uppy/xhr-upload";
|
||||
import { getOwnerWithFallback } from "discourse-common/lib/get-owner";
|
||||
import getUrl from "discourse-common/lib/get-url";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import I18n from "discourse-i18n";
|
||||
import imagePreviews from "./image-previews";
|
||||
|
||||
export default Component.extend({
|
||||
classNames: ["wizard-container__image-upload"],
|
||||
|
@ -17,11 +17,7 @@ export default Component.extend({
|
|||
|
||||
@discourseComputed("field.id")
|
||||
previewComponent(id) {
|
||||
const componentName = `image-preview-${dasherize(id)}`;
|
||||
const exists = getOwnerWithFallback(this).lookup(
|
||||
`component:${componentName}`
|
||||
);
|
||||
return exists ? componentName : "wizard-image-preview";
|
||||
return imagePreviews[dasherize(id)] ?? imagePreviews.generic;
|
||||
},
|
||||
|
||||
didInsertElement() {
|
|
@ -0,0 +1,15 @@
|
|||
import Checkbox from "./checkbox";
|
||||
import Checkboxes from "./checkboxes";
|
||||
import Dropdown from "./dropdown";
|
||||
import Image from "./image";
|
||||
import StylingPreview from "./styling-preview";
|
||||
import Text from "./text";
|
||||
|
||||
export default {
|
||||
checkbox: Checkbox,
|
||||
checkboxes: Checkboxes,
|
||||
"styling-preview": StylingPreview,
|
||||
dropdown: Dropdown,
|
||||
image: Image,
|
||||
text: Text,
|
||||
};
|
|
@ -1,7 +1,7 @@
|
|||
import { darkLightDiff, LOREM } from "wizard/lib/preview";
|
||||
import WizardPreviewBaseComponent from "./wizard-preview-base";
|
||||
import { darkLightDiff, LOREM } from "../../../lib/preview";
|
||||
import PreviewBaseComponent from "./-preview-base";
|
||||
|
||||
export default WizardPreviewBaseComponent.extend({
|
||||
export default PreviewBaseComponent.extend({
|
||||
width: 628,
|
||||
height: 322,
|
||||
logo: null,
|
|
@ -6,7 +6,7 @@ import { htmlSafe } from "@ember/template";
|
|||
import { Promise } from "rsvp";
|
||||
import PreloadStore from "discourse/lib/preload-store";
|
||||
import getUrl from "discourse-common/lib/get-url";
|
||||
import { darkLightDiff, drawHeader } from "wizard/lib/preview";
|
||||
import { darkLightDiff, drawHeader } from "../../../lib/preview";
|
||||
|
||||
export const LOREM = `
|
||||
Lorem ipsum dolor sit amet,
|
|
@ -8,7 +8,7 @@
|
|||
</canvas>
|
||||
</div>
|
||||
<div class="wizard-container__preview homepage-preview">
|
||||
<HomepagePreview @wizard={{this.wizard}} @step={{this.step}} />
|
||||
<this.HomepagePreview @wizard={{this.wizard}} @step={{this.step}} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import { action } from "@ember/object";
|
||||
import { bind, observes } from "discourse-common/utils/decorators";
|
||||
import I18n from "discourse-i18n";
|
||||
import { chooseDarker, darkLightDiff } from "wizard/lib/preview";
|
||||
import WizardPreviewBaseComponent from "./wizard-preview-base";
|
||||
import { chooseDarker, darkLightDiff } from "../../../lib/preview";
|
||||
import HomepagePreview from "./-homepage-preview";
|
||||
import PreviewBaseComponent from "./-preview-base";
|
||||
|
||||
const LOREM = `
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing.
|
||||
|
@ -10,7 +11,7 @@ Nullam eget sem non elit tincidunt rhoncus. Fusce
|
|||
velit nisl, porttitor sed nisl ac, consectetur interdum
|
||||
metus. Fusce in consequat augue, vel facilisis felis.`;
|
||||
|
||||
export default WizardPreviewBaseComponent.extend({
|
||||
export default PreviewBaseComponent.extend({
|
||||
width: 628,
|
||||
height: 322,
|
||||
logo: null,
|
||||
|
@ -19,6 +20,7 @@ export default WizardPreviewBaseComponent.extend({
|
|||
draggingActive: false,
|
||||
startX: 0,
|
||||
scrollLeft: 0,
|
||||
HomepagePreview,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
|
@ -1,11 +1,12 @@
|
|||
import Component from "@ember/component";
|
||||
import Component from "@glimmer/component";
|
||||
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
||||
import willDestroy from "@ember/render-modifiers/modifiers/will-destroy";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
|
||||
const MAX_PARTICLES = 150;
|
||||
|
||||
const SIZE = 144;
|
||||
|
||||
let width, height;
|
||||
|
||||
const COLORS = [
|
||||
"--tertiary",
|
||||
"--quaternary",
|
||||
|
@ -14,13 +15,12 @@ const COLORS = [
|
|||
];
|
||||
|
||||
class Particle {
|
||||
constructor() {
|
||||
this.reset();
|
||||
this.y = Math.random() * (height + SIZE) - SIZE;
|
||||
constructor(width, height) {
|
||||
this.reset(width, height);
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.y = -SIZE;
|
||||
reset(width, height) {
|
||||
this.y = Math.random() * (height + SIZE) - SIZE;
|
||||
this.origX = Math.random() * (width + SIZE);
|
||||
this.speed = 0.5 + Math.random();
|
||||
this.ang = Math.random() * 2 * Math.PI;
|
||||
|
@ -31,11 +31,13 @@ class Particle {
|
|||
this.flipped = Math.random() > 0.5 ? 1 : -1;
|
||||
}
|
||||
|
||||
move() {
|
||||
move(width, height) {
|
||||
this.y += this.speed;
|
||||
|
||||
if (this.y > height + SIZE) {
|
||||
this.reset();
|
||||
this.reset(width, height);
|
||||
// start at the top
|
||||
this.y = -SIZE;
|
||||
}
|
||||
|
||||
this.ang += this.speed / 30.0;
|
||||
|
@ -47,66 +49,66 @@ class Particle {
|
|||
}
|
||||
}
|
||||
|
||||
export default Component.extend({
|
||||
classNames: ["wizard-canvas"],
|
||||
tagName: "canvas",
|
||||
ctx: null,
|
||||
ready: false,
|
||||
particles: null,
|
||||
export default class WizardCanvasComponent extends Component {
|
||||
canvas = null;
|
||||
particles = null;
|
||||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
get ready() {
|
||||
return this.canvas !== null;
|
||||
}
|
||||
|
||||
const canvas = this.element;
|
||||
this.ctx = canvas.getContext("2d");
|
||||
get ctx() {
|
||||
return this.canvas.getContext("2d");
|
||||
}
|
||||
|
||||
@bind
|
||||
setup(canvas) {
|
||||
this.canvas = canvas;
|
||||
this.resized();
|
||||
|
||||
let { width, height } = canvas;
|
||||
|
||||
this.particles = [];
|
||||
for (let i = 0; i < MAX_PARTICLES; i++) {
|
||||
this.particles.push(new Particle());
|
||||
this.particles.push(new Particle(width, height));
|
||||
}
|
||||
|
||||
this.ready = true;
|
||||
this.paint();
|
||||
this.paint(width, height);
|
||||
|
||||
window.addEventListener("resize", this.resized);
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
this._super(...arguments);
|
||||
}
|
||||
|
||||
@bind
|
||||
teardown() {
|
||||
this.canvas = null;
|
||||
window.removeEventListener("resize", this.resized);
|
||||
},
|
||||
}
|
||||
|
||||
@bind
|
||||
resized() {
|
||||
width = window.innerWidth;
|
||||
height = window.innerHeight;
|
||||
|
||||
const canvas = this.element;
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
},
|
||||
this.canvas.width = window.innerWidth;
|
||||
this.canvas.height = window.innerHeight;
|
||||
}
|
||||
|
||||
@bind
|
||||
paint() {
|
||||
if (this.isDestroying || this.isDestroyed || !this.ready) {
|
||||
if (!this.ready) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { ctx } = this;
|
||||
let { ctx } = this;
|
||||
let { width, height } = this.canvas;
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
this.particles.forEach((particle) => {
|
||||
particle.move();
|
||||
this.drawParticle(particle);
|
||||
});
|
||||
for (let particle of this.particles) {
|
||||
particle.move(width, height);
|
||||
this.drawParticle(ctx, particle);
|
||||
}
|
||||
|
||||
window.requestAnimationFrame(() => this.paint());
|
||||
},
|
||||
|
||||
drawParticle(p) {
|
||||
const c = this.ctx;
|
||||
window.requestAnimationFrame(this.paint);
|
||||
}
|
||||
|
||||
drawParticle(c, p) {
|
||||
c.save();
|
||||
c.translate(p.x - SIZE, p.y - SIZE);
|
||||
c.scale(p.scale * p.flipped, p.scale);
|
||||
|
@ -174,5 +176,13 @@ export default Component.extend({
|
|||
c.fill();
|
||||
c.stroke();
|
||||
c.restore();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
<template>
|
||||
<canvas
|
||||
class="wizard-canvas"
|
||||
{{didInsert this.setup}}
|
||||
{{willDestroy this.teardown}}
|
||||
/>
|
||||
</template>
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { assert } from "@ember/debug";
|
||||
import { dasherize } from "@ember/string";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import fields from "./fields";
|
||||
|
||||
export default class WizardFieldComponent extends Component {
|
||||
get field() {
|
||||
return this.args.field;
|
||||
}
|
||||
|
||||
get classes() {
|
||||
let classes = ["wizard-container__field"];
|
||||
|
||||
let { type, id, invalid, disabled } = this.field;
|
||||
|
||||
classes.push(`${dasherize(type)}-field`);
|
||||
classes.push(`${dasherize(type)}-${dasherize(id)}`);
|
||||
|
||||
if (invalid) {
|
||||
classes.push("invalid");
|
||||
}
|
||||
|
||||
if (disabled) {
|
||||
classes.push("disabled");
|
||||
}
|
||||
|
||||
return classes.join(" ");
|
||||
}
|
||||
|
||||
get fieldClass() {
|
||||
return `field-${dasherize(this.field.id)} wizard-focusable`;
|
||||
}
|
||||
|
||||
get component() {
|
||||
let { type } = this.field;
|
||||
assert(`"${type}" is not a valid wizard field type`, type in fields);
|
||||
return fields[type];
|
||||
}
|
||||
|
||||
<template>
|
||||
<div class={{this.classes}}>
|
||||
{{#if @field.label}}
|
||||
<label for={{@field.id}}>
|
||||
<span class="wizard-container__label">
|
||||
{{@field.label}}
|
||||
</span>
|
||||
|
||||
{{#if @field.required}}
|
||||
<span class="wizard-container__label required">*</span>
|
||||
{{/if}}
|
||||
|
||||
{{#if @field.description}}
|
||||
<div class="wizard-container__description">
|
||||
{{htmlSafe @field.description}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</label>
|
||||
{{/if}}
|
||||
|
||||
<div class="wizard-container__input">
|
||||
<this.component
|
||||
@wizard={{@wizard}}
|
||||
@step={{@step}}
|
||||
@field={{@field}}
|
||||
@fieldClass={{this.fieldClass}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{{#if @field.errorDescription}}
|
||||
<div class="wizard-container__description error">
|
||||
{{htmlSafe this.field.errorDescription}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if @field.extraDescription}}
|
||||
<div class="wizard-container__description extra">
|
||||
{{htmlSafe this.field.extraDescription}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
{{#if this.field.label}}
|
||||
<label for={{this.field.id}}>
|
||||
<span class="wizard-container__label">
|
||||
{{this.field.label}}
|
||||
</span>
|
||||
|
||||
{{#if this.field.required}}
|
||||
<span class="wizard-container__label required">*</span>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.field.description}}
|
||||
<div class="wizard-container__description">{{html-safe
|
||||
this.field.description
|
||||
}}</div>
|
||||
{{/if}}
|
||||
</label>
|
||||
{{/if}}
|
||||
|
||||
<div class="wizard-container__input">
|
||||
{{component
|
||||
this.inputComponentName
|
||||
field=this.field
|
||||
step=this.step
|
||||
fieldClass=this.fieldClass
|
||||
wizard=this.wizard
|
||||
}}
|
||||
</div>
|
||||
|
||||
{{#if this.field.errorDescription}}
|
||||
<div class="wizard-container__description error">{{html-safe
|
||||
this.field.errorDescription
|
||||
}}</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.field.extraDescription}}
|
||||
<div class="wizard-container__description extra">{{html-safe
|
||||
this.field.extraDescription
|
||||
}}</div>
|
||||
{{/if}}
|
|
@ -1,24 +0,0 @@
|
|||
import Component from "@ember/component";
|
||||
import { dasherize } from "@ember/string";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
|
||||
export default Component.extend({
|
||||
classNameBindings: [
|
||||
":wizard-container__field",
|
||||
"typeClasses",
|
||||
"field.invalid",
|
||||
"field.disabled",
|
||||
],
|
||||
|
||||
@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`,
|
||||
|
||||
@discourseComputed("field.type", "field.id")
|
||||
inputComponentName(type, id) {
|
||||
return type === "component" ? dasherize(id) : `wizard-field-${type}`;
|
||||
},
|
||||
});
|
|
@ -1,5 +0,0 @@
|
|||
import Component from "@ember/component";
|
||||
|
||||
export default Component.extend({
|
||||
classNameBindings: [":wizard-container__step-form"],
|
||||
});
|
301
app/assets/javascripts/wizard/addon/components/wizard-step.gjs
Normal file
301
app/assets/javascripts/wizard/addon/components/wizard-step.gjs
Normal file
|
@ -0,0 +1,301 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { on } from "@ember/modifier";
|
||||
import { action } from "@ember/object";
|
||||
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
||||
import didUpdate from "@ember/render-modifiers/modifiers/did-update";
|
||||
import { schedule } from "@ember/runloop";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import emoji from "discourse/helpers/emoji";
|
||||
import I18n from "discourse-i18n";
|
||||
import WizardField from "./wizard-field";
|
||||
|
||||
const i18n = (...args) => I18n.t(...args);
|
||||
|
||||
export default class WizardStepComponent extends Component {
|
||||
@tracked saving = false;
|
||||
|
||||
get wizard() {
|
||||
return this.args.wizard;
|
||||
}
|
||||
|
||||
get step() {
|
||||
return this.args.step;
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.step.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Step Back Button? Primary Action Secondary Action
|
||||
* ------------------------------------------------------------------
|
||||
* First No Next N/A
|
||||
* ------------------------------------------------------------------
|
||||
* ... Yes Next N/A
|
||||
* ------------------------------------------------------------------
|
||||
* Ready Yes Jump In Configure More
|
||||
* ------------------------------------------------------------------
|
||||
* ... Yes Next Exit Setup
|
||||
* ------------------------------------------------------------------
|
||||
* Last Yes Jump In N/A
|
||||
* ------------------------------------------------------------------
|
||||
*
|
||||
* Back Button: without saving, go back to the last page
|
||||
* Next Button: save, and if successful, go to the next page
|
||||
* Configure More: re-skinned next button
|
||||
* Exit Setup: without saving, go to the home page ("finish")
|
||||
* Jump In: on the "ready" page, it exits the setup ("finish"), on the
|
||||
* last page, it saves, and if successful, go to the home page
|
||||
*/
|
||||
get isFinalStep() {
|
||||
return this.step.displayIndex === this.wizard.steps.length;
|
||||
}
|
||||
|
||||
get showBackButton() {
|
||||
return this.step.index > 0;
|
||||
}
|
||||
|
||||
get showFinishButton() {
|
||||
const ready = this.wizard.findStep("ready");
|
||||
const isReady = ready && this.step.index > ready.index;
|
||||
return isReady && !this.isFinalStep;
|
||||
}
|
||||
|
||||
get showConfigureMore() {
|
||||
return this.id === "ready";
|
||||
}
|
||||
|
||||
get showJumpInButton() {
|
||||
return this.id === "ready" || this.isFinalStep;
|
||||
}
|
||||
|
||||
get includeSidebar() {
|
||||
return !!this.step.fields.find((f) => f.showInSidebar);
|
||||
}
|
||||
|
||||
@action
|
||||
stepChanged() {
|
||||
this.saving = false;
|
||||
this.autoFocus();
|
||||
}
|
||||
|
||||
@action
|
||||
onKeyUp(event) {
|
||||
if (event.key === "Enter") {
|
||||
if (this.showJumpInButton) {
|
||||
this.jumpIn();
|
||||
} else {
|
||||
this.nextStep();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
autoFocus() {
|
||||
schedule("afterRender", () => {
|
||||
const firstInvalidElement = document.querySelector(
|
||||
".wizard-container__input.invalid:nth-of-type(1) .wizard-focusable"
|
||||
);
|
||||
|
||||
if (firstInvalidElement) {
|
||||
return firstInvalidElement.focus();
|
||||
}
|
||||
|
||||
document.querySelector(".wizard-focusable:nth-of-type(1)")?.focus();
|
||||
});
|
||||
}
|
||||
|
||||
async advance() {
|
||||
try {
|
||||
this.saving = true;
|
||||
const response = await this.step.save();
|
||||
this.args.goNext(response);
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
finish(event) {
|
||||
event?.preventDefault();
|
||||
|
||||
if (this.saving) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.args.goHome();
|
||||
}
|
||||
|
||||
@action
|
||||
jumpIn(event) {
|
||||
event?.preventDefault();
|
||||
|
||||
if (this.saving) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.id === "ready") {
|
||||
this.finish();
|
||||
} else {
|
||||
this.nextStep();
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
backStep(event) {
|
||||
event?.preventDefault();
|
||||
|
||||
if (this.saving) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.args.goBack();
|
||||
}
|
||||
|
||||
@action
|
||||
nextStep(event) {
|
||||
event?.preventDefault();
|
||||
|
||||
if (this.saving) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.step.validate()) {
|
||||
this.advance();
|
||||
} else {
|
||||
this.autoFocus();
|
||||
}
|
||||
}
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="wizard-container__step {{@step.id}}"
|
||||
{{didInsert this.autoFocus}}
|
||||
{{didUpdate this.stepChanged @step.id}}
|
||||
>
|
||||
<div class="wizard-container__step-counter">
|
||||
<span class="wizard-container__step-text">
|
||||
{{i18n "wizard.step-text"}}
|
||||
</span>
|
||||
<span class="wizard-container__step-count">
|
||||
{{i18n
|
||||
"wizard.step"
|
||||
current=@step.displayIndex
|
||||
total=@wizard.totalSteps
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="wizard-container">
|
||||
<div class="wizard-container__step-contents">
|
||||
<div class="wizard-container__step-header">
|
||||
{{#if @step.emoji}}
|
||||
<div class="wizard-container__step-header--emoji">
|
||||
{{emoji @step.emoji}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if @step.title}}
|
||||
<h1 class="wizard-container__step-title">{{@step.title}}</h1>
|
||||
{{#if @step.description}}
|
||||
<p class="wizard-container__step-description">
|
||||
{{htmlSafe @step.description}}
|
||||
</p>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="wizard-container__step-container">
|
||||
{{#if @step.fields}}
|
||||
<div class="wizard-container__step-form">
|
||||
{{#if this.includeSidebar}}
|
||||
<div class="wizard-container__sidebar">
|
||||
{{#each @step.fields as |field|}}
|
||||
{{#if field.showInSidebar}}
|
||||
<WizardField
|
||||
@field={{field}}
|
||||
@step={{@step}}
|
||||
@wizard={{@wizard}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="wizard-container__fields">
|
||||
{{#each @step.fields as |field|}}
|
||||
{{#unless field.showInSidebar}}
|
||||
<WizardField
|
||||
@field={{field}}
|
||||
@step={{@step}}
|
||||
@wizard={{@wizard}}
|
||||
/>
|
||||
{{/unless}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wizard-container__step-footer">
|
||||
<div class="wizard-container__buttons-left">
|
||||
{{#if this.showBackButton}}
|
||||
<button
|
||||
{{on "click" this.backStep}}
|
||||
disabled={{this.saving}}
|
||||
type="button"
|
||||
class="wizard-container__button back"
|
||||
>
|
||||
{{i18n "wizard.back"}}
|
||||
</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="wizard-container__buttons-right">
|
||||
{{#if this.showFinishButton}}
|
||||
<button
|
||||
{{on "click" this.finish}}
|
||||
disabled={{this.saving}}
|
||||
type="button"
|
||||
class="wizard-container__button finish"
|
||||
>
|
||||
{{i18n "wizard.finish"}}
|
||||
</button>
|
||||
{{else if this.showConfigureMore}}
|
||||
<button
|
||||
{{on "click" this.nextStep}}
|
||||
disabled={{this.saving}}
|
||||
type="button"
|
||||
class="wizard-container__button configure-more"
|
||||
>
|
||||
{{i18n "wizard.configure_more"}}
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showJumpInButton}}
|
||||
<button
|
||||
{{on "click" this.jumpIn}}
|
||||
disabled={{this.saving}}
|
||||
type="button"
|
||||
class="wizard-container__button primary jump-in"
|
||||
>
|
||||
{{i18n "wizard.jump_in"}}
|
||||
</button>
|
||||
{{else}}
|
||||
<button
|
||||
{{on "click" this.nextStep}}
|
||||
disabled={{this.saving}}
|
||||
type="button"
|
||||
class="wizard-container__button primary next"
|
||||
>
|
||||
{{i18n "wizard.next"}}
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
<div class="wizard-container__step-counter">
|
||||
<span class="wizard-container__step-text">{{bound-i18n
|
||||
"wizard.step-text"
|
||||
}}</span>
|
||||
<span class="wizard-container__step-count">{{bound-i18n
|
||||
"wizard.step"
|
||||
current=this.step.displayIndex
|
||||
total=this.wizard.totalSteps
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div class="wizard-container">
|
||||
<div class="wizard-container__step-contents">
|
||||
<div class="wizard-container__step-header">
|
||||
{{#if this.step.emoji}}
|
||||
<div class="wizard-container__step-header--emoji">
|
||||
{{emoji this.step.emoji}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if this.step.title}}
|
||||
<h1 class="wizard-container__step-title">{{this.step.title}}</h1>
|
||||
{{#if this.step.description}}
|
||||
<p class="wizard-container__step-description">{{html-safe
|
||||
this.step.description
|
||||
}}</p>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="wizard-container__step-container">
|
||||
{{#if this.step.fields}}
|
||||
<WizardStepForm @step={{this.step}}>
|
||||
{{#if this.includeSidebar}}
|
||||
<div class="wizard-container__sidebar">
|
||||
{{#each this.step.fields as |field|}}
|
||||
{{#if field.showInSidebar}}
|
||||
<WizardField
|
||||
@field={{field}}
|
||||
@step={{this.step}}
|
||||
@wizard={{this.wizard}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="wizard-container__fields">
|
||||
{{#each this.step.fields as |field|}}
|
||||
{{#unless field.showInSidebar}}
|
||||
<WizardField
|
||||
@field={{field}}
|
||||
@step={{this.step}}
|
||||
@wizard={{this.wizard}}
|
||||
/>
|
||||
{{/unless}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</WizardStepForm>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wizard-container__step-footer">
|
||||
<div class="wizard-container__buttons">
|
||||
{{#if this.showBackButton}}
|
||||
<button
|
||||
{{on "click" this.backStep}}
|
||||
disabled={{this.saving}}
|
||||
type="button"
|
||||
class="wizard-container__button btn-back"
|
||||
>
|
||||
{{i18n "wizard.back"}}
|
||||
</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="wizard-container__step-progress">
|
||||
{{#if this.showFinishButton}}
|
||||
<button
|
||||
{{on "click" this.exitEarly}}
|
||||
disabled={{this.saving}}
|
||||
type="button"
|
||||
class="wizard-container__button jump-in"
|
||||
>
|
||||
{{i18n "wizard.jump_in"}}
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showConfigureMore}}
|
||||
<button
|
||||
{{on "click" this.nextStep}}
|
||||
disabled={{this.saving}}
|
||||
type="button"
|
||||
class="wizard-container__button primary {{this.nextButtonClass}}"
|
||||
>
|
||||
{{i18n this.nextButtonLabel}}
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showJumpInButton}}
|
||||
<button
|
||||
{{on "click" this.quit}}
|
||||
disabled={{this.saving}}
|
||||
type="button"
|
||||
class="wizard-container__button {{this.jumpInButtonClass}}"
|
||||
>
|
||||
{{i18n this.jumpInButtonLabel}}
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showNextButton}}
|
||||
<button
|
||||
{{on "click" this.nextStep}}
|
||||
disabled={{this.saving}}
|
||||
type="button"
|
||||
class="wizard-container__button primary {{this.nextButtonClass}}"
|
||||
>
|
||||
{{i18n this.nextButtonLabel}}
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
|
@ -1,162 +0,0 @@
|
|||
import Component from "@ember/component";
|
||||
import { action } from "@ember/object";
|
||||
import { schedule } from "@ember/runloop";
|
||||
import { inject as service } from "@ember/service";
|
||||
import $ from "jquery";
|
||||
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
||||
|
||||
export default Component.extend({
|
||||
router: service(),
|
||||
classNameBindings: [":wizard-container__step", "stepClass"],
|
||||
saving: null,
|
||||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
this.autoFocus();
|
||||
},
|
||||
|
||||
@discourseComputed("step.index")
|
||||
showBackButton(index) {
|
||||
return index > 0;
|
||||
},
|
||||
|
||||
@discourseComputed("step.displayIndex", "wizard.totalSteps")
|
||||
showNextButton(current, total) {
|
||||
if (this.showConfigureMore === true) {
|
||||
return false;
|
||||
}
|
||||
return current < total;
|
||||
},
|
||||
|
||||
@discourseComputed("step.id")
|
||||
nextButtonLabel(step) {
|
||||
return `wizard.${step === "ready" ? "configure_more" : "next"}`;
|
||||
},
|
||||
|
||||
@discourseComputed("step.id")
|
||||
nextButtonClass(step) {
|
||||
return step === "ready" ? "configure-more" : "next";
|
||||
},
|
||||
|
||||
@discourseComputed("step.id")
|
||||
showConfigureMore(step) {
|
||||
return step === "ready";
|
||||
},
|
||||
|
||||
@discourseComputed("step.id")
|
||||
showJumpInButton(step) {
|
||||
return ["ready", "styling", "branding"].includes(step);
|
||||
},
|
||||
|
||||
@discourseComputed("step.id")
|
||||
jumpInButtonLabel(step) {
|
||||
return `wizard.${step === "ready" ? "jump_in" : "finish"}`;
|
||||
},
|
||||
|
||||
@discourseComputed("step.id")
|
||||
jumpInButtonClass(step) {
|
||||
return step === "ready" ? "jump-in" : "finish";
|
||||
},
|
||||
|
||||
@discourseComputed("step.id")
|
||||
showFinishButton(step) {
|
||||
return step === "corporate";
|
||||
},
|
||||
|
||||
@discourseComputed("step.id")
|
||||
stepClass(step) {
|
||||
return step;
|
||||
},
|
||||
|
||||
@observes("step")
|
||||
_stepChanged() {
|
||||
this.set("saving", false);
|
||||
this.autoFocus();
|
||||
},
|
||||
|
||||
keyPress(event) {
|
||||
if (event.key === "Enter") {
|
||||
if (this.showJumpInButton) {
|
||||
this.send("quit");
|
||||
} else {
|
||||
this.send("nextStep");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("step.fields")
|
||||
includeSidebar(fields) {
|
||||
return !!fields.findBy("showInSidebar");
|
||||
},
|
||||
|
||||
autoFocus() {
|
||||
schedule("afterRender", () => {
|
||||
const $invalid = $(
|
||||
".wizard-container__input.invalid:nth-of-type(1) .wizard-focusable"
|
||||
);
|
||||
|
||||
if ($invalid.length) {
|
||||
return $invalid.focus();
|
||||
}
|
||||
|
||||
$(".wizard-focusable:nth-of-type(1)").focus();
|
||||
});
|
||||
},
|
||||
|
||||
advance() {
|
||||
this.set("saving", true);
|
||||
this.step
|
||||
.save()
|
||||
.then((response) => this.goNext(response))
|
||||
.finally(() => this.set("saving", false));
|
||||
},
|
||||
|
||||
@action
|
||||
quit(event) {
|
||||
event?.preventDefault();
|
||||
this.router.transitionTo("discovery.latest");
|
||||
},
|
||||
|
||||
@action
|
||||
exitEarly(event) {
|
||||
event?.preventDefault();
|
||||
const step = this.step;
|
||||
|
||||
if (step.validate()) {
|
||||
this.set("saving", true);
|
||||
|
||||
step
|
||||
.save()
|
||||
.then((response) => this.goNext(response))
|
||||
.finally(() => this.set("saving", false));
|
||||
} else {
|
||||
this.autoFocus();
|
||||
}
|
||||
},
|
||||
|
||||
@action
|
||||
backStep(event) {
|
||||
event?.preventDefault();
|
||||
|
||||
if (this.saving) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.goBack();
|
||||
},
|
||||
|
||||
@action
|
||||
nextStep(event) {
|
||||
event?.preventDefault();
|
||||
|
||||
if (this.saving) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.step.validate()) {
|
||||
this.advance();
|
||||
} else {
|
||||
this.autoFocus();
|
||||
}
|
||||
},
|
||||
});
|
|
@ -20,6 +20,7 @@ export default RouteTemplate(
|
|||
@wizard={{@model.wizard}}
|
||||
@goNext={{this.goNext}}
|
||||
@goBack={{this.goBack}}
|
||||
@goHome={{this.goHome}}
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
@ -48,5 +49,10 @@ export default RouteTemplate(
|
|||
goBack() {
|
||||
this.router.transitionTo("wizard.step", this.step.previous);
|
||||
}
|
||||
|
||||
@action
|
||||
goHome() {
|
||||
this.router.transitionTo("discovery.latest");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
@ -20,24 +20,47 @@ export default function (helpers) {
|
|||
description: "Your name",
|
||||
},
|
||||
],
|
||||
next: "styling",
|
||||
next: "hello-again",
|
||||
},
|
||||
{
|
||||
id: "styling",
|
||||
title: "Second step",
|
||||
id: "hello-again",
|
||||
title: "hello again",
|
||||
index: 1,
|
||||
fields: [{ id: "some_title", type: "text" }],
|
||||
fields: [
|
||||
{
|
||||
id: "nick_name",
|
||||
type: "text",
|
||||
required: false,
|
||||
description: "Your nick name",
|
||||
},
|
||||
],
|
||||
previous: "hello-world",
|
||||
next: "ready",
|
||||
},
|
||||
{
|
||||
id: "ready",
|
||||
title: "your site is ready",
|
||||
index: 2,
|
||||
fields: [],
|
||||
previous: "hello-again",
|
||||
next: "optional",
|
||||
},
|
||||
{
|
||||
id: "optional",
|
||||
title: "Optional step",
|
||||
index: 3,
|
||||
fields: [{ id: "some_title", type: "text" }],
|
||||
previous: "ready",
|
||||
next: "corporate",
|
||||
},
|
||||
{
|
||||
id: "corporate",
|
||||
index: 2,
|
||||
index: 4,
|
||||
fields: [
|
||||
{ id: "company_name", type: "text", required: true },
|
||||
{ id: "styling_preview", type: "component" },
|
||||
{ id: "styling_preview", type: "styling-preview" },
|
||||
],
|
||||
previous: "styling",
|
||||
previous: "optional",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -47,11 +70,11 @@ export default function (helpers) {
|
|||
this.put("/wizard/steps/:id", (request) => {
|
||||
const body = parsePostData(request.requestBody);
|
||||
|
||||
if (body.fields.full_name === "Server Fail") {
|
||||
if (body.fields?.full_name === "Server Fail") {
|
||||
return response(422, {
|
||||
errors: [{ field: "full_name", description: "Invalid name" }],
|
||||
});
|
||||
} else if (body.fields.company_name === "Server Fail") {
|
||||
} else if (body.fields?.company_name === "Server Fail") {
|
||||
return response(422, {
|
||||
errors: [
|
||||
{ field: "company_name", description: "Invalid company name" },
|
||||
|
|
|
@ -290,17 +290,21 @@ body.wizard {
|
|||
}
|
||||
}
|
||||
|
||||
&__step.ready {
|
||||
.wizard-container__buttons {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
}
|
||||
|
||||
&__step.branding .wizard-container__description {
|
||||
font-size: var(--font-0);
|
||||
}
|
||||
|
||||
&__step-progress {
|
||||
&__buttons-left {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1em;
|
||||
align-items: center;
|
||||
@include breakpoint("mobile-extra-large") {
|
||||
order: 2;
|
||||
}
|
||||
}
|
||||
|
||||
&__buttons-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: bold;
|
||||
|
@ -309,15 +313,6 @@ body.wizard {
|
|||
margin-right: 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
.wizard-container__link {
|
||||
color: var(--primary-400);
|
||||
margin: 0 1em;
|
||||
&.inactive {
|
||||
// disabling instead of removing, to hold space
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__step-text {
|
||||
|
@ -381,8 +376,13 @@ body.wizard {
|
|||
}
|
||||
|
||||
&__button.primary {
|
||||
margin-left: 1em;
|
||||
background-color: var(--tertiary);
|
||||
color: var(--secondary);
|
||||
@include breakpoint("mobile-extra-large") {
|
||||
order: 1;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
&__button.primary:hover,
|
||||
&__button.primary:focus {
|
||||
|
@ -412,13 +412,6 @@ body.wizard {
|
|||
}
|
||||
|
||||
&__button.jump-in {
|
||||
background-color: var(--tertiary);
|
||||
color: var(--secondary);
|
||||
margin-left: 1em;
|
||||
@include breakpoint("mobile-extra-large") {
|
||||
order: 1;
|
||||
margin-left: 0;
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--primary-300);
|
||||
}
|
||||
|
@ -506,16 +499,6 @@ body.wizard {
|
|||
}
|
||||
}
|
||||
|
||||
&__buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1em;
|
||||
align-items: center;
|
||||
@include breakpoint("mobile-extra-large") {
|
||||
order: 2;
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-weight: bold;
|
||||
font-size: var(--font-up-1);
|
||||
|
|
|
@ -194,7 +194,7 @@ class Wizard
|
|||
style.add_choice("latest")
|
||||
CategoryPageStyle.values.each { |page| style.add_choice(page[:value]) }
|
||||
|
||||
step.add_field(id: "styling_preview", type: "component")
|
||||
step.add_field(id: "styling_preview", type: "styling-preview")
|
||||
|
||||
step.on_update do |updater|
|
||||
updater.update_setting(:base_font, updater.fields[:body_font])
|
||||
|
|
Loading…
Reference in New Issue
Block a user