Skip to content

Commit e11b804

Browse files
Add search and sorting functionality to MCP marketplace view
1 parent 8207d25 commit e11b804

File tree

2 files changed

+95
-10
lines changed

2 files changed

+95
-10
lines changed

webview-ui/src/components/mcp/marketplace/McpMarketplaceView.tsx

Lines changed: 87 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,29 @@
1-
import { useEffect, useState } from "react"
2-
import { VSCodeButton, VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"
1+
import { useEffect, useState, useMemo } from "react"
2+
import {
3+
VSCodeButton,
4+
VSCodeProgressRing,
5+
VSCodeDropdown,
6+
VSCodeOption,
7+
VSCodeTextField,
8+
} from "@vscode/webview-ui-toolkit/react"
39
import { useAppTranslation } from "../../../i18n/TranslationContext"
410
import { useExtensionState } from "../../../context/ExtensionStateContext"
511
import { vscode } from "../../../utils/vscode"
612
import { McpMarketplaceItem } from "../../../../../src/shared/mcp"
713
import McpMarketplaceCard from "./McpMarketplaceCard"
814
import McpSubmitCard from "./McpSubmitCard"
915

16+
type SortOption = "stars" | "downloads" | "newest" | "updated"
17+
1018
const McpMarketplaceView = () => {
1119
const { t } = useAppTranslation()
1220
const { mcpServers } = useExtensionState()
1321
const [isLoading, setIsLoading] = useState(true)
1422
const [error, setError] = useState<string | null>(null)
1523
const [items, setItems] = useState<McpMarketplaceItem[]>([])
24+
const [searchQuery, setSearchQuery] = useState("")
25+
const [selectedCategory, setSelectedCategory] = useState<string>("all")
26+
const [sortBy, setSortBy] = useState<SortOption>("stars")
1627

1728
useEffect(() => {
1829
const handleMessage = (event: MessageEvent) => {
@@ -31,15 +42,52 @@ const McpMarketplaceView = () => {
3142
}
3243
}
3344
}
34-
3545
window.addEventListener("message", handleMessage)
36-
37-
// Request marketplace data when component mounts
3846
vscode.postMessage({ type: "fetchMcpMarketplace" })
39-
4047
return () => window.removeEventListener("message", handleMessage)
4148
}, [])
4249

50+
const categories = useMemo(() => {
51+
const categorySet = new Set(items.map((item) => item.category))
52+
return ["all", ...Array.from(categorySet)]
53+
}, [items])
54+
55+
const filteredAndSortedItems = useMemo(() => {
56+
let filtered = items
57+
58+
// Apply search filter
59+
if (searchQuery) {
60+
const query = searchQuery.toLowerCase()
61+
filtered = filtered.filter(
62+
(item) =>
63+
item.name.toLowerCase().includes(query) ||
64+
item.description.toLowerCase().includes(query) ||
65+
item.tags.some((tag) => tag.toLowerCase().includes(query)),
66+
)
67+
}
68+
69+
// Apply category filter
70+
if (selectedCategory !== "all") {
71+
filtered = filtered.filter((item) => item.category === selectedCategory)
72+
}
73+
74+
// Apply sorting
75+
return [...filtered].sort((a, b) => {
76+
switch (sortBy) {
77+
case "stars":
78+
return b.githubStars - a.githubStars
79+
case "downloads":
80+
return b.downloadCount - a.downloadCount
81+
case "newest":
82+
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
83+
case "updated":
84+
return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
85+
default:
86+
return 0
87+
}
88+
})
89+
}, [items, searchQuery, selectedCategory, sortBy])
90+
4391
if (isLoading) {
4492
return (
4593
<div
@@ -84,7 +132,33 @@ const McpMarketplaceView = () => {
84132

85133
return (
86134
<div style={{ padding: "0 10px" }}>
87-
{items.length === 0 ? (
135+
<div style={{ padding: "16px 0", display: "flex", gap: "16px", alignItems: "center" }}>
136+
<VSCodeTextField
137+
placeholder={t("mcp:marketplace.searchPlaceholder")}
138+
value={searchQuery}
139+
onChange={(e) => setSearchQuery((e.target as HTMLInputElement).value)}
140+
style={{ flexGrow: 1 }}
141+
/>
142+
<VSCodeDropdown
143+
value={selectedCategory}
144+
onChange={(e) => setSelectedCategory((e.target as HTMLSelectElement).value)}>
145+
{categories.map((category) => (
146+
<VSCodeOption key={category} value={category}>
147+
{category === "all" ? t("mcp:marketplace.allCategories") : category}
148+
</VSCodeOption>
149+
))}
150+
</VSCodeDropdown>
151+
<VSCodeDropdown
152+
value={sortBy}
153+
onChange={(e) => setSortBy((e.target as HTMLSelectElement).value as SortOption)}>
154+
<VSCodeOption value="stars">{t("mcp:marketplace.sortByStars")}</VSCodeOption>
155+
<VSCodeOption value="downloads">{t("mcp:marketplace.sortByDownloads")}</VSCodeOption>
156+
<VSCodeOption value="newest">{t("mcp:marketplace.sortByNewest")}</VSCodeOption>
157+
<VSCodeOption value="updated">{t("mcp:marketplace.sortByUpdated")}</VSCodeOption>
158+
</VSCodeDropdown>
159+
</div>
160+
161+
{filteredAndSortedItems.length === 0 ? (
88162
<div
89163
style={{
90164
display: "flex",
@@ -96,7 +170,11 @@ const McpMarketplaceView = () => {
96170
textAlign: "center",
97171
}}>
98172
<span className="codicon codicon-inbox" style={{ fontSize: "48px", marginBottom: "16px" }} />
99-
<p style={{ margin: 0 }}>{t("mcp:marketplace.noServers")}</p>
173+
<p style={{ margin: 0 }}>
174+
{searchQuery || selectedCategory !== "all"
175+
? t("mcp:marketplace.noResults")
176+
: t("mcp:marketplace.noServers")}
177+
</p>
100178
</div>
101179
) : (
102180
<div
@@ -106,7 +184,7 @@ const McpMarketplaceView = () => {
106184
gap: "16px",
107185
padding: "16px 0",
108186
}}>
109-
{items.map((item) => (
187+
{filteredAndSortedItems.map((item) => (
110188
<McpMarketplaceCard key={item.mcpId} item={item} installedServers={mcpServers} />
111189
))}
112190
<McpSubmitCard />

webview-ui/src/i18n/locales/en/mcp.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,16 @@
3131
"marketplace": {
3232
"loading": "Loading marketplace...",
3333
"noServers": "No servers available",
34+
"noResults": "No matching servers found",
3435
"retry": "Retry",
3536
"install": "Install",
36-
"by": "by {{author}}"
37+
"by": "by {{author}}",
38+
"searchPlaceholder": "Search servers...",
39+
"allCategories": "All Categories",
40+
"sortByStars": "Sort by Stars",
41+
"sortByDownloads": "Sort by Downloads",
42+
"sortByNewest": "Sort by Newest",
43+
"sortByUpdated": "Sort by Last Updated"
3744
},
3845
"networkTimeout": {
3946
"label": "Network Timeout",

0 commit comments

Comments
 (0)