Files
authentik/web/src/components/ak-wizard-main/ak-wizard-frame.ts
Jens L 6c4c535d57 web/admin: rework initial wizard pages and add grid layout (#9668)
* remove @goauthentik/authentik as TS path

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* initial implementation

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* oh yeah

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* format earlier changes

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* support plain alert

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* initial attempt at dedupe

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* make it a base class

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* migrate all wizards

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* create type create mixin to dedupe more, add icon to source create

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add ldap icon

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* Optimised images with calibre/image-actions

* match inverting

we should probably replace all icons with coloured ones so we don't need to invert them...I guess

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* format

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* make everything more explicit

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add icons to provider

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add remaining provider icons

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* rework to not use inheritance

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix unrelated typo

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* make app wizard use grid layout

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* keep wizard height consistent

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2024-05-22 02:41:33 +02:00

210 lines
6.5 KiB
TypeScript

import { ModalButton } from "@goauthentik/elements/buttons/ModalButton";
import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter";
import { msg } from "@lit/localize";
import { customElement, property, query } from "@lit/reactive-element/decorators.js";
import { TemplateResult, css, html, nothing } from "lit";
import { classMap } from "lit/directives/class-map.js";
import { map } from "lit/directives/map.js";
import PFWizard from "@patternfly/patternfly/components/Wizard/wizard.css";
import { type WizardButton, WizardStepLabel } from "./types";
/**
* AKWizardFrame is the main container for displaying Wizard pages.
*
* AKWizardFrame is one component of a Wizard development environment. It provides the header,
* titled navigation sidebar, and bottom row button bar. It takes its cues about what to render from
* two data structure, `this.steps: WizardStep[]`, which lists all the current steps *in order* and
* doesn't care otherwise about their structure, and `this.currentStep: WizardStep` which must be a
* _reference_ to a member of `this.steps`.
*
* @element ak-wizard-frame
*
* @slot trigger - (Inherited from ModalButton) Define the "summon modal" button here
*
* @fires ak-wizard-nav - Tell the orchestrator what page the user wishes to move to.
*
*/
@customElement("ak-wizard-frame")
export class AkWizardFrame extends CustomEmitterElement(ModalButton) {
static get styles() {
return [
...super.styles,
PFWizard,
css`
.pf-c-modal-box {
height: 75%;
}
`,
];
}
/**
* The text for the title of the wizard
*/
@property()
header?: string;
/**
* The text for a descriptive subtitle for the wizard
*/
@property()
description?: string;
/**
* The labels for all current steps, including their availability
*/
@property({ attribute: false, type: Array })
stepLabels!: WizardStepLabel[];
/**
* What buttons to Show
*/
@property({ attribute: false, type: Array })
buttons: WizardButton[] = [];
/**
* Show the [Cancel] icon and offer the [Cancel] button
*/
@property({ type: Boolean, attribute: "can-cancel" })
canCancel = false;
/**
* The form renderer, passed as a function
*/
@property({ type: Object })
form!: () => TemplateResult;
@query("#main-content *:first-child")
content!: HTMLElement;
constructor() {
super();
this.renderButtons = this.renderButtons.bind(this);
}
renderModalInner() {
// prettier-ignore
return html`<div class="pf-c-wizard">
${this.renderHeader()}
<div class="pf-c-wizard__outer-wrap">
<div class="pf-c-wizard__inner-wrap">
${this.renderNavigation()}
${this.renderMainSection()}
</div>
${this.renderFooter()}
</div>
</div>`;
}
renderHeader() {
return html`<div class="pf-c-wizard__header">
${this.canCancel ? this.renderHeaderCancelIcon() : 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>`;
}
renderHeaderCancelIcon() {
return html`<button
class="pf-c-button pf-m-plain pf-c-wizard__close"
type="button"
aria-label="${msg("Close")}"
@click=${() => this.dispatchCustomEvent("ak-wizard-nav", { command: "close" })}
>
<i class="fas fa-times" aria-hidden="true"></i>
</button>`;
}
renderNavigation() {
return html`<nav class="pf-c-wizard__nav">
<ol class="pf-c-wizard__nav-list">
${this.stepLabels.map((step) => {
return this.renderNavigationStep(step);
})}
</ol>
</nav>`;
}
renderNavigationStep(step: WizardStepLabel) {
const buttonClasses = {
"pf-c-wizard__nav-link": true,
"pf-m-current": step.active,
};
return html`
<li class="pf-c-wizard__nav-item">
<button
class=${classMap(buttonClasses)}
?disabled=${step.disabled}
@click=${() =>
this.dispatchCustomEvent("ak-wizard-nav", {
command: "goto",
step: step.index,
})}
>
${step.label}
</button>
</li>
`;
}
// This is where the panel is shown. We expect the panel to get its information from an
// independent context.
renderMainSection() {
return html`<main class="pf-c-wizard__main">
<div id="main-content" class="pf-c-wizard__main-body">${this.form()}</div>
</main>`;
}
renderFooter() {
return html`
<footer class="pf-c-wizard__footer">${map(this.buttons, this.renderButtons)}</footer>
`;
}
renderButtons([label, command]: WizardButton) {
switch (command.command) {
case "next":
return this.renderButton(label, "pf-m-primary", command.command);
case "back":
return this.renderButton(label, "pf-m-secondary", command.command);
case "close":
return this.renderLink(label, "pf-m-link");
default:
throw new Error(`Button type not understood: ${command} for ${label}`);
}
}
renderButton(label: string, classname: string, command: string) {
const buttonClasses = { "pf-c-button": true, [classname]: true };
return html`<button
class=${classMap(buttonClasses)}
type="button"
@click=${() => {
this.dispatchCustomEvent("ak-wizard-nav", { command });
}}
>
${label}
</button>`;
}
renderLink(label: string, classname: string) {
const buttonClasses = { "pf-c-button": true, [classname]: true };
return html`<div class="pf-c-wizard__footer-cancel">
<button
class=${classMap(buttonClasses)}
type="button"
@click=${() => this.dispatchCustomEvent("ak-wizard-nav", { command: "close" })}
>
${label}
</button>
</div>`;
}
}
export default AkWizardFrame;