import { useCallback, useEffect, useRef, useState } from 'react'; import { Button, Input, InputNumber, Popover, Tooltip, Typography } from 'antd'; import type { DefaultOptionType } from 'antd/es/select'; import cx from 'classnames'; import { LogViewMode } from 'container/LogsTable'; import { FontSize, OptionsMenuConfig } from 'container/OptionsMenu/types'; import useDebouncedFn from 'hooks/useDebouncedFunction'; import { Check, ChevronLeft, ChevronRight, Minus, Plus, Sliders, X, } from 'lucide-react'; import './LogsFormatOptionsMenu.styles.scss'; interface LogsFormatOptionsMenuProps { items: any; selectedOptionFormat: any; config: OptionsMenuConfig; } function OptionsMenu({ items, selectedOptionFormat, config, }: LogsFormatOptionsMenuProps): JSX.Element { const { maxLines, format, addColumn, fontSize } = config; const [selectedItem, setSelectedItem] = useState(selectedOptionFormat); const maxLinesNumber = (maxLines?.value as number) || 1; const [maxLinesPerRow, setMaxLinesPerRow] = useState(maxLinesNumber); const [fontSizeValue, setFontSizeValue] = useState( fontSize?.value || FontSize.SMALL, ); const [isFontSizeOptionsOpen, setIsFontSizeOptionsOpen] = useState( false, ); const [showAddNewColumnContainer, setShowAddNewColumnContainer] = useState( false, ); const [selectedValue, setSelectedValue] = useState(null); const listRef = useRef(null); const initialMouseEnterRef = useRef(false); const onChange = useCallback( (key: LogViewMode) => { if (!format) { return; } format.onChange(key); }, [format], ); const handleMenuItemClick = (key: LogViewMode): void => { setSelectedItem(key); onChange(key); setShowAddNewColumnContainer(false); }; const incrementMaxLinesPerRow = (): void => { if (maxLinesPerRow < 10) { setMaxLinesPerRow(maxLinesPerRow + 1); } }; const decrementMaxLinesPerRow = (): void => { if (maxLinesPerRow > 1) { setMaxLinesPerRow(maxLinesPerRow - 1); } }; const handleSearchValueChange = useDebouncedFn((event): void => { // @ts-ignore const value = event?.target?.value || ''; if (addColumn && addColumn?.onSearch) { addColumn?.onSearch(value); } }, 300); const handleToggleAddNewColumn = (): void => { addColumn?.onSearch?.(''); setShowAddNewColumnContainer(!showAddNewColumnContainer); }; const handleLinesPerRowChange = (maxLinesPerRow: number | null): void => { if ( maxLinesPerRow && Number.isInteger(maxLinesNumber) && maxLinesPerRow > 1 ) { setMaxLinesPerRow(maxLinesPerRow); } }; useEffect(() => { if (maxLinesPerRow && config && config.maxLines?.onChange) { config.maxLines.onChange(maxLinesPerRow); } }, [maxLinesPerRow]); useEffect(() => { if (fontSizeValue && config && config.fontSize?.onChange) { config.fontSize.onChange(fontSizeValue); } }, [fontSizeValue]); function handleColumnSelection( currentIndex: number, optionsData: DefaultOptionType[], ): void { const currentItem = optionsData[currentIndex]; const itemLength = optionsData.length; if (addColumn && addColumn?.onSelect) { addColumn?.onSelect(selectedValue, { label: currentItem.label, disabled: false, }); // if the last element is selected then select the previous one if (currentIndex === itemLength - 1) { // there should be more than 1 element in the list if (currentIndex - 1 >= 0) { const prevValue = optionsData[currentIndex - 1]?.value || null; setSelectedValue(prevValue as string | null); } else { // if there is only one element then just select and do nothing setSelectedValue(null); } } else { // selecting any random element from the list except the last one const nextIndex = currentIndex + 1; const nextValue = optionsData[nextIndex]?.value || null; setSelectedValue(nextValue as string | null); } } } const handleKeyDown = (e: KeyboardEvent): void => { if (!selectedValue) { return; } const optionsData = addColumn?.options || []; const currentIndex = optionsData.findIndex( (item) => item?.value === selectedValue, ); const itemLength = optionsData.length; switch (e.key) { case 'ArrowUp': { const newValue = optionsData[Math.max(0, currentIndex - 1)]?.value; setSelectedValue(newValue as string | null); e.preventDefault(); break; } case 'ArrowDown': { const newValue = optionsData[Math.min(itemLength - 1, currentIndex + 1)]?.value; setSelectedValue(newValue as string | null); e.preventDefault(); break; } case 'Enter': e.preventDefault(); handleColumnSelection(currentIndex, optionsData); break; default: break; } }; useEffect(() => { // Scroll the selected item into view const listNode = listRef.current; if (listNode && selectedValue) { const optionsData = addColumn?.options || []; const currentIndex = optionsData.findIndex( (item) => item?.value === selectedValue, ); const itemNode = listNode.children[currentIndex] as HTMLElement; if (itemNode) { itemNode.scrollIntoView({ behavior: 'smooth', block: 'nearest', }); } } }, [selectedValue]); useEffect(() => { window.addEventListener('keydown', handleKeyDown); return (): void => { window.removeEventListener('keydown', handleKeyDown); }; }, [selectedValue]); return (
{ // this is to restrict click events to propogate to parent event.stopPropagation(); }} > {isFontSizeOptionsOpen ? (
) : null} {showAddNewColumnContainer && (
Add New Column
{addColumn?.isFetching && (
Loading ...
)}
{addColumn?.options?.map(({ label, value }, index) => (
{ if (!initialMouseEnterRef.current) { setSelectedValue(value as string | null); } initialMouseEnterRef.current = true; }} onMouseMove={(): void => { // this is added to handle the mouse move explicit event and not the re-rendered on mouse enter event setSelectedValue(value as string | null); }} onClick={(eve): void => { eve.stopPropagation(); handleColumnSelection(index, addColumn?.options || []); }} >
{label}
))}
)} {!isFontSizeOptionsOpen && !showAddNewColumnContainer && (
Font Size
FORMAT
{items.map( (item: any): JSX.Element => (
handleMenuItemClick(item.key)} >
{item.label} {selectedItem === item.key && }
), )}
{selectedItem && ( <> <>
max lines per row
{!showAddNewColumnContainer &&
}
{!showAddNewColumnContainer && (
columns {' '}
)}
{addColumn?.value?.map(({ name }) => (
{name}
{addColumn?.value?.length > 1 && ( addColumn.onRemove(name)} /> )}
))} {addColumn && addColumn?.value?.length === 0 && (
No columns selected
)}
)}
)}
); } function LogsFormatOptionsMenu({ items, selectedOptionFormat, config, }: LogsFormatOptionsMenuProps): JSX.Element { const [isPopoverOpen, setIsPopoverOpen] = useState(false); return ( } trigger="click" placement="bottomRight" arrow={false} open={isPopoverOpen} onOpenChange={setIsPopoverOpen} rootClassName="format-options-popover" destroyTooltipOnHide >