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): 修复字符串替换安全问题
This commit is contained in:
YHH
2025-11-18 14:46:51 +08:00
committed by GitHub
parent eac660b1a0
commit bce3a6e253
251 changed files with 26144 additions and 8844 deletions

View File

@@ -0,0 +1,36 @@
import { Connection } from '../../../domain/models/Connection';
import { BaseCommand } from '@esengine/editor-core';
import { ITreeState } from '../ITreeState';
/**
* 添加连接命令
*/
export class AddConnectionCommand extends BaseCommand {
constructor(
private readonly state: ITreeState,
private readonly connection: Connection
) {
super();
}
execute(): void {
const tree = this.state.getTree();
const newTree = tree.addConnection(this.connection);
this.state.setTree(newTree);
}
undo(): void {
const tree = this.state.getTree();
const newTree = tree.removeConnection(
this.connection.from,
this.connection.to,
this.connection.fromProperty,
this.connection.toProperty
);
this.state.setTree(newTree);
}
getDescription(): string {
return `添加连接: ${this.connection.from} -> ${this.connection.to}`;
}
}

View File

@@ -0,0 +1,34 @@
import { Node } from '../../../domain/models/Node';
import { BaseCommand } from '@esengine/editor-core';
import { ITreeState } from '../ITreeState';
/**
* 创建节点命令
*/
export class CreateNodeCommand extends BaseCommand {
private createdNodeId: string;
constructor(
private readonly state: ITreeState,
private readonly node: Node
) {
super();
this.createdNodeId = node.id;
}
execute(): void {
const tree = this.state.getTree();
const newTree = tree.addNode(this.node);
this.state.setTree(newTree);
}
undo(): void {
const tree = this.state.getTree();
const newTree = tree.removeNode(this.createdNodeId);
this.state.setTree(newTree);
}
getDescription(): string {
return `创建节点: ${this.node.template.displayName}`;
}
}

View File

@@ -0,0 +1,38 @@
import { Node } from '../../../domain/models/Node';
import { BaseCommand } from '@esengine/editor-core';
import { ITreeState } from '../ITreeState';
/**
* 删除节点命令
*/
export class DeleteNodeCommand extends BaseCommand {
private deletedNode: Node | null = null;
constructor(
private readonly state: ITreeState,
private readonly nodeId: string
) {
super();
}
execute(): void {
const tree = this.state.getTree();
this.deletedNode = tree.getNode(this.nodeId);
const newTree = tree.removeNode(this.nodeId);
this.state.setTree(newTree);
}
undo(): void {
if (!this.deletedNode) {
throw new Error('无法撤销:未保存已删除的节点');
}
const tree = this.state.getTree();
const newTree = tree.addNode(this.deletedNode);
this.state.setTree(newTree);
}
getDescription(): string {
return `删除节点: ${this.deletedNode?.template.displayName ?? this.nodeId}`;
}
}

View File

@@ -0,0 +1,74 @@
import { Position } from '../../../domain/value-objects/Position';
import { BaseCommand, ICommand } from '@esengine/editor-core';
import { ITreeState } from '../ITreeState';
/**
* 移动节点命令
* 支持合并连续的移动操作
*/
export class MoveNodeCommand extends BaseCommand {
private oldPosition: Position;
constructor(
private readonly state: ITreeState,
private readonly nodeId: string,
private readonly newPosition: Position
) {
super();
const tree = this.state.getTree();
const node = tree.getNode(nodeId);
this.oldPosition = node.position;
}
execute(): void {
const tree = this.state.getTree();
const newTree = tree.updateNode(this.nodeId, (node) =>
node.moveToPosition(this.newPosition)
);
this.state.setTree(newTree);
}
undo(): void {
const tree = this.state.getTree();
const newTree = tree.updateNode(this.nodeId, (node) =>
node.moveToPosition(this.oldPosition)
);
this.state.setTree(newTree);
}
getDescription(): string {
return `移动节点: ${this.nodeId}`;
}
/**
* 移动命令可以合并
*/
canMergeWith(other: ICommand): boolean {
if (!(other instanceof MoveNodeCommand)) {
return false;
}
return this.nodeId === other.nodeId;
}
/**
* 合并移动命令
* 保留初始位置,更新最终位置
*/
mergeWith(other: ICommand): ICommand {
if (!(other instanceof MoveNodeCommand)) {
throw new Error('只能与 MoveNodeCommand 合并');
}
if (this.nodeId !== other.nodeId) {
throw new Error('只能合并同一节点的移动命令');
}
const merged = new MoveNodeCommand(
this.state,
this.nodeId,
other.newPosition
);
merged.oldPosition = this.oldPosition;
return merged;
}
}

View File

@@ -0,0 +1,50 @@
import { Connection } from '../../../domain/models/Connection';
import { BaseCommand } from '@esengine/editor-core';
import { ITreeState } from '../ITreeState';
/**
* 移除连接命令
*/
export class RemoveConnectionCommand extends BaseCommand {
private removedConnection: Connection | null = null;
constructor(
private readonly state: ITreeState,
private readonly from: string,
private readonly to: string,
private readonly fromProperty?: string,
private readonly toProperty?: string
) {
super();
}
execute(): void {
const tree = this.state.getTree();
const connection = tree.connections.find((c) =>
c.matches(this.from, this.to, this.fromProperty, this.toProperty)
);
if (!connection) {
throw new Error(`连接不存在: ${this.from} -> ${this.to}`);
}
this.removedConnection = connection;
const newTree = tree.removeConnection(this.from, this.to, this.fromProperty, this.toProperty);
this.state.setTree(newTree);
}
undo(): void {
if (!this.removedConnection) {
throw new Error('无法撤销:未保存已删除的连接');
}
const tree = this.state.getTree();
const newTree = tree.addConnection(this.removedConnection);
this.state.setTree(newTree);
}
getDescription(): string {
return `移除连接: ${this.from} -> ${this.to}`;
}
}

View File

@@ -0,0 +1,40 @@
import { BaseCommand } from '@esengine/editor-core';
import { ITreeState } from '../ITreeState';
/**
* 更新节点数据命令
*/
export class UpdateNodeDataCommand extends BaseCommand {
private oldData: Record<string, unknown>;
constructor(
private readonly state: ITreeState,
private readonly nodeId: string,
private readonly newData: Record<string, unknown>
) {
super();
const tree = this.state.getTree();
const node = tree.getNode(nodeId);
this.oldData = node.data;
}
execute(): void {
const tree = this.state.getTree();
const newTree = tree.updateNode(this.nodeId, (node) =>
node.updateData(this.newData)
);
this.state.setTree(newTree);
}
undo(): void {
const tree = this.state.getTree();
const newTree = tree.updateNode(this.nodeId, (node) =>
node.updateData(this.oldData)
);
this.state.setTree(newTree);
}
getDescription(): string {
return `更新节点数据: ${this.nodeId}`;
}
}

View File

@@ -0,0 +1,6 @@
export { CreateNodeCommand } from './CreateNodeCommand';
export { DeleteNodeCommand } from './DeleteNodeCommand';
export { AddConnectionCommand } from './AddConnectionCommand';
export { RemoveConnectionCommand } from './RemoveConnectionCommand';
export { MoveNodeCommand } from './MoveNodeCommand';
export { UpdateNodeDataCommand } from './UpdateNodeDataCommand';