150 lines
4.9 KiB
TypeScript
Executable File
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; |