Web/cleanup/empty state better slot handling (#14289)
* web: Add InvalidationFlow to Radius Provider dialogues
## What
- Bugfix: adds the InvalidationFlow to the Radius Provider dialogues
- Repairs: `{"invalidation_flow":["This field is required."]}` message, which was *not* propagated
to the Notification.
- Nitpick: Pretties `?foo=${true}` expressions: `s/\?([^=]+)=\$\{true\}/\1/`
## Note
Yes, I know I'm going to have to do more magic when we harmonize the forms, and no, I didn't add the
Property Mappings to the wizard, and yes, I know I'm going to have pain with the *new* version of
the wizard. But this is a serious bug; you can't make Radius servers with *either* of the current
dialogues at the moment.
* This (temporary) change is needed to prevent the unit tests from failing.
\# What
\# Why
\# How
\# Designs
\# Test Steps
\# Other Notes
* Revert "This (temporary) change is needed to prevent the unit tests from failing."
This reverts commit dddde09be5.
* web: remove Lit syntax from always true attributes
## What
Replaces instances of `?loading=${true}` and `?loading="${true}"` with `loading`
## Why
The Lit syntax is completely unnecessary when the attribute's state is constant, and it's a few
(just a few) extra CPU cycles for Lit to process that.
More to the point, it annoys me.
## How
```
$ perl -pi.bak -e 's/\?loading=\$\{true\}/loading/' $(rg -l '\?loading=\$\{true\}')
$ find . -name '*.bak' -exec rm {} \;
$ perl -pi.bak -e 's/\?loading="\$\{true\}"/loading/' $(rg -l '\?loading="\$\{true\}"')
$ find . -name '*.bak' -exec rm {} \;
```
* Prettier had opinions
* web: move optional textual information out of attributes and into slots
## What
Replaces instances of:
```
<ak-empty-state header=${msg(...)}></ak-empty-state>
```
with
```
<ak-empty-state><span slot="header">${msg(...)}</span></ak-empty-state>
```
## Why
1. It's correct.
2. It lets us elide the decorations for any slots we aren't using.
3. It's preparation for moving to Patternfly 5
4. It annoyed me.
## How
Since we already have Patternfly Elements installed, we have access to the PFE-Core, which has the
unbelievable useful `SlotsController`. Using it, I created a conditional render template that will
only put in the header, body, and primary slots if there is something in the lightDOM requesting
those slots. The conditional template will still put the spinner in if the header is not provided
but the loading state is true.
I then had to edit all the places where this is used. For about 30 of them, this script sufficed:
```
perl -pi.bak -e 's/header="?(\$\{msg\([^)]+\)\})"?>/><span slot="header">\1<\/span>/' \
$(rg -l `<ak-empty-state[^>]header')
```
The other six had to be done by hand. I have tested a handful of the automatic ones, and all of the
ones that were edited manually. I'm pleasantly surprised that the textual rules [are inherited by
the slots as expected](https://htmlwithsuperpowers.netlify.app/styling/inheritable.htm).
This commit is contained in:
@ -88,7 +88,8 @@ export class RecentEventsCard extends Table<Event> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return super.renderEmpty(
|
return super.renderEmpty(
|
||||||
html`<ak-empty-state header=${msg("No Events found.")}>
|
html`<ak-empty-state
|
||||||
|
><span slot="header">${msg("No Events found.")}</span>
|
||||||
<div slot="body">${msg("No matching events could be found.")}</div>
|
<div slot="body">${msg("No matching events could be found.")}</div>
|
||||||
</ak-empty-state>`,
|
</ak-empty-state>`,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -115,7 +115,8 @@ export class ApplicationWizardBindingsStep extends ApplicationWizardStep {
|
|||||||
.columns=${COLUMNS}
|
.columns=${COLUMNS}
|
||||||
.content=${[]}
|
.content=${[]}
|
||||||
></ak-select-table>
|
></ak-select-table>
|
||||||
<ak-empty-state header=${msg("No bound policies.")} icon="pf-icon-module">
|
<ak-empty-state icon="pf-icon-module"
|
||||||
|
><span slot="header">${msg("No bound policies.")} </span>
|
||||||
<div slot="body">${msg("No policies are currently bound to this object.")}</div>
|
<div slot="body">${msg("No policies are currently bound to this object.")}</div>
|
||||||
<div slot="primary">
|
<div slot="primary">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@ -135,7 +135,8 @@ export class BoundStagesList extends Table<FlowStageBinding> {
|
|||||||
|
|
||||||
renderEmpty(): TemplateResult {
|
renderEmpty(): TemplateResult {
|
||||||
return super.renderEmpty(
|
return super.renderEmpty(
|
||||||
html`<ak-empty-state header=${msg("No Stages bound")} icon="pf-icon-module">
|
html`<ak-empty-state icon="pf-icon-module">
|
||||||
|
<span slot="header">${msg("No Stages bound")}</span>
|
||||||
<div slot="body">${msg("No stages are currently bound to this flow.")}</div>
|
<div slot="body">${msg("No stages are currently bound to this flow.")}</div>
|
||||||
<div slot="primary">
|
<div slot="primary">
|
||||||
<ak-stage-wizard
|
<ak-stage-wizard
|
||||||
|
|||||||
@ -198,7 +198,8 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
|||||||
|
|
||||||
renderEmpty(): TemplateResult {
|
renderEmpty(): TemplateResult {
|
||||||
return super.renderEmpty(
|
return super.renderEmpty(
|
||||||
html`<ak-empty-state header=${msg("No Policies bound.")} icon="pf-icon-module">
|
html`<ak-empty-state icon="pf-icon-module"
|
||||||
|
><span slot="header">${msg("No Policies bound.")}</span>
|
||||||
<div slot="body">${msg("No policies are currently bound to this object.")}</div>
|
<div slot="body">${msg("No policies are currently bound to this object.")}</div>
|
||||||
<div slot="primary">
|
<div slot="primary">
|
||||||
<ak-policy-wizard
|
<ak-policy-wizard
|
||||||
|
|||||||
@ -94,7 +94,8 @@ export class ObjectChangelog extends Table<Event> {
|
|||||||
|
|
||||||
renderEmpty(): TemplateResult {
|
renderEmpty(): TemplateResult {
|
||||||
return super.renderEmpty(
|
return super.renderEmpty(
|
||||||
html`<ak-empty-state header=${msg("No Events found.")}>
|
html`<ak-empty-state
|
||||||
|
><span slot="header">${msg("No Events found.")}</span>
|
||||||
<div slot="body">${msg("No matching events could be found.")}</div>
|
<div slot="body">${msg("No matching events could be found.")}</div>
|
||||||
</ak-empty-state>`,
|
</ak-empty-state>`,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -66,7 +66,8 @@ export class UserEvents extends Table<Event> {
|
|||||||
|
|
||||||
renderEmpty(): TemplateResult {
|
renderEmpty(): TemplateResult {
|
||||||
return super.renderEmpty(
|
return super.renderEmpty(
|
||||||
html`<ak-empty-state header=${msg("No Events found.")}>
|
html`<ak-empty-state
|
||||||
|
><span slot="header">${msg("No Events found.")}</span>
|
||||||
<div slot="body">${msg("No matching events could be found.")}</div>
|
<div slot="body">${msg("No matching events could be found.")}</div>
|
||||||
</ak-empty-state>`,
|
</ak-empty-state>`,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { AKElement } from "@goauthentik/elements/Base";
|
|||||||
import "@goauthentik/elements/Spinner";
|
import "@goauthentik/elements/Spinner";
|
||||||
import { type SlottedTemplateResult, type Spread } from "@goauthentik/elements/types";
|
import { type SlottedTemplateResult, type Spread } from "@goauthentik/elements/types";
|
||||||
import { spread } from "@open-wc/lit-helpers";
|
import { spread } from "@open-wc/lit-helpers";
|
||||||
|
import { SlotController } from "@patternfly/pfe-core/controllers/slot-controller.js";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { css, html, nothing } from "lit";
|
import { css, html, nothing } from "lit";
|
||||||
@ -33,6 +34,8 @@ export class EmptyState extends AKElement implements IEmptyState {
|
|||||||
@property()
|
@property()
|
||||||
header?: string;
|
header?: string;
|
||||||
|
|
||||||
|
slots = new SlotController(this, "header", "body", "primary");
|
||||||
|
|
||||||
static get styles() {
|
static get styles() {
|
||||||
return [
|
return [
|
||||||
PFBase,
|
PFBase,
|
||||||
@ -48,6 +51,12 @@ export class EmptyState extends AKElement implements IEmptyState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const showHeader = this.loading || this.slots.hasSlotted("header");
|
||||||
|
const header = () =>
|
||||||
|
this.slots.hasSlotted("header")
|
||||||
|
? html`<slot name="header"></slot>`
|
||||||
|
: html`<span>${msg("Loading")}</span>`;
|
||||||
|
|
||||||
return html`<div class="pf-c-empty-state ${this.fullHeight && "pf-m-full-height"}">
|
return html`<div class="pf-c-empty-state ${this.fullHeight && "pf-m-full-height"}">
|
||||||
<div class="pf-c-empty-state__content">
|
<div class="pf-c-empty-state__content">
|
||||||
${this.loading
|
${this.loading
|
||||||
@ -59,15 +68,17 @@ export class EmptyState extends AKElement implements IEmptyState {
|
|||||||
"fa-question-circle"} pf-c-empty-state__icon"
|
"fa-question-circle"} pf-c-empty-state__icon"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
></i>`}
|
></i>`}
|
||||||
<h1 class="pf-c-title pf-m-lg">
|
${showHeader ? html` <h1 class="pf-c-title pf-m-lg">${header()}</h1>` : nothing}
|
||||||
${this.loading && this.header === undefined ? msg("Loading") : this.header}
|
${this.slots.hasSlotted("body")
|
||||||
</h1>
|
? html` <div class="pf-c-empty-state__body">
|
||||||
<div class="pf-c-empty-state__body">
|
<slot name="body"></slot>
|
||||||
<slot name="body"></slot>
|
</div>`
|
||||||
</div>
|
: nothing}
|
||||||
<div class="pf-c-empty-state__primary">
|
${this.slots.hasSlotted("primary")
|
||||||
<slot name="primary"></slot>
|
? html` <div class="pf-c-empty-state__primary">
|
||||||
</div>
|
<slot name="primary"></slot>
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -200,7 +200,8 @@ export abstract class AKChart<T> extends AKElement {
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
${this.error
|
${this.error
|
||||||
? html`
|
? html`
|
||||||
<ak-empty-state header="${msg("Failed to fetch data.")}" icon="fa-times">
|
<ak-empty-state icon="fa-times"
|
||||||
|
><span slot="header">${msg("Failed to fetch data.")}</span>
|
||||||
<p slot="body">${pluckErrorDetail(this.error)}</p>
|
<p slot="body">${pluckErrorDetail(this.error)}</p>
|
||||||
</ak-empty-state>
|
</ak-empty-state>
|
||||||
`
|
`
|
||||||
|
|||||||
@ -40,7 +40,9 @@ export class LogViewer extends Table<LogEvent> {
|
|||||||
|
|
||||||
renderEmpty(): TemplateResult {
|
renderEmpty(): TemplateResult {
|
||||||
return super.renderEmpty(
|
return super.renderEmpty(
|
||||||
html`<ak-empty-state header=${msg("No log messages.")}> </ak-empty-state>`,
|
html`<ak-empty-state
|
||||||
|
><span slot="header">${msg("No log messages.")}</span>
|
||||||
|
</ak-empty-state>`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -163,7 +163,8 @@ export class NotificationDrawer extends AKElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderEmpty() {
|
renderEmpty() {
|
||||||
return html`<ak-empty-state header=${msg("No notifications found.")}>
|
return html`<ak-empty-state
|
||||||
|
><span slot="header">${msg("No notifications found.")}</span>
|
||||||
<div slot="body">${msg("You don't have any notifications currently.")}</div>
|
<div slot="body">${msg("You don't have any notifications currently.")}</div>
|
||||||
</ak-empty-state>`;
|
</ak-empty-state>`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -288,7 +288,9 @@ export abstract class Table<T> extends AKElement implements TableLike {
|
|||||||
return html`<tr role="row">
|
return html`<tr role="row">
|
||||||
<td role="cell" colspan="25">
|
<td role="cell" colspan="25">
|
||||||
<div class="pf-l-bullseye">
|
<div class="pf-l-bullseye">
|
||||||
<ak-empty-state loading header=${msg("Loading")}></ak-empty-state>
|
<ak-empty-state loading
|
||||||
|
><span slot="header">${msg("Loading")}</span></ak-empty-state
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
@ -300,8 +302,9 @@ export abstract class Table<T> extends AKElement implements TableLike {
|
|||||||
<td role="cell" colspan="8">
|
<td role="cell" colspan="8">
|
||||||
<div class="pf-l-bullseye">
|
<div class="pf-l-bullseye">
|
||||||
${inner ??
|
${inner ??
|
||||||
html`<ak-empty-state header="${msg("No objects found.")}"
|
html`<ak-empty-state
|
||||||
><div slot="primary">${this.renderObjectCreate()}</div>
|
><span slot="header">${msg("No objects found.")}</span> >
|
||||||
|
<div slot="primary">${this.renderObjectCreate()}</div>
|
||||||
</ak-empty-state>`}
|
</ak-empty-state>`}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@ -316,7 +319,8 @@ export abstract class Table<T> extends AKElement implements TableLike {
|
|||||||
renderError(): SlottedTemplateResult {
|
renderError(): SlottedTemplateResult {
|
||||||
if (!this.error) return nothing;
|
if (!this.error) return nothing;
|
||||||
|
|
||||||
return html`<ak-empty-state header="${msg("Failed to fetch objects.")}" icon="fa-ban">
|
return html`<ak-empty-state icon="fa-ban"
|
||||||
|
><span slot="header">${msg("Failed to fetch objects.")}</span>
|
||||||
<div slot="body">${pluckErrorDetail(this.error)}</div>
|
<div slot="body">${pluckErrorDetail(this.error)}</div>
|
||||||
</ak-empty-state>`;
|
</ak-empty-state>`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,11 @@ describe("ak-empty-state", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should render the default loader", async () => {
|
it("should render the default loader", async () => {
|
||||||
render(html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`);
|
render(
|
||||||
|
html`<ak-empty-state loading
|
||||||
|
><span slot="header">${msg("Loading")}</span>
|
||||||
|
</ak-empty-state>`,
|
||||||
|
);
|
||||||
|
|
||||||
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
||||||
await expect(empty).toExist();
|
await expect(empty).toExist();
|
||||||
@ -29,7 +33,11 @@ describe("ak-empty-state", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should handle standard boolean", async () => {
|
it("should handle standard boolean", async () => {
|
||||||
render(html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`);
|
render(
|
||||||
|
html`<ak-empty-state loading
|
||||||
|
><span slot="header">${msg("Loading")}</span>
|
||||||
|
</ak-empty-state>`,
|
||||||
|
);
|
||||||
|
|
||||||
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
||||||
await expect(empty).toExist();
|
await expect(empty).toExist();
|
||||||
@ -39,7 +47,11 @@ describe("ak-empty-state", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should render a static empty state", async () => {
|
it("should render a static empty state", async () => {
|
||||||
render(html`<ak-empty-state header=${msg("No messages found")}> </ak-empty-state>`);
|
render(
|
||||||
|
html`<ak-empty-state
|
||||||
|
><span slot="header">${msg("No messages found")}</span>
|
||||||
|
</ak-empty-state>`,
|
||||||
|
);
|
||||||
|
|
||||||
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
||||||
await expect(empty).toExist();
|
await expect(empty).toExist();
|
||||||
@ -51,7 +63,8 @@ describe("ak-empty-state", () => {
|
|||||||
|
|
||||||
it("should render a slotted message", async () => {
|
it("should render a slotted message", async () => {
|
||||||
render(
|
render(
|
||||||
html`<ak-empty-state header=${msg("No messages found")}>
|
html`<ak-empty-state
|
||||||
|
><span slot="header">${msg("No messages found")}</span>
|
||||||
<p slot="body">Try again with a different filter</p>
|
<p slot="body">Try again with a different filter</p>
|
||||||
</ak-empty-state>`,
|
</ak-empty-state>`,
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user