diff --git a/app/srv/ws/handler.ts b/app/srv/ws/handler.ts index ecea1c37..c1b85c31 100644 --- a/app/srv/ws/handler.ts +++ b/app/srv/ws/handler.ts @@ -9,8 +9,11 @@ import { svLocal } from "./edit/action/sv-local"; import { svdiffRemote } from "./edit/action/svdiff-remote"; import { redo, undo } from "./edit/action/undo-redo"; import { eg } from "./edit/edit-global"; -import { sendWS } from "./edit/send";import { syncHandler } from "./sync/sync-handler"; +import { sendWS } from "./edit/send"; +import { syncHandler } from "./sync/sync-handler"; +import * as Y from "yjs"; +(globalThis as any).Y = Y; eg.edit = { site: {}, comp: {}, diff --git a/app/srv/ws/sync/actions-def.ts b/app/srv/ws/sync/actions-def.ts index 1d910792..b48f2881 100644 --- a/app/srv/ws/sync/actions-def.ts +++ b/app/srv/ws/sync/actions-def.ts @@ -7,7 +7,7 @@ export const SyncActionDefinition = { "comp": { "list": "3", "group": "4", - "doc": "5" + "load": "5" }, "page": { "list": "6", @@ -20,7 +20,7 @@ export const SyncActionPaths = { "2": "site.load", "3": "comp.list", "4": "comp.group", - "5": "comp.doc", + "5": "comp.load", "6": "page.list", "7": "page.load" }; diff --git a/app/srv/ws/sync/actions.ts b/app/srv/ws/sync/actions.ts index 5a645c7f..e11cfd20 100644 --- a/app/srv/ws/sync/actions.ts +++ b/app/srv/ws/sync/actions.ts @@ -1,5 +1,5 @@ import { component, page } from "dbgen"; -import { EPage, ESite } from "../../../web/src/render/ed/logic/ed-global"; +import { EComp, EPage, ESite } from "../../../web/src/render/ed/logic/ed-global"; /* WARNING: @@ -20,7 +20,7 @@ export const SyncActions = { comp: { list: () => ({}) as Record>, group: () => ({}) as Record, - doc: (id: string) => ({}) as Uint8Array, + load: async (id: string) => ({}) as EComp | void, }, page: { list: (id_site: string) => diff --git a/app/srv/ws/sync/actions/comp_load.ts b/app/srv/ws/sync/actions/comp_load.ts new file mode 100644 index 00000000..e508d6a1 --- /dev/null +++ b/app/srv/ws/sync/actions/comp_load.ts @@ -0,0 +1,49 @@ +import { syncronize } from "y-pojo"; +import { docs } from "../entity/docs"; +import { snapshot } from "../entity/snapshot"; +import { ActionCtx } from "../type"; + +export const comp_load = async function (this: ActionCtx, id: string) { + let snap = snapshot.get("comp", id); + let ydoc = docs.comp[id]; + + 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 }); + + const um = new Y.UndoManager(root, { ignoreRemoteMapChanges: true }); + docs.comp[id] = { + doc: doc as any, + id, + um, + }; + + const bin = Y.encodeStateAsUpdate(doc); + snapshot.set({ + bin, + id, + type: "comp", + name: comp.name, + url: "", + ts: Date.now(), + }); + + return { + id: id, + name: comp.name, + snapshot: bin, + }; + } + } + + if (snap) { + return { + id: snap.id, + name: snap.name, + snapshot: snap.bin, + }; + } +}; diff --git a/app/srv/ws/sync/actions/index.ts b/app/srv/ws/sync/actions/index.ts index 77660dbc..102cb8af 100644 --- a/app/srv/ws/sync/actions/index.ts +++ b/app/srv/ws/sync/actions/index.ts @@ -1,3 +1,4 @@ export * from "./site_load"; export * from "./site_group"; export * from "./page_load"; +export * from "./comp_load"; diff --git a/app/srv/ws/sync/actions/page_load.ts b/app/srv/ws/sync/actions/page_load.ts index 089bf093..b58c49f8 100644 --- a/app/srv/ws/sync/actions/page_load.ts +++ b/app/srv/ws/sync/actions/page_load.ts @@ -8,10 +8,10 @@ export const page_load: SAction["page"]["load"] = async function ( this: ActionCtx, id: string ) { - let ss = snapshot.get("page", id); + let snap = snapshot.get("page", id); let ydoc = docs.page[id]; - if (!ss || !ydoc) { + if (!snap || !ydoc) { const page = await db.page.findFirst({ where: { id } }); if (page) { const doc = new Y.Doc(); @@ -44,12 +44,12 @@ export const page_load: SAction["page"]["load"] = async function ( } } - if (ss) { + if (snap) { return { - id: ss.id, - url: ss.url, - name: ss.name, - snapshot: ss.bin, + id: snap.id, + url: snap.url, + name: snap.name, + snapshot: snap.bin, }; } }; diff --git a/app/srv/ws/sync/entity/snapshot.ts b/app/srv/ws/sync/entity/snapshot.ts index 89200fbf..c8bd58d8 100644 --- a/app/srv/ws/sync/entity/snapshot.ts +++ b/app/srv/ws/sync/entity/snapshot.ts @@ -8,7 +8,7 @@ const emptySnapshot = { bin: new Uint8Array(), url: "", name: "", - ts: Date.now(), + ts: Date.now() }; export type DocSnapshot = typeof emptySnapshot; diff --git a/app/srv/ws/sync/sync-handler.ts b/app/srv/ws/sync/sync-handler.ts index fe644a40..9d11904b 100644 --- a/app/srv/ws/sync/sync-handler.ts +++ b/app/srv/ws/sync/sync-handler.ts @@ -81,7 +81,7 @@ export const syncHandler: WebSocketHandler = { if (actionName) { const baseAction = (actions as any)[actionName]; if (!baseAction) { - console.log(`app/ws/edit/sync/${actionName}.ts not found}`); + console.log(`app/ws/edit/sync/${actionName}.ts not found`); } if (baseAction) { const action = baseAction.bind({ diff --git a/app/web/src/render/ed/logic/ed-global.ts b/app/web/src/render/ed/logic/ed-global.ts index 18acc464..a612302d 100644 --- a/app/web/src/render/ed/logic/ed-global.ts +++ b/app/web/src/render/ed/logic/ed-global.ts @@ -2,9 +2,8 @@ import { NodeModel } from "@minoru/react-dnd-treeview"; import { clientStartSync } from "../../../utils/sync/client"; import { IContent, MContent } from "../../../utils/types/general"; import { IItem, MItem } from "../../../utils/types/item"; -import { DPage, IRoot } from "../../../utils/types/root"; +import { DComp, DPage, IRoot } from "../../../utils/types/root"; import { IText, MText } from "../../../utils/types/text"; -import { NodeMeta } from "../../editor/logic/global"; const EmptySite = { id: "", @@ -16,6 +15,7 @@ const EmptySite = { }; export type ESite = typeof EmptySite; export type EPage = typeof EmptyPage; +export type EComp = typeof EmptyComp; const EmptyPage = { id: "", @@ -24,9 +24,18 @@ const EmptyPage = { snapshot: null as null | Uint8Array, }; +const EmptyComp = { + id: "", + snapshot: null as null | Uint8Array, +}; + export type EdMeta = { item: IItem | IText; mitem?: MItem | MText; + parent_comp?: { + ref_ids: Record; + mcomp: MItem; + }; }; export const EDGlobal = { @@ -39,12 +48,19 @@ export const EDGlobal = { sync: null as unknown as Awaited>, site: EmptySite, page: { - current: EmptyPage, + cur: EmptyPage, doc: null as null | DPage, root: null as null | IRoot, entry: [] as string[], tree: [] as NodeModel[], meta: {} as Record, + list: {} as Record, + }, + comp: { + cur: EmptyComp, + doc: null as null | DComp, + item: null as null | IItem, + list: {} as Record, }, }; diff --git a/app/web/src/render/ed/logic/ed-route.ts b/app/web/src/render/ed/logic/ed-route.ts index 9175e5a8..1c73e55b 100644 --- a/app/web/src/render/ed/logic/ed-route.ts +++ b/app/web/src/render/ed/logic/ed-route.ts @@ -16,7 +16,7 @@ export const edRoute = async (p: PG) => { p.site = site; } - if (p.page.current.id !== params.page_id || !p.page.current.snapshot) { + if (p.page.cur.id !== params.page_id || !p.page.cur.snapshot) { p.status = "loading"; const page = await p.sync.page.load(params.page_id); @@ -26,7 +26,7 @@ export const edRoute = async (p: PG) => { return; } - p.page.current = page; + p.page.cur = page; if (page.snapshot) { const doc = new Y.Doc(); Y.applyUpdate(doc, page.snapshot); diff --git a/app/web/src/render/ed/logic/tree/build.tsx b/app/web/src/render/ed/logic/tree/build.tsx index af5f8359..2ca4f199 100644 --- a/app/web/src/render/ed/logic/tree/build.tsx +++ b/app/web/src/render/ed/logic/tree/build.tsx @@ -1,7 +1,8 @@ +import { createId } from "@paralleldrive/cuid2"; +import { MContent } from "../../../../utils/types/general"; import { IItem, MItem } from "../../../../utils/types/item"; -import { MRoot } from "../../../../utils/types/root"; +import { DComp } from "../../../../utils/types/root"; import { MSection } from "../../../../utils/types/section"; -import { walk } from "../../../editor/logic/tree-logic"; import { EdMeta, PG } from "../ed-global"; export const treeRebuild = async (p: PG) => { @@ -24,13 +25,7 @@ export const treeRebuild = async (p: PG) => { } }; -const walkMap = async ( - p: PG, - arg: { mitem: MItem | MSection; tree_parent_id: string } -) => { - const { mitem, tree_parent_id } = arg; - - const item = {} as unknown as IItem; +const mapItem = (mitem: MContent, item: any) => { mitem.forEach((e, k) => { if (k !== "childs") { let val = e; @@ -39,15 +34,104 @@ const walkMap = async ( val = e.toJSON() as any; } } - (item as any)[k] = val; + item[k] = val; } else { item[k] = []; } }); +}; + +const walkMap = async ( + p: PG, + arg: { + mitem: MItem | MSection; + tree_parent_id: string; + parent_comp?: EdMeta["parent_comp"]; + } +) => { + const { mitem, tree_parent_id, parent_comp } = arg; + + const item = {} as unknown as IItem; + mapItem(mitem, item); + + // sesuaikan item instance id dengan parent comp + if (parent_comp) { + if (!parent_comp["ref_ids"][item.id]) { + parent_comp["ref_ids"][item.id] = createId(); + } + + if (parent_comp["ref_ids"][item.id]) { + item.id = parent_comp["ref_ids"][item.id]; + } + } + + const metaNotFound = () => { + p.page.tree.push({ + id: item.id, + parent: tree_parent_id, + text: item.name, + }); + }; + + const item_comp = item.component; + if (item_comp && item_comp.id) { + if (!p.comp.list[item_comp.id]) { + let found = false; + const cur = await p.sync.comp.load(item_comp.id); + if (cur && cur.snapshot) { + const doc = new Y.Doc() as DComp; + if (cur.snapshot) { + Y.applyUpdate(doc as any, cur.snapshot); + p.comp.list[item_comp.id] = { cur, doc }; + found = true; + } + } + + if (!found) { + metaNotFound(); + return; + } + } + + const ref_comp = p.comp.list[item_comp.id]; + if (ref_comp) { + const mcomp = ref_comp.doc.getMap("map").get("item"); + if (mcomp) { + const ref_ids: Record = {}; + + if (parent_comp) { + let old_id = item.id; + mapItem(mcomp, item); + ref_ids[item.id] = old_id; + item.id = old_id; + } else { + mapItem(mcomp, item); + ref_ids[item.id] = createId(); + item.id = ref_ids[item.id]; + } + + await Promise.all( + mcomp.get("childs")?.map(async (e) => { + await walkMap(p, { + mitem: e, + tree_parent_id: item.id, + parent_comp: { ref_ids, mcomp }, + }); + }) || [] + ); + return; + } + } + + metaNotFound(); + + return; + } const meta: EdMeta = { item, mitem: mitem as MItem, + parent_comp, }; p.page.meta[item.id] = meta; @@ -59,10 +143,13 @@ const walkMap = async ( data: meta, }); - await Promise.all( - mitem.get("childs")?.map(async (e, k) => { - item.childs.push(e.get("id")); - await walkMap(p, { mitem: e, tree_parent_id: item.id }); - }) || [] - ); + const mchilds = mitem.get("childs"); + if (mchilds) { + await Promise.all( + mchilds.map(async (e, k) => { + item.childs.push(e.get("id")); + await walkMap(p, { mitem: e, tree_parent_id: item.id }); + }) || [] + ); + } }; diff --git a/app/web/src/utils/types/root.ts b/app/web/src/utils/types/root.ts index 24ac0886..732e06b4 100644 --- a/app/web/src/utils/types/root.ts +++ b/app/web/src/utils/types/root.ts @@ -1,5 +1,6 @@ import { TypedArray, TypedDoc, TypedMap } from "yjs-types"; import { ISection } from "./section"; +import { MItem } from "./item"; export type IRoot = { id: "root"; @@ -14,3 +15,4 @@ export type MRoot = TypedMap<{ }>; export type DPage = TypedDoc<{ map: TypedMap<{ id: string; root: MRoot }> }>; +export type DComp = TypedDoc<{ map: TypedMap<{ id: string; item: MItem }> }>;