mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06: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,
|
visit,
|
||||||
} from "@ember/test-helpers";
|
} from "@ember/test-helpers";
|
||||||
import { test } from "qunit";
|
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) {
|
acceptance("Wizard", function (needs) {
|
||||||
needs.user();
|
needs.user();
|
||||||
|
|
||||||
test("Wizard starts", async function (assert) {
|
test("Wizard starts", async function (assert) {
|
||||||
await visit("/wizard");
|
await visit("/wizard");
|
||||||
assert.ok(exists(".wizard-container"));
|
assert.dom(".wizard-container").exists();
|
||||||
assert.notOk(
|
assert
|
||||||
exists(".d-header-wrap"),
|
.dom(".d-header-wrap")
|
||||||
"header is not rendered on wizard pages"
|
.doesNotExist("header is not rendered on wizard pages");
|
||||||
);
|
|
||||||
assert.strictEqual(currentRouteName(), "wizard.step");
|
assert.strictEqual(currentRouteName(), "wizard.step");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Going back and forth in steps", async function (assert) {
|
test("Going back and forth in steps", async function (assert) {
|
||||||
await visit("/wizard/steps/hello-world");
|
await visit("/wizard/steps/hello-world");
|
||||||
assert.ok(exists(".wizard-container__step"));
|
assert.dom(".wizard-container__step").exists();
|
||||||
assert.ok(
|
assert
|
||||||
exists(".wizard-container__step.hello-world"),
|
.dom(".wizard-container__step.hello-world")
|
||||||
"it adds a class for the step id"
|
.exists("it adds a class for the step id");
|
||||||
);
|
assert.dom(".wizard-container__step-title").exists();
|
||||||
assert.ok(
|
assert.dom(".wizard-container__step-description").exists();
|
||||||
!exists(".wizard-container__button.finish"),
|
assert
|
||||||
"cannot finish on first step"
|
.dom(".invalid #full_name")
|
||||||
);
|
.doesNotExist("don't show it as invalid until the user does something");
|
||||||
assert.ok(exists(".wizard-container__step-progress"));
|
assert.dom(".wizard-container__field .error").doesNotExist();
|
||||||
assert.ok(exists(".wizard-container__step-title"));
|
|
||||||
assert.ok(exists(".wizard-container__step-description"));
|
// First step: only next button
|
||||||
assert.ok(
|
assert.dom(".wizard-canvas").doesNotExist("First step: no confetti");
|
||||||
!exists(".invalid #full_name"),
|
assert
|
||||||
"don't show it as invalid until the user does something"
|
.dom(".wizard-container__button.back")
|
||||||
);
|
.doesNotExist("First step: no back button");
|
||||||
assert.ok(!exists(".wizard-container__button.btn-back"));
|
assert
|
||||||
assert.ok(!exists(".wizard-container__field .error"));
|
.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
|
// invalid data
|
||||||
await click(".wizard-container__button.next");
|
await click(".wizard-container__button.next");
|
||||||
assert.ok(exists(".invalid #full_name"));
|
assert.dom(".invalid #full_name").exists();
|
||||||
|
|
||||||
// server validation fail
|
// server validation fail
|
||||||
await fillIn("input#full_name", "Server Fail");
|
await fillIn("input#full_name", "Server Fail");
|
||||||
await click(".wizard-container__button.next");
|
await click(".wizard-container__button.next");
|
||||||
assert.ok(exists(".invalid #full_name"));
|
assert.dom(".invalid #full_name").exists();
|
||||||
assert.ok(exists(".wizard-container__field .error"));
|
assert.dom(".wizard-container__field .error").exists();
|
||||||
|
|
||||||
// server validation ok
|
// server validation ok
|
||||||
await fillIn("input#full_name", "Evil Trout");
|
await fillIn("input#full_name", "Evil Trout");
|
||||||
await click(".wizard-container__button.next");
|
await click(".wizard-container__button.next");
|
||||||
assert.ok(!exists(".wizard-container__field .error"));
|
assert
|
||||||
assert.ok(!exists(".wizard-container__step-description"));
|
.dom(".wizard-container__step.hello-again")
|
||||||
assert.ok(
|
.exists("step: hello-again");
|
||||||
exists(".wizard-container__button.finish"),
|
assert.dom(".wizard-container__field .error").doesNotExist();
|
||||||
"shows finish on an intermediate step"
|
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");
|
await click(".wizard-container__button.finish");
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
currentURL(),
|
currentURL(),
|
||||||
@ -69,51 +140,107 @@ acceptance("Wizard", function (needs) {
|
|||||||
"it should transition to the homepage"
|
"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");
|
await click(".wizard-container__button.primary.next");
|
||||||
assert.ok(
|
assert.dom(".wizard-container__step.corporate").exists("step: corporate");
|
||||||
exists(".wizard-container__text-input#company_name"),
|
|
||||||
"went to the next step"
|
// Final step: back and jump-in buttons
|
||||||
);
|
assert.dom(".wizard-canvas").doesNotExist("Finish step: no confetti");
|
||||||
assert.ok(
|
assert
|
||||||
exists(".wizard-container__preview"),
|
.dom(".wizard-container__button.back")
|
||||||
"renders the component field"
|
.exists("Finish step: back button");
|
||||||
);
|
assert
|
||||||
assert.ok(
|
.dom(".wizard-container__button.next")
|
||||||
exists(".wizard-container__button.jump-in"),
|
.doesNotExist("Finish step: no next button");
|
||||||
"last step shows a jump in button"
|
assert
|
||||||
);
|
.dom(".wizard-container__button.jump-in")
|
||||||
assert.ok(
|
.exists("Finish step: jump-in button");
|
||||||
exists(".wizard-container__button.btn-back"),
|
assert
|
||||||
"shows the back button"
|
.dom(".wizard-container__button.configure-more")
|
||||||
);
|
.doesNotExist("Finish step: no configure-more button");
|
||||||
assert.ok(!exists(".wizard-container__step-title"));
|
assert
|
||||||
assert.ok(
|
.dom(".wizard-container__button.finish")
|
||||||
!exists(".wizard-container__button.next"),
|
.doesNotExist("Finish step: no finish button");
|
||||||
"does not show next button"
|
|
||||||
);
|
assert
|
||||||
assert.ok(
|
.dom(".wizard-container__text-input#company_name")
|
||||||
!exists(".wizard-container__button.finish"),
|
.exists("went to the next step");
|
||||||
"cannot finish on last 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");
|
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
|
// server validation fail
|
||||||
await fillIn("input#company_name", "Server Fail");
|
await fillIn("input#company_name", "Server Fail");
|
||||||
await click(".wizard-container__button.jump-in");
|
await click(".wizard-container__button.jump-in");
|
||||||
assert.ok(
|
assert
|
||||||
exists(".invalid #company_name"),
|
.dom(".invalid #company_name")
|
||||||
"highlights the field with error"
|
.exists("highlights the field with error");
|
||||||
);
|
assert.dom(".wizard-container__field .error").exists("shows the error");
|
||||||
assert.ok(exists(".wizard-container__field .error"), "shows the error");
|
|
||||||
|
|
||||||
await fillIn("input#company_name", "Foo Bar");
|
await fillIn("input#company_name", "Foo Bar");
|
||||||
await click(".wizard-container__button.jump-in");
|
await click(".wizard-container__button.jump-in");
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{{component
|
{{component
|
||||||
this.componentName
|
this.component
|
||||||
class="wizard-container__dropdown"
|
class="wizard-container__dropdown"
|
||||||
value=this.field.value
|
value=this.field.value
|
||||||
content=this.field.choices
|
content=this.field.choices
|
@ -1,6 +1,8 @@
|
|||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
import { action, set } from "@ember/object";
|
import { action, set } from "@ember/object";
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
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({
|
export default Component.extend({
|
||||||
init() {
|
init() {
|
||||||
@ -16,8 +18,8 @@ export default Component.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed("field.id")
|
@discourseComputed("field.id")
|
||||||
componentName(id) {
|
component(id) {
|
||||||
return id === "color_scheme" ? "color-palettes" : "combo-box";
|
return id === "color_scheme" ? ColorPalettes : ComboBox;
|
||||||
},
|
},
|
||||||
|
|
||||||
keyPress(e) {
|
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 { action } from "@ember/object";
|
||||||
import { drawHeader, LOREM } from "wizard/lib/preview";
|
import { drawHeader, LOREM } from "../../../lib/preview";
|
||||||
import WizardPreviewBaseComponent from "./wizard-preview-base";
|
import PreviewBaseComponent from "../styling-preview/-preview-base";
|
||||||
|
|
||||||
export default WizardPreviewBaseComponent.extend({
|
export default PreviewBaseComponent.extend({
|
||||||
width: 375,
|
width: 375,
|
||||||
height: 100,
|
height: 100,
|
||||||
image: null,
|
image: null,
|
@ -1,8 +1,8 @@
|
|||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import { drawHeader } from "wizard/lib/preview";
|
import { drawHeader } from "../../../lib/preview";
|
||||||
import WizardPreviewBaseComponent from "./wizard-preview-base";
|
import PreviewBaseComponent from "../styling-preview/-preview-base";
|
||||||
|
|
||||||
export default WizardPreviewBaseComponent.extend({
|
export default PreviewBaseComponent.extend({
|
||||||
width: 400,
|
width: 400,
|
||||||
height: 100,
|
height: 100,
|
||||||
image: null,
|
image: null,
|
@ -5,10 +5,10 @@ import { dasherize } from "@ember/string";
|
|||||||
import Uppy from "@uppy/core";
|
import Uppy from "@uppy/core";
|
||||||
import DropTarget from "@uppy/drop-target";
|
import DropTarget from "@uppy/drop-target";
|
||||||
import XHRUpload from "@uppy/xhr-upload";
|
import XHRUpload from "@uppy/xhr-upload";
|
||||||
import { getOwnerWithFallback } from "discourse-common/lib/get-owner";
|
|
||||||
import getUrl from "discourse-common/lib/get-url";
|
import getUrl from "discourse-common/lib/get-url";
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
import I18n from "discourse-i18n";
|
import I18n from "discourse-i18n";
|
||||||
|
import imagePreviews from "./image-previews";
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
classNames: ["wizard-container__image-upload"],
|
classNames: ["wizard-container__image-upload"],
|
||||||
@ -17,11 +17,7 @@ export default Component.extend({
|
|||||||
|
|
||||||
@discourseComputed("field.id")
|
@discourseComputed("field.id")
|
||||||
previewComponent(id) {
|
previewComponent(id) {
|
||||||
const componentName = `image-preview-${dasherize(id)}`;
|
return imagePreviews[dasherize(id)] ?? imagePreviews.generic;
|
||||||
const exists = getOwnerWithFallback(this).lookup(
|
|
||||||
`component:${componentName}`
|
|
||||||
);
|
|
||||||
return exists ? componentName : "wizard-image-preview";
|
|
||||||
},
|
},
|
||||||
|
|
||||||
didInsertElement() {
|
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 { darkLightDiff, LOREM } from "../../../lib/preview";
|
||||||
import WizardPreviewBaseComponent from "./wizard-preview-base";
|
import PreviewBaseComponent from "./-preview-base";
|
||||||
|
|
||||||
export default WizardPreviewBaseComponent.extend({
|
export default PreviewBaseComponent.extend({
|
||||||
width: 628,
|
width: 628,
|
||||||
height: 322,
|
height: 322,
|
||||||
logo: null,
|
logo: null,
|
@ -6,7 +6,7 @@ import { htmlSafe } from "@ember/template";
|
|||||||
import { Promise } from "rsvp";
|
import { Promise } from "rsvp";
|
||||||
import PreloadStore from "discourse/lib/preload-store";
|
import PreloadStore from "discourse/lib/preload-store";
|
||||||
import getUrl from "discourse-common/lib/get-url";
|
import getUrl from "discourse-common/lib/get-url";
|
||||||
import { darkLightDiff, drawHeader } from "wizard/lib/preview";
|
import { darkLightDiff, drawHeader } from "../../../lib/preview";
|
||||||
|
|
||||||
export const LOREM = `
|
export const LOREM = `
|
||||||
Lorem ipsum dolor sit amet,
|
Lorem ipsum dolor sit amet,
|
@ -8,7 +8,7 @@
|
|||||||
</canvas>
|
</canvas>
|
||||||
</div>
|
</div>
|
||||||
<div class="wizard-container__preview homepage-preview">
|
<div class="wizard-container__preview homepage-preview">
|
||||||
<HomepagePreview @wizard={{this.wizard}} @step={{this.step}} />
|
<this.HomepagePreview @wizard={{this.wizard}} @step={{this.step}} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1,8 +1,9 @@
|
|||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import { bind, observes } from "discourse-common/utils/decorators";
|
import { bind, observes } from "discourse-common/utils/decorators";
|
||||||
import I18n from "discourse-i18n";
|
import I18n from "discourse-i18n";
|
||||||
import { chooseDarker, darkLightDiff } from "wizard/lib/preview";
|
import { chooseDarker, darkLightDiff } from "../../../lib/preview";
|
||||||
import WizardPreviewBaseComponent from "./wizard-preview-base";
|
import HomepagePreview from "./-homepage-preview";
|
||||||
|
import PreviewBaseComponent from "./-preview-base";
|
||||||
|
|
||||||
const LOREM = `
|
const LOREM = `
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing.
|
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
|
velit nisl, porttitor sed nisl ac, consectetur interdum
|
||||||
metus. Fusce in consequat augue, vel facilisis felis.`;
|
metus. Fusce in consequat augue, vel facilisis felis.`;
|
||||||
|
|
||||||
export default WizardPreviewBaseComponent.extend({
|
export default PreviewBaseComponent.extend({
|
||||||
width: 628,
|
width: 628,
|
||||||
height: 322,
|
height: 322,
|
||||||
logo: null,
|
logo: null,
|
||||||
@ -19,6 +20,7 @@ export default WizardPreviewBaseComponent.extend({
|
|||||||
draggingActive: false,
|
draggingActive: false,
|
||||||
startX: 0,
|
startX: 0,
|
||||||
scrollLeft: 0,
|
scrollLeft: 0,
|
||||||
|
HomepagePreview,
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this._super(...arguments);
|
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";
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
const MAX_PARTICLES = 150;
|
const MAX_PARTICLES = 150;
|
||||||
|
|
||||||
const SIZE = 144;
|
const SIZE = 144;
|
||||||
|
|
||||||
let width, height;
|
|
||||||
|
|
||||||
const COLORS = [
|
const COLORS = [
|
||||||
"--tertiary",
|
"--tertiary",
|
||||||
"--quaternary",
|
"--quaternary",
|
||||||
@ -14,13 +15,12 @@ const COLORS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
class Particle {
|
class Particle {
|
||||||
constructor() {
|
constructor(width, height) {
|
||||||
this.reset();
|
this.reset(width, height);
|
||||||
this.y = Math.random() * (height + SIZE) - SIZE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset(width, height) {
|
||||||
this.y = -SIZE;
|
this.y = Math.random() * (height + SIZE) - SIZE;
|
||||||
this.origX = Math.random() * (width + SIZE);
|
this.origX = Math.random() * (width + SIZE);
|
||||||
this.speed = 0.5 + Math.random();
|
this.speed = 0.5 + Math.random();
|
||||||
this.ang = Math.random() * 2 * Math.PI;
|
this.ang = Math.random() * 2 * Math.PI;
|
||||||
@ -31,11 +31,13 @@ class Particle {
|
|||||||
this.flipped = Math.random() > 0.5 ? 1 : -1;
|
this.flipped = Math.random() > 0.5 ? 1 : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
move() {
|
move(width, height) {
|
||||||
this.y += this.speed;
|
this.y += this.speed;
|
||||||
|
|
||||||
if (this.y > height + SIZE) {
|
if (this.y > height + SIZE) {
|
||||||
this.reset();
|
this.reset(width, height);
|
||||||
|
// start at the top
|
||||||
|
this.y = -SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ang += this.speed / 30.0;
|
this.ang += this.speed / 30.0;
|
||||||
@ -47,66 +49,66 @@ class Particle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Component.extend({
|
export default class WizardCanvasComponent extends Component {
|
||||||
classNames: ["wizard-canvas"],
|
canvas = null;
|
||||||
tagName: "canvas",
|
particles = null;
|
||||||
ctx: null,
|
|
||||||
ready: false,
|
|
||||||
particles: null,
|
|
||||||
|
|
||||||
didInsertElement() {
|
get ready() {
|
||||||
this._super(...arguments);
|
return this.canvas !== null;
|
||||||
|
}
|
||||||
|
|
||||||
const canvas = this.element;
|
get ctx() {
|
||||||
this.ctx = canvas.getContext("2d");
|
return this.canvas.getContext("2d");
|
||||||
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
|
setup(canvas) {
|
||||||
|
this.canvas = canvas;
|
||||||
this.resized();
|
this.resized();
|
||||||
|
|
||||||
|
let { width, height } = canvas;
|
||||||
|
|
||||||
this.particles = [];
|
this.particles = [];
|
||||||
for (let i = 0; i < MAX_PARTICLES; i++) {
|
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(width, height);
|
||||||
this.paint();
|
|
||||||
|
|
||||||
window.addEventListener("resize", this.resized);
|
window.addEventListener("resize", this.resized);
|
||||||
},
|
}
|
||||||
|
|
||||||
willDestroyElement() {
|
|
||||||
this._super(...arguments);
|
|
||||||
|
|
||||||
|
@bind
|
||||||
|
teardown() {
|
||||||
|
this.canvas = null;
|
||||||
window.removeEventListener("resize", this.resized);
|
window.removeEventListener("resize", this.resized);
|
||||||
},
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
resized() {
|
resized() {
|
||||||
width = window.innerWidth;
|
this.canvas.width = window.innerWidth;
|
||||||
height = window.innerHeight;
|
this.canvas.height = window.innerHeight;
|
||||||
|
}
|
||||||
const canvas = this.element;
|
|
||||||
canvas.width = width;
|
|
||||||
canvas.height = height;
|
|
||||||
},
|
|
||||||
|
|
||||||
|
@bind
|
||||||
paint() {
|
paint() {
|
||||||
if (this.isDestroying || this.isDestroyed || !this.ready) {
|
if (!this.ready) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { ctx } = this;
|
let { ctx } = this;
|
||||||
|
let { width, height } = this.canvas;
|
||||||
ctx.clearRect(0, 0, width, height);
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
this.particles.forEach((particle) => {
|
for (let particle of this.particles) {
|
||||||
particle.move();
|
particle.move(width, height);
|
||||||
this.drawParticle(particle);
|
this.drawParticle(ctx, particle);
|
||||||
});
|
}
|
||||||
|
|
||||||
window.requestAnimationFrame(() => this.paint());
|
window.requestAnimationFrame(this.paint);
|
||||||
},
|
}
|
||||||
|
|
||||||
drawParticle(p) {
|
|
||||||
const c = this.ctx;
|
|
||||||
|
|
||||||
|
drawParticle(c, p) {
|
||||||
c.save();
|
c.save();
|
||||||
c.translate(p.x - SIZE, p.y - SIZE);
|
c.translate(p.x - SIZE, p.y - SIZE);
|
||||||
c.scale(p.scale * p.flipped, p.scale);
|
c.scale(p.scale * p.flipped, p.scale);
|
||||||
@ -174,5 +176,13 @@ export default Component.extend({
|
|||||||
c.fill();
|
c.fill();
|
||||||
c.stroke();
|
c.stroke();
|
||||||
c.restore();
|
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}}
|
@wizard={{@model.wizard}}
|
||||||
@goNext={{this.goNext}}
|
@goNext={{this.goNext}}
|
||||||
@goBack={{this.goBack}}
|
@goBack={{this.goBack}}
|
||||||
|
@goHome={{this.goHome}}
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -48,5 +49,10 @@ export default RouteTemplate(
|
|||||||
goBack() {
|
goBack() {
|
||||||
this.router.transitionTo("wizard.step", this.step.previous);
|
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",
|
description: "Your name",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
next: "styling",
|
next: "hello-again",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "styling",
|
id: "hello-again",
|
||||||
title: "Second step",
|
title: "hello again",
|
||||||
index: 1,
|
index: 1,
|
||||||
fields: [{ id: "some_title", type: "text" }],
|
fields: [
|
||||||
|
{
|
||||||
|
id: "nick_name",
|
||||||
|
type: "text",
|
||||||
|
required: false,
|
||||||
|
description: "Your nick name",
|
||||||
|
},
|
||||||
|
],
|
||||||
previous: "hello-world",
|
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",
|
next: "corporate",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "corporate",
|
id: "corporate",
|
||||||
index: 2,
|
index: 4,
|
||||||
fields: [
|
fields: [
|
||||||
{ id: "company_name", type: "text", required: true },
|
{ 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) => {
|
this.put("/wizard/steps/:id", (request) => {
|
||||||
const body = parsePostData(request.requestBody);
|
const body = parsePostData(request.requestBody);
|
||||||
|
|
||||||
if (body.fields.full_name === "Server Fail") {
|
if (body.fields?.full_name === "Server Fail") {
|
||||||
return response(422, {
|
return response(422, {
|
||||||
errors: [{ field: "full_name", description: "Invalid name" }],
|
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, {
|
return response(422, {
|
||||||
errors: [
|
errors: [
|
||||||
{ field: "company_name", description: "Invalid company name" },
|
{ 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 {
|
&__step.branding .wizard-container__description {
|
||||||
font-size: var(--font-0);
|
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;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@ -309,15 +313,6 @@ body.wizard {
|
|||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
flex-direction: column;
|
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 {
|
&__step-text {
|
||||||
@ -381,8 +376,13 @@ body.wizard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__button.primary {
|
&__button.primary {
|
||||||
|
margin-left: 1em;
|
||||||
background-color: var(--tertiary);
|
background-color: var(--tertiary);
|
||||||
color: var(--secondary);
|
color: var(--secondary);
|
||||||
|
@include breakpoint("mobile-extra-large") {
|
||||||
|
order: 1;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&__button.primary:hover,
|
&__button.primary:hover,
|
||||||
&__button.primary:focus {
|
&__button.primary:focus {
|
||||||
@ -412,13 +412,6 @@ body.wizard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__button.jump-in {
|
&__button.jump-in {
|
||||||
background-color: var(--tertiary);
|
|
||||||
color: var(--secondary);
|
|
||||||
margin-left: 1em;
|
|
||||||
@include breakpoint("mobile-extra-large") {
|
|
||||||
order: 1;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--primary-300);
|
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 {
|
&__label {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: var(--font-up-1);
|
font-size: var(--font-up-1);
|
||||||
|
@ -194,7 +194,7 @@ class Wizard
|
|||||||
style.add_choice("latest")
|
style.add_choice("latest")
|
||||||
CategoryPageStyle.values.each { |page| style.add_choice(page[:value]) }
|
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|
|
step.on_update do |updater|
|
||||||
updater.update_setting(:base_font, updater.fields[:body_font])
|
updater.update_setting(:base_font, updater.fields[:body_font])
|
||||||
|
Loading…
Reference in New Issue
Block a user