From 51d0f5accd68767472f60aff9a1e7aca0a179959 Mon Sep 17 00:00:00 2001 From: Rizky Date: Mon, 12 Feb 2024 17:06:14 +0700 Subject: [PATCH] wip fix --- app/db/prisma/schema.prisma | 33 ++-- app/srv/ws/sync/actions/code_edit.ts | 1 + app/srv/ws/sync/actions/yjs_diff_local.ts | 29 ++++ app/web/src/nova/ed/ed-base.tsx | 38 +++-- app/web/src/nova/ed/ed-left.tsx | 57 +++++-- .../nova/ed/logic/active/is-meta.active.tsx | 2 + app/web/src/nova/ed/logic/ed-global.ts | 4 + .../src/nova/ed/panel/main/main-history.tsx | 152 ++++++++++++++++++ app/web/src/nova/ed/panel/main/main.tsx | 2 +- .../nova/ed/panel/popup/comp/comp-popup.tsx | 36 +++-- .../src/nova/ed/panel/tree/history-btn.tsx | 41 +++++ .../src/nova/ed/panel/tree/history-list.tsx | 63 ++++++++ app/web/src/nova/ed/panel/tree/search.tsx | 1 - 13 files changed, 397 insertions(+), 62 deletions(-) create mode 100644 app/web/src/nova/ed/panel/main/main-history.tsx create mode 100644 app/web/src/nova/ed/panel/tree/history-btn.tsx create mode 100644 app/web/src/nova/ed/panel/tree/history-list.tsx diff --git a/app/db/prisma/schema.prisma b/app/db/prisma/schema.prisma index 16ac0c75..31e0e076 100644 --- a/app/db/prisma/schema.prisma +++ b/app/db/prisma/schema.prisma @@ -146,24 +146,25 @@ model org_user { } model page { - id String @id(map: "page_id") @default(dbgenerated("gen_random_uuid()")) @db.Uuid + id String @id(map: "page_id") @default(dbgenerated("gen_random_uuid()")) @db.Uuid name String url String content_tree Json - id_site String @db.Uuid - created_at DateTime? @default(now()) @db.Timestamp(6) + id_site String @db.Uuid + created_at DateTime? @default(now()) @db.Timestamp(6) js_compiled String? js String? - updated_at DateTime? @default(now()) @db.Timestamp(6) - id_folder String? @db.Uuid - is_deleted Boolean @default(false) - id_layout String? @db.Uuid - is_default_layout Boolean @default(false) + updated_at DateTime? @default(now()) @db.Timestamp(6) + id_folder String? @db.Uuid + is_deleted Boolean @default(false) + id_layout String? @db.Uuid + is_default_layout Boolean @default(false) code_assign code_assign[] - page_folder page_folder? @relation(fields: [id_folder], references: [id], onDelete: NoAction, onUpdate: NoAction) - page page? @relation("pageTopage", fields: [id_layout], references: [id], onDelete: NoAction, onUpdate: NoAction) - other_page page[] @relation("pageTopage") - site site @relation(fields: [id_site], references: [id], onDelete: NoAction, onUpdate: NoAction) + page_folder page_folder? @relation(fields: [id_folder], references: [id], onDelete: NoAction, onUpdate: NoAction) + page page? @relation("pageTopage", fields: [id_layout], references: [id], onDelete: NoAction, onUpdate: NoAction) + other_page page[] @relation("pageTopage") + site site @relation(fields: [id_site], references: [id], onDelete: NoAction, onUpdate: NoAction) + page_history page_history[] } model page_folder { @@ -307,3 +308,11 @@ model code_assign { component_group component_group? @relation(fields: [id_component_group], references: [id], onDelete: NoAction, onUpdate: NoAction) page page? @relation(fields: [id_page], references: [id], onDelete: NoAction, onUpdate: NoAction) } + +model page_history { + id String @id(map: "page_history_id") @default(dbgenerated("gen_random_uuid()")) @db.Uuid + id_page String @db.Uuid + content_tree Bytes + ts String + page page @relation(fields: [id_page], references: [id], onDelete: NoAction, onUpdate: NoAction) +} diff --git a/app/srv/ws/sync/actions/code_edit.ts b/app/srv/ws/sync/actions/code_edit.ts index c1a84d6e..86d42cd8 100644 --- a/app/srv/ws/sync/actions/code_edit.ts +++ b/app/srv/ws/sync/actions/code_edit.ts @@ -11,6 +11,7 @@ import { SyncConnection } from "../type"; import { parseJs } from "../editor/parser/parse-js"; import { snapshot } from "../entity/snapshot"; import { validate } from "uuid"; +import { gzipAsync } from "utils/diff"; const decoder = new TextDecoder(); const timeout = { diff --git a/app/srv/ws/sync/actions/yjs_diff_local.ts b/app/srv/ws/sync/actions/yjs_diff_local.ts index 5ba046bd..54bb1e23 100644 --- a/app/srv/ws/sync/actions/yjs_diff_local.ts +++ b/app/srv/ws/sync/actions/yjs_diff_local.ts @@ -3,6 +3,9 @@ import { SAction } from "../actions"; import { docs } from "../entity/docs"; import { gunzipAsync } from "../entity/zlib"; import { SyncConnection } from "../type"; +import { gzipAsync } from "utils/diff"; + +const history = {} as Record; export const yjs_diff_local: SAction["yjs"]["diff_local"] = async function ( this: SyncConnection, @@ -25,6 +28,32 @@ export const yjs_diff_local: SAction["yjs"]["diff_local"] = async function ( if (root) { if (mode === "page") { if (validate(id) && id) { + let mode = "create" as "create" | "update"; + const cur = Math.round(Date.now() / 5000) + ""; + if (history[id] && history[id] === cur) { + mode = "update"; + } + history[id] = cur; + if (mode === "create") { + await _db.page_history.create({ + data: { + id_page: id, + content_tree: await gzipAsync(JSON.stringify(root.toJSON())), + ts: history[id], + }, + }); + } else { + await _db.page_history.updateMany({ + data: { + content_tree: await gzipAsync(JSON.stringify(root.toJSON())), + }, + where: { + id_page: id, + ts: history[id], + }, + }); + } + await _db.page.update({ where: { id }, data: { diff --git a/app/web/src/nova/ed/ed-base.tsx b/app/web/src/nova/ed/ed-base.tsx index 1d04217a..8bd82a4e 100644 --- a/app/web/src/nova/ed/ed-base.tsx +++ b/app/web/src/nova/ed/ed-base.tsx @@ -16,6 +16,7 @@ import { EdPopComp } from "./panel/popup/comp/comp-popup"; import { EdPopPage } from "./panel/popup/page/page-popup"; import { EdPopScript } from "./panel/popup/script/pop-script"; import { EdPopSite } from "./panel/popup/site/site-popup"; +import { EdPageHistoryMain } from "./panel/main/main-history"; export const EdBase = () => { const p = useGlobal(EDGlobal, "EDITOR"); @@ -51,22 +52,27 @@ export const EdBase = () => { )}
-
- {p.status !== "ready" ? ( - - ) : ( - <> - - - - - )} -
+ + {p.page.history.id ? ( + + ) : ( +
+ {p.status !== "ready" ? ( + + ) : ( + <> + + + + + )} +
+ )}
<> diff --git a/app/web/src/nova/ed/ed-left.tsx b/app/web/src/nova/ed/ed-left.tsx index e34838d9..5df702bc 100644 --- a/app/web/src/nova/ed/ed-left.tsx +++ b/app/web/src/nova/ed/ed-left.tsx @@ -7,11 +7,17 @@ import { EdApi } from "./panel/header/left/api"; import { EdSiteJS } from "./panel/header/left/js"; import { EdSitePicker } from "./panel/header/left/site-picker"; import { EdTreeBody } from "./panel/tree/body"; +import { EdPageHistoryBtn } from "./panel/tree/history-btn"; +import { EdPageHistoryList } from "./panel/tree/history-list"; import { EdTreeSearch } from "./panel/tree/search"; +import { treeRebuild } from "./logic/tree/build"; export const EdLeft = () => { const p = useGlobal(EDGlobal, "EDITOR"); - const local = useLocal({ tree: null as any, timeout: null as any }); + const local = useLocal({ + tree: null as any, + timeout: null as any, + }); if (!local.tree) { clearTimeout(local.timeout); @@ -40,27 +46,46 @@ export const EdLeft = () => { - +
+ { + p.page.history.id = ""; + p.page.history.show = show; + if (!show) { + await treeRebuild(p); + } + + p.render(); + local.render(); + }} + /> + {!p.page.history.show && } +
{ if (ref) local.tree = ref; }} > -
- {local.tree && ( - - - - )} -
+ {p.page.history.show ? ( + + ) : ( +
+ {local.tree && ( + + + + )} +
+ )}
diff --git a/app/web/src/nova/ed/logic/active/is-meta.active.tsx b/app/web/src/nova/ed/logic/active/is-meta.active.tsx index 032de73b..39325776 100644 --- a/app/web/src/nova/ed/logic/active/is-meta.active.tsx +++ b/app/web/src/nova/ed/logic/active/is-meta.active.tsx @@ -2,6 +2,8 @@ import { getCompMeta } from "../comp/comp-meta"; import { IMeta, PG, active } from "../ed-global"; export const isMetaActive = (p: PG, meta: IMeta) => { + if (!meta.item) return false; + let is_active: boolean = active.item_id === meta.item.id; if (active.comp_id) { if (meta.parent?.comp_id === active.comp_id) { diff --git a/app/web/src/nova/ed/logic/ed-global.ts b/app/web/src/nova/ed/logic/ed-global.ts index 8404adb7..71b6f428 100644 --- a/app/web/src/nova/ed/logic/ed-global.ts +++ b/app/web/src/nova/ed/logic/ed-global.ts @@ -168,6 +168,10 @@ export const EDGlobal = { init_local_effect: {} as Record, }, page: { + history: { + id: "", + show: false + }, root_id: "root", cur: EmptyPage, doc: null as null | DPage, diff --git a/app/web/src/nova/ed/panel/main/main-history.tsx b/app/web/src/nova/ed/panel/main/main-history.tsx new file mode 100644 index 00000000..a36ef602 --- /dev/null +++ b/app/web/src/nova/ed/panel/main/main-history.tsx @@ -0,0 +1,152 @@ +import { useGlobal, useLocal } from "web-utils"; +import { EDGlobal } from "../../logic/ed-global"; +import { FC, useEffect } from "react"; +import { decompress } from "wasm-gzip"; +import { Vi } from "../../../vi/vi"; +import { genMeta } from "../../../vi/meta/meta"; +import { IRoot } from "../../../../utils/types/root"; +import { IContent } from "../../../../utils/types/general"; +import { initLoadComp } from "../../../vi/meta/comp/init-comp-load"; +import { loadCompSnapshot } from "../../logic/comp/load"; +import { IItem } from "../../../../utils/types/item"; +import { mainStyle } from "./main"; +import { Loading } from "../../../../utils/ui/loading"; +import { treeRebuild } from "../../logic/tree/build"; + +const decoder = new TextDecoder(); +export const EdPageHistoryMain: FC<{}> = ({}) => { + const p = useGlobal(EDGlobal, "EDITOR"); + const local = useLocal({ + loading: true, + root: null as any, + meta: {} as any, + entry: [] as any, + width: 0, + height: 0, + }); + + useEffect(() => { + local.loading = true; + local.render(); + _db.page_history + .findFirst({ + where: { id: p.page.history.id }, + select: { + content_tree: true, + }, + }) + .then(async (e) => { + if (e) { + const zip = new Uint8Array((e.content_tree as any).data); + const root = JSON.parse(decoder.decode(decompress(zip))) as IRoot; + local.root = root; + await initLoadComp( + { + comps: p.comp.loaded, + meta: local.meta, + mode: "page", + }, + root as unknown as IItem, + { + async load(comp_ids) { + if (!p.sync) return; + + const ids = comp_ids.filter((id) => !p.comp.loaded[id]); + const comps = await p.sync.comp.load(ids, true); + let result = Object.entries(comps); + + for (const [id_comp, comp] of result) { + if (comp && comp.snapshot && !p.comp.list[id_comp]) { + if (!p.comp.loaded[id_comp]) { + await loadCompSnapshot(p, id_comp, comp.snapshot); + } + } + } + }, + } + ); + + local.meta = {}; + local.entry = []; + for (const item of root.childs) { + local.entry.push(item.id); + genMeta( + { + note: "cache-rebuild", + comps: p.comp.loaded, + meta: local.meta, + mode: "page", + }, + { item: item as IContent } + ); + } + + local.loading = false; + local.render(); + p.render(); + } + }); + }, [p.page.history.id]); + + return ( +
+
+
{ + if (confirm("Are you sure ?")) { + p.page.history.id = ""; + p.page.history.show = false; + + p.page.doc?.transact(() => { + const root = p.page.doc?.getMap("map").get("root"); + + if (root) { + syncronize(root as any, local.root); + } + }); + + p.render(); + } + }} + > + Revert to this version +
+
+
{ + if (el) { + const bound = el.getBoundingClientRect(); + if (local.width !== bound.width || local.height !== bound.height) { + local.width = bound.width; + local.height = bound.height; + local.render(); + } + } + }} + > + {local.loading ? ( + + ) : ( +
+ +
+ )} +
+
+ ); +}; diff --git a/app/web/src/nova/ed/panel/main/main.tsx b/app/web/src/nova/ed/panel/main/main.tsx index 26a98ace..0e573cd5 100644 --- a/app/web/src/nova/ed/panel/main/main.tsx +++ b/app/web/src/nova/ed/panel/main/main.tsx @@ -107,7 +107,7 @@ export const EdMain = () => { return null; }; -const mainStyle = (p: PG, meta?: IMeta) => { +export const mainStyle = (p: PG, meta?: IMeta) => { let is_active = meta ? isMetaActive(p, meta) : false; const scale = parseInt(p.ui.zoom.replace("%", "")) / 100; diff --git a/app/web/src/nova/ed/panel/popup/comp/comp-popup.tsx b/app/web/src/nova/ed/panel/popup/comp/comp-popup.tsx index d1957899..ae72d6fb 100644 --- a/app/web/src/nova/ed/panel/popup/comp/comp-popup.tsx +++ b/app/web/src/nova/ed/panel/popup/comp/comp-popup.tsx @@ -87,9 +87,9 @@ export const EdPopComp = () => { className={cx( "border cursor-pointer -mb-[1px] px-2 hover:text-blue-500 hover:border-blue-500 hover:border-b-transparent select-none", local.tab === e && - "bg-white border-b-transparent", + "bg-white border-b-transparent", local.tab !== e && - "text-slate-400 border-b-slate-200 border-transparent bg-transparent" + "text-slate-400 border-b-slate-200 border-transparent bg-transparent" )} onClick={() => { local.tab = e; @@ -101,7 +101,7 @@ export const EdPopComp = () => { ); })} -
+
{ background: #efefff; } `, - compPicker.search ? css` - > .tree-root { - display: flex; - flex-direction: row; - flex-wrap: wrap; - position: relative; - }` : css` - > .tree-root > .listitem > .container { - display: flex; - flex-direction: row; - flex-wrap: wrap; - position: relative; - }` + compPicker.search + ? css` + > .tree-root { + display: flex; + flex-direction: row; + flex-wrap: wrap; + position: relative; + } + ` + : css` + > .tree-root > .listitem > .container { + display: flex; + flex-direction: row; + flex-wrap: wrap; + position: relative; + } + ` )} > {compPicker.ref && compPicker.status === "ready" && ( diff --git a/app/web/src/nova/ed/panel/tree/history-btn.tsx b/app/web/src/nova/ed/panel/tree/history-btn.tsx new file mode 100644 index 00000000..bd66a1e9 --- /dev/null +++ b/app/web/src/nova/ed/panel/tree/history-btn.tsx @@ -0,0 +1,41 @@ +import { FC } from "react"; + +export const EdPageHistoryBtn: FC<{ + show: boolean; + onShow: (show: boolean) => void; +}> = ({ show, onShow }) => { + return ( + <> +
{ + onShow(!show); + }} + > +
`, + }} + >
+ {show && ( + <> +
History
+
+ × +
+ + )} +
+ + ); +}; diff --git a/app/web/src/nova/ed/panel/tree/history-list.tsx b/app/web/src/nova/ed/panel/tree/history-list.tsx new file mode 100644 index 00000000..18653a4b --- /dev/null +++ b/app/web/src/nova/ed/panel/tree/history-list.tsx @@ -0,0 +1,63 @@ +import { useGlobal, useLocal } from "web-utils"; +import { EDGlobal } from "../../logic/ed-global"; +import { Loading } from "../../../../utils/ui/loading"; +import { format, formatDistance } from "date-fns"; +export const EdPageHistoryList = () => { + const p = useGlobal(EDGlobal, "EDITOR"); + const local = useLocal( + { loading: true, list: [] as Awaited> }, + async () => { + console.log("query list"); + local.list = await queryList(p.page.cur.id); + local.loading = false; + local.render(); + } + ); + + return ( + <> + {local.loading ? ( + + ) : ( +
+ {local.list.map((e) => { + return ( +
{ + p.page.history.id = e.id; + p.render(); + local.render(); + }} + > +
+ {format(parseInt(e.ts) * 5000, "yyyy-MM-dd HH:mm:ss")} +
+ +
+ {formatDistance(Date.now(), parseInt(e.ts) * 5000) + " ago"} +
+
+ ); + })} +
+ )} + + ); +}; + +const queryList = async (page_id: string) => { + return await _db.page_history.findMany({ + where: { id_page: page_id }, + select: { + id: true, + ts: true, + }, + orderBy: { ts: "desc" }, + }); +}; diff --git a/app/web/src/nova/ed/panel/tree/search.tsx b/app/web/src/nova/ed/panel/tree/search.tsx index 9721b7b5..f40fc80b 100644 --- a/app/web/src/nova/ed/panel/tree/search.tsx +++ b/app/web/src/nova/ed/panel/tree/search.tsx @@ -22,7 +22,6 @@ export const EdTreeSearch = () => { return (
{ if (local.focus) { local.hover = true;