refactor: 代码规范化与依赖清理 (#317)
* refactor(deps): 统一编辑器包依赖配置 & 优化分层架构 - 将 ecs-engine-bindgen 提升为 Layer 1 核心包 - 统一 9 个编辑器包的依赖声明模式 - 清理废弃的包目录 (ui, ui-editor, network-*) * refactor(tokens): 修复 PrefabService 令牌冲突 & 补充 module.json - 将 editor-core 的 PrefabServiceToken 改名为 EditorPrefabServiceToken 避免与 asset-system 的 PrefabServiceToken 冲突 (Symbol.for 冲突) - 为 mesh-3d 添加 module.json - 为 world-streaming 添加 module.json * refactor(editor-core): 整理导出结构 & 添加 blueprint tokens.ts - 按功能分组整理 editor-core 的 65 行导出 - 添加清晰的分组注释 (中英双语) - 为 blueprint 添加占位符 tokens.ts * chore(editor): 为 14 个编辑器插件包添加 module.json 统一编辑器包的模块配置,包含: - isEditorPlugin 标识 - runtimeModule 关联 - exports 导出清单 (inspectors, panels, gizmos) * refactor(core): 改进类型安全 - 减少 as any 使用 - 添加 GlobalTypes.ts 定义小游戏平台和 Chrome API 类型 - SoAStorage 使用 IComponentTypeMetadata 替代 as any - PlatformDetector 使用类型安全的平台检测 - 添加 ISoAStorageStats/ISoAFieldStats 接口 * feat(editor): 添加 EditorServicesContext 解决 prop drilling - 新增 contexts/EditorServicesContext.tsx 提供统一服务访问 - App.tsx 包裹 EditorServicesProvider - 提供 useEditorServices/useMessageHub 等便捷 hooks - SceneHierarchy 添加迁移注释,后续可移除 props * docs(editor): 澄清 inspector 目录架构关系 - inspector/ 标记为内部实现,添加 @deprecated 警告 - inspectors/ 标记为公共 API 入口点 - 添加架构说明文档 * refactor(editor): 添加全局类型声明消除 window as any - 创建 editor-app/src/global.d.ts 声明 Window 接口扩展 - 创建 editor-core/src/global.d.ts 声明 Window 接口扩展 - 更新 App.tsx 使用类型安全的 window 属性访问 - 更新 PluginLoader.ts 使用 window.__ESENGINE_PLUGINS__ - 更新 PluginSDKRegistry.ts 使用 window.__ESENGINE_SDK__ - 更新 UserCodeService.ts 使用类型安全的全局变量访问 * refactor(editor): 提取项目和场景操作到独立 hooks - 创建 useProjectActions hook 封装项目操作 - 创建 useSceneActions hook 封装场景操作 - 为渐进式重构 App.tsx 做准备 * refactor(editor): 清理冗余代码和未使用文件 删除的目录和文件: - application/state/ - 重复的状态管理(与 stores/ 重复) - 8 个孤立 CSS 文件(对应组件不存在) - AssetBrowser.tsx - 仅为 ContentBrowser 的向后兼容包装 - AssetPicker.tsx - 未被使用 - AssetPickerDialog.tsx (顶级) - 已被 dialogs/ 版本取代 - EntityInspector.tsx (顶级) - 已被 inspectors/views/ 版本取代 修复: - 移除 App.tsx 中未使用的导入 - 更新 application/index.ts 移除已删除模块 - 修复 useProjectActions.ts 的 MutableRefObject 类型 * refactor(editor): 统一 inspectors 模块导出结构 - 在 inspectors/index.ts 重新导出 PropertyInspector - 创建 inspectors/fields/index.ts barrel export - 导出 views、fields、common 子模块 - 更新 EntityInspector 使用统一入口导入 * refactor(editor): 删除废弃的 Profiler 组件 删除未使用的组件(共 1059 行): - ProfilerPanel.tsx (229 行) - ProfilerWindow.tsx (589 行) - ProfilerDockPanel.tsx (241 行) - ProfilerPanel.css - ProfilerDockPanel.css 保留:AdvancedProfiler + AdvancedProfilerWindow(正在使用) * refactor(runtime-core): 统一依赖处理与插件状态管理 - 新增 DependencyUtils 统一拓扑排序和依赖验证 - 新增 PluginState 定义插件生命周期状态机 - 合并 UnifiedPluginLoader 到 PluginLoader - 清理 index.ts 移除不必要的 Token re-exports - 新增 RuntimeMode/UserCodeRealm/ImportMapGenerator * refactor(editor-core): 使用统一的 ImportMapGenerator - WebBuildPipeline 使用 runtime-core 的 generateImportMap - UserCodeService 添加 ImportMap 相关接口 * feat(compiler): 增强 esbuild 查找策略 - 支持本地 node_modules、pnpm exec、npx、全局多种来源 - EngineService 使用 RuntimeMode * refactor(runtime-core): 简化 GameRuntime 代码 - 合并 _disableGameLogicSystems/_enableGameLogicSystems 为 _setGameLogicSystemsEnabled - 精简本地 Token 定义的注释 * refactor(editor-core): 引入 BaseRegistry 基类消除代码重复 - 新增 BaseRegistry 和 PrioritizedRegistry 基类 - 重构 CompilerRegistry, InspectorRegistry, FieldEditorRegistry - 统一注册表的日志记录和错误处理 * refactor(editor-core): 扩展 BaseRegistry 重构 - ComponentInspectorRegistry 继承 PrioritizedRegistry - EditorComponentRegistry 继承 BaseRegistry - EntityCreationRegistry 继承 BaseRegistry - PropertyRendererRegistry 继承 PrioritizedRegistry - 导出 BaseRegistry 基类供外部使用 - 统一双语注释格式 * refactor(editor-core): 代码优雅性优化 CommandManager: - 提取 tryMergeWithLast() 和 pushToUndoStack() 消除重复代码 - 统一双语注释格式 FileActionRegistry: - 提取 normalizeExtension() 消除扩展名规范化重复 - 统一私有属性命名风格(_前缀) - 使用 createRegistryToken 统一 Token 创建 BaseRegistry: - 添加 IOrdered 接口 - 添加 sortByOrder() 排序辅助方法 EntityCreationRegistry: - 使用 sortByOrder() 简化排序逻辑 * refactor(editor-core): 统一日志系统 & 代码规范优化 - GizmoRegistry: 使用 createLogger 替代 console.warn - VirtualNodeRegistry: 使用 createLogger 替代 console.warn - WindowRegistry: 使用 logger、添加 _ 前缀、导出 IWindowRegistry token - EditorViewportService: 使用 createLogger 替代 console.warn - ComponentActionRegistry: 使用 logger、添加 _ 前缀、返回值改进 - SettingsRegistry: 使用 logger、提取 ensureCategory/ensureSection 方法 - 添加 WindowRegistry 到主导出 * refactor(editor-core): ModuleRegistry 使用 logger 替代 console * refactor(editor-core): SerializerRegistry/UIRegistry 添加 token 和 _ 前缀 * refactor(editor-core): UIRegistry 代码优雅性 & Token 命名统一 - UIRegistry: 提取 _sortByOrder 消除 6 处重复排序逻辑 - UIRegistry: 添加分节注释和双语文档 - FieldEditorRegistry: Token 重命名为 FieldEditorRegistryToken - PropertyRendererRegistry: Token 重命名为 PropertyRendererRegistryToken * refactor(core): 统一日志系统 - console 替换为 logger - ComponentSerializer: 使用 logger 替代 console.warn - ComponentRegistry: console.warn → logger.warn (已有 logger) - SceneSerializer: 添加 logger,替换 console.warn/error - SystemScheduler: 添加 logger,替换 console.warn - VersionMigration: 添加 logger,替换所有 console.warn - RuntimeModeService: console.error → logger.error - Core.ts: _logger 改为 readonly,双语错误消息 - SceneSerializer 修复:使用 getComponentTypeName 替代 constructor.name * fix(core): 修复 constructor.name 压缩后失效问题 - Scene.ts: 使用 system.systemName 替代 system.constructor.name - CommandBuffer.ts: 使用 getComponentTypeName() 替代 constructor.name * refactor(editor-core): 代码规范优化 - 私有方法命名 & 日志统一 - BuildService: console → logger - FileActionRegistry: 添加 logger, 私有方法 _ 前缀 - SettingsRegistry: 私有方法 _ 前缀 (ensureCategory → _ensureCategory) * refactor(core): Scene.ts 私有变量命名规范化 - logger → _logger (遵循私有变量 _ 前缀规范) * refactor(editor-core): 服务类私有成员命名规范化 - CommandManager: 私有变量/方法添加 _ 前缀 - undoStack/redoStack/config/isExecuting - tryMergeWithLast/pushToUndoStack - LocaleService: 私有变量/方法添加 _ 前缀 - currentLocale/translations/changeListeners - deepMerge/getNestedValue/loadSavedLocale/saveLocale * refactor(core): 私有成员命名规范化 & 单例模式优化 - Component.ts: _idGenerator 私有静态变量规范化 - PlatformManager.ts: _instance, _adapter, _logger 规范化 - AutoProfiler.ts: _instance, _config 及所有私有方法规范化 - ProfilerSDK.ts: _instance, _config 及所有私有方法规范化 - ComponentPoolManager: _instance, _pools, _usageTracker 规范化 - GlobalEventBus: _instance 规范化 - 添加中英双语 JSDoc 注释 * refactor(editor-app,behavior-tree-editor): 私有成员 & 单例模式命名规范化 editor-app: - EngineService: private static instance → _instance - EditorEngineSync: 所有私有成员添加 _ 前缀 - RuntimeResolver: 所有私有成员和方法添加 _ 前缀 - SettingsService: 所有私有成员和方法添加 _ 前缀 behavior-tree-editor: - GlobalBlackboardService: 所有私有成员和方法添加 _ 前缀 - NotificationService: private static instance → _instance - NodeRegistryService: 所有私有成员和方法添加 _ 前缀 - TreeStateAdapter: private static instance → _instance * fix(editor-runtime): 添加 editor-core 到 external 避免传递依赖问题 将 @esengine/editor-core 添加到 vite external 配置, 避免 editor-core → runtime-core → ecs-engine-bindgen 的传递依赖 被错误地打包进 editor-runtime.js,导致 CI 构建失败。 * fix(core): 修复空接口 lint 错误 将 IByteDanceMiniGameAPI、IAlipayMiniGameAPI、IBaiduMiniGameAPI 从空接口改为类型别名,修复 no-empty-object-type 规则报错
This commit is contained in:
@@ -8,9 +8,11 @@
|
||||
* 使用注册表模式替代原型修改,实现更清晰的架构。
|
||||
*/
|
||||
|
||||
import type { Component, ComponentType, Entity } from '@esengine/ecs-framework';
|
||||
import { createLogger, type Component, type ComponentType, type Entity } from '@esengine/ecs-framework';
|
||||
import type { IGizmoProvider, IGizmoRenderData } from './IGizmoProvider';
|
||||
|
||||
const logger = createLogger('GizmoRegistry');
|
||||
|
||||
/**
|
||||
* Gizmo provider function type
|
||||
* Gizmo 提供者函数类型
|
||||
@@ -124,9 +126,7 @@ export class GizmoRegistry {
|
||||
try {
|
||||
return provider(component, entity, isSelected);
|
||||
} catch (e) {
|
||||
// Silently ignore errors from gizmo providers
|
||||
// 静默忽略 gizmo 提供者的错误
|
||||
console.warn(`[GizmoRegistry] Error in gizmo provider for ${componentType.name}:`, e);
|
||||
logger.warn(`Error in gizmo provider for ${componentType.name}:`, e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
301
packages/editor-core/src/Services/BaseRegistry.ts
Normal file
301
packages/editor-core/src/Services/BaseRegistry.ts
Normal file
@@ -0,0 +1,301 @@
|
||||
/**
|
||||
* @zh 通用注册表基类
|
||||
* @en Generic Registry Base Class
|
||||
*
|
||||
* @zh 提供注册表的通用实现,消除 16+ 个 Registry 类中的重复代码。
|
||||
* @en Provides common registry implementation, eliminating duplicate code in 16+ Registry classes.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* interface IMyItem { id: string; name: string; }
|
||||
*
|
||||
* class MyRegistry extends BaseRegistry<IMyItem> {
|
||||
* constructor() {
|
||||
* super('MyRegistry');
|
||||
* }
|
||||
*
|
||||
* protected getItemId(item: IMyItem): string {
|
||||
* return item.id;
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
|
||||
import { IService, createLogger, type ILogger } from '@esengine/ecs-framework';
|
||||
|
||||
/**
|
||||
* @zh 可注册项的基础接口
|
||||
* @en Base interface for registrable items
|
||||
*/
|
||||
export interface IRegistrable {
|
||||
/** @zh 唯一标识符 @en Unique identifier */
|
||||
readonly id?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 带优先级的可注册项
|
||||
* @en Registrable item with priority
|
||||
*/
|
||||
export interface IPrioritized {
|
||||
/** @zh 优先级(越高越先匹配) @en Priority (higher = matched first) */
|
||||
readonly priority?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 带顺序的可注册项
|
||||
* @en Registrable item with order
|
||||
*/
|
||||
export interface IOrdered {
|
||||
/** @zh 排序权重(越小越靠前) @en Sort order (lower = first) */
|
||||
readonly order?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 注册表配置
|
||||
* @en Registry configuration
|
||||
*/
|
||||
export interface RegistryOptions {
|
||||
/** @zh 是否允许覆盖已存在的项 @en Allow overwriting existing items */
|
||||
allowOverwrite?: boolean;
|
||||
/** @zh 是否在覆盖时发出警告 @en Warn when overwriting */
|
||||
warnOnOverwrite?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 通用注册表基类
|
||||
* @en Generic Registry Base Class
|
||||
*
|
||||
* @typeParam T - @zh 注册项类型 @en Registered item type
|
||||
* @typeParam K - @zh 键类型(默认 string) @en Key type (default string)
|
||||
*/
|
||||
export abstract class BaseRegistry<T, K extends string = string> implements IService {
|
||||
protected readonly _items: Map<K, T> = new Map();
|
||||
protected readonly _logger: ILogger;
|
||||
protected readonly _options: Required<RegistryOptions>;
|
||||
|
||||
constructor(
|
||||
protected readonly _name: string,
|
||||
options: RegistryOptions = {}
|
||||
) {
|
||||
this._logger = createLogger(_name);
|
||||
this._options = {
|
||||
allowOverwrite: options.allowOverwrite ?? true,
|
||||
warnOnOverwrite: options.warnOnOverwrite ?? true
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取项的键
|
||||
* @en Get item key
|
||||
*/
|
||||
protected abstract getItemKey(item: T): K;
|
||||
|
||||
/**
|
||||
* @zh 获取项的显示名称(用于日志)
|
||||
* @en Get item display name (for logging)
|
||||
*/
|
||||
protected getItemDisplayName(item: T): string {
|
||||
const key = this.getItemKey(item);
|
||||
return String(key);
|
||||
}
|
||||
|
||||
// ========== 核心 CRUD 操作 | Core CRUD Operations ==========
|
||||
|
||||
/**
|
||||
* @zh 注册项
|
||||
* @en Register item
|
||||
*/
|
||||
register(item: T): boolean {
|
||||
const key = this.getItemKey(item);
|
||||
const displayName = this.getItemDisplayName(item);
|
||||
|
||||
if (this._items.has(key)) {
|
||||
if (this._options.warnOnOverwrite) {
|
||||
this._logger.warn(`Overwriting: ${displayName}`);
|
||||
}
|
||||
if (!this._options.allowOverwrite) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this._items.set(key, item);
|
||||
this._logger.debug(`Registered: ${displayName}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 批量注册
|
||||
* @en Register multiple items
|
||||
*/
|
||||
registerMany(items: T[]): number {
|
||||
let count = 0;
|
||||
for (const item of items) {
|
||||
if (this.register(item)) count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 注销项
|
||||
* @en Unregister item
|
||||
*/
|
||||
unregister(key: K): boolean {
|
||||
if (this._items.delete(key)) {
|
||||
this._logger.debug(`Unregistered: ${key}`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取项
|
||||
* @en Get item
|
||||
*/
|
||||
get(key: K): T | undefined {
|
||||
return this._items.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 检查是否存在
|
||||
* @en Check if exists
|
||||
*/
|
||||
has(key: K): boolean {
|
||||
return this._items.has(key);
|
||||
}
|
||||
|
||||
// ========== 查询操作 | Query Operations ==========
|
||||
|
||||
/**
|
||||
* @zh 获取所有项
|
||||
* @en Get all items
|
||||
*/
|
||||
getAll(): T[] {
|
||||
return Array.from(this._items.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取所有键
|
||||
* @en Get all keys
|
||||
*/
|
||||
getAllKeys(): K[] {
|
||||
return Array.from(this._items.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取项数量
|
||||
* @en Get item count
|
||||
*/
|
||||
get size(): number {
|
||||
return this._items.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 是否为空
|
||||
* @en Is empty
|
||||
*/
|
||||
get isEmpty(): boolean {
|
||||
return this._items.size === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 按条件过滤
|
||||
* @en Filter by predicate
|
||||
*/
|
||||
filter(predicate: (item: T, key: K) => boolean): T[] {
|
||||
const result: T[] = [];
|
||||
for (const [key, item] of this._items) {
|
||||
if (predicate(item, key)) {
|
||||
result.push(item);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 查找第一个匹配项
|
||||
* @en Find first matching item
|
||||
*/
|
||||
find(predicate: (item: T, key: K) => boolean): T | undefined {
|
||||
for (const [key, item] of this._items) {
|
||||
if (predicate(item, key)) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// ========== 排序操作 | Sorting Operations ==========
|
||||
|
||||
/**
|
||||
* @zh 按 order 字段排序(越小越靠前)
|
||||
* @en Sort items by order field (lower = first)
|
||||
*/
|
||||
protected sortByOrder<U extends IOrdered>(items: U[], defaultOrder = 0): U[] {
|
||||
return items.sort((a, b) => (a.order ?? defaultOrder) - (b.order ?? defaultOrder));
|
||||
}
|
||||
|
||||
// ========== 生命周期 | Lifecycle ==========
|
||||
|
||||
/**
|
||||
* @zh 清空注册表
|
||||
* @en Clear registry
|
||||
*/
|
||||
clear(): void {
|
||||
this._items.clear();
|
||||
this._logger.debug('Cleared');
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 释放资源
|
||||
* @en Dispose resources
|
||||
*/
|
||||
dispose(): void {
|
||||
this.clear();
|
||||
this._logger.debug('Disposed');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 带优先级查找的注册表基类
|
||||
* @en Registry base class with priority-based lookup
|
||||
*
|
||||
* @zh 适用于需要按优先级匹配的场景(如 Inspector、FieldEditor)
|
||||
* @en For scenarios requiring priority-based matching (e.g., Inspector, FieldEditor)
|
||||
*/
|
||||
export abstract class PrioritizedRegistry<T extends IPrioritized, K extends string = string>
|
||||
extends BaseRegistry<T, K> {
|
||||
|
||||
/**
|
||||
* @zh 按优先级排序获取所有项
|
||||
* @en Get all items sorted by priority
|
||||
*/
|
||||
getAllSorted(): T[] {
|
||||
return this.getAll().sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 查找第一个能处理目标的项
|
||||
* @en Find first item that can handle the target
|
||||
*
|
||||
* @param canHandle - @zh 判断函数 @en Predicate function
|
||||
*/
|
||||
findByPriority(canHandle: (item: T) => boolean): T | undefined {
|
||||
for (const item of this.getAllSorted()) {
|
||||
if (canHandle(item)) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 创建注册表服务标识符
|
||||
* @en Create registry service identifier
|
||||
*
|
||||
* @zh 使用 Symbol.for 确保跨包共享同一个 Symbol
|
||||
* @en Uses Symbol.for to ensure same Symbol is shared across packages
|
||||
*/
|
||||
export function createRegistryToken<T>(name: string): symbol {
|
||||
return Symbol.for(`IRegistry:${name}`);
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import type { IService } from '@esengine/ecs-framework';
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import type {
|
||||
IBuildPipeline,
|
||||
IBuildPipelineRegistry,
|
||||
@@ -17,6 +18,8 @@ import type {
|
||||
} from './IBuildPipeline';
|
||||
import { BuildStatus } from './IBuildPipeline';
|
||||
|
||||
const logger = createLogger('BuildService');
|
||||
|
||||
/**
|
||||
* Build task.
|
||||
* 构建任务。
|
||||
@@ -88,10 +91,10 @@ export class BuildService implements IService, IBuildPipelineRegistry {
|
||||
*/
|
||||
register(pipeline: IBuildPipeline): void {
|
||||
if (this._pipelines.has(pipeline.platform)) {
|
||||
console.warn(`[BuildService] Overwriting existing pipeline: ${pipeline.platform} | 覆盖已存在的构建管线: ${pipeline.platform}`);
|
||||
logger.warn(`Overwriting existing pipeline: ${pipeline.platform} | 覆盖已存在的构建管线: ${pipeline.platform}`);
|
||||
}
|
||||
this._pipelines.set(pipeline.platform, pipeline);
|
||||
console.log(`[BuildService] Registered pipeline: ${pipeline.displayName} | 注册构建管线: ${pipeline.displayName}`);
|
||||
logger.info(`Registered pipeline: ${pipeline.displayName} | 注册构建管线: ${pipeline.displayName}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -256,7 +259,7 @@ export class BuildService implements IService, IBuildPipelineRegistry {
|
||||
if (this._currentTask) {
|
||||
this._currentTask.abortController.abort();
|
||||
this._currentTask.progress.status = BuildStatus.Cancelled;
|
||||
console.log('[BuildService] Build cancelled | 构建已取消');
|
||||
logger.info('Build cancelled | 构建已取消');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ import type {
|
||||
import { BuildPlatform, BuildStatus } from '../IBuildPipeline';
|
||||
import type { ModuleManifest } from '../../Module/ModuleTypes';
|
||||
import { hashFileInfo } from '@esengine/asset-system';
|
||||
import { generateImportMap, type ImportMapConfig } from '@esengine/runtime-core';
|
||||
|
||||
// ============================================================================
|
||||
// Build File System Interface
|
||||
@@ -1655,33 +1656,20 @@ Built with ESEngine | 使用 ESEngine 构建
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Import Map from module manifests.
|
||||
* 从模块清单生成 Import Map。
|
||||
* @zh 使用共享的 ImportMapGenerator 生成 Import Map
|
||||
* @en Generate Import Map using shared ImportMapGenerator
|
||||
*
|
||||
* @zh 统一使用 @esengine/runtime-core 的 generateImportMap 工具
|
||||
* @en Unified usage of generateImportMap utility from @esengine/runtime-core
|
||||
*/
|
||||
private _generateImportMap(coreModules: ModuleManifest[], pluginModules: ModuleManifest[]): Record<string, string> {
|
||||
const imports: Record<string, string> = {};
|
||||
|
||||
for (const module of coreModules) {
|
||||
if (module.name) {
|
||||
imports[module.name] = './libs/esengine.core.js';
|
||||
}
|
||||
}
|
||||
|
||||
for (const module of pluginModules) {
|
||||
if (module.name) {
|
||||
imports[module.name] = `./libs/plugins/${module.id}.js`;
|
||||
}
|
||||
if (module.externalDependencies) {
|
||||
for (const dep of module.externalDependencies) {
|
||||
if (!imports[dep]) {
|
||||
const depId = dep.startsWith('@esengine/') ? dep.slice(10) : dep;
|
||||
imports[dep] = `./libs/plugins/${depId}.js`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return imports;
|
||||
const config: ImportMapConfig = {
|
||||
mode: 'production',
|
||||
basePath: '.',
|
||||
coreModules,
|
||||
pluginModules
|
||||
};
|
||||
return generateImportMap(config);
|
||||
}
|
||||
|
||||
private _generateSingleBundleHtml(mainScenePath: string, wasmRuntimePath: string | null): string {
|
||||
|
||||
@@ -1,202 +1,185 @@
|
||||
import { ICommand } from './ICommand';
|
||||
|
||||
/**
|
||||
* 命令历史记录配置
|
||||
* @zh 命令历史记录配置
|
||||
* @en Command history configuration
|
||||
*/
|
||||
export interface CommandManagerConfig {
|
||||
/**
|
||||
* 最大历史记录数量
|
||||
*/
|
||||
/** @zh 最大历史记录数量 @en Maximum history size */
|
||||
maxHistorySize?: number;
|
||||
|
||||
/**
|
||||
* 是否自动合并相似命令
|
||||
*/
|
||||
/** @zh 是否自动合并相似命令 @en Auto merge similar commands */
|
||||
autoMerge?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 命令管理器
|
||||
* 管理命令的执行、撤销、重做以及历史记录
|
||||
* @zh 命令管理器 - 管理命令的执行、撤销、重做以及历史记录
|
||||
* @en Command Manager - Manages command execution, undo, redo and history
|
||||
*/
|
||||
export class CommandManager {
|
||||
private undoStack: ICommand[] = [];
|
||||
private redoStack: ICommand[] = [];
|
||||
private readonly config: Required<CommandManagerConfig>;
|
||||
private isExecuting = false;
|
||||
private _undoStack: ICommand[] = [];
|
||||
private _redoStack: ICommand[] = [];
|
||||
private readonly _config: Required<CommandManagerConfig>;
|
||||
private _isExecuting = false;
|
||||
|
||||
constructor(config: CommandManagerConfig = {}) {
|
||||
this.config = {
|
||||
this._config = {
|
||||
maxHistorySize: config.maxHistorySize ?? 100,
|
||||
autoMerge: config.autoMerge ?? true
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行命令
|
||||
* @zh 尝试将命令与栈顶命令合并
|
||||
* @en Try to merge command with the top of stack
|
||||
*/
|
||||
execute(command: ICommand): void {
|
||||
if (this.isExecuting) {
|
||||
throw new Error('不能在命令执行过程中执行新命令');
|
||||
private _tryMergeWithLast(command: ICommand): boolean {
|
||||
if (!this._config.autoMerge || this._undoStack.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.isExecuting = true;
|
||||
const lastCommand = this._undoStack[this._undoStack.length - 1];
|
||||
if (lastCommand?.canMergeWith(command)) {
|
||||
this._undoStack[this._undoStack.length - 1] = lastCommand.mergeWith(command);
|
||||
this._redoStack = [];
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
command.execute();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.config.autoMerge && this.undoStack.length > 0) {
|
||||
const lastCommand = this.undoStack[this.undoStack.length - 1];
|
||||
if (lastCommand && lastCommand.canMergeWith(command)) {
|
||||
const mergedCommand = lastCommand.mergeWith(command);
|
||||
this.undoStack[this.undoStack.length - 1] = mergedCommand;
|
||||
this.redoStack = [];
|
||||
return;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @zh 将命令推入撤销栈
|
||||
* @en Push command to undo stack
|
||||
*/
|
||||
private _pushToUndoStack(command: ICommand): void {
|
||||
if (this._tryMergeWithLast(command)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.undoStack.push(command);
|
||||
this.redoStack = [];
|
||||
this._undoStack.push(command);
|
||||
this._redoStack = [];
|
||||
|
||||
if (this.undoStack.length > this.config.maxHistorySize) {
|
||||
this.undoStack.shift();
|
||||
}
|
||||
} finally {
|
||||
this.isExecuting = false;
|
||||
if (this._undoStack.length > this._config.maxHistorySize) {
|
||||
this._undoStack.shift();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤销上一个命令
|
||||
* @zh 执行命令
|
||||
* @en Execute command
|
||||
*/
|
||||
execute(command: ICommand): void {
|
||||
if (this._isExecuting) {
|
||||
throw new Error('Cannot execute command while another is executing');
|
||||
}
|
||||
|
||||
this._isExecuting = true;
|
||||
|
||||
try {
|
||||
command.execute();
|
||||
this._pushToUndoStack(command);
|
||||
} finally {
|
||||
this._isExecuting = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 撤销上一个命令
|
||||
* @en Undo last command
|
||||
*/
|
||||
undo(): void {
|
||||
if (this.isExecuting) {
|
||||
throw new Error('不能在命令执行过程中撤销');
|
||||
if (this._isExecuting) {
|
||||
throw new Error('Cannot undo while executing');
|
||||
}
|
||||
|
||||
const command = this.undoStack.pop();
|
||||
if (!command) {
|
||||
return;
|
||||
}
|
||||
const command = this._undoStack.pop();
|
||||
if (!command) return;
|
||||
|
||||
this.isExecuting = true;
|
||||
this._isExecuting = true;
|
||||
|
||||
try {
|
||||
command.undo();
|
||||
this.redoStack.push(command);
|
||||
this._redoStack.push(command);
|
||||
} catch (error) {
|
||||
this.undoStack.push(command);
|
||||
this._undoStack.push(command);
|
||||
throw error;
|
||||
} finally {
|
||||
this.isExecuting = false;
|
||||
this._isExecuting = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重做上一个被撤销的命令
|
||||
* @zh 重做上一个被撤销的命令
|
||||
* @en Redo last undone command
|
||||
*/
|
||||
redo(): void {
|
||||
if (this.isExecuting) {
|
||||
throw new Error('不能在命令执行过程中重做');
|
||||
if (this._isExecuting) {
|
||||
throw new Error('Cannot redo while executing');
|
||||
}
|
||||
|
||||
const command = this.redoStack.pop();
|
||||
if (!command) {
|
||||
return;
|
||||
}
|
||||
const command = this._redoStack.pop();
|
||||
if (!command) return;
|
||||
|
||||
this.isExecuting = true;
|
||||
this._isExecuting = true;
|
||||
|
||||
try {
|
||||
command.execute();
|
||||
this.undoStack.push(command);
|
||||
this._undoStack.push(command);
|
||||
} catch (error) {
|
||||
this.redoStack.push(command);
|
||||
this._redoStack.push(command);
|
||||
throw error;
|
||||
} finally {
|
||||
this.isExecuting = false;
|
||||
this._isExecuting = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否可以撤销
|
||||
*/
|
||||
/** @zh 检查是否可以撤销 @en Check if can undo */
|
||||
canUndo(): boolean {
|
||||
return this.undoStack.length > 0;
|
||||
return this._undoStack.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否可以重做
|
||||
*/
|
||||
/** @zh 检查是否可以重做 @en Check if can redo */
|
||||
canRedo(): boolean {
|
||||
return this.redoStack.length > 0;
|
||||
return this._redoStack.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取撤销栈的描述列表
|
||||
*/
|
||||
/** @zh 获取撤销栈的描述列表 @en Get undo history descriptions */
|
||||
getUndoHistory(): string[] {
|
||||
return this.undoStack.map((cmd) => cmd.getDescription());
|
||||
return this._undoStack.map(cmd => cmd.getDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取重做栈的描述列表
|
||||
*/
|
||||
/** @zh 获取重做栈的描述列表 @en Get redo history descriptions */
|
||||
getRedoHistory(): string[] {
|
||||
return this.redoStack.map((cmd) => cmd.getDescription());
|
||||
return this._redoStack.map(cmd => cmd.getDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有历史记录
|
||||
*/
|
||||
/** @zh 清空所有历史记录 @en Clear all history */
|
||||
clear(): void {
|
||||
this.undoStack = [];
|
||||
this.redoStack = [];
|
||||
this._undoStack = [];
|
||||
this._redoStack = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量执行命令(作为单一操作,可以一次撤销)
|
||||
* @zh 批量执行命令(作为单一操作,可以一次撤销)
|
||||
* @en Execute batch commands (as single operation, can be undone at once)
|
||||
*/
|
||||
executeBatch(commands: ICommand[]): void {
|
||||
if (commands.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const batchCommand = new BatchCommand(commands);
|
||||
this.execute(batchCommand);
|
||||
if (commands.length === 0) return;
|
||||
this.execute(new BatchCommand(commands));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将命令推入撤销栈但不执行
|
||||
* Push command to undo stack without executing
|
||||
*
|
||||
* 用于已经执行过的操作(如拖动变换),只需要记录到历史
|
||||
* Used for operations that have already been performed (like drag transforms),
|
||||
* only need to record to history
|
||||
* @zh 将命令推入撤销栈但不执行(用于已执行的操作如拖动变换)
|
||||
* @en Push command to undo stack without executing (for already performed operations)
|
||||
*/
|
||||
pushWithoutExecute(command: ICommand): void {
|
||||
if (this.config.autoMerge && this.undoStack.length > 0) {
|
||||
const lastCommand = this.undoStack[this.undoStack.length - 1];
|
||||
if (lastCommand && lastCommand.canMergeWith(command)) {
|
||||
const mergedCommand = lastCommand.mergeWith(command);
|
||||
this.undoStack[this.undoStack.length - 1] = mergedCommand;
|
||||
this.redoStack = [];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.undoStack.push(command);
|
||||
this.redoStack = [];
|
||||
|
||||
if (this.undoStack.length > this.config.maxHistorySize) {
|
||||
this.undoStack.shift();
|
||||
}
|
||||
this._pushToUndoStack(command);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量命令
|
||||
* 将多个命令组合为一个命令
|
||||
* @zh 批量命令 - 将多个命令组合为一个命令
|
||||
* @en Batch Command - Combines multiple commands into one
|
||||
*/
|
||||
class BatchCommand implements ICommand {
|
||||
constructor(private readonly commands: ICommand[]) {}
|
||||
@@ -209,15 +192,12 @@ class BatchCommand implements ICommand {
|
||||
|
||||
undo(): void {
|
||||
for (let i = this.commands.length - 1; i >= 0; i--) {
|
||||
const command = this.commands[i];
|
||||
if (command) {
|
||||
command.undo();
|
||||
}
|
||||
this.commands[i]?.undo();
|
||||
}
|
||||
}
|
||||
|
||||
getDescription(): string {
|
||||
return `批量操作 (${this.commands.length} 个命令)`;
|
||||
return `Batch (${this.commands.length} commands)`;
|
||||
}
|
||||
|
||||
canMergeWith(): boolean {
|
||||
@@ -225,6 +205,6 @@ class BatchCommand implements ICommand {
|
||||
}
|
||||
|
||||
mergeWith(): ICommand {
|
||||
throw new Error('批量命令不支持合并');
|
||||
throw new Error('Batch commands cannot be merged');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,28 @@
|
||||
import { IService } from '@esengine/ecs-framework';
|
||||
import { ICompiler } from './ICompiler';
|
||||
/**
|
||||
* @zh 编译器注册表
|
||||
* @en Compiler Registry
|
||||
*/
|
||||
|
||||
export class CompilerRegistry implements IService {
|
||||
private compilers: Map<string, ICompiler> = new Map();
|
||||
import { BaseRegistry, createRegistryToken } from './BaseRegistry';
|
||||
import type { ICompiler } from './ICompiler';
|
||||
|
||||
register(compiler: ICompiler): void {
|
||||
if (this.compilers.has(compiler.id)) {
|
||||
console.warn(`Compiler with id "${compiler.id}" is already registered. Overwriting.`);
|
||||
}
|
||||
this.compilers.set(compiler.id, compiler);
|
||||
/**
|
||||
* @zh 编译器注册表
|
||||
* @en Compiler Registry
|
||||
*/
|
||||
export class CompilerRegistry extends BaseRegistry<ICompiler> {
|
||||
constructor() {
|
||||
super('CompilerRegistry');
|
||||
}
|
||||
|
||||
unregister(compilerId: string): void {
|
||||
this.compilers.delete(compilerId);
|
||||
protected getItemKey(item: ICompiler): string {
|
||||
return item.id;
|
||||
}
|
||||
|
||||
get(compilerId: string): ICompiler | undefined {
|
||||
return this.compilers.get(compilerId);
|
||||
}
|
||||
|
||||
getAll(): ICompiler[] {
|
||||
return Array.from(this.compilers.values());
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.compilers.clear();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.clear();
|
||||
protected override getItemDisplayName(item: ICompiler): string {
|
||||
return `${item.name} (${item.id})`;
|
||||
}
|
||||
}
|
||||
|
||||
// Service identifier for DI registration (用于跨包插件访问)
|
||||
// 使用 Symbol.for 确保跨包共享同一个 Symbol
|
||||
export const ICompilerRegistry = Symbol.for('ICompilerRegistry');
|
||||
/** @zh 编译器注册表服务标识符 @en Compiler registry service identifier */
|
||||
export const ICompilerRegistry = createRegistryToken<CompilerRegistry>('CompilerRegistry');
|
||||
|
||||
@@ -1,41 +1,55 @@
|
||||
/**
|
||||
* Component Action Registry Service
|
||||
* @zh 组件动作注册表服务
|
||||
* @en Component Action Registry Service
|
||||
*
|
||||
* Manages component-specific actions for the inspector panel
|
||||
* @zh 管理检视器面板中的组件特定动作
|
||||
* @en Manages component-specific actions for the inspector panel
|
||||
*/
|
||||
|
||||
import { injectable } from 'tsyringe';
|
||||
import type { IService } from '@esengine/ecs-framework';
|
||||
import { createLogger, type ILogger, type IService } from '@esengine/ecs-framework';
|
||||
import type { ComponentAction } from '../Plugin/EditorModule';
|
||||
// Re-export ComponentAction type from Plugin system
|
||||
import { createRegistryToken } from './BaseRegistry';
|
||||
|
||||
export type { ComponentAction } from '../Plugin/EditorModule';
|
||||
|
||||
@injectable()
|
||||
/**
|
||||
* @zh 组件动作注册表
|
||||
* @en Component Action Registry
|
||||
*/
|
||||
export class ComponentActionRegistry implements IService {
|
||||
private actions: Map<string, ComponentAction[]> = new Map();
|
||||
private readonly _actions = new Map<string, ComponentAction[]>();
|
||||
private readonly _logger: ILogger;
|
||||
|
||||
constructor() {
|
||||
this._logger = createLogger('ComponentActionRegistry');
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a component action
|
||||
* @zh 注册组件动作
|
||||
* @en Register component action
|
||||
*/
|
||||
register(action: ComponentAction): void {
|
||||
const componentName = action.componentName;
|
||||
if (!this.actions.has(componentName)) {
|
||||
this.actions.set(componentName, []);
|
||||
const { componentName, id } = action;
|
||||
|
||||
if (!this._actions.has(componentName)) {
|
||||
this._actions.set(componentName, []);
|
||||
}
|
||||
|
||||
const actions = this.actions.get(componentName)!;
|
||||
const existingIndex = actions.findIndex(a => a.id === action.id);
|
||||
const actions = this._actions.get(componentName)!;
|
||||
const existingIndex = actions.findIndex(a => a.id === id);
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
console.warn(`[ComponentActionRegistry] Action '${action.id}' already exists for '${componentName}', overwriting`);
|
||||
this._logger.warn(`Overwriting action: ${id} for ${componentName}`);
|
||||
actions[existingIndex] = action;
|
||||
} else {
|
||||
actions.push(action);
|
||||
this._logger.debug(`Registered action: ${id} for ${componentName}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register multiple actions
|
||||
* @zh 批量注册动作
|
||||
* @en Register multiple actions
|
||||
*/
|
||||
registerMany(actions: ComponentAction[]): void {
|
||||
for (const action of actions) {
|
||||
@@ -44,45 +58,55 @@ export class ComponentActionRegistry implements IService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister an action by ID
|
||||
* @zh 注销动作
|
||||
* @en Unregister action
|
||||
*/
|
||||
unregister(componentName: string, actionId: string): void {
|
||||
const actions = this.actions.get(componentName);
|
||||
if (actions) {
|
||||
const index = actions.findIndex(a => a.id === actionId);
|
||||
if (index >= 0) {
|
||||
actions.splice(index, 1);
|
||||
}
|
||||
unregister(componentName: string, actionId: string): boolean {
|
||||
const actions = this._actions.get(componentName);
|
||||
if (!actions) return false;
|
||||
|
||||
const index = actions.findIndex(a => a.id === actionId);
|
||||
if (index < 0) return false;
|
||||
|
||||
actions.splice(index, 1);
|
||||
this._logger.debug(`Unregistered action: ${actionId} from ${componentName}`);
|
||||
|
||||
if (actions.length === 0) {
|
||||
this._actions.delete(componentName);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all actions for a component type sorted by order
|
||||
* @zh 获取组件的所有动作(按 order 排序)
|
||||
* @en Get all actions for component (sorted by order)
|
||||
*/
|
||||
getActionsForComponent(componentName: string): ComponentAction[] {
|
||||
const actions = this.actions.get(componentName) || [];
|
||||
const actions = this._actions.get(componentName);
|
||||
if (!actions) return [];
|
||||
return [...actions].sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a component has any actions
|
||||
* @zh 检查组件是否有动作
|
||||
* @en Check if component has actions
|
||||
*/
|
||||
hasActions(componentName: string): boolean {
|
||||
const actions = this.actions.get(componentName);
|
||||
const actions = this._actions.get(componentName);
|
||||
return actions !== undefined && actions.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all actions
|
||||
*/
|
||||
/** @zh 清空所有动作 @en Clear all actions */
|
||||
clear(): void {
|
||||
this.actions.clear();
|
||||
this._actions.clear();
|
||||
this._logger.debug('Cleared');
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose resources
|
||||
*/
|
||||
/** @zh 释放资源 @en Dispose resources */
|
||||
dispose(): void {
|
||||
this.actions.clear();
|
||||
this.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/** @zh 组件动作注册表服务标识符 @en Component action registry service identifier */
|
||||
export const IComponentActionRegistry = createRegistryToken<ComponentActionRegistry>('ComponentActionRegistry');
|
||||
|
||||
@@ -1,162 +1,136 @@
|
||||
import React from 'react';
|
||||
import { Component, IService, createLogger } from '@esengine/ecs-framework';
|
||||
|
||||
const logger = createLogger('ComponentInspectorRegistry');
|
||||
import { Component } from '@esengine/ecs-framework';
|
||||
import { PrioritizedRegistry, createRegistryToken, type IPrioritized } from './BaseRegistry';
|
||||
|
||||
/**
|
||||
* 组件检查器上下文
|
||||
* Context passed to component inspectors
|
||||
* @zh 组件检查器上下文
|
||||
* @en Context passed to component inspectors
|
||||
*/
|
||||
export interface ComponentInspectorContext {
|
||||
/** 被检查的组件 */
|
||||
/** @zh 被检查的组件 @en The component being inspected */
|
||||
component: Component;
|
||||
/** 所属实体 */
|
||||
/** @zh 所属实体 @en Owner entity */
|
||||
entity: any;
|
||||
/** 版本号(用于触发重渲染) */
|
||||
/** @zh 版本号(用于触发重渲染) @en Version (for triggering re-renders) */
|
||||
version?: number;
|
||||
/** 属性变更回调 */
|
||||
/** @zh 属性变更回调 @en Property change callback */
|
||||
onChange?: (propertyName: string, value: any) => void;
|
||||
/** 动作回调 */
|
||||
/** @zh 动作回调 @en Action callback */
|
||||
onAction?: (actionId: string, propertyName: string, component: Component) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspector render mode.
|
||||
* 检查器渲染模式。
|
||||
* @zh 检查器渲染模式
|
||||
* @en Inspector render mode
|
||||
*/
|
||||
export type InspectorRenderMode = 'replace' | 'append';
|
||||
|
||||
/**
|
||||
* 组件检查器接口
|
||||
* Interface for custom component inspectors
|
||||
* @zh 组件检查器接口
|
||||
* @en Interface for custom component inspectors
|
||||
*/
|
||||
export interface IComponentInspector<T extends Component = Component> {
|
||||
/** 唯一标识符 */
|
||||
export interface IComponentInspector<T extends Component = Component> extends IPrioritized {
|
||||
/** @zh 唯一标识符 @en Unique identifier */
|
||||
readonly id: string;
|
||||
/** 显示名称 */
|
||||
/** @zh 显示名称 @en Display name */
|
||||
readonly name: string;
|
||||
/** 优先级(数字越大优先级越高) */
|
||||
readonly priority?: number;
|
||||
/** 目标组件类型名称列表 */
|
||||
/** @zh 目标组件类型名称列表 @en Target component type names */
|
||||
readonly targetComponents: string[];
|
||||
/**
|
||||
* 渲染模式
|
||||
* - 'replace': 替换默认的 PropertyInspector(默认)
|
||||
* - 'append': 追加到默认的 PropertyInspector 后面
|
||||
* @zh 渲染模式:'replace' 替换默认检查器,'append' 追加到默认检查器后
|
||||
* @en Render mode: 'replace' replaces default, 'append' appends after default
|
||||
*/
|
||||
readonly renderMode?: InspectorRenderMode;
|
||||
|
||||
/**
|
||||
* 判断是否可以处理该组件
|
||||
*/
|
||||
/** @zh 判断是否可以处理该组件 @en Check if can handle the component */
|
||||
canHandle(component: Component): component is T;
|
||||
|
||||
/**
|
||||
* 渲染组件检查器
|
||||
*/
|
||||
/** @zh 渲染组件检查器 @en Render component inspector */
|
||||
render(context: ComponentInspectorContext): React.ReactElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件检查器注册表
|
||||
* Registry for custom component inspectors
|
||||
* @zh 组件检查器注册表
|
||||
* @en Registry for custom component inspectors
|
||||
*/
|
||||
export class ComponentInspectorRegistry implements IService {
|
||||
private inspectors: Map<string, IComponentInspector> = new Map();
|
||||
export class ComponentInspectorRegistry extends PrioritizedRegistry<IComponentInspector> {
|
||||
constructor() {
|
||||
super('ComponentInspectorRegistry');
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册组件检查器
|
||||
*/
|
||||
register(inspector: IComponentInspector): void {
|
||||
if (this.inspectors.has(inspector.id)) {
|
||||
logger.warn(`Overwriting existing component inspector: ${inspector.id}`);
|
||||
}
|
||||
this.inspectors.set(inspector.id, inspector);
|
||||
logger.debug(`Registered component inspector: ${inspector.name} (${inspector.id})`);
|
||||
protected getItemKey(item: IComponentInspector): string {
|
||||
return item.id;
|
||||
}
|
||||
|
||||
protected override getItemDisplayName(item: IComponentInspector): string {
|
||||
return `${item.name} (${item.id})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销组件检查器
|
||||
*/
|
||||
unregister(inspectorId: string): void {
|
||||
if (this.inspectors.delete(inspectorId)) {
|
||||
logger.debug(`Unregistered component inspector: ${inspectorId}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找可以处理指定组件的检查器(仅 replace 模式)
|
||||
* Find inspector that can handle the component (replace mode only)
|
||||
* @zh 查找可以处理指定组件的检查器(仅 replace 模式)
|
||||
* @en Find inspector that can handle the component (replace mode only)
|
||||
*/
|
||||
findInspector(component: Component): IComponentInspector | undefined {
|
||||
const inspectors = Array.from(this.inspectors.values())
|
||||
.filter(i => i.renderMode !== 'append')
|
||||
.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
||||
|
||||
for (const inspector of inspectors) {
|
||||
const sorted = this.getAllSorted().filter(i => i.renderMode !== 'append');
|
||||
for (const inspector of sorted) {
|
||||
try {
|
||||
if (inspector.canHandle(component)) {
|
||||
return inspector;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Error in canHandle for inspector ${inspector.id}:`, error);
|
||||
this._logger.error(`Error in canHandle for ${inspector.id}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找所有追加模式的检查器
|
||||
* Find all append-mode inspectors for the component
|
||||
* @zh 查找所有追加模式的检查器
|
||||
* @en Find all append-mode inspectors for the component
|
||||
*/
|
||||
findAppendInspectors(component: Component): IComponentInspector[] {
|
||||
const sorted = this.getAllSorted().filter(i => i.renderMode === 'append');
|
||||
const result: IComponentInspector[] = [];
|
||||
const inspectors = Array.from(this.inspectors.values())
|
||||
.filter(i => i.renderMode === 'append')
|
||||
.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
||||
|
||||
for (const inspector of inspectors) {
|
||||
for (const inspector of sorted) {
|
||||
try {
|
||||
if (inspector.canHandle(component)) {
|
||||
result.push(inspector);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Error in canHandle for inspector ${inspector.id}:`, error);
|
||||
this._logger.error(`Error in canHandle for ${inspector.id}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有自定义检查器(replace 模式)
|
||||
* @zh 检查是否有自定义检查器(replace 模式)
|
||||
* @en Check if has custom inspector (replace mode)
|
||||
*/
|
||||
hasInspector(component: Component): boolean {
|
||||
return this.findInspector(component) !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有追加检查器
|
||||
* @zh 检查是否有追加检查器
|
||||
* @en Check if has append inspectors
|
||||
*/
|
||||
hasAppendInspectors(component: Component): boolean {
|
||||
return this.findAppendInspectors(component).length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染组件(replace 模式)
|
||||
* Render component with replace-mode inspector
|
||||
* @zh 渲染组件(replace 模式)
|
||||
* @en Render component with replace-mode inspector
|
||||
*/
|
||||
render(context: ComponentInspectorContext): React.ReactElement | null {
|
||||
const inspector = this.findInspector(context.component);
|
||||
if (!inspector) {
|
||||
return null;
|
||||
}
|
||||
if (!inspector) return null;
|
||||
|
||||
try {
|
||||
return inspector.render(context);
|
||||
} catch (error) {
|
||||
logger.error(`Error rendering with inspector ${inspector.id}:`, error);
|
||||
this._logger.error(`Error rendering with ${inspector.id}:`, error);
|
||||
return React.createElement(
|
||||
'span',
|
||||
{ style: { color: '#f87171', fontStyle: 'italic' } },
|
||||
@@ -166,46 +140,37 @@ export class ComponentInspectorRegistry implements IService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染追加检查器
|
||||
* Render append-mode inspectors
|
||||
* @zh 渲染追加检查器
|
||||
* @en Render append-mode inspectors
|
||||
*/
|
||||
renderAppendInspectors(context: ComponentInspectorContext): React.ReactElement[] {
|
||||
const inspectors = this.findAppendInspectors(context.component);
|
||||
const elements: React.ReactElement[] = [];
|
||||
|
||||
for (const inspector of inspectors) {
|
||||
return inspectors.map(inspector => {
|
||||
try {
|
||||
elements.push(
|
||||
React.createElement(
|
||||
React.Fragment,
|
||||
{ key: inspector.id },
|
||||
inspector.render(context)
|
||||
)
|
||||
return React.createElement(
|
||||
React.Fragment,
|
||||
{ key: inspector.id },
|
||||
inspector.render(context)
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error(`Error rendering append inspector ${inspector.id}:`, error);
|
||||
elements.push(
|
||||
React.createElement(
|
||||
'span',
|
||||
{ key: inspector.id, style: { color: '#f87171', fontStyle: 'italic' } },
|
||||
`[${inspector.name} Error]`
|
||||
)
|
||||
this._logger.error(`Error rendering ${inspector.id}:`, error);
|
||||
return React.createElement(
|
||||
'span',
|
||||
{ key: inspector.id, style: { color: '#f87171', fontStyle: 'italic' } },
|
||||
`[${inspector.name} Error]`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return elements;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有注册的检查器
|
||||
* @zh 获取所有注册的检查器
|
||||
* @en Get all registered inspectors
|
||||
*/
|
||||
getAllInspectors(): IComponentInspector[] {
|
||||
return Array.from(this.inspectors.values());
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.inspectors.clear();
|
||||
logger.debug('ComponentInspectorRegistry disposed');
|
||||
return this.getAll();
|
||||
}
|
||||
}
|
||||
|
||||
/** @zh 组件检查器注册表服务标识符 @en Component inspector registry service identifier */
|
||||
export const IComponentInspectorRegistry = createRegistryToken<ComponentInspectorRegistry>('ComponentInspectorRegistry');
|
||||
|
||||
@@ -1,60 +1,85 @@
|
||||
import { Injectable, IService, Component } from '@esengine/ecs-framework';
|
||||
import { Component } from '@esengine/ecs-framework';
|
||||
import { BaseRegistry, createRegistryToken } from './BaseRegistry';
|
||||
|
||||
/**
|
||||
* @zh 组件类型信息
|
||||
* @en Component type info
|
||||
*/
|
||||
export interface ComponentTypeInfo {
|
||||
name: string;
|
||||
type?: new (...args: any[]) => Component;
|
||||
category?: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
metadata?: {
|
||||
path?: string;
|
||||
fileName?: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
/** @zh 组件名称 @en Component name */
|
||||
name: string;
|
||||
/** @zh 组件类型 @en Component type */
|
||||
type?: new (...args: any[]) => Component;
|
||||
/** @zh 分类 @en Category */
|
||||
category?: string;
|
||||
/** @zh 描述 @en Description */
|
||||
description?: string;
|
||||
/** @zh 图标 @en Icon */
|
||||
icon?: string;
|
||||
/** @zh 元数据 @en Metadata */
|
||||
metadata?: {
|
||||
path?: string;
|
||||
fileName?: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑器组件注册表
|
||||
* Editor Component Registry
|
||||
* @zh 编辑器组件注册表
|
||||
* @en Editor Component Registry
|
||||
*
|
||||
* 管理编辑器中可用的组件类型元数据(名称、分类、图标等)。
|
||||
* 与 ECS 核心的 ComponentRegistry(管理组件位掩码)不同。
|
||||
*
|
||||
* Manages component type metadata (name, category, icon, etc.) for the editor.
|
||||
* Different from the ECS core ComponentRegistry (which manages component bitmasks).
|
||||
* @zh 管理编辑器中可用的组件类型元数据(名称、分类、图标等)。
|
||||
* 与 ECS 核心的 ComponentRegistry(管理组件位掩码)不同。
|
||||
* @en Manages component type metadata (name, category, icon, etc.) for the editor.
|
||||
* Different from the ECS core ComponentRegistry (which manages component bitmasks).
|
||||
*/
|
||||
@Injectable()
|
||||
export class EditorComponentRegistry implements IService {
|
||||
private components: Map<string, ComponentTypeInfo> = new Map();
|
||||
|
||||
public dispose(): void {
|
||||
this.components.clear();
|
||||
export class EditorComponentRegistry extends BaseRegistry<ComponentTypeInfo> {
|
||||
constructor() {
|
||||
super('EditorComponentRegistry');
|
||||
}
|
||||
|
||||
public register(info: ComponentTypeInfo): void {
|
||||
this.components.set(info.name, info);
|
||||
protected getItemKey(item: ComponentTypeInfo): string {
|
||||
return item.name;
|
||||
}
|
||||
|
||||
public unregister(name: string): void {
|
||||
this.components.delete(name);
|
||||
protected override getItemDisplayName(item: ComponentTypeInfo): string {
|
||||
return `${item.name}${item.category ? ` [${item.category}]` : ''}`;
|
||||
}
|
||||
|
||||
public getComponent(name: string): ComponentTypeInfo | undefined {
|
||||
return this.components.get(name);
|
||||
/**
|
||||
* @zh 获取组件信息
|
||||
* @en Get component info
|
||||
*/
|
||||
getComponent(name: string): ComponentTypeInfo | undefined {
|
||||
return this.get(name);
|
||||
}
|
||||
|
||||
public getAllComponents(): ComponentTypeInfo[] {
|
||||
return Array.from(this.components.values());
|
||||
/**
|
||||
* @zh 获取所有组件
|
||||
* @en Get all components
|
||||
*/
|
||||
getAllComponents(): ComponentTypeInfo[] {
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
public getComponentsByCategory(category: string): ComponentTypeInfo[] {
|
||||
return this.getAllComponents().filter((c) => c.category === category);
|
||||
/**
|
||||
* @zh 按分类获取组件
|
||||
* @en Get components by category
|
||||
*/
|
||||
getComponentsByCategory(category: string): ComponentTypeInfo[] {
|
||||
return this.filter(c => c.category === category);
|
||||
}
|
||||
|
||||
public createInstance(name: string, ...args: any[]): Component | null {
|
||||
const info = this.components.get(name);
|
||||
/**
|
||||
* @zh 创建组件实例
|
||||
* @en Create component instance
|
||||
*/
|
||||
createInstance(name: string, ...args: any[]): Component | null {
|
||||
const info = this.get(name);
|
||||
if (!info || !info.type) return null;
|
||||
|
||||
return new info.type(...args);
|
||||
}
|
||||
}
|
||||
|
||||
/** @zh 编辑器组件注册表服务标识符 @en Editor component registry service identifier */
|
||||
export const IEditorComponentRegistry = createRegistryToken<EditorComponentRegistry>('EditorComponentRegistry');
|
||||
|
||||
@@ -6,11 +6,14 @@
|
||||
* 管理带有预览场景支持和覆盖层渲染的编辑器视口。
|
||||
*/
|
||||
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import type { IViewportService, ViewportCameraConfig } from './IViewportService';
|
||||
import type { IPreviewScene } from './PreviewSceneService';
|
||||
import { PreviewSceneService } from './PreviewSceneService';
|
||||
import type { IViewportOverlay, OverlayRenderContext } from '../Rendering/IViewportOverlay';
|
||||
|
||||
const logger = createLogger('EditorViewportService');
|
||||
|
||||
/**
|
||||
* Configuration for an editor viewport
|
||||
* 编辑器视口配置
|
||||
@@ -201,7 +204,7 @@ export class EditorViewportService implements IEditorViewportService {
|
||||
|
||||
registerViewport(config: EditorViewportConfig): void {
|
||||
if (this._viewports.has(config.id)) {
|
||||
console.warn(`[EditorViewportService] Viewport "${config.id}" already registered`);
|
||||
logger.warn(`Viewport already registered: ${config.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -266,16 +269,17 @@ export class EditorViewportService implements IEditorViewportService {
|
||||
addOverlay(viewportId: string, overlay: IViewportOverlay): void {
|
||||
const state = this._viewports.get(viewportId);
|
||||
if (!state) {
|
||||
console.warn(`[EditorViewportService] Viewport "${viewportId}" not found`);
|
||||
logger.warn(`Viewport not found: ${viewportId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.overlays.has(overlay.id)) {
|
||||
console.warn(`[EditorViewportService] Overlay "${overlay.id}" already exists in viewport "${viewportId}"`);
|
||||
logger.warn(`Overlay already exists: ${overlay.id} in ${viewportId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
state.overlays.set(overlay.id, overlay);
|
||||
logger.debug(`Added overlay: ${overlay.id} to ${viewportId}`);
|
||||
}
|
||||
|
||||
removeOverlay(viewportId: string, overlayId: string): void {
|
||||
|
||||
@@ -1,76 +1,47 @@
|
||||
/**
|
||||
* Entity Creation Registry Service
|
||||
* @zh 实体创建模板注册表
|
||||
* @en Entity Creation Registry Service
|
||||
*
|
||||
* Manages entity creation templates for the scene hierarchy context menu
|
||||
* @zh 管理场景层级右键菜单中的实体创建模板
|
||||
* @en Manages entity creation templates for the scene hierarchy context menu
|
||||
*/
|
||||
|
||||
import { injectable } from 'tsyringe';
|
||||
import type { IService } from '@esengine/ecs-framework';
|
||||
import { BaseRegistry, createRegistryToken } from './BaseRegistry';
|
||||
import type { EntityCreationTemplate } from '../Types/UITypes';
|
||||
|
||||
@injectable()
|
||||
export class EntityCreationRegistry implements IService {
|
||||
private templates: Map<string, EntityCreationTemplate> = new Map();
|
||||
/**
|
||||
* @zh 实体创建模板注册表
|
||||
* @en Entity Creation Registry
|
||||
*/
|
||||
export class EntityCreationRegistry extends BaseRegistry<EntityCreationTemplate> {
|
||||
constructor() {
|
||||
super('EntityCreationRegistry');
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an entity creation template
|
||||
*/
|
||||
register(template: EntityCreationTemplate): void {
|
||||
if (this.templates.has(template.id)) {
|
||||
console.warn(`[EntityCreationRegistry] Template '${template.id}' already exists, overwriting`);
|
||||
}
|
||||
this.templates.set(template.id, template);
|
||||
protected getItemKey(item: EntityCreationTemplate): string {
|
||||
return item.id;
|
||||
}
|
||||
|
||||
protected override getItemDisplayName(item: EntityCreationTemplate): string {
|
||||
return `${item.label} (${item.id})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register multiple templates
|
||||
* @zh 获取所有模板(按 order 排序)
|
||||
* @en Get all templates sorted by order
|
||||
*/
|
||||
registerMany(templates: EntityCreationTemplate[]): void {
|
||||
for (const template of templates) {
|
||||
this.register(template);
|
||||
}
|
||||
getAllSorted(): EntityCreationTemplate[] {
|
||||
return this.sortByOrder(this.getAll(), 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a template by ID
|
||||
* @zh 获取指定分类的模板
|
||||
* @en Get templates by category
|
||||
*/
|
||||
unregister(id: string): void {
|
||||
this.templates.delete(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered templates sorted by order
|
||||
*/
|
||||
getAll(): EntityCreationTemplate[] {
|
||||
return Array.from(this.templates.values())
|
||||
.sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a template by ID
|
||||
*/
|
||||
get(id: string): EntityCreationTemplate | undefined {
|
||||
return this.templates.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a template exists
|
||||
*/
|
||||
has(id: string): boolean {
|
||||
return this.templates.has(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all templates
|
||||
*/
|
||||
clear(): void {
|
||||
this.templates.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose resources
|
||||
*/
|
||||
dispose(): void {
|
||||
this.templates.clear();
|
||||
getByCategory(category: string): EntityCreationTemplate[] {
|
||||
return this.sortByOrder(this.filter(t => t.category === category), 100);
|
||||
}
|
||||
}
|
||||
|
||||
/** @zh 实体创建模板注册表服务标识符 @en Entity creation registry service identifier */
|
||||
export const IEntityCreationRegistry = createRegistryToken<EntityCreationRegistry>('EntityCreationRegistry');
|
||||
|
||||
@@ -1,50 +1,52 @@
|
||||
import { IService, createLogger } from '@esengine/ecs-framework';
|
||||
import { IFieldEditor, IFieldEditorRegistry, FieldEditorContext } from './IFieldEditor';
|
||||
/**
|
||||
* @zh 字段编辑器注册表
|
||||
* @en Field Editor Registry
|
||||
*/
|
||||
|
||||
const logger = createLogger('FieldEditorRegistry');
|
||||
import { PrioritizedRegistry, createRegistryToken } from './BaseRegistry';
|
||||
import type { IFieldEditor, IFieldEditorRegistry, FieldEditorContext } from './IFieldEditor';
|
||||
|
||||
export class FieldEditorRegistry implements IFieldEditorRegistry, IService {
|
||||
private editors: Map<string, IFieldEditor> = new Map();
|
||||
/**
|
||||
* @zh 字段编辑器注册表
|
||||
* @en Field Editor Registry
|
||||
*/
|
||||
export class FieldEditorRegistry
|
||||
extends PrioritizedRegistry<IFieldEditor>
|
||||
implements IFieldEditorRegistry {
|
||||
|
||||
register(editor: IFieldEditor): void {
|
||||
if (this.editors.has(editor.type)) {
|
||||
logger.warn(`Overwriting existing field editor: ${editor.type}`);
|
||||
}
|
||||
|
||||
this.editors.set(editor.type, editor);
|
||||
logger.debug(`Registered field editor: ${editor.name} (${editor.type})`);
|
||||
constructor() {
|
||||
super('FieldEditorRegistry');
|
||||
}
|
||||
|
||||
unregister(type: string): void {
|
||||
if (this.editors.delete(type)) {
|
||||
logger.debug(`Unregistered field editor: ${type}`);
|
||||
}
|
||||
protected getItemKey(item: IFieldEditor): string {
|
||||
return item.type;
|
||||
}
|
||||
|
||||
protected override getItemDisplayName(item: IFieldEditor): string {
|
||||
return `${item.name} (${item.type})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取字段编辑器
|
||||
* @en Get field editor
|
||||
*/
|
||||
getEditor(type: string, context?: FieldEditorContext): IFieldEditor | undefined {
|
||||
const editor = this.editors.get(type);
|
||||
if (editor) {
|
||||
return editor;
|
||||
}
|
||||
// 先尝试精确匹配
|
||||
const exact = this.get(type);
|
||||
if (exact) return exact;
|
||||
|
||||
const editors = Array.from(this.editors.values())
|
||||
.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
||||
|
||||
for (const editor of editors) {
|
||||
if (editor.canHandle(type, context)) {
|
||||
return editor;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
// 再按优先级查找可处理的编辑器
|
||||
return this.findByPriority(editor => editor.canHandle(type, context));
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取所有编辑器
|
||||
* @en Get all editors
|
||||
*/
|
||||
getAllEditors(): IFieldEditor[] {
|
||||
return Array.from(this.editors.values());
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.editors.clear();
|
||||
logger.debug('FieldEditorRegistry disposed');
|
||||
return this.getAll();
|
||||
}
|
||||
}
|
||||
|
||||
/** @zh 字段编辑器注册表服务标识符 @en Field editor registry service identifier */
|
||||
export const FieldEditorRegistryToken = createRegistryToken<FieldEditorRegistry>('FieldEditorRegistry');
|
||||
|
||||
@@ -1,115 +1,99 @@
|
||||
import { IService } from '@esengine/ecs-framework';
|
||||
import { IService, createLogger, type ILogger } from '@esengine/ecs-framework';
|
||||
import type { FileActionHandler, FileCreationTemplate } from '../Plugin/EditorModule';
|
||||
import { createRegistryToken } from './BaseRegistry';
|
||||
|
||||
// Re-export for backwards compatibility
|
||||
export type { FileCreationTemplate } from '../Plugin/EditorModule';
|
||||
|
||||
/**
|
||||
* 资产创建消息映射
|
||||
* Asset creation message mapping
|
||||
*
|
||||
* 定义扩展名到创建消息的映射,用于 PropertyInspector 中的资产字段创建按钮
|
||||
* @zh 资产创建消息映射
|
||||
* @en Asset creation message mapping
|
||||
*/
|
||||
export interface AssetCreationMapping {
|
||||
/** 文件扩展名(包含点号,如 '.tilemap')| File extension (with dot) */
|
||||
/** @zh 文件扩展名(包含点号,如 '.tilemap') @en File extension (with dot) */
|
||||
extension: string;
|
||||
/** 创建资产时发送的消息名 | Message name to publish when creating asset */
|
||||
/** @zh 创建资产时发送的消息名 @en Message name to publish when creating asset */
|
||||
createMessage: string;
|
||||
/** 是否支持创建(可选,默认 true)| Whether creation is supported */
|
||||
/** @zh 是否支持创建(可选,默认 true) @en Whether creation is supported */
|
||||
canCreate?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* FileActionRegistry 服务标识符
|
||||
* FileActionRegistry service identifier
|
||||
*/
|
||||
export const IFileActionRegistry = Symbol.for('IFileActionRegistry');
|
||||
/** @zh FileActionRegistry 服务标识符 @en FileActionRegistry service identifier */
|
||||
export const IFileActionRegistry = createRegistryToken<FileActionRegistry>('FileActionRegistry');
|
||||
|
||||
/**
|
||||
* 文件操作注册表服务
|
||||
*
|
||||
* 管理插件注册的文件操作处理器和文件创建模板
|
||||
* @zh 文件操作注册表服务 - 管理插件注册的文件操作处理器和文件创建模板
|
||||
* @en File Action Registry Service - Manages file action handlers and creation templates
|
||||
*/
|
||||
export class FileActionRegistry implements IService {
|
||||
private actionHandlers: Map<string, FileActionHandler[]> = new Map();
|
||||
private creationTemplates: FileCreationTemplate[] = [];
|
||||
private assetCreationMappings: Map<string, AssetCreationMapping> = new Map();
|
||||
private readonly _actionHandlers = new Map<string, FileActionHandler[]>();
|
||||
private readonly _creationTemplates: FileCreationTemplate[] = [];
|
||||
private readonly _assetCreationMappings = new Map<string, AssetCreationMapping>();
|
||||
private readonly _logger: ILogger;
|
||||
|
||||
constructor() {
|
||||
this._logger = createLogger('FileActionRegistry');
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册文件操作处理器
|
||||
* @zh 规范化扩展名(确保以 . 开头且小写)
|
||||
* @en Normalize extension (ensure starts with . and lowercase)
|
||||
*/
|
||||
private _normalizeExtension(ext: string): string {
|
||||
const lower = ext.toLowerCase();
|
||||
return lower.startsWith('.') ? lower : `.${lower}`;
|
||||
}
|
||||
|
||||
/** @zh 注册文件操作处理器 @en Register file action handler */
|
||||
registerActionHandler(handler: FileActionHandler): void {
|
||||
for (const ext of handler.extensions) {
|
||||
const handlers = this.actionHandlers.get(ext) || [];
|
||||
const handlers = this._actionHandlers.get(ext) ?? [];
|
||||
handlers.push(handler);
|
||||
this.actionHandlers.set(ext, handlers);
|
||||
this._actionHandlers.set(ext, handlers);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销文件操作处理器
|
||||
*/
|
||||
/** @zh 注销文件操作处理器 @en Unregister file action handler */
|
||||
unregisterActionHandler(handler: FileActionHandler): void {
|
||||
for (const ext of handler.extensions) {
|
||||
const handlers = this.actionHandlers.get(ext);
|
||||
if (handlers) {
|
||||
const index = handlers.indexOf(handler);
|
||||
if (index !== -1) {
|
||||
handlers.splice(index, 1);
|
||||
}
|
||||
if (handlers.length === 0) {
|
||||
this.actionHandlers.delete(ext);
|
||||
}
|
||||
}
|
||||
const handlers = this._actionHandlers.get(ext);
|
||||
if (!handlers) continue;
|
||||
|
||||
const index = handlers.indexOf(handler);
|
||||
if (index !== -1) handlers.splice(index, 1);
|
||||
if (handlers.length === 0) this._actionHandlers.delete(ext);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册文件创建模板
|
||||
*/
|
||||
/** @zh 注册文件创建模板 @en Register file creation template */
|
||||
registerCreationTemplate(template: FileCreationTemplate): void {
|
||||
this.creationTemplates.push(template);
|
||||
this._creationTemplates.push(template);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销文件创建模板
|
||||
*/
|
||||
/** @zh 注销文件创建模板 @en Unregister file creation template */
|
||||
unregisterCreationTemplate(template: FileCreationTemplate): void {
|
||||
const index = this.creationTemplates.indexOf(template);
|
||||
if (index !== -1) {
|
||||
this.creationTemplates.splice(index, 1);
|
||||
}
|
||||
const index = this._creationTemplates.indexOf(template);
|
||||
if (index !== -1) this._creationTemplates.splice(index, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件扩展名的处理器
|
||||
*/
|
||||
/** @zh 获取文件扩展名的处理器 @en Get handlers for extension */
|
||||
getHandlersForExtension(extension: string): FileActionHandler[] {
|
||||
return this.actionHandlers.get(extension) || [];
|
||||
return this._actionHandlers.get(extension) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件的处理器
|
||||
*/
|
||||
/** @zh 获取文件的处理器 @en Get handlers for file */
|
||||
getHandlersForFile(filePath: string): FileActionHandler[] {
|
||||
const extension = this.getFileExtension(filePath);
|
||||
return extension ? this.getHandlersForExtension(extension) : [];
|
||||
const ext = this._extractFileExtension(filePath);
|
||||
return ext ? this.getHandlersForExtension(ext) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有文件创建模板
|
||||
*/
|
||||
/** @zh 获取所有文件创建模板 @en Get all creation templates */
|
||||
getCreationTemplates(): FileCreationTemplate[] {
|
||||
return this.creationTemplates;
|
||||
return this._creationTemplates;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理文件双击
|
||||
*/
|
||||
/** @zh 处理文件双击 @en Handle file double click */
|
||||
async handleDoubleClick(filePath: string): Promise<boolean> {
|
||||
const handlers = this.getHandlersForFile(filePath);
|
||||
|
||||
for (const handler of handlers) {
|
||||
for (const handler of this.getHandlersForFile(filePath)) {
|
||||
if (handler.onDoubleClick) {
|
||||
await handler.onDoubleClick(filePath);
|
||||
return true;
|
||||
@@ -118,12 +102,9 @@ export class FileActionRegistry implements IService {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理文件打开
|
||||
*/
|
||||
/** @zh 处理文件打开 @en Handle file open */
|
||||
async handleOpen(filePath: string): Promise<boolean> {
|
||||
const handlers = this.getHandlersForFile(filePath);
|
||||
for (const handler of handlers) {
|
||||
for (const handler of this.getHandlersForFile(filePath)) {
|
||||
if (handler.onOpen) {
|
||||
await handler.onOpen(filePath);
|
||||
return true;
|
||||
@@ -132,78 +113,51 @@ export class FileActionRegistry implements IService {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册资产创建消息映射
|
||||
* Register asset creation message mapping
|
||||
*/
|
||||
/** @zh 注册资产创建消息映射 @en Register asset creation mapping */
|
||||
registerAssetCreationMapping(mapping: AssetCreationMapping): void {
|
||||
const normalizedExt = mapping.extension.startsWith('.')
|
||||
? mapping.extension.toLowerCase()
|
||||
: `.${mapping.extension.toLowerCase()}`;
|
||||
this.assetCreationMappings.set(normalizedExt, {
|
||||
...mapping,
|
||||
extension: normalizedExt
|
||||
});
|
||||
const ext = this._normalizeExtension(mapping.extension);
|
||||
this._assetCreationMappings.set(ext, { ...mapping, extension: ext });
|
||||
this._logger.debug(`Registered asset creation mapping: ${ext}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销资产创建消息映射
|
||||
* Unregister asset creation message mapping
|
||||
*/
|
||||
/** @zh 注销资产创建消息映射 @en Unregister asset creation mapping */
|
||||
unregisterAssetCreationMapping(extension: string): void {
|
||||
const normalizedExt = extension.startsWith('.')
|
||||
? extension.toLowerCase()
|
||||
: `.${extension.toLowerCase()}`;
|
||||
this.assetCreationMappings.delete(normalizedExt);
|
||||
const ext = this._normalizeExtension(extension);
|
||||
if (this._assetCreationMappings.delete(ext)) {
|
||||
this._logger.debug(`Unregistered asset creation mapping: ${ext}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取扩展名对应的资产创建消息映射
|
||||
* Get asset creation mapping for extension
|
||||
*/
|
||||
/** @zh 获取扩展名对应的资产创建消息映射 @en Get asset creation mapping for extension */
|
||||
getAssetCreationMapping(extension: string): AssetCreationMapping | undefined {
|
||||
const normalizedExt = extension.startsWith('.')
|
||||
? extension.toLowerCase()
|
||||
: `.${extension.toLowerCase()}`;
|
||||
return this.assetCreationMappings.get(normalizedExt);
|
||||
return this._assetCreationMappings.get(this._normalizeExtension(extension));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查扩展名是否支持创建资产
|
||||
* Check if extension supports asset creation
|
||||
*/
|
||||
/** @zh 检查扩展名是否支持创建资产 @en Check if extension supports asset creation */
|
||||
canCreateAsset(extension: string): boolean {
|
||||
const mapping = this.getAssetCreationMapping(extension);
|
||||
return mapping?.canCreate !== false;
|
||||
return this.getAssetCreationMapping(extension)?.canCreate !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有资产创建映射
|
||||
* Get all asset creation mappings
|
||||
*/
|
||||
/** @zh 获取所有资产创建映射 @en Get all asset creation mappings */
|
||||
getAllAssetCreationMappings(): AssetCreationMapping[] {
|
||||
return Array.from(this.assetCreationMappings.values());
|
||||
return Array.from(this._assetCreationMappings.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有注册
|
||||
*/
|
||||
/** @zh 清空所有注册 @en Clear all registrations */
|
||||
clear(): void {
|
||||
this.actionHandlers.clear();
|
||||
this.creationTemplates = [];
|
||||
this.assetCreationMappings.clear();
|
||||
this._actionHandlers.clear();
|
||||
this._creationTemplates.length = 0;
|
||||
this._assetCreationMappings.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放资源
|
||||
*/
|
||||
/** @zh 释放资源 @en Dispose resources */
|
||||
dispose(): void {
|
||||
this.clear();
|
||||
}
|
||||
|
||||
private getFileExtension(filePath: string): string | null {
|
||||
/** @zh 提取文件扩展名 @en Extract file extension */
|
||||
private _extractFileExtension(filePath: string): string | null {
|
||||
const lastDot = filePath.lastIndexOf('.');
|
||||
if (lastDot === -1) return null;
|
||||
return filePath.substring(lastDot + 1).toLowerCase();
|
||||
return lastDot === -1 ? null : filePath.substring(lastDot + 1).toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,81 +1,58 @@
|
||||
import { IInspectorProvider, InspectorContext } from './IInspectorProvider';
|
||||
import { IService } from '@esengine/ecs-framework';
|
||||
/**
|
||||
* @zh Inspector 注册表
|
||||
* @en Inspector Registry
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { PrioritizedRegistry, createRegistryToken } from './BaseRegistry';
|
||||
import type { IInspectorProvider, InspectorContext } from './IInspectorProvider';
|
||||
|
||||
export class InspectorRegistry implements IService {
|
||||
private providers: Map<string, IInspectorProvider> = new Map();
|
||||
/**
|
||||
* @zh Inspector 注册表
|
||||
* @en Inspector Registry
|
||||
*/
|
||||
export class InspectorRegistry extends PrioritizedRegistry<IInspectorProvider> {
|
||||
constructor() {
|
||||
super('InspectorRegistry');
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册Inspector提供器
|
||||
*/
|
||||
register(provider: IInspectorProvider): void {
|
||||
if (this.providers.has(provider.id)) {
|
||||
console.warn(`Inspector provider with id "${provider.id}" is already registered`);
|
||||
return;
|
||||
}
|
||||
this.providers.set(provider.id, provider);
|
||||
protected getItemKey(item: IInspectorProvider): string {
|
||||
return item.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销Inspector提供器
|
||||
*/
|
||||
unregister(providerId: string): void {
|
||||
this.providers.delete(providerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定ID的提供器
|
||||
* @zh 获取指定 ID 的提供器
|
||||
* @en Get provider by ID
|
||||
*/
|
||||
getProvider(providerId: string): IInspectorProvider | undefined {
|
||||
return this.providers.get(providerId);
|
||||
return this.get(providerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有提供器
|
||||
* @zh 获取所有提供器
|
||||
* @en Get all providers
|
||||
*/
|
||||
getAllProviders(): IInspectorProvider[] {
|
||||
return Array.from(this.providers.values());
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找可以处理指定目标的提供器
|
||||
* 按优先级排序,返回第一个可以处理的提供器
|
||||
* @zh 查找可以处理指定目标的提供器
|
||||
* @en Find provider that can handle the target
|
||||
*/
|
||||
findProvider(target: unknown): IInspectorProvider | undefined {
|
||||
const providers = Array.from(this.providers.values())
|
||||
.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
||||
|
||||
for (const provider of providers) {
|
||||
if (provider.canHandle(target)) {
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return this.findByPriority(provider => provider.canHandle(target));
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染Inspector内容
|
||||
* 自动查找合适的提供器并渲染
|
||||
* @zh 渲染 Inspector 内容
|
||||
* @en Render inspector content
|
||||
*/
|
||||
render(target: unknown, context: InspectorContext): React.ReactElement | null {
|
||||
const provider = this.findProvider(target);
|
||||
if (!provider) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return provider.render(target, context);
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.providers.clear();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.clear();
|
||||
return provider?.render(target, context) ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
// Service identifier for DI registration (用于跨包插件访问)
|
||||
// 使用 Symbol.for 确保跨包共享同一个 Symbol
|
||||
export const IInspectorRegistry = Symbol.for('IInspectorRegistry');
|
||||
/** @zh Inspector 注册表服务标识符 @en Inspector registry service identifier */
|
||||
export const IInspectorRegistry = createRegistryToken<InspectorRegistry>('InspectorRegistry');
|
||||
|
||||
@@ -91,14 +91,14 @@ export type TranslationParams = Record<string, string | number>;
|
||||
*/
|
||||
@Injectable()
|
||||
export class LocaleService implements IService {
|
||||
private currentLocale: Locale = 'en';
|
||||
private translations: Map<Locale, Translations> = new Map();
|
||||
private changeListeners: Set<(locale: Locale) => void> = new Set();
|
||||
private _currentLocale: Locale = 'en';
|
||||
private _translations: Map<Locale, Translations> = new Map();
|
||||
private _changeListeners: Set<(locale: Locale) => void> = new Set();
|
||||
|
||||
constructor() {
|
||||
const savedLocale = this.loadSavedLocale();
|
||||
const savedLocale = this._loadSavedLocale();
|
||||
if (savedLocale) {
|
||||
this.currentLocale = savedLocale;
|
||||
this._currentLocale = savedLocale;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ export class LocaleService implements IService {
|
||||
* @param translations - 翻译对象 | Translation object
|
||||
*/
|
||||
public registerTranslations(locale: Locale, translations: Translations): void {
|
||||
this.translations.set(locale, translations);
|
||||
this._translations.set(locale, translations);
|
||||
logger.info(`Registered translations for locale: ${locale}`);
|
||||
}
|
||||
|
||||
@@ -153,19 +153,19 @@ export class LocaleService implements IService {
|
||||
const locales: Locale[] = ['en', 'zh', 'es'];
|
||||
|
||||
for (const locale of locales) {
|
||||
const existing = this.translations.get(locale) || {};
|
||||
const existing = this._translations.get(locale) || {};
|
||||
const pluginTrans = pluginTranslations[locale];
|
||||
|
||||
if (pluginTrans) {
|
||||
// 深度合并到命名空间下 | Deep merge under namespace
|
||||
const merged = {
|
||||
...existing,
|
||||
[namespace]: this.deepMerge(
|
||||
[namespace]: this._deepMerge(
|
||||
(existing[namespace] as Translations) || {},
|
||||
pluginTrans
|
||||
)
|
||||
};
|
||||
this.translations.set(locale, merged);
|
||||
this._translations.set(locale, merged);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ export class LocaleService implements IService {
|
||||
* 深度合并两个翻译对象
|
||||
* Deep merge two translation objects
|
||||
*/
|
||||
private deepMerge(target: Translations, source: Translations): Translations {
|
||||
private _deepMerge(target: Translations, source: Translations): Translations {
|
||||
const result: Translations = { ...target };
|
||||
|
||||
for (const key of Object.keys(source)) {
|
||||
@@ -189,7 +189,7 @@ export class LocaleService implements IService {
|
||||
typeof targetValue === 'object' &&
|
||||
targetValue !== null
|
||||
) {
|
||||
result[key] = this.deepMerge(
|
||||
result[key] = this._deepMerge(
|
||||
targetValue as Translations,
|
||||
sourceValue as Translations
|
||||
);
|
||||
@@ -206,7 +206,7 @@ export class LocaleService implements IService {
|
||||
* Get current locale
|
||||
*/
|
||||
public getCurrentLocale(): Locale {
|
||||
return this.currentLocale;
|
||||
return this._currentLocale;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,15 +224,15 @@ export class LocaleService implements IService {
|
||||
* @param locale - 目标语言代码 | Target locale code
|
||||
*/
|
||||
public setLocale(locale: Locale): void {
|
||||
if (!this.translations.has(locale)) {
|
||||
if (!this._translations.has(locale)) {
|
||||
logger.warn(`Translations not found for locale: ${locale}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentLocale = locale;
|
||||
this.saveLocale(locale);
|
||||
this._currentLocale = locale;
|
||||
this._saveLocale(locale);
|
||||
|
||||
this.changeListeners.forEach((listener) => listener(locale));
|
||||
this._changeListeners.forEach((listener) => listener(locale));
|
||||
|
||||
logger.info(`Locale changed to: ${locale}`);
|
||||
}
|
||||
@@ -261,12 +261,12 @@ export class LocaleService implements IService {
|
||||
* ```
|
||||
*/
|
||||
public t(key: string, params?: TranslationParams, fallback?: string): string {
|
||||
const translations = this.translations.get(this.currentLocale);
|
||||
const translations = this._translations.get(this._currentLocale);
|
||||
if (!translations) {
|
||||
return fallback || key;
|
||||
}
|
||||
|
||||
const value = this.getNestedValue(translations, key);
|
||||
const value = this._getNestedValue(translations, key);
|
||||
if (typeof value === 'string') {
|
||||
// 支持参数替换 {{key}} | Support parameter substitution {{key}}
|
||||
if (params) {
|
||||
@@ -288,10 +288,10 @@ export class LocaleService implements IService {
|
||||
* @returns 取消订阅函数 | Unsubscribe function
|
||||
*/
|
||||
public onChange(listener: (locale: Locale) => void): () => void {
|
||||
this.changeListeners.add(listener);
|
||||
this._changeListeners.add(listener);
|
||||
|
||||
return () => {
|
||||
this.changeListeners.delete(listener);
|
||||
this._changeListeners.delete(listener);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -303,12 +303,12 @@ export class LocaleService implements IService {
|
||||
* @param locale - 可选的语言代码,默认使用当前语言 | Optional locale, defaults to current
|
||||
*/
|
||||
public hasKey(key: string, locale?: Locale): boolean {
|
||||
const targetLocale = locale || this.currentLocale;
|
||||
const translations = this.translations.get(targetLocale);
|
||||
const targetLocale = locale || this._currentLocale;
|
||||
const translations = this._translations.get(targetLocale);
|
||||
if (!translations) {
|
||||
return false;
|
||||
}
|
||||
const value = this.getNestedValue(translations, key);
|
||||
const value = this._getNestedValue(translations, key);
|
||||
return typeof value === 'string';
|
||||
}
|
||||
|
||||
@@ -316,7 +316,7 @@ export class LocaleService implements IService {
|
||||
* 获取嵌套对象的值
|
||||
* Get nested object value
|
||||
*/
|
||||
private getNestedValue(obj: Translations, path: string): string | Translations | undefined {
|
||||
private _getNestedValue(obj: Translations, path: string): string | Translations | undefined {
|
||||
const keys = path.split('.');
|
||||
let current: string | Translations | undefined = obj;
|
||||
|
||||
@@ -335,7 +335,7 @@ export class LocaleService implements IService {
|
||||
* 从 localStorage 加载保存的语言设置
|
||||
* Load saved locale from localStorage
|
||||
*/
|
||||
private loadSavedLocale(): Locale | null {
|
||||
private _loadSavedLocale(): Locale | null {
|
||||
try {
|
||||
const saved = localStorage.getItem('editor-locale');
|
||||
if (saved === 'en' || saved === 'zh' || saved === 'es') {
|
||||
@@ -351,7 +351,7 @@ export class LocaleService implements IService {
|
||||
* 保存语言设置到 localStorage
|
||||
* Save locale to localStorage
|
||||
*/
|
||||
private saveLocale(locale: Locale): void {
|
||||
private _saveLocale(locale: Locale): void {
|
||||
try {
|
||||
localStorage.setItem('editor-locale', locale);
|
||||
} catch (error) {
|
||||
@@ -360,8 +360,8 @@ export class LocaleService implements IService {
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.translations.clear();
|
||||
this.changeListeners.clear();
|
||||
this._translations.clear();
|
||||
this._changeListeners.clear();
|
||||
logger.info('LocaleService disposed');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* 管理引擎模块、其依赖关系和项目配置。
|
||||
*/
|
||||
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import type {
|
||||
ModuleManifest,
|
||||
ModuleRegistryEntry,
|
||||
@@ -37,7 +38,8 @@ export interface IModuleFileSystem {
|
||||
* 模块注册表服务。
|
||||
*/
|
||||
export class ModuleRegistry {
|
||||
private _modules: Map<string, ModuleRegistryEntry> = new Map();
|
||||
private readonly _modules = new Map<string, ModuleRegistryEntry>();
|
||||
private readonly _logger = createLogger('ModuleRegistry');
|
||||
private _projectConfig: ProjectModuleConfig = { enabled: [] };
|
||||
private _fileSystem: IModuleFileSystem | null = null;
|
||||
private _engineModulesPath: string = '';
|
||||
@@ -332,7 +334,7 @@ export class ModuleRegistry {
|
||||
}>;
|
||||
}>(indexPath);
|
||||
|
||||
console.log(`[ModuleRegistry] Loaded ${index.modules.length} modules from index.json`);
|
||||
this._logger.debug(`Loaded ${index.modules.length} modules from index.json`);
|
||||
|
||||
// Use data directly from index.json (includes jsSize, wasmSize)
|
||||
// 直接使用 index.json 中的数据(包含 jsSize、wasmSize)
|
||||
@@ -386,10 +388,10 @@ export class ModuleRegistry {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.warn(`[ModuleRegistry] index.json not found at ${indexPath}, run 'pnpm copy-modules' first`);
|
||||
this._logger.warn(`index.json not found at ${indexPath}, run 'pnpm copy-modules' first`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[ModuleRegistry] Failed to load index.json:', error);
|
||||
this._logger.error('Failed to load index.json:', error);
|
||||
}
|
||||
|
||||
// Compute dependents | 计算依赖者
|
||||
@@ -433,7 +435,7 @@ export class ModuleRegistry {
|
||||
this._projectConfig = { enabled: this._getDefaultEnabledModules() };
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[ModuleRegistry] Failed to load project config:', error);
|
||||
this._logger.error('Failed to load project config:', error);
|
||||
this._projectConfig = { enabled: this._getDefaultEnabledModules() };
|
||||
}
|
||||
}
|
||||
@@ -457,7 +459,7 @@ export class ModuleRegistry {
|
||||
config.modules = this._projectConfig;
|
||||
await this._fileSystem.writeJson(configPath, config);
|
||||
} catch (error) {
|
||||
console.error('[ModuleRegistry] Failed to save project config:', error);
|
||||
this._logger.error('Failed to save project config:', error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -545,7 +547,7 @@ export class ModuleRegistry {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[ModuleRegistry] Failed to check scene usage:', error);
|
||||
this._logger.warn('Failed to check scene usage:', error);
|
||||
}
|
||||
|
||||
return usages;
|
||||
@@ -602,7 +604,7 @@ export class ModuleRegistry {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[ModuleRegistry] Failed to check script usage:', error);
|
||||
this._logger.warn('Failed to check script usage:', error);
|
||||
}
|
||||
|
||||
return usages;
|
||||
|
||||
@@ -1,56 +1,63 @@
|
||||
/**
|
||||
* @zh 属性渲染器注册表
|
||||
* @en Property Renderer Registry
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { IService, createLogger } from '@esengine/ecs-framework';
|
||||
import { IPropertyRenderer, IPropertyRendererRegistry, PropertyContext } from './IPropertyRenderer';
|
||||
import { PrioritizedRegistry, createRegistryToken } from './BaseRegistry';
|
||||
import type { IPropertyRenderer, IPropertyRendererRegistry, PropertyContext } from './IPropertyRenderer';
|
||||
|
||||
const logger = createLogger('PropertyRendererRegistry');
|
||||
/**
|
||||
* @zh 属性渲染器注册表
|
||||
* @en Property Renderer Registry
|
||||
*/
|
||||
export class PropertyRendererRegistry
|
||||
extends PrioritizedRegistry<IPropertyRenderer>
|
||||
implements IPropertyRendererRegistry {
|
||||
|
||||
export class PropertyRendererRegistry implements IPropertyRendererRegistry, IService {
|
||||
private renderers: Map<string, IPropertyRenderer> = new Map();
|
||||
|
||||
register(renderer: IPropertyRenderer): void {
|
||||
if (this.renderers.has(renderer.id)) {
|
||||
logger.warn(`Overwriting existing property renderer: ${renderer.id}`);
|
||||
}
|
||||
|
||||
this.renderers.set(renderer.id, renderer);
|
||||
logger.debug(`Registered property renderer: ${renderer.name} (${renderer.id})`);
|
||||
constructor() {
|
||||
super('PropertyRendererRegistry');
|
||||
}
|
||||
|
||||
unregister(rendererId: string): void {
|
||||
if (this.renderers.delete(rendererId)) {
|
||||
logger.debug(`Unregistered property renderer: ${rendererId}`);
|
||||
}
|
||||
protected getItemKey(item: IPropertyRenderer): string {
|
||||
return item.id;
|
||||
}
|
||||
|
||||
findRenderer(value: any, context: PropertyContext): IPropertyRenderer | undefined {
|
||||
const renderers = Array.from(this.renderers.values())
|
||||
.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
||||
protected override getItemDisplayName(item: IPropertyRenderer): string {
|
||||
return `${item.name} (${item.id})`;
|
||||
}
|
||||
|
||||
for (const renderer of renderers) {
|
||||
/**
|
||||
* @zh 查找渲染器
|
||||
* @en Find renderer
|
||||
*/
|
||||
findRenderer(value: unknown, context: PropertyContext): IPropertyRenderer | undefined {
|
||||
return this.findByPriority(renderer => {
|
||||
try {
|
||||
if (renderer.canHandle(value, context)) {
|
||||
return renderer;
|
||||
}
|
||||
return renderer.canHandle(value, context);
|
||||
} catch (error) {
|
||||
logger.error(`Error in canHandle for renderer ${renderer.id}:`, error);
|
||||
this._logger.error(`Error in canHandle for ${renderer.id}:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
render(value: any, context: PropertyContext): React.ReactElement | null {
|
||||
/**
|
||||
* @zh 渲染属性
|
||||
* @en Render property
|
||||
*/
|
||||
render(value: unknown, context: PropertyContext): React.ReactElement | null {
|
||||
const renderer = this.findRenderer(value, context);
|
||||
|
||||
if (!renderer) {
|
||||
logger.debug(`No renderer found for value type: ${typeof value}`);
|
||||
this._logger.debug(`No renderer found for value type: ${typeof value}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return renderer.render(value, context);
|
||||
} catch (error) {
|
||||
logger.error(`Error rendering with ${renderer.id}:`, error);
|
||||
this._logger.error(`Error rendering with ${renderer.id}:`, error);
|
||||
return React.createElement(
|
||||
'span',
|
||||
{ style: { color: '#f87171', fontStyle: 'italic' } },
|
||||
@@ -59,16 +66,22 @@ export class PropertyRendererRegistry implements IPropertyRendererRegistry, ISer
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取所有渲染器
|
||||
* @en Get all renderers
|
||||
*/
|
||||
getAllRenderers(): IPropertyRenderer[] {
|
||||
return Array.from(this.renderers.values());
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
hasRenderer(value: any, context: PropertyContext): boolean {
|
||||
/**
|
||||
* @zh 检查是否有可用渲染器
|
||||
* @en Check if renderer is available
|
||||
*/
|
||||
hasRenderer(value: unknown, context: PropertyContext): boolean {
|
||||
return this.findRenderer(value, context) !== undefined;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.renderers.clear();
|
||||
logger.debug('PropertyRendererRegistry disposed');
|
||||
}
|
||||
}
|
||||
|
||||
/** @zh 属性渲染器注册表服务标识符 @en Property renderer registry service identifier */
|
||||
export const PropertyRendererRegistryToken = createRegistryToken<PropertyRendererRegistry>('PropertyRendererRegistry');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { IService } from '@esengine/ecs-framework';
|
||||
import { Injectable } from '@esengine/ecs-framework';
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import { Injectable, createLogger } from '@esengine/ecs-framework';
|
||||
import type { ISerializer } from '../Plugin/EditorModule';
|
||||
import { createRegistryToken } from './BaseRegistry';
|
||||
|
||||
const logger = createLogger('SerializerRegistry');
|
||||
|
||||
@@ -12,7 +12,7 @@ const logger = createLogger('SerializerRegistry');
|
||||
*/
|
||||
@Injectable()
|
||||
export class SerializerRegistry implements IService {
|
||||
private serializers: Map<string, ISerializer> = new Map();
|
||||
private readonly _serializers = new Map<string, ISerializer>();
|
||||
|
||||
/**
|
||||
* 注册序列化器
|
||||
@@ -24,12 +24,12 @@ export class SerializerRegistry implements IService {
|
||||
const type = serializer.getSupportedType();
|
||||
const key = `${pluginName}:${type}`;
|
||||
|
||||
if (this.serializers.has(key)) {
|
||||
if (this._serializers.has(key)) {
|
||||
logger.warn(`Serializer for ${key} is already registered`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.serializers.set(key, serializer);
|
||||
this._serializers.set(key, serializer);
|
||||
logger.info(`Registered serializer: ${key}`);
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ export class SerializerRegistry implements IService {
|
||||
*/
|
||||
public unregister(pluginName: string, type: string): boolean {
|
||||
const key = `${pluginName}:${type}`;
|
||||
const result = this.serializers.delete(key);
|
||||
const result = this._serializers.delete(key);
|
||||
|
||||
if (result) {
|
||||
logger.info(`Unregistered serializer: ${key}`);
|
||||
@@ -72,14 +72,14 @@ export class SerializerRegistry implements IService {
|
||||
const prefix = `${pluginName}:`;
|
||||
const keysToDelete: string[] = [];
|
||||
|
||||
for (const key of this.serializers.keys()) {
|
||||
for (const key of this._serializers.keys()) {
|
||||
if (key.startsWith(prefix)) {
|
||||
keysToDelete.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
for (const key of keysToDelete) {
|
||||
this.serializers.delete(key);
|
||||
this._serializers.delete(key);
|
||||
logger.info(`Unregistered serializer: ${key}`);
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,7 @@ export class SerializerRegistry implements IService {
|
||||
*/
|
||||
public get(pluginName: string, type: string): ISerializer | undefined {
|
||||
const key = `${pluginName}:${type}`;
|
||||
return this.serializers.get(key);
|
||||
return this._serializers.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,7 +105,7 @@ export class SerializerRegistry implements IService {
|
||||
public findByType(type: string): ISerializer[] {
|
||||
const result: ISerializer[] = [];
|
||||
|
||||
for (const [key, serializer] of this.serializers) {
|
||||
for (const [key, serializer] of this._serializers) {
|
||||
if (key.endsWith(`:${type}`)) {
|
||||
result.push(serializer);
|
||||
}
|
||||
@@ -120,7 +120,7 @@ export class SerializerRegistry implements IService {
|
||||
* @returns 序列化器映射表
|
||||
*/
|
||||
public getAll(): Map<string, ISerializer> {
|
||||
return new Map(this.serializers);
|
||||
return new Map(this._serializers);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,7 +132,7 @@ export class SerializerRegistry implements IService {
|
||||
*/
|
||||
public has(pluginName: string, type: string): boolean {
|
||||
const key = `${pluginName}:${type}`;
|
||||
return this.serializers.has(key);
|
||||
return this._serializers.has(key);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,7 +175,10 @@ export class SerializerRegistry implements IService {
|
||||
* 释放资源
|
||||
*/
|
||||
public dispose(): void {
|
||||
this.serializers.clear();
|
||||
this._serializers.clear();
|
||||
logger.info('SerializerRegistry disposed');
|
||||
}
|
||||
}
|
||||
|
||||
/** @zh 序列化器注册表服务标识符 @en Serializer registry service identifier */
|
||||
export const ISerializerRegistry = createRegistryToken<SerializerRegistry>('SerializerRegistry');
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { Injectable, IService } from '@esengine/ecs-framework';
|
||||
import { createLogger, type ILogger, type IService } from '@esengine/ecs-framework';
|
||||
import { createRegistryToken } from './BaseRegistry';
|
||||
|
||||
/**
|
||||
* @zh 设置类型
|
||||
* @en Setting type
|
||||
*/
|
||||
export type SettingType = 'string' | 'number' | 'boolean' | 'select' | 'color' | 'range' | 'pluginList' | 'collisionMatrix' | 'moduleList';
|
||||
|
||||
/**
|
||||
@@ -82,127 +87,154 @@ export interface SettingCategory {
|
||||
sections: SettingSection[];
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
/**
|
||||
* @zh 设置注册表
|
||||
* @en Settings Registry
|
||||
*/
|
||||
export class SettingsRegistry implements IService {
|
||||
private categories: Map<string, SettingCategory> = new Map();
|
||||
private readonly _categories = new Map<string, SettingCategory>();
|
||||
private readonly _logger: ILogger;
|
||||
|
||||
public dispose(): void {
|
||||
this.categories.clear();
|
||||
constructor() {
|
||||
this._logger = createLogger('SettingsRegistry');
|
||||
}
|
||||
|
||||
public registerCategory(category: SettingCategory): void {
|
||||
if (this.categories.has(category.id)) {
|
||||
console.warn(`[SettingsRegistry] Category ${category.id} already registered, overwriting`);
|
||||
}
|
||||
console.log(`[SettingsRegistry] Registering category: ${category.id} (${category.title}), sections: ${category.sections.map(s => s.id).join(', ')}`);
|
||||
this.categories.set(category.id, category);
|
||||
/** @zh 释放资源 @en Dispose resources */
|
||||
dispose(): void {
|
||||
this._categories.clear();
|
||||
this._logger.debug('Disposed');
|
||||
}
|
||||
|
||||
public registerSection(categoryId: string, section: SettingSection): void {
|
||||
let category = this.categories.get(categoryId);
|
||||
|
||||
if (!category) {
|
||||
category = {
|
||||
id: categoryId,
|
||||
title: categoryId,
|
||||
sections: []
|
||||
};
|
||||
this.categories.set(categoryId, category);
|
||||
/**
|
||||
* @zh 注册设置分类
|
||||
* @en Register setting category
|
||||
*/
|
||||
registerCategory(category: SettingCategory): void {
|
||||
if (this._categories.has(category.id)) {
|
||||
this._logger.warn(`Overwriting category: ${category.id}`);
|
||||
}
|
||||
this._categories.set(category.id, category);
|
||||
this._logger.debug(`Registered category: ${category.id}`);
|
||||
}
|
||||
|
||||
const existingIndex = category.sections.findIndex((s) => s.id === section.id);
|
||||
/**
|
||||
* @zh 注册设置分区
|
||||
* @en Register setting section
|
||||
*/
|
||||
registerSection(categoryId: string, section: SettingSection): void {
|
||||
const category = this._ensureCategory(categoryId);
|
||||
|
||||
const existingIndex = category.sections.findIndex(s => s.id === section.id);
|
||||
if (existingIndex >= 0) {
|
||||
category.sections[existingIndex] = section;
|
||||
console.warn(`[SettingsRegistry] Section ${section.id} in category ${categoryId} already exists, overwriting`);
|
||||
this._logger.warn(`Overwriting section: ${section.id} in ${categoryId}`);
|
||||
} else {
|
||||
category.sections.push(section);
|
||||
this._logger.debug(`Registered section: ${section.id} in ${categoryId}`);
|
||||
}
|
||||
}
|
||||
|
||||
public registerSetting(categoryId: string, sectionId: string, setting: SettingDescriptor): void {
|
||||
let category = this.categories.get(categoryId);
|
||||
/**
|
||||
* @zh 注册单个设置项
|
||||
* @en Register single setting
|
||||
*/
|
||||
registerSetting(categoryId: string, sectionId: string, setting: SettingDescriptor): void {
|
||||
const category = this._ensureCategory(categoryId);
|
||||
const section = this._ensureSection(category, sectionId);
|
||||
|
||||
if (!category) {
|
||||
category = {
|
||||
id: categoryId,
|
||||
title: categoryId,
|
||||
sections: []
|
||||
};
|
||||
this.categories.set(categoryId, category);
|
||||
}
|
||||
|
||||
let section = category.sections.find((s) => s.id === sectionId);
|
||||
if (!section) {
|
||||
section = {
|
||||
id: sectionId,
|
||||
title: sectionId,
|
||||
settings: []
|
||||
};
|
||||
category.sections.push(section);
|
||||
}
|
||||
|
||||
const existingIndex = section.settings.findIndex((s) => s.key === setting.key);
|
||||
const existingIndex = section.settings.findIndex(s => s.key === setting.key);
|
||||
if (existingIndex >= 0) {
|
||||
section.settings[existingIndex] = setting;
|
||||
console.warn(`[SettingsRegistry] Setting ${setting.key} in section ${sectionId} already exists, overwriting`);
|
||||
this._logger.warn(`Overwriting setting: ${setting.key} in ${sectionId}`);
|
||||
} else {
|
||||
section.settings.push(setting);
|
||||
this._logger.debug(`Registered setting: ${setting.key} in ${sectionId}`);
|
||||
}
|
||||
}
|
||||
|
||||
public unregisterCategory(categoryId: string): void {
|
||||
this.categories.delete(categoryId);
|
||||
/**
|
||||
* @zh 确保分类存在
|
||||
* @en Ensure category exists
|
||||
*/
|
||||
private _ensureCategory(categoryId: string): SettingCategory {
|
||||
let category = this._categories.get(categoryId);
|
||||
if (!category) {
|
||||
category = { id: categoryId, title: categoryId, sections: [] };
|
||||
this._categories.set(categoryId, category);
|
||||
}
|
||||
return category;
|
||||
}
|
||||
|
||||
public unregisterSection(categoryId: string, sectionId: string): void {
|
||||
const category = this.categories.get(categoryId);
|
||||
if (category) {
|
||||
category.sections = category.sections.filter((s) => s.id !== sectionId);
|
||||
if (category.sections.length === 0) {
|
||||
this.categories.delete(categoryId);
|
||||
}
|
||||
/**
|
||||
* @zh 确保分区存在
|
||||
* @en Ensure section exists
|
||||
*/
|
||||
private _ensureSection(category: SettingCategory, sectionId: string): SettingSection {
|
||||
let section = category.sections.find(s => s.id === sectionId);
|
||||
if (!section) {
|
||||
section = { id: sectionId, title: sectionId, settings: [] };
|
||||
category.sections.push(section);
|
||||
}
|
||||
return section;
|
||||
}
|
||||
|
||||
/** @zh 注销分类 @en Unregister category */
|
||||
unregisterCategory(categoryId: string): void {
|
||||
this._categories.delete(categoryId);
|
||||
}
|
||||
|
||||
/** @zh 注销分区 @en Unregister section */
|
||||
unregisterSection(categoryId: string, sectionId: string): void {
|
||||
const category = this._categories.get(categoryId);
|
||||
if (!category) return;
|
||||
|
||||
category.sections = category.sections.filter(s => s.id !== sectionId);
|
||||
if (category.sections.length === 0) {
|
||||
this._categories.delete(categoryId);
|
||||
}
|
||||
}
|
||||
|
||||
public getCategory(categoryId: string): SettingCategory | undefined {
|
||||
return this.categories.get(categoryId);
|
||||
/** @zh 获取分类 @en Get category */
|
||||
getCategory(categoryId: string): SettingCategory | undefined {
|
||||
return this._categories.get(categoryId);
|
||||
}
|
||||
|
||||
public getAllCategories(): SettingCategory[] {
|
||||
return Array.from(this.categories.values());
|
||||
/** @zh 获取所有分类 @en Get all categories */
|
||||
getAllCategories(): SettingCategory[] {
|
||||
return Array.from(this._categories.values());
|
||||
}
|
||||
|
||||
public getSetting(categoryId: string, sectionId: string, key: string): SettingDescriptor | undefined {
|
||||
const category = this.categories.get(categoryId);
|
||||
if (!category) return undefined;
|
||||
|
||||
const section = category.sections.find((s) => s.id === sectionId);
|
||||
if (!section) return undefined;
|
||||
|
||||
return section.settings.find((s) => s.key === key);
|
||||
/** @zh 获取设置项 @en Get setting */
|
||||
getSetting(categoryId: string, sectionId: string, key: string): SettingDescriptor | undefined {
|
||||
const section = this._categories.get(categoryId)?.sections.find(s => s.id === sectionId);
|
||||
return section?.settings.find(s => s.key === key);
|
||||
}
|
||||
|
||||
public getAllSettings(): Map<string, SettingDescriptor> {
|
||||
/** @zh 获取所有设置项 @en Get all settings */
|
||||
getAllSettings(): Map<string, SettingDescriptor> {
|
||||
const allSettings = new Map<string, SettingDescriptor>();
|
||||
|
||||
for (const category of this.categories.values()) {
|
||||
for (const category of this._categories.values()) {
|
||||
for (const section of category.sections) {
|
||||
for (const setting of section.settings) {
|
||||
allSettings.set(setting.key, setting);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allSettings;
|
||||
}
|
||||
|
||||
public validateSetting(setting: SettingDescriptor, value: any): boolean {
|
||||
/**
|
||||
* @zh 验证设置值
|
||||
* @en Validate setting value
|
||||
*/
|
||||
validateSetting(setting: SettingDescriptor, value: unknown): boolean {
|
||||
if (setting.validator) {
|
||||
return setting.validator.validate(value);
|
||||
}
|
||||
|
||||
switch (setting.type) {
|
||||
case 'number':
|
||||
case 'range':
|
||||
if (typeof value !== 'number') return false;
|
||||
if (setting.min !== undefined && value < setting.min) return false;
|
||||
if (setting.max !== undefined && value > setting.max) return false;
|
||||
@@ -215,14 +247,7 @@ export class SettingsRegistry implements IService {
|
||||
return typeof value === 'string';
|
||||
|
||||
case 'select':
|
||||
if (!setting.options) return false;
|
||||
return setting.options.some((opt) => opt.value === value);
|
||||
|
||||
case 'range':
|
||||
if (typeof value !== 'number') return false;
|
||||
if (setting.min !== undefined && value < setting.min) return false;
|
||||
if (setting.max !== undefined && value > setting.max) return false;
|
||||
return true;
|
||||
return setting.options?.some(opt => opt.value === value) ?? false;
|
||||
|
||||
case 'color':
|
||||
return typeof value === 'string' && /^#[0-9A-Fa-f]{6}$/.test(value);
|
||||
@@ -232,3 +257,6 @@ export class SettingsRegistry implements IService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @zh 设置注册表服务标识符 @en Settings registry service identifier */
|
||||
export const ISettingsRegistry = createRegistryToken<SettingsRegistry>('SettingsRegistry');
|
||||
|
||||
@@ -1,36 +1,52 @@
|
||||
import type { IService } from '@esengine/ecs-framework';
|
||||
import { Injectable } from '@esengine/ecs-framework';
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import { Injectable, createLogger } from '@esengine/ecs-framework';
|
||||
import type { MenuItem, ToolbarItem, PanelDescriptor } from '../Types/UITypes';
|
||||
import { createRegistryToken } from './BaseRegistry';
|
||||
|
||||
const logger = createLogger('UIRegistry');
|
||||
|
||||
/**
|
||||
* UI 注册表
|
||||
*
|
||||
* 管理所有编辑器 UI 扩展点的注册和查询。
|
||||
* @zh UI 注册表 - 管理所有编辑器 UI 扩展点的注册和查询
|
||||
* @en UI Registry - Manages all editor UI extension point registration and queries
|
||||
*/
|
||||
|
||||
/** @zh 带排序权重的项 @en Item with sort order */
|
||||
interface IOrdered {
|
||||
readonly order?: number;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class UIRegistry implements IService {
|
||||
private menus: Map<string, MenuItem> = new Map();
|
||||
private toolbarItems: Map<string, ToolbarItem> = new Map();
|
||||
private panels: Map<string, PanelDescriptor> = new Map();
|
||||
private readonly _menus = new Map<string, MenuItem>();
|
||||
private readonly _toolbarItems = new Map<string, ToolbarItem>();
|
||||
private readonly _panels = new Map<string, PanelDescriptor>();
|
||||
|
||||
// ========== 辅助方法 | Helper Methods ==========
|
||||
|
||||
/** @zh 按 order 排序 @en Sort by order */
|
||||
private _sortByOrder<T extends IOrdered>(items: T[]): T[] {
|
||||
return items.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
||||
}
|
||||
|
||||
// ========== 菜单管理 | Menu Management ==========
|
||||
|
||||
/**
|
||||
* 注册菜单项
|
||||
* @zh 注册菜单项
|
||||
* @en Register menu item
|
||||
*/
|
||||
public registerMenu(item: MenuItem): void {
|
||||
if (this.menus.has(item.id)) {
|
||||
if (this._menus.has(item.id)) {
|
||||
logger.warn(`Menu item ${item.id} is already registered`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.menus.set(item.id, item);
|
||||
this._menus.set(item.id, item);
|
||||
logger.debug(`Registered menu item: ${item.id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量注册菜单项
|
||||
* @zh 批量注册菜单项
|
||||
* @en Register multiple menu items
|
||||
*/
|
||||
public registerMenus(items: MenuItem[]): void {
|
||||
for (const item of items) {
|
||||
@@ -39,56 +55,59 @@ export class UIRegistry implements IService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销菜单项
|
||||
* @zh 注销菜单项
|
||||
* @en Unregister menu item
|
||||
*/
|
||||
public unregisterMenu(id: string): boolean {
|
||||
const result = this.menus.delete(id);
|
||||
const result = this._menus.delete(id);
|
||||
if (result) {
|
||||
logger.debug(`Unregistered menu item: ${id}`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取菜单项
|
||||
*/
|
||||
/** @zh 获取菜单项 @en Get menu item */
|
||||
public getMenu(id: string): MenuItem | undefined {
|
||||
return this.menus.get(id);
|
||||
return this._menus.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有菜单项
|
||||
* @zh 获取所有菜单项
|
||||
* @en Get all menu items
|
||||
*/
|
||||
public getAllMenus(): MenuItem[] {
|
||||
return Array.from(this.menus.values()).sort((a, b) => {
|
||||
return (a.order ?? 0) - (b.order ?? 0);
|
||||
});
|
||||
return this._sortByOrder(Array.from(this._menus.values()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定父菜单的子菜单
|
||||
* @zh 获取指定父菜单的子菜单
|
||||
* @en Get child menus of specified parent
|
||||
*/
|
||||
public getChildMenus(parentId: string): MenuItem[] {
|
||||
return this.getAllMenus()
|
||||
.filter((item) => item.parentId === parentId)
|
||||
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
||||
return this._sortByOrder(
|
||||
Array.from(this._menus.values()).filter((item) => item.parentId === parentId)
|
||||
);
|
||||
}
|
||||
|
||||
// ========== 工具栏管理 | Toolbar Management ==========
|
||||
|
||||
/**
|
||||
* 注册工具栏项
|
||||
* @zh 注册工具栏项
|
||||
* @en Register toolbar item
|
||||
*/
|
||||
public registerToolbarItem(item: ToolbarItem): void {
|
||||
if (this.toolbarItems.has(item.id)) {
|
||||
if (this._toolbarItems.has(item.id)) {
|
||||
logger.warn(`Toolbar item ${item.id} is already registered`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.toolbarItems.set(item.id, item);
|
||||
this._toolbarItems.set(item.id, item);
|
||||
logger.debug(`Registered toolbar item: ${item.id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量注册工具栏项
|
||||
* @zh 批量注册工具栏项
|
||||
* @en Register multiple toolbar items
|
||||
*/
|
||||
public registerToolbarItems(items: ToolbarItem[]): void {
|
||||
for (const item of items) {
|
||||
@@ -97,56 +116,59 @@ export class UIRegistry implements IService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销工具栏项
|
||||
* @zh 注销工具栏项
|
||||
* @en Unregister toolbar item
|
||||
*/
|
||||
public unregisterToolbarItem(id: string): boolean {
|
||||
const result = this.toolbarItems.delete(id);
|
||||
const result = this._toolbarItems.delete(id);
|
||||
if (result) {
|
||||
logger.debug(`Unregistered toolbar item: ${id}`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工具栏项
|
||||
*/
|
||||
/** @zh 获取工具栏项 @en Get toolbar item */
|
||||
public getToolbarItem(id: string): ToolbarItem | undefined {
|
||||
return this.toolbarItems.get(id);
|
||||
return this._toolbarItems.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有工具栏项
|
||||
* @zh 获取所有工具栏项
|
||||
* @en Get all toolbar items
|
||||
*/
|
||||
public getAllToolbarItems(): ToolbarItem[] {
|
||||
return Array.from(this.toolbarItems.values()).sort((a, b) => {
|
||||
return (a.order ?? 0) - (b.order ?? 0);
|
||||
});
|
||||
return this._sortByOrder(Array.from(this._toolbarItems.values()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定组的工具栏项
|
||||
* @zh 获取指定组的工具栏项
|
||||
* @en Get toolbar items by group
|
||||
*/
|
||||
public getToolbarItemsByGroup(groupId: string): ToolbarItem[] {
|
||||
return this.getAllToolbarItems()
|
||||
.filter((item) => item.groupId === groupId)
|
||||
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
||||
return this._sortByOrder(
|
||||
Array.from(this._toolbarItems.values()).filter((item) => item.groupId === groupId)
|
||||
);
|
||||
}
|
||||
|
||||
// ========== 面板管理 | Panel Management ==========
|
||||
|
||||
/**
|
||||
* 注册面板
|
||||
* @zh 注册面板
|
||||
* @en Register panel
|
||||
*/
|
||||
public registerPanel(panel: PanelDescriptor): void {
|
||||
if (this.panels.has(panel.id)) {
|
||||
if (this._panels.has(panel.id)) {
|
||||
logger.warn(`Panel ${panel.id} is already registered`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.panels.set(panel.id, panel);
|
||||
this._panels.set(panel.id, panel);
|
||||
logger.debug(`Registered panel: ${panel.id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量注册面板
|
||||
* @zh 批量注册面板
|
||||
* @en Register multiple panels
|
||||
*/
|
||||
public registerPanels(panels: PanelDescriptor[]): void {
|
||||
for (const panel of panels) {
|
||||
@@ -155,48 +177,50 @@ export class UIRegistry implements IService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销面板
|
||||
* @zh 注销面板
|
||||
* @en Unregister panel
|
||||
*/
|
||||
public unregisterPanel(id: string): boolean {
|
||||
const result = this.panels.delete(id);
|
||||
const result = this._panels.delete(id);
|
||||
if (result) {
|
||||
logger.debug(`Unregistered panel: ${id}`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取面板
|
||||
*/
|
||||
/** @zh 获取面板 @en Get panel */
|
||||
public getPanel(id: string): PanelDescriptor | undefined {
|
||||
return this.panels.get(id);
|
||||
return this._panels.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有面板
|
||||
* @zh 获取所有面板
|
||||
* @en Get all panels
|
||||
*/
|
||||
public getAllPanels(): PanelDescriptor[] {
|
||||
return Array.from(this.panels.values()).sort((a, b) => {
|
||||
return (a.order ?? 0) - (b.order ?? 0);
|
||||
});
|
||||
return this._sortByOrder(Array.from(this._panels.values()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定位置的面板
|
||||
* @zh 获取指定位置的面板
|
||||
* @en Get panels by position
|
||||
*/
|
||||
public getPanelsByPosition(position: string): PanelDescriptor[] {
|
||||
return this.getAllPanels()
|
||||
.filter((panel) => panel.position === position)
|
||||
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
||||
return this._sortByOrder(
|
||||
Array.from(this._panels.values()).filter((panel) => panel.position === position)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放资源
|
||||
*/
|
||||
// ========== 生命周期 | Lifecycle ==========
|
||||
|
||||
/** @zh 释放资源 @en Dispose resources */
|
||||
public dispose(): void {
|
||||
this.menus.clear();
|
||||
this.toolbarItems.clear();
|
||||
this.panels.clear();
|
||||
logger.info('UIRegistry disposed');
|
||||
this._menus.clear();
|
||||
this._toolbarItems.clear();
|
||||
this._panels.clear();
|
||||
logger.debug('Disposed');
|
||||
}
|
||||
}
|
||||
|
||||
/** @zh UI 注册表服务标识符 @en UI registry service identifier */
|
||||
export const IUIRegistry = createRegistryToken<UIRegistry>('UIRegistry');
|
||||
|
||||
@@ -411,6 +411,27 @@ export interface IUserCodeService {
|
||||
* 打开新项目时调用以重置就绪 Promise。
|
||||
*/
|
||||
resetReady(): void;
|
||||
|
||||
/**
|
||||
* Get the user code realm.
|
||||
* 获取用户代码隔离域。
|
||||
*
|
||||
* The realm provides isolated registration for user components, systems, and services.
|
||||
* 隔离域提供用户组件、系统和服务的隔离注册。
|
||||
*
|
||||
* @returns User code realm instance | 用户代码隔离域实例
|
||||
*/
|
||||
getUserCodeRealm(): import('@esengine/runtime-core').UserCodeRealm;
|
||||
|
||||
/**
|
||||
* Reset the user code realm for project switching.
|
||||
* 重置用户代码隔离域(用于项目切换)。
|
||||
*
|
||||
* This clears all user-registered components, systems, and services,
|
||||
* preparing for a new project's user code.
|
||||
* 这会清除所有用户注册的组件、系统和服务,为新项目的用户代码做准备。
|
||||
*/
|
||||
resetRealm(): void;
|
||||
}
|
||||
|
||||
import { EditorConfig } from '../../Config';
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
COMPONENT_TYPE_NAME,
|
||||
SYSTEM_TYPE_NAME
|
||||
} from '@esengine/ecs-framework';
|
||||
import { UserCodeRealm, type UserCodeRealmConfig } from '@esengine/runtime-core';
|
||||
import type {
|
||||
IUserCodeService,
|
||||
UserScriptInfo,
|
||||
@@ -89,10 +90,17 @@ export class UserCodeService implements IService, IUserCodeService {
|
||||
private _readyPromise: Promise<void>;
|
||||
private _readyResolve: (() => void) | undefined;
|
||||
|
||||
constructor(fileSystem: IFileSystem) {
|
||||
/**
|
||||
* 用户代码隔离域
|
||||
* User code realm for isolation
|
||||
*/
|
||||
private _userCodeRealm: UserCodeRealm;
|
||||
|
||||
constructor(fileSystem: IFileSystem, realmConfig?: UserCodeRealmConfig) {
|
||||
this._fileSystem = fileSystem;
|
||||
this._hotReloadCoordinator = new HotReloadCoordinator();
|
||||
this._readyPromise = this._createReadyPromise();
|
||||
this._userCodeRealm = new UserCodeRealm(realmConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -388,6 +396,15 @@ export class UserCodeService implements IService, IUserCodeService {
|
||||
if (this._isComponentClass(exported)) {
|
||||
logger.debug(`Found component: ${name} | 发现组件: ${name}`);
|
||||
|
||||
// Register to UserCodeRealm for isolation
|
||||
// 注册到 UserCodeRealm 实现隔离
|
||||
try {
|
||||
this._userCodeRealm.registerComponent(exported);
|
||||
logger.info(`Component ${name} registered to user code realm`);
|
||||
} catch (err) {
|
||||
logger.warn(`Failed to register component ${name} to realm:`, err);
|
||||
}
|
||||
|
||||
// Register with Core ComponentRegistry for serialization/deserialization
|
||||
// 注册到核心 ComponentRegistry 用于序列化/反序列化
|
||||
try {
|
||||
@@ -459,7 +476,7 @@ export class UserCodeService implements IService, IUserCodeService {
|
||||
|
||||
// Access scene through Core.scene
|
||||
// 通过 Core.scene 访问场景
|
||||
const sdkGlobal = (window as any)[EditorConfig.globals.sdk];
|
||||
const sdkGlobal = window.__ESENGINE_SDK__;
|
||||
const Core = sdkGlobal?.Core;
|
||||
const scene = Core?.scene;
|
||||
if (!scene || !scene.entities) {
|
||||
@@ -782,7 +799,7 @@ export class UserCodeService implements IService, IUserCodeService {
|
||||
|
||||
// Initialize hot reload coordinator with Core reference
|
||||
// 使用 Core 引用初始化热更新协调器
|
||||
const sdkGlobal = (window as any)[EditorConfig.globals.sdk];
|
||||
const sdkGlobal = window.__ESENGINE_SDK__;
|
||||
const Core = sdkGlobal?.Core;
|
||||
if (Core) {
|
||||
this._hotReloadCoordinator.initialize(Core);
|
||||
@@ -982,6 +999,32 @@ export class UserCodeService implements IService, IUserCodeService {
|
||||
this.stopWatch();
|
||||
this._runtimeModule = undefined;
|
||||
this._editorModule = undefined;
|
||||
this._userCodeRealm.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user code realm.
|
||||
* 获取用户代码隔离域。
|
||||
*
|
||||
* The realm provides isolated registration for user components, systems, and services.
|
||||
* 隔离域提供用户组件、系统和服务的隔离注册。
|
||||
*
|
||||
* @returns User code realm instance | 用户代码隔离域实例
|
||||
*/
|
||||
getUserCodeRealm(): UserCodeRealm {
|
||||
return this._userCodeRealm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the user code realm for project switching.
|
||||
* 重置用户代码隔离域(用于项目切换)。
|
||||
*
|
||||
* This clears all user-registered components, systems, and services,
|
||||
* preparing for a new project's user code.
|
||||
* 这会清除所有用户注册的组件、系统和服务,为新项目的用户代码做准备。
|
||||
*/
|
||||
resetRealm(): void {
|
||||
this._userCodeRealm.reset();
|
||||
}
|
||||
|
||||
// ==================== Private Methods | 私有方法 ====================
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
* 使用事件驱动架构实现高效的变化通知。
|
||||
*/
|
||||
|
||||
import type { Component, ComponentType, Entity } from '@esengine/ecs-framework';
|
||||
import { createLogger, type Component, type ComponentType, type Entity } from '@esengine/ecs-framework';
|
||||
|
||||
const logger = createLogger('VirtualNodeRegistry');
|
||||
|
||||
/**
|
||||
* Virtual node data
|
||||
@@ -154,7 +156,7 @@ export class VirtualNodeRegistry {
|
||||
try {
|
||||
return provider(component, entity);
|
||||
} catch (e) {
|
||||
console.warn(`[VirtualNodeRegistry] Error in provider for ${componentType.name}:`, e);
|
||||
logger.warn(`Error in provider for ${componentType.name}:`, e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -249,7 +251,7 @@ export class VirtualNodeRegistry {
|
||||
try {
|
||||
listener(event);
|
||||
} catch (e) {
|
||||
console.warn('[VirtualNodeRegistry] Error in change listener:', e);
|
||||
logger.warn('Error in change listener:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,177 +1,162 @@
|
||||
import { IService } from '@esengine/ecs-framework';
|
||||
import { ComponentType } from 'react';
|
||||
import { createLogger, type ILogger, type IService } from '@esengine/ecs-framework';
|
||||
import type { ComponentType } from 'react';
|
||||
import { createRegistryToken } from './BaseRegistry';
|
||||
|
||||
/**
|
||||
* 窗口描述符
|
||||
* @zh 窗口描述符
|
||||
* @en Window descriptor
|
||||
*/
|
||||
export interface WindowDescriptor {
|
||||
/**
|
||||
* 窗口唯一标识
|
||||
*/
|
||||
/** @zh 窗口唯一标识 @en Unique window ID */
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* 窗口组件
|
||||
*/
|
||||
component: ComponentType<any>;
|
||||
|
||||
/**
|
||||
* 窗口标题
|
||||
*/
|
||||
/** @zh 窗口组件 @en Window component */
|
||||
component: ComponentType<unknown>;
|
||||
/** @zh 窗口标题 @en Window title */
|
||||
title?: string;
|
||||
|
||||
/**
|
||||
* 默认宽度
|
||||
*/
|
||||
/** @zh 默认宽度 @en Default width */
|
||||
defaultWidth?: number;
|
||||
|
||||
/**
|
||||
* 默认高度
|
||||
*/
|
||||
/** @zh 默认高度 @en Default height */
|
||||
defaultHeight?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 窗口实例
|
||||
* @zh 窗口实例
|
||||
* @en Window instance
|
||||
*/
|
||||
export interface WindowInstance {
|
||||
/**
|
||||
* 窗口描述符
|
||||
*/
|
||||
/** @zh 窗口描述符 @en Window descriptor */
|
||||
descriptor: WindowDescriptor;
|
||||
|
||||
/**
|
||||
* 是否打开
|
||||
*/
|
||||
/** @zh 是否打开 @en Whether the window is open */
|
||||
isOpen: boolean;
|
||||
|
||||
/**
|
||||
* 窗口参数
|
||||
*/
|
||||
params?: Record<string, any>;
|
||||
/** @zh 窗口参数 @en Window parameters */
|
||||
params?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 窗口注册表服务
|
||||
*
|
||||
* 管理插件注册的窗口组件
|
||||
* @zh 窗口注册表服务 - 管理插件注册的窗口组件
|
||||
* @en Window Registry Service - Manages plugin-registered window components
|
||||
*/
|
||||
export class WindowRegistry implements IService {
|
||||
private windows: Map<string, WindowDescriptor> = new Map();
|
||||
private openWindows: Map<string, WindowInstance> = new Map();
|
||||
private listeners: Set<() => void> = new Set();
|
||||
private readonly _windows = new Map<string, WindowDescriptor>();
|
||||
private readonly _openWindows = new Map<string, WindowInstance>();
|
||||
private readonly _listeners = new Set<() => void>();
|
||||
private readonly _logger: ILogger;
|
||||
|
||||
constructor() {
|
||||
this._logger = createLogger('WindowRegistry');
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册窗口
|
||||
* @zh 注册窗口
|
||||
* @en Register a window
|
||||
*/
|
||||
registerWindow(descriptor: WindowDescriptor): void {
|
||||
if (this.windows.has(descriptor.id)) {
|
||||
console.warn(`Window ${descriptor.id} is already registered`);
|
||||
if (this._windows.has(descriptor.id)) {
|
||||
this._logger.warn(`Window already registered: ${descriptor.id}`);
|
||||
return;
|
||||
}
|
||||
this.windows.set(descriptor.id, descriptor);
|
||||
this._windows.set(descriptor.id, descriptor);
|
||||
this._logger.debug(`Registered window: ${descriptor.id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消注册窗口
|
||||
* @zh 取消注册窗口
|
||||
* @en Unregister a window
|
||||
*/
|
||||
unregisterWindow(windowId: string): void {
|
||||
this.windows.delete(windowId);
|
||||
this.openWindows.delete(windowId);
|
||||
this.notifyListeners();
|
||||
this._windows.delete(windowId);
|
||||
this._openWindows.delete(windowId);
|
||||
this._notifyListeners();
|
||||
this._logger.debug(`Unregistered window: ${windowId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取窗口描述符
|
||||
*/
|
||||
/** @zh 获取窗口描述符 @en Get window descriptor */
|
||||
getWindow(windowId: string): WindowDescriptor | undefined {
|
||||
return this.windows.get(windowId);
|
||||
return this._windows.get(windowId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有窗口描述符
|
||||
*/
|
||||
/** @zh 获取所有窗口描述符 @en Get all window descriptors */
|
||||
getAllWindows(): WindowDescriptor[] {
|
||||
return Array.from(this.windows.values());
|
||||
return Array.from(this._windows.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开窗口
|
||||
* @zh 打开窗口
|
||||
* @en Open a window
|
||||
*/
|
||||
openWindow(windowId: string, params?: Record<string, any>): void {
|
||||
const descriptor = this.windows.get(windowId);
|
||||
openWindow(windowId: string, params?: Record<string, unknown>): void {
|
||||
const descriptor = this._windows.get(windowId);
|
||||
if (!descriptor) {
|
||||
console.warn(`Window ${windowId} is not registered`);
|
||||
this._logger.warn(`Window not registered: ${windowId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.openWindows.set(windowId, {
|
||||
this._openWindows.set(windowId, {
|
||||
descriptor,
|
||||
isOpen: true,
|
||||
params
|
||||
});
|
||||
this.notifyListeners();
|
||||
this._notifyListeners();
|
||||
this._logger.debug(`Opened window: ${windowId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭窗口
|
||||
* @zh 关闭窗口
|
||||
* @en Close a window
|
||||
*/
|
||||
closeWindow(windowId: string): void {
|
||||
this.openWindows.delete(windowId);
|
||||
this.notifyListeners();
|
||||
this._openWindows.delete(windowId);
|
||||
this._notifyListeners();
|
||||
this._logger.debug(`Closed window: ${windowId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取打开的窗口实例
|
||||
*/
|
||||
/** @zh 获取打开的窗口实例 @en Get open window instance */
|
||||
getOpenWindow(windowId: string): WindowInstance | undefined {
|
||||
return this.openWindows.get(windowId);
|
||||
return this._openWindows.get(windowId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有打开的窗口
|
||||
*/
|
||||
/** @zh 获取所有打开的窗口 @en Get all open windows */
|
||||
getAllOpenWindows(): WindowInstance[] {
|
||||
return Array.from(this.openWindows.values());
|
||||
return Array.from(this._openWindows.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查窗口是否打开
|
||||
*/
|
||||
/** @zh 检查窗口是否打开 @en Check if window is open */
|
||||
isWindowOpen(windowId: string): boolean {
|
||||
return this.openWindows.has(windowId);
|
||||
return this._openWindows.has(windowId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加变化监听器
|
||||
* @zh 添加变化监听器
|
||||
* @en Add change listener
|
||||
* @returns @zh 取消订阅函数 @en Unsubscribe function
|
||||
*/
|
||||
addListener(listener: () => void): () => void {
|
||||
this.listeners.add(listener);
|
||||
this._listeners.add(listener);
|
||||
return () => {
|
||||
this.listeners.delete(listener);
|
||||
this._listeners.delete(listener);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知所有监听器
|
||||
*/
|
||||
private notifyListeners(): void {
|
||||
this.listeners.forEach((listener) => listener());
|
||||
/** @zh 通知所有监听器 @en Notify all listeners */
|
||||
private _notifyListeners(): void {
|
||||
for (const listener of this._listeners) {
|
||||
listener();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有窗口
|
||||
*/
|
||||
/** @zh 清空所有窗口 @en Clear all windows */
|
||||
clear(): void {
|
||||
this.windows.clear();
|
||||
this.openWindows.clear();
|
||||
this.listeners.clear();
|
||||
this._windows.clear();
|
||||
this._openWindows.clear();
|
||||
this._listeners.clear();
|
||||
this._logger.debug('Cleared');
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放资源
|
||||
*/
|
||||
/** @zh 释放资源 @en Dispose resources */
|
||||
dispose(): void {
|
||||
this.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/** @zh 窗口注册表服务标识符 @en Window registry service identifier */
|
||||
export const IWindowRegistry = createRegistryToken<WindowRegistry>('WindowRegistry');
|
||||
|
||||
49
packages/editor-core/src/global.d.ts
vendored
Normal file
49
packages/editor-core/src/global.d.ts
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* @zh 全局类型声明
|
||||
* @en Global type declarations
|
||||
*
|
||||
* @zh 扩展 Window 接口以支持编辑器运行时全局变量
|
||||
* @en Extend Window interface to support editor runtime global variables
|
||||
*/
|
||||
|
||||
/**
|
||||
* @zh SDK 全局对象结构
|
||||
* @en SDK global object structure
|
||||
*/
|
||||
interface ESEngineSDK {
|
||||
Core: typeof import('@esengine/ecs-framework').Core;
|
||||
Scene: typeof import('@esengine/ecs-framework').Scene;
|
||||
Entity: typeof import('@esengine/ecs-framework').Entity;
|
||||
Component: typeof import('@esengine/ecs-framework').Component;
|
||||
System: typeof import('@esengine/ecs-framework').System;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 插件容器结构
|
||||
* @en Plugin container structure
|
||||
*/
|
||||
interface PluginContainer {
|
||||
[pluginName: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 用户代码导出结构
|
||||
* @en User code exports structure
|
||||
*/
|
||||
interface UserExports {
|
||||
[name: string]: unknown;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
// ESEngine 全局变量(与 EditorConfig.globals 对应)
|
||||
// ESEngine globals (matching EditorConfig.globals)
|
||||
__ESENGINE_SDK__: ESEngineSDK | undefined;
|
||||
__ESENGINE_PLUGINS__: PluginContainer | undefined;
|
||||
__USER_RUNTIME_EXPORTS__: UserExports | undefined;
|
||||
__USER_EDITOR_EXPORTS__: UserExports | undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
@@ -1,75 +1,110 @@
|
||||
/**
|
||||
* ECS Framework Editor Core
|
||||
* @esengine/editor-core
|
||||
*
|
||||
* Plugin-based editor framework for ECS Framework
|
||||
* 基于插件的 ECS 编辑器框架
|
||||
*/
|
||||
|
||||
// Service Tokens | 服务令牌
|
||||
// ============================================================================
|
||||
// Service Tokens | 服务令牌 (推荐导入)
|
||||
// ============================================================================
|
||||
export * from './tokens';
|
||||
|
||||
// 配置 | Configuration
|
||||
// ============================================================================
|
||||
// Plugin System | 插件系统
|
||||
// ============================================================================
|
||||
export * from './Config';
|
||||
|
||||
// 新插件系统 | New plugin system
|
||||
export * from './Plugin';
|
||||
|
||||
export * from './Services/UIRegistry';
|
||||
// ============================================================================
|
||||
// Registry Base | 注册表基类
|
||||
// ============================================================================
|
||||
export * from './Services/BaseRegistry';
|
||||
|
||||
// ============================================================================
|
||||
// Core Services | 核心服务
|
||||
// ============================================================================
|
||||
export * from './Services/MessageHub';
|
||||
export * from './Services/SerializerRegistry';
|
||||
export * from './Services/LocaleService';
|
||||
export * from './Services/LogService';
|
||||
export * from './Services/CommandManager';
|
||||
export * from './Services/SettingsRegistry';
|
||||
|
||||
// ============================================================================
|
||||
// Entity & Component Services | 实体与组件服务
|
||||
// ============================================================================
|
||||
export * from './Services/EntityStoreService';
|
||||
export * from './Services/ComponentRegistry';
|
||||
export * from './Services/LocaleService';
|
||||
export * from './Services/PropertyMetadata';
|
||||
export * from './Services/ProjectService';
|
||||
export * from './Services/ComponentDiscoveryService';
|
||||
export * from './Services/LogService';
|
||||
export * from './Services/SettingsRegistry';
|
||||
export * from './Services/SerializerRegistry';
|
||||
export * from './Services/PropertyMetadata';
|
||||
|
||||
// ============================================================================
|
||||
// Scene & Project Services | 场景与项目服务
|
||||
// ============================================================================
|
||||
export * from './Services/ProjectService';
|
||||
export * from './Services/SceneManagerService';
|
||||
export * from './Services/SceneTemplateRegistry';
|
||||
export * from './Services/FileActionRegistry';
|
||||
export * from './Services/EntityCreationRegistry';
|
||||
export * from './Services/CompilerRegistry';
|
||||
export * from './Services/ICompiler';
|
||||
export * from './Services/ICommand';
|
||||
export * from './Services/BaseCommand';
|
||||
export * from './Services/CommandManager';
|
||||
export * from './Services/IEditorDataStore';
|
||||
export * from './Services/IFileSystem';
|
||||
export * from './Services/IDialog';
|
||||
export * from './Services/INotification';
|
||||
export * from './Services/IInspectorProvider';
|
||||
export * from './Services/PrefabService';
|
||||
|
||||
// ============================================================================
|
||||
// UI & Inspector Services | UI 与检视器服务
|
||||
// ============================================================================
|
||||
export * from './Services/UIRegistry';
|
||||
export * from './Services/InspectorRegistry';
|
||||
export * from './Services/IPropertyRenderer';
|
||||
export * from './Services/PropertyRendererRegistry';
|
||||
export * from './Services/IFieldEditor';
|
||||
export * from './Services/FieldEditorRegistry';
|
||||
export * from './Services/ComponentInspectorRegistry';
|
||||
export * from './Services/ComponentActionRegistry';
|
||||
export * from './Services/WindowRegistry';
|
||||
|
||||
// ============================================================================
|
||||
// Asset & File Services | 资产与文件服务
|
||||
// ============================================================================
|
||||
export * from './Services/AssetRegistryService';
|
||||
export * from './Services/FileActionRegistry';
|
||||
export * from './Services/VirtualNodeRegistry';
|
||||
|
||||
// ============================================================================
|
||||
// Viewport & Gizmo Services | 视口与 Gizmo 服务
|
||||
// ============================================================================
|
||||
export * from './Services/IViewportService';
|
||||
export * from './Services/PreviewSceneService';
|
||||
export * from './Services/EditorViewportService';
|
||||
export * from './Services/PrefabService';
|
||||
export * from './Services/VirtualNodeRegistry';
|
||||
export * from './Services/GizmoInteractionService';
|
||||
|
||||
// Build System | 构建系统
|
||||
export * from './Services/Build';
|
||||
|
||||
// User Code System | 用户代码系统
|
||||
export * from './Services/UserCode';
|
||||
|
||||
// Module System | 模块系统
|
||||
export * from './Services/Module';
|
||||
|
||||
export * from './Gizmos';
|
||||
export * from './Rendering';
|
||||
|
||||
// ============================================================================
|
||||
// Build & Compile System | 构建与编译系统
|
||||
// ============================================================================
|
||||
export * from './Services/Build';
|
||||
export * from './Services/UserCode';
|
||||
export * from './Services/CompilerRegistry';
|
||||
|
||||
// ============================================================================
|
||||
// Module System | 模块系统
|
||||
// ============================================================================
|
||||
export * from './Services/Module';
|
||||
export * from './Module/IEventBus';
|
||||
export * from './Module/ICommandRegistry';
|
||||
export * from './Module/IPanelRegistry';
|
||||
export * from './Module/IModuleContext';
|
||||
export * from './Module/IEditorModule';
|
||||
|
||||
// ============================================================================
|
||||
// Interfaces | 接口定义
|
||||
// ============================================================================
|
||||
export * from './Services/ICompiler';
|
||||
export * from './Services/ICommand';
|
||||
export * from './Services/BaseCommand';
|
||||
export * from './Services/IEditorDataStore';
|
||||
export * from './Services/IFileSystem';
|
||||
export * from './Services/IDialog';
|
||||
export * from './Services/INotification';
|
||||
export * from './Services/IInspectorProvider';
|
||||
export * from './Services/IPropertyRenderer';
|
||||
export * from './Services/IFieldEditor';
|
||||
export * from './Services/ComponentActionRegistry';
|
||||
export * from './Services/EntityCreationRegistry';
|
||||
export * from './Types/IFileAPI';
|
||||
export * from './Types/UITypes';
|
||||
|
||||
@@ -153,18 +153,26 @@ export type { MessageHandler, RequestHandler } from './Services/MessageHub';
|
||||
export type { EntityTreeNode } from './Services/EntityStoreService';
|
||||
|
||||
// ============================================================================
|
||||
// PrefabService Token
|
||||
// 预制体服务令牌
|
||||
// EditorPrefabService Token
|
||||
// 编辑器预制体服务令牌
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* PrefabService 接口
|
||||
* PrefabService interface
|
||||
* EditorPrefabService 接口
|
||||
* EditorPrefabService interface
|
||||
*
|
||||
* 提供类型安全的预制体服务访问接口。
|
||||
* Provides type-safe prefab service access interface.
|
||||
* 编辑器侧的预制体实例管理服务。
|
||||
* Editor-side prefab instance management service.
|
||||
*
|
||||
* 注意:这与 asset-system 的 IPrefabService 不同!
|
||||
* - asset-system.IPrefabService: 运行时预制体资源加载/实例化
|
||||
* - editor-core.IEditorPrefabService: 编辑器预制体实例状态管理
|
||||
*
|
||||
* Note: This is different from asset-system's IPrefabService!
|
||||
* - asset-system.IPrefabService: Runtime prefab asset loading/instantiation
|
||||
* - editor-core.IEditorPrefabService: Editor prefab instance state management
|
||||
*/
|
||||
export interface IPrefabService {
|
||||
export interface IEditorPrefabService {
|
||||
/** 设置文件 API | Set file API */
|
||||
setFileAPI(fileAPI: IPrefabFileAPI): void;
|
||||
/** 检查是否为预制体实例 | Check if prefab instance */
|
||||
@@ -192,13 +200,13 @@ export interface IPrefabService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 预制体服务令牌
|
||||
* Prefab service token
|
||||
* 编辑器预制体服务令牌
|
||||
* Editor prefab service token
|
||||
*
|
||||
* 用于注册和获取预制体服务。
|
||||
* For registering and getting prefab service.
|
||||
* 用于注册和获取编辑器预制体服务。
|
||||
* For registering and getting editor prefab service.
|
||||
*/
|
||||
export const PrefabServiceToken = createServiceToken<IPrefabService>('prefabService');
|
||||
export const EditorPrefabServiceToken = createServiceToken<IEditorPrefabService>('editorPrefabService');
|
||||
|
||||
// Re-export types for convenience
|
||||
// 重新导出类型方便使用
|
||||
|
||||
Reference in New Issue
Block a user