import useSWR from "swr"; import { forwardRef, useImperativeHandle, useState, useMemo, useEffect, } from "react"; import { useLockFn } from "ahooks"; import { useTranslation } from "react-i18next"; import { relaunch } from "@tauri-apps/plugin-process"; import { check as checkUpdate } from "@tauri-apps/plugin-updater"; import { Event, UnlistenFn } from "@tauri-apps/api/event"; import { open as openUrl } from "@tauri-apps/plugin-shell"; import ReactMarkdown from "react-markdown"; // Новые импорты import { DialogRef } from "@/components/base"; import { useUpdateState, useSetUpdateState } from "@/services/states"; import { portableFlag } from "@/pages/_layout"; import { useListen } from "@/hooks/use-listen"; import { showNotice } from "@/services/noticeService"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogClose, } from "@/components/ui/dialog"; import { Progress } from "@/components/ui/progress"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { AlertTriangle, ExternalLink } from "lucide-react"; export const UpdateViewer = forwardRef((props, ref) => { const { t } = useTranslation(); const [open, setOpen] = useState(false); const [currentProgressListener, setCurrentProgressListener] = useState(null); const updateState = useUpdateState(); const setUpdateState = useSetUpdateState(); const { addListener } = useListen(); const { data: updateInfo } = useSWR("checkUpdate", checkUpdate, { errorRetryCount: 2, revalidateIfStale: false, focusThrottleInterval: 36e5, }); const [downloaded, setDownloaded] = useState(0); const [total, setTotal] = useState(0); useImperativeHandle(ref, () => ({ open: () => setOpen(true), close: () => setOpen(false), })); const markdownContent = useMemo(() => { if (!updateInfo?.body) return t("New Version is available"); return updateInfo.body; }, [updateInfo, t]); const breakChangeFlag = useMemo(() => { return updateInfo?.body?.toLowerCase().includes("break change") ?? false; }, [updateInfo]); const onUpdate = useLockFn(async () => { if (portableFlag) { showNotice("error", t("Portable Updater Error")); return; } if (!updateInfo?.body) return; if (breakChangeFlag) { showNotice("error", t("Break Change Update Error")); return; } if (updateState) return; setUpdateState(true); setDownloaded(0); // Сбрасываем прогресс перед новой загрузкой setTotal(0); if (currentProgressListener) currentProgressListener(); const progressListener = await addListener( "tauri://update-download-progress", (e: Event) => { setTotal(e.payload.contentLength); setDownloaded((prev) => prev + e.payload.chunkLength); }, ); setCurrentProgressListener(() => progressListener); try { await updateInfo.downloadAndInstall(); await relaunch(); } catch (err: any) { showNotice("error", err?.message || err.toString()); } finally { setUpdateState(false); progressListener?.(); setCurrentProgressListener(null); } }); useEffect(() => { return () => { currentProgressListener?.(); }; }, [currentProgressListener]); const downloadProgress = total > 0 ? (downloaded / total) * 100 : 0; return (
{t("New Version")} v{updateInfo?.version}
{breakChangeFlag && ( {t("Warning")} {t("Break Change Warning")} )} {/* Оборачиваем ReactMarkdown для красивой стилизации */}
{updateState && (

{Math.round(downloadProgress)}%

)}
); });