import React, { useEffect, useMemo } from 'react'; import { Button, Progress } from 'antd'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import { BrowserOpenURL } from '../../wailsjs/runtime'; import Modal from './common/ResizableDraggableModal'; import { t } from '../i18n'; export type UpdateReleaseNotesDownloadProgress = { status: 'idle' | 'start' | 'downloading' | 'done' | 'error'; percent: number; downloaded: number; total: number; message?: string; }; export type UpdateReleaseNotesModalProps = { open: boolean; onClose: () => void; /** 弹窗打开时回调(用于标记已读等) */ onOpen?: () => void; darkMode?: boolean; version?: string; channel?: string; releaseName?: string; releasePublishedAt?: string; releaseNotes?: string; releaseNotesUrl?: string; /** 需高于设置中心等父层弹窗,否则会沉在背后 */ zIndex?: number; /** 下载进度(与更新日志同窗展示,可边看边下) */ downloadProgress?: UpdateReleaseNotesDownloadProgress | null; formatBytes?: (value: number) => string; progressHint?: string; /** 自定义底部操作(下载/安装/后台等) */ footerActions?: React.ReactNode[]; }; const formatReleaseTime = (value?: string): string => { const text = String(value || '').trim(); if (!text) return ''; const date = new Date(text); if (Number.isNaN(date.getTime())) return text; const pad = (n: number) => String(n).padStart(2, '0'); return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`; }; /** * 应用内「更新日志 + 下载进度」统一弹窗: * 检查更新后可读 changelog;下载时进度与日志同屏,支持边看边下。 */ const UpdateReleaseNotesModal: React.FC = ({ open, onClose, onOpen, darkMode = false, version, channel, releaseName, releasePublishedAt, releaseNotes, releaseNotesUrl, zIndex, downloadProgress = null, formatBytes, progressHint, footerActions, }) => { const notes = String(releaseNotes || '').trim(); const url = String(releaseNotesUrl || '').trim(); const progress = downloadProgress && downloadProgress.status !== 'idle' ? downloadProgress : null; const meta = useMemo(() => { const parts = [ String(version || '').trim() || String(releaseName || '').trim(), String(channel || '').trim(), formatReleaseTime(releasePublishedAt), ].filter(Boolean); return parts.join(' · '); }, [channel, releaseName, releasePublishedAt, version]); useEffect(() => { if (open) { onOpen?.(); } }, [open, onOpen]); const openGitHub = () => { if (!url) return; try { BrowserOpenURL(url); } catch { window.open(url, '_blank', 'noopener,noreferrer'); } }; const progressStatus = progress?.status === 'error' ? 'exception' : (progress?.status === 'done' ? 'success' : 'active'); const title = progress ? (version ? t('app.about.download_progress.title_with_version', { version }) : t('app.about.download_progress.title')) : t('app.about.release_notes.modal.title'); const defaultFooter = [ url ? ( ) : null, , ].filter(Boolean); return ( 0 ? footerActions : defaultFooter} destroyOnHidden > {meta ? (
{meta}
) : null} {progress ? (
{t('app.about.release_notes.modal.download_section')}
{progress.status === 'done' ? (progressHint || t('app.about.download_progress.complete_hint')) : (formatBytes ? `${formatBytes(progress.downloaded)} / ${formatBytes(progress.total)}` : `${Math.round(progress.percent)}%`)}
{progress.message ? (
{progress.message}
) : null}
) : null}
{notes ? ( ( { event.preventDefault(); if (href) { try { BrowserOpenURL(href); } catch { window.open(href, '_blank', 'noopener,noreferrer'); } } }} > {children} ), }} skipHtml > {notes} ) : (
{url ? t('app.about.release_notes.modal.empty_with_link') : t('app.about.release_notes.modal.empty')}
)}
); }; export default UpdateReleaseNotesModal;