This commit is contained in:
Rizky 2023-11-25 13:56:53 +07:00
parent fd0b71bc8f
commit 812bb40b31
9 changed files with 222 additions and 38 deletions

View File

@ -27,7 +27,7 @@ export const EdBase = () => {
edRoute(p); edRoute(p);
if (p.status === "loading") { if (p.status === "loading") {
return <Loading note={`base-${p.status}-page`} />; return <Loading note={`page-${p.status}`} />;
} }
if (p.status === "site-not-found" || p.status === "page-not-found") { if (p.status === "site-not-found" || p.status === "page-not-found") {
return ( return (

View File

@ -1,6 +1,7 @@
import { MultiBackend, getBackendOptions } from "@minoru/react-dnd-treeview"; import { getBackendOptions } from "@minoru/react-dnd-treeview";
import { DndProvider } from "react-dnd"; import { DndProvider } from "react-dnd";
import { useGlobal } from "web-utils"; import { HTML5Backend } from "react-dnd-html5-backend";
import { useGlobal, useLocal } from "web-utils";
import { EDGlobal } from "./logic/ed-global"; import { EDGlobal } from "./logic/ed-global";
import { EdApi } from "./panel/header/left/api"; import { EdApi } from "./panel/header/left/api";
import { EdSiteJS } from "./panel/header/left/js"; import { EdSiteJS } from "./panel/header/left/js";
@ -10,6 +11,7 @@ import { EdTreeSearch } from "./panel/tree/search";
export const EdLeft = () => { export const EdLeft = () => {
const p = useGlobal(EDGlobal, "EDITOR"); const p = useGlobal(EDGlobal, "EDITOR");
const local = useLocal({ tree: null as any });
return ( return (
<div <div
className={cx( className={cx(
@ -33,11 +35,25 @@ export const EdLeft = () => {
</div> </div>
<EdTreeSearch /> <EdTreeSearch />
<div className="tree-body flex relative flex-1 overflow-y-auto overflow-x-hidden"> <div
className="tree-body flex relative flex-1 overflow-y-auto overflow-x-hidden"
ref={(ref) => {
if (ref) local.tree = ref;
}}
>
<div className="absolute inset-0 flex flex-col"> <div className="absolute inset-0 flex flex-col">
<DndProvider backend={MultiBackend} options={getBackendOptions()}> {local.tree && (
<EdTreeBody /> <DndProvider
</DndProvider> backend={HTML5Backend}
options={getBackendOptions({
html5: {
rootElement: local.tree,
},
})}
>
<EdTreeBody />
</DndProvider>
)}
</div> </div>
</div> </div>
</div> </div>

View File

@ -9,6 +9,7 @@ export const EdPagePicker = () => {
<TopBtn <TopBtn
onClick={(e) => { onClick={(e) => {
p.ui.popup.page.open = (page_id) => { p.ui.popup.page.open = (page_id) => {
p.ui.popup.page.open = null;
navigate(`/ed/${p.site.id}/${page_id}`); navigate(`/ed/${p.site.id}/${page_id}`);
}; };
p.render(); p.render();

View File

@ -1,6 +1,12 @@
import { Tree as DNDTree, DndProvider, MultiBackend, getBackendOptions } from "@minoru/react-dnd-treeview"; import {
Tree as DNDTree,
DndProvider,
TreeMethods,
getBackendOptions,
} from "@minoru/react-dnd-treeview";
import { useEffect } from "react"; import { useEffect } from "react";
import { useGlobal, useLocal } from "web-utils"; import { HTML5Backend } from "react-dnd-html5-backend";
import { useGlobal, useLocal, waitUntil } from "web-utils";
import { Loading } from "../../../../../utils/ui/loading"; import { Loading } from "../../../../../utils/ui/loading";
import { Modal } from "../../../../../utils/ui/modal"; import { Modal } from "../../../../../utils/ui/modal";
import { EDGlobal } from "../../../logic/ed-global"; import { EDGlobal } from "../../../logic/ed-global";
@ -9,25 +15,38 @@ import { PageItem, edPageTreeRender } from "./page-tree";
export const EdPagePop = () => { export const EdPagePop = () => {
const p = useGlobal(EDGlobal, "EDITOR"); const p = useGlobal(EDGlobal, "EDITOR");
const local = useLocal({}); const local = useLocal({
tree: null as TreeMethods | null,
});
const TypedTree = DNDTree<PageItem>; const TypedTree = DNDTree<PageItem>;
pagePicker.render = local.render; pagePicker.render = local.render;
useEffect(() => { useEffect(() => {
p.ui.popup.page.open = () => {}; waitUntil(() => local.tree).then(() => {
if (local.tree) {
(async () => { const parents: string[] = [];
if (pagePicker.status === "ready") { let cur = pagePicker.tree.find((e) => e.id === p.page.cur.id);
reloadPagePicker(p); while (cur) {
if (typeof cur.id === "string") {
const parent_id = cur.parent;
parents.push(cur.id);
cur = pagePicker.tree.find((e) => e.id === parent_id);
}
}
local.tree.open(parents);
} }
})(); });
}, [p.site.id]); }, [p.ui.popup.page.open]);
if (!p.ui.popup.page.open) return null; if (!p.ui.popup.page.open) return null;
if (p.site.id !== pagePicker.site_id) {
pagePicker.site_id = p.site.id;
reloadPagePicker(p);
}
return ( return (
<> <>
{pagePicker.status === "loading" && <Loading note="listing-page" />}
<Modal <Modal
open open
onOpenChange={(open) => { onOpenChange={(open) => {
@ -37,16 +56,47 @@ export const EdPagePop = () => {
} }
}} }}
> >
<div className="absolute inset-[5%] bg-white flex"> <div
<div className="relative flex flex-1"> id="page-picker"
<DndProvider backend={MultiBackend} options={getBackendOptions()}> ref={(ref) => {
<TypedTree if (ref) {
tree={pagePicker.tree} pagePicker.ref = ref;
rootId={"root"} }
onDrop={() => {}} }}
render={edPageTreeRender} className={cx(
/> "absolute inset-[5%] bg-white flex",
</DndProvider> css`
z-index: 100;
`
)}
>
<div className="relative flex flex-1 items-stretch text-[12px] overflow-auto">
{pagePicker.status === "loading" && <Loading note="listing-page" backdrop={false} />}
{pagePicker.ref && (
<DndProvider
backend={HTML5Backend}
options={getBackendOptions({
html5: {
rootElement: pagePicker.ref,
},
})}
>
<TypedTree
ref={(ref) => {
if (local.tree !== ref) {
local.tree = ref;
}
}}
tree={pagePicker.tree}
rootId={"page-root"}
onDrop={() => {}}
canDrag={() => true}
classes={{ root: "flex-1" }}
render={edPageTreeRender}
/>
</DndProvider>
)}
</div> </div>
</div> </div>
</Modal> </Modal>

View File

@ -1,8 +1,10 @@
import { NodeModel } from "@minoru/react-dnd-treeview"; import { NodeModel } from "@minoru/react-dnd-treeview";
import { PageItem } from "./page-tree";
import { PG } from "../../../logic/ed-global"; import { PG } from "../../../logic/ed-global";
import { PageItem } from "./page-tree";
export const pagePicker = { export const pagePicker = {
site_id: "",
ref: null as any,
tree: [] as NodeModel<PageItem>[], tree: [] as NodeModel<PageItem>[],
status: "ready" as "loading" | "ready", status: "ready" as "loading" | "ready",
render: () => {}, render: () => {},
@ -10,7 +12,6 @@ export const pagePicker = {
export const reloadPagePicker = async (p: PG) => { export const reloadPagePicker = async (p: PG) => {
pagePicker.status = "loading"; pagePicker.status = "loading";
pagePicker.render();
const pages = await db.page.findMany({ const pages = await db.page.findMany({
where: { id_site: p.site.id, is_deleted: false }, where: { id_site: p.site.id, is_deleted: false },
@ -29,6 +30,36 @@ export const reloadPagePicker = async (p: PG) => {
select: { id: true, is_deleted: false, name: true, parent_id: true }, select: { id: true, is_deleted: false, name: true, parent_id: true },
}); });
pagePicker.tree = [];
const tree = pagePicker.tree;
for (const page of pages) {
tree.push({
id: page.id,
parent: page.id_folder || "page-root",
text: page.name,
data: {
id: page.id,
name: page.name,
type: "page",
url: page.url,
},
});
}
for (const folder of folders) {
tree.push({
id: folder.id,
parent: folder.parent_id || "page-root",
text: folder.name || "",
droppable: true,
data: {
id: folder.id,
name: folder.name || "",
type: "folder",
},
});
}
pagePicker.status = "ready"; pagePicker.status = "ready";
pagePicker.render(); pagePicker.render();
}; };

View File

@ -1,6 +1,83 @@
import { NodeModel, NodeRender } from "@minoru/react-dnd-treeview";
import { useGlobal } from "web-utils";
import { EDGlobal } from "../../../logic/ed-global";
export type PageItem = { export type PageItem = {
id: string; id: string;
name: string;
url?: string;
type: "page" | "folder";
}; };
export const edPageTreeRender = () => { export const edPageTreeRender: NodeRender<PageItem> = (
return <></>; node: NodeModel<PageItem>,
{ depth, isOpen, onToggle }
) => {
const p = useGlobal(EDGlobal, "EDITOR");
const item = node.data;
if (!item) return <></>;
return (
<div
className="px-1 pl-2 flex border-b py-[2px] items-center hover:bg-blue-50 cursor-pointer relative"
onClick={() => {
if (item.type === "folder") {
onToggle();
} else if (p.ui.popup.page.open) {
p.ui.popup.page.open(item.id);
}
}}
>
{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] ",
css`
width: ${depth * 13}px;
`
)}
></div>
{item.type === "folder" && (
<>
{isOpen && <FolderOpen />}
{!isOpen && <FolderClose />}
</>
)}
<div className="pl-1 flex-1">{item.name}</div>
<div className="pl-1 flex-1">{item.url}</div>
</div>
);
}; };
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 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

@ -84,7 +84,6 @@ export const EdPopSite = () => {
return ( return (
<> <>
{local.status === "loading" && <Loading note="listing-site" />}
<Modal <Modal
open open
onOpenChange={(open) => { onOpenChange={(open) => {
@ -94,8 +93,17 @@ export const EdPopSite = () => {
} }
}} }}
> >
<div className="absolute inset-[5%] bg-white flex"> <div
className={cx(
"absolute inset-[5%] bg-white flex",
css`
z-index: 100;
`
)}
>
<div className="relative flex flex-1"> <div className="relative flex flex-1">
{local.status === "loading" && <Loading note="listing-site" backdrop={false} />}
{(local.status === "ready" || local.group.length > 0) && ( {(local.status === "ready" || local.group.length > 0) && (
<SitePicker <SitePicker
group={local.group} group={local.group}

View File

@ -42,13 +42,13 @@ export const EdTreeBody = () => {
useEffect(() => { useEffect(() => {
if (local.tree) { if (local.tree) {
let parents = []; let parents: string[] = [];
if (active.comp_id) { if (active.comp_id && p.comp.list[local.comp_id].scope[active.item_id]) {
parents = p.comp.list[local.comp_id].scope[active.item_id].p; parents = p.comp.list[local.comp_id].scope[active.item_id].p;
} else { } else if (p.page.scope[active.item_id]) {
parents = p.page.scope[active.item_id].p; parents = p.page.scope[active.item_id].p;
} }
if (parents.length === 1) { if (parents.length <= 1) {
local.tree.open( local.tree.open(
tree.filter((e) => e.parent === "root").map((e) => e.id) tree.filter((e) => e.parent === "root").map((e) => e.id)
); );

View File

@ -163,6 +163,7 @@ export const ModalContent = React.forwardRef<
"modal-overlay", "modal-overlay",
"flex items-center justify-center transition-all ", "flex items-center justify-center transition-all ",
css` css`
z-index: 100;
background: rgba(0, 0, 0, 0.3); background: rgba(0, 0, 0, 0.3);
display: grid; display: grid;
place-items: center; place-items: center;