File size: 4,990 Bytes
1270bff
0233bb7
0d8f49d
0233bb7
 
7fda361
7ed5764
0d8f49d
0233bb7
 
1e30a56
0233bb7
0d8f49d
f0ea27c
7fda361
 
483664b
7fda361
 
 
 
 
 
 
d57c7f6
7fda361
7ed5764
 
 
 
 
 
7fda361
d57c7f6
7ed5764
7fda361
d57c7f6
7ed5764
7fda361
 
 
 
 
 
964d1be
 
 
 
7fda361
7ed5764
7fda361
 
 
 
 
9736182
4e79985
9736182
4e79985
9736182
4e79985
9736182
 
0d8f49d
483664b
7fda361
483664b
d57c7f6
483664b
 
 
7ed5764
 
 
 
 
 
9736182
22718a7
 
9736182
7fda361
7ed5764
8ec0e19
7ed5764
 
 
 
 
7fda361
9736182
 
 
 
 
f0ea27c
0233bb7
f0ea27c
0233bb7
f0ea27c
0233bb7
 
f0ea27c
0233bb7
f0ea27c
0233bb7
f0ea27c
 
 
7fda361
7ed5764
 
 
0d8f49d
4837736
0d8f49d
 
 
 
 
 
 
6629baa
0d8f49d
7ed5764
 
 
 
 
 
 
 
 
 
d983a3c
9736182
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
import { Handle, NodeResizeControl, type Position, useReactFlow } from "@xyflow/react";
import type React from "react";
import { ErrorBoundary } from "react-error-boundary";
// @ts-ignore
import AlertTriangle from "~icons/tabler/alert-triangle-filled.jsx";
// @ts-ignore
import ChevronDownRight from "~icons/tabler/chevron-down-right.jsx";
// @ts-ignore
import Dots from "~icons/tabler/dots.jsx";
// @ts-ignore
import Help from "~icons/tabler/question-mark.jsx";
// @ts-ignore
import Skull from "~icons/tabler/skull.jsx";
import Tooltip from "../../Tooltip";

interface LynxKiteNodeProps {
  id: string;
  width: number;
  height: number;
  nodeStyle: any;
  data: any;
  children: any;
}

function getHandles(inputs: any[], outputs: any[]) {
  const handles: {
    position: "top" | "bottom" | "left" | "right";
    name: string;
    index: number;
    offsetPercentage: number;
    showLabel: boolean;
    type: "source" | "target";
  }[] = [];
  for (const e of inputs) {
    handles.push({ ...e, type: "target" });
  }
  for (const e of 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]++;
  }
  const simpleHorizontal =
    counts.top === 0 && counts.bottom === 0 && counts.left <= 1 && counts.right <= 1;
  const simpleVertical =
    counts.left === 0 && counts.right === 0 && counts.top <= 1 && counts.bottom <= 1;
  for (const e of handles) {
    e.offsetPercentage = (100 * (e.index + 1)) / (counts[e.position] + 1);
    e.showLabel = !simpleHorizontal && !simpleVertical;
  }
  return handles;
}

const OP_COLORS: { [key: string]: string } = {
  pink: "oklch(75% 0.2 0)",
  orange: "oklch(75% 0.2 55)",
  green: "oklch(75% 0.2 150)",
  blue: "oklch(75% 0.2 230)",
  purple: "oklch(75% 0.2 290)",
};

function LynxKiteNodeComponent(props: LynxKiteNodeProps) {
  const reactFlow = useReactFlow();
  const data = props.data;
  const expanded = !data.collapsed;
  const handles = getHandles(data.meta?.value?.inputs || [], data.meta?.value?.outputs || []);
  function titleClicked() {
    reactFlow.updateNodeData(props.id, { collapsed: expanded });
  }
  const handleOffsetDirection = {
    top: "left",
    bottom: "left",
    left: "top",
    right: "top",
  };
  const titleStyle: { backgroundColor?: string } = {};
  if (data.meta?.value?.color) {
    titleStyle.backgroundColor = OP_COLORS[data.meta.value.color] || data.meta.value.color;
  }
  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}`}
          style={titleStyle}
          onClick={titleClicked}
        >
          <span className="title-title">{data.title}</span>
          {data.error && (
            <Tooltip doc={`Error: ${data.error}`}>
              <AlertTriangle />
            </Tooltip>
          )}
          {expanded || (
            <Tooltip doc="Click to expand node">
              <Dots />
            </Tooltip>
          )}
          <Tooltip doc={data.meta?.value?.doc}>
            <Help />
          </Tooltip>
        </div>
        {expanded && (
          <>
            {data.error && <div className="error">{data.error}</div>}
            <ErrorBoundary
              resetKeys={[props]}
              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} on ${handle.position}`}
            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>
    );
  };
}