Adding Node Toolbar ReactFlow

This commit is contained in:
Cristhian Zanforlin Lousa 2023-05-30 19:37:36 -03:00
commit 8aa2d2e4c9
2 changed files with 300 additions and 186 deletions

View file

@ -30,6 +30,9 @@ import { TabsContext } from "../../contexts/tabsContext";
import { debounce } from "../../utils"; import { debounce } from "../../utils";
import TooltipReact from "../../components/ReactTooltipComponent"; import TooltipReact from "../../components/ReactTooltipComponent";
import Tooltip from "../../components/TooltipComponent"; import Tooltip from "../../components/TooltipComponent";
import { NodeToolbar } from "reactflow";
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
export default function GenericNode({ export default function GenericNode({
data, data,
selected, selected,
@ -97,199 +100,173 @@ export default function GenericNode({
deleteNode(data.id); deleteNode(data.id);
return; return;
} }
console.log(data);
return ( return (
<div <>
className={classNames( <NodeToolbar>
selected ? "border border-blue-500" : "border dark:border-gray-700", <NodeToolbarComponent
"prompt-node relative flex w-96 flex-col justify-center rounded-lg bg-white dark:bg-gray-900" data={data}
)} openPopUp={openPopUp}
> deleteNode={deleteNode}
<div className="flex w-full items-center justify-between gap-8 rounded-t-lg border-b bg-gray-50 p-4 dark:border-b-gray-700 dark:bg-gray-800 dark:text-white "> ></NodeToolbarComponent>
<div className="flex w-full items-center gap-2 truncate text-lg"> </NodeToolbar>
<Icon
className="h-10 w-10 rounded p-1" <div
style={{ className={classNames(
color: nodeColors[types[data.type]] ?? nodeColors.unknown, selected ? "border border-blue-500" : "border dark:border-gray-700",
}} "prompt-node relative flex w-96 flex-col justify-center rounded-lg bg-white dark:bg-gray-900"
/> )}
<div className="ml-2 truncate"> >
<TooltipReact <div className="flex w-full items-center justify-between gap-8 rounded-t-lg border-b bg-gray-50 p-4 dark:border-b-gray-700 dark:bg-gray-800 dark:text-white ">
delayShow={1000} <div className="flex w-full items-center gap-2 truncate text-lg">
selector={`node-selector-${data.type}`} <Icon
htmlContent={data.type} className="h-10 w-10 rounded p-1"
position="top" style={{
> color: nodeColors[types[data.type]] ?? nodeColors.unknown,
<div className="ml-2 truncate">{data.type}</div> }}
</TooltipReact> />
</div> <div className="ml-2 truncate">
</div> <TooltipReact
<div className="flex gap-3"> delayShow={1000}
<button selector={`node-selector-${data.type}`}
className="relative" htmlContent={data.type}
onClick={(event) => { position="top"
event.preventDefault(); >
openPopUp(<NodeModal data={data} />); <div className="ml-2 truncate">{data.type}</div>
}} </TooltipReact>
>
<div className=" absolute -right-1 -top-2 text-red-600">
{Object.keys(data.node.template).some(
(t) =>
data.node.template[t].advanced &&
data.node.template[t].required
)
? " *"
: ""}
</div> </div>
<Cog6ToothIcon </div>
<div className="flex gap-3">
<div>
<Tooltip
title={
!validationStatus ? (
"Validating..."
) : (
<div className="max-h-96 overflow-auto">
{validationStatus.params
.split("\n")
.map((line, index) => (
<div key={index}>{line}</div>
))}
</div>
)
}
>
<div className="w-5 h-5 relative top-[3px]">
<div
className={classNames(
validationStatus && validationStatus.valid
? "w-4 h-4 rounded-full bg-green-500 opacity-100"
: "w-4 h-4 rounded-full bg-gray-500 opacity-0 hidden animate-spin",
"absolute w-4 hover:text-gray-500 hover:dark:text-gray-300 transition-all ease-in-out duration-200"
)}
></div>
<div
className={classNames(
validationStatus && !validationStatus.valid
? "w-4 h-4 rounded-full bg-red-500 opacity-100"
: "w-4 h-4 rounded-full bg-gray-500 opacity-0 hidden animate-spin",
"absolute w-4 hover:text-gray-500 hover:dark:text-gray-300 transition-all ease-in-out duration-200"
)}
></div>
<div
className={classNames(
!validationStatus
? "w-4 h-4 rounded-full bg-yellow-500 opacity-100"
: "w-4 h-4 rounded-full bg-gray-500 opacity-0 hidden animate-spin",
"absolute w-4 hover:text-gray-500 hover:dark:text-gray-300 transition-all ease-in-out duration-200"
)}
></div>
</div>
</Tooltip>
</div>
</div>
</div>
<div className="h-full w-full py-5">
<div className="w-full px-5 pb-3 text-sm text-gray-500 dark:text-gray-300">
{data.node.description}
</div>
<>
{Object.keys(data.node.template)
.filter((t) => t.charAt(0) !== "_")
.map((t: string, idx) => (
<div key={idx}>
{/* {idx === 0 ? (
<div
className={classNames(
"px-5 py-2 mt-2 dark:text-white text-center",
Object.keys(data.node.template).filter(
(key) =>
!key.startsWith("_") &&
data.node.template[key].show &&
!data.node.template[key].advanced
).length === 0
? "hidden"
: ""
)}
>
Inputs
</div>
) : (
<></>
)} */}
{data.node.template[t].show &&
!data.node.template[t].advanced ? (
<ParameterComponent
data={data}
color={
nodeColors[types[data.node.template[t].type]] ??
nodeColors.unknown
}
title={
data.node.template[t].display_name
? data.node.template[t].display_name
: data.node.template[t].name
? toTitleCase(data.node.template[t].name)
: toTitleCase(t)
}
name={t}
tooltipTitle={
"Type: " +
data.node.template[t].type +
(data.node.template[t].list ? " list" : "")
}
required={data.node.template[t].required}
id={data.node.template[t].type + "|" + t + "|" + data.id}
left={true}
type={data.node.template[t].type}
/>
) : (
<></>
)}
</div>
))}
<div
className={classNames( className={classNames(
Object.keys(data.node.template).some( Object.keys(data.node.template).length < 1 ? "hidden" : "",
(t) => "flex w-full justify-center"
data.node.template[t].advanced && data.node.template[t].show
)
? ""
: "hidden",
"w-5 h-5 dark:text-gray-300"
)} )}
></Cog6ToothIcon>
</button>
<button
onClick={() => {
deleteNode(data.id);
}}
>
<TrashIcon className="w-5 h-5 dark:text-gray-300"></TrashIcon>
</button>
<div>
<Tooltip
title={
!validationStatus ? (
"Validating..."
) : (
<div className="max-h-96 overflow-auto">
{validationStatus.params.split("\n").map((line, index) => (
<div key={index}>{line}</div>
))}
</div>
)
}
> >
<div className="w-5 h-5 relative top-[3px]"> {" "}
<div </div>
className={classNames( {/* <div className="px-5 py-2 mt-2 dark:text-white text-center">
validationStatus && validationStatus.valid Output
? "w-4 h-4 rounded-full bg-green-500 opacity-100" </div> */}
: "w-4 h-4 rounded-full bg-gray-500 opacity-0 hidden animate-spin", <ParameterComponent
"absolute w-4 hover:text-gray-500 hover:dark:text-gray-300 transition-all ease-in-out duration-200" data={data}
)} color={nodeColors[types[data.type]] ?? nodeColors.unknown}
></div> title={data.type}
<div tooltipTitle={`Type: ${data.node.base_classes.join(" | ")}`}
className={classNames( id={[data.type, data.id, ...data.node.base_classes].join("|")}
validationStatus && !validationStatus.valid type={data.node.base_classes.join("|")}
? "w-4 h-4 rounded-full bg-red-500 opacity-100" left={false}
: "w-4 h-4 rounded-full bg-gray-500 opacity-0 hidden animate-spin", />
"absolute w-4 hover:text-gray-500 hover:dark:text-gray-300 transition-all ease-in-out duration-200" </>
)}
></div>
<div
className={classNames(
!validationStatus
? "w-4 h-4 rounded-full bg-yellow-500 opacity-100"
: "w-4 h-4 rounded-full bg-gray-500 opacity-0 hidden animate-spin",
"absolute w-4 hover:text-gray-500 hover:dark:text-gray-300 transition-all ease-in-out duration-200"
)}
></div>
</div>
</Tooltip>
</div>
</div> </div>
</div> </div>
</>
<div className="h-full w-full py-5">
<div className="w-full px-5 pb-3 text-sm text-gray-500 dark:text-gray-300">
{data.node.description}
</div>
<>
{Object.keys(data.node.template)
.filter((t) => t.charAt(0) !== "_")
.map((t: string, idx) => (
<div key={idx}>
{/* {idx === 0 ? (
<div
className={classNames(
"px-5 py-2 mt-2 dark:text-white text-center",
Object.keys(data.node.template).filter(
(key) =>
!key.startsWith("_") &&
data.node.template[key].show &&
!data.node.template[key].advanced
).length === 0
? "hidden"
: ""
)}
>
Inputs
</div>
) : (
<></>
)} */}
{data.node.template[t].show &&
!data.node.template[t].advanced ? (
<ParameterComponent
data={data}
color={
nodeColors[types[data.node.template[t].type]] ??
nodeColors.unknown
}
title={
data.node.template[t].display_name
? data.node.template[t].display_name
: data.node.template[t].name
? toTitleCase(data.node.template[t].name)
: toTitleCase(t)
}
name={t}
tooltipTitle={
"Type: " +
data.node.template[t].type +
(data.node.template[t].list ? " list" : "")
}
required={data.node.template[t].required}
id={data.node.template[t].type + "|" + t + "|" + data.id}
left={true}
type={data.node.template[t].type}
/>
) : (
<></>
)}
</div>
))}
<div
className={classNames(
Object.keys(data.node.template).length < 1 ? "hidden" : "",
"flex w-full justify-center"
)}
>
{" "}
</div>
{/* <div className="px-5 py-2 mt-2 dark:text-white text-center">
Output
</div> */}
<ParameterComponent
data={data}
color={nodeColors[types[data.type]] ?? nodeColors.unknown}
title={data.type}
tooltipTitle={`Type: ${data.node.base_classes.join(" | ")}`}
id={[data.type, data.id, ...data.node.base_classes].join("|")}
type={data.node.base_classes.join("|")}
left={false}
/>
</>
</div>
</div>
); );
} }

View file

@ -0,0 +1,137 @@
import React from "react";
import { Menu, Transition } from "@headlessui/react";
import { EllipsisVerticalIcon } from "@heroicons/react/20/solid";
import {
Cog6ToothIcon,
TrashIcon,
PencilSquareIcon,
DocumentDuplicateIcon,
} from "@heroicons/react/24/outline";
import { classNames } from "../../../../utils";
import { Fragment } from "react";
import NodeModal from "../../../../modals/NodeModal";
const NodeToolbarComponent = (props) => {
return (
<>
<div className="h-10 w-26">
<span className="isolate inline-flex rounded-md shadow-sm">
<button
className="hover:dark:hover:bg-[#242f47] text-gray-700 transition-all duration-500 ease-in-out dark:bg-gray-800 dark:text-gray-300 shadow-md relative inline-flex items-center rounded-l-md bg-white px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
onClick={() => {
props.deleteNode(props.data.id);
}}
>
<TrashIcon className="w-5 h-5 dark:text-gray-300"></TrashIcon>
</button>
<button
className={classNames(
Object.keys(props.data.node.template).some(
(t) =>
props.data.node.template[t].advanced &&
props.data.node.template[t].show
)
? "hover:dark:hover:bg-[#242f47] text-gray-700 transition-all duration-500 ease-in-out dark:bg-gray-800 dark:text-gray-300 shadow-md relative -ml-px inline-flex items-center bg-white px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
: "hidden"
)}
onClick={(event) => {
event.preventDefault();
props.openPopUp(<NodeModal data={props.data} />);
}}
>
<div className=" absolute -right-1 -top-2 text-red-600">
{Object.keys(props.data.node.template).some(
(t) =>
props.data.node.template[t].advanced &&
props.data.node.template[t].required
)
? " *"
: ""}
</div>
<Cog6ToothIcon
className={classNames(
Object.keys(props.data.node.template).some(
(t) =>
props.data.node.template[t].advanced &&
props.data.node.template[t].show
)
? ""
: "hidden",
"w-5 h-5 dark:text-gray-300"
)}
></Cog6ToothIcon>
</button>
<Menu as="div" className="relative inline-block text-left z-100">
<button className="hover:dark:hover:bg-[#242f47] text-gray-700 transition-all duration-500 ease-in-out dark:bg-gray-800 dark:text-gray-300 shadow-md relative -ml-px inline-flex items-center bg-white px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10 rounded-r-md">
<div>
<Menu.Button className="flex items-center">
<EllipsisVerticalIcon
className="w-5 h-5 dark:text-gray-300"
aria-hidden="true"
/>
</Menu.Button>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute z-40 mt-2 w-56 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none top-[28px]">
<div className="py-1">
<Menu.Item>
{({ active }) => (
<a
href="#"
className={classNames(
active
? "bg-gray-100 text-gray-900"
: "text-gray-700",
"group flex items-center px-4 py-2 text-sm"
)}
>
<PencilSquareIcon
className="mr-3 h-5 w-5 text-gray-400 group-hover:text-gray-500"
aria-hidden="true"
/>
Edit
</a>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<a
href="#"
className={classNames(
active
? "bg-gray-100 text-gray-900"
: "text-gray-700",
"group flex items-center px-4 py-2 text-sm"
)}
>
<DocumentDuplicateIcon
className="mr-3 h-5 w-5 text-gray-400 group-hover:text-gray-500"
aria-hidden="true"
/>
Duplicate
</a>
)}
</Menu.Item>
</div>
</Menu.Items>
</Transition>
</button>
</Menu>
</span>
</div>
</>
);
};
export default NodeToolbarComponent;