web/elements: add new API to store attributes in URL, use for table and tabs

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-11-18 21:01:58 +01:00
parent c363b1cfde
commit 465898c7d0
6 changed files with 83 additions and 13 deletions

View File

@ -81,3 +81,12 @@ migrate:
run: run:
go run -v cmd/server/main.go go run -v cmd/server/main.go
web: web-lint-fix web-lint
web-lint-fix:
cd web && npm run prettier
web-lint:
cd web && npm run lint
cd web && npm run lit-analyse

View File

@ -9,6 +9,7 @@ import PFTabs from "@patternfly/patternfly/components/Tabs/tabs.css";
import PFGlobal from "@patternfly/patternfly/patternfly-base.css"; import PFGlobal from "@patternfly/patternfly/patternfly-base.css";
import { CURRENT_CLASS, ROUTE_SEPARATOR } from "../constants"; import { CURRENT_CLASS, ROUTE_SEPARATOR } from "../constants";
import { getURLParams, updateURLParams } from "./router/RouteMatch";
@customElement("ak-tabs") @customElement("ak-tabs")
export class Tabs extends LitElement { export class Tabs extends LitElement {
@ -65,9 +66,9 @@ export class Tabs extends LitElement {
onClick(slot?: string): void { onClick(slot?: string): void {
this.currentPage = slot; this.currentPage = slot;
const currentUrl = window.location.hash.slice(1, Infinity).split(ROUTE_SEPARATOR)[0]; updateURLParams({
const newUrl = `#${currentUrl};${slot}`; page: slot,
history.replaceState(undefined, "", newUrl); });
} }
renderTab(page: Element): TemplateResult { renderTab(page: Element): TemplateResult {
@ -81,18 +82,20 @@ export class Tabs extends LitElement {
render(): TemplateResult { render(): TemplateResult {
const pages = Array.from(this.querySelectorAll("[slot^='page-']")); const pages = Array.from(this.querySelectorAll("[slot^='page-']"));
if (window.location.hash.includes(ROUTE_SEPARATOR)) {
const params = getURLParams();
if ("page" in params) {
if (this.querySelector(`[slot='${params.page}']`) !== null) {
// To update the URL to match with the current slot
this.currentPage = params.page;
}
}
}
if (!this.currentPage) { if (!this.currentPage) {
if (pages.length < 1) { if (pages.length < 1) {
return html`<h1>${t`no tabs defined`}</h1>`; return html`<h1>${t`no tabs defined`}</h1>`;
} }
let wantedPage = pages[0].attributes.getNamedItem("slot")?.value; const wantedPage = pages[0].attributes.getNamedItem("slot")?.value;
if (window.location.hash.includes(ROUTE_SEPARATOR)) {
const urlParts = window.location.hash.slice(1, Infinity).split(ROUTE_SEPARATOR);
if (this.querySelector(`[slot='${urlParts[1]}']`) !== null) {
// To update the URL to match with the current slot
wantedPage = urlParts[1];
}
}
this.onClick(wantedPage); this.onClick(wantedPage);
} }
return html`<div class="pf-c-tabs ${this.vertical ? "pf-m-vertical pf-m-box" : ""}"> return html`<div class="pf-c-tabs ${this.vertical ? "pf-m-vertical pf-m-box" : ""}">

View File

@ -1,5 +1,6 @@
import { TemplateResult } from "lit"; import { TemplateResult } from "lit";
import { ROUTE_SEPARATOR } from "../../constants";
import { Route } from "./Route"; import { Route } from "./Route";
export class RouteMatch { export class RouteMatch {
@ -22,3 +23,44 @@ export class RouteMatch {
)}>`; )}>`;
} }
} }
export function getURLParam<T>(key: string, fallback: T): T {
const params = getURLParams();
if (key in params) {
return params[key] as T;
}
return fallback;
}
export function getURLParams(): { [key: string]: unknown } {
const params = {};
if (window.location.hash.includes(ROUTE_SEPARATOR)) {
const urlParts = window.location.hash.slice(1, Infinity).split(ROUTE_SEPARATOR, 2);
const rawParams = decodeURIComponent(urlParts[1]);
try {
return JSON.parse(rawParams);
} catch {
return params;
}
}
return params;
}
export function setURLParams(params: { [key: string]: unknown }, replace = true): void {
const paramsString = JSON.stringify(params);
const currentUrl = window.location.hash.slice(1, Infinity).split(ROUTE_SEPARATOR)[0];
const newUrl = `#${currentUrl};${encodeURIComponent(paramsString)}`;
if (replace) {
history.replaceState(undefined, "", newUrl);
} else {
history.pushState(undefined, "", newUrl);
}
}
export function updateURLParams(params: { [key: string]: unknown }, replace = true): void {
const currentParams = getURLParams();
for (const key in params) {
currentParams[key] = params[key] as string;
}
setURLParams(currentParams, replace);
}

View File

@ -19,6 +19,7 @@ import { groupBy } from "../../utils";
import "../EmptyState"; import "../EmptyState";
import "../chips/Chip"; import "../chips/Chip";
import "../chips/ChipGroup"; import "../chips/ChipGroup";
import { getURLParams, updateURLParams } from "../router/RouteMatch";
import "./TablePagination"; import "./TablePagination";
import "./TableSearch"; import "./TableSearch";
@ -355,6 +356,9 @@ export abstract class Table<T> extends LitElement {
composed: true, composed: true,
}), }),
); );
updateURLParams({
search: value,
});
}} }}
> >
</ak-table-search>`; </ak-table-search>`;
@ -393,6 +397,10 @@ export abstract class Table<T> extends LitElement {
} }
firstUpdated(): void { firstUpdated(): void {
const params = getURLParams();
if ("search" in params) {
this.search = params.search;
}
this.fetch(); this.fetch();
} }

