import { useState, useEffect } from 'react'; import { X, File, FolderTree, FolderOpen } from 'lucide-react'; import { open } from '@tauri-apps/plugin-dialog'; import { useLocale } from '../hooks/useLocale'; import '../styles/ExportRuntimeDialog.css'; interface ExportRuntimeDialogProps { isOpen: boolean; onClose: () => void; onExport: (options: ExportOptions) => void; hasProject: boolean; availableFiles: string[]; currentFileName?: string; projectPath?: string; } export interface ExportOptions { mode: 'single' | 'workspace'; assetOutputPath: string; typeOutputPath: string; selectedFiles: string[]; fileFormats: Map; } /** * 导出运行时资产对话框 */ export const ExportRuntimeDialog: React.FC = ({ isOpen, onClose, onExport, hasProject, availableFiles, currentFileName, projectPath }) => { const { t } = useLocale(); const [selectedMode, setSelectedMode] = useState<'single' | 'workspace'>('workspace'); const [assetOutputPath, setAssetOutputPath] = useState(''); const [typeOutputPath, setTypeOutputPath] = useState(''); const [selectedFiles, setSelectedFiles] = useState>(new Set()); const [fileFormats, setFileFormats] = useState>(new Map()); const [selectAll, setSelectAll] = useState(true); const [isExporting, setIsExporting] = useState(false); const [exportProgress, setExportProgress] = useState(0); const [exportMessage, setExportMessage] = useState(''); // 从 localStorage 加载上次的路径 useEffect(() => { if (isOpen && projectPath) { const savedAssetPath = localStorage.getItem('export-asset-path'); const savedTypePath = localStorage.getItem('export-type-path'); if (savedAssetPath) { setAssetOutputPath(savedAssetPath); } if (savedTypePath) { setTypeOutputPath(savedTypePath); } } }, [isOpen, projectPath]); useEffect(() => { if (isOpen) { if (selectedMode === 'workspace') { const newSelectedFiles = new Set(availableFiles); setSelectedFiles(newSelectedFiles); setSelectAll(true); const newFormats = new Map(); availableFiles.forEach((file) => { newFormats.set(file, 'binary'); }); setFileFormats(newFormats); } else { setSelectedFiles(new Set()); setSelectAll(false); if (currentFileName) { const newFormats = new Map(); newFormats.set(currentFileName, 'binary'); setFileFormats(newFormats); } } } }, [isOpen, selectedMode, availableFiles, currentFileName]); if (!isOpen) return null; const handleSelectAll = () => { if (selectAll) { setSelectedFiles(new Set()); setSelectAll(false); } else { setSelectedFiles(new Set(availableFiles)); setSelectAll(true); } }; const handleToggleFile = (file: string) => { const newSelected = new Set(selectedFiles); if (newSelected.has(file)) { newSelected.delete(file); } else { newSelected.add(file); } setSelectedFiles(newSelected); setSelectAll(newSelected.size === availableFiles.length); }; const handleFileFormatChange = (file: string, format: 'json' | 'binary') => { const newFormats = new Map(fileFormats); newFormats.set(file, format); setFileFormats(newFormats); }; const handleBrowseAssetPath = async () => { try { const selected = await open({ directory: true, multiple: false, title: t('exportRuntime.selectAssetDir'), defaultPath: assetOutputPath || projectPath }); if (selected) { const path = selected as string; setAssetOutputPath(path); localStorage.setItem('export-asset-path', path); } } catch (error) { console.error('Failed to browse asset path:', error); } }; const handleBrowseTypePath = async () => { try { const selected = await open({ directory: true, multiple: false, title: t('exportRuntime.selectTypeDir'), defaultPath: typeOutputPath || projectPath }); if (selected) { const path = selected as string; setTypeOutputPath(path); localStorage.setItem('export-type-path', path); } } catch (error) { console.error('Failed to browse type path:', error); } }; const handleExport = async () => { if (!assetOutputPath) { setExportMessage(t('exportRuntime.errorSelectAssetPath')); return; } if (!typeOutputPath) { setExportMessage(t('exportRuntime.errorSelectTypePath')); return; } if (selectedMode === 'workspace' && selectedFiles.size === 0) { setExportMessage(t('exportRuntime.errorSelectFile')); return; } if (selectedMode === 'single' && !currentFileName) { setExportMessage(t('exportRuntime.errorNoCurrentFile')); return; } // 保存路径到 localStorage localStorage.setItem('export-asset-path', assetOutputPath); localStorage.setItem('export-type-path', typeOutputPath); setIsExporting(true); setExportProgress(0); setExportMessage(t('exportRuntime.exporting')); try { await onExport({ mode: selectedMode, assetOutputPath, typeOutputPath, selectedFiles: selectedMode === 'workspace' ? Array.from(selectedFiles) : [currentFileName!], fileFormats }); setExportProgress(100); setExportMessage(t('exportRuntime.exportSuccess')); } catch (error) { setExportMessage(t('exportRuntime.exportFailed', { error: String(error) })); } finally { setIsExporting(false); } }; return (

{t('exportRuntime.title')}

{/* Tab 页签 */}
{/* 资产输出路径 */}

{t('exportRuntime.assetOutputPath')}

setAssetOutputPath(e.target.value)} placeholder={t('exportRuntime.selectAssetDirPlaceholder')} style={{ flex: 1, padding: '8px 12px', backgroundColor: '#2d2d2d', border: '1px solid #3a3a3a', borderRadius: '4px', color: '#cccccc', fontSize: '12px' }} />
{/* TypeScript 类型定义输出路径 */}

{t('exportRuntime.typeOutputPath')}

{selectedMode === 'workspace' ? t('exportRuntime.typeOutputHintWorkspace') : t('exportRuntime.typeOutputHintSingle') }
setTypeOutputPath(e.target.value)} placeholder={t('exportRuntime.selectTypeDirPlaceholder')} style={{ flex: 1, padding: '8px 12px', backgroundColor: '#2d2d2d', border: '1px solid #3a3a3a', borderRadius: '4px', color: '#cccccc', fontSize: '12px' }} />
{/* 文件列表 */} {selectedMode === 'workspace' && availableFiles.length > 0 && (

{t('exportRuntime.selectFilesToExport')} ({selectedFiles.size}/{availableFiles.length})

{availableFiles.map((file) => (
handleToggleFile(file)} />
{file}.btree
))}
)} {/* 单文件模式 */} {selectedMode === 'single' && (

{t('exportRuntime.currentFile')}

{currentFileName ? (
{currentFileName}.btree
) : (
{t('exportRuntime.noOpenFile')}
{t('exportRuntime.openFileHint')}
)}
)}
{exportMessage && (
{exportMessage}
)} {isExporting && (
)}
); };