web: Fix issues surrounding wizard step behavior. (#12779)
This resolves a few stateful situations which may arise when opening and closing wizard pages.
This commit is contained in:
@ -52,6 +52,21 @@ export class PolicyWizard extends AKElement {
|
||||
});
|
||||
}
|
||||
|
||||
selectListener = ({ detail }: CustomEvent<TypeCreate>) => {
|
||||
if (!this.wizard) return;
|
||||
|
||||
const { component, modelName } = detail;
|
||||
const idx = this.wizard.steps.indexOf("initial") + 1;
|
||||
|
||||
// Exclude all current steps starting with type-,
|
||||
// this happens when the user selects a type and then goes back
|
||||
this.wizard.steps = this.wizard.steps.filter((step) => !step.startsWith("type-"));
|
||||
|
||||
this.wizard.steps.splice(idx, 0, `type-${component}-${modelName}`);
|
||||
|
||||
this.wizard.isValid = true;
|
||||
};
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`
|
||||
<ak-wizard
|
||||
@ -62,23 +77,10 @@ export class PolicyWizard extends AKElement {
|
||||
<ak-wizard-page-type-create
|
||||
slot="initial"
|
||||
.types=${this.policyTypes}
|
||||
@select=${(ev: CustomEvent<TypeCreate>) => {
|
||||
if (!this.wizard) return;
|
||||
const idx = this.wizard.steps.indexOf("initial") + 1;
|
||||
// Exclude all current steps starting with type-,
|
||||
// this happens when the user selects a type and then goes back
|
||||
this.wizard.steps = this.wizard.steps.filter(
|
||||
(step) => !step.startsWith("type-"),
|
||||
);
|
||||
this.wizard.steps.splice(
|
||||
idx,
|
||||
0,
|
||||
`type-${ev.detail.component}-${ev.detail.modelName}`,
|
||||
);
|
||||
this.wizard.isValid = true;
|
||||
}}
|
||||
@select=${this.selectListener}
|
||||
>
|
||||
</ak-wizard-page-type-create>
|
||||
|
||||
${this.policyTypes.map((type) => {
|
||||
return html`
|
||||
<ak-wizard-page-form
|
||||
|
@ -41,6 +41,7 @@ export class ActionWizardPage extends WizardPage {
|
||||
|
||||
activeCallback = async (): Promise<void> => {
|
||||
this.states = [];
|
||||
|
||||
this.host.actions.map((act, idx) => {
|
||||
this.states.push({
|
||||
action: act,
|
||||
@ -48,9 +49,12 @@ export class ActionWizardPage extends WizardPage {
|
||||
idx: idx,
|
||||
});
|
||||
});
|
||||
|
||||
this.host.canBack = false;
|
||||
this.host.canCancel = false;
|
||||
|
||||
await this.run();
|
||||
|
||||
// Ensure wizard is closable, even when run() failed
|
||||
this.host.isValid = true;
|
||||
};
|
||||
@ -59,15 +63,20 @@ export class ActionWizardPage extends WizardPage {
|
||||
|
||||
async run(): Promise<void> {
|
||||
this.currentStep = this.states[0];
|
||||
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
|
||||
for await (const bundle of this.states) {
|
||||
this.currentStep = bundle;
|
||||
this.currentStep.state = ActionState.running;
|
||||
this.requestUpdate();
|
||||
try {
|
||||
await bundle.action.run();
|
||||
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
|
||||
this.currentStep.state = ActionState.done;
|
||||
|
||||
this.requestUpdate();
|
||||
} catch (exc) {
|
||||
if (exc instanceof ResponseError) {
|
||||
@ -75,12 +84,16 @@ export class ActionWizardPage extends WizardPage {
|
||||
} else {
|
||||
this.currentStep.action.subText = (exc as Error).toString();
|
||||
}
|
||||
|
||||
this.currentStep.state = ActionState.failed;
|
||||
this.requestUpdate();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.host.isValid = true;
|
||||
|
||||
this.dispatchEvent(
|
||||
new CustomEvent(EVENT_REFRESH, {
|
||||
bubbles: true,
|
||||
|
@ -5,6 +5,7 @@ import { WizardPage } from "@goauthentik/elements/wizard/WizardPage";
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { Ref, createRef, ref } from "lit/directives/ref.js";
|
||||
|
||||
import PFCard from "@patternfly/patternfly/components/Card/card.css";
|
||||
import PFForm from "@patternfly/patternfly/components/Form/form.css";
|
||||
@ -21,6 +22,8 @@ export enum TypeCreateWizardPageLayouts {
|
||||
|
||||
@customElement("ak-wizard-page-type-create")
|
||||
export class TypeCreateWizardPage extends WithLicenseSummary(WizardPage) {
|
||||
//#region Properties
|
||||
|
||||
@property({ attribute: false })
|
||||
types: TypeCreate[] = [];
|
||||
|
||||
@ -30,6 +33,8 @@ export class TypeCreateWizardPage extends WithLicenseSummary(WizardPage) {
|
||||
@property({ type: String })
|
||||
layout: TypeCreateWizardPageLayouts = TypeCreateWizardPageLayouts.list;
|
||||
|
||||
//#endregion
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
PFBase,
|
||||
@ -49,10 +54,25 @@ export class TypeCreateWizardPage extends WithLicenseSummary(WizardPage) {
|
||||
];
|
||||
}
|
||||
|
||||
sidebarLabel = () => msg("Select type");
|
||||
//#region Refs
|
||||
|
||||
formRef: Ref<HTMLFormElement> = createRef();
|
||||
|
||||
//#endregion
|
||||
|
||||
public sidebarLabel = () => msg("Select type");
|
||||
|
||||
public reset = () => {
|
||||
super.reset();
|
||||
this.selectedType = undefined;
|
||||
this.formRef.value?.reset();
|
||||
};
|
||||
|
||||
activeCallback = (): void => {
|
||||
const form = this.formRef.value;
|
||||
|
||||
this.host.isValid = form?.checkValidity() ?? false;
|
||||
|
||||
activeCallback: () => Promise<void> = async () => {
|
||||
this.host.isValid = false;
|
||||
if (this.selectedType) {
|
||||
this.selectDispatch(this.selectedType);
|
||||
}
|
||||
@ -92,9 +112,8 @@ export class TypeCreateWizardPage extends WithLicenseSummary(WizardPage) {
|
||||
data-ouid-component-type="ak-type-create-grid-card"
|
||||
data-ouid-component-name=${componentName}
|
||||
@click=${() => {
|
||||
if (requiresEnterprise) {
|
||||
return;
|
||||
}
|
||||
if (requiresEnterprise) return;
|
||||
|
||||
this.selectDispatch(type);
|
||||
this.selectedType = type;
|
||||
}}
|
||||
@ -120,11 +139,13 @@ export class TypeCreateWizardPage extends WithLicenseSummary(WizardPage) {
|
||||
|
||||
renderList(): TemplateResult {
|
||||
return html`<form
|
||||
${ref(this.formRef)}
|
||||
class="pf-c-form pf-m-horizontal"
|
||||
data-ouid-component-type="ak-type-create-list"
|
||||
>
|
||||
${this.types.map((type) => {
|
||||
const requiresEnterprise = type.requiresEnterprise && !this.hasEnterpriseLicense;
|
||||
|
||||
return html`<div
|
||||
class="pf-c-radio"
|
||||
data-ouid-component-type="ak-type-create-list-card"
|
||||
@ -160,6 +181,8 @@ export class TypeCreateWizardPage extends WithLicenseSummary(WizardPage) {
|
||||
return this.renderGrid();
|
||||
case TypeCreateWizardPageLayouts.list:
|
||||
return this.renderList();
|
||||
default:
|
||||
throw new Error(`Unknown layout: ${this.layout}`) as never;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,9 @@ import { WizardPage } from "@goauthentik/elements/wizard/WizardPage";
|
||||
import { msg } from "@lit/localize";
|
||||
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
|
||||
import { property } from "@lit/reactive-element/decorators/property.js";
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
||||
import { state } from "lit/decorators.js";
|
||||
import { classMap } from "lit/directives/class-map.js";
|
||||
|
||||
import PFWizard from "@patternfly/patternfly/components/Wizard/wizard.css";
|
||||
|
||||
@ -20,21 +21,6 @@ export const ApplyActionsSlot = "apply-actions";
|
||||
|
||||
@customElement("ak-wizard")
|
||||
export class Wizard extends ModalButton {
|
||||
@property({ type: Boolean })
|
||||
canCancel = true;
|
||||
|
||||
@property({ type: Boolean })
|
||||
canBack = true;
|
||||
|
||||
@property()
|
||||
header?: string;
|
||||
|
||||
@property()
|
||||
description?: string;
|
||||
|
||||
@property({ type: Boolean })
|
||||
isValid = false;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return super.styles.concat(
|
||||
PFWizard,
|
||||
@ -46,65 +32,138 @@ export class Wizard extends ModalButton {
|
||||
);
|
||||
}
|
||||
|
||||
@state()
|
||||
_steps: string[] = [];
|
||||
//#region Properties
|
||||
|
||||
get steps(): string[] {
|
||||
return this._steps;
|
||||
}
|
||||
/**
|
||||
* Whether the wizard can be cancelled.
|
||||
*/
|
||||
@property({ type: Boolean })
|
||||
canCancel = true;
|
||||
|
||||
set steps(steps: string[]) {
|
||||
const addApplyActionsSlot = this.steps.includes(ApplyActionsSlot);
|
||||
this._steps = steps;
|
||||
if (addApplyActionsSlot) {
|
||||
this.steps.push(ApplyActionsSlot);
|
||||
}
|
||||
this.steps.forEach((step) => {
|
||||
const exists = this.querySelector(`[slot=${step}]`) !== null;
|
||||
if (!exists) {
|
||||
const el = document.createElement(step);
|
||||
el.slot = step;
|
||||
el.dataset["wizardmanaged"] = "true";
|
||||
this.appendChild(el);
|
||||
}
|
||||
});
|
||||
this.requestUpdate();
|
||||
}
|
||||
/**
|
||||
* Whether the wizard can go back to the previous step.
|
||||
*/
|
||||
@property({ type: Boolean })
|
||||
canBack = true;
|
||||
|
||||
_initialSteps: string[] = [];
|
||||
/**
|
||||
* Header title of the wizard.
|
||||
*/
|
||||
@property()
|
||||
header?: string;
|
||||
|
||||
/**
|
||||
* Description of the wizard.
|
||||
*/
|
||||
@property()
|
||||
description?: string;
|
||||
|
||||
/**
|
||||
* Whether the wizard is valid and can proceed to the next step.
|
||||
*/
|
||||
@property({ type: Boolean })
|
||||
isValid = false;
|
||||
|
||||
/**
|
||||
* Actions to display at the end of the wizard.
|
||||
*/
|
||||
@property({ attribute: false })
|
||||
actions: WizardAction[] = [];
|
||||
|
||||
@state()
|
||||
_currentStep?: WizardPage;
|
||||
|
||||
set currentStep(value: WizardPage | undefined) {
|
||||
this._currentStep = value;
|
||||
if (this._currentStep) {
|
||||
this._currentStep.activeCallback();
|
||||
this._currentStep.requestUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
get currentStep(): WizardPage | undefined {
|
||||
return this._currentStep;
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
finalHandler: () => Promise<void> = () => {
|
||||
finalHandler = () => {
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
@property({ attribute: false })
|
||||
state: { [key: string]: unknown } = {};
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region State
|
||||
|
||||
/**
|
||||
* Memoized step tag names.
|
||||
*/
|
||||
@state()
|
||||
_steps: string[] = [];
|
||||
|
||||
/**
|
||||
* Step tag names present in the wizard.
|
||||
*/
|
||||
get steps(): string[] {
|
||||
return this._steps;
|
||||
}
|
||||
|
||||
set steps(nextSteps: string[]) {
|
||||
const addApplyActionsSlot = this._steps.includes(ApplyActionsSlot);
|
||||
|
||||
this._steps = nextSteps;
|
||||
|
||||
if (addApplyActionsSlot) {
|
||||
this.steps.push(ApplyActionsSlot);
|
||||
}
|
||||
|
||||
for (const step of this._steps) {
|
||||
const existingStepElement = this.getStepElementByName(step);
|
||||
|
||||
if (existingStepElement) continue;
|
||||
|
||||
const stepElement = document.createElement(step);
|
||||
|
||||
stepElement.slot = step;
|
||||
stepElement.dataset["wizardmanaged"] = "true";
|
||||
|
||||
this.appendChild(stepElement);
|
||||
}
|
||||
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initial steps to reset to.
|
||||
*/
|
||||
_initialSteps: string[] = [];
|
||||
|
||||
@state()
|
||||
_activeStep?: WizardPage;
|
||||
|
||||
set activeStepElement(nextActiveStepElement: WizardPage | undefined) {
|
||||
this._activeStep = nextActiveStepElement;
|
||||
|
||||
if (!this._activeStep) return;
|
||||
|
||||
this._activeStep.activeCallback();
|
||||
this._activeStep.requestUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* The active step element being displayed.
|
||||
*/
|
||||
get activeStepElement(): WizardPage | undefined {
|
||||
return this._activeStep;
|
||||
}
|
||||
|
||||
getStepElementByIndex(stepIndex: number): WizardPage | null {
|
||||
const stepName = this._steps[stepIndex];
|
||||
|
||||
return this.querySelector<WizardPage>(`[slot=${stepName}]`);
|
||||
}
|
||||
|
||||
getStepElementByName(stepName: string): WizardPage | null {
|
||||
return this.querySelector<WizardPage>(`[slot=${stepName}]`);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Lifecycle
|
||||
|
||||
firstUpdated(): void {
|
||||
this._initialSteps = this._steps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add action to the beginning of the list
|
||||
* Add action to the beginning of the list.
|
||||
*/
|
||||
addActionBefore(displayName: string, run: () => Promise<boolean>): void {
|
||||
this.actions.unshift({
|
||||
@ -114,7 +173,9 @@ export class Wizard extends ModalButton {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add action at the end of the list
|
||||
* Add action at the end of the list.
|
||||
*
|
||||
* @todo: Is this used?
|
||||
*/
|
||||
addActionAfter(displayName: string, run: () => Promise<boolean>): void {
|
||||
this.actions.push({
|
||||
@ -123,17 +184,60 @@ export class Wizard extends ModalButton {
|
||||
});
|
||||
}
|
||||
|
||||
renderModalInner(): TemplateResult {
|
||||
const firstPage = this.querySelector<WizardPage>(`[slot=${this.steps[0]}]`);
|
||||
if (!this.currentStep && firstPage) {
|
||||
this.currentStep = firstPage;
|
||||
/**
|
||||
* Reset the wizard to it's initial state.
|
||||
*/
|
||||
reset = () => {
|
||||
this.open = false;
|
||||
|
||||
this.querySelectorAll("[data-wizardmanaged=true]").forEach((el) => {
|
||||
el.remove();
|
||||
});
|
||||
|
||||
for (const step of this.steps) {
|
||||
const stepElement = this.getStepElementByName(step);
|
||||
|
||||
stepElement?.reset?.();
|
||||
}
|
||||
const currentIndex = this.currentStep ? this.steps.indexOf(this.currentStep.slot) : 0;
|
||||
let lastPage = currentIndex === this.steps.length - 1;
|
||||
|
||||
this.steps = this._initialSteps;
|
||||
this.actions = [];
|
||||
this.state = {};
|
||||
this.activeStepElement = undefined;
|
||||
this.canBack = true;
|
||||
this.canCancel = true;
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Rendering
|
||||
|
||||
renderModalInner(): TemplateResult {
|
||||
const firstPage = this.getStepElementByIndex(0);
|
||||
|
||||
if (!this.activeStepElement && firstPage) {
|
||||
this.activeStepElement = firstPage;
|
||||
}
|
||||
|
||||
const activeStepIndex = this.activeStepElement
|
||||
? this.steps.indexOf(this.activeStepElement.slot)
|
||||
: 0;
|
||||
|
||||
let lastPage = activeStepIndex === this.steps.length - 1;
|
||||
|
||||
if (lastPage && !this.steps.includes("ak-wizard-page-action") && this.actions.length > 0) {
|
||||
this.steps = this.steps.concat("ak-wizard-page-action");
|
||||
lastPage = currentIndex === this.steps.length - 1;
|
||||
lastPage = activeStepIndex === this.steps.length - 1;
|
||||
}
|
||||
|
||||
const navigateToPreviousStep = () => {
|
||||
const prevPage = this.getStepElementByIndex(activeStepIndex - 1);
|
||||
|
||||
if (prevPage) {
|
||||
this.activeStepElement = prevPage;
|
||||
}
|
||||
};
|
||||
|
||||
return html`<div class="pf-c-wizard">
|
||||
<div class="pf-c-wizard__header">
|
||||
${this.canCancel
|
||||
@ -141,13 +245,11 @@ export class Wizard extends ModalButton {
|
||||
class="pf-c-button pf-m-plain pf-c-wizard__close"
|
||||
type="button"
|
||||
aria-label="${msg("Close")}"
|
||||
@click=${() => {
|
||||
this.reset();
|
||||
}}
|
||||
@click=${this.reset}
|
||||
>
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>`
|
||||
: html``}
|
||||
: nothing}
|
||||
<h1 class="pf-c-title pf-m-3xl pf-c-wizard__title">${this.header}</h1>
|
||||
<p class="pf-c-wizard__description">${this.description}</p>
|
||||
</div>
|
||||
@ -156,28 +258,25 @@ export class Wizard extends ModalButton {
|
||||
<nav class="pf-c-wizard__nav">
|
||||
<ol class="pf-c-wizard__nav-list">
|
||||
${this.steps.map((step, idx) => {
|
||||
const currentIdx = this.currentStep
|
||||
? this.steps.indexOf(this.currentStep.slot)
|
||||
: 0;
|
||||
const stepEl = this.getStepElementByName(step);
|
||||
|
||||
if (!stepEl) return html`<p>Unexpected missing step: ${step}</p>`;
|
||||
|
||||
const sidebarLabel = stepEl.sidebarLabel();
|
||||
|
||||
return html`
|
||||
<li class="pf-c-wizard__nav-item">
|
||||
<button
|
||||
class="pf-c-wizard__nav-link ${idx === currentIdx
|
||||
? "pf-m-current"
|
||||
: ""}"
|
||||
?disabled=${currentIdx < idx}
|
||||
class=${classMap({
|
||||
"pf-c-wizard__nav-link": true,
|
||||
"pf-m-current": idx === activeStepIndex,
|
||||
})}
|
||||
?disabled=${activeStepIndex < idx}
|
||||
@click=${() => {
|
||||
const stepEl = this.querySelector<WizardPage>(
|
||||
`[slot=${step}]`,
|
||||
);
|
||||
if (stepEl) {
|
||||
this.currentStep = stepEl;
|
||||
}
|
||||
this.activeStepElement = stepEl;
|
||||
}}
|
||||
>
|
||||
${this.querySelector<WizardPage>(
|
||||
`[slot=${step}]`,
|
||||
)?.sidebarLabel()}
|
||||
${sidebarLabel}
|
||||
</button>
|
||||
</li>
|
||||
`;
|
||||
@ -186,7 +285,7 @@ export class Wizard extends ModalButton {
|
||||
</nav>
|
||||
<main class="pf-c-wizard__main">
|
||||
<div class="pf-c-wizard__main-body">
|
||||
<slot name=${this.currentStep?.slot || this.steps[0]}></slot>
|
||||
<slot name=${this.activeStepElement?.slot || this.steps[0]}></slot>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
@ -196,44 +295,38 @@ export class Wizard extends ModalButton {
|
||||
type="submit"
|
||||
?disabled=${!this.isValid}
|
||||
@click=${async () => {
|
||||
const cb = await this.currentStep?.nextCallback();
|
||||
if (!cb) {
|
||||
return;
|
||||
}
|
||||
const completedStep = await this.activeStepElement?.nextCallback();
|
||||
if (!completedStep) return;
|
||||
|
||||
if (lastPage) {
|
||||
await this.finalHandler();
|
||||
this.reset();
|
||||
} else {
|
||||
const nextPage = this.querySelector<WizardPage>(
|
||||
`[slot=${this.steps[currentIndex + 1]}]`,
|
||||
);
|
||||
if (nextPage) {
|
||||
this.currentStep = nextPage;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const nextPage = this.getStepElementByIndex(activeStepIndex + 1);
|
||||
|
||||
if (nextPage) {
|
||||
this.activeStepElement = nextPage;
|
||||
}
|
||||
}}
|
||||
>
|
||||
${lastPage ? msg("Finish") : msg("Next")}
|
||||
</button>
|
||||
${(this.currentStep ? this.steps.indexOf(this.currentStep.slot) : 0) > 0 &&
|
||||
this.canBack
|
||||
${(this.activeStepElement
|
||||
? this.steps.indexOf(this.activeStepElement.slot)
|
||||
: 0) > 0 && this.canBack
|
||||
? html`
|
||||
<button
|
||||
class="pf-c-button pf-m-secondary"
|
||||
type="button"
|
||||
@click=${() => {
|
||||
const prevPage = this.querySelector<WizardPage>(
|
||||
`[slot=${this.steps[currentIndex - 1]}]`,
|
||||
);
|
||||
if (prevPage) {
|
||||
this.currentStep = prevPage;
|
||||
}
|
||||
}}
|
||||
@click=${navigateToPreviousStep}
|
||||
>
|
||||
${msg("Back")}
|
||||
</button>
|
||||
`
|
||||
: html``}
|
||||
: nothing}
|
||||
${this.canCancel
|
||||
? html`<div class="pf-c-wizard__footer-cancel">
|
||||
<button
|
||||
@ -246,24 +339,13 @@ export class Wizard extends ModalButton {
|
||||
${msg("Cancel")}
|
||||
</button>
|
||||
</div>`
|
||||
: html``}
|
||||
: nothing}
|
||||
</footer>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.open = false;
|
||||
this.querySelectorAll("[data-wizardmanaged=true]").forEach((el) => {
|
||||
el.remove();
|
||||
});
|
||||
this.steps = this._initialSteps;
|
||||
this.actions = [];
|
||||
this.state = {};
|
||||
this.currentStep = undefined;
|
||||
this.canBack = true;
|
||||
this.canCancel = true;
|
||||
}
|
||||
//#endregion
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@ -39,24 +39,24 @@ export class WizardFormPage extends WizardPage {
|
||||
|
||||
inputCallback(): void {
|
||||
const form = this.shadowRoot?.querySelector<HTMLFormElement>("form");
|
||||
if (!form) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!form) return;
|
||||
|
||||
const state = form.checkValidity();
|
||||
this.host.isValid = state;
|
||||
}
|
||||
|
||||
nextCallback = async (): Promise<boolean> => {
|
||||
const form = this.shadowRoot?.querySelector<WizardForm>("ak-wizard-form");
|
||||
|
||||
if (!form) {
|
||||
console.warn("authentik/wizard: could not find form element");
|
||||
return false;
|
||||
}
|
||||
|
||||
const response = await form.submit();
|
||||
if (response === undefined) {
|
||||
return false;
|
||||
}
|
||||
return response;
|
||||
|
||||
return Boolean(response);
|
||||
};
|
||||
|
||||
nextDataCallback: (data: KeyUnknown) => Promise<boolean> = async (): Promise<boolean> => {
|
||||
|
@ -6,14 +6,32 @@ import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
/**
|
||||
* Callback for when the page is brought into view.
|
||||
*/
|
||||
export type WizardPageActiveCallback = () => void | Promise<void>;
|
||||
|
||||
/**
|
||||
* Callback for when the next button is pressed.
|
||||
*
|
||||
* @returns `true` if the wizard can proceed to the next page, `false` otherwise.
|
||||
*/
|
||||
export type WizardPageNextCallback = () => boolean | Promise<boolean>;
|
||||
|
||||
@customElement("ak-wizard-page")
|
||||
export class WizardPage extends AKElement {
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase];
|
||||
}
|
||||
|
||||
/**
|
||||
* The label to display in the sidebar for this page.
|
||||
*
|
||||
* Override this to provide a custom label.
|
||||
* @todo: Should this be a getter or static property?
|
||||
*/
|
||||
@property()
|
||||
sidebarLabel: () => string = () => {
|
||||
sidebarLabel = (): string => {
|
||||
return "UNNAMED";
|
||||
};
|
||||
|
||||
@ -22,9 +40,18 @@ export class WizardPage extends AKElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this is the page brought into view
|
||||
* Reset the page to its initial state.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
activeCallback: () => Promise<void> = async () => {
|
||||
public reset(): void | Promise<void> {
|
||||
console.debug(`authentik/wizard ${this.localName}: reset)`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this is the page brought into view.
|
||||
*/
|
||||
activeCallback: WizardPageActiveCallback = () => {
|
||||
this.host.isValid = false;
|
||||
};
|
||||
|
||||
@ -32,9 +59,11 @@ export class WizardPage extends AKElement {
|
||||
* Called when the `next` button on the wizard is pressed. For forms, results in the submission
|
||||
* of the current form to the back-end before being allowed to proceed to the next page. This is
|
||||
* sub-optimal if we want to collect multiple bits of data before finishing the whole course.
|
||||
*
|
||||
* @returns `true` if the wizard can proceed to the next page, `false` otherwise.
|
||||
*/
|
||||
nextCallback: () => Promise<boolean> = async () => {
|
||||
return true;
|
||||
nextCallback: WizardPageNextCallback = () => {
|
||||
return Promise.resolve(true);
|
||||
};
|
||||
|
||||
requestUpdate(
|
||||
|
Reference in New Issue
Block a user