mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
update ui to look like vs marketplace
This commit is contained in:
parent
e3b4b2c816
commit
0cbd6b321f
3 changed files with 360 additions and 184 deletions
|
|
@ -13,6 +13,7 @@ import { McpServer } from "../../../../src/shared/mcp"
|
|||
import McpToolRow from "./McpToolRow"
|
||||
import McpResourceRow from "./McpResourceRow"
|
||||
import McpMarketplaceView from "./marketplace/McpMarketplaceView"
|
||||
import styled from "styled-components"
|
||||
|
||||
type McpViewProps = {
|
||||
onDone: () => void
|
||||
|
|
@ -20,7 +21,7 @@ type McpViewProps = {
|
|||
|
||||
const McpView = ({ onDone }: McpViewProps) => {
|
||||
const { mcpServers: servers } = useExtensionState()
|
||||
const [activeTab, setActiveTab] = useState(servers.length === 0 ? "marketplace" : "installed")
|
||||
const [activeTab, setActiveTab] = useState("marketplace")
|
||||
|
||||
// const [servers, setServers] = useState<McpServer[]>([
|
||||
// // Add some mock servers for testing
|
||||
|
|
@ -99,20 +100,34 @@ const McpView = ({ onDone }: McpViewProps) => {
|
|||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
padding: "10px 17px 10px 20px",
|
||||
padding: "10px 17px 5px 20px",
|
||||
}}>
|
||||
<h3 style={{ color: "var(--vscode-foreground)", margin: 0 }}>MCP Servers</h3>
|
||||
<VSCodeButton onClick={onDone}>Done</VSCodeButton>
|
||||
</div>
|
||||
|
||||
<div style={{ flex: 1, overflow: "auto", padding: "0 20px" }}>
|
||||
<VSCodePanels activeid={activeTab} onchange={(e: any) => setActiveTab(e.target.activeid)}>
|
||||
<VSCodePanelTab id="installed">Installed</VSCodePanelTab>
|
||||
<VSCodePanelTab id="marketplace">Marketplace</VSCodePanelTab>
|
||||
<VSCodePanelTab id="settings">Settings</VSCodePanelTab>
|
||||
<div style={{ flex: 1, overflow: "auto" }}>
|
||||
{/* Tabs container */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "1px",
|
||||
padding: "0 8px 0 10px",
|
||||
borderBottom: "1px solid var(--vscode-panel-border)",
|
||||
}}>
|
||||
<TabButton isActive={activeTab === "marketplace"} onClick={() => setActiveTab("marketplace")}>
|
||||
Marketplace
|
||||
</TabButton>
|
||||
<TabButton isActive={activeTab === "installed"} onClick={() => setActiveTab("installed")}>
|
||||
Installed
|
||||
</TabButton>
|
||||
</div>
|
||||
|
||||
<VSCodePanelView id="installed">
|
||||
<div style={{ padding: "0 20px" }}>
|
||||
{/* Content container */}
|
||||
<div style={{ width: "100%" }}>
|
||||
{activeTab === "marketplace" && <McpMarketplaceView />}
|
||||
{activeTab === "installed" && (
|
||||
<div style={{ padding: "16px 20px" }}>
|
||||
<div
|
||||
style={{
|
||||
color: "var(--vscode-foreground)",
|
||||
|
|
@ -164,51 +179,62 @@ const McpView = ({ onDone }: McpViewProps) => {
|
|||
</VSCodeButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</VSCodePanelView>
|
||||
|
||||
<VSCodePanelView id="marketplace">
|
||||
<div style={{ maxWidth: "100%" }}>
|
||||
<McpMarketplaceView />
|
||||
</div>
|
||||
</VSCodePanelView>
|
||||
|
||||
<VSCodePanelView id="settings">
|
||||
<div style={{ maxWidth: "100%" }}>
|
||||
{/* Server Configuration Button */}
|
||||
<div style={{ marginTop: "10px", width: "100%" }}>
|
||||
{/* Settings Section */}
|
||||
<div style={{ marginBottom: "20px", marginTop: 10 }}>
|
||||
<VSCodeButton
|
||||
appearance="secondary"
|
||||
style={{ width: "100%" }}
|
||||
style={{ width: "100%", marginBottom: "5px" }}
|
||||
onClick={() => {
|
||||
vscode.postMessage({ type: "openMcpSettings" })
|
||||
}}>
|
||||
<span className="codicon codicon-server" style={{ marginRight: "6px" }}></span>
|
||||
Configure MCP Servers
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
|
||||
{/* Advanced Settings Link */}
|
||||
<div style={{ textAlign: "center", marginTop: "5px" }}>
|
||||
<VSCodeLink
|
||||
onClick={() => {
|
||||
vscode.postMessage({
|
||||
type: "openExtensionSettings",
|
||||
text: "cline.mcp",
|
||||
})
|
||||
}}
|
||||
style={{ fontSize: "12px" }}>
|
||||
Advanced MCP Settings
|
||||
</VSCodeLink>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<VSCodeLink
|
||||
onClick={() => {
|
||||
vscode.postMessage({
|
||||
type: "openExtensionSettings",
|
||||
text: "cline.mcp",
|
||||
})
|
||||
}}
|
||||
style={{ fontSize: "12px" }}>
|
||||
Advanced MCP Settings
|
||||
</VSCodeLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</VSCodePanelView>
|
||||
</VSCodePanels>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledTabButton = styled.button<{ isActive: boolean }>`
|
||||
background: none;
|
||||
border: none;
|
||||
border-bottom: 2px solid ${(props) => (props.isActive ? "var(--vscode-foreground)" : "transparent")};
|
||||
color: ${(props) => (props.isActive ? "var(--vscode-foreground)" : "var(--vscode-descriptionForeground)")};
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
margin-bottom: -1px;
|
||||
font-family: inherit;
|
||||
|
||||
&:hover {
|
||||
color: var(--vscode-foreground);
|
||||
}
|
||||
`
|
||||
|
||||
const TabButton = ({ children, isActive, onClick }: { children: React.ReactNode; isActive: boolean; onClick: () => void }) => (
|
||||
<StyledTabButton isActive={isActive} onClick={onClick}>
|
||||
{children}
|
||||
</StyledTabButton>
|
||||
)
|
||||
|
||||
// Server Row Component
|
||||
const ServerRow = ({ server }: { server: McpServer }) => {
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useEffect, useState } from "react"
|
||||
import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
import { CSSProperties, useEffect, useState } from "react"
|
||||
import styled from "styled-components"
|
||||
import { McpMarketplaceItem, McpServer } from "../../../../../src/shared/mcp"
|
||||
import { vscode } from "../../../utils/vscode"
|
||||
|
||||
|
|
@ -27,40 +28,62 @@ const McpMarketplaceCard = ({ item, installedServers }: McpMarketplaceCardProps)
|
|||
}, [])
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
background: "var(--vscode-textCodeBlock-background)",
|
||||
borderRadius: "4px",
|
||||
padding: "16px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "12px",
|
||||
}}>
|
||||
<div>
|
||||
<div style={{ display: "flex", gap: "16px", alignItems: "flex-start" }}>
|
||||
<>
|
||||
<style>
|
||||
{`
|
||||
.mcp-card {
|
||||
cursor: pointer;
|
||||
}
|
||||
.mcp-card:hover {
|
||||
background-color: var(--vscode-list-hoverBackground);
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<div
|
||||
className="mcp-card"
|
||||
onClick={() => {
|
||||
console.log("Card clicked:", item.mcpId)
|
||||
}}
|
||||
style={{
|
||||
borderBottom: "1px solid var(--vscode-textCodeBlock-background)",
|
||||
padding: "12px 16px",
|
||||
}}>
|
||||
{/* Main container with logo and content */}
|
||||
<div style={{ display: "flex", gap: "12px" }}>
|
||||
{/* Logo */}
|
||||
{item.logoUrl && (
|
||||
<img
|
||||
src={item.logoUrl}
|
||||
alt={`${item.name} logo`}
|
||||
style={{
|
||||
width: "48px",
|
||||
height: "48px",
|
||||
width: "32px",
|
||||
height: "32px",
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start" }}>
|
||||
<div>
|
||||
<h3 style={{ margin: 0, fontSize: "16px" }}>{item.name}</h3>
|
||||
<div style={{ fontSize: "12px", color: "var(--vscode-descriptionForeground)" }}>
|
||||
by {item.author}
|
||||
</div>
|
||||
</div>
|
||||
<VSCodeButton
|
||||
appearance={isInstalled ? "secondary" : "primary"}
|
||||
disabled={isInstalled || isDownloading}
|
||||
onClick={() => {
|
||||
|
||||
{/* Content section */}
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
{/* First row: name and install button */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
gap: "12px",
|
||||
}}>
|
||||
<h3
|
||||
style={{
|
||||
margin: 0,
|
||||
fontSize: "13px",
|
||||
fontWeight: 600,
|
||||
}}>
|
||||
{item.name}
|
||||
</h3>
|
||||
<div
|
||||
onClick={(e) => {
|
||||
e.stopPropagation() // Prevent card click when clicking install
|
||||
if (!isInstalled && !isDownloading) {
|
||||
setIsDownloading(true)
|
||||
vscode.postMessage({
|
||||
|
|
@ -68,22 +91,25 @@ const McpMarketplaceCard = ({ item, installedServers }: McpMarketplaceCardProps)
|
|||
mcpId: item.mcpId,
|
||||
})
|
||||
}
|
||||
}}>
|
||||
<span
|
||||
className={`codicon codicon-${isInstalled ? "check" : isDownloading ? "sync codicon-modifier-spin" : "cloud-download"}`}
|
||||
style={{ marginRight: "6px" }}
|
||||
/>
|
||||
{isInstalled ? "Installed" : isDownloading ? "Downloading..." : "Download"}
|
||||
</VSCodeButton>
|
||||
}}
|
||||
style={{}}>
|
||||
<StyledInstallButton disabled={isInstalled || isDownloading} $isInstalled={isInstalled}>
|
||||
{isInstalled ? "Installed" : isDownloading ? "Installing..." : "Install"}
|
||||
</StyledInstallButton>
|
||||
</div>
|
||||
</div>
|
||||
<p style={{ margin: "8px 0", fontSize: "13px" }}>{item.description}</p>
|
||||
|
||||
{/* Second row: metadata */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "16px",
|
||||
alignItems: "center",
|
||||
gap: "6px",
|
||||
fontSize: "12px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
alignItems: "center",
|
||||
marginTop: "2px",
|
||||
flexWrap: "wrap",
|
||||
minWidth: 0,
|
||||
}}>
|
||||
<VSCodeLink
|
||||
href={item.githubUrl}
|
||||
|
|
@ -91,60 +117,137 @@ const McpMarketplaceCard = ({ item, installedServers }: McpMarketplaceCardProps)
|
|||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
height: "16px", // hacky way to get the icon to center. vscodelink does weird stuff
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
marginBottom: "-3px",
|
||||
minWidth: 0,
|
||||
}}>
|
||||
<span className="codicon codicon-github" style={{ fontSize: "16px" }} />
|
||||
<span className="codicon codicon-github" style={{ fontSize: "14px" }} />
|
||||
</VSCodeLink>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "4px", marginRight: "8px" }}>
|
||||
<span className="codicon codicon-star-full" />
|
||||
{item.githubStars?.toLocaleString() ?? 0}
|
||||
</div>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "4px" }}>
|
||||
<span className="codicon codicon-cloud-download" />
|
||||
{item.downloadCount?.toLocaleString() ?? 0}
|
||||
</div>
|
||||
{item.requiresApiKey && (
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "4px" }}>
|
||||
<span className="codicon codicon-key" title="Requires API key" />
|
||||
</div>
|
||||
)}
|
||||
{item.isRecommended && (
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "4px" }}>
|
||||
<span className="codicon codicon-verified" title="Recommended" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: "8px", marginTop: "8px", flexWrap: "wrap" }}>
|
||||
<span
|
||||
style={{
|
||||
fontSize: "11px",
|
||||
padding: "2px 6px",
|
||||
borderRadius: "4px",
|
||||
background: "var(--vscode-charts-blue)",
|
||||
color: "var(--vscode-foreground)",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
wordBreak: "break-all",
|
||||
minWidth: 0,
|
||||
}}>
|
||||
{item.category}
|
||||
{item.author}
|
||||
</span>
|
||||
{item.tags.map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
style={{
|
||||
fontSize: "11px",
|
||||
padding: "2px 6px",
|
||||
borderRadius: "4px",
|
||||
background: "var(--vscode-badge-background)",
|
||||
color: "var(--vscode-badge-foreground)",
|
||||
}}>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
<span style={{ color: "var(--vscode-descriptionForeground)", flexShrink: 0 }}>|</span>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "4px",
|
||||
minWidth: 0,
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
<span className="codicon codicon-star-full" />
|
||||
<span style={{ wordBreak: "break-all" }}>{item.githubStars?.toLocaleString() ?? 0}</span>
|
||||
</div>
|
||||
<span style={{ color: "var(--vscode-descriptionForeground)", flexShrink: 0 }}>|</span>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "4px",
|
||||
minWidth: 0,
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
<span className="codicon codicon-cloud-download" />
|
||||
<span style={{ wordBreak: "break-all" }}>{item.downloadCount?.toLocaleString() ?? 0}</span>
|
||||
</div>
|
||||
{item.requiresApiKey && (
|
||||
<span className="codicon codicon-key" title="Requires API key" style={{ flexShrink: 0 }} />
|
||||
)}
|
||||
{item.isRecommended && (
|
||||
<span className="codicon codicon-verified" title="Recommended" style={{ flexShrink: 0 }} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Description and tags */}
|
||||
<div style={{ marginTop: "8px" }}>
|
||||
<p style={{ margin: "0 0 4px 0", fontSize: "13px" }}>{item.description}</p>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "6px",
|
||||
flexWrap: "nowrap",
|
||||
overflow: "hidden",
|
||||
position: "relative",
|
||||
}}>
|
||||
<span
|
||||
style={{
|
||||
fontSize: "10px",
|
||||
padding: "1px 4px",
|
||||
borderRadius: "3px",
|
||||
border: "1px solid color-mix(in srgb, var(--vscode-descriptionForeground) 50%, transparent)",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
whiteSpace: "nowrap",
|
||||
}}>
|
||||
{item.category}
|
||||
</span>
|
||||
{item.tags.map((tag, index) => (
|
||||
<span
|
||||
key={tag}
|
||||
style={{
|
||||
fontSize: "10px",
|
||||
padding: "1px 4px",
|
||||
borderRadius: "3px",
|
||||
border: "1px solid color-mix(in srgb, var(--vscode-descriptionForeground) 50%, transparent)",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
whiteSpace: "nowrap",
|
||||
display: "inline-flex",
|
||||
}}>
|
||||
{tag}
|
||||
{index === item.tags.length - 1 ? "" : ""}
|
||||
</span>
|
||||
))}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
width: "32px",
|
||||
background: "linear-gradient(to right, transparent, var(--vscode-editor-background))",
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledInstallButton = styled.button<{ $isInstalled?: boolean }>`
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
padding: 2px 6px;
|
||||
border-radius: 2px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
background: ${(props) =>
|
||||
props.$isInstalled ? "var(--vscode-button-secondaryBackground)" : "var(--vscode-button-background)"};
|
||||
color: var(--vscode-button-foreground);
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: ${(props) =>
|
||||
props.$isInstalled ? "var(--vscode-button-secondaryHoverBackground)" : "var(--vscode-button-hoverBackground)"};
|
||||
}
|
||||
|
||||
&:active:not(:disabled) {
|
||||
background: ${(props) =>
|
||||
props.$isInstalled ? "var(--vscode-button-secondaryBackground)" : "var(--vscode-button-background)"};
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
`
|
||||
|
||||
export default McpMarketplaceCard
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
import { useEffect, useMemo, useState } from "react"
|
||||
import { VSCodeButton, VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"
|
||||
import {
|
||||
VSCodeButton,
|
||||
VSCodeProgressRing,
|
||||
VSCodeRadioGroup,
|
||||
VSCodeRadio,
|
||||
VSCodeDropdown,
|
||||
VSCodeOption,
|
||||
VSCodeTextField,
|
||||
} from "@vscode/webview-ui-toolkit/react"
|
||||
import { McpMarketplaceItem } from "../../../../../src/shared/mcp"
|
||||
import { useExtensionState } from "../../../context/ExtensionStateContext"
|
||||
import { vscode } from "../../../utils/vscode"
|
||||
|
|
@ -33,11 +41,6 @@ const selectStyles = {
|
|||
cursor: "pointer", // Show pointer cursor on hover
|
||||
} as const
|
||||
|
||||
const refreshStyles = {
|
||||
height: controlHeight,
|
||||
alignSelf: "flex-end",
|
||||
} as const
|
||||
|
||||
const McpMarketplaceView = () => {
|
||||
const { mcpServers } = useExtensionState()
|
||||
const [items, setItems] = useState<McpMarketplaceItem[]>([])
|
||||
|
|
@ -46,7 +49,7 @@ const McpMarketplaceView = () => {
|
|||
const [isRefreshing, setIsRefreshing] = useState(false)
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null)
|
||||
const [sortBy, setSortBy] = useState<"downloadCount" | "stars" | "name">("downloadCount")
|
||||
const [sortBy, setSortBy] = useState<"downloadCount" | "stars" | "name" | "newest">("downloadCount")
|
||||
|
||||
const categories = useMemo(() => {
|
||||
const uniqueCategories = new Set(items.map((item) => item.category))
|
||||
|
|
@ -72,6 +75,8 @@ const McpMarketplaceView = () => {
|
|||
return b.githubStars - a.githubStars
|
||||
case "name":
|
||||
return a.name.localeCompare(b.name)
|
||||
case "newest":
|
||||
return b.githubStars - a.githubStars // FIXME: b.createdAt - a.createdAt // Assuming there's a createdAt field
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
|
|
@ -154,73 +159,115 @@ const McpMarketplaceView = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "12px", width: "100%" }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: "16px",
|
||||
gap: "12px",
|
||||
flexWrap: "wrap",
|
||||
}}>
|
||||
<div style={{ display: "flex", gap: "12px", alignItems: "center", flexWrap: "wrap", minWidth: 0 }}>
|
||||
{" "}
|
||||
{/* Added minWidth: 0 to prevent flex item from overflowing */}
|
||||
<div style={{ position: "relative", flex: 1 }}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search MCPs..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="mcp-search-input"
|
||||
style={searchInputStyles}
|
||||
/>
|
||||
<span
|
||||
className="codicon codicon-search"
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
}}>
|
||||
<div style={{ padding: "16px 16px 5px 16px" }}>
|
||||
{/* Search row */}
|
||||
<VSCodeTextField
|
||||
style={{ width: "100%" }}
|
||||
placeholder="Search MCPs..."
|
||||
value={searchQuery}
|
||||
onInput={(e) => setSearchQuery((e.target as HTMLInputElement).value)}>
|
||||
<div
|
||||
slot="start"
|
||||
className="codicon codicon-search"
|
||||
style={{
|
||||
fontSize: 13,
|
||||
marginTop: 2.5,
|
||||
opacity: 0.8,
|
||||
}}
|
||||
/>
|
||||
{searchQuery && (
|
||||
<div
|
||||
className="codicon codicon-close"
|
||||
aria-label="Clear search"
|
||||
onClick={() => setSearchQuery("")}
|
||||
slot="end"
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: "10px",
|
||||
top: "50%",
|
||||
transform: "translateY(-50%)",
|
||||
color: "var(--vscode-input-placeholderForeground)",
|
||||
pointerEvents: "none",
|
||||
fontSize: "14px", // Match input text size
|
||||
lineHeight: 1, // Ensure icon is centered properly
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</VSCodeTextField>
|
||||
|
||||
{/* Filter row */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "8px",
|
||||
marginTop: "8px",
|
||||
}}>
|
||||
<span
|
||||
style={{
|
||||
fontSize: "11px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
textTransform: "uppercase",
|
||||
fontWeight: 500,
|
||||
}}>
|
||||
Filter:
|
||||
</span>
|
||||
<div
|
||||
style={{
|
||||
// transform: "scale(0.9)",
|
||||
// transformOrigin: "left center",
|
||||
position: "relative",
|
||||
zIndex: 2,
|
||||
}}>
|
||||
<VSCodeDropdown
|
||||
value={selectedCategory || ""}
|
||||
onChange={(e) => setSelectedCategory((e.target as HTMLSelectElement).value || null)}>
|
||||
<VSCodeOption value="">All Categories</VSCodeOption>
|
||||
{categories.map((category) => (
|
||||
<VSCodeOption key={category} value={category}>
|
||||
{category}
|
||||
</VSCodeOption>
|
||||
))}
|
||||
</VSCodeDropdown>
|
||||
</div>
|
||||
<select
|
||||
value={selectedCategory || ""}
|
||||
onChange={(e) => setSelectedCategory(e.target.value || null)}
|
||||
className="mcp-select"
|
||||
style={selectStyles}>
|
||||
<option value="">All Categories</option>
|
||||
{categories.map((category) => (
|
||||
<option key={category} value={category}>
|
||||
{category}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<select
|
||||
value={sortBy}
|
||||
onChange={(e) => setSortBy(e.target.value as "downloadCount" | "stars" | "name")}
|
||||
className="mcp-select"
|
||||
style={selectStyles}>
|
||||
<option value="downloadCount">Sort by Downloads</option>
|
||||
<option value="stars">Sort by Stars</option>
|
||||
<option value="name">Sort by Name</option>
|
||||
</select>
|
||||
</div>
|
||||
<VSCodeButton
|
||||
appearance="secondary"
|
||||
onClick={() => fetchMarketplace(true)}
|
||||
disabled={isRefreshing}
|
||||
style={refreshStyles}>
|
||||
<span className="codicon codicon-refresh" style={{ marginRight: "6px" }} />
|
||||
Refresh
|
||||
</VSCodeButton>
|
||||
|
||||
{/* Sort row */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "8px",
|
||||
marginTop: "8px",
|
||||
}}>
|
||||
<span
|
||||
style={{
|
||||
fontSize: "11px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
textTransform: "uppercase",
|
||||
fontWeight: 500,
|
||||
marginTop: "3px",
|
||||
}}>
|
||||
Sort:
|
||||
</span>
|
||||
<VSCodeRadioGroup
|
||||
style={{
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
marginTop: "-2.5px",
|
||||
}}
|
||||
value={sortBy}
|
||||
onChange={(e) => setSortBy((e.target as HTMLInputElement).value as typeof sortBy)}>
|
||||
<VSCodeRadio value="downloadCount">Most Installs</VSCodeRadio>
|
||||
<VSCodeRadio value="stars">Most Stars</VSCodeRadio>
|
||||
<VSCodeRadio value="newest">Newest</VSCodeRadio>
|
||||
<VSCodeRadio value="name">Name</VSCodeRadio>
|
||||
</VSCodeRadioGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
{`
|
||||
.mcp-search-input,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue