Files
npc-config-app/src/components/nodes/VariablesNode.tsx

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';