This commit is contained in:
zhom
2025-05-29 10:17:16 +04:00
commit 08678dcacc
154 changed files with 29456 additions and 0 deletions
+372
View File
@@ -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,
};
}
+79
View File
@@ -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,
};
}
+268
View File
@@ -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,
};
}