Spaces:
Running
Running
File size: 3,915 Bytes
1270bff 0d8f49d 7fda361 7ed5764 0d8f49d 7fda361 483664b 7fda361 483664b 7fda361 7ed5764 7fda361 7ed5764 7fda361 7ed5764 7fda361 7ed5764 1270bff 7fda361 0d8f49d 483664b 7fda361 483664b 7fda361 483664b 7ed5764 7fda361 7ed5764 8ec0e19 7ed5764 7fda361 1270bff 7fda361 7ed5764 0d8f49d 6629baa 0d8f49d 7ed5764 d983a3c 7ed5764 1270bff 7ed5764 1270bff 7ed5764 d983a3c 7fda361 0d8f49d |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
import { Handle, NodeResizeControl, type Position, useReactFlow } from "@xyflow/react";
import { ErrorBoundary } from "react-error-boundary";
// @ts-ignore
import ChevronDownRight from "~icons/tabler/chevron-down-right.jsx";
// @ts-ignore
import Skull from "~icons/tabler/skull.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;
}
function LynxKiteNodeComponent(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>}
<ErrorBoundary
fallback={
<p className="error" style={{ display: "flex", alignItems: "center", gap: 8 }}>
<Skull style={{ fontSize: 20 }} />
Failed to display this node.
</p>
}
>
<div className="node-content">{props.children}</div>
</ErrorBoundary>
<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>
);
}
export default function LynxKiteNode(Component: React.ComponentType<any>) {
return (props: any) => {
return (
<LynxKiteNodeComponent {...props}>
<Component {...props} />
</LynxKiteNodeComponent>
);
};
}
|