212 lines
8.0 KiB
TypeScript
212 lines
8.0 KiB
TypeScript
import { memo, useState } from 'react';
|
|
import { Handle, Position } from '@xyflow/react';
|
|
import type { NodeProps } from '@xyflow/react';
|
|
import { Code, Plus, Trash2, ChevronDown, ChevronUp } from 'lucide-react';
|
|
import type { VariablesNodeData } from '../../types/nodes';
|
|
import type { MoLangConfigVariable } from '../../types/npc';
|
|
|
|
export const VariablesNode = memo(({ data }: NodeProps) => {
|
|
const [isExpanded, setIsExpanded] = useState(true);
|
|
const nodeData = data as VariablesNodeData;
|
|
|
|
const handleChange = (variables: MoLangConfigVariable[]) => {
|
|
Object.assign(nodeData, { configVariables: variables });
|
|
};
|
|
|
|
const addVariable = () => {
|
|
const newVariable: MoLangConfigVariable = {
|
|
variableName: `var_${Date.now()}`,
|
|
displayName: 'New Variable',
|
|
description: 'Description',
|
|
type: 'NUMBER',
|
|
defaultValue: 0,
|
|
};
|
|
handleChange([...(nodeData.configVariables || []), newVariable]);
|
|
};
|
|
|
|
const removeVariable = (index: number) => {
|
|
const newVariables = [...(nodeData.configVariables || [])];
|
|
newVariables.splice(index, 1);
|
|
handleChange(newVariables);
|
|
};
|
|
|
|
const updateVariable = (
|
|
index: number,
|
|
field: keyof MoLangConfigVariable,
|
|
value: string | number | boolean
|
|
) => {
|
|
const newVariables = [...(nodeData.configVariables || [])];
|
|
newVariables[index] = { ...newVariables[index], [field]: value };
|
|
handleChange(newVariables);
|
|
};
|
|
|
|
return (
|
|
<div className="bg-white rounded-lg shadow-xl border-2 border-yellow-500 min-w-[350px]">
|
|
{/* Node Header */}
|
|
<div className="bg-yellow-500 text-white px-4 py-2 rounded-t-lg flex items-center justify-between">
|
|
<div className="flex items-center gap-2">
|
|
<Code className="h-4 w-4" />
|
|
<span className="font-semibold text-sm">Config Variables</span>
|
|
</div>
|
|
<button
|
|
onClick={() => setIsExpanded(!isExpanded)}
|
|
className="text-white hover:bg-yellow-600 rounded p-1 transition-colors"
|
|
>
|
|
{isExpanded ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
|
|
</button>
|
|
</div>
|
|
|
|
{/* Input Handle */}
|
|
<Handle
|
|
type="target"
|
|
position={Position.Left}
|
|
className="w-3 h-3 bg-yellow-500 border-2 border-white"
|
|
/>
|
|
|
|
{/* Node Content */}
|
|
{isExpanded && (
|
|
<div className="p-4 space-y-3">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-xs font-medium text-gray-700">
|
|
Variables ({nodeData.configVariables?.length || 0})
|
|
</span>
|
|
<button
|
|
type="button"
|
|
onClick={addVariable}
|
|
className="inline-flex items-center px-2 py-1 text-xs font-medium rounded text-yellow-700 bg-yellow-100 hover:bg-yellow-200 transition-colors"
|
|
>
|
|
<Plus className="h-3 w-3 mr-1" />
|
|
Add
|
|
</button>
|
|
</div>
|
|
|
|
<div className="space-y-3 max-h-80 overflow-y-auto">
|
|
{(!nodeData.configVariables || nodeData.configVariables.length === 0) && (
|
|
<div className="text-xs text-gray-500 italic py-4 text-center">
|
|
No variables defined
|
|
</div>
|
|
)}
|
|
|
|
{nodeData.configVariables?.map((variable, index) => (
|
|
<div
|
|
key={index}
|
|
className="border border-gray-200 rounded-lg p-3 space-y-2 bg-gray-50"
|
|
>
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-xs font-semibold text-gray-700">
|
|
Variable {index + 1}
|
|
</span>
|
|
<button
|
|
type="button"
|
|
onClick={() => removeVariable(index)}
|
|
className="p-1 text-red-600 hover:bg-red-50 rounded transition-colors"
|
|
>
|
|
<Trash2 className="h-3 w-3" />
|
|
</button>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-xs font-medium text-gray-600 mb-1">
|
|
Variable Name
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={variable.variableName}
|
|
onChange={(e) =>
|
|
updateVariable(index, 'variableName', e.target.value)
|
|
}
|
|
className="w-full px-2 py-1 text-xs border border-gray-300 rounded focus:ring-2 focus:ring-yellow-500 focus:border-yellow-500"
|
|
placeholder="my_variable"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-xs font-medium text-gray-600 mb-1">
|
|
Display Name
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={variable.displayName}
|
|
onChange={(e) =>
|
|
updateVariable(index, 'displayName', e.target.value)
|
|
}
|
|
className="w-full px-2 py-1 text-xs border border-gray-300 rounded focus:ring-2 focus:ring-yellow-500 focus:border-yellow-500"
|
|
placeholder="My Variable"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-xs font-medium text-gray-600 mb-1">
|
|
Type
|
|
</label>
|
|
<select
|
|
value={variable.type}
|
|
onChange={(e) =>
|
|
updateVariable(
|
|
index,
|
|
'type',
|
|
e.target.value as 'NUMBER' | 'TEXT' | 'BOOLEAN'
|
|
)
|
|
}
|
|
className="w-full px-2 py-1 text-xs border border-gray-300 rounded focus:ring-2 focus:ring-yellow-500 focus:border-yellow-500"
|
|
>
|
|
<option value="NUMBER">Number</option>
|
|
<option value="TEXT">Text</option>
|
|
<option value="BOOLEAN">Boolean</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-xs font-medium text-gray-600 mb-1">
|
|
Default Value
|
|
</label>
|
|
{variable.type === 'BOOLEAN' ? (
|
|
<select
|
|
value={variable.defaultValue.toString()}
|
|
onChange={(e) =>
|
|
updateVariable(
|
|
index,
|
|
'defaultValue',
|
|
e.target.value === 'true'
|
|
)
|
|
}
|
|
className="w-full px-2 py-1 text-xs border border-gray-300 rounded focus:ring-2 focus:ring-yellow-500 focus:border-yellow-500"
|
|
>
|
|
<option value="true">True</option>
|
|
<option value="false">False</option>
|
|
</select>
|
|
) : (
|
|
<input
|
|
type={variable.type === 'NUMBER' ? 'number' : 'text'}
|
|
value={String(variable.defaultValue)}
|
|
onChange={(e) =>
|
|
updateVariable(
|
|
index,
|
|
'defaultValue',
|
|
variable.type === 'NUMBER'
|
|
? parseFloat(e.target.value) || 0
|
|
: e.target.value
|
|
)
|
|
}
|
|
className="w-full px-2 py-1 text-xs border border-gray-300 rounded focus:ring-2 focus:ring-yellow-500 focus:border-yellow-500"
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Output Handle */}
|
|
<Handle
|
|
type="source"
|
|
position={Position.Right}
|
|
className="w-3 h-3 bg-yellow-500 border-2 border-white"
|
|
/>
|
|
</div>
|
|
);
|
|
});
|
|
|
|
VariablesNode.displayName = 'VariablesNode';
|