调试实体和组件属性
This commit is contained in:
@@ -13,19 +13,42 @@ interface EntityInspectorProps {
|
||||
|
||||
export function EntityInspector({ entityStore: _entityStore, messageHub }: EntityInspectorProps) {
|
||||
const [selectedEntity, setSelectedEntity] = useState<Entity | null>(null);
|
||||
const [remoteEntity, setRemoteEntity] = useState<any | null>(null);
|
||||
const [remoteEntityDetails, setRemoteEntityDetails] = useState<any | null>(null);
|
||||
const [showAddComponent, setShowAddComponent] = useState(false);
|
||||
const [expandedComponents, setExpandedComponents] = useState<Set<number>>(new Set());
|
||||
|
||||
useEffect(() => {
|
||||
const handleSelection = (data: { entity: Entity | null }) => {
|
||||
setSelectedEntity(data.entity);
|
||||
setRemoteEntity(null);
|
||||
setRemoteEntityDetails(null);
|
||||
setShowAddComponent(false);
|
||||
};
|
||||
|
||||
const handleRemoteSelection = (data: { entity: any }) => {
|
||||
setRemoteEntity(data.entity);
|
||||
setRemoteEntityDetails(null);
|
||||
setSelectedEntity(null);
|
||||
setShowAddComponent(false);
|
||||
};
|
||||
|
||||
const handleEntityDetails = (event: Event) => {
|
||||
const customEvent = event as CustomEvent;
|
||||
const details = customEvent.detail;
|
||||
console.log('[EntityInspector] Received entity details:', details);
|
||||
setRemoteEntityDetails(details);
|
||||
};
|
||||
|
||||
const unsubSelect = messageHub.subscribe('entity:selected', handleSelection);
|
||||
const unsubRemoteSelect = messageHub.subscribe('remote-entity:selected', handleRemoteSelection);
|
||||
|
||||
window.addEventListener('profiler:entity-details', handleEntityDetails);
|
||||
|
||||
return () => {
|
||||
unsubSelect();
|
||||
unsubRemoteSelect();
|
||||
window.removeEventListener('profiler:entity-details', handleEntityDetails);
|
||||
};
|
||||
}, [messageHub]);
|
||||
|
||||
@@ -82,7 +105,215 @@ export function EntityInspector({ entityStore: _entityStore, messageHub }: Entit
|
||||
});
|
||||
};
|
||||
|
||||
if (!selectedEntity) {
|
||||
const renderRemoteProperty = (key: string, value: any) => {
|
||||
if (value === null || value === undefined) {
|
||||
return (
|
||||
<div key={key} className="property-field">
|
||||
<label className="property-label">{key}</label>
|
||||
<span className="property-value-text">null</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return (
|
||||
<div key={key} className="property-field">
|
||||
<label className="property-label">{key}</label>
|
||||
<div style={{ flex: 1, display: 'flex', flexWrap: 'wrap', gap: '4px' }}>
|
||||
{value.length === 0 ? (
|
||||
<span className="property-value-text" style={{ opacity: 0.5 }}>Empty Array</span>
|
||||
) : (
|
||||
value.map((item, index) => (
|
||||
<span
|
||||
key={index}
|
||||
style={{
|
||||
padding: '2px 6px',
|
||||
background: 'var(--color-bg-inset)',
|
||||
border: '1px solid var(--color-border-default)',
|
||||
borderRadius: '3px',
|
||||
fontSize: '10px',
|
||||
color: 'var(--color-text-primary)',
|
||||
fontFamily: 'var(--font-family-mono)'
|
||||
}}
|
||||
>
|
||||
{typeof item === 'object' ? JSON.stringify(item) : String(item)}
|
||||
</span>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const valueType = typeof value;
|
||||
|
||||
if (valueType === 'boolean') {
|
||||
return (
|
||||
<div key={key} className="property-field property-field-boolean">
|
||||
<label className="property-label">{key}</label>
|
||||
<div className={`property-toggle ${value ? 'property-toggle-on' : 'property-toggle-off'} property-toggle-readonly`}>
|
||||
<span className="property-toggle-thumb" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (valueType === 'number') {
|
||||
return (
|
||||
<div key={key} className="property-field">
|
||||
<label className="property-label">{key}</label>
|
||||
<input
|
||||
type="number"
|
||||
className="property-input property-input-number"
|
||||
value={value}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (valueType === 'string') {
|
||||
return (
|
||||
<div key={key} className="property-field">
|
||||
<label className="property-label">{key}</label>
|
||||
<input
|
||||
type="text"
|
||||
className="property-input property-input-text"
|
||||
value={value}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (valueType === 'object' && value.minX !== undefined && value.maxX !== undefined && value.minY !== undefined && value.maxY !== undefined) {
|
||||
return (
|
||||
<div key={key} className="property-field" style={{ flexDirection: 'column', alignItems: 'stretch' }}>
|
||||
<label className="property-label" style={{ flex: 'none', marginBottom: '4px' }}>{key}</label>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
||||
<div className="property-vector-compact">
|
||||
<div className="property-vector-axis-compact">
|
||||
<span className="property-vector-axis-label property-vector-axis-x">X</span>
|
||||
<input
|
||||
type="number"
|
||||
className="property-input property-input-number-compact"
|
||||
value={value.minX}
|
||||
disabled
|
||||
placeholder="Min"
|
||||
/>
|
||||
</div>
|
||||
<div className="property-vector-axis-compact">
|
||||
<span className="property-vector-axis-label property-vector-axis-x">X</span>
|
||||
<input
|
||||
type="number"
|
||||
className="property-input property-input-number-compact"
|
||||
value={value.maxX}
|
||||
disabled
|
||||
placeholder="Max"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="property-vector-compact">
|
||||
<div className="property-vector-axis-compact">
|
||||
<span className="property-vector-axis-label property-vector-axis-y">Y</span>
|
||||
<input
|
||||
type="number"
|
||||
className="property-input property-input-number-compact"
|
||||
value={value.minY}
|
||||
disabled
|
||||
placeholder="Min"
|
||||
/>
|
||||
</div>
|
||||
<div className="property-vector-axis-compact">
|
||||
<span className="property-vector-axis-label property-vector-axis-y">Y</span>
|
||||
<input
|
||||
type="number"
|
||||
className="property-input property-input-number-compact"
|
||||
value={value.maxY}
|
||||
disabled
|
||||
placeholder="Max"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (valueType === 'object' && value.x !== undefined && value.y !== undefined) {
|
||||
if (value.z !== undefined) {
|
||||
return (
|
||||
<div key={key} className="property-field">
|
||||
<label className="property-label">{key}</label>
|
||||
<div className="property-vector-compact">
|
||||
<div className="property-vector-axis-compact">
|
||||
<span className="property-vector-axis-label property-vector-axis-x">X</span>
|
||||
<input
|
||||
type="number"
|
||||
className="property-input property-input-number-compact"
|
||||
value={value.x}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div className="property-vector-axis-compact">
|
||||
<span className="property-vector-axis-label property-vector-axis-y">Y</span>
|
||||
<input
|
||||
type="number"
|
||||
className="property-input property-input-number-compact"
|
||||
value={value.y}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div className="property-vector-axis-compact">
|
||||
<span className="property-vector-axis-label property-vector-axis-z">Z</span>
|
||||
<input
|
||||
type="number"
|
||||
className="property-input property-input-number-compact"
|
||||
value={value.z}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div key={key} className="property-field">
|
||||
<label className="property-label">{key}</label>
|
||||
<div className="property-vector-compact">
|
||||
<div className="property-vector-axis-compact">
|
||||
<span className="property-vector-axis-label property-vector-axis-x">X</span>
|
||||
<input
|
||||
type="number"
|
||||
className="property-input property-input-number-compact"
|
||||
value={value.x}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div className="property-vector-axis-compact">
|
||||
<span className="property-vector-axis-label property-vector-axis-y">Y</span>
|
||||
<input
|
||||
type="number"
|
||||
className="property-input property-input-number-compact"
|
||||
value={value.y}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={key} className="property-field">
|
||||
<label className="property-label">{key}</label>
|
||||
<span className="property-value-text">{JSON.stringify(value)}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
if (!selectedEntity && !remoteEntity) {
|
||||
return (
|
||||
<div className="entity-inspector">
|
||||
<div className="inspector-header">
|
||||
@@ -100,7 +331,102 @@ export function EntityInspector({ entityStore: _entityStore, messageHub }: Entit
|
||||
);
|
||||
}
|
||||
|
||||
const components = selectedEntity.components;
|
||||
// 显示远程实体
|
||||
if (remoteEntity) {
|
||||
const displayData = remoteEntityDetails || remoteEntity;
|
||||
const hasDetailedComponents = remoteEntityDetails && remoteEntityDetails.components && remoteEntityDetails.components.length > 0;
|
||||
|
||||
return (
|
||||
<div className="entity-inspector">
|
||||
<div className="inspector-header">
|
||||
<FileSearch size={16} className="inspector-header-icon" />
|
||||
<h3>Inspector</h3>
|
||||
</div>
|
||||
<div className="inspector-content scrollable">
|
||||
<div className="inspector-section">
|
||||
<div className="section-header">
|
||||
<Settings size={12} className="section-icon" />
|
||||
<span>Entity Info (Remote)</span>
|
||||
</div>
|
||||
<div className="section-content">
|
||||
<div className="info-row">
|
||||
<span className="info-label">ID:</span>
|
||||
<span className="info-value">{displayData.id}</span>
|
||||
</div>
|
||||
<div className="info-row">
|
||||
<span className="info-label">Name:</span>
|
||||
<span className="info-value">{displayData.name}</span>
|
||||
</div>
|
||||
<div className="info-row">
|
||||
<span className="info-label">Enabled:</span>
|
||||
<span className="info-value">{displayData.enabled ? 'Yes' : 'No'}</span>
|
||||
</div>
|
||||
{displayData.scene && (
|
||||
<div className="info-row">
|
||||
<span className="info-label">Scene:</span>
|
||||
<span className="info-value">{displayData.scene}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="inspector-section">
|
||||
<div className="section-header">
|
||||
<Settings size={12} className="section-icon" />
|
||||
<span>Components ({displayData.componentCount})</span>
|
||||
</div>
|
||||
<div className="section-content">
|
||||
{hasDetailedComponents ? (
|
||||
<ul className="component-list">
|
||||
{remoteEntityDetails!.components.map((component: any, index: number) => {
|
||||
const isExpanded = expandedComponents.has(index);
|
||||
return (
|
||||
<li key={index} className={`component-item ${isExpanded ? 'expanded' : ''}`}>
|
||||
<div className="component-header" onClick={() => toggleComponentExpanded(index)}>
|
||||
<button
|
||||
className="component-expand-btn"
|
||||
title={isExpanded ? 'Collapse' : 'Expand'}
|
||||
>
|
||||
{isExpanded ? <ChevronDown size={14} /> : <ChevronRight size={14} />}
|
||||
</button>
|
||||
<Settings size={14} className="component-icon" />
|
||||
<span className="component-name">{component.typeName}</span>
|
||||
</div>
|
||||
{isExpanded && (
|
||||
<div className="component-properties animate-slideDown">
|
||||
<div className="property-inspector">
|
||||
{Object.entries(component.properties).map(([key, value]) =>
|
||||
renderRemoteProperty(key, value)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
) : displayData.componentTypes && displayData.componentTypes.length > 0 ? (
|
||||
<ul className="component-list">
|
||||
{displayData.componentTypes.map((componentType: string, index: number) => (
|
||||
<li key={index} className="component-item">
|
||||
<div className="component-header">
|
||||
<Settings size={14} className="component-icon" />
|
||||
<span className="component-name">{componentType}</span>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<div className="empty-state-small">No components</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const components = selectedEntity!.components;
|
||||
|
||||
return (
|
||||
<div className="entity-inspector">
|
||||
@@ -121,11 +447,11 @@ export function EntityInspector({ entityStore: _entityStore, messageHub }: Entit
|
||||
</div>
|
||||
<div className="info-row">
|
||||
<span className="info-label">Name:</span>
|
||||
<span className="info-value">Entity {selectedEntity.id}</span>
|
||||
<span className="info-value">Entity {selectedEntity!.id}</span>
|
||||
</div>
|
||||
<div className="info-row">
|
||||
<span className="info-label">Enabled:</span>
|
||||
<span className="info-value">{selectedEntity.enabled ? 'Yes' : 'No'}</span>
|
||||
<span className="info-value">{selectedEntity!.enabled ? 'Yes' : 'No'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -74,6 +74,27 @@ export function SceneHierarchy({ entityStore, messageHub }: SceneHierarchyProps)
|
||||
entityStore.selectEntity(entity);
|
||||
};
|
||||
|
||||
const handleRemoteEntityClick = (entity: RemoteEntity) => {
|
||||
setSelectedId(entity.id);
|
||||
|
||||
// 请求完整的实体详情(包含组件属性)
|
||||
const profilerService = (window as any).__PROFILER_SERVICE__ as ProfilerService | undefined;
|
||||
if (profilerService) {
|
||||
profilerService.requestEntityDetails(entity.id);
|
||||
}
|
||||
|
||||
// 先发布基本信息,详细信息稍后通过 ProfilerService 异步返回
|
||||
messageHub.publish('remote-entity:selected', {
|
||||
entity: {
|
||||
id: entity.id,
|
||||
name: entity.name,
|
||||
enabled: entity.enabled,
|
||||
componentCount: entity.componentCount,
|
||||
componentTypes: entity.componentTypes
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Determine which entities to display
|
||||
const displayEntities = isRemoteConnected ? remoteEntities : entities;
|
||||
const showRemoteIndicator = isRemoteConnected && remoteEntities.length > 0;
|
||||
@@ -105,13 +126,14 @@ export function SceneHierarchy({ entityStore, messageHub }: SceneHierarchyProps)
|
||||
{remoteEntities.map(entity => (
|
||||
<li
|
||||
key={entity.id}
|
||||
className={`entity-item remote-entity ${!entity.enabled ? 'disabled' : ''}`}
|
||||
title={`${entity.name} - ${entity.components.join(', ')}`}
|
||||
className={`entity-item remote-entity ${selectedId === entity.id ? 'selected' : ''} ${!entity.enabled ? 'disabled' : ''}`}
|
||||
title={`${entity.name} - ${entity.componentTypes.join(', ')}`}
|
||||
onClick={() => handleRemoteEntityClick(entity)}
|
||||
>
|
||||
<Box size={14} className="entity-icon" />
|
||||
<span className="entity-name">{entity.name}</span>
|
||||
{entity.components.length > 0 && (
|
||||
<span className="component-count">{entity.components.length}</span>
|
||||
{entity.componentCount > 0 && (
|
||||
<span className="component-count">{entity.componentCount}</span>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user