X-Git-Url: https://git.arvados.org/arvados-workbench2.git/blobdiff_plain/a94211e27f3eebaea55ccba096869f70161e74ad..e31bd3f0af6e0b3d4166af144ef8aed5d110b5af:/src/views-components/search-bar/search-bar-view.tsx diff --git a/src/views-components/search-bar/search-bar-view.tsx b/src/views-components/search-bar/search-bar-view.tsx index d5d885ea..28408347 100644 --- a/src/views-components/search-bar/search-bar-view.tsx +++ b/src/views-components/search-bar/search-bar-view.tsx @@ -2,7 +2,8 @@ // // SPDX-License-Identifier: AGPL-3.0 -import * as React from 'react'; +import React from 'react'; +import { compose } from 'redux'; import { IconButton, Paper, @@ -11,26 +12,30 @@ import { WithStyles, Tooltip, InputAdornment, Input, - ClickAwayListener } from '@material-ui/core'; import SearchIcon from '@material-ui/icons/Search'; -import { ArvadosTheme } from '~/common/custom-theme'; -import { SearchView } from '~/store/search-bar/search-bar-reducer'; +import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown'; +import { ArvadosTheme } from 'common/custom-theme'; +import { SearchView } from 'store/search-bar/search-bar-reducer'; import { SearchBarBasicView, SearchBarBasicViewDataProps, SearchBarBasicViewActionProps -} from '~/views-components/search-bar/search-bar-basic-view'; +} from 'views-components/search-bar/search-bar-basic-view'; import { SearchBarAutocompleteView, SearchBarAutocompleteViewDataProps, SearchBarAutocompleteViewActionProps -} from '~/views-components/search-bar/search-bar-autocomplete-view'; +} from 'views-components/search-bar/search-bar-autocomplete-view'; import { SearchBarAdvancedView, SearchBarAdvancedViewDataProps, SearchBarAdvancedViewActionProps -} from '~/views-components/search-bar/search-bar-advanced-view'; +} from 'views-components/search-bar/search-bar-advanced-view'; +import { KEY_CODE_DOWN, KEY_CODE_ESC, KEY_CODE_UP, KEY_ENTER } from "common/codes"; +import { debounce } from 'debounce'; +import { Vocabulary } from 'models/vocabulary'; +import { connectVocabulary } from '../resource-properties-form/property-field-common'; type CssRules = 'container' | 'containerSearchViewOpened' | 'input' | 'view'; @@ -39,16 +44,18 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => { container: { position: 'relative', width: '100%', - borderRadius: theme.spacing.unit / 2 + borderRadius: theme.spacing.unit / 2, + zIndex: theme.zIndex.modal, }, containerSearchViewOpened: { position: 'relative', width: '100%', - borderRadius: `${theme.spacing.unit / 2}px ${theme.spacing.unit / 2}px 0 0` + borderRadius: `${theme.spacing.unit / 2}px ${theme.spacing.unit / 2}px 0 0`, + zIndex: theme.zIndex.modal, }, input: { border: 'none', - padding: `0px ${theme.spacing.unit}px` + padding: `0` }, view: { position: 'absolute', @@ -64,9 +71,11 @@ export type SearchBarDataProps = SearchBarViewDataProps & SearchBarBasicViewDataProps; interface SearchBarViewDataProps { + searchValue: string; currentView: string; isPopoverOpen: boolean; debounce?: number; + vocabulary?: Vocabulary; } export type SearchBarActionProps = SearchBarViewActionProps @@ -75,122 +84,170 @@ export type SearchBarActionProps = SearchBarViewActionProps & SearchBarBasicViewActionProps; interface SearchBarViewActionProps { - onSearch: (value: string) => any; - searchDataOnEnter: (value: string) => void; + onChange: (event: React.ChangeEvent) => void; + onSubmit: (event: React.FormEvent) => void; onSetView: (currentView: string) => void; closeView: () => void; openSearchView: () => void; - saveRecentQuery: (query: string) => void; loadRecentQueries: () => string[]; + moveUp: () => void; + moveDown: () => void; + setAdvancedDataFromSearchValue: (search: string, vocabulary?: Vocabulary) => void; } type SearchBarViewProps = SearchBarDataProps & SearchBarActionProps & WithStyles; -interface SearchBarState { - value: string; -} +const handleKeyDown = (e: React.KeyboardEvent, props: SearchBarViewProps) => { + if (e.keyCode === KEY_CODE_DOWN) { + e.preventDefault(); + if (!props.isPopoverOpen) { + props.onSetView(SearchView.AUTOCOMPLETE); + props.openSearchView(); + } else { + props.moveDown(); + } + } else if (e.keyCode === KEY_CODE_UP) { + e.preventDefault(); + props.moveUp(); + } else if (e.keyCode === KEY_CODE_ESC) { + e.preventDefault(); + props.closeView(); + } else if (e.keyCode === KEY_ENTER) { + if (props.currentView === SearchView.BASIC) { + e.preventDefault(); + props.onSearch(props.selectedItem.query); + } else if (props.currentView === SearchView.AUTOCOMPLETE) { + if (props.selectedItem.id !== props.searchValue) { + e.preventDefault(); + props.navigateTo(props.selectedItem.id); + } + } + } +}; -export const DEFAULT_SEARCH_DEBOUNCE = 1000; +const handleInputClick = (e: React.MouseEvent, props: SearchBarViewProps) => { + if (props.searchValue) { + props.onSetView(SearchView.AUTOCOMPLETE); + } else { + props.onSetView(SearchView.BASIC); + } + props.openSearchView(); +}; -export const SearchBarView = withStyles(styles)( +const handleDropdownClick = (e: React.MouseEvent, props: SearchBarViewProps) => { + e.stopPropagation(); + if (props.isPopoverOpen && props.currentView === SearchView.ADVANCED) { + props.closeView(); + } else { + props.setAdvancedDataFromSearchValue(props.searchValue, props.vocabulary); + props.onSetView(SearchView.ADVANCED); + } +}; + +export const SearchBarView = compose(connectVocabulary, withStyles(styles))( class extends React.Component { - state: SearchBarState = { - value: "" - }; - timeout: number; + debouncedSearch = debounce(() => { + this.props.onSearch(this.props.searchValue); + }, 1000); - render() { - const { classes, currentView, openSearchView, closeView, isPopoverOpen } = this.props; - return - -
- - - - - - - - } /> -
-
- {isPopoverOpen && this.getView(currentView)} -
-
-
; - } - - componentDidMount() { - this.setState({ value: this.props.searchValue }); + handleChange = (event: React.ChangeEvent) => { + this.debouncedSearch(); + this.props.onChange(event); } - componentWillReceiveProps(nextProps: SearchBarViewProps) { - if (nextProps.searchValue !== this.props.searchValue) { - this.setState({ value: nextProps.searchValue }); - } + handleSubmit = (event: React.FormEvent) => { + this.debouncedSearch.clear(); + this.props.onSubmit(event); } componentWillUnmount() { - clearTimeout(this.timeout); - } - - getView = (currentView: string) => { - const { onSetView, loadRecentQueries, savedQueries, deleteSavedQuery, searchValue, - searchResults, saveQuery, onSearch, navigateTo, editSavedQuery, tags } = this.props; - switch (currentView) { - case SearchView.AUTOCOMPLETE: - return ; - case SearchView.ADVANCED: - return ; - default: - return ; - } - } - - handleSubmit = (event: React.FormEvent) => { - event.preventDefault(); - clearTimeout(this.timeout); - this.props.saveRecentQuery(this.state.value); - this.props.searchDataOnEnter(this.state.value); - this.props.loadRecentQueries(); + this.debouncedSearch.clear(); } - // ToDo: nie pokazywac autocomplete jezeli jestesmy w advance - // currentView ze state.searchBar.currentView - handleChange = (event: React.ChangeEvent) => { - clearTimeout(this.timeout); - this.setState({ value: event.target.value }); - this.timeout = window.setTimeout( - () => this.props.onSearch(this.state.value), - this.props.debounce || DEFAULT_SEARCH_DEBOUNCE + render() { + const { children, ...props } = this.props; + const { classes, isPopoverOpen } = this.props; + return ( + <> + + {isPopoverOpen && + } + + +
+ handleInputClick(e, props)} + onKeyDown={e => handleKeyDown(e, props)} + startAdornment={ + + + + + + + + } + endAdornment={ + + + handleDropdownClick(e, props)}> + + + + + } /> +
+
+ {isPopoverOpen && getView({ ...props })} +
+
+ ); - if (event.target.value.length > 0) { - this.props.onSetView(SearchView.AUTOCOMPLETE); - } else { - this.props.onSetView(SearchView.BASIC); - } } + }); + +const getView = (props: SearchBarViewProps) => { + switch (props.currentView) { + case SearchView.AUTOCOMPLETE: + return ; + case SearchView.ADVANCED: + return ; + default: + return ; + } +}; + +const Backdrop = withStyles<'backdrop'>(theme => ({ + backdrop: { + position: 'fixed', + top: 0, + right: 0, + bottom: 0, + left: 0, + zIndex: theme.zIndex.modal } -); +}))( + ({ classes, ...props }: WithStyles<'backdrop'> & React.HTMLProps) => +
);