Spaces:
Running
Running
import { useReactFlow, Handle, NodeResizeControl, Position } 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" 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> | |
); | |
} | |