mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-06-07 15:33:57 +02:00
init
This commit is contained in:
@@ -0,0 +1,372 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
showDownloadToast,
|
||||
showFetchingToast,
|
||||
showSuccessToast,
|
||||
showErrorToast,
|
||||
dismissToast,
|
||||
} from "../components/custom-toast";
|
||||
import { getBrowserDisplayName } from "@/lib/browser-utils";
|
||||
|
||||
interface GithubRelease {
|
||||
tag_name: string;
|
||||
assets: Array<{
|
||||
name: string;
|
||||
browser_download_url: string;
|
||||
hash?: string;
|
||||
}>;
|
||||
published_at: string;
|
||||
is_alpha: boolean;
|
||||
}
|
||||
|
||||
interface BrowserVersionInfo {
|
||||
version: string;
|
||||
is_prerelease: boolean;
|
||||
date: string;
|
||||
}
|
||||
|
||||
interface DownloadProgress {
|
||||
browser: string;
|
||||
version: string;
|
||||
downloaded_bytes: number;
|
||||
total_bytes?: number;
|
||||
percentage: number;
|
||||
speed_bytes_per_sec: number;
|
||||
eta_seconds?: number;
|
||||
stage: string;
|
||||
}
|
||||
|
||||
interface BrowserVersionsResult {
|
||||
versions: string[];
|
||||
new_versions_count?: number;
|
||||
total_versions_count: number;
|
||||
}
|
||||
|
||||
interface VersionUpdateProgress {
|
||||
current_browser: string;
|
||||
total_browsers: number;
|
||||
completed_browsers: number;
|
||||
new_versions_found: number;
|
||||
browser_new_versions: number;
|
||||
status: string;
|
||||
}
|
||||
|
||||
const isAlphaVersion = (version: string): boolean => {
|
||||
// Check for common alpha/beta/dev indicators
|
||||
const lowerVersion = version.toLowerCase();
|
||||
return (
|
||||
lowerVersion.includes("a") ||
|
||||
lowerVersion.includes("b") ||
|
||||
lowerVersion.includes("alpha") ||
|
||||
lowerVersion.includes("beta") ||
|
||||
lowerVersion.includes("dev") ||
|
||||
lowerVersion.includes("rc") ||
|
||||
lowerVersion.includes("pre") ||
|
||||
// Check for patterns like "139.0b1" or "140.0a1"
|
||||
/\d+\.\d+[ab]\d+/.test(lowerVersion)
|
||||
);
|
||||
};
|
||||
|
||||
export function useBrowserDownload() {
|
||||
const [availableVersions, setAvailableVersions] = useState<GithubRelease[]>(
|
||||
[]
|
||||
);
|
||||
const [downloadedVersions, setDownloadedVersions] = useState<string[]>([]);
|
||||
const [isDownloading, setIsDownloading] = useState(false);
|
||||
const [downloadProgress, setDownloadProgress] =
|
||||
useState<DownloadProgress | null>(null);
|
||||
const [isUpdatingVersions, setIsUpdatingVersions] = useState(false);
|
||||
|
||||
// Listen for download progress events
|
||||
useEffect(() => {
|
||||
const unlisten = listen<DownloadProgress>("download-progress", (event) => {
|
||||
const progress = event.payload;
|
||||
setDownloadProgress(progress);
|
||||
|
||||
const browserName = getBrowserDisplayName(progress.browser);
|
||||
|
||||
// Check if this is an auto-update download to suppress completion toast
|
||||
const checkAutoUpdate = async () => {
|
||||
let isAutoUpdate = false;
|
||||
try {
|
||||
isAutoUpdate = await invoke<boolean>("is_auto_update_download", {
|
||||
browser: progress.browser,
|
||||
version: progress.version,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to check auto-update status:", error);
|
||||
}
|
||||
|
||||
// Show toast with progress
|
||||
if (progress.stage === "downloading") {
|
||||
const speedMBps = (
|
||||
progress.speed_bytes_per_sec /
|
||||
(1024 * 1024)
|
||||
).toFixed(1);
|
||||
const etaText = progress.eta_seconds
|
||||
? formatTime(progress.eta_seconds)
|
||||
: "calculating...";
|
||||
|
||||
showDownloadToast(browserName, progress.version, "downloading", {
|
||||
percentage: progress.percentage,
|
||||
speed: speedMBps,
|
||||
eta: etaText,
|
||||
});
|
||||
} else if (progress.stage === "extracting") {
|
||||
showDownloadToast(browserName, progress.version, "extracting");
|
||||
} else if (progress.stage === "verifying") {
|
||||
showDownloadToast(browserName, progress.version, "verifying");
|
||||
} else if (progress.stage === "completed") {
|
||||
// Suppress completion toast for auto-updates
|
||||
showDownloadToast(
|
||||
browserName,
|
||||
progress.version,
|
||||
"completed",
|
||||
undefined,
|
||||
{
|
||||
suppressCompletionToast: isAutoUpdate,
|
||||
}
|
||||
);
|
||||
setDownloadProgress(null);
|
||||
}
|
||||
};
|
||||
|
||||
void checkAutoUpdate();
|
||||
});
|
||||
|
||||
return () => {
|
||||
void unlisten.then((fn) => {
|
||||
fn();
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Listen for version update progress events
|
||||
useEffect(() => {
|
||||
const unlisten = listen<VersionUpdateProgress>(
|
||||
"version-update-progress",
|
||||
(event) => {
|
||||
const progress = event.payload;
|
||||
|
||||
if (progress.status === "updating") {
|
||||
setIsUpdatingVersions(true);
|
||||
if (progress.current_browser) {
|
||||
const browserName = getBrowserDisplayName(progress.current_browser);
|
||||
showFetchingToast(browserName, {
|
||||
id: `version-update-${progress.current_browser}`,
|
||||
description: "Fetching latest release information...",
|
||||
});
|
||||
}
|
||||
} else if (progress.status === "completed") {
|
||||
setIsUpdatingVersions(false);
|
||||
if (progress.new_versions_found > 0) {
|
||||
showSuccessToast(
|
||||
`Found ${progress.new_versions_found} new browser versions!`,
|
||||
{
|
||||
duration: 3000,
|
||||
}
|
||||
);
|
||||
}
|
||||
// Dismiss any update toasts
|
||||
toast.dismiss();
|
||||
} else if (progress.status === "error") {
|
||||
setIsUpdatingVersions(false);
|
||||
showErrorToast("Failed to check for new versions", {
|
||||
duration: 4000,
|
||||
});
|
||||
toast.dismiss();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
void unlisten.then((fn) => {
|
||||
fn();
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
const formatTime = (seconds: number): string => {
|
||||
if (seconds < 60) {
|
||||
return `${Math.round(seconds)}s`;
|
||||
} else if (seconds < 3600) {
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainingSeconds = Math.round(seconds % 60);
|
||||
return `${minutes}m ${remainingSeconds}s`;
|
||||
} else {
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
return `${hours}h ${minutes}m`;
|
||||
}
|
||||
};
|
||||
|
||||
const formatBytes = (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 `${Number.parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${
|
||||
sizes[i]
|
||||
}`;
|
||||
};
|
||||
|
||||
const loadVersions = useCallback(async (browserStr: string) => {
|
||||
const browserName = getBrowserDisplayName(browserStr);
|
||||
|
||||
// Show fetching toast
|
||||
const toastId = showFetchingToast(browserName, {
|
||||
id: `fetch-${browserStr}`,
|
||||
});
|
||||
|
||||
try {
|
||||
const versionInfos = await invoke<BrowserVersionInfo[]>(
|
||||
"fetch_browser_versions_cached_first",
|
||||
{ browserStr }
|
||||
);
|
||||
|
||||
// Convert BrowserVersionInfo to GithubRelease format for compatibility
|
||||
const githubReleases: GithubRelease[] = versionInfos.map(
|
||||
(versionInfo) => ({
|
||||
tag_name: versionInfo.version,
|
||||
assets: [],
|
||||
published_at: versionInfo.date,
|
||||
is_alpha: versionInfo.is_prerelease,
|
||||
})
|
||||
);
|
||||
|
||||
setAvailableVersions(githubReleases);
|
||||
dismissToast(toastId);
|
||||
return githubReleases;
|
||||
} catch (error) {
|
||||
console.error("Failed to load versions:", error);
|
||||
dismissToast(toastId);
|
||||
showErrorToast(`Failed to fetch ${browserName} versions`, {
|
||||
description:
|
||||
error instanceof Error ? error.message : "Unknown error occurred",
|
||||
duration: 4000,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const loadVersionsWithNewCount = useCallback(async (browserStr: string) => {
|
||||
const browserName = getBrowserDisplayName(browserStr);
|
||||
|
||||
try {
|
||||
// Get versions with new count info and cached detailed info
|
||||
const result = await invoke<BrowserVersionsResult>(
|
||||
"fetch_browser_versions_with_count_cached_first",
|
||||
{ browserStr }
|
||||
);
|
||||
|
||||
// Get detailed version info for compatibility
|
||||
const versionInfos = await invoke<BrowserVersionInfo[]>(
|
||||
"fetch_browser_versions_cached_first",
|
||||
{ browserStr }
|
||||
);
|
||||
|
||||
// Convert BrowserVersionInfo to GithubRelease format for compatibility
|
||||
const githubReleases: GithubRelease[] = versionInfos.map(
|
||||
(versionInfo) => ({
|
||||
tag_name: versionInfo.version,
|
||||
assets: [],
|
||||
published_at: versionInfo.date,
|
||||
is_alpha: versionInfo.is_prerelease,
|
||||
})
|
||||
);
|
||||
|
||||
setAvailableVersions(githubReleases);
|
||||
|
||||
// Show notification about new versions if any were found
|
||||
if (result.new_versions_count && result.new_versions_count > 0) {
|
||||
showSuccessToast(
|
||||
`Found ${result.new_versions_count} new ${browserName} versions!`,
|
||||
{
|
||||
duration: 3000,
|
||||
description: `Total available: ${result.total_versions_count} versions`,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return githubReleases;
|
||||
} catch (error) {
|
||||
console.error("Failed to load versions:", error);
|
||||
showErrorToast(`Failed to fetch ${browserName} versions`, {
|
||||
description:
|
||||
error instanceof Error ? error.message : "Unknown error occurred",
|
||||
duration: 4000,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const loadDownloadedVersions = useCallback(async (browserStr: string) => {
|
||||
try {
|
||||
const downloadedVersions = await invoke<string[]>(
|
||||
"get_downloaded_browser_versions",
|
||||
{ browserStr }
|
||||
);
|
||||
setDownloadedVersions(downloadedVersions);
|
||||
return downloadedVersions;
|
||||
} catch (error) {
|
||||
console.error("Failed to load downloaded versions:", error);
|
||||
throw error;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const downloadBrowser = useCallback(
|
||||
async (
|
||||
browserStr: string,
|
||||
version: string,
|
||||
suppressNotifications: boolean = false
|
||||
) => {
|
||||
const browserName = getBrowserDisplayName(browserStr);
|
||||
setIsDownloading(true);
|
||||
|
||||
try {
|
||||
await invoke("download_browser", { browserStr, version });
|
||||
await loadDownloadedVersions(browserStr);
|
||||
} catch (error) {
|
||||
console.error("Failed to download browser:", error);
|
||||
|
||||
if (!suppressNotifications) {
|
||||
// Dismiss any existing download toast and show error
|
||||
dismissToast(`download-${browserStr}-${version}`);
|
||||
showErrorToast(`Failed to download ${browserName} ${version}`, {
|
||||
description:
|
||||
error instanceof Error ? error.message : "Unknown error occurred",
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
setIsDownloading(false);
|
||||
}
|
||||
},
|
||||
[loadDownloadedVersions]
|
||||
);
|
||||
|
||||
const isVersionDownloaded = useCallback(
|
||||
(version: string) => {
|
||||
return downloadedVersions.includes(version);
|
||||
},
|
||||
[downloadedVersions]
|
||||
);
|
||||
|
||||
return {
|
||||
availableVersions,
|
||||
downloadedVersions,
|
||||
isDownloading,
|
||||
downloadProgress,
|
||||
isUpdatingVersions,
|
||||
loadVersions,
|
||||
loadVersionsWithNewCount,
|
||||
loadDownloadedVersions,
|
||||
downloadBrowser,
|
||||
isVersionDownloaded,
|
||||
formatBytes,
|
||||
formatTime,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import type { TableSortingSettings } from "@/types";
|
||||
import type { SortingState } from "@tanstack/react-table";
|
||||
|
||||
export function useTableSorting() {
|
||||
const [sortingSettings, setSortingSettings] = useState<TableSortingSettings>({
|
||||
column: "name",
|
||||
direction: "asc",
|
||||
});
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
|
||||
// Load sorting settings on mount
|
||||
useEffect(() => {
|
||||
const loadSettings = async () => {
|
||||
try {
|
||||
const settings = await invoke<TableSortingSettings>(
|
||||
"get_table_sorting_settings"
|
||||
);
|
||||
setSortingSettings(settings);
|
||||
} catch (error) {
|
||||
console.error("Failed to load table sorting settings:", error);
|
||||
// Keep default settings if loading fails
|
||||
} finally {
|
||||
setIsLoaded(true);
|
||||
}
|
||||
};
|
||||
|
||||
void loadSettings();
|
||||
}, []);
|
||||
|
||||
// Save sorting settings to disk
|
||||
const saveSortingSettings = useCallback(
|
||||
async (settings: TableSortingSettings) => {
|
||||
try {
|
||||
await invoke("save_table_sorting_settings", { sorting: settings });
|
||||
setSortingSettings(settings);
|
||||
} catch (error) {
|
||||
console.error("Failed to save table sorting settings:", error);
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
// Convert our settings to tanstack table sorting format
|
||||
const getTableSorting = useCallback((): SortingState => {
|
||||
if (!isLoaded) return [];
|
||||
|
||||
return [
|
||||
{
|
||||
id: sortingSettings.column,
|
||||
desc: sortingSettings.direction === "desc",
|
||||
},
|
||||
];
|
||||
}, [sortingSettings, isLoaded]);
|
||||
|
||||
// Update sorting when table state changes
|
||||
const updateSorting = useCallback(
|
||||
(sorting: SortingState) => {
|
||||
if (!isLoaded) return;
|
||||
|
||||
if (sorting.length > 0) {
|
||||
const newSettings: TableSortingSettings = {
|
||||
column: sorting[0].id,
|
||||
direction: sorting[0].desc ? "desc" : "asc",
|
||||
};
|
||||
void saveSortingSettings(newSettings);
|
||||
}
|
||||
},
|
||||
[saveSortingSettings, isLoaded]
|
||||
);
|
||||
|
||||
return {
|
||||
sortingSettings,
|
||||
isLoaded,
|
||||
getTableSorting,
|
||||
updateSorting,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
showVersionUpdateToast,
|
||||
showLoadingToast,
|
||||
dismissToast,
|
||||
} from "../components/custom-toast";
|
||||
import { getBrowserDisplayName } from "@/lib/browser-utils";
|
||||
|
||||
interface VersionUpdateProgress {
|
||||
current_browser: string;
|
||||
total_browsers: number;
|
||||
completed_browsers: number;
|
||||
new_versions_found: number;
|
||||
browser_new_versions: number;
|
||||
status: string; // "updating", "completed", "error"
|
||||
}
|
||||
|
||||
interface BackgroundUpdateResult {
|
||||
browser: string;
|
||||
new_versions_count: number;
|
||||
total_versions_count: number;
|
||||
updated_successfully: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
interface BrowserVersionsResult {
|
||||
versions: string[];
|
||||
new_versions_count?: number;
|
||||
total_versions_count: number;
|
||||
}
|
||||
|
||||
export function useVersionUpdater() {
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
const [lastUpdateTime, setLastUpdateTime] = useState<number | null>(null);
|
||||
const [timeUntilNextUpdate, setTimeUntilNextUpdate] = useState<number>(0);
|
||||
const [updateProgress, setUpdateProgress] =
|
||||
useState<VersionUpdateProgress | null>(null);
|
||||
|
||||
// Listen for version update progress events
|
||||
useEffect(() => {
|
||||
const unlisten = listen<VersionUpdateProgress>(
|
||||
"version-update-progress",
|
||||
(event) => {
|
||||
const progress = event.payload;
|
||||
setUpdateProgress(progress);
|
||||
|
||||
if (progress.status === "updating") {
|
||||
setIsUpdating(true);
|
||||
|
||||
if (progress.current_browser) {
|
||||
const browserName = getBrowserDisplayName(progress.current_browser);
|
||||
showVersionUpdateToast(
|
||||
`Downloading release information for ${browserName}`,
|
||||
{
|
||||
id: "version-update-progress",
|
||||
progress: {
|
||||
current: progress.completed_browsers + 1,
|
||||
total: progress.total_browsers,
|
||||
found: progress.new_versions_found,
|
||||
},
|
||||
}
|
||||
);
|
||||
} else {
|
||||
showLoadingToast("Starting version update check...", {
|
||||
id: "version-update-progress",
|
||||
description: "Initializing browser version check...",
|
||||
});
|
||||
}
|
||||
} else if (progress.status === "completed") {
|
||||
setIsUpdating(false);
|
||||
setUpdateProgress(null);
|
||||
|
||||
if (progress.new_versions_found > 0) {
|
||||
toast.success(
|
||||
`Found ${progress.new_versions_found} new browser versions!`,
|
||||
{
|
||||
id: "version-update-progress",
|
||||
duration: 4000,
|
||||
description:
|
||||
"Version information has been updated in the background",
|
||||
}
|
||||
);
|
||||
} else {
|
||||
toast.success("No new browser versions found", {
|
||||
id: "version-update-progress",
|
||||
duration: 3000,
|
||||
description: "All browser versions are up to date",
|
||||
});
|
||||
}
|
||||
|
||||
// Refresh status
|
||||
void loadUpdateStatus();
|
||||
} else if (progress.status === "error") {
|
||||
setIsUpdating(false);
|
||||
setUpdateProgress(null);
|
||||
|
||||
toast.error("Failed to update browser versions", {
|
||||
id: "version-update-progress",
|
||||
duration: 4000,
|
||||
description: "Check your internet connection and try again",
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
void unlisten.then((fn) => {
|
||||
fn();
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Load update status on mount and periodically
|
||||
useEffect(() => {
|
||||
void loadUpdateStatus();
|
||||
|
||||
// Update status every minute
|
||||
const interval = setInterval(() => {
|
||||
void loadUpdateStatus();
|
||||
}, 60000);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const loadUpdateStatus = useCallback(async () => {
|
||||
try {
|
||||
const [lastUpdate, timeUntilNext] = await invoke<[number | null, number]>(
|
||||
"get_version_update_status"
|
||||
);
|
||||
setLastUpdateTime(lastUpdate);
|
||||
setTimeUntilNextUpdate(timeUntilNext);
|
||||
} catch (error) {
|
||||
console.error("Failed to load version update status:", error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const triggerManualUpdate = useCallback(async () => {
|
||||
try {
|
||||
setIsUpdating(true);
|
||||
const results = await invoke<BackgroundUpdateResult[]>(
|
||||
"trigger_manual_version_update"
|
||||
);
|
||||
|
||||
const totalNewVersions = results.reduce(
|
||||
(sum, result) => sum + result.new_versions_count,
|
||||
0
|
||||
);
|
||||
const successfulUpdates = results.filter(
|
||||
(r) => r.updated_successfully
|
||||
).length;
|
||||
const failedUpdates = results.filter(
|
||||
(r) => !r.updated_successfully
|
||||
).length;
|
||||
|
||||
if (failedUpdates > 0) {
|
||||
toast.warning(`Update completed with some errors`, {
|
||||
description: `${totalNewVersions} new versions found, ${failedUpdates} browsers failed to update`,
|
||||
duration: 5000,
|
||||
});
|
||||
} else if (totalNewVersions > 0) {
|
||||
toast.success(`Found ${totalNewVersions} new browser versions!`, {
|
||||
description: `Updated ${successfulUpdates} browsers successfully`,
|
||||
duration: 4000,
|
||||
});
|
||||
} else {
|
||||
toast.success("No new browser versions found", {
|
||||
description: "All browser versions are up to date",
|
||||
duration: 3000,
|
||||
});
|
||||
}
|
||||
|
||||
await loadUpdateStatus();
|
||||
return results;
|
||||
} catch (error) {
|
||||
console.error("Failed to trigger manual update:", error);
|
||||
toast.error("Failed to update browser versions", {
|
||||
description:
|
||||
error instanceof Error ? error.message : "Unknown error occurred",
|
||||
duration: 4000,
|
||||
});
|
||||
throw error;
|
||||
} finally {
|
||||
setIsUpdating(false);
|
||||
}
|
||||
}, [loadUpdateStatus]);
|
||||
|
||||
const fetchBrowserVersionsWithNewCount = useCallback(
|
||||
async (browserStr: string) => {
|
||||
try {
|
||||
const result = await invoke<BrowserVersionsResult>(
|
||||
"fetch_browser_versions_with_count",
|
||||
{ browserStr }
|
||||
);
|
||||
|
||||
// Show notification about new versions if any were found
|
||||
if (result.new_versions_count && result.new_versions_count > 0) {
|
||||
const browserName = getBrowserDisplayName(browserStr);
|
||||
toast.success(
|
||||
`Found ${result.new_versions_count} new ${browserName} versions!`,
|
||||
{
|
||||
duration: 3000,
|
||||
description: `Total available: ${result.total_versions_count} versions`,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch browser versions with count:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const formatTimeUntilUpdate = useCallback((seconds: number): string => {
|
||||
if (seconds <= 0) return "Update overdue";
|
||||
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
|
||||
if (hours > 0) {
|
||||
return `${hours}h ${minutes}m`;
|
||||
} else if (minutes > 0) {
|
||||
return `${minutes}m`;
|
||||
} else {
|
||||
return "< 1m";
|
||||
}
|
||||
}, []);
|
||||
|
||||
const formatLastUpdateTime = useCallback(
|
||||
(timestamp: number | null): string => {
|
||||
if (!timestamp) return "Never";
|
||||
|
||||
const date = new Date(timestamp * 1000);
|
||||
const now = new Date();
|
||||
const diffMs = now.getTime() - date.getTime();
|
||||
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
||||
const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
|
||||
|
||||
if (diffHours > 0) {
|
||||
return `${diffHours}h ${diffMinutes}m ago`;
|
||||
} else if (diffMinutes > 0) {
|
||||
return `${diffMinutes}m ago`;
|
||||
} else {
|
||||
return "Just now";
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return {
|
||||
isUpdating,
|
||||
lastUpdateTime,
|
||||
timeUntilNextUpdate,
|
||||
updateProgress,
|
||||
triggerManualUpdate,
|
||||
fetchBrowserVersionsWithNewCount,
|
||||
formatTimeUntilUpdate,
|
||||
formatLastUpdateTime,
|
||||
loadUpdateStatus,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user