check point

This commit is contained in:
Rizky 2024-05-03 15:34:53 +07:00
parent 9755f4bc1e
commit 3f780647b2
30 changed files with 101 additions and 2487 deletions

File diff suppressed because one or more lines are too long

View File

@ -1,18 +1,72 @@
import globalExternals from "@fal-works/esbuild-plugin-global-externals";
import style from "@hyrious/esbuild-plugin-style";
import { dir } from "dir"; import { dir } from "dir";
import { code } from "../../code"; import { context } from "esbuild";
import { Parcel } from "@parcel/core";
import { removeAsync } from "fs-jetpack"; import { removeAsync } from "fs-jetpack";
import { $ } from "bun"; import { code } from "../../code";
let bundler = new Parcel({
entries: "a.js",
defaultConfig: "@parcel/config-default",
});
export const initFrontEnd = async (root: string, id_site: string) => { export const initFrontEnd = async (root: string, id_site: string) => {
let existing = code.internal.frontend[id_site]; let existing = code.internal.frontend[id_site];
if (!existing) { if (!existing) {
// const run = $`` const build_path = dir.data(`code/${id_site}/site/build`);
await removeAsync(build_path);
existing = await context({
absWorkingDir: root,
entryPoints: ["index.tsx"],
outdir: build_path,
format: "esm",
bundle: true,
minify: true,
treeShaking: true,
splitting: true,
logLevel: "silent",
sourcemap: true,
plugins: [
style(),
globalExternals({
react: {
varName: "window.React",
type: "cjs",
},
"react-dom": {
varName: "window.ReactDOM",
type: "cjs",
},
}),
{
name: "prasi",
setup(setup) {
setup.onEnd(async (res) => {
if (res.errors.length > 0) {
await codeError(
id_site,
res.errors.map((e) => e.text).join("\n\n"),
"site"
);
} }
});
},
},
],
});
code.internal.frontend[id_site] = existing;
existing.watch()
}
await code.internal.frontend[id_site].rebuild()
};
const codeError = async (
id_site: string,
error: string,
mode: "server" | "site"
) => {
const path = code.path(
id_site,
"site",
"src",
mode === "server" ? "server.log" : "index.log"
);
await Bun.write(path, error);
}; };

View File

