From 2e4603e716b47a9625c40e1f360c0da6072eb2b1 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 5 Apr 2018 16:04:47 -0700 Subject: [PATCH] Introduce concept of "Selected Function" and allow arrow key selection --- ui/src/ifql/components/FuncList.tsx | 44 ++++-- ui/src/ifql/components/FuncListItem.tsx | 19 ++- ui/src/ifql/components/FuncSelector.tsx | 126 +++++++++++++----- ui/src/style/components/fancy-scrollbars.scss | 37 ++++- 4 files changed, 170 insertions(+), 56 deletions(-) diff --git a/ui/src/ifql/components/FuncList.tsx b/ui/src/ifql/components/FuncList.tsx index 098c123c9..2a8a91880 100644 --- a/ui/src/ifql/components/FuncList.tsx +++ b/ui/src/ifql/components/FuncList.tsx @@ -1,42 +1,58 @@ import React, {SFC, ChangeEvent, KeyboardEvent} from 'react' import FancyScrollbar from 'src/shared/components/FancyScrollbar' -import DropdownInput from 'src/shared/components/DropdownInput' +import FuncSelectorInput from 'src/shared/components/FuncSelectorInput' import FuncListItem from 'src/ifql/components/FuncListItem' interface Props { inputText: string - isOpen: boolean onInputChange: (e: ChangeEvent) => void onKeyDown: (e: KeyboardEvent) => void onAddNode: (name: string) => void funcs: string[] + selectedFunc: string + onSetSelectedFunc: (name: string) => void } const FuncList: SFC = ({ inputText, - isOpen, onAddNode, onKeyDown, onInputChange, funcs, + selectedFunc, + onSetSelectedFunc, }) => { return ( -
    - + - - {isOpen && - funcs.map((func, i) => ( - - ))} - -
+
    + + {funcs.length > 0 ? ( + funcs.map((func, i) => ( + + )) + ) : ( +
    No results
    + )} +
    +
+ ) } diff --git a/ui/src/ifql/components/FuncListItem.tsx b/ui/src/ifql/components/FuncListItem.tsx index 602624797..4fc54de64 100644 --- a/ui/src/ifql/components/FuncListItem.tsx +++ b/ui/src/ifql/components/FuncListItem.tsx @@ -3,6 +3,8 @@ import React, {PureComponent} from 'react' interface Props { name: string onAddNode: (name: string) => void + selectedFunc: string + onSetSelectedFunc: (name: string) => void } export default class FuncListItem extends PureComponent { @@ -10,12 +12,25 @@ export default class FuncListItem extends PureComponent { const {name} = this.props return ( -
  • - {name} +
  • + {name}
  • ) } + private getActiveClass(): string { + const {name, selectedFunc} = this.props + return name === selectedFunc ? 'active' : '' + } + + private handleMouseEnter = () => { + this.props.onSetSelectedFunc(this.props.name) + } + private handleClick = () => { this.props.onAddNode(this.props.name) } diff --git a/ui/src/ifql/components/FuncSelector.tsx b/ui/src/ifql/components/FuncSelector.tsx index 67b2e1f4c..aa37a6393 100644 --- a/ui/src/ifql/components/FuncSelector.tsx +++ b/ui/src/ifql/components/FuncSelector.tsx @@ -1,4 +1,5 @@ import React, {PureComponent, ChangeEvent, KeyboardEvent} from 'react' +import _ from 'lodash' import {ClickOutside} from 'src/shared/components/ClickOutside' import FuncList from 'src/ifql/components/FuncList' @@ -6,6 +7,8 @@ import FuncList from 'src/ifql/components/FuncList' interface State { isOpen: boolean inputText: string + selectedFunc: string + availableFuncs: string[] } interface Props { @@ -20,71 +23,128 @@ export class FuncSelector extends PureComponent { this.state = { isOpen: false, inputText: '', + selectedFunc: '', + availableFuncs: [], } } public render() { - const {isOpen, inputText} = this.state + const {isOpen, inputText, selectedFunc, availableFuncs} = this.state return ( -
    - - +
    + {isOpen ? ( + + ) : ( + + )}
    ) } - private get openClass(): string { - if (this.state.isOpen) { - return 'open' - } - - return '' + private handleCloseList = () => { + this.setState({isOpen: false, selectedFunc: '', availableFuncs: []}) } private handleAddNode = (name: string) => { - this.setState({isOpen: false}) + this.handleCloseList() this.props.onAddNode(name) } - private get availableFuncs(): string[] { - return this.props.funcs.filter(f => - f.toLowerCase().includes(this.state.inputText) + private setAvailableFuncs = inputText => { + const {funcs} = this.props + const {selectedFunc} = this.state + + const availableFuncs = funcs.filter(f => + f.toLowerCase().includes(inputText) ) + const isSelectedVisible = !!availableFuncs.find(a => a === selectedFunc) + const newSelectedFunc = availableFuncs.length > 0 ? availableFuncs[0] : '' + + this.setState({ + inputText, + availableFuncs, + selectedFunc: isSelectedVisible ? selectedFunc : newSelectedFunc, + }) } private handleInputChange = (e: ChangeEvent) => { - this.setState({inputText: e.target.value}) + this.setAvailableFuncs(e.target.value) } private handleKeyDown = (e: KeyboardEvent) => { - if (e.key !== 'Escape') { - return + const {selectedFunc, availableFuncs} = this.state + const selectedFuncExists = selectedFunc !== '' + + if (e.key === 'Enter' && selectedFuncExists) { + return this.handleAddNode(selectedFunc) } - this.setState({inputText: '', isOpen: false}) + if (e.key === 'Escape' || e.key === 'Tab') { + return this.handleCloseList() + } + + if (e.key === 'ArrowUp' && selectedFuncExists) { + // get index of selectedFunc in availableFuncs + const selectedIndex = _.findIndex( + availableFuncs, + func => func === selectedFunc + ) + const previousIndex = selectedIndex - 1 + // if there is selectedIndex - 1 in availableFuncs make that the new SelectedFunc + if (previousIndex >= 0) { + return this.setState({selectedFunc: availableFuncs[previousIndex]}) + } + // if not then keep selectedFunc as is + } + + if (e.key === 'ArrowDown' && selectedFuncExists) { + // get index of selectedFunc in availableFuncs + const selectedIndex = _.findIndex( + availableFuncs, + func => func === selectedFunc + ) + const nextIndex = selectedIndex + 1 + // if there is selectedIndex + 1 in availableFuncs make that the new SelectedFunc + if (nextIndex < availableFuncs.length) { + return this.setState({selectedFunc: availableFuncs[nextIndex]}) + } + // if not then keep selectedFunc as is + } } - private handleClick = () => { - this.setState({isOpen: !this.state.isOpen}) + private handleSetSelectedFunc = funcName => { + this.setState({selectedFunc: funcName}) + } + + private handleOpenList = () => { + const {funcs} = this.props + this.setState({ + isOpen: true, + inputText: '', + availableFuncs: funcs, + selectedFunc: funcs[0], + }) } private handleClickOutside = () => { - this.setState({isOpen: false}) + this.handleCloseList() } } diff --git a/ui/src/style/components/fancy-scrollbars.scss b/ui/src/style/components/fancy-scrollbars.scss index 11f2c5d2d..a30c0575a 100644 --- a/ui/src/style/components/fancy-scrollbars.scss +++ b/ui/src/style/components/fancy-scrollbars.scss @@ -11,7 +11,9 @@ $scrollbar-color-b: $c-comet; .fancy-scroll--track-h, .fancy-scroll--track-v { - &:hover {cursor: pointer;} + &:hover { + cursor: pointer; + } } /* Horizontal Scrollbar Styles */ .fancy-scroll--track-h { @@ -24,7 +26,7 @@ $scrollbar-color-b: $c-comet; .fancy-scroll--thumb-h { height: $scrollbar-thumb-size !important; border-radius: ($scrollbar-thumb-size / 2); - @include gradient-h($scrollbar-color-a,$scrollbar-color-b); + @include gradient-h($scrollbar-color-a, $scrollbar-color-b); } /* Vertical Scrollbar Styles */ .fancy-scroll--track-v { @@ -37,7 +39,7 @@ $scrollbar-color-b: $c-comet; .fancy-scroll--thumb-v { width: $scrollbar-thumb-size !important; border-radius: ($scrollbar-thumb-size / 2); - @include gradient-v($scrollbar-color-a,$scrollbar-color-b); + @include gradient-v($scrollbar-color-a, $scrollbar-color-b); } /* Kapacitor Theme Scrollbars */ @@ -45,14 +47,35 @@ $scrollbar-color-kap-a: $c-rainforest; $scrollbar-color-kap-b: $c-pool; .fancy-scroll--kapacitor { - .fancy-scroll--thumb-h { @include gradient-h($scrollbar-color-kap-a,$scrollbar-color-kap-b); } - .fancy-scroll--thumb-v { @include gradient-v($scrollbar-color-kap-a,$scrollbar-color-kap-b); } + .fancy-scroll--thumb-h { + @include gradient-h($scrollbar-color-kap-a, $scrollbar-color-kap-b); + } + .fancy-scroll--thumb-v { + @include gradient-v($scrollbar-color-kap-a, $scrollbar-color-kap-b); + } +} + +/* Kapacitor Theme Scrollbars */ +$scrollbar-color-kap-a: $c-rainforest; +$scrollbar-color-kap-b: $c-pool; + +.fancy-scroll--func-selector { + .fancy-scroll--thumb-h { + @include gradient-h($c-neutrino, $c-hydrogen); + } + .fancy-scroll--thumb-v { + @include gradient-v($c-neutrino, $c-hydrogen); + } } /* Dropdown Theme Scrollbars */ ul.dropdown-menu { - .fancy-scroll--thumb-h { @include gradient-h($c-neutrino,$c-laser); } - .fancy-scroll--thumb-v { @include gradient-v($c-neutrino,$c-laser); } + .fancy-scroll--thumb-h { + @include gradient-h($c-neutrino, $c-laser); + } + .fancy-scroll--thumb-v { + @include gradient-v($c-neutrino, $c-laser); + } } /* Hacky Fix to make fancy scrollbars work in Safari */ .query-builder--list {