tree keyboard improvement
This commit is contained in:
parent
2270c7c071
commit
0279eb30be
|
|
@ -87,10 +87,12 @@ export const EDGlobal = {
|
||||||
group: {} as Record<string, Awaited<ReturnType<SAction["comp"]["group"]>>>,
|
group: {} as Record<string, Awaited<ReturnType<SAction["comp"]["group"]>>>,
|
||||||
},
|
},
|
||||||
ui: {
|
ui: {
|
||||||
|
prevent_indent_hook: false,
|
||||||
syncing: false,
|
syncing: false,
|
||||||
tree: {
|
tree: {
|
||||||
item_loading: [] as string[],
|
item_loading: [] as string[],
|
||||||
search: "",
|
search: "",
|
||||||
|
search_ref: null as null | HTMLInputElement,
|
||||||
search_mode: {
|
search_mode: {
|
||||||
Name: true,
|
Name: true,
|
||||||
JS: false,
|
JS: false,
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ export const edRoute = async (p: PG) => {
|
||||||
doc.on("update", async (bin: Uint8Array, origin: any) => {
|
doc.on("update", async (bin: Uint8Array, origin: any) => {
|
||||||
if (origin === "sv_remote" || origin === "local") return;
|
if (origin === "sv_remote" || origin === "local") return;
|
||||||
|
|
||||||
console.log(origin);
|
|
||||||
const res = await p.sync.yjs.sv_local(
|
const res = await p.sync.yjs.sv_local(
|
||||||
"page",
|
"page",
|
||||||
p.page.cur.id,
|
p.page.cur.id,
|
||||||
|
|
|
||||||
|
|
@ -147,8 +147,10 @@ const walkMap = (
|
||||||
const fcomp = parent_comp.mitem.get("component");
|
const fcomp = parent_comp.mitem.get("component");
|
||||||
if (fcomp) {
|
if (fcomp) {
|
||||||
const ref_ids = fcomp.get("ref_ids");
|
const ref_ids = fcomp.get("ref_ids");
|
||||||
|
|
||||||
if (ref_ids) {
|
if (ref_ids) {
|
||||||
let ref_id = ref_ids.get(id);
|
let ref_id = ref_ids.get(id);
|
||||||
|
|
||||||
if (!ref_id) {
|
if (!ref_id) {
|
||||||
ref_id = createId();
|
ref_id = createId();
|
||||||
ref_ids.set(id, ref_id);
|
ref_ids.set(id, ref_id);
|
||||||
|
|
@ -157,7 +159,9 @@ const walkMap = (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mapItem(mitem, item);
|
mapItem(mitem, item);
|
||||||
|
|
||||||
if (override_id) {
|
if (override_id) {
|
||||||
item.id = override_id;
|
item.id = override_id;
|
||||||
}
|
}
|
||||||
|
|
@ -190,8 +194,9 @@ const walkMap = (
|
||||||
mitem_comp.set("ref_ids", new Y.Map() as any);
|
mitem_comp.set("ref_ids", new Y.Map() as any);
|
||||||
ref_ids = {};
|
ref_ids = {};
|
||||||
}
|
}
|
||||||
|
const original_id = item.id;
|
||||||
mapItem(mcomp, item);
|
mapItem(mcomp, item);
|
||||||
|
item.id = original_id;
|
||||||
|
|
||||||
const meta: EdMeta = {
|
const meta: EdMeta = {
|
||||||
item,
|
item,
|
||||||
|
|
@ -200,6 +205,7 @@ const walkMap = (
|
||||||
parent_comp,
|
parent_comp,
|
||||||
};
|
};
|
||||||
p.page.meta[item.id] = meta;
|
p.page.meta[item.id] = meta;
|
||||||
|
|
||||||
if (!skip_tree) {
|
if (!skip_tree) {
|
||||||
p.page.tree.push({
|
p.page.tree.push({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { syncronize } from "y-pojo";
|
import { syncronize } from "y-pojo";
|
||||||
import { IContent, MContent } from "../../../../../../../utils/types/general";
|
import { IContent, MContent } from "../../../../../../../utils/types/general";
|
||||||
|
import { IItem } from "../../../../../../../utils/types/item";
|
||||||
import { fillID } from "../../../../../../editor/tools/fill-id";
|
import { fillID } from "../../../../../../editor/tools/fill-id";
|
||||||
import { PG } from "../../../../../logic/ed-global";
|
import { PG } from "../../../../../logic/ed-global";
|
||||||
import { treeRebuild } from "../../../../../logic/tree/build";
|
import { treeRebuild } from "../../../../../logic/tree/build";
|
||||||
|
|
@ -10,9 +11,11 @@ export const edActionClone = (p: PG, item: IContent) => {
|
||||||
mitem.doc?.transact(() => {
|
mitem.doc?.transact(() => {
|
||||||
mitem.parent.forEach((e: MContent, idx) => {
|
mitem.parent.forEach((e: MContent, idx) => {
|
||||||
if (e.get("id") === mitem.get("id")) {
|
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();
|
const map = new Y.Map();
|
||||||
syncronize(map, fillID(json));
|
syncronize(map, json);
|
||||||
mitem.parent.insert(idx, [map]);
|
mitem.parent.insert(idx, [map]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -7,6 +7,10 @@ export const indentHook = (
|
||||||
local: { tree: null | TreeMethods; render: () => void }
|
local: { tree: null | TreeMethods; render: () => void }
|
||||||
) => {
|
) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (p.ui.prevent_indent_hook) {
|
||||||
|
p.ui.prevent_indent_hook = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
const open = JSON.parse(localStorage.getItem("prasi-tree-open") || "{}");
|
const open = JSON.parse(localStorage.getItem("prasi-tree-open") || "{}");
|
||||||
p.ui.tree.open = open;
|
p.ui.tree.open = open;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,11 @@ export const EdTreeIndent = ({
|
||||||
<ComponentIcon />
|
<ComponentIcon />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{item.type !== "text" && prm.hasChild && (
|
{item.type === "item" && prm.hasChild && (
|
||||||
|
<>{prm.isOpen ? <ChevronDown /> : <ChevronRight />}</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{item.type === "section" && (
|
||||||
<>{prm.isOpen ? <ChevronDown /> : <ChevronRight />}</>
|
<>{prm.isOpen ? <ChevronDown /> : <ChevronRight />}</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { EdTreeCtxMenu } from "./item/ctx-menu";
|
||||||
import { EdTreeIndent } from "./item/indent";
|
import { EdTreeIndent } from "./item/indent";
|
||||||
import { EdTreeName } from "./item/name";
|
import { EdTreeName } from "./item/name";
|
||||||
import { Loading } from "../../../../../utils/ui/loading";
|
import { Loading } from "../../../../../utils/ui/loading";
|
||||||
|
import { edActionDelete } from "./item/action/del";
|
||||||
|
|
||||||
export const nodeRender: NodeRender<EdMeta> = (node, prm) => {
|
export const nodeRender: NodeRender<EdMeta> = (node, prm) => {
|
||||||
const p = useGlobal(EDGlobal, "EDITOR");
|
const p = useGlobal(EDGlobal, "EDITOR");
|
||||||
|
|
@ -26,24 +27,214 @@ export const nodeRender: NodeRender<EdMeta> = (node, prm) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
tabIndex={0}
|
||||||
className={cx(
|
className={cx(
|
||||||
item.id,
|
"tree-item",
|
||||||
"relative border-b flex items-stretch min-h-[26px]",
|
`tree-${item.id}`,
|
||||||
|
"relative border-b flex items-stretch outline-none min-h-[26px]",
|
||||||
|
prm.hasChild && "has-child",
|
||||||
|
css`
|
||||||
|
&:focus {
|
||||||
|
.focus {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
active.item_id === item.id
|
active.item_id === item.id
|
||||||
? ["bg-blue-100"]
|
? ["bg-blue-100"]
|
||||||
: ["hover:bg-blue-50", isComponent && `bg-purple-50`]
|
: ["hover:bg-blue-50", isComponent && `bg-purple-50`]
|
||||||
)}
|
)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
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) => {
|
onContextMenu={(event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
local.rightClick = event;
|
local.rightClick = event;
|
||||||
local.render();
|
local.render();
|
||||||
}}
|
}}
|
||||||
|
onFocus={(e) => {
|
||||||
|
active.item_id = item.id;
|
||||||
|
p.render();
|
||||||
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
active.item_id = item.id;
|
active.item_id = item.id;
|
||||||
p.ui.tree.search = "";
|
p.ui.tree.search = "";
|
||||||
p.render();
|
p.render();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<div className="focus hidden absolute left-0 bottom-0 top-0 w-[4px] bg-blue-500"></div>
|
||||||
{local.rightClick && (
|
{local.rightClick && (
|
||||||
<EdTreeCtxMenu
|
<EdTreeCtxMenu
|
||||||
node={node}
|
node={node}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useGlobal, useLocal } from "web-utils";
|
import { useGlobal, useLocal } from "web-utils";
|
||||||
import { EDGlobal, EdMeta, PG } from "../../logic/ed-global";
|
import { EDGlobal, EdMeta, PG, active } from "../../logic/ed-global";
|
||||||
import { NodeModel } from "@minoru/react-dnd-treeview";
|
import { NodeModel } from "@minoru/react-dnd-treeview";
|
||||||
|
|
||||||
import uFuzzy from "@leeoniya/ufuzzy";
|
import uFuzzy from "@leeoniya/ufuzzy";
|
||||||
|
|
@ -15,6 +15,8 @@ export const EdTreeSearch = () => {
|
||||||
cursor: null as number | null,
|
cursor: null as number | null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
p.ui.tree.search_ref = local.sref;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const input = local.sref;
|
const input = local.sref;
|
||||||
if (input) input.setSelectionRange(local.cursor, local.cursor);
|
if (input) input.setSelectionRange(local.cursor, local.cursor);
|
||||||
|
|
@ -61,6 +63,14 @@ export const EdTreeSearch = () => {
|
||||||
local.render();
|
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();
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{(local.focus || local.hover || p.ui.tree.search) && (
|
{(local.focus || local.hover || p.ui.tree.search) && (
|
||||||
|
|
@ -129,9 +139,8 @@ export const doTreeSearch = (p: PG) => {
|
||||||
<div
|
<div
|
||||||
className={css`
|
className={css`
|
||||||
b {
|
b {
|
||||||
font-weight: bold;
|
background: #4c71f6;
|
||||||
color: #df9100;
|
color: white;
|
||||||
text-decoration: underline;
|
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
dangerouslySetInnerHTML={{ __html: text }}
|
dangerouslySetInnerHTML={{ __html: text }}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue