mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-04-26 05:46:23 +02:00
refactor: use ids instead of names for all profile operations
This commit is contained in:
Vendored
+1
@@ -20,6 +20,7 @@
|
||||
"CFURL",
|
||||
"checkin",
|
||||
"chrono",
|
||||
"ciphertext",
|
||||
"CLICOLOR",
|
||||
"clippy",
|
||||
"cmdk",
|
||||
|
||||
@@ -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}"))
|
||||
}
|
||||
|
||||
|
||||
@@ -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,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};
|
||||
|
||||
@@ -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
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
|
||||
@@ -99,7 +99,7 @@ export function ProfileSelectorDialog({
|
||||
}
|
||||
try {
|
||||
await invoke("open_url_with_profile", {
|
||||
profileName: selectedProfile,
|
||||
profileId: selectedProfile,
|
||||
url,
|
||||
});
|
||||
onClose();
|
||||
|
||||
Reference in New Issue
Block a user