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:
YHH
2025-12-24 20:57:08 +08:00
committed by GitHub
parent 58f70a5783
commit dbc6793dc4
133 changed files with 6880 additions and 9141 deletions

View File

@@ -0,0 +1,314 @@
/**
* @zh 项目操作 Hook
* @en Project Actions Hook
*
* @zh 封装项目相关的操作(打开、创建、关闭项目)
* @en Encapsulates project-related operations (open, create, close project)
*/
import { useCallback } from 'react';
import { Core } from '@esengine/ecs-framework';
import {
ProjectService,
PluginManager,
SceneManagerService,
UserCodeService
} from '@esengine/editor-core';
import { useEditorStore, useDialogStore } from '../stores';
import { TauriAPI } from '../api/tauri';
import { SettingsService } from '../services/SettingsService';
import { EngineService } from '../services/EngineService';
import { PluginLoader } from '../services/PluginLoader';
import { useLocale } from './useLocale';
interface UseProjectActionsParams {
pluginLoader: PluginLoader;
pluginManagerRef: React.RefObject<PluginManager | null>;
projectServiceRef: React.MutableRefObject<ProjectService | null>;
showToast: (message: string, type: 'success' | 'error' | 'warning' | 'info') => void;
}
export function useProjectActions({
pluginLoader,
pluginManagerRef,
projectServiceRef,
showToast,
}: UseProjectActionsParams) {
const { t } = useLocale();
const {
setProjectLoaded,
setCurrentProjectPath,
setAvailableScenes,
setIsLoading,
setStatus,
setShowProjectWizard,
} = useEditorStore();
const { setErrorDialog, setConfirmDialog } = useDialogStore();
/**
* @zh 打开最近项目
* @en Open recent project
*/
const handleOpenRecentProject = useCallback(async (projectPath: string) => {
try {
setIsLoading(true, t('loading.step1'));
const projectService = Core.services.resolve(ProjectService);
if (!projectService) {
console.error('Required services not available');
setIsLoading(false);
return;
}
projectServiceRef.current = projectService;
await projectService.openProject(projectPath);
await TauriAPI.setProjectBasePath(projectPath);
try {
await TauriAPI.updateProjectTsconfig(projectPath);
} catch (e) {
console.warn('[useProjectActions] Failed to update project tsconfig:', e);
}
const settings = SettingsService.getInstance();
settings.addRecentProject(projectPath);
setCurrentProjectPath(projectPath);
try {
const sceneFiles = await TauriAPI.scanDirectory(`${projectPath}/scenes`, '*.ecs');
const sceneNames = sceneFiles.map(f => `scenes/${f.split(/[\\/]/).pop()}`);
setAvailableScenes(sceneNames);
} catch (e) {
console.warn('[useProjectActions] Failed to scan scenes:', e);
}
setProjectLoaded(true);
setIsLoading(true, t('loading.step2'));
const engineService = EngineService.getInstance();
const engineReady = await engineService.waitForInitialization(30000);
if (!engineReady) {
throw new Error(t('loading.engineTimeoutError'));
}
if (pluginManagerRef.current) {
const pluginSettings = projectService.getPluginSettings();
if (pluginSettings && pluginSettings.enabledPlugins.length > 0) {
await pluginManagerRef.current.loadConfig({ enabledPlugins: pluginSettings.enabledPlugins });
}
}
await engineService.initializeModuleSystems();
const uiResolution = projectService.getUIDesignResolution();
engineService.setUICanvasSize(uiResolution.width, uiResolution.height);
setStatus(t('header.status.projectOpened'));
setIsLoading(true, t('loading.step3'));
const userCodeService = Core.services.tryResolve(UserCodeService);
if (userCodeService) {
await userCodeService.waitForReady();
}
const sceneManagerService = Core.services.resolve(SceneManagerService);
if (sceneManagerService) {
await sceneManagerService.newScene();
}
if (pluginManagerRef.current) {
setIsLoading(true, t('loading.loadingPlugins'));
await pluginLoader.loadProjectPlugins(projectPath, pluginManagerRef.current);
}
setIsLoading(false);
} catch (error) {
console.error('Failed to open project:', error);
setStatus(t('header.status.failed'));
setIsLoading(false);
const errorMessage = error instanceof Error ? error.message : String(error);
setErrorDialog({
title: t('project.openFailed'),
message: `${t('project.openFailed')}:\n${errorMessage}`
});
}
}, [t, pluginLoader, pluginManagerRef, projectServiceRef, setProjectLoaded, setCurrentProjectPath, setAvailableScenes, setIsLoading, setStatus, setErrorDialog]);
/**
* @zh 打开项目对话框
* @en Open project dialog
*/
const handleOpenProject = useCallback(async () => {
try {
const projectPath = await TauriAPI.openProjectDialog();
if (!projectPath) return;
await handleOpenRecentProject(projectPath);
} catch (error) {
console.error('Failed to open project dialog:', error);
}
}, [handleOpenRecentProject]);
/**
* @zh 显示创建项目向导
* @en Show create project wizard
*/
const handleCreateProject = useCallback(() => {
setShowProjectWizard(true);
}, [setShowProjectWizard]);
/**
* @zh 从向导创建项目
* @en Create project from wizard
*/
const handleCreateProjectFromWizard = useCallback(async (
projectName: string,
projectPath: string,
_templateId: string
) => {
const sep = projectPath.includes('/') ? '/' : '\\';
const fullProjectPath = `${projectPath}${sep}${projectName}`;
try {
setIsLoading(true, t('project.creating'));
const projectService = Core.services.resolve(ProjectService);
if (!projectService) {
console.error('ProjectService not available');
setIsLoading(false);
setErrorDialog({
title: t('project.createFailed'),
message: t('project.serviceUnavailable')
});
return;
}
await projectService.createProject(fullProjectPath);
setIsLoading(true, t('project.createdOpening'));
await handleOpenRecentProject(fullProjectPath);
} catch (error) {
console.error('Failed to create project:', error);
setIsLoading(false);
const errorMessage = error instanceof Error ? error.message : String(error);
if (errorMessage.includes('already exists')) {
setConfirmDialog({
title: t('project.alreadyExists'),
message: t('project.existsQuestion'),
confirmText: t('project.open'),
cancelText: t('common.cancel'),
onConfirm: () => {
setConfirmDialog(null);
setIsLoading(true, t('project.opening'));
handleOpenRecentProject(fullProjectPath).catch((err) => {
console.error('Failed to open project:', err);
setIsLoading(false);
setErrorDialog({
title: t('project.openFailed'),
message: `${t('project.openFailed')}:\n${err instanceof Error ? err.message : String(err)}`
});
});
}
});
} else {
setStatus(t('project.createFailed'));
setErrorDialog({
title: t('project.createFailed'),
message: `${t('project.createFailed')}:\n${errorMessage}`
});
}
}
}, [t, handleOpenRecentProject, setIsLoading, setStatus, setErrorDialog, setConfirmDialog]);
/**
* @zh 浏览项目路径
* @en Browse project path
*/
const handleBrowseProjectPath = useCallback(async (): Promise<string | null> => {
try {
const path = await TauriAPI.openProjectDialog();
return path || null;
} catch (error) {
console.error('Failed to browse path:', error);
return null;
}
}, []);
/**
* @zh 关闭项目
* @en Close project
*/
const handleCloseProject = useCallback(async () => {
if (pluginManagerRef.current) {
await pluginLoader.unloadProjectPlugins(pluginManagerRef.current);
}
const scene = Core.scene;
if (scene) {
scene.end();
}
const engineService = EngineService.getInstance();
engineService.clearModuleSystems();
const projectService = Core.services.tryResolve(ProjectService);
if (projectService) {
await projectService.closeProject();
}
setProjectLoaded(false);
setCurrentProjectPath(null);
setStatus(t('header.status.ready'));
}, [t, pluginLoader, pluginManagerRef, setProjectLoaded, setCurrentProjectPath, setStatus]);
/**
* @zh 删除项目
* @en Delete project
*/
const handleDeleteProject = useCallback(async (projectPath: string) => {
console.log('[useProjectActions] handleDeleteProject called with path:', projectPath);
try {
console.log('[useProjectActions] Calling TauriAPI.deleteFolder...');
await TauriAPI.deleteFolder(projectPath);
console.log('[useProjectActions] deleteFolder succeeded');
const settings = SettingsService.getInstance();
settings.removeRecentProject(projectPath);
setStatus(t('header.status.ready'));
} catch (error) {
console.error('[useProjectActions] Failed to delete project:', error);
setErrorDialog({
title: t('project.deleteFailed'),
message: `${t('project.deleteFailed')}:\n${error instanceof Error ? error.message : String(error)}`
});
}
}, [t, setStatus, setErrorDialog]);
/**
* @zh 从最近项目列表移除
* @en Remove from recent projects
*/
const handleRemoveRecentProject = useCallback((projectPath: string) => {
const settings = SettingsService.getInstance();
settings.removeRecentProject(projectPath);
setStatus(t('header.status.ready'));
}, [t, setStatus]);
return {
handleOpenProject,
handleOpenRecentProject,
handleCreateProject,
handleCreateProjectFromWizard,
handleBrowseProjectPath,
handleCloseProject,
handleDeleteProject,
handleRemoveRecentProject,
};
}

