2025-11-25 22:23:19 +08:00
|
|
|
|
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
|
|
|
|
|
import * as ReactDOM from 'react-dom';
|
|
|
|
|
|
import * as ReactJSXRuntime from 'react/jsx-runtime';
|
2025-12-08 21:26:35 +08:00
|
|
|
|
import { Core, createLogger, Scene } from '@esengine/ecs-framework';
|
|
|
|
|
|
import * as ECSFramework from '@esengine/ecs-framework';
|
2025-12-09 11:07:44 +08:00
|
|
|
|
import { getProfilerService } from './services/getService';
|
2025-11-25 22:23:19 +08:00
|
|
|
|
|
|
|
|
|
|
// 将 React 暴露到全局,供动态加载的插件使用
|
|
|
|
|
|
// editor-runtime.js 将 React 设为 external,需要从全局获取
|
|
|
|
|
|
(window as any).React = React;
|
|
|
|
|
|
(window as any).ReactDOM = ReactDOM;
|
|
|
|
|
|
(window as any).ReactJSXRuntime = ReactJSXRuntime;
|
2025-11-18 14:46:51 +08:00
|
|
|
|
import {
|
2025-11-27 20:42:46 +08:00
|
|
|
|
PluginManager,
|
2025-11-18 14:46:51 +08:00
|
|
|
|
UIRegistry,
|
|
|
|
|
|
MessageHub,
|
|
|
|
|
|
EntityStoreService,
|
|
|
|
|
|
ComponentRegistry,
|
|
|
|
|
|
LocaleService,
|
|
|
|
|
|
LogService,
|
|
|
|
|
|
SettingsRegistry,
|
|
|
|
|
|
SceneManagerService,
|
|
|
|
|
|
ProjectService,
|
|
|
|
|
|
CompilerRegistry,
|
2025-11-25 22:23:19 +08:00
|
|
|
|
ICompilerRegistry,
|
2025-11-18 14:46:51 +08:00
|
|
|
|
InspectorRegistry,
|
2025-11-21 10:03:18 +08:00
|
|
|
|
INotification,
|
2025-12-03 22:15:22 +08:00
|
|
|
|
CommandManager,
|
|
|
|
|
|
BuildService
|
2025-11-18 14:46:51 +08:00
|
|
|
|
} from '@esengine/editor-core';
|
|
|
|
|
|
import type { IDialogExtended } from './services/TauriDialogService';
|
2025-10-27 09:29:11 +08:00
|
|
|
|
import { GlobalBlackboardService } from '@esengine/behavior-tree';
|
2025-11-18 14:46:51 +08:00
|
|
|
|
import { ServiceRegistry, PluginInstaller, useDialogStore } from './app/managers';
|
2025-12-13 19:44:08 +08:00
|
|
|
|
import { useEditorStore } from './stores';
|
2025-10-15 09:34:44 +08:00
|
|
|
|
import { StartupPage } from './components/StartupPage';
|
2025-11-23 21:45:10 +08:00
|
|
|
|
import { ProjectCreationWizard } from './components/ProjectCreationWizard';
|
2025-10-14 23:31:09 +08:00
|
|
|
|
import { SceneHierarchy } from './components/SceneHierarchy';
|
2025-12-13 19:44:08 +08:00
|
|
|
|
import { ContentBrowser } from './components/ContentBrowser';
|
2025-11-21 10:03:18 +08:00
|
|
|
|
import { Inspector } from './components/inspectors/Inspector';
|
2025-10-15 09:34:44 +08:00
|
|
|
|
import { AssetBrowser } from './components/AssetBrowser';
|
2025-11-21 10:03:18 +08:00
|
|
|
|
import { Viewport } from './components/Viewport';
|
2025-11-30 00:22:47 +08:00
|
|
|
|
import { AdvancedProfilerWindow } from './components/AdvancedProfilerWindow';
|
2025-12-16 12:46:14 +08:00
|
|
|
|
import { RenderDebugPanel } from './components/debug/RenderDebugPanel';
|
|
|
|
|
|
import { emit, emitTo, listen } from '@tauri-apps/api/event';
|
|
|
|
|
|
import { renderDebugService } from './services/RenderDebugService';
|
2025-10-15 22:30:49 +08:00
|
|
|
|
import { PortManager } from './components/PortManager';
|
2025-10-16 13:07:19 +08:00
|
|
|
|
import { SettingsWindow } from './components/SettingsWindow';
|
2025-10-16 22:26:50 +08:00
|
|
|
|
import { AboutDialog } from './components/AboutDialog';
|
2025-10-17 18:13:31 +08:00
|
|
|
|
import { ErrorDialog } from './components/ErrorDialog';
|
|
|
|
|
|
import { ConfirmDialog } from './components/ConfirmDialog';
|
2025-12-16 12:46:14 +08:00
|
|
|
|
import { ExternalModificationDialog } from './components/ExternalModificationDialog';
|
2025-10-28 11:45:35 +08:00
|
|
|
|
import { PluginGeneratorWindow } from './components/PluginGeneratorWindow';
|
2025-12-03 22:15:22 +08:00
|
|
|
|
import { BuildSettingsWindow } from './components/BuildSettingsWindow';
|
2025-12-04 09:51:04 +08:00
|
|
|
|
import { ForumPanel } from './components/forum';
|
2025-11-18 14:46:51 +08:00
|
|
|
|
import { ToastProvider, useToast } from './components/Toast';
|
2025-11-29 23:00:48 +08:00
|
|
|
|
import { TitleBar } from './components/TitleBar';
|
|
|
|
|
|
import { MainToolbar } from './components/MainToolbar';
|
2025-12-13 19:44:08 +08:00
|
|
|
|
import { FlexLayoutDockContainer, FlexDockPanel, type FlexLayoutDockContainerHandle } from './components/FlexLayoutDockContainer';
|
2025-11-29 23:00:48 +08:00
|
|
|
|
import { StatusBar } from './components/StatusBar';
|
2025-10-14 22:53:26 +08:00
|
|
|
|
import { TauriAPI } from './api/tauri';
|
2025-10-16 17:10:22 +08:00
|
|
|
|
import { SettingsService } from './services/SettingsService';
|
2025-10-28 11:45:35 +08:00
|
|
|
|
import { PluginLoader } from './services/PluginLoader';
|
2025-11-27 20:42:46 +08:00
|
|
|
|
import { EngineService } from './services/EngineService';
|
2025-11-18 14:46:51 +08:00
|
|
|
|
import { CompilerConfigDialog } from './components/CompilerConfigDialog';
|
2025-10-16 22:26:50 +08:00
|
|
|
|
import { checkForUpdatesOnStartup } from './utils/updater';
|
2025-10-14 23:56:54 +08:00
|
|
|
|
import { useLocale } from './hooks/useLocale';
|
2025-12-13 19:44:08 +08:00
|
|
|
|
import { useStoreSubscriptions } from './hooks/useStoreSubscriptions';
|
2025-12-09 18:04:03 +08:00
|
|
|
|
import { en, zh, es } from './locales';
|
2025-11-23 21:45:10 +08:00
|
|
|
|
import type { Locale } from '@esengine/editor-core';
|
2025-12-16 12:46:14 +08:00
|
|
|
|
import { UserCodeService } from '@esengine/editor-core';
|
2025-11-29 23:00:48 +08:00
|
|
|
|
import { Loader2 } from 'lucide-react';
|
2025-10-14 22:53:26 +08:00
|
|
|
|
import './styles/App.css';
|
|
|
|
|
|
|
2025-10-15 00:15:12 +08:00
|
|
|
|
const coreInstance = Core.create({ debug: true });
|
2025-10-14 23:56:54 +08:00
|
|
|
|
|
|
|
|
|
|
const localeService = new LocaleService();
|
|
|
|
|
|
localeService.registerTranslations('en', en);
|
|
|
|
|
|
localeService.registerTranslations('zh', zh);
|
2025-12-09 18:04:03 +08:00
|
|
|
|
localeService.registerTranslations('es', es);
|
2025-10-14 23:56:54 +08:00
|
|
|
|
Core.services.registerInstance(LocaleService, localeService);
|
|
|
|
|
|
|
2025-10-27 09:29:11 +08:00
|
|
|
|
Core.services.registerSingleton(GlobalBlackboardService);
|
2025-11-18 14:46:51 +08:00
|
|
|
|
Core.services.registerSingleton(CompilerRegistry);
|
2025-10-27 09:29:11 +08:00
|
|
|
|
|
2025-11-25 22:23:19 +08:00
|
|
|
|
// 在 CompilerRegistry 实例化后,也用 Symbol 注册,用于跨包插件访问
|
|
|
|
|
|
// 注意:registerSingleton 会延迟实例化,所以需要在第一次使用后再注册 Symbol
|
|
|
|
|
|
const compilerRegistryInstance = Core.services.resolve(CompilerRegistry);
|
|
|
|
|
|
Core.services.registerInstance(ICompilerRegistry, compilerRegistryInstance);
|
|
|
|
|
|
|
2025-11-04 23:53:26 +08:00
|
|
|
|
const logger = createLogger('App');
|
|
|
|
|
|
|
2025-12-16 12:46:14 +08:00
|
|
|
|
// 检查是否为独立窗口模式 | Check if standalone window mode
|
|
|
|
|
|
const isFrameDebuggerMode = new URLSearchParams(window.location.search).get('mode') === 'frame-debugger';
|
|
|
|
|
|
|
2025-10-14 22:53:26 +08:00
|
|
|
|
function App() {
|
2025-11-02 23:50:41 +08:00
|
|
|
|
const initRef = useRef(false);
|
2025-12-13 19:44:08 +08:00
|
|
|
|
const layoutContainerRef = useRef<FlexLayoutDockContainerHandle>(null);
|
2025-11-18 14:46:51 +08:00
|
|
|
|
const [pluginLoader] = useState(() => new PluginLoader());
|
|
|
|
|
|
const { showToast, hideToast } = useToast();
|
2025-12-13 19:44:08 +08:00
|
|
|
|
|
2025-12-16 12:46:14 +08:00
|
|
|
|
// 如果是独立调试窗口模式,只渲染调试面板 | If standalone debugger mode, only render debug panel
|
|
|
|
|
|
if (isFrameDebuggerMode) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div style={{ width: '100vw', height: '100vh', overflow: 'hidden' }}>
|
|
|
|
|
|
<RenderDebugPanel visible={true} onClose={() => window.close()} standalone />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
// ===== 本地初始化状态(只用于初始化阶段)| Local init state (only for initialization) =====
|
2025-11-02 23:50:41 +08:00
|
|
|
|
const [initialized, setInitialized] = useState(false);
|
2025-12-13 19:44:08 +08:00
|
|
|
|
|
|
|
|
|
|
// ===== 从 EditorStore 获取状态 | Get state from EditorStore =====
|
|
|
|
|
|
const {
|
|
|
|
|
|
projectLoaded, setProjectLoaded,
|
|
|
|
|
|
currentProjectPath, setCurrentProjectPath,
|
|
|
|
|
|
availableScenes, setAvailableScenes,
|
|
|
|
|
|
isLoading, setIsLoading,
|
|
|
|
|
|
loadingMessage,
|
|
|
|
|
|
panels, setPanels,
|
|
|
|
|
|
activeDynamicPanels, addDynamicPanel, removeDynamicPanel, clearDynamicPanels,
|
|
|
|
|
|
dynamicPanelTitles, setDynamicPanelTitle,
|
|
|
|
|
|
activePanelId, setActivePanelId,
|
|
|
|
|
|
pluginUpdateTrigger, triggerPluginUpdate,
|
|
|
|
|
|
isRemoteConnected, setIsRemoteConnected,
|
|
|
|
|
|
isContentBrowserDocked, setIsContentBrowserDocked,
|
|
|
|
|
|
isEditorFullscreen, setIsEditorFullscreen,
|
|
|
|
|
|
status, setStatus,
|
|
|
|
|
|
showProjectWizard, setShowProjectWizard,
|
|
|
|
|
|
settingsInitialCategory, setSettingsInitialCategory,
|
|
|
|
|
|
compilerDialog, openCompilerDialog, closeCompilerDialog,
|
|
|
|
|
|
} = useEditorStore();
|
|
|
|
|
|
|
|
|
|
|
|
// ===== 服务实例用 useRef(不触发重渲染)| Service instances use useRef (no re-renders) =====
|
|
|
|
|
|
const pluginManagerRef = useRef<PluginManager | null>(null);
|
|
|
|
|
|
const entityStoreRef = useRef<EntityStoreService | null>(null);
|
|
|
|
|
|
const messageHubRef = useRef<MessageHub | null>(null);
|
|
|
|
|
|
const inspectorRegistryRef = useRef<InspectorRegistry | null>(null);
|
|
|
|
|
|
const logServiceRef = useRef<LogService | null>(null);
|
|
|
|
|
|
const uiRegistryRef = useRef<UIRegistry | null>(null);
|
|
|
|
|
|
const settingsRegistryRef = useRef<SettingsRegistry | null>(null);
|
|
|
|
|
|
const sceneManagerRef = useRef<SceneManagerService | null>(null);
|
|
|
|
|
|
const notificationRef = useRef<INotification | null>(null);
|
|
|
|
|
|
const dialogRef = useRef<IDialogExtended | null>(null);
|
|
|
|
|
|
const buildServiceRef = useRef<BuildService | null>(null);
|
|
|
|
|
|
const projectServiceRef = useRef<ProjectService | null>(null);
|
|
|
|
|
|
|
|
|
|
|
|
// 兼容层:提供 getter 访问服务 | Compatibility layer: provide getter access to services
|
|
|
|
|
|
const pluginManager = pluginManagerRef.current;
|
|
|
|
|
|
const entityStore = entityStoreRef.current;
|
|
|
|
|
|
const messageHub = messageHubRef.current;
|
|
|
|
|
|
const inspectorRegistry = inspectorRegistryRef.current;
|
|
|
|
|
|
const logService = logServiceRef.current;
|
|
|
|
|
|
const uiRegistry = uiRegistryRef.current;
|
|
|
|
|
|
const settingsRegistry = settingsRegistryRef.current;
|
|
|
|
|
|
const sceneManager = sceneManagerRef.current;
|
|
|
|
|
|
const notification = notificationRef.current;
|
|
|
|
|
|
const dialog = dialogRef.current;
|
|
|
|
|
|
const buildService = buildServiceRef.current;
|
|
|
|
|
|
const projectServiceState = projectServiceRef.current;
|
|
|
|
|
|
|
2025-11-21 10:03:18 +08:00
|
|
|
|
const [commandManager] = useState(() => new CommandManager());
|
2025-11-02 23:50:41 +08:00
|
|
|
|
const { t, locale, changeLocale } = useLocale();
|
2025-11-18 14:46:51 +08:00
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
// 初始化 Store 订阅(集中管理 MessageHub 订阅)
|
|
|
|
|
|
// Initialize store subscriptions (centrally manage MessageHub subscriptions)
|
|
|
|
|
|
useStoreSubscriptions({
|
|
|
|
|
|
messageHub: messageHubRef.current,
|
|
|
|
|
|
entityStore: entityStoreRef.current,
|
|
|
|
|
|
sceneManager: sceneManagerRef.current,
|
|
|
|
|
|
enabled: initialized,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-11-18 14:46:51 +08:00
|
|
|
|
// 同步 locale 到 TauriDialogService
|
|
|
|
|
|
useEffect(() => {
|
2025-12-13 19:44:08 +08:00
|
|
|
|
if (dialogRef.current) {
|
|
|
|
|
|
dialogRef.current.setLocale(locale);
|
2025-11-18 14:46:51 +08:00
|
|
|
|
}
|
2025-12-13 19:44:08 +08:00
|
|
|
|
}, [locale]);
|
2025-11-18 14:46:51 +08:00
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
// ===== 从 DialogStore 获取对话框状态 | Get dialog state from DialogStore =====
|
2025-11-18 14:46:51 +08:00
|
|
|
|
const {
|
|
|
|
|
|
showProfiler, setShowProfiler,
|
2025-11-30 00:22:47 +08:00
|
|
|
|
showAdvancedProfiler, setShowAdvancedProfiler,
|
2025-11-18 14:46:51 +08:00
|
|
|
|
showPortManager, setShowPortManager,
|
|
|
|
|
|
showSettings, setShowSettings,
|
|
|
|
|
|
showAbout, setShowAbout,
|
|
|
|
|
|
showPluginGenerator, setShowPluginGenerator,
|
2025-12-03 22:15:22 +08:00
|
|
|
|
showBuildSettings, setShowBuildSettings,
|
2025-12-16 12:46:14 +08:00
|
|
|
|
showRenderDebug, setShowRenderDebug,
|
2025-11-18 14:46:51 +08:00
|
|
|
|
errorDialog, setErrorDialog,
|
2025-12-16 12:46:14 +08:00
|
|
|
|
confirmDialog, setConfirmDialog,
|
|
|
|
|
|
externalModificationDialog, setExternalModificationDialog
|
2025-11-18 14:46:51 +08:00
|
|
|
|
} = useDialogStore();
|
2025-10-14 22:53:26 +08:00
|
|
|
|
|
2025-12-16 12:46:14 +08:00
|
|
|
|
// 全局监听独立调试窗口的数据请求 | Global listener for standalone debug window requests
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
let broadcastInterval: ReturnType<typeof setInterval> | null = null;
|
|
|
|
|
|
|
|
|
|
|
|
const unlistenPromise = listen('render-debug-request-data', () => {
|
|
|
|
|
|
// 开始定时广播数据 | Start broadcasting data periodically
|
|
|
|
|
|
if (!broadcastInterval) {
|
|
|
|
|
|
const broadcast = () => {
|
|
|
|
|
|
renderDebugService.setEnabled(true);
|
|
|
|
|
|
const snap = renderDebugService.collectSnapshot();
|
|
|
|
|
|
if (snap) {
|
|
|
|
|
|
// 使用 emitTo 发送到独立窗口 | Use emitTo to send to standalone window
|
|
|
|
|
|
emitTo('frame-debugger', 'render-debug-snapshot', snap).catch(() => {});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
broadcast(); // 立即广播一次 | Broadcast immediately
|
|
|
|
|
|
broadcastInterval = setInterval(broadcast, 500);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
unlistenPromise.then(unlisten => unlisten());
|
|
|
|
|
|
if (broadcastInterval) {
|
|
|
|
|
|
clearInterval(broadcastInterval);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
useEffect(() => {
|
2025-11-03 21:22:16 +08:00
|
|
|
|
// 禁用默认右键菜单
|
2025-11-02 23:50:41 +08:00
|
|
|
|
const handleContextMenu = (e: MouseEvent) => {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
};
|
2025-10-15 20:23:55 +08:00
|
|
|
|
|
2025-11-23 21:45:10 +08:00
|
|
|
|
document.addEventListener('contextmenu', handleContextMenu);
|
|
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
document.removeEventListener('contextmenu', handleContextMenu);
|
|
|
|
|
|
};
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
// Global keyboard shortcuts for undo/redo | 全局撤销/重做快捷键
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
|
|
|
|
// Skip if user is typing in an input | 如果用户正在输入则跳过
|
|
|
|
|
|
const target = e.target as HTMLElement;
|
|
|
|
|
|
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Ctrl+Z: Undo | 撤销
|
|
|
|
|
|
if (e.ctrlKey && !e.shiftKey && e.key === 'z') {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
if (commandManager.canUndo()) {
|
|
|
|
|
|
commandManager.undo();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// Ctrl+Y or Ctrl+Shift+Z: Redo | 重做
|
|
|
|
|
|
else if ((e.ctrlKey && e.key === 'y') || (e.ctrlKey && e.shiftKey && e.key === 'z')) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
if (commandManager.canRedo()) {
|
|
|
|
|
|
commandManager.redo();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('keydown', handleKeyDown);
|
|
|
|
|
|
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
|
|
|
|
}, [commandManager]);
|
|
|
|
|
|
|
2025-11-23 21:45:10 +08:00
|
|
|
|
// 快捷键监听
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const handleKeyDown = async (e: KeyboardEvent) => {
|
|
|
|
|
|
if (e.ctrlKey || e.metaKey) {
|
|
|
|
|
|
switch (e.key.toLowerCase()) {
|
2025-12-10 18:23:29 +08:00
|
|
|
|
case 's': {
|
|
|
|
|
|
// Skip if any modal/dialog is open
|
|
|
|
|
|
// 如果有模态窗口/对话框打开则跳过
|
|
|
|
|
|
const hasModalOpen = showBuildSettings || showSettings || showAbout ||
|
|
|
|
|
|
showPluginGenerator || showPortManager || showAdvancedProfiler ||
|
|
|
|
|
|
errorDialog || confirmDialog;
|
|
|
|
|
|
if (hasModalOpen) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Skip if focus is in an input/textarea/contenteditable element
|
|
|
|
|
|
// 如果焦点在输入框/文本域/可编辑元素中则跳过
|
|
|
|
|
|
const activeEl = document.activeElement;
|
|
|
|
|
|
const isInInput = activeEl instanceof HTMLInputElement ||
|
|
|
|
|
|
activeEl instanceof HTMLTextAreaElement ||
|
|
|
|
|
|
activeEl?.getAttribute('contenteditable') === 'true';
|
|
|
|
|
|
if (isInInput) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-23 21:45:10 +08:00
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
if (sceneManager) {
|
|
|
|
|
|
try {
|
2025-12-13 19:44:08 +08:00
|
|
|
|
// 检查是否在预制体编辑模式 | Check if in prefab edit mode
|
|
|
|
|
|
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');
|
|
|
|
|
|
}
|
2025-11-23 21:45:10 +08:00
|
|
|
|
} catch (error) {
|
2025-12-13 19:44:08 +08:00
|
|
|
|
console.error('Failed to save:', error);
|
|
|
|
|
|
if (sceneManager.isPrefabEditMode()) {
|
|
|
|
|
|
showToast(t('editMode.prefab.saveFailed'), 'error');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
showToast(t('scene.saveFailed'), 'error');
|
|
|
|
|
|
}
|
2025-11-23 21:45:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
2025-12-10 18:23:29 +08:00
|
|
|
|
}
|
2025-11-23 21:45:10 +08:00
|
|
|
|
case 'r':
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
handleReloadPlugins();
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2025-11-18 14:46:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('keydown', handleKeyDown);
|
2025-10-15 20:23:55 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
return () => {
|
2025-11-18 14:46:51 +08:00
|
|
|
|
document.removeEventListener('keydown', handleKeyDown);
|
2025-11-02 23:50:41 +08:00
|
|
|
|
};
|
2025-12-10 18:23:29 +08:00
|
|
|
|
}, [sceneManager, locale, currentProjectPath, pluginManager,
|
|
|
|
|
|
showBuildSettings, showSettings, showAbout, showPluginGenerator,
|
|
|
|
|
|
showPortManager, showAdvancedProfiler, errorDialog, confirmDialog]);
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
// 插件和通知订阅 | Plugin and notification subscriptions
|
2025-11-02 23:50:41 +08:00
|
|
|
|
useEffect(() => {
|
2025-12-13 19:44:08 +08:00
|
|
|
|
if (!initialized || !messageHubRef.current) return;
|
|
|
|
|
|
const hub = messageHubRef.current;
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
const unsubscribeEnabled = hub.subscribe('plugin:enabled', () => {
|
|
|
|
|
|
triggerPluginUpdate();
|
|
|
|
|
|
});
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
const unsubscribeDisabled = hub.subscribe('plugin:disabled', () => {
|
|
|
|
|
|
triggerPluginUpdate();
|
|
|
|
|
|
});
|
2025-11-18 14:46:51 +08:00
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
const unsubscribeNotification = hub.subscribe('notification:show', (notification: { message: string; type: 'success' | 'error' | 'warning' | 'info'; timestamp: number }) => {
|
|
|
|
|
|
if (notification && notification.message) {
|
|
|
|
|
|
showToast(notification.message, notification.type);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
unsubscribeEnabled();
|
|
|
|
|
|
unsubscribeDisabled();
|
|
|
|
|
|
unsubscribeNotification();
|
|
|
|
|
|
};
|
|
|
|
|
|
}, [initialized, triggerPluginUpdate, showToast]);
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
|
|
|
|
|
// 监听远程连接状态
|
2025-12-09 11:07:44 +08:00
|
|
|
|
// Monitor remote connection status
|
2025-11-02 23:50:41 +08:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const checkConnection = () => {
|
2025-12-09 11:07:44 +08:00
|
|
|
|
const profilerService = getProfilerService();
|
|
|
|
|
|
const connected = !!(profilerService && profilerService.isConnected());
|
2025-11-18 14:46:51 +08:00
|
|
|
|
|
|
|
|
|
|
setIsRemoteConnected((prevConnected) => {
|
|
|
|
|
|
if (connected !== prevConnected) {
|
2025-12-09 11:07:44 +08:00
|
|
|
|
// 状态发生变化 | State has changed
|
2025-11-18 14:46:51 +08:00
|
|
|
|
if (connected) {
|
|
|
|
|
|
setStatus(t('header.status.remoteConnected'));
|
2025-11-02 23:50:41 +08:00
|
|
|
|
} else {
|
2025-11-18 14:46:51 +08:00
|
|
|
|
if (projectLoaded) {
|
|
|
|
|
|
const componentRegistry = Core.services.resolve(ComponentRegistry);
|
|
|
|
|
|
const componentCount = componentRegistry?.getAllComponents().length || 0;
|
|
|
|
|
|
setStatus(t('header.status.projectOpened') + (componentCount > 0 ? ` (${componentCount} components registered)` : ''));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
setStatus(t('header.status.ready'));
|
|
|
|
|
|
}
|
2025-11-02 23:50:41 +08:00
|
|
|
|
}
|
2025-11-18 14:46:51 +08:00
|
|
|
|
return connected;
|
2025-11-02 23:50:41 +08:00
|
|
|
|
}
|
2025-11-18 14:46:51 +08:00
|
|
|
|
return prevConnected;
|
|
|
|
|
|
});
|
2025-11-02 23:50:41 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const interval = setInterval(checkConnection, 1000);
|
|
|
|
|
|
|
|
|
|
|
|
return () => clearInterval(interval);
|
2025-11-18 14:46:51 +08:00
|
|
|
|
}, [projectLoaded, t]);
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const initializeEditor = async () => {
|
|
|
|
|
|
// 使用 ref 防止 React StrictMode 的双重调用
|
|
|
|
|
|
if (initRef.current) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
initRef.current = true;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2025-12-09 11:07:44 +08:00
|
|
|
|
// ECS Framework 已通过 PluginSDKRegistry 暴露到全局
|
|
|
|
|
|
// ECS Framework is exposed globally via PluginSDKRegistry
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
|
|
|
|
|
const editorScene = new Scene();
|
|
|
|
|
|
Core.setScene(editorScene);
|
|
|
|
|
|
|
2025-11-18 14:46:51 +08:00
|
|
|
|
const serviceRegistry = new ServiceRegistry();
|
|
|
|
|
|
const services = serviceRegistry.registerAllServices(coreInstance);
|
|
|
|
|
|
|
|
|
|
|
|
serviceRegistry.setupRemoteLogListener(services.logService);
|
|
|
|
|
|
|
|
|
|
|
|
const pluginInstaller = new PluginInstaller();
|
|
|
|
|
|
await pluginInstaller.installBuiltinPlugins(services.pluginManager);
|
|
|
|
|
|
|
2025-11-27 20:42:46 +08:00
|
|
|
|
// 初始化编辑器模块(安装设置、面板等)
|
|
|
|
|
|
await services.pluginManager.initializeEditor(Core.services);
|
|
|
|
|
|
|
2025-11-18 14:46:51 +08:00
|
|
|
|
services.notification.setCallbacks(showToast, hideToast);
|
|
|
|
|
|
(services.dialog as IDialogExtended).setConfirmCallback(setConfirmDialog);
|
|
|
|
|
|
|
|
|
|
|
|
services.messageHub.subscribe('ui:openWindow', (data: any) => {
|
|
|
|
|
|
const { windowId } = data;
|
2025-11-04 18:29:28 +08:00
|
|
|
|
|
|
|
|
|
|
if (windowId === 'profiler') {
|
2025-11-02 23:50:41 +08:00
|
|
|
|
setShowProfiler(true);
|
2025-11-30 00:22:47 +08:00
|
|
|
|
} else if (windowId === 'advancedProfiler') {
|
|
|
|
|
|
setShowAdvancedProfiler(true);
|
2025-11-04 18:29:28 +08:00
|
|
|
|
} else if (windowId === 'pluginManager') {
|
2025-11-27 20:42:46 +08:00
|
|
|
|
// 插件管理现在整合到设置窗口中
|
|
|
|
|
|
setSettingsInitialCategory('plugins');
|
|
|
|
|
|
setShowSettings(true);
|
2025-11-02 23:50:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
// 设置服务引用(不触发重渲染)| Set service refs (no re-renders)
|
|
|
|
|
|
pluginManagerRef.current = services.pluginManager;
|
|
|
|
|
|
entityStoreRef.current = services.entityStore;
|
|
|
|
|
|
messageHubRef.current = services.messageHub;
|
|
|
|
|
|
inspectorRegistryRef.current = services.inspectorRegistry;
|
|
|
|
|
|
logServiceRef.current = services.logService;
|
|
|
|
|
|
uiRegistryRef.current = services.uiRegistry;
|
|
|
|
|
|
settingsRegistryRef.current = services.settingsRegistry;
|
|
|
|
|
|
sceneManagerRef.current = services.sceneManager;
|
|
|
|
|
|
notificationRef.current = services.notification;
|
|
|
|
|
|
dialogRef.current = services.dialog as IDialogExtended;
|
|
|
|
|
|
buildServiceRef.current = services.buildService;
|
|
|
|
|
|
|
|
|
|
|
|
// 设置初始化完成(触发一次重渲染)| Set initialized (triggers one re-render)
|
2025-11-02 23:50:41 +08:00
|
|
|
|
setInitialized(true);
|
|
|
|
|
|
setStatus(t('header.status.ready'));
|
|
|
|
|
|
|
|
|
|
|
|
// Check for updates on startup (after 3 seconds)
|
|
|
|
|
|
checkForUpdatesOnStartup();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Failed to initialize editor:', error);
|
|
|
|
|
|
setStatus(t('header.status.failed'));
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
initializeEditor();
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
// 初始化后订阅消息 | Subscribe to messages after initialization
|
2025-11-04 18:29:28 +08:00
|
|
|
|
useEffect(() => {
|
2025-12-13 19:44:08 +08:00
|
|
|
|
if (!initialized || !messageHubRef.current) return;
|
|
|
|
|
|
const hub = messageHubRef.current;
|
2025-11-04 18:29:28 +08:00
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
const unsubscribe = hub.subscribe('dynamic-panel:open', (data: any) => {
|
2025-11-04 23:53:26 +08:00
|
|
|
|
const { panelId, title } = data;
|
|
|
|
|
|
logger.info('Opening dynamic panel:', panelId, 'with title:', title);
|
2025-12-13 19:44:08 +08:00
|
|
|
|
addDynamicPanel(panelId, title);
|
2025-11-04 23:53:26 +08:00
|
|
|
|
setActivePanelId(panelId);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return () => unsubscribe?.();
|
2025-12-13 19:44:08 +08:00
|
|
|
|
}, [initialized, addDynamicPanel, setActivePanelId]);
|
2025-11-04 23:53:26 +08:00
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2025-12-13 19:44:08 +08:00
|
|
|
|
if (!initialized || !messageHubRef.current) return;
|
|
|
|
|
|
const hub = messageHubRef.current;
|
2025-11-04 23:53:26 +08:00
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
const unsubscribe = hub.subscribe('editor:fullscreen', (data: any) => {
|
2025-11-04 23:53:26 +08:00
|
|
|
|
const { fullscreen } = data;
|
|
|
|
|
|
logger.info('Editor fullscreen state changed:', fullscreen);
|
|
|
|
|
|
setIsEditorFullscreen(fullscreen);
|
2025-11-04 18:29:28 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return () => unsubscribe?.();
|
2025-12-13 19:44:08 +08:00
|
|
|
|
}, [initialized, setIsEditorFullscreen]);
|
2025-11-04 18:29:28 +08:00
|
|
|
|
|
2025-11-18 14:46:51 +08:00
|
|
|
|
useEffect(() => {
|
2025-12-13 19:44:08 +08:00
|
|
|
|
if (!initialized || !messageHubRef.current) return;
|
|
|
|
|
|
const hub = messageHubRef.current;
|
2025-11-18 14:46:51 +08:00
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
const unsubscribe = hub.subscribe('compiler:open-dialog', (data: {
|
2025-11-18 14:46:51 +08:00
|
|
|
|
compilerId: string;
|
|
|
|
|
|
currentFileName?: string;
|
|
|
|
|
|
projectPath?: string;
|
|
|
|
|
|
}) => {
|
|
|
|
|
|
logger.info('Opening compiler dialog:', data.compilerId);
|
2025-12-13 19:44:08 +08:00
|
|
|
|
openCompilerDialog(data.compilerId, data.currentFileName);
|
2025-11-18 14:46:51 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return () => unsubscribe?.();
|
2025-12-13 19:44:08 +08:00
|
|
|
|
}, [initialized, openCompilerDialog]);
|
|
|
|
|
|
|
|
|
|
|
|
// 注册引擎快照请求处理器(用于预制体编辑模式)
|
|
|
|
|
|
// Register engine snapshot request handlers (for prefab edit mode)
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (!initialized || !messageHubRef.current) return;
|
|
|
|
|
|
const hub = messageHubRef.current;
|
|
|
|
|
|
|
|
|
|
|
|
const unsubscribeSave = hub.onRequest<void, boolean>(
|
|
|
|
|
|
'engine:saveSceneSnapshot',
|
|
|
|
|
|
async () => {
|
|
|
|
|
|
const engineService = EngineService.getInstance();
|
|
|
|
|
|
return engineService.saveSceneSnapshot();
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const unsubscribeRestore = hub.onRequest<void, boolean>(
|
|
|
|
|
|
'engine:restoreSceneSnapshot',
|
|
|
|
|
|
async () => {
|
|
|
|
|
|
const engineService = EngineService.getInstance();
|
|
|
|
|
|
return await engineService.restoreSceneSnapshot();
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
unsubscribeSave?.();
|
|
|
|
|
|
unsubscribeRestore?.();
|
|
|
|
|
|
};
|
|
|
|
|
|
}, [initialized]);
|
2025-11-18 14:46:51 +08:00
|
|
|
|
|
2025-12-16 12:46:14 +08:00
|
|
|
|
// Handle external scene file changes
|
|
|
|
|
|
// 处理外部场景文件变更
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (!initialized || !messageHubRef.current || !sceneManagerRef.current) return;
|
|
|
|
|
|
const hub = messageHubRef.current;
|
|
|
|
|
|
const sm = sceneManagerRef.current;
|
|
|
|
|
|
|
|
|
|
|
|
const unsubscribe = hub.subscribe('scene:external-change', (data: {
|
|
|
|
|
|
path: string;
|
|
|
|
|
|
sceneName: string;
|
|
|
|
|
|
}) => {
|
|
|
|
|
|
logger.info('Scene externally modified:', data.path);
|
|
|
|
|
|
|
|
|
|
|
|
// Show confirmation dialog to reload the scene
|
|
|
|
|
|
// 显示确认对话框以重新加载场景
|
|
|
|
|
|
setConfirmDialog({
|
|
|
|
|
|
title: t('scene.externalChange.title'),
|
|
|
|
|
|
message: t('scene.externalChange.message', { name: data.sceneName }),
|
|
|
|
|
|
confirmText: t('scene.externalChange.reload'),
|
|
|
|
|
|
cancelText: t('scene.externalChange.ignore'),
|
|
|
|
|
|
onConfirm: async () => {
|
|
|
|
|
|
setConfirmDialog(null);
|
|
|
|
|
|
try {
|
|
|
|
|
|
await sm.openScene(data.path);
|
|
|
|
|
|
showToast(t('scene.reloadedSuccess', { name: data.sceneName }), 'success');
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Failed to reload scene:', error);
|
|
|
|
|
|
showToast(t('scene.reloadFailed'), 'error');
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
onCancel: () => {
|
|
|
|
|
|
// User chose to ignore, do nothing
|
|
|
|
|
|
// 用户选择忽略,不做任何操作
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return () => unsubscribe?.();
|
|
|
|
|
|
}, [initialized, t, showToast]);
|
|
|
|
|
|
|
|
|
|
|
|
// Handle external modification when saving scene
|
|
|
|
|
|
// 处理保存场景时的外部修改检测
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (!initialized || !messageHubRef.current || !sceneManagerRef.current) return;
|
|
|
|
|
|
const hub = messageHubRef.current;
|
|
|
|
|
|
const sm = sceneManagerRef.current;
|
|
|
|
|
|
|
|
|
|
|
|
const unsubscribe = hub.subscribe('scene:externalModification', (data: {
|
|
|
|
|
|
path: string;
|
|
|
|
|
|
sceneName: string;
|
|
|
|
|
|
}) => {
|
|
|
|
|
|
logger.info('Scene file externally modified during save:', data.path);
|
|
|
|
|
|
|
|
|
|
|
|
// Show external modification dialog with three options
|
|
|
|
|
|
// 显示外部修改对话框,提供三个选项
|
|
|
|
|
|
setExternalModificationDialog({
|
|
|
|
|
|
sceneName: data.sceneName,
|
|
|
|
|
|
onReload: async () => {
|
|
|
|
|
|
setExternalModificationDialog(null);
|
|
|
|
|
|
try {
|
|
|
|
|
|
await sm.reloadScene();
|
|
|
|
|
|
showToast(t('scene.reloadedSuccess', { name: data.sceneName }), 'success');
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Failed to reload scene:', error);
|
|
|
|
|
|
showToast(t('scene.reloadFailed'), 'error');
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
onOverwrite: async () => {
|
|
|
|
|
|
setExternalModificationDialog(null);
|
|
|
|
|
|
try {
|
|
|
|
|
|
await sm.saveScene(true); // Force save, overwriting external changes
|
|
|
|
|
|
showToast(t('scene.savedSuccess', { name: data.sceneName }), 'success');
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Failed to save scene:', error);
|
|
|
|
|
|
showToast(t('scene.saveFailed'), 'error');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return () => unsubscribe?.();
|
|
|
|
|
|
}, [initialized, t, showToast, setExternalModificationDialog]);
|
|
|
|
|
|
|
|
|
|
|
|
// Handle user code compilation results
|
|
|
|
|
|
// 处理用户代码编译结果
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (!initialized || !messageHubRef.current) return;
|
|
|
|
|
|
const hub = messageHubRef.current;
|
|
|
|
|
|
|
|
|
|
|
|
const unsubscribe = hub.subscribe('usercode:compilation-result', (data: {
|
|
|
|
|
|
success: boolean;
|
|
|
|
|
|
exports: string[];
|
|
|
|
|
|
errors: string[];
|
|
|
|
|
|
}) => {
|
|
|
|
|
|
if (data.success) {
|
|
|
|
|
|
if (data.exports.length > 0) {
|
|
|
|
|
|
showToast(t('usercode.compileSuccess', { count: data.exports.length }), 'success');
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const errorMsg = data.errors[0] ?? t('usercode.compileError');
|
|
|
|
|
|
showToast(errorMsg, 'error');
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return () => unsubscribe?.();
|
|
|
|
|
|
}, [initialized, t, showToast]);
|
|
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
const handleOpenRecentProject = async (projectPath: string) => {
|
|
|
|
|
|
try {
|
2025-12-13 19:44:08 +08:00
|
|
|
|
setIsLoading(true, t('loading.step1'));
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
|
|
|
|
|
const projectService = Core.services.resolve(ProjectService);
|
|
|
|
|
|
|
|
|
|
|
|
if (!projectService) {
|
|
|
|
|
|
console.error('Required services not available');
|
|
|
|
|
|
setIsLoading(false);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
projectServiceRef.current = projectService;
|
2025-11-02 23:50:41 +08:00
|
|
|
|
await projectService.openProject(projectPath);
|
|
|
|
|
|
|
2025-12-01 22:28:51 +08:00
|
|
|
|
// 注意:插件配置会在引擎初始化后加载和激活
|
|
|
|
|
|
// Note: Plugin config will be loaded and activated after engine initialization
|
|
|
|
|
|
|
2025-11-25 22:23:19 +08:00
|
|
|
|
// 设置 Tauri project:// 协议的基础路径(用于加载插件等项目文件)
|
|
|
|
|
|
await TauriAPI.setProjectBasePath(projectPath);
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-12-04 19:32:51 +08:00
|
|
|
|
// 更新项目 tsconfig,直接引用引擎类型定义
|
|
|
|
|
|
// Update project tsconfig to reference engine type definitions directly
|
2025-12-04 14:04:39 +08:00
|
|
|
|
try {
|
2025-12-04 19:32:51 +08:00
|
|
|
|
await TauriAPI.updateProjectTsconfig(projectPath);
|
2025-12-04 14:04:39 +08:00
|
|
|
|
} catch (e) {
|
2025-12-04 19:32:51 +08:00
|
|
|
|
console.warn('[App] Failed to update project tsconfig:', e);
|
2025-12-04 14:04:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 20:42:46 +08:00
|
|
|
|
const settings = SettingsService.getInstance();
|
|
|
|
|
|
settings.addRecentProject(projectPath);
|
|
|
|
|
|
|
|
|
|
|
|
setCurrentProjectPath(projectPath);
|
2025-12-10 18:23:29 +08:00
|
|
|
|
|
|
|
|
|
|
// Scan for available scenes in project
|
|
|
|
|
|
// 扫描项目中可用的场景
|
|
|
|
|
|
try {
|
|
|
|
|
|
const sceneFiles = await TauriAPI.scanDirectory(`${projectPath}/scenes`, '*.ecs');
|
|
|
|
|
|
const sceneNames = sceneFiles.map(f => `scenes/${f.split(/[\\/]/).pop()}`);
|
|
|
|
|
|
setAvailableScenes(sceneNames);
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.warn('[App] Failed to scan scenes:', e);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 20:42:46 +08:00
|
|
|
|
// 设置 projectLoaded 为 true,触发主界面渲染(包括 Viewport)
|
|
|
|
|
|
setProjectLoaded(true);
|
|
|
|
|
|
|
|
|
|
|
|
// 等待引擎初始化完成(Viewport 渲染后会触发引擎初始化)
|
2025-12-13 19:44:08 +08:00
|
|
|
|
setIsLoading(true, t('loading.step2'));
|
2025-11-27 20:42:46 +08:00
|
|
|
|
const engineService = EngineService.getInstance();
|
|
|
|
|
|
|
|
|
|
|
|
// 等待引擎初始化(最多等待 30 秒,因为需要等待 Viewport 渲染)
|
|
|
|
|
|
const engineReady = await engineService.waitForInitialization(30000);
|
|
|
|
|
|
if (!engineReady) {
|
2025-12-09 18:04:03 +08:00
|
|
|
|
throw new Error(t('loading.engineTimeoutError'));
|
2025-11-27 20:42:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-01 22:28:51 +08:00
|
|
|
|
// 加载项目插件配置并激活插件(在引擎初始化后、模块系统初始化前)
|
|
|
|
|
|
// Load project plugin config and activate plugins (after engine init, before module system init)
|
2025-12-13 19:44:08 +08:00
|
|
|
|
if (pluginManagerRef.current) {
|
2025-12-01 22:28:51 +08:00
|
|
|
|
const pluginSettings = projectService.getPluginSettings();
|
|
|
|
|
|
if (pluginSettings && pluginSettings.enabledPlugins.length > 0) {
|
2025-12-13 19:44:08 +08:00
|
|
|
|
await pluginManagerRef.current.loadConfig({ enabledPlugins: pluginSettings.enabledPlugins });
|
2025-12-01 22:28:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 20:42:46 +08:00
|
|
|
|
// 初始化模块系统(所有插件的 runtimeModule 会在 PluginManager 安装时自动注册)
|
|
|
|
|
|
await engineService.initializeModuleSystems();
|
|
|
|
|
|
|
|
|
|
|
|
// 应用项目的 UI 设计分辨率
|
|
|
|
|
|
// Apply project's UI design resolution
|
|
|
|
|
|
const uiResolution = projectService.getUIDesignResolution();
|
|
|
|
|
|
engineService.setUICanvasSize(uiResolution.width, uiResolution.height);
|
|
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
setStatus(t('header.status.projectOpened'));
|
|
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
setIsLoading(true, t('loading.step3'));
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-12-16 12:46:14 +08:00
|
|
|
|
// Wait for user code to be compiled and registered before loading scenes
|
|
|
|
|
|
// 等待用户代码编译和注册完成后再加载场景
|
|
|
|
|
|
const userCodeService = Core.services.tryResolve(UserCodeService);
|
|
|
|
|
|
if (userCodeService) {
|
|
|
|
|
|
await userCodeService.waitForReady();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
const sceneManagerService = Core.services.resolve(SceneManagerService);
|
2025-11-25 22:23:19 +08:00
|
|
|
|
if (sceneManagerService) {
|
|
|
|
|
|
await sceneManagerService.newScene();
|
2025-11-02 23:50:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
if (pluginManagerRef.current) {
|
|
|
|
|
|
setIsLoading(true, t('loading.loadingPlugins'));
|
|
|
|
|
|
await pluginLoader.loadProjectPlugins(projectPath, pluginManagerRef.current);
|
2025-11-02 23:50:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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({
|
2025-12-09 18:04:03 +08:00
|
|
|
|
title: t('project.openFailed'),
|
|
|
|
|
|
message: `${t('project.openFailed')}:\n${errorMessage}`
|
2025-11-02 23:50:41 +08:00
|
|
|
|
});
|
2025-10-15 23:24:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
const handleOpenProject = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const projectPath = await TauriAPI.openProjectDialog();
|
|
|
|
|
|
if (!projectPath) return;
|
2025-10-16 22:26:50 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
await handleOpenRecentProject(projectPath);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Failed to open project dialog:', error);
|
|
|
|
|
|
}
|
2025-10-14 22:53:26 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-23 21:45:10 +08:00
|
|
|
|
const handleCreateProject = () => {
|
|
|
|
|
|
setShowProjectWizard(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleCreateProjectFromWizard = async (projectName: string, projectPath: string, _templateId: string) => {
|
2025-12-04 14:04:39 +08:00
|
|
|
|
// 使用与 projectPath 相同的路径分隔符 | Use same separator as projectPath
|
|
|
|
|
|
const sep = projectPath.includes('/') ? '/' : '\\';
|
|
|
|
|
|
const fullProjectPath = `${projectPath}${sep}${projectName}`;
|
2025-10-14 22:53:26 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
try {
|
2025-12-13 19:44:08 +08:00
|
|
|
|
setIsLoading(true, t('project.creating'));
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
|
|
|
|
|
const projectService = Core.services.resolve(ProjectService);
|
|
|
|
|
|
if (!projectService) {
|
|
|
|
|
|
console.error('ProjectService not available');
|
|
|
|
|
|
setIsLoading(false);
|
|
|
|
|
|
setErrorDialog({
|
2025-12-09 18:04:03 +08:00
|
|
|
|
title: t('project.createFailed'),
|
|
|
|
|
|
message: t('project.serviceUnavailable')
|
2025-11-02 23:50:41 +08:00
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-10-15 00:40:27 +08:00
|
|
|
|
|
2025-11-23 21:45:10 +08:00
|
|
|
|
await projectService.createProject(fullProjectPath);
|
2025-10-15 00:40:27 +08:00
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
setIsLoading(true, t('project.createdOpening'));
|
2025-10-15 17:15:05 +08:00
|
|
|
|
|
2025-11-23 21:45:10 +08:00
|
|
|
|
await handleOpenRecentProject(fullProjectPath);
|
2025-11-02 23:50:41 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Failed to create project:', error);
|
|
|
|
|
|
setIsLoading(false);
|
|
|
|
|
|
|
|
|
|
|
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
|
|
|
|
|
2025-11-23 21:45:10 +08:00
|
|
|
|
if (errorMessage.includes('already exists')) {
|
2025-11-02 23:50:41 +08:00
|
|
|
|
setConfirmDialog({
|
2025-12-09 18:04:03 +08:00
|
|
|
|
title: t('project.alreadyExists'),
|
|
|
|
|
|
message: t('project.existsQuestion'),
|
|
|
|
|
|
confirmText: t('project.open'),
|
|
|
|
|
|
cancelText: t('common.cancel'),
|
2025-11-02 23:50:41 +08:00
|
|
|
|
onConfirm: () => {
|
|
|
|
|
|
setConfirmDialog(null);
|
2025-12-13 19:44:08 +08:00
|
|
|
|
setIsLoading(true, t('project.opening'));
|
2025-11-23 21:45:10 +08:00
|
|
|
|
handleOpenRecentProject(fullProjectPath).catch((err) => {
|
2025-11-02 23:50:41 +08:00
|
|
|
|
console.error('Failed to open project:', err);
|
|
|
|
|
|
setIsLoading(false);
|
|
|
|
|
|
setErrorDialog({
|
2025-12-09 18:04:03 +08:00
|
|
|
|
title: t('project.openFailed'),
|
|
|
|
|
|
message: `${t('project.openFailed')}:\n${err instanceof Error ? err.message : String(err)}`
|
2025-11-02 23:50:41 +08:00
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
2025-12-09 18:04:03 +08:00
|
|
|
|
setStatus(t('project.createFailed'));
|
2025-11-02 23:50:41 +08:00
|
|
|
|
setErrorDialog({
|
2025-12-09 18:04:03 +08:00
|
|
|
|
title: t('project.createFailed'),
|
|
|
|
|
|
message: `${t('project.createFailed')}:\n${errorMessage}`
|
2025-11-02 23:50:41 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2025-10-15 17:15:05 +08:00
|
|
|
|
|
2025-11-23 21:45:10 +08:00
|
|
|
|
const handleBrowseProjectPath = async (): Promise<string | null> => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const path = await TauriAPI.openProjectDialog();
|
|
|
|
|
|
return path || null;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Failed to browse path:', error);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
const handleNewScene = async () => {
|
|
|
|
|
|
if (!sceneManager) {
|
|
|
|
|
|
console.error('SceneManagerService not available');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-10-15 00:40:27 +08:00
|
|
|
|
|
2025-10-17 18:13:31 +08:00
|
|
|
|
try {
|
2025-11-02 23:50:41 +08:00
|
|
|
|
await sceneManager.newScene();
|
2025-12-09 18:04:03 +08:00
|
|
|
|
setStatus(t('scene.newCreated'));
|
2025-11-02 23:50:41 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Failed to create new scene:', error);
|
2025-12-09 18:04:03 +08:00
|
|
|
|
setStatus(t('scene.createFailed'));
|
2025-11-02 23:50:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
};
|
2025-10-15 00:40:27 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
const handleOpenScene = async () => {
|
|
|
|
|
|
if (!sceneManager) {
|
|
|
|
|
|
console.error('SceneManagerService not available');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-10-15 17:15:05 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
try {
|
2025-12-16 12:46:14 +08:00
|
|
|
|
// Wait for user code to be ready before loading scene
|
|
|
|
|
|
// 在加载场景前等待用户代码就绪
|
|
|
|
|
|
const userCodeService = Core.services.tryResolve(UserCodeService);
|
|
|
|
|
|
if (userCodeService) {
|
|
|
|
|
|
await userCodeService.waitForReady();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
await sceneManager.openScene();
|
|
|
|
|
|
const sceneState = sceneManager.getSceneState();
|
2025-12-09 18:04:03 +08:00
|
|
|
|
setStatus(t('scene.openedSuccess', { name: sceneState.sceneName }));
|
2025-10-17 18:13:31 +08:00
|
|
|
|
} catch (error) {
|
2025-11-02 23:50:41 +08:00
|
|
|
|
console.error('Failed to open scene:', error);
|
2025-12-09 18:04:03 +08:00
|
|
|
|
setStatus(t('scene.openFailed'));
|
2025-10-17 18:13:31 +08:00
|
|
|
|
}
|
2025-11-02 23:50:41 +08:00
|
|
|
|
};
|
2025-10-15 00:23:19 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
const handleOpenSceneByPath = useCallback(async (scenePath: string) => {
|
2025-12-16 12:46:14 +08:00
|
|
|
|
console.log('[App] handleOpenSceneByPath called:', scenePath);
|
2025-11-02 23:50:41 +08:00
|
|
|
|
if (!sceneManager) {
|
|
|
|
|
|
console.error('SceneManagerService not available');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2025-12-16 12:46:14 +08:00
|
|
|
|
// Wait for user code to be ready before loading scene
|
|
|
|
|
|
// 在加载场景前等待用户代码就绪
|
|
|
|
|
|
const userCodeService = Core.services.tryResolve(UserCodeService);
|
|
|
|
|
|
if (userCodeService) {
|
|
|
|
|
|
console.log('[App] Waiting for user code service...');
|
|
|
|
|
|
await userCodeService.waitForReady();
|
|
|
|
|
|
console.log('[App] User code service ready');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log('[App] Calling sceneManager.openScene...');
|
2025-11-02 23:50:41 +08:00
|
|
|
|
await sceneManager.openScene(scenePath);
|
2025-12-16 12:46:14 +08:00
|
|
|
|
console.log('[App] Scene opened successfully');
|
2025-11-02 23:50:41 +08:00
|
|
|
|
const sceneState = sceneManager.getSceneState();
|
2025-12-09 18:04:03 +08:00
|
|
|
|
setStatus(t('scene.openedSuccess', { name: sceneState.sceneName }));
|
2025-11-02 23:50:41 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Failed to open scene:', error);
|
2025-12-09 18:04:03 +08:00
|
|
|
|
setStatus(t('scene.openFailed'));
|
2025-11-02 23:50:41 +08:00
|
|
|
|
setErrorDialog({
|
2025-12-09 18:04:03 +08:00
|
|
|
|
title: t('scene.openFailed'),
|
|
|
|
|
|
message: `${t('scene.openFailed')}:\n${error instanceof Error ? error.message : String(error)}`
|
2025-10-17 18:13:31 +08:00
|
|
|
|
});
|
2025-11-02 23:50:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
}, [sceneManager, locale]);
|
2025-10-17 18:13:31 +08:00
|
|
|
|
|
2025-10-15 09:34:44 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
const handleSaveScene = async () => {
|
|
|
|
|
|
if (!sceneManager) {
|
|
|
|
|
|
console.error('SceneManagerService not available');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-10-15 09:34:44 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
try {
|
|
|
|
|
|
await sceneManager.saveScene();
|
|
|
|
|
|
const sceneState = sceneManager.getSceneState();
|
2025-12-09 18:04:03 +08:00
|
|
|
|
setStatus(t('scene.savedSuccess', { name: sceneState.sceneName }));
|
2025-11-02 23:50:41 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Failed to save scene:', error);
|
2025-12-09 18:04:03 +08:00
|
|
|
|
setStatus(t('scene.saveFailed'));
|
2025-11-02 23:50:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
};
|
2025-10-17 18:13:31 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
const handleSaveSceneAs = async () => {
|
|
|
|
|
|
if (!sceneManager) {
|
|
|
|
|
|
console.error('SceneManagerService not available');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-10-17 18:13:31 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
try {
|
|
|
|
|
|
await sceneManager.saveSceneAs();
|
|
|
|
|
|
const sceneState = sceneManager.getSceneState();
|
2025-12-09 18:04:03 +08:00
|
|
|
|
setStatus(t('scene.savedSuccess', { name: sceneState.sceneName }));
|
2025-11-02 23:50:41 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Failed to save scene as:', error);
|
2025-12-09 18:04:03 +08:00
|
|
|
|
setStatus(t('scene.saveAsFailed'));
|
2025-11-02 23:50:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
};
|
2025-10-17 18:13:31 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
const handleCloseProject = async () => {
|
2025-11-27 20:42:46 +08:00
|
|
|
|
// 卸载项目插件
|
2025-11-02 23:50:41 +08:00
|
|
|
|
if (pluginManager) {
|
2025-11-18 14:46:51 +08:00
|
|
|
|
await pluginLoader.unloadProjectPlugins(pluginManager);
|
2025-11-02 23:50:41 +08:00
|
|
|
|
}
|
2025-11-27 20:42:46 +08:00
|
|
|
|
|
2025-12-04 14:04:39 +08:00
|
|
|
|
// 清理场景(会清理所有实体和系统)
|
|
|
|
|
|
// Clear scene (clears all entities and systems)
|
|
|
|
|
|
const scene = Core.scene;
|
|
|
|
|
|
if (scene) {
|
|
|
|
|
|
scene.end();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 20:42:46 +08:00
|
|
|
|
// 清理模块系统
|
|
|
|
|
|
const engineService = EngineService.getInstance();
|
|
|
|
|
|
engineService.clearModuleSystems();
|
|
|
|
|
|
|
|
|
|
|
|
// 关闭 ProjectService 中的项目
|
|
|
|
|
|
const projectService = Core.services.tryResolve(ProjectService);
|
|
|
|
|
|
if (projectService) {
|
|
|
|
|
|
await projectService.closeProject();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
setProjectLoaded(false);
|
|
|
|
|
|
setCurrentProjectPath(null);
|
|
|
|
|
|
setStatus(t('header.status.ready'));
|
|
|
|
|
|
};
|
2025-10-15 18:24:13 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
const handleExit = () => {
|
|
|
|
|
|
window.close();
|
|
|
|
|
|
};
|
2025-10-17 18:13:31 +08:00
|
|
|
|
|
2025-11-23 21:45:10 +08:00
|
|
|
|
const handleLocaleChange = (newLocale: Locale) => {
|
2025-11-02 23:50:41 +08:00
|
|
|
|
changeLocale(newLocale);
|
2025-10-17 18:13:31 +08:00
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
// 通知所有已加载的插件更新语言 | Notify all loaded plugins to update locale
|
|
|
|
|
|
if (pluginManagerRef.current) {
|
|
|
|
|
|
pluginManagerRef.current.setLocale(newLocale);
|
2025-10-17 18:13:31 +08:00
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
// 通过 MessageHub 通知需要重新获取节点模板 | Notify via MessageHub to refetch node templates
|
|
|
|
|
|
if (messageHubRef.current) {
|
|
|
|
|
|
messageHubRef.current.publish('locale:changed', { locale: newLocale });
|
2025-11-02 23:50:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2025-10-15 18:24:13 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
const handleToggleDevtools = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await TauriAPI.toggleDevtools();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Failed to toggle devtools:', error);
|
2025-10-28 17:19:28 +08:00
|
|
|
|
}
|
2025-11-02 23:50:41 +08:00
|
|
|
|
};
|
2025-10-28 17:19:28 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
const handleOpenAbout = () => {
|
|
|
|
|
|
setShowAbout(true);
|
|
|
|
|
|
};
|
2025-10-15 09:34:44 +08:00
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
const handleCreatePlugin = () => {
|
|
|
|
|
|
setShowPluginGenerator(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-18 14:46:51 +08:00
|
|
|
|
const handleReloadPlugins = async () => {
|
2025-12-13 19:44:08 +08:00
|
|
|
|
if (currentProjectPath && pluginManagerRef.current) {
|
2025-11-18 14:46:51 +08:00
|
|
|
|
try {
|
2025-12-13 19:44:08 +08:00
|
|
|
|
// 1. 关闭所有动态面板 | Close all dynamic panels
|
|
|
|
|
|
clearDynamicPanels();
|
2025-11-18 14:46:51 +08:00
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
// 2. 清空当前面板列表(强制卸载插件面板组件)| Clear panel list (force unmount plugin panels)
|
2025-11-18 14:46:51 +08:00
|
|
|
|
setPanels((prev) => prev.filter((p) =>
|
|
|
|
|
|
['scene-hierarchy', 'inspector', 'console', 'asset-browser'].includes(p.id)
|
|
|
|
|
|
));
|
|
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
// 3. 等待React完成卸载 | Wait for React to unmount
|
2025-11-23 14:49:37 +08:00
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
2025-11-18 14:46:51 +08:00
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
// 4. 卸载所有项目插件(清理UIRegistry、调用uninstall)| Unload all project plugins
|
|
|
|
|
|
await pluginLoader.unloadProjectPlugins(pluginManagerRef.current);
|
2025-11-18 14:46:51 +08:00
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
// 5. 等待卸载完成 | Wait for unload
|
2025-11-23 14:49:37 +08:00
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
2025-11-18 14:46:51 +08:00
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
// 6. 重新加载插件 | Reload plugins
|
|
|
|
|
|
await pluginLoader.loadProjectPlugins(currentProjectPath, pluginManagerRef.current);
|
2025-11-18 14:46:51 +08:00
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
// 7. 触发面板重新渲染 | Trigger panel re-render
|
|
|
|
|
|
triggerPluginUpdate();
|
2025-11-18 14:46:51 +08:00
|
|
|
|
|
2025-12-09 18:04:03 +08:00
|
|
|
|
showToast(t('plugin.reloadedSuccess'), 'success');
|
2025-11-18 14:46:51 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Failed to reload plugins:', error);
|
2025-12-09 18:04:03 +08:00
|
|
|
|
showToast(t('plugin.reloadFailed'), 'error');
|
2025-11-18 14:46:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
// ===== 面板构建(拆分依赖减少重建)| Panel building (split deps to reduce rebuilds) =====
|
|
|
|
|
|
// 使用 ref 存储面板构建函数,避免频繁重建
|
|
|
|
|
|
// Use ref to store panel builder function to avoid frequent rebuilds
|
|
|
|
|
|
const buildPanelsRef = useRef<() => void>(() => {});
|
|
|
|
|
|
|
|
|
|
|
|
// 更新面板构建函数(不触发重渲染)| Update panel builder (no re-render)
|
|
|
|
|
|
buildPanelsRef.current = () => {
|
|
|
|
|
|
if (!projectLoaded || !initialized) return;
|
|
|
|
|
|
|
|
|
|
|
|
const hub = messageHubRef.current;
|
|
|
|
|
|
const store = entityStoreRef.current;
|
|
|
|
|
|
const registry = uiRegistryRef.current;
|
|
|
|
|
|
const inspReg = inspectorRegistryRef.current;
|
|
|
|
|
|
|
|
|
|
|
|
if (!hub || !store || !registry) return;
|
|
|
|
|
|
|
|
|
|
|
|
const corePanels: FlexDockPanel[] = [
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 'scene-hierarchy',
|
|
|
|
|
|
title: t('panel.sceneHierarchy'),
|
|
|
|
|
|
content: <SceneHierarchy entityStore={store} messageHub={hub} commandManager={commandManager} />,
|
|
|
|
|
|
closable: false,
|
|
|
|
|
|
layout: { position: 'right-top' }
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 'viewport',
|
|
|
|
|
|
title: t('panel.viewport'),
|
|
|
|
|
|
content: <Viewport locale={locale} messageHub={hub} commandManager={commandManager} />,
|
|
|
|
|
|
closable: false,
|
|
|
|
|
|
layout: { position: 'center' }
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 'inspector',
|
|
|
|
|
|
title: t('panel.inspector'),
|
|
|
|
|
|
content: <Inspector entityStore={store} messageHub={hub} inspectorRegistry={inspReg!} projectPath={currentProjectPath} commandManager={commandManager} />,
|
|
|
|
|
|
closable: false,
|
|
|
|
|
|
layout: { position: 'right-bottom' }
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 'forum',
|
|
|
|
|
|
title: t('panel.forum'),
|
|
|
|
|
|
content: <ForumPanel />,
|
|
|
|
|
|
closable: true,
|
|
|
|
|
|
layout: { position: 'center' }
|
|
|
|
|
|
}
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// 如果内容管理器已停靠,添加到面板 | If content browser is docked, add to panels
|
|
|
|
|
|
if (isContentBrowserDocked) {
|
|
|
|
|
|
corePanels.push({
|
|
|
|
|
|
id: 'content-browser',
|
|
|
|
|
|
title: t('panel.contentBrowser'),
|
|
|
|
|
|
content: (
|
|
|
|
|
|
<ContentBrowser
|
|
|
|
|
|
projectPath={currentProjectPath}
|
|
|
|
|
|
locale={locale}
|
|
|
|
|
|
onOpenScene={handleOpenSceneByPath}
|
|
|
|
|
|
isDrawer={false}
|
|
|
|
|
|
onDockInLayout={() => setIsContentBrowserDocked(false)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
),
|
|
|
|
|
|
closable: true,
|
|
|
|
|
|
layout: { position: 'bottom', weight: 20, requiresSeparateTabset: true }
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
// 获取启用的插件面板 | Get enabled plugin panels
|
|
|
|
|
|
const pluginPanels: FlexDockPanel[] = registry.getAllPanels()
|
|
|
|
|
|
.filter((panelDesc) => panelDesc.component && !panelDesc.isDynamic)
|
|
|
|
|
|
.map((panelDesc) => {
|
|
|
|
|
|
const Component = panelDesc.component!;
|
|
|
|
|
|
const title = panelDesc.titleKey ? t(panelDesc.titleKey) : panelDesc.title;
|
|
|
|
|
|
return {
|
|
|
|
|
|
id: panelDesc.id,
|
|
|
|
|
|
title,
|
|
|
|
|
|
content: <Component key={`${panelDesc.id}-${pluginUpdateTrigger}`} projectPath={currentProjectPath} />,
|
|
|
|
|
|
closable: panelDesc.closable ?? true
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 添加激活的动态面板 | Add active dynamic panels
|
|
|
|
|
|
const dynamicPanels: FlexDockPanel[] = activeDynamicPanels
|
|
|
|
|
|
.filter((panelId) => {
|
|
|
|
|
|
const panelDesc = registry.getPanel(panelId);
|
|
|
|
|
|
return panelDesc && (panelDesc.component || panelDesc.render);
|
|
|
|
|
|
})
|
|
|
|
|
|
.map((panelId) => {
|
|
|
|
|
|
const panelDesc = registry.getPanel(panelId)!;
|
|
|
|
|
|
const customTitle = dynamicPanelTitles.get(panelId);
|
|
|
|
|
|
const defaultTitle = panelDesc.titleKey ? t(panelDesc.titleKey) : panelDesc.title;
|
|
|
|
|
|
|
|
|
|
|
|
let content: React.ReactNode;
|
|
|
|
|
|
if (panelDesc.component) {
|
2025-11-02 23:50:41 +08:00
|
|
|
|
const Component = panelDesc.component;
|
2025-12-13 19:44:08 +08:00
|
|
|
|
content = <Component projectPath={currentProjectPath} locale={locale} />;
|
|
|
|
|
|
} else if (panelDesc.render) {
|
|
|
|
|
|
content = panelDesc.render();
|
|
|
|
|
|
}
|
2025-11-04 18:29:28 +08:00
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
return {
|
|
|
|
|
|
id: panelDesc.id,
|
|
|
|
|
|
title: customTitle || defaultTitle,
|
|
|
|
|
|
content,
|
|
|
|
|
|
closable: panelDesc.closable ?? true
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
2025-11-28 10:32:28 +08:00
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
setPanels([...corePanels, ...pluginPanels, ...dynamicPanels]);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Effect 1: 项目加载后首次构建面板 | Build panels after project loads
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (projectLoaded && initialized) {
|
|
|
|
|
|
buildPanelsRef.current();
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [projectLoaded, initialized]);
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-12-13 19:44:08 +08:00
|
|
|
|
// Effect 2: 插件更新时重建 | Rebuild on plugin update
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (projectLoaded && initialized && pluginUpdateTrigger > 0) {
|
|
|
|
|
|
buildPanelsRef.current();
|
2025-11-02 23:50:41 +08:00
|
|
|
|
}
|
2025-12-13 19:44:08 +08:00
|
|
|
|
}, [projectLoaded, initialized, pluginUpdateTrigger]);
|
|
|
|
|
|
|
|
|
|
|
|
// Effect 3: 动态面板变化时重建 | Rebuild on dynamic panel change
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (projectLoaded && initialized) {
|
|
|
|
|
|
buildPanelsRef.current();
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [projectLoaded, initialized, activeDynamicPanels, isContentBrowserDocked]);
|
|
|
|
|
|
|
|
|
|
|
|
// Effect 4: 语言变化时更新面板标题(不重建组件)| Update panel titles on locale change (don't rebuild components)
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (projectLoaded && initialized) {
|
|
|
|
|
|
// 只更新标题,不重建组件 | Only update titles, don't rebuild components
|
|
|
|
|
|
setPanels((prev) => prev.map(panel => ({
|
|
|
|
|
|
...panel,
|
|
|
|
|
|
title: panel.id === 'scene-hierarchy' ? t('panel.sceneHierarchy') :
|
|
|
|
|
|
panel.id === 'viewport' ? t('panel.viewport') :
|
|
|
|
|
|
panel.id === 'inspector' ? t('panel.inspector') :
|
|
|
|
|
|
panel.id === 'forum' ? t('panel.forum') :
|
|
|
|
|
|
panel.id === 'content-browser' ? t('panel.contentBrowser') :
|
|
|
|
|
|
panel.title
|
|
|
|
|
|
})));
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [locale, t, projectLoaded, initialized, setPanels]);
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!initialized) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="editor-loading">
|
|
|
|
|
|
<Loader2 size={32} className="animate-spin" />
|
|
|
|
|
|
<h2>Loading Editor...</h2>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
2025-10-15 09:58:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
if (!projectLoaded) {
|
|
|
|
|
|
const settings = SettingsService.getInstance();
|
|
|
|
|
|
const recentProjects = settings.getRecentProjects();
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<StartupPage
|
|
|
|
|
|
onOpenProject={handleOpenProject}
|
|
|
|
|
|
onCreateProject={handleCreateProject}
|
|
|
|
|
|
onOpenRecentProject={handleOpenRecentProject}
|
2025-12-04 14:04:39 +08:00
|
|
|
|
onRemoveRecentProject={(projectPath) => {
|
|
|
|
|
|
settings.removeRecentProject(projectPath);
|
|
|
|
|
|
// 强制重新渲染 | Force re-render
|
|
|
|
|
|
setStatus(t('header.status.ready'));
|
|
|
|
|
|
}}
|
|
|
|
|
|
onDeleteProject={async (projectPath) => {
|
2025-12-04 19:32:51 +08:00
|
|
|
|
console.log('[App] onDeleteProject called with path:', projectPath);
|
2025-12-04 14:04:39 +08:00
|
|
|
|
try {
|
2025-12-04 19:32:51 +08:00
|
|
|
|
console.log('[App] Calling TauriAPI.deleteFolder...');
|
2025-12-04 14:04:39 +08:00
|
|
|
|
await TauriAPI.deleteFolder(projectPath);
|
2025-12-04 19:32:51 +08:00
|
|
|
|
console.log('[App] deleteFolder succeeded');
|
2025-12-04 14:04:39 +08:00
|
|
|
|
// 删除成功后从列表中移除并触发重新渲染
|
|
|
|
|
|
// Remove from list and trigger re-render after successful deletion
|
|
|
|
|
|
settings.removeRecentProject(projectPath);
|
|
|
|
|
|
setStatus(t('header.status.ready'));
|
|
|
|
|
|
} catch (error) {
|
2025-12-04 19:32:51 +08:00
|
|
|
|
console.error('[App] Failed to delete project:', error);
|
2025-12-04 14:04:39 +08:00
|
|
|
|
setErrorDialog({
|
2025-12-09 18:04:03 +08:00
|
|
|
|
title: t('project.deleteFailed'),
|
|
|
|
|
|
message: `${t('project.deleteFailed')}:\n${error instanceof Error ? error.message : String(error)}`
|
2025-12-04 14:04:39 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}}
|
2025-11-23 21:45:10 +08:00
|
|
|
|
onLocaleChange={handleLocaleChange}
|
2025-11-02 23:50:41 +08:00
|
|
|
|
recentProjects={recentProjects}
|
|
|
|
|
|
/>
|
2025-11-23 21:45:10 +08:00
|
|
|
|
<ProjectCreationWizard
|
|
|
|
|
|
isOpen={showProjectWizard}
|
|
|
|
|
|
onClose={() => setShowProjectWizard(false)}
|
|
|
|
|
|
onCreateProject={handleCreateProjectFromWizard}
|
|
|
|
|
|
onBrowsePath={handleBrowseProjectPath}
|
|
|
|
|
|
locale={locale}
|
|
|
|
|
|
/>
|
2025-11-02 23:50:41 +08:00
|
|
|
|
{isLoading && (
|
|
|
|
|
|
<div className="loading-overlay">
|
|
|
|
|
|
<div className="loading-content">
|
|
|
|
|
|
<Loader2 size={40} className="animate-spin" />
|
|
|
|
|
|
<p className="loading-message">{loadingMessage}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{errorDialog && (
|
|
|
|
|
|
<ErrorDialog
|
|
|
|
|
|
title={errorDialog.title}
|
|
|
|
|
|
message={errorDialog.message}
|
|
|
|
|
|
onClose={() => setErrorDialog(null)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{confirmDialog && (
|
|
|
|
|
|
<ConfirmDialog
|
|
|
|
|
|
title={confirmDialog.title}
|
|
|
|
|
|
message={confirmDialog.message}
|
|
|
|
|
|
confirmText={confirmDialog.confirmText}
|
|
|
|
|
|
cancelText={confirmDialog.cancelText}
|
2025-11-18 14:46:51 +08:00
|
|
|
|
onConfirm={() => {
|
|
|
|
|
|
confirmDialog.onConfirm();
|
|
|
|
|
|
setConfirmDialog(null);
|
|
|
|
|
|
}}
|
|
|
|
|
|
onCancel={() => {
|
|
|
|
|
|
if (confirmDialog.onCancel) {
|
|
|
|
|
|
confirmDialog.onCancel();
|
|
|
|
|
|
}
|
|
|
|
|
|
setConfirmDialog(null);
|
|
|
|
|
|
}}
|
2025-11-02 23:50:41 +08:00
|
|
|
|
/>
|
|
|
|
|
|
)}
|
2025-12-16 12:46:14 +08:00
|
|
|
|
{externalModificationDialog && (
|
|
|
|
|
|
<ExternalModificationDialog
|
|
|
|
|
|
sceneName={externalModificationDialog.sceneName}
|
|
|
|
|
|
onReload={externalModificationDialog.onReload}
|
|
|
|
|
|
onOverwrite={externalModificationDialog.onOverwrite}
|
|
|
|
|
|
onCancel={() => setExternalModificationDialog(null)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
2025-11-02 23:50:41 +08:00
|
|
|
|
</>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2025-10-15 17:15:05 +08:00
|
|
|
|
|
2025-11-23 21:45:10 +08:00
|
|
|
|
const projectName = currentProjectPath ? currentProjectPath.split(/[\\/]/).pop() : 'Untitled';
|
|
|
|
|
|
|
2025-10-15 09:34:44 +08:00
|
|
|
|
return (
|
2025-11-02 23:50:41 +08:00
|
|
|
|
<div className="editor-container">
|
2025-11-04 23:53:26 +08:00
|
|
|
|
{!isEditorFullscreen && (
|
2025-11-23 21:45:10 +08:00
|
|
|
|
<>
|
2025-11-29 23:00:48 +08:00
|
|
|
|
<TitleBar
|
|
|
|
|
|
projectName={projectName}
|
2025-11-04 23:53:26 +08:00
|
|
|
|
uiRegistry={uiRegistry || undefined}
|
|
|
|
|
|
messageHub={messageHub || undefined}
|
|
|
|
|
|
pluginManager={pluginManager || undefined}
|
|
|
|
|
|
onNewScene={handleNewScene}
|
|
|
|
|
|
onOpenScene={handleOpenScene}
|
|
|
|
|
|
onSaveScene={handleSaveScene}
|
|
|
|
|
|
onSaveSceneAs={handleSaveSceneAs}
|
|
|
|
|
|
onOpenProject={handleOpenProject}
|
|
|
|
|
|
onCloseProject={handleCloseProject}
|
|
|
|
|
|
onExit={handleExit}
|
2025-11-27 20:42:46 +08:00
|
|
|
|
onOpenPluginManager={() => {
|
|
|
|
|
|
setSettingsInitialCategory('plugins');
|
|
|
|
|
|
setShowSettings(true);
|
|
|
|
|
|
}}
|
2025-11-04 23:53:26 +08:00
|
|
|
|
onOpenProfiler={() => setShowProfiler(true)}
|
|
|
|
|
|
onOpenPortManager={() => setShowPortManager(true)}
|
|
|
|
|
|
onOpenSettings={() => setShowSettings(true)}
|
|
|
|
|
|
onToggleDevtools={handleToggleDevtools}
|
|
|
|
|
|
onOpenAbout={handleOpenAbout}
|
|
|
|
|
|
onCreatePlugin={handleCreatePlugin}
|
2025-11-18 14:46:51 +08:00
|
|
|
|
onReloadPlugins={handleReloadPlugins}
|
2025-12-03 22:15:22 +08:00
|
|
|
|
onOpenBuildSettings={() => setShowBuildSettings(true)}
|
2025-12-16 12:46:14 +08:00
|
|
|
|
onOpenRenderDebug={() => setShowRenderDebug(true)}
|
2025-11-04 23:53:26 +08:00
|
|
|
|
/>
|
2025-11-29 23:00:48 +08:00
|
|
|
|
<MainToolbar
|
|
|
|
|
|
messageHub={messageHub || undefined}
|
|
|
|
|
|
commandManager={commandManager}
|
|
|
|
|
|
onSaveScene={handleSaveScene}
|
|
|
|
|
|
onOpenScene={handleOpenScene}
|
|
|
|
|
|
/>
|
2025-11-23 21:45:10 +08:00
|
|
|
|
</>
|
2025-11-04 23:53:26 +08:00
|
|
|
|
)}
|
2025-10-15 09:34:44 +08:00
|
|
|
|
|
2025-11-18 14:46:51 +08:00
|
|
|
|
<CompilerConfigDialog
|
|
|
|
|
|
isOpen={compilerDialog.isOpen}
|
|
|
|
|
|
compilerId={compilerDialog.compilerId}
|
|
|
|
|
|
projectPath={currentProjectPath}
|
|
|
|
|
|
currentFileName={compilerDialog.currentFileName}
|
2025-12-13 19:44:08 +08:00
|
|
|
|
onClose={closeCompilerDialog}
|
2025-11-18 14:46:51 +08:00
|
|
|
|
onCompileComplete={(result) => {
|
|
|
|
|
|
if (result.success) {
|
|
|
|
|
|
showToast(result.message, 'success');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
showToast(result.message, 'error');
|
|
|
|
|
|
}
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
<div className="editor-content">
|
2025-11-04 18:29:28 +08:00
|
|
|
|
<FlexLayoutDockContainer
|
2025-12-13 19:44:08 +08:00
|
|
|
|
ref={layoutContainerRef}
|
2025-11-04 18:29:28 +08:00
|
|
|
|
panels={panels}
|
2025-11-04 23:53:26 +08:00
|
|
|
|
activePanelId={activePanelId}
|
2025-12-13 19:44:08 +08:00
|
|
|
|
messageHub={messageHubRef.current}
|
2025-11-04 18:29:28 +08:00
|
|
|
|
onPanelClose={(panelId) => {
|
2025-11-04 23:53:26 +08:00
|
|
|
|
logger.info('Panel closed:', panelId);
|
2025-12-13 19:44:08 +08:00
|
|
|
|
// 如果关闭的是内容管理器,重置停靠状态
|
|
|
|
|
|
// If closing content browser, reset dock state
|
|
|
|
|
|
if (panelId === 'content-browser') {
|
|
|
|
|
|
setIsContentBrowserDocked(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
removeDynamicPanel(panelId);
|
2025-11-04 18:29:28 +08:00
|
|
|
|
}}
|
|
|
|
|
|
/>
|
2025-11-02 23:50:41 +08:00
|
|
|
|
</div>
|
2025-10-16 17:10:22 +08:00
|
|
|
|
|
2025-11-29 23:00:48 +08:00
|
|
|
|
<StatusBar
|
|
|
|
|
|
pluginCount={pluginManager?.getAllPlugins().length ?? 0}
|
|
|
|
|
|
entityCount={entityStore?.getAllEntities().length ?? 0}
|
|
|
|
|
|
messageHub={messageHub}
|
|
|
|
|
|
logService={logService}
|
|
|
|
|
|
locale={locale}
|
|
|
|
|
|
projectPath={currentProjectPath}
|
|
|
|
|
|
onOpenScene={handleOpenSceneByPath}
|
2025-12-13 19:44:08 +08:00
|
|
|
|
onDockContentBrowser={() => setIsContentBrowserDocked(true)}
|
|
|
|
|
|
onResetLayout={() => layoutContainerRef.current?.resetLayout()}
|
2025-11-29 23:00:48 +08:00
|
|
|
|
/>
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
|
|
|
|
|
|
2025-12-01 22:28:51 +08:00
|
|
|
|
{(showProfiler || showAdvancedProfiler) && (
|
|
|
|
|
|
<AdvancedProfilerWindow onClose={() => {
|
|
|
|
|
|
setShowProfiler(false);
|
|
|
|
|
|
setShowAdvancedProfiler(false);
|
|
|
|
|
|
}} />
|
2025-11-30 00:22:47 +08:00
|
|
|
|
)}
|
|
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
{showPortManager && (
|
|
|
|
|
|
<PortManager onClose={() => setShowPortManager(false)} />
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{showSettings && settingsRegistry && (
|
2025-11-27 20:42:46 +08:00
|
|
|
|
<SettingsWindow
|
|
|
|
|
|
onClose={() => {
|
|
|
|
|
|
setShowSettings(false);
|
|
|
|
|
|
setSettingsInitialCategory(undefined);
|
|
|
|
|
|
}}
|
|
|
|
|
|
settingsRegistry={settingsRegistry}
|
|
|
|
|
|
initialCategoryId={settingsInitialCategory}
|
|
|
|
|
|
/>
|
2025-11-02 23:50:41 +08:00
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{showAbout && (
|
2025-12-09 18:04:03 +08:00
|
|
|
|
<AboutDialog onClose={() => setShowAbout(false)} />
|
2025-11-02 23:50:41 +08:00
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{showPluginGenerator && (
|
|
|
|
|
|
<PluginGeneratorWindow
|
|
|
|
|
|
onClose={() => setShowPluginGenerator(false)}
|
|
|
|
|
|
projectPath={currentProjectPath}
|
|
|
|
|
|
onSuccess={async () => {
|
|
|
|
|
|
if (currentProjectPath && pluginManager) {
|
2025-11-18 14:46:51 +08:00
|
|
|
|
await pluginLoader.loadProjectPlugins(currentProjectPath, pluginManager);
|
2025-11-02 23:50:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2025-12-03 22:15:22 +08:00
|
|
|
|
{showBuildSettings && (
|
|
|
|
|
|
<BuildSettingsWindow
|
|
|
|
|
|
onClose={() => setShowBuildSettings(false)}
|
|
|
|
|
|
projectPath={currentProjectPath || undefined}
|
|
|
|
|
|
buildService={buildService || undefined}
|
|
|
|
|
|
sceneManager={sceneManager || undefined}
|
2025-12-10 18:23:29 +08:00
|
|
|
|
projectService={projectServiceState || undefined}
|
|
|
|
|
|
availableScenes={availableScenes}
|
2025-12-03 22:15:22 +08:00
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2025-12-16 12:46:14 +08:00
|
|
|
|
{/* 渲染调试面板 | Render Debug Panel */}
|
|
|
|
|
|
<RenderDebugPanel
|
|
|
|
|
|
visible={showRenderDebug}
|
|
|
|
|
|
onClose={() => setShowRenderDebug(false)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
2025-11-02 23:50:41 +08:00
|
|
|
|
{errorDialog && (
|
|
|
|
|
|
<ErrorDialog
|
|
|
|
|
|
title={errorDialog.title}
|
|
|
|
|
|
message={errorDialog.message}
|
|
|
|
|
|
onClose={() => setErrorDialog(null)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
2025-11-18 14:46:51 +08:00
|
|
|
|
|
|
|
|
|
|
{confirmDialog && (
|
|
|
|
|
|
<ConfirmDialog
|
|
|
|
|
|
title={confirmDialog.title}
|
|
|
|
|
|
message={confirmDialog.message}
|
|
|
|
|
|
confirmText={confirmDialog.confirmText}
|
|
|
|
|
|
cancelText={confirmDialog.cancelText}
|
|
|
|
|
|
onConfirm={() => {
|
|
|
|
|
|
confirmDialog.onConfirm();
|
|
|
|
|
|
setConfirmDialog(null);
|
|
|
|
|
|
}}
|
|
|
|
|
|
onCancel={() => {
|
|
|
|
|
|
if (confirmDialog.onCancel) {
|
|
|
|
|
|
confirmDialog.onCancel();
|
|
|
|
|
|
}
|
|
|
|
|
|
setConfirmDialog(null);
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
2025-12-16 12:46:14 +08:00
|
|
|
|
|
|
|
|
|
|
{externalModificationDialog && (
|
|
|
|
|
|
<ExternalModificationDialog
|
|
|
|
|
|
sceneName={externalModificationDialog.sceneName}
|
|
|
|
|
|
onReload={externalModificationDialog.onReload}
|
|
|
|
|
|
onOverwrite={externalModificationDialog.onOverwrite}
|
|
|
|
|
|
onCancel={() => setExternalModificationDialog(null)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
2025-10-14 23:31:09 +08:00
|
|
|
|
</div>
|
2025-11-02 23:50:41 +08:00
|
|
|
|
);
|
2025-10-14 22:53:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-27 09:29:11 +08:00
|
|
|
|
function AppWithToast() {
|
2025-11-02 23:50:41 +08:00
|
|
|
|
return (
|
|
|
|
|
|
<ToastProvider>
|
|
|
|
|
|
<App />
|
|
|
|
|
|
</ToastProvider>
|
|
|
|
|
|
);
|
2025-10-27 09:29:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default AppWithToast;
|