This commit is contained in:
Rizky 2023-11-27 12:09:31 +07:00
parent ac5117b4ab
commit 3429621f3e
9 changed files with 406 additions and 7 deletions

View File

@ -10,11 +10,12 @@ import { edUndoManager } from "./logic/ed-undo";
import { EdPane } from "./panel/main/pane-resize";
import { EdPopCode } from "./panel/popup/code/code";
import { EdPopCompGroup } from "./panel/popup/comp/comp-group";
import { EdPagePop } from "./panel/popup/page/page-popup";
import { EdPopPage } from "./panel/popup/page/page-popup";
import { EdPopScript } from "./panel/popup/script/pop-script";
import { EdPopSite } from "./panel/popup/site/site-popup";
import { EdScriptInit } from "./panel/script/monaco/init";
import { EdPopApi } from "./panel/popup/api/api-server";
import { EdPopComp } from "./panel/popup/comp/comp-popup";
export const EdBase = () => {
const p = useGlobal(EDGlobal, "EDITOR");
@ -53,8 +54,9 @@ export const EdBase = () => {
<EdPopScript />
<EdPopSite />
<EdPopApi />
<EdPagePop />
<EdPopPage />
<EdPopCompGroup />
<EdPopComp />
<EdScriptInit />
</>
</div>

View File

@ -15,7 +15,7 @@ export const EdMid: FC<{}> = () => {
<EdPagePicker />
</div>
<div className="flex items-stretch">
<div className="flex items-center">
<EdCompPicker />
</div>
<div className="flex items-stretch justify-end"></div>

View File

@ -240,7 +240,9 @@ export const EDGlobal = {
domain?: string;
responsive?: string;
},
comp: null as null | ((comp_id: string) => void | Promise<void>),
comp: {
open: null as null | ((comp_id: string) => void | Promise<void>),
},
comp_group: null as null | {
mouse_event: React.MouseEvent<HTMLElement, MouseEvent>;
on_pick?: (group_id: string) => void | Promise<void>;

View File

@ -7,7 +7,10 @@ export const EdCompPicker = () => {
return (
<TopBtn
onClick={(e) => {}}
onClick={(e) => {
p.ui.popup.comp.open = (comp_id) => {};
p.render();
}}
style="slim"
>
<div

View File

@ -0,0 +1,90 @@
import {
Tree as DNDTree,
DndProvider,
TreeMethods,
getBackendOptions,
} from "@minoru/react-dnd-treeview";
import { useEffect } from "react";
import { HTML5Backend } from "react-dnd-html5-backend";
import { useGlobal, useLocal } from "web-utils";
import { Loading } from "../../../../../utils/ui/loading";
import { Modal } from "../../../../../utils/ui/modal";
import { EDGlobal } from "../../../logic/ed-global";
import { compPicker, reloadCompPicker } from "./comp-reload";
import { CompItem, edPageTreeRender } from "./comp-tree";
export const EdPopComp = () => {
const p = useGlobal(EDGlobal, "EDITOR");
const local = useLocal({
tree: null as TreeMethods | null,
});
const TypedTree = DNDTree<CompItem>;
compPicker.render = local.render;
useEffect(() => {
// waitUntil(() => local.tree).then(() => {});
}, [p.ui.popup.comp.open, compPicker.site_id]);
if (!p.ui.popup.comp.open) return null;
if (p.site.id !== compPicker.site_id) {
compPicker.site_id = p.site.id;
reloadCompPicker(p);
}
return (
<>
<Modal
open
onOpenChange={(open) => {
if (!open) {
p.ui.popup.comp.open = null;
p.render();
}
}}
>
<div
id="comp-picker"
ref={(ref) => {
if (ref) {
compPicker.ref = ref;
}
}}
className={cx("absolute inset-[5%] bg-white flex")}
>
<div className="relative flex flex-1 items-stretch text-[12px] overflow-auto">
{compPicker.status === "loading" && (
<Loading note="listing-comp" backdrop={false} />
)}
{compPicker.ref && (
<DndProvider
backend={HTML5Backend}
options={getBackendOptions({
html5: {
rootElement: compPicker.ref,
},
})}
>
<TypedTree
ref={(ref) => {
if (local.tree !== ref) {
local.tree = ref;
}
}}
tree={compPicker.tree}
rootId={"comp-root"}
onDrop={() => {}}
dragPreviewRender={() => <></>}
canDrag={() => true}
classes={{ root: "flex-1" }}
render={edPageTreeRender}
/>
</DndProvider>
)}
</div>
</div>
</Modal>
</>
);
};

View File

@ -0,0 +1,18 @@
import { NodeModel } from "@minoru/react-dnd-treeview";
import { PG } from "../../../logic/ed-global";
import { CompItem } from "./comp-tree";
export const compPicker = {
site_id: "",
ref: null as any,
tree: [] as NodeModel<CompItem>[],
status: "ready" as "loading" | "ready",
render: () => {},
};
export const reloadCompPicker = async (p: PG) => {
compPicker.status = "loading";
compPicker.status = "ready";
compPicker.render();
};

View File

@ -0,0 +1,284 @@
import { NodeModel, NodeRender } from "@minoru/react-dnd-treeview";
import { FC } from "react";
import { useGlobal, useLocal } from "web-utils";
import { EDGlobal } from "../../../logic/ed-global";
import { compPicker, reloadCompPicker } from "./comp-reload";
export type CompItem = {
id: string;
name: string;
type: "page" | "folder";
};
export const edPageTreeRender: NodeRender<CompItem> = (
node: NodeModel<CompItem>,
{ depth, isOpen, onToggle }
) => {
const p = useGlobal(EDGlobal, "EDITOR");
const local = useLocal({ renaming: node.id === "", rename_to: "" });
const item = node.data;
if (!item) return <></>;
return (
<div
className={cx(
"flex border-b py-[2px] items-center hover:bg-blue-50 cursor-pointer relative",
css`
.btn {
opacity: 0;
}
&:hover .btn {
opacity: 1;
}
`,
item.id === p.page.cur.id && `bg-blue-50`
)}
onClick={() => {
if (item.type === "folder") {
onToggle();
} else if (p.ui.popup.page.open) {
p.ui.popup.page.open(item.id);
}
}}
>
<div className="flex w-[40%] items-center relative ">
{item.id === p.page.cur.id && (
<div className="absolute left-0 top-0 bottom-0 bg-blue-500 w-1"></div>
)}
<div
className={cx(
"h-[13px] pl-1",
css`
width: ${depth * 13}px;
`
)}
></div>
{item.type === "folder" && (
<>
{isOpen && <FolderOpen />}
{!isOpen && <FolderClose />}
</>
)}
<div className="flex flex-1 pl-1">
{local.renaming ? (
<input
value={local.rename_to}
autoFocus
onBlur={async () => {
local.renaming = false;
item.name = local.rename_to;
if (item.id === "") {
if (item.name) {
db.page_folder.create({
data: { name: local.rename_to, id_site: p.site.id },
});
}
await reloadCompPicker(p);
} else {
db.page_folder.update({
where: { id: item.id },
data: { name: local.rename_to },
});
}
local.render();
}}
className="border px-1 bg-white flex-1 outline-none mr-1 border-blue-500 "
onChange={(e) => {
local.rename_to = e.currentTarget.value;
local.render();
}}
onKeyDown={(e) => {
if (e.key === "Enter") e.currentTarget.blur();
if (e.key === "Escape") {
local.rename_to = item.name;
local.render();
e.currentTarget.blur();
}
}}
/>
) : (
<Name name={item.name} />
)}
</div>
{!local.renaming && (
<div className="flex pr-2 items-stretch h-[18px] space-x-[2px] text-[10px]">
{item.type === "folder" && (
<>
<div
className="btn transition-all bg-white flex items-center border px-1 hover:border-blue-300 hover:bg-blue-100"
onClick={(e) => {
e.stopPropagation();
compPicker.tree.push({
id: "",
parent: item.id,
text: "",
data: {
id: "",
name: "",
type: "folder",
},
});
p.render();
}}
>
+ Folder
</div>
<div
className="btn transition-all bg-white flex items-center border px-1 hover:border-blue-300 hover:bg-blue-100"
onClick={(e) => {
e.stopPropagation();
p.ui.popup.page.form = {
id_site: p.site.id,
id_folder: item.id === "root" ? null : item.id,
};
p.render();
}}
>
+ Page
</div>
</>
)}
{item.id !== "root" && (
<>
<div
className="btn transition-all bg-white flex items-center border px-1 hover:border-blue-300 hover:bg-blue-100"
onClick={(e) => {
e.stopPropagation();
if (item.type === "folder") {
local.rename_to = item.name;
local.renaming = true;
local.render();
} else {
p.ui.popup.page.form = item;
p.render();
}
}}
>
<EditIcon />
</div>
<div
className="btn transition-all bg-white flex items-center border px-1 hover:border-red-300 hover:bg-red-100"
onClick={async (e) => {
e.stopPropagation();
if (confirm("Deletting cannot be undone. Are you sure ?")) {
if (item.type === "folder") {
await db.page.updateMany({
where: { id_folder: node.id as string },
data: {
id_folder:
node.parent === "root"
? null
: (node.parent as string),
},
});
await db.page_folder.update({
where: { id: node.id as string },
data: {
is_deleted: true,
},
});
} else {
await db.page.update({
where: { id: node.id as string },
data: {
is_deleted: true,
},
});
}
await reloadCompPicker(p);
}
}}
>
<DeleteIcon />
</div>
</>
)}
</div>
)}
</div>
</div>
);
};
const Name: FC<{ name: string }> = ({ name }) => {
if (name.startsWith("layout::")) {
return (
<div className="flex items-center">
<div className="border border-green-600 text-green-600 mr-1 font-mono text-[8px] px-1 bg-white ">
LAYOUT
</div>
<div>{name.substring("layout::".length)}</div>
</div>
);
}
return <div>{name}</div>;
};
const DeleteIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="12"
height="12"
fill="none"
viewBox="0 0 15 15"
>
<path
fill="currentColor"
fillRule="evenodd"
d="M5.5 1a.5.5 0 000 1h4a.5.5 0 000-1h-4zM3 3.5a.5.5 0 01.5-.5h8a.5.5 0 010 1H11v8a1 1 0 01-1 1H5a1 1 0 01-1-1V4h-.5a.5.5 0 01-.5-.5zM5 4h5v8H5V4z"
clipRule="evenodd"
></path>
</svg>
);
const FolderClose = () => (
<svg
fill="currentColor"
viewBox="0 0 20 20"
strokeWidth={1}
width={13}
height={13}
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path d="M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" />
</svg>
);
const EditIcon = () => (
<svg
width="12"
height="12"
viewBox="0 0 15 15"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.8536 1.14645C11.6583 0.951184 11.3417 0.951184 11.1465 1.14645L3.71455 8.57836C3.62459 8.66832 3.55263 8.77461 3.50251 8.89155L2.04044 12.303C1.9599 12.491 2.00189 12.709 2.14646 12.8536C2.29103 12.9981 2.50905 13.0401 2.69697 12.9596L6.10847 11.4975C6.2254 11.4474 6.3317 11.3754 6.42166 11.2855L13.8536 3.85355C14.0488 3.65829 14.0488 3.34171 13.8536 3.14645L11.8536 1.14645ZM4.42166 9.28547L11.5 2.20711L12.7929 3.5L5.71455 10.5784L4.21924 11.2192L3.78081 10.7808L4.42166 9.28547Z"
fill="currentColor"
fillRule="evenodd"
clipRule="evenodd"
></path>
</svg>
);
const FolderOpen = () => (
<svg
fill="currentColor"
strokeWidth={1}
width={13}
height={13}
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
clipRule="evenodd"
fillRule="evenodd"
d="M2 6a2 2 0 012-2h4l2 2h4a2 2 0 012 2v1H8a3 3 0 00-3 3v1.5a1.5 1.5 0 01-3 0V6z"
/>
<path d="M6 12a2 2 0 012-2h8a2 2 0 012 2v2a2 2 0 01-2 2H2h2a2 2 0 002-2v-2z" />
</svg>
);

View File

@ -14,7 +14,7 @@ import { pagePicker, reloadPagePicker } from "./page-reload";
import { PageItem, edPageTreeRender } from "./page-tree";
import { EdFormPage } from "./page-form";
export const EdPagePop = () => {
export const EdPopPage = () => {
const p = useGlobal(EDGlobal, "EDITOR");
const local = useLocal({
tree: null as TreeMethods | null,

View File

@ -3,7 +3,7 @@ import { PG } from "../../../../../logic/ed-global";
export const edActionAttach = (p: PG, item: IItem) => {
const pick = () => {
p.ui.popup.comp = (comp_id) => {};
p.ui.popup.comp.open = (comp_id) => {};
p.render();
};
pick();