Merge branch '14502_admin_compute_nodes'
[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 import { KEY_CODE_DOWN, KEY_CODE_ESC, KEY_CODE_UP, KEY_ENTER } from "~/common/codes";
35
36 type CssRules = 'container' | 'containerSearchViewOpened' | 'input' | 'view';
37
38 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => {
39     return {
40         container: {
41             position: 'relative',
42             width: '100%',
43             borderRadius: theme.spacing.unit / 2
44         },
45         containerSearchViewOpened: {
46             position: 'relative',
47             width: '100%',
48             borderRadius: `${theme.spacing.unit / 2}px ${theme.spacing.unit / 2}px 0 0`
49         },
50         input: {
51             border: 'none',
52             padding: `0px ${theme.spacing.unit}px`
53         },
54         view: {
55             position: 'absolute',
56             width: '100%',
57             zIndex: 1
58         }
59     };
60 };
61
62 export type SearchBarDataProps = SearchBarViewDataProps
63     & SearchBarAutocompleteViewDataProps
64     & SearchBarAdvancedViewDataProps
65     & SearchBarBasicViewDataProps;
66
67 interface SearchBarViewDataProps {
68     searchValue: string;
69     currentView: string;
70     isPopoverOpen: boolean;
71     debounce?: number;
72 }
73
74 export type SearchBarActionProps = SearchBarViewActionProps
75     & SearchBarAutocompleteViewActionProps
76     & SearchBarAdvancedViewActionProps
77     & SearchBarBasicViewActionProps;
78
79 interface SearchBarViewActionProps {
80     onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
81     onSubmit: (event: React.FormEvent<HTMLFormElement>) => void;
82     onSetView: (currentView: string) => void;
83     closeView: () => void;
84     openSearchView: () => void;
85     loadRecentQueries: () => string[];
86     moveUp: () => void;
87     moveDown: () => void;
88 }
89
90 type SearchBarViewProps = SearchBarDataProps & SearchBarActionProps & WithStyles<CssRules>;
91
92 const handleKeyDown = (e: React.KeyboardEvent, props: SearchBarViewProps) => {
93     if (e.keyCode === KEY_CODE_DOWN) {
94         e.preventDefault();
95         if (!props.isPopoverOpen) {
96             props.openSearchView();
97         } else {
98             props.moveDown();
99         }
100     } else if (e.keyCode === KEY_CODE_UP) {
101         e.preventDefault();
102         props.moveUp();
103     } else if (e.keyCode === KEY_CODE_ESC) {
104         e.preventDefault();
105         props.closeView();
106     } else if (e.keyCode === KEY_ENTER) {
107         if (props.currentView === SearchView.BASIC) {
108             e.preventDefault();
109             props.onSearch(props.selectedItem.query);
110         } else if (props.currentView === SearchView.AUTOCOMPLETE) {
111             if (props.selectedItem.id !== props.searchValue) {
112                 e.preventDefault();
113                 props.navigateTo(props.selectedItem.id);
114             }
115         }
116     }
117 };
118
119 export const SearchBarView = withStyles(styles)(
120     (props : SearchBarViewProps) => {
121         const { classes, isPopoverOpen } = props;
122         return (
123             <ClickAwayListener onClickAway={props.closeView}>
124                 <Paper className={isPopoverOpen ? classes.containerSearchViewOpened : classes.container} >
125                     <form onSubmit={props.onSubmit}>
126                         <Input
127                             className={classes.input}
128                             onChange={props.onChange}
129                             placeholder="Search"
130                             value={props.searchValue}
131                             fullWidth={true}
132                             disableUnderline={true}
133                             onClick={props.openSearchView}
134                             onKeyDown={e => handleKeyDown(e, props)}
135                             endAdornment={
136                                 <InputAdornment position="end">
137                                     <Tooltip title='Search'>
138                                         <IconButton type="submit">
139                                             <SearchIcon />
140                                         </IconButton>
141                                     </Tooltip>
142                                 </InputAdornment>
143                             } />
144                     </form>
145                     <div className={classes.view}>
146                         {isPopoverOpen && getView({...props})}
147                     </div>
148                 </Paper >
149             </ClickAwayListener>
150         );
151     }
152 );
153
154 const getView = (props: SearchBarViewProps) => {
155     switch (props.currentView) {
156         case SearchView.AUTOCOMPLETE:
157             return <SearchBarAutocompleteView
158                 navigateTo={props.navigateTo}
159                 searchResults={props.searchResults}
160                 searchValue={props.searchValue}
161                 selectedItem={props.selectedItem} />;
162         case SearchView.ADVANCED:
163             return <SearchBarAdvancedView
164                 closeAdvanceView={props.closeAdvanceView}
165                 tags={props.tags} />;
166         default:
167             return <SearchBarBasicView
168                 onSetView={props.onSetView}
169                 onSearch={props.onSearch}
170                 loadRecentQueries={props.loadRecentQueries}
171                 savedQueries={props.savedQueries}
172                 deleteSavedQuery={props.deleteSavedQuery}
173                 editSavedQuery={props.editSavedQuery}
174                 selectedItem={props.selectedItem} />;
175     }
176 };