File size: 3,879 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
 
 
 
 
 
 
 
 
 
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>
              }
            >
              {props.children}
            </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>
    );
  };
}