* basic i18n * tweak * translate app * add linting to make sure we have translations * run linter on pr * format
209 lines
6.2 KiB
TypeScript
209 lines
6.2 KiB
TypeScript
import React, { useState, useEffect, useRef } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { check } from "@tauri-apps/plugin-updater";
|
|
import { relaunch } from "@tauri-apps/plugin-process";
|
|
import { listen } from "@tauri-apps/api/event";
|
|
import { ProgressBar } from "../shared";
|
|
import { useSettings } from "../../hooks/useSettings";
|
|
|
|
interface UpdateCheckerProps {
|
|
className?: string;
|
|
}
|
|
|
|
const UpdateChecker: React.FC<UpdateCheckerProps> = ({ className = "" }) => {
|
|
const { t } = useTranslation();
|
|
// Update checking state
|
|
const [isChecking, setIsChecking] = useState(false);
|
|
const [updateAvailable, setUpdateAvailable] = useState(false);
|
|
const [isInstalling, setIsInstalling] = useState(false);
|
|
const [downloadProgress, setDownloadProgress] = useState(0);
|
|
const [showUpToDate, setShowUpToDate] = useState(false);
|
|
|
|
const { settings, isLoading } = useSettings();
|
|
const settingsLoaded = !isLoading && settings !== null;
|
|
const updateChecksEnabled = settings?.update_checks_enabled ?? false;
|
|
|
|
const upToDateTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
|
const isManualCheckRef = useRef(false);
|
|
const downloadedBytesRef = useRef(0);
|
|
const contentLengthRef = useRef(0);
|
|
|
|
useEffect(() => {
|
|
// Wait for settings to load before doing anything
|
|
if (!settingsLoaded) return;
|
|
|
|
if (!updateChecksEnabled) {
|
|
if (upToDateTimeoutRef.current) {
|
|
clearTimeout(upToDateTimeoutRef.current);
|
|
}
|
|
setIsChecking(false);
|
|
setUpdateAvailable(false);
|
|
setShowUpToDate(false);
|
|
return;
|
|
}
|
|
|
|
checkForUpdates();
|
|
|
|
// Listen for update check events
|
|
const updateUnlisten = listen("check-for-updates", () => {
|
|
handleManualUpdateCheck();
|
|
});
|
|
|
|
return () => {
|
|
if (upToDateTimeoutRef.current) {
|
|
clearTimeout(upToDateTimeoutRef.current);
|
|
}
|
|
updateUnlisten.then((fn) => fn());
|
|
};
|
|
}, [settingsLoaded, updateChecksEnabled]);
|
|
|
|
// Update checking functions
|
|
const checkForUpdates = async () => {
|
|
if (!updateChecksEnabled || isChecking) return;
|
|
|
|
try {
|
|
setIsChecking(true);
|
|
const update = await check();
|
|
|
|
if (update) {
|
|
setUpdateAvailable(true);
|
|
setShowUpToDate(false);
|
|
} else {
|
|
setUpdateAvailable(false);
|
|
|
|
if (isManualCheckRef.current) {
|
|
setShowUpToDate(true);
|
|
if (upToDateTimeoutRef.current) {
|
|
clearTimeout(upToDateTimeoutRef.current);
|
|
}
|
|
upToDateTimeoutRef.current = setTimeout(() => {
|
|
setShowUpToDate(false);
|
|
}, 3000);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to check for updates:", error);
|
|
} finally {
|
|
setIsChecking(false);
|
|
isManualCheckRef.current = false;
|
|
}
|
|
};
|
|
|
|
const handleManualUpdateCheck = () => {
|
|
if (!updateChecksEnabled) return;
|
|
isManualCheckRef.current = true;
|
|
checkForUpdates();
|
|
};
|
|
|
|
const installUpdate = async () => {
|
|
if (!updateChecksEnabled) return;
|
|
try {
|
|
setIsInstalling(true);
|
|
setDownloadProgress(0);
|
|
downloadedBytesRef.current = 0;
|
|
contentLengthRef.current = 0;
|
|
const update = await check();
|
|
|
|
if (!update) {
|
|
console.log("No update available during install attempt");
|
|
return;
|
|
}
|
|
|
|
await update.downloadAndInstall((event) => {
|
|
switch (event.event) {
|
|
case "Started":
|
|
downloadedBytesRef.current = 0;
|
|
contentLengthRef.current = event.data.contentLength ?? 0;
|
|
break;
|
|
case "Progress":
|
|
downloadedBytesRef.current += event.data.chunkLength;
|
|
const progress =
|
|
contentLengthRef.current > 0
|
|
? Math.round(
|
|
(downloadedBytesRef.current / contentLengthRef.current) *
|
|
100,
|
|
)
|
|
: 0;
|
|
setDownloadProgress(Math.min(progress, 100));
|
|
break;
|
|
}
|
|
});
|
|
await relaunch();
|
|
} catch (error) {
|
|
console.error("Failed to install update:", error);
|
|
} finally {
|
|
setIsInstalling(false);
|
|
setDownloadProgress(0);
|
|
downloadedBytesRef.current = 0;
|
|
contentLengthRef.current = 0;
|
|
}
|
|
};
|
|
|
|
// Update status functions
|
|
const getUpdateStatusText = () => {
|
|
if (!updateChecksEnabled) {
|
|
return t("footer.updateCheckingDisabled");
|
|
}
|
|
if (isInstalling) {
|
|
return downloadProgress > 0 && downloadProgress < 100
|
|
? t("footer.downloading", {
|
|
progress: downloadProgress.toString().padStart(3),
|
|
})
|
|
: downloadProgress === 100
|
|
? t("footer.installing")
|
|
: t("footer.preparing");
|
|
}
|
|
if (isChecking) return t("footer.checkingUpdates");
|
|
if (showUpToDate) return t("footer.upToDate");
|
|
if (updateAvailable) return t("footer.updateAvailableShort");
|
|
return t("footer.checkForUpdates");
|
|
};
|
|
|
|
const getUpdateStatusAction = () => {
|
|
if (!updateChecksEnabled) return undefined;
|
|
if (updateAvailable && !isInstalling) return installUpdate;
|
|
if (!isChecking && !isInstalling && !updateAvailable)
|
|
return handleManualUpdateCheck;
|
|
return undefined;
|
|
};
|
|
|
|
const isUpdateDisabled = !updateChecksEnabled || isChecking || isInstalling;
|
|
const isUpdateClickable =
|
|
!isUpdateDisabled && (updateAvailable || (!isChecking && !showUpToDate));
|
|
|
|
return (
|
|
<div className={`flex items-center gap-3 ${className}`}>
|
|
{isUpdateClickable ? (
|
|
<button
|
|
onClick={getUpdateStatusAction()}
|
|
disabled={isUpdateDisabled}
|
|
className={`transition-colors disabled:opacity-50 tabular-nums ${
|
|
updateAvailable
|
|
? "text-logo-primary hover:text-logo-primary/80 font-medium"
|
|
: "text-text/60 hover:text-text/80"
|
|
}`}
|
|
>
|
|
{getUpdateStatusText()}
|
|
</button>
|
|
) : (
|
|
<span className="text-text/60 tabular-nums">
|
|
{getUpdateStatusText()}
|
|
</span>
|
|
)}
|
|
|
|
{isInstalling && downloadProgress > 0 && downloadProgress < 100 && (
|
|
<ProgressBar
|
|
progress={[
|
|
{
|
|
id: "update",
|
|
percentage: downloadProgress,
|
|
},
|
|
]}
|
|
size="large"
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default UpdateChecker;
|