Files
govoplan-workflow/webui/src/features/workflow/WorkflowNode.tsx
T

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}%`;
}