// SPDX-License-Identifier: AGPL-3.0
import { unionize, ofType, UnionOf } from "~/common/unionize";
+ import { GroupContentsResource, GroupContentsResourcePrefix } from '~/services/groups-service/groups-service';
import { Dispatch } from 'redux';
import { RootState } from '~/store/store';
import { ServiceRepository } from '~/services/services';
+ import { FilterBuilder } from "~/services/api/filter-builder";
+ import { ResourceKind } from '~/models/resource';
+ import { GroupClass } from '~/models/group';
export const searchBarActions = unionize({
SET_CURRENT_VIEW: ofType<string>(),
OPEN_SEARCH_VIEW: ofType<{}>(),
- CLOSE_SEARCH_VIEW: ofType<{}>()
+ CLOSE_SEARCH_VIEW: ofType<{}>(),
+ SET_SEARCH_RESULTS: ofType<GroupContentsResource[]>(),
+ SET_SEARCH_VALUE: ofType<string>()
});
export type SearchBarActions = UnionOf<typeof searchBarActions>;
export const goToView = (currentView: string) => searchBarActions.SET_CURRENT_VIEW(currentView);
-export const searchData = (searchValue: string) =>
+export const saveRecentQuery = (query: string) =>
+ (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+ services.searchQueriesService.saveRecentQuery(query);
+ };
+
+export const loadRecentQueries = () =>
+ (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+ const recentSearchQueries = services.searchQueriesService.getRecentQueries();
+ return recentSearchQueries || [];
- };
++ };
++
++export const searchData = (searchValue: string) =>
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ dispatch(searchBarActions.SET_SEARCH_VALUE(searchValue));
+ dispatch(searchBarActions.SET_SEARCH_RESULTS([]));
+ if (searchValue) {
+ const filters = getFilters('name', searchValue);
+ const { items } = await services.groupsService.contents('', {
- filters,
++ filters,
+ limit: 5,
+ recursive: true
+ });
+ dispatch(searchBarActions.SET_SEARCH_RESULTS(items));
+ }
+ };
+
-
+ const getFilters = (filterName: string, searchValue: string): string => {
+ return new FilterBuilder()
+ .addIsA("uuid", [ResourceKind.PROJECT, ResourceKind.COLLECTION, ResourceKind.PROCESS])
+ .addILike(filterName, searchValue, GroupContentsResourcePrefix.COLLECTION)
+ .addILike(filterName, searchValue, GroupContentsResourcePrefix.PROCESS)
+ .addILike(filterName, searchValue, GroupContentsResourcePrefix.PROJECT)
+ .addEqual('groupClass', GroupClass.PROJECT, GroupContentsResourcePrefix.PROJECT)
+ .getFilters();
-};
++};
import * as React from 'react';
import { Paper, StyleRulesCallback, withStyles, WithStyles, List, Button } from '@material-ui/core';
import { SearchView } from '~/store/search-bar/search-bar-reducer';
- import { RenderRecentQueries } from '~/views-components/search-bar/search-bar-view';
+ import { RecentQueriesItem } from '~/views-components/search-bar/search-bar-view';
-type CssRules = 'list';
+type CssRules = 'list' | 'searchView';
const styles: StyleRulesCallback<CssRules> = theme => {
return {
list: {
padding: '0px'
+ },
+ searchView: {
+ borderRadius: `0 0 ${theme.spacing.unit / 4}px ${theme.spacing.unit / 4}px`
}
};
};
export const SearchBarAdvancedView = withStyles(styles)(
({ classes, setView }: SearchBarAdvancedViewProps & WithStyles<CssRules>) =>
- <Paper>
+ <Paper className={classes.searchView}>
<List component="nav" className={classes.list}>
- <RenderRecentQueries text='ADVANCED VIEW' />
+ <RecentQueriesItem text='ADVANCED VIEW' />
</List>
<Button onClick={() => setView(SearchView.BASIC)}>Back</Button>
</Paper>
// SPDX-License-Identifier: AGPL-3.0
import * as React from 'react';
-import { Paper, StyleRulesCallback, withStyles, WithStyles, List, ListItem, ListItemText } from '@material-ui/core';
-import { ArvadosTheme } from '~/common/custom-theme';
+import { Paper, StyleRulesCallback, withStyles, WithStyles, List } from '@material-ui/core';
- import { RenderRecentQueries } from '~/views-components/search-bar/search-bar-view';
+ import { RecentQueriesItem } from '~/views-components/search-bar/search-bar-view';
+ import { GroupContentsResource } from '~/services/groups-service/groups-service';
+ import Highlighter from "react-highlight-words";
-type CssRules = 'list';
+type CssRules = 'list' | 'searchView';
-const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
- list: {
- padding: 0
- }
-});
+const styles: StyleRulesCallback<CssRules> = theme => {
+ return {
+ list: {
- padding: '0px'
++ padding: 0
+ },
+ searchView: {
+ borderRadius: `0 0 ${theme.spacing.unit / 4}px ${theme.spacing.unit / 4}px`
+ }
+ };
+};
- interface SearchBarAutocompleteViewProps {
+ export interface SearchBarAutocompleteViewDataProps {
+ searchResults?: GroupContentsResource[];
+ searchValue?: string;
}
+ type SearchBarAutocompleteViewProps = SearchBarAutocompleteViewDataProps & WithStyles<CssRules>;
+
export const SearchBarAutocompleteView = withStyles(styles)(
- ({ classes }: SearchBarAutocompleteViewProps & WithStyles<CssRules>) =>
- ({ classes, searchResults, searchValue }: SearchBarAutocompleteViewProps ) =>
- <Paper>
- {searchResults && <List component="nav" className={classes.list}>
++ ({ classes, searchResults, searchValue }: SearchBarAutocompleteViewProps) =>
+ <Paper className={classes.searchView}>
- <List component="nav" className={classes.list}>
- <RenderRecentQueries text='AUTOCOMPLETE VIEW' />
- </List>
++ {searchResults && <List component="nav" className={classes.list}>
+ {searchResults.map((item: GroupContentsResource) => {
+ return <RecentQueriesItem key={item.uuid} text={getFormattedText(item.name, searchValue)} />;
+ })}
+ </List>}
</Paper>
- );
+ );
+
+ const getFormattedText = (textToHighlight: string, searchString = '') => {
+ return <Highlighter searchWords={[searchString]} autoEscape={true} textToHighlight={textToHighlight} />;
+ };
import * as React from 'react';
import { Paper, StyleRulesCallback, withStyles, WithStyles, List } from '@material-ui/core';
import { SearchView } from '~/store/search-bar/search-bar-reducer';
- import { RenderRecentQueries, RenderSavedQueries } from '~/views-components/search-bar/search-bar-view';
+ import { RecentQueriesItem, RenderSavedQueries } from '~/views-components/search-bar/search-bar-view';
type CssRules = 'advanced' | 'searchQueryList' | 'list' | 'searchView';
padding: '0px'
},
searchView: {
- color: theme.palette.common.black
+ color: theme.palette.common.black,
+ borderRadius: `0 0 ${theme.spacing.unit / 4}px ${theme.spacing.unit / 4}px`
}
};
};
interface SearchBarBasicViewProps {
setView: (currentView: string) => void;
+ recentQueries: () => string[];
}
export const SearchBarBasicView = withStyles(styles)(
- ({ classes, setView }: SearchBarBasicViewProps & WithStyles<CssRules>) =>
+ ({ classes, setView, recentQueries }: SearchBarBasicViewProps & WithStyles<CssRules>) =>
<Paper className={classes.searchView}>
<div className={classes.searchQueryList}>Saved search queries</div>
<List component="nav" className={classes.list}>
</List>
<div className={classes.searchQueryList}>Recent search queries</div>
<List component="nav" className={classes.list}>
- {recentQueries().map((query, index) => <RenderRecentQueries key={query + index} text={query} />)}
- <RecentQueriesItem text="cos" />
- <RecentQueriesItem text="testtest" />
++ {recentQueries().map((query, index) => <RecentQueriesItem key={query + index} text={query} />)}
</List>
<div className={classes.advanced} onClick={() => setView(SearchView.ADVANCED)}>Advanced search</div>
</Paper>
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 } from '~/views-components/search-bar/search-bar-autocomplete-view';
+ import { SearchBarAutocompleteView, SearchBarAutocompleteViewDataProps } from '~/views-components/search-bar/search-bar-autocomplete-view';
+ import { ArvadosTheme } from '~/common/custom-theme';
-type CssRules = 'container' | 'input' | 'searchBar';
+type CssRules = 'container' | 'containerSearchViewOpened' | 'input' | 'searchBar';
- const styles: StyleRulesCallback<CssRules> = theme => {
+ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => {
return {
container: {
position: 'relative',
width: '100%',
- borderRadius: '0px'
+ borderRadius: theme.spacing.unit / 4
+ },
+ containerSearchViewOpened: {
+ position: 'relative',
+ width: '100%',
+ borderRadius: `${theme.spacing.unit / 4}px ${theme.spacing.unit / 4}px 0 0`
},
input: {
border: 'none',
};
};
- interface SearchBarDataProps {
- value: string;
+ type SearchBarDataProps = {
+ searchValue: string;
currentView: string;
open: boolean;
- }
+ } & SearchBarAutocompleteViewDataProps;
interface SearchBarActionProps {
onSearch: (value: string) => any;
onSetView: (currentView: string) => void;
openView: () => void;
closeView: () => void;
+ saveQuery: (query: string) => void;
+ loadQueries: () => string[];
}
type SearchBarProps = SearchBarDataProps & SearchBarActionProps & WithStyles<CssRules>;
}
interface RenderQueriesProps {
- text: string;
+ text: string | JSX.Element;
}
- export const RenderRecentQueries = (props: RenderQueriesProps) => {
+ export const RecentQueriesItem = (props: RenderQueriesProps) => {
return <ListItem button>
<ListItemText secondary={props.text} />
</ListItem>;
render() {
const { classes, currentView, openView, closeView, open } = this.props;
return <ClickAwayListener onClickAway={() => closeView()}>
- <Paper className={classes.container} >
+ <Paper className={open ? classes.containerSearchViewOpened : classes.container} >
<form onSubmit={this.handleSubmit} className={classes.searchBar}>
<Input
className={classes.input}
}
componentDidMount() {
- this.setState({ value: this.props.value });
+ this.setState({ value: this.props.searchValue });
}
componentWillReceiveProps(nextProps: SearchBarProps) {
- if (nextProps.value !== this.props.value) {
- this.setState({ value: nextProps.value });
+ if (nextProps.searchValue !== this.props.searchValue) {
+ this.setState({ value: nextProps.searchValue });
}
}
getView = (currentView: string) => {
switch (currentView) {
case SearchView.BASIC:
- return <SearchBarBasicView setView={this.props.onSetView} />;
+ return <SearchBarBasicView setView={this.props.onSetView} recentQueries={this.props.loadQueries} />;
case SearchView.ADVANCED:
return <SearchBarAdvancedView setView={this.props.onSetView} />;
case SearchView.AUTOCOMPLETE:
- return <SearchBarAutocompleteView />;
+ return <SearchBarAutocompleteView
+ searchResults={this.props.searchResults}
+ searchValue={this.props.searchValue} />;
default:
- return <SearchBarBasicView setView={this.props.onSetView} />;
+ return <SearchBarBasicView setView={this.props.onSetView} recentQueries={this.props.loadQueries} />;
}
}
handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
clearTimeout(this.timeout);
+ this.props.saveQuery(this.state.value);
this.props.onSearch(this.state.value);
+ this.props.loadQueries();
}
handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
import { connect } from 'react-redux';
import { RootState } from '~/store/store';
import { Dispatch } from 'redux';
- import { goToView, searchBarActions } from '~/store/search-bar/search-bar-actions';
+ import { goToView, searchData, searchBarActions } from '~/store/search-bar/search-bar-actions';
import { SearchBarView } from '~/views-components/search-bar/search-bar-view';
+import { saveRecentQuery, loadRecentQueries } from '~/store/search-bar/search-bar-actions';
const mapStateToProps = ({ searchBar }: RootState) => {
return {
+ searchValue: searchBar.searchValue,
currentView: searchBar.currentView,
- open: searchBar.open
+ open: searchBar.open,
+ searchResults: searchBar.searchResults
};
};
const mapDispatchToProps = (dispatch: Dispatch) => ({
+ onSearch: (valueSearch: string) => dispatch<any>(searchData(valueSearch)),
onSetView: (currentView: string) => dispatch(goToView(currentView)),
openView: () => dispatch<any>(searchBarActions.OPEN_SEARCH_VIEW()),
- closeView: () => dispatch<any>(searchBarActions.CLOSE_SEARCH_VIEW())
+ closeView: () => dispatch<any>(searchBarActions.CLOSE_SEARCH_VIEW()),
+ saveQuery: (query: string) => dispatch<any>(saveRecentQuery(query)),
+ loadQueries: () => dispatch<any>(loadRecentQueries())
});
export const SearchBar = connect(mapStateToProps, mapDispatchToProps)(SearchBarView);