/** * Build Settings Panel. * 构建设置面板。 * * Provides build settings interface for managing platform builds, * scenes, and player settings. * 提供构建设置界面,用于管理平台构建、场景和玩家设置。 */ import { useState, useEffect, useCallback, useRef } from 'react'; import { Monitor, Apple, Smartphone, Globe, Server, Gamepad2, Plus, Minus, ChevronDown, ChevronRight, Settings, Package, Loader2, CheckCircle, XCircle, AlertTriangle, X } from 'lucide-react'; import type { BuildService, BuildProgress, BuildConfig, WebBuildConfig, WeChatBuildConfig, SceneManagerService } from '@esengine/editor-core'; import { BuildPlatform, BuildStatus } from '@esengine/editor-core'; import '../styles/BuildSettingsPanel.css'; // ==================== Types | 类型定义 ==================== /** Platform type | 平台类型 */ type PlatformType = | 'windows' | 'macos' | 'linux' | 'android' | 'ios' | 'web' | 'wechat-minigame'; /** Build profile | 构建配置 */ interface BuildProfile { id: string; name: string; platform: PlatformType; isActive?: boolean; } /** Scene entry | 场景条目 */ interface SceneEntry { path: string; enabled: boolean; } /** Platform configuration | 平台配置 */ interface PlatformConfig { platform: PlatformType; label: string; icon: React.ReactNode; available: boolean; } /** Build settings | 构建设置 */ interface BuildSettings { scenes: SceneEntry[]; scriptingDefines: string[]; companyName: string; productName: string; version: string; // Platform-specific | 平台特定 developmentBuild: boolean; sourceMap: boolean; compressionMethod: 'Default' | 'LZ4' | 'LZ4HC'; bundleModules: boolean; } // ==================== Constants | 常量 ==================== const PLATFORMS: PlatformConfig[] = [ { platform: 'windows', label: 'Windows', icon: , available: true }, { platform: 'macos', label: 'macOS', icon: , available: true }, { platform: 'linux', label: 'Linux', icon: , available: true }, { platform: 'android', label: 'Android', icon: , available: true }, { platform: 'ios', label: 'iOS', icon: , available: true }, { platform: 'web', label: 'Web', icon: , available: true }, { platform: 'wechat-minigame', label: 'WeChat Mini Game', icon: , available: true }, ]; const DEFAULT_SETTINGS: BuildSettings = { scenes: [], scriptingDefines: [], companyName: 'DefaultCompany', productName: 'MyGame', version: '0.1.0', developmentBuild: false, sourceMap: false, compressionMethod: 'Default', bundleModules: false, }; // ==================== i18n | 国际化 ==================== const i18n = { en: { buildProfiles: 'Build Profiles', addBuildProfile: 'Add Build Profile', playerSettings: 'Player Settings', assetImportOverrides: 'Asset Import Overrides', platforms: 'Platforms', sceneList: 'Scene List', active: 'Active', switchProfile: 'Switch Profile', build: 'Build', buildAndRun: 'Build And Run', buildData: 'Build Data', scriptingDefines: 'Scripting Defines', listIsEmpty: 'List is empty', addOpenScenes: 'Add Open Scenes', platformSettings: 'Platform Settings', architecture: 'Architecture', developmentBuild: 'Development Build', sourceMap: 'Source Map', compressionMethod: 'Compression Method', bundleModules: 'Bundle Modules', bundleModulesHint: 'Merge all modules into single file', separateModulesHint: 'Keep modules as separate files', playerSettingsOverrides: 'Player Settings Overrides', companyName: 'Company Name', productName: 'Product Name', version: 'Version', defaultIcon: 'Default Icon', none: 'None', // Build progress | 构建进度 buildInProgress: 'Build in Progress', preparing: 'Preparing...', compiling: 'Compiling...', packaging: 'Packaging assets...', copying: 'Copying files...', postProcessing: 'Post-processing...', completed: 'Completed', failed: 'Failed', cancelled: 'Cancelled', cancel: 'Cancel', close: 'Close', buildSucceeded: 'Build succeeded!', buildFailed: 'Build failed', warnings: 'Warnings', outputPath: 'Output Path', duration: 'Duration', }, zh: { buildProfiles: '构建配置', addBuildProfile: '添加构建配置', playerSettings: '玩家设置', assetImportOverrides: '资源导入覆盖', platforms: '平台', sceneList: '场景列表', active: '激活', switchProfile: '切换配置', build: '构建', buildAndRun: '构建并运行', buildData: '构建数据', scriptingDefines: '脚本定义', listIsEmpty: '列表为空', addOpenScenes: '添加已打开的场景', platformSettings: '平台设置', architecture: '架构', developmentBuild: '开发版本', sourceMap: 'Source Map', compressionMethod: '压缩方式', bundleModules: '打包模块', bundleModulesHint: '合并所有模块为单文件', separateModulesHint: '保持模块为独立文件', playerSettingsOverrides: '玩家设置覆盖', companyName: '公司名称', productName: '产品名称', version: '版本', defaultIcon: '默认图标', none: '无', // Build progress | 构建进度 buildInProgress: '正在构建', preparing: '准备中...', compiling: '编译中...', packaging: '打包资源...', copying: '复制文件...', postProcessing: '后处理...', completed: '已完成', failed: '失败', cancelled: '已取消', cancel: '取消', close: '关闭', buildSucceeded: '构建成功!', buildFailed: '构建失败', warnings: '警告', outputPath: '输出路径', duration: '耗时', } }; // ==================== Props | 属性 ==================== interface BuildSettingsPanelProps { projectPath?: string; locale?: string; buildService?: BuildService; sceneManager?: SceneManagerService; onBuild?: (profile: BuildProfile, settings: BuildSettings) => void; onClose?: () => void; } // ==================== Component | 组件 ==================== export function BuildSettingsPanel({ projectPath, locale = 'en', buildService, sceneManager, onBuild, onClose }: BuildSettingsPanelProps) { const t = i18n[locale as keyof typeof i18n] || i18n.en; // State | 状态 const [profiles, setProfiles] = useState([ { id: 'web-dev', name: 'Web - Development', platform: 'web', isActive: true }, { id: 'web-prod', name: 'Web - Production', platform: 'web' }, { id: 'wechat', name: 'WeChat Mini Game', platform: 'wechat-minigame' }, ]); const [selectedPlatform, setSelectedPlatform] = useState('web'); const [selectedProfile, setSelectedProfile] = useState(profiles[0] || null); const [settings, setSettings] = useState(DEFAULT_SETTINGS); const [expandedSections, setExpandedSections] = useState>({ sceneList: true, scriptingDefines: true, platformSettings: true, playerSettings: true, }); // Build state | 构建状态 const [isBuilding, setIsBuilding] = useState(false); const [buildProgress, setBuildProgress] = useState(null); const [buildResult, setBuildResult] = useState<{ success: boolean; outputPath: string; duration: number; warnings: string[]; error?: string; } | null>(null); const [showBuildProgress, setShowBuildProgress] = useState(false); const buildAbortRef = useRef(null); // Handlers | 处理函数 const toggleSection = useCallback((section: string) => { setExpandedSections(prev => ({ ...prev, [section]: !prev[section] })); }, []); const handlePlatformSelect = useCallback((platform: PlatformType) => { setSelectedPlatform(platform); // Find first profile for this platform | 查找此平台的第一个配置 const profile = profiles.find(p => p.platform === platform); setSelectedProfile(profile || null); }, [profiles]); const handleProfileSelect = useCallback((profile: BuildProfile) => { setSelectedProfile(profile); setSelectedPlatform(profile.platform); }, []); const handleAddProfile = useCallback(() => { const newProfile: BuildProfile = { id: `profile-${Date.now()}`, name: `${selectedPlatform} - New Profile`, platform: selectedPlatform, }; setProfiles(prev => [...prev, newProfile]); setSelectedProfile(newProfile); }, [selectedPlatform]); // Map platform type to BuildPlatform enum | 将平台类型映射到 BuildPlatform 枚举 const getPlatformEnum = useCallback((platformType: PlatformType): BuildPlatform => { const platformMap: Record = { 'web': BuildPlatform.Web, 'wechat-minigame': BuildPlatform.WeChatMiniGame, 'windows': BuildPlatform.Desktop, 'macos': BuildPlatform.Desktop, 'linux': BuildPlatform.Desktop, 'android': BuildPlatform.Android, 'ios': BuildPlatform.iOS }; return platformMap[platformType]; }, []); const handleBuild = useCallback(async () => { if (!selectedProfile || !projectPath) { return; } // Call external handler if provided | 如果提供了外部处理程序则调用 if (onBuild) { onBuild(selectedProfile, settings); } // Use BuildService if available | 如果可用则使用 BuildService if (buildService) { setIsBuilding(true); setBuildProgress(null); setBuildResult(null); setShowBuildProgress(true); try { const platform = getPlatformEnum(selectedProfile.platform); const baseConfig = { platform, outputPath: `${projectPath}/build/${selectedProfile.platform}`, isRelease: !settings.developmentBuild, sourceMap: settings.sourceMap, scenes: settings.scenes.filter(s => s.enabled).map(s => s.path) }; // Build platform-specific config | 构建平台特定配置 let buildConfig: BuildConfig; if (platform === BuildPlatform.Web) { const webConfig: WebBuildConfig = { ...baseConfig, platform: BuildPlatform.Web, format: 'iife', bundleModules: settings.bundleModules, generateHtml: true }; buildConfig = webConfig; } else if (platform === BuildPlatform.WeChatMiniGame) { const wechatConfig: WeChatBuildConfig = { ...baseConfig, platform: BuildPlatform.WeChatMiniGame, appId: '', useSubpackages: false, mainPackageLimit: 4096, usePlugins: false }; buildConfig = wechatConfig; } else { buildConfig = baseConfig; } // Execute build with progress callback | 执行构建并传入进度回调 const result = await buildService.build(buildConfig, (progress) => { setBuildProgress(progress); }); // Set result | 设置结果 setBuildResult({ success: result.success, outputPath: result.outputPath, duration: result.duration, warnings: result.warnings, error: result.error }); } catch (error) { console.error('Build failed:', error); setBuildResult({ success: false, outputPath: '', duration: 0, warnings: [], error: error instanceof Error ? error.message : String(error) }); } finally { setIsBuilding(false); } } }, [selectedProfile, settings, projectPath, buildService, onBuild, getPlatformEnum]); // Monitor build progress from service | 从服务监控构建进度 useEffect(() => { if (!buildService || !isBuilding) { return; } const interval = setInterval(() => { const task = buildService.getCurrentTask(); if (task) { setBuildProgress(task.progress); } }, 100); return () => clearInterval(interval); }, [buildService, isBuilding]); const handleCancelBuild = useCallback(() => { if (buildService) { buildService.cancelBuild(); } }, [buildService]); const handleCloseBuildProgress = useCallback(() => { if (!isBuilding) { setShowBuildProgress(false); setBuildProgress(null); setBuildResult(null); } }, [isBuilding]); // Get status message | 获取状态消息 const getStatusMessage = useCallback((status: BuildStatus): string => { const statusMessages: Record = { [BuildStatus.Idle]: 'preparing', [BuildStatus.Preparing]: 'preparing', [BuildStatus.Compiling]: 'compiling', [BuildStatus.Packaging]: 'packaging', [BuildStatus.Copying]: 'copying', [BuildStatus.PostProcessing]: 'postProcessing', [BuildStatus.Completed]: 'completed', [BuildStatus.Failed]: 'failed', [BuildStatus.Cancelled]: 'cancelled' }; return t[statusMessages[status]] || status; }, [t]); const handleAddScene = useCallback(() => { if (!sceneManager) { console.warn('SceneManagerService not available'); return; } const sceneState = sceneManager.getSceneState(); const currentScenePath = sceneState.currentScenePath; if (!currentScenePath) { console.warn('No scene is currently open'); return; } // Check if scene is already in the list | 检查场景是否已在列表中 const exists = settings.scenes.some(s => s.path === currentScenePath); if (exists) { console.log('Scene already in list:', currentScenePath); return; } // Add current scene to the list | 将当前场景添加到列表中 setSettings(prev => ({ ...prev, scenes: [...prev.scenes, { path: currentScenePath, enabled: true }] })); }, [sceneManager, settings.scenes]); const handleAddDefine = useCallback(() => { const define = prompt('Enter scripting define:'); if (define) { setSettings(prev => ({ ...prev, scriptingDefines: [...prev.scriptingDefines, define] })); } }, []); const handleRemoveDefine = useCallback((index: number) => { setSettings(prev => ({ ...prev, scriptingDefines: prev.scriptingDefines.filter((_, i) => i !== index) })); }, []); // Get platform config | 获取平台配置 const currentPlatformConfig = PLATFORMS.find(p => p.platform === selectedPlatform); return ( {/* Header Tabs | 头部标签 */} {t.buildProfiles} {t.playerSettings} {t.assetImportOverrides} {/* Add Profile Bar | 添加配置栏 */} {t.addBuildProfile} {/* Main Content | 主要内容 */} {/* Left Sidebar | 左侧边栏 */} {/* Platforms Section | 平台部分 */} {t.platforms} {PLATFORMS.map(platform => { const isActive = profiles.some(p => p.platform === platform.platform && p.isActive); return ( handlePlatformSelect(platform.platform)} > {platform.icon} {platform.label} {isActive && {t.active}} ); })} {/* Build Profiles Section | 构建配置部分 */} {t.buildProfiles} {profiles .filter(p => p.platform === selectedPlatform) .map(profile => ( handleProfileSelect(profile)} > {currentPlatformConfig?.icon} {profile.name} ))} {/* Right Panel | 右侧面板 */} {selectedProfile ? ( <> {/* Profile Header | 配置头部 */} {currentPlatformConfig?.icon} {selectedProfile.name} {currentPlatformConfig?.label} {t.switchProfile} {t.build} {/* Build Data Section | 构建数据部分 */} {t.buildData} {/* Scene List | 场景列表 */} toggleSection('sceneList')} > {expandedSections.sceneList ? : } {t.sceneList} {expandedSections.sceneList && ( {settings.scenes.length === 0 ? ( ) : ( settings.scenes.map((scene, index) => ( {scene.path} )) )} {t.addOpenScenes} )} {/* Scripting Defines | 脚本定义 */} toggleSection('scriptingDefines')} > {expandedSections.scriptingDefines ? : } {t.scriptingDefines} {expandedSections.scriptingDefines && ( {settings.scriptingDefines.length === 0 ? ( {t.listIsEmpty} ) : ( settings.scriptingDefines.map((define, index) => ( {define} handleRemoveDefine(index)}> )) )} )} {/* Platform Settings Section | 平台设置部分 */} {t.platformSettings} toggleSection('platformSettings')} > {expandedSections.platformSettings ? : } {currentPlatformConfig?.label} Settings {expandedSections.platformSettings && ( {t.developmentBuild} setSettings(prev => ({ ...prev, developmentBuild: e.target.checked }))} /> {t.sourceMap} setSettings(prev => ({ ...prev, sourceMap: e.target.checked }))} /> {t.compressionMethod} setSettings(prev => ({ ...prev, compressionMethod: e.target.value as any }))} > Default LZ4 LZ4HC {t.bundleModules} setSettings(prev => ({ ...prev, bundleModules: e.target.checked }))} /> {settings.bundleModules ? t.bundleModulesHint : t.separateModulesHint} )} {/* Player Settings Overrides | 玩家设置覆盖 */} {t.playerSettingsOverrides} toggleSection('playerSettings')} > {expandedSections.playerSettings ? : } Player Settings {expandedSections.playerSettings && ( {t.companyName} setSettings(prev => ({ ...prev, companyName: e.target.value }))} /> {t.productName} setSettings(prev => ({ ...prev, productName: e.target.value }))} /> {t.version} setSettings(prev => ({ ...prev, version: e.target.value }))} /> {t.defaultIcon} {t.none} (Texture 2D) )} > ) : ( Select a platform or build profile )} {/* Build Progress Dialog | 构建进度对话框 */} {showBuildProgress && ( {t.buildInProgress} {!isBuilding && ( )} {/* Status Icon | 状态图标 */} {isBuilding ? ( ) : buildResult?.success ? ( ) : ( )} {/* Status Message | 状态消息 */} {isBuilding ? ( buildProgress?.message || getStatusMessage(buildProgress?.status || BuildStatus.Preparing) ) : buildResult?.success ? ( t.buildSucceeded ) : ( t.buildFailed )} {/* Progress Bar | 进度条 */} {isBuilding && buildProgress && ( {Math.round(buildProgress.progress)}% )} {/* Build Result Details | 构建结果详情 */} {!isBuilding && buildResult && ( {buildResult.success && ( <> {t.outputPath}: {buildResult.outputPath} {t.duration}: {(buildResult.duration / 1000).toFixed(2)}s > )} {/* Error Message | 错误消息 */} {buildResult.error && ( {buildResult.error} )} {/* Warnings | 警告 */} {buildResult.warnings.length > 0 && ( {t.warnings} ({buildResult.warnings.length}) {buildResult.warnings.map((warning, index) => ( {warning} ))} )} )} {/* Actions | 操作按钮 */} {isBuilding ? ( {t.cancel} ) : ( {t.close} )} )} ); } export default BuildSettingsPanel;
Select a platform or build profile