saving-in-store-for-dynamic-deleting
[arvados.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' | 'containerSearchViewOpened' | 'input' | 'searchBar' | 'view';
26
27 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => {
28     return {
29         container: {
30             position: 'relative',
31             width: '100%',
32             borderRadius: theme.spacing.unit / 4
33         },
34         containerSearchViewOpened: {
35             position: 'relative',
36             width: '100%',
37             borderRadius: `${theme.spacing.unit / 4}px ${theme.spacing.unit / 4}px 0 0`
38         },
39         input: {
40             border: 'none',
41             padding: `0px ${theme.spacing.unit}px`
42         },
43         searchBar: {
44             height: '30px'
45         },
46         view: {
47             position: 'absolute',
48             width: '100%',
49             zIndex: 10000
50         }
51     };
52 };
53
54 type SearchBarDataProps = {
55     searchValue: string;
56     currentView: string;
57     isPopoverOpen: boolean;
58     savedQueries: string[];
59 } & SearchBarAutocompleteViewDataProps;
60
61 interface SearchBarActionProps {
62     onSearch: (value: string) => any;
63     debounce?: number;
64     onSetView: (currentView: string) => void;
65     openView: () => void;
66     closeView: () => void;
67     saveRecentQuery: (query: string) => void;
68     loadRecentQueries: () => string[];
69     saveQuery: (query: string) => void;
70     deleteSavedQuery: (id: number) => void;
71 }
72
73 type SearchBarProps = SearchBarDataProps & SearchBarActionProps & WithStyles<CssRules>;
74
75 interface SearchBarState {
76     value: string;
77 }
78
79 interface RenderSavedQueriesProps {
80     text: string | JSX.Element;
81     id: number;
82     deleteSavedQuery: (id: number) => void;
83 }
84
85 interface RenderRecentQueriesProps {
86     text: string | JSX.Element;
87 }
88
89 export const RecentQueriesItem = (props: RenderRecentQueriesProps) => {
90     return <ListItem button>
91         <ListItemText secondary={props.text} />
92     </ListItem>;
93 };
94
95
96 export const RenderSavedQueries = (props: RenderSavedQueriesProps) => {
97     return <ListItem button>
98         <ListItemText secondary={props.text} />
99         <ListItemSecondaryAction>
100             <Tooltip title="Remove">
101                 <IconButton aria-label="Remove" onClick={() => props.deleteSavedQuery(props.id)}>
102                     <RemoveIcon />
103                 </IconButton>
104             </Tooltip>
105         </ListItemSecondaryAction>
106     </ListItem>;
107 };
108
109 export const DEFAULT_SEARCH_DEBOUNCE = 1000;
110
111 export const SearchBarView = withStyles(styles)(
112     class extends React.Component<SearchBarProps> {
113         state: SearchBarState = {
114             value: ""
115         };
116
117         timeout: number;
118
119         render() {
120             const { classes, currentView, openView, closeView, isPopoverOpen } = this.props;
121             return <ClickAwayListener onClickAway={() => closeView()}>
122                 <Paper className={isPopoverOpen ? classes.containerSearchViewOpened : classes.container} >
123                     <form onSubmit={this.handleSubmit} className={classes.searchBar}>
124                         <Input
125                             className={classes.input}
126                             onChange={this.handleChange}
127                             placeholder="Search"
128                             value={this.state.value}
129                             fullWidth={true}
130                             disableUnderline={true}
131                             onClick={() => openView()}
132                             endAdornment={
133                                 <InputAdornment position="end">
134                                     <Tooltip title='Search'>
135                                         <IconButton>
136                                             <SearchIcon />
137                                         </IconButton>
138                                     </Tooltip>
139                                 </InputAdornment>
140                             } />
141                     </form>
142                     <div className={classes.view}>
143                         {isPopoverOpen && this.getView(currentView)}
144                     </div>
145                 </Paper >
146             </ClickAwayListener>;
147         }
148
149         componentDidMount() {
150             this.setState({ value: this.props.searchValue });
151         }
152
153         componentWillReceiveProps(nextProps: SearchBarProps) {
154             if (nextProps.searchValue !== this.props.searchValue) {
155                 this.setState({ value: nextProps.searchValue });
156             }
157         }
158
159         componentWillUnmount() {
160             clearTimeout(this.timeout);
161         }
162
163         getView = (currentView: string) => {
164             const { onSetView, loadRecentQueries, savedQueries, deleteSavedQuery, searchValue, searchResults } = this.props;
165             switch (currentView) {
166                 case SearchView.BASIC:
167                     return <SearchBarBasicView setView={onSetView} recentQueries={loadRecentQueries} savedQueries={savedQueries} deleteSavedQuery={deleteSavedQuery} />;
168                 case SearchView.ADVANCED:
169                     return <SearchBarAdvancedView setView={onSetView} />;
170                 case SearchView.AUTOCOMPLETE:
171                     return <SearchBarAutocompleteView
172                         searchResults={searchResults}
173                         searchValue={searchValue} />;
174                 default:
175                     return <SearchBarBasicView setView={onSetView} recentQueries={loadRecentQueries} savedQueries={savedQueries} deleteSavedQuery={deleteSavedQuery} />;
176             }
177         }
178
179         handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
180             event.preventDefault();
181             clearTimeout(this.timeout);
182             this.props.saveRecentQuery(this.state.value);
183             this.props.saveQuery(this.state.value);
184             this.props.onSearch(this.state.value);
185             this.props.loadRecentQueries();
186         }
187
188         handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
189             clearTimeout(this.timeout);
190             this.setState({ value: event.target.value });
191             this.timeout = window.setTimeout(
192                 () => this.props.onSearch(this.state.value),
193                 this.props.debounce || DEFAULT_SEARCH_DEBOUNCE
194             );
195             if (event.target.value.length > 0) {
196                 this.props.onSetView(SearchView.AUTOCOMPLETE);
197             } else {
198                 this.props.onSetView(SearchView.BASIC);
199             }
200         }
201     }
202 );