From 030abd91b04bf475373758a3c10ee7a45b063df6 Mon Sep 17 00:00:00 2001 From: Rizky Date: Mon, 23 Oct 2023 08:55:00 +0700 Subject: [PATCH] checkpoint --- app/srv/ws/sync/actions-def.ts | 38 +++++--- app/srv/ws/sync/actions.ts | 4 + app/srv/ws/sync/actions/comp_redo.ts | 3 + app/srv/ws/sync/actions/comp_undo.ts | 3 + app/srv/ws/sync/actions/index.ts | 4 + app/srv/ws/sync/actions/page_load.ts | 2 +- app/srv/ws/sync/actions/page_redo.ts | 13 +++ app/srv/ws/sync/actions/page_undo.ts | 13 +++ app/web/src/render/ed/ed-base.tsx | 3 + app/web/src/render/ed/logic/ed-global.ts | 7 +- app/web/src/render/ed/logic/ed-undo.ts | 61 +++++++++++++ app/web/src/render/ed/logic/tree/build.tsx | 2 +- .../ed/panel/tree/node/item/action/attach.tsx | 12 +++ .../ed/panel/tree/node/item/action/clone.tsx | 4 + .../ed/panel/tree/node/item/action/copy.tsx | 14 +++ .../ed/panel/tree/node/item/action/cut.tsx | 25 +++++ .../ed/panel/tree/node/item/action/detach.tsx | 4 + .../ed/panel/tree/node/item/action/hide.tsx | 4 + .../panel/tree/node/item/action/new-comp.tsx | 4 + .../ed/panel/tree/node/item/action/paste.tsx | 4 + .../ed/panel/tree/node/item/ctx-menu.tsx | 91 +++++++++++++++++-- .../src/render/ed/panel/tree/node/render.tsx | 22 ++++- package.json | 4 +- 23 files changed, 313 insertions(+), 28 deletions(-) create mode 100644 app/srv/ws/sync/actions/comp_redo.ts create mode 100644 app/srv/ws/sync/actions/comp_undo.ts create mode 100644 app/srv/ws/sync/actions/page_redo.ts create mode 100644 app/srv/ws/sync/actions/page_undo.ts create mode 100644 app/web/src/render/ed/logic/ed-undo.ts create mode 100644 app/web/src/render/ed/panel/tree/node/item/action/attach.tsx create mode 100644 app/web/src/render/ed/panel/tree/node/item/action/clone.tsx create mode 100644 app/web/src/render/ed/panel/tree/node/item/action/copy.tsx create mode 100644 app/web/src/render/ed/panel/tree/node/item/action/cut.tsx create mode 100644 app/web/src/render/ed/panel/tree/node/item/action/detach.tsx create mode 100644 app/web/src/render/ed/panel/tree/node/item/action/hide.tsx create mode 100644 app/web/src/render/ed/panel/tree/node/item/action/new-comp.tsx create mode 100644 app/web/src/render/ed/panel/tree/node/item/action/paste.tsx diff --git a/app/srv/ws/sync/actions-def.ts b/app/srv/ws/sync/actions-def.ts index b8e1251f..09305fdb 100644 --- a/app/srv/ws/sync/actions-def.ts +++ b/app/srv/ws/sync/actions-def.ts @@ -5,28 +5,36 @@ export const SyncActionDefinition = { "load": "2" }, "comp": { - "list": "3", - "group": "4", - "load": "5" - }, - "page": { - "list": "6", + "undo": "3", + "redo": "4", + "list": "5", + "group": "6", "load": "7" }, + "page": { + "undo": "8", + "redo": "9", + "list": "10", + "load": "11" + }, "yjs": { - "sv_local": "8", - "diff_local": "9" + "sv_local": "12", + "diff_local": "13" } }; 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.sv_local", - "9": "yjs.diff_local" + "3": "comp.undo", + "4": "comp.redo", + "5": "comp.list", + "6": "comp.group", + "7": "comp.load", + "8": "page.undo", + "9": "page.redo", + "10": "page.list", + "11": "page.load", + "12": "yjs.sv_local", + "13": "yjs.diff_local" }; diff --git a/app/srv/ws/sync/actions.ts b/app/srv/ws/sync/actions.ts index c2b6c8a4..bfca5c7c 100644 --- a/app/srv/ws/sync/actions.ts +++ b/app/srv/ws/sync/actions.ts @@ -22,11 +22,15 @@ export const SyncActions = { load: async (id: string) => ({}) as ESite | void, }, comp: { + undo: async (id_comp: string) => {}, + redo: async (id_comp: string) => {}, list: () => ({}) as Record>, group: () => ({}) as Record, load: async (id: string) => ({}) as EComp | void, }, page: { + undo: async (id_page: string) => {}, + redo: async (id_page: string) => {}, list: (id_site: string) => ({}) as Record>, load: async (id: string) => ({}) as EPage | void, diff --git a/app/srv/ws/sync/actions/comp_redo.ts b/app/srv/ws/sync/actions/comp_redo.ts new file mode 100644 index 00000000..fa9718e0 --- /dev/null +++ b/app/srv/ws/sync/actions/comp_redo.ts @@ -0,0 +1,3 @@ +import { ActionCtx } from "../type"; + +export const comp_redo = async function (this: ActionCtx, id: string) {}; diff --git a/app/srv/ws/sync/actions/comp_undo.ts b/app/srv/ws/sync/actions/comp_undo.ts new file mode 100644 index 00000000..e47ee334 --- /dev/null +++ b/app/srv/ws/sync/actions/comp_undo.ts @@ -0,0 +1,3 @@ +import { ActionCtx } from "../type"; + +export const comp_undo = async function (this: ActionCtx, id: string) {}; diff --git a/app/srv/ws/sync/actions/index.ts b/app/srv/ws/sync/actions/index.ts index 7a25970e..f5c44931 100644 --- a/app/srv/ws/sync/actions/index.ts +++ b/app/srv/ws/sync/actions/index.ts @@ -2,5 +2,9 @@ export * from "./site_load"; export * from "./site_group"; export * from "./page_load"; export * from "./comp_load"; +export * from "./page_undo"; +export * from "./page_redo"; +export * from "./comp_undo"; +export * from "./comp_redo"; 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 f79e3b6f..64fc1190 100644 --- a/app/srv/ws/sync/actions/page_load.ts +++ b/app/srv/ws/sync/actions/page_load.ts @@ -11,7 +11,7 @@ export const page_load: SAction["page"]["load"] = async function ( ) { let snap = snapshot.get("page", id); let ydoc = docs.page[id]; - + if (!snap && !ydoc) { const page = await db.page.findFirst({ where: { id } }); if (page) { diff --git a/app/srv/ws/sync/actions/page_redo.ts b/app/srv/ws/sync/actions/page_redo.ts new file mode 100644 index 00000000..63c8e7d5 --- /dev/null +++ b/app/srv/ws/sync/actions/page_redo.ts @@ -0,0 +1,13 @@ +import { docs } from "../entity/docs"; +import { ActionCtx } from "../type"; + +export const page_redo = async function (this: ActionCtx, id: string) { + if (!docs.page[id]) { + return; + } + + const um = docs.page[id].um; + if (um.canRedo()) { + um.redo(); + } +}; diff --git a/app/srv/ws/sync/actions/page_undo.ts b/app/srv/ws/sync/actions/page_undo.ts new file mode 100644 index 00000000..88d14efd --- /dev/null +++ b/app/srv/ws/sync/actions/page_undo.ts @@ -0,0 +1,13 @@ +import { docs } from "../entity/docs"; +import { ActionCtx } from "../type"; + +export const page_undo = async function (this: ActionCtx, id: string) { + if (!docs.page[id]) { + return; + } + + const um = docs.page[id].um; + if (um.canUndo()) { + um.undo(); + } +}; diff --git a/app/web/src/render/ed/ed-base.tsx b/app/web/src/render/ed/ed-base.tsx index 2b8339cb..83192cb5 100644 --- a/app/web/src/render/ed/ed-base.tsx +++ b/app/web/src/render/ed/ed-base.tsx @@ -5,10 +5,13 @@ import { edInit } from "./logic/ed-init"; import { edRoute } from "./logic/ed-route"; import { EdMain } from "./panel/main/main"; import { EdTree } from "./panel/tree/tree"; +import { edUndoManager } from "./logic/ed-undo"; export const EdBase = () => { const p = useGlobal(EDGlobal, "EDITOR"); + edUndoManager(p); + if (p.status === "init") { edInit(p); } diff --git a/app/web/src/render/ed/logic/ed-global.ts b/app/web/src/render/ed/logic/ed-global.ts index 03ca85e3..dbc4624a 100644 --- a/app/web/src/render/ed/logic/ed-global.ts +++ b/app/web/src/render/ed/logic/ed-global.ts @@ -68,11 +68,16 @@ export const EDGlobal = { item: null as null | IItem, list: {} as Record, }, - ui: { + select: { + id: "", + }, tree: { open: {} as Record, }, + popup: { + comp: null as null | ((comp_id: string) => void | Promise), + }, }, }; diff --git a/app/web/src/render/ed/logic/ed-undo.ts b/app/web/src/render/ed/logic/ed-undo.ts new file mode 100644 index 00000000..fb6f218c --- /dev/null +++ b/app/web/src/render/ed/logic/ed-undo.ts @@ -0,0 +1,61 @@ +import { useEffect } from "react"; +import { PG } from "./ed-global"; +import { treeRebuild } from "./tree/build"; + +export const edUndoManager = async (p: PG) => { + useEffect(() => { + const keyDown = async (evt: KeyboardEvent) => { + if ( + (evt.key === "s" || evt.key === "s") && + (evt.ctrlKey || evt.metaKey) + ) { + evt.preventDefault(); + evt.stopPropagation(); + } + + if ( + (evt.key === "Y" || evt.key === "y") && + (evt.ctrlKey || evt.metaKey) && + !evt.shiftKey + ) { + console.log("redo"); + return; + } + + if ( + (evt.key === "Z" || evt.key === "z") && + (evt.ctrlKey || evt.metaKey) && + evt.shiftKey + ) { + console.log("redo"); + + return; + } + + if ( + (evt.key === "Z" || evt.key === "z") && + (evt.ctrlKey || evt.metaKey) && + !evt.shiftKey + ) { + if (p.comp.cur.id) { + p.sync.comp.undo(p.comp.cur.id); + } else { + p.sync.page.undo(p.page.cur.id); + } + } + + if ( + (evt.key === "r" || evt.key === "R" || evt.key === "®") && + evt.altKey + ) { + evt.preventDefault(); + evt.stopPropagation(); + await treeRebuild(p, { note: "reload" }); + } + }; + window.addEventListener("keydown", keyDown, true); + return () => { + window.removeEventListener("keydown", keyDown, true); + }; + }, []); +}; diff --git a/app/web/src/render/ed/logic/tree/build.tsx b/app/web/src/render/ed/logic/tree/build.tsx index 733d5b43..8bbd1685 100644 --- a/app/web/src/render/ed/logic/tree/build.tsx +++ b/app/web/src/render/ed/logic/tree/build.tsx @@ -14,7 +14,7 @@ import { MSection } from "../../../../utils/types/section"; import { EdMeta, PG } from "../ed-global"; import { decompress } from "wasm-gzip"; -export const treeRebuild = async (p: PG) => { +export const treeRebuild = async (p: PG, arg?: { note?: string }) => { const doc = p.page.doc; if (!doc) return; diff --git a/app/web/src/render/ed/panel/tree/node/item/action/attach.tsx b/app/web/src/render/ed/panel/tree/node/item/action/attach.tsx new file mode 100644 index 00000000..907b91c0 --- /dev/null +++ b/app/web/src/render/ed/panel/tree/node/item/action/attach.tsx @@ -0,0 +1,12 @@ +import { IItem } from "../../../../../../../utils/types/item"; +import { MenuItem } from "../../../../../../../utils/ui/context-menu"; +import { PG } from "../../../../../logic/ed-global"; + +export const edActionAttach = (p: PG, item: IItem) => { + p.ui.select.id = item.id; + const pick = () => { + p.ui.popup.comp = (comp_id) => {}; + p.render(); + }; + pick(); +}; diff --git a/app/web/src/render/ed/panel/tree/node/item/action/clone.tsx b/app/web/src/render/ed/panel/tree/node/item/action/clone.tsx new file mode 100644 index 00000000..db04dbdf --- /dev/null +++ b/app/web/src/render/ed/panel/tree/node/item/action/clone.tsx @@ -0,0 +1,4 @@ +import { IContent } from "../../../../../../../utils/types/general"; +import { PG } from "../../../../../logic/ed-global"; + +export const edActionClone = (p: PG, item: IContent) => {}; diff --git a/app/web/src/render/ed/panel/tree/node/item/action/copy.tsx b/app/web/src/render/ed/panel/tree/node/item/action/copy.tsx new file mode 100644 index 00000000..a06450ff --- /dev/null +++ b/app/web/src/render/ed/panel/tree/node/item/action/copy.tsx @@ -0,0 +1,14 @@ +import { IContent } from "../../../../../../../utils/types/general"; +import { PG } from "../../../../../logic/ed-global"; + +export const edActionCopy = async (p: PG, item: IContent) => { + const perm = await navigator.permissions.query({ + name: "clipboard-read", + allowWithoutGesture: false, + } as any); + if (perm.state !== "granted") { + await navigator.clipboard.read(); + } + let str = `prasi-clipboard:` + JSON.stringify(item); + navigator.clipboard.writeText(str); +}; diff --git a/app/web/src/render/ed/panel/tree/node/item/action/cut.tsx b/app/web/src/render/ed/panel/tree/node/item/action/cut.tsx new file mode 100644 index 00000000..b90a437c --- /dev/null +++ b/app/web/src/render/ed/panel/tree/node/item/action/cut.tsx @@ -0,0 +1,25 @@ +import { IContent } from "../../../../../../../utils/types/general"; +import { PG } from "../../../../../logic/ed-global"; +import { treeRebuild } from "../../../../../logic/tree/build"; + +export const edActionCut = async (p: PG, item: IContent) => { + const perm = await navigator.permissions.query({ + name: "clipboard-read", + allowWithoutGesture: false, + } as any); + if (perm.state !== "granted") { + await navigator.clipboard.read(); + } + let str = `prasi-clipboard:` + JSON.stringify(item); + navigator.clipboard.writeText(str); + + const mitem = p.page.meta[item.id].mitem; + if (mitem) { + mitem.parent.forEach((e, k) => { + if (e == mitem) { + mitem.parent.delete(k); + } + }); + await treeRebuild(p); + } +}; diff --git a/app/web/src/render/ed/panel/tree/node/item/action/detach.tsx b/app/web/src/render/ed/panel/tree/node/item/action/detach.tsx new file mode 100644 index 00000000..4358fd05 --- /dev/null +++ b/app/web/src/render/ed/panel/tree/node/item/action/detach.tsx @@ -0,0 +1,4 @@ +import { IItem } from "../../../../../../../utils/types/item"; +import { PG } from "../../../../../logic/ed-global"; + +export const edActionDetach = (p: PG, item: IItem) => {}; diff --git a/app/web/src/render/ed/panel/tree/node/item/action/hide.tsx b/app/web/src/render/ed/panel/tree/node/item/action/hide.tsx new file mode 100644 index 00000000..5d5a1852 --- /dev/null +++ b/app/web/src/render/ed/panel/tree/node/item/action/hide.tsx @@ -0,0 +1,4 @@ +import { IContent } from "../../../../../../../utils/types/general"; +import { PG } from "../../../../../logic/ed-global"; + +export const edActionHide = (p: PG, item: IContent) => {}; 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 new file mode 100644 index 00000000..3dfb6aec --- /dev/null +++ b/app/web/src/render/ed/panel/tree/node/item/action/new-comp.tsx @@ -0,0 +1,4 @@ +import { IItem } from "../../../../../../../utils/types/item"; +import { PG } from "../../../../../logic/ed-global"; + +export const edActionNewComp = (p: PG, item: IItem) => {}; diff --git a/app/web/src/render/ed/panel/tree/node/item/action/paste.tsx b/app/web/src/render/ed/panel/tree/node/item/action/paste.tsx new file mode 100644 index 00000000..6ae4f799 --- /dev/null +++ b/app/web/src/render/ed/panel/tree/node/item/action/paste.tsx @@ -0,0 +1,4 @@ +import { IContent } from "../../../../../../../utils/types/general"; +import { PG } from "../../../../../logic/ed-global"; + +export const edActionPaste = (p: PG, item: IContent) => {}; 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 49c0fb24..c46c54e8 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 @@ -1,16 +1,93 @@ -import { - NodeModel, - NodeRender, - RenderParams, -} from "@minoru/react-dnd-treeview"; -import { EdMeta } from "../../../../logic/ed-global"; +import { NodeModel, RenderParams } from "@minoru/react-dnd-treeview"; +import { useGlobal, useLocal } from "web-utils"; +import { IItem } from "../../../../../../utils/types/item"; +import { FNComponent } from "../../../../../../utils/types/meta-fn"; +import { Menu, MenuItem } from "../../../../../../utils/ui/context-menu"; +import { EDGlobal, EdMeta } from "../../../../logic/ed-global"; +import { edActionAttach } from "./action/attach"; +import { edActionClone } from "./action/clone"; +import { edActionCopy } from "./action/copy"; +import { edActionCut } from "./action/cut"; +import { edActionDetach } from "./action/detach"; +import { edActionHide } from "./action/hide"; +import { edActionNewComp } from "./action/new-comp"; +import { edActionPaste } from "./action/paste"; export const EdTreeCtxMenu = ({ node, prm, + event, + onClose, }: { + event: React.MouseEvent; node: NodeModel; prm: RenderParams; + onClose: () => void; }) => { - return <>; + const p = useGlobal(EDGlobal, "EDITOR"); + const local = useLocal({ allowCopy: false, allowPaste: false }, async () => { + const permissionStatus = await navigator.permissions.query({ + name: "clipboard-read", + allowWithoutGesture: false, + } as any); + if (permissionStatus.state === "granted") { + local.allowCopy = true; + local.render(); + + navigator.clipboard + .readText() + .then((e) => { + if (e.startsWith("prasi-clipboard:")) { + local.allowPaste = true; + local.render(); + } + }) + .catch(() => {}); + } + }); + const item = node.data?.item; + const type = item?.type; + const comp = (item as IItem).component as FNComponent | undefined; + const rootComp = p.comp.cur; + const isActiveComponent = rootComp && rootComp.id === item?.id && rootComp.id; + + if (!item) { + return ( + + Unavailable} /> + + ); + } + + return ( + + {type === "item" && !isActiveComponent && !item.component?.id && ( + edActionAttach(p, item)} + /> + )} + {type === "item" && comp?.id && !isActiveComponent && ( + edActionDetach(p, item)} + /> + )} + {type === "item" && comp?.id && ( + edActionNewComp(p, item)} + /> + )} + {!item.hidden && ( + edActionHide(p, item)} /> + )} + edActionClone(p, item)} /> + edActionCut(p, item)} /> + edActionCopy(p, item)} /> + {local.allowCopy && local.allowPaste && ( + edActionPaste(p, item)} /> + )} + + ); }; diff --git a/app/web/src/render/ed/panel/tree/node/render.tsx b/app/web/src/render/ed/panel/tree/node/render.tsx index 4b14e0e8..c673c895 100644 --- a/app/web/src/render/ed/panel/tree/node/render.tsx +++ b/app/web/src/render/ed/panel/tree/node/render.tsx @@ -5,19 +5,39 @@ import { EdTreeCtxMenu } from "./item/ctx-menu"; import { EdTreeIndent } from "./item/indent"; import { EdTreeName } from "./item/name"; import { indentHook } from "./item/indent-hook"; +import { useLocal } from "web-utils"; export const nodeRender: NodeRender = (node, prm) => { + const local = useLocal({ + rightClick: null as null | React.MouseEvent, + }); if (!node || !node.data) return <>; const item = node.data?.item; const isComponent = item.type === "item" && item.component?.id; + return (
{ + event.preventDefault(); + local.rightClick = event; + local.render(); + }} > - + {local.rightClick && ( + { + local.rightClick = null; + local.render(); + }} + /> + )} diff --git a/package.json b/package.json index e8090210..afd93d93 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "type": "module", "scripts": { "dev": "bun run --silent --watch ./pkgs/core/index.ts dev", - "clean": "rm -rf app/static && rm -rf app/web/.parcel-cache", + "clean": "rm -rf data && rm -rf app/static && rm -rf app/web/.parcel-cache", "build": "bun run --silent ./pkgs/core/build.ts", "db-pull": "bun run ./pkgs/core/db-pull.ts", "parcel": "bun clean && bun run ./pkgs/core/parcel.ts", @@ -13,7 +13,7 @@ "pull": "cd app/db && bun prisma db pull && bun prisma generate", "pkgs-upgrade": "bun run --silent ./pkgs/core/upgrade.ts" }, - "workspaces": [ + "workspaces": [ "app/*", "pkgs/*" ],