checkpoint

This commit is contained in:
Rizky 2023-10-23 08:55:00 +07:00
parent 0a789d9b02
commit 030abd91b0
23 changed files with 313 additions and 28 deletions

View File

@ -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"
};

View File

@ -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<string, Exclude<component, "content_tree">>,
group: () => ({}) as Record<string, string[]>,
load: async (id: string) => ({}) as EComp | void,
},
page: {
undo: async (id_page: string) => {},
redo: async (id_page: string) => {},
list: (id_site: string) =>
({}) as Record<string, Exclude<page, "content_tree">>,
load: async (id: string) => ({}) as EPage | void,

View File

@ -0,0 +1,3 @@
import { ActionCtx } from "../type";
export const comp_redo = async function (this: ActionCtx, id: string) {};

View File

@ -0,0 +1,3 @@
import { ActionCtx } from "../type";
export const comp_undo = async function (this: ActionCtx, id: string) {};

View File

@ -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";

View File

@ -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) {

View File

@ -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();
}
};

View File

@ -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();
}
};

View File

@ -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);
}

View File

@ -68,11 +68,16 @@ export const EDGlobal = {
item: null as null | IItem,
list: {} as Record<string, { cur: EComp; doc: DComp }>,
},
ui: {
select: {
id: "",
},
tree: {
open: {} as Record<string, string[]>,
},
popup: {
comp: null as null | ((comp_id: string) => void | Promise<void>),
},
},
};

View File

@ -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);
};
}, []);
};

View File

@ -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;

View File

@ -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();
};

View File

@ -0,0 +1,4 @@
import { IContent } from "../../../../../../../utils/types/general";
import { PG } from "../../../../../logic/ed-global";
export const edActionClone = (p: PG, item: IContent) => {};

View File

@ -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);
};

View File

@ -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);
}
};

View File

@ -0,0 +1,4 @@
import { IItem } from "../../../../../../../utils/types/item";
import { PG } from "../../../../../logic/ed-global";
export const edActionDetach = (p: PG, item: IItem) => {};

View File

@ -0,0 +1,4 @@
import { IContent } from "../../../../../../../utils/types/general";
import { PG } from "../../../../../logic/ed-global";
export const edActionHide = (p: PG, item: IContent) => {};

View File

@ -0,0 +1,4 @@
import { IItem } from "../../../../../../../utils/types/item";
import { PG } from "../../../../../logic/ed-global";
export const edActionNewComp = (p: PG, item: IItem) => {};

View File

@ -0,0 +1,4 @@
import { IContent } from "../../../../../../../utils/types/general";
import { PG } from "../../../../../logic/ed-global";
export const edActionPaste = (p: PG, item: IContent) => {};

View File

@ -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<HTMLDivElement, MouseEvent>;
node: NodeModel<EdMeta>;
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>
);
};

View File

@ -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<EdMeta> = (node, prm) => {
const local = useLocal({
rightClick: null as null | React.MouseEvent<HTMLDivElement, MouseEvent>,
});
if (!node || !node.data) return <></>;
const item = node.data?.item;
const isComponent = item.type === "item" && item.component?.id;
return (
<div
className={cx(
"relative border-b flex items-stretch hover:bg-blue-50 min-h-[26px]",
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} />
<EdTreeName node={node} prm={prm} />
<EdTreeAction node={node} prm={prm} />

View File

@ -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/*"
],