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 = new Map(); private static _page: { route: string, page: JSX.Element }; private static pageObservableServer = new Observable(); 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>(); 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;