1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import * as React from 'react';
6 import { compose } from 'redux';
14 InputAdornment, Input,
15 } from '@material-ui/core';
16 import SearchIcon from '@material-ui/icons/Search';
17 import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown';
18 import { ArvadosTheme } from '~/common/custom-theme';
19 import { SearchView } from '~/store/search-bar/search-bar-reducer';
22 SearchBarBasicViewDataProps,
23 SearchBarBasicViewActionProps
24 } from '~/views-components/search-bar/search-bar-basic-view';
26 SearchBarAutocompleteView,
27 SearchBarAutocompleteViewDataProps,
28 SearchBarAutocompleteViewActionProps
29 } from '~/views-components/search-bar/search-bar-autocomplete-view';
31 SearchBarAdvancedView,
32 SearchBarAdvancedViewDataProps,
33 SearchBarAdvancedViewActionProps
34 } from '~/views-components/search-bar/search-bar-advanced-view';
35 import { KEY_CODE_DOWN, KEY_CODE_ESC, KEY_CODE_UP, KEY_ENTER } from "~/common/codes";
36 import { debounce } from 'debounce';
37 import { Vocabulary } from '~/models/vocabulary';
38 import { connectVocabulary } from '../resource-properties-form/property-field-common';
40 type CssRules = 'container' | 'containerSearchViewOpened' | 'input' | 'view';
42 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => {
47 borderRadius: theme.spacing.unit / 2,
48 zIndex: theme.zIndex.modal,
50 containerSearchViewOpened: {
53 borderRadius: `${theme.spacing.unit / 2}px ${theme.spacing.unit / 2}px 0 0`,
54 zIndex: theme.zIndex.modal,
68 export type SearchBarDataProps = SearchBarViewDataProps
69 & SearchBarAutocompleteViewDataProps
70 & SearchBarAdvancedViewDataProps
71 & SearchBarBasicViewDataProps;
73 interface SearchBarViewDataProps {
76 isPopoverOpen: boolean;
78 vocabulary?: Vocabulary;
81 export type SearchBarActionProps = SearchBarViewActionProps
82 & SearchBarAutocompleteViewActionProps
83 & SearchBarAdvancedViewActionProps
84 & SearchBarBasicViewActionProps;
86 interface SearchBarViewActionProps {
87 onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
88 onSubmit: (event: React.FormEvent<HTMLFormElement>) => void;
89 onSetView: (currentView: string) => void;
90 closeView: () => void;
91 openSearchView: () => void;
92 loadRecentQueries: () => string[];
95 setAdvancedDataFromSearchValue: (search: string, vocabulary?: Vocabulary) => void;
98 type SearchBarViewProps = SearchBarDataProps & SearchBarActionProps & WithStyles<CssRules>;
100 const handleKeyDown = (e: React.KeyboardEvent, props: SearchBarViewProps) => {
101 if (e.keyCode === KEY_CODE_DOWN) {
103 if (!props.isPopoverOpen) {
104 props.onSetView(SearchView.AUTOCOMPLETE);
105 props.openSearchView();
109 } else if (e.keyCode === KEY_CODE_UP) {
112 } else if (e.keyCode === KEY_CODE_ESC) {
115 } else if (e.keyCode === KEY_ENTER) {
116 if (props.currentView === SearchView.BASIC) {
118 props.onSearch(props.selectedItem.query);
119 } else if (props.currentView === SearchView.AUTOCOMPLETE) {
120 if (props.selectedItem.id !== props.searchValue) {
122 props.navigateTo(props.selectedItem.id);
128 const handleInputClick = (e: React.MouseEvent, props: SearchBarViewProps) => {
129 if (props.searchValue) {
130 props.onSetView(SearchView.AUTOCOMPLETE);
131 props.openSearchView();
137 const handleDropdownClick = (e: React.MouseEvent, props: SearchBarViewProps) => {
139 if (props.isPopoverOpen && props.currentView === SearchView.ADVANCED) {
142 props.setAdvancedDataFromSearchValue(props.searchValue, props.vocabulary);
143 props.onSetView(SearchView.ADVANCED);
147 export const SearchBarView = compose(connectVocabulary, withStyles(styles))(
148 class extends React.Component<SearchBarViewProps> {
150 debouncedSearch = debounce(() => {
151 this.props.onSearch(this.props.searchValue);
154 handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
155 this.debouncedSearch();
156 this.props.onChange(event);
159 handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
160 this.debouncedSearch.clear();
161 this.props.onSubmit(event);
164 componentWillUnmount() {
165 this.debouncedSearch.clear();
169 const { children, ...props } = this.props;
170 const { classes, isPopoverOpen } = this.props;
175 <Backdrop onClick={props.closeView} />}
177 <Paper className={isPopoverOpen ? classes.containerSearchViewOpened : classes.container} >
178 <form onSubmit={this.handleSubmit}>
180 data-cy='searchbar-input-field'
181 className={classes.input}
182 onChange={this.handleChange}
184 value={props.searchValue}
186 disableUnderline={true}
187 onClick={e => handleInputClick(e, props)}
188 onKeyDown={e => handleKeyDown(e, props)}
190 <InputAdornment position="start">
191 <Tooltip title='Search'>
192 <IconButton type="submit">
199 <InputAdornment position="end">
200 <Tooltip title='Advanced search'>
201 <IconButton onClick={e => handleDropdownClick(e, props)}>
202 <ArrowDropDownIcon />
208 <div className={classes.view}>
209 {isPopoverOpen && getView({ ...props })}
217 const getView = (props: SearchBarViewProps) => {
218 switch (props.currentView) {
219 case SearchView.AUTOCOMPLETE:
220 return <SearchBarAutocompleteView
221 navigateTo={props.navigateTo}
222 searchResults={props.searchResults}
223 searchValue={props.searchValue}
224 selectedItem={props.selectedItem} />;
225 case SearchView.ADVANCED:
226 return <SearchBarAdvancedView
227 closeAdvanceView={props.closeAdvanceView}
229 saveQuery={props.saveQuery} />;
231 return <SearchBarBasicView
232 onSetView={props.onSetView}
233 onSearch={props.onSearch}
234 loadRecentQueries={props.loadRecentQueries}
235 savedQueries={props.savedQueries}
236 deleteSavedQuery={props.deleteSavedQuery}
237 editSavedQuery={props.editSavedQuery}
238 selectedItem={props.selectedItem} />;
242 const Backdrop = withStyles<'backdrop'>(theme => ({
249 zIndex: theme.zIndex.modal
252 ({ classes, ...props }: WithStyles<'backdrop'> & React.HTMLProps<HTMLDivElement>) =>
253 <div className={classes.backdrop} {...props} />);