add react-highlight-word and change autocomplete list
[arvados-workbench2.git] / src / views-components / search-bar / search-bar-view.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import * as React from 'react';
6 import {
7     IconButton,
8     Paper,
9     StyleRulesCallback,
10     withStyles,
11     WithStyles,
12     Tooltip,
13     InputAdornment, Input,
14     ListItem, ListItemText, ListItemSecondaryAction,
15     ClickAwayListener
16 } from '@material-ui/core';
17 import SearchIcon from '@material-ui/icons/Search';
18 import { RemoveIcon } from '~/components/icon/icon';
19 import { SearchView } from '~/store/search-bar/search-bar-reducer';
20 import { SearchBarBasicView } from '~/views-components/search-bar/search-bar-basic-view';
21 import { SearchBarAdvancedView } from '~/views-components/search-bar/search-bar-advanced-view';
22 import { SearchBarAutocompleteView, SearchBarAutocompleteViewDataProps } from '~/views-components/search-bar/search-bar-autocomplete-view';
23 import { ArvadosTheme } from '~/common/custom-theme';
24
25 type CssRules = 'container' | 'input' | 'searchBar';
26
27 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => {
28     return {
29         container: {
30             position: 'relative',
31             width: '100%',
32             borderRadius: '0px'
33         },
34         input: {
35             border: 'none',
36             padding: `0px ${theme.spacing.unit}px`
37         },
38         searchBar: {
39             height: '30px'
40         }
41     };
42 };
43
44 type SearchBarDataProps = {
45     searchValue: string;
46     currentView: string;
47     open: boolean;
48 } & SearchBarAutocompleteViewDataProps;
49
50 interface SearchBarActionProps {
51     onSearch: (value: string) => any;
52     debounce?: number;
53     onSetView: (currentView: string) => void;
54     openView: () => void;
55     closeView: () => void;
56 }
57
58 type SearchBarProps = SearchBarDataProps & SearchBarActionProps & WithStyles<CssRules>;
59
60 interface SearchBarState {
61     value: string;
62 }
63
64 interface RenderQueriesProps {
65     text: string | JSX.Element;
66 }
67
68 export const RecentQueriesItem = (props: RenderQueriesProps) => {
69     return <ListItem button>
70         <ListItemText secondary={props.text} />
71     </ListItem>;
72 };
73
74
75 export const RenderSavedQueries = (props: RenderQueriesProps) => {
76     return <ListItem button>
77         <ListItemText secondary={props.text} />
78         <ListItemSecondaryAction>
79             <Tooltip title="Remove">
80                 <IconButton aria-label="Remove">
81                     <RemoveIcon />
82                 </IconButton>
83             </Tooltip>
84         </ListItemSecondaryAction>
85     </ListItem>;
86 };
87
88 export const DEFAULT_SEARCH_DEBOUNCE = 1000;
89
90 export const SearchBarView = withStyles(styles)(
91     class extends React.Component<SearchBarProps> {
92         state: SearchBarState = {
93             value: ""
94         };
95
96         timeout: number;
97
98         render() {
99             const { classes, currentView, openView, closeView, open } = this.props;
100             return <ClickAwayListener onClickAway={() => closeView()}>
101                 <Paper className={classes.container} >
102                     <form onSubmit={this.handleSubmit} className={classes.searchBar}>
103                         <Input
104                             className={classes.input}
105                             onChange={this.handleChange}
106                             placeholder="Search"
107                             value={this.state.value}
108                             fullWidth={true}
109                             disableUnderline={true}
110                             onClick={() => openView()}
111                             endAdornment={
112                                 <InputAdornment position="end">
113                                     <Tooltip title='Search'>
114                                         <IconButton>
115                                             <SearchIcon />
116                                         </IconButton>
117                                     </Tooltip>
118                                 </InputAdornment>
119                             } />
120                         {open && this.getView(currentView)}
121                     </form>
122                 </Paper >
123             </ClickAwayListener>;
124         }
125
126         componentDidMount() {
127             this.setState({ value: this.props.searchValue });
128         }
129
130         componentWillReceiveProps(nextProps: SearchBarProps) {
131             if (nextProps.searchValue !== this.props.searchValue) {
132                 this.setState({ value: nextProps.searchValue });
133             }
134         }
135
136         componentWillUnmount() {
137             clearTimeout(this.timeout);
138         }
139
140         getView = (currentView: string) => {
141             switch (currentView) {
142                 case SearchView.BASIC:
143                     return <SearchBarBasicView setView={this.props.onSetView} />;
144                 case SearchView.ADVANCED:
145                     return <SearchBarAdvancedView setView={this.props.onSetView} />;
146                 case SearchView.AUTOCOMPLETE:
147                     return <SearchBarAutocompleteView 
148                                 searchResults={this.props.searchResults} 
149                                 searchValue={this.props.searchValue} />;
150                 default:
151                     return <SearchBarBasicView setView={this.props.onSetView} />;
152             }
153         }
154
155         handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
156             event.preventDefault();
157             clearTimeout(this.timeout);
158             this.props.onSearch(this.state.value);
159         }
160
161         handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
162             clearTimeout(this.timeout);
163             this.setState({ value: event.target.value });
164             this.timeout = window.setTimeout(
165                 () => this.props.onSearch(this.state.value),
166                 this.props.debounce || DEFAULT_SEARCH_DEBOUNCE
167             );
168             if (event.target.value.length > 0) {
169                 this.props.onSetView(SearchView.AUTOCOMPLETE);
170             } else {
171                 this.props.onSetView(SearchView.BASIC);
172             }
173         }
174     }
175 );