diff --git a/app/srv/ws/sync/actions-def.ts b/app/srv/ws/sync/actions-def.ts index 09305fdb..8c175c3b 100644 --- a/app/srv/ws/sync/actions-def.ts +++ b/app/srv/ws/sync/actions-def.ts @@ -19,7 +19,8 @@ export const SyncActionDefinition = { }, "yjs": { "sv_local": "12", - "diff_local": "13" + "diff_local": "13", + "sv_remote": "14" } }; export const SyncActionPaths = { @@ -36,5 +37,6 @@ export const SyncActionPaths = { "10": "page.list", "11": "page.load", "12": "yjs.sv_local", - "13": "yjs.diff_local" + "13": "yjs.diff_local", + "14": "yjs.sv_remote" }; diff --git a/app/srv/ws/sync/actions.ts b/app/srv/ws/sync/actions.ts index bfca5c7c..56be8694 100644 --- a/app/srv/ws/sync/actions.ts +++ b/app/srv/ws/sync/actions.ts @@ -43,5 +43,11 @@ export const SyncActions = { id: string, bin: Uint8Array ) => {}, + sv_remote: async ( + mode: "page" | "comp", + id: string, + sv: Uint8Array, + diff: Uint8Array + ) => ({}) as { diff: Uint8Array } | void, }, }; diff --git a/app/srv/ws/sync/actions/comp_load.ts b/app/srv/ws/sync/actions/comp_load.ts index 48571661..d5babfaa 100644 --- a/app/srv/ws/sync/actions/comp_load.ts +++ b/app/srv/ws/sync/actions/comp_load.ts @@ -2,9 +2,9 @@ import { syncronize } from "y-pojo"; import { docs } from "../entity/docs"; import { snapshot } from "../entity/snapshot"; import { gzipAsync } from "../entity/zlib"; -import { ActionCtx } from "../type"; +import { SyncConnection } from "../type"; -export const comp_load = async function (this: ActionCtx, id: string) { +export const comp_load = async function (this: SyncConnection, id: string) { let snap = snapshot.get("comp", id); let ydoc = docs.comp[id]; diff --git a/app/srv/ws/sync/actions/comp_redo.ts b/app/srv/ws/sync/actions/comp_redo.ts index fa9718e0..ecd2ad01 100644 --- a/app/srv/ws/sync/actions/comp_redo.ts +++ b/app/srv/ws/sync/actions/comp_redo.ts @@ -1,3 +1,3 @@ -import { ActionCtx } from "../type"; +import { SyncConnection } from "../type"; -export const comp_redo = async function (this: ActionCtx, id: string) {}; +export const comp_redo = async function (this: SyncConnection, id: string) {}; diff --git a/app/srv/ws/sync/actions/comp_undo.ts b/app/srv/ws/sync/actions/comp_undo.ts index e47ee334..871f1250 100644 --- a/app/srv/ws/sync/actions/comp_undo.ts +++ b/app/srv/ws/sync/actions/comp_undo.ts @@ -1,3 +1,3 @@ -import { ActionCtx } from "../type"; +import { SyncConnection } from "../type"; -export const comp_undo = async function (this: ActionCtx, id: string) {}; +export const comp_undo = async function (this: SyncConnection, id: string) {}; diff --git a/app/srv/ws/sync/actions/index.ts b/app/srv/ws/sync/actions/index.ts index f5c44931..897e9cb6 100644 --- a/app/srv/ws/sync/actions/index.ts +++ b/app/srv/ws/sync/actions/index.ts @@ -8,3 +8,4 @@ export * from "./comp_undo"; export * from "./comp_redo"; export * from "./yjs_sv_local"; export * from "./yjs_diff_local"; +export * from "./yjs_sv_remote"; diff --git a/app/srv/ws/sync/actions/page_load.ts b/app/srv/ws/sync/actions/page_load.ts index 64fc1190..a849d791 100644 --- a/app/srv/ws/sync/actions/page_load.ts +++ b/app/srv/ws/sync/actions/page_load.ts @@ -2,16 +2,49 @@ import { syncronize } from "y-pojo"; import { SAction } from "../actions"; import { Y, docs } from "../entity/docs"; import { snapshot } from "../entity/snapshot"; +import { user } from "../entity/user"; import { gzipAsync } from "../entity/zlib"; -import { ActionCtx } from "../type"; +import { SyncConnection, SyncType } from "../type"; +import { conns } from "../entity/conn"; +import { sendWS } from "../sync-handler"; export const page_load: SAction["page"]["load"] = async function ( - this: ActionCtx, + this: SyncConnection, id: string ) { let snap = snapshot.get("page", id); let ydoc = docs.page[id]; - + + if (this.conf) this.conf.page_id = id; + + const createUndoManager = (root: Y.Map) => { + const um = new Y.UndoManager(root, { ignoreRemoteMapChanges: false }); + return um; + }; + + const attachOnUpdate = (doc: Y.Doc, um: Y.UndoManager) => { + doc.on("update", async (update: Uint8Array, origin: any) => { + if (origin === um) { + } else { + const sv_local = await gzipAsync(update); + user.active.findAll({ page_id: id }).map((e) => { + if (e.client_id === origin) return; + const ws = conns.get(e.client_id)?.ws; + if (ws) + sendWS(ws, { + type: SyncType.Event, + event: "remote_svlocal", + data: { type: "page", sv_local, id }, + }); + }); + } + }); + }; + + const defaultActive = { + select: "" as "" | "comp" | "item" | "section" | "text", + }; + if (!snap && !ydoc) { const page = await db.page.findFirst({ where: { id } }); if (page) { @@ -19,7 +52,7 @@ export const page_load: SAction["page"]["load"] = async function ( let root = doc.getMap("map"); syncronize(root, { id, root: page.content_tree }); - const um = new Y.UndoManager(root, { ignoreRemoteMapChanges: true }); + const um = createUndoManager(root); docs.page[id] = { doc: doc as any, id, @@ -27,6 +60,8 @@ export const page_load: SAction["page"]["load"] = async function ( }; const bin = Y.encodeStateAsUpdate(doc); + attachOnUpdate(doc, um); + snapshot.update({ bin, id, @@ -34,6 +69,15 @@ export const page_load: SAction["page"]["load"] = async function ( name: page.name, ts: Date.now(), url: page.url, + id_site: page.id_site, + }); + + user.active.add({ + ...defaultActive, + client_id: this.client_id, + user_id: this.user_id, + site_id: page.id_site, + page_id: page.id, }); return { @@ -48,13 +92,23 @@ export const page_load: SAction["page"]["load"] = async function ( Y.applyUpdate(doc, snap.bin); let root = doc.getMap("map"); - const um = new Y.UndoManager(root, { ignoreRemoteMapChanges: true }); + const um = createUndoManager(root); + attachOnUpdate(doc, um); + docs.page[id] = { doc: doc as any, id, um, }; + user.active.add({ + ...defaultActive, + client_id: this.client_id, + user_id: this.user_id, + site_id: snap.id_site, + page_id: snap.id, + }); + return { id: id, url: snap.url, @@ -62,6 +116,14 @@ export const page_load: SAction["page"]["load"] = async function ( snapshot: await gzipAsync(snap.bin), }; } else if (snap && ydoc) { + user.active.add({ + ...defaultActive, + client_id: this.client_id, + user_id: this.user_id, + site_id: snap.id_site, + page_id: snap.id, + }); + return { id: snap.id, url: snap.url, diff --git a/app/srv/ws/sync/actions/page_redo.ts b/app/srv/ws/sync/actions/page_redo.ts index 63c8e7d5..a5e78b94 100644 --- a/app/srv/ws/sync/actions/page_redo.ts +++ b/app/srv/ws/sync/actions/page_redo.ts @@ -1,13 +1,14 @@ import { docs } from "../entity/docs"; -import { ActionCtx } from "../type"; +import { SyncConnection } from "../type"; -export const page_redo = async function (this: ActionCtx, id: string) { +export const page_redo = async function (this: SyncConnection, id: string) { if (!docs.page[id]) { return; } const um = docs.page[id].um; if (um.canRedo()) { + console.log("redoing"); um.redo(); } }; diff --git a/app/srv/ws/sync/actions/page_undo.ts b/app/srv/ws/sync/actions/page_undo.ts index 88d14efd..65ee9bea 100644 --- a/app/srv/ws/sync/actions/page_undo.ts +++ b/app/srv/ws/sync/actions/page_undo.ts @@ -1,13 +1,15 @@ import { docs } from "../entity/docs"; -import { ActionCtx } from "../type"; +import { SyncConnection } from "../type"; -export const page_undo = async function (this: ActionCtx, id: string) { +export const page_undo = async function (this: SyncConnection, id: string) { if (!docs.page[id]) { return; } const um = docs.page[id].um; if (um.canUndo()) { + console.log("undoing"); + um.undo(); } }; diff --git a/app/srv/ws/sync/actions/site_group.ts b/app/srv/ws/sync/actions/site_group.ts index 07bbd230..2f0e84be 100644 --- a/app/srv/ws/sync/actions/site_group.ts +++ b/app/srv/ws/sync/actions/site_group.ts @@ -1,6 +1,6 @@ -import { ActionCtx } from "../type"; +import { SyncConnection } from "../type"; -export const site_group = async function (this: ActionCtx) { +export const site_group = async function (this: SyncConnection) { console.log(this.user); return "gruop"; }; diff --git a/app/srv/ws/sync/actions/site_load.ts b/app/srv/ws/sync/actions/site_load.ts index c97ac1f5..8bc0ffe7 100644 --- a/app/srv/ws/sync/actions/site_load.ts +++ b/app/srv/ws/sync/actions/site_load.ts @@ -1,22 +1,24 @@ import { validate } from "uuid"; -import { ActionCtx } from "../type"; import { ESite } from "../../../../web/src/render/ed/logic/ed-global"; import { SAction } from "../actions"; +import { SyncConnection } from "../type"; export const site_load: SAction["site"]["load"] = async function ( - this: ActionCtx, - id: string + this: SyncConnection, + id: string ) { - if (validate(id)) { + if (validate(id)) { const site = await db.site.findFirst({ where: { id } }); if (site) { - return { - id: site.id, + if (this.conf) this.conf.site_id = site.id; + + return { + id: site.id, config: site.config as ESite["config"], - domain: site.domain, + domain: site.domain, js: site.js || "", js_compiled: site.js_compiled || "", - name: site.name, + name: site.name, }; } } diff --git a/app/srv/ws/sync/actions/yjs_diff_local.ts b/app/srv/ws/sync/actions/yjs_diff_local.ts index dfc78c14..4a4e9d00 100644 --- a/app/srv/ws/sync/actions/yjs_diff_local.ts +++ b/app/srv/ws/sync/actions/yjs_diff_local.ts @@ -2,10 +2,10 @@ import { SAction } from "../actions"; import { Y, docs } from "../entity/docs"; import { snapshot } from "../entity/snapshot"; import { gunzipAsync } from "../entity/zlib"; -import { ActionCtx } from "../type"; +import { SyncConnection } from "../type"; export const yjs_diff_local: SAction["yjs"]["diff_local"] = async function ( - this: ActionCtx, + this: SyncConnection, mode, id, bin @@ -15,7 +15,10 @@ export const yjs_diff_local: SAction["yjs"]["diff_local"] = async function ( } const doc = docs[mode][id].doc as Y.Doc; const diff = await gunzipAsync(bin); - Y.applyUpdate(doc, diff); + + const um = docs[mode][id].um; + um.addTrackedOrigin(this.client_id); + Y.applyUpdate(doc, diff, this.client_id); const save = Y.encodeStateAsUpdate(doc); snapshot.set(mode, id, "bin", save); diff --git a/app/srv/ws/sync/actions/yjs_sv_local.ts b/app/srv/ws/sync/actions/yjs_sv_local.ts index 7e308cea..3b682314 100644 --- a/app/srv/ws/sync/actions/yjs_sv_local.ts +++ b/app/srv/ws/sync/actions/yjs_sv_local.ts @@ -1,10 +1,10 @@ import { SAction } from "../actions"; import { Y, docs } from "../entity/docs"; import { gunzipAsync, gzipAsync } from "../entity/zlib"; -import { ActionCtx } from "../type"; +import { SyncConnection } from "../type"; export const yjs_sv_local: SAction["yjs"]["sv_local"] = async function ( - this: ActionCtx, + this: SyncConnection, mode, id, bin diff --git a/app/srv/ws/sync/actions/yjs_sv_remote.ts b/app/srv/ws/sync/actions/yjs_sv_remote.ts new file mode 100644 index 00000000..eb9b90df --- /dev/null +++ b/app/srv/ws/sync/actions/yjs_sv_remote.ts @@ -0,0 +1,20 @@ +import { SAction } from "../actions"; +import { Y, docs } from "../entity/docs"; +import { gunzipAsync, gzipAsync } from "../entity/zlib"; +import { SyncConnection } from "../type"; + +export const yjs_sv_remote: SAction["yjs"]["sv_remote"] = async function ( + this: SyncConnection, + mode, + id, + sv, + diff +) { + if (!docs[mode][id]) { + return; + } + const doc = docs[mode][id].doc; + const diff_local = Y.encodeStateAsUpdate(doc as any, await gunzipAsync(sv)); + Y.applyUpdate(doc as any, await gunzipAsync(diff), "local"); + return { diff: await gzipAsync(diff_local) }; +}; diff --git a/app/srv/ws/sync/entity/conn.ts b/app/srv/ws/sync/entity/conn.ts new file mode 100644 index 00000000..78dd9ba5 --- /dev/null +++ b/app/srv/ws/sync/entity/conn.ts @@ -0,0 +1,6 @@ +import { ServerWebSocket } from "bun"; +import { SyncConnection } from "../type"; +import { WSData } from "../../../../../pkgs/core/server/create"; + +export const conns = new Map(); +export const wconns = new WeakMap, string>(); diff --git a/app/srv/ws/sync/entity/snapshot.ts b/app/srv/ws/sync/entity/snapshot.ts index 202e7966..b676085c 100644 --- a/app/srv/ws/sync/entity/snapshot.ts +++ b/app/srv/ws/sync/entity/snapshot.ts @@ -6,11 +6,14 @@ const emptySnapshot = { type: "" as "" | "comp" | "page", id: "", bin: new Uint8Array(), - url: "", name: "", ts: Date.now(), }; -export type DocSnapshot = typeof emptySnapshot; +export type DocSnapshot = typeof emptySnapshot & { + type: "page"; + url: string; + id_site: string; +}; export const snapshot = { _db: null as null | RootDatabase, @@ -32,7 +35,7 @@ export const snapshot = { const id = `${data.type}-${data.id}`; let res = this.db.get(id); if (!res) { - await this.db.put(id, structuredClone(emptySnapshot)); + await this.db.put(id, structuredClone(emptySnapshot as DocSnapshot)); res = this.db.get(id); } return res as DocSnapshot; diff --git a/app/srv/ws/sync/entity/user.ts b/app/srv/ws/sync/entity/user.ts index 1c70233e..6838a10d 100644 --- a/app/srv/ws/sync/entity/user.ts +++ b/app/srv/ws/sync/entity/user.ts @@ -1,7 +1,7 @@ import { dir } from "dir"; import { RootDatabase, open } from "lmdb"; import { g } from "utils/global"; -import { KeyMap } from "../../../../web/src/utils/sync/keymap"; +import { IndexedMap } from "../../../../web/src/utils/sync/idx-map"; const defaultConf = { site_id: "", @@ -10,11 +10,15 @@ const defaultConf = { export type UserConf = typeof defaultConf; export const user = { - active: KeyMap.create<{ - user_id: string; - site_id: string; - page_id: string; - }>("user_id"), + active: IndexedMap.create< + { + user_id: string; + site_id: string; + page_id: string; + select: "" | "comp" | "item" | "section" | "text"; + }, + "client_id" + >("client_id"), conf: { _db: null as null | RootDatabase, init() { diff --git a/app/srv/ws/sync/sync-handler.ts b/app/srv/ws/sync/sync-handler.ts index 43337334..457a8482 100644 --- a/app/srv/ws/sync/sync-handler.ts +++ b/app/srv/ws/sync/sync-handler.ts @@ -3,27 +3,15 @@ import { ServerWebSocket, WebSocketHandler } from "bun"; import { Packr } from "msgpackr"; import { WSData } from "../../../../pkgs/core/server/create"; import { ClientEvent } from "../../../web/src/utils/sync/ws-client"; -import { loadDefaultSite } from "./editor/load"; -import { ActionCtx, SyncType } from "./type"; import { SyncActionPaths } from "./actions-def"; import * as actions from "./actions/index"; +import { loadDefaultSite } from "./editor/load"; import { UserConf, user } from "./entity/user"; +import { SyncType } from "./type"; +import { conns, wconns } from "./entity/conn"; const packr = new Packr({ structuredClone: true }); -const conns = new Map< - string, - { - user_id: string; - conf?: UserConf & { toJSON: () => UserConf }; - ws: ServerWebSocket; - msg: { - pending: Record>; - resolve: Record void>; - }; - } ->(); -const wconns = new WeakMap, string>(); -const send = (ws: ServerWebSocket, msg: any) => { +export const sendWS = (ws: ServerWebSocket, msg: any) => { ws.sendBinary(packr.pack(msg)); }; export const syncHandler: WebSocketHandler = { @@ -31,11 +19,12 @@ export const syncHandler: WebSocketHandler = { const client_id = createId(); conns.set(client_id, { user_id: "", + client_id, ws, msg: { pending: {}, resolve: {} }, }); wconns.set(ws, client_id); - send(ws, { type: SyncType.ClientID, client_id }); + sendWS(ws, { type: SyncType.ClientID, client_id }); }, close(ws, code, reason) { const conn_id = wconns.get(ws); @@ -69,7 +58,7 @@ export const syncHandler: WebSocketHandler = { return true; }, }) as UserConf & { toJSON: () => UserConf }; - send(ws, { + sendWS(ws, { type: SyncType.Event, event: "editor_start" as ClientEvent, data: conn.conf.toJSON(), @@ -84,9 +73,7 @@ export const syncHandler: WebSocketHandler = { console.log(`app/ws/edit/sync/${actionName}.ts not found`); } if (baseAction) { - const action = baseAction.bind({ - user: { id: conn.user_id, conf: conn.conf }, - } as ActionCtx); + const action = baseAction.bind(conn); ws.sendBinary( packr.pack({ diff --git a/app/srv/ws/sync/type.ts b/app/srv/ws/sync/type.ts index ec86e988..29bb2162 100644 --- a/app/srv/ws/sync/type.ts +++ b/app/srv/ws/sync/type.ts @@ -1,4 +1,6 @@ +import { ServerWebSocket } from "bun"; import { UserConf } from "./entity/user"; +import { WSData } from "../../../../pkgs/core/server/create"; export enum SyncType { ClientID, @@ -7,6 +9,14 @@ export enum SyncType { Action, ActionResult, } -export type ActionCtx = { - user: { id: string; conf: UserConf & { toJSON: () => UserConf } }; + +export type SyncConnection = { + user_id: string; + client_id: string; + conf?: UserConf & { toJSON: () => UserConf }; + ws: ServerWebSocket; + msg: { + pending: Record>; + resolve: Record void>; + }; }; diff --git a/app/web/src/render/ed/logic/ed-route.ts b/app/web/src/render/ed/logic/ed-route.ts index 21994a49..762f1270 100644 --- a/app/web/src/render/ed/logic/ed-route.ts +++ b/app/web/src/render/ed/logic/ed-route.ts @@ -31,6 +31,8 @@ export const edRoute = async (p: PG) => { const doc = new Y.Doc(); Y.applyUpdate(doc, decompress(page.snapshot)); doc.on("update", async (bin: Uint8Array, origin: any) => { + if (origin === "sv_remote") return; + const res = await p.sync.yjs.sv_local( "page", p.page.cur.id, diff --git a/app/web/src/render/ed/logic/ed-sync.tsx b/app/web/src/render/ed/logic/ed-sync.tsx index 162f34ea..fd46a98b 100644 --- a/app/web/src/render/ed/logic/ed-sync.tsx +++ b/app/web/src/render/ed/logic/ed-sync.tsx @@ -1,6 +1,9 @@ +import { compress, decompress } from "wasm-gzip"; import { clientStartSync } from "../../../utils/sync/ws-client"; import { Loading } from "../../../utils/ui/loading"; import { PG } from "./ed-global"; +import { Y } from "../../../../../srv/ws/sync/entity/docs"; +import { treeRebuild } from "./tree/build"; export const edInitSync = (p: PG) => { const session = JSON.parse( @@ -24,12 +27,12 @@ export const edInitSync = (p: PG) => { if (!paramsOK) { if (e.site_id && e.page_id) { p.site.id = e.site_id; - p.page.id = e.page_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.id = params.page_id; + p.page.cur.id = params.page_id; p.render(); } }, @@ -37,6 +40,32 @@ export const edInitSync = (p: PG) => { p.site = site; p.render(); }, + async remote_svlocal(data) { + if (p[data.type].cur.id === data.id) { + const doc = p[data.type].doc as Y.Doc; + + if (doc) { + 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"); + await treeRebuild(p, { note: "sv_remote" }); + } + } + } + }, }, }).then((e) => (p.sync = e)); diff --git a/app/web/src/render/ed/logic/ed-undo.ts b/app/web/src/render/ed/logic/ed-undo.ts index fb6f218c..053690c4 100644 --- a/app/web/src/render/ed/logic/ed-undo.ts +++ b/app/web/src/render/ed/logic/ed-undo.ts @@ -18,7 +18,11 @@ export const edUndoManager = async (p: PG) => { (evt.ctrlKey || evt.metaKey) && !evt.shiftKey ) { - console.log("redo"); + if (p.comp.cur.id) { + p.sync.comp.redo(p.comp.cur.id); + } else { + p.sync.page.redo(p.page.cur.id); + } return; } @@ -27,7 +31,11 @@ export const edUndoManager = async (p: PG) => { (evt.ctrlKey || evt.metaKey) && evt.shiftKey ) { - console.log("redo"); + if (p.comp.cur.id) { + p.sync.comp.redo(p.comp.cur.id); + } else { + p.sync.page.redo(p.page.cur.id); + } return; } diff --git a/app/web/src/utils/sync/idx-map.ts b/app/web/src/utils/sync/idx-map.ts new file mode 100644 index 00000000..4278243e --- /dev/null +++ b/app/web/src/utils/sync/idx-map.ts @@ -0,0 +1,34 @@ +export const IndexedMap = { + create: , KEY extends string>(id: KEY) => { + const all = {} as Record>; + + return { + add(item: OBJ & Record) { + const _id = item[id]; + all[_id] = item as any; + return _id; + }, + find(id: KEY) { + return all[id]; + }, + del(id: KEY) { + delete all[id]; + }, + findAll(where: Partial) { + return Object.values(all).filter((item) => { + for (const key in where) { + if (item[key] !== where[key]) { + return false; + } + } + return true; + }); + }, + delAll(where: Partial) { + for (const item of this.findAll(where)) { + delete all[item[id]]; + } + }, + }; + }, +}; diff --git a/app/web/src/utils/sync/keymap.ts b/app/web/src/utils/sync/keymap.ts deleted file mode 100644 index 6f6630d8..00000000 --- a/app/web/src/utils/sync/keymap.ts +++ /dev/null @@ -1,86 +0,0 @@ -export const KeyMap = { - create: >(main: keyof K) => { - const childs = {} as Record>; - - const all = {} as Record>; - - const createChild = (key: string) => { - return { - remove(id: string) { - if (all[id]) { - map.delete(all[key][id]); - } - }, - }; - }; - - const map = new EventedMap({ - onSet({ key, value }) { - for (const [k, v] of Object.entries(value)) { - if (!childs[k]) { - childs[k] = createChild(k); - all[k] = {}; - } - all[k][v] = key; - } - }, - onDelete(key) { - const val = map.get(key); - for (const [k, v] of Object.entries(val)) { - delete all[k][v as string]; - } - return true; - }, - }); - return new Proxy( - {}, - { - get(_, key: string, receiver) { - if (key === "get") { - return map.get; - } - if (key === "set") { - return (val: K) => { - const key = val[main]; - map.set(key, val); - }; - } - - if (!childs[key]) { - childs[key] = createChild(key); - } - return childs[key]; - }, - } - ) as { get: (key: typeof main) => K; set: (val: K) => void } & Record< - keyof K, - EventedMap - >; - }, -}; - -type MapEvents = Partial<{ - onSet: (arg: { key: string; value: Record }) => void; - onDelete: (key: string) => boolean; -}>; - -class EventedMap extends Map { - _events: MapEvents; - - constructor(events: MapEvents) { - super(); - this._events = events; - } - set(key: string, value: Record): this { - super.set(key, value); - this._events.onSet?.({ key, value }); - return this; - } - delete(key: string): boolean { - const value = this.get(key); - if (value) { - this._events.onDelete?.(key); - } - return super.delete(key); - } -} diff --git a/app/web/src/utils/sync/ws-client.ts b/app/web/src/utils/sync/ws-client.ts index c590448f..fa828fce 100644 --- a/app/web/src/utils/sync/ws-client.ts +++ b/app/web/src/utils/sync/ws-client.ts @@ -8,6 +8,7 @@ import { SyncActionDefinition, SyncActionPaths, } from "../../../../srv/ws/sync/actions-def"; +import { UserConf } from "../../../../srv/ws/sync/entity/user"; import { SyncType } from "../../../../srv/ws/sync/type"; import { ESite } from "../../render/ed/logic/ed-global"; import { w } from "../types/general"; @@ -48,12 +49,13 @@ const sendWs = (ws: WebSocket, msg: any) => { export const clientStartSync = async (arg: { user_id: string; events: { - editor_start: (arg: { - user_id: string; - site_id?: string; - page_id?: string; - }) => void; + editor_start: (arg: UserConf) => void; site_loaded: (arg: { site: ESite }) => void; + remote_svlocal: (arg: { + type: "page" | "comp"; + id: string; + sv_local: Uint8Array; + }) => void; }; }) => { const { user_id, events } = arg; @@ -115,6 +117,7 @@ const connect = (user_id: string, event: ClientEventObject) => { conf.ws = ws; }; ws.onclose = async () => { + console.log("disconnected.."); w.offline = true; if (!conf.ws) { await connect(user_id, event);