2022-06-25 15:44:17 +00:00
|
|
|
import { EVENT_REFRESH } from "@goauthentik/web/constants";
|
2022-09-06 17:02:40 +00:00
|
|
|
import { setURLParams } from "@goauthentik/web/elements/router/RouteMatch";
|
2022-06-25 15:44:17 +00:00
|
|
|
|
2022-06-15 10:12:26 +00:00
|
|
|
import { t } from "@lingui/macro";
|
|
|
|
|
|
|
|
import { CSSResult, LitElement, TemplateResult, html } from "lit";
|
|
|
|
import { customElement, property, state } from "lit/decorators.js";
|
|
|
|
|
2022-06-25 15:44:17 +00:00
|
|
|
import AKGlobal from "@goauthentik/web/authentik.css";
|
2022-06-15 10:12:26 +00:00
|
|
|
import PFTreeView from "@patternfly/patternfly/components/TreeView/tree-view.css";
|
|
|
|
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
|
|
|
|
|
|
|
export interface TreeViewItem {
|
2022-06-15 18:06:01 +00:00
|
|
|
id?: string;
|
2022-06-15 10:12:26 +00:00
|
|
|
label: string;
|
|
|
|
childItems: TreeViewItem[];
|
|
|
|
parent?: TreeViewItem;
|
|
|
|
level: number;
|
|
|
|
}
|
|
|
|
|
|
|
|
@customElement("ak-treeview-node")
|
|
|
|
export class TreeViewNode extends LitElement {
|
|
|
|
@property({ attribute: false })
|
|
|
|
item?: TreeViewItem;
|
|
|
|
|
|
|
|
@property({ type: Boolean })
|
|
|
|
open = false;
|
|
|
|
|
|
|
|
@property({ attribute: false })
|
|
|
|
host?: TreeView;
|
|
|
|
|
|
|
|
@property()
|
2022-06-15 18:06:01 +00:00
|
|
|
activePath = "";
|
2022-06-15 10:12:26 +00:00
|
|
|
|
|
|
|
@property()
|
|
|
|
separator = "";
|
|
|
|
|
|
|
|
get openable(): boolean {
|
|
|
|
return (this.item?.childItems || []).length > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
get fullPath(): string {
|
|
|
|
const pathItems = [];
|
|
|
|
let item = this.item;
|
|
|
|
while (item) {
|
2022-06-15 18:06:01 +00:00
|
|
|
if (item.id) {
|
|
|
|
pathItems.push(item.id);
|
|
|
|
}
|
2022-06-15 10:12:26 +00:00
|
|
|
item = item.parent;
|
|
|
|
}
|
|
|
|
return pathItems.reverse().join(this.separator);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected createRenderRoot(): Element {
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
firstUpdated(): void {
|
2022-06-15 18:06:01 +00:00
|
|
|
const pathSegments = this.activePath.split(this.separator);
|
2022-06-15 10:12:26 +00:00
|
|
|
const level = this.item?.level || 0;
|
|
|
|
// Ignore the last item as that shouldn't be expanded
|
|
|
|
pathSegments.pop();
|
|
|
|
if (pathSegments[level] == this.item?.id) {
|
|
|
|
this.open = true;
|
|
|
|
}
|
2022-06-15 18:06:01 +00:00
|
|
|
if (this.activePath === this.fullPath && this.host !== undefined) {
|
2022-06-15 10:12:26 +00:00
|
|
|
this.host.activeNode = this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
render(): TemplateResult {
|
|
|
|
const shouldRenderChildren = (this.item?.childItems || []).length > 0 && this.open;
|
|
|
|
return html`
|
|
|
|
<li
|
|
|
|
class="pf-c-tree-view__list-item ${this.open ? "pf-m-expanded" : ""}"
|
|
|
|
role="treeitem"
|
|
|
|
tabindex="0"
|
|
|
|
>
|
|
|
|
<div class="pf-c-tree-view__content">
|
|
|
|
<button
|
|
|
|
class="pf-c-tree-view__node ${this.host?.activeNode === this
|
|
|
|
? "pf-m-current"
|
|
|
|
: ""}"
|
|
|
|
@click=${() => {
|
|
|
|
if (this.host) {
|
|
|
|
this.host.activeNode = this;
|
|
|
|
}
|
|
|
|
setURLParams({ path: this.fullPath });
|
|
|
|
this.dispatchEvent(
|
|
|
|
new CustomEvent(EVENT_REFRESH, {
|
|
|
|
bubbles: true,
|
|
|
|
composed: true,
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<div class="pf-c-tree-view__node-container">
|
|
|
|
${this.openable
|
|
|
|
? html` <button
|
|
|
|
class="pf-c-tree-view__node-toggle"
|
|
|
|
@click=${(e: Event) => {
|
|
|
|
if (this.openable) {
|
|
|
|
this.open = !this.open;
|
|
|
|
e.stopPropagation();
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<span class="pf-c-tree-view__node-toggle-icon">
|
|
|
|
<i class="fas fa-angle-right" aria-hidden="true"></i>
|
|
|
|
</span>
|
|
|
|
</button>`
|
|
|
|
: html``}
|
|
|
|
<span class="pf-c-tree-view__node-icon">
|
|
|
|
<i
|
|
|
|
class="fas ${this.open ? "fa-folder-open" : "fa-folder"}"
|
|
|
|
aria-hidden="true"
|
|
|
|
></i>
|
|
|
|
</span>
|
|
|
|
<span class="pf-c-tree-view__node-text">${this.item?.label}</span>
|
|
|
|
</div>
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
<ul class="pf-c-tree-view__list" role="group" ?hidden=${!shouldRenderChildren}>
|
|
|
|
${this.item?.childItems.map((item) => {
|
|
|
|
return html`<ak-treeview-node
|
|
|
|
.item=${item}
|
2022-06-15 18:06:01 +00:00
|
|
|
activePath=${this.activePath}
|
2022-06-15 10:12:26 +00:00
|
|
|
separator=${this.separator}
|
|
|
|
.host=${this.host}
|
|
|
|
></ak-treeview-node>`;
|
|
|
|
})}
|
|
|
|
</ul>
|
|
|
|
</li>
|
|
|
|
`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@customElement("ak-treeview")
|
|
|
|
export class TreeView extends LitElement {
|
|
|
|
static get styles(): CSSResult[] {
|
|
|
|
return [PFBase, PFTreeView, AKGlobal];
|
|
|
|
}
|
|
|
|
|
|
|
|
@property({ type: Array })
|
|
|
|
items: string[] = [];
|
|
|
|
|
|
|
|
@property()
|
2022-06-15 18:06:01 +00:00
|
|
|
activePath = "";
|
2022-06-15 10:12:26 +00:00
|
|
|
|
|
|
|
@state()
|
|
|
|
activeNode?: TreeViewNode;
|
|
|
|
|
|
|
|
separator = "/";
|
|
|
|
|
2022-06-15 18:06:01 +00:00
|
|
|
createNode(path: string[], parentItem: TreeViewItem, level: number): TreeViewItem {
|
2022-06-15 10:12:26 +00:00
|
|
|
const id = path.shift();
|
2022-06-15 18:06:01 +00:00
|
|
|
const idx = parentItem.childItems.findIndex((e: TreeViewItem) => {
|
2022-06-15 10:12:26 +00:00
|
|
|
return e.id == id;
|
|
|
|
});
|
|
|
|
if (idx < 0) {
|
|
|
|
const item: TreeViewItem = {
|
2022-06-15 18:06:01 +00:00
|
|
|
id: id,
|
2022-06-15 10:12:26 +00:00
|
|
|
label: id || "",
|
|
|
|
childItems: [],
|
|
|
|
level: level,
|
2022-06-15 18:06:01 +00:00
|
|
|
parent: parentItem,
|
2022-06-15 10:12:26 +00:00
|
|
|
};
|
2022-06-15 18:06:01 +00:00
|
|
|
parentItem.childItems.push(item);
|
2022-06-15 10:12:26 +00:00
|
|
|
if (path.length !== 0) {
|
2022-06-15 18:06:01 +00:00
|
|
|
const child = this.createNode(path, item, level + 1);
|
2022-06-15 10:12:26 +00:00
|
|
|
child.parent = item;
|
|
|
|
}
|
|
|
|
return item;
|
|
|
|
} else {
|
2022-06-15 18:06:01 +00:00
|
|
|
const child = this.createNode(path, parentItem.childItems[idx], level + 1);
|
2022-06-15 12:25:01 +00:00
|
|
|
return child;
|
2022-06-15 10:12:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-15 18:06:01 +00:00
|
|
|
parse(data: string[]): TreeViewItem {
|
|
|
|
const rootItem: TreeViewItem = {
|
|
|
|
id: undefined,
|
|
|
|
label: t`Root`,
|
|
|
|
childItems: [],
|
|
|
|
level: -1,
|
|
|
|
};
|
2022-06-15 10:12:26 +00:00
|
|
|
for (let i = 0; i < data.length; i++) {
|
|
|
|
const path: string = data[i];
|
|
|
|
const split: string[] = path.split(this.separator);
|
2022-06-15 18:06:01 +00:00
|
|
|
this.createNode(split, rootItem, 0);
|
2022-06-15 10:12:26 +00:00
|
|
|
}
|
2022-06-15 18:06:01 +00:00
|
|
|
return rootItem;
|
2022-06-15 10:12:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
render(): TemplateResult {
|
2022-06-15 18:06:01 +00:00
|
|
|
const rootItem = this.parse(this.items);
|
2022-06-15 10:12:26 +00:00
|
|
|
return html`<div class="pf-c-tree-view pf-m-guides">
|
|
|
|
<ul class="pf-c-tree-view__list" role="tree">
|
|
|
|
<!-- @ts-ignore -->
|
|
|
|
<ak-treeview-node
|
2022-06-15 18:06:01 +00:00
|
|
|
.item=${rootItem}
|
|
|
|
activePath=${this.activePath}
|
2022-06-15 10:12:26 +00:00
|
|
|
?open=${true}
|
|
|
|
separator=${this.separator}
|
|
|
|
.host=${this}
|
|
|
|
></ak-treeview-node>
|
|
|
|
</ul>
|
|
|
|
</div>`;
|
|
|
|
}
|
|
|
|
}
|