Feature/runtime cdn and plugin loader (#240)
* feat(ui): 完善 UI 布局系统和编辑器可视化工具 * refactor: 移除 ModuleRegistry,统一使用 PluginManager 插件系统 * fix: 修复 CodeQL 警告并提升测试覆盖率 * refactor: 分离运行时入口点,解决 runtime bundle 包含 React 的问题 * fix(ci): 添加 editor-core 和 editor-runtime 到 CI 依赖构建步骤 * docs: 完善 ServiceContainer 文档,新增 Symbol.for 模式和 @InjectProperty 说明 * fix(ci): 修复 type-check 失败问题 * fix(ci): 修复类型检查失败问题 * fix(ci): 修复类型检查失败问题 * fix(ci): behavior-tree 构建添加 @tauri-apps 外部依赖 * fix(ci): behavior-tree 添加 @tauri-apps/plugin-fs 类型依赖 * fix(ci): platform-web 添加缺失的 behavior-tree 依赖 * fix(lint): 移除正则表达式中不必要的转义字符
This commit is contained in:
@@ -1,203 +0,0 @@
|
||||
import { ICommand } from './ICommand';
|
||||
|
||||
/**
|
||||
* 命令历史记录配置
|
||||
*/
|
||||
export interface CommandManagerConfig {
|
||||
/**
|
||||
* 最大历史记录数量
|
||||
*/
|
||||
maxHistorySize?: number;
|
||||
|
||||
/**
|
||||
* 是否自动合并相似命令
|
||||
*/
|
||||
autoMerge?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 命令管理器
|
||||
* 管理命令的执行、撤销、重做以及历史记录
|
||||
*/
|
||||
export class CommandManager {
|
||||
private undoStack: ICommand[] = [];
|
||||
private redoStack: ICommand[] = [];
|
||||
private readonly config: Required<CommandManagerConfig>;
|
||||
private isExecuting = false;
|
||||
|
||||
constructor(config: CommandManagerConfig = {}) {
|
||||
this.config = {
|
||||
maxHistorySize: config.maxHistorySize ?? 100,
|
||||
autoMerge: config.autoMerge ?? true
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行命令
|
||||
*/
|
||||
execute(command: ICommand): void {
|
||||
if (this.isExecuting) {
|
||||
throw new Error('不能在命令执行过程中执行新命令');
|
||||
}
|
||||
|
||||
this.isExecuting = true;
|
||||
|
||||
try {
|
||||
command.execute();
|
||||
|
||||
if (this.config.autoMerge && this.undoStack.length > 0) {
|
||||
const lastCommand = this.undoStack[this.undoStack.length - 1];
|
||||
if (lastCommand && lastCommand.canMergeWith(command)) {
|
||||
const mergedCommand = lastCommand.mergeWith(command);
|
||||
this.undoStack[this.undoStack.length - 1] = mergedCommand;
|
||||
this.redoStack = [];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.undoStack.push(command);
|
||||
this.redoStack = [];
|
||||
|
||||
if (this.undoStack.length > this.config.maxHistorySize) {
|
||||
this.undoStack.shift();
|
||||
}
|
||||
} finally {
|
||||
this.isExecuting = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤销上一个命令
|
||||
*/
|
||||
undo(): void {
|
||||
if (this.isExecuting) {
|
||||
throw new Error('不能在命令执行过程中撤销');
|
||||
}
|
||||
|
||||
const command = this.undoStack.pop();
|
||||
if (!command) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isExecuting = true;
|
||||
|
||||
try {
|
||||
command.undo();
|
||||
this.redoStack.push(command);
|
||||
} catch (error) {
|
||||
this.undoStack.push(command);
|
||||
throw error;
|
||||
} finally {
|
||||
this.isExecuting = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重做上一个被撤销的命令
|
||||
*/
|
||||
redo(): void {
|
||||
if (this.isExecuting) {
|
||||
throw new Error('不能在命令执行过程中重做');
|
||||
}
|
||||
|
||||
const command = this.redoStack.pop();
|
||||
if (!command) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isExecuting = true;
|
||||
|
||||
try {
|
||||
command.execute();
|
||||
this.undoStack.push(command);
|
||||
} catch (error) {
|
||||
this.redoStack.push(command);
|
||||
throw error;
|
||||
} finally {
|
||||
this.isExecuting = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否可以撤销
|
||||
*/
|
||||
canUndo(): boolean {
|
||||
return this.undoStack.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否可以重做
|
||||
*/
|
||||
canRedo(): boolean {
|
||||
return this.redoStack.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取撤销栈的描述列表
|
||||
*/
|
||||
getUndoHistory(): string[] {
|
||||
return this.undoStack.map((cmd) => cmd.getDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取重做栈的描述列表
|
||||
*/
|
||||
getRedoHistory(): string[] {
|
||||
return this.redoStack.map((cmd) => cmd.getDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有历史记录
|
||||
*/
|
||||
clear(): void {
|
||||
this.undoStack = [];
|
||||
this.redoStack = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量执行命令(作为单一操作,可以一次撤销)
|
||||
*/
|
||||
executeBatch(commands: ICommand[]): void {
|
||||
if (commands.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const batchCommand = new BatchCommand(commands);
|
||||
this.execute(batchCommand);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量命令
|
||||
* 将多个命令组合为一个命令
|
||||
*/
|
||||
class BatchCommand implements ICommand {
|
||||
constructor(private readonly commands: ICommand[]) {}
|
||||
|
||||
execute(): void {
|
||||
for (const command of this.commands) {
|
||||
command.execute();
|
||||
}
|
||||
}
|
||||
|
||||
undo(): void {
|
||||
for (let i = this.commands.length - 1; i >= 0; i--) {
|
||||
const command = this.commands[i];
|
||||
if (command) {
|
||||
command.undo();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getDescription(): string {
|
||||
return `批量操作 (${this.commands.length} 个命令)`;
|
||||
}
|
||||
|
||||
canMergeWith(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
mergeWith(): ICommand {
|
||||
throw new Error('批量命令不支持合并');
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* 命令接口
|
||||
* 实现命令模式,支持撤销/重做功能
|
||||
*/
|
||||
export interface ICommand {
|
||||
/**
|
||||
* 执行命令
|
||||
*/
|
||||
execute(): void;
|
||||
|
||||
/**
|
||||
* 撤销命令
|
||||
*/
|
||||
undo(): void;
|
||||
|
||||
/**
|
||||
* 获取命令描述(用于显示历史记录)
|
||||
*/
|
||||
getDescription(): string;
|
||||
|
||||
/**
|
||||
* 检查命令是否可以合并
|
||||
* 用于优化撤销/重做历史,例如连续的移动操作可以合并为一个
|
||||
*/
|
||||
canMergeWith(other: ICommand): boolean;
|
||||
|
||||
/**
|
||||
* 与另一个命令合并
|
||||
*/
|
||||
mergeWith(other: ICommand): ICommand;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { BehaviorTree } from '../../domain/models/BehaviorTree';
|
||||
|
||||
/**
|
||||
* 行为树状态接口
|
||||
* 命令通过此接口操作状态
|
||||
*/
|
||||
export interface ITreeState {
|
||||
/**
|
||||
* 获取当前行为树
|
||||
*/
|
||||
getTree(): BehaviorTree;
|
||||
|
||||
/**
|
||||
* 设置行为树
|
||||
*/
|
||||
setTree(tree: BehaviorTree): void;
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
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}`;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
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}`;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
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}`;
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
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}`;
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
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}`;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
export { CreateNodeCommand } from './CreateNodeCommand';
|
||||
export { DeleteNodeCommand } from './DeleteNodeCommand';
|
||||
export { AddConnectionCommand } from './AddConnectionCommand';
|
||||
export { RemoveConnectionCommand } from './RemoveConnectionCommand';
|
||||
export { MoveNodeCommand } from './MoveNodeCommand';
|
||||
export { UpdateNodeDataCommand } from './UpdateNodeDataCommand';
|
||||
Reference in New Issue
Block a user