[ISSUE-1] Add src/components/nodes/BasicInfoNode.tsx - Modular NPC system
This commit is contained in:
148
src/components/nodes/BasicInfoNode.tsx
Normal file
148
src/components/nodes/BasicInfoNode.tsx
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
import { memo, useState } from 'react';
|
||||||
|
import { Handle, Position } from '@xyflow/react';
|
||||||
|
import type { NodeProps } from '@xyflow/react';
|
||||||
|
import { Settings, ChevronDown, ChevronUp } from 'lucide-react';
|
||||||
|
import type { BasicInfoNodeData } from '../../types/nodes';
|
||||||
|
|
||||||
|
export const BasicInfoNode = memo(({ data }: NodeProps) => {
|
||||||
|
const [isExpanded, setIsExpanded] = useState(true);
|
||||||
|
const nodeData = data as BasicInfoNodeData;
|
||||||
|
|
||||||
|
const handleChange = (field: keyof BasicInfoNodeData, value: string | number | boolean | string[] | undefined | { width: number; height: number }) => {
|
||||||
|
// Update node data - in a real implementation, this would use a store or context
|
||||||
|
// For now, we'll use data mutation which React Flow handles
|
||||||
|
Object.assign(nodeData, { [field]: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNamesChange = (names: string) => {
|
||||||
|
handleChange('names', names.split(',').map(name => name.trim()).filter(name => name));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAspectsChange = (aspects: string) => {
|
||||||
|
handleChange('aspects', aspects ? aspects.split(',').map(aspect => aspect.trim()).filter(aspect => aspect) : undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-white rounded-lg shadow-xl border-2 border-blue-500 min-w-[350px]">
|
||||||
|
{/* Node Header */}
|
||||||
|
<div className="bg-blue-500 text-white px-4 py-2 rounded-t-lg flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Settings className="h-4 w-4" />
|
||||||
|
<span className="font-semibold text-sm">Basic Info</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => setIsExpanded(!isExpanded)}
|
||||||
|
className="text-white hover:bg-blue-600 rounded p-1 transition-colors"
|
||||||
|
>
|
||||||
|
{isExpanded ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Node Content */}
|
||||||
|
{isExpanded && (
|
||||||
|
<div className="p-4 space-y-3">
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-medium text-gray-700 mb-1">
|
||||||
|
Resource Identifier
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={nodeData.resourceIdentifier}
|
||||||
|
onChange={(e) => handleChange('resourceIdentifier', e.target.value)}
|
||||||
|
className="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
placeholder="cobblemon:npc_name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-medium text-gray-700 mb-1">
|
||||||
|
Names (comma-separated)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={nodeData.names.join(', ')}
|
||||||
|
onChange={(e) => handleNamesChange(e.target.value)}
|
||||||
|
className="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
placeholder="NPC Name 1, NPC Name 2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-medium text-gray-700 mb-1">Model Scale</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
value={nodeData.modelScale || 0.9375}
|
||||||
|
onChange={(e) => handleChange('modelScale', parseFloat(e.target.value))}
|
||||||
|
className="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-medium text-gray-700 mb-1">
|
||||||
|
Aspects (comma-separated)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={nodeData.aspects?.join(', ') || ''}
|
||||||
|
onChange={(e) => handleAspectsChange(e.target.value)}
|
||||||
|
className="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
placeholder="aspect1, aspect2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="border-t pt-3">
|
||||||
|
<div className="text-xs font-medium text-gray-700 mb-2">Behavior</div>
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
<label className="flex items-center text-xs">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={nodeData.isInvulnerable || false}
|
||||||
|
onChange={(e) => handleChange('isInvulnerable', e.target.checked)}
|
||||||
|
className="mr-1.5 h-3 w-3 text-blue-600 rounded focus:ring-blue-500"
|
||||||
|
/>
|
||||||
|
Invulnerable
|
||||||
|
</label>
|
||||||
|
<label className="flex items-center text-xs">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={!(nodeData.canDespawn ?? true)}
|
||||||
|
onChange={(e) => handleChange('canDespawn', !e.target.checked)}
|
||||||
|
className="mr-1.5 h-3 w-3 text-blue-600 rounded focus:ring-blue-500"
|
||||||
|
/>
|
||||||
|
Persistent
|
||||||
|
</label>
|
||||||
|
<label className="flex items-center text-xs">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={nodeData.isMovable ?? true}
|
||||||
|
onChange={(e) => handleChange('isMovable', e.target.checked)}
|
||||||
|
className="mr-1.5 h-3 w-3 text-blue-600 rounded focus:ring-blue-500"
|
||||||
|
/>
|
||||||
|
Movable
|
||||||
|
</label>
|
||||||
|
<label className="flex items-center text-xs">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={nodeData.hideNameTag || false}
|
||||||
|
onChange={(e) => handleChange('hideNameTag', e.target.checked)}
|
||||||
|
className="mr-1.5 h-3 w-3 text-blue-600 rounded focus:ring-blue-500"
|
||||||
|
/>
|
||||||
|
Hide Nametag
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Output Handle */}
|
||||||
|
<Handle
|
||||||
|
type="source"
|
||||||
|
position={Position.Right}
|
||||||
|
className="w-3 h-3 bg-blue-500 border-2 border-white"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
BasicInfoNode.displayName = 'BasicInfoNode';
|
||||||
Reference in New Issue
Block a user