mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-10-01 13:39:52 +02:00
feat: add profile search
This commit is contained in:
1 parent
274b275c03
commit
181c76980a
2 files changed
+61
-8
No files matched your search
+32
-4
@@ -80,6 +80,7 @@ export default function Home() {
|
|||||||
string[]
|
string[]
|
||||||
>([]);
|
>([]);
|
||||||
const [selectedProfiles, setSelectedProfiles] = useState<string[]>([]);
|
const [selectedProfiles, setSelectedProfiles] = useState<string[]>([]);
|
||||||
|
const [searchQuery, setSearchQuery] = useState<string>("");
|
||||||
const [pendingUrls, setPendingUrls] = useState<PendingUrl[]>([]);
|
const [pendingUrls, setPendingUrls] = useState<PendingUrl[]>([]);
|
||||||
const [currentProfileForCamoufoxConfig, setCurrentProfileForCamoufoxConfig] =
|
const [currentProfileForCamoufoxConfig, setCurrentProfileForCamoufoxConfig] =
|
||||||
useState<BrowserProfile | null>(null);
|
useState<BrowserProfile | null>(null);
|
||||||
@@ -655,14 +656,39 @@ export default function Home() {
|
|||||||
}
|
}
|
||||||
}, [isInitialized, checkAllPermissions]);
|
}, [isInitialized, checkAllPermissions]);
|
||||||
|
|
||||||
// Filter data by selected group
|
// Filter data by selected group and search query
|
||||||
const filteredProfiles = useMemo(() => {
|
const filteredProfiles = useMemo(() => {
|
||||||
|
let filtered = profiles;
|
||||||
|
|
||||||
|
// Filter by group
|
||||||
if (!selectedGroupId || selectedGroupId === "default") {
|
if (!selectedGroupId || selectedGroupId === "default") {
|
||||||
return profiles.filter((profile) => !profile.group_id);
|
filtered = profiles.filter((profile) => !profile.group_id);
|
||||||
|
} else {
|
||||||
|
filtered = profiles.filter(
|
||||||
|
(profile) => profile.group_id === selectedGroupId,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return profiles.filter((profile) => profile.group_id === selectedGroupId);
|
// Filter by search query
|
||||||
}, [profiles, selectedGroupId]);
|
if (searchQuery.trim()) {
|
||||||
|
const query = searchQuery.toLowerCase().trim();
|
||||||
|
filtered = filtered.filter((profile) => {
|
||||||
|
// Search in profile name
|
||||||
|
if (profile.name.toLowerCase().includes(query)) return true;
|
||||||
|
|
||||||
|
// Search in browser name
|
||||||
|
if (profile.browser.toLowerCase().includes(query)) return true;
|
||||||
|
|
||||||
|
// Search in tags
|
||||||
|
if (profile.tags?.some((tag) => tag.toLowerCase().includes(query)))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
}, [profiles, selectedGroupId, searchQuery]);
|
||||||
|
|
||||||
// Update loading states
|
// Update loading states
|
||||||
const isLoading = profilesLoading || groupsLoading || proxiesLoading;
|
const isLoading = profilesLoading || groupsLoading || proxiesLoading;
|
||||||
@@ -680,6 +706,8 @@ export default function Home() {
|
|||||||
onImportProfileDialogOpen={setImportProfileDialogOpen}
|
onImportProfileDialogOpen={setImportProfileDialogOpen}
|
||||||
onProxyManagementDialogOpen={setProxyManagementDialogOpen}
|
onProxyManagementDialogOpen={setProxyManagementDialogOpen}
|
||||||
onSettingsDialogOpen={setSettingsDialogOpen}
|
onSettingsDialogOpen={setSettingsDialogOpen}
|
||||||
|
searchQuery={searchQuery}
|
||||||
|
onSearchQueryChange={setSearchQuery}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-4 w-full">
|
<div className="space-y-4 w-full">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { FaDownload } from "react-icons/fa";
|
import { FaDownload } from "react-icons/fa";
|
||||||
import { FiWifi } from "react-icons/fi";
|
import { FiWifi } from "react-icons/fi";
|
||||||
import { GoGear, GoKebabHorizontal, GoPlus } from "react-icons/go";
|
import { GoGear, GoKebabHorizontal, GoPlus } from "react-icons/go";
|
||||||
import { LuTrash2, LuUsers } from "react-icons/lu";
|
import { LuSearch, LuTrash2, LuUsers, LuX } from "react-icons/lu";
|
||||||
import { Logo } from "./icons/logo";
|
import { Logo } from "./icons/logo";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import { CardTitle } from "./ui/card";
|
import { CardTitle } from "./ui/card";
|
||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "./ui/dropdown-menu";
|
} from "./ui/dropdown-menu";
|
||||||
|
import { Input } from "./ui/input";
|
||||||
import { RippleButton } from "./ui/ripple";
|
import { RippleButton } from "./ui/ripple";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
||||||
|
|
||||||
@@ -23,6 +24,8 @@ type Props = {
|
|||||||
onGroupManagementDialogOpen: (open: boolean) => void;
|
onGroupManagementDialogOpen: (open: boolean) => void;
|
||||||
onImportProfileDialogOpen: (open: boolean) => void;
|
onImportProfileDialogOpen: (open: boolean) => void;
|
||||||
onCreateProfileDialogOpen: (open: boolean) => void;
|
onCreateProfileDialogOpen: (open: boolean) => void;
|
||||||
|
searchQuery: string;
|
||||||
|
onSearchQueryChange: (query: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const HomeHeader = ({
|
const HomeHeader = ({
|
||||||
@@ -34,6 +37,8 @@ const HomeHeader = ({
|
|||||||
onGroupManagementDialogOpen,
|
onGroupManagementDialogOpen,
|
||||||
onImportProfileDialogOpen,
|
onImportProfileDialogOpen,
|
||||||
onCreateProfileDialogOpen,
|
onCreateProfileDialogOpen,
|
||||||
|
searchQuery,
|
||||||
|
onSearchQueryChange,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const handleLogoClick = () => {
|
const handleLogoClick = () => {
|
||||||
// Trigger the same URL handling logic as if the URL came from the system
|
// Trigger the same URL handling logic as if the URL came from the system
|
||||||
@@ -76,7 +81,7 @@ const HomeHeader = ({
|
|||||||
className="flex gap-2 items-center"
|
className="flex gap-2 items-center"
|
||||||
>
|
>
|
||||||
<LuTrash2 className="w-4 h-4" />
|
<LuTrash2 className="w-4 h-4" />
|
||||||
Delete Selected
|
Delete
|
||||||
</RippleButton>
|
</RippleButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -85,12 +90,32 @@ const HomeHeader = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
|
<div className="relative">
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search profiles..."
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => onSearchQueryChange(e.target.value)}
|
||||||
|
className="pr-8 pl-10 w-48"
|
||||||
|
/>
|
||||||
|
<LuSearch className="absolute left-3 top-1/2 w-4 h-4 transform -translate-y-1/2 text-muted-foreground" />
|
||||||
|
{searchQuery && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => onSearchQueryChange("")}
|
||||||
|
className="absolute right-2 top-1/2 p-1 rounded-sm transition-colors transform -translate-y-1/2 hover:bg-accent"
|
||||||
|
aria-label="Clear search"
|
||||||
|
>
|
||||||
|
<LuX className="w-4 h-4 text-muted-foreground hover:text-foreground" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="flex gap-2 items-center"
|
className="flex gap-2 items-center h-[36px]"
|
||||||
>
|
>
|
||||||
<GoKebabHorizontal className="w-4 h-4" />
|
<GoKebabHorizontal className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -138,7 +163,7 @@ const HomeHeader = ({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
onCreateProfileDialogOpen(true);
|
onCreateProfileDialogOpen(true);
|
||||||
}}
|
}}
|
||||||
className="flex gap-2 items-center"
|
className="flex gap-2 items-center h-[36px]"
|
||||||
>
|
>
|
||||||
<GoPlus className="w-4 h-4" />
|
<GoPlus className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Reference in new issue
Block a user