diff --git a/src/app/page.tsx b/src/app/page.tsx index f3bcbea..a18f8b2 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -16,7 +16,6 @@ import { PermissionDialog } from "@/components/permission-dialog"; import { ProfilesDataTable } from "@/components/profile-data-table"; import { ProfileSelectorDialog } from "@/components/profile-selector-dialog"; import { ProxyManagementDialog } from "@/components/proxy-management-dialog"; -import { ProxySettingsDialog } from "@/components/proxy-settings-dialog"; import { SettingsDialog } from "@/components/settings-dialog"; import { useAppUpdateNotifications } from "@/hooks/use-app-update-notifications"; import type { PermissionType } from "@/hooks/use-permissions"; @@ -43,7 +42,6 @@ interface PendingUrl { export default function Home() { const [profiles, setProfiles] = useState([]); const [error, setError] = useState(null); - const [proxyDialogOpen, setProxyDialogOpen] = useState(false); const [createProfileDialogOpen, setCreateProfileDialogOpen] = useState(false); const [settingsDialogOpen, setSettingsDialogOpen] = useState(false); const [importProfileDialogOpen, setImportProfileDialogOpen] = useState(false); @@ -61,8 +59,6 @@ export default function Home() { >([]); const [selectedProfiles, setSelectedProfiles] = useState([]); const [pendingUrls, setPendingUrls] = useState([]); - const [currentProfileForProxy, setCurrentProfileForProxy] = - useState(null); const [currentProfileForCamoufoxConfig, setCurrentProfileForCamoufoxConfig] = useState(null); const [hasCheckedStartupPrompt, setHasCheckedStartupPrompt] = useState(false); @@ -349,11 +345,6 @@ export default function Home() { } }, [handleUrlOpen]); - const openProxyDialog = useCallback((profile: BrowserProfile | null) => { - setCurrentProfileForProxy(profile); - setProxyDialogOpen(true); - }, []); - const handleConfigureCamoufox = useCallback((profile: BrowserProfile) => { setCurrentProfileForCamoufoxConfig(profile); setCamoufoxConfigDialogOpen(true); @@ -378,28 +369,6 @@ export default function Home() { [loadProfiles], ); - const handleSaveProxy = useCallback( - async (proxyId: string | null) => { - setProxyDialogOpen(false); - setError(null); - - try { - if (currentProfileForProxy) { - await invoke("update_profile_proxy", { - profileName: currentProfileForProxy.name, - proxyId: proxyId, - }); - } - await loadProfiles(); - // Trigger proxy data reload in the table - } catch (err: unknown) { - console.error("Failed to update proxy settings:", err); - setError(`Failed to update proxy settings: ${JSON.stringify(err)}`); - } - }, - [currentProfileForProxy, loadProfiles], - ); - const loadGroups = useCallback(async () => { setGroupsLoading(true); try { @@ -795,7 +764,6 @@ export default function Home() { data={profiles} onLaunchProfile={launchProfile} onKillProfile={handleKillProfile} - onProxySettings={openProxyDialog} onDeleteProfile={handleDeleteProfile} onRenameProfile={handleRenameProfile} onConfigureCamoufox={handleConfigureCamoufox} @@ -810,16 +778,6 @@ export default function Home() { - { - setProxyDialogOpen(false); - }} - onSave={handleSaveProxy} - initialProxyId={currentProfileForProxy?.proxy_id} - browserType={currentProfileForProxy?.browser} - /> - { diff --git a/src/components/profile-data-table.tsx b/src/components/profile-data-table.tsx index 8e098af..1a0ac35 100644 --- a/src/components/profile-data-table.tsx +++ b/src/components/profile-data-table.tsx @@ -12,10 +12,18 @@ import { invoke } from "@tauri-apps/api/core"; import * as React from "react"; import { CiCircleCheck } from "react-icons/ci"; import { IoEllipsisHorizontal } from "react-icons/io5"; -import { LuChevronDown, LuChevronUp } from "react-icons/lu"; +import { LuCheck, LuChevronDown, LuChevronUp } from "react-icons/lu"; import { DeleteConfirmationDialog } from "@/components/delete-confirmation-dialog"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; import { Dialog, DialogContent, @@ -29,6 +37,11 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Table, @@ -61,13 +74,11 @@ interface ProfilesDataTableProps { data: BrowserProfile[]; onLaunchProfile: (profile: BrowserProfile) => void | Promise; onKillProfile: (profile: BrowserProfile) => void | Promise; - onProxySettings: (profile: BrowserProfile) => void; onDeleteProfile: (profile: BrowserProfile) => void | Promise; onRenameProfile: (oldName: string, newName: string) => Promise; onConfigureCamoufox?: (profile: BrowserProfile) => void; runningProfiles: Set; isUpdating: (browser: string) => boolean; - onReloadProxyData?: () => void | Promise; onDeleteSelectedProfiles?: (profileNames: string[]) => Promise; onAssignProfilesToGroup?: (profileNames: string[]) => void; selectedGroupId?: string | null; @@ -79,13 +90,11 @@ export function ProfilesDataTable({ data, onLaunchProfile, onKillProfile, - onProxySettings, onDeleteProfile, onRenameProfile, onConfigureCamoufox, runningProfiles, isUpdating, - onDeleteSelectedProfiles: _onDeleteSelectedProfiles, onAssignProfilesToGroup, selectedGroupId, selectedProfiles: externalSelectedProfiles = [], @@ -108,38 +117,32 @@ export function ProfilesDataTable({ ); const [storedProxies, setStoredProxies] = React.useState([]); + const [openProxySelectorFor, setOpenProxySelectorFor] = React.useState< + string | null + >(null); + const [proxyOverrides, setProxyOverrides] = React.useState< + Record + >({}); const [selectedProfiles, setSelectedProfiles] = React.useState>( new Set(externalSelectedProfiles), ); const [showCheckboxes, setShowCheckboxes] = React.useState(false); - // Helper function to check if a profile has a proxy - const hasProxy = React.useCallback( - (profile: BrowserProfile): boolean => { - if (!profile.proxy_id) return false; - const proxy = storedProxies.find((p) => p.id === profile.proxy_id); - return proxy !== undefined; + const handleProxySelection = React.useCallback( + async (profileName: string, proxyId: string | null) => { + try { + await invoke("update_profile_proxy", { + profileName, + proxyId, + }); + setProxyOverrides((prev) => ({ ...prev, [profileName]: proxyId })); + } catch (error) { + console.error("Failed to update proxy settings:", error); + } finally { + setOpenProxySelectorFor(null); + } }, - [storedProxies], - ); - - // Helper function to get proxy info for a profile - const getProxyInfo = React.useCallback( - (profile: BrowserProfile): StoredProxy | null => { - if (!profile.proxy_id) return null; - return storedProxies.find((p) => p.id === profile.proxy_id) ?? null; - }, - [storedProxies], - ); - - // Helper function to get proxy name for display - const getProxyDisplayName = React.useCallback( - (profile: BrowserProfile): string => { - if (!profile.proxy_id) return "Disabled"; - const proxy = storedProxies.find((p) => p.id === profile.proxy_id); - return proxy?.name ?? "Unknown Proxy"; - }, - [storedProxies], + [], ); // Filter data by selected group @@ -669,41 +672,135 @@ export function ProfilesDataTable({ header: "Proxy", cell: ({ row }) => { const profile = row.original; - const profileHasProxy = hasProxy(profile); - const proxyDisplayName = getProxyDisplayName(profile); - const proxyInfo = getProxyInfo(profile); + const isRunning = + browserState.isClient && runningProfiles.has(profile.name); + const isLaunching = launchingProfiles.has(profile.name); + const isStopping = stoppingProfiles.has(profile.name); + const isBrowserUpdating = isUpdating(profile.browser); + const isDisabled = + isRunning || isLaunching || isStopping || isBrowserUpdating; + const hasOverride = Object.hasOwn(proxyOverrides, profile.name); + const effectiveProxyId = hasOverride + ? proxyOverrides[profile.name] + : (profile.proxy_id ?? null); + const effectiveProxy = effectiveProxyId + ? (storedProxies.find((p) => p.id === effectiveProxyId) ?? null) + : null; + const displayName = + profile.browser === "tor-browser" + ? "Not supported" + : effectiveProxy + ? effectiveProxy.name + : "Not Selected"; + const profileHasProxy = Boolean(effectiveProxy); const tooltipText = profile.browser === "tor-browser" ? "Proxies are not supported for TOR browser" - : profileHasProxy && proxyInfo - ? `${proxyDisplayName}, ${proxyInfo.proxy_settings.proxy_type.toUpperCase()} (${ - proxyInfo.proxy_settings.host - }:${proxyInfo.proxy_settings.port})` + : profileHasProxy && effectiveProxy + ? `${effectiveProxy.name} (${effectiveProxy.proxy_settings.proxy_type.toUpperCase()})` : ""; + const isSelectorOpen = openProxySelectorFor === profile.name; + + if (profile.browser === "tor-browser") { + return ( + + + + + Not supported + + + + {tooltipText && {tooltipText}} + + ); + } return ( - - - - {profileHasProxy && ( - - )} - {proxyDisplayName.length > 10 ? ( - - {proxyDisplayName.slice(0, 10)}... + + setOpenProxySelectorFor(open ? profile.name : null) + } + > + + + + + {profileHasProxy && ( + + )} + {displayName.length > 18 ? ( + + {displayName.slice(0, 18)}... + + ) : ( + + {displayName} + + )} - ) : ( - - {profile.browser === "tor-browser" - ? "Not supported" - : proxyDisplayName} - - )} - - - {tooltipText && {tooltipText}} - + + + {tooltipText && {tooltipText}} + + + {!isDisabled && ( + + + + + No proxies found. + + + void handleProxySelection(profile.name, null) + } + > + + No Proxy + + {storedProxies.map((proxy) => ( + + void handleProxySelection(profile.name, proxy.id) + } + > + + {proxy.name} + + ))} + + + + + )} + ); }, }, @@ -734,14 +831,6 @@ export function ProfilesDataTable({ - { - onProxySettings(profile); - }} - disabled={isDisabled} - > - Configure Proxy - { if (onAssignProfilesToGroup) { @@ -794,10 +883,6 @@ export function ProfilesDataTable({ handleIconClick, runningProfiles, browserState, - hasProxy, - getProxyDisplayName, - getProxyInfo, - onProxySettings, onLaunchProfile, onKillProfile, onConfigureCamoufox, @@ -807,6 +892,10 @@ export function ProfilesDataTable({ stoppingProfiles, filteredData, browserState.isClient, + storedProxies, + openProxySelectorFor, + proxyOverrides, + handleProxySelection, ], ); diff --git a/src/components/proxy-settings-dialog.tsx b/src/components/proxy-settings-dialog.tsx deleted file mode 100644 index b02a845..0000000 --- a/src/components/proxy-settings-dialog.tsx +++ /dev/null @@ -1,330 +0,0 @@ -"use client"; - -import { invoke } from "@tauri-apps/api/core"; -import { listen } from "@tauri-apps/api/event"; -import { useCallback, useEffect, useState } from "react"; -import { FiPlus } from "react-icons/fi"; -import { toast } from "sonner"; -import { ProxyFormDialog } from "@/components/proxy-form-dialog"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent } from "@/components/ui/card"; -import { - Dialog, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import { Label } from "@/components/ui/label"; -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from "@/components/ui/tooltip"; -import { cn } from "@/lib/utils"; -import type { StoredProxy } from "@/types"; -import { RippleButton } from "./ui/ripple"; - -interface ProxySettingsDialogProps { - isOpen: boolean; - onClose: () => void; - onSave: (proxyId: string | null) => void; - initialProxyId?: string | null; - browserType?: string; -} - -export function ProxySettingsDialog({ - isOpen, - onClose, - onSave, - initialProxyId, - browserType, -}: ProxySettingsDialogProps) { - const [storedProxies, setStoredProxies] = useState([]); - const [selectedProxyId, setSelectedProxyId] = useState( - initialProxyId || null, - ); - const [loading, setLoading] = useState(false); - const [showProxyForm, setShowProxyForm] = useState(false); - const [proxyUsage, setProxyUsage] = useState>({}); - - // Helper to determine if proxy should be disabled for the selected browser - const isProxyDisabled = browserType === "tor-browser"; - - const loadStoredProxies = useCallback(async () => { - try { - setLoading(true); - const proxies = await invoke("get_stored_proxies"); - setStoredProxies(proxies); - } catch (error) { - console.error("Failed to load stored proxies:", error); - toast.error("Failed to load proxies"); - } finally { - setLoading(false); - } - }, []); - - const loadProxyUsage = useCallback(async () => { - try { - const profiles = await invoke>( - "list_browser_profiles", - ); - const counts: Record = {}; - for (const p of profiles) { - if (p.proxy_id) { - counts[p.proxy_id] = (counts[p.proxy_id] ?? 0) + 1; - } - } - setProxyUsage(counts); - } catch (error) { - // Non-fatal - console.error("Failed to load proxy usage:", error); - } - }, []); - - useEffect(() => { - if (isOpen) { - loadStoredProxies(); - void loadProxyUsage(); - if (isProxyDisabled) { - setSelectedProxyId(null); - } else { - // Reset to initial proxy ID when dialog opens - setSelectedProxyId(initialProxyId || null); - } - } - }, [ - isOpen, - isProxyDisabled, - loadStoredProxies, - initialProxyId, - loadProxyUsage, - ]); - - // Refresh usage when profiles change - useEffect(() => { - let unlisten: (() => void) | undefined; - const setup = async () => { - try { - unlisten = await listen("profile-updated", () => { - void loadProxyUsage(); - }); - } catch (e) { - console.error(e); - } - }; - if (isOpen) void setup(); - return () => { - if (unlisten) unlisten(); - }; - }, [isOpen, loadProxyUsage]); - - const handleCreateProxy = useCallback(() => { - setShowProxyForm(true); - }, []); - - const handleProxySaved = useCallback((savedProxy: StoredProxy) => { - setStoredProxies((prev) => { - const existingIndex = prev.findIndex((p) => p.id === savedProxy.id); - if (existingIndex >= 0) { - // Update existing proxy - const updated = [...prev]; - updated[existingIndex] = savedProxy; - return updated; - } else { - // Add new proxy - return [...prev, savedProxy]; - } - }); - setSelectedProxyId(savedProxy.id); - setShowProxyForm(false); - }, []); - - const handleProxyFormClose = useCallback(() => { - setShowProxyForm(false); - }, []); - - const handleSave = () => { - onSave(selectedProxyId); - }; - - const hasChanged = () => { - return selectedProxyId !== initialProxyId; - }; - - return ( - <> - { - if (!open) { - onClose(); - } - }} - > - - - Proxy Settings - - -
- {isProxyDisabled && ( -
-

- Tor Browser has its own built-in proxy system and doesn't - support additional proxy configuration. -

-
- )} - - {!isProxyDisabled && ( - <> - {/* Proxy Selection */} -
-
- - - - - - Create - - - -

Create a new proxy configuration

-
-
-
- -
- - - {loading ? ( -

- Loading proxies... -

- ) : ( - storedProxies.map((proxy) => ( - - )) - )} - - {!loading && storedProxies.length === 0 && ( -
-

- No saved proxies available. -

- -
- )} -
-
- - )} -
- - - - Cancel - - - Save - - -
-
- - - - ); -}