checkpoint
This commit is contained in:
parent
0a789d9b02
commit
030abd91b0
|
|
@ -5,28 +5,36 @@ export const SyncActionDefinition = {
|
||||||
"load": "2"
|
"load": "2"
|
||||||
},
|
},
|
||||||
"comp": {
|
"comp": {
|
||||||
"list": "3",
|
"undo": "3",
|
||||||
"group": "4",
|
"redo": "4",
|
||||||
"load": "5"
|
"list": "5",
|
||||||
},
|
"group": "6",
|
||||||
"page": {
|
|
||||||
"list": "6",
|
|
||||||
"load": "7"
|
"load": "7"
|
||||||
},
|
},
|
||||||
|
"page": {
|
||||||
|
"undo": "8",
|
||||||
|
"redo": "9",
|
||||||
|
"list": "10",
|
||||||
|
"load": "11"
|
||||||
|
},
|
||||||
"yjs": {
|
"yjs": {
|
||||||
"sv_local": "8",
|
"sv_local": "12",
|
||||||
"diff_local": "9"
|
"diff_local": "13"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
export const SyncActionPaths = {
|
export const SyncActionPaths = {
|
||||||
"0": "site.list",
|
"0": "site.list",
|
||||||
"1": "site.group",
|
"1": "site.group",
|
||||||
"2": "site.load",
|
"2": "site.load",
|
||||||
"3": "comp.list",
|
"3": "comp.undo",
|
||||||
"4": "comp.group",
|
"4": "comp.redo",
|
||||||
"5": "comp.load",
|
"5": "comp.list",
|
||||||
"6": "page.list",
|
"6": "comp.group",
|
||||||
"7": "page.load",
|
"7": "comp.load",
|
||||||
"8": "yjs.sv_local",
|
"8": "page.undo",
|
||||||
"9": "yjs.diff_local"
|
"9": "page.redo",
|
||||||
|
"10": "page.list",
|
||||||
|
"11": "page.load",
|
||||||
|
"12": "yjs.sv_local",
|
||||||
|
"13": "yjs.diff_local"
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,15 @@ export const SyncActions = {
|
||||||
load: async (id: string) => ({}) as ESite | void,
|
load: async (id: string) => ({}) as ESite | void,
|
||||||
},
|
},
|
||||||
comp: {
|
comp: {
|
||||||
|
undo: async (id_comp: string) => {},
|
||||||
|
redo: async (id_comp: string) => {},
|
||||||
list: () => ({}) as Record<string, Exclude<component, "content_tree">>,
|
list: () => ({}) as Record<string, Exclude<component, "content_tree">>,
|
||||||
group: () => ({}) as Record<string, string[]>,
|
group: () => ({}) as Record<string, string[]>,
|
||||||
load: async (id: string) => ({}) as EComp | void,
|
load: async (id: string) => ({}) as EComp | void,
|
||||||
},
|
},
|
||||||
page: {
|
page: {
|
||||||
|
undo: async (id_page: string) => {},
|
||||||
|
redo: async (id_page: string) => {},
|
||||||
list: (id_site: string) =>
|
list: (id_site: string) =>
|
||||||
({}) as Record<string, Exclude<page, "content_tree">>,
|
({}) as Record<string, Exclude<page, "content_tree">>,
|
||||||
load: async (id: string) => ({}) as EPage | void,
|
load: async (id: string) => ({}) as EPage | void,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { ActionCtx } from "../type";
|
||||||
|
|
||||||
|
export const comp_redo = async function (this: ActionCtx, id: string) {};
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { ActionCtx } from "../type";
|
||||||
|
|
||||||
|
export const comp_undo = async function (this: ActionCtx, id: string) {};
|
||||||
|
|
@ -2,5 +2,9 @@ export * from "./site_load";
|
||||||
export * from "./site_group";
|
export * from "./site_group";
|
||||||
export * from "./page_load";
|
export * from "./page_load";
|
||||||
export * from "./comp_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_sv_local";
|
||||||
export * from "./yjs_diff_local";
|
export * from "./yjs_diff_local";
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -5,10 +5,13 @@ import { edInit } from "./logic/ed-init";
|
||||||
import { edRoute } from "./logic/ed-route";
|
import { edRoute } from "./logic/ed-route";
|
||||||
import { EdMain } from "./panel/main/main";
|
import { EdMain } from "./panel/main/main";
|
||||||
import { EdTree } from "./panel/tree/tree";
|
import { EdTree } from "./panel/tree/tree";
|
||||||
|
import { edUndoManager } from "./logic/ed-undo";
|
||||||
|
|
||||||
export const EdBase = () => {
|
export const EdBase = () => {
|
||||||
const p = useGlobal(EDGlobal, "EDITOR");
|
const p = useGlobal(EDGlobal, "EDITOR");
|
||||||
|
|
||||||
|
edUndoManager(p);
|
||||||
|
|
||||||
if (p.status === "init") {
|
if (p.status === "init") {
|
||||||
edInit(p);
|
edInit(p);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,11 +68,16 @@ export const EDGlobal = {
|
||||||
item: null as null | IItem,
|
item: null as null | IItem,
|
||||||
list: {} as Record<string, { cur: EComp; doc: DComp }>,
|
list: {} as Record<string, { cur: EComp; doc: DComp }>,
|
||||||
},
|
},
|
||||||
|
|
||||||
ui: {
|
ui: {
|
||||||
|
select: {
|
||||||
|
id: "",
|
||||||
|
},
|
||||||
tree: {
|
tree: {
|
||||||
open: {} as Record<string, string[]>,
|
open: {} as Record<string, string[]>,
|
||||||
},
|
},
|
||||||
|
popup: {
|
||||||
|
comp: null as null | ((comp_id: string) => void | Promise<void>),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
|
|
@ -14,7 +14,7 @@ import { MSection } from "../../../../utils/types/section";
|
||||||
import { EdMeta, PG } from "../ed-global";
|
import { EdMeta, PG } from "../ed-global";
|
||||||
import { decompress } from "wasm-gzip";
|
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;
|
const doc = p.page.doc;
|
||||||
if (!doc) return;
|
if (!doc) return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { IContent } from "../../../../../../../utils/types/general";
|
||||||
|
import { PG } from "../../../../../logic/ed-global";
|
||||||
|
|
||||||
|
export const edActionClone = (p: PG, item: IContent) => {};
|
||||||
|
|
@ -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);
|
||||||
|
};
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { IItem } from "../../../../../../../utils/types/item";
|
||||||
|
import { PG } from "../../../../../logic/ed-global";
|
||||||
|
|
||||||
|
export const edActionDetach = (p: PG, item: IItem) => {};
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { IContent } from "../../../../../../../utils/types/general";
|
||||||
|
import { PG } from "../../../../../logic/ed-global";
|
||||||
|
|
||||||
|
export const edActionHide = (p: PG, item: IContent) => {};
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { IItem } from "../../../../../../../utils/types/item";
|
||||||
|
import { PG } from "../../../../../logic/ed-global";
|
||||||
|
|
||||||
|
export const edActionNewComp = (p: PG, item: IItem) => {};
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { IContent } from "../../../../../../../utils/types/general";
|
||||||
|
import { PG } from "../../../../../logic/ed-global";
|
||||||
|
|
||||||
|
export const edActionPaste = (p: PG, item: IContent) => {};
|
||||||
|
|
@ -1,16 +1,93 @@
|
||||||
import {
|
import { NodeModel, RenderParams } from "@minoru/react-dnd-treeview";
|
||||||
NodeModel,
|
import { useGlobal, useLocal } from "web-utils";
|
||||||
NodeRender,
|
import { IItem } from "../../../../../../utils/types/item";
|
||||||
RenderParams,
|
import { FNComponent } from "../../../../../../utils/types/meta-fn";
|
||||||
} from "@minoru/react-dnd-treeview";
|
import { Menu, MenuItem } from "../../../../../../utils/ui/context-menu";
|
||||||
import { EdMeta } from "../../../../logic/ed-global";
|
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 = ({
|
export const EdTreeCtxMenu = ({
|
||||||
node,
|
node,
|
||||||
prm,
|
prm,
|
||||||
|
event,
|
||||||
|
onClose,
|
||||||
}: {
|
}: {
|
||||||
|
event: React.MouseEvent<HTMLDivElement, MouseEvent>;
|
||||||
node: NodeModel<EdMeta>;
|
node: NodeModel<EdMeta>;
|
||||||
prm: RenderParams;
|
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 (
|
||||||
|
<Menu mouseEvent={event} onClose={onClose}>
|
||||||
|
<MenuItem label={<div className="text-gray-400">Unavailable</div>} />
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu mouseEvent={event} onClose={onClose}>
|
||||||
|
{type === "item" && !isActiveComponent && !item.component?.id && (
|
||||||
|
<MenuItem
|
||||||
|
label="Attach Component"
|
||||||
|
onClick={() => edActionAttach(p, item)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{type === "item" && comp?.id && !isActiveComponent && (
|
||||||
|
<MenuItem
|
||||||
|
label="Detach Component"
|
||||||
|
onClick={() => edActionDetach(p, item)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{type === "item" && comp?.id && (
|
||||||
|
<MenuItem
|
||||||
|
label="Create Component"
|
||||||
|
onClick={() => edActionNewComp(p, item)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!item.hidden && (
|
||||||
|
<MenuItem label="Hide" onClick={() => edActionHide(p, item)} />
|
||||||
|
)}
|
||||||
|
<MenuItem label="Clone" onClick={() => edActionClone(p, item)} />
|
||||||
|
<MenuItem label="Cut" onClick={() => edActionCut(p, item)} />
|
||||||
|
<MenuItem label="Copy" onClick={() => edActionCopy(p, item)} />
|
||||||
|
{local.allowCopy && local.allowPaste && (
|
||||||
|
<MenuItem label="Paste" onClick={() => edActionPaste(p, item)} />
|
||||||
|
)}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,19 +5,39 @@ 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 { indentHook } from "./item/indent-hook";
|
import { indentHook } from "./item/indent-hook";
|
||||||
|
import { useLocal } from "web-utils";
|
||||||
|
|
||||||
export const nodeRender: NodeRender<EdMeta> = (node, prm) => {
|
export const nodeRender: NodeRender<EdMeta> = (node, prm) => {
|
||||||
|
const local = useLocal({
|
||||||
|
rightClick: null as null | React.MouseEvent<HTMLDivElement, MouseEvent>,
|
||||||
|
});
|
||||||
if (!node || !node.data) return <></>;
|
if (!node || !node.data) return <></>;
|
||||||
const item = node.data?.item;
|
const item = node.data?.item;
|
||||||
const isComponent = item.type === "item" && item.component?.id;
|
const isComponent = item.type === "item" && item.component?.id;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
"relative border-b flex items-stretch hover:bg-blue-50 min-h-[26px]",
|
"relative border-b flex items-stretch hover:bg-blue-50 min-h-[26px]",
|
||||||
isComponent && `bg-purple-50`
|
isComponent && `bg-purple-50`
|
||||||
)}
|
)}
|
||||||
|
onContextMenu={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
local.rightClick = event;
|
||||||
|
local.render();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<EdTreeCtxMenu node={node} prm={prm} />
|
{local.rightClick && (
|
||||||
|
<EdTreeCtxMenu
|
||||||
|
node={node}
|
||||||
|
prm={prm}
|
||||||
|
event={local.rightClick}
|
||||||
|
onClose={() => {
|
||||||
|
local.rightClick = null;
|
||||||
|
local.render();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<EdTreeIndent node={node} prm={prm} />
|
<EdTreeIndent node={node} prm={prm} />
|
||||||
<EdTreeName node={node} prm={prm} />
|
<EdTreeName node={node} prm={prm} />
|
||||||
<EdTreeAction node={node} prm={prm} />
|
<EdTreeAction node={node} prm={prm} />
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun run --silent --watch ./pkgs/core/index.ts dev",
|
"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",
|
"build": "bun run --silent ./pkgs/core/build.ts",
|
||||||
"db-pull": "bun run ./pkgs/core/db-pull.ts",
|
"db-pull": "bun run ./pkgs/core/db-pull.ts",
|
||||||
"parcel": "bun clean && bun run ./pkgs/core/parcel.ts",
|
"parcel": "bun clean && bun run ./pkgs/core/parcel.ts",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue