web/elements: trigger search select data update on connected callback

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2023-01-02 10:26:52 +01:00
parent 042cd0b2cb
commit 9564894eda
No known key found for this signature in database
4 changed files with 89 additions and 39 deletions

View File

@ -154,6 +154,24 @@ export class IdentificationStageForm extends ModelForm<IdentificationStage, stri
${t`When enabled, user fields are matched regardless of their casing.`} ${t`When enabled, user fields are matched regardless of their casing.`}
</p> </p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal name="showMatchedUser">
<div class="pf-c-check">
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.showMatchedUser, true)}
/>
<label class="pf-c-check__label"> ${t`Show matched user`} </label>
</div>
<p class="pf-c-form__helper-text">
${t`When a valid username/email has been entered, and this option is enabled, the user's username and avatar will be shown. Otherwise, the text that the user entered will be shown.`}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group>
<span slot="header"> ${t`Source settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal <ak-form-element-horizontal
label=${t`Sources`} label=${t`Sources`}
?required=${true} ?required=${true}
@ -210,19 +228,6 @@ export class IdentificationStageForm extends ModelForm<IdentificationStage, stri
${t`By default, only icons are shown for sources. Enable this to show their full names.`} ${t`By default, only icons are shown for sources. Enable this to show their full names.`}
</p> </p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal name="showMatchedUser">
<div class="pf-c-check">
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.showMatchedUser, true)}
/>
<label class="pf-c-check__label"> ${t`Show matched user`} </label>
</div>
<p class="pf-c-form__helper-text">
${t`When a valid username/email has been entered, and this option is enabled, the user's username and avatar will be shown. Otherwise, the text that the user entered will be shown.`}
</p>
</ak-form-element-horizontal>
</div> </div>
</ak-form-group> </ak-form-group>
<ak-form-group> <ak-form-group>

View File

@ -1,5 +1,7 @@
import { EVENT_REFRESH } from "@goauthentik/common/constants";
import { groupBy } from "@goauthentik/common/utils"; import { groupBy } from "@goauthentik/common/utils";
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement } from "@goauthentik/elements/Base";
import { PreventFormSubmit } from "@goauthentik/elements/forms/Form";
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
@ -18,7 +20,7 @@ export class SearchSelect<T> extends AKElement {
query?: string; query?: string;
@property({ attribute: false }) @property({ attribute: false })
objects: T[] = []; objects?: T[];
@property({ attribute: false }) @property({ attribute: false })
selectedObject?: T; selectedObject?: T;
@ -54,17 +56,6 @@ export class SearchSelect<T> extends AKElement {
@property({ attribute: false }) @property({ attribute: false })
selected?: (element: T, elements: T[]) => boolean; selected?: (element: T, elements: T[]) => boolean;
firstUpdated(): void {
this.fetchObjects(this.query).then((objects) => {
this.objects = objects;
this.objects.forEach((obj) => {
if (this.selected && this.selected(obj, this.objects)) {
this.selectedObject = obj;
}
});
});
}
@property() @property()
emptyOption = "---------"; emptyOption = "---------";
@ -82,15 +73,40 @@ export class SearchSelect<T> extends AKElement {
this.dropdownContainer = document.createElement("div"); this.dropdownContainer = document.createElement("div");
} }
toForm(): unknown {
if (!this.objects) {
return new PreventFormSubmit(t`Loading options...`);
}
return this.value(this.selectedObject) || "";
}
firstUpdated(): void {
this.updateData();
}
updateData(): void {
this.fetchObjects(this.query).then((objects) => {
this.objects = objects;
this.objects.forEach((obj) => {
if (this.selected && this.selected(obj, this.objects || [])) {
this.selectedObject = obj;
}
});
});
}
connectedCallback(): void { connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
this.dropdownContainer = document.createElement("div"); this.dropdownContainer = document.createElement("div");
this.dropdownContainer.dataset["managedBy"] = "ak-search-select"; this.dropdownContainer.dataset["managedBy"] = "ak-search-select";
document.body.append(this.dropdownContainer); document.body.append(this.dropdownContainer);
this.updateData();
this.addEventListener(EVENT_REFRESH, this.updateData);
} }
disconnectedCallback(): void { disconnectedCallback(): void {
super.disconnectedCallback(); super.disconnectedCallback();
this.removeEventListener(EVENT_REFRESH, this.updateData);
this.dropdownContainer.remove(); this.dropdownContainer.remove();
} }
@ -106,6 +122,9 @@ export class SearchSelect<T> extends AKElement {
* the pf-c-dropdown CSS needs to be loaded on the body. * the pf-c-dropdown CSS needs to be loaded on the body.
*/ */
renderMenu(): void { renderMenu(): void {
if (!this.objects) {
return;
}
const pos = this.getBoundingClientRect(); const pos = this.getBoundingClientRect();
let groupedItems = this.groupBy(this.objects); let groupedItems = this.groupBy(this.objects);
let shouldRenderGroups = true; let shouldRenderGroups = true;
@ -209,7 +228,7 @@ export class SearchSelect<T> extends AKElement {
placeholder=${this.placeholder} placeholder=${this.placeholder}
@input=${(ev: InputEvent) => { @input=${(ev: InputEvent) => {
this.query = (ev.target as HTMLInputElement).value; this.query = (ev.target as HTMLInputElement).value;
this.firstUpdated(); this.updateData();
}} }}
@focus=${() => { @focus=${() => {
this.open = true; this.open = true;

View File

@ -78,18 +78,6 @@ export abstract class AKChart<T> extends AKElement {
constructor() { constructor() {
super(); super();
window.addEventListener("resize", () => {
if (this.chart) {
this.chart.resize();
}
});
window.addEventListener(EVENT_REFRESH, () => {
this.apiRequest().then((r: T) => {
if (!this.chart) return;
this.chart.data = this.getChartData(r);
this.chart.update();
});
});
const matcher = window.matchMedia("(prefers-color-scheme: light)"); const matcher = window.matchMedia("(prefers-color-scheme: light)");
const handler = (ev?: MediaQueryListEvent) => { const handler = (ev?: MediaQueryListEvent) => {
if (ev?.matches || matcher.matches) { if (ev?.matches || matcher.matches) {
@ -103,6 +91,33 @@ export abstract class AKChart<T> extends AKElement {
handler(); handler();
} }
connectedCallback(): void {
super.connectedCallback();
window.addEventListener("resize", this.resizeHandler);
this.addEventListener(EVENT_REFRESH, this.refreshHandler);
}
disconnectedCallback(): void {
super.disconnectedCallback();
window.removeEventListener("resize", this.resizeHandler);
this.removeEventListener(EVENT_REFRESH, this.refreshHandler);
}
refreshHandler(): void {
this.apiRequest().then((r: T) => {
if (!this.chart) return;
this.chart.data = this.getChartData(r);
this.chart.update();
});
}
resizeHandler(): void {
if (!this.chart) {
return;
}
this.chart.resize();
}
firstUpdated(): void { firstUpdated(): void {
this.apiRequest().then((r) => { this.apiRequest().then((r) => {
const canvas = this.shadowRoot?.querySelector<HTMLCanvasElement>("canvas"); const canvas = this.shadowRoot?.querySelector<HTMLCanvasElement>("canvas");

View File

@ -23,6 +23,11 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { ResponseError, ValidationError } from "@goauthentik/api"; import { ResponseError, ValidationError } from "@goauthentik/api";
export class PreventFormSubmit {
// Stub class which can be returned by form elements to prevent the form from submitting
constructor(public message: string) {}
}
export class APIError extends Error { export class APIError extends Error {
constructor(public response: ValidationError) { constructor(public response: ValidationError) {
super(); super();
@ -162,11 +167,17 @@ export class Form<T> extends AKElement {
json[element.name] = element.checked; json[element.name] = element.checked;
} else if (element.tagName.toLowerCase() === "ak-search-select") { } else if (element.tagName.toLowerCase() === "ak-search-select") {
const select = element as unknown as SearchSelect<unknown>; const select = element as unknown as SearchSelect<unknown>;
let value: unknown;
try { try {
json[element.name] = select.value(select.selectedObject) || ""; value = select.toForm();
} catch { } catch {
console.debug("authentik/form: SearchSelect.value error"); console.debug("authentik/form: SearchSelect.value error");
return;
} }
if (value instanceof PreventFormSubmit) {
throw new Error(value.message);
}
json[element.name] = value;
} else { } else {
for (let v = 0; v < values.length; v++) { for (let v = 0; v < values.length; v++) {
this.serializeFieldRecursive(element, values[v], json); this.serializeFieldRecursive(element, values[v], json);