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> | |
); | |
} | |