From 0a3958893233513538b1ca7f55d3f39b87b6220f Mon Sep 17 00:00:00 2001 From: Rizky Date: Tue, 24 Oct 2023 14:40:17 +0700 Subject: [PATCH] fix --- app/srv/api/comp-create.ts | 1 + app/srv/ws/sync/actions-def.ts | 38 ++++---- app/srv/ws/sync/actions.ts | 5 +- app/srv/ws/sync/actions/comp_group.ts | 31 ++++++ app/srv/ws/sync/actions/comp_load.ts | 94 +++++++++++++++++-- app/srv/ws/sync/actions/comp_new.ts | 6 ++ app/srv/ws/sync/actions/index.ts | 1 + app/srv/ws/sync/actions/page_load.ts | 5 +- app/srv/ws/sync/actions/yjs_diff_local.ts | 1 - app/srv/ws/sync/entity/snapshot.ts | 48 ++++++++-- app/srv/ws/sync/entity/user.ts | 1 + app/web/src/render/ed/ed-base.tsx | 5 + app/web/src/render/ed/logic/ed-global.ts | 18 ++-- .../src/render/ed/panel/popup/comp-group.tsx | 60 ++++++++++++ .../panel/tree/node/item/action/new-comp.tsx | 13 ++- .../ed/panel/tree/node/item/ctx-menu.tsx | 7 +- .../ed/panel/tree/node/item/indent-hook.ts | 3 +- app/web/src/render/ed/panel/tree/search.tsx | 12 +-- app/web/src/utils/ui/context-menu.tsx | 2 +- 19 files changed, 291 insertions(+), 60 deletions(-) create mode 100644 app/srv/ws/sync/actions/comp_group.ts create mode 100644 app/srv/ws/sync/actions/comp_new.ts create mode 100644 app/web/src/render/ed/panel/popup/comp-group.tsx diff --git a/app/srv/api/comp-create.ts b/app/srv/api/comp-create.ts index 6f775b16..e8a6c64a 100644 --- a/app/srv/api/comp-create.ts +++ b/app/srv/api/comp-create.ts @@ -142,6 +142,7 @@ export const _ = { id: newcomp.id, name: "", props: {}, + ref_ids: {}, }, } as IItem ); diff --git a/app/srv/ws/sync/actions-def.ts b/app/srv/ws/sync/actions-def.ts index cb201a5c..fd6244bb 100644 --- a/app/srv/ws/sync/actions-def.ts +++ b/app/srv/ws/sync/actions-def.ts @@ -5,32 +5,34 @@ export const SyncActionDefinition = { "load": "2" }, "comp": { - "list": "3", - "group": "4", - "load": "5" + "new": "3", + "list": "4", + "group": "5", + "load": "6" }, "page": { - "list": "6", - "load": "7" + "list": "7", + "load": "8" }, "yjs": { - "um": "8", - "sv_local": "9", - "diff_local": "10", - "sv_remote": "11" + "um": "9", + "sv_local": "10", + "diff_local": "11", + "sv_remote": "12" } }; export const SyncActionPaths = { "0": "site.list", "1": "site.group", "2": "site.load", - "3": "comp.list", - "4": "comp.group", - "5": "comp.load", - "6": "page.list", - "7": "page.load", - "8": "yjs.um", - "9": "yjs.sv_local", - "10": "yjs.diff_local", - "11": "yjs.sv_remote" + "3": "comp.new", + "4": "comp.list", + "5": "comp.group", + "6": "comp.load", + "7": "page.list", + "8": "page.load", + "9": "yjs.um", + "10": "yjs.sv_local", + "11": "yjs.diff_local", + "12": "yjs.sv_remote" }; diff --git a/app/srv/ws/sync/actions.ts b/app/srv/ws/sync/actions.ts index e0bd6d04..71461220 100644 --- a/app/srv/ws/sync/actions.ts +++ b/app/srv/ws/sync/actions.ts @@ -4,6 +4,7 @@ import { EPage, ESite, } from "../../../web/src/render/ed/logic/ed-global"; +import { IItem } from "../../../web/src/utils/types/item"; /* WARNING: @@ -22,8 +23,10 @@ export const SyncActions = { load: async (id: string) => ({}) as ESite | void, }, comp: { + new: async (arg: { group_id: string; item: IItem }) => {}, list: () => ({}) as Record>, - group: () => ({}) as Record, + group: async (id_site: string) => + ({}) as Record, load: async (id: string) => ({}) as EComp | void, }, page: { diff --git a/app/srv/ws/sync/actions/comp_group.ts b/app/srv/ws/sync/actions/comp_group.ts new file mode 100644 index 00000000..332fe551 --- /dev/null +++ b/app/srv/ws/sync/actions/comp_group.ts @@ -0,0 +1,31 @@ +import { SAction } from "../actions"; +import { SyncConnection } from "../type"; + +export const comp_group: SAction["comp"]["group"] = async function ( + this: SyncConnection, + id_site +) { + const result: Awaited> = {}; + const groups = await db.component_group.findMany({ + where: { + component_site: { + some: { + id_site, + }, + }, + }, + select: { + name: true, + id: true, + }, + }); + + for (const g of groups) { + result[g.id] = { + id: g.id, + name: g.name, + comps: [], + }; + } + return result; +}; diff --git a/app/srv/ws/sync/actions/comp_load.ts b/app/srv/ws/sync/actions/comp_load.ts index d5babfaa..f9ed8017 100644 --- a/app/srv/ws/sync/actions/comp_load.ts +++ b/app/srv/ws/sync/actions/comp_load.ts @@ -1,21 +1,65 @@ import { syncronize } from "y-pojo"; -import { docs } from "../entity/docs"; +import { SAction } from "../actions"; +import { conns } from "../entity/conn"; +import { Y, docs } from "../entity/docs"; import { snapshot } from "../entity/snapshot"; +import { user } from "../entity/user"; import { gzipAsync } from "../entity/zlib"; -import { SyncConnection } from "../type"; +import { sendWS } from "../sync-handler"; +import { SyncConnection, SyncType } from "../type"; -export const comp_load = async function (this: SyncConnection, id: string) { +export const comp_load: SAction["comp"]["load"] = async function ( + this: SyncConnection, + id: string +) { let snap = snapshot.get("comp", id); let ydoc = docs.comp[id]; + const conf = this.conf; + if (!conf) return undefined; - if (!snap || !ydoc) { + const createUndoManager = async (root: Y.Map) => { + const um = new Y.UndoManager(root, { + ignoreRemoteMapChanges: true, + }); + + return um; + }; + + const attachOnUpdate = async (doc: Y.Doc, um: Y.UndoManager) => { + snapshot.set("comp", id, "id_doc", um.doc.clientID); + + doc.on("update", async (update: Uint8Array, origin: any) => { + const bin = Y.encodeStateAsUpdate(doc); + snapshot.set("comp", id, "bin", bin); + + const sv_local = await gzipAsync(update); + user.active.findAll({ comp_id: id }).map((e) => { + if (origin !== um) { + 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: "comp", sv_local, id }, + }); + }); + }); + }; + + const defaultActive = { + select: "" as "" | "comp" | "item" | "section" | "text", + }; + + if (!snap && !ydoc) { const comp = await db.component.findFirst({ where: { id } }); if (comp) { const doc = new Y.Doc(); let root = doc.getMap("map"); - syncronize(root, { id, item: comp.content_tree }); + syncronize(root, { id, root: comp.content_tree }); - const um = new Y.UndoManager(root, { ignoreRemoteMapChanges: true }); + const um = await createUndoManager(root); docs.comp[id] = { doc: doc as any, id, @@ -23,13 +67,24 @@ export const comp_load = async function (this: SyncConnection, id: string) { }; const bin = Y.encodeStateAsUpdate(doc); + await attachOnUpdate(doc, um); + snapshot.update({ bin, id, type: "comp", name: comp.name, - url: "", ts: Date.now(), + id_doc: doc.clientID, + }); + + user.active.add({ + ...defaultActive, + client_id: this.client_id, + user_id: this.user_id, + site_id: conf.site_id, + page_id: conf.page_id, + comp_id: comp.id, }); return { @@ -40,22 +95,43 @@ export const comp_load = async function (this: SyncConnection, id: string) { } } else if (snap && !ydoc) { const doc = new Y.Doc(); + snapshot.set("comp", id, "id_doc", doc.clientID); Y.applyUpdate(doc, snap.bin); let root = doc.getMap("map"); - const um = new Y.UndoManager(root, { ignoreRemoteMapChanges: true }); - docs.page[id] = { + const um = await createUndoManager(root); + await attachOnUpdate(doc, um); + + docs.comp[id] = { doc: doc as any, id, um, }; + user.active.add({ + ...defaultActive, + client_id: this.client_id, + user_id: this.user_id, + site_id: conf.site_id, + page_id: conf.page_id, + comp_id: id, + }); + return { id: id, name: snap.name, 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: conf.site_id, + page_id: conf.page_id, + comp_id: id, + }); + return { id: snap.id, name: snap.name, diff --git a/app/srv/ws/sync/actions/comp_new.ts b/app/srv/ws/sync/actions/comp_new.ts new file mode 100644 index 00000000..3a5f9e14 --- /dev/null +++ b/app/srv/ws/sync/actions/comp_new.ts @@ -0,0 +1,6 @@ +import { SAction } from "../actions"; +import { SyncConnection } from "../type"; + +export const comp_new: SAction["comp"]["new"] = async function ( + this: SyncConnection +) {}; diff --git a/app/srv/ws/sync/actions/index.ts b/app/srv/ws/sync/actions/index.ts index ff296e90..27c12b74 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_group"; export * from "./yjs_um"; export * from "./yjs_sv_local"; export * from "./yjs_diff_local"; diff --git a/app/srv/ws/sync/actions/page_load.ts b/app/srv/ws/sync/actions/page_load.ts index 5aac23b2..53491cb1 100644 --- a/app/srv/ws/sync/actions/page_load.ts +++ b/app/srv/ws/sync/actions/page_load.ts @@ -15,7 +15,10 @@ export const page_load: SAction["page"]["load"] = async function ( let snap = snapshot.get("page", id); let ydoc = docs.page[id]; - if (this.conf) this.conf.page_id = id; + const conf = this.conf; + if (!conf) return undefined; + + conf.page_id = id; const createUndoManager = async (root: Y.Map) => { const um = new Y.UndoManager(root, { diff --git a/app/srv/ws/sync/actions/yjs_diff_local.ts b/app/srv/ws/sync/actions/yjs_diff_local.ts index f9f290fb..052fbbfe 100644 --- a/app/srv/ws/sync/actions/yjs_diff_local.ts +++ b/app/srv/ws/sync/actions/yjs_diff_local.ts @@ -1,6 +1,5 @@ import { SAction } from "../actions"; import { Y, docs } from "../entity/docs"; -import { snapshot } from "../entity/snapshot"; import { gunzipAsync } from "../entity/zlib"; import { SyncConnection } from "../type"; diff --git a/app/srv/ws/sync/entity/snapshot.ts b/app/srv/ws/sync/entity/snapshot.ts index a6475a83..10c3eeac 100644 --- a/app/srv/ws/sync/entity/snapshot.ts +++ b/app/srv/ws/sync/entity/snapshot.ts @@ -2,19 +2,47 @@ import { dir } from "dir"; import { RootDatabase, open } from "lmdb"; import { g } from "utils/global"; -const emptySnapshot = { - type: "" as "" | "comp" | "page" | "site", +type EmptySnapshot = { + type: ""; + id: string; + bin: Uint8Array; + id_doc: number; + name: string; + ts: number; +}; +type CompSnapshot = { + type: "comp"; + id: string; + bin: Uint8Array; + id_doc: number; + name: string; + ts: number; +}; +type PageSnapshot = { + type: "page"; + id: string; + bin: Uint8Array; + id_doc: number; + name: string; + ts: number; + url: string; + id_site: string; +}; +type DocSnapshotMap = { + page: PageSnapshot; + comp: CompSnapshot; + "": EmptySnapshot; +}; +export type DocSnapshot = EmptySnapshot | CompSnapshot | PageSnapshot; + +const emptySnapshot: DocSnapshot = { + type: "", id: "", bin: new Uint8Array(), id_doc: 0, name: "", ts: Date.now(), }; -export type DocSnapshot = typeof emptySnapshot & { - type: "page"; - url: string; - id_site: string; -}; export const snapshot = { _db: null as null | RootDatabase, @@ -41,8 +69,8 @@ export const snapshot = { } return res as DocSnapshot; }, - get(type: string, id: string) { - return this.db.get(`${type}-${id}`); + get(type: K, id: string) { + return this.db.get(`${type}-${id}`) as DocSnapshotMap[K] | null; }, async update(data: DocSnapshot) { const id = `${data.type}-${data.id}`; @@ -50,7 +78,7 @@ export const snapshot = { return true; }, async set( - type: string, + type: keyof DocSnapshotMap, id: string, key: T, value: DocSnapshot[T] diff --git a/app/srv/ws/sync/entity/user.ts b/app/srv/ws/sync/entity/user.ts index 6838a10d..f258ccea 100644 --- a/app/srv/ws/sync/entity/user.ts +++ b/app/srv/ws/sync/entity/user.ts @@ -15,6 +15,7 @@ export const user = { user_id: string; site_id: string; page_id: string; + comp_id?: string; select: "" | "comp" | "item" | "section" | "text"; }, "client_id" diff --git a/app/web/src/render/ed/ed-base.tsx b/app/web/src/render/ed/ed-base.tsx index 83192cb5..47ec70be 100644 --- a/app/web/src/render/ed/ed-base.tsx +++ b/app/web/src/render/ed/ed-base.tsx @@ -6,6 +6,7 @@ import { edRoute } from "./logic/ed-route"; import { EdMain } from "./panel/main/main"; import { EdTree } from "./panel/tree/tree"; import { edUndoManager } from "./logic/ed-undo"; +import { EdPopCompGroup } from "./panel/popup/comp-group"; export const EdBase = () => { const p = useGlobal(EDGlobal, "EDITOR"); @@ -35,6 +36,10 @@ export const EdBase = () => { + + <> + + ); }; diff --git a/app/web/src/render/ed/logic/ed-global.ts b/app/web/src/render/ed/logic/ed-global.ts index 073da2c1..070b3bb9 100644 --- a/app/web/src/render/ed/logic/ed-global.ts +++ b/app/web/src/render/ed/logic/ed-global.ts @@ -5,6 +5,7 @@ import { IItem, MItem } from "../../../utils/types/item"; import { DComp, DPage, IRoot } from "../../../utils/types/root"; import { ISection } from "../../../utils/types/section"; import { IText, MText } from "../../../utils/types/text"; +import { SAction } from "../../../../../srv/ws/sync/actions"; const EmptySite = { id: "", @@ -83,11 +84,12 @@ export const EDGlobal = { doc: null as null | DComp, item: null as null | IItem, list: {} as Record, + group: {} as Record>>, }, ui: { tree: { search: "", - searchMode: { + search_mode: { Name: true, JS: false, HTML: false, @@ -96,14 +98,12 @@ export const EDGlobal = { open: {} as Record, }, popup: { - comp: null as null | true | ((comp_id: string) => void | Promise), - compGroup: null as - | null - | true - | { - event: React.MouseEvent; - pick: (group_id: string) => void | Promise; - }, + comp: null as null | ((comp_id: string) => void | Promise), + comp_group: null as null | { + mouse_event: React.MouseEvent; + on_pick?: (group_id: string) => void | Promise; + on_close?: () => void | Promise; + }, }, }, }; diff --git a/app/web/src/render/ed/panel/popup/comp-group.tsx b/app/web/src/render/ed/panel/popup/comp-group.tsx new file mode 100644 index 00000000..9c247053 --- /dev/null +++ b/app/web/src/render/ed/panel/popup/comp-group.tsx @@ -0,0 +1,60 @@ +import { useGlobal } from "web-utils"; +import { Menu, MenuItem } from "../../../../utils/ui/context-menu"; +import { EDGlobal } from "../../logic/ed-global"; +import { useEffect } from "react"; +import { Loading } from "../../../../utils/ui/loading"; + +export const EdPopCompGroup = () => { + const p = useGlobal(EDGlobal, "EDITOR"); + + useEffect(() => { + (async () => { + if (!p.comp.group[p.site.id]) { + p.comp.group[p.site.id] = await p.sync.comp.group(p.site.id); + } + console.log(p.comp.group[p.site.id]); + p.render(); + })(); + }, []); + + if (!p.ui.popup.comp_group) return null; + const pop = p.ui.popup.comp_group; + const group = p.comp.group[p.site.id]; + return ( + { + p.ui.popup.comp_group = null; + p.render(); + if (pop.on_close) pop.on_close(); + }} + > + +
+ +
+ + ) : ( +
Choose Component Group:
+ ) + } + /> + {Object.values(group || {}) + .filter((g) => g.name !== "__TRASH__") + .sort((a, b) => (a.name > b.name ? 1 : -1)) + .map((g) => ( + { + p.ui.popup.comp_group?.on_pick?.(g.id); + }} + label={
{g.name}
} + key={g.id} + /> + ))} +
+ ); +}; diff --git a/app/web/src/render/ed/panel/tree/node/item/action/new-comp.tsx b/app/web/src/render/ed/panel/tree/node/item/action/new-comp.tsx index b142939a..8ac8f2d0 100644 --- a/app/web/src/render/ed/panel/tree/node/item/action/new-comp.tsx +++ b/app/web/src/render/ed/panel/tree/node/item/action/new-comp.tsx @@ -1,8 +1,19 @@ import { IItem } from "../../../../../../../utils/types/item"; import { PG } from "../../../../../logic/ed-global"; -export const edActionNewComp = (p: PG, item: IItem) => { +export const edActionNewComp = ( + p: PG, + item: IItem, + e: React.MouseEvent +) => { const mitem = p.page.meta[item.id].mitem; if (mitem) { + p.ui.popup.comp_group = { + mouse_event: e, + async on_pick(group_id) { + await p.sync.comp.new({ group_id, item }); + }, + }; + p.render(); } }; diff --git a/app/web/src/render/ed/panel/tree/node/item/ctx-menu.tsx b/app/web/src/render/ed/panel/tree/node/item/ctx-menu.tsx index 390ca90d..6e89d208 100644 --- a/app/web/src/render/ed/panel/tree/node/item/ctx-menu.tsx +++ b/app/web/src/render/ed/panel/tree/node/item/ctx-menu.tsx @@ -57,7 +57,10 @@ export const EdTreeCtxMenu = ({ if (!item) { return ( - Unavailable} /> + Unavailable} + /> ); } @@ -79,7 +82,7 @@ export const EdTreeCtxMenu = ({ {type === "item" && !comp?.id && ( edActionNewComp(p, item)} + onClick={(e) => edActionNewComp(p, item, e)} /> )} {!item.hidden && ( 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 370def08..30a2ad7b 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 @@ -12,7 +12,8 @@ export const indentHook = ( let shouldOpen = open[p.page.cur.id] || []; - let meta = p.page.meta[active.item_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]; diff --git a/app/web/src/render/ed/panel/tree/search.tsx b/app/web/src/render/ed/panel/tree/search.tsx index b41116b8..70a18bf5 100644 --- a/app/web/src/render/ed/panel/tree/search.tsx +++ b/app/web/src/render/ed/panel/tree/search.tsx @@ -66,7 +66,7 @@ export const EdTreeSearch = () => { {(local.focus || local.hover || p.ui.tree.search) && (
- {Object.entries(p.ui.tree.searchMode).map(([name, active]) => { + {Object.entries(p.ui.tree.search_mode).map(([name, active]) => { return (
{ active ? "bg-blue-500 text-white" : "hover:bg-blue-100" )} onClick={() => { - (p.ui.tree.searchMode as any)[name] = !active; + (p.ui.tree.search_mode as any)[name] = !active; local.render(); local.sref?.focus(); }} @@ -94,7 +94,7 @@ export const EdTreeSearch = () => { export const doTreeSearch = (p: PG) => { let tree: Record }> = {}; - if (p.ui.tree.searchMode.Name) { + if (p.ui.tree.search_mode.Name) { const [idxs, info] = uf.search( p.page.tree.map((e) => e.text), p.ui.tree.search @@ -162,7 +162,7 @@ export const doTreeSearch = (p: PG) => { if (item) { const js = item.adv?.js; if (js) { - if (p.ui.tree.searchMode.JS) { + if (p.ui.tree.search_mode.JS) { if ((js as string).toLowerCase().includes(search)) { tree[item.id] = { idx: i++, node: { ...row, parent: "root" } }; } @@ -170,7 +170,7 @@ export const doTreeSearch = (p: PG) => { } const css = item.adv?.css; if (css) { - if (p.ui.tree.searchMode.CSS) { + if (p.ui.tree.search_mode.CSS) { if (css.toString().toLowerCase().includes(search)) { tree[item.id] = { idx: i++, node: { ...row, parent: "root" } }; } @@ -179,7 +179,7 @@ export const doTreeSearch = (p: PG) => { const html = item.adv?.html; if (html) { - if (p.ui.tree.searchMode.HTML) { + if (p.ui.tree.search_mode.HTML) { if (html.toString().toLowerCase().includes(search)) { tree[item.id] = { idx: i++, node: { ...row, parent: "root" } }; } diff --git a/app/web/src/utils/ui/context-menu.tsx b/app/web/src/utils/ui/context-menu.tsx index eaa156b3..24d7127d 100644 --- a/app/web/src/utils/ui/context-menu.tsx +++ b/app/web/src/utils/ui/context-menu.tsx @@ -52,7 +52,7 @@ export const MenuItem = forwardRef< interface Props { label?: string; nested?: boolean; - mouseEvent: React.MouseEvent; + mouseEvent: React.MouseEvent; onClose: () => void; }