|
|
@@ -1,18 +1,21 @@
|
|
|
/**
|
|
|
- * 节点渲染组件(方形节点、输入/输出端口)
|
|
|
+ * 节点渲染器(父类/基类组件)
|
|
|
+ * 所有节点类型都使用此组件,通过配置来实现不同的节点类型
|
|
|
+ *
|
|
|
+ * 核心设计思想:
|
|
|
+ * - 所有公共逻辑(事件处理、端口渲染、样式计算)都在此实现
|
|
|
+ * - 子类只需要提供配置信息(className、标题文本、布局类型)
|
|
|
+ * - 节点的差异主要体现在:端口数量(通过node.inputs/outputs定义)、节点尺寸(通过CSS控制)、样式主题(通过className控制)
|
|
|
*/
|
|
|
|
|
|
import './node-renderer.css';
|
|
|
import {
|
|
|
getNodeStyle,
|
|
|
- calculatePortPosition,
|
|
|
getNodeClassName,
|
|
|
getPortTypeClass,
|
|
|
- getPortStyle,
|
|
|
isPortConnected,
|
|
|
getPortDefaultValue,
|
|
|
formatOutputLabel,
|
|
|
- separatePorts,
|
|
|
handleNodeMouseDown,
|
|
|
handleNodeDoubleClick,
|
|
|
handlePortMouseDown,
|
|
|
@@ -21,231 +24,230 @@ import {
|
|
|
validateNodeCoordinates
|
|
|
} from './node-renderer.js';
|
|
|
|
|
|
-export function NodeRenderer({ node, isSelected, connections = [], onPortMouseDown, onPortMouseUp, onNodeMouseDown, onNodeDoubleClick, onPortValueChange }) {
|
|
|
+/**
|
|
|
+ * 节点渲染器组件
|
|
|
+ * @param {Object} props - 组件属性
|
|
|
+ * @param {Object} props.node - 节点数据(包含inputs/outputs定义端口数量)
|
|
|
+ * @param {boolean} props.isSelected - 是否选中
|
|
|
+ * @param {Array} props.connections - 连接数组
|
|
|
+ * @param {string} props.className - 额外的CSS类名(用于子类样式)
|
|
|
+ * @param {string} props.headerText - 标题文本(如果不提供,使用node.label)
|
|
|
+ * @param {boolean} props.showHeader - 是否显示标题(GET节点不显示)
|
|
|
+ * @param {Function} props.renderCustomBody - 自定义主体渲染(可选,用于特殊节点如Begin)
|
|
|
+ * @param {string} props.layoutType - 布局类型:'standard'(标准), 'variable'(GET变量), 'set-variable'(SET变量)
|
|
|
+ */
|
|
|
+export function NodeRenderer({
|
|
|
+ node,
|
|
|
+ isSelected,
|
|
|
+ connections = [],
|
|
|
+ onPortMouseDown,
|
|
|
+ onPortMouseUp,
|
|
|
+ onNodeMouseDown,
|
|
|
+ onNodeDoubleClick,
|
|
|
+ onPortValueChange,
|
|
|
+ className = '',
|
|
|
+ headerText,
|
|
|
+ showHeader = true,
|
|
|
+ renderCustomBody,
|
|
|
+ layoutType = 'standard'
|
|
|
+}) {
|
|
|
if (!node) return null;
|
|
|
|
|
|
validateNodeCoordinates(node);
|
|
|
|
|
|
const nodeStyle = getNodeStyle(node);
|
|
|
const nodeClassName = getNodeClassName(isSelected, node);
|
|
|
+ const finalClassName = className ? `${nodeClassName} ${className}` : nodeClassName;
|
|
|
+
|
|
|
+ // 分离端口类型(通过node.inputs/outputs获取端口数量和类型)
|
|
|
+ const executionInputs = node.inputs?.filter(p => p.type === 'execution') || [];
|
|
|
+ const executionOutputs = node.outputs?.filter(p => p.type === 'execution') || [];
|
|
|
+ const dataInputs = node.inputs?.filter(p => p.type !== 'execution') || [];
|
|
|
+ const dataOutputs = node.outputs?.filter(p => p.type !== 'execution') || [];
|
|
|
|
|
|
return (
|
|
|
<div
|
|
|
- className={nodeClassName}
|
|
|
+ className={finalClassName}
|
|
|
style={nodeStyle}
|
|
|
data-node-id={node.id}
|
|
|
onMouseDown={(e) => handleNodeMouseDown(e, onNodeMouseDown, node.id)}
|
|
|
onDoubleClick={(e) => handleNodeDoubleClick(e, onNodeDoubleClick, node.id)}
|
|
|
>
|
|
|
- {node.type === 'variable' ? (
|
|
|
- <div className="Blueprint-variable-node-label">
|
|
|
- {node.varMode === 'set' ? `Set ${node.varName || node.label}` : (node.label || node.varName || node.type)}
|
|
|
+ {/* 标题栏(GET节点不显示) */}
|
|
|
+ {showHeader && renderHeader(headerText || node.label || node.type, className)}
|
|
|
+
|
|
|
+ {/* 自定义主体内容(可选,用于特殊节点) */}
|
|
|
+ {renderCustomBody && renderCustomBody(node)}
|
|
|
+
|
|
|
+ {/* 根据布局类型渲染端口 */}
|
|
|
+ {layoutType === 'variable' ? (
|
|
|
+ // GET变量节点布局:只有输出数据端口,端口在右侧中间
|
|
|
+ // 变量名直接显示在节点中央
|
|
|
+ <div className="Blueprint-variable-node-body">
|
|
|
+ <span className="Blueprint-get-node-label">{headerText || node.label || node.varName || 'Variable'}</span>
|
|
|
+ {dataOutputs.map(port => (
|
|
|
+ <div key={port.id} style={{ position: 'absolute', right: '-8px', top: '50%', transform: 'translateY(-50%)' }}>
|
|
|
+ {renderPortWithoutLabel(port, node.id, false, connections, { onPortMouseDown, onPortMouseUp })}
|
|
|
+ </div>
|
|
|
+ ))}
|
|
|
</div>
|
|
|
- ) : (
|
|
|
- <div className="Blueprint-node-header">
|
|
|
- {node.label || node.type}
|
|
|
- </div>
|
|
|
- )}
|
|
|
- {node.type === 'variable' ? (
|
|
|
- /* 变量节点:根据 varMode 决定端口位置 */
|
|
|
- /* Get 节点:只有右侧输出端口 */
|
|
|
- /* Set 节点:只有左侧输入端口 */
|
|
|
+ ) : layoutType === 'set-variable' ? (
|
|
|
+ // SET变量节点布局:有执行端口区域和数据端口区域
|
|
|
<>
|
|
|
- <div className="Blueprint-variable-node-body">
|
|
|
- {/* Set 节点的输入端口(左侧) */}
|
|
|
- {node.varMode === 'set' && node.inputs && node.inputs.length > 0 && node.inputs.map((input, index) => {
|
|
|
- const portTypeClass = getPortTypeClass(input);
|
|
|
- const isConnected = isPortConnected(node.id, input.id, connections);
|
|
|
- return (
|
|
|
- <div
|
|
|
- key={input.id}
|
|
|
- className={`Blueprint-node-port input ${portTypeClass} ${isConnected ? 'connected' : 'disconnected'}`}
|
|
|
- style={{ left: '-8px', top: '50%', transform: 'translateY(-50%)' }}
|
|
|
- data-port-type={input.type}
|
|
|
- data-param-type={input.paramType || ''}
|
|
|
- onMouseDown={(e) => handlePortMouseDown(e, onPortMouseDown, node.id, input.id)}
|
|
|
- onMouseUp={(e) => handlePortMouseUp(e, onPortMouseUp, node.id, input.id)}
|
|
|
- title={input.label || input.id}
|
|
|
- >
|
|
|
- <div className={`Blueprint-node-port-dot ${isConnected ? 'solid' : 'hollow'}`}></div>
|
|
|
- </div>
|
|
|
- );
|
|
|
- })}
|
|
|
- {/* Get 节点的输出端口(右侧) */}
|
|
|
- {node.varMode === 'get' && node.outputs && node.outputs.length > 0 && node.outputs.map((output, index) => {
|
|
|
- const portTypeClass = getPortTypeClass(output);
|
|
|
- const isConnected = isPortConnected(node.id, output.id, connections);
|
|
|
- return (
|
|
|
- <div
|
|
|
- key={output.id}
|
|
|
- className={`Blueprint-node-port output ${portTypeClass} ${isConnected ? 'connected' : 'disconnected'}`}
|
|
|
- style={{ right: '-8px', top: '50%', transform: 'translateY(-50%)' }}
|
|
|
- data-port-type={output.type}
|
|
|
- data-param-type={output.paramType || ''}
|
|
|
- onMouseDown={(e) => handlePortMouseDown(e, onPortMouseDown, node.id, output.id)}
|
|
|
- onMouseUp={(e) => handlePortMouseUp(e, onPortMouseUp, node.id, output.id)}
|
|
|
- title={output.label || output.id}
|
|
|
- >
|
|
|
- <div className={`Blueprint-node-port-dot ${isConnected ? 'solid' : 'hollow'}`}></div>
|
|
|
- </div>
|
|
|
- );
|
|
|
- })}
|
|
|
- {/* 兼容旧的变量节点(没有 varMode 的情况,默认为 get) */}
|
|
|
- {!node.varMode && node.outputs && node.outputs.length > 0 && node.outputs.map((output, index) => {
|
|
|
- const portTypeClass = getPortTypeClass(output);
|
|
|
- const isConnected = isPortConnected(node.id, output.id, connections);
|
|
|
- return (
|
|
|
- <div
|
|
|
- key={output.id}
|
|
|
- className={`Blueprint-node-port output ${portTypeClass} ${isConnected ? 'connected' : 'disconnected'}`}
|
|
|
- style={{ right: '-8px', top: '50%', transform: 'translateY(-50%)' }}
|
|
|
- data-port-type={output.type}
|
|
|
- data-param-type={output.paramType || ''}
|
|
|
- onMouseDown={(e) => handlePortMouseDown(e, onPortMouseDown, node.id, output.id)}
|
|
|
- onMouseUp={(e) => handlePortMouseUp(e, onPortMouseUp, node.id, output.id)}
|
|
|
- title={output.label || output.id}
|
|
|
- >
|
|
|
- <div className={`Blueprint-node-port-dot ${isConnected ? 'solid' : 'hollow'}`}></div>
|
|
|
- </div>
|
|
|
- );
|
|
|
- })}
|
|
|
+ {/* 执行端口区域 */}
|
|
|
+ <div className="Blueprint-variable-set-execution">
|
|
|
+ {executionInputs.map(port => renderPort(port, node.id, true, connections, { onPortMouseDown, onPortMouseUp }))}
|
|
|
+ {executionOutputs.map(port => renderPort(port, node.id, false, connections, { onPortMouseDown, onPortMouseUp }))}
|
|
|
+ </div>
|
|
|
+ {/* 数据端口区域 */}
|
|
|
+ <div className="Blueprint-variable-set-data">
|
|
|
+ {dataInputs.map(port => renderPort(port, node.id, true, connections, { onPortMouseDown, onPortMouseUp }))}
|
|
|
</div>
|
|
|
</>
|
|
|
) : (
|
|
|
- /* 流程节点:分为三个区域 */
|
|
|
+ // 标准流程节点布局:执行端口区域 + 数据端口区域
|
|
|
<>
|
|
|
- {/* 区域2:执行箭头区域(固定高度) */}
|
|
|
- <div className="Blueprint-node-execution">
|
|
|
- {/* 输入执行端口 */}
|
|
|
- {node.inputs && (() => {
|
|
|
- const executionInputs = node.inputs.filter(input => input.type === 'execution');
|
|
|
- return executionInputs.map((input, index) => {
|
|
|
- const portTypeClass = getPortTypeClass(input);
|
|
|
- const isConnected = isPortConnected(node.id, input.id, connections);
|
|
|
-
|
|
|
- return (
|
|
|
- <div
|
|
|
- key={input.id}
|
|
|
- className={`Blueprint-node-port input ${portTypeClass} ${isConnected ? 'connected' : 'disconnected'}`}
|
|
|
- data-port-type={input.type}
|
|
|
- data-param-type={input.paramType || ''}
|
|
|
- onMouseDown={(e) => {
|
|
|
- e.stopPropagation();
|
|
|
- onPortMouseDown?.(e, node.id, input.id);
|
|
|
- }}
|
|
|
- onMouseUp={(e) => {
|
|
|
- e.stopPropagation();
|
|
|
- onPortMouseUp?.(e, node.id, input.id);
|
|
|
- }}
|
|
|
- title={input.label || input.id}
|
|
|
- >
|
|
|
- <div className="Blueprint-node-port-arrow execution-input"></div>
|
|
|
- </div>
|
|
|
- );
|
|
|
- });
|
|
|
- })()}
|
|
|
- {/* 输出执行端口 */}
|
|
|
- {node.outputs && (() => {
|
|
|
- const executionOutputs = node.outputs.filter(output => output.type === 'execution');
|
|
|
- return executionOutputs.map((output, index) => {
|
|
|
- const portTypeClass = getPortTypeClass(output);
|
|
|
- const isConnected = isPortConnected(node.id, output.id, connections);
|
|
|
-
|
|
|
- return (
|
|
|
- <div
|
|
|
- key={output.id}
|
|
|
- className={`Blueprint-node-port output ${portTypeClass} ${isConnected ? 'connected' : 'disconnected'}`}
|
|
|
- data-port-type={output.type}
|
|
|
- data-param-type={output.paramType || ''}
|
|
|
- onMouseDown={(e) => handlePortMouseDown(e, onPortMouseDown, node.id, output.id)}
|
|
|
- onMouseUp={(e) => handlePortMouseUp(e, onPortMouseUp, node.id, output.id)}
|
|
|
- title={output.label || output.id}
|
|
|
- >
|
|
|
- <div className="Blueprint-node-port-arrow execution-output"></div>
|
|
|
- </div>
|
|
|
- );
|
|
|
- });
|
|
|
- })()}
|
|
|
- </div>
|
|
|
-
|
|
|
- {/* 区域3:参数区域 */}
|
|
|
- <div className="Blueprint-node-params">
|
|
|
- {/* 输入数据端口 */}
|
|
|
- {node.inputs && (() => {
|
|
|
- const dataInputs = node.inputs.filter(input => input.type !== 'execution');
|
|
|
-
|
|
|
- return dataInputs.map((input, dataIndex) => {
|
|
|
- const portTypeClass = getPortTypeClass(input);
|
|
|
- const isConnected = isPortConnected(node.id, input.id, connections);
|
|
|
- const isDataPort = input.type === 'data';
|
|
|
- const defaultValue = isDataPort ? getPortDefaultValue(input, node) : '';
|
|
|
-
|
|
|
- return (
|
|
|
- <div
|
|
|
- key={input.id}
|
|
|
- className={`Blueprint-node-port input ${portTypeClass} ${isConnected ? 'connected' : 'disconnected'}`}
|
|
|
- data-port-type={input.type}
|
|
|
- data-param-type={input.paramType || ''}
|
|
|
- onMouseDown={(e) => {
|
|
|
- if (e.target.tagName === 'INPUT') {
|
|
|
- e.stopPropagation();
|
|
|
- return;
|
|
|
- }
|
|
|
- e.stopPropagation();
|
|
|
- onPortMouseDown?.(e, node.id, input.id);
|
|
|
- }}
|
|
|
- onMouseUp={(e) => {
|
|
|
- e.stopPropagation();
|
|
|
- onPortMouseUp?.(e, node.id, input.id);
|
|
|
- }}
|
|
|
- title={input.label || input.id}
|
|
|
- >
|
|
|
- <div className={`Blueprint-node-port-dot ${isConnected ? 'solid' : 'hollow'}`}></div>
|
|
|
- <span className="Blueprint-node-port-label">{input.label}</span>
|
|
|
- {isDataPort && !isConnected && (
|
|
|
- <input
|
|
|
- type="text"
|
|
|
- className="Blueprint-node-port-input"
|
|
|
- value={defaultValue}
|
|
|
- placeholder={input.paramType === 'int' ? '0' : '""'}
|
|
|
- onChange={(e) => handleInputChange(e, onPortValueChange, node.id, input.id, input.paramName)}
|
|
|
- onClick={(e) => e.stopPropagation()}
|
|
|
- onMouseDown={(e) => e.stopPropagation()}
|
|
|
- />
|
|
|
- )}
|
|
|
- </div>
|
|
|
- );
|
|
|
- });
|
|
|
- })()}
|
|
|
-
|
|
|
- {/* 输出数据端口 */}
|
|
|
- {node.outputs && (() => {
|
|
|
- const dataOutputs = node.outputs.filter(output => output.type !== 'execution');
|
|
|
-
|
|
|
- return dataOutputs.map((output, dataIndex) => {
|
|
|
- const portTypeClass = getPortTypeClass(output);
|
|
|
- const isConnected = isPortConnected(node.id, output.id, connections);
|
|
|
- const displayLabel = formatOutputLabel(output.label);
|
|
|
-
|
|
|
- return (
|
|
|
- <div
|
|
|
- key={output.id}
|
|
|
- className={`Blueprint-node-port output ${portTypeClass} ${isConnected ? 'connected' : 'disconnected'}`}
|
|
|
- data-port-type={output.type}
|
|
|
- data-param-type={output.paramType || ''}
|
|
|
- onMouseDown={(e) => handlePortMouseDown(e, onPortMouseDown, node.id, output.id)}
|
|
|
- onMouseUp={(e) => handlePortMouseUp(e, onPortMouseUp, node.id, output.id)}
|
|
|
- title={output.label || output.id}
|
|
|
- >
|
|
|
- <div className={`Blueprint-node-port-dot ${isConnected ? 'solid' : 'hollow'}`}></div>
|
|
|
- {displayLabel && <span className="Blueprint-node-port-label">{displayLabel}</span>}
|
|
|
- </div>
|
|
|
- );
|
|
|
- });
|
|
|
- })()}
|
|
|
- </div>
|
|
|
+ {/* 执行端口区域 */}
|
|
|
+ {(executionInputs.length > 0 || executionOutputs.length > 0) && (
|
|
|
+ <div className="Blueprint-node-execution">
|
|
|
+ {executionInputs.map(port => renderPort(port, node.id, true, connections, { onPortMouseDown, onPortMouseUp }))}
|
|
|
+ {executionOutputs.map(port => renderPort(port, node.id, false, connections, { onPortMouseDown, onPortMouseUp }))}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ {/* 数据端口区域 */}
|
|
|
+ {(dataInputs.length > 0 || dataOutputs.length > 0) && (
|
|
|
+ <div className="Blueprint-node-params">
|
|
|
+ {dataInputs.map(port => (
|
|
|
+ <div key={port.id}>{renderDataPort(port, node, true, connections, { onPortMouseDown, onPortMouseUp, onPortValueChange })}</div>
|
|
|
+ ))}
|
|
|
+ {dataOutputs.map(port => (
|
|
|
+ <div key={port.id}>{renderPort(port, node.id, false, connections, { onPortMouseDown, onPortMouseUp })}</div>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 渲染标题(公共方法)
|
|
|
+ */
|
|
|
+function renderHeader(text, nodeClassName) {
|
|
|
+ // 根据节点类型使用不同的标题样式类
|
|
|
+ const headerClass = nodeClassName.includes('begin') ? 'Blueprint-begin-node-header' :
|
|
|
+ nodeClassName.includes('set-node') ? 'Blueprint-set-node-header' :
|
|
|
+ nodeClassName.includes('get-node') ? 'Blueprint-get-node-label' :
|
|
|
+ nodeClassName.includes('echo') ? 'Blueprint-echo-node-header' :
|
|
|
+ nodeClassName.includes('schedule') ? 'Blueprint-schedule-node-header' :
|
|
|
+ nodeClassName.includes('while') ? 'Blueprint-while-node-header' :
|
|
|
+ nodeClassName.includes('if') ? 'Blueprint-if-node-header' :
|
|
|
+ nodeClassName.includes('delay') ? 'Blueprint-delay-node-header' :
|
|
|
+ 'Blueprint-node-header';
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className={headerClass}>
|
|
|
+ {text}
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 渲染单个端口(公共方法)
|
|
|
+ */
|
|
|
+function renderPort(port, nodeId, isInput, connections, handlers) {
|
|
|
+ const { onPortMouseDown, onPortMouseUp } = handlers;
|
|
|
+ const portTypeClass = getPortTypeClass(port);
|
|
|
+ const connected = isPortConnected(nodeId, port.id, connections);
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div
|
|
|
+ key={port.id}
|
|
|
+ className={`Blueprint-node-port ${isInput ? 'input' : 'output'} ${portTypeClass} ${connected ? 'connected' : 'disconnected'}`}
|
|
|
+ data-port-type={port.type}
|
|
|
+ data-param-type={port.paramType || ''}
|
|
|
+ onMouseDown={(e) => handlePortMouseDown(e, onPortMouseDown, nodeId, port.id)}
|
|
|
+ onMouseUp={(e) => handlePortMouseUp(e, onPortMouseUp, nodeId, port.id)}
|
|
|
+ title={port.label || port.id}
|
|
|
+ >
|
|
|
+ {port.type === 'execution' ? (
|
|
|
+ <div className={`Blueprint-node-port-arrow ${isInput ? 'execution-input' : 'execution-output'}`}></div>
|
|
|
+ ) : (
|
|
|
+ <>
|
|
|
+ <div className={`Blueprint-node-port-dot ${connected ? 'solid' : 'hollow'}`}></div>
|
|
|
+ {port.label && <span className="Blueprint-node-port-label">{formatOutputLabel(port.label)}</span>}
|
|
|
</>
|
|
|
)}
|
|
|
</div>
|
|
|
);
|
|
|
}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 渲染单个端口(不显示标签,用于GET变量节点)
|
|
|
+ */
|
|
|
+function renderPortWithoutLabel(port, nodeId, isInput, connections, handlers) {
|
|
|
+ const { onPortMouseDown, onPortMouseUp } = handlers;
|
|
|
+ const portTypeClass = getPortTypeClass(port);
|
|
|
+ const connected = isPortConnected(nodeId, port.id, connections);
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div
|
|
|
+ key={port.id}
|
|
|
+ className={`Blueprint-node-port ${isInput ? 'input' : 'output'} ${portTypeClass} ${connected ? 'connected' : 'disconnected'}`}
|
|
|
+ data-port-type={port.type}
|
|
|
+ data-param-type={port.paramType || ''}
|
|
|
+ onMouseDown={(e) => handlePortMouseDown(e, onPortMouseDown, nodeId, port.id)}
|
|
|
+ onMouseUp={(e) => handlePortMouseUp(e, onPortMouseUp, nodeId, port.id)}
|
|
|
+ title={port.label || port.id}
|
|
|
+ >
|
|
|
+ <div className={`Blueprint-node-port-dot ${connected ? 'solid' : 'hollow'}`}></div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 渲染数据输入端口(带输入框)
|
|
|
+ */
|
|
|
+function renderDataPort(port, node, isInput, connections, handlers) {
|
|
|
+ const { onPortMouseDown, onPortMouseUp, onPortValueChange } = handlers;
|
|
|
+ const portTypeClass = getPortTypeClass(port);
|
|
|
+ const connected = isPortConnected(node.id, port.id, connections);
|
|
|
+ const defaultValue = getPortDefaultValue(port, node);
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div
|
|
|
+ key={port.id}
|
|
|
+ className={`Blueprint-node-port input ${portTypeClass} ${connected ? 'connected' : 'disconnected'}`}
|
|
|
+ data-port-type={port.type}
|
|
|
+ data-param-type={port.paramType || ''}
|
|
|
+ onMouseDown={(e) => {
|
|
|
+ if (e.target.tagName === 'INPUT') {
|
|
|
+ e.stopPropagation();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ e.stopPropagation();
|
|
|
+ onPortMouseDown?.(e, node.id, port.id);
|
|
|
+ }}
|
|
|
+ onMouseUp={(e) => {
|
|
|
+ e.stopPropagation();
|
|
|
+ onPortMouseUp?.(e, node.id, port.id);
|
|
|
+ }}
|
|
|
+ title={port.label || port.id}
|
|
|
+ >
|
|
|
+ <div className={`Blueprint-node-port-dot ${connected ? 'solid' : 'hollow'}`}></div>
|
|
|
+ <span className="Blueprint-node-port-label">{port.label}</span>
|
|
|
+ {!connected && (
|
|
|
+ <input
|
|
|
+ type="text"
|
|
|
+ className="Blueprint-node-port-input"
|
|
|
+ value={defaultValue}
|
|
|
+ placeholder={port.paramType === 'int' ? '0' : '""'}
|
|
|
+ onChange={(e) => handleInputChange(e, onPortValueChange, node.id, port.id, port.paramName)}
|
|
|
+ onClick={(e) => e.stopPropagation()}
|
|
|
+ onMouseDown={(e) => e.stopPropagation()}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+}
|