WithStyles,
Tooltip,
InputAdornment, Input,
- ListItem, ListItemText, ListItemSecondaryAction,
- ClickAwayListener
} from '@material-ui/core';
import SearchIcon from '@material-ui/icons/Search';
-import { RemoveIcon, EditSavedQueryIcon } from '~/components/icon/icon';
-import { SearchView } from '~/store/search-bar/search-bar-reducer';
-import { SearchBarBasicView } from '~/views-components/search-bar/search-bar-basic-view';
-import { SearchBarAdvancedView } from '~/views-components/search-bar/search-bar-advanced-view';
-import { SearchBarAutocompleteView, SearchBarAutocompleteViewDataProps } from '~/views-components/search-bar/search-bar-autocomplete-view';
+import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown';
import { ArvadosTheme } from '~/common/custom-theme';
-import { SearchBarAdvanceFormData } from '~/models/search-bar';
+import { SearchView } from '~/store/search-bar/search-bar-reducer';
+import {
+ SearchBarBasicView,
+ SearchBarBasicViewDataProps,
+ SearchBarBasicViewActionProps
+} from '~/views-components/search-bar/search-bar-basic-view';
+import {
+ SearchBarAutocompleteView,
+ SearchBarAutocompleteViewDataProps,
+ SearchBarAutocompleteViewActionProps
+} from '~/views-components/search-bar/search-bar-autocomplete-view';
+import {
+ SearchBarAdvancedView,
+ SearchBarAdvancedViewDataProps,
+ SearchBarAdvancedViewActionProps
+} from '~/views-components/search-bar/search-bar-advanced-view';
+import { KEY_CODE_DOWN, KEY_CODE_ESC, KEY_CODE_UP, KEY_ENTER } from "~/common/codes";
type CssRules = 'container' | 'containerSearchViewOpened' | 'input' | 'view';
container: {
position: 'relative',
width: '100%',
- borderRadius: theme.spacing.unit / 2
+ borderRadius: theme.spacing.unit / 2,
+ zIndex: theme.zIndex.modal,
},
containerSearchViewOpened: {
position: 'relative',
width: '100%',
- borderRadius: `${theme.spacing.unit / 2}px ${theme.spacing.unit / 2}px 0 0`
+ borderRadius: `${theme.spacing.unit / 2}px ${theme.spacing.unit / 2}px 0 0`,
+ zIndex: theme.zIndex.modal,
},
input: {
border: 'none',
- padding: `0px ${theme.spacing.unit}px`
+ padding: `0`
},
view: {
position: 'absolute',
};
};
-type SearchBarDataProps = {
+export type SearchBarDataProps = SearchBarViewDataProps
+ & SearchBarAutocompleteViewDataProps
+ & SearchBarAdvancedViewDataProps
+ & SearchBarBasicViewDataProps;
+
+interface SearchBarViewDataProps {
searchValue: string;
currentView: string;
isPopoverOpen: boolean;
- savedQueries: SearchBarAdvanceFormData[];
-} & SearchBarAutocompleteViewDataProps;
-
-interface SearchBarActionProps {
- onSearch: (value: string) => any;
debounce?: number;
- onSetView: (currentView: string) => void;
- closeView: () => void;
- saveRecentQuery: (query: string) => void;
- loadRecentQueries: () => string[];
- saveQuery: (data: SearchBarAdvanceFormData) => void;
- deleteSavedQuery: (id: number) => void;
- openSearchView: () => void;
- navigateTo: (uuid: string) => void;
- editSavedQuery: (data: SearchBarAdvanceFormData, id: number) => void;
}
-type SearchBarProps = SearchBarDataProps & SearchBarActionProps & WithStyles<CssRules>;
+export type SearchBarActionProps = SearchBarViewActionProps
+ & SearchBarAutocompleteViewActionProps
+ & SearchBarAdvancedViewActionProps
+ & SearchBarBasicViewActionProps;
-interface SearchBarState {
- value: string;
+interface SearchBarViewActionProps {
+ onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
+ onSubmit: (event: React.FormEvent<HTMLFormElement>) => void;
+ onSetView: (currentView: string) => void;
+ closeView: () => void;
+ openSearchView: () => void;
+ loadRecentQueries: () => string[];
+ moveUp: () => void;
+ moveDown: () => void;
+ setAdvancedDataFromSearchValue: (search: string) => void;
}
-interface RenderRecentQueriesProps {
- text: string;
- onSearch: (searchValue: string) => void;
-}
+type SearchBarViewProps = SearchBarDataProps & SearchBarActionProps & WithStyles<CssRules>;
-export const RenderRecentQueries = (props: RenderRecentQueriesProps) => {
- return <ListItem button>
- <ListItemText secondary={props.text} onClick={() => props.onSearch(props.text)} />
- </ListItem>;
+const handleKeyDown = (e: React.KeyboardEvent, props: SearchBarViewProps) => {
+ if (e.keyCode === KEY_CODE_DOWN) {
+ e.preventDefault();
+ if (!props.isPopoverOpen) {
+ props.onSetView(SearchView.AUTOCOMPLETE);
+ props.openSearchView();
+ } else {
+ props.moveDown();
+ }
+ } else if (e.keyCode === KEY_CODE_UP) {
+ e.preventDefault();
+ props.moveUp();
+ } else if (e.keyCode === KEY_CODE_ESC) {
+ e.preventDefault();
+ props.closeView();
+ } else if (e.keyCode === KEY_ENTER) {
+ if (props.currentView === SearchView.BASIC) {
+ e.preventDefault();
+ props.onSearch(props.selectedItem.query);
+ } else if (props.currentView === SearchView.AUTOCOMPLETE) {
+ if (props.selectedItem.id !== props.searchValue) {
+ e.preventDefault();
+ props.navigateTo(props.selectedItem.id);
+ }
+ }
+ }
};
-interface RenderAutocompleteItemsProps {
- text: string | JSX.Element;
- navigateTo: (uuid: string) => void;
- uuid: string;
-}
-
-export const RenderAutocompleteItems = (props: RenderAutocompleteItemsProps) => {
- return <ListItem button>
- <ListItemText secondary={props.text} onClick={() => props.navigateTo(props.uuid)} />
- </ListItem>;
+const handleInputClick = (e: React.MouseEvent, props: SearchBarViewProps) => {
+ if (props.searchValue) {
+ props.onSetView(SearchView.AUTOCOMPLETE);
+ props.openSearchView();
+ } else {
+ props.closeView();
+ }
};
-interface RenderSavedQueriesProps {
- text: string;
- id: number;
- deleteSavedQuery: (id: number) => void;
- onSearch: (searchValue: string) => void;
- editSavedQuery: (data: SearchBarAdvanceFormData, id: number) => void;
- data: SearchBarAdvanceFormData;
-}
-
-export const RenderSavedQueries = (props: RenderSavedQueriesProps) => {
- return <ListItem button>
- <ListItemText secondary={props.text} onClick={() => props.onSearch(props.text)} />
- <ListItemSecondaryAction>
- <Tooltip title="Edit">
- <IconButton aria-label="Edit" onClick={() => props.editSavedQuery(props.data, props.id)}>
- <EditSavedQueryIcon />
- </IconButton>
- </Tooltip>
- <Tooltip title="Remove">
- <IconButton aria-label="Remove" onClick={() => props.deleteSavedQuery(props.id)}>
- <RemoveIcon />
- </IconButton>
- </Tooltip>
- </ListItemSecondaryAction>
- </ListItem>;
+const handleDropdownClick = (e: React.MouseEvent, props: SearchBarViewProps) => {
+ e.stopPropagation();
+ if (props.isPopoverOpen) {
+ if (props.currentView === SearchView.ADVANCED) {
+ props.closeView();
+ } else {
+ props.setAdvancedDataFromSearchValue(props.searchValue);
+ props.onSetView(SearchView.ADVANCED);
+ }
+ } else {
+ props.setAdvancedDataFromSearchValue(props.searchValue);
+ props.onSetView(SearchView.ADVANCED);
+ }
};
-export const DEFAULT_SEARCH_DEBOUNCE = 1000;
-
export const SearchBarView = withStyles(styles)(
- class extends React.Component<SearchBarProps> {
- state: SearchBarState = {
- value: ""
- };
+ (props: SearchBarViewProps) => {
+ const { classes, isPopoverOpen } = props;
+ return (
+ <>
- timeout: number;
+ {isPopoverOpen &&
+ <Backdrop onClick={props.closeView} />}
- render() {
- const { classes, currentView, openSearchView, closeView, isPopoverOpen } = this.props;
- return <ClickAwayListener onClickAway={closeView}>
<Paper className={isPopoverOpen ? classes.containerSearchViewOpened : classes.container} >
- <form onSubmit={this.handleSubmit}>
+ <form onSubmit={props.onSubmit}>
<Input
className={classes.input}
- onChange={this.handleChange}
+ onChange={props.onChange}
placeholder="Search"
- value={this.state.value}
+ value={props.searchValue}
fullWidth={true}
disableUnderline={true}
- onClick={openSearchView}
- endAdornment={
- <InputAdornment position="end">
+ onClick={e => handleInputClick(e, props)}
+ onKeyDown={e => handleKeyDown(e, props)}
+ startAdornment={
+ <InputAdornment position="start">
<Tooltip title='Search'>
- <IconButton>
+ <IconButton type="submit">
<SearchIcon />
</IconButton>
</Tooltip>
</InputAdornment>
+ }
+ endAdornment={
+ <InputAdornment position="end">
+ <Tooltip title='Advanced search'>
+ <IconButton onClick={e => handleDropdownClick(e, props)}>
+ <ArrowDropDownIcon />
+ </IconButton>
+ </Tooltip>
+ </InputAdornment>
} />
</form>
<div className={classes.view}>
- {isPopoverOpen && this.getView(currentView)}
+ {isPopoverOpen && getView({ ...props })}
</div>
</Paper >
- </ClickAwayListener>;
- }
-
- componentDidMount() {
- this.setState({ value: this.props.searchValue });
- }
-
- componentWillReceiveProps(nextProps: SearchBarProps) {
- if (nextProps.searchValue !== this.props.searchValue) {
- this.setState({ value: nextProps.searchValue });
- }
- }
-
- componentWillUnmount() {
- clearTimeout(this.timeout);
- }
-
- getView = (currentView: string) => {
- const { onSetView, loadRecentQueries, savedQueries, deleteSavedQuery, searchValue, searchResults, saveQuery, onSearch, navigateTo, editSavedQuery } = this.props;
- switch (currentView) {
- case SearchView.BASIC:
- return <SearchBarBasicView setView={onSetView} recentQueries={loadRecentQueries} savedQueries={savedQueries} deleteSavedQuery={deleteSavedQuery} onSearch={onSearch} editSavedQuery={editSavedQuery} />;
- case SearchView.ADVANCED:
- return <SearchBarAdvancedView setView={onSetView} saveQuery={saveQuery} />;
- case SearchView.AUTOCOMPLETE:
- return <SearchBarAutocompleteView
- navigateTo={navigateTo}
- searchResults={searchResults}
- searchValue={searchValue} />;
- default:
- return <SearchBarBasicView setView={onSetView} recentQueries={loadRecentQueries} savedQueries={savedQueries} deleteSavedQuery={deleteSavedQuery} onSearch={onSearch} editSavedQuery={editSavedQuery} />;
- }
- }
+ </>
+ );
+ }
+);
- handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
- event.preventDefault();
- clearTimeout(this.timeout);
- this.props.saveRecentQuery(this.state.value);
- this.props.onSearch(this.state.value);
- this.props.loadRecentQueries();
- }
+const getView = (props: SearchBarViewProps) => {
+ switch (props.currentView) {
+ case SearchView.AUTOCOMPLETE:
+ return <SearchBarAutocompleteView
+ navigateTo={props.navigateTo}
+ searchResults={props.searchResults}
+ searchValue={props.searchValue}
+ selectedItem={props.selectedItem} />;
+ case SearchView.ADVANCED:
+ return <SearchBarAdvancedView
+ closeAdvanceView={props.closeAdvanceView}
+ tags={props.tags}
+ saveQuery={props.saveQuery} />;
+ default:
+ return <SearchBarBasicView
+ onSetView={props.onSetView}
+ onSearch={props.onSearch}
+ loadRecentQueries={props.loadRecentQueries}
+ savedQueries={props.savedQueries}
+ deleteSavedQuery={props.deleteSavedQuery}
+ editSavedQuery={props.editSavedQuery}
+ selectedItem={props.selectedItem} />;
+ }
+};
- handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
- clearTimeout(this.timeout);
- this.setState({ value: event.target.value });
- this.timeout = window.setTimeout(
- () => this.props.onSearch(this.state.value),
- this.props.debounce || DEFAULT_SEARCH_DEBOUNCE
- );
- if (event.target.value.length > 0) {
- this.props.onSetView(SearchView.AUTOCOMPLETE);
- } else {
- this.props.onSetView(SearchView.BASIC);
- }
- }
+const Backdrop = withStyles<'backdrop'>(theme => ({
+ backdrop: {
+ position: 'fixed',
+ top: 0,
+ right: 0,
+ bottom: 0,
+ left: 0,
+ zIndex: theme.zIndex.modal
}
-);
+}))(
+ ({ classes, ...props }: WithStyles<'backdrop'> & React.HTMLProps<HTMLDivElement>) =>
+ <div className={classes.backdrop} {...props} />);