UNPKG

badmfck-presenter

Version:

Wrapper for react-router-dom

314 lines (313 loc) 11.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.usePresenter = exports.useCreateRouter = exports.useMenu = exports.useCurrentPath = exports.Presenter = exports.S_NAV_CHANGED = void 0; const react_1 = __importDefault(require("react")); const react_router_dom_1 = require("react-router-dom"); const react_2 = require("react"); const badmfck_signal_1 = __importStar(require("badmfck-signal")); // Signals and requests exports.S_NAV_CHANGED = new badmfck_signal_1.default(); const REQ_NAV_MENU = new badmfck_signal_1.Req(); const REQ_NAV_CURRENT = new badmfck_signal_1.Req(); // Handle all navigation requests, Singleton class Presenter { // Sitemap static title; sitemap = []; currentNav = null; interceptor; static instance; constructor() { if (Presenter.instance) throw (Error("Presenter already constructed")); Presenter.instance = this; REQ_NAV_CURRENT.listener = async (data) => this.currentNav; REQ_NAV_MENU.listener = async (menuID) => { if (!menuID) menuID = "home"; let menu = this.getMenuById(menuID); if (!menu) return [{ name: "not-found", id: "not-found" }]; if (!menu.sub) return [{ name: "no-items", id: "no-items" }]; return menu.sub; }; } static setupInterceptor(interceptor) { if (!Presenter.instance) { console.error("Presenter not initialized"); return; } Presenter.instance.interceptor = interceptor; } setupNavigationTree(tree) { this.sitemap = tree; } markUnselected(arr) { if (!arr) arr = this.sitemap; for (let i of arr) { i.selected = false; if (i.sub && i.sub.length > 0) this.markUnselected(i.sub); } } static async intercept(navigationPoint, params) { if (!Presenter.instance) return true; if (!Presenter.instance.interceptor) return true; return await Presenter.instance.interceptor(navigationPoint, Presenter.instance.currentNav, params); // await ? } static getRouter(Outfit, ErrorPage, InnerErrorPage) { if (!Presenter.instance) return null; let i = 0; const router = (0, react_router_dom_1.createBrowserRouter)((0, react_router_dom_1.createRoutesFromElements)(react_1.default.createElement(react_router_dom_1.Route, { path: '/', element: react_1.default.createElement(react_1.default.Fragment, null, react_1.default.createElement(Navigator, null), Outfit), errorElement: ErrorPage }, Presenter.instance.createRouterTree("/", InnerErrorPage ?? react_1.default.createElement(react_1.default.Fragment, null))))); return router; } createRouterTree(menuID, ErrorPage) { let i = 0; const map = this.getMenuById(menuID)?.sub?.map(val => { return react_1.default.createElement(react_router_dom_1.Route, { key: i++, path: val.path ?? val.id, element: val.page, errorElement: ErrorPage }, val.sub && this.createRouterTree(val.id, ErrorPage)); }); return map; } getLinkTree(id, tree, result) { if (!result) result = []; if (!tree) tree = this.sitemap; for (let i of tree) { if (i.id === id) { result?.push(i); break; } if (i.sub && i.sub.length > 0) { let ref = result.length; const r = this.getLinkTree(id, i.sub, result); if (r.length > ref) result.unshift(i); } } return result; } static setSiteMap(sitemap) { if (!Presenter.instance) return; Presenter.instance.sitemap = sitemap; } static changeNavigation(navpoint, params) { if (!Presenter.instance) return; // detect menu ID const linkTree = Presenter.instance.getLinkTree(navpoint.id); if (!linkTree || linkTree.length === 0) { console.error("Can't navigate to: " + navpoint.id + ", no routes"); return; } let url = linkTree.map(val => val.id === "/" ? undefined : (val.path ?? val.id)).join("/"); //+(data.params?data.params:"") if (params) { for (let i in params) { let replacedValue = ""; let val = params[i]; if (val !== null && val !== undefined && (typeof val === "string" || typeof val === "number" || typeof val === "boolean")) replacedValue = val + ""; url = url.replaceAll(":" + i, replacedValue); } } if (url.indexOf("/:") !== -1) { const tmp = url.split("/"); for (let i in tmp) { let p = tmp[i]; if (p.startsWith(":")) { tmp[i] = ""; // remove unused param } } url = tmp.join("/"); } Presenter.instance.markUnselected(); linkTree.map(val => val.selected = true); Presenter.instance.currentNav = { url: url, path: linkTree, current: linkTree[linkTree.length - 1], params: params }; if (Presenter.instance.currentNav.current.redirect) { const redirect = Presenter.instance.currentNav.current.redirect; if (redirect.toLowerCase().startsWith("http")) { window.location.href = redirect; return; } const newpoint = getPointOrDie(redirect); if (newpoint) { this.changeNavigation(newpoint); return; } } exports.S_NAV_CHANGED.invoke(Presenter.instance.currentNav); } static getNavigationPointByID(id) { if (!Presenter.instance) return null; return Presenter.instance.getMenuById(id); } getMenuById(id, tree) { let result = null; if (!tree) tree = this.sitemap; for (let i of tree) { if (i.id === id) { result = i; break; } if (i.sub && i.sub.length > 0) { result = this.getMenuById(id, i.sub); if (result && result.id === id) break; } } return result; } static setInitialLocation(loc) { if (!loc || !loc.pathname || loc.pathname.indexOf("/") === -1) { if (Presenter.instance) this.changeNavigation(Presenter.instance.sitemap[0]); return; } const tmp = loc.pathname.split("/"); tmp.shift(); if (tmp.length === 0) { if (Presenter.instance) this.changeNavigation(Presenter.instance.sitemap[0]); return; } let found = null; const linkParams = []; if (!Presenter.instance) return; for (let i of tmp) { const f = Presenter.instance.getMenuById(i); if (!f) { linkParams.push(i); break; } if (linkParams.length === 0) found = f; } if (!found) { if (Presenter.instance) this.changeNavigation(Presenter.instance.sitemap[0]); return; } let params = undefined; if (linkParams && found.path) { let n = 0; const tmp = found.path.split("/"); for (let i of tmp) { if (!i.startsWith(":")) continue; const p = i.substring(1); if (!params) params = {}; params[p] = linkParams[n]; n++; } } this.changeNavigation(found, params); } } exports.Presenter = Presenter; const useCurrentPath = () => { return REQ_NAV_CURRENT.use(undefined, [exports.S_NAV_CHANGED.use([])]); }; exports.useCurrentPath = useCurrentPath; const useMenu = (id) => { return REQ_NAV_MENU.use(id ?? "/", [exports.S_NAV_CHANGED.use([])]); }; exports.useMenu = useMenu; // Create router from sitemap const useCreateRouter = (sitemap, Outfit, ErrorPage) => { const [router, sR] = (0, react_2.useState)(null); (0, react_2.useEffect)(() => { Presenter.setSiteMap(sitemap); const r = Presenter.getRouter(Outfit, ErrorPage ?? react_1.default.createElement(react_1.default.Fragment, null)); sR(r); }, []); return router; }; exports.useCreateRouter = useCreateRouter; // Presenter functions const getPointOrDie = (navigationPoint) => { if (typeof navigationPoint === "string") navigationPoint = Presenter.getNavigationPointByID(navigationPoint) ?? ""; if (!navigationPoint || typeof navigationPoint === "string") return null; return navigationPoint; }; const navigationChange = (navigationPoint, params) => { Presenter.intercept(navigationPoint, params).then(value => { if (!value) return; const nav = getPointOrDie(navigationPoint); if (!nav) return; Presenter.changeNavigation(nav, params); }); /*navigationPoint = getPointOrDie(navigationPoint); if(!navigationPoint) return null; Presenter.changeNavigation(navigationPoint,params);*/ }; const navigationPointDisable = (navigationPoint, disable) => { navigationPoint = getPointOrDie(navigationPoint); if (!navigationPoint) return null; navigationPoint.disabled = disable; // TODO: FIRE NAV_CHANGED }; const usePresenter = () => { return { navChange: navigationChange, navDisable: navigationPointDisable, }; }; exports.usePresenter = usePresenter; // Navigator component to determine the start location const Navigator = () => { const navigate = (0, react_router_dom_1.useNavigate)(); exports.S_NAV_CHANGED.use([], data => { document.title = data?.current.title ?? Presenter.title + "-" + (data?.current.name ?? data?.current.id ?? ""); navigate(data.url); }); const loc = (0, react_router_dom_1.useLocation)(); (0, react_2.useEffect)(() => { Presenter.setInitialLocation(loc); }, []); return react_1.default.createElement(react_1.default.Fragment, null); }; new Presenter();