Feature/tilemap editor (#237)
* feat: 添加 Tilemap 编辑器插件和组件生命周期支持 * feat(editor-core): 添加声明式插件注册 API * feat(editor-core): 改进tiledmap结构合并tileset进tiledmapeditor * feat: 添加 editor-runtime SDK 和插件系统改进 * fix(ci): 修复SceneResourceManager里变量未使用问题
This commit is contained in:
@@ -31,3 +31,7 @@ export class CompilerRegistry implements IService {
|
||||
this.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Service identifier for DI registration (用于跨包插件访问)
|
||||
// 使用 Symbol.for 确保跨包共享同一个 Symbol
|
||||
export const ICompilerRegistry = Symbol.for('ICompilerRegistry');
|
||||
|
||||
95
packages/editor-core/src/Services/ComponentActionRegistry.ts
Normal file
95
packages/editor-core/src/Services/ComponentActionRegistry.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Component Action Registry Service
|
||||
*
|
||||
* Manages component-specific actions for the inspector panel
|
||||
*/
|
||||
|
||||
import { injectable } from 'tsyringe';
|
||||
import type { IService, Component, Entity } from '@esengine/ecs-framework';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export interface ComponentAction {
|
||||
id: string;
|
||||
componentName: string;
|
||||
label: string;
|
||||
icon?: ReactNode;
|
||||
order?: number;
|
||||
execute: (component: Component, entity: Entity) => void | Promise<void>;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class ComponentActionRegistry implements IService {
|
||||
private actions: Map<string, ComponentAction[]> = new Map();
|
||||
|
||||
/**
|
||||
* Register a component action
|
||||
*/
|
||||
register(action: ComponentAction): void {
|
||||
const componentName = action.componentName;
|
||||
if (!this.actions.has(componentName)) {
|
||||
this.actions.set(componentName, []);
|
||||
}
|
||||
|
||||
const actions = this.actions.get(componentName)!;
|
||||
const existingIndex = actions.findIndex(a => a.id === action.id);
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
console.warn(`[ComponentActionRegistry] Action '${action.id}' already exists for '${componentName}', overwriting`);
|
||||
actions[existingIndex] = action;
|
||||
} else {
|
||||
actions.push(action);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register multiple actions
|
||||
*/
|
||||
registerMany(actions: ComponentAction[]): void {
|
||||
for (const action of actions) {
|
||||
this.register(action);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister an action by ID
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all actions for a component type sorted by order
|
||||
*/
|
||||
getActionsForComponent(componentName: string): ComponentAction[] {
|
||||
const actions = this.actions.get(componentName) || [];
|
||||
return [...actions].sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a component has any actions
|
||||
*/
|
||||
hasActions(componentName: string): boolean {
|
||||
const actions = this.actions.get(componentName);
|
||||
return actions !== undefined && actions.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all actions
|
||||
*/
|
||||
clear(): void {
|
||||
this.actions.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose resources
|
||||
*/
|
||||
dispose(): void {
|
||||
this.actions.clear();
|
||||
}
|
||||
}
|
||||
76
packages/editor-core/src/Services/EntityCreationRegistry.ts
Normal file
76
packages/editor-core/src/Services/EntityCreationRegistry.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Entity Creation Registry Service
|
||||
*
|
||||
* Manages entity creation templates for the scene hierarchy context menu
|
||||
*/
|
||||
|
||||
import { injectable } from 'tsyringe';
|
||||
import type { IService } from '@esengine/ecs-framework';
|
||||
import type { EntityCreationTemplate } from '../Types/UITypes';
|
||||
|
||||
@injectable()
|
||||
export class EntityCreationRegistry implements IService {
|
||||
private templates: Map<string, EntityCreationTemplate> = new Map();
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register multiple templates
|
||||
*/
|
||||
registerMany(templates: EntityCreationTemplate[]): void {
|
||||
for (const template of templates) {
|
||||
this.register(template);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a template by ID
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,13 @@ export interface SaveDialogOptions extends DialogOptions {
|
||||
}
|
||||
|
||||
export interface IDialog {
|
||||
dispose(): void;
|
||||
openDialog(options: OpenDialogOptions): Promise<string | string[] | null>;
|
||||
saveDialog(options: SaveDialogOptions): Promise<string | null>;
|
||||
showMessage(title: string, message: string, type?: 'info' | 'warning' | 'error'): Promise<void>;
|
||||
showConfirm(title: string, message: string): Promise<boolean>;
|
||||
}
|
||||
|
||||
// Service identifier for DI registration
|
||||
// 使用 Symbol.for 确保跨包共享同一个 Symbol
|
||||
export const IDialogService = Symbol.for('IDialogService');
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export interface IFileSystem {
|
||||
dispose(): void;
|
||||
readFile(path: string): Promise<string>;
|
||||
writeFile(path: string, content: string): Promise<void>;
|
||||
writeBinary(path: string, data: Uint8Array): Promise<void>;
|
||||
@@ -8,6 +9,12 @@ export interface IFileSystem {
|
||||
deleteFile(path: string): Promise<void>;
|
||||
deleteDirectory(path: string): Promise<void>;
|
||||
scanFiles(basePath: string, pattern: string): Promise<string[]>;
|
||||
/**
|
||||
* Convert a local file path to an asset URL that can be used in browser contexts (img src, audio src, etc.)
|
||||
* @param filePath The local file path
|
||||
* @returns The converted asset URL
|
||||
*/
|
||||
convertToAssetUrl(filePath: string): string;
|
||||
}
|
||||
|
||||
export interface FileEntry {
|
||||
@@ -17,3 +24,7 @@ export interface FileEntry {
|
||||
size?: number;
|
||||
modified?: Date;
|
||||
}
|
||||
|
||||
// Service identifier for DI registration
|
||||
// 使用 Symbol.for 确保跨包共享同一个 Symbol
|
||||
export const IFileSystemService = Symbol.for('IFileSystemService');
|
||||
|
||||
@@ -75,3 +75,7 @@ export class InspectorRegistry implements IService {
|
||||
this.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Service identifier for DI registration (用于跨包插件访问)
|
||||
// 使用 Symbol.for 确保跨包共享同一个 Symbol
|
||||
export const IInspectorRegistry = Symbol.for('IInspectorRegistry');
|
||||
|
||||
@@ -215,3 +215,7 @@ export class MessageHub implements IService {
|
||||
logger.info('MessageHub disposed');
|
||||
}
|
||||
}
|
||||
|
||||
// Service identifier for DI registration (用于跨包插件访问)
|
||||
// 使用 Symbol.for 确保跨包共享同一个 Symbol
|
||||
export const IMessageHub = Symbol.for('IMessageHub');
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { IService } from '@esengine/ecs-framework';
|
||||
import { Injectable, Core, createLogger, SceneSerializer, Scene } from '@esengine/ecs-framework';
|
||||
import type { SceneResourceManager } from '@esengine/asset-system';
|
||||
import type { MessageHub } from './MessageHub';
|
||||
import type { IFileAPI } from '../Types/IFileAPI';
|
||||
import type { ProjectService } from './ProjectService';
|
||||
@@ -24,6 +25,7 @@ export class SceneManagerService implements IService {
|
||||
};
|
||||
|
||||
private unsubscribeHandlers: Array<() => void> = [];
|
||||
private sceneResourceManager: SceneResourceManager | null = null;
|
||||
|
||||
constructor(
|
||||
private messageHub: MessageHub,
|
||||
@@ -35,6 +37,14 @@ export class SceneManagerService implements IService {
|
||||
logger.info('SceneManagerService initialized');
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置场景资源管理器
|
||||
* Set scene resource manager
|
||||
*/
|
||||
public setSceneResourceManager(manager: SceneResourceManager | null): void {
|
||||
this.sceneResourceManager = manager;
|
||||
}
|
||||
|
||||
public async newScene(): Promise<void> {
|
||||
if (!await this.canClose()) {
|
||||
return;
|
||||
@@ -91,6 +101,13 @@ export class SceneManagerService implements IService {
|
||||
strategy: 'replace'
|
||||
});
|
||||
|
||||
// 加载场景资源 / Load scene resources
|
||||
if (this.sceneResourceManager) {
|
||||
await this.sceneResourceManager.loadSceneResources(scene);
|
||||
} else {
|
||||
logger.warn('[SceneManagerService] SceneResourceManager not available, skipping resource loading');
|
||||
}
|
||||
|
||||
const fileName = path.split(/[/\\]/).pop() || 'Untitled';
|
||||
const sceneName = fileName.replace('.ecs', '');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user