refactor: use ids instead of names for all profile operations

This commit is contained in:
zhom
2025-08-19 13:31:46 +04:00
parent f8a4ec3277
commit e12a5661b1
11 changed files with 177 additions and 133 deletions
+1
View File
@@ -20,6 +20,7 @@
"CFURL",
"checkin",
"chrono",
"ciphertext",
"CLICOLOR",
"clippy",
"cmdk",
+7 -7
View File
@@ -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<String>,
) -> Result<BrowserProfile, String> {
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<String>,
) -> Result<BrowserProfile, String> {
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<BrowserProfile, String> {
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}"))
}
+4 -4
View File
@@ -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<String>,
profile_ids: Vec<String>,
group_id: Option<String>,
) -> 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<String>,
profile_ids: Vec<String>,
) -> 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}"))
}
-1
View File
@@ -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};
+58 -38
View File
@@ -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<BrowserProfile, Box<dyn std::error::Error>> {
// 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<dyn std::error::Error>> {
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<BrowserProfile, Box<dyn std::error::Error>> {
// 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<String>,
profile_ids: Vec<String>,
group_id: Option<String>,
) -> Result<(), Box<dyn std::error::Error>> {
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<String>,
) -> Result<BrowserProfile, Box<dyn std::error::Error>> {
// 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<String> = Vec::with_capacity(tags.len());
@@ -488,21 +498,23 @@ impl ProfileManager {
pub fn delete_multiple_profiles(
&self,
app_handle: &tauri::AppHandle,
profile_names: Vec<String>,
profile_ids: Vec<String>,
) -> Result<(), Box<dyn std::error::Error>> {
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<dyn std::error::Error + Send + Sync>> {
// Find the profile by name
// Find the profile by ID
let profile_uuid = uuid::Uuid::parse_str(profile_id)
.map_err(|_| -> Box<dyn std::error::Error + Send + Sync> {
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<dyn std::error::Error + Send + Sync> {
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<String>,
) -> Result<BrowserProfile, Box<dyn std::error::Error + Send + Sync>> {
// Find the profile by name
// Find the profile by ID
let profile_uuid = uuid::Uuid::parse_str(profile_id)
.map_err(|_| -> Box<dyn std::error::Error + Send + Sync> {
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<dyn std::error::Error + Send + Sync> {
format!("Profile {profile_name} not found").into()
format!("Profile with ID '{profile_id}' not found").into()
})?;
// Update proxy settings
+12 -10
View File
@@ -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}
/>
<DeleteConfirmationDialog
@@ -807,7 +808,8 @@ export default function Home() {
description={`This action cannot be undone. This will permanently delete ${selectedProfiles.length} profile${selectedProfiles.length !== 1 ? "s" : ""} and all associated data.`}
confirmButtonText={`Delete ${selectedProfiles.length} Profile${selectedProfiles.length !== 1 ? "s" : ""}`}
isLoading={isBulkDeleting}
profileNames={selectedProfiles}
profileIds={selectedProfiles}
profiles={profiles.map((p) => ({ id: p.id, name: p.name }))}
/>
</div>
);
+14 -8
View File
@@ -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({
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
{profileNames && profileNames.length > 0 && (
{profileIds && profileIds.length > 0 && (
<div className="mt-4">
<p className="text-sm font-medium mb-2">
Profiles to be deleted:
</p>
<div className="bg-muted rounded-md p-3 max-h-32 overflow-y-auto">
<ul className="space-y-1">
{profileNames.map((name) => (
<li key={name} className="text-sm text-muted-foreground">
{name}
</li>
))}
{profileIds.map((id) => {
const profile = profiles.find((p) => p.id === id);
const displayName = profile ? profile.name : id;
return (
<li key={id} className="text-sm text-muted-foreground">
{displayName}
</li>
);
})}
</ul>
</div>
</div>
+4 -4
View File
@@ -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,
});
}
+16 -7
View File
@@ -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<ProfileGroup[]>([]);
const [selectedGroupId, setSelectedGroupId] = useState<string | null>(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({
<Label>Selected Profiles:</Label>
<div className="p-3 bg-muted rounded-md max-h-32 overflow-y-auto">
<ul className="text-sm space-y-1">
{selectedProfiles.map((profileName) => (
<li key={profileName} className="truncate">
{profileName}
</li>
))}
{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 (
<li key={profileId} className="truncate">
{displayName}
</li>
);
})}
</ul>
</div>
</div>
+60 -53
View File
@@ -96,15 +96,15 @@ type TableMeta = {
proxyOverrides: Record<string, string | null>;
storedProxies: StoredProxy[];
handleProxySelection: (
profileName: string,
profileId: string,
proxyId: string | null,
) => void | Promise<void>;
// 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<void>;
@@ -125,7 +125,7 @@ type TableMeta = {
onLaunchProfile: (profile: BrowserProfile) => void | Promise<void>;
// 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<string>();
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<BrowserProfile>("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<HTMLDivElement | null>(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 = (
<button
@@ -292,7 +299,7 @@ const TagsCell = React.memo<{
isDisabled ? "opacity-60" : "cursor-pointer hover:bg-accent/50",
)}
onClick={() => {
if (!isDisabled) setOpenTagsEditorFor(profile.name);
if (!isDisabled) setOpenTagsEditorFor(profile.id);
}}
>
{effectiveTags.slice(0, visibleCount).map((t) => (
@@ -372,12 +379,12 @@ interface ProfilesDataTableProps {
onLaunchProfile: (profile: BrowserProfile) => void | Promise<void>;
onKillProfile: (profile: BrowserProfile) => void | Promise<void>;
onDeleteProfile: (profile: BrowserProfile) => void | Promise<void>;
onRenameProfile: (oldName: string, newName: string) => Promise<void>;
onRenameProfile: (profileId: string, newName: string) => Promise<void>;
onConfigureCamoufox: (profile: BrowserProfile) => void;
runningProfiles: Set<string>;
isUpdating: (browser: string) => boolean;
onDeleteSelectedProfiles: (profileNames: string[]) => Promise<void>;
onAssignProfilesToGroup: (profileNames: string[]) => void;
onDeleteSelectedProfiles: (profileIds: string[]) => Promise<void>;
onAssignProfilesToGroup: (profileIds: string[]) => void;
selectedGroupId: string | null;
selectedProfiles: string[];
onSelectedProfilesChange: Dispatch<SetStateAction<string[]>>;
@@ -441,13 +448,13 @@ export function ProfilesDataTable({
}, []);
const handleProxySelection = React.useCallback(
async (profileName: string, proxyId: string | null) => {
async (profileId: string, proxyId: string | null) => {
try {
await invoke("update_profile_proxy", {
profileName,
profileId,
proxyId,
});
setProxyOverrides((prev) => ({ ...prev, [profileName]: proxyId }));
setProxyOverrides((prev) => ({ ...prev, [profileId]: proxyId }));
// Notify other parts of the app so usage counts and lists refresh
await emit("profile-updated");
} catch (error) {
@@ -527,8 +534,8 @@ export function ProfilesDataTable({
const newSet = new Set(selectedProfiles);
let hasChanges = false;
for (const profileName of selectedProfiles) {
const profile = profiles.find((p) => p.name === profileName);
for (const profileId of selectedProfiles) {
const profile = profiles.find((p) => p.id === profileId);
if (profile) {
const isRunning =
browserState.isClient && runningProfiles.has(profile.id);
@@ -537,7 +544,7 @@ export function ProfilesDataTable({
const isBrowserUpdating = isUpdating(profile.browser);
if (isRunning || isLaunching || isStopping || isBrowserUpdating) {
newSet.delete(profileName);
newSet.delete(profileId);
hasChanges = true;
}
}
@@ -581,7 +588,7 @@ export function ProfilesDataTable({
try {
setIsRenamingSaving(true);
await onRenameProfile(profileToRename.name, newProfileName.trim());
await onRenameProfile(profileToRename.id, newProfileName.trim());
setProfileToRename(null);
setNewProfileName("");
setRenameError(null);
@@ -631,8 +638,8 @@ export function ProfilesDataTable({
// Handle icon/checkbox click
const handleIconClick = React.useCallback(
(profileName: string) => {
const profile = profiles.find((p) => p.name === profileName);
(profileId: string) => {
const profile = profiles.find((p) => p.id === profileId);
if (!profile) return;
// Prevent selection of profiles whose browsers are updating
@@ -642,10 +649,10 @@ export function ProfilesDataTable({
setShowCheckboxes(true);
const newSet = new Set(selectedProfiles);
if (newSet.has(profileName)) {
newSet.delete(profileName);
if (newSet.has(profileId)) {
newSet.delete(profileId);
} else {
newSet.add(profileName);
newSet.add(profileId);
}
// Hide checkboxes if no profiles are selected
@@ -671,12 +678,12 @@ export function ProfilesDataTable({
// Handle checkbox change
const handleCheckboxChange = React.useCallback(
(profileName: string, checked: boolean) => {
(profileId: string, checked: boolean) => {
const newSet = new Set(selectedProfiles);
if (checked) {
newSet.add(profileName);
newSet.add(profileId);
} else {
newSet.delete(profileName);
newSet.delete(profileId);
}
// Hide checkboxes if no profiles are selected
@@ -708,7 +715,7 @@ export function ProfilesDataTable({
!isBrowserUpdating
);
})
.map((profile) => profile.name),
.map((profile) => profile.id),
)
: new Set<string>();
@@ -774,7 +781,7 @@ export function ProfilesDataTable({
handleProxySelection,
// Selection helpers
isProfileSelected: (name: string) => selectedProfiles.includes(name),
isProfileSelected: (id: string) => selectedProfiles.includes(id),
handleToggleAll,
handleCheckboxChange,
handleIconClick,
@@ -857,7 +864,7 @@ export function ProfilesDataTable({
const browser = profile.browser;
const IconComponent = getBrowserIcon(browser);
const isSelected = meta.isProfileSelected(profile.name);
const isSelected = meta.isProfileSelected(profile.id);
const isRunning =
meta.isClient && meta.runningProfiles.has(profile.id);
const isLaunching = meta.launchingProfiles.has(profile.id);
@@ -897,7 +904,7 @@ export function ProfilesDataTable({
<Checkbox
checked={isSelected}
onCheckedChange={(value) =>
meta.handleCheckboxChange(profile.name, !!value)
meta.handleCheckboxChange(profile.id, !!value)
}
aria-label="Select row"
className="w-4 h-4"
@@ -911,7 +918,7 @@ export function ProfilesDataTable({
<button
type="button"
className="flex justify-center items-center p-0 border-none cursor-pointer"
onClick={() => meta.handleIconClick(profile.name)}
onClick={() => meta.handleIconClick(profile.id)}
aria-label="Select profile"
>
<span className="w-4 h-4 group">
@@ -1039,7 +1046,7 @@ export function ProfilesDataTable({
const profile = row.original as BrowserProfile;
const rawName: string = row.getValue("name");
const name = getBrowserDisplayName(rawName);
const isEditing = meta.profileToRename?.name === profile.name;
const isEditing = meta.profileToRename?.id === profile.id;
if (isEditing) {
const isSaveDisabled =
@@ -1227,9 +1234,9 @@ export function ProfilesDataTable({
const isDisabled =
isRunning || isLaunching || isStopping || isBrowserUpdating;
const hasOverride = Object.hasOwn(meta.proxyOverrides, profile.name);
const hasOverride = Object.hasOwn(meta.proxyOverrides, profile.id);
const effectiveProxyId = hasOverride
? meta.proxyOverrides[profile.name]
? meta.proxyOverrides[profile.id]
: (profile.proxy_id ?? null);
const effectiveProxy = effectiveProxyId
? (meta.storedProxies.find((p) => p.id === effectiveProxyId) ??
@@ -1248,7 +1255,7 @@ export function ProfilesDataTable({
: profileHasProxy && effectiveProxy
? effectiveProxy.name
: null;
const isSelectorOpen = meta.openProxySelectorFor === profile.name;
const isSelectorOpen = meta.openProxySelectorFor === profile.id;
if (profile.browser === "tor-browser") {
return (
@@ -1271,7 +1278,7 @@ export function ProfilesDataTable({
<Popover
open={isSelectorOpen}
onOpenChange={(open) =>
meta.setOpenProxySelectorFor(open ? profile.name : null)
meta.setOpenProxySelectorFor(open ? profile.id : null)
}
>
<Tooltip>
@@ -1311,7 +1318,7 @@ export function ProfilesDataTable({
<CommandItem
value="__none__"
onSelect={() =>
void meta.handleProxySelection(profile.name, null)
void meta.handleProxySelection(profile.id, null)
}
>
<LuCheck
@@ -1330,7 +1337,7 @@ export function ProfilesDataTable({
value={proxy.name}
onSelect={() =>
void meta.handleProxySelection(
profile.name,
profile.id,
proxy.id,
)
}
@@ -1385,7 +1392,7 @@ export function ProfilesDataTable({
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={() => {
meta.onAssignProfilesToGroup?.([profile.name]);
meta.onAssignProfilesToGroup?.([profile.id]);
}}
disabled={isDisabled}
>
+1 -1
View File
@@ -99,7 +99,7 @@ export function ProfileSelectorDialog({
}
try {
await invoke("open_url_with_profile", {
profileName: selectedProfile,
profileId: selectedProfile,
url,
});
onClose();