@ -16,7 +16,7 @@ export const codeInternal = {
get frontend() { get frontend() {
if (!g.prasi_code) g.prasi_code = {}; if (!g.prasi_code) g.prasi_code = {};
if (!g.prasi_code.frontend) g.prasi_code.frontend = {}; if (!g.prasi_code.frontend) g.prasi_code.frontend = {};
return g.prasi_code.frontend as Record<SITE_ID, Parcel>; return g.prasi_code.frontend as Record<SITE_ID, BuildContext>;
}, },
get typings() { get typings() {
if (!g.prasi_code) g.prasi_code = {}; if (!g.prasi_code) g.prasi_code = {};

View File

@ -1,150 +0,0 @@
import { FC, useEffect } from "react";
import { page, useGlobal, useLocal } from "web-utils";
import { EditorGlobal } from "../../render/editor/logic/global";
import { Loading } from "../../utils/ui/loading";
export default page({
url: "/editor/:site_id/:page_id",
component: ({}) => {
const p = useGlobal(EditorGlobal, "EDITOR");
const local = useLocal({
loading: true,
session: null as any,
notfound: false,
init: false,
Editor: null as null | FC<any>,
});
const site_id = params.site_id === "_" ? "" : params.site_id;
const page_id = params.page_id === "_" ? "" : params.page_id;
useEffect(() => {
if (!local.init) {
(async () => {
if (!local.Editor) {
local.Editor = (await import("../../render/editor/editor")).Editor;
}
let ses: any = null;
try {
ses = JSON.parse(localStorage.getItem("prasi-session") || "");
} catch (e) {}
await new Promise<void>(async (done) => {
try {
if (!!ses) {
done();
}
let e = await _api.session();
if (!e) {
(window as any).redirectTo = location.pathname;
navigate("/login");
localStorage.removeItem("prasi-session");
} else {
localStorage.setItem("prasi-session", JSON.stringify(e));
}
if (!ses) {
ses = e;
done();
}
} catch (e) {
console.error(e);
}
});
if (ses) {
local.session = ses;
if (!site_id) {
const res = await _db.site.findFirst({
where: {
is_deleted: false,
org: {
org_user: {
some: { id_user: ses.data.user.id },
},
},
},
select: {
id: true,
},
});
if (res) {
const page = await _db.page.findFirst({
where: {
id_site: res.id,
is_deleted: false,
},
select: {
id: true,
},
});
if (page) {
local.loading = false;
local.render();
navigate(`/editor/${res.id}/${page.id}`);
return;
}
} else {
local.loading = false;
local.render();
return;
}
} else if (!page_id) {
let res = await _db.page.findFirst({
where: {
id_site: site_id,
is_deleted: false,
},
select: {
id: true,
},
});
if (!res) {
res = await _db.page.create({
data: {
content_tree: {
childs: [],
id: "root",
type: "root",
},
name: "home",
url: "/",
id_site: site_id,
},
});
}
if (res) {
local.loading = false;
local.render();
navigate(`/editor/${site_id}/${res.id}`);
return;
}
}
}
local.init = true;
local.loading = false;
local.render();
})();
}
}, [local.init]);
const Editor = local.Editor;
if (local.loading || !Editor) return <Loading note="base-page" />;
const sw = navigator.serviceWorker.controller;
if (sw) {
sw.postMessage({
type: "add-cache",
url: location.href,
});
}
return (
<Editor session={local.session} site_id={site_id} page_id={page_id} />
);
},
});

View File

@ -1,34 +0,0 @@
import { validate } from "uuid";
import { page } from "web-utils";
import { devLoader } from "../../render/live/dev-loader";
import { Live } from "../../render/live/live";
export default page({
url: "/live/:domain/**",
component: ({}) => {
params.site_id = params.domain;
let pathname = `/${params._ === "_" ? "" : params._}`;
if (validate(params._)) {
const arr = params._.split("/");
params.page_id = arr.shift();
pathname = `/${arr.join("/")}`;
}
(window as any).pathname = pathname;
if (navigator.serviceWorker) {
navigator.serviceWorker.controller?.postMessage({
type: "add-cache",
url: location.href,
});
}
return (
<Live
domain_or_siteid={params.domain}
pathname={pathname}
loader={devLoader}
liveSync
/>
);
},
});

View File

@ -18,14 +18,6 @@ export const ed = {
url: "/ed/:site_id/:page_id", url: "/ed/:site_id/:page_id",
page: () => import("./page/ed"), page: () => import("./page/ed"),
}; };
export const editor = {
url: "/editor/:site_id/:page_id",
page: () => import("./page/editor"),
};
export const live = {
url: "/live/:domain/**",
page: () => import("./page/live"),
};
export const vi = { export const vi = {
url: "/vi/:domain/**", url: "/vi/:domain/**",
page: () => import("./page/vi"), page: () => import("./page/vi"),

View File

@ -5,7 +5,6 @@ import { loadApiProxyDef } from "./base/load/api/api-proxy-def";
import { dbProxy } from "./base/load/db/db-proxy"; import { dbProxy } from "./base/load/db/db-proxy";
import { Root } from "./base/root"; import { Root } from "./base/root";
import "./index.css"; import "./index.css";
import { registerMobile } from "./render/live/logic/mobile";
import { sworkerAddCache, sworkerRegister } from "./sworker-boot"; import { sworkerAddCache, sworkerRegister } from "./sworker-boot";
import { w } from "./utils/types/general"; import { w } from "./utils/types/general";
@ -13,7 +12,6 @@ const start = async () => {
let react = { let react = {
root: null as null | ReactRoot, root: null as null | ReactRoot,
}; };
w.mobile = registerMobile();
const cur = new URL(w.basehost || location.href); const cur = new URL(w.basehost || location.href);
const base_url = `${cur.protocol}//${cur.host}`; const base_url = `${cur.protocol}//${cur.host}`;

View File

@ -1,10 +1,10 @@
import { syncronize } from "y-pojo"; import { syncronize } from "y-pojo";
import { fillID } from "../../../../../../../render/editor/tools/fill-id";
import { IContent, MContent } from "../../../../../../../utils/types/general"; import { IContent, MContent } from "../../../../../../../utils/types/general";
import { IItem } from "../../../../../../../utils/types/item"; import { IItem } from "../../../../../../../utils/types/item";
import { getMetaById } from "../../../../../logic/active/get-meta"; import { getMetaById } from "../../../../../logic/active/get-meta";
import { PG } from "../../../../../logic/ed-global"; import { PG } from "../../../../../logic/ed-global";
import { treeRebuild } from "../../../../../logic/tree/build"; import { treeRebuild } from "../../../../../logic/tree/build";
import { fillID } from "../../../../../logic/tree/fill-id";
export const edActionClone = (p: PG, item: IContent) => { export const edActionClone = (p: PG, item: IContent) => {
const mitem = getMetaById(p, item.id)?.mitem; const mitem = getMetaById(p, item.id)?.mitem;

View File

@ -1,9 +1,9 @@
import { syncronize } from "y-pojo"; import { syncronize } from "y-pojo";
import { fillID } from "../../../../../../../render/editor/tools/fill-id";
import { IItem } from "../../../../../../../utils/types/item"; import { IItem } from "../../../../../../../utils/types/item";
import { getMetaById } from "../../../../../logic/active/get-meta"; import { getMetaById } from "../../../../../logic/active/get-meta";
import { PG } from "../../../../../logic/ed-global"; import { PG } from "../../../../../logic/ed-global";
import { treeRebuild } from "../../../../../logic/tree/build"; import { treeRebuild } from "../../../../../logic/tree/build";
import { fillID } from "../../../../../logic/tree/fill-id";
export const edActionDetach = (p: PG, item: IItem) => { export const edActionDetach = (p: PG, item: IItem) => {
const mitem = getMetaById(p, item.id)?.mitem; const mitem = getMetaById(p, item.id)?.mitem;

View File

@ -1,5 +1,4 @@
import { syncronize } from "y-pojo"; import { syncronize } from "y-pojo";
import { fillID } from "../../../../../../../render/editor/tools/fill-id";
import { IContent } from "../../../../../../../utils/types/general"; import { IContent } from "../../../../../../../utils/types/general";
import { IItem, MItem } from "../../../../../../../utils/types/item"; import { IItem, MItem } from "../../../../../../../utils/types/item";
import { MSection } from "../../../../../../../utils/types/section"; import { MSection } from "../../../../../../../utils/types/section";
@ -8,6 +7,7 @@ import { PG, active } from "../../../../../logic/ed-global";
import { treeRebuild } from "../../../../../logic/tree/build"; import { treeRebuild } from "../../../../../logic/tree/build";
import { TypedArray } from "yjs-types"; import { TypedArray } from "yjs-types";
import { loadComponent } from "../../../../../logic/comp/load"; import { loadComponent } from "../../../../../logic/comp/load";
import { fillID } from "../../../../../logic/tree/fill-id";
export const edActionPaste = async (p: PG, item: IContent) => { export const edActionPaste = async (p: PG, item: IContent) => {
let mitem = getMetaById(p, item.id)?.mitem; let mitem = getMetaById(p, item.id)?.mitem;

View File

@ -1,121 +0,0 @@
import { LSite, Loader } from "./logic/global";
const cache = { site: null as any, pages: [] as any, api: null };
const config = { serverurl: "" };
export const devLoader: Loader = {
async site(p, where) {
config.serverurl = serverurl;
const site = (await _db.site.findFirst({
where:
where.type === "siteid" ? { id: where.id } : { domain: where.domain },
select: {
id: true,
config: true,
domain: true,
name: true,
js: true,
responsive: true,
js_compiled: true,
},
})) as unknown as LSite;
if (!site) {
return null;
}
const cgroups = await _db.site_use_comp.findMany({
where: { id_site: site.id },
select: { use_id_site: true },
});
if (Array.isArray(cgroups)) {
site.cgroup_ids = [];
for (const id of cgroups.map((c: any) => c.use_id_site)) {
site.cgroup_ids.push(id);
}
}
const layout = await _db.page.findFirst({
where: {
id_site: site.id,
name: { startsWith: "layout:" },
is_default_layout: true,
is_deleted: false,
},
select: { content_tree: true, id: true },
});
if (layout && layout.content_tree) {
const childs = (layout.content_tree as any).childs;
if (childs && childs.length > 0) {
site.layout = childs[0];
site.layout_id = layout.id;
}
}
cache.site = site;
return site;
},
async comp(p, id) {
const comp = await load(`${config.serverurl}/_web/comp/${id}`);
p.comps.all[id] = comp;
return comp;
},
npm(p, type, id) {
if (type === "site") {
return `${config.serverurl}/npm/site/${id}/site.js`;
}
return `${config.serverurl}/npm/page/${id}/page.js`;
},
async page(p, id) {
return await load(`${config.serverurl}/_web/page/${id}`);
},
async pages(p, id) {
let pages = [] as any;
/** load pages */
const pagesLocal = localStorage.getItem(`prasi-pages-[${id}]`);
if (pagesLocal) {
try {
pages = JSON.parse(pagesLocal);
} catch (e) {}
}
const loadPages = async () => {
return await _db.page.findMany({
where: {
id_site: cache.site.id,
is_deleted: false,
name: { not: { startsWith: "layout:" } },
},
select: {
id: true,
name: true,
url: true,
},
});
};
if (pages.length === 0) {
pages = await loadPages();
localStorage.setItem(`prasi-pages-[${id}]`, JSON.stringify(pages));
} else {
loadPages().then((pages) => {
localStorage.setItem(`prasi-pages-[${id}]`, JSON.stringify(pages));
});
}
return pages;
},
};
const load = async (url: string) => {
const res = await fetch(url);
try {
const text = await res.text();
const json = JSON.parse(text);
return json;
} catch (e) {
return null;
}
};

View File

@ -1,37 +0,0 @@
import { FC } from "react";
import { LRender } from "./l-render";
import { LText } from "./l-text";
export const LItem: FC<{
id: string;
_scopeIndex?: Record<string, any>;
fromProp?: boolean;
}> = ({ id, fromProp, _scopeIndex }) => {
return (
<LRender id={id} _scopeIndex={_scopeIndex}>
{(childs) => {
return childs.map((e) => {
if (e.type === "item") {
return (
<LItem
id={e.id}
key={e.id}
fromProp={fromProp}
_scopeIndex={_scopeIndex}
/>
);
} else {
return (
<LText
id={e.id}
key={e.id}
fromProp={fromProp}
_scopeIndex={_scopeIndex}
/>
);
}
});
}}
</LRender>
);
};

View File

@ -1,73 +0,0 @@
import { useGlobal } from "web-utils";
import { LiveGlobal } from "../logic/global";
import { LSection } from "./l-section";
import { Loading } from "../../../utils/ui/loading";
export const LPage = () => {
const p = useGlobal(LiveGlobal, "LIVE");
const mode = p.mode;
let childs = Object.values(p.page?.content_tree?.childs || []);
if (p.layout.section && p.layout.content) {
childs = [p.layout.section];
}
const rootChilds: string[] | undefined = Object.values(childs).map(
(e) => e.id
);
return (
<div className={cx("relative flex flex-1 items-center justify-center")}>
<div
className={cx(
"absolute flex flex-col items-stretch flex-1 bg-white ",
mode === "mobile"
? css`
@media (min-width: 768px) {
border-left: 1px solid #ccc;
border-right: 1px solid #ccc;
width: 375px;
top: 0px;
overflow-x: hidden;
overflow-y: auto;
bottom: 0px;
}
@media (max-width: 767px) {
left: 0px;
right: 0px;
top: 0px;
bottom: 0px;
overflow-y: auto;
}
`
: "inset-0 overflow-auto",
css`
contain: content;
`
)}
>
{p.status === "ready" || p.status === "tree-rebuild" ? (
rootChilds?.map((id) => <LSection key={id} id={id} />)
) : (
<Loading />
)}
</div>
</div>
);
};
export const mobileCSS = css`
background-color: white;
background-image: linear-gradient(45deg, #fafafa 25%, transparent 25%),
linear-gradient(-45deg, #fafafa 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, #fafafa 75%),
linear-gradient(-45deg, transparent 75%, #fafafa 75%);
background-size: 20px 20px;
background-position:
0 0,
0 10px,
10px -10px,
-10px 0px;
`;

View File

@ -1,243 +0,0 @@
import { FC, ReactNode, useEffect, useState } from "react";
import { deepClone, useGlobal, useLocal } from "web-utils";
import { produceCSS } from "../../../utils/css/gen";
import { IContent } from "../../../utils/types/general";
import { FNAdv, FNCompDef, FNLinkTag } from "../../../utils/types/meta-fn";
import { responsiveVal } from "../../editor/tools/responsive-val";
import { ItemMeta, LiveGlobal, PG } from "../logic/global";
import { preload } from "../logic/route";
import { treePropEval } from "../logic/tree-prop";
import { treeScopeEval } from "../logic/tree-scope";
import { LTextInternal } from "./l-text";
export const LRender: FC<{
id: string;
children?: (childs: IContent[]) => ReactNode;
fromProp?: boolean;
_scopeIndex?: Record<string, any>;
}> = ({ id, children, fromProp, _scopeIndex }) => {
const p = useGlobal(LiveGlobal, "LIVE");
const meta = p.treeMeta[id];
if (meta) {
if (meta.item.name.startsWith("::")) {
if (meta.isLayout) {
meta.className = produceCSS(meta.item, {
mode: p.mode,
});
return <PrasiPortal name={meta.item.name} />;
} else {
if (!p.portal[meta.item.name]) return null;
p.portal[meta.item.name].el = (
<LRenderInternal
p={p}
id={id}
children={children}
fromProp={fromProp}
meta={meta}
_scopeIndex={_scopeIndex}
/>
);
return null;
}
}
}
return (
<LRenderInternal
p={p}
id={id}
children={children}
fromProp={fromProp}
meta={meta}
_scopeIndex={_scopeIndex}
/>
);
};
export const LRenderInternal: FC<{
id: string;
children?: (childs: IContent[]) => ReactNode;
fromProp?: boolean;
meta?: ItemMeta;
_scopeIndex?: Record<string, any>;
p: PG;
}> = ({ id, children, meta, p, _scopeIndex }) => {
const [_, render] = useState({});
useEffect(() => {
if (meta) {
meta.mounted = true;
if (meta.pendingRender) {
meta.pendingRender = false;
render({});
}
}
});
if (!meta) {
return null;
}
meta.render = () => {
if (meta) {
if (meta.mounted) {
render({});
} else {
meta.pendingRender = true;
}
}
};
let item = meta.item;
if (meta.comp?.id) {
const comp = meta.comp;
let props = {} as Record<string, FNCompDef>;
let cprops = {} as [string, FNCompDef][];
props = deepClone(
p.comps.all[meta.comp.id]?.content_tree.component?.props || {}
);
cprops = Object.entries(props);
comp.propval = treePropEval(p, id, cprops, _scopeIndex);
}
let _children = null;
if (children) {
if (item.type === "text") _children = children([]);
else {
_children = children(
item.childs.filter((c) => {
if (c.hidden === "all") {
return false;
}
if (!p.treeMeta[c.id]) {
if (p.treePending) p.treePending.then(meta.render);
return false;
}
return true;
}) || []
);
}
}
meta.className = produceCSS(item, {
mode: p.mode,
});
const className = meta.className;
const adv = item.adv;
if (!(adv?.jsBuilt && adv?.js) && meta.comp) {
return treeScopeEval(
p,
id,
_children,
`render(React.createElement("div",{...props},children));`,
_scopeIndex
);
}
if (adv) {
if (adv.html) {
const html = renderHTML(className, adv);
if (html) return html;
} else if (adv.jsBuilt && adv.js) {
const el = treeScopeEval(p, id, _children, adv.jsBuilt, _scopeIndex);
return el;
}
}
const linktag = responsiveVal<FNLinkTag>(item, "linktag", p.mode, {});
const isComponent = item.type === "item" && item.component?.id;
if (linktag && linktag.link && !isComponent) {
let href = linktag.link || "";
if (href.startsWith("/")) {
preload(p, href);
if (
(location.pathname.startsWith("/preview/") ||
location.pathname.startsWith("/site/")) &&
["localhost", "127.0.0.1", "prasi.app"].includes(location.hostname)
) {
const parts = location.pathname.split("/");
if (parts.length >= 3) {
href = `/${parts[1]}/${parts[2]}${href}`;
}
}
}
const props = {
className: className,
href: href,
onClick: (e: any) => {
e.preventDefault();
if (href.startsWith("/")) {
navigate(href);
} else {
location.href = href;
}
},
};
if (item.type === "text") {
return (
<a
{...props}
dangerouslySetInnerHTML={{ __html: item.html || item.text }}
/>
);
}
return <a {...props}>{_children}</a>;
}
if (item.type === "text") {
return (
<LTextInternal
key={item.id}
className={className}
item={item}
p={p}
_children={item.html || item.text}
/>
);
}
return (
<div className={className}>
{/* {isComponent && (
<pre className={"text-[9px] font-mono text-black"}>
{item.id}-{item.name}
</pre>
)} */}
{_children}
</div>
);
};
export const renderHTML = (className: string, adv: FNAdv) => {
if (adv.html) {
return (
<div
className={className}
dangerouslySetInnerHTML={{ __html: adv.html }}
></div>
);
}
return null;
};
const PrasiPortal = ({ name }: { name: string }) => {
const p = useGlobal(LiveGlobal, "LIVE");
const local = useLocal({});
if (!p.portal[name]) {
p.portal[name] = { render: local.render, el: null };
} else {
p.portal[name].render = local.render;
}
return p.portal[name].el;
};

View File

@ -1,15 +0,0 @@
import { FC } from "react";
import { LItem } from "./l-item";
import { LRender } from "./l-render";
export const LSection: FC<{ id: string }> = ({ id }) => {
return (
<LRender id={id}>
{(childs) =>
childs.map((e) => {
return <LItem id={e.id} key={e.id} />;
})
}
</LRender>
);
};

View File

@ -1,34 +0,0 @@
import { FC } from "react";
import { IText } from "../../../utils/types/text";
import { PG } from "../logic/global";
import { LRender } from "./l-render";
export const LText: FC<{
id: string;
fromProp?: boolean;
_scopeIndex?: Record<string, any>;
}> = ({ id, fromProp, _scopeIndex }) => {
return <LRender id={id} fromProp={fromProp} _scopeIndex={_scopeIndex} />;
};
export const LTextInternal: FC<{
className: any;
p: PG;
item: IText;
_children: any;
}> = ({ className, item, _children }) => {
return (
<div
id={`text-${item.id}`}
className={cx(
className,
css`
outline: none;
min-width: 3px !important;
min-height: 10px !important;
`
)}
dangerouslySetInnerHTML={{ __html: _children || "" }}
/>
);
};

View File

@ -1,83 +0,0 @@
import { FC, useCallback, useEffect } from "react";
import parseUA from "ua-parser-js";
import { useGlobal } from "web-utils";
import { LPage } from "./elements/l-page";
import { LiveGlobal, Loader } from "./logic/global";
import { initLive, w } from "./logic/init";
import { preload, routeLive } from "./logic/route";
import { Loading } from "../../utils/ui/loading";
export const Live: FC<{
domain_or_siteid: string;
pathname: string;
loader: Loader;
liveSync?: boolean;
}> = ({ domain_or_siteid, pathname, loader, liveSync }) => {
const p = useGlobal(LiveGlobal, "LIVE");
p.loader = loader;
w.preload = (url: string) => {
preload(p, url);
};
if (p.site.id) {
if (!p.mode && !!p.site.responsive) {
if (p.site.responsive === "all") {
const parsed = parseUA();
p.mode = parsed.device.type === "mobile" ? "mobile" : "desktop";
if (localStorage.getItem("prasi-editor-mode")) {
p.mode = localStorage.getItem("prasi-editor-mode") as any;
}
} else if (p.site.responsive === "mobile-only") {
p.mode = "mobile";
} else if (p.site.responsive === "desktop-only") {
p.mode = "desktop";
}
}
}
const onResize = useCallback(() => {
let newmode = p.mode;
if (window.innerWidth < 600) newmode = "mobile";
else newmode = "desktop";
if (newmode !== p.mode) {
p.mode = newmode;
p.render();
}
}, [p]);
useEffect(() => {
if (p.site.id) {
window.removeEventListener("resize", onResize);
if (p.site.responsive === "all") {
window.addEventListener("resize", onResize);
}
}
}, [p.site.responsive]);
if (p.status === "init") {
initLive(p, domain_or_siteid);
}
if (p.site.id) {
routeLive(p, pathname);
}
if (!p.mode) return <Loading />;
else {
w.isMobile = p.mode === "mobile";
w.isDesktop = p.mode === "desktop";
}
if (p.status === "not-found")
return (
<div className="absolute inset-0 flex items-center justify-center flex-col">
<div className="text-[40px]">404</div>
<div>NOT FOUND</div>
</div>
);
return <LPage />;
};

View File

@ -1,139 +0,0 @@
import { createId } from "@paralleldrive/cuid2";
import * as Y from "yjs";
import { IContent } from "../../../utils/types/general";
import { IItem, MItem } from "../../../utils/types/item";
import { FNCompDef, FNComponent } from "../../../utils/types/meta-fn";
import { IRoot } from "../../../utils/types/root";
import { fillID } from "../../editor/tools/fill-id";
import { PG } from "./global";
import { PRASI_COMPONENT } from "../../../utils/types/render";
import { deepClone } from "web-utils";
export const scanComponent = (
item: IRoot | IContent,
componentIDS?: Set<string>
) => {
const ids = componentIDS || new Set();
if (!item) return ids;
if (item.type === "item" && item.component?.id) {
ids.add(item.component.id);
if (item.component.props) {
for (const p of Object.values(item.component.props)) {
if (p.meta?.type === "content-element" && p.content) {
scanComponent(p.content, ids);
}
}
}
}
if (item.type !== "text" && item.childs) {
for (const c of item.childs) {
scanComponent(c, ids);
}
}
return ids;
};
export const loadComponent = async (
p: PG,
itemOrID: string | IRoot | IContent
) => {
const compIds = new Set<string>();
let tree: IRoot | IContent = null as any;
if (typeof itemOrID !== "string") {
tree = itemOrID;
} else {
await loadComponentById(p, itemOrID);
}
scanComponent(tree, compIds);
const promises = [...compIds]
.filter((id) => {
if (!p.comps.all[id] && !p.comps.pending[id]) return true;
return false;
})
.map(async (id) => {
const comp = await loadComponentById(p, id);
await loadComponent(p, comp.content_tree);
});
if (promises.length > 0) {
await Promise.all(promises);
}
};
const loadComponentById = async (p: PG, id: string) => {
if (!!p.comps.pending[id]) {
return await p.comps.pending[id];
}
const res = p.loader.comp(p, id);
p.comps.pending[id] = res;
p.comps.all[id] = await res;
return p.comps.all[id];
};
export const instantiateComp = (
p: PG,
item: IItem,
mcomp: { item: MItem; type: "m" } | { item: PRASI_COMPONENT; type: "i" },
child_ids: Record<string, string>
) => {
const comp = item.component as FNComponent;
let target = null as any;
let mprops = {} as Record<string, FNCompDef>;
if (mcomp.type === "m") {
const mitem = mcomp.item;
mprops = mitem.get("component")?.get("props")?.toJSON() as Record<
string,
FNCompDef
>;
if (!mprops) {
mitem.get("component")?.set("props", new Y.Map() as any);
mprops = mitem.get("component")?.get("props")?.toJSON() as any;
}
target = mitem.toJSON();
} else {
target = deepClone(mcomp.item.content_tree);
mprops = target.component.props || {};
}
let nitem = {};
nitem = fillID(target, (i) => {
if (!i.originalId) {
i.originalId = i.id;
}
let newid = i.id;
if (child_ids[i.originalId]) {
newid = child_ids[i.originalId];
} else {
newid = createId();
}
i.id = newid;
child_ids[i.originalId] = newid;
return false;
}) as IItem;
const props: any = {};
for (const [k, v] of Object.entries(mprops)) {
props[k] = v;
if (comp.props[k]) {
props[k].value = comp.props[k].value;
props[k].valueBuilt = comp.props[k].valueBuilt;
props[k].content = comp.props[k].content;
}
}
return {
...nitem,
id: item.id,
originalId: item.originalId || (nitem as IItem).originalId,
component: { ...comp, props },
} as IItem;
};

View File

@ -1,135 +0,0 @@
import { createRouter } from "radix3";
import { FC, ReactNode } from "react";
import { CompDoc } from "../../../base/global/content-editor";
import { IContent, MPage } from "../../../utils/types/general";
import { IItem, MItem } from "../../../utils/types/item";
import { PRASI_COMPONENT } from "../../../utils/types/render";
import { IRoot } from "../../../utils/types/root";
import { ISection } from "../../../utils/types/section";
import { IText } from "../../../utils/types/text";
type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
export type ItemMeta = {
item: IContent;
scope?: any;
indexedScope: Record<string, any>;
comp?: {
id: string;
propval?: any;
child_ids: Record<string, string>;
};
className?: string;
parent_id: string;
parent_comp?: WithRequired<ItemMeta, "comp"> & { item: IItem };
memoize?: Record<string, {
Local: FC<any>;
PassProp: FC<any>;
}>;
isLayout: boolean;
render?: () => void;
mounted?: boolean;
pendingRender?: boolean;
};
export type LPage = {
id: string;
name: string;
url: string;
content_tree?: IRoot;
js: string;
};
export type LSite = {
id: string;
api_url: string;
api_prasi: {
port: string;
db: string;
};
responsive: "all" | "mobile-only" | "desktop-only";
domain: string;
name: string;
js: string;
js_compiled: string;
layout?: ISection;
layout_id?: string;
cgroup_ids?: string[];
config?: any;
};
export type Loader = {
site: (
p: PG,
where: { type: "domain"; domain: string } | { type: "siteid"; id: string }
) => Promise<LSite | null>;
page: (p: PG, id: string) => Promise<LPage | null>;
pages: (p: PG, site_id: string) => Promise<LPage[]>;
npm: (p: PG, type: "site" | "page", id: string) => string;
comp: (p: PG, id: string) => Promise<PRASI_COMPONENT>;
};
export const LiveGlobal = {
liveSync: {
ws: null as null | WebSocket,
init: false,
decompress: null as null | ((buf: Uint8Array) => Uint8Array),
},
prod: false,
loader: undefined as unknown as Loader,
mode: "" as "desktop" | "mobile",
layout: {
section: null as null | ISection,
content: null as null | IItem | IText,
},
status: "init" as
| "init"
| "loading"
| "reload"
| "ready"
| "not-found"
| "error"
| "tree-rebuild",
site: {
id: "",
api_url: "",
api_prasi: {
port: "",
db: "",
},
responsive: "all" as "all" | "mobile-only" | "desktop-only",
domain: "",
name: "",
js: "",
js_compiled: "",
} as LSite,
page: null as null | LPage,
mpage: null as null | MPage,
mpageLoaded: null as null | ((mpage: MPage) => void),
pagePreload: {} as Record<string, true>,
pages: {} as Record<string, LPage>,
route: createRouter<{
id: string;
url: string;
}>(),
treePending: null as null | Promise<void>,
treeMeta: {} as Record<string, ItemMeta>,
cachedParentID: {} as Record<string, string>,
portal: {} as Record<string, { el?: ReactNode; render: () => void }>,
comps: {
pending: {} as Record<string, Promise<PRASI_COMPONENT>>,
resolve: {} as Record<string, (comp: PRASI_COMPONENT) => void>,
all: {} as Record<string, PRASI_COMPONENT>,
},
script: {
db: null as any,
api: null as any,
},
compInstance: {} as Record<string, Record<string, string>>,
ws: null as null | WebSocket,
wsRetry: {
fast: false,
localIP: false,
disabled: false,
reconnecting: false,
},
};
export type PG = typeof LiveGlobal & { render: () => void };

View File

@ -1,153 +0,0 @@
import { createRouter } from "radix3";
import { validate } from "uuid";
import { apiProxy } from "../../../base/load/api/api-proxy";
import { dbProxy } from "../../../base/load/db/db-proxy";
import importModule from "../../editor/tools/dynamic-import";
import { LSite, PG } from "./global";
import { validateLayout } from "./layout";
import { registerMobile } from "./mobile";
export const w = window as unknown as {
basepath: string;
navigateOverride: (s: string) => string;
isEditor: boolean;
isMobile: boolean;
isLayout: boolean;
apiHeaders: any;
isDesktop: boolean;
exports: any;
params: any;
apiurl: string;
preload: (path: string) => void;
mobile?: ReturnType<typeof registerMobile>;
externalAPI: {
mode: "dev" | "prod";
devUrl: string;
prodUrl: string;
};
};
export const initLive = async (p: PG, domain_or_siteid: string) => {
if (p.status === "init") {
p.status = "loading";
if (w.mobile) {
w.mobile.bind(p);
w.mobile.send({ type: "ready" });
}
w.isEditor = false;
w.isLayout = true;
w.apiHeaders = {};
w.navigateOverride = (_href) => {
if (_href && _href.startsWith("/")) {
if (w.basepath.length > 1) {
_href = `${w.basepath}${_href}`;
}
if (
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
) {
if (
location.pathname.startsWith("/live") &&
!_href.startsWith("/live")
) {
const patharr = location.pathname.split("/");
_href = `/live/${patharr[2]}${_href}`;
}
}
}
return _href;
};
/** load site */
let site = null as null | LSite;
site = await p.loader.site(
p,
validate(domain_or_siteid)
? { type: "siteid", id: domain_or_siteid }
: { type: "domain", domain: domain_or_siteid }
);
if (site) {
/** import site module */
w.exports = {};
if (site.cgroup_ids) {
for (const id of site.cgroup_ids) {
await importModule(p.loader.npm(p, "site", id));
}
}
await importModule(p.loader.npm(p, "site", site.id));
p.site.id = site.id;
p.site.layout = site.layout;
p.site.js = site.js_compiled || "";
p.site.responsive = site.responsive as any;
await validateLayout(p);
p.site.api_url = site.config.api_url || "";
w.apiurl = p.site.api_url;
let pages = await p.loader.pages(p, site.id);
/** execute site module */
const exec = (fn: string, scopes: any) => {
if (p) {
if (p.site.api_url) {
scopes["api"] = apiProxy(p.site.api_url);
scopes["db"] = dbProxy(p.site.api_url);
}
if (!w.params) {
w.params = {};
}
scopes.params = w.params;
scopes.module = {};
const f = new Function(...Object.keys(scopes), fn);
const res = f(...Object.values(scopes));
return res;
}
return null;
};
const scope = {
types: {},
exports: w.exports,
load: importModule,
render: p.render,
module: {
exports: {} as any,
},
};
exec(p.site.js, scope);
if (scope.module.exports) {
for (const [k, v] of Object.entries(scope.module.exports)) {
w.exports[k] = v;
}
}
/** create router */
p.pages = {};
p.route = createRouter({ strictTrailingSlash: false });
if (pages && pages.length > 0) {
for (const page of pages) {
p.pages[page.id] = page;
p.route.insert(page.url, page);
}
}
p.status = "ready";
p.render();
} else {
p.status = "not-found";
p.render();
}
}
};

View File

@ -1,36 +0,0 @@
import { IItem } from "../../../utils/types/item";
import { IText } from "../../../utils/types/text";
import { LSite, PG } from "./global";
export const validateLayout = async (p: {
site: LSite;
layout: PG["layout"];
}) => {
if (p.site.layout) {
p.layout.section = p.site.layout;
for (const child of p.layout.section.childs) {
const found = await walk(child);
if (found) {
p.layout.content = found;
break;
}
}
}
};
const walk = async (
item: IItem | IText
): Promise<IItem | IText | undefined> => {
if (item.name === "content") {
return item;
}
if (item.type === "item" && !item.component?.id) {
for (const c of item.childs) {
const found = await walk(c);
if (found) {
return found;
}
}
}
};

View File

@ -1,164 +0,0 @@
import { w } from "../../../utils/types/general";
import { PG } from "./global";
type NOTIF_ARG = {
user_id: any;
body: string;
title: string;
data?: any;
};
export const registerMobile = () => {
const default_mobile = {
send: () => {},
bind: (p: PG) => {},
notif: {
register: (user_id: string) => {},
send: async (data: NOTIF_ARG) => {
const p = getP();
if (p) {
return await p.script.api._notif("send", {
type: "send",
id:
typeof data.user_id === "string"
? data.user_id
: data.user_id.toString(),
body: data.body,
title: data.title,
data: data.data,
});
}
},
onTap: (data: NOTIF_ARG) => {},
onReceive: (data: NOTIF_ARG) => {},
},
};
let config = { notif_token: "", p: null as null | PG };
const getP = () => {
const p = config.p;
if (p && p.site && p.site.api_url) {
const api = w.prasiApi[p.site.api_url];
if (
api &&
api.apiEntry &&
api.apiEntry._notif &&
p.script &&
p.script.api
) {
return p;
}
}
};
if (window.parent) {
window.addEventListener("message", async ({ data: raw }) => {
if (typeof raw === "object" && raw.mobile) {
const data = raw as unknown as
| {
type: "notification-token";
token: string;
}
| { type: "notification-tap"; notif: NOTIF_ARG }
| { type: "notification-receive"; notif: NOTIF_ARG };
const waitUntil = async (fn: () => boolean) => {
if (!notifObject.notif.onTap) {
let ival = null as any;
let i = 0;
await new Promise(() => {
ival = setInterval(() => {
i++;
if (i > 20) {
clearInterval(ival);
}
if (fn()) {
clearInterval(ival);
}
}, 500);
});
return;
}
};
switch (data.type) {
case "notification-token":
config.notif_token = data.token;
break;
case "notification-tap":
if (!notifObject.notif.onTap) {
waitUntil(() => {
if (notifObject.notif.onTap) {
notifObject.notif.onTap(data.notif);
return true;
}
return false;
});
return;
}
if (notifObject.notif.onTap) {
notifObject.notif.onTap(data.notif);
}
break;
case "notification-receive":
if (!notifObject.notif.onReceive) {
waitUntil(() => {
if (notifObject.notif.onReceive) {
notifObject.notif.onReceive(data.notif);
return true;
}
return false;
});
}
if (notifObject.notif.onReceive) {
notifObject.notif.onReceive(data.notif);
}
break;
}
}
});
const notifObject = {
send: (msg: { type: "ready" }) => {
window.parent.postMessage({ mobile: true, ...msg }, "*");
},
bind(p: PG) {
config.p = p;
},
config,
notif: {
register: async (user_id: any) => {
const p = getP();
if (p) {
return await p.script.api._notif("register", {
type: "register",
id: typeof user_id === "string" ? user_id : user_id.toString(),
token: config.notif_token,
});
}
},
send: async (data: NOTIF_ARG) => {
const p = getP();
if (p) {
return await p.script.api._notif("send", {
type: "send",
id:
typeof data.user_id === "string"
? data.user_id
: data.user_id.toString(),
body: data.body,
title: data.title,
data: data.data,
});
}
},
onTap: null as null | typeof default_mobile.notif.onTap,
onReceive: null as null | typeof default_mobile.notif.onReceive,
},
};
return notifObject;
}
return {
...default_mobile,
};
};

View File

@ -1,169 +0,0 @@
import { w } from "../../../utils/types/general";
import importModule from "../../editor/tools/dynamic-import";
import { loadComponent } from "./comp";
import { LPage, PG } from "./global";
import { rebuildTree } from "./tree-logic";
export const routeLive = (p: PG, pathname: string) => {
if (p.status !== "loading" && p.status !== "not-found") {
if (!w.params) w.params = {};
let page_id = w.params.page_id;
if (!page_id) {
const found = p.route.lookup(pathname);
if (!found) {
p.status = "not-found";
} else {
if (found.params) {
for (const [k, v] of Object.entries(found.params)) {
w.params[k] = v;
}
}
page_id = found.id;
}
}
if (page_id) {
(window as any).pageid = page_id;
const promises: Promise<void>[] = [];
if (page_id !== p.page?.id) {
p.page = p.pages[page_id];
p.treeMeta = {};
p.portal = {};
}
if (!p.page || !p.page.content_tree) {
promises.push(loadNpmPage(p, page_id));
promises.push(loadPage(p, page_id));
}
if (promises.length > 0) {
p.status = "loading";
Promise.all(promises).then(async () => {
p.page = p.pages[page_id];
if (p.page) {
await pageLoaded(p);
p.render();
}
});
} else {
if (!firstRender[page_id]) {
firstRender[page_id] = true;
pageLoaded(p).then(p.render);
} else {
pageLoaded(p);
}
}
}
}
};
const firstRender = {} as Record<string, true>;
const pageLoaded = async (p: PG) => {
if (p.page) {
await rebuildTree(p, { render: false, note: "render", reset: false });
p.status = "ready";
} else {
p.status = "not-found";
}
};
export const preload = async (p: PG, pathname: string) => {
const found = p.route.lookup(pathname);
if (found) {
if (
(!p.pages[found.id] ||
(p.pages[found.id] && !p.pages[found.id].content_tree)) &&
!p.pagePreload[found.id]
) {
p.pagePreload[found.id] = true;
const dbpage = await p.loader.page(p, found.id);
if (dbpage) {
p.pages[dbpage.id] = dbpage;
const page = p.pages[dbpage.id];
if (page && page.content_tree) {
await loadComponent(p, page.content_tree);
}
delete p.pagePreload[found.id];
await loadNpmPage(p, dbpage.id);
}
}
}
};
const npmPageLoaded = {} as Record<string, true>;
const loadNpmPage = async (p: PG, id: string) => {
try {
if (!npmPageLoaded[id]) {
npmPageLoaded[id] = true;
if (typeof window.exports === "undefined") {
window.exports = {};
}
const npmurl = p.loader.npm(p, "page", id);
if (npmurl) {
await importModule(npmurl);
}
}
} catch (e) {
console.error(e);
}
};
const loading = {} as Record<string, Promise<LPage | null>>;
const loadPage = async (p: PG, id: string) => {
if (!loading[id]) {
loading[id] = p.loader.page(p, id);
}
const page = await loading[id];
if (page) {
p.pages[page.id] = {
id: page.id,
url: page.url,
name: page.name,
content_tree: page.content_tree as any,
js: (page as any).js_compiled as any,
};
const cur = p.pages[page.id];
if (cur && cur.content_tree) {
await loadComponent(p, cur.content_tree);
}
}
};
export const extractNavigate = (str: string) => {
return [
...findBetween(str, `navigate(`, `)`),
...findBetween(str, `href = `, `;`),
];
};
const findBetween = (text: string, opener: string, closer: string) => {
let i = 0;
let last = 0;
const founds: string[] = [];
while (true) {
const startIndex = text.indexOf(opener, i);
last = i;
if (startIndex >= 0) {
const char = text[startIndex + opener.length];
if (char === '"' || char === "'" || char === "`") {
const end = text.indexOf(
`${char}${closer}`,
startIndex + opener.length + 1
);
const found = text.substring(startIndex + opener.length + 1, end);
i = end + 2 + closer.length;
founds.push(found);
}
}
if (last === i) {
break;
}
}
return founds;
};

View File

@ -1,189 +0,0 @@
import { produceCSS } from "../../../utils/css/gen";
import { IContent } from "../../../utils/types/general";
import { IItem } from "../../../utils/types/item";
import { instantiateComp, loadComponent } from "./comp";
import { ItemMeta, LiveGlobal, PG } from "./global";
export const rebuildTree = async (
p: PG,
_: { note: string; render?: boolean; reset?: boolean }
) => {
if (!p.page?.content_tree) return;
p.treePending = new Promise<void>(async (resolve) => {
const treeMeta = p.treeMeta;
if (p.page) {
let childs = Object.values(p.page.content_tree?.childs || []);
if (
p.layout.section &&
p.layout.content &&
!p.page?.name.startsWith("layout:")
) {
childs = [p.layout.section];
p.layout.content.type = "item";
if (p.layout.content.type === "item") {
p.layout.content.childs = p.page.content_tree?.childs.map((e) => ({
...e,
type: "item",
})) as IItem[];
}
}
await Promise.all(
childs.map(async (item, idx) => {
await walk(p, {
treeMeta,
item,
parent_id: "root",
idx,
isLayout: !!(p.layout.section && p.layout.content),
});
}) || []
);
}
resolve();
p.treePending = null;
if (_.render !== false) {
p.render();
}
});
await p.treePending;
};
const walk = async (
p: PG,
val: {
treeMeta: (typeof LiveGlobal)["treeMeta"];
item?: IContent;
isLayout: boolean;
parent_id: string;
idx: number;
parent_comp?: ItemMeta["parent_comp"];
}
) => {
const treeMeta = val.treeMeta;
let item = val.item as IContent;
if (item.hidden === "all") return;
if (val.parent_comp) {
const pchild_ids = val.parent_comp.comp?.child_ids;
if (pchild_ids && item.originalId) {
if (pchild_ids[item.originalId]) {
item.id = pchild_ids[item.originalId];
}
}
}
if (item) {
let comp: ItemMeta["comp"] = undefined as any;
if (item.type === "item" && item.component?.id) {
comp = {
id: item.component.id,
child_ids: {},
};
}
const meta: ItemMeta = {
item,
parent_id: p.cachedParentID[item.id] || val.parent_id,
parent_comp: val.parent_comp as any,
className: produceCSS(item, {
mode: p.mode,
}),
comp,
indexedScope: treeMeta[item.id] ? treeMeta[item.id].indexedScope : {},
isLayout: val.isLayout,
};
treeMeta[meta.item.id] = meta;
if (item.type === "item" && item.component?.id) {
const cid = item.component.id;
let comp = p.comps.all[cid];
if (!comp) {
await loadComponent(p, cid);
comp = p.comps.all[cid];
}
if (comp) {
if (!p.compInstance[item.id]) {
p.compInstance[item.id] = {};
}
const child_ids = p.compInstance[item.id];
const itemnew = instantiateComp(
p,
item,
{ type: "i", item: comp },
child_ids
);
for (const [k, v] of Object.entries(itemnew)) {
if (k !== "id") (item as any)[k] = v;
}
const cprops = comp.content_tree.component?.props;
const iprops = item.component.props;
if (cprops && iprops) {
for (const [name, mprop] of Object.entries(cprops)) {
const jprop = iprops[name];
if (jprop) {
if (mprop.meta?.type === "content-element") {
let icontent = jprop.content;
if (icontent)
await walk(p, {
treeMeta,
item: icontent,
parent_id: item.id,
parent_comp: val.parent_comp,
idx: mprop.idx,
isLayout: meta.isLayout,
});
}
}
}
}
}
await Promise.all(
item.childs.map(async (child, idx) => {
return await walk(p, {
treeMeta,
item: child,
parent_comp: meta as any,
parent_id: item.id,
idx,
isLayout: meta.isLayout,
});
})
);
} else {
if (item.type !== "text" && Array.isArray(item.childs)) {
await Promise.all(
item.childs.map(async (child, idx) => {
let isLayout = false;
if (meta.isLayout && item.name !== "content") {
isLayout = true;
}
return await walk(p, {
treeMeta,
idx,
item: child,
parent_comp: val.parent_comp,
parent_id: item.id || "",
isLayout,
});
})
);
}
}
}
};

View File

@ -1,106 +0,0 @@
import { FC } from "react";
import { apiProxy } from "../../../base/load/api/api-proxy";
import { dbProxy } from "../../../base/load/db/db-proxy";
import { FNCompDef } from "../../../utils/types/meta-fn";
import { LItem } from "../elements/l-item";
import { PG } from "./global";
import { extractNavigate, preload } from "./route";
import { mergeScopeUpwards } from "./tree-scope";
export type PropCompFC = FC<{}>;
const jsxProps = {} as Record<string, any>;
export const treePropEval = (
p: PG,
id: string,
cprops: [string, FNCompDef][],
_scopeIndex?: Record<string, string>
) => {
const meta = p.treeMeta[id];
if (meta.item.type === "item" && meta.item.component) {
if (p.site.api_url) {
if (!p.script.db) p.script.db = dbProxy(p.site.api_url);
if (!p.script.api) p.script.api = apiProxy(p.site.api_url);
}
const props = meta.item.component.props;
const w = window as any;
const finalScope = mergeScopeUpwards(p, id, { _scopeIndex });
const args = {
...w.exports,
...finalScope,
db: p.script.db,
api: p.script.api,
};
const result: any = {};
for (const [name, _prop] of cprops) {
const prop = props[name] || _prop;
let value: any = null;
if (prop.valueBuilt) {
const fn = new Function(
...Object.keys(args),
`return ${prop.valueBuilt}`
);
try {
value = fn(...Object.values(args)) || null;
const navs = extractNavigate(prop.valueBuilt || "");
if (navs.length > 0) {
navs.map((nav) => preload(p, nav));
}
} catch (e) {
const cname = meta.item.name;
console.warn(e);
console.warn(
`ERROR in Component [${cname}], in prop [${name}]:\n ` + prop.value
);
}
}
if (prop.meta?.type === "content-element") {
if (!(typeof value === "object" && !!value && value._jsx)) {
const id = `${meta.item.id}-${name}`;
if (!jsxProps[id]) {
jsxProps[id] = {
_jsx: true,
Comp: ({
parent_id,
_scopeIndex,
}: {
parent_id: string;
_scopeIndex?: Record<string, any>;
}) => {
if (prop.content) {
const meta = p.treeMeta[prop.content.id];
if (meta) {
meta.parent_id = parent_id;
p.cachedParentID[prop.content.id] = parent_id;
return (
<LItem
id={prop.content.id}
fromProp={true}
_scopeIndex={_scopeIndex}
/>
);
}
}
return <></>;
},
};
}
value = jsxProps[id];
}
}
result[name] = value;
}
return result;
}
};

View File

@ -1,369 +0,0 @@
import hash_sum from "hash-sum";
import { FC, ReactNode, Suspense, useEffect, useState } from "react";
import { deepClone } from "web-utils";
import { apiProxy } from "../../../base/load/api/api-proxy";
import { dbProxy } from "../../../base/load/db/db-proxy";
import { ErrorBox } from "../../editor/elements/e-error";
import { ItemMeta, PG } from "./global";
import { extractNavigate, preload } from "./route";
export const JS_DEBUG = false;
export const treeScopeEval = (
p: PG,
id: string,
children: ReactNode,
js: string,
_scopeIndex?: Record<string, any>
) => {
const meta = p.treeMeta[id];
const className = meta.className;
let item = meta.item;
const adv = item.adv;
let args = {};
try {
if (!meta.memoize) {
meta.memoize = {};
}
const memoizeKey = hash_sum(_scopeIndex) || "default";
if (!meta.memoize[memoizeKey]) {
meta.memoize[memoizeKey] = {
Local: createLocal(p, id, _scopeIndex),
PassProp: createPassProp(p, id, _scopeIndex),
};
}
if (typeof adv?.js === "string") {
const navs = extractNavigate(adv.js || "");
if (navs.length > 0) {
navs.map((nav) => preload(p, nav));
}
}
// prepare args
if (p.site.api_url) {
if (!p.script.db) p.script.db = dbProxy(p.site.api_url);
if (!p.script.api) p.script.api = apiProxy(p.site.api_url);
}
const w = window as any;
const finalScope = mergeScopeUpwards(p, id, { _scopeIndex });
for (const [k, v] of Object.entries(finalScope)) {
if (v && typeof v === "object") {
const t: {
_jsx: true;
Comp: FC<{ parent_id: string; _scopeIndex?: Record<string, any> }>;
} = v as any;
if (t._jsx && t.Comp) {
finalScope[k] = (
<t.Comp parent_id={meta.item.id} _scopeIndex={_scopeIndex} />
);
}
}
}
const output = { jsx: null as any };
args = {
...w.exports,
...finalScope,
...meta.memoize[memoizeKey],
db: p.script.db,
api: p.script.api,
children,
props: { className },
useEffect: useEffect,
render: (jsx: ReactNode) => {
output.jsx = (
<ErrorBox>
<Suspense>{jsx}</Suspense>
</ErrorBox>
);
},
};
// execute
const fn = new Function(...Object.keys(args), js);
const res = fn(...Object.values(args));
if (res instanceof Promise) {
res.catch((e: any) => {
console.warn(e);
console.warn(
(
`ERROR in ${item.type} [${item.name}]:\n ` +
((adv?.js || "") as any)
).trim()
);
console.warn(`Available var:`, args, `\n\n`);
});
}
return output.jsx;
} catch (e) {
console.warn(e);
console.warn(
(
`ERROR in ${item.type} [${item.name}]:\n ` + ((adv?.js || "") as any)
).trim()
);
console.warn(`Available var:`, args, `\n\n`);
}
};
export const mergeScopeUpwards = (
p: PG,
id: string,
opt?: {
_scopeIndex?: Record<string, any>;
debug?: boolean;
each?: (meta: ItemMeta, values: Record<string, any>) => boolean;
}
) => {
const meta = p.treeMeta[id];
if (!meta.scope) {
meta.scope = {};
}
let cur = meta;
const finalScope: any = {};
while (cur) {
let scope = null;
let indexedScope = null;
if (cur.indexedScope && opt?._scopeIndex) {
const idx = opt._scopeIndex[cur.item.id];
if (typeof idx !== "undefined" && cur.indexedScope[idx]) {
indexedScope = cur.indexedScope[idx];
}
}
if (indexedScope || cur.scope || cur.comp?.propval) {
scope = { ...cur.scope, ...indexedScope, ...cur.comp?.propval };
for (const [k, v] of Object.entries(scope)) {
if (typeof finalScope[k] === "undefined") finalScope[k] = v;
}
if (opt?.each) {
if (!opt.each(cur, scope)) {
break;
}
}
}
cur = p.treeMeta[cur.parent_id];
}
return finalScope;
};
const modifyChildIndex = (
child: ReactNode | ReactNode[],
_scopeIndex: Record<string, any>
) => {
if (Array.isArray(child)) {
const childs: any[] = [];
for (const c of child) {
childs.push(modifyChildIndex(c, _scopeIndex));
}
return childs;
}
if (typeof child === "object" && child) {
return { ...child, props: { ...(child as any).props, _scopeIndex } };
}
return child;
};
const createPassProp = (
p: PG,
id: string,
_existingScopeIndex?: Record<string, any>
) => {
return function (
arg: Record<string, any> & { children: ReactNode; idx?: any }
) {
const meta = p.treeMeta[id];
if (typeof arg.idx !== "undefined" && meta && meta.item && meta.item.id) {
meta.indexedScope[arg.idx] = {};
for (const [k, v] of Object.entries(arg)) {
if (k === "children") continue;
meta.indexedScope[arg.idx][k] = v;
}
const scopeIndex = { ..._existingScopeIndex, [meta.item.id]: arg.idx };
if (!meta.scope) {
meta.scope = {};
}
for (const [k, v] of Object.entries(arg)) {
if (k === "children") continue;
meta.scope[k] = v;
}
return modifyChildIndex(arg.children, scopeIndex);
}
if (!meta.scope) {
meta.scope = {};
}
for (const [k, v] of Object.entries(arg)) {
if (k === "children") continue;
meta.scope[k] = v;
}
return arg.children;
};
};
const cachedLocal = {} as Record<string, Record<string, any>>;
const cachedPath = {} as Record<string, Record<string, any>>;
const cachedLayout = {} as Record<string, true>;
const createLocal = (
p: PG,
id: string,
_existingScopeIndex?: Record<string, any>
) => {
const Local = ({
name,
idx: local_id,
value,
effect,
children,
hook,
deps,
cache,
}: {
name: string;
value: any;
idx?: string;
effect?: (value: any) => void | Promise<void>;
children: ReactNode;
hook?: (value: any) => void;
deps?: any[];
cache?: boolean;
}) => {
const meta = p.treeMeta[id];
const [_, set] = useState({});
let scope = null as any;
if (!local_id) {
if (!meta.scope) {
meta.scope = {};
}
scope = meta.scope;
} else {
if (!meta.indexedScope) {
meta.indexedScope = {};
}
if (!meta.indexedScope[local_id]) meta.indexedScope[local_id] = {};
scope = meta.indexedScope[local_id];
}
const render = () => {
if (!local_id) {
if (meta.render) meta.render();
else p.render();
} else {
set({});
}
};
const genScope = () => {
try {
const nval = deepClone(value);
if (!scope[name]) {
scope[name] = {
...nval,
render,
};
} else {
for (const [k, v] of Object.entries(nval)) {
scope[name][k] = v;
}
scope[name].render = render;
}
} catch (e) {
console.warn(e);
}
};
let page_id = p.page?.id || "";
const itemid = meta.item.id + (local_id ? `_${local_id}` : "");
if (meta.isLayout) {
page_id = "layout";
if (cachedLayout[meta.item.id]) {
scope[name] = cachedLocal[page_id][itemid];
scope[name].render = render;
}
}
if (
page_id !== "layout" ||
(page_id === "layout" && !cachedLayout[meta.item.id])
) {
if (!cachedLocal[page_id]) {
cachedLocal[page_id] = {};
}
if (!cachedPath[page_id]) {
cachedPath[page_id] = {};
}
if (cachedLocal[page_id][itemid]) {
if (cache === false) {
if (cachedPath[page_id][itemid] !== location.href) {
cachedPath[page_id][itemid] = location.href;
genScope();
cachedLocal[page_id][itemid] = scope[name];
} else {
scope[name] = cachedLocal[page_id][itemid];
scope[name].render = render;
}
} else {
scope[name] = cachedLocal[page_id][itemid];
scope[name].render = render;
}
} else {
genScope();
cachedLocal[page_id][itemid] = scope[name];
cachedPath[page_id][itemid] = location.href;
}
}
if (typeof hook === "function") {
try {
hook(scope[name]);
} catch (e) {
console.warn(e);
}
}
useEffect(() => {
if (effect) {
if (meta.isLayout) {
if (cachedLayout[meta.item.id]) {
return;
}
cachedLayout[meta.item.id] = true;
}
try {
effect(scope[name]);
} catch (e) {
console.warn(e);
}
}
}, [...(deps || []), location.href]);
if (local_id) {
const scopeIndex = { ..._existingScopeIndex, [meta.item.id]: local_id };
return modifyChildIndex(children, scopeIndex);
}
return children;
};
return Local;
};

View File

@ -1,66 +0,0 @@
import { w } from "../../utils/types/general";
import { Loader } from "../live/logic/global";
const cache = {
site: null as any,
pages: [] as any,
api: null,
npm_pages: [] as string[],
};
export const mobileLoader: Loader = {
async site(p, id) {
const res = (await load(`/content/site/site.json`)) as any;
cache.site = res;
const pages = (await load(`/content/site/pages.json`)) as any;
cache.pages = pages;
const npm_pages = (await load(`/content/site/npm_pages.json`)) as any;
cache.npm_pages = npm_pages;
w.serverurl = res.config.api_url;
w.apiurl = res.config.api_url;
w.prasiApi = {
[res.config.api_url]: { apiEntry: res.api },
};
return res;
},
async comp(p, id) {
const comp = (await load(`/content/comps/${id}.json`)) as any;
p.comps.all[id] = comp;
return comp;
},
npm(p, type, id) {
if (type === "site") return `/content/npm/site/index.js`;
if (cache.npm_pages.includes(id)) {
return `/content/npm/pages/${id}/index.js`;
}
return "";
},
async page(p, id) {
const page = cache.pages.find((x: any) => x.id === id);
if (page && !page.content_tree) {
const res = (await load(`/content/pages/${id}.json`)) as any;
return res;
}
return null;
},
async pages(p, id) {
return cache.pages;
},
};
const load = async (url: string) => {
const res = await fetch(`${(w as any).mobilepath}${url}`);
try {
const text = await res.text();
const json = JSON.parse(text);
return json;
} catch (e) {
return null;
}
};

View File

@ -1,59 +0,0 @@
import { w } from "../../utils/types/general";
import { Loader } from "../live/logic/global";
const base = `/_web/${(window as any).id_site}`;
const cache = { site: null as any, pages: [] as any, api: null };
export const siteLoader: Loader = {
async site(p, id) {
const res = (await load(`/site?prod=1`)) as {
site: any;
pages: any;
api: any;
};
cache.site = res.site;
cache.pages = res.pages;
cache.api = res.api;
w.serverurl = res.site.config.api_url;
w.apiurl = res.site.config.api_url;
w.prasiApi = {
[res.site.config.api_url]: { apiEntry: res.api },
};
return res.site;
},
async comp(p, id) {
const comp = (await load(`/comp/${id}`)) as any;
p.comps.all[id] = comp;
return comp;
},
npm(p, type, id) {
if (type === "site") return `/_web/${cache.site.id}/npm-site/site.js`;
return `/_web/${cache.site.id}/npm-page/${id}/page.js`;
},
async page(p, id) {
const page = cache.pages.find((x: any) => x.id === id);
if (page && !page.content_tree) {
const res = (await load(`/page/${id}`)) as any;
return res;
}
return null;
},
async pages(p, id) {
return cache.pages;
},
};
const load = async (url: string) => {
const res = await fetch(`${base}${url}`);
try {
const text = await res.text();
const json = JSON.parse(text);
return json;
} catch (e) {
return null;
}
};

View File

@ -1,55 +0,0 @@
import { FC, useState } from "react";
import { createRoot } from "react-dom/client";
import { GlobalContext, defineReact, defineWindow } from "web-utils";
import { siteLoader } from "./site-loader";
import { registerMobile } from "../live/logic/mobile";
import { mobileLoader } from "./mobile-loader";
const w = window as unknown as {
prasiContext: any;
rootRender: any;
mobile: any;
mobilepath?: any;
};
if (!w.mobile) {
w.mobile = registerMobile();
}
w.prasiContext = {
global: {},
render() {},
};
const Root: FC<{ url: URL; Live: any }> = ({ url, Live }) => {
const [_, render] = useState({});
w.prasiContext.render = () => {
render({});
};
const Provider = GlobalContext.Provider as FC<{ value: any; children: any }>;
return (
<Provider value={w.prasiContext}>
<Live
domain={url.host}
pathname={location.pathname}
loader={w.mobilepath ? mobileLoader : siteLoader}
/>
</Provider>
);
};
(async () => {
const div = document.getElementById("root");
if (div) {
const root = createRoot(div);
const url = new URL(location.href);
await defineWindow(false);
defineReact();
const { Live } = await import("../live/live");
root.render(<Root url={url} Live={Live} />);
if (document.body.classList.contains("opacity-0")) {
document.body.classList.remove("opacity-0");
}
}
})();

View File

@ -61,7 +61,7 @@ export type MPage = TypedDoc<{
}>; }>;
export type IContent = ISection | IItem | IText; export type IContent = ISection | IItem | IText;
export type MContent = MSection | MItem | MText; export type MContent = MItem;
export type RenderContentProp = Partial<{ export type RenderContentProp = Partial<{
active: IContent | null; active: IContent | null;