2020-12-01 16:27:19 +00:00
|
|
|
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
|
2020-11-21 23:06:25 +00:00
|
|
|
// @ts-ignore
|
2020-11-22 20:25:58 +00:00
|
|
|
import CodeMirrorStyle from "codemirror/lib/codemirror.css";
|
|
|
|
// @ts-ignore
|
|
|
|
import CodeMirrorTheme from "codemirror/theme/monokai.css";
|
2020-11-22 21:05:11 +00:00
|
|
|
import { ColorStyles } from "../constants";
|
2020-11-26 14:55:01 +00:00
|
|
|
import { COMMON_STYLES } from "../common/styles";
|
2020-11-21 23:06:25 +00:00
|
|
|
|
2020-11-24 22:02:10 +00:00
|
|
|
export class Route {
|
2020-11-21 23:06:25 +00:00
|
|
|
url: RegExp;
|
2020-11-24 22:02:10 +00:00
|
|
|
|
|
|
|
private element?: TemplateResult;
|
2020-11-26 14:58:05 +00:00
|
|
|
private callback?: (args: { [key: string]: string }) => TemplateResult;
|
2020-11-24 22:02:10 +00:00
|
|
|
|
|
|
|
constructor(url: RegExp, element?: TemplateResult) {
|
|
|
|
this.url = url;
|
|
|
|
this.element = element;
|
|
|
|
}
|
|
|
|
|
|
|
|
redirect(to: string): Route {
|
|
|
|
this.callback = () => {
|
2020-11-26 16:23:29 +00:00
|
|
|
console.debug(`passbook/router: redirecting ${to}`);
|
2020-11-24 22:02:10 +00:00
|
|
|
window.location.hash = `#${to}`;
|
|
|
|
return html``;
|
|
|
|
};
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2020-11-26 14:58:05 +00:00
|
|
|
then(render: (args: { [key: string]: string }) => TemplateResult): Route {
|
2020-11-26 12:58:45 +00:00
|
|
|
this.callback = render;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2020-11-26 14:58:05 +00:00
|
|
|
render(args: { [key: string]: string }): TemplateResult {
|
2020-11-24 22:02:10 +00:00
|
|
|
if (this.callback) {
|
2020-11-26 12:58:45 +00:00
|
|
|
return this.callback(args);
|
2020-11-24 22:02:10 +00:00
|
|
|
}
|
|
|
|
if (this.element) {
|
|
|
|
return this.element;
|
|
|
|
}
|
|
|
|
throw new Error("Route does not have callback or element");
|
|
|
|
}
|
2020-11-26 22:31:56 +00:00
|
|
|
|
|
|
|
toString(): string {
|
2020-11-26 22:35:59 +00:00
|
|
|
return `<Route url=${this.url} callback=${this.callback ? "true" : "false"}>`;
|
2020-11-26 22:31:56 +00:00
|
|
|
}
|
2020-11-21 23:06:25 +00:00
|
|
|
}
|
|
|
|
|
2020-11-26 12:58:45 +00:00
|
|
|
export const SLUG_REGEX = "[-a-zA-Z0-9_]+";
|
2020-11-21 23:06:25 +00:00
|
|
|
export const ROUTES: Route[] = [
|
2020-11-24 22:02:10 +00:00
|
|
|
// Prevent infinite Shell loops
|
2020-12-01 08:15:41 +00:00
|
|
|
new Route(new RegExp("^/$")).redirect("/library/"),
|
|
|
|
new Route(new RegExp("^#.*")).redirect("/library/"),
|
|
|
|
new Route(new RegExp("^/library/$"), html`<pb-library></pb-library>`),
|
2020-12-01 09:21:04 +00:00
|
|
|
new Route(new RegExp("^/administration/overview/$"), html`<pb-admin-overview></pb-admin-overview>`),
|
2020-12-01 08:15:41 +00:00
|
|
|
new Route(new RegExp("^/applications/$"), html`<pb-application-list></pb-application-list>`),
|
2020-11-26 22:35:59 +00:00
|
|
|
new Route(new RegExp(`^/applications/(?<slug>${SLUG_REGEX})/$`)).then((args) => {
|
|
|
|
return html`<pb-application-view .args=${args}></pb-application-view>`;
|
|
|
|
}),
|
2020-11-21 23:06:25 +00:00
|
|
|
];
|
|
|
|
|
2020-11-26 12:58:45 +00:00
|
|
|
class RouteMatch {
|
|
|
|
route: Route;
|
2020-12-01 16:27:19 +00:00
|
|
|
arguments: { [key: string]: string; };
|
2020-11-26 12:58:45 +00:00
|
|
|
fullUrl?: string;
|
|
|
|
|
|
|
|
constructor(route: Route) {
|
|
|
|
this.route = route;
|
2020-12-01 16:27:19 +00:00
|
|
|
this.arguments = {};
|
2020-11-26 12:58:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
render(): TemplateResult {
|
2020-12-01 16:27:19 +00:00
|
|
|
return this.route.render(this.arguments);
|
2020-11-26 12:58:45 +00:00
|
|
|
}
|
2020-11-26 22:31:56 +00:00
|
|
|
|
|
|
|
toString(): string {
|
|
|
|
return `<RouteMatch url=${this.fullUrl} route=${this.route} arguments=${this.arguments}>`;
|
|
|
|
}
|
2020-11-26 12:58:45 +00:00
|
|
|
}
|
|
|
|
|
2020-11-21 23:06:25 +00:00
|
|
|
@customElement("pb-router-outlet")
|
|
|
|
export class RouterOutlet extends LitElement {
|
|
|
|
@property()
|
2020-11-26 12:58:45 +00:00
|
|
|
current?: RouteMatch;
|
2020-11-21 23:06:25 +00:00
|
|
|
|
2020-11-22 12:13:45 +00:00
|
|
|
@property()
|
|
|
|
defaultUrl?: string;
|
|
|
|
|
2020-12-01 16:27:19 +00:00
|
|
|
static get styles(): CSSResult[] {
|
2020-11-22 21:05:11 +00:00
|
|
|
return [
|
|
|
|
CodeMirrorStyle,
|
|
|
|
CodeMirrorTheme,
|
|
|
|
ColorStyles,
|
2020-11-25 11:41:13 +00:00
|
|
|
css`
|
|
|
|
:host {
|
|
|
|
height: 100%;
|
|
|
|
}
|
|
|
|
`,
|
2020-11-26 14:55:01 +00:00
|
|
|
].concat(...COMMON_STYLES);
|
2020-11-21 23:06:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
super();
|
2020-12-01 08:15:41 +00:00
|
|
|
window.addEventListener("hashchange", () => this.navigate());
|
2020-11-21 23:06:25 +00:00
|
|
|
}
|
|
|
|
|
2020-12-01 08:15:41 +00:00
|
|
|
firstUpdated(): void {
|
2020-11-22 12:13:45 +00:00
|
|
|
this.navigate();
|
|
|
|
}
|
|
|
|
|
2020-12-01 08:15:41 +00:00
|
|
|
navigate(): void {
|
2020-11-22 12:13:45 +00:00
|
|
|
let activeUrl = window.location.hash.slice(1, Infinity);
|
|
|
|
if (activeUrl === "") {
|
2020-12-01 16:27:19 +00:00
|
|
|
activeUrl = this.defaultUrl || "/";
|
2020-11-22 22:48:34 +00:00
|
|
|
window.location.hash = `#${activeUrl}`;
|
2020-11-26 22:31:56 +00:00
|
|
|
console.debug(`passbook/router: set to ${window.location.hash}`);
|
2020-11-22 22:48:34 +00:00
|
|
|
return;
|
2020-11-22 12:13:45 +00:00
|
|
|
}
|
2020-11-26 12:58:45 +00:00
|
|
|
let matchedRoute: RouteMatch | null = null;
|
2020-11-30 11:33:09 +00:00
|
|
|
ROUTES.some((route) => {
|
2020-11-26 22:35:59 +00:00
|
|
|
console.debug(`passbook/router: matching ${activeUrl} against ${route.url}`);
|
2020-11-26 12:58:45 +00:00
|
|
|
const match = route.url.exec(activeUrl);
|
|
|
|
if (match != null) {
|
|
|
|
matchedRoute = new RouteMatch(route);
|
2020-12-01 16:27:19 +00:00
|
|
|
matchedRoute.arguments = match.groups || {};
|
2020-11-26 12:58:45 +00:00
|
|
|
matchedRoute.fullUrl = activeUrl;
|
2020-11-26 22:31:56 +00:00
|
|
|
console.debug(`passbook/router: found match ${matchedRoute}`);
|
2020-11-30 11:33:09 +00:00
|
|
|
return true;
|
2020-11-21 23:06:25 +00:00
|
|
|
}
|
|
|
|
});
|
2020-11-26 12:58:45 +00:00
|
|
|
if (!matchedRoute) {
|
2020-11-26 22:35:59 +00:00
|
|
|
console.debug(`passbook/router: route "${activeUrl}" not defined, defaulting to shell`);
|
2020-11-26 12:58:45 +00:00
|
|
|
const route = new Route(
|
2020-11-24 22:02:10 +00:00
|
|
|
RegExp(""),
|
2020-11-25 11:41:13 +00:00
|
|
|
html`<pb-site-shell url=${activeUrl}>
|
|
|
|
<div slot="body"></div>
|
|
|
|
</pb-site-shell>`
|
2020-11-24 22:02:10 +00:00
|
|
|
);
|
2020-11-26 12:58:45 +00:00
|
|
|
matchedRoute = new RouteMatch(route);
|
2020-12-01 16:27:19 +00:00
|
|
|
matchedRoute.arguments = route.url.exec(activeUrl)?.groups || {};
|
2020-11-26 12:58:45 +00:00
|
|
|
matchedRoute.fullUrl = activeUrl;
|
2020-11-24 22:02:10 +00:00
|
|
|
}
|
2020-11-26 12:58:45 +00:00
|
|
|
this.current = matchedRoute;
|
2020-11-21 23:06:25 +00:00
|
|
|
}
|
|
|
|
|
2020-12-01 08:46:59 +00:00
|
|
|
render(): TemplateResult | undefined {
|
|
|
|
// TODO: Render 404 when current Route is empty
|
2020-11-26 12:58:45 +00:00
|
|
|
return this.current?.render();
|
2020-11-21 23:06:25 +00:00
|
|
|
}
|
|
|
|
}
|