From 0279eb30be46674931025e995a58d2059fb18ab5 Mon Sep 17 00:00:00 2001 From: Rizky Date: Tue, 24 Oct 2023 20:50:37 +0700 Subject: [PATCH] tree keyboard improvement --- app/web/src/render/ed/logic/ed-global.ts | 2 + app/web/src/render/ed/logic/ed-route.ts | 1 - app/web/src/render/ed/logic/tree/build.tsx | 8 +- .../ed/panel/tree/node/item/action/clone.tsx | 7 +- .../ed/panel/tree/node/item/action/del.tsx | 15 ++ .../ed/panel/tree/node/item/indent-hook.ts | 4 + .../render/ed/panel/tree/node/item/indent.tsx | 6 +- .../src/render/ed/panel/tree/node/render.tsx | 195 +++++++++++++++++- app/web/src/render/ed/panel/tree/search.tsx | 17 +- 9 files changed, 244 insertions(+), 11 deletions(-) create mode 100644 app/web/src/render/ed/panel/tree/node/item/action/del.tsx diff --git a/app/web/src/render/ed/logic/ed-global.ts b/app/web/src/render/ed/logic/ed-global.ts index aa98c795..e3eb63de 100644 --- a/app/web/src/render/ed/logic/ed-global.ts +++ b/app/web/src/render/ed/logic/ed-global.ts @@ -87,10 +87,12 @@ export const EDGlobal = { group: {} as Record>>, }, ui: { + prevent_indent_hook: false, syncing: false, tree: { item_loading: [] as string[], search: "", + search_ref: null as null | HTMLInputElement, search_mode: { Name: true, JS: false, diff --git a/app/web/src/render/ed/logic/ed-route.ts b/app/web/src/render/ed/logic/ed-route.ts index 6e4bf3b2..a165c8fc 100644 --- a/app/web/src/render/ed/logic/ed-route.ts +++ b/app/web/src/render/ed/logic/ed-route.ts @@ -33,7 +33,6 @@ export const edRoute = async (p: PG) => { doc.on("update", async (bin: Uint8Array, origin: any) => { if (origin === "sv_remote" || origin === "local") return; - console.log(origin); const res = await p.sync.yjs.sv_local( "page", p.page.cur.id, diff --git a/app/web/src/render/ed/logic/tree/build.tsx b/app/web/src/render/ed/logic/tree/build.tsx index ddfc3311..095da7e8 100644 --- a/app/web/src/render/ed/logic/tree/build.tsx +++ b/app/web/src/render/ed/logic/tree/build.tsx @@ -147,8 +147,10 @@ const walkMap = ( const fcomp = parent_comp.mitem.get("component"); if (fcomp) { const ref_ids = fcomp.get("ref_ids"); + if (ref_ids) { let ref_id = ref_ids.get(id); + if (!ref_id) { ref_id = createId(); ref_ids.set(id, ref_id); @@ -157,7 +159,9 @@ const walkMap = ( } } } + mapItem(mitem, item); + if (override_id) { item.id = override_id; } @@ -190,8 +194,9 @@ const walkMap = ( mitem_comp.set("ref_ids", new Y.Map() as any); ref_ids = {}; } - + const original_id = item.id; mapItem(mcomp, item); + item.id = original_id; const meta: EdMeta = { item, @@ -200,6 +205,7 @@ const walkMap = ( parent_comp, }; p.page.meta[item.id] = meta; + if (!skip_tree) { p.page.tree.push({ id: item.id, 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 index 9f11e4c9..7b3b5a04 100644 --- 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 @@ -1,5 +1,6 @@ import { syncronize } from "y-pojo"; import { IContent, MContent } from "../../../../../../../utils/types/general"; +import { IItem } from "../../../../../../../utils/types/item"; import { fillID } from "../../../../../../editor/tools/fill-id"; import { PG } from "../../../../../logic/ed-global"; import { treeRebuild } from "../../../../../logic/tree/build"; @@ -10,9 +11,11 @@ export const edActionClone = (p: PG, item: IContent) => { mitem.doc?.transact(() => { mitem.parent.forEach((e: MContent, idx) => { if (e.get("id") === mitem.get("id")) { - const json = e.toJSON() as IContent; + const json = e.toJSON() as IItem; + fillID(json); + if (json.component) json.component.ref_ids = {}; const map = new Y.Map(); - syncronize(map, fillID(json)); + syncronize(map, json); mitem.parent.insert(idx, [map]); } }); diff --git a/app/web/src/render/ed/panel/tree/node/item/action/del.tsx b/app/web/src/render/ed/panel/tree/node/item/action/del.tsx new file mode 100644 index 00000000..a0807c71 --- /dev/null +++ b/app/web/src/render/ed/panel/tree/node/item/action/del.tsx @@ -0,0 +1,15 @@ +import { IContent } from "../../../../../../../utils/types/general"; +import { PG } from "../../../../../logic/ed-global"; +import { treeRebuild } from "../../../../../logic/tree/build"; + +export const edActionDelete = async (p: PG, item: IContent) => { + 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/indent-hook.ts b/app/web/src/render/ed/panel/tree/node/item/indent-hook.ts index f1f4b6f9..18d0f6bc 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 @@ -7,6 +7,10 @@ export const indentHook = ( local: { tree: null | TreeMethods; render: () => void } ) => { useEffect(() => { + if (p.ui.prevent_indent_hook) { + p.ui.prevent_indent_hook = false; + return; + } const open = JSON.parse(localStorage.getItem("prasi-tree-open") || "{}"); p.ui.tree.open = open; diff --git a/app/web/src/render/ed/panel/tree/node/item/indent.tsx b/app/web/src/render/ed/panel/tree/node/item/indent.tsx index 047a7b89..6c789bbd 100644 --- a/app/web/src/render/ed/panel/tree/node/item/indent.tsx +++ b/app/web/src/render/ed/panel/tree/node/item/indent.tsx @@ -60,7 +60,11 @@ export const EdTreeIndent = ({ )} - {item.type !== "text" && prm.hasChild && ( + {item.type === "item" && prm.hasChild && ( + <>{prm.isOpen ? : } + )} + + {item.type === "section" && ( <>{prm.isOpen ? : } )} 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 3d1b5d0c..c668d4ef 100644 --- a/app/web/src/render/ed/panel/tree/node/render.tsx +++ b/app/web/src/render/ed/panel/tree/node/render.tsx @@ -6,6 +6,7 @@ import { EdTreeCtxMenu } from "./item/ctx-menu"; import { EdTreeIndent } from "./item/indent"; import { EdTreeName } from "./item/name"; import { Loading } from "../../../../../utils/ui/loading"; +import { edActionDelete } from "./item/action/del"; export const nodeRender: NodeRender = (node, prm) => { const p = useGlobal(EDGlobal, "EDITOR"); @@ -26,24 +27,214 @@ export const nodeRender: NodeRender = (node, prm) => { return (
{ + p.ui.prevent_indent_hook = true; + if (e.key === "ArrowLeft") { + if (prm.isOpen) { + prm.onToggle(); + return; + } + const up = + e.currentTarget.parentElement?.parentElement?.parentElement; + if (up) { + const c = up.children[0] as HTMLInputElement; + if (c) c.focus(); + } + return; + } + if (e.key === "ArrowRight") { + if (prm.hasChild) { + if (!prm.isOpen) { + prm.onToggle(); + } + + const target = e.currentTarget; + setTimeout(() => { + let next = target.nextElementSibling; + if (next) { + if (next.children[0].children[0].childElementCount > 1) { + const c = next.children[0].children[0] as HTMLInputElement; + c.focus(); + } + } + }); + } else { + let up = e.currentTarget.parentElement; + while (up) { + if (up.nextElementSibling) { + break; + } + up = up.parentElement; + } + + if (up) { + let next = up.nextElementSibling; + while (next) { + if (next.children[0].classList.contains("has-child")) { + const c = next.children[0] as HTMLInputElement; + if (c) { + c.focus(); + break; + } + } + if (next.nextElementSibling) { + next = next.nextElementSibling; + } else { + (next as HTMLInputElement).focus(); + break; + } + } + } + } + return; + } + + if (e.key === "ArrowDown") { + const child = e.currentTarget.nextElementSibling; + if (child) { + const c = child.children[0]?.children[0] as HTMLInputElement; + if (c) c.focus(); + return; + } + let up = e.currentTarget.parentElement; + while (up) { + if (up.nextElementSibling) { + break; + } + up = up.parentElement; + } + + if (up) { + const next = up.nextElementSibling; + if (next) { + const c = next.children[0] as HTMLInputElement; + if (c) c.focus(); + } + } + return; + } + + if (e.key === "ArrowUp") { + let down = e.currentTarget.parentElement?.previousElementSibling; + if (down) { + if (down.childElementCount === 2) { + while (down) { + if (down.childElementCount === 2) { + down = down.children[1].lastElementChild; + } else { + if (down.nextElementSibling) { + down = down.nextElementSibling; + } else break; + } + } + } + if (down) { + (down.children[0] as HTMLInputElement).focus(); + return; + } + } else { + const up = + e.currentTarget.parentElement?.parentElement?.parentElement; + + if (up) { + if (!up.classList.contains("absolute")) { + const c = up.children[0] as HTMLInputElement; + if (c) { + c.focus(); + return; + } + } + } + } + + p.ui.tree.search_ref?.focus(); + return; + } + + if (e.key === "Enter") { + p.ui.tree.search = ""; + p.ui.prevent_indent_hook = false; + active.item_id = ""; + p.render(); + setTimeout(() => { + active.item_id = item.id; + p.render(); + setTimeout(() => { + const f = document.querySelector( + `.tree-${item.id}` + ) as HTMLInputElement; + if (f) { + f.focus(); + } + }); + }); + return; + } + + if (e.key === "Backspace" || e.key === "Delete") { + let last = ""; + let found = null as HTMLInputElement | null; + p.page.meta[item.id].parent_item.mitem + ?.get("childs") + ?.forEach((e) => { + if (e.get("id") === item.id) { + found = document.querySelector(`.tree-${last}`); + } + if (!found) { + last = e.get("id"); + } + }); + + if (!found) { + last = p.page.meta[item.id].parent_item.mitem?.get("id") || ""; + found = document.querySelector(`.tree-${last}`); + } + + edActionDelete(p, item); + + if (found) { + found.focus(); + } + return; + } + + if (e.key.length === 1) { + p.ui.tree.search_ref?.focus(); + } + }} onContextMenu={(event) => { event.preventDefault(); local.rightClick = event; local.render(); }} + onFocus={(e) => { + active.item_id = item.id; + p.render(); + }} onClick={() => { active.item_id = item.id; p.ui.tree.search = ""; p.render(); }} > +
{local.rightClick && ( { cursor: null as number | null, }); + p.ui.tree.search_ref = local.sref; + useEffect(() => { const input = local.sref; if (input) input.setSelectionRange(local.cursor, local.cursor); @@ -61,6 +63,14 @@ export const EdTreeSearch = () => { local.render(); } }} + onKeyDown={(e) => { + if (e.key === "ArrowDown" || e.key === "Enter") { + const first = document.querySelector( + ".tree-item:first-child" + ) as HTMLInputElement; + if (first) first.focus(); + } + }} />
{(local.focus || local.hover || p.ui.tree.search) && ( @@ -129,9 +139,8 @@ export const doTreeSearch = (p: PG) => {