import { useState, useEffect } from 'react'; import { Entity } from '@esengine/ecs-framework'; import { EntityStoreService, MessageHub } from '@esengine/editor-core'; import { PropertyInspector } from './PropertyInspector'; import { FileSearch, ChevronDown, ChevronRight, X, Settings } from 'lucide-react'; import '../styles/EntityInspector.css'; interface EntityInspectorProps { entityStore: EntityStoreService; messageHub: MessageHub; } export function EntityInspector({ entityStore: _entityStore, messageHub }: EntityInspectorProps) { const [selectedEntity, setSelectedEntity] = useState(null); const [remoteEntity, setRemoteEntity] = useState(null); const [remoteEntityDetails, setRemoteEntityDetails] = useState(null); const [expandedComponents, setExpandedComponents] = useState>(new Set()); const [componentVersion, setComponentVersion] = useState(0); useEffect(() => { const handleSelection = (data: { entity: Entity | null }) => { setSelectedEntity((prev) => { // Only reset version when selecting a different entity // 只在选择不同实体时重置版本 if (prev?.id !== data.entity?.id) { setComponentVersion(0); } else { // Same entity re-selected, trigger refresh // 同一实体重新选择,触发刷新 setComponentVersion((v) => v + 1); } return data.entity; }); setRemoteEntity(null); setRemoteEntityDetails(null); }; const handleRemoteSelection = (data: { entity: any }) => { setRemoteEntity(data.entity); setRemoteEntityDetails(null); setSelectedEntity(null); }; const handleEntityDetails = (event: Event) => { const customEvent = event as CustomEvent; const details = customEvent.detail; setRemoteEntityDetails(details); }; const handleComponentChange = () => { setComponentVersion((prev) => prev + 1); }; const unsubSelect = messageHub.subscribe('entity:selected', handleSelection); const unsubRemoteSelect = messageHub.subscribe('remote-entity:selected', handleRemoteSelection); const unsubComponentAdded = messageHub.subscribe('component:added', handleComponentChange); const unsubComponentRemoved = messageHub.subscribe('component:removed', handleComponentChange); const unsubPropertyChanged = messageHub.subscribe('component:property:changed', handleComponentChange); window.addEventListener('profiler:entity-details', handleEntityDetails); return () => { unsubSelect(); unsubRemoteSelect(); unsubComponentAdded(); unsubComponentRemoved(); unsubPropertyChanged(); window.removeEventListener('profiler:entity-details', handleEntityDetails); }; }, [messageHub]); const handleRemoveComponent = (index: number) => { if (!selectedEntity) return; const component = selectedEntity.components[index]; if (component) { selectedEntity.removeComponent(component); messageHub.publish('component:removed', { entity: selectedEntity, component }); } }; const toggleComponentExpanded = (index: number) => { setExpandedComponents((prev) => { const newSet = new Set(prev); if (newSet.has(index)) { newSet.delete(index); } else { newSet.add(index); } return newSet; }); }; const handlePropertyChange = (component: any, propertyName: string, value: any) => { if (!selectedEntity) return; // Actually update the component property // 实际更新组件属性 component[propertyName] = value; messageHub.publish('component:property:changed', { entity: selectedEntity, component, propertyName, value }); }; const renderRemoteProperty = (key: string, value: any) => { if (value === null || value === undefined) { return (
null
); } if (Array.isArray(value)) { return (
{value.length === 0 ? ( Empty Array ) : ( value.map((item, index) => ( {typeof item === 'object' ? JSON.stringify(item) : String(item)} )) )}
); } const valueType = typeof value; if (valueType === 'boolean') { return (
); } if (valueType === 'number') { return (
); } if (valueType === 'string') { return (
); } if (valueType === 'object' && value.r !== undefined && value.g !== undefined && value.b !== undefined) { const r = Math.round(value.r * 255); const g = Math.round(value.g * 255); const b = Math.round(value.b * 255); const a = value.a !== undefined ? value.a : 1; const hexColor = `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`; return (
); } if (valueType === 'object' && value.minX !== undefined && value.maxX !== undefined && value.minY !== undefined && value.maxY !== undefined) { return (
X
X
Y
Y
); } if (valueType === 'object' && value.x !== undefined && value.y !== undefined) { if (value.z !== undefined) { return (
X
Y
Z
); } else { return (
X
Y
); } } return (
{JSON.stringify(value)}
); }; if (!selectedEntity && !remoteEntity) { return (

Inspector

No entity selected
Select an entity from the hierarchy
); } // 显示远程实体 if (remoteEntity) { const displayData = remoteEntityDetails || remoteEntity; const hasDetailedComponents = remoteEntityDetails && remoteEntityDetails.components && remoteEntityDetails.components.length > 0; return (

Inspector

Entity Info (Remote)
ID: {displayData.id}
Name: {displayData.name}
Enabled: {displayData.enabled ? 'Yes' : 'No'}
{displayData.scene && (
Scene: {displayData.scene}
)}
Components ({displayData.componentCount})
{hasDetailedComponents ? (
    {remoteEntityDetails!.components.map((component: any, index: number) => { const isExpanded = expandedComponents.has(index); return (
  • toggleComponentExpanded(index)}> {component.typeName}
    {isExpanded && (
    {Object.entries(component.properties).map(([key, value]) => renderRemoteProperty(key, value) )}
    )}
  • ); })}
) : displayData.componentTypes && displayData.componentTypes.length > 0 ? (
    {displayData.componentTypes.map((componentType: string, index: number) => (
  • {componentType}
  • ))}
) : (
No components
)}
); } const components = selectedEntity!.components; return (

Inspector

Entity Info
ID: {selectedEntity!.id}
Name: Entity {selectedEntity!.id}
Enabled: {selectedEntity!.enabled ? 'Yes' : 'No'}
Components ({components.length})
{components.length === 0 ? (
No components
) : (
    {components.map((component, index) => { const isExpanded = expandedComponents.has(index); return (
  • toggleComponentExpanded(index)}> {component.constructor.name}
    {isExpanded && (
    handlePropertyChange(component, propertyName, value)} />
    )}
  • ); })}
)}
); }