Files
esengine/packages/behavior-tree-editor/src/providers/BehaviorTreeNodeInspectorProvider.tsx

358 lines
13 KiB
TypeScript
Raw Normal View History

import {
React,
useState,
useCallback,
type IInspectorProvider,
type InspectorContext,
MessageHub,
FieldEditorRegistry,
type FieldEditorContext,
PluginAPI,
} from '@esengine/editor-runtime';
refactor(editor): 提取行为树编辑器为独立包并重构编辑器架构 (#216) * refactor(editor): 提取行为树编辑器为独立包并重构编辑器架构 * feat(editor): 添加插件市场功能 * feat(editor): 重构插件市场以支持版本管理和ZIP打包 * feat(editor): 重构插件发布流程并修复React渲染警告 * fix(plugin): 修复插件发布和市场的路径不一致问题 * feat: 重构插件发布流程并添加插件删除功能 * fix(editor): 完善插件删除功能并修复多个关键问题 * fix(auth): 修复自动登录与手动登录的竞态条件问题 * feat(editor): 重构插件管理流程 * feat(editor): 支持 ZIP 文件直接发布插件 - 新增 PluginSourceParser 解析插件源 - 重构发布流程支持文件夹和 ZIP 两种方式 - 优化发布向导 UI * feat(editor): 插件市场支持多版本安装 - 插件解压到项目 plugins 目录 - 新增 Tauri 后端安装/卸载命令 - 支持选择任意版本安装 - 修复打包逻辑,保留完整 dist 目录结构 * feat(editor): 个人中心支持多版本管理 - 合并同一插件的不同版本 - 添加版本历史展开/折叠功能 - 禁止有待审核 PR 时更新插件 * fix(editor): 修复 InspectorRegistry 服务注册 - InspectorRegistry 实现 IService 接口 - 注册到 Core.services 供插件使用 * feat(behavior-tree-editor): 完善插件注册和文件操作 - 添加文件创建模板和操作处理器 - 实现右键菜单创建行为树功能 - 修复文件读取权限问题(使用 Tauri 命令) - 添加 BehaviorTreeEditorPanel 组件 - 修复 rollup 配置支持动态导入 * feat(plugin): 完善插件构建和发布流程 * fix(behavior-tree-editor): 完整恢复编辑器并修复 Toast 集成 * fix(behavior-tree-editor): 修复节点选中、连线跟随和文件加载问题并优化性能 * fix(behavior-tree-editor): 修复端口连接失败问题并优化连线样式 * refactor(behavior-tree-editor): 移除调试面板功能简化代码结构 * refactor(behavior-tree-editor): 清理冗余代码合并重复逻辑 * feat(behavior-tree-editor): 完善编辑器核心功能增强扩展性 * fix(lint): 修复ESLint错误确保CI通过 * refactor(behavior-tree-editor): 优化编辑器工具栏和编译器功能 * refactor(behavior-tree-editor): 清理技术债务,优化代码质量 * fix(editor-app): 修复字符串替换安全问题
2025-11-18 14:46:51 +08:00
import { Node as BehaviorTreeNode } from '../domain/models/Node';
import type { PropertyDefinition } from '@esengine/behavior-tree';
refactor(editor): 提取行为树编辑器为独立包并重构编辑器架构 (#216) * refactor(editor): 提取行为树编辑器为独立包并重构编辑器架构 * feat(editor): 添加插件市场功能 * feat(editor): 重构插件市场以支持版本管理和ZIP打包 * feat(editor): 重构插件发布流程并修复React渲染警告 * fix(plugin): 修复插件发布和市场的路径不一致问题 * feat: 重构插件发布流程并添加插件删除功能 * fix(editor): 完善插件删除功能并修复多个关键问题 * fix(auth): 修复自动登录与手动登录的竞态条件问题 * feat(editor): 重构插件管理流程 * feat(editor): 支持 ZIP 文件直接发布插件 - 新增 PluginSourceParser 解析插件源 - 重构发布流程支持文件夹和 ZIP 两种方式 - 优化发布向导 UI * feat(editor): 插件市场支持多版本安装 - 插件解压到项目 plugins 目录 - 新增 Tauri 后端安装/卸载命令 - 支持选择任意版本安装 - 修复打包逻辑,保留完整 dist 目录结构 * feat(editor): 个人中心支持多版本管理 - 合并同一插件的不同版本 - 添加版本历史展开/折叠功能 - 禁止有待审核 PR 时更新插件 * fix(editor): 修复 InspectorRegistry 服务注册 - InspectorRegistry 实现 IService 接口 - 注册到 Core.services 供插件使用 * feat(behavior-tree-editor): 完善插件注册和文件操作 - 添加文件创建模板和操作处理器 - 实现右键菜单创建行为树功能 - 修复文件读取权限问题(使用 Tauri 命令) - 添加 BehaviorTreeEditorPanel 组件 - 修复 rollup 配置支持动态导入 * feat(plugin): 完善插件构建和发布流程 * fix(behavior-tree-editor): 完整恢复编辑器并修复 Toast 集成 * fix(behavior-tree-editor): 修复节点选中、连线跟随和文件加载问题并优化性能 * fix(behavior-tree-editor): 修复端口连接失败问题并优化连线样式 * refactor(behavior-tree-editor): 移除调试面板功能简化代码结构 * refactor(behavior-tree-editor): 清理冗余代码合并重复逻辑 * feat(behavior-tree-editor): 完善编辑器核心功能增强扩展性 * fix(lint): 修复ESLint错误确保CI通过 * refactor(behavior-tree-editor): 优化编辑器工具栏和编译器功能 * refactor(behavior-tree-editor): 清理技术债务,优化代码质量 * fix(editor-app): 修复字符串替换安全问题
2025-11-18 14:46:51 +08:00
/**
*
*/
interface PropertyEditorProps {
property: PropertyDefinition;
value: any;
onChange: (name: string, value: any) => void;
}
const PropertyEditor: React.FC<PropertyEditorProps> = ({ property, value, onChange }) => {
const handleChange = useCallback((newValue: any) => {
onChange(property.name, newValue);
}, [property.name, onChange]);
const renderInput = () => {
// 特殊处理 treeAssetId 字段使用 asset 编辑器
if (property.name === 'treeAssetId') {
const fieldRegistry = PluginAPI.resolve<FieldEditorRegistry>(FieldEditorRegistry);
const assetEditor = fieldRegistry.getEditor('asset');
if (assetEditor) {
const context: FieldEditorContext = {
readonly: false,
metadata: {
fileExtension: '.btree',
placeholder: '拖拽或选择行为树文件'
}
};
return assetEditor.render({
label: '',
value: value ?? property.defaultValue ?? null,
onChange: handleChange,
context
});
}
}
// 检查是否有特定的字段编辑器类型
if (property.fieldEditor) {
const fieldRegistry = PluginAPI.resolve<FieldEditorRegistry>(FieldEditorRegistry);
const editor = fieldRegistry.getEditor(property.fieldEditor.type);
if (editor) {
const context: FieldEditorContext = {
readonly: false,
metadata: property.fieldEditor.options
};
return editor.render({
label: '',
value: value ?? property.defaultValue,
onChange: handleChange,
context
});
}
}
refactor(editor): 提取行为树编辑器为独立包并重构编辑器架构 (#216) * refactor(editor): 提取行为树编辑器为独立包并重构编辑器架构 * feat(editor): 添加插件市场功能 * feat(editor): 重构插件市场以支持版本管理和ZIP打包 * feat(editor): 重构插件发布流程并修复React渲染警告 * fix(plugin): 修复插件发布和市场的路径不一致问题 * feat: 重构插件发布流程并添加插件删除功能 * fix(editor): 完善插件删除功能并修复多个关键问题 * fix(auth): 修复自动登录与手动登录的竞态条件问题 * feat(editor): 重构插件管理流程 * feat(editor): 支持 ZIP 文件直接发布插件 - 新增 PluginSourceParser 解析插件源 - 重构发布流程支持文件夹和 ZIP 两种方式 - 优化发布向导 UI * feat(editor): 插件市场支持多版本安装 - 插件解压到项目 plugins 目录 - 新增 Tauri 后端安装/卸载命令 - 支持选择任意版本安装 - 修复打包逻辑,保留完整 dist 目录结构 * feat(editor): 个人中心支持多版本管理 - 合并同一插件的不同版本 - 添加版本历史展开/折叠功能 - 禁止有待审核 PR 时更新插件 * fix(editor): 修复 InspectorRegistry 服务注册 - InspectorRegistry 实现 IService 接口 - 注册到 Core.services 供插件使用 * feat(behavior-tree-editor): 完善插件注册和文件操作 - 添加文件创建模板和操作处理器 - 实现右键菜单创建行为树功能 - 修复文件读取权限问题(使用 Tauri 命令) - 添加 BehaviorTreeEditorPanel 组件 - 修复 rollup 配置支持动态导入 * feat(plugin): 完善插件构建和发布流程 * fix(behavior-tree-editor): 完整恢复编辑器并修复 Toast 集成 * fix(behavior-tree-editor): 修复节点选中、连线跟随和文件加载问题并优化性能 * fix(behavior-tree-editor): 修复端口连接失败问题并优化连线样式 * refactor(behavior-tree-editor): 移除调试面板功能简化代码结构 * refactor(behavior-tree-editor): 清理冗余代码合并重复逻辑 * feat(behavior-tree-editor): 完善编辑器核心功能增强扩展性 * fix(lint): 修复ESLint错误确保CI通过 * refactor(behavior-tree-editor): 优化编辑器工具栏和编译器功能 * refactor(behavior-tree-editor): 清理技术债务,优化代码质量 * fix(editor-app): 修复字符串替换安全问题
2025-11-18 14:46:51 +08:00
switch (property.type) {
case 'number':
return (
<input
type="number"
value={value ?? property.defaultValue ?? 0}
min={property.min}
max={property.max}
step={property.step || 1}
onChange={(e) => handleChange(parseFloat(e.target.value) || 0)}
style={{
width: '100%',
padding: '4px 8px',
backgroundColor: '#2a2a2a',
border: '1px solid #444',
borderRadius: '3px',
color: '#e0e0e0',
fontSize: '12px'
}}
/>
);
case 'boolean':
return (
<label style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
<input
type="checkbox"
checked={value ?? property.defaultValue ?? false}
onChange={(e) => handleChange(e.target.checked)}
/>
<span style={{ fontSize: '11px', color: value ? '#4ade80' : '#888' }}>
{value ? '是' : '否'}
</span>
</label>
);
case 'select':
return (
<select
value={value ?? property.defaultValue ?? ''}
onChange={(e) => handleChange(e.target.value)}
style={{
width: '100%',
padding: '4px 8px',
backgroundColor: '#2a2a2a',
border: '1px solid #444',
borderRadius: '3px',
color: '#e0e0e0',
fontSize: '12px'
}}
>
{property.options?.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
);
case 'code':
return (
<textarea
value={value ?? property.defaultValue ?? ''}
onChange={(e) => handleChange(e.target.value)}
rows={4}
style={{
width: '100%',
padding: '6px 8px',
backgroundColor: '#1e1e1e',
border: '1px solid #444',
borderRadius: '3px',
color: '#d4d4d4',
fontSize: '11px',
fontFamily: 'Consolas, Monaco, monospace',
resize: 'vertical'
}}
/>
);
case 'string':
default:
return (
<input
type="text"
value={value ?? property.defaultValue ?? ''}
onChange={(e) => handleChange(e.target.value)}
style={{
width: '100%',
padding: '4px 8px',
backgroundColor: '#2a2a2a',
border: '1px solid #444',
borderRadius: '3px',
color: '#e0e0e0',
fontSize: '12px'
}}
/>
);
}
};
return (
<div className="property-field" style={{
marginBottom: '6px',
display: 'grid',
gridTemplateColumns: '120px 1fr',
alignItems: 'center',
gap: '8px'
}}>
<label
className="property-label"
style={{
fontSize: '11px',
color: '#999',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}}
title={property.description || property.label || property.name}
>
{property.label || property.name}
{property.required && <span style={{ color: '#f87171', marginLeft: '2px' }}>*</span>}
</label>
{renderInput()}
</div>
);
};
/**
*
*/
interface NodePropertiesPanelProps {
node: BehaviorTreeNode;
onPropertyChange?: (nodeId: string, propertyName: string, value: any) => void;
}
const NodePropertiesPanel: React.FC<NodePropertiesPanelProps> = ({ node, onPropertyChange }) => {
const [localData, setLocalData] = useState<Record<string, any>>(node.data);
const handlePropertyChange = useCallback((name: string, value: any) => {
setLocalData((prev) => ({ ...prev, [name]: value }));
refactor(editor): 提取行为树编辑器为独立包并重构编辑器架构 (#216) * refactor(editor): 提取行为树编辑器为独立包并重构编辑器架构 * feat(editor): 添加插件市场功能 * feat(editor): 重构插件市场以支持版本管理和ZIP打包 * feat(editor): 重构插件发布流程并修复React渲染警告 * fix(plugin): 修复插件发布和市场的路径不一致问题 * feat: 重构插件发布流程并添加插件删除功能 * fix(editor): 完善插件删除功能并修复多个关键问题 * fix(auth): 修复自动登录与手动登录的竞态条件问题 * feat(editor): 重构插件管理流程 * feat(editor): 支持 ZIP 文件直接发布插件 - 新增 PluginSourceParser 解析插件源 - 重构发布流程支持文件夹和 ZIP 两种方式 - 优化发布向导 UI * feat(editor): 插件市场支持多版本安装 - 插件解压到项目 plugins 目录 - 新增 Tauri 后端安装/卸载命令 - 支持选择任意版本安装 - 修复打包逻辑,保留完整 dist 目录结构 * feat(editor): 个人中心支持多版本管理 - 合并同一插件的不同版本 - 添加版本历史展开/折叠功能 - 禁止有待审核 PR 时更新插件 * fix(editor): 修复 InspectorRegistry 服务注册 - InspectorRegistry 实现 IService 接口 - 注册到 Core.services 供插件使用 * feat(behavior-tree-editor): 完善插件注册和文件操作 - 添加文件创建模板和操作处理器 - 实现右键菜单创建行为树功能 - 修复文件读取权限问题(使用 Tauri 命令) - 添加 BehaviorTreeEditorPanel 组件 - 修复 rollup 配置支持动态导入 * feat(plugin): 完善插件构建和发布流程 * fix(behavior-tree-editor): 完整恢复编辑器并修复 Toast 集成 * fix(behavior-tree-editor): 修复节点选中、连线跟随和文件加载问题并优化性能 * fix(behavior-tree-editor): 修复端口连接失败问题并优化连线样式 * refactor(behavior-tree-editor): 移除调试面板功能简化代码结构 * refactor(behavior-tree-editor): 清理冗余代码合并重复逻辑 * feat(behavior-tree-editor): 完善编辑器核心功能增强扩展性 * fix(lint): 修复ESLint错误确保CI通过 * refactor(behavior-tree-editor): 优化编辑器工具栏和编译器功能 * refactor(behavior-tree-editor): 清理技术债务,优化代码质量 * fix(editor-app): 修复字符串替换安全问题
2025-11-18 14:46:51 +08:00
onPropertyChange?.(node.id, name, value);
}, [node.id, onPropertyChange]);
const properties = node.template.properties || [];
if (properties.length === 0) {
return (
<div style={{ padding: '12px', color: '#888', fontSize: '12px', textAlign: 'center' }}>
</div>
);
}
return (
<div style={{ padding: '4px 0' }}>
{properties.map((prop) => (
<PropertyEditor
key={prop.name}
property={prop}
value={localData[prop.name]}
onChange={handlePropertyChange}
/>
))}
</div>
);
};
/**
* Inspector提供器
*
*/
export class BehaviorTreeNodeInspectorProvider implements IInspectorProvider<BehaviorTreeNode> {
readonly id = 'behavior-tree-node-inspector';
readonly name = '行为树节点检视器';
readonly priority = 100;
private messageHub?: MessageHub;
setMessageHub(hub: MessageHub): void {
this.messageHub = hub;
}
canHandle(target: unknown): target is BehaviorTreeNode {
return target instanceof BehaviorTreeNode ||
(typeof target === 'object' &&
target !== null &&
'template' in target &&
'data' in target &&
'position' in target &&
'children' in target);
}
render(node: BehaviorTreeNode, context: InspectorContext): React.ReactElement {
const handlePropertyChange = (nodeId: string, propertyName: string, value: any) => {
if (this.messageHub) {
this.messageHub.publish('behavior-tree:node-property-changed', {
nodeId,
propertyName,
value
});
}
};
return (
<div className="entity-inspector">
<div className="inspector-header">
<span className="entity-name">{node.template.displayName || '未命名节点'}</span>
</div>
<div className="inspector-content">
<div className="inspector-section">
<div className="section-title"></div>
<div style={{
display: 'grid',
gridTemplateColumns: '80px 1fr',
gap: '4px 8px',
fontSize: '11px'
}}>
<span style={{ color: '#888' }}></span>
<span style={{ color: '#e0e0e0' }}>{node.template.type}</span>
<span style={{ color: '#888' }}></span>
<span style={{ color: '#e0e0e0' }}>{node.template.category}</span>
{node.template.description && (
<>
<span style={{ color: '#888' }}></span>
<span
style={{
color: '#999',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}}
title={node.template.description}
>
{node.template.description}
</span>
</>
)}
</div>
</div>
{node.template.properties && node.template.properties.length > 0 && (
<div className="inspector-section">
<div className="section-title"></div>
<NodePropertiesPanel
node={node}
onPropertyChange={handlePropertyChange}
/>
</div>
)}
<div className="inspector-section">
<div className="section-title"></div>
<div style={{
display: 'grid',
gridTemplateColumns: '80px 1fr',
gap: '4px 8px',
fontSize: '11px'
}}>
<span style={{ color: '#888' }}>ID</span>
<span style={{
fontFamily: 'Consolas, Monaco, monospace',
color: '#666',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}} title={node.id}>
{node.id}
</span>
<span style={{ color: '#888' }}></span>
<span style={{ color: '#999' }}>
({node.position.x.toFixed(0)}, {node.position.y.toFixed(0)})
</span>
<span style={{ color: '#888' }}></span>
<span style={{ color: '#e0e0e0' }}>{node.children.length}</span>
</div>
</div>
</div>
</div>
);
}
}