check point
This commit is contained in:
parent
9755f4bc1e
commit
3f780647b2
File diff suppressed because one or more lines are too long
|
|
@ -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 { code } from "../../code";
|
||||
import { Parcel } from "@parcel/core";
|
||||
import { context } from "esbuild";
|
||||
import { removeAsync } from "fs-jetpack";
|
||||
import { $ } from "bun";
|
||||
let bundler = new Parcel({
|
||||
entries: "a.js",
|
||||
defaultConfig: "@parcel/config-default",
|
||||
});
|
||||
import { code } from "../../code";
|
||||
|
||||
export const initFrontEnd = async (root: string, id_site: string) => {
|
||||
let existing = code.internal.frontend[id_site];
|
||||
|
||||
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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export const codeInternal = {
|
|||
get frontend() {
|
||||
if (!g.prasi_code) g.prasi_code = {};
|
||||
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() {
|
||||
if (!g.prasi_code) g.prasi_code = {};
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -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
|
||||
/>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -18,14 +18,6 @@ export const ed = {
|
|||
url: "/ed/:site_id/:page_id",
|
||||
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 = {
|
||||
url: "/vi/:domain/**",
|
||||
page: () => import("./page/vi"),
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { loadApiProxyDef } from "./base/load/api/api-proxy-def";
|
|||
import { dbProxy } from "./base/load/db/db-proxy";
|
||||
import { Root } from "./base/root";
|
||||
import "./index.css";
|
||||
import { registerMobile } from "./render/live/logic/mobile";
|
||||
import { sworkerAddCache, sworkerRegister } from "./sworker-boot";
|
||||
import { w } from "./utils/types/general";
|
||||
|
||||
|
|
@ -13,7 +12,6 @@ const start = async () => {
|
|||
let react = {
|
||||
root: null as null | ReactRoot,
|
||||
};
|
||||
w.mobile = registerMobile();
|
||||
|
||||
const cur = new URL(w.basehost || location.href);
|
||||
const base_url = `${cur.protocol}//${cur.host}`;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { syncronize } from "y-pojo";
|
||||
import { fillID } from "../../../../../../../render/editor/tools/fill-id";
|
||||
import { IContent, MContent } from "../../../../../../../utils/types/general";
|
||||
import { IItem } from "../../../../../../../utils/types/item";
|
||||
import { getMetaById } from "../../../../../logic/active/get-meta";
|
||||
import { PG } from "../../../../../logic/ed-global";
|
||||
import { treeRebuild } from "../../../../../logic/tree/build";
|
||||
import { fillID } from "../../../../../logic/tree/fill-id";
|
||||
|
||||
export const edActionClone = (p: PG, item: IContent) => {
|
||||
const mitem = getMetaById(p, item.id)?.mitem;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { syncronize } from "y-pojo";
|
||||
import { fillID } from "../../../../../../../render/editor/tools/fill-id";
|
||||
import { IItem } from "../../../../../../../utils/types/item";
|
||||
import { getMetaById } from "../../../../../logic/active/get-meta";
|
||||
import { PG } from "../../../../../logic/ed-global";
|
||||
import { treeRebuild } from "../../../../../logic/tree/build";
|
||||
import { fillID } from "../../../../../logic/tree/fill-id";
|
||||
|
||||
export const edActionDetach = (p: PG, item: IItem) => {
|
||||
const mitem = getMetaById(p, item.id)?.mitem;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { syncronize } from "y-pojo";
|
||||
import { fillID } from "../../../../../../../render/editor/tools/fill-id";
|
||||
import { IContent } from "../../../../../../../utils/types/general";
|
||||
import { IItem, MItem } from "../../../../../../../utils/types/item";
|
||||
import { MSection } from "../../../../../../../utils/types/section";
|
||||
|
|
@ -8,6 +7,7 @@ import { PG, active } from "../../../../../logic/ed-global";
|
|||
import { treeRebuild } from "../../../../../logic/tree/build";
|
||||
import { TypedArray } from "yjs-types";
|
||||
import { loadComponent } from "../../../../../logic/comp/load";
|
||||
import { fillID } from "../../../../../logic/tree/fill-id";
|
||||
|
||||
export const edActionPaste = async (p: PG, item: IContent) => {
|
||||
let mitem = getMetaById(p, item.id)?.mitem;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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;
|
||||
`;
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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 || "" }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
@ -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 />;
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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 };
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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,
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
@ -61,7 +61,7 @@ export type MPage = TypedDoc<{
|
|||
}>;
|
||||
|
||||
export type IContent = ISection | IItem | IText;
|
||||
export type MContent = MSection | MItem | MText;
|
||||
export type MContent = MItem;
|
||||
|
||||
export type RenderContentProp = Partial<{
|
||||
active: IContent | null;
|
||||
|
|
|
|||
Loading…
Reference in New Issue