mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 12:21:13 -05:00
211 lines
6.0 KiB
TypeScript
211 lines
6.0 KiB
TypeScript
import React, { useEffect, useMemo, useRef } from "react"
|
|
import { ContextMenuOptionType, ContextMenuQueryItem, getContextMenuOptions } from "../../utils/context-mentions"
|
|
import { removeLeadingNonAlphanumeric } from "../common/CodeAccordian"
|
|
|
|
interface ContextMenuProps {
|
|
onSelect: (type: ContextMenuOptionType, value?: string) => void
|
|
searchQuery: string
|
|
onMouseDown: () => void
|
|
selectedIndex: number
|
|
setSelectedIndex: (index: number) => void
|
|
selectedType: ContextMenuOptionType | null
|
|
queryItems: ContextMenuQueryItem[]
|
|
}
|
|
|
|
const ContextMenu: React.FC<ContextMenuProps> = ({
|
|
onSelect,
|
|
searchQuery,
|
|
onMouseDown,
|
|
selectedIndex,
|
|
setSelectedIndex,
|
|
selectedType,
|
|
queryItems,
|
|
}) => {
|
|
const menuRef = useRef<HTMLDivElement>(null)
|
|
|
|
const filteredOptions = useMemo(
|
|
() => getContextMenuOptions(searchQuery, selectedType, queryItems),
|
|
[searchQuery, selectedType, queryItems],
|
|
)
|
|
|
|
useEffect(() => {
|
|
if (menuRef.current) {
|
|
const selectedElement = menuRef.current.children[selectedIndex] as HTMLElement
|
|
if (selectedElement) {
|
|
const menuRect = menuRef.current.getBoundingClientRect()
|
|
const selectedRect = selectedElement.getBoundingClientRect()
|
|
|
|
if (selectedRect.bottom > menuRect.bottom) {
|
|
menuRef.current.scrollTop += selectedRect.bottom - menuRect.bottom
|
|
} else if (selectedRect.top < menuRect.top) {
|
|
menuRef.current.scrollTop -= menuRect.top - selectedRect.top
|
|
}
|
|
}
|
|
}
|
|
}, [selectedIndex])
|
|
|
|
const renderOptionContent = (option: ContextMenuQueryItem) => {
|
|
switch (option.type) {
|
|
case ContextMenuOptionType.Problems:
|
|
return <span>Problems</span>
|
|
case ContextMenuOptionType.URL:
|
|
return <span>Paste URL to fetch contents</span>
|
|
case ContextMenuOptionType.NoResults:
|
|
return <span>No results found</span>
|
|
case ContextMenuOptionType.Git:
|
|
if (option.value) {
|
|
return (
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 0 }}>
|
|
<span style={{ lineHeight: '1.2' }}>{option.label}</span>
|
|
<span style={{
|
|
fontSize: '0.85em',
|
|
opacity: 0.7,
|
|
whiteSpace: 'nowrap',
|
|
overflow: 'hidden',
|
|
textOverflow: 'ellipsis',
|
|
lineHeight: '1.2'
|
|
}}>
|
|
{option.description}
|
|
</span>
|
|
</div>
|
|
)
|
|
} else {
|
|
return <span>Git Commits</span>
|
|
}
|
|
case ContextMenuOptionType.File:
|
|
case ContextMenuOptionType.Folder:
|
|
if (option.value) {
|
|
return (
|
|
<>
|
|
<span>/</span>
|
|
{option.value?.startsWith("/.") && <span>.</span>}
|
|
<span
|
|
style={{
|
|
whiteSpace: "nowrap",
|
|
overflow: "hidden",
|
|
textOverflow: "ellipsis",
|
|
direction: "rtl",
|
|
textAlign: "left",
|
|
}}>
|
|
{removeLeadingNonAlphanumeric(option.value || "") + "\u200E"}
|
|
</span>
|
|
</>
|
|
)
|
|
} else {
|
|
return <span>Add {option.type === ContextMenuOptionType.File ? "File" : "Folder"}</span>
|
|
}
|
|
}
|
|
}
|
|
|
|
const getIconForOption = (option: ContextMenuQueryItem): string => {
|
|
switch (option.type) {
|
|
case ContextMenuOptionType.File:
|
|
return "file"
|
|
case ContextMenuOptionType.Folder:
|
|
return "folder"
|
|
case ContextMenuOptionType.Problems:
|
|
return "warning"
|
|
case ContextMenuOptionType.URL:
|
|
return "link"
|
|
case ContextMenuOptionType.Git:
|
|
return "git-commit"
|
|
case ContextMenuOptionType.NoResults:
|
|
return "info"
|
|
default:
|
|
return "file"
|
|
}
|
|
}
|
|
|
|
const isOptionSelectable = (option: ContextMenuQueryItem): boolean => {
|
|
return option.type !== ContextMenuOptionType.NoResults && option.type !== ContextMenuOptionType.URL
|
|
}
|
|
|
|
return (
|
|
<div
|
|
style={{
|
|
position: "absolute",
|
|
bottom: "calc(100% - 10px)",
|
|
left: 15,
|
|
right: 15,
|
|
overflowX: "hidden",
|
|
}}
|
|
onMouseDown={onMouseDown}>
|
|
<div
|
|
ref={menuRef}
|
|
style={{
|
|
backgroundColor: "var(--vscode-dropdown-background)",
|
|
border: "1px solid var(--vscode-editorGroup-border)",
|
|
borderRadius: "3px",
|
|
boxShadow: "0 4px 10px rgba(0, 0, 0, 0.25)",
|
|
zIndex: 1000,
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
maxHeight: "200px",
|
|
overflowY: "auto",
|
|
}}>
|
|
{filteredOptions.map((option, index) => (
|
|
<div
|
|
key={`${option.type}-${option.value || index}`}
|
|
onClick={() => isOptionSelectable(option) && onSelect(option.type, option.value)}
|
|
style={{
|
|
padding: "8px 12px",
|
|
cursor: isOptionSelectable(option) ? "pointer" : "default",
|
|
color: "var(--vscode-dropdown-foreground)",
|
|
borderBottom: "1px solid var(--vscode-editorGroup-border)",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "space-between",
|
|
backgroundColor:
|
|
index === selectedIndex && isOptionSelectable(option)
|
|
? "var(--vscode-list-activeSelectionBackground)"
|
|
: "",
|
|
}}
|
|
onMouseEnter={() => isOptionSelectable(option) && setSelectedIndex(index)}>
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
flex: 1,
|
|
minWidth: 0,
|
|
overflow: "hidden",
|
|
paddingTop: 0
|
|
}}>
|
|
<i
|
|
className={`codicon codicon-${getIconForOption(option)}`}
|
|
style={{
|
|
marginRight: "6px",
|
|
flexShrink: 0,
|
|
fontSize: "14px",
|
|
marginTop: 0
|
|
}}
|
|
/>
|
|
{renderOptionContent(option)}
|
|
</div>
|
|
{((option.type === ContextMenuOptionType.File ||
|
|
option.type === ContextMenuOptionType.Folder ||
|
|
option.type === ContextMenuOptionType.Git) &&
|
|
!option.value) && (
|
|
<i
|
|
className="codicon codicon-chevron-right"
|
|
style={{ fontSize: "14px", flexShrink: 0, marginLeft: 8 }}
|
|
/>
|
|
)}
|
|
{(option.type === ContextMenuOptionType.Problems ||
|
|
((option.type === ContextMenuOptionType.File ||
|
|
option.type === ContextMenuOptionType.Folder ||
|
|
option.type === ContextMenuOptionType.Git) &&
|
|
option.value)) && (
|
|
<i
|
|
className="codicon codicon-add"
|
|
style={{ fontSize: "14px", flexShrink: 0, marginLeft: 8 }}
|
|
/>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default ContextMenu
|