From e12a5661b1dbd5c651d015d8c840ec4adfca5caa Mon Sep 17 00:00:00 2001 From: zhom <2717306+zhom@users.noreply.github.com> Date: Tue, 19 Aug 2025 13:31:46 +0400 Subject: [PATCH] refactor: use ids instead of names for all profile operations --- .vscode/settings.json | 1 + src-tauri/src/browser_runner.rs | 14 +-- src-tauri/src/group_manager.rs | 8 +- src-tauri/src/lib.rs | 1 - src-tauri/src/profile/manager.rs | 96 +++++++++------ src/app/page.tsx | 22 ++-- src/components/delete-confirmation-dialog.tsx | 22 ++-- src/components/delete-group-dialog.tsx | 8 +- src/components/group-assignment-dialog.tsx | 23 ++-- src/components/profile-data-table.tsx | 113 ++++++++++-------- src/components/profile-selector-dialog.tsx | 2 +- 11 files changed, 177 insertions(+), 133 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 5a9f887..4225b89 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -20,6 +20,7 @@ "CFURL", "checkin", "chrono", + "ciphertext", "CLICOLOR", "clippy", "cmdk", diff --git a/src-tauri/src/browser_runner.rs b/src-tauri/src/browser_runner.rs index c010d9b..9cd3de7 100644 --- a/src-tauri/src/browser_runner.rs +++ b/src-tauri/src/browser_runner.rs @@ -2064,12 +2064,12 @@ pub async fn launch_browser_profile( #[tauri::command] pub async fn update_profile_proxy( app_handle: tauri::AppHandle, - profile_name: String, + profile_id: String, proxy_id: Option, ) -> Result { let profile_manager = ProfileManager::instance(); profile_manager - .update_profile_proxy(app_handle, &profile_name, proxy_id) + .update_profile_proxy(app_handle, &profile_id, proxy_id) .await .map_err(|e| format!("Failed to update profile: {e}")) } @@ -2077,12 +2077,12 @@ pub async fn update_profile_proxy( #[tauri::command] pub fn update_profile_tags( app_handle: tauri::AppHandle, - profile_name: String, + profile_id: String, tags: Vec, ) -> Result { let profile_manager = ProfileManager::instance(); profile_manager - .update_profile_tags(&app_handle, &profile_name, tags) + .update_profile_tags(&app_handle, &profile_id, tags) .map_err(|e| format!("Failed to update profile tags: {e}")) } @@ -2101,12 +2101,12 @@ pub async fn check_browser_status( #[tauri::command] pub fn rename_profile( app_handle: tauri::AppHandle, - old_name: &str, - new_name: &str, + profile_id: String, + new_name: String, ) -> Result { let profile_manager = ProfileManager::instance(); profile_manager - .rename_profile(&app_handle, old_name, new_name) + .rename_profile(&app_handle, &profile_id, &new_name) .map_err(|e| format!("Failed to rename profile: {e}")) } diff --git a/src-tauri/src/group_manager.rs b/src-tauri/src/group_manager.rs index 877ee21..ce92a0b 100644 --- a/src-tauri/src/group_manager.rs +++ b/src-tauri/src/group_manager.rs @@ -293,22 +293,22 @@ pub async fn delete_profile_group( #[tauri::command] pub async fn assign_profiles_to_group( app_handle: tauri::AppHandle, - profile_names: Vec, + profile_ids: Vec, group_id: Option, ) -> Result<(), String> { let profile_manager = crate::profile::ProfileManager::instance(); profile_manager - .assign_profiles_to_group(&app_handle, profile_names, group_id) + .assign_profiles_to_group(&app_handle, profile_ids, group_id) .map_err(|e| format!("Failed to assign profiles to group: {e}")) } #[tauri::command] pub async fn delete_selected_profiles( app_handle: tauri::AppHandle, - profile_names: Vec, + profile_ids: Vec, ) -> Result<(), String> { let profile_manager = crate::profile::ProfileManager::instance(); profile_manager - .delete_multiple_profiles(&app_handle, profile_names) + .delete_multiple_profiles(&app_handle, profile_ids) .map_err(|e| format!("Failed to delete profiles: {e}")) } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 315da4d..0231d10 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,5 +1,4 @@ // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ -use directories::BaseDirs; use std::env; use std::sync::Mutex; use tauri::{Emitter, Manager, Runtime, WebviewUrl, WebviewWindow, WebviewWindowBuilder}; diff --git a/src-tauri/src/profile/manager.rs b/src-tauri/src/profile/manager.rs index 020a4f4..0313a1b 100644 --- a/src-tauri/src/profile/manager.rs +++ b/src-tauri/src/profile/manager.rs @@ -268,7 +268,7 @@ impl ProfileManager { pub fn rename_profile( &self, app_handle: &tauri::AppHandle, - old_name: &str, + profile_id: &str, new_name: &str, ) -> Result> { // Check if new name already exists (case insensitive) @@ -280,11 +280,13 @@ impl ProfileManager { return Err(format!("Profile with name '{new_name}' already exists").into()); } - // Find the profile by old name + // Find the profile by ID + let profile_uuid = uuid::Uuid::parse_str(profile_id) + .map_err(|_| format!("Invalid profile ID: {profile_id}"))?; let mut profile = existing_profiles .into_iter() - .find(|p| p.name == old_name) - .ok_or_else(|| format!("Profile '{old_name}' not found"))?; + .find(|p| p.id == profile_uuid) + .ok_or_else(|| format!("Profile with ID '{profile_id}' not found"))?; // Update profile name (no need to move directories since we use UUID) profile.name = new_name.to_string(); @@ -308,16 +310,18 @@ impl ProfileManager { pub fn delete_profile( &self, app_handle: &tauri::AppHandle, - profile_name: &str, + profile_id: &str, ) -> Result<(), Box> { - println!("Attempting to delete profile: {profile_name}"); + println!("Attempting to delete profile with ID: {profile_id}"); - // Find the profile by name + // Find the profile by ID + let profile_uuid = uuid::Uuid::parse_str(profile_id) + .map_err(|_| format!("Invalid profile ID: {profile_id}"))?; let profiles = self.list_profiles()?; let profile = profiles .into_iter() - .find(|p| p.name == profile_name) - .ok_or_else(|| format!("Profile '{profile_name}' not found"))?; + .find(|p| p.id == profile_uuid) + .ok_or_else(|| format!("Profile with ID '{profile_id}' not found"))?; // Check if browser is running if profile.process_id.is_some() { @@ -338,10 +342,10 @@ impl ProfileManager { // Verify deletion was successful if profile_uuid_dir.exists() { - return Err(format!("Failed to completely delete profile '{profile_name}'").into()); + return Err(format!("Failed to completely delete profile '{}'", profile.name).into()); } - println!("Profile '{profile_name}' deleted successfully"); + println!("Profile '{}' (ID: {}) deleted successfully", profile.name, profile_id); // Rebuild tag suggestions after deletion let _ = crate::tag_manager::TAG_MANAGER.lock().map(|tm| { @@ -359,15 +363,17 @@ impl ProfileManager { pub fn update_profile_version( &self, app_handle: &tauri::AppHandle, - profile_name: &str, + profile_id: &str, version: &str, ) -> Result> { - // Find the profile by name + // Find the profile by ID + let profile_uuid = uuid::Uuid::parse_str(profile_id) + .map_err(|_| format!("Invalid profile ID: {profile_id}"))?; let profiles = self.list_profiles()?; let mut profile = profiles .into_iter() - .find(|p| p.name == profile_name) - .ok_or_else(|| format!("Profile {profile_name} not found"))?; + .find(|p| p.id == profile_uuid) + .ok_or_else(|| format!("Profile with ID '{profile_id}' not found"))?; // Check if the browser is currently running if profile.process_id.is_some() { @@ -411,22 +417,24 @@ impl ProfileManager { pub fn assign_profiles_to_group( &self, app_handle: &tauri::AppHandle, - profile_names: Vec, + profile_ids: Vec, group_id: Option, ) -> Result<(), Box> { let profiles = self.list_profiles()?; - for profile_name in profile_names { + for profile_id in profile_ids { + let profile_uuid = uuid::Uuid::parse_str(&profile_id) + .map_err(|_| format!("Invalid profile ID: {profile_id}"))?; let mut profile = profiles .iter() - .find(|p| p.name == profile_name) - .ok_or_else(|| format!("Profile '{profile_name}' not found"))? + .find(|p| p.id == profile_uuid) + .ok_or_else(|| format!("Profile with ID '{profile_id}' not found"))? .clone(); // Check if browser is running if profile.process_id.is_some() { return Err(format!( - "Cannot modify group for profile '{profile_name}' while browser is running. Please stop the browser first." + "Cannot modify group for profile '{}' while browser is running. Please stop the browser first.", profile.name ).into()); } @@ -450,15 +458,17 @@ impl ProfileManager { pub fn update_profile_tags( &self, app_handle: &tauri::AppHandle, - profile_name: &str, + profile_id: &str, tags: Vec, ) -> Result> { - // Find the profile by name + // Find the profile by ID + let profile_uuid = uuid::Uuid::parse_str(profile_id) + .map_err(|_| format!("Invalid profile ID: {profile_id}"))?; let profiles = self.list_profiles()?; let mut profile = profiles .into_iter() - .find(|p| p.name == profile_name) - .ok_or_else(|| format!("Profile {profile_name} not found"))?; + .find(|p| p.id == profile_uuid) + .ok_or_else(|| format!("Profile with ID '{profile_id}' not found"))?; let mut seen = std::collections::HashSet::new(); let mut deduped: Vec = Vec::with_capacity(tags.len()); @@ -488,21 +498,23 @@ impl ProfileManager { pub fn delete_multiple_profiles( &self, app_handle: &tauri::AppHandle, - profile_names: Vec, + profile_ids: Vec, ) -> Result<(), Box> { let profiles = self.list_profiles()?; - for profile_name in profile_names { + for profile_id in profile_ids { + let profile_uuid = uuid::Uuid::parse_str(&profile_id) + .map_err(|_| format!("Invalid profile ID: {profile_id}"))?; let profile = profiles .iter() - .find(|p| p.name == profile_name) - .ok_or_else(|| format!("Profile '{profile_name}' not found"))?; + .find(|p| p.id == profile_uuid) + .ok_or_else(|| format!("Profile with ID '{profile_id}' not found"))?; // Check if browser is running if profile.process_id.is_some() { return Err( format!( - "Cannot delete profile '{profile_name}' while browser is running. Please stop the browser first." + "Cannot delete profile '{}' while browser is running. Please stop the browser first.", profile.name ) .into(), ); @@ -528,10 +540,14 @@ impl ProfileManager { pub async fn update_camoufox_config( &self, app_handle: tauri::AppHandle, - profile_name: &str, + profile_id: &str, config: CamoufoxConfig, ) -> Result<(), Box> { - // Find the profile by name + // Find the profile by ID + let profile_uuid = uuid::Uuid::parse_str(profile_id) + .map_err(|_| -> Box { + format!("Invalid profile ID: {profile_id}").into() + })?; let profiles = self .list_profiles() @@ -540,9 +556,9 @@ impl ProfileManager { })?; let mut profile = profiles .into_iter() - .find(|p| p.name == profile_name) + .find(|p| p.id == profile_uuid) .ok_or_else(|| -> Box { - format!("Profile {profile_name} not found").into() + format!("Profile with ID '{profile_id}' not found").into() })?; // Check if the browser is currently running using the comprehensive status check @@ -566,7 +582,7 @@ impl ProfileManager { format!("Failed to save profile: {e}").into() })?; - println!("Camoufox configuration updated for profile '{profile_name}'."); + println!("Camoufox configuration updated for profile '{}' (ID: {}).", profile.name, profile_id); // Emit profile config update event if let Err(e) = app_handle.emit("profiles-changed", ()) { @@ -579,10 +595,14 @@ impl ProfileManager { pub async fn update_profile_proxy( &self, app_handle: tauri::AppHandle, - profile_name: &str, + profile_id: &str, proxy_id: Option, ) -> Result> { - // Find the profile by name + // Find the profile by ID + let profile_uuid = uuid::Uuid::parse_str(profile_id) + .map_err(|_| -> Box { + format!("Invalid profile ID: {profile_id}").into() + })?; let profiles = self .list_profiles() @@ -592,9 +612,9 @@ impl ProfileManager { let mut profile = profiles .into_iter() - .find(|p| p.name == profile_name) + .find(|p| p.id == profile_uuid) .ok_or_else(|| -> Box { - format!("Profile {profile_name} not found").into() + format!("Profile with ID '{profile_id}' not found").into() })?; // Update proxy settings diff --git a/src/app/page.tsx b/src/app/page.tsx index 52f32d3..b493f1d 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -378,7 +378,7 @@ export default function Home() { async (profile: BrowserProfile, config: CamoufoxConfig) => { try { await invoke("update_camoufox_config", { - profileName: profile.name, + profileId: profile.id, config, }); // No need to manually reload - useProfileEvents will handle the update @@ -464,7 +464,7 @@ export default function Home() { } // Attempt to delete the profile - await invoke("delete_profile", { profileName: profile.name }); + await invoke("delete_profile", { profileId: profile.id }); console.log("Profile deletion command completed successfully"); // No need to manually reload - useProfileEvents will handle the update @@ -477,9 +477,9 @@ export default function Home() { }, []); const handleRenameProfile = useCallback( - async (oldName: string, newName: string) => { + async (profileId: string, newName: string) => { try { - await invoke("rename_profile", { oldName, newName }); + await invoke("rename_profile", { profileId, newName }); // No need to manually reload - useProfileEvents will handle the update } catch (err: unknown) { console.error("Failed to rename profile:", err); @@ -507,9 +507,9 @@ export default function Home() { }, []); const handleDeleteSelectedProfiles = useCallback( - async (profileNames: string[]) => { + async (profileIds: string[]) => { try { - await invoke("delete_selected_profiles", { profileNames }); + await invoke("delete_selected_profiles", { profileIds }); // No need to manually reload - useProfileEvents will handle the update } catch (err: unknown) { console.error("Failed to delete selected profiles:", err); @@ -521,8 +521,8 @@ export default function Home() { [], ); - const handleAssignProfilesToGroup = useCallback((profileNames: string[]) => { - setSelectedProfilesForGroup(profileNames); + const handleAssignProfilesToGroup = useCallback((profileIds: string[]) => { + setSelectedProfilesForGroup(profileIds); setGroupAssignmentDialogOpen(true); }, []); @@ -537,7 +537,7 @@ export default function Home() { setIsBulkDeleting(true); try { await invoke("delete_selected_profiles", { - profileNames: selectedProfiles, + profileIds: selectedProfiles, }); // No need to manually reload - useProfileEvents will handle the update setSelectedProfiles([]); @@ -797,6 +797,7 @@ export default function Home() { }} selectedProfiles={selectedProfilesForGroup} onAssignmentComplete={handleGroupAssignmentComplete} + profiles={profiles} /> ({ id: p.id, name: p.name }))} /> ); diff --git a/src/components/delete-confirmation-dialog.tsx b/src/components/delete-confirmation-dialog.tsx index 5316484..b27a994 100644 --- a/src/components/delete-confirmation-dialog.tsx +++ b/src/components/delete-confirmation-dialog.tsx @@ -19,7 +19,8 @@ interface DeleteConfirmationDialogProps { description: string; confirmButtonText?: string; isLoading?: boolean; - profileNames?: string[]; + profileIds?: string[]; + profiles?: { id: string; name: string }[]; } export function DeleteConfirmationDialog({ @@ -30,7 +31,8 @@ export function DeleteConfirmationDialog({ description, confirmButtonText = "Delete", isLoading = false, - profileNames, + profileIds, + profiles = [], }: DeleteConfirmationDialogProps) { const handleConfirm = async () => { await onConfirm(); @@ -42,18 +44,22 @@ export function DeleteConfirmationDialog({ {title} {description} - {profileNames && profileNames.length > 0 && ( + {profileIds && profileIds.length > 0 && (

Profiles to be deleted:

    - {profileNames.map((name) => ( -
  • - • {name} -
  • - ))} + {profileIds.map((id) => { + const profile = profiles.find((p) => p.id === id); + const displayName = profile ? profile.name : id; + return ( +
  • + • {displayName} +
  • + ); + })}
diff --git a/src/components/delete-group-dialog.tsx b/src/components/delete-group-dialog.tsx index 74a2232..cc2a549 100644 --- a/src/components/delete-group-dialog.tsx +++ b/src/components/delete-group-dialog.tsx @@ -74,13 +74,13 @@ export function DeleteGroupDialog({ try { if (deleteAction === "delete" && associatedProfiles.length > 0) { // Delete all associated profiles first - const profileNames = associatedProfiles.map((p) => p.name); - await invoke("delete_selected_profiles", { profileNames }); + const profileIds = associatedProfiles.map((p) => p.id); + await invoke("delete_selected_profiles", { profileIds }); } else if (deleteAction === "move" && associatedProfiles.length > 0) { // Move profiles to default group (null group_id) - const profileNames = associatedProfiles.map((p) => p.name); + const profileIds = associatedProfiles.map((p) => p.id); await invoke("assign_profiles_to_group", { - profileNames, + profileIds, groupId: null, }); } diff --git a/src/components/group-assignment-dialog.tsx b/src/components/group-assignment-dialog.tsx index 28fe5e9..1bdb1c7 100644 --- a/src/components/group-assignment-dialog.tsx +++ b/src/components/group-assignment-dialog.tsx @@ -22,7 +22,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import type { ProfileGroup } from "@/types"; +import type { BrowserProfile, ProfileGroup } from "@/types"; import { RippleButton } from "./ui/ripple"; interface GroupAssignmentDialogProps { @@ -30,6 +30,7 @@ interface GroupAssignmentDialogProps { onClose: () => void; selectedProfiles: string[]; onAssignmentComplete: () => void; + profiles?: BrowserProfile[]; } export function GroupAssignmentDialog({ @@ -37,6 +38,7 @@ export function GroupAssignmentDialog({ onClose, selectedProfiles, onAssignmentComplete, + profiles = [], }: GroupAssignmentDialogProps) { const [groups, setGroups] = useState([]); const [selectedGroupId, setSelectedGroupId] = useState(null); @@ -64,7 +66,7 @@ export function GroupAssignmentDialog({ setError(null); try { await invoke("assign_profiles_to_group", { - profileNames: selectedProfiles, + profileIds: selectedProfiles, groupId: selectedGroupId, }); @@ -119,11 +121,18 @@ export function GroupAssignmentDialog({
    - {selectedProfiles.map((profileName) => ( -
  • - • {profileName} -
  • - ))} + {selectedProfiles.map((profileId) => { + // Find the profile name for display + const profile = profiles.find( + (p: BrowserProfile) => p.id === profileId, + ); + const displayName = profile ? profile.name : profileId; + return ( +
  • + • {displayName} +
  • + ); + })}
diff --git a/src/components/profile-data-table.tsx b/src/components/profile-data-table.tsx index 643a04b..b44a73c 100644 --- a/src/components/profile-data-table.tsx +++ b/src/components/profile-data-table.tsx @@ -96,15 +96,15 @@ type TableMeta = { proxyOverrides: Record; storedProxies: StoredProxy[]; handleProxySelection: ( - profileName: string, + profileId: string, proxyId: string | null, ) => void | Promise; // Selection helpers - isProfileSelected: (name: string) => boolean; + isProfileSelected: (id: string) => boolean; handleToggleAll: (checked: boolean) => void; - handleCheckboxChange: (name: string, checked: boolean) => void; - handleIconClick: (name: string) => void; + handleCheckboxChange: (id: string, checked: boolean) => void; + handleIconClick: (id: string) => void; // Rename helpers handleRename: () => void | Promise; @@ -125,7 +125,7 @@ type TableMeta = { onLaunchProfile: (profile: BrowserProfile) => void | Promise; // Overflow actions - onAssignProfilesToGroup?: (profileNames: string[]) => void; + onAssignProfilesToGroup?: (profileIds: string[]) => void; onConfigureCamoufox?: (profile: BrowserProfile) => void; }; @@ -151,8 +151,8 @@ const TagsCell = React.memo<{ setOpenTagsEditorFor, setTagsOverrides, }) => { - const effectiveTags: string[] = Object.hasOwn(tagsOverrides, profile.name) - ? tagsOverrides[profile.name] + const effectiveTags: string[] = Object.hasOwn(tagsOverrides, profile.id) + ? tagsOverrides[profile.id] : (profile.tags ?? []); const valueOptions: Option[] = React.useMemo( @@ -164,10 +164,9 @@ const TagsCell = React.memo<{ [allTags], ); - const handleChange = React.useCallback( - async (opts: Option[]) => { - const newTagsRaw = opts.map((o) => o.value); - // Dedupe while preserving order + const onTagsChange = React.useCallback( + async (newTagsRaw: string[]) => { + // Dedupe tags const seen = new Set(); const newTags: string[] = []; for (const t of newTagsRaw) { @@ -176,10 +175,10 @@ const TagsCell = React.memo<{ newTags.push(t); } } - setTagsOverrides((prev) => ({ ...prev, [profile.name]: newTags })); + setTagsOverrides((prev) => ({ ...prev, [profile.id]: newTags })); try { await invoke("update_profile_tags", { - profileName: profile.name, + profileId: profile.id, tags: newTags, }); setAllTags((prev) => { @@ -191,7 +190,15 @@ const TagsCell = React.memo<{ console.error("Failed to update tags:", error); } }, - [profile.name, setAllTags, setTagsOverrides], + [profile.id, setTagsOverrides, setAllTags], + ); + + const handleChange = React.useCallback( + async (opts: Option[]) => { + const newTagsRaw = opts.map((o) => o.value); + await onTagsChange(newTagsRaw); + }, + [onTagsChange], ); const containerRef = React.useRef(null); @@ -202,7 +209,7 @@ const TagsCell = React.memo<{ React.useLayoutEffect(() => { // Only measure when not editing this profile's tags - if (openTagsEditorFor === profile.name) return; + if (openTagsEditorFor === profile.id) return; const container = containerRef.current; if (!container) return; @@ -253,10 +260,10 @@ const TagsCell = React.memo<{ ro.disconnect(); if (timeoutId) clearTimeout(timeoutId); }; - }, [effectiveTags, openTagsEditorFor, profile.name]); + }, [effectiveTags, openTagsEditorFor, profile.id]); React.useEffect(() => { - if (openTagsEditorFor !== profile.name) return; + if (openTagsEditorFor !== profile.id) return; const handleClick = (e: MouseEvent) => { const target = e.target as Node | null; if ( @@ -269,19 +276,19 @@ const TagsCell = React.memo<{ }; document.addEventListener("mousedown", handleClick); return () => document.removeEventListener("mousedown", handleClick); - }, [openTagsEditorFor, profile.name, setOpenTagsEditorFor]); + }, [openTagsEditorFor, profile.id, setOpenTagsEditorFor]); React.useEffect(() => { - if (openTagsEditorFor === profile.name && editorRef.current) { + if (openTagsEditorFor === profile.id && editorRef.current) { // Focus the inner input of MultipleSelector on open const inputEl = editorRef.current.querySelector("input"); if (inputEl) { (inputEl as HTMLInputElement).focus(); } } - }, [openTagsEditorFor, profile.name]); + }, [openTagsEditorFor, profile.id]); - if (openTagsEditorFor !== profile.name) { + if (openTagsEditorFor !== profile.id) { const hiddenCount = Math.max(0, effectiveTags.length - visibleCount); const ButtonContent = (