160 lines
3.6 KiB
TypeScript
160 lines
3.6 KiB
TypeScript
import {
|
|
Asterisk,
|
|
BadgeDollarSign,
|
|
BoxSelect,
|
|
Braces,
|
|
CircleDashed,
|
|
CircleDot,
|
|
CircleDotDashed,
|
|
CirclePlus,
|
|
CircleStop,
|
|
Cog,
|
|
Database,
|
|
Diamond,
|
|
ExternalLink,
|
|
File,
|
|
FileCode2,
|
|
GitBranch,
|
|
Hand,
|
|
Inbox,
|
|
MessagesSquare,
|
|
Plus,
|
|
RadioTower,
|
|
RectangleHorizontal,
|
|
Rows3,
|
|
Scale,
|
|
Send,
|
|
Shuffle,
|
|
Square,
|
|
TextQuote,
|
|
UserRoundCheck,
|
|
CalendarClock,
|
|
CheckCircle2,
|
|
CirclePlay,
|
|
CircleX,
|
|
ClipboardCheck,
|
|
GitFork,
|
|
PlugZap,
|
|
Radio,
|
|
Split,
|
|
SquareCheckBig,
|
|
Timer,
|
|
Waypoints,
|
|
type LucideIcon
|
|
} from "lucide-react";
|
|
import {
|
|
Handle,
|
|
Position,
|
|
type Node,
|
|
type NodeProps
|
|
} from "@xyflow/react";
|
|
import type { WorkflowNodeType } from "../../api/workflow";
|
|
|
|
export type WorkflowFlowNodeData = {
|
|
label: string;
|
|
workflowType: string;
|
|
definition: WorkflowNodeType;
|
|
hasError: boolean;
|
|
};
|
|
|
|
export type WorkflowFlowNode = Node<WorkflowFlowNodeData, "workflow">;
|
|
|
|
const iconByName: Record<string, LucideIcon> = {
|
|
"calendar-clock": CalendarClock,
|
|
"circle-check-big": CheckCircle2,
|
|
"circle-play": CirclePlay,
|
|
"circle-x": CircleX,
|
|
"clipboard-check": ClipboardCheck,
|
|
"plug-zap": PlugZap,
|
|
radio: Radio,
|
|
split: Split,
|
|
"square-check-big": SquareCheckBig,
|
|
timer: Timer,
|
|
waypoints: Waypoints,
|
|
asterisk: Asterisk,
|
|
"badge-dollar-sign": BadgeDollarSign,
|
|
"box-select": BoxSelect,
|
|
braces: Braces,
|
|
"circle-dashed": CircleDashed,
|
|
"circle-dot": CircleDot,
|
|
"circle-dot-dashed": CircleDotDashed,
|
|
"circle-plus": CirclePlus,
|
|
"circle-stop": CircleStop,
|
|
cog: Cog,
|
|
database: Database,
|
|
diamond: Diamond,
|
|
"external-link": ExternalLink,
|
|
file: File,
|
|
"file-code-2": FileCode2,
|
|
"git-branch": GitBranch,
|
|
hand: Hand,
|
|
inbox: Inbox,
|
|
"messages-square": MessagesSquare,
|
|
plus: Plus,
|
|
"radio-tower": RadioTower,
|
|
"rectangle-horizontal": RectangleHorizontal,
|
|
"rows-3": Rows3,
|
|
scale: Scale,
|
|
send: Send,
|
|
shuffle: Shuffle,
|
|
square: Square,
|
|
"text-quote": TextQuote,
|
|
"user-round-check": UserRoundCheck
|
|
};
|
|
|
|
export default function WorkflowNode({
|
|
data,
|
|
selected,
|
|
isConnectable
|
|
}: NodeProps<WorkflowFlowNode>) {
|
|
const Icon = iconByName[data.definition.icon] ?? GitFork;
|
|
const shape = String(data.definition.metadata?.shape ?? "activity");
|
|
const inputPorts = data.definition.input_ports;
|
|
const outputPorts = data.definition.output_ports;
|
|
return (
|
|
<div
|
|
className={[
|
|
"workflow-node",
|
|
`workflow-node-${data.definition.category}`,
|
|
`workflow-node-shape-${shape}`,
|
|
selected ? "is-selected" : "",
|
|
data.hasError ? "has-error" : ""
|
|
].filter(Boolean).join(" ")}
|
|
>
|
|
{inputPorts.map((port, index) => (
|
|
<Handle
|
|
key={port.id}
|
|
id={port.id}
|
|
type="target"
|
|
position={Position.Left}
|
|
className="workflow-node-handle"
|
|
style={{ top: portPosition(index, inputPorts.length) }}
|
|
isConnectable={isConnectable}
|
|
title={port.label}
|
|
/>
|
|
))}
|
|
<span className="workflow-node-icon"><Icon size={17} /></span>
|
|
<span className="workflow-node-copy">
|
|
<strong>{data.label}</strong>
|
|
<small>{data.definition.label}</small>
|
|
</span>
|
|
{outputPorts.map((port, index) => (
|
|
<Handle
|
|
key={port.id}
|
|
id={port.id}
|
|
type="source"
|
|
position={Position.Right}
|
|
className="workflow-node-handle"
|
|
style={{ top: portPosition(index, outputPorts.length) }}
|
|
isConnectable={isConnectable}
|
|
title={port.label}
|
|
/>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function portPosition(index: number, count: number): string {
|
|
return `${((index + 1) / (count + 1)) * 100}%`;
|
|
}
|