diff --git a/app/srv/package.json b/app/srv/package.json index 1ea4ea1b..e58a57d8 100644 --- a/app/srv/package.json +++ b/app/srv/package.json @@ -12,6 +12,7 @@ "mime-types": "^2.1.35", "msgpackr": "^1.9.9", "radix3": "^1.1.0", + "uuid": "^9.0.1", "y-pojo": "^0.0.8", "yjs": "^13.6.8", "yjs-types": "^0.0.1" diff --git a/app/srv/ws/sync/actions/comp_new.ts b/app/srv/ws/sync/actions/comp_new.ts index 3a5f9e14..2bfa925b 100644 --- a/app/srv/ws/sync/actions/comp_new.ts +++ b/app/srv/ws/sync/actions/comp_new.ts @@ -2,5 +2,8 @@ import { SAction } from "../actions"; import { SyncConnection } from "../type"; export const comp_new: SAction["comp"]["new"] = async function ( - this: SyncConnection -) {}; + this: SyncConnection, + arg +) { + console.log(arg); +}; diff --git a/app/srv/ws/sync/actions/index.ts b/app/srv/ws/sync/actions/index.ts index 27c12b74..02288f85 100644 --- a/app/srv/ws/sync/actions/index.ts +++ b/app/srv/ws/sync/actions/index.ts @@ -2,6 +2,7 @@ export * from "./site_load"; export * from "./site_group"; export * from "./page_load"; export * from "./comp_load"; +export * from "./comp_new"; export * from "./comp_group"; export * from "./yjs_um"; export * from "./yjs_sv_local"; diff --git a/app/srv/ws/sync/editor/load.ts b/app/srv/ws/sync/editor/load-default.ts similarity index 100% rename from app/srv/ws/sync/editor/load.ts rename to app/srv/ws/sync/editor/load-default.ts diff --git a/app/srv/ws/sync/editor/load-sitepage.ts b/app/srv/ws/sync/editor/load-sitepage.ts new file mode 100644 index 00000000..bfb881f6 --- /dev/null +++ b/app/srv/ws/sync/editor/load-sitepage.ts @@ -0,0 +1,37 @@ +import { validate } from "uuid"; +import { user } from "../entity/user"; + +export const loadSitePage = async ( + user_id: string, + site_id: string, + page_id?: string +) => { + if (validate(site_id)) { + const site = await db.site.findFirst({ + where: { id: site_id }, + select: { id: true }, + }); + + if (site) { + await user.conf.set(user_id, "site_id", site_id); + } + + let page = null; + if (validate(page_id || "")) { + page = await db.page.findFirst({ + where: { id: page_id, id_site: site_id, is_deleted: false }, + select: { id: true }, + }); + } + if (!page) { + page = await db.page.findFirst({ + where: { id_site: site_id, is_deleted: false }, + select: { id: true }, + }); + } + if (page) { + await user.conf.set(user_id, "page_id", page.id); + } + } + return user.conf.get(user_id); +}; diff --git a/app/srv/ws/sync/sync-handler.ts b/app/srv/ws/sync/sync-handler.ts index 457a8482..d0f3f47b 100644 --- a/app/srv/ws/sync/sync-handler.ts +++ b/app/srv/ws/sync/sync-handler.ts @@ -5,10 +5,11 @@ import { WSData } from "../../../../pkgs/core/server/create"; import { ClientEvent } from "../../../web/src/utils/sync/ws-client"; import { SyncActionPaths } from "./actions-def"; import * as actions from "./actions/index"; -import { loadDefaultSite } from "./editor/load"; +import { loadDefaultSite } from "./editor/load-default"; +import { loadSitePage } from "./editor/load-sitepage"; +import { conns, wconns } from "./entity/conn"; import { UserConf, user } from "./entity/user"; import { SyncType } from "./type"; -import { conns, wconns } from "./entity/conn"; const packr = new Packr({ structuredClone: true }); export const sendWS = (ws: ServerWebSocket, msg: any) => { @@ -40,13 +41,19 @@ export const syncHandler: WebSocketHandler = { if (conn) { const msg = packr.unpack(Buffer.from(raw)); if (msg.type === SyncType.UserID) { - const { user_id } = msg; + const { user_id, page_id, site_id } = msg; conn.user_id = user_id; - const conf = await user.conf.getOrCreate(user_id); - if (!conf.site_id) { + let conf = await user.conf.getOrCreate(user_id); + if (site_id) { + const newconf = await loadSitePage(user_id, site_id, page_id); + if (newconf) conf = newconf; + } else if (!conf.site_id) { await loadDefaultSite(user_id); } + + console.log(conf); + conn.conf = new Proxy(conf, { get(_, p) { const conf = user.conf.get(user_id); diff --git a/app/web/src/render/ed/logic/ed-sync.tsx b/app/web/src/render/ed/logic/ed-sync.tsx index fd46a98b..ea41c779 100644 --- a/app/web/src/render/ed/logic/ed-sync.tsx +++ b/app/web/src/render/ed/logic/ed-sync.tsx @@ -4,6 +4,7 @@ import { Loading } from "../../../utils/ui/loading"; import { PG } from "./ed-global"; import { Y } from "../../../../../srv/ws/sync/entity/docs"; import { treeRebuild } from "./tree/build"; +import { w } from "../../../utils/types/general"; export const edInitSync = (p: PG) => { const session = JSON.parse( @@ -13,26 +14,34 @@ export const edInitSync = (p: PG) => { navigate("/login"); return ; } - const paramsOK = - !!params.site_id && - !!params.page_id && - params.site_id !== "_" && - params.page_id !== "_"; if (!p.sync) { clientStartSync({ user_id: session.data.user.id, + site_id: params.site_id, + page_id: params.page_id, events: { + connected() { + if (w.offline) console.log("connected"); + w.offline = false; + p.render(); + }, + disconnected() { + console.log("offline, reconnecting..."); + w.offline = true; + p.render(); + return { + reconnect: true, + }; + }, editor_start(e) { - if (!paramsOK) { - if (e.site_id && e.page_id) { - p.site.id = e.site_id; - p.page.cur.id = e.page_id; - navigate(`/ed/${e.site_id}/${e.page_id}`); - } + if (params.site_id !== e.site_id || params.page_id !== e.page_id) { + p.site.id = e.site_id; + p.page.cur.id = e.page_id; + navigate(`/ed/${e.site_id}/${e.page_id}`); } else { - p.site.id = params.site_id; - p.page.cur.id = params.page_id; + p.site.id = e.site_id; + p.page.cur.id = e.page_id; p.render(); } }, diff --git a/app/web/src/render/ed/panel/popup/comp-group.tsx b/app/web/src/render/ed/panel/popup/comp-group.tsx index 9c247053..0b999e81 100644 --- a/app/web/src/render/ed/panel/popup/comp-group.tsx +++ b/app/web/src/render/ed/panel/popup/comp-group.tsx @@ -9,13 +9,14 @@ export const EdPopCompGroup = () => { useEffect(() => { (async () => { - if (!p.comp.group[p.site.id]) { - p.comp.group[p.site.id] = await p.sync.comp.group(p.site.id); + if (p.ui.popup.comp_group) { + if (!p.comp.group[p.site.id]) { + p.comp.group[p.site.id] = await p.sync.comp.group(p.site.id); + } + p.render(); } - console.log(p.comp.group[p.site.id]); - p.render(); })(); - }, []); + }, [p.ui.popup.comp_group]); if (!p.ui.popup.comp_group) return null; const pop = p.ui.popup.comp_group; diff --git a/app/web/src/render/ed/panel/tree/node/item/indent-hook.ts b/app/web/src/render/ed/panel/tree/node/item/indent-hook.ts index 30a2ad7b..f1f4b6f9 100644 --- a/app/web/src/render/ed/panel/tree/node/item/indent-hook.ts +++ b/app/web/src/render/ed/panel/tree/node/item/indent-hook.ts @@ -13,10 +13,12 @@ export const indentHook = ( let shouldOpen = open[p.page.cur.id] || []; const cur = p.page.meta[active.item_id]; - let meta = p.page.meta[cur.parent_item.id]; - while (meta) { - if (meta.item.id) shouldOpen.push(meta.item.id); - meta = p.page.meta[meta.parent_item.id]; + if (cur && cur.parent_item) { + let meta = p.page.meta[cur.parent_item.id]; + while (meta) { + if (meta.item.id) shouldOpen.push(meta.item.id); + meta = p.page.meta[meta.parent_item.id]; + } } if (shouldOpen.length > 0 && local.tree) { diff --git a/app/web/src/utils/sync/ws-client.ts b/app/web/src/utils/sync/ws-client.ts index fa828fce..b14770a4 100644 --- a/app/web/src/utils/sync/ws-client.ts +++ b/app/web/src/utils/sync/ws-client.ts @@ -48,6 +48,8 @@ const sendWs = (ws: WebSocket, msg: any) => { export const clientStartSync = async (arg: { user_id: string; + site_id?: string; + page_id?: string; events: { editor_start: (arg: UserConf) => void; site_loaded: (arg: { site: ESite }) => void; @@ -56,11 +58,13 @@ export const clientStartSync = async (arg: { id: string; sv_local: Uint8Array; }) => void; + disconnected: () => { reconnect: boolean }; + connected: () => void; }; }) => { - const { user_id, events } = arg; + const { user_id, site_id, page_id, events } = arg; conf.idb = initIDB(user_id); - await connect(user_id, events); + await connect({ user_id, site_id, page_id }, events); return new DeepProxy( SyncActionDefinition, ({ target, trapName, value, key, DEFAULT, PROXY }) => { @@ -91,7 +95,11 @@ export const clientStartSync = async (arg: { ) as unknown as typeof SyncActions; }; -const connect = (user_id: string, event: ClientEventObject) => { +const connect = ( + opt: { user_id: string; page_id?: string; site_id?: string }, + event: ClientEventObject +) => { + const { user_id, page_id, site_id } = opt; conf.event = event; if (w.offline) { return new Promise(async (resolve) => { @@ -104,55 +112,64 @@ const connect = (user_id: string, event: ClientEventObject) => { } }); } else { - return new Promise((resolve) => { + return new Promise((resolve, reject) => { if (!conf.ws) { - const url = new URL(location.href); - url.pathname = "/sync"; - url.protocol = url.protocol === "http:" ? "ws:" : "wss:"; + let reconnect = 0; + const retry = () => { + const url = new URL(location.href); + url.pathname = "/sync"; + url.protocol = url.protocol === "http:" ? "ws:" : "wss:"; - const ws = new WebSocket(url.toString()); + const ws = new WebSocket(url.toString()); - ws.onopen = () => { - sendWs(ws, { type: SyncType.UserID, user_id }); - conf.ws = ws; - }; - ws.onclose = async () => { - console.log("disconnected.."); - w.offline = true; - if (!conf.ws) { - await connect(user_id, event); - resolve(); - } - }; - ws.onmessage = async (e) => { - const raw = e.data as Blob; - const msg = packr.unpack(Buffer.from(await raw.arrayBuffer())); - if (WS_DEBUG) console.log(`%c⬇`, `color:red`, msg); - - if (msg.type === SyncType.ClientID) { - conf.client_id = msg.client_id; - resolve(); - } else if (msg.type === SyncType.Event) { - const eventName = msg.event as ClientEvent; - - if (event[eventName]) { - if (offlineEvents.includes(eventName)) { - saveEventOffline(eventName, msg.data); - } - event[eventName](msg.data); + ws.onopen = () => { + console.clear(); + sendWs(ws, { type: SyncType.UserID, user_id, site_id, page_id }); + conf.ws = ws; + event.connected(); + }; + ws.onclose = async () => { + const res = event.disconnected(); + if (res.reconnect) { + setTimeout(async () => { + reconnect++; + retry(); + }, reconnect * 5000); + } else { + reject(); } - } else if (msg.type === SyncType.ActionResult) { - const pending = runtime.action.pending[msg.argid]; - if (pending) { - delete runtime.action.pending[msg.argid]; - const idb = conf.idb; - if (idb) { - await set(msg.argid, msg.val, idb); + }; + ws.onmessage = async (e) => { + const raw = e.data as Blob; + const msg = packr.unpack(Buffer.from(await raw.arrayBuffer())); + if (WS_DEBUG) console.log(`%c⬇`, `color:red`, msg); + + if (msg.type === SyncType.ClientID) { + conf.client_id = msg.client_id; + resolve(); + } else if (msg.type === SyncType.Event) { + const eventName = msg.event as ClientEvent; + + if (event[eventName]) { + if (offlineEvents.includes(eventName)) { + saveEventOffline(eventName, msg.data); + } + event[eventName](msg.data); + } + } else if (msg.type === SyncType.ActionResult) { + const pending = runtime.action.pending[msg.argid]; + if (pending) { + delete runtime.action.pending[msg.argid]; + const idb = conf.idb; + if (idb) { + await set(msg.argid, msg.val, idb); + } + pending.resolve(msg.val); } - pending.resolve(msg.val); } - } + }; }; + retry(); } }); } diff --git a/bun.lockb b/bun.lockb index f78c6f29..27d19bc0 100755 Binary files a/bun.lockb and b/bun.lockb differ