SecureNotes/src/navigation.ts
2019-06-03 16:51:11 +02:00

150 lines
4.9 KiB
TypeScript
Executable File

import { Observable } from "@hibas123/utils";
import { Page, PageProps } from "./page";
import { h, VNode } from "preact";
function serializQuery(obj: any) {
var str = [];
for (var p in obj)
if (obj.hasOwnProperty(p)) {
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
}
return str.join("&");
}
function parseQuery(query: string) {
let data: any = {};
if (query.startsWith("?")) query = query.slice(1)
query.split("&").forEach(e => {
let [key, value] = e.split("=");
key = decodeURIComponent(key)
value = decodeURIComponent(value)
data[key] = value
})
return data
}
type PageFunction = () => JSX.Element;
export default class Navigation {
private static _pages: Map<string, typeof Page | PageFunction> = new Map();
private static _page: { route: string, page: JSX.Element };
private static pageObservableServer = new Observable<JSX.Element>();
public static pageObservable = Navigation.pageObservableServer.getPublicApi();
private static _state: { [key: string]: any };
private static _hidden_state: { [key: string]: any };
private static _page_cache = new Map<string, VNode<any>>();
public static addPage(route: string, comp: typeof Page | PageFunction) {
Navigation._pages.set(route, comp);
}
// public static get state() {
// return Navigation._state;
// }
public static set default(comp: typeof Page) {
console.log("Set default");
Navigation._pages.set("/", comp);
}
public static set not_found(comp: typeof Page) {
Navigation._pages.set("/404", comp);
}
public static setPage(route: string, state?: { [key: string]: string }, hidden?: { [key: string]: string }, replace?: boolean) {
if (!state) state = {};
let component = Navigation._pages.get(route);
if (!component && route !== "/404") {
Navigation.setPage("/404", { route, key: "404" })
} else {
if (!Navigation.page || Navigation.page.route !== route || JSON.stringify(state) !== JSON.stringify(Navigation._state) || JSON.stringify(Navigation._hidden_state) !== JSON.stringify(hidden)) {
let s = "";
if (Object.keys(state).length > 0) {
s = "?" + serializQuery(state)
}
let newhash = "#" + route + s;
let newkey = newhash + serializQuery(hidden)
let whash = window.location.hash;
if (!whash || whash === "") whash = "#/"
let oldkey = whash + serializQuery(history.state);
if (newkey !== oldkey) {
if (replace)
window.history.replaceState(hidden, document.title, "./" + newhash);
else
window.history.pushState(hidden, document.title, "./" + newhash);
}
let page = this._page_cache.get(newkey);
if (!page) {
console.log("Creating new page")
page = h(component as any, { state: state, key: newhash + serializQuery(hidden), hidden: hidden });
this._page_cache.set(newkey, page);
if (this._page_cache.size > 10) {
let cnt = this._page_cache.size - 10;
for (let key of this._page_cache.keys()) {
this._page_cache.delete(key);
cnt--;
if (cnt <= 0)
break;
}
}
}
Navigation._state = state;
Navigation._hidden_state = hidden;
Navigation._page = { page, route };
console.log(route, state, hidden);
Navigation.pageObservableServer.send(page);
}
}
}
static get page() {
return Navigation._page;
}
static onHashChange(hidden_state: { [key: string]: string }) {
let hash = window.location.hash.substring(1);
if (hash && hash !== "") {
let [route, state] = hash.split("?");
let s;
if (state) {
try {
s = parseQuery(state)
} catch (err) {
s = undefined;
console.error(err);
}
}
Navigation.setPage(route, s, hidden_state)
} else {
Navigation.setPage("/");
}
}
static getQuery() {
let hash = window.location.hash.substring(1);
let s = undefined;
if (hash && hash !== "") {
let [_, query] = hash.split("?");
if (query) {
try {
s = parseQuery(query)
} catch (err) {
console.error(err);
}
}
}
return s;
}
static start() {
window.addEventListener("popstate", (ev) => {
Navigation.onHashChange(ev.state);
})
Navigation.onHashChange(undefined);
}
}
window.debug.navigation = Navigation;