import { IconChevronDown } from '@tabler/icons';
import Dropdown from 'components/Dropdown/index';
import {
IconGrpcBidiStreaming,
IconGrpcClientStreaming,
IconGrpcServerStreaming,
IconGrpcUnary
} from 'components/Icons/Grpc';
import SearchInput from 'components/SearchInput/index';
import { search } from 'fast-fuzzy';
import React, { forwardRef, useEffect, useRef, useState } from 'react';
import { useTheme } from 'providers/Theme';
import StyledWrapper from './StyledWrapper';
const MethodDropdown = ({
grpcMethods,
selectedGrpcMethod,
onMethodSelect,
onMethodDropdownCreate
}) => {
const { theme } = useTheme();
const [searchText, setSearchText] = useState('');
const [focusedIndex, setFocusedIndex] = useState(-1);
const searchInputRef = useRef();
const listRef = useRef();
useEffect(() => {
const activeItem = listRef.current?.querySelector(`[data-index="${focusedIndex}"]`);
if (activeItem) {
activeItem.scrollIntoView({ block: 'nearest' });
}
}, [focusedIndex]);
const groupMethodsByService = (methods) => {
if (!methods || !methods.length) return {};
const groupedMethods = {};
methods.forEach((method) => {
const pathWithoutLeadingSlash = method.path.startsWith('/') ? method.path.slice(1) : method.path;
const parts = pathWithoutLeadingSlash.split('/');
const serviceName = parts[0] || 'Default';
const methodName = parts[1] || method.path;
const enhancedMethod = {
...method,
serviceName,
methodName
};
if (!groupedMethods[serviceName]) {
groupedMethods[serviceName] = [];
}
groupedMethods[serviceName].push(enhancedMethod);
});
return groupedMethods;
};
const getIconForMethodType = (type) => {
switch (type) {
case 'unary':
return ;
case 'client-streaming':
return ;
case 'server-streaming':
return ;
case 'bidi-streaming':
return ;
default:
return ;
}
};
const MethodsDropdownIcon = forwardRef((props, ref) => {
return (
{selectedGrpcMethod &&
{getIconForMethodType(selectedGrpcMethod.type)}
}
{selectedGrpcMethod ? (selectedGrpcMethod.path.split('.').at(-1) || selectedGrpcMethod.path) : 'Select Method'}
);
});
const handleGrpcMethodSelect = (method) => {
const methodType = method.type;
onMethodSelect({ path: method.path, type: methodType });
};
const filteredMethods = searchText ? search(String(searchText), grpcMethods, { keySelector: (obj) => obj.path }) : grpcMethods;
const groupedMethods = groupMethodsByService(filteredMethods);
// Flatten grouped methods for keyboard navigation
const flatMethodList = Object.values(groupedMethods).flat();
const handleKeyDown = (e) => {
if (!flatMethodList.length) return;
if (e.key === 'ArrowDown') {
e.preventDefault();
setFocusedIndex((prev) =>
prev < flatMethodList.length - 1 ? prev + 1 : flatMethodList.length - 1);
} else if (e.key === 'ArrowUp') {
e.preventDefault();
setFocusedIndex((prev) =>
prev >= 0 ? prev - 1 : -1);
} else if (e.key === 'Enter' && focusedIndex >= 0) {
e.preventDefault();
handleGrpcMethodSelect(flatMethodList[focusedIndex]);
}
};
const focusSearchInput = () => {
setTimeout(() => {
if (searchInputRef.current) {
searchInputRef.current.focus();
}
}, 0); // 0ms to ensure the dropdown is fully rendered and focused
};
const handleDropdownShow = () => {
focusSearchInput();
setSearchText('');
setFocusedIndex(-1);
};
const handleSearchChange = (e) => {
// auto focus the first method when the search input is not empty
if (e.target.value.trim().length > 0) {
setFocusedIndex(0);
} else {
setFocusedIndex(-1);
}
};
if (!grpcMethods || grpcMethods.length === 0) {
return null;
}
return (
} placement="bottom-end" style={{ maxWidth: 'unset' }} onShow={handleDropdownShow}>
{Object.entries(groupedMethods).map(([serviceName, methods], serviceIndex) => (
{serviceName || 'Default Service'}
{methods.map((method, methodIndex) => {
const globalMethodIndex
= Object.values(groupedMethods)
.slice(0, serviceIndex)
.reduce((acc, group) => acc + group.length, 0) + methodIndex;
const isSelected = selectedGrpcMethod && selectedGrpcMethod.path === method.path;
const isFocused = focusedIndex === globalMethodIndex;
return (
handleGrpcMethodSelect(method)}
data-index={globalMethodIndex}
data-testid="grpc-method-item"
>
{getIconForMethodType(method.type)}
{method.methodName}
{method.type}
);
})}
))}
{filteredMethods.length === 0 && (
No methods found for the search term
)}
);
};
export default MethodDropdown;