web: provide tier-3 click capability.
This commit enables the search function to work when changing from one tier 3 link to another in the tier 2 category (Providers, Events, Stages, etc.). Because the router ignores such changes, we must bring those changes to the attention of the receiver by hand. This commit makes the recepient Table object locatable with a data attribute tag and a painstakingly researched path to its most common location in our code. It then checks any `anchor:click` event from the Sidebar to see if its likely to be elided by the router. If it is, we take over, identify the Table object, set or clear its search state to the desired state, and call the `.fetch()` method on the Table to initiate a new search with that state. This is, frankly, _gross_. The URL and internal states mirror the actual desired state more or less by accident. But it's what we've got to work with at the moment.
This commit is contained in:
parent
5b898bef01
commit
f2834cc7e2
|
@ -160,14 +160,14 @@ export class AkAdminSidebar extends AKElement {
|
||||||
[
|
[
|
||||||
reload,
|
reload,
|
||||||
msg(
|
msg(
|
||||||
str`You're currently impersonating ${this.impersonation}. Click to stop.`
|
str`You're currently impersonating ${this.impersonation}. Click to stop.`,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const enterpriseMenu: LocalSidebarEntry[] = this.config?.capabilities.includes(
|
const enterpriseMenu: LocalSidebarEntry[] = this.config?.capabilities.includes(
|
||||||
CapabilitiesEnum.IsEnterprise
|
CapabilitiesEnum.IsEnterprise,
|
||||||
)
|
)
|
||||||
? [[null, msg("Enterprise"), null, [["/enterprise/licenses", msg("Licenses")]]]]
|
? [[null, msg("Enterprise"), null, [["/enterprise/licenses", msg("Licenses")]]]]
|
||||||
: [];
|
: [];
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
import { ROUTE_SEPARATOR } from "@goauthentik/common/constants";
|
||||||
import { AKElement } from "@goauthentik/elements/Base";
|
import { AKElement } from "@goauthentik/elements/Base";
|
||||||
|
import { findTable } from "@goauthentik/elements/table/TablePage";
|
||||||
|
|
||||||
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators.js";
|
import { customElement, property, state } from "lit/decorators.js";
|
||||||
|
@ -113,6 +115,8 @@ export class SidebarItems extends AKElement {
|
||||||
super();
|
super();
|
||||||
this.renderItem = this.renderItem.bind(this);
|
this.renderItem = this.renderItem.bind(this);
|
||||||
this.toggleExpand = this.toggleExpand.bind(this);
|
this.toggleExpand = this.toggleExpand.bind(this);
|
||||||
|
this.onHashChange = this.onHashChange.bind(this);
|
||||||
|
this.reclick = this.reclick.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
|
@ -153,6 +157,46 @@ export class SidebarItems extends AKElement {
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is gross and feels like 2007: using a path from the root through the shadowDoms (see
|
||||||
|
// `TablePage:findTable()`), this code finds the element that *should* be triggered by an event
|
||||||
|
// on the URL, and forcibly injects the text of the search and the click of the search button.
|
||||||
|
|
||||||
|
reclick(ev: Event, path: string) {
|
||||||
|
const oldPath = window.location.hash.split(ROUTE_SEPARATOR)[0];
|
||||||
|
const [curPath, ...curSearchComponents] = path.split(ROUTE_SEPARATOR);
|
||||||
|
const curSearch: string =
|
||||||
|
curSearchComponents.length > 0 ? curSearchComponents.join(ROUTE_SEPARATOR) : "";
|
||||||
|
|
||||||
|
if (curPath !== oldPath) {
|
||||||
|
// A Tier 1 or Tier 2 change should be handled by the router. (So should a Tier 3
|
||||||
|
// change, but... here we are.)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const table = findTable();
|
||||||
|
if (!table) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always wrap the minimal exceptional code possible in an IIFE and supply the failure
|
||||||
|
// alternative. Turn exceptions into expressions with the smallest functional rewind
|
||||||
|
// whenever possible.
|
||||||
|
const search = (() => {
|
||||||
|
try {
|
||||||
|
return curSearch ? JSON.parse(decodeURIComponent(curSearch)) : { search: "" };
|
||||||
|
} catch {
|
||||||
|
return { search: "" };
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
if ("search" in search) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
table.search = search.search;
|
||||||
|
table.fetch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
const lightThemed = { "pf-m-light": this.activeTheme === UiThemeEnum.Light };
|
const lightThemed = { "pf-m-light": this.activeTheme === UiThemeEnum.Light };
|
||||||
|
|
||||||
|
@ -213,8 +257,10 @@ export class SidebarItems extends AKElement {
|
||||||
${entry.label}
|
${entry.label}
|
||||||
</a>`;
|
</a>`;
|
||||||
}
|
}
|
||||||
|
const path = `${entry.attributes?.isAbsoluteLink ? "" : "#"}${entry.path}`;
|
||||||
return html` <a
|
return html` <a
|
||||||
href="${entry.attributes?.isAbsoluteLink ? "" : "#"}${entry.path}"
|
href=${path}
|
||||||
|
@click=${(ev: Event) => this.reclick(ev, path)}
|
||||||
class=${classMap(this.getLinkClasses(entry))}
|
class=${classMap(this.getLinkClasses(entry))}
|
||||||
>
|
>
|
||||||
${entry.label}
|
${entry.label}
|
||||||
|
@ -246,9 +292,11 @@ export class SidebarItems extends AKElement {
|
||||||
|
|
||||||
renderLinkAndChildren(entry: SidebarEntry): TemplateResult {
|
renderLinkAndChildren(entry: SidebarEntry): TemplateResult {
|
||||||
const handler = () => this.toggleExpand(entry);
|
const handler = () => this.toggleExpand(entry);
|
||||||
|
const path = `${entry.attributes?.isAbsoluteLink ? "" : "#"}${entry.path}`;
|
||||||
return html` <div class="pf-c-nav__link">
|
return html` <div class="pf-c-nav__link">
|
||||||
<a
|
<a
|
||||||
href="${entry.attributes?.isAbsoluteLink ? "" : "#"}${entry.path}"
|
href=${path}
|
||||||
|
@click=${(ev: Event) => this.reclick(ev, path)}
|
||||||
class="ak-nav__link"
|
class="ak-nav__link"
|
||||||
>
|
>
|
||||||
${entry.label}
|
${entry.label}
|
||||||
|
|
|
@ -20,6 +20,11 @@ export abstract class TablePage<T> extends Table<T> {
|
||||||
return super.styles.concat(PFPage, PFContent, PFSidebar);
|
return super.styles.concat(PFPage, PFContent, PFSidebar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.dataset.akApiTable = "true";
|
||||||
|
}
|
||||||
|
|
||||||
renderSidebarBefore(): TemplateResult {
|
renderSidebarBefore(): TemplateResult {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
@ -92,3 +97,18 @@ export abstract class TablePage<T> extends Table<T> {
|
||||||
${this.renderSectionAfter()}`;
|
${this.renderSectionAfter()}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This painstakingly researched path is nonetheless surprisingly robust; it works for every extant
|
||||||
|
// TablePage, but only because Jens has been utterly consistent in where he puts his TablePage
|
||||||
|
// elements with respect to the Interface object. If we ever re-arrange this code, we're going
|
||||||
|
// to have to re-arrange this as well.
|
||||||
|
|
||||||
|
export function findTable<T, U extends TablePage<T>>(): U | undefined {
|
||||||
|
return (
|
||||||
|
(document.body
|
||||||
|
?.querySelector("[data-ak-interface-root]")
|
||||||
|
?.shadowRoot?.querySelector("ak-locale-context")
|
||||||
|
?.querySelector("ak-router-outlet")
|
||||||
|
?.shadowRoot?.querySelector("[data-ak-api-table]") as U) ?? undefined
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Reference in a new issue