prasi-bun/app/web/src/nova/ed/panel/popup/comp/comp-popup.tsx

206 lines
7.5 KiB
TypeScript

import {
Tree as DNDTree,
DndProvider,
TreeMethods,
getBackendOptions,
} from "@minoru/react-dnd-treeview";
import { HTML5Backend } from "react-dnd-html5-backend";
import { useEffect } from "react";
import { deepClone, useGlobal, useLocal } from "web-utils";
import { Loading } from "../../../../../utils/ui/loading";
import { Modal } from "../../../../../utils/ui/modal";
import { EDGlobal, active } from "../../../logic/ed-global";
import { compPicker, reloadCompPicker } from "./comp-reload";
import { CompItem, edPageTreeRender } from "./comp-tree";
import { EdCompPreview } from "./comp-preview";
import { fuzzy } from "../../../../../utils/ui/fuzzy";
export const EdPopComp = () => {
const p = useGlobal(EDGlobal, "EDITOR");
const local = useLocal({
tree: null as TreeMethods | null,
tab: "Components",
});
const TypedTree = DNDTree<CompItem>;
compPicker.render = local.render;
useEffect(() => {
local.tree?.openAll();
}, [p.ui.popup.comp.open, compPicker.site_id, local.tab]);
if (!p.ui.popup.comp.open) return null;
if (!compPicker.active_id && active.item_id) {
compPicker.active_id = active.item_id;
}
if (p.site.id !== compPicker.site_id) {
compPicker.site_id = p.site.id;
reloadCompPicker(p);
}
let tree = compPicker.tree;
if (local.tab === "Trash") {
tree = compPicker.trash;
}
if (compPicker.search) {
tree = fuzzy(tree, "text", compPicker.search);
tree.forEach((e) => (e.parent = "comp-root"));
tree = tree.filter((e) => e.data?.type === "component");
}
return (
<>
<Modal
open
onOpenChange={(open) => {
if (!open) {
p.ui.popup.comp.open = null;
p.render();
}
}}
fade={false}
>
<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-[14px] overflow-auto">
{compPicker.status === "loading" && (
<Loading note="listing-comp" backdrop={false} />
)}
{compPicker.status !== "loading" && (
<>
<div className="flex flex-1 flex-col">
<div className="flex h-[30px] border-b items-stretch mb-2 bg-slate-100">
<div className="flex items-end pl-1 space-x-1">
{["Components", "Trash"].map((e) => {
return (
<div
key={e}
className={cx(
"border cursor-pointer -mb-[1px] px-2 hover:text-blue-500 hover:border-blue-500 hover:border-b-transparent select-none",
local.tab === e &&
"bg-white border-b-transparent",
local.tab !== e &&
"text-slate-400 border-b-slate-200 border-transparent bg-transparent"
)}
onClick={() => {
local.tab = e;
p.render();
}}
>
{e}
</div>
);
})}
</div>
<div className="flex flex-1 mr-1 justify-end">
<input
type="search"
placeholder="Search"
spellCheck={false}
className="my-1 bg-transparent bg-white border outline-none px-1 focus:border-blue-500 focus:w-[300px] transition-all"
value={compPicker.search}
onChange={(e) => {
compPicker.search = e.currentTarget.value;
p.render();
}}
/>
</div>
</div>
<div className="relative flex-1 overflow-auto flex">
<div
className={cx(
"absolute inset-0",
css`
> .tree-root > .listitem:first-child > div {
border-top: 0;
}
> .tree-root > .listitem > .container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
position: relative;
}
.dropping {
background: #efefff;
}
`
)}
>
{compPicker.ref && compPicker.status === "ready" && (
<DndProvider
backend={HTML5Backend}
options={getBackendOptions({
html5: {
rootElement: compPicker.ref,
},
})}
>
<TypedTree
ref={(ref) => {
if (local.tree !== ref) {
local.tree = ref;
}
}}
tree={tree}
initialOpen={true}
rootId={"comp-root"}
onDrop={async (newTree, opt) => {
compPicker.tree = newTree;
p.render();
if (
typeof opt.dragSourceId === "string" &&
typeof opt.dropTargetId === "string"
) {
db.component.update({
where: {
id: opt.dragSourceId,
},
data: {
id_component_group: opt.dropTargetId,
},
});
}
}}
dragPreviewRender={() => <></>}
canDrag={() => true}
canDrop={(tree, opt) => {
if (opt.dropTarget?.data?.type === "component")
return false;
if (opt.dropTargetId === "comp-root")
return false;
return true;
}}
classes={{
root: "tree-root flex-1",
listItem: "listitem",
container: "container",
dropTarget: "dropping",
}}
render={edPageTreeRender}
/>
</DndProvider>
)}
</div>
</div>
</div>
<EdCompPreview />
</>
)}
</div>
</div>
</Modal>
</>
);
};