Files
esengine/packages/editor-app/src/components/AddComponent.tsx
2025-10-15 17:15:05 +08:00

122 lines
4.1 KiB
TypeScript

import { useState, useEffect } from 'react';
import { Entity } from '@esengine/ecs-framework';
import { ComponentRegistry, ComponentTypeInfo } from '@esengine/editor-core';
import './AddComponent.css';
interface AddComponentProps {
entity: Entity;
componentRegistry: ComponentRegistry;
onAdd: (componentName: string) => void;
onCancel: () => void;
}
export function AddComponent({ entity, componentRegistry, onAdd, onCancel }: AddComponentProps) {
const [components, setComponents] = useState<ComponentTypeInfo[]>([]);
const [selectedComponent, setSelectedComponent] = useState<string | null>(null);
const [filter, setFilter] = useState('');
useEffect(() => {
if (!componentRegistry) {
console.error('ComponentRegistry is null');
return;
}
const allComponents = componentRegistry.getAllComponents();
console.log('All registered components:', allComponents);
allComponents.forEach(comp => {
console.log(`Component ${comp.name}: has type = ${!!comp.type}`);
});
const existingComponentNames = entity.components.map(c => c.constructor.name);
const availableComponents = allComponents.filter(
comp => comp.type && !existingComponentNames.includes(comp.name)
);
console.log('Available components to add:', availableComponents);
console.log('Components filtered out:', allComponents.filter(comp => !comp.type).map(c => c.name));
setComponents(availableComponents);
}, [entity, componentRegistry]);
const filteredComponents = components.filter(comp =>
comp.name.toLowerCase().includes(filter.toLowerCase()) ||
comp.category?.toLowerCase().includes(filter.toLowerCase())
);
const handleAdd = () => {
if (selectedComponent) {
onAdd(selectedComponent);
}
};
const groupedComponents = filteredComponents.reduce((groups, comp) => {
const category = comp.category || 'Other';
if (!groups[category]) {
groups[category] = [];
}
groups[category].push(comp);
return groups;
}, {} as Record<string, ComponentTypeInfo[]>);
return (
<div className="modal-overlay" onClick={onCancel}>
<div className="add-component-dialog" onClick={(e) => e.stopPropagation()}>
<div className="dialog-header">
<h3>Add Component</h3>
<button className="close-btn" onClick={onCancel}>&times;</button>
</div>
<div className="dialog-content">
<input
type="text"
className="component-filter"
placeholder="Search components..."
value={filter}
onChange={(e) => setFilter(e.target.value)}
autoFocus
/>
<div className="component-list">
{Object.keys(groupedComponents).length === 0 ? (
<div className="empty-message">No available components</div>
) : (
Object.entries(groupedComponents).map(([category, comps]) => (
<div key={category} className="component-category">
<div className="category-header">{category}</div>
{comps.map(comp => (
<div
key={comp.name}
className={`component-option ${selectedComponent === comp.name ? 'selected' : ''}`}
onClick={() => setSelectedComponent(comp.name)}
onDoubleClick={handleAdd}
>
<div className="component-name">{comp.name}</div>
{comp.description && (
<div className="component-description">{comp.description}</div>
)}
</div>
))}
</div>
))
)}
</div>
</div>
<div className="dialog-footer">
<button className="btn btn-cancel" onClick={onCancel}>
Cancel
</button>
<button
className="btn btn-primary"
onClick={handleAdd}
disabled={!selectedComponent}
>
Add Component
</button>
</div>
</div>
</div>
);
}