import { set as setkv } from "idb-keyval"; import { FC, useState } from "react"; import { validate } from "uuid"; import { GlobalContext, useLocal } from "web-utils"; import { IContent } from "../../utils/types/general"; import { IItem } from "../../utils/types/item"; import { IRoot } from "../../utils/types/root"; import { DeadEnd } from "../../utils/ui/deadend"; import { Loading } from "../../utils/ui/loading"; import { Vi } from "../vi/vi"; import { base } from "./base/base"; import { scanComponent } from "./base/component"; import { loadPage, loadUrls } from "./base/page"; import { detectResponsiveMode } from "./base/responsive"; import { initBaseRoute, rebuildMeta } from "./base/route"; import { w } from "./w"; import { MatchedRoute } from "radix3"; import { listenChanges } from "./base/live-reload/dev-live-reload"; export const isPreview = () => { return ( location.hostname.split(".").length === 4 || location.hostname === "prasi.app" || location.hostname === "prasi.avolut.com" || location.hostname.includes("ngrok") || location.hostname === "localhost" || location.hostname === "127.0.0.1" || location.hostname === "10.0.2.2" ); // android localhost }; export const Root = () => { // #region context const local = useLocal({ page_id: "" }); const [_, set] = useState({}); const render = () => set({}); w.prasiContext.render = render; const Provider = GlobalContext.Provider as FC<{ value: any; children: any }>; // #endregion // #region init const isPreviewProd = isPreview() && location.pathname.startsWith("/prod"); if ( base.route.status !== "ready" || base.route.router?.ctx.rootNode.children.size === 0 ) { if (base.route.status !== "loading") { base.route.status = "loading"; initBaseRoute().then(async ({ router, pages }) => { detectResponsiveMode(); base.route.status = "ready"; base.route.router = router; base.route.pages = pages; const basepath = w._prasi.basepath; let url = `${ basepath.endsWith("/") ? basepath : `${basepath}/` }_prasi/code/index.js`; const cur = new URL(location.href); if (url.startsWith("/")) { url = `${cur.protocol}//${cur.host}${url}`; } const fn = new Function("callback", `import("${url}").then(callback)`); await new Promise((resolve) => { fn((exports: any) => { for (const [k, v] of Object.entries(exports)) { (w as any)[k] = v; } resolve(); }); }); render(); }); } return ; } // #endregion // #region routing const router = base.route.router; if (!router) return Failed to create Router; let page_id_from_url = ""; if (isPreviewProd) { const parts = location.pathname.split("/"); if (validate(parts[3])) { page_id_from_url = parts[3]; } } let page: MatchedRoute<{ id: string; url: string; }> | null = null; let page_found = null; // hydrate page_id from server.ts if (w._prasi.page_id) { router.insert(base.pathname, { id: w._prasi.page_id, url: base.pathname, }); page = { id: w._prasi.page_id, url: "", params: w._prasi.params }; w._prasi.routed = { page_id: w._prasi.page_id, params: w._prasi.params }; delete w._prasi.page_id; delete w._prasi.params; } // regular route else { page = router.lookup(base.pathname); if (page_id_from_url) { const found = base.route.pages.find((e) => page_id_from_url === e.id); page_found = found; if (found) { page = found; } } if ( page && w._prasi.routed && w._prasi.routed.page_id === page.id && w._prasi.routed.params ) { page.params = w._prasi.routed.params; } } if (!page) { console.error("Page Not Found:", page, base); return Page Not Found; } if (page.id !== local.page_id) { base.init_local_effect = {}; listenChanges({ type: "page", id: page.id }); } w.params = page.params || {}; base.page.id = page.id; base.page.url = page.url; const cache = base.page.cache[page.id]; if (!cache) { loadPage(page.id) .then(async ({ root }) => { if (page) { const p = { id: page.id, url: page.url, root, meta: {}, }; await scanComponent(root.childs, true); rebuildMeta(p.meta, root); base.page.cache[p.id] = p; render(); } }) .catch(() => { render(); }); return ; } else { base.page.root = cache.root; base.page.meta = cache.meta; } // #endregion let mobileCSS = ""; if (base.page.root.responsive === "mobile") { base.mode = "mobile"; mobileCSS = css` @media (min-width: 1280px) { border-left: 1px solid #ccc; border-right: 1px solid #ccc; width: 375px; top: 0px; overflow-x: hidden; overflow-y: auto; bottom: 0px; contain: content; } @media (max-width: 1279px) { left: 0px; right: 0px; top: 0px; bottom: 0px; overflow-y: auto; } `; } else if (base.page.root.responsive === "desktop") { base.mode = "desktop"; } if (!base.mode) { base.mode = "mobile"; } return (
{ w.pointerActive = false; }} onPointerEnter={() => { w.pointerActive = true; }} >
e) .map((e) => e.id)} meta={base.page.meta} mode={base.mode} page_id={base.page.id} site_id={base.site.id} db={base.site.db} api={base.site.api} preloaded={(url) => { const found = base.route.router?.lookup(url); if (found) { if (base.page.cache[found.id]) { return true; } } return false; }} comp_load={async (comp_id) => { const comp = base.comp; if (comp.list[comp_id]) { return comp.list[comp_id]; } try { const res = (await ( await fetch(base.url`_prasi/comp`, { method: "POST", body: JSON.stringify({ ids: [comp_id] }), }) ).json()) as Record; for (const [id, item] of Object.entries(res)) { delete comp.pending[id]; comp.list[id] = item; await setkv(`comp-${id}`, item); } } catch (e) {} return comp.list[comp_id]; }} layout={ base.layout.id && base.layout.root && base.layout.meta ? { id: base.layout.id, meta: base.layout.meta, entry: Object.values(base.layout.root.childs) .filter((e) => e) .map((e) => e.id), } : undefined } script={{ init_local_effect: base.init_local_effect }} on_preload={async ({ urls, opt }) => { const load_urls: string[] = []; if (base.cache.urls) { for (const url of urls) { if (!base.cache.urls.has(url)) { load_urls.push(url); base.cache.urls.add(url); } } } if (load_urls.length > 0) { const pages = await loadUrls(load_urls); if (opt?.on_load) { opt.on_load(pages, walkRoot); } if (Array.isArray(pages)) { for (const page of pages) { const p = { id: page.id, url: page.url, root: page.root, meta: {}, }; await scanComponent(page.root.childs); rebuildMeta(p.meta, page.root); base.page.cache[p.id] = p; } } } }} />
); }; const walkRoot = ( pages: { root: IRoot }[], visit: (item: IContent) => void | Promise ) => { for (const page of pages) { for (const child of page.root.childs) { walk(child, visit); } } }; const walk = ( item: IContent, visit: (item: IContent) => void | Promise ) => { visit(item); if (item.type !== "text") { if (item.type === "item" && item.component?.props) { for (const prop of Object.values(item.component.props)) { if (prop.content) { walk(prop.content, visit); } } } for (const child of item.childs) { walk(child, visit); } } };