Files
netrunner-app/src/features/UpdateChecker.tsx
T
nineap b8e1e925b3 Добавляет UpdateChecker (десктоп) и React ErrorBoundary
UpdateChecker: динамический импорт @tauri-apps/plugin-updater (no-op вне
Tauri/на мобилке), диалог с прогрессом, downloadAndInstall + relaunch.
ErrorBoundary оборачивает всё приложение — раньше необработанное
исключение в рендере роняло весь SPA в белый экран без возможности
восстановиться без ручной перезагрузки страницы.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 16:36:39 +07:00

95 lines
3.1 KiB
TypeScript

import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import type { Update } from "@tauri-apps/plugin-updater";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
type Status = "idle" | "downloading" | "error";
/**
* Проверка обновлений — только десктоп (Windows/macOS/Linux). На мобильных
* сборках плагин не зарегистрирован (см. src-tauri/src/lib.rs, #[cfg(desktop)]),
* поэтому check() там просто недоступен — ошибка тихо проглатывается ниже,
* без падения остального приложения.
*/
export function UpdateChecker() {
const { t } = useTranslation();
const [update, setUpdate] = useState<Update | null>(null);
const [status, setStatus] = useState<Status>("idle");
const [progress, setProgress] = useState(0);
const installing = useRef(false);
useEffect(() => {
let cancelled = false;
(async () => {
try {
const { check } = await import("@tauri-apps/plugin-updater");
const result = await check();
if (!cancelled && result?.available) {
setUpdate(result);
}
} catch {
// Мобильная сборка или плагин недоступен в этом окружении — не критично.
}
})();
return () => {
cancelled = true;
};
}, []);
if (!update) return null;
const installUpdate = async () => {
if (installing.current) return;
installing.current = true;
setStatus("downloading");
try {
let downloaded = 0;
let total = 0;
await update.downloadAndInstall((event) => {
if (event.event === "Started") {
total = event.data.contentLength ?? 0;
} else if (event.event === "Progress") {
downloaded += event.data.chunkLength;
setProgress(total > 0 ? Math.round((downloaded / total) * 100) : 0);
}
});
const { relaunch } = await import("@tauri-apps/plugin-process");
await relaunch();
} catch (e) {
console.error("[UpdateChecker] Установка обновления не удалась:", e);
setStatus("error");
installing.current = false;
}
};
return (
<Dialog open onOpenChange={() => {}}>
<DialogContent showCloseButton={status !== "downloading"}>
<DialogHeader>
<DialogTitle>{t("update_available_title")}</DialogTitle>
<DialogDescription>
{t("update_available_desc", { version: update.version })}
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button onClick={installUpdate} disabled={status === "downloading"}>
{status === "downloading"
? t("update_downloading", { progress })
: status === "error"
? t("update_retry")
: t("update_install_now")}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}