import { useState } from 'react'; import { X, FolderOpen, Loader, CheckCircle, AlertCircle, RefreshCw } from 'lucide-react'; import { open as openDialog } from '@tauri-apps/plugin-dialog'; import type { GitHubService, PublishedPlugin } from '../services/GitHubService'; import { PluginPublishService, type PublishProgress } from '../services/PluginPublishService'; import { PluginBuildService, type BuildProgress } from '../services/PluginBuildService'; import { open } from '@tauri-apps/plugin-shell'; import { EditorPluginCategory } from '@esengine/editor-core'; import type { IEditorPluginMetadata } from '@esengine/editor-core'; import '../styles/PluginUpdateDialog.css'; interface PluginUpdateDialogProps { plugin: PublishedPlugin; githubService: GitHubService; onClose: () => void; onSuccess: () => void; locale: string; } type Step = 'selectFolder' | 'info' | 'building' | 'publishing' | 'success' | 'error'; function calculateNextVersion(currentVersion: string): string { const parts = currentVersion.split('.').map(Number); if (parts.length !== 3 || parts.some(isNaN)) return currentVersion; const [major, minor, patch] = parts; return `${major}.${minor}.${(patch ?? 0) + 1}`; } export function PluginUpdateDialog({ plugin, githubService, onClose, onSuccess, locale }: PluginUpdateDialogProps) { const [publishService] = useState(() => new PluginPublishService(githubService)); const [buildService] = useState(() => new PluginBuildService()); const [step, setStep] = useState('selectFolder'); const [pluginFolder, setPluginFolder] = useState(''); const [version, setVersion] = useState(''); const [releaseNotes, setReleaseNotes] = useState(''); const [suggestedVersion] = useState(() => calculateNextVersion(plugin.latestVersion)); const [error, setError] = useState(''); const [buildLog, setBuildLog] = useState([]); const [buildProgress, setBuildProgress] = useState(null); const [publishProgress, setPublishProgress] = useState(null); const [prUrl, setPrUrl] = useState(''); const t = (key: string) => { const translations: Record> = { zh: { title: '更新插件', currentVersion: '当前版本', newVersion: '新版本号', useSuggested: '使用建议版本', releaseNotes: '更新说明', releaseNotesPlaceholder: '描述这个版本的变更...', selectFolder: '选择插件文件夹', selectFolderDesc: '选择包含更新后插件源代码的文件夹', browseFolder: '浏览文件夹', selectedFolder: '已选择文件夹', next: '下一步', back: '上一步', buildAndPublish: '构建并发布', building: '构建中...', publishing: '发布中...', success: '更新成功!', error: '更新失败', viewPR: '查看 PR', close: '关闭', buildError: '构建失败', publishError: '发布失败', buildingStep1: '正在安装依赖...', buildingStep2: '正在构建项目...', buildingStep3: '正在打包 ZIP...', publishingStep1: '正在 Fork 仓库...', publishingStep2: '正在创建分支...', publishingStep3: '正在上传文件...', publishingStep4: '正在创建 Pull Request...', reviewMessage: '你的插件更新已创建 PR,维护者将进行审核。审核通过后,新版本将自动发布到市场。' }, en: { title: 'Update Plugin', currentVersion: 'Current Version', newVersion: 'New Version', useSuggested: 'Use Suggested', releaseNotes: 'Release Notes', releaseNotesPlaceholder: 'Describe the changes in this version...', selectFolder: 'Select Plugin Folder', selectFolderDesc: 'Select the folder containing your updated plugin source code', browseFolder: 'Browse Folder', selectedFolder: 'Selected Folder', next: 'Next', back: 'Back', buildAndPublish: 'Build & Publish', building: 'Building...', publishing: 'Publishing...', success: 'Update Successful!', error: 'Update Failed', viewPR: 'View PR', close: 'Close', buildError: 'Build Failed', publishError: 'Publish Failed', buildingStep1: 'Installing dependencies...', buildingStep2: 'Building project...', buildingStep3: 'Packaging ZIP...', publishingStep1: 'Forking repository...', publishingStep2: 'Creating branch...', publishingStep3: 'Uploading files...', publishingStep4: 'Creating Pull Request...', reviewMessage: 'Your plugin update has been created as a PR. Maintainers will review it. Once approved, the new version will be published to the marketplace.' } }; return translations[locale]?.[key] || translations.en?.[key] || key; }; const handleSelectFolder = async () => { try { const selected = await openDialog({ directory: true, multiple: false, title: t('selectFolder') }); if (!selected) return; setPluginFolder(selected as string); setStep('info'); } catch (err) { console.error('[PluginUpdateDialog] Failed to select folder:', err); setError(err instanceof Error ? err.message : 'Failed to select folder'); } }; const handleBuildAndPublish = async () => { if (!version || !releaseNotes) { alert('Please fill in all required fields'); return; } setStep('building'); setBuildLog([]); setError(''); try { buildService.setProgressCallback((progress) => { setBuildProgress(progress); if (progress.output) { setBuildLog((prev) => [...prev, progress.output!]); } }); const zipPath = await buildService.buildPlugin(pluginFolder); console.log('[PluginUpdateDialog] Build completed:', zipPath); setStep('publishing'); publishService.setProgressCallback((progress) => { setPublishProgress(progress); }); const { readTextFile } = await import('@tauri-apps/plugin-fs'); const packageJsonPath = `${pluginFolder}/package.json`; const packageJsonContent = await readTextFile(packageJsonPath); const pkgJson = JSON.parse(packageJsonContent); const pluginMetadata: IEditorPluginMetadata = { name: pkgJson.name, displayName: pkgJson.description || pkgJson.name, description: pkgJson.description || '', version: pkgJson.version, category: EditorPluginCategory.Tool, icon: 'Package', enabled: true, installedAt: Date.now() }; const publishInfo = { pluginMetadata, version, releaseNotes, category: plugin.category_type as 'official' | 'community', repositoryUrl: plugin.repositoryUrl || '', tags: [] }; const prUrl = await publishService.publishPlugin(publishInfo, zipPath); console.log('[PluginUpdateDialog] Update published:', prUrl); setPrUrl(prUrl); setStep('success'); onSuccess(); } catch (err) { console.error('[PluginUpdateDialog] Failed to update plugin:', err); setError(err instanceof Error ? err.message : 'Update failed'); setStep('error'); } }; const renderStepContent = () => { switch (step) { case 'selectFolder': return (

{t('selectFolder')}

{t('selectFolderDesc')}

); case 'info': return (

{plugin.name}

{t('currentVersion')}: v{plugin.latestVersion}

{pluginFolder && (
{pluginFolder}
)}
setVersion(e.target.value)} placeholder={suggestedVersion} />