View File

@ -15,6 +15,7 @@ import "../../elements/buttons/SpinnerButton";
import "../../elements/forms/DeleteBulkForm"; import "../../elements/forms/DeleteBulkForm";
import "../../elements/forms/ModalForm"; import "../../elements/forms/ModalForm";
import "../../elements/forms/ProxyForm"; import "../../elements/forms/ProxyForm";
import { getURLParam, updateURLParams } from "../../elements/router/RouteMatch";
import { TableColumn } from "../../elements/table/Table"; import { TableColumn } from "../../elements/table/Table";
import { TablePage } from "../../elements/table/TablePage"; import { TablePage } from "../../elements/table/TablePage";
import { groupBy } from "../../utils"; import { groupBy } from "../../utils";
@ -45,7 +46,7 @@ export class PropertyMappingListPage extends TablePage<PropertyMapping> {
order = "name"; order = "name";
@property({ type: Boolean }) @property({ type: Boolean })
hideManaged = false; hideManaged = getURLParam<boolean>("hideManaged", true);
async apiEndpoint(page: number): Promise<AKResponse<PropertyMapping>> { async apiEndpoint(page: number): Promise<AKResponse<PropertyMapping>> {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsAllList({ return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsAllList({
@ -171,6 +172,9 @@ export class PropertyMappingListPage extends TablePage<PropertyMapping> {
this.hideManaged = !this.hideManaged; this.hideManaged = !this.hideManaged;
this.page = 1; this.page = 1;
this.fetch(); this.fetch();
updateURLParams({
hideManaged: this.hideManaged,
});
}} }}
/> />
<label class="pf-c-check__label" for="hide-managed" <label class="pf-c-check__label" for="hide-managed"

View File

@ -16,6 +16,7 @@ import "../../elements/forms/DeleteBulkForm";
import "../../elements/forms/ModalForm"; import "../../elements/forms/ModalForm";
import { MessageLevel } from "../../elements/messages/Message"; import { MessageLevel } from "../../elements/messages/Message";
import { showMessage } from "../../elements/messages/MessageContainer"; import { showMessage } from "../../elements/messages/MessageContainer";
import { getURLParam, updateURLParams } from "../../elements/router/RouteMatch";
import { TableColumn } from "../../elements/table/Table"; import { TableColumn } from "../../elements/table/Table";
import { TablePage } from "../../elements/table/TablePage"; import { TablePage } from "../../elements/table/TablePage";
import { first } from "../../utils"; import { first } from "../../utils";
@ -46,7 +47,7 @@ export class UserListPage extends TablePage<User> {
order = "last_login"; order = "last_login";
@property({ type: Boolean }) @property({ type: Boolean })
hideServiceAccounts = true; hideServiceAccounts = getURLParam<boolean>("hideServiceAccounts", true);
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return super.styles.concat(PFDescriptionList); return super.styles.concat(PFDescriptionList);
@ -284,6 +285,9 @@ export class UserListPage extends TablePage<User> {
this.hideServiceAccounts = !this.hideServiceAccounts; this.hideServiceAccounts = !this.hideServiceAccounts;
this.page = 1; this.page = 1;
this.fetch(); this.fetch();
updateURLParams({
hideServiceAccounts: this.hideServiceAccounts,
});
}} }}
/> />
<label class="pf-c-check__label" for="hide-service-accounts"> <label class="pf-c-check__label" for="hide-service-accounts">