diff --git a/app/srv/package.json b/app/srv/package.json index d6c775bc..d34a8386 100644 --- a/app/srv/package.json +++ b/app/srv/package.json @@ -8,6 +8,7 @@ "@paralleldrive/cuid2": "^2.2.2", "@types/mime-types": "^2.1.2", "esbuild": "^0.19.4", + "lmdb": "^2.8.5", "mime-types": "^2.1.35", "msgpackr": "^1.9.9", "radix3": "^1.1.0" diff --git a/app/srv/ws/sync/actions.ts b/app/srv/ws/sync/actions.ts index 446f555f..210340e7 100644 --- a/app/srv/ws/sync/actions.ts +++ b/app/srv/ws/sync/actions.ts @@ -1,5 +1,6 @@ import { component, site, page } from "dbgen"; + export const SyncActions = { site: { all: () => diff --git a/app/srv/ws/sync/editor/load.ts b/app/srv/ws/sync/editor/load.ts new file mode 100644 index 00000000..5dfd0494 --- /dev/null +++ b/app/srv/ws/sync/editor/load.ts @@ -0,0 +1,31 @@ +import { user } from "../user"; + +export const syncEditorLoad = async (user_id: string) => { + const conf = user.conf.get(user_id); + if (conf) { + if (!conf.site_id) { + const site = await db.site.findFirst({ + where: { + id_user: user_id, + is_deleted: false, + }, + select: { id: true }, + }); + if (site) conf.site_id = site.id; + } + + if (conf.site_id && !conf.page_id) { + const page = await db.page.findFirst({ + select: { id: true }, + where: { + id_site: conf.site_id, + is_deleted: false, + }, + }); + + if (page) { + conf.page_id = page.id; + } + } + } +}; diff --git a/app/srv/ws/sync/sync-handler.ts b/app/srv/ws/sync/sync-handler.ts index fb662832..fb237e3e 100644 --- a/app/srv/ws/sync/sync-handler.ts +++ b/app/srv/ws/sync/sync-handler.ts @@ -2,6 +2,7 @@ import { createId } from "@paralleldrive/cuid2"; import { ServerWebSocket, WebSocketHandler } from "bun"; import { Packr } from "msgpackr"; import { WSData } from "../../../../pkgs/core/server/create"; +import { SyncType } from "./type"; const packr = new Packr({ structuredClone: true }); const conns = new Map< @@ -25,7 +26,7 @@ export const syncHandler: WebSocketHandler = { msg: { pending: {}, resolve: {} }, }); wconns.set(ws, client_id); - ws.sendBinary(packr.pack({ type: "client_id", client_id })); + ws.sendBinary(packr.pack({ type: SyncType.ClientID, client_id })); }, close(ws, code, reason) { const conn_id = wconns.get(ws); @@ -40,7 +41,7 @@ export const syncHandler: WebSocketHandler = { const conn = conns.get(conn_id); if (conn) { const msg = packr.unpack(Buffer.from(raw)); - if (msg.type === "user_id") { + if (msg.type === SyncType.UserID) { const { user_id } = msg; conn.user_id = user_id; } diff --git a/app/srv/ws/sync/type.ts b/app/srv/ws/sync/type.ts new file mode 100644 index 00000000..e3b97518 --- /dev/null +++ b/app/srv/ws/sync/type.ts @@ -0,0 +1,5 @@ +export enum SyncType { + ClientID, + UserID, + Event +} \ No newline at end of file diff --git a/app/srv/ws/sync/user.ts b/app/srv/ws/sync/user.ts new file mode 100644 index 00000000..eebb8522 --- /dev/null +++ b/app/srv/ws/sync/user.ts @@ -0,0 +1,33 @@ +import { dir } from "dir"; +import { open } from "lmdb"; +import { g } from "utils/global"; + +const defaultConf = { + site_id: "", + page_id: "", +}; +type UserConf = typeof defaultConf; + +const db = open({ + name: "user-conf", + path: dir.path(`${g.datadir}/lmdb/user-conf.lmdb`), +}); + +export const user = { + conf: { + get(user_id: string) { + return db.get(user_id); + }, + set(user_id: string, key: T, value: UserConf[T]) { + let current = this.get(user_id); + if (!current) { + db.put(user_id, structuredClone(defaultConf)); + current = this.get(user_id); + } + + if (current) { + db.put(user_id, { ...current, [key]: value }); + } + }, + }, +}; diff --git a/app/web/src/base/page/all.tsx b/app/web/src/base/page/all.tsx index 428ebc33..2844b768 100644 --- a/app/web/src/base/page/all.tsx +++ b/app/web/src/base/page/all.tsx @@ -6,13 +6,17 @@ export default page({ url: "**", component: ({}) => { useEffect(() => { - if (localStorage.getItem("prasi-session")) { - navigate("/editor/"); + if (location.pathname.startsWith("/ed/")) { + navigate("/ed/_/_"); + } else { + navigate("/editor/_/_"); + } } else { navigate("/login"); } }, []); + return ; }, }); diff --git a/app/web/src/base/page/ed.tsx b/app/web/src/base/page/ed.tsx deleted file mode 100644 index eb6923e7..00000000 --- a/app/web/src/base/page/ed.tsx +++ /dev/null @@ -1,130 +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/**", - component: ({}) => { - const p = useGlobal(EditorGlobal, "EDITOR"); - - const local = useLocal({ - loading: true, - session: null as any, - notfound: false, - init: false, - }); - const site_id = params.site_id === "_" ? "" : params.site_id; - const page_id = params.page_id === "_" ? "" : params.page_id; - - useEffect(() => { - if (!local.init) { - (async () => { - let ses: any = null; - try { - ses = JSON.parse(localStorage.getItem("prasi-session") || ""); - } catch (e) {} - - await new Promise(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; - } - } - } - - navigate(`/editor/${site_id}/${page_id}`); - })(); - } - }, [local.init]); - - return ; - }, -}); diff --git a/app/web/src/base/page/ned.tsx b/app/web/src/base/page/ned.tsx index e907ac5d..d7c8f1f5 100644 --- a/app/web/src/base/page/ned.tsx +++ b/app/web/src/base/page/ned.tsx @@ -2,11 +2,12 @@ import { page, useGlobal } from "web-utils"; import { EditorGlobal } from "../../render/editor/logic/global"; import { Loading } from "../../utils/ui/loading"; import { clientStartSync } from "../../utils/sync/client"; +import { EDGlobal } from "../../render/ed/logic/global"; export default page({ - url: "/ned/:site_id/:page_id", + url: "/ed/:site_id/:page_id", component: ({}) => { - const p = useGlobal(EditorGlobal, "EDITOR"); + const p = useGlobal(EDGlobal, "EDITOR"); const session = JSON.parse( localStorage.getItem("prasi-session") || "null" @@ -17,9 +18,10 @@ export default page({ } if (!p.sync) { - // p.sync = clientStartSync({ - // user_id: session.data.user.id, - // }); + p.sync = clientStartSync({ + user_id: session.data.user.id, + events: { editor_start() {} }, + }); return ; } diff --git a/app/web/src/base/pages.ts b/app/web/src/base/pages.ts index 14a81af7..b4c6873a 100644 --- a/app/web/src/base/pages.ts +++ b/app/web/src/base/pages.ts @@ -14,10 +14,6 @@ export const all = { url: "**", page: () => import("./page/all"), }; -export const ed = { - url: "/editor/**", - page: () => import("./page/ed"), -}; export const editor = { url: "/editor/:site_id/:page_id", page: () => import("./page/editor"), @@ -27,6 +23,6 @@ export const live = { page: () => import("./page/live"), }; export const ned = { - url: "/ned/:site_id/:page_id", + url: "/ed/:site_id/:page_id", page: () => import("./page/ned"), }; diff --git a/app/web/src/render/ed/logic/global.ts b/app/web/src/render/ed/logic/global.ts new file mode 100644 index 00000000..d684de82 --- /dev/null +++ b/app/web/src/render/ed/logic/global.ts @@ -0,0 +1,5 @@ +import { clientStartSync } from "../../../utils/sync/client"; + +export const EDGlobal = { + sync: null as unknown as ReturnType, +}; diff --git a/app/web/src/utils/sync/client.ts b/app/web/src/utils/sync/client.ts index 5db15d66..6fc274fe 100644 --- a/app/web/src/utils/sync/client.ts +++ b/app/web/src/utils/sync/client.ts @@ -6,6 +6,7 @@ import { stringify } from "safe-stable-stringify"; import { SyncActions } from "../../../../srv/ws/sync/actions"; import { SyncActionDefinition } from "../../../../srv/ws/sync/actions-def"; import { initIDB } from "./idb"; +import { SyncType } from "../../../../srv/ws/sync/type"; const packr = new Packr({ structuredClone: true }); const conf = { ws: null as null | WebSocket, @@ -21,7 +22,11 @@ type User = { export const clientStartSync = async (arg: { user_id: string; events: { - site_open: (arg: { site_id: string; user: User }) => void; + editor_start: (arg: { + user_id: string; + site_id?: string; + page_id?: string; + }) => void; }; }) => { const { user_id, events } = arg; @@ -69,15 +74,16 @@ const connect = (user_id: string) => { const ws = new WebSocket(url.toString()); conf.ws = ws; ws.onopen = () => { - ws.send(packr.pack({ type: "user_id", user_id })); + ws.send(packr.pack({ type: SyncType.UserID, user_id })); }; ws.onmessage = async (e) => { const raw = e.data as Blob; const msg = packr.unpack(Buffer.from(await raw.arrayBuffer())); - if (msg.type === "client_id") { + if (msg.type === SyncType.ClientID) { conf.client_id = msg.client_id; resolve(ws); + } else if (msg.type === "event") { } }; } diff --git a/bun.lockb b/bun.lockb index 5958f7e1..5c9da669 100755 Binary files a/bun.lockb and b/bun.lockb differ