code refactoring and improve views
[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     ClickAwayListener
15 } from '@material-ui/core';
16 import SearchIcon from '@material-ui/icons/Search';
17 import { ArvadosTheme } from '~/common/custom-theme';
18 import { SearchView } from '~/store/search-bar/search-bar-reducer';
19 import {
20     SearchBarBasicView,
21     SearchBarBasicViewDataProps,
22     SearchBarBasicViewActionProps
23 } from '~/views-components/search-bar/search-bar-basic-view';
24 import {
25     SearchBarAutocompleteView,
26     SearchBarAutocompleteViewDataProps,
27     SearchBarAutocompleteViewActionProps
28 } from '~/views-components/search-bar/search-bar-autocomplete-view';
29 import {
30     SearchBarAdvancedView,
31     SearchBarAdvancedViewDataProps,
32     SearchBarAdvancedViewActionProps
33 } from '~/views-components/search-bar/search-bar-advanced-view';
34
35 type CssRules = 'container' | 'containerSearchViewOpened' | 'input' | 'view';
36
37 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => {
38     return {
39         container: {
40             position: 'relative',
41             width: '100%',
42             borderRadius: theme.spacing.unit / 2
43         },
44         containerSearchViewOpened: {
45             position: 'relative',
46             width: '100%',
47             borderRadius: `${theme.spacing.unit / 2}px ${theme.spacing.unit / 2}px 0 0`
48         },
49         input: {
50             border: 'none',
51             padding: `0px ${theme.spacing.unit}px`
52         },
53         view: {
54             position: 'absolute',
55             width: '100%',
56             zIndex: 1
57         }
58     };
59 };
60
61 export type SearchBarDataProps = SearchBarViewDataProps
62     & SearchBarAutocompleteViewDataProps
63     & SearchBarAdvancedViewDataProps
64     & SearchBarBasicViewDataProps;
65
66 interface SearchBarViewDataProps {
67     currentView: string;
68     isPopoverOpen: boolean;
69     debounce?: number;
70 }
71
72 export type SearchBarActionProps = SearchBarViewActionProps
73     & SearchBarAutocompleteViewActionProps
74     & SearchBarAdvancedViewActionProps
75     & SearchBarBasicViewActionProps;
76
77 interface SearchBarViewActionProps {
78     onSearch: (value: string) => any;
79     searchDataOnEnter: (value: string) => void;
80     onSetView: (currentView: string) => void;
81     closeView: () => void;
82     openSearchView: () => void;
83     saveRecentQuery: (query: string) => void;
84     loadRecentQueries: () => string[];
85 }
86
87 type SearchBarViewProps = SearchBarDataProps & SearchBarActionProps & WithStyles<CssRules>;
88
89 interface SearchBarState {
90     value: string;
91 }
92
93 export const DEFAULT_SEARCH_DEBOUNCE = 1000;
94
95 export const SearchBarView = withStyles(styles)(
96     class extends React.Component<SearchBarViewProps> {
97         state: SearchBarState = {
98             value: ""
99         };
100
101         timeout: number;
102
103         render() {
104             const { classes, currentView, openSearchView, closeView, isPopoverOpen } = this.props;
105             return <ClickAwayListener onClickAway={closeView}>
106                 <Paper className={isPopoverOpen ? classes.containerSearchViewOpened : classes.container} >
107                     <form onSubmit={this.handleSubmit}>
108                         <Input
109                             className={classes.input}
110                             onChange={this.handleChange}
111                             placeholder="Search"
112                             value={this.state.value}
113                             fullWidth={true}
114                             disableUnderline={true}
115                             onClick={openSearchView}
116                             endAdornment={
117                                 <InputAdornment position="end">
118                                     <Tooltip title='Search'>
119                                         <IconButton>
120                                             <SearchIcon />
121                                         </IconButton>
122                                     </Tooltip>
123                                 </InputAdornment>
124                             } />
125                     </form>
126                     <div className={classes.view}>
127                         {isPopoverOpen && this.getView(currentView)}
128                     </div>
129                 </Paper >
130             </ClickAwayListener>;
131         }
132
133         componentDidMount() {
134             this.setState({ value: this.props.searchValue });
135         }
136
137         componentWillReceiveProps(nextProps: SearchBarViewProps) {
138             if (nextProps.searchValue !== this.props.searchValue) {
139                 this.setState({ value: nextProps.searchValue });
140             }
141         }
142
143         componentWillUnmount() {
144             clearTimeout(this.timeout);
145         }
146
147         getView = (currentView: string) => {
148             const { onSetView, loadRecentQueries, savedQueries, deleteSavedQuery, searchValue, 
149                 searchResults, saveQuery, onSearch, navigateTo, editSavedQuery, tags } = this.props;
150             switch (currentView) {
151                 case SearchView.AUTOCOMPLETE:
152                     return <SearchBarAutocompleteView
153                         navigateTo={navigateTo}
154                         searchResults={searchResults}
155                         searchValue={searchValue} />;
156                 case SearchView.ADVANCED:
157                     return <SearchBarAdvancedView
158                         onSetView={onSetView}
159                         saveQuery={saveQuery}
160                         tags={tags} />;
161                 default:
162                     return <SearchBarBasicView
163                         onSetView={onSetView}
164                         onSearch={onSearch}
165                         loadRecentQueries={loadRecentQueries}
166                         savedQueries={savedQueries}
167                         deleteSavedQuery={deleteSavedQuery}
168                         editSavedQuery={editSavedQuery} />;
169             }
170         }
171
172         handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
173             event.preventDefault();
174             clearTimeout(this.timeout);
175             this.props.saveRecentQuery(this.state.value);
176             this.props.searchDataOnEnter(this.state.value);
177             this.props.loadRecentQueries();
178         }
179
180         // ToDo: nie pokazywac autocomplete jezeli jestesmy w advance
181         // currentView ze state.searchBar.currentView
182         handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
183             clearTimeout(this.timeout);
184             this.setState({ value: event.target.value });
185             this.timeout = window.setTimeout(
186                 () => this.props.onSearch(this.state.value),
187                 this.props.debounce || DEFAULT_SEARCH_DEBOUNCE
188             );
189             if (event.target.value.length > 0) {
190                 this.props.onSetView(SearchView.AUTOCOMPLETE);
191             } else {
192                 this.props.onSetView(SearchView.BASIC);
193             }
194         }
195     }
196 );