prasi-bun/app/web/src/nova/ed/logic/ed-sync.tsx

273 lines
7.7 KiB
TypeScript

import { createRouter, RadixRouter } from "radix3";
import { compress, decompress } from "wasm-gzip";
import { deepClone } from "web-utils";
import * as Y from "yjs";
import { registerSiteTypings } from "../../../utils/script/typings";
import { clientStartSync } from "../../../utils/sync/ws-client";
import { w } from "../../../utils/types/general";
import { Loading } from "../../../utils/ui/loading";
import { loadFrontEnd, loadTypings } from "./code-loader";
import { updateComponentMeta } from "./comp/load";
import { EmptySite, PG } from "./ed-global";
import { reloadPage } from "./ed-route";
import { loadSite } from "./ed-site";
import { treeRebuild } from "./tree/build";
import { format } from "date-fns";
const decoder = new TextDecoder();
const page = {
list: [] as { id: string; url: string }[],
route: null as null | RadixRouter<{ id: string; url: string }>,
};
export const loadSession = (p: PG) => {
const session = JSON.parse(
localStorage.getItem("prasi-session") || "null"
) as { data: { user: { id: string; username: string } } };
if (!session && location.pathname.startsWith("/ed/")) {
location.href = "/login";
return <Loading note="logging in" />;
}
if (session?.data?.user) {
p.user.id = session.data.user.id;
p.user.username = session.data.user.username;
} else {
p.user.id = "ab1390f5-40d5-448e-a8c3-84b0fb600930";
p.user.username = "anonymous";
}
};
export const edInitSync = (p: PG) => {
loadSession(p);
if (location.pathname.startsWith("/vi/")) {
if (page.list.length === 0) {
_db.page
.findMany({
where: {
id_site: params.site_id,
is_deleted: false,
is_default_layout: false,
},
select: {
id: true,
url: true,
},
})
.then((list) => {
page.list = list;
edInitSync(p);
});
return;
}
if (!page.route) {
page.route = createRouter();
for (const e of page.list) {
page.route.insert(e.url, e);
}
}
const arrpath = location.pathname.split("/");
const pathname = "/" + arrpath.slice(3).join("/");
if (!params.page_id) {
const res = page.route.lookup(pathname);
if (res) {
params.page_id = res.id;
if (res.params) {
for (const [k, v] of Object.entries(res.params)) {
if (!["site_id", "page_id"].includes(k)) {
params[k] = v;
}
}
}
}
}
}
if (p.sync) {
if (p.site.id === "--loading--") return false;
if (params.site_id !== p.site.id) {
p.site = deepClone(EmptySite);
p.site.id = "--loading--";
p.ui.popup.code.init = false;
p.sync.site.load(params.site_id).then(async (site) => {
if (site) {
await loadSite(p, site, "from-sync");
p.render();
} else {
alert("Site not found. redirecting...");
location.href = `/ed/`;
}
return;
});
return false;
}
if (
!p.page.cur.id &&
!params.page_id &&
params.site_id &&
location.pathname.startsWith("/ed/")
) {
_db.page
.findFirst({
where: {
is_deleted: false,
is_default_layout: false,
id_site: params.site_id,
},
select: { id: true },
})
.then((e) => {
if (params.site_id === "_") {
alert("asdsa");
return;
}
if (e) location.href = `/ed/${params.site_id}/${e.id}`;
});
return false;
}
}
if (!p.sync && !p.sync_assigned) {
p.sync_assigned = true;
p.site = deepClone(EmptySite);
clientStartSync({
user_id: p.user.id,
site_id: params.site_id,
page_id: params.page_id,
events: {
opened() {
if (w.offline) {
console.log("reconnected!");
w.offline = false;
p.ui.syncing = true;
p.render();
} else {
w.offline = false;
p.render();
}
},
shakehand(client_id) {
p.user.client_id = client_id;
console.log(`Prasi connected: ${client_id}`);
},
disconnected() {
console.log("offline, reconnecting...");
w.offline = true;
p.render();
return {
reconnect: true,
};
},
async editor_start(e) {
if (params.site_id !== e.site_id || params.page_id !== e.page_id) {
if (!p.page.cur.id) {
p.site.id = e.site_id;
p.page.cur.id = e.page_id;
if (location.pathname.startsWith("/ed/")) {
location.href = `/ed/${e.site_id}/${e.page_id}`;
}
}
} else {
p.site.id = e.site_id;
p.page.cur.id = e.page_id;
p.render();
}
},
site_updated(site) {
for (const [k, v] of Object.entries(site)) {
if (k === "js" || k === "js_compiled") {
p.site[k] = decoder.decode(decompress(v as any));
} else {
(p.site as any)[k] = v;
}
}
p.render();
},
async code_changes({ ts, mode, status }) {
if (mode === "frontend") {
if (status === "ok") {
console.clear();
console.log(
`${format(Date.now(), "HH:mm:ss")} 🚧 Code updated from vscode `
);
await loadFrontEnd(p, ts);
} else if (status === "building") {
console.log(
`${format(
Date.now(),
"HH:mm:ss"
)} ⏳ Code changed from vscode, rebuilding...`
);
}
} else {
await loadTypings(p);
if (p.ui.monaco) {
registerSiteTypings(p.ui.monaco, p);
}
}
await treeRebuild(p);
p.render();
},
async remote_svlocal(data) {
let doc = null as any;
if (data.type === "page" && p.page.cur.id === data.id) {
doc = p.page.doc as Y.Doc;
} else if (data.type === "comp" && p.comp.list[data.id]) {
doc = p.comp.list[data.id].doc;
}
if (doc && p.sync) {
const diff_remote = Y.encodeStateAsUpdate(
doc,
decompress(data.sv_local)
);
const sv_remote = Y.encodeStateVector(doc);
const sv = Buffer.from(compress(sv_remote));
const diff = Buffer.from(compress(diff_remote));
const res = await p.sync.yjs.sv_remote(
data.type,
data.id,
sv,
diff
);
if (res) {
Y.applyUpdate(doc, decompress(res.diff), "sv_remote");
if (data.type === "page") {
delete p.preview.meta_cache[data.id];
await treeRebuild(p, { note: "sv_remote" });
} else if (data.type === "comp") {
const updated = await updateComponentMeta(p, doc, data.id);
if (updated) {
p.comp.list[data.id].meta = updated.meta;
p.comp.list[data.id].tree = updated.tree;
}
await treeRebuild(p, { note: "sv_remote" });
}
p.render();
}
}
},
},
}).then((e) => {
p.sync = e;
});
return false;
} else {
if (
params.page_id !== p.page.cur.id &&
location.pathname.startsWith("/ed")
) {
reloadPage(p, params.page_id, "change page");
return false;
}
}
return true;
};