diff --git a/src/renderer/src/components/PlaywrightDownloadDialog.tsx b/src/renderer/src/components/PlaywrightDownloadDialog.tsx new file mode 100644 index 0000000..a51ba1a --- /dev/null +++ b/src/renderer/src/components/PlaywrightDownloadDialog.tsx @@ -0,0 +1,263 @@ +import React, { useEffect, useState, useCallback } from 'react' +import { DownloadCloud, LoaderCircle, X } from 'lucide-react' +import Modal from './ui/Modal' +interface DownloadProgress { + percent: number // 0-100 + downloadedBytes: number + totalBytes: number + currentFile: string + speed: number // bytes/s + eta?: number // seconds +} + +interface PlaywrightDownloadDialogProps { + isOpen: boolean + onClose: () => void + onDownloadComplete: () => void +} + +export default function PlaywrightDownloadDialog({ + isOpen, + onClose, + onDownloadComplete +}: PlaywrightDownloadDialogProps): React.JSX.Element { + const [progress, setProgress] = useState(null) + const [error, setError] = useState(null) + const [isDownloading, setIsDownloading] = useState(false) + const [showCancelConfirm, setShowCancelConfirm] = useState(false) + + // Format bytes to human-readable string + const formatBytes = useCallback((bytes: number): string => { + if (bytes === 0) return '0 B' + const k = 1024 + const sizes = ['B', 'KB', 'MB', 'GB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i] + }, []) + + // Format ETA to human-readable string + const formatETA = useCallback((seconds: number): string => { + if (seconds < 0 || !Number.isFinite(seconds)) return '计算中...' + if (seconds < 60) return `${Math.round(seconds)}秒` + const mins = Math.floor(seconds / 60) + const secs = Math.round(seconds % 60) + return `${mins}分${secs}秒` + }, []) + + // Subscribe to progress events + useEffect(() => { + if (!isOpen) return + + const unsubscribe = window.electron.playwrightBrowser.onProgress((data) => { + setProgress(data) + setError(null) + }) + + return unsubscribe + }, [isOpen]) + + // Start download when dialog opens + useEffect(() => { + if (!isOpen) return + + let mounted = true + setIsDownloading(true) + setError(null) + setProgress(null) + + const startDownload = async () => { + try { + const result = await window.electron.playwrightBrowser.download() + if (mounted) { + if (result.success) { + setIsDownloading(false) + onDownloadComplete() + } else { + setError(result.error || '下载失败') + setIsDownloading(false) + } + } + } catch (err) { + if (mounted) { + setError(err instanceof Error ? err.message : '下载失败') + setIsDownloading(false) + } + } + } + + void startDownload() + + return () => { + mounted = false + } + }, [isOpen, onDownloadComplete]) + + const handleCancel = useCallback(async () => { + try { + await window.electron.playwrightBrowser.cancel() + } catch (err) { + console.error('Failed to cancel download:', err) + } finally { + setShowCancelConfirm(false) + setIsDownloading(false) + onClose() + } + }, [onClose]) + + const handleConfirmCancel = useCallback(() => { + void handleCancel() + }, [handleCancel]) + + const handleCancelDownloadClick = useCallback(() => { + setShowCancelConfirm(true) + }, []) + + const handleCloseConfirm = useCallback(() => { + setShowCancelConfirm(false) + }, []) + + return ( + <> + undefined : onClose} + title="下载 Playwright 浏览器" + size="lg" + disableBackdropClick={isDownloading} + disableEscapeKey={isDownloading} + showCloseButton={!isDownloading} + > +
+ {/* Progress Section */} + {isDownloading && ( +
+ {/* Progress Bar */} +
+
+ 下载进度 + {progress?.percent ?? 0}% +
+
+
+
+
+ + {/* Current File */} + {progress?.currentFile && ( +
+
当前文件
+
+ {progress.currentFile} +
+
+ )} + + {/* Stats Grid */} +
+
+
已下载
+
+ {progress ? formatBytes(progress.downloadedBytes) : '0 B'} +
+
+
+
总大小
+
+ {progress ? formatBytes(progress.totalBytes) : '计算中...'} +
+
+
+
速度
+
+ {progress?.speed ? `${formatBytes(progress.speed)}/秒` : '计算中...'} +
+
+
+
剩余时间
+
+ {progress?.eta !== undefined ? formatETA(progress.eta) : '计算中...'} +
+
+
+ + {/* Cancel Button */} +
+ +
+
+ )} + + {/* Error Section */} + {error && ( +
+
下载失败
+
{error}
+
+ )} + + {/* Success Section (shown briefly before closing) */} + {!isDownloading && !error && progress?.percent === 100 && ( +
+
+ + 下载完成 +
+
Playwright 浏览器已准备就绪。
+
+ )} + + {/* Initial Loading State */} + {isDownloading && !progress && ( +
+ +
正在初始化下载...
+
+ )} +
+ + + {/* Cancel Confirmation Dialog */} + +
+
+
确定要取消下载吗?
+
这将退出应用程序,您需要重新启动来继续下载。
+
+
+ + +
+
+
+ + ) +}