import React, { Component } from 'react'; import { Button, InputGroup, InputGroupAddon, InputGroupText, Input, } from 'reactstrap'; import Downshift, { ControllerStateAndHelpers } from 'downshift'; import fuzzy from 'fuzzy'; import SanitizeHTML from './components/SanitizeHTML'; import { library } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faSearch, faSpinner } from '@fortawesome/free-solid-svg-icons'; library.add(faSearch, faSpinner); interface ExpressionInputProps { value: string; metricNames: string[]; executeQuery: (expr: string) => void; loading: boolean; } interface ExpressionInputState { height: number | string; value: string; } class ExpressionInput extends Component { private prevNoMatchValue: string | null = null; private exprInputRef = React.createRef(); constructor(props: ExpressionInputProps) { super(props); this.state = { value: props.value, height: 'auto' } } componentDidMount() { this.setHeight(); } setHeight = () => { const { offsetHeight, clientHeight, scrollHeight } = this.exprInputRef.current!; const offset = offsetHeight - clientHeight; // Needed in order for the height to be more accurate. this.setState({ height: scrollHeight + offset }); } handleInput = () => { this.setState({ height: 'auto', value: this.exprInputRef.current!.value }, this.setHeight); } handleDropdownSelection = (value: string) => this.setState({ value }); handleKeyPress = (event: React.KeyboardEvent) => { if (event.key === 'Enter' && !event.shiftKey) { this.props.executeQuery(this.exprInputRef.current!.value); event.preventDefault(); } } executeQuery = () => this.props.executeQuery(this.exprInputRef.current!.value) renderAutosuggest = (downshift: ControllerStateAndHelpers) => { const { inputValue } = downshift if (!inputValue || (this.prevNoMatchValue && inputValue.includes(this.prevNoMatchValue))) { downshift.closeMenu(); return null; } const matches = fuzzy.filter(inputValue.replace(/ /g, ''), this.props.metricNames, { pre: "", post: "", }); if (matches.length === 0) { this.prevNoMatchValue = inputValue; downshift.closeMenu(); return null; } return (
    { matches .slice(0, 200) // Limit DOM rendering to 100 results, as DOM rendering is sloooow. .map((item, index) => (
  • {item.string}
  • )) }
); } render() { const { value, height } = this.state; return ( {(downshift) => (
{this.props.loading ? : } { switch (event.key) { case 'Home': case 'End': // We want to be able to jump to the beginning/end of the input field. // By default, Downshift otherwise jumps to the first/last suggestion item instead. (event.nativeEvent as any).preventDownshiftDefault = true; break; case 'ArrowUp': case 'ArrowDown': if (!downshift.isOpen) { (event.nativeEvent as any).preventDownshiftDefault = true; } break; case 'Enter': downshift.closeMenu(); break; case 'Escape': if (!downshift.isOpen) { this.exprInputRef.current!.blur(); } break; default: } } } as any)} /> {downshift.isOpen && this.renderAutosuggest(downshift)}
)}
); } } export default ExpressionInput;