badmfck-presenter
Version:
Wrapper for react-router-dom
314 lines (313 loc) • 11.7 kB
JavaScript
"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();