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:
355
packages/editor-core/src/Plugin/IPluginLoader.ts
Normal file
355
packages/editor-core/src/Plugin/IPluginLoader.ts
Normal file
@@ -0,0 +1,355 @@
|
||||
/**
|
||||
* 插件加载器接口
|
||||
* Plugin loader interfaces
|
||||
*/
|
||||
|
||||
import type { IScene, ServiceContainer, ComponentRegistry } from '@esengine/ecs-framework';
|
||||
import type { PluginDescriptor } from './PluginDescriptor';
|
||||
|
||||
/**
|
||||
* 系统创建上下文
|
||||
* System creation context
|
||||
*/
|
||||
export interface SystemContext {
|
||||
/** 是否为编辑器模式 | Is editor mode */
|
||||
isEditor: boolean;
|
||||
|
||||
/** 引擎桥接(如有) | Engine bridge (if available) */
|
||||
engineBridge?: any;
|
||||
|
||||
/** 渲染系统(如有) | Render system (if available) */
|
||||
renderSystem?: any;
|
||||
|
||||
/** 其他已创建的系统引用 | Other created system references */
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行时模块加载器
|
||||
* Runtime module loader
|
||||
*/
|
||||
export interface IRuntimeModuleLoader {
|
||||
/**
|
||||
* 注册组件到 ComponentRegistry
|
||||
* Register components to ComponentRegistry
|
||||
*/
|
||||
registerComponents(registry: typeof ComponentRegistry): void;
|
||||
|
||||
/**
|
||||
* 注册服务到 ServiceContainer
|
||||
* Register services to ServiceContainer
|
||||
*/
|
||||
registerServices?(services: ServiceContainer): void;
|
||||
|
||||
/**
|
||||
* 为场景创建系统
|
||||
* Create systems for scene
|
||||
*/
|
||||
createSystems?(scene: IScene, context: SystemContext): void;
|
||||
|
||||
/**
|
||||
* 模块初始化完成回调
|
||||
* Module initialization complete callback
|
||||
*/
|
||||
onInitialize?(): Promise<void>;
|
||||
|
||||
/**
|
||||
* 模块销毁回调
|
||||
* Module destroy callback
|
||||
*/
|
||||
onDestroy?(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 面板位置
|
||||
* Panel position
|
||||
*/
|
||||
export enum PanelPosition {
|
||||
Left = 'left',
|
||||
Right = 'right',
|
||||
Bottom = 'bottom',
|
||||
Center = 'center'
|
||||
}
|
||||
|
||||
/**
|
||||
* 面板描述符
|
||||
* Panel descriptor
|
||||
*/
|
||||
export interface PanelDescriptor {
|
||||
/** 面板ID | Panel ID */
|
||||
id: string;
|
||||
/** 面板标题 | Panel title */
|
||||
title: string;
|
||||
/** 面板图标 | Panel icon */
|
||||
icon?: string;
|
||||
/** 面板位置 | Panel position */
|
||||
position: PanelPosition;
|
||||
/** 渲染组件 | Render component */
|
||||
component?: any;
|
||||
/** 渲染函数 | Render function */
|
||||
render?: () => any;
|
||||
/** 默认大小 | Default size */
|
||||
defaultSize?: number;
|
||||
/** 是否可调整大小 | Is resizable */
|
||||
resizable?: boolean;
|
||||
/** 是否可关闭 | Is closable */
|
||||
closable?: boolean;
|
||||
/** 排序权重 | Order weight */
|
||||
order?: number;
|
||||
/** 是否为动态面板 | Is dynamic panel */
|
||||
isDynamic?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单项描述符
|
||||
* Menu item descriptor
|
||||
*/
|
||||
export interface MenuItemDescriptor {
|
||||
/** 菜单ID | Menu ID */
|
||||
id: string;
|
||||
/** 菜单标签 | Menu label */
|
||||
label: string;
|
||||
/** 父菜单ID | Parent menu ID */
|
||||
parentId?: string;
|
||||
/** 图标 | Icon */
|
||||
icon?: string;
|
||||
/** 快捷键 | Shortcut */
|
||||
shortcut?: string;
|
||||
/** 执行函数 | Execute function */
|
||||
execute?: () => void;
|
||||
/** 子菜单 | Submenu items */
|
||||
children?: MenuItemDescriptor[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具栏项描述符
|
||||
* Toolbar item descriptor
|
||||
*/
|
||||
export interface ToolbarItemDescriptor {
|
||||
/** 工具栏项ID | Toolbar item ID */
|
||||
id: string;
|
||||
/** 标签 | Label */
|
||||
label: string;
|
||||
/** 图标 | Icon */
|
||||
icon: string;
|
||||
/** 提示 | Tooltip */
|
||||
tooltip?: string;
|
||||
/** 执行函数 | Execute function */
|
||||
execute: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件检视器提供者(简化版)
|
||||
* Component inspector provider (simplified)
|
||||
*/
|
||||
export interface ComponentInspectorProviderDef {
|
||||
/** 组件类型名 | Component type name */
|
||||
componentType: string;
|
||||
/** 优先级 | Priority */
|
||||
priority?: number;
|
||||
/** 渲染函数 | Render function */
|
||||
render: (component: any, entity: any, onChange: (key: string, value: any) => void) => any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gizmo 提供者注册
|
||||
* Gizmo provider registration
|
||||
*/
|
||||
export interface GizmoProviderRegistration {
|
||||
/** 组件类型 | Component type */
|
||||
componentType: any;
|
||||
/** 获取 Gizmo 数据 | Get gizmo data */
|
||||
getGizmoData: (component: any, entity: any, isSelected: boolean) => any[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件操作处理器
|
||||
* File action handler
|
||||
*/
|
||||
export interface FileActionHandler {
|
||||
/** 支持的文件扩展名 | Supported file extensions */
|
||||
extensions: string[];
|
||||
/** 双击处理 | Double click handler */
|
||||
onDoubleClick?: (filePath: string) => void | Promise<void>;
|
||||
/** 打开处理 | Open handler */
|
||||
onOpen?: (filePath: string) => void | Promise<void>;
|
||||
/** 获取上下文菜单 | Get context menu */
|
||||
getContextMenuItems?: (filePath: string, parentPath: string) => any[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 实体创建模板
|
||||
* Entity creation template
|
||||
*/
|
||||
export interface EntityCreationTemplate {
|
||||
/** 模板ID | Template ID */
|
||||
id: string;
|
||||
/** 标签 | Label */
|
||||
label: string;
|
||||
/** 图标组件 | Icon component */
|
||||
icon?: any;
|
||||
/** 分类 | Category */
|
||||
category?: string;
|
||||
/** 排序权重 | Order weight */
|
||||
order?: number;
|
||||
/** 创建函数 | Create function */
|
||||
create: (parentEntityId?: number) => number | Promise<number>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件操作
|
||||
* Component action
|
||||
*/
|
||||
export interface ComponentAction {
|
||||
/** 操作ID | Action ID */
|
||||
id: string;
|
||||
/** 组件名 | Component name */
|
||||
componentName: string;
|
||||
/** 标签 | Label */
|
||||
label: string;
|
||||
/** 图标 | Icon */
|
||||
icon?: any;
|
||||
/** 排序权重 | Order weight */
|
||||
order?: number;
|
||||
/** 执行函数 | Execute function */
|
||||
execute: (component: any, entity: any) => void | Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化器接口
|
||||
* Serializer interface
|
||||
*/
|
||||
export interface ISerializer<T = any> {
|
||||
/** 获取支持的类型 | Get supported type */
|
||||
getSupportedType(): string;
|
||||
/** 序列化数据 | Serialize data */
|
||||
serialize(data: T): Uint8Array;
|
||||
/** 反序列化数据 | Deserialize data */
|
||||
deserialize(data: Uint8Array): T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑器模块加载器
|
||||
* Editor module loader
|
||||
*/
|
||||
export interface IEditorModuleLoader {
|
||||
/**
|
||||
* 安装编辑器模块
|
||||
* Install editor module
|
||||
*/
|
||||
install(services: ServiceContainer): Promise<void>;
|
||||
|
||||
/**
|
||||
* 卸载编辑器模块
|
||||
* Uninstall editor module
|
||||
*/
|
||||
uninstall?(): Promise<void>;
|
||||
|
||||
/**
|
||||
* 返回面板描述列表
|
||||
* Get panel descriptors
|
||||
*/
|
||||
getPanels?(): PanelDescriptor[];
|
||||
|
||||
/**
|
||||
* 返回菜单项列表
|
||||
* Get menu items
|
||||
*/
|
||||
getMenuItems?(): MenuItemDescriptor[];
|
||||
|
||||
/**
|
||||
* 返回工具栏项列表
|
||||
* Get toolbar items
|
||||
*/
|
||||
getToolbarItems?(): ToolbarItemDescriptor[];
|
||||
|
||||
/**
|
||||
* 返回检视器提供者列表
|
||||
* Get inspector providers
|
||||
*/
|
||||
getInspectorProviders?(): ComponentInspectorProviderDef[];
|
||||
|
||||
/**
|
||||
* 返回 Gizmo 提供者列表
|
||||
* Get gizmo providers
|
||||
*/
|
||||
getGizmoProviders?(): GizmoProviderRegistration[];
|
||||
|
||||
/**
|
||||
* 返回文件操作处理器列表
|
||||
* Get file action handlers
|
||||
*/
|
||||
getFileActionHandlers?(): FileActionHandler[];
|
||||
|
||||
/**
|
||||
* 返回实体创建模板列表
|
||||
* Get entity creation templates
|
||||
*/
|
||||
getEntityCreationTemplates?(): EntityCreationTemplate[];
|
||||
|
||||
/**
|
||||
* 返回组件操作列表
|
||||
* Get component actions
|
||||
*/
|
||||
getComponentActions?(): ComponentAction[];
|
||||
|
||||
/**
|
||||
* 返回文件创建模板列表
|
||||
* Get file creation templates
|
||||
*/
|
||||
getFileCreationTemplates?(): FileCreationTemplate[];
|
||||
|
||||
// ===== 生命周期钩子 | Lifecycle hooks =====
|
||||
|
||||
/** 编辑器就绪 | Editor ready */
|
||||
onEditorReady?(): void | Promise<void>;
|
||||
|
||||
/** 项目打开 | Project open */
|
||||
onProjectOpen?(projectPath: string): void | Promise<void>;
|
||||
|
||||
/** 项目关闭 | Project close */
|
||||
onProjectClose?(): void | Promise<void>;
|
||||
|
||||
/** 场景加载 | Scene loaded */
|
||||
onSceneLoaded?(scenePath: string): void;
|
||||
|
||||
/** 场景保存前 | Before scene save */
|
||||
onSceneSaving?(scenePath: string): boolean | void;
|
||||
|
||||
/** 设置语言 | Set locale */
|
||||
setLocale?(locale: string): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一插件加载器
|
||||
* Unified plugin loader
|
||||
*/
|
||||
export interface IPluginLoader {
|
||||
/** 插件描述符 | Plugin descriptor */
|
||||
readonly descriptor: PluginDescriptor;
|
||||
|
||||
/** 运行时模块(可选) | Runtime module (optional) */
|
||||
readonly runtimeModule?: IRuntimeModuleLoader;
|
||||
|
||||
/** 编辑器模块(可选) | Editor module (optional) */
|
||||
readonly editorModule?: IEditorModuleLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件创建模板
|
||||
* File creation template
|
||||
*/
|
||||
export interface FileCreationTemplate {
|
||||
/** 模板ID | Template ID */
|
||||
id: string;
|
||||
/** 标签 | Label */
|
||||
label: string;
|
||||
/** 扩展名 | Extension */
|
||||
extension: string;
|
||||
/** 图标 | Icon */
|
||||
icon?: string;
|
||||
/** 分类 | Category */
|
||||
category?: string;
|
||||
/** 创建函数 | Create function */
|
||||
create: (filePath: string) => Promise<void>;
|
||||
}
|
||||
163
packages/editor-core/src/Plugin/PluginDescriptor.ts
Normal file
163
packages/editor-core/src/Plugin/PluginDescriptor.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
/**
|
||||
* 插件系统类型定义
|
||||
* Plugin system type definitions
|
||||
*/
|
||||
|
||||
/**
|
||||
* 插件类别
|
||||
* Plugin category
|
||||
*/
|
||||
export type PluginCategory =
|
||||
| 'core' // 核心功能 | Core functionality
|
||||
| 'rendering' // 渲染相关 | Rendering
|
||||
| 'ui' // UI 系统 | UI System
|
||||
| 'ai' // AI/行为树 | AI/Behavior
|
||||
| 'physics' // 物理引擎 | Physics
|
||||
| 'audio' // 音频系统 | Audio
|
||||
| 'networking' // 网络功能 | Networking
|
||||
| 'tools' // 工具/编辑器扩展 | Tools/Editor extensions
|
||||
| 'content'; // 内容/资源 | Content/Assets
|
||||
|
||||
/**
|
||||
* 加载阶段 - 控制插件模块的加载顺序
|
||||
* Loading phase - controls the loading order of plugin modules
|
||||
*/
|
||||
export type LoadingPhase =
|
||||
| 'earliest' // 最早加载(核心模块) | Earliest (core modules)
|
||||
| 'preDefault' // 默认之前 | Before default
|
||||
| 'default' // 默认阶段 | Default phase
|
||||
| 'postDefault' // 默认之后 | After default
|
||||
| 'postEngine'; // 引擎初始化后 | After engine init
|
||||
|
||||
/**
|
||||
* 模块类型
|
||||
* Module type
|
||||
*/
|
||||
export type ModuleType = 'runtime' | 'editor';
|
||||
|
||||
/**
|
||||
* 模块描述符 - 描述插件内的一个模块
|
||||
* Module descriptor - describes a module within a plugin
|
||||
*/
|
||||
export interface ModuleDescriptor {
|
||||
/** 模块名称 | Module name */
|
||||
name: string;
|
||||
|
||||
/** 模块类型 | Module type */
|
||||
type: ModuleType;
|
||||
|
||||
/** 加载阶段 | Loading phase */
|
||||
loadingPhase?: LoadingPhase;
|
||||
|
||||
/** 模块入口文件(相对路径) | Module entry file (relative path) */
|
||||
entry?: string;
|
||||
|
||||
// ===== 运行时模块配置 | Runtime module config =====
|
||||
|
||||
/** 导出的组件类名列表 | Exported component class names */
|
||||
components?: string[];
|
||||
|
||||
/** 导出的系统类名列表 | Exported system class names */
|
||||
systems?: string[];
|
||||
|
||||
/** 导出的服务类名列表 | Exported service class names */
|
||||
services?: string[];
|
||||
|
||||
// ===== 编辑器模块配置 | Editor module config =====
|
||||
|
||||
/** 注册的面板ID列表 | Registered panel IDs */
|
||||
panels?: string[];
|
||||
|
||||
/** 注册的检视器类型列表 | Registered inspector types */
|
||||
inspectors?: string[];
|
||||
|
||||
/** 注册的 Gizmo 提供者列表 | Registered Gizmo providers */
|
||||
gizmoProviders?: string[];
|
||||
|
||||
/** 注册的编译器列表 | Registered compilers */
|
||||
compilers?: string[];
|
||||
|
||||
/** 注册的文件处理器扩展名 | Registered file handler extensions */
|
||||
fileHandlers?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 插件依赖
|
||||
* Plugin dependency
|
||||
*/
|
||||
export interface PluginDependency {
|
||||
/** 依赖的插件ID | Dependent plugin ID */
|
||||
id: string;
|
||||
|
||||
/** 版本要求(semver) | Version requirement (semver) */
|
||||
version?: string;
|
||||
|
||||
/** 是否可选 | Optional */
|
||||
optional?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插件描述符 - 对应 plugin.json 文件
|
||||
* Plugin descriptor - corresponds to plugin.json
|
||||
*/
|
||||
export interface PluginDescriptor {
|
||||
/** 插件唯一标识符,如 "@esengine/tilemap" | Unique plugin ID */
|
||||
id: string;
|
||||
|
||||
/** 显示名称 | Display name */
|
||||
name: string;
|
||||
|
||||
/** 版本号 | Version */
|
||||
version: string;
|
||||
|
||||
/** 描述 | Description */
|
||||
description?: string;
|
||||
|
||||
/** 作者 | Author */
|
||||
author?: string;
|
||||
|
||||
/** 许可证 | License */
|
||||
license?: string;
|
||||
|
||||
/** 插件类别 | Plugin category */
|
||||
category: PluginCategory;
|
||||
|
||||
/** 标签(用于搜索) | Tags (for search) */
|
||||
tags?: string[];
|
||||
|
||||
/** 图标(Lucide 图标名) | Icon (Lucide icon name) */
|
||||
icon?: string;
|
||||
|
||||
/** 是否默认启用 | Enabled by default */
|
||||
enabledByDefault: boolean;
|
||||
|
||||
/** 是否可以包含内容资产 | Can contain content assets */
|
||||
canContainContent: boolean;
|
||||
|
||||
/** 是否为引擎内置插件 | Is engine built-in plugin */
|
||||
isEnginePlugin: boolean;
|
||||
|
||||
/** 是否为核心插件(不可禁用) | Is core plugin (cannot be disabled) */
|
||||
isCore?: boolean;
|
||||
|
||||
/** 模块列表 | Module list */
|
||||
modules: ModuleDescriptor[];
|
||||
|
||||
/** 依赖列表 | Dependency list */
|
||||
dependencies?: PluginDependency[];
|
||||
|
||||
/** 平台要求 | Platform requirements */
|
||||
platforms?: ('web' | 'desktop' | 'mobile')[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 插件状态
|
||||
* Plugin state
|
||||
*/
|
||||
export type PluginState =
|
||||
| 'unloaded' // 未加载 | Not loaded
|
||||
| 'loading' // 加载中 | Loading
|
||||
| 'loaded' // 已加载 | Loaded
|
||||
| 'active' // 已激活 | Active
|
||||
| 'error' // 错误 | Error
|
||||
| 'disabled'; // 已禁用 | Disabled
|
||||
775
packages/editor-core/src/Plugin/PluginManager.ts
Normal file
775
packages/editor-core/src/Plugin/PluginManager.ts
Normal file
@@ -0,0 +1,775 @@
|
||||
/**
|
||||
* 统一插件管理器
|
||||
* Unified Plugin Manager
|
||||
*/
|
||||
|
||||
import { createLogger, ComponentRegistry } from '@esengine/ecs-framework';
|
||||
import type { IScene, ServiceContainer, IService } from '@esengine/ecs-framework';
|
||||
import type {
|
||||
PluginDescriptor,
|
||||
PluginState,
|
||||
PluginCategory,
|
||||
LoadingPhase
|
||||
} from './PluginDescriptor';
|
||||
import type {
|
||||
IPluginLoader,
|
||||
SystemContext
|
||||
} from './IPluginLoader';
|
||||
import { EntityCreationRegistry } from '../Services/EntityCreationRegistry';
|
||||
import { ComponentActionRegistry } from '../Services/ComponentActionRegistry';
|
||||
import { FileActionRegistry } from '../Services/FileActionRegistry';
|
||||
import { UIRegistry } from '../Services/UIRegistry';
|
||||
|
||||
const logger = createLogger('PluginManager');
|
||||
|
||||
/**
|
||||
* PluginManager 服务标识符
|
||||
* PluginManager service identifier
|
||||
*/
|
||||
export const IPluginManager = Symbol.for('IPluginManager');
|
||||
|
||||
/**
|
||||
* 已注册的插件信息
|
||||
* Registered plugin info
|
||||
*/
|
||||
export interface RegisteredPlugin {
|
||||
/** 插件加载器 | Plugin loader */
|
||||
loader: IPluginLoader;
|
||||
/** 插件状态 | Plugin state */
|
||||
state: PluginState;
|
||||
/** 错误信息 | Error info */
|
||||
error?: Error;
|
||||
/** 是否启用 | Is enabled */
|
||||
enabled: boolean;
|
||||
/** 加载时间 | Load time */
|
||||
loadedAt?: number;
|
||||
/** 激活时间 | Activation time */
|
||||
activatedAt?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插件配置
|
||||
* Plugin configuration
|
||||
*/
|
||||
export interface PluginConfig {
|
||||
/** 启用的插件ID列表 | Enabled plugin IDs */
|
||||
enabledPlugins: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载阶段顺序
|
||||
* Loading phase order
|
||||
*/
|
||||
const LOADING_PHASE_ORDER: LoadingPhase[] = [
|
||||
'earliest',
|
||||
'preDefault',
|
||||
'default',
|
||||
'postDefault',
|
||||
'postEngine'
|
||||
];
|
||||
|
||||
/**
|
||||
* 统一插件管理器
|
||||
* Unified Plugin Manager
|
||||
*
|
||||
* 使用方式:
|
||||
* 1. 在 ServiceContainer 中注册: services.registerInstance(IPluginManager, new PluginManager())
|
||||
* 2. 获取实例: services.resolve<PluginManager>(IPluginManager)
|
||||
*/
|
||||
export class PluginManager implements IService {
|
||||
private plugins: Map<string, RegisteredPlugin> = new Map();
|
||||
private initialized = false;
|
||||
private editorInitialized = false;
|
||||
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* 释放资源
|
||||
* Dispose resources
|
||||
*/
|
||||
dispose(): void {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册插件
|
||||
* Register plugin
|
||||
*/
|
||||
register(loader: IPluginLoader): void {
|
||||
if (!loader) {
|
||||
logger.error('Cannot register plugin: loader is null or undefined');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!loader.descriptor) {
|
||||
logger.error('Cannot register plugin: descriptor is null or undefined', loader);
|
||||
return;
|
||||
}
|
||||
|
||||
const { id } = loader.descriptor;
|
||||
|
||||
if (!id) {
|
||||
logger.error('Cannot register plugin: descriptor.id is null or undefined', loader.descriptor);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.plugins.has(id)) {
|
||||
logger.warn(`Plugin ${id} is already registered, skipping`);
|
||||
return;
|
||||
}
|
||||
|
||||
const enabled = loader.descriptor.isCore || loader.descriptor.enabledByDefault;
|
||||
|
||||
this.plugins.set(id, {
|
||||
loader,
|
||||
state: 'loaded',
|
||||
enabled,
|
||||
loadedAt: Date.now()
|
||||
});
|
||||
|
||||
logger.info(`Plugin registered: ${id} (${loader.descriptor.name})`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用插件
|
||||
* Enable plugin
|
||||
*/
|
||||
enable(pluginId: string): boolean {
|
||||
const plugin = this.plugins.get(pluginId);
|
||||
if (!plugin) {
|
||||
logger.error(`Plugin ${pluginId} not found`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (plugin.loader.descriptor.isCore) {
|
||||
logger.warn(`Core plugin ${pluginId} cannot be disabled/enabled`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查依赖
|
||||
const deps = plugin.loader.descriptor.dependencies || [];
|
||||
for (const dep of deps) {
|
||||
if (dep.optional) continue;
|
||||
const depPlugin = this.plugins.get(dep.id);
|
||||
if (!depPlugin || !depPlugin.enabled) {
|
||||
logger.error(`Cannot enable ${pluginId}: dependency ${dep.id} is not enabled`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
plugin.enabled = true;
|
||||
plugin.state = 'loaded';
|
||||
logger.info(`Plugin enabled: ${pluginId}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用插件
|
||||
* Disable plugin
|
||||
*/
|
||||
disable(pluginId: string): boolean {
|
||||
const plugin = this.plugins.get(pluginId);
|
||||
if (!plugin) {
|
||||
logger.error(`Plugin ${pluginId} not found`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (plugin.loader.descriptor.isCore) {
|
||||
logger.warn(`Core plugin ${pluginId} cannot be disabled`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否有其他插件依赖此插件
|
||||
for (const [id, p] of this.plugins) {
|
||||
if (!p.enabled || id === pluginId) continue;
|
||||
const deps = p.loader.descriptor.dependencies || [];
|
||||
const hasDep = deps.some(d => d.id === pluginId && !d.optional);
|
||||
if (hasDep) {
|
||||
logger.error(`Cannot disable ${pluginId}: plugin ${id} depends on it`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
plugin.enabled = false;
|
||||
plugin.state = 'disabled';
|
||||
logger.info(`Plugin disabled: ${pluginId}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查插件是否启用
|
||||
* Check if plugin is enabled
|
||||
*/
|
||||
isEnabled(pluginId: string): boolean {
|
||||
const plugin = this.plugins.get(pluginId);
|
||||
return plugin?.enabled ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件状态
|
||||
* Get plugin state
|
||||
*/
|
||||
getState(pluginId: string): PluginState | undefined {
|
||||
return this.plugins.get(pluginId)?.state;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件
|
||||
* Get plugin
|
||||
*/
|
||||
getPlugin(pluginId: string): RegisteredPlugin | undefined {
|
||||
return this.plugins.get(pluginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有插件
|
||||
* Get all plugins
|
||||
*/
|
||||
getAllPlugins(): RegisteredPlugin[] {
|
||||
return Array.from(this.plugins.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 按类别获取插件
|
||||
* Get plugins by category
|
||||
*/
|
||||
getPluginsByCategory(category: PluginCategory): RegisteredPlugin[] {
|
||||
return this.getAllPlugins().filter(
|
||||
p => p.loader.descriptor.category === category
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已启用的插件
|
||||
* Get enabled plugins
|
||||
*/
|
||||
getEnabledPlugins(): RegisteredPlugin[] {
|
||||
return this.getAllPlugins().filter(p => p.enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化所有运行时模块
|
||||
* Initialize all runtime modules
|
||||
*/
|
||||
async initializeRuntime(services: ServiceContainer): Promise<void> {
|
||||
if (this.initialized) {
|
||||
logger.warn('Runtime already initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info('Initializing runtime modules...');
|
||||
|
||||
const sortedPlugins = this.sortByLoadingPhase('runtime');
|
||||
|
||||
// 注册组件
|
||||
for (const pluginId of sortedPlugins) {
|
||||
const plugin = this.plugins.get(pluginId);
|
||||
if (!plugin?.enabled) continue;
|
||||
|
||||
const runtimeModule = plugin.loader.runtimeModule;
|
||||
if (runtimeModule) {
|
||||
try {
|
||||
runtimeModule.registerComponents(ComponentRegistry);
|
||||
logger.debug(`Components registered for: ${pluginId}`);
|
||||
} catch (e) {
|
||||
logger.error(`Failed to register components for ${pluginId}:`, e);
|
||||
plugin.state = 'error';
|
||||
plugin.error = e as Error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 注册服务
|
||||
for (const pluginId of sortedPlugins) {
|
||||
const plugin = this.plugins.get(pluginId);
|
||||
if (!plugin?.enabled || plugin.state === 'error') continue;
|
||||
|
||||
const runtimeModule = plugin.loader.runtimeModule;
|
||||
if (runtimeModule?.registerServices) {
|
||||
try {
|
||||
runtimeModule.registerServices(services);
|
||||
logger.debug(`Services registered for: ${pluginId}`);
|
||||
} catch (e) {
|
||||
logger.error(`Failed to register services for ${pluginId}:`, e);
|
||||
plugin.state = 'error';
|
||||
plugin.error = e as Error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 调用初始化回调 | Call initialization callbacks
|
||||
for (const pluginId of sortedPlugins) {
|
||||
const plugin = this.plugins.get(pluginId);
|
||||
if (!plugin?.enabled || plugin.state === 'error') continue;
|
||||
|
||||
const runtimeModule = plugin.loader.runtimeModule;
|
||||
if (runtimeModule?.onInitialize) {
|
||||
try {
|
||||
await runtimeModule.onInitialize();
|
||||
plugin.state = 'active';
|
||||
plugin.activatedAt = Date.now();
|
||||
logger.debug(`Initialized: ${pluginId}`);
|
||||
} catch (e) {
|
||||
logger.error(`Failed to initialize ${pluginId}:`, e);
|
||||
plugin.state = 'error';
|
||||
plugin.error = e as Error;
|
||||
}
|
||||
} else {
|
||||
// 没有初始化回调,直接激活 | No init callback, activate directly
|
||||
plugin.state = 'active';
|
||||
plugin.activatedAt = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
this.initialized = true;
|
||||
logger.info('Runtime modules initialized');
|
||||
}
|
||||
|
||||
/**
|
||||
* 为场景创建系统
|
||||
* Create systems for scene
|
||||
*/
|
||||
createSystemsForScene(scene: IScene, context: SystemContext): void {
|
||||
logger.info('Creating systems for scene...');
|
||||
|
||||
const sortedPlugins = this.sortByLoadingPhase('runtime');
|
||||
|
||||
for (const pluginId of sortedPlugins) {
|
||||
const plugin = this.plugins.get(pluginId);
|
||||
if (!plugin?.enabled || plugin.state === 'error') continue;
|
||||
|
||||
const runtimeModule = plugin.loader.runtimeModule;
|
||||
if (runtimeModule?.createSystems) {
|
||||
try {
|
||||
runtimeModule.createSystems(scene, context);
|
||||
logger.debug(`Systems created for: ${pluginId}`);
|
||||
} catch (e) {
|
||||
logger.error(`Failed to create systems for ${pluginId}:`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.info('Systems created for scene');
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化所有编辑器模块
|
||||
* Initialize all editor modules
|
||||
*/
|
||||
async initializeEditor(services: ServiceContainer): Promise<void> {
|
||||
if (this.editorInitialized) {
|
||||
logger.warn('Editor already initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info('Initializing editor modules...');
|
||||
|
||||
const sortedPlugins = this.sortByLoadingPhase('editor');
|
||||
|
||||
// 获取注册表服务 | Get registry services
|
||||
const entityCreationRegistry = services.tryResolve(EntityCreationRegistry);
|
||||
const componentActionRegistry = services.tryResolve(ComponentActionRegistry);
|
||||
const fileActionRegistry = services.tryResolve(FileActionRegistry);
|
||||
const uiRegistry = services.tryResolve(UIRegistry);
|
||||
|
||||
for (const pluginId of sortedPlugins) {
|
||||
const plugin = this.plugins.get(pluginId);
|
||||
if (!plugin?.enabled) continue;
|
||||
|
||||
const editorModule = plugin.loader.editorModule;
|
||||
if (editorModule) {
|
||||
try {
|
||||
// 安装编辑器模块 | Install editor module
|
||||
await editorModule.install(services);
|
||||
logger.debug(`Editor module installed: ${pluginId}`);
|
||||
|
||||
// 注册实体创建模板 | Register entity creation templates
|
||||
if (entityCreationRegistry && editorModule.getEntityCreationTemplates) {
|
||||
const templates = editorModule.getEntityCreationTemplates();
|
||||
if (templates && templates.length > 0) {
|
||||
entityCreationRegistry.registerMany(templates);
|
||||
logger.debug(`Registered ${templates.length} entity templates from: ${pluginId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 注册组件操作 | Register component actions
|
||||
if (componentActionRegistry && editorModule.getComponentActions) {
|
||||
const actions = editorModule.getComponentActions();
|
||||
if (actions && actions.length > 0) {
|
||||
for (const action of actions) {
|
||||
componentActionRegistry.register(action);
|
||||
}
|
||||
logger.debug(`Registered ${actions.length} component actions from: ${pluginId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 注册文件操作处理器 | Register file action handlers
|
||||
if (fileActionRegistry && editorModule.getFileActionHandlers) {
|
||||
const handlers = editorModule.getFileActionHandlers();
|
||||
if (handlers && handlers.length > 0) {
|
||||
for (const handler of handlers) {
|
||||
fileActionRegistry.registerActionHandler(handler);
|
||||
}
|
||||
logger.debug(`Registered ${handlers.length} file action handlers from: ${pluginId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 注册文件创建模板 | Register file creation templates
|
||||
if (fileActionRegistry && editorModule.getFileCreationTemplates) {
|
||||
const templates = editorModule.getFileCreationTemplates();
|
||||
if (templates && templates.length > 0) {
|
||||
for (const template of templates) {
|
||||
fileActionRegistry.registerCreationTemplate(template);
|
||||
}
|
||||
logger.debug(`Registered ${templates.length} file creation templates from: ${pluginId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 注册面板 | Register panels
|
||||
if (uiRegistry && editorModule.getPanels) {
|
||||
const panels = editorModule.getPanels();
|
||||
if (panels && panels.length > 0) {
|
||||
uiRegistry.registerPanels(panels);
|
||||
logger.debug(`Registered ${panels.length} panels from: ${pluginId}`);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error(`Failed to install editor module for ${pluginId}:`, e);
|
||||
plugin.state = 'error';
|
||||
plugin.error = e as Error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.editorInitialized = true;
|
||||
logger.info('Editor modules initialized');
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化单个插件的编辑器模块(用于动态加载的项目插件)
|
||||
* Initialize a single plugin's editor module (for dynamically loaded project plugins)
|
||||
*/
|
||||
async initializePluginEditor(pluginId: string, services: ServiceContainer): Promise<void> {
|
||||
const plugin = this.plugins.get(pluginId);
|
||||
if (!plugin?.enabled) {
|
||||
logger.warn(`Plugin ${pluginId} not found or not enabled`);
|
||||
return;
|
||||
}
|
||||
|
||||
const editorModule = plugin.loader.editorModule;
|
||||
if (!editorModule) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取注册表服务 | Get registry services
|
||||
const entityCreationRegistry = services.tryResolve(EntityCreationRegistry);
|
||||
const componentActionRegistry = services.tryResolve(ComponentActionRegistry);
|
||||
const fileActionRegistry = services.tryResolve(FileActionRegistry);
|
||||
const uiRegistry = services.tryResolve(UIRegistry);
|
||||
|
||||
try {
|
||||
// 安装编辑器模块 | Install editor module
|
||||
await editorModule.install(services);
|
||||
logger.debug(`Editor module installed: ${pluginId}`);
|
||||
|
||||
// 注册实体创建模板 | Register entity creation templates
|
||||
if (entityCreationRegistry && editorModule.getEntityCreationTemplates) {
|
||||
const templates = editorModule.getEntityCreationTemplates();
|
||||
if (templates && templates.length > 0) {
|
||||
entityCreationRegistry.registerMany(templates);
|
||||
logger.debug(`Registered ${templates.length} entity templates from: ${pluginId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 注册组件操作 | Register component actions
|
||||
if (componentActionRegistry && editorModule.getComponentActions) {
|
||||
const actions = editorModule.getComponentActions();
|
||||
if (actions && actions.length > 0) {
|
||||
for (const action of actions) {
|
||||
componentActionRegistry.register(action);
|
||||
}
|
||||
logger.debug(`Registered ${actions.length} component actions from: ${pluginId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 注册文件操作处理器 | Register file action handlers
|
||||
if (fileActionRegistry && editorModule.getFileActionHandlers) {
|
||||
const handlers = editorModule.getFileActionHandlers();
|
||||
if (handlers && handlers.length > 0) {
|
||||
for (const handler of handlers) {
|
||||
fileActionRegistry.registerActionHandler(handler);
|
||||
}
|
||||
logger.debug(`Registered ${handlers.length} file action handlers from: ${pluginId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 注册文件创建模板 | Register file creation templates
|
||||
if (fileActionRegistry && editorModule.getFileCreationTemplates) {
|
||||
const templates = editorModule.getFileCreationTemplates();
|
||||
if (templates && templates.length > 0) {
|
||||
for (const template of templates) {
|
||||
fileActionRegistry.registerCreationTemplate(template);
|
||||
}
|
||||
logger.debug(`Registered ${templates.length} file creation templates from: ${pluginId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 注册面板 | Register panels
|
||||
if (uiRegistry && editorModule.getPanels) {
|
||||
const panels = editorModule.getPanels();
|
||||
if (panels && panels.length > 0) {
|
||||
uiRegistry.registerPanels(panels);
|
||||
logger.debug(`Registered ${panels.length} panels from: ${pluginId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 调用 onEditorReady(如果编辑器已就绪)
|
||||
if (this.editorInitialized && editorModule.onEditorReady) {
|
||||
await editorModule.onEditorReady();
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error(`Failed to install editor module for ${pluginId}:`, e);
|
||||
plugin.state = 'error';
|
||||
plugin.error = e as Error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知编辑器就绪
|
||||
* Notify editor ready
|
||||
*/
|
||||
async notifyEditorReady(): Promise<void> {
|
||||
for (const [pluginId, plugin] of this.plugins) {
|
||||
if (!plugin.enabled) continue;
|
||||
const editorModule = plugin.loader.editorModule;
|
||||
if (editorModule?.onEditorReady) {
|
||||
try {
|
||||
await editorModule.onEditorReady();
|
||||
} catch (e) {
|
||||
logger.error(`Error in ${pluginId}.onEditorReady:`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知项目打开
|
||||
* Notify project open
|
||||
*/
|
||||
async notifyProjectOpen(projectPath: string): Promise<void> {
|
||||
for (const [pluginId, plugin] of this.plugins) {
|
||||
if (!plugin.enabled) continue;
|
||||
const editorModule = plugin.loader.editorModule;
|
||||
if (editorModule?.onProjectOpen) {
|
||||
try {
|
||||
await editorModule.onProjectOpen(projectPath);
|
||||
} catch (e) {
|
||||
logger.error(`Error in ${pluginId}.onProjectOpen:`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知项目关闭
|
||||
* Notify project close
|
||||
*/
|
||||
async notifyProjectClose(): Promise<void> {
|
||||
for (const [pluginId, plugin] of this.plugins) {
|
||||
if (!plugin.enabled) continue;
|
||||
const editorModule = plugin.loader.editorModule;
|
||||
if (editorModule?.onProjectClose) {
|
||||
try {
|
||||
await editorModule.onProjectClose();
|
||||
} catch (e) {
|
||||
logger.error(`Error in ${pluginId}.onProjectClose:`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知场景加载
|
||||
* Notify scene loaded
|
||||
*/
|
||||
notifySceneLoaded(scenePath: string): void {
|
||||
for (const [pluginId, plugin] of this.plugins) {
|
||||
if (!plugin.enabled) continue;
|
||||
const editorModule = plugin.loader.editorModule;
|
||||
if (editorModule?.onSceneLoaded) {
|
||||
try {
|
||||
editorModule.onSceneLoaded(scenePath);
|
||||
} catch (e) {
|
||||
logger.error(`Error in ${pluginId}.onSceneLoaded:`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知场景保存
|
||||
* Notify scene saving
|
||||
*/
|
||||
notifySceneSaving(scenePath: string): boolean {
|
||||
for (const [pluginId, plugin] of this.plugins) {
|
||||
if (!plugin.enabled) continue;
|
||||
const editorModule = plugin.loader.editorModule;
|
||||
if (editorModule?.onSceneSaving) {
|
||||
try {
|
||||
const result = editorModule.onSceneSaving(scenePath);
|
||||
if (result === false) {
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error(`Error in ${pluginId}.onSceneSaving:`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置所有插件的语言
|
||||
* Set locale for all plugins
|
||||
*/
|
||||
setLocale(locale: string): void {
|
||||
for (const [pluginId, plugin] of this.plugins) {
|
||||
if (!plugin.enabled) continue;
|
||||
const editorModule = plugin.loader.editorModule;
|
||||
if (editorModule?.setLocale) {
|
||||
try {
|
||||
editorModule.setLocale(locale);
|
||||
} catch (e) {
|
||||
logger.error(`Error in ${pluginId}.setLocale:`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出配置
|
||||
* Export configuration
|
||||
*/
|
||||
exportConfig(): PluginConfig {
|
||||
const enabledPlugins: string[] = [];
|
||||
for (const [id, plugin] of this.plugins) {
|
||||
if (plugin.enabled && !plugin.loader.descriptor.isCore) {
|
||||
enabledPlugins.push(id);
|
||||
}
|
||||
}
|
||||
return { enabledPlugins };
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载配置
|
||||
* Load configuration
|
||||
*/
|
||||
loadConfig(config: PluginConfig): void {
|
||||
const { enabledPlugins } = config;
|
||||
|
||||
for (const [id, plugin] of this.plugins) {
|
||||
if (plugin.loader.descriptor.isCore) {
|
||||
plugin.enabled = true;
|
||||
} else {
|
||||
plugin.enabled = enabledPlugins.includes(id);
|
||||
plugin.state = plugin.enabled ? 'loaded' : 'disabled';
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`Loaded config: ${enabledPlugins.length} plugins enabled`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按加载阶段排序
|
||||
* Sort by loading phase
|
||||
*/
|
||||
private sortByLoadingPhase(moduleType: 'runtime' | 'editor'): string[] {
|
||||
const pluginIds = Array.from(this.plugins.keys());
|
||||
|
||||
// 先按依赖拓扑排序
|
||||
const sorted = this.topologicalSort(pluginIds);
|
||||
|
||||
// 再按加载阶段排序(稳定排序)
|
||||
sorted.sort((a, b) => {
|
||||
const pluginA = this.plugins.get(a);
|
||||
const pluginB = this.plugins.get(b);
|
||||
|
||||
const moduleA = moduleType === 'runtime'
|
||||
? pluginA?.loader.descriptor.modules.find(m => m.type === 'runtime')
|
||||
: pluginA?.loader.descriptor.modules.find(m => m.type === 'editor');
|
||||
|
||||
const moduleB = moduleType === 'runtime'
|
||||
? pluginB?.loader.descriptor.modules.find(m => m.type === 'runtime')
|
||||
: pluginB?.loader.descriptor.modules.find(m => m.type === 'editor');
|
||||
|
||||
const phaseA = moduleA?.loadingPhase || 'default';
|
||||
const phaseB = moduleB?.loadingPhase || 'default';
|
||||
|
||||
return LOADING_PHASE_ORDER.indexOf(phaseA) - LOADING_PHASE_ORDER.indexOf(phaseB);
|
||||
});
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拓扑排序(处理依赖)
|
||||
* Topological sort (handle dependencies)
|
||||
*/
|
||||
private topologicalSort(pluginIds: string[]): string[] {
|
||||
const visited = new Set<string>();
|
||||
const result: string[] = [];
|
||||
|
||||
const visit = (id: string) => {
|
||||
if (visited.has(id)) return;
|
||||
visited.add(id);
|
||||
|
||||
const plugin = this.plugins.get(id);
|
||||
if (plugin) {
|
||||
const deps = plugin.loader.descriptor.dependencies || [];
|
||||
for (const dep of deps) {
|
||||
if (pluginIds.includes(dep.id)) {
|
||||
visit(dep.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.push(id);
|
||||
};
|
||||
|
||||
for (const id of pluginIds) {
|
||||
visit(id);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理所有场景系统
|
||||
* Clear scene systems
|
||||
*/
|
||||
clearSceneSystems(): void {
|
||||
for (const [pluginId, plugin] of this.plugins) {
|
||||
if (!plugin.enabled) continue;
|
||||
const runtimeModule = plugin.loader.runtimeModule;
|
||||
if (runtimeModule?.onDestroy) {
|
||||
try {
|
||||
runtimeModule.onDestroy();
|
||||
} catch (e) {
|
||||
logger.error(`Error in ${pluginId}.onDestroy:`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置
|
||||
* Reset
|
||||
*/
|
||||
reset(): void {
|
||||
this.clearSceneSystems();
|
||||
this.plugins.clear();
|
||||
this.initialized = false;
|
||||
this.editorInitialized = false;
|
||||
logger.info('PluginManager reset');
|
||||
}
|
||||
}
|
||||
8
packages/editor-core/src/Plugin/index.ts
Normal file
8
packages/editor-core/src/Plugin/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* 插件系统
|
||||
* Plugin System
|
||||
*/
|
||||
|
||||
export * from './PluginDescriptor';
|
||||
export * from './IPluginLoader';
|
||||
export * from './PluginManager';
|
||||
@@ -1,479 +0,0 @@
|
||||
import { PluginManager } from '@esengine/ecs-framework';
|
||||
import type { Core, ServiceContainer } from '@esengine/ecs-framework';
|
||||
import { Injectable } from '@esengine/ecs-framework';
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import type { IEditorPlugin, IEditorPluginMetadata } from './IEditorPlugin';
|
||||
import { EditorPluginCategory } from './IEditorPlugin';
|
||||
import { UIRegistry } from '../Services/UIRegistry';
|
||||
import { MessageHub } from '../Services/MessageHub';
|
||||
import { SerializerRegistry } from '../Services/SerializerRegistry';
|
||||
import { FileActionRegistry } from '../Services/FileActionRegistry';
|
||||
import { EntityCreationRegistry } from '../Services/EntityCreationRegistry';
|
||||
import { ComponentActionRegistry } from '../Services/ComponentActionRegistry';
|
||||
import { pluginRegistry } from './PluginRegistry';
|
||||
import type { EditorPluginDefinition } from './PluginTypes';
|
||||
|
||||
const logger = createLogger('EditorPluginManager');
|
||||
|
||||
/**
|
||||
* 编辑器插件管理器
|
||||
*
|
||||
* 扩展运行时插件管理器,提供编辑器特定的插件管理功能。
|
||||
*/
|
||||
@Injectable()
|
||||
export class EditorPluginManager extends PluginManager {
|
||||
private editorPlugins: Map<string, IEditorPlugin> = new Map();
|
||||
private pluginMetadata: Map<string, IEditorPluginMetadata> = new Map();
|
||||
private uiRegistry: UIRegistry | null = null;
|
||||
private messageHub: MessageHub | null = null;
|
||||
private serializerRegistry: SerializerRegistry | null = null;
|
||||
private fileActionRegistry: FileActionRegistry | null = null;
|
||||
private entityCreationRegistry: EntityCreationRegistry | null = null;
|
||||
private componentActionRegistry: ComponentActionRegistry | null = null;
|
||||
|
||||
/**
|
||||
* 初始化编辑器插件管理器
|
||||
*/
|
||||
public override initialize(core: Core, services: ServiceContainer): void {
|
||||
super.initialize(core, services);
|
||||
|
||||
this.uiRegistry = services.resolve(UIRegistry);
|
||||
this.messageHub = services.resolve(MessageHub);
|
||||
this.serializerRegistry = services.resolve(SerializerRegistry);
|
||||
this.fileActionRegistry = services.resolve(FileActionRegistry);
|
||||
this.entityCreationRegistry = services.resolve(EntityCreationRegistry);
|
||||
this.componentActionRegistry = services.resolve(ComponentActionRegistry);
|
||||
|
||||
logger.info('EditorPluginManager initialized');
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装编辑器插件
|
||||
*/
|
||||
public async installEditor(plugin: IEditorPlugin): Promise<void> {
|
||||
if (!this.uiRegistry || !this.messageHub || !this.serializerRegistry) {
|
||||
throw new Error('EditorPluginManager not initialized. Call initialize() first.');
|
||||
}
|
||||
|
||||
logger.info(`Installing editor plugin: ${plugin.name} (${plugin.displayName})`);
|
||||
|
||||
await super.install(plugin);
|
||||
|
||||
this.editorPlugins.set(plugin.name, plugin);
|
||||
|
||||
const metadata: IEditorPluginMetadata = {
|
||||
name: plugin.name,
|
||||
displayName: plugin.displayName,
|
||||
version: plugin.version,
|
||||
category: plugin.category,
|
||||
description: plugin.description,
|
||||
icon: plugin.icon,
|
||||
enabled: true,
|
||||
installedAt: Date.now()
|
||||
};
|
||||
this.pluginMetadata.set(plugin.name, metadata);
|
||||
|
||||
try {
|
||||
if (plugin.registerMenuItems) {
|
||||
const menuItems = plugin.registerMenuItems();
|
||||
this.uiRegistry.registerMenus(menuItems);
|
||||
logger.debug(`Registered ${menuItems.length} menu items for ${plugin.name}`);
|
||||
}
|
||||
|
||||
if (plugin.registerToolbar) {
|
||||
const toolbarItems = plugin.registerToolbar();
|
||||
this.uiRegistry.registerToolbarItems(toolbarItems);
|
||||
logger.debug(`Registered ${toolbarItems.length} toolbar items for ${plugin.name}`);
|
||||
}
|
||||
|
||||
if (plugin.registerPanels) {
|
||||
const panels = plugin.registerPanels();
|
||||
this.uiRegistry.registerPanels(panels);
|
||||
logger.debug(`Registered ${panels.length} panels for ${plugin.name}`);
|
||||
}
|
||||
|
||||
if (plugin.getSerializers) {
|
||||
const serializers = plugin.getSerializers();
|
||||
this.serializerRegistry.registerMultiple(plugin.name, serializers);
|
||||
logger.debug(`Registered ${serializers.length} serializers for ${plugin.name}`);
|
||||
}
|
||||
|
||||
if (plugin.registerFileActionHandlers && this.fileActionRegistry) {
|
||||
const handlers = plugin.registerFileActionHandlers();
|
||||
for (const handler of handlers) {
|
||||
this.fileActionRegistry.registerActionHandler(handler);
|
||||
}
|
||||
logger.debug(`Registered ${handlers.length} file action handlers for ${plugin.name}`);
|
||||
}
|
||||
|
||||
if (plugin.registerFileCreationTemplates && this.fileActionRegistry) {
|
||||
const templates = plugin.registerFileCreationTemplates();
|
||||
for (const template of templates) {
|
||||
this.fileActionRegistry.registerCreationTemplate(template);
|
||||
}
|
||||
logger.debug(`Registered ${templates.length} file creation templates for ${plugin.name}`);
|
||||
}
|
||||
|
||||
if (plugin.registerEntityCreationTemplates && this.entityCreationRegistry) {
|
||||
const templates = plugin.registerEntityCreationTemplates();
|
||||
this.entityCreationRegistry.registerMany(templates);
|
||||
logger.debug(`Registered ${templates.length} entity creation templates for ${plugin.name}`);
|
||||
}
|
||||
|
||||
if (plugin.registerComponentActions && this.componentActionRegistry) {
|
||||
const actions = plugin.registerComponentActions();
|
||||
this.componentActionRegistry.registerMany(actions);
|
||||
logger.debug(`Registered ${actions.length} component actions for ${plugin.name}`);
|
||||
}
|
||||
|
||||
if (plugin.onEditorReady) {
|
||||
await plugin.onEditorReady();
|
||||
}
|
||||
|
||||
await this.messageHub.publish('plugin:installed', {
|
||||
name: plugin.name,
|
||||
displayName: plugin.displayName,
|
||||
category: plugin.category
|
||||
});
|
||||
|
||||
logger.info(`Editor plugin ${plugin.name} installed successfully`);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to install editor plugin ${plugin.name}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 卸载编辑器插件
|
||||
*/
|
||||
public async uninstallEditor(name: string): Promise<void> {
|
||||
const plugin = this.editorPlugins.get(name);
|
||||
if (!plugin) {
|
||||
throw new Error(`Editor plugin ${name} is not installed`);
|
||||
}
|
||||
|
||||
logger.info(`Uninstalling editor plugin: ${name}`);
|
||||
|
||||
try {
|
||||
if (plugin.registerMenuItems) {
|
||||
const menuItems = plugin.registerMenuItems();
|
||||
for (const item of menuItems) {
|
||||
this.uiRegistry?.unregisterMenu(item.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (plugin.registerToolbar) {
|
||||
const toolbarItems = plugin.registerToolbar();
|
||||
for (const item of toolbarItems) {
|
||||
this.uiRegistry?.unregisterToolbarItem(item.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (plugin.registerPanels) {
|
||||
const panels = plugin.registerPanels();
|
||||
for (const panel of panels) {
|
||||
this.uiRegistry?.unregisterPanel(panel.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (plugin.registerFileActionHandlers && this.fileActionRegistry) {
|
||||
const handlers = plugin.registerFileActionHandlers();
|
||||
for (const handler of handlers) {
|
||||
this.fileActionRegistry.unregisterActionHandler(handler);
|
||||
}
|
||||
}
|
||||
|
||||
if (plugin.registerFileCreationTemplates && this.fileActionRegistry) {
|
||||
const templates = plugin.registerFileCreationTemplates();
|
||||
for (const template of templates) {
|
||||
this.fileActionRegistry.unregisterCreationTemplate(template);
|
||||
}
|
||||
}
|
||||
|
||||
if (plugin.registerEntityCreationTemplates && this.entityCreationRegistry) {
|
||||
const templates = plugin.registerEntityCreationTemplates();
|
||||
for (const template of templates) {
|
||||
this.entityCreationRegistry.unregister(template.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (plugin.registerComponentActions && this.componentActionRegistry) {
|
||||
const actions = plugin.registerComponentActions();
|
||||
for (const action of actions) {
|
||||
this.componentActionRegistry.unregister(action.componentName, action.id);
|
||||
}
|
||||
}
|
||||
|
||||
this.serializerRegistry?.unregisterAll(name);
|
||||
|
||||
await super.uninstall(name);
|
||||
|
||||
this.editorPlugins.delete(name);
|
||||
this.pluginMetadata.delete(name);
|
||||
|
||||
await this.messageHub?.publish('plugin:uninstalled', { name });
|
||||
|
||||
logger.info(`Editor plugin ${name} uninstalled successfully`);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to uninstall editor plugin ${name}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取编辑器插件
|
||||
*/
|
||||
public getEditorPlugin(name: string): IEditorPlugin | undefined {
|
||||
return this.editorPlugins.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有编辑器插件
|
||||
*/
|
||||
public getAllEditorPlugins(): IEditorPlugin[] {
|
||||
return Array.from(this.editorPlugins.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件元数据
|
||||
*/
|
||||
public getPluginMetadata(name: string): IEditorPluginMetadata | undefined {
|
||||
return this.pluginMetadata.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有插件元数据
|
||||
*
|
||||
* 实时从插件实例获取 displayName 和 description,以支持多语言切换
|
||||
*/
|
||||
public getAllPluginMetadata(): IEditorPluginMetadata[] {
|
||||
const metadataList: IEditorPluginMetadata[] = [];
|
||||
|
||||
for (const [name, metadata] of this.pluginMetadata.entries()) {
|
||||
const plugin = this.editorPlugins.get(name);
|
||||
|
||||
// 如果插件实例存在,使用实时的 displayName 和 description
|
||||
if (plugin) {
|
||||
metadataList.push({
|
||||
...metadata,
|
||||
displayName: plugin.displayName,
|
||||
description: plugin.description
|
||||
});
|
||||
} else {
|
||||
// 回退到缓存的元数据
|
||||
metadataList.push(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
return metadataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按类别获取插件
|
||||
*/
|
||||
public getPluginsByCategory(category: EditorPluginCategory): IEditorPlugin[] {
|
||||
return this.getAllEditorPlugins().filter((plugin) => plugin.category === category);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用插件
|
||||
*/
|
||||
public async enablePlugin(name: string): Promise<void> {
|
||||
const metadata = this.pluginMetadata.get(name);
|
||||
if (!metadata) {
|
||||
throw new Error(`Plugin ${name} not found`);
|
||||
}
|
||||
|
||||
if (metadata.enabled) {
|
||||
logger.warn(`Plugin ${name} is already enabled`);
|
||||
return;
|
||||
}
|
||||
|
||||
metadata.enabled = true;
|
||||
await this.messageHub?.publish('plugin:enabled', { name });
|
||||
logger.info(`Plugin ${name} enabled`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用插件
|
||||
*/
|
||||
public async disablePlugin(name: string): Promise<void> {
|
||||
const metadata = this.pluginMetadata.get(name);
|
||||
if (!metadata) {
|
||||
throw new Error(`Plugin ${name} not found`);
|
||||
}
|
||||
|
||||
if (!metadata.enabled) {
|
||||
logger.warn(`Plugin ${name} is already disabled`);
|
||||
return;
|
||||
}
|
||||
|
||||
metadata.enabled = false;
|
||||
await this.messageHub?.publish('plugin:disabled', { name });
|
||||
logger.info(`Plugin ${name} disabled`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目打开通知
|
||||
*/
|
||||
public async notifyProjectOpen(projectPath: string): Promise<void> {
|
||||
logger.info(`Notifying plugins of project open: ${projectPath}`);
|
||||
|
||||
for (const plugin of this.editorPlugins.values()) {
|
||||
if (plugin.onProjectOpen) {
|
||||
try {
|
||||
await plugin.onProjectOpen(projectPath);
|
||||
} catch (error) {
|
||||
logger.error(`Error in ${plugin.name}.onProjectOpen:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.messageHub?.publish('project:opened', { path: projectPath });
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目关闭通知
|
||||
*/
|
||||
public async notifyProjectClose(): Promise<void> {
|
||||
logger.info('Notifying plugins of project close');
|
||||
|
||||
for (const plugin of this.editorPlugins.values()) {
|
||||
if (plugin.onProjectClose) {
|
||||
try {
|
||||
await plugin.onProjectClose();
|
||||
} catch (error) {
|
||||
logger.error(`Error in ${plugin.name}.onProjectClose:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.messageHub?.publish('project:closed', {});
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件保存前通知
|
||||
*/
|
||||
public async notifyBeforeSave(filePath: string, data: any): Promise<void> {
|
||||
for (const plugin of this.editorPlugins.values()) {
|
||||
if (plugin.onBeforeSave) {
|
||||
try {
|
||||
await plugin.onBeforeSave(filePath, data);
|
||||
} catch (error) {
|
||||
logger.error(`Error in ${plugin.name}.onBeforeSave:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.messageHub?.publish('file:beforeSave', { path: filePath, data });
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件保存后通知
|
||||
*/
|
||||
public async notifyAfterSave(filePath: string): Promise<void> {
|
||||
for (const plugin of this.editorPlugins.values()) {
|
||||
if (plugin.onAfterSave) {
|
||||
try {
|
||||
await plugin.onAfterSave(filePath);
|
||||
} catch (error) {
|
||||
logger.error(`Error in ${plugin.name}.onAfterSave:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.messageHub?.publish('file:afterSave', { path: filePath });
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用声明式 API 注册插件
|
||||
* Register plugin using declarative API
|
||||
*/
|
||||
public async registerPlugin(definition: EditorPluginDefinition): Promise<void> {
|
||||
logger.info(`Registering plugin with declarative API: ${definition.id}`);
|
||||
|
||||
try {
|
||||
// 使用 PluginRegistry 注册
|
||||
await pluginRegistry.register(definition);
|
||||
|
||||
// 同步到旧的元数据系统以保持兼容性
|
||||
const metadata: IEditorPluginMetadata = {
|
||||
name: definition.id,
|
||||
displayName: definition.name,
|
||||
version: definition.version || '1.0.0',
|
||||
category: EditorPluginCategory.Tool,
|
||||
description: definition.description,
|
||||
enabled: true,
|
||||
installedAt: Date.now()
|
||||
};
|
||||
this.pluginMetadata.set(definition.id, metadata);
|
||||
|
||||
// 注册实体创建模板
|
||||
if (definition.entityTemplates && this.entityCreationRegistry) {
|
||||
for (const template of definition.entityTemplates) {
|
||||
this.entityCreationRegistry.register({
|
||||
id: `${definition.id}:${template.id}`,
|
||||
label: template.label,
|
||||
icon: template.icon,
|
||||
order: template.priority,
|
||||
create: template.create
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 注册组件操作
|
||||
if (definition.components && this.componentActionRegistry) {
|
||||
for (const comp of definition.components) {
|
||||
if (comp.actions) {
|
||||
for (const action of comp.actions) {
|
||||
this.componentActionRegistry.register({
|
||||
id: action.id,
|
||||
componentName: comp.type.name,
|
||||
label: action.label,
|
||||
icon: action.icon,
|
||||
execute: action.execute as unknown as (component: any, entity: any) => void | Promise<void>
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.messageHub?.publish('plugin:installed', {
|
||||
name: definition.id,
|
||||
displayName: definition.name,
|
||||
category: EditorPluginCategory.Tool
|
||||
});
|
||||
|
||||
logger.info(`Plugin ${definition.id} registered successfully`);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to register plugin ${definition.id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 PluginRegistry 实例
|
||||
* Get PluginRegistry instance
|
||||
*/
|
||||
public getPluginRegistry() {
|
||||
return pluginRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放资源
|
||||
*/
|
||||
public override dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
this.editorPlugins.clear();
|
||||
this.pluginMetadata.clear();
|
||||
this.uiRegistry = null;
|
||||
this.messageHub = null;
|
||||
this.serializerRegistry = null;
|
||||
this.fileActionRegistry = null;
|
||||
this.entityCreationRegistry = null;
|
||||
this.componentActionRegistry = null;
|
||||
|
||||
logger.info('EditorPluginManager disposed');
|
||||
}
|
||||
}
|
||||
@@ -1,286 +0,0 @@
|
||||
import type { IPlugin } from '@esengine/ecs-framework';
|
||||
import type { MenuItem, ToolbarItem, PanelDescriptor, EntityCreationTemplate } from '../Types/UITypes';
|
||||
import type { ComponentAction } from '../Services/ComponentActionRegistry';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
/**
|
||||
* 编辑器插件类别
|
||||
*/
|
||||
export enum EditorPluginCategory {
|
||||
/**
|
||||
* 工具插件
|
||||
*/
|
||||
Tool = 'tool',
|
||||
|
||||
/**
|
||||
* 窗口插件
|
||||
*/
|
||||
Window = 'window',
|
||||
|
||||
/**
|
||||
* 检视器插件
|
||||
*/
|
||||
Inspector = 'inspector',
|
||||
|
||||
/**
|
||||
* 系统插件
|
||||
*/
|
||||
System = 'system',
|
||||
|
||||
/**
|
||||
* 导入导出插件
|
||||
*/
|
||||
ImportExport = 'import-export'
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化器接口
|
||||
*/
|
||||
export interface ISerializer<T = any> {
|
||||
/**
|
||||
* 序列化为二进制数据
|
||||
*/
|
||||
serialize(data: T): Uint8Array;
|
||||
|
||||
/**
|
||||
* 从二进制数据反序列化
|
||||
*/
|
||||
deserialize(data: Uint8Array): T;
|
||||
|
||||
/**
|
||||
* 获取序列化器支持的数据类型
|
||||
*/
|
||||
getSupportedType(): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件上下文菜单项
|
||||
*/
|
||||
export interface FileContextMenuItem {
|
||||
/**
|
||||
* 菜单项标签
|
||||
*/
|
||||
label: string;
|
||||
|
||||
/**
|
||||
* 图标
|
||||
*/
|
||||
icon?: ReactNode;
|
||||
|
||||
/**
|
||||
* 点击处理函数
|
||||
*/
|
||||
onClick: (filePath: string, parentPath: string) => void | Promise<void>;
|
||||
|
||||
/**
|
||||
* 是否禁用
|
||||
*/
|
||||
disabled?: boolean;
|
||||
|
||||
/**
|
||||
* 是否为分隔符
|
||||
*/
|
||||
separator?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件创建模板
|
||||
*/
|
||||
export interface FileCreationTemplate {
|
||||
/**
|
||||
* 模板名称
|
||||
*/
|
||||
label: string;
|
||||
|
||||
/**
|
||||
* 文件扩展名(不含点)
|
||||
*/
|
||||
extension: string;
|
||||
|
||||
/**
|
||||
* 默认文件名
|
||||
*/
|
||||
defaultFileName: string;
|
||||
|
||||
/**
|
||||
* 图标
|
||||
*/
|
||||
icon?: ReactNode;
|
||||
|
||||
/**
|
||||
* 创建文件内容的函数
|
||||
*/
|
||||
createContent: (fileName: string) => string | Promise<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件操作处理器
|
||||
*/
|
||||
export interface FileActionHandler {
|
||||
/**
|
||||
* 支持的文件扩展名列表
|
||||
*/
|
||||
extensions: string[];
|
||||
|
||||
/**
|
||||
* 双击处理函数
|
||||
*/
|
||||
onDoubleClick?: (filePath: string) => void | Promise<void>;
|
||||
|
||||
/**
|
||||
* 打开文件处理函数
|
||||
*/
|
||||
onOpen?: (filePath: string) => void | Promise<void>;
|
||||
|
||||
/**
|
||||
* 获取上下文菜单项
|
||||
*/
|
||||
getContextMenuItems?: (filePath: string, parentPath: string) => FileContextMenuItem[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑器插件接口
|
||||
*
|
||||
* 扩展了运行时插件接口,添加了编辑器特定的功能。
|
||||
*/
|
||||
export interface IEditorPlugin extends IPlugin {
|
||||
/**
|
||||
* 插件显示名称
|
||||
*/
|
||||
readonly displayName: string;
|
||||
|
||||
/**
|
||||
* 插件类别
|
||||
*/
|
||||
readonly category: EditorPluginCategory;
|
||||
|
||||
/**
|
||||
* 插件描述
|
||||
*/
|
||||
readonly description?: string;
|
||||
|
||||
/**
|
||||
* 插件图标
|
||||
*/
|
||||
readonly icon?: string;
|
||||
|
||||
/**
|
||||
* 注册菜单项
|
||||
*/
|
||||
registerMenuItems?(): MenuItem[];
|
||||
|
||||
/**
|
||||
* 注册工具栏项
|
||||
*/
|
||||
registerToolbar?(): ToolbarItem[];
|
||||
|
||||
/**
|
||||
* 注册面板
|
||||
*/
|
||||
registerPanels?(): PanelDescriptor[];
|
||||
|
||||
/**
|
||||
* 提供序列化器
|
||||
*/
|
||||
getSerializers?(): ISerializer[];
|
||||
|
||||
/**
|
||||
* 编辑器就绪回调
|
||||
*/
|
||||
onEditorReady?(): void | Promise<void>;
|
||||
|
||||
/**
|
||||
* 项目打开回调
|
||||
*/
|
||||
onProjectOpen?(projectPath: string): void | Promise<void>;
|
||||
|
||||
/**
|
||||
* 项目关闭回调
|
||||
*/
|
||||
onProjectClose?(): void | Promise<void>;
|
||||
|
||||
/**
|
||||
* 文件保存前回调
|
||||
*/
|
||||
onBeforeSave?(filePath: string, data: any): void | Promise<void>;
|
||||
|
||||
/**
|
||||
* 文件保存后回调
|
||||
*/
|
||||
onAfterSave?(filePath: string): void | Promise<void>;
|
||||
|
||||
/**
|
||||
* 设置插件语言
|
||||
*/
|
||||
setLocale?(locale: string): void;
|
||||
|
||||
/**
|
||||
* 获取行为树节点模板
|
||||
*/
|
||||
getNodeTemplates?(): any[];
|
||||
|
||||
/**
|
||||
* 注册文件操作处理器
|
||||
*/
|
||||
registerFileActionHandlers?(): FileActionHandler[];
|
||||
|
||||
/**
|
||||
* 注册文件创建模板
|
||||
*/
|
||||
registerFileCreationTemplates?(): FileCreationTemplate[];
|
||||
|
||||
/**
|
||||
* 注册实体创建模板
|
||||
*/
|
||||
registerEntityCreationTemplates?(): EntityCreationTemplate[];
|
||||
|
||||
/**
|
||||
* 注册组件操作
|
||||
*/
|
||||
registerComponentActions?(): ComponentAction[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑器插件元数据
|
||||
*/
|
||||
export interface IEditorPluginMetadata {
|
||||
/**
|
||||
* 插件名称
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* 显示名称
|
||||
*/
|
||||
displayName: string;
|
||||
|
||||
/**
|
||||
* 版本
|
||||
*/
|
||||
version: string;
|
||||
|
||||
/**
|
||||
* 类别
|
||||
*/
|
||||
category: EditorPluginCategory;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
description?: string;
|
||||
|
||||
/**
|
||||
* 图标
|
||||
*/
|
||||
icon?: string;
|
||||
|
||||
/**
|
||||
* 是否已启用
|
||||
*/
|
||||
enabled: boolean;
|
||||
|
||||
/**
|
||||
* 安装时间戳
|
||||
*/
|
||||
installedAt?: number;
|
||||
}
|
||||
@@ -1,528 +0,0 @@
|
||||
/**
|
||||
* 统一插件注册中心
|
||||
* Unified plugin registry
|
||||
*/
|
||||
|
||||
import type { ComponentType } from '@esengine/ecs-framework';
|
||||
import type { ComponentType as ReactComponentType } from 'react';
|
||||
import {
|
||||
EditorPluginDefinition,
|
||||
RegisteredPlugin,
|
||||
PluginState,
|
||||
ComponentRegistration,
|
||||
MenuItemRegistration,
|
||||
PanelRegistration,
|
||||
ToolbarItemRegistration,
|
||||
AssetHandlerRegistration,
|
||||
MenuTreeNode,
|
||||
ComponentActionDefinition
|
||||
} from './PluginTypes';
|
||||
|
||||
/**
|
||||
* 插件注册中心
|
||||
* Plugin registry - central hub for all plugin registrations
|
||||
*/
|
||||
export class PluginRegistry {
|
||||
private static _instance: PluginRegistry | null = null;
|
||||
|
||||
/** 已注册的插件 */
|
||||
private plugins: Map<string, RegisteredPlugin> = new Map();
|
||||
|
||||
/** 组件到检查器的映射 */
|
||||
private componentInspectors: Map<string, ReactComponentType<any>> = new Map();
|
||||
|
||||
/** 组件元数据 */
|
||||
private componentMeta: Map<string, ComponentRegistration> = new Map();
|
||||
|
||||
/** 组件操作 */
|
||||
private componentActions: Map<string, ComponentActionDefinition[]> = new Map();
|
||||
|
||||
/** 菜单树 */
|
||||
private menuTree: MenuTreeNode = {
|
||||
name: 'root',
|
||||
path: '',
|
||||
children: new Map()
|
||||
};
|
||||
|
||||
/** 面板注册 */
|
||||
private panels: Map<string, PanelRegistration & { pluginId: string }> = new Map();
|
||||
|
||||
/** 工具栏项 */
|
||||
private toolbarItems: Map<string, ToolbarItemRegistration & { pluginId: string }> = new Map();
|
||||
|
||||
/** 资产处理器 */
|
||||
private assetHandlers: Map<string, AssetHandlerRegistration & { pluginId: string }> = new Map();
|
||||
|
||||
/** 事件监听器 */
|
||||
private listeners: Map<string, Set<Function>> = new Map();
|
||||
|
||||
private constructor() {}
|
||||
|
||||
/**
|
||||
* 获取单例实例
|
||||
*/
|
||||
static getInstance(): PluginRegistry {
|
||||
if (!PluginRegistry._instance) {
|
||||
PluginRegistry._instance = new PluginRegistry();
|
||||
}
|
||||
return PluginRegistry._instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册插件
|
||||
* Register a plugin
|
||||
*/
|
||||
async register(definition: EditorPluginDefinition): Promise<void> {
|
||||
if (this.plugins.has(definition.id)) {
|
||||
console.warn(`Plugin ${definition.id} is already registered`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查依赖
|
||||
if (definition.dependencies) {
|
||||
for (const dep of definition.dependencies) {
|
||||
if (!this.plugins.has(dep)) {
|
||||
throw new Error(`Plugin ${definition.id} depends on ${dep}, which is not registered`);
|
||||
}
|
||||
const depPlugin = this.plugins.get(dep)!;
|
||||
if (depPlugin.state !== 'active') {
|
||||
throw new Error(`Plugin ${definition.id} depends on ${dep}, which is not active`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建注册记录
|
||||
const registered: RegisteredPlugin = {
|
||||
definition,
|
||||
state: 'inactive'
|
||||
};
|
||||
this.plugins.set(definition.id, registered);
|
||||
|
||||
// 激活插件
|
||||
await this.activatePlugin(definition.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 激活插件
|
||||
*/
|
||||
private async activatePlugin(pluginId: string): Promise<void> {
|
||||
const registered = this.plugins.get(pluginId);
|
||||
if (!registered) return;
|
||||
|
||||
const { definition } = registered;
|
||||
registered.state = 'activating';
|
||||
|
||||
try {
|
||||
// 注册组件
|
||||
if (definition.components) {
|
||||
for (const comp of definition.components) {
|
||||
this.registerComponent(pluginId, comp);
|
||||
}
|
||||
}
|
||||
|
||||
// 注册菜单项
|
||||
if (definition.menuItems) {
|
||||
for (const item of definition.menuItems) {
|
||||
this.registerMenuItem(pluginId, item);
|
||||
}
|
||||
}
|
||||
|
||||
// 注册面板
|
||||
if (definition.panels) {
|
||||
for (const panel of definition.panels) {
|
||||
this.registerPanel(pluginId, panel);
|
||||
}
|
||||
}
|
||||
|
||||
// 注册工具栏项
|
||||
if (definition.toolbarItems) {
|
||||
for (const item of definition.toolbarItems) {
|
||||
this.registerToolbarItem(pluginId, item);
|
||||
}
|
||||
}
|
||||
|
||||
// 注册资产处理器
|
||||
if (definition.assetHandlers) {
|
||||
for (const handler of definition.assetHandlers) {
|
||||
this.registerAssetHandler(pluginId, handler);
|
||||
}
|
||||
}
|
||||
|
||||
// 调用激活钩子
|
||||
if (definition.onActivate) {
|
||||
await definition.onActivate();
|
||||
}
|
||||
|
||||
registered.state = 'active';
|
||||
registered.activatedAt = Date.now();
|
||||
|
||||
this.emit('plugin:activated', { pluginId });
|
||||
} catch (error) {
|
||||
registered.state = 'error';
|
||||
registered.error = error instanceof Error ? error.message : String(error);
|
||||
console.error(`Failed to activate plugin ${pluginId}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停用插件
|
||||
*/
|
||||
async deactivate(pluginId: string): Promise<void> {
|
||||
const registered = this.plugins.get(pluginId);
|
||||
if (!registered || registered.state !== 'active') return;
|
||||
|
||||
const { definition } = registered;
|
||||
registered.state = 'deactivating';
|
||||
|
||||
try {
|
||||
// 调用停用钩子
|
||||
if (definition.onDeactivate) {
|
||||
definition.onDeactivate();
|
||||
}
|
||||
|
||||
// 清理注册的资源
|
||||
this.unregisterPluginResources(pluginId);
|
||||
|
||||
registered.state = 'inactive';
|
||||
this.emit('plugin:deactivated', { pluginId });
|
||||
} catch (error) {
|
||||
registered.state = 'error';
|
||||
registered.error = error instanceof Error ? error.message : String(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册组件
|
||||
*/
|
||||
private registerComponent(pluginId: string, config: ComponentRegistration): void {
|
||||
const typeName = config.type.name;
|
||||
|
||||
// 保存元数据
|
||||
this.componentMeta.set(typeName, config);
|
||||
|
||||
// 注册检查器
|
||||
if (config.inspector) {
|
||||
this.componentInspectors.set(typeName, config.inspector);
|
||||
}
|
||||
|
||||
// 注册操作
|
||||
if (config.actions) {
|
||||
this.componentActions.set(typeName, config.actions);
|
||||
}
|
||||
|
||||
this.emit('component:registered', { pluginId, typeName, config });
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册菜单项
|
||||
*/
|
||||
private registerMenuItem(pluginId: string, item: MenuItemRegistration): void {
|
||||
const parts = item.path.split('/');
|
||||
let current = this.menuTree;
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const part = parts[i];
|
||||
const isLast = i === parts.length - 1;
|
||||
|
||||
if (!current.children.has(part)) {
|
||||
current.children.set(part, {
|
||||
name: part,
|
||||
path: parts.slice(0, i + 1).join('/'),
|
||||
children: new Map()
|
||||
});
|
||||
}
|
||||
|
||||
current = current.children.get(part)!;
|
||||
|
||||
if (isLast) {
|
||||
current.item = item;
|
||||
current.pluginId = pluginId;
|
||||
}
|
||||
}
|
||||
|
||||
this.emit('menu:registered', { pluginId, path: item.path });
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册面板
|
||||
*/
|
||||
private registerPanel(pluginId: string, panel: PanelRegistration): void {
|
||||
this.panels.set(panel.id, { ...panel, pluginId });
|
||||
this.emit('panel:registered', { pluginId, panelId: panel.id });
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册工具栏项
|
||||
*/
|
||||
private registerToolbarItem(pluginId: string, item: ToolbarItemRegistration): void {
|
||||
this.toolbarItems.set(item.id, { ...item, pluginId });
|
||||
this.emit('toolbar:registered', { pluginId, itemId: item.id });
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册资产处理器
|
||||
*/
|
||||
private registerAssetHandler(pluginId: string, handler: AssetHandlerRegistration): void {
|
||||
for (const ext of handler.extensions) {
|
||||
this.assetHandlers.set(ext.toLowerCase(), { ...handler, pluginId });
|
||||
}
|
||||
this.emit('assetHandler:registered', { pluginId, extensions: handler.extensions });
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理插件资源
|
||||
*/
|
||||
private unregisterPluginResources(pluginId: string): void {
|
||||
const definition = this.plugins.get(pluginId)?.definition;
|
||||
if (!definition) return;
|
||||
|
||||
// 清理组件
|
||||
if (definition.components) {
|
||||
for (const comp of definition.components) {
|
||||
const typeName = comp.type.name;
|
||||
this.componentMeta.delete(typeName);
|
||||
this.componentInspectors.delete(typeName);
|
||||
this.componentActions.delete(typeName);
|
||||
}
|
||||
}
|
||||
|
||||
// 清理面板
|
||||
if (definition.panels) {
|
||||
for (const panel of definition.panels) {
|
||||
this.panels.delete(panel.id);
|
||||
}
|
||||
}
|
||||
|
||||
// 清理工具栏项
|
||||
if (definition.toolbarItems) {
|
||||
for (const item of definition.toolbarItems) {
|
||||
this.toolbarItems.delete(item.id);
|
||||
}
|
||||
}
|
||||
|
||||
// 清理资产处理器
|
||||
if (definition.assetHandlers) {
|
||||
for (const handler of definition.assetHandlers) {
|
||||
for (const ext of handler.extensions) {
|
||||
this.assetHandlers.delete(ext.toLowerCase());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 清理菜单项(需要遍历树)
|
||||
this.removeMenuItemsForPlugin(pluginId, this.menuTree);
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归移除插件的菜单项
|
||||
*/
|
||||
private removeMenuItemsForPlugin(pluginId: string, node: MenuTreeNode): void {
|
||||
for (const [key, child] of node.children) {
|
||||
if (child.pluginId === pluginId) {
|
||||
node.children.delete(key);
|
||||
} else {
|
||||
this.removeMenuItemsForPlugin(pluginId, child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === 查询 API ===
|
||||
|
||||
/**
|
||||
* 获取组件的检查器
|
||||
*/
|
||||
getComponentInspector(typeName: string): ReactComponentType<any> | undefined {
|
||||
return this.componentInspectors.get(typeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件元数据
|
||||
*/
|
||||
getComponentMeta(typeName: string): ComponentRegistration | undefined {
|
||||
return this.componentMeta.get(typeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件操作
|
||||
*/
|
||||
getComponentActionDefinitions(typeName: string): ComponentActionDefinition[] {
|
||||
return this.componentActions.get(typeName) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有已注册的组件
|
||||
*/
|
||||
getAllComponents(): Map<string, ComponentRegistration> {
|
||||
return new Map(this.componentMeta);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取菜单树
|
||||
*/
|
||||
getMenuTree(): MenuTreeNode {
|
||||
return this.menuTree;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定路径下的菜单项
|
||||
*/
|
||||
getMenuItems(parentPath: string): MenuItemRegistration[] {
|
||||
const parts = parentPath ? parentPath.split('/') : [];
|
||||
let current = this.menuTree;
|
||||
|
||||
for (const part of parts) {
|
||||
if (!current.children.has(part)) {
|
||||
return [];
|
||||
}
|
||||
current = current.children.get(part)!;
|
||||
}
|
||||
|
||||
const items: MenuItemRegistration[] = [];
|
||||
for (const child of current.children.values()) {
|
||||
if (child.item) {
|
||||
items.push(child.item);
|
||||
}
|
||||
}
|
||||
|
||||
return items.sort((a, b) => (a.priority || 100) - (b.priority || 100));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有面板
|
||||
*/
|
||||
getAllPanels(): Map<string, PanelRegistration & { pluginId: string }> {
|
||||
return new Map(this.panels);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取面板
|
||||
*/
|
||||
getPanel(panelId: string): (PanelRegistration & { pluginId: string }) | undefined {
|
||||
return this.panels.get(panelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有工具栏项
|
||||
*/
|
||||
getAllToolbarItems(): (ToolbarItemRegistration & { pluginId: string })[] {
|
||||
return Array.from(this.toolbarItems.values())
|
||||
.sort((a, b) => (a.priority || 100) - (b.priority || 100));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取资产处理器
|
||||
*/
|
||||
getAssetHandler(extension: string): (AssetHandlerRegistration & { pluginId: string }) | undefined {
|
||||
return this.assetHandlers.get(extension.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件状态
|
||||
*/
|
||||
getPluginState(pluginId: string): PluginState | undefined {
|
||||
return this.plugins.get(pluginId)?.state;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有已注册插件
|
||||
*/
|
||||
getAllPlugins(): Map<string, RegisteredPlugin> {
|
||||
return new Map(this.plugins);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查插件是否已激活
|
||||
*/
|
||||
isPluginActive(pluginId: string): boolean {
|
||||
return this.plugins.get(pluginId)?.state === 'active';
|
||||
}
|
||||
|
||||
// === 事件系统 ===
|
||||
|
||||
/**
|
||||
* 添加事件监听器
|
||||
*/
|
||||
on(event: string, listener: Function): void {
|
||||
if (!this.listeners.has(event)) {
|
||||
this.listeners.set(event, new Set());
|
||||
}
|
||||
this.listeners.get(event)!.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件监听器
|
||||
*/
|
||||
off(event: string, listener: Function): void {
|
||||
this.listeners.get(event)?.delete(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发射事件
|
||||
*/
|
||||
private emit(event: string, data: any): void {
|
||||
const listeners = this.listeners.get(event);
|
||||
if (listeners) {
|
||||
for (const listener of listeners) {
|
||||
try {
|
||||
listener(data);
|
||||
} catch (error) {
|
||||
console.error(`Error in event listener for ${event}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知编辑器准备就绪
|
||||
*/
|
||||
notifyEditorReady(): void {
|
||||
for (const [pluginId, registered] of this.plugins) {
|
||||
if (registered.state === 'active' && registered.definition.onEditorReady) {
|
||||
try {
|
||||
registered.definition.onEditorReady();
|
||||
} catch (error) {
|
||||
console.error(`Error in onEditorReady for plugin ${pluginId}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知场景加载
|
||||
*/
|
||||
notifySceneLoaded(scenePath: string): void {
|
||||
for (const [pluginId, registered] of this.plugins) {
|
||||
if (registered.state === 'active' && registered.definition.onSceneLoaded) {
|
||||
try {
|
||||
registered.definition.onSceneLoaded(scenePath);
|
||||
} catch (error) {
|
||||
console.error(`Error in onSceneLoaded for plugin ${pluginId}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知场景保存
|
||||
*/
|
||||
notifySceneSaving(scenePath: string): boolean {
|
||||
for (const [pluginId, registered] of this.plugins) {
|
||||
if (registered.state === 'active' && registered.definition.onSceneSaving) {
|
||||
try {
|
||||
const result = registered.definition.onSceneSaving(scenePath);
|
||||
if (result === false) {
|
||||
return false; // 取消保存
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error in onSceneSaving for plugin ${pluginId}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例访问
|
||||
export const pluginRegistry = PluginRegistry.getInstance();
|
||||
@@ -1,227 +0,0 @@
|
||||
/**
|
||||
* 统一插件类型定义
|
||||
* Unified plugin type definitions
|
||||
*/
|
||||
|
||||
import type { ComponentType } from '@esengine/ecs-framework';
|
||||
import type { ComponentType as ReactComponentType } from 'react';
|
||||
|
||||
/**
|
||||
* 组件注册配置
|
||||
* Component registration configuration
|
||||
*/
|
||||
export interface ComponentRegistration {
|
||||
/** 组件类型 */
|
||||
type: ComponentType;
|
||||
/** 自定义检查器组件 */
|
||||
inspector?: ReactComponentType<any>;
|
||||
/** 图标名称 */
|
||||
icon?: string;
|
||||
/** 分类 */
|
||||
category?: string;
|
||||
/** 显示名称 */
|
||||
displayName?: string;
|
||||
/** 组件操作(如右键菜单) */
|
||||
actions?: ComponentActionDefinition[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件操作定义(用于声明式 API)
|
||||
* Component action definition (for declarative API)
|
||||
*/
|
||||
export interface ComponentActionDefinition {
|
||||
/** 操作ID */
|
||||
id: string;
|
||||
/** 显示标签 */
|
||||
label: string;
|
||||
/** 图标 */
|
||||
icon?: string;
|
||||
/** 执行函数 */
|
||||
execute: (componentData: any, entityId: number) => void | Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单项注册配置
|
||||
* Menu item registration configuration
|
||||
*/
|
||||
export interface MenuItemRegistration {
|
||||
/** 菜单路径,如 "GameObject/2D Object/Tilemap" */
|
||||
path: string;
|
||||
/** 执行动作 */
|
||||
action: () => void | number | Promise<void> | Promise<number>;
|
||||
/** 图标 */
|
||||
icon?: string;
|
||||
/** 快捷键 */
|
||||
shortcut?: string;
|
||||
/** 优先级(数字越小越靠前) */
|
||||
priority?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 面板注册配置
|
||||
* Panel registration configuration
|
||||
*/
|
||||
export interface PanelRegistration {
|
||||
/** 面板ID */
|
||||
id: string;
|
||||
/** 面板组件 */
|
||||
component: ReactComponentType<any>;
|
||||
/** 标题 */
|
||||
title: string;
|
||||
/** 图标 */
|
||||
icon?: string;
|
||||
/** 默认位置 */
|
||||
defaultPosition?: 'left' | 'right' | 'bottom' | 'float';
|
||||
/** 默认是否可见 */
|
||||
defaultVisible?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具栏项注册配置
|
||||
* Toolbar item registration configuration
|
||||
*/
|
||||
export interface ToolbarItemRegistration {
|
||||
/** 工具ID */
|
||||
id: string;
|
||||
/** 显示标签 */
|
||||
label: string;
|
||||
/** 图标 */
|
||||
icon: string;
|
||||
/** 工具提示 */
|
||||
tooltip?: string;
|
||||
/** 执行动作 */
|
||||
action: () => void;
|
||||
/** 分组 */
|
||||
group?: string;
|
||||
/** 优先级 */
|
||||
priority?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 实体创建模板注册配置
|
||||
*/
|
||||
export interface EntityTemplateRegistration {
|
||||
/** 模板ID */
|
||||
id: string;
|
||||
/** 显示名称 */
|
||||
label: string;
|
||||
/** 分类路径,如 "2D Object" */
|
||||
category?: string;
|
||||
/** 图标 */
|
||||
icon?: string;
|
||||
/** 排序优先级 */
|
||||
priority?: number;
|
||||
/** 创建实体函数,返回实体ID */
|
||||
create: (parentEntityId?: number) => number | Promise<number>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 资产处理器注册配置
|
||||
* Asset handler registration configuration
|
||||
*/
|
||||
export interface AssetHandlerRegistration {
|
||||
/** 文件扩展名列表 */
|
||||
extensions: string[];
|
||||
/** 处理器名称 */
|
||||
name: string;
|
||||
/** 图标 */
|
||||
icon?: string;
|
||||
/** 打开资产 */
|
||||
onOpen?: (assetPath: string) => void | Promise<void>;
|
||||
/** 预览资产 */
|
||||
onPreview?: (assetPath: string) => ReactComponentType<any> | null;
|
||||
/** 创建资产 */
|
||||
onCreate?: () => Promise<string | null>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑器插件定义
|
||||
* Editor plugin definition
|
||||
*/
|
||||
export interface EditorPluginDefinition {
|
||||
/** 插件唯一ID */
|
||||
id: string;
|
||||
/** 插件显示名称 */
|
||||
name: string;
|
||||
/** 版本号 */
|
||||
version?: string;
|
||||
/** 描述 */
|
||||
description?: string;
|
||||
/** 依赖的其他插件ID */
|
||||
dependencies?: string[];
|
||||
|
||||
// === 注册配置 ===
|
||||
|
||||
/** 组件注册 */
|
||||
components?: ComponentRegistration[];
|
||||
|
||||
/** 菜单项注册 */
|
||||
menuItems?: MenuItemRegistration[];
|
||||
|
||||
/** 面板注册 */
|
||||
panels?: PanelRegistration[];
|
||||
|
||||
/** 工具栏项注册 */
|
||||
toolbarItems?: ToolbarItemRegistration[];
|
||||
|
||||
/** 资产处理器注册 */
|
||||
assetHandlers?: AssetHandlerRegistration[];
|
||||
|
||||
/** 实体创建模板 */
|
||||
entityTemplates?: EntityTemplateRegistration[];
|
||||
|
||||
// === 生命周期钩子 ===
|
||||
|
||||
/** 插件激活时调用 */
|
||||
onActivate?: () => void | Promise<void>;
|
||||
|
||||
/** 插件停用时调用 */
|
||||
onDeactivate?: () => void;
|
||||
|
||||
/** 编辑器准备就绪时调用 */
|
||||
onEditorReady?: () => void | Promise<void>;
|
||||
|
||||
/** 场景加载后调用 */
|
||||
onSceneLoaded?: (scenePath: string) => void;
|
||||
|
||||
/** 场景保存前调用 */
|
||||
onSceneSaving?: (scenePath: string) => boolean | void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插件状态
|
||||
* Plugin state
|
||||
*/
|
||||
export type PluginState = 'inactive' | 'activating' | 'active' | 'deactivating' | 'error';
|
||||
|
||||
/**
|
||||
* 已注册的插件信息
|
||||
* Registered plugin info
|
||||
*/
|
||||
export interface RegisteredPlugin {
|
||||
/** 插件定义 */
|
||||
definition: EditorPluginDefinition;
|
||||
/** 当前状态 */
|
||||
state: PluginState;
|
||||
/** 错误信息(如果有) */
|
||||
error?: string;
|
||||
/** 激活时间 */
|
||||
activatedAt?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单树节点
|
||||
* Menu tree node
|
||||
*/
|
||||
export interface MenuTreeNode {
|
||||
/** 节点名称 */
|
||||
name: string;
|
||||
/** 完整路径 */
|
||||
path: string;
|
||||
/** 子节点 */
|
||||
children: Map<string, MenuTreeNode>;
|
||||
/** 菜单项(叶子节点) */
|
||||
item?: MenuItemRegistration;
|
||||
/** 来源插件ID */
|
||||
pluginId?: string;
|
||||
}
|
||||
@@ -5,17 +5,10 @@
|
||||
*/
|
||||
|
||||
import { injectable } from 'tsyringe';
|
||||
import type { IService, Component, Entity } from '@esengine/ecs-framework';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export interface ComponentAction {
|
||||
id: string;
|
||||
componentName: string;
|
||||
label: string;
|
||||
icon?: ReactNode;
|
||||
order?: number;
|
||||
execute: (component: Component, entity: Entity) => void | Promise<void>;
|
||||
}
|
||||
import type { IService } from '@esengine/ecs-framework';
|
||||
import type { ComponentAction } from '../Plugin/IPluginLoader';
|
||||
// Re-export ComponentAction type from Plugin system
|
||||
export type { ComponentAction } from '../Plugin/IPluginLoader';
|
||||
|
||||
@injectable()
|
||||
export class ComponentActionRegistry implements IService {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { IService } from '@esengine/ecs-framework';
|
||||
import { FileActionHandler, FileCreationTemplate } from '../Plugins/IEditorPlugin';
|
||||
import type { FileActionHandler, FileCreationTemplate } from '../Plugin/IPluginLoader';
|
||||
|
||||
// Re-export for backwards compatibility
|
||||
export type { FileCreationTemplate } from '../Plugin/IPluginLoader';
|
||||
|
||||
/**
|
||||
* 文件操作注册表服务
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { IFileAPI } from '../Types/IFileAPI';
|
||||
|
||||
const logger = createLogger('ProjectService');
|
||||
|
||||
export type ProjectType = 'cocos' | 'laya' | 'unknown';
|
||||
export type ProjectType = 'esengine' | 'unknown';
|
||||
|
||||
export interface ProjectInfo {
|
||||
path: string;
|
||||
@@ -15,6 +15,17 @@ export interface ProjectInfo {
|
||||
configPath?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* UI 设计分辨率配置
|
||||
* UI Design Resolution Configuration
|
||||
*/
|
||||
export interface UIDesignResolution {
|
||||
/** 设计宽度 / Design width */
|
||||
width: number;
|
||||
/** 设计高度 / Design height */
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface ProjectConfig {
|
||||
projectType?: ProjectType;
|
||||
componentsPath?: string;
|
||||
@@ -22,6 +33,8 @@ export interface ProjectConfig {
|
||||
buildOutput?: string;
|
||||
scenesPath?: string;
|
||||
defaultScene?: string;
|
||||
/** UI 设计分辨率 / UI design resolution */
|
||||
uiDesignResolution?: UIDesignResolution;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@@ -47,11 +60,11 @@ export class ProjectService implements IService {
|
||||
}
|
||||
|
||||
const config: ProjectConfig = {
|
||||
projectType: 'cocos',
|
||||
projectType: 'esengine',
|
||||
componentsPath: 'components',
|
||||
componentPattern: '**/*.ts',
|
||||
buildOutput: 'temp/editor-components',
|
||||
scenesPath: 'ecs-scenes',
|
||||
scenesPath: 'scenes',
|
||||
defaultScene: 'main.ecs'
|
||||
};
|
||||
|
||||
@@ -176,7 +189,7 @@ export class ProjectService implements IService {
|
||||
|
||||
try {
|
||||
projectInfo.configPath = configPath;
|
||||
projectInfo.type = 'cocos';
|
||||
projectInfo.type = 'esengine';
|
||||
} catch (error) {
|
||||
logger.warn('No ecs-editor.config.json found, using defaults');
|
||||
}
|
||||
@@ -185,14 +198,86 @@ export class ProjectService implements IService {
|
||||
}
|
||||
|
||||
private async loadConfig(configPath: string): Promise<ProjectConfig> {
|
||||
return {
|
||||
projectType: 'cocos',
|
||||
componentsPath: '',
|
||||
componentPattern: '**/*.ts',
|
||||
buildOutput: 'temp/editor-components',
|
||||
scenesPath: 'ecs-scenes',
|
||||
defaultScene: 'main.ecs'
|
||||
try {
|
||||
const content = await this.fileAPI.readFileContent(configPath);
|
||||
const config = JSON.parse(content) as ProjectConfig;
|
||||
return {
|
||||
projectType: config.projectType || 'esengine',
|
||||
componentsPath: config.componentsPath || '',
|
||||
componentPattern: config.componentPattern || '**/*.ts',
|
||||
buildOutput: config.buildOutput || 'temp/editor-components',
|
||||
scenesPath: config.scenesPath || 'scenes',
|
||||
defaultScene: config.defaultScene || 'main.ecs',
|
||||
uiDesignResolution: config.uiDesignResolution
|
||||
};
|
||||
} catch (error) {
|
||||
logger.warn('Failed to load config, using defaults', error);
|
||||
return {
|
||||
projectType: 'esengine',
|
||||
componentsPath: '',
|
||||
componentPattern: '**/*.ts',
|
||||
buildOutput: 'temp/editor-components',
|
||||
scenesPath: 'scenes',
|
||||
defaultScene: 'main.ecs'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存项目配置
|
||||
*/
|
||||
public async saveConfig(): Promise<void> {
|
||||
if (!this.currentProject?.configPath || !this.projectConfig) {
|
||||
logger.warn('No project or config to save');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const content = JSON.stringify(this.projectConfig, null, 2);
|
||||
await this.fileAPI.writeFileContent(this.currentProject.configPath, content);
|
||||
logger.info('Project config saved');
|
||||
} catch (error) {
|
||||
logger.error('Failed to save project config', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新项目配置
|
||||
*/
|
||||
public async updateConfig(updates: Partial<ProjectConfig>): Promise<void> {
|
||||
if (!this.projectConfig) {
|
||||
logger.warn('No project config to update');
|
||||
return;
|
||||
}
|
||||
|
||||
this.projectConfig = {
|
||||
...this.projectConfig,
|
||||
...updates
|
||||
};
|
||||
|
||||
await this.saveConfig();
|
||||
await this.messageHub.publish('project:configUpdated', { config: this.projectConfig });
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 UI 设计分辨率
|
||||
* Get UI design resolution
|
||||
*
|
||||
* @returns UI design resolution, defaults to 1920x1080 if not set
|
||||
*/
|
||||
public getUIDesignResolution(): UIDesignResolution {
|
||||
return this.projectConfig?.uiDesignResolution || { width: 1920, height: 1080 };
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 UI 设计分辨率
|
||||
* Set UI design resolution
|
||||
*
|
||||
* @param resolution - The new design resolution
|
||||
*/
|
||||
public async setUIDesignResolution(resolution: UIDesignResolution): Promise<void> {
|
||||
await this.updateConfig({ uiDesignResolution: resolution });
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { MessageHub } from './MessageHub';
|
||||
import type { IFileAPI } from '../Types/IFileAPI';
|
||||
import type { ProjectService } from './ProjectService';
|
||||
import type { EntityStoreService } from './EntityStoreService';
|
||||
import { SceneTemplateRegistry } from './SceneTemplateRegistry';
|
||||
|
||||
const logger = createLogger('SceneManagerService');
|
||||
|
||||
@@ -45,7 +46,13 @@ export class SceneManagerService implements IService {
|
||||
this.sceneResourceManager = manager;
|
||||
}
|
||||
|
||||
public async newScene(): Promise<void> {
|
||||
/**
|
||||
* 创建新场景
|
||||
* Create a new scene
|
||||
*
|
||||
* @param templateName - 场景模板名称,不传则使用默认模板 / Scene template name, uses default if not specified
|
||||
*/
|
||||
public async newScene(templateName?: string): Promise<void> {
|
||||
if (!await this.canClose()) {
|
||||
return;
|
||||
}
|
||||
@@ -54,11 +61,15 @@ export class SceneManagerService implements IService {
|
||||
if (!scene) {
|
||||
throw new Error('No active scene');
|
||||
}
|
||||
|
||||
// 只移除实体,保留系统(系统由模块管理)
|
||||
// Only remove entities, preserve systems (systems managed by modules)
|
||||
scene.entities.removeAllEntities();
|
||||
const systems = [...scene.systems];
|
||||
for (const system of systems) {
|
||||
scene.removeEntityProcessor(system);
|
||||
}
|
||||
|
||||
// 使用场景模板创建默认实体
|
||||
// Create default entities using scene template
|
||||
const createdEntities = SceneTemplateRegistry.createDefaultEntities(scene, templateName);
|
||||
logger.debug(`Created ${createdEntities.length} default entities from template`);
|
||||
|
||||
this.sceneState = {
|
||||
currentScenePath: null,
|
||||
@@ -67,7 +78,16 @@ export class SceneManagerService implements IService {
|
||||
isSaved: false
|
||||
};
|
||||
|
||||
// 同步到 EntityStore
|
||||
// Sync to EntityStore
|
||||
this.entityStore?.syncFromScene();
|
||||
|
||||
// 通知创建的实体
|
||||
// Notify about created entities
|
||||
for (const entity of createdEntities) {
|
||||
await this.messageHub.publish('entity:added', { entity });
|
||||
}
|
||||
|
||||
await this.messageHub.publish('scene:new', {});
|
||||
logger.info('New scene created');
|
||||
}
|
||||
|
||||
128
packages/editor-core/src/Services/SceneTemplateRegistry.ts
Normal file
128
packages/editor-core/src/Services/SceneTemplateRegistry.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import type { Scene, Entity } from '@esengine/ecs-framework';
|
||||
|
||||
/**
|
||||
* 默认实体创建函数类型
|
||||
* Default entity creator function type
|
||||
*/
|
||||
export type DefaultEntityCreator = (scene: Scene) => Entity | null;
|
||||
|
||||
/**
|
||||
* 场景模板配置
|
||||
* Scene template configuration
|
||||
*/
|
||||
export interface SceneTemplate {
|
||||
/** 模板名称 / Template name */
|
||||
name: string;
|
||||
/** 模板描述 / Template description */
|
||||
description?: string;
|
||||
/** 默认实体创建器列表 / Default entity creators */
|
||||
defaultEntities: DefaultEntityCreator[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景模板注册表
|
||||
* Registry for scene templates that define default entities
|
||||
*
|
||||
* This allows the editor-app to register what entities should be created
|
||||
* when a new scene is created, without editor-core needing to know about
|
||||
* specific components like CameraComponent.
|
||||
*
|
||||
* 这允许 editor-app 注册新建场景时应该创建的实体,
|
||||
* 而 editor-core 不需要知道具体的组件如 CameraComponent。
|
||||
*/
|
||||
export class SceneTemplateRegistry {
|
||||
private static templates: Map<string, SceneTemplate> = new Map();
|
||||
private static defaultTemplateName = 'default';
|
||||
|
||||
/**
|
||||
* 注册场景模板
|
||||
* Register a scene template
|
||||
*/
|
||||
static registerTemplate(template: SceneTemplate): void {
|
||||
this.templates.set(template.name, template);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册默认实体创建器到默认模板
|
||||
* Register a default entity creator to the default template
|
||||
*/
|
||||
static registerDefaultEntity(creator: DefaultEntityCreator): void {
|
||||
let defaultTemplate = this.templates.get(this.defaultTemplateName);
|
||||
if (!defaultTemplate) {
|
||||
defaultTemplate = {
|
||||
name: this.defaultTemplateName,
|
||||
description: 'Default scene template',
|
||||
defaultEntities: []
|
||||
};
|
||||
this.templates.set(this.defaultTemplateName, defaultTemplate);
|
||||
}
|
||||
defaultTemplate.defaultEntities.push(creator);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取场景模板
|
||||
* Get a scene template by name
|
||||
*/
|
||||
static getTemplate(name: string): SceneTemplate | undefined {
|
||||
return this.templates.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认模板
|
||||
* Get the default template
|
||||
*/
|
||||
static getDefaultTemplate(): SceneTemplate | undefined {
|
||||
return this.templates.get(this.defaultTemplateName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认模板名称
|
||||
* Set the default template name
|
||||
*/
|
||||
static setDefaultTemplateName(name: string): void {
|
||||
this.defaultTemplateName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有模板名称
|
||||
* Get all template names
|
||||
*/
|
||||
static getTemplateNames(): string[] {
|
||||
return Array.from(this.templates.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* 为场景创建默认实体
|
||||
* Create default entities for a scene using a template
|
||||
*
|
||||
* @param scene - 目标场景 / Target scene
|
||||
* @param templateName - 模板名称,默认使用默认模板 / Template name, uses default if not specified
|
||||
* @returns 创建的实体列表 / List of created entities
|
||||
*/
|
||||
static createDefaultEntities(scene: Scene, templateName?: string): Entity[] {
|
||||
const template = templateName
|
||||
? this.templates.get(templateName)
|
||||
: this.getDefaultTemplate();
|
||||
|
||||
if (!template) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const entities: Entity[] = [];
|
||||
for (const creator of template.defaultEntities) {
|
||||
const entity = creator(scene);
|
||||
if (entity) {
|
||||
entities.push(entity);
|
||||
}
|
||||
}
|
||||
return entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有模板
|
||||
* Clear all templates
|
||||
*/
|
||||
static clear(): void {
|
||||
this.templates.clear();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { IService } from '@esengine/ecs-framework';
|
||||
import { Injectable } from '@esengine/ecs-framework';
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import type { ISerializer } from '../Plugins/IEditorPlugin';
|
||||
import type { ISerializer } from '../Plugin/IPluginLoader';
|
||||
|
||||
const logger = createLogger('SerializerRegistry');
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Injectable, IService } from '@esengine/ecs-framework';
|
||||
|
||||
export type SettingType = 'string' | 'number' | 'boolean' | 'select' | 'color' | 'range';
|
||||
export type SettingType = 'string' | 'number' | 'boolean' | 'select' | 'color' | 'range' | 'pluginList';
|
||||
|
||||
export interface SettingOption {
|
||||
label: string;
|
||||
|
||||
@@ -88,70 +88,8 @@ export interface ToolbarItem {
|
||||
order?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 面板位置
|
||||
*/
|
||||
export enum PanelPosition {
|
||||
Left = 'left',
|
||||
Right = 'right',
|
||||
Bottom = 'bottom',
|
||||
Center = 'center'
|
||||
}
|
||||
|
||||
/**
|
||||
* 面板描述符
|
||||
*/
|
||||
export interface PanelDescriptor {
|
||||
/**
|
||||
* 面板唯一标识
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* 显示标题
|
||||
*/
|
||||
title: string;
|
||||
|
||||
/**
|
||||
* 面板位置
|
||||
*/
|
||||
position: PanelPosition;
|
||||
|
||||
/**
|
||||
* 渲染组件或HTML
|
||||
*/
|
||||
component?: any;
|
||||
|
||||
/**
|
||||
* 默认宽度/高度(像素)
|
||||
*/
|
||||
defaultSize?: number;
|
||||
|
||||
/**
|
||||
* 是否可调整大小
|
||||
*/
|
||||
resizable?: boolean;
|
||||
|
||||
/**
|
||||
* 是否可关闭
|
||||
*/
|
||||
closable?: boolean;
|
||||
|
||||
/**
|
||||
* 图标
|
||||
*/
|
||||
icon?: string;
|
||||
|
||||
/**
|
||||
* 排序权重
|
||||
*/
|
||||
order?: number;
|
||||
|
||||
/**
|
||||
* 是否为动态面板(不默认显示,需要手动打开)
|
||||
*/
|
||||
isDynamic?: boolean;
|
||||
}
|
||||
// Re-export PanelPosition and PanelDescriptor from Plugin system
|
||||
export { PanelPosition, type PanelDescriptor } from '../Plugin/IPluginLoader';
|
||||
|
||||
/**
|
||||
* UI 扩展点类型
|
||||
@@ -164,39 +102,5 @@ export enum UIExtensionType {
|
||||
StatusBar = 'statusbar'
|
||||
}
|
||||
|
||||
/**
|
||||
* 实体创建模板
|
||||
*/
|
||||
export interface EntityCreationTemplate {
|
||||
/**
|
||||
* 模板唯一标识
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* 显示名称
|
||||
*/
|
||||
label: string;
|
||||
|
||||
/**
|
||||
* 图标组件
|
||||
*/
|
||||
icon?: any;
|
||||
|
||||
/**
|
||||
* 分类 (如 'basic', 'rendering', 'ui', 'physics' 等)
|
||||
*/
|
||||
category?: string;
|
||||
|
||||
/**
|
||||
* 排序权重(数字越小越靠前)
|
||||
*/
|
||||
order?: number;
|
||||
|
||||
/**
|
||||
* 创建实体的函数
|
||||
* @param parentEntityId 父实体ID(可选)
|
||||
* @returns 创建的实体ID
|
||||
*/
|
||||
create: (parentEntityId?: number) => number | Promise<number>;
|
||||
}
|
||||
// Re-export EntityCreationTemplate from Plugin system
|
||||
export type { EntityCreationTemplate } from '../Plugin/IPluginLoader';
|
||||
|
||||
@@ -4,10 +4,8 @@
|
||||
* Plugin-based editor framework for ECS Framework
|
||||
*/
|
||||
|
||||
export * from './Plugins/IEditorPlugin';
|
||||
export * from './Plugins/EditorPluginManager';
|
||||
export * from './Plugins/PluginTypes';
|
||||
export * from './Plugins/PluginRegistry';
|
||||
// 新插件系统 | New plugin system
|
||||
export * from './Plugin';
|
||||
|
||||
export * from './Services/UIRegistry';
|
||||
export * from './Services/MessageHub';
|
||||
@@ -21,6 +19,7 @@ export * from './Services/ComponentDiscoveryService';
|
||||
export * from './Services/LogService';
|
||||
export * from './Services/SettingsRegistry';
|
||||
export * from './Services/SceneManagerService';
|
||||
export * from './Services/SceneTemplateRegistry';
|
||||
export * from './Services/FileActionRegistry';
|
||||
export * from './Services/EntityCreationRegistry';
|
||||
export * from './Services/CompilerRegistry';
|
||||
@@ -38,8 +37,8 @@ export * from './Services/IPropertyRenderer';
|
||||
export * from './Services/PropertyRendererRegistry';
|
||||
export * from './Services/IFieldEditor';
|
||||
export * from './Services/FieldEditorRegistry';
|
||||
export * from './Services/ComponentActionRegistry';
|
||||
export * from './Services/ComponentInspectorRegistry';
|
||||
export * from './Services/ComponentActionRegistry';
|
||||
|
||||
export * from './Gizmos';
|
||||
|
||||
@@ -49,5 +48,5 @@ export * from './Module/IPanelRegistry';
|
||||
export * from './Module/IModuleContext';
|
||||
export * from './Module/IEditorModule';
|
||||
|
||||
export * from './Types/UITypes';
|
||||
export * from './Types/IFileAPI';
|
||||
export * from './Types/UITypes';
|
||||
|
||||
Reference in New Issue
Block a user