wip fix
This commit is contained in:
parent
42c1793880
commit
7497b32195
|
|
@ -1,14 +1,19 @@
|
|||
import { dir } from "dir";
|
||||
import { apiContext } from "service-srv";
|
||||
import { g } from "utils/global";
|
||||
import { validate } from "uuid";
|
||||
import { gzipAsync } from "../ws/sync/entity/zlib";
|
||||
import { code } from "../ws/sync/editor/code/util-code";
|
||||
|
||||
export const _ = {
|
||||
url: "/deploy/**",
|
||||
url: "/deploy/:site_id/**",
|
||||
async api() {
|
||||
const { req, res } = apiContext(this);
|
||||
|
||||
const pathname = req.params["*"];
|
||||
if (pathname === "index.html" || pathname === "_") {
|
||||
return new Response(
|
||||
const pathname: string = req.params["*"] || "";
|
||||
const site_id = req.params.site_id as string;
|
||||
|
||||
const index_html = new Response(
|
||||
`\
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
|
@ -20,11 +25,98 @@ export const _ = {
|
|||
</head>
|
||||
<body class="flex-col flex-1 w-full min-h-screen flex opacity-0">
|
||||
<div id="root"></div>
|
||||
<script src="/deploy/main.js" type="module"></script>
|
||||
<script>
|
||||
window._prasi={basepath: "/deploy/${site_id}",site_id:"${site_id}"}
|
||||
</script>
|
||||
<script src="/deploy/${site_id}/main.js" type="module"></script>
|
||||
</body>
|
||||
</html>`,
|
||||
{ headers: { "content-type": "text/html" } }
|
||||
);
|
||||
|
||||
if (!validate(site_id))
|
||||
return new Response("site not found", { status: 403 });
|
||||
|
||||
if (pathname.startsWith("_prasi")) {
|
||||
const action = pathname.split("/")[1];
|
||||
|
||||
switch (action) {
|
||||
case "code": {
|
||||
const arr = pathname.split("/").slice(2);
|
||||
const codepath = arr.join("/");
|
||||
const build_path = code.path(site_id, "site", "build", codepath);
|
||||
const file = Bun.file(build_path);
|
||||
if (!(await file.exists()))
|
||||
return new Response("Code file not found", { status: 403 });
|
||||
return new Response(file);
|
||||
}
|
||||
case "route": {
|
||||
const site = await _db.site.findFirst({
|
||||
where: { id: site_id },
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
domain: true,
|
||||
responsive: true,
|
||||
config: true,
|
||||
},
|
||||
});
|
||||
|
||||
let api_url = "";
|
||||
if (site && site.config && (site.config as any).api_url) {
|
||||
api_url = (site.config as any).api_url;
|
||||
delete (site as any).config;
|
||||
}
|
||||
const urls = await _db.page.findMany({
|
||||
where: {
|
||||
id_site: site_id,
|
||||
is_default_layout: false,
|
||||
is_deleted: false,
|
||||
},
|
||||
select: { url: true, id: true },
|
||||
});
|
||||
return gzipAsync(
|
||||
JSON.stringify({ site: { ...site, api_url }, urls }) as any
|
||||
);
|
||||
}
|
||||
case "page": {
|
||||
const page_id = pathname.split("/").pop() as string;
|
||||
if (validate(page_id)) {
|
||||
const page = await _db.page.findFirst({
|
||||
where: { id: page_id },
|
||||
select: { content_tree: true },
|
||||
});
|
||||
if (page) {
|
||||
return gzipAsync(JSON.stringify(page.content_tree) as any);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case "comp": {
|
||||
const ids = req.params.ids as string[];
|
||||
const result = {} as Record<string, any>;
|
||||
if (Array.isArray(ids)) {
|
||||
const comps = await _db.component.findMany({
|
||||
where: { id: { in: ids } },
|
||||
select: { content_tree: true, id: true },
|
||||
});
|
||||
for (const comp of comps) {
|
||||
result[comp.id] = comp.content_tree;
|
||||
}
|
||||
}
|
||||
return gzipAsync(JSON.stringify(result) as any);
|
||||
}
|
||||
}
|
||||
return new Response("action " + action + ": not found");
|
||||
} else if (pathname === "index.html" || pathname === "_") {
|
||||
return index_html;
|
||||
} else {
|
||||
const res = dir.path(`${g.datadir}/deploy/${pathname}`);
|
||||
const file = Bun.file(res);
|
||||
if (!(await file.exists())) {
|
||||
return index_html;
|
||||
}
|
||||
return new Response(file);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
import { RadixRouter, createRouter } from "radix3";
|
||||
import { IRoot } from "../../../utils/types/root";
|
||||
import { PG } from "../../ed/logic/ed-global";
|
||||
import { IMeta } from "../../vi/utils/types";
|
||||
import { IItem } from "../../../utils/types/item";
|
||||
|
||||
const w = window as any;
|
||||
|
||||
export const base = {
|
||||
root: null as unknown as URL,
|
||||
url(...arg: any[]) {
|
||||
const pathname = arg
|
||||
.map((e) => (Array.isArray(e) ? e.join("") : e))
|
||||
.join("");
|
||||
if (pathname.startsWith("/")) return this.root + pathname;
|
||||
else return this.root.toString() + "/" + pathname;
|
||||
},
|
||||
get pathname() {
|
||||
return location.pathname.substring(base.root.pathname.length);
|
||||
},
|
||||
site: { id: w._prasi?.site_id } as {
|
||||
id: string;
|
||||
name: string;
|
||||
responsive: PG["site"]["responsive"];
|
||||
domain: string;
|
||||
api_url: string;
|
||||
code: {
|
||||
mode: "new";
|
||||
};
|
||||
api: any;
|
||||
db: any;
|
||||
},
|
||||
init_local_effect: {} as any,
|
||||
mode: "" as "desktop" | "mobile",
|
||||
route: {
|
||||
status: "init" as "init" | "loading" | "ready",
|
||||
router: null as null | RadixRouter<{ id: string; url: string }>,
|
||||
},
|
||||
comp: {
|
||||
list: {} as Record<string, IItem>,
|
||||
pending: new Set<string>(),
|
||||
},
|
||||
page: {
|
||||
id: "",
|
||||
url: "",
|
||||
root: null as null | IRoot,
|
||||
meta: null as null | Record<string, IMeta>,
|
||||
cache: {} as Record<
|
||||
string,
|
||||
{
|
||||
id: string;
|
||||
url: string;
|
||||
root: IRoot;
|
||||
meta: Record<string, IMeta>;
|
||||
}
|
||||
>,
|
||||
},
|
||||
};
|
||||
|
||||
export const initBaseConfig = () => {
|
||||
if (!base.root) {
|
||||
let url = new URL(location.href);
|
||||
if (w._prasi.basepath) {
|
||||
url.pathname = w._prasi.basepath;
|
||||
}
|
||||
|
||||
base.root = new URL(`${url.protocol}//${url.host}${url.pathname}`);
|
||||
if (base.root.pathname.endsWith("/")) {
|
||||
base.root.pathname = base.root.pathname.substring(
|
||||
0,
|
||||
base.root.length - 1
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
import { IContent } from "../../../utils/types/general";
|
||||
import { IItem } from "../../../utils/types/item";
|
||||
import { ISection } from "../../../utils/types/section";
|
||||
import { base } from "./base";
|
||||
import { decompressBlob } from "./util";
|
||||
|
||||
export const scanComponent = async (items: IContent[]) => {
|
||||
const comp = base.comp;
|
||||
|
||||
for (const item of items) {
|
||||
if (item && item.type !== "text") {
|
||||
scanSingle(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (comp.pending.size > 0) {
|
||||
try {
|
||||
const raw = await (
|
||||
await fetch(base.url`_prasi/comp`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ ids: [...comp.pending] }),
|
||||
})
|
||||
).blob();
|
||||
const res = JSON.parse(
|
||||
await (await decompressBlob(raw)).text()
|
||||
) as Record<string, IItem>;
|
||||
for (const [id, item] of Object.entries(res)) {
|
||||
comp.pending.delete(id);
|
||||
comp.list[id] = item;
|
||||
}
|
||||
await scanComponent(Object.values(res));
|
||||
} catch (e) {}
|
||||
}
|
||||
};
|
||||
|
||||
const scanSingle = (item: IItem | ISection) => {
|
||||
const comp = base.comp;
|
||||
if (item.type === "item") {
|
||||
const comp_id = item.component?.id;
|
||||
if (comp_id) {
|
||||
if (!comp.list[comp_id] && !comp.pending.has(comp_id)) {
|
||||
comp.pending.add(comp_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (item.childs) {
|
||||
for (const child of item.childs) {
|
||||
if (child && child.type !== "text") {
|
||||
scanSingle(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { base } from "./base";
|
||||
import { IRoot } from "../../../utils/types/root";
|
||||
import { decompressBlob } from "./util";
|
||||
|
||||
export const loadPage = async (page_id: string) => {
|
||||
const raw = await (await fetch(base.url`_prasi/page/${page_id}`)).blob();
|
||||
const res = JSON.parse(await (await decompressBlob(raw)).text()) as IRoot;
|
||||
return res;
|
||||
};
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import { base } from "./base";
|
||||
import parseUA from "ua-parser-js";
|
||||
|
||||
export const detectResponsiveMode = () => {
|
||||
const p = base;
|
||||
if (p.site.id) {
|
||||
if (!p.mode && !!p.site.responsive) {
|
||||
if (
|
||||
p.site.responsive !== "mobile-only" &&
|
||||
p.site.responsive !== "desktop-only"
|
||||
) {
|
||||
const parsed = parseUA();
|
||||
p.mode = parsed.device.type === "mobile" ? "mobile" : "desktop";
|
||||
} else if (p.site.responsive === "mobile-only") {
|
||||
p.mode = "mobile";
|
||||
} else if (p.site.responsive === "desktop-only") {
|
||||
p.mode = "desktop";
|
||||
}
|
||||
}
|
||||
if (localStorage.getItem("prasi-editor-mode")) {
|
||||
p.mode = localStorage.getItem("prasi-editor-mode") as any;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
import { createRouter } from "radix3";
|
||||
import { base } from "./base";
|
||||
import { decompressBlob } from "./util";
|
||||
import { IMeta } from "../../vi/utils/types";
|
||||
import { IRoot } from "../../../utils/types/root";
|
||||
import { genMeta } from "../../vi/meta/meta";
|
||||
import { IItem } from "../../../utils/types/item";
|
||||
import { apiProxy } from "../../../base/load/api/api-proxy";
|
||||
import { dbProxy } from "../../../base/load/db/db-proxy";
|
||||
|
||||
export const initBaseRoute = async () => {
|
||||
const raw = await (await fetch(base.url`_prasi/route`)).blob();
|
||||
const router = createRouter<{ id: string; url: string }>();
|
||||
try {
|
||||
const res = JSON.parse(await (await decompressBlob(raw)).text()) as {
|
||||
site: any;
|
||||
urls: {
|
||||
id: string;
|
||||
url: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
if (res && res.site && res.urls) {
|
||||
base.site = res.site;
|
||||
base.site.code = { mode: "new" };
|
||||
await injectSiteScript();
|
||||
|
||||
base.site.api = apiProxy(base.site.api_url);
|
||||
base.site.db = dbProxy(base.site.api_url);
|
||||
|
||||
const w = window as any;
|
||||
w.serverurl = base.site.api_url;
|
||||
w.db = base.site.db;
|
||||
w.api = base.site.api;
|
||||
|
||||
|
||||
for (const item of res.urls) {
|
||||
router.insert(item.url, item);
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
return router;
|
||||
};
|
||||
|
||||
const injectSiteScript = () => {
|
||||
return new Promise<void>((done) => {
|
||||
const d = document;
|
||||
const script = d.createElement("script");
|
||||
script.onload = async () => {
|
||||
done();
|
||||
};
|
||||
const url = base.site.api_url;
|
||||
|
||||
if (!localStorage.getItem("api-ts-" + url)) {
|
||||
localStorage.setItem("api-ts-" + url, Date.now().toString());
|
||||
}
|
||||
|
||||
const ts = localStorage.getItem("api-ts-" + url);
|
||||
|
||||
script.src = `${url}/_prasi/load.js?url=${url}&v3&ts=${ts}`;
|
||||
|
||||
if (!document.querySelector(`script[src="${script.src}"]`)) {
|
||||
d.body.appendChild(script);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const rebuildMeta = (meta: Record<string, IMeta>, root: IRoot) => {
|
||||
for (const item of root.childs) {
|
||||
genMeta(
|
||||
{
|
||||
comps: base.comp.list,
|
||||
meta,
|
||||
mode: "page",
|
||||
},
|
||||
{ item }
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
export async function decompressBlob(blob: Blob) {
|
||||
let ds = new DecompressionStream("gzip");
|
||||
let decompressedStream = blob.stream().pipeThrough(ds);
|
||||
return await new Response(decompressedStream).blob();
|
||||
}
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
import { createRoot } from "react-dom/client";
|
||||
import { defineReact, defineWindow } from "web-utils";
|
||||
import { Root } from "./root";
|
||||
import { initBaseConfig } from "./base/base";
|
||||
|
||||
(async () => {
|
||||
initBaseConfig();
|
||||
const div = document.getElementById("root");
|
||||
if (div) {
|
||||
const root = createRoot(div);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,129 @@
|
|||
import { useState } from "react";
|
||||
import { useLocal } from "web-utils";
|
||||
import { DeadEnd } from "../../utils/ui/deadend";
|
||||
import { Loading } from "../../utils/ui/loading";
|
||||
import { base } from "./base/base";
|
||||
import { loadPage } from "./base/page";
|
||||
import { detectResponsiveMode } from "./base/responsive";
|
||||
import { initBaseRoute, rebuildMeta } from "./base/route";
|
||||
import { scanComponent } from "./base/component";
|
||||
import { Vi } from "../vi/vi";
|
||||
import { evalCJS } from "../ed/logic/ed-sync";
|
||||
|
||||
const w = window as any;
|
||||
|
||||
export const Root = () => {
|
||||
const [_, render] = useState({});
|
||||
const local = useLocal({});
|
||||
|
||||
return <></>;
|
||||
// #region init
|
||||
if (base.route.status !== "ready") {
|
||||
if (base.route.status === "init") {
|
||||
base.route.status = "loading";
|
||||
initBaseRoute().then(async (router) => {
|
||||
detectResponsiveMode();
|
||||
base.route.status = "ready";
|
||||
base.route.router = router;
|
||||
|
||||
const site_script = evalCJS(
|
||||
await (
|
||||
await fetch(`/deploy/${base.site.id}/_prasi/code/index.js`)
|
||||
).text()
|
||||
);
|
||||
if (site_script) {
|
||||
for (const [k, v] of Object.entries(site_script)) {
|
||||
w[k] = v;
|
||||
}
|
||||
}
|
||||
|
||||
local.render();
|
||||
});
|
||||
}
|
||||
return <Loading note="Loading router" />;
|
||||
}
|
||||
// #endregion
|
||||
|
||||
// #region routing
|
||||
const router = base.route.router;
|
||||
if (!router) return <DeadEnd>Failed to create Router</DeadEnd>;
|
||||
|
||||
const page = router.lookup(base.pathname);
|
||||
if (!page) return <DeadEnd>Page Not Found</DeadEnd>;
|
||||
|
||||
w.params = page.params;
|
||||
|
||||
base.page.id = page.id;
|
||||
base.page.url = page.url;
|
||||
const cache = base.page.cache[page.id];
|
||||
|
||||
if (!cache) {
|
||||
loadPage(page.id)
|
||||
.then(async (root) => {
|
||||
const p = {
|
||||
id: page.id,
|
||||
url: page.url,
|
||||
root,
|
||||
meta: {},
|
||||
};
|
||||
await scanComponent(root.childs);
|
||||
rebuildMeta(p.meta, root);
|
||||
base.page.cache[p.id] = p;
|
||||
local.render();
|
||||
})
|
||||
.catch(() => {
|
||||
local.render();
|
||||
});
|
||||
|
||||
return <Loading note="Loading page" />;
|
||||
} else {
|
||||
base.page.root = cache.root;
|
||||
base.page.meta = cache.meta;
|
||||
}
|
||||
// #endregion
|
||||
|
||||
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 main-content-preview",
|
||||
base.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;
|
||||
`
|
||||
)}
|
||||
>
|
||||
<Vi
|
||||
api_url={base.site.api_url}
|
||||
entry={Object.values(base.page.root.childs)
|
||||
.filter((e) => e)
|
||||
.map((e) => e.id)}
|
||||
meta={base.page.meta}
|
||||
mode={base.mode}
|
||||
page_id={base.page.id}
|
||||
site_id={base.site.id}
|
||||
db={base.site.db}
|
||||
api={base.site.api}
|
||||
script={{ init_local_effect: base.init_local_effect }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
import { get, set } from "idb-keyval";
|
||||
import { IContent } from "../../../../utils/types/general";
|
||||
import { IItem, MItem } from "../../../../utils/types/item";
|
||||
import { FMCompDef } from "../../../../utils/types/meta-fn";
|
||||
import { initLoadComp } from "../../../vi/meta/comp/init-comp-load";
|
||||
import { genMeta } from "../../../vi/meta/meta";
|
||||
import { nav } from "../../../vi/render/script/extract-nav";
|
||||
import { loadCompSnapshot, loadComponent } from "../comp/load";
|
||||
import { loadCompSnapshot } from "../comp/load";
|
||||
import { IMeta, PG, active } from "../ed-global";
|
||||
import { assignMitem } from "./assign-mitem";
|
||||
import { pushTreeNode } from "./build/push-tree";
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import { FMCompDef } from "../../../../utils/types/meta-fn";
|
||||
|
||||
export const treeCacheBuild = async (p: PG, page_id: string) => {
|
||||
const page_cache = p.preview.page_cache[page_id];
|
||||
|
|
@ -30,6 +29,8 @@ export const treeCacheBuild = async (p: PG, page_id: string) => {
|
|||
page_cache.root as unknown as IItem,
|
||||
{
|
||||
async load(comp_ids) {
|
||||
if (!p.sync) return;
|
||||
|
||||
const ids = comp_ids.filter((id) => !p.comp.loaded[id]);
|
||||
const comps = await p.sync.comp.load(ids, true);
|
||||
let result = Object.entries(comps);
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export const Vi: FC<{
|
|||
api?: any;
|
||||
db?: any;
|
||||
layout?: VG["layout"];
|
||||
script?: { init_local_effect: Record<string, boolean> };
|
||||
script: { init_local_effect: Record<string, boolean> };
|
||||
visit?: VG["visit"];
|
||||
render_stat?: "enabled" | "disabled";
|
||||
on_status_changed?: (status: VG["status"]) => void;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
import { FC } from "react";
|
||||
|
||||
export const DeadEnd: FC<{ children: any }> = ({ children }) => {
|
||||
return (
|
||||
<div className="flex items-center justify-center w-full h-full fixed inset-0">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
import { dir } from "dir";
|
||||
import { context } from "esbuild";
|
||||
import { g } from "./utils/global";
|
||||
|
||||
const ctx = await context({
|
||||
bundle: true,
|
||||
absWorkingDir: dir.path(""),
|
||||
entryPoints: [dir.path("app/web/src/nova/deploy/main.tsx")],
|
||||
outdir: dir.path("app/static/deploy"),
|
||||
outdir: dir.path(`${g.datadir}/deploy`),
|
||||
splitting: true,
|
||||
format: "esm",
|
||||
jsx: "transform",
|
||||
|
|
@ -77,6 +77,8 @@ if (!g.parcel) {
|
|||
await parcelBuild();
|
||||
}
|
||||
|
||||
await import("./build-deploy");
|
||||
|
||||
const { createServer } = await import("./server/create");
|
||||
await createServer();
|
||||
g.status = "ready";
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { join } from "path";
|
|||
|
||||
export const dir = {
|
||||
path: (path: string) => {
|
||||
return join(process.cwd(), path);
|
||||
const final_path = path.split("/").filter((e) => e !== "..").join('/');
|
||||
return join(process.cwd(), final_path);
|
||||
},
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue