Spaces:
Running
Running
| import { | |
| Handle, | |
| NodeResizeControl, | |
| type Position, | |
| useReactFlow, | |
| } from "@xyflow/react"; | |
| // @ts-ignore | |
| import ChevronDownRight from "~icons/tabler/chevron-down-right.jsx"; | |
| interface LynxKiteNodeProps { | |
| id: string; | |
| width: number; | |
| height: number; | |
| nodeStyle: any; | |
| data: any; | |
| children: any; | |
| } | |
| function getHandles(inputs: object, outputs: object) { | |
| const handles: { | |
| position: "top" | "bottom" | "left" | "right"; | |
| name: string; | |
| index: number; | |
| offsetPercentage: number; | |
| showLabel: boolean; | |
| type: "source" | "target"; | |
| }[] = []; | |
| for (const e of Object.values(inputs)) { | |
| handles.push({ ...e, type: "target" }); | |
| } | |
| for (const e of Object.values(outputs)) { | |
| handles.push({ ...e, type: "source" }); | |
| } | |
| const counts = { top: 0, bottom: 0, left: 0, right: 0 }; | |
| for (const e of handles) { | |
| e.index = counts[e.position]; | |
| counts[e.position]++; | |
| } | |
| for (const e of handles) { | |
| e.offsetPercentage = (100 * (e.index + 1)) / (counts[e.position] + 1); | |
| const simpleHorizontal = | |
| counts.top === 0 && counts.bottom === 0 && handles.length <= 2; | |
| const simpleVertical = | |
| counts.left === 0 && counts.right === 0 && handles.length <= 2; | |
| e.showLabel = !simpleHorizontal && !simpleVertical; | |
| } | |
| return handles; | |
| } | |
| export default function LynxKiteNode(props: LynxKiteNodeProps) { | |
| const reactFlow = useReactFlow(); | |
| const data = props.data; | |
| const expanded = !data.collapsed; | |
| const handles = getHandles(data.meta?.inputs || {}, data.meta?.outputs || {}); | |
| function titleClicked() { | |
| reactFlow.updateNodeData(props.id, { collapsed: expanded }); | |
| } | |
| const handleOffsetDirection = { | |
| top: "left", | |
| bottom: "left", | |
| left: "top", | |
| right: "top", | |
| }; | |
| return ( | |
| <div | |
| className={`node-container ${expanded ? "expanded" : "collapsed"} `} | |
| style={{ | |
| width: props.width || 200, | |
| height: expanded ? props.height || 200 : undefined, | |
| }} | |
| > | |
| <div className="lynxkite-node" style={props.nodeStyle}> | |
| <div | |
| className={`title bg-primary ${data.status}`} | |
| onClick={titleClicked} | |
| > | |
| {data.title} | |
| {data.error && <span className="title-icon">⚠️</span>} | |
| {expanded || <span className="title-icon">⋯</span>} | |
| </div> | |
| {expanded && ( | |
| <> | |
| {data.error && <div className="error">{data.error}</div>} | |
| {props.children} | |
| <NodeResizeControl | |
| minWidth={100} | |
| minHeight={50} | |
| style={{ background: "transparent", border: "none" }} | |
| > | |
| <ChevronDownRight className="node-resizer" /> | |
| </NodeResizeControl> | |
| </> | |
| )} | |
| {handles.map((handle) => ( | |
| <Handle | |
| key={handle.name} | |
| id={handle.name} | |
| type={handle.type} | |
| position={handle.position as Position} | |
| style={{ | |
| [handleOffsetDirection[handle.position]]: | |
| `${handle.offsetPercentage}% `, | |
| }} | |
| > | |
| {handle.showLabel && ( | |
| <span className="handle-name"> | |
| {handle.name.replace(/_/g, " ")} | |
| </span> | |
| )} | |
| </Handle> | |
| ))} | |
| </div> | |
| </div> | |
| ); | |
| } | |