Skip to content

Commit b4e29da

Browse files
authored
Merge pull request #2515 from divaltor/filter-projects-shortcut
feat(input): Add focus by Cmd + K shortcut to search input
2 parents 090ec2b + 57dc24b commit b4e29da

File tree

4 files changed

+99
-24
lines changed

4 files changed

+99
-24
lines changed

apps/dokploy/components/dashboard/projects/show.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import {
4444
DropdownMenuSeparator,
4545
DropdownMenuTrigger,
4646
} from "@/components/ui/dropdown-menu";
47-
import { Input } from "@/components/ui/input";
47+
import { FocusShortcutInput } from "@/components/shared/focus-shortcut-input";
4848
import {
4949
Select,
5050
SelectContent,
@@ -144,12 +144,13 @@ export const ShowProjects = () => {
144144
<>
145145
<div className="flex max-sm:flex-col gap-4 items-center w-full">
146146
<div className="flex-1 relative max-sm:w-full">
147-
<Input
147+
<FocusShortcutInput
148148
placeholder="Filter projects..."
149149
value={searchQuery}
150150
onChange={(e) => setSearchQuery(e.target.value)}
151151
className="pr-10"
152152
/>
153+
153154
<Search className="absolute right-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
154155
</div>
155156
<div className="flex items-center gap-2 min-w-48 max-sm:w-full">

apps/dokploy/components/dashboard/search-command.tsx

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
import { BookIcon, CircuitBoard, GlobeIcon } from "lucide-react";
44
import { useRouter } from "next/router";
55
import React from "react";
6+
import {
7+
extractServices,
8+
type Services,
9+
} from "@/components/dashboard/settings/users/add-permissions";
610
import {
711
MariadbIcon,
812
MongodbIcon,
@@ -20,13 +24,34 @@ import {
2024
CommandSeparator,
2125
} from "@/components/ui/command";
2226
import { authClient } from "@/lib/auth-client";
23-
// import {
24-
// extractServices,
25-
// type Services,
26-
// } from "@/pages/dashboard/project/[projectId]";
2727
import { api } from "@/utils/api";
2828
import { StatusTooltip } from "../shared/status-tooltip";
2929

30+
// Extended Services type to include environmentId and environmentName for search navigation
31+
type SearchServices = Services & {
32+
environmentId: string;
33+
environmentName: string;
34+
};
35+
36+
const extractAllServicesFromProject = (project: any): SearchServices[] => {
37+
const allServices: SearchServices[] = [];
38+
39+
// Iterate through all environments in the project
40+
project.environments?.forEach((environment: any) => {
41+
const environmentServices = extractServices(environment);
42+
const servicesWithEnvironmentId: SearchServices[] = environmentServices.map(
43+
(service) => ({
44+
...service,
45+
environmentId: environment.environmentId,
46+
environmentName: environment.name,
47+
}),
48+
);
49+
allServices.push(...servicesWithEnvironmentId);
50+
});
51+
52+
return allServices;
53+
};
54+
3055
export const SearchCommand = () => {
3156
const router = useRouter();
3257
const [open, setOpen] = React.useState(false);
@@ -51,7 +76,7 @@ export const SearchCommand = () => {
5176

5277
return (
5378
<div>
54-
{/* <CommandDialog open={open} onOpenChange={setOpen}>
79+
<CommandDialog open={open} onOpenChange={setOpen}>
5580
<CommandInput
5681
placeholder={"Search projects or settings"}
5782
value={search}
@@ -63,25 +88,37 @@ export const SearchCommand = () => {
6388
</CommandEmpty>
6489
<CommandGroup heading={"Projects"}>
6590
<CommandList>
66-
{data?.map((project) => (
67-
<CommandItem
68-
key={project.projectId}
69-
onSelect={() => {
70-
router.push(`/dashboard/project/${project.projectId}`);
71-
setOpen(false);
72-
}}
73-
>
74-
<BookIcon className="size-4 text-muted-foreground mr-2" />
75-
{project.name}
76-
</CommandItem>
77-
))}
91+
{data?.map((project) => {
92+
console.log("project", project);
93+
const productionEnvironment = project.environments.find(
94+
(environment) => environment.name === "production",
95+
);
96+
97+
if (!productionEnvironment) return null;
98+
99+
return (
100+
<CommandItem
101+
key={project.projectId}
102+
onSelect={() => {
103+
router.push(
104+
`/dashboard/project/${project.projectId}/environment/${productionEnvironment!.environmentId}`,
105+
);
106+
setOpen(false);
107+
}}
108+
>
109+
<BookIcon className="size-4 text-muted-foreground mr-2" />
110+
{project.name} / {productionEnvironment!.name}
111+
</CommandItem>
112+
);
113+
})}
78114
</CommandList>
79115
</CommandGroup>
80116
<CommandSeparator />
81117
<CommandGroup heading={"Services"}>
82118
<CommandList>
83119
{data?.map((project) => {
84-
const applications: Services[] = extractServices(project);
120+
const applications: SearchServices[] =
121+
extractAllServicesFromProject(project);
85122
return applications.map((application) => (
86123
<CommandItem
87124
key={application.id}
@@ -114,7 +151,8 @@ export const SearchCommand = () => {
114151
<CircuitBoard className="h-6 w-6 mr-2" />
115152
)}
116153
<span className="flex-grow">
117-
{project.name} / {application.name}{" "}
154+
{project.name} / {application.environmentName} /{" "}
155+
{application.name}{" "}
118156
<div style={{ display: "none" }}>{application.id}</div>
119157
</span>
120158
<div>
@@ -181,7 +219,7 @@ export const SearchCommand = () => {
181219
</CommandItem>
182220
</CommandGroup>
183221
</CommandList>
184-
</CommandDialog> */}
222+
</CommandDialog>
185223
</div>
186224
);
187225
};
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { useEffect, useRef } from "react";
2+
import { Input } from "@/components/ui/input";
3+
4+
type Props = React.ComponentPropsWithoutRef<typeof Input>;
5+
6+
export const FocusShortcutInput = (props: Props) => {
7+
const inputRef = useRef<HTMLInputElement | null>(null);
8+
9+
useEffect(() => {
10+
const onKeyDown = (e: KeyboardEvent) => {
11+
const isMod = e.metaKey || e.ctrlKey;
12+
if (!isMod || e.key.toLowerCase() !== "k") return;
13+
14+
const target = e.target as HTMLElement | null;
15+
if (target) {
16+
const tag = target.tagName;
17+
if (
18+
target.isContentEditable ||
19+
tag === "INPUT" ||
20+
tag === "TEXTAREA" ||
21+
tag === "SELECT" ||
22+
target.getAttribute("role") === "textbox"
23+
)
24+
return;
25+
}
26+
27+
e.preventDefault();
28+
inputRef.current?.focus();
29+
};
30+
31+
window.addEventListener("keydown", onKeyDown);
32+
return () => window.removeEventListener("keydown", onKeyDown);
33+
}, []);
34+
35+
return <Input {...props} ref={inputRef} />;
36+
};

apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ import {
8080
DropdownMenuSeparator,
8181
DropdownMenuTrigger,
8282
} from "@/components/ui/dropdown-menu";
83-
import { Input } from "@/components/ui/input";
8483
import {
8584
Popover,
8685
PopoverContent,
@@ -96,6 +95,7 @@ import {
9695
import { cn } from "@/lib/utils";
9796
import { appRouter } from "@/server/api/root";
9897
import { api } from "@/utils/api";
98+
import { FocusShortcutInput } from "@/components/shared/focus-shortcut-input";
9999

100100
export type Services = {
101101
appName: string;
@@ -1197,7 +1197,7 @@ const EnvironmentPage = (
11971197

11981198
<div className="flex flex-col gap-2 lg:flex-row lg:gap-4 lg:items-center">
11991199
<div className="w-full relative">
1200-
<Input
1200+
<FocusShortcutInput
12011201
placeholder="Filter services..."
12021202
value={searchQuery}
12031203
onChange={(e) => setSearchQuery(e.target.value)}

0 commit comments

Comments
 (0)