mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-06-07 07:23:56 +02:00
style: only allow user to switch between releases
This commit is contained in:
+4
-2
@@ -301,6 +301,7 @@ export default function Home() {
|
||||
name: string;
|
||||
browserStr: BrowserTypeString;
|
||||
version: string;
|
||||
releaseType: string;
|
||||
proxy?: ProxySettings;
|
||||
}) => {
|
||||
setError(null);
|
||||
@@ -312,6 +313,7 @@ export default function Home() {
|
||||
name: profileData.name,
|
||||
browserStr: profileData.browserStr,
|
||||
version: profileData.version,
|
||||
releaseType: profileData.releaseType,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -474,7 +476,7 @@ export default function Home() {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 gap-8 sm:p-12 font-[family-name:var(--font-geist-sans)] bg-white dark:bg-black">
|
||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen gap-8 font-[family-name:var(--font-geist-sans)] bg-white dark:bg-black">
|
||||
<main className="flex flex-col row-start-2 gap-8 items-center w-full max-w-3xl">
|
||||
<Card className="w-full">
|
||||
<CardHeader>
|
||||
@@ -527,7 +529,7 @@ export default function Home() {
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<CardContent>
|
||||
<ProfilesDataTable
|
||||
data={profiles}
|
||||
onLaunchProfile={launchProfile}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { LoadingButton } from "@/components/loading-button";
|
||||
import { ReleaseTypeSelector } from "@/components/release-type-selector";
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
@@ -12,9 +13,8 @@ import {
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { VersionSelector } from "@/components/version-selector";
|
||||
import { useBrowserDownload } from "@/hooks/use-browser-download";
|
||||
import type { BrowserProfile } from "@/types";
|
||||
import type { BrowserProfile, BrowserReleaseTypes } from "@/types";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { useEffect, useState } from "react";
|
||||
import { LuTriangleAlert } from "react-icons/lu";
|
||||
@@ -32,16 +32,18 @@ export function ChangeVersionDialog({
|
||||
profile,
|
||||
onVersionChanged,
|
||||
}: ChangeVersionDialogProps) {
|
||||
const [selectedVersion, setSelectedVersion] = useState<string | null>(null);
|
||||
const [selectedReleaseType, setSelectedReleaseType] = useState<
|
||||
"stable" | "nightly" | null
|
||||
>(null);
|
||||
const [releaseTypes, setReleaseTypes] = useState<BrowserReleaseTypes>({});
|
||||
const [isLoadingReleaseTypes, setIsLoadingReleaseTypes] = useState(false);
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
const [showDowngradeWarning, setShowDowngradeWarning] = useState(false);
|
||||
const [acknowledgeDowngrade, setAcknowledgeDowngrade] = useState(false);
|
||||
|
||||
const {
|
||||
availableVersions,
|
||||
downloadedVersions,
|
||||
isDownloading,
|
||||
loadVersions,
|
||||
loadDownloadedVersions,
|
||||
downloadBrowser,
|
||||
isVersionDownloaded,
|
||||
@@ -49,49 +51,73 @@ export function ChangeVersionDialog({
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen && profile) {
|
||||
setSelectedVersion(profile.version);
|
||||
// Set current release type based on profile
|
||||
setSelectedReleaseType(profile.release_type as "stable" | "nightly");
|
||||
setAcknowledgeDowngrade(false);
|
||||
void loadVersions(profile.browser);
|
||||
void loadReleaseTypes(profile.browser);
|
||||
void loadDownloadedVersions(profile.browser);
|
||||
}
|
||||
}, [isOpen, profile, loadVersions, loadDownloadedVersions]);
|
||||
}, [isOpen, profile, loadDownloadedVersions]);
|
||||
|
||||
const loadReleaseTypes = async (browser: string) => {
|
||||
setIsLoadingReleaseTypes(true);
|
||||
try {
|
||||
const releaseTypes = await invoke<BrowserReleaseTypes>(
|
||||
"get_browser_release_types",
|
||||
{ browserStr: browser },
|
||||
);
|
||||
setReleaseTypes(releaseTypes);
|
||||
} catch (error) {
|
||||
console.error("Failed to load release types:", error);
|
||||
} finally {
|
||||
setIsLoadingReleaseTypes(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (profile && selectedVersion) {
|
||||
// Check if this is a downgrade
|
||||
const currentVersionIndex = availableVersions.findIndex(
|
||||
(v) => v.tag_name === profile.version,
|
||||
);
|
||||
const selectedVersionIndex = availableVersions.findIndex(
|
||||
(v) => v.tag_name === selectedVersion,
|
||||
);
|
||||
|
||||
// If selected version has a higher index, it's older (downgrade)
|
||||
if (
|
||||
profile &&
|
||||
selectedReleaseType &&
|
||||
selectedReleaseType !== profile.release_type
|
||||
) {
|
||||
// For simplicity, we'll show downgrade warning when switching from stable to nightly
|
||||
// since nightly versions might be considered "downgrades" in terms of stability
|
||||
const isDowngrade =
|
||||
currentVersionIndex !== -1 &&
|
||||
selectedVersionIndex !== -1 &&
|
||||
selectedVersionIndex > currentVersionIndex;
|
||||
profile.release_type === "stable" && selectedReleaseType === "nightly";
|
||||
setShowDowngradeWarning(isDowngrade);
|
||||
|
||||
if (!isDowngrade) {
|
||||
setAcknowledgeDowngrade(false);
|
||||
}
|
||||
}
|
||||
}, [selectedVersion, profile, availableVersions]);
|
||||
}, [selectedReleaseType, profile]);
|
||||
|
||||
const handleDownload = async () => {
|
||||
if (!profile || !selectedVersion) return;
|
||||
await downloadBrowser(profile.browser, selectedVersion);
|
||||
if (!profile || !selectedReleaseType) return;
|
||||
|
||||
const version =
|
||||
selectedReleaseType === "stable"
|
||||
? releaseTypes.stable
|
||||
: releaseTypes.nightly;
|
||||
if (!version) return;
|
||||
|
||||
await downloadBrowser(profile.browser, version);
|
||||
};
|
||||
|
||||
const handleVersionChange = async () => {
|
||||
if (!profile || !selectedVersion) return;
|
||||
if (!profile || !selectedReleaseType) return;
|
||||
|
||||
const version =
|
||||
selectedReleaseType === "stable"
|
||||
? releaseTypes.stable
|
||||
: releaseTypes.nightly;
|
||||
if (!version) return;
|
||||
|
||||
setIsUpdating(true);
|
||||
try {
|
||||
await invoke("update_profile_version", {
|
||||
profileName: profile.name,
|
||||
version: selectedVersion,
|
||||
version,
|
||||
});
|
||||
onVersionChanged();
|
||||
onClose();
|
||||
@@ -102,10 +128,15 @@ export function ChangeVersionDialog({
|
||||
}
|
||||
};
|
||||
|
||||
const selectedVersion =
|
||||
selectedReleaseType === "stable"
|
||||
? releaseTypes.stable
|
||||
: releaseTypes.nightly;
|
||||
|
||||
const canUpdate =
|
||||
profile &&
|
||||
selectedVersion &&
|
||||
selectedVersion !== profile.version &&
|
||||
selectedReleaseType &&
|
||||
selectedReleaseType !== profile.release_type &&
|
||||
selectedVersion &&
|
||||
isVersionDownloaded(selectedVersion) &&
|
||||
(!showDowngradeWarning || acknowledgeDowngrade);
|
||||
@@ -116,7 +147,7 @@ export function ChangeVersionDialog({
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Change Browser Version</DialogTitle>
|
||||
<DialogTitle>Change Release Type</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="grid gap-4 py-4">
|
||||
@@ -126,26 +157,33 @@ export function ChangeVersionDialog({
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-medium">Current Version:</Label>
|
||||
<div className="p-2 bg-muted rounded text-sm">
|
||||
{profile.version}
|
||||
<Label className="text-sm font-medium">Current Release:</Label>
|
||||
<div className="p-2 bg-muted rounded text-sm capitalize">
|
||||
{profile.release_type} ({profile.version})
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Version Selection */}
|
||||
{/* Release Type Selection */}
|
||||
<div className="grid gap-2">
|
||||
<Label>New Version</Label>
|
||||
<VersionSelector
|
||||
selectedVersion={selectedVersion}
|
||||
onVersionSelect={setSelectedVersion}
|
||||
availableVersions={availableVersions}
|
||||
downloadedVersions={downloadedVersions}
|
||||
isDownloading={isDownloading}
|
||||
onDownload={() => {
|
||||
void handleDownload();
|
||||
}}
|
||||
placeholder="Select version..."
|
||||
/>
|
||||
<Label>New Release Type</Label>
|
||||
{isLoadingReleaseTypes ? (
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Loading release types...
|
||||
</div>
|
||||
) : (
|
||||
<ReleaseTypeSelector
|
||||
selectedReleaseType={selectedReleaseType}
|
||||
onReleaseTypeSelect={setSelectedReleaseType}
|
||||
availableReleaseTypes={releaseTypes}
|
||||
browser={profile.browser}
|
||||
isDownloading={isDownloading}
|
||||
onDownload={() => {
|
||||
void handleDownload();
|
||||
}}
|
||||
placeholder="Select release type..."
|
||||
downloadedVersions={downloadedVersions}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Downgrade Warning */}
|
||||
@@ -153,12 +191,12 @@ export function ChangeVersionDialog({
|
||||
<Alert className="border-orange-700">
|
||||
<LuTriangleAlert className="h-4 w-4 text-orange-700" />
|
||||
<AlertTitle className="text-orange-700">
|
||||
Downgrade Warning
|
||||
Stability Warning
|
||||
</AlertTitle>
|
||||
<AlertDescription className="text-orange-700">
|
||||
You are about to downgrade from version {profile.version} to{" "}
|
||||
{selectedVersion}. This may lead to compatibility issues, data
|
||||
loss, or unexpected behavior.
|
||||
You are about to switch from stable to nightly releases. Nightly
|
||||
versions may be less stable and could contain bugs or incomplete
|
||||
features.
|
||||
<div className="flex items-center space-x-2 mt-3">
|
||||
<Checkbox
|
||||
id="acknowledge-downgrade"
|
||||
@@ -187,7 +225,7 @@ export function ChangeVersionDialog({
|
||||
}}
|
||||
disabled={!canUpdate}
|
||||
>
|
||||
{isUpdating ? "Updating..." : "Update Version"}
|
||||
{isUpdating ? "Updating..." : "Update Release Type"}
|
||||
</LoadingButton>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { LoadingButton } from "@/components/loading-button";
|
||||
import { ReleaseTypeSelector } from "@/components/release-type-selector";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import {
|
||||
@@ -24,11 +25,14 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { VersionSelector } from "@/components/version-selector";
|
||||
import { useBrowserDownload } from "@/hooks/use-browser-download";
|
||||
import { useBrowserSupport } from "@/hooks/use-browser-support";
|
||||
import { getBrowserDisplayName } from "@/lib/browser-utils";
|
||||
import type { BrowserProfile, ProxySettings } from "@/types";
|
||||
import type {
|
||||
BrowserProfile,
|
||||
BrowserReleaseTypes,
|
||||
ProxySettings,
|
||||
} from "@/types";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { useEffect, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
@@ -49,6 +53,7 @@ interface CreateProfileDialogProps {
|
||||
name: string;
|
||||
browserStr: BrowserTypeString;
|
||||
version: string;
|
||||
releaseType: string;
|
||||
proxy?: ProxySettings;
|
||||
}) => Promise<void>;
|
||||
}
|
||||
@@ -61,11 +66,18 @@ export function CreateProfileDialog({
|
||||
const [profileName, setProfileName] = useState("");
|
||||
const [selectedBrowser, setSelectedBrowser] =
|
||||
useState<BrowserTypeString | null>("mullvad-browser");
|
||||
const [selectedVersion, setSelectedVersion] = useState<string | null>(null);
|
||||
const [selectedReleaseType, setSelectedReleaseType] = useState<
|
||||
"stable" | "nightly" | null
|
||||
>(null);
|
||||
const [releaseTypes, setReleaseTypes] = useState<BrowserReleaseTypes>({
|
||||
stable: undefined,
|
||||
nightly: undefined,
|
||||
});
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const [existingProfiles, setExistingProfiles] = useState<BrowserProfile[]>(
|
||||
[],
|
||||
);
|
||||
const [isLoadingReleaseTypes, setIsLoadingReleaseTypes] = useState(false);
|
||||
|
||||
// Proxy settings
|
||||
const [proxyEnabled, setProxyEnabled] = useState(false);
|
||||
@@ -76,13 +88,10 @@ export function CreateProfileDialog({
|
||||
const [proxyPassword, setProxyPassword] = useState("");
|
||||
|
||||
const {
|
||||
availableVersions,
|
||||
downloadedVersions,
|
||||
isDownloading,
|
||||
loadVersions,
|
||||
loadDownloadedVersions,
|
||||
downloadBrowser,
|
||||
isVersionDownloaded,
|
||||
isDownloading,
|
||||
downloadedVersions,
|
||||
loadDownloadedVersions,
|
||||
} = useBrowserDownload();
|
||||
|
||||
const {
|
||||
@@ -110,29 +119,26 @@ export function CreateProfileDialog({
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen && selectedBrowser) {
|
||||
// Reset selected version when browser changes
|
||||
setSelectedVersion(null);
|
||||
void loadVersions(selectedBrowser);
|
||||
// Reset selected release type when browser changes
|
||||
setSelectedReleaseType(null);
|
||||
void loadReleaseTypes(selectedBrowser);
|
||||
void loadDownloadedVersions(selectedBrowser);
|
||||
}
|
||||
}, [isOpen, selectedBrowser, loadVersions, loadDownloadedVersions]);
|
||||
}, [isOpen, selectedBrowser, loadDownloadedVersions]);
|
||||
|
||||
// Set default version when versions are loaded and no version is selected
|
||||
// Set default release type when release types are loaded
|
||||
useEffect(() => {
|
||||
if (availableVersions.length > 0 && selectedBrowser) {
|
||||
// Always reset version when browser changes or versions are loaded
|
||||
// Find the latest stable version (not alpha/beta)
|
||||
const stableVersions = availableVersions.filter((v) => !v.is_nightly);
|
||||
|
||||
if (stableVersions.length > 0) {
|
||||
// Select the first stable version (they're already sorted newest first)
|
||||
setSelectedVersion(stableVersions[0].tag_name);
|
||||
} else if (availableVersions.length > 0) {
|
||||
// If no stable version found, select the first available version
|
||||
setSelectedVersion(availableVersions[0].tag_name);
|
||||
if (!selectedReleaseType && Object.keys(releaseTypes).length > 0) {
|
||||
// First try to set stable if it exists
|
||||
if (releaseTypes.stable) {
|
||||
setSelectedReleaseType("stable");
|
||||
}
|
||||
// If stable doesn't exist but nightly does, set nightly as default
|
||||
else if (releaseTypes.nightly && selectedBrowser !== "chromium") {
|
||||
setSelectedReleaseType("nightly");
|
||||
}
|
||||
}
|
||||
}, [availableVersions, selectedBrowser]);
|
||||
}, [releaseTypes, selectedReleaseType, selectedBrowser]);
|
||||
|
||||
const loadExistingProfiles = async () => {
|
||||
try {
|
||||
@@ -143,9 +149,34 @@ export function CreateProfileDialog({
|
||||
}
|
||||
};
|
||||
|
||||
const loadReleaseTypes = async (browser: string) => {
|
||||
try {
|
||||
setIsLoadingReleaseTypes(true);
|
||||
const types = await invoke<BrowserReleaseTypes>(
|
||||
"get_browser_release_types",
|
||||
{
|
||||
browserStr: browser,
|
||||
},
|
||||
);
|
||||
setReleaseTypes(types);
|
||||
} catch (error) {
|
||||
console.error("Failed to load release types:", error);
|
||||
toast.error("Failed to load available versions");
|
||||
} finally {
|
||||
setIsLoadingReleaseTypes(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownload = async () => {
|
||||
if (!selectedBrowser || !selectedVersion) return;
|
||||
await downloadBrowser(selectedBrowser, selectedVersion);
|
||||
if (!selectedBrowser || !selectedReleaseType) return;
|
||||
|
||||
const version =
|
||||
selectedReleaseType === "stable"
|
||||
? releaseTypes.stable
|
||||
: releaseTypes.nightly;
|
||||
if (!version) return;
|
||||
|
||||
await downloadBrowser(selectedBrowser, version);
|
||||
};
|
||||
|
||||
const validateProfileName = (name: string): string | null => {
|
||||
@@ -178,7 +209,7 @@ export function CreateProfileDialog({
|
||||
}, [selectedBrowser, proxyEnabled]);
|
||||
|
||||
const handleCreate = async () => {
|
||||
if (!profileName.trim() || !selectedBrowser || !selectedVersion) return;
|
||||
if (!profileName.trim() || !selectedBrowser || !selectedReleaseType) return;
|
||||
|
||||
// Validate profile name
|
||||
const nameError = validateProfileName(profileName);
|
||||
@@ -187,6 +218,15 @@ export function CreateProfileDialog({
|
||||
return;
|
||||
}
|
||||
|
||||
const version =
|
||||
selectedReleaseType === "stable"
|
||||
? releaseTypes.stable
|
||||
: releaseTypes.nightly;
|
||||
if (!version) {
|
||||
toast.error("Selected release type is not available");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsCreating(true);
|
||||
try {
|
||||
const proxy =
|
||||
@@ -204,13 +244,14 @@ export function CreateProfileDialog({
|
||||
await onCreateProfile({
|
||||
name: profileName.trim(),
|
||||
browserStr: selectedBrowser,
|
||||
version: selectedVersion,
|
||||
version,
|
||||
releaseType: selectedReleaseType,
|
||||
proxy,
|
||||
});
|
||||
|
||||
// Reset form
|
||||
setProfileName("");
|
||||
setSelectedVersion(null);
|
||||
setSelectedReleaseType(null);
|
||||
setProxyEnabled(false);
|
||||
setProxyHost("");
|
||||
setProxyPort(8080);
|
||||
@@ -227,11 +268,17 @@ export function CreateProfileDialog({
|
||||
const nameError = profileName.trim()
|
||||
? validateProfileName(profileName)
|
||||
: null;
|
||||
|
||||
const selectedVersion =
|
||||
selectedReleaseType === "stable"
|
||||
? releaseTypes.stable
|
||||
: releaseTypes.nightly;
|
||||
|
||||
const canCreate =
|
||||
profileName.trim() &&
|
||||
selectedBrowser &&
|
||||
selectedReleaseType &&
|
||||
selectedVersion &&
|
||||
isVersionDownloaded(selectedVersion) &&
|
||||
(!proxyEnabled || isProxyDisabled || (proxyHost && proxyPort)) &&
|
||||
!nameError;
|
||||
|
||||
@@ -322,20 +369,27 @@ export function CreateProfileDialog({
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Version Selection */}
|
||||
{/* Release Type Selection */}
|
||||
<div className="grid gap-2">
|
||||
<Label>Version</Label>
|
||||
<VersionSelector
|
||||
selectedVersion={selectedVersion}
|
||||
onVersionSelect={setSelectedVersion}
|
||||
availableVersions={availableVersions}
|
||||
downloadedVersions={downloadedVersions}
|
||||
isDownloading={isDownloading}
|
||||
onDownload={() => {
|
||||
void handleDownload();
|
||||
}}
|
||||
placeholder="Select version..."
|
||||
/>
|
||||
<Label>Release Type</Label>
|
||||
{isLoadingReleaseTypes ? (
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Loading release types...
|
||||
</div>
|
||||
) : (
|
||||
<ReleaseTypeSelector
|
||||
selectedReleaseType={selectedReleaseType}
|
||||
onReleaseTypeSelect={setSelectedReleaseType}
|
||||
availableReleaseTypes={releaseTypes}
|
||||
browser={selectedBrowser ?? ""}
|
||||
isDownloading={isDownloading}
|
||||
onDownload={() => {
|
||||
void handleDownload();
|
||||
}}
|
||||
placeholder="Select release type..."
|
||||
downloadedVersions={downloadedVersions}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Proxy Settings */}
|
||||
|
||||
@@ -260,10 +260,21 @@ export function ProfilesDataTable({
|
||||
cell: ({ row }) => {
|
||||
const browser: string = row.getValue("browser");
|
||||
const IconComponent = getBrowserIcon(browser);
|
||||
return (
|
||||
const browserDisplayName = getBrowserDisplayName(browser);
|
||||
return browserDisplayName.length > 15 ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="flex gap-2 items-center">
|
||||
{IconComponent && <IconComponent className="w-4 h-4" />}
|
||||
<span>{browserDisplayName.slice(0, 15)}...</span>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{browserDisplayName}</TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<div className="flex gap-2 items-center">
|
||||
{IconComponent && <IconComponent className="w-4 h-4" />}
|
||||
<span>{getBrowserDisplayName(browser)}</span>
|
||||
<span>{browserDisplayName}</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
@@ -274,6 +285,36 @@ export function ProfilesDataTable({
|
||||
return browserA.localeCompare(browserB);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "release_type",
|
||||
header: "Release",
|
||||
cell: ({ row }) => {
|
||||
const releaseType: string = row.getValue("release_type");
|
||||
const isNightly = releaseType === "nightly";
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<span
|
||||
className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${
|
||||
isNightly
|
||||
? "text-yellow-800 bg-yellow-100 dark:bg-yellow-900 dark:text-yellow-200"
|
||||
: "text-green-800 bg-green-100 dark:bg-green-900 dark:text-green-200"
|
||||
}`}
|
||||
>
|
||||
{isNightly ? "Nightly" : "Stable"}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
enableSorting: true,
|
||||
sortingFn: (rowA, rowB, columnId) => {
|
||||
const releaseA: string = rowA.getValue(columnId);
|
||||
const releaseB: string = rowB.getValue(columnId);
|
||||
// Sort with "stable" before "nightly"
|
||||
if (releaseA === "stable" && releaseB === "nightly") return -1;
|
||||
if (releaseA === "nightly" && releaseB === "stable") return 1;
|
||||
return 0;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "proxy",
|
||||
header: "Proxy",
|
||||
@@ -346,7 +387,7 @@ export function ProfilesDataTable({
|
||||
}}
|
||||
disabled={!isClient || isRunning || isBrowserUpdating}
|
||||
>
|
||||
Change version
|
||||
Switch Release
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
@@ -527,6 +568,11 @@ export function ProfilesDataTable({
|
||||
setDeleteConfirmationName(e.target.value);
|
||||
setDeleteError(null);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
void handleDelete();
|
||||
}
|
||||
}}
|
||||
placeholder="Type the profile name here"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
"use client";
|
||||
|
||||
import { LoadingButton } from "@/components/loading-button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from "@/components/ui/command";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { BrowserReleaseTypes } from "@/types";
|
||||
import { useState } from "react";
|
||||
import { LuDownload } from "react-icons/lu";
|
||||
import { LuCheck, LuChevronsUpDown } from "react-icons/lu";
|
||||
|
||||
interface ReleaseTypeSelectorProps {
|
||||
selectedReleaseType: "stable" | "nightly" | null;
|
||||
onReleaseTypeSelect: (releaseType: "stable" | "nightly" | null) => void;
|
||||
availableReleaseTypes: BrowserReleaseTypes;
|
||||
browser: string;
|
||||
isDownloading: boolean;
|
||||
onDownload: () => void;
|
||||
placeholder?: string;
|
||||
showDownloadButton?: boolean;
|
||||
downloadedVersions?: string[];
|
||||
}
|
||||
|
||||
export function ReleaseTypeSelector({
|
||||
selectedReleaseType,
|
||||
onReleaseTypeSelect,
|
||||
availableReleaseTypes,
|
||||
browser,
|
||||
isDownloading,
|
||||
onDownload,
|
||||
placeholder = "Select release type...",
|
||||
showDownloadButton = true,
|
||||
downloadedVersions = [],
|
||||
}: ReleaseTypeSelectorProps) {
|
||||
const [popoverOpen, setPopoverOpen] = useState(false);
|
||||
|
||||
const releaseOptions = [
|
||||
...(availableReleaseTypes.stable
|
||||
? [{ type: "stable" as const, version: availableReleaseTypes.stable }]
|
||||
: []),
|
||||
...(availableReleaseTypes.nightly && browser !== "chromium"
|
||||
? [{ type: "nightly" as const, version: availableReleaseTypes.nightly }]
|
||||
: []),
|
||||
];
|
||||
|
||||
const selectedDisplayText = selectedReleaseType
|
||||
? selectedReleaseType === "stable"
|
||||
? "Stable"
|
||||
: "Nightly"
|
||||
: placeholder;
|
||||
|
||||
const selectedVersion =
|
||||
selectedReleaseType === "stable"
|
||||
? availableReleaseTypes.stable
|
||||
: selectedReleaseType === "nightly"
|
||||
? availableReleaseTypes.nightly
|
||||
: null;
|
||||
|
||||
const isVersionDownloaded =
|
||||
selectedVersion && downloadedVersions.includes(selectedVersion);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<Popover open={popoverOpen} onOpenChange={setPopoverOpen} modal={true}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={popoverOpen}
|
||||
className="justify-between w-full"
|
||||
>
|
||||
{selectedDisplayText}
|
||||
<LuChevronsUpDown className="ml-2 w-4 h-4 opacity-50 shrink-0" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[300px] p-0">
|
||||
<Command>
|
||||
<CommandEmpty>No release types available.</CommandEmpty>
|
||||
<CommandList>
|
||||
<CommandGroup>
|
||||
{releaseOptions.map((option) => {
|
||||
const isDownloaded = downloadedVersions.includes(
|
||||
option.version,
|
||||
);
|
||||
return (
|
||||
<CommandItem
|
||||
key={option.type}
|
||||
value={option.type}
|
||||
onSelect={(currentValue) => {
|
||||
const selectedType = currentValue as
|
||||
| "stable"
|
||||
| "nightly";
|
||||
onReleaseTypeSelect(
|
||||
selectedType === selectedReleaseType
|
||||
? null
|
||||
: selectedType,
|
||||
);
|
||||
setPopoverOpen(false);
|
||||
}}
|
||||
>
|
||||
<LuCheck
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
selectedReleaseType === option.type
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
<div className="flex gap-2 items-center">
|
||||
<span className="capitalize">{option.type}</span>
|
||||
{option.type === "nightly" && (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
Nightly
|
||||
</Badge>
|
||||
)}
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{option.version}
|
||||
</Badge>
|
||||
{isDownloaded && (
|
||||
<Badge variant="default" className="text-xs">
|
||||
Downloaded
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</CommandItem>
|
||||
);
|
||||
})}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
{showDownloadButton &&
|
||||
selectedReleaseType &&
|
||||
selectedVersion &&
|
||||
!isVersionDownloaded && (
|
||||
<LoadingButton
|
||||
isLoading={isDownloading}
|
||||
onClick={() => {
|
||||
onDownload();
|
||||
}}
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
>
|
||||
<LuDownload className="mr-2 w-4 h-4" />
|
||||
{isDownloading ? "Downloading..." : "Download Browser"}
|
||||
</LoadingButton>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -20,6 +20,7 @@ export interface BrowserProfile {
|
||||
proxy?: ProxySettings;
|
||||
process_id?: number;
|
||||
last_launch?: number;
|
||||
release_type: string; // "stable" or "nightly"
|
||||
}
|
||||
|
||||
export interface DetectedProfile {
|
||||
@@ -29,6 +30,11 @@ export interface DetectedProfile {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface BrowserReleaseTypes {
|
||||
stable?: string;
|
||||
nightly?: string;
|
||||
}
|
||||
|
||||
export interface AppUpdateInfo {
|
||||
current_version: string;
|
||||
new_version: string;
|
||||
|
||||
Reference in New Issue
Block a user