Skip to content

Commit 8c1f7e8

Browse files
committed
refactor(reorder-profile): simplify code
1 parent 70f01ed commit 8c1f7e8

File tree

7 files changed

+160
-240
lines changed

7 files changed

+160
-240
lines changed

src/core/webview/webviewMessageHandler.ts

Lines changed: 0 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1638,7 +1638,6 @@ export const webviewMessageHandler = async (
16381638
break
16391639
case "setApiConfigsCustomOrder":
16401640
if (message.values && Array.isArray(message.values.customOrder)) {
1641-
// Validate that each item has the required structure
16421641
const isValidOrder = message.values.customOrder.every(
16431642
(item: any) =>
16441643
typeof item === "object" &&
@@ -1653,66 +1652,6 @@ export const webviewMessageHandler = async (
16531652
}
16541653
}
16551654
break
1656-
case "moveApiConfigUp":
1657-
if (message.text) {
1658-
const currentOrder = getGlobalState("apiConfigsCustomOrder") || []
1659-
const pinnedApiConfigs = getGlobalState("pinnedApiConfigs") || {}
1660-
const isPinned = !!pinnedApiConfigs[message.text]
1661-
1662-
// Find the item in the current order
1663-
const itemIndex = currentOrder.findIndex((item: any) => item.id === message.text)
1664-
1665-
if (itemIndex > 0) {
1666-
// Find previous item with same pinned status
1667-
const sameTypeItems = currentOrder.filter((item: any) => item.pinned === isPinned)
1668-
const currentItemInType = sameTypeItems.findIndex((item: any) => item.id === message.text)
1669-
1670-
if (currentItemInType > 0) {
1671-
const newOrder = [...currentOrder]
1672-
const prevItem = sameTypeItems[currentItemInType - 1]
1673-
const prevItemIndex = currentOrder.findIndex((item: any) => item.id === prevItem.id)
1674-
1675-
// Swap indices
1676-
const tempIndex = newOrder[itemIndex].index
1677-
newOrder[itemIndex].index = newOrder[prevItemIndex].index
1678-
newOrder[prevItemIndex].index = tempIndex
1679-
1680-
await updateGlobalState("apiConfigsCustomOrder", newOrder)
1681-
await provider.postStateToWebview()
1682-
}
1683-
}
1684-
}
1685-
break
1686-
case "moveApiConfigDown":
1687-
if (message.text) {
1688-
const currentOrder = getGlobalState("apiConfigsCustomOrder") || []
1689-
const pinnedApiConfigs = getGlobalState("pinnedApiConfigs") || {}
1690-
const isPinned = !!pinnedApiConfigs[message.text]
1691-
1692-
// Find the item in the current order
1693-
const itemIndex = currentOrder.findIndex((item: any) => item.id === message.text)
1694-
1695-
if (itemIndex !== -1 && itemIndex < currentOrder.length - 1) {
1696-
// Find next item with same pinned status
1697-
const sameTypeItems = currentOrder.filter((item: any) => item.pinned === isPinned)
1698-
const currentItemInType = sameTypeItems.findIndex((item: any) => item.id === message.text)
1699-
1700-
if (currentItemInType < sameTypeItems.length - 1) {
1701-
const newOrder = [...currentOrder]
1702-
const nextItem = sameTypeItems[currentItemInType + 1]
1703-
const nextItemIndex = currentOrder.findIndex((item: any) => item.id === nextItem.id)
1704-
1705-
// Swap indices
1706-
const tempIndex = newOrder[itemIndex].index
1707-
newOrder[itemIndex].index = newOrder[nextItemIndex].index
1708-
newOrder[nextItemIndex].index = tempIndex
1709-
1710-
await updateGlobalState("apiConfigsCustomOrder", newOrder)
1711-
await provider.postStateToWebview()
1712-
}
1713-
}
1714-
}
1715-
break
17161655
case "enhancementApiConfigId":
17171656
await updateGlobalState("enhancementApiConfigId", message.text)
17181657
await provider.postStateToWebview()

src/shared/ExtensionMessage.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,6 @@ export interface ExtensionMessage {
103103
| "maxReadFileLine"
104104
| "fileSearchResults"
105105
| "toggleApiConfigPin"
106-
| "moveApiConfigUp"
107-
| "moveApiConfigDown"
108106
| "acceptInput"
109107
| "setHistoryPreviewCollapsed"
110108
| "commandExecutionStatus"

src/shared/WebviewMessage.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,6 @@ export interface WebviewMessage {
178178
| "searchFiles"
179179
| "toggleApiConfigPin"
180180
| "setApiConfigsCustomOrder"
181-
| "moveApiConfigUp"
182-
| "moveApiConfigDown"
183181
| "setHistoryPreviewCollapsed"
184182
| "hasOpenedModeSelector"
185183
| "cloudButtonClicked"

webview-ui/src/components/chat/ApiConfigSelector.tsx

Lines changed: 104 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { useState, useMemo, useCallback, useRef } from "react"
2-
import { useTranslation } from "react-i18next"
1+
import { useState, useMemo, useCallback } from "react"
32
import { Fzf } from "fzf"
43

54
import { cn } from "@/lib/utils"
@@ -10,6 +9,84 @@ import { Button } from "@/components/ui"
109
import { useExtensionState } from "@/context/ExtensionStateContext"
1110

1211
import { IconButton } from "./IconButton"
12+
import { useAppTranslation } from "@/i18n/TranslationContext"
13+
14+
interface ConfigItemProps {
15+
config: { id: string; name: string; modelId?: string }
16+
isPinned: boolean
17+
index: number
18+
value: string
19+
onSelect: (configId: string) => void
20+
togglePinnedApiConfig: (id: string) => void
21+
}
22+
23+
const ConfigItem = ({ config, isPinned, index, value, onSelect, togglePinnedApiConfig }: ConfigItemProps) => {
24+
const { t } = useAppTranslation()
25+
const isCurrentConfig = config.id === value
26+
27+
return (
28+
<div
29+
key={config.id}
30+
data-config-item
31+
data-config-item-index={index}
32+
role="option"
33+
aria-selected={isCurrentConfig}
34+
aria-label={`${config.name}${config.modelId ? ` - ${config.modelId}` : ""}`}
35+
onClick={() => onSelect(config.id)}
36+
onKeyDown={(e) => {
37+
if (e.key === "Enter" || e.key === " ") {
38+
e.preventDefault()
39+
onSelect(config.id)
40+
}
41+
}}
42+
className={cn(
43+
"px-3 py-1.5 text-sm flex items-center group relative",
44+
"cursor-pointer hover:bg-vscode-list-hoverBackground",
45+
isCurrentConfig &&
46+
"bg-vscode-list-activeSelectionBackground text-vscode-list-activeSelectionForeground",
47+
)}>
48+
<div className="flex-1 min-w-0 flex items-center gap-1 overflow-hidden">
49+
<span className="flex-shrink-0">{config.name}</span>
50+
{config.modelId && (
51+
<>
52+
<span
53+
className="text-vscode-descriptionForeground opacity-70 min-w-0 overflow-hidden"
54+
style={{ direction: "rtl", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
55+
{config.modelId}
56+
</span>
57+
</>
58+
)}
59+
</div>
60+
61+
<div className="flex items-center gap-1">
62+
{isCurrentConfig && (
63+
<div className="size-5 p-1 flex items-center justify-center">
64+
<span className="codicon codicon-check text-xs" />
65+
</div>
66+
)}
67+
<StandardTooltip
68+
content={isPinned ? t("chat:apiConfigSelector.unpin") : t("chat:apiConfigSelector.pin")}>
69+
<Button
70+
variant="ghost"
71+
size="icon"
72+
tabIndex={-1}
73+
aria-label={isPinned ? t("chat:apiConfigSelector.unpin") : t("chat:apiConfigSelector.pin")}
74+
onClick={(e) => {
75+
e.stopPropagation()
76+
togglePinnedApiConfig(config.id)
77+
vscode.postMessage({ type: "toggleApiConfigPin", text: config.id })
78+
}}
79+
className={cn("size-5 flex items-center justify-center", {
80+
"opacity-0 group-hover:opacity-100": !isPinned && !isCurrentConfig,
81+
"bg-accent opacity-100": isPinned,
82+
})}>
83+
<span className="codicon codicon-pin text-xs opacity-50" />
84+
</Button>
85+
</StandardTooltip>
86+
</div>
87+
</div>
88+
)
89+
}
1390

1491
type SortMode = "alphabetical" | "custom"
1592

@@ -36,14 +113,13 @@ export const ApiConfigSelector = ({
36113
pinnedApiConfigs,
37114
togglePinnedApiConfig,
38115
}: ApiConfigSelectorProps) => {
39-
const { t } = useTranslation()
116+
const { t } = useAppTranslation()
40117
const { apiConfigsCustomOrder: customOrder = [] } = useExtensionState()
41118
const [open, setOpen] = useState(false)
42119
const [searchValue, setSearchValue] = useState("")
43120
const [sortMode, setSortMode] = useState<SortMode>("alphabetical")
44121

45122
const portalContainer = useRooPortal("roo-portal")
46-
const scrollContainerRef = useRef<HTMLDivElement | null>(null)
47123

48124
// Sort configs based on sort mode
49125
const sortedConfigs = useMemo(() => {
@@ -75,13 +151,8 @@ export const ApiConfigSelector = ({
75151
return sortedConfigs
76152
}
77153

78-
const searchableItems = sortedConfigs.map((config) => ({
79-
original: config,
80-
searchStr: config.name,
81-
}))
82-
83-
const fzf = new Fzf(searchableItems, { selector: (item) => item.searchStr })
84-
const matchingItems = fzf.find(searchValue).map((result) => result.item.original)
154+
const fzf = new Fzf(sortedConfigs, { selector: (item) => item.name })
155+
const matchingItems = fzf.find(searchValue).map((result) => result.item)
85156
return matchingItems
86157
}, [sortedConfigs, searchValue])
87158

@@ -106,88 +177,12 @@ export const ApiConfigSelector = ({
106177
setOpen(false)
107178
}, [])
108179

109-
const renderConfigItem = useCallback(
110-
(config: { id: string; name: string; modelId?: string }, isPinned: boolean, index: number) => {
111-
const isCurrentConfig = config.id === value
112-
113-
return (
114-
<div
115-
key={config.id}
116-
data-config-item
117-
data-config-item-index={index}
118-
role="option"
119-
aria-selected={isCurrentConfig}
120-
aria-label={`${config.name}${config.modelId ? ` - ${config.modelId}` : ""}`}
121-
onClick={() => handleSelect(config.id)}
122-
onKeyDown={(e) => {
123-
if (e.key === "Enter" || e.key === " ") {
124-
e.preventDefault()
125-
handleSelect(config.id)
126-
}
127-
}}
128-
tabIndex={0}
129-
className={cn(
130-
"px-3 py-1.5 text-sm flex items-center group relative",
131-
"cursor-pointer hover:bg-vscode-list-hoverBackground",
132-
isCurrentConfig &&
133-
"bg-vscode-list-activeSelectionBackground text-vscode-list-activeSelectionForeground",
134-
)}>
135-
<div className="flex-1 min-w-0 flex items-center gap-1 overflow-hidden">
136-
<span className="flex-shrink-0">{config.name}</span>
137-
{config.modelId && (
138-
<>
139-
<span
140-
className="text-vscode-descriptionForeground opacity-70 min-w-0 overflow-hidden"
141-
style={{ direction: "rtl", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
142-
{config.modelId}
143-
</span>
144-
</>
145-
)}
146-
</div>
147-
148-
<div className="flex items-center gap-1">
149-
{isCurrentConfig && (
150-
<div className="size-5 p-1 flex items-center justify-center">
151-
<span className="codicon codicon-check text-xs" />
152-
</div>
153-
)}
154-
<StandardTooltip
155-
content={isPinned ? t("chat:apiConfigSelector.unpin") : t("chat:apiConfigSelector.pin")}>
156-
<Button
157-
variant="ghost"
158-
size="icon"
159-
tabIndex={-1}
160-
aria-label={
161-
isPinned ? t("chat:apiConfigSelector.unpin") : t("chat:apiConfigSelector.pin")
162-
}
163-
onClick={(e) => {
164-
e.stopPropagation()
165-
togglePinnedApiConfig(config.id)
166-
vscode.postMessage({ type: "toggleApiConfigPin", text: config.id })
167-
}}
168-
className={cn("size-5 flex items-center justify-center", {
169-
"opacity-0 group-hover:opacity-100": !isPinned && !isCurrentConfig,
170-
"bg-accent opacity-100": isPinned,
171-
})}>
172-
<span className="codicon codicon-pin text-xs opacity-50" />
173-
</Button>
174-
</StandardTooltip>
175-
</div>
176-
</div>
177-
)
178-
},
179-
[value, handleSelect, togglePinnedApiConfig, t],
180-
)
181-
182180
return (
183181
<Popover open={open} onOpenChange={setOpen} data-testid="api-config-selector-root">
184182
<StandardTooltip content={title}>
185183
<PopoverTrigger
186184
disabled={disabled}
187185
data-testid="dropdown-trigger"
188-
aria-label={title}
189-
aria-expanded={open}
190-
aria-haspopup="listbox"
191186
className={cn(
192187
"min-w-0 inline-flex items-center relative whitespace-nowrap px-1.5 py-1 text-xs",
193188
"bg-transparent border border-[rgba(255,255,255,0.08)] rounded-md text-vscode-foreground",
@@ -222,15 +217,6 @@ export const ApiConfigSelector = ({
222217
<span
223218
className="codicon codicon-close text-vscode-input-foreground opacity-50 hover:opacity-100 text-xs cursor-pointer"
224219
onClick={() => setSearchValue("")}
225-
aria-label="Clear search"
226-
role="button"
227-
tabIndex={0}
228-
onKeyDown={(e) => {
229-
if (e.key === "Enter" || e.key === " ") {
230-
e.preventDefault()
231-
setSearchValue("")
232-
}
233-
}}
234220
/>
235221
</div>
236222
)}
@@ -245,7 +231,6 @@ export const ApiConfigSelector = ({
245231

246232
{/* Config list */}
247233
<div
248-
ref={scrollContainerRef}
249234
className="max-h-[300px] overflow-y-auto"
250235
role="listbox"
251236
aria-label={t("prompts:apiConfiguration.select")}>
@@ -256,17 +241,35 @@ export const ApiConfigSelector = ({
256241
) : (
257242
<div className="py-1">
258243
{/* Pinned configs */}
259-
{pinnedConfigs.map((config, index) => renderConfigItem(config, true, index))}
244+
{pinnedConfigs.map((config, index) => (
245+
<ConfigItem
246+
key={config.id}
247+
config={config}
248+
isPinned
249+
index={index}
250+
value={value}
251+
onSelect={handleSelect}
252+
togglePinnedApiConfig={togglePinnedApiConfig}
253+
/>
254+
))}
260255

261256
{/* Separator between pinned and unpinned */}
262257
{pinnedConfigs.length > 0 && unpinnedConfigs.length > 0 && (
263258
<div className="mx-1 my-1 h-px bg-vscode-dropdown-foreground/10" />
264259
)}
265260

266261
{/* Unpinned configs */}
267-
{unpinnedConfigs.map((config, index) =>
268-
renderConfigItem(config, false, pinnedConfigs.length + index),
269-
)}
262+
{unpinnedConfigs.map((config, index) => (
263+
<ConfigItem
264+
key={config.id}
265+
config={config}
266+
isPinned={false}
267+
index={pinnedConfigs.length + index}
268+
value={value}
269+
onSelect={handleSelect}
270+
togglePinnedApiConfig={togglePinnedApiConfig}
271+
/>
272+
))}
270273
</div>
271274
)}
272275
</div>

webview-ui/src/components/settings/ApiConfigManager.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,6 @@ const ApiConfigManager = ({
9191
[organizationAllowList],
9292
)
9393

94-
// Get existing config names for validation
95-
const existingConfigNames = useMemo(() => {
96-
return listApiConfigMeta.map((config) => config.name)
97-
}, [listApiConfigMeta])
98-
9994
const validateName = useCallback(
10095
(name: string, isNewProfile: boolean): string | null => {
10196
const trimmed = name.trim()
@@ -358,8 +353,8 @@ const ApiConfigManager = ({
358353
isFocused={focusedIndex === index}
359354
isValid={isProfileValid(config)}
360355
isOnlyProfile={isOnlyProfile}
361-
existingConfigNames={existingConfigNames}
362356
isReorderingMode={isReorderingMode}
357+
validateName={validateName}
363358
onDragStart={handleDragStart}
364359
onDragEnd={handleDragEnd}
365360
onDragOver={handleDragOver}

0 commit comments

Comments
 (0)