From c0294191adce4a1d476ad3dff07caedbcb35cb85 Mon Sep 17 00:00:00 2001 From: Ken Sternberg Date: Fri, 11 Aug 2023 14:57:48 -0700 Subject: [PATCH] Turns out that was one layer too many; the topmost component was fine for maintaining the context. --- .../ak-wizard-main/ak-wizard-context.ts | 85 ------------------ .../ak-wizard-main/ak-wizard-frame.ts | 2 + .../ak-wizard-main/ak-wizard-main.ts | 89 ++++++++++++++++--- 3 files changed, 77 insertions(+), 99 deletions(-) delete mode 100644 web/src/components/ak-wizard-main/ak-wizard-context.ts diff --git a/web/src/components/ak-wizard-main/ak-wizard-context.ts b/web/src/components/ak-wizard-main/ak-wizard-context.ts deleted file mode 100644 index 399909080..000000000 --- a/web/src/components/ak-wizard-main/ak-wizard-context.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { CustomListenerElement } from "@goauthentik/elements/utils/eventEmitter"; - -import { provide } from "@lit-labs/context"; -import { customElement, property, state } from "@lit/reactive-element/decorators.js"; -import { LitElement, html } from "lit"; - -import { akWizardCurrentStepContextName } from "./akWizardCurrentStepContextName"; -import { akWizardStepsContextName } from "./akWizardStepsContextName"; -import type { WizardStep, WizardStepId } from "./types"; - -/** - * AkWizardContext - * - * @element ak-wizard-context - * - * The WizardContext controls the navigation for the wizard. It listens for navigation events from - * the wizard frame and responds with changes to the view, including handling the close button. - * - */ - -@customElement("ak-wizard-context") -export class AkWizardContext extends CustomListenerElement(LitElement) { - @property() - eventName: string = "ak-wizard-nav"; - - @provide({ context: akWizardStepsContextName }) - @property({ attribute: false }) - steps: WizardStep[] = []; - - @provide({ context: akWizardCurrentStepContextName }) - @state() - currentStep!: WizardStep; - - constructor() { - super(); - this.handleNavigation = this.handleNavigation.bind(this); - } - - // This is the only case where currentStep could be anything other than a valid entry. Unless, - // of course, a step itself is so badly messed up it can't point to a real object. - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - willUpdate(_changedProperties: Map) { - if (this.currentStep === undefined) { - this.currentStep = this.steps[0]; - } - } - - // Note that we always scan for the valid next step and throw an error if we can't find it. - // There should never be a question that the currentStep is a *valid* step. - // - // TODO: Put a phase in there so that the current step can validate the contents asynchronously - // before setting the currentStep. Especially since setting the currentStep triggers a second - // asynchronous event-- scheduling a re-render of everything interested in the currentStep - // object. - handleNavigation(event: CustomEvent<{ step: WizardStepId }>) { - const requestedStep = event.detail.step; - if (!requestedStep) { - throw new Error("Request for next step when no next step is available"); - } - const step = this.steps.find(({ id }) => id === requestedStep); - if (!step) { - throw new Error("Request for next step when no next step is available."); - } - if (step.disabled) { - throw new Error("Request for next step when the next step is disabled."); - } - this.currentStep = step; - return; - } - - connectedCallback() { - super.connectedCallback(); - this.addCustomListener(this.eventName, this.handleNavigation); - } - - disconnectedCallback() { - this.removeCustomListener(this.eventName, this.handleNavigation); - super.disconnectedCallback(); - } - - render() { - return html``; - } -} diff --git a/web/src/components/ak-wizard-main/ak-wizard-frame.ts b/web/src/components/ak-wizard-main/ak-wizard-frame.ts index 77c458bc7..6368a9010 100644 --- a/web/src/components/ak-wizard-main/ak-wizard-frame.ts +++ b/web/src/components/ak-wizard-main/ak-wizard-frame.ts @@ -123,6 +123,8 @@ export class AkWizardFrame extends CustomEmitterElement(ModalButton) { `; } + // This is where the panel is shown. We expect the panel to get its information from an + // independent context. renderMainSection() { return html`
${this.currentStep.renderer()}
diff --git a/web/src/components/ak-wizard-main/ak-wizard-main.ts b/web/src/components/ak-wizard-main/ak-wizard-main.ts index c31df43c3..80f1c15d0 100644 --- a/web/src/components/ak-wizard-main/ak-wizard-main.ts +++ b/web/src/components/ak-wizard-main/ak-wizard-main.ts @@ -1,16 +1,18 @@ import { AKElement } from "@goauthentik/elements/Base"; +import { CustomListenerElement } from "@goauthentik/elements/utils/eventEmitter"; -import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; +import { provide } from "@lit-labs/context"; import { html } from "lit"; -import { property } from "lit/decorators.js"; +import { customElement, property, state } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFRadio from "@patternfly/patternfly/components/Radio/radio.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; -import "./ak-wizard-context"; import "./ak-wizard-frame"; +import { akWizardCurrentStepContextName } from "./akWizardCurrentStepContextName"; +import { akWizardStepsContextName } from "./akWizardStepsContextName"; import type { WizardStep } from "./types"; /** @@ -23,21 +25,39 @@ import type { WizardStep } from "./types"; */ @customElement("ak-wizard-main") -export class AkWizardMain extends AKElement { +export class AkWizardMain extends CustomListenerElement(AKElement) { static get styles() { return [PFBase, PFButton, PFRadio]; } + @property() + eventName: string = "ak-wizard-nav"; + /** * The steps of the Wizard. * * @attribute */ + @provide({ context: akWizardStepsContextName }) @property({ attribute: false }) steps: WizardStep[] = []; /** - * The text of the button + * The current step of the wizard. + * + * @attribute + */ + @provide({ context: akWizardCurrentStepContextName }) + @state() + currentStep!: WizardStep; + + constructor() { + super(); + this.handleNavigation = this.handleNavigation.bind(this); + } + + /** + * The text of the modal button * * @attribute */ @@ -68,17 +88,58 @@ export class AkWizardMain extends AKElement { @property() description?: string; + // Guarantee that if the current step was not passed in by the client, that we know + // and set to the first step. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + willUpdate(_changedProperties: Map) { + if (this.currentStep === undefined) { + this.currentStep = this.steps[0]; + } + } + + connectedCallback() { + super.connectedCallback(); + this.addCustomListener(this.eventName, this.handleNavigation); + } + + disconnectedCallback() { + this.removeCustomListener(this.eventName, this.handleNavigation); + super.disconnectedCallback(); + } + + // Note that we always scan for the valid next step and throw an error if we can't find it. + // There should never be a question that the currentStep is a *valid* step. + // + // TODO: Put a phase in there so that the current step can validate the contents asynchronously + // before setting the currentStep. Especially since setting the currentStep triggers a second + // asynchronous event-- scheduling a re-render of everything interested in the currentStep + // object. + handleNavigation(event: CustomEvent<{ step: string }>) { + const requestedStep = event.detail.step; + if (!requestedStep) { + throw new Error("Request for next step when no next step is available"); + } + const step = this.steps.find(({ id }) => id === requestedStep); + if (!step) { + throw new Error("Request for next step when no next step is available."); + } + if (step.disabled) { + throw new Error("Request for next step when the next step is disabled."); + } + this.currentStep = step; + return; + } + render() { return html` - - - - - + + + `; } }