View File

@@ -0,0 +1,187 @@
/**
* @zh 场景操作 Hook
* @en Scene Actions Hook
*
* @zh 封装场景相关的操作(新建、打开、保存场景)
* @en Encapsulates scene-related operations (new, open, save scene)
*/
import { useCallback } from 'react';
import { Core } from '@esengine/ecs-framework';
import { SceneManagerService, UserCodeService } from '@esengine/editor-core';
import { useEditorStore, useDialogStore } from '../stores';
import { useLocale } from './useLocale';
interface UseSceneActionsParams {
sceneManagerRef: React.RefObject<SceneManagerService | null>;
showToast: (message: string, type: 'success' | 'error' | 'warning' | 'info') => void;
}
export function useSceneActions({
sceneManagerRef,
showToast,
}: UseSceneActionsParams) {
const { t } = useLocale();
const { setStatus } = useEditorStore();
const { setErrorDialog } = useDialogStore();
/**
* @zh 新建场景
* @en Create new scene
*/
const handleNewScene = useCallback(async () => {
const sceneManager = sceneManagerRef.current;
if (!sceneManager) {
console.error('SceneManagerService not available');
return;
}
try {
await sceneManager.newScene();
setStatus(t('scene.newCreated'));
} catch (error) {
console.error('Failed to create new scene:', error);
setStatus(t('scene.createFailed'));
}
}, [t, sceneManagerRef, setStatus]);
/**
* @zh 打开场景(通过对话框选择)
* @en Open scene (via dialog)
*/
const handleOpenScene = useCallback(async () => {
const sceneManager = sceneManagerRef.current;
if (!sceneManager) {
console.error('SceneManagerService not available');
return;
}
try {
const userCodeService = Core.services.tryResolve(UserCodeService);
if (userCodeService) {
await userCodeService.waitForReady();
}
await sceneManager.openScene();
const sceneState = sceneManager.getSceneState();
setStatus(t('scene.openedSuccess', { name: sceneState.sceneName }));
} catch (error) {
console.error('Failed to open scene:', error);
setStatus(t('scene.openFailed'));
}
}, [t, sceneManagerRef, setStatus]);
/**
* @zh 通过路径打开场景
* @en Open scene by path
*/
const handleOpenSceneByPath = useCallback(async (scenePath: string) => {
console.log('[useSceneActions] handleOpenSceneByPath called:', scenePath);
const sceneManager = sceneManagerRef.current;
if (!sceneManager) {
console.error('SceneManagerService not available');
return;
}
try {
const userCodeService = Core.services.tryResolve(UserCodeService);
if (userCodeService) {
console.log('[useSceneActions] Waiting for user code service...');
await userCodeService.waitForReady();
console.log('[useSceneActions] User code service ready');
}
console.log('[useSceneActions] Calling sceneManager.openScene...');
await sceneManager.openScene(scenePath);
console.log('[useSceneActions] Scene opened successfully');
const sceneState = sceneManager.getSceneState();
setStatus(t('scene.openedSuccess', { name: sceneState.sceneName }));
} catch (error) {
console.error('Failed to open scene:', error);
setStatus(t('scene.openFailed'));
setErrorDialog({
title: t('scene.openFailed'),
message: `${t('scene.openFailed')}:\n${error instanceof Error ? error.message : String(error)}`
});
}
}, [t, sceneManagerRef, setStatus, setErrorDialog]);
/**
* @zh 保存场景
* @en Save scene
*/
const handleSaveScene = useCallback(async () => {
const sceneManager = sceneManagerRef.current;
if (!sceneManager) {
console.error('SceneManagerService not available');
return;
}
try {
await sceneManager.saveScene();
const sceneState = sceneManager.getSceneState();
setStatus(t('scene.savedSuccess', { name: sceneState.sceneName }));
} catch (error) {
console.error('Failed to save scene:', error);
setStatus(t('scene.saveFailed'));
}
}, [t, sceneManagerRef, setStatus]);
/**
* @zh 另存为场景
* @en Save scene as
*/
const handleSaveSceneAs = useCallback(async () => {
const sceneManager = sceneManagerRef.current;
if (!sceneManager) {
console.error('SceneManagerService not available');
return;
}
try {
await sceneManager.saveSceneAs();
const sceneState = sceneManager.getSceneState();
setStatus(t('scene.savedSuccess', { name: sceneState.sceneName }));
} catch (error) {
console.error('Failed to save scene as:', error);
setStatus(t('scene.saveAsFailed'));
}
}, [t, sceneManagerRef, setStatus]);
/**
* @zh 保存预制体或场景(用于快捷键)
* @en Save prefab or scene (for shortcut)
*/
const handleSave = useCallback(async () => {
const sceneManager = sceneManagerRef.current;
if (!sceneManager) return;
try {
if (sceneManager.isPrefabEditMode()) {
await sceneManager.savePrefab();
const prefabState = sceneManager.getPrefabEditModeState();
showToast(t('editMode.prefab.savedSuccess', { name: prefabState?.prefabName ?? 'Prefab' }), 'success');
} else {
await sceneManager.saveScene();
const sceneState = sceneManager.getSceneState();
showToast(t('scene.savedSuccess', { name: sceneState.sceneName }), 'success');
}
} catch (error) {
console.error('Failed to save:', error);
if (sceneManager.isPrefabEditMode()) {
showToast(t('editMode.prefab.saveFailed'), 'error');
} else {
showToast(t('scene.saveFailed'), 'error');
}
}
}, [t, sceneManagerRef, showToast]);
return {
handleNewScene,
handleOpenScene,
handleOpenSceneByPath,
handleSaveScene,
handleSaveSceneAs,
handleSave,
};
}