[ARVADOS-WORKBENCH2] created: 1.2.0-747-g4ad549e
Git user
git at public.curoverse.com
Mon Oct 29 19:11:37 EDT 2018
at 4ad549e2cebe285c9a854e466f3e0ddac10b808d (commit)
commit 4ad549e2cebe285c9a854e466f3e0ddac10b808d
Author: Daniel Kos <daniel.kos at contractors.roche.com>
Date: Tue Oct 30 00:11:28 2018 +0100
Add arrow navigation in autocomplete view
Feature #14364
Arvados-DCO-1.1-Signed-off-by: Daniel Kos <daniel.kos at contractors.roche.com>
diff --git a/src/common/codes.ts b/src/common/codes.ts
new file mode 100644
index 0000000..6342a29
--- /dev/null
+++ b/src/common/codes.ts
@@ -0,0 +1,8 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+export const KEY_CODE_UP = 38;
+export const KEY_CODE_DOWN = 40;
+export const KEY_CODE_ESC = 27;
+export const KEY_ENTER = 13;
diff --git a/src/services/search-service/search-service.ts b/src/services/search-service/search-service.ts
index 5817275..bcc42bd 100644
--- a/src/services/search-service/search-service.ts
+++ b/src/services/search-service/search-service.ts
@@ -11,10 +11,8 @@ export class SearchService {
saveRecentQuery(query: string) {
if (this.recentQueries.length >= MAX_NUMBER_OF_RECENT_QUERIES) {
this.recentQueries.shift();
- this.recentQueries.push(query);
- } else {
- this.recentQueries.push(query);
}
+ this.recentQueries.push(query);
localStorage.setItem('recentQueries', JSON.stringify(this.recentQueries));
}
@@ -43,4 +41,4 @@ export class SearchService {
}
}
-const MAX_NUMBER_OF_RECENT_QUERIES = 5;
\ No newline at end of file
+const MAX_NUMBER_OF_RECENT_QUERIES = 5;
diff --git a/src/store/search-bar/search-bar-actions.ts b/src/store/search-bar/search-bar-actions.ts
index 653a8f5..f953a57 100644
--- a/src/store/search-bar/search-bar-actions.ts
+++ b/src/store/search-bar/search-bar-actions.ts
@@ -26,7 +26,10 @@ export const searchBarActions = unionize({
SET_SEARCH_RESULTS: ofType<GroupContentsResource[]>(),
SET_SEARCH_VALUE: ofType<string>(),
SET_SAVED_QUERIES: ofType<SearchBarAdvanceFormData[]>(),
- UPDATE_SAVED_QUERY: ofType<SearchBarAdvanceFormData[]>()
+ UPDATE_SAVED_QUERY: ofType<SearchBarAdvanceFormData[]>(),
+ SET_SELECTED_ITEM: ofType<string>(),
+ MOVE_UP: ofType<{}>(),
+ MOVE_DOWN: ofType<{}>()
});
export type SearchBarActions = UnionOf<typeof searchBarActions>;
@@ -51,23 +54,22 @@ export const loadRecentQueries = () =>
};
export const searchData = (searchValue: string) =>
- async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ async (dispatch: Dispatch, getState: () => RootState) => {
const currentView = getState().searchBar.currentView;
dispatch(searchBarActions.SET_SEARCH_VALUE(searchValue));
- dispatch(searchBarActions.SET_SEARCH_RESULTS([]));
- dispatch<any>(searchGroups(searchValue));
- if (currentView === SearchView.BASIC) {
- dispatch(searchBarActions.CLOSE_SEARCH_VIEW());
- dispatch(navigateToSearchResults);
+ // dispatch(searchBarActions.SET_SEARCH_RESULTS([]));
+ if (searchValue.length > 0) {
+ dispatch<any>(searchGroups(searchValue));
+ if (currentView === SearchView.BASIC) {
+ dispatch(searchBarActions.CLOSE_SEARCH_VIEW());
+ dispatch(navigateToSearchResults);
+ }
}
-
};
export const searchAdvanceData = (data: SearchBarAdvanceFormData) =>
- async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- const searchValue = getState().searchBar.searchValue;
+ async (dispatch: Dispatch) => {
dispatch<any>(saveQuery(data));
- dispatch<any>(searchGroups(searchValue, 100, data.type));
dispatch(searchBarActions.SET_CURRENT_VIEW(SearchView.BASIC));
dispatch(searchBarActions.CLOSE_SEARCH_VIEW());
dispatch(navigateToSearchResults);
@@ -82,11 +84,11 @@ const saveQuery = (data: SearchBarAdvanceFormData) =>
if (filteredQuery) {
services.searchService.editSavedQueries(data);
dispatch(searchBarActions.UPDATE_SAVED_QUERY(savedSearchQueries));
- dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Query has been sucessfully updated', hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Query has been successfully updated', hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
} else {
services.searchService.saveQuery(data);
dispatch(searchBarActions.SET_SAVED_QUERIES(savedSearchQueries));
- dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Query has been sucessfully saved', hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Query has been successfully saved', hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
}
}
};
@@ -100,7 +102,7 @@ export const deleteSavedQuery = (id: number) =>
};
export const editSavedQuery = (data: SearchBarAdvanceFormData) =>
- (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+ (dispatch: Dispatch<any>) => {
dispatch(searchBarActions.SET_CURRENT_VIEW(SearchView.ADVANCED));
dispatch(searchBarActions.SET_SEARCH_VALUE(data.searchQuery));
dispatch<any>(initialize(SEARCH_BAR_ADVANCE_FORM_NAME, data));
@@ -114,7 +116,7 @@ export const openSearchView = () =>
};
export const closeSearchView = () =>
- (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+ (dispatch: Dispatch<any>, getState: () => RootState) => {
const isOpen = getState().searchBar.open;
if (isOpen) {
dispatch(searchBarActions.CLOSE_SEARCH_VIEW());
@@ -122,20 +124,20 @@ export const closeSearchView = () =>
}
};
-export const closeAdvanceView = () =>
- (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+export const closeAdvanceView = () =>
+ (dispatch: Dispatch<any>) => {
dispatch(searchBarActions.SET_SEARCH_VALUE(''));
dispatch(searchBarActions.SET_CURRENT_VIEW(SearchView.BASIC));
};
export const navigateToItem = (uuid: string) =>
- (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+ (dispatch: Dispatch<any>) => {
dispatch(searchBarActions.CLOSE_SEARCH_VIEW());
dispatch(navigateTo(uuid));
};
-export const changeData = (searchValue: string) =>
- (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+export const changeData = (searchValue: string) =>
+ (dispatch: Dispatch, getState: () => RootState) => {
dispatch(searchBarActions.SET_SEARCH_VALUE(searchValue));
const currentView = getState().searchBar.currentView;
const searchValuePresent = searchValue.length > 0;
@@ -153,35 +155,29 @@ export const changeData = (searchValue: string) =>
};
export const submitData = (event: React.FormEvent<HTMLFormElement>) =>
- (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ (dispatch: Dispatch, getState: () => RootState) => {
event.preventDefault();
const searchValue = getState().searchBar.searchValue;
dispatch<any>(saveRecentQuery(searchValue));
- dispatch<any>(searchDataOnEnter(searchValue));
dispatch<any>(loadRecentQueries());
- };
-
-const searchDataOnEnter = (searchValue: string) =>
- (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
dispatch(searchBarActions.CLOSE_SEARCH_VIEW());
dispatch(searchBarActions.SET_SEARCH_VALUE(searchValue));
dispatch(searchBarActions.SET_SEARCH_RESULTS([]));
- dispatch<any>(searchGroups(searchValue, 100));
dispatch(navigateToSearchResults);
};
const debounceStartSearch = debounce((dispatch: Dispatch) => dispatch<any>(startSearch()), DEFAULT_SEARCH_DEBOUNCE);
-const startSearch = () =>
- (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+const startSearch = () =>
+ (dispatch: Dispatch, getState: () => RootState) => {
const searchValue = getState().searchBar.searchValue;
dispatch<any>(searchData(searchValue));
};
-const searchGroups = (searchValue: string, limit = 5, resourceKind?: ResourceKind) =>
+const searchGroups = (searchValue: string, limit = 5, resourceKind?: ResourceKind) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
const currentView = getState().searchBar.currentView;
-
+
if (searchValue || currentView === SearchView.ADVANCED) {
const filters = getFilters('name', searchValue, resourceKind);
const { items } = await services.groupsService.contents('', {
@@ -208,16 +204,26 @@ const buildUuidFilter = (type?: ResourceKind): ResourceKind[] => {
};
export const initAdvanceFormProjectsTree = () =>
- (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ (dispatch: Dispatch) => {
dispatch<any>(initUserProject(SEARCH_BAR_ADVANCE_FORM_PICKER_ID));
};
export const changeAdvanceFormProperty = (property: string, value: PropertyValues[] | string = '') =>
- (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ (dispatch: Dispatch) => {
dispatch(change(SEARCH_BAR_ADVANCE_FORM_NAME, property, value));
};
export const updateAdvanceFormProperties = (propertyValues: PropertyValues) =>
- (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ (dispatch: Dispatch) => {
dispatch(arrayPush(SEARCH_BAR_ADVANCE_FORM_NAME, 'properties', propertyValues));
- };
\ No newline at end of file
+ };
+
+export const moveUp = () =>
+ (dispatch: Dispatch) => {
+ dispatch(searchBarActions.MOVE_UP());
+ };
+
+export const moveDown = () =>
+ (dispatch: Dispatch) => {
+ dispatch(searchBarActions.MOVE_DOWN());
+ };
diff --git a/src/store/search-bar/search-bar-reducer.ts b/src/store/search-bar/search-bar-reducer.ts
index 781246a..fd417d3 100644
--- a/src/store/search-bar/search-bar-reducer.ts
+++ b/src/store/search-bar/search-bar-reducer.ts
@@ -6,12 +6,15 @@ import { searchBarActions, SearchBarActions } from '~/store/search-bar/search-ba
import { GroupContentsResource } from '~/services/groups-service/groups-service';
import { SearchBarAdvanceFormData } from '~/models/search-bar';
+type SearchResult = GroupContentsResource;
+
interface SearchBar {
currentView: string;
open: boolean;
- searchResults: GroupContentsResource[];
+ searchResults: SearchResult[];
searchValue: string;
savedQueries: SearchBarAdvanceFormData[];
+ selectedItem: string;
}
export enum SearchView {
@@ -25,17 +28,67 @@ const initialState: SearchBar = {
open: false,
searchResults: [],
searchValue: '',
- savedQueries: []
+ savedQueries: [],
+ selectedItem: ''
};
export const searchBarReducer = (state = initialState, action: SearchBarActions): SearchBar =>
searchBarActions.match(action, {
- SET_CURRENT_VIEW: currentView => ({ ...state, currentView }),
+ SET_CURRENT_VIEW: currentView => ({
+ ...state,
+ currentView,
+ open: true
+ }),
OPEN_SEARCH_VIEW: () => ({ ...state, open: true }),
CLOSE_SEARCH_VIEW: () => ({ ...state, open: false }),
- SET_SEARCH_RESULTS: (searchResults) => ({ ...state, searchResults }),
- SET_SEARCH_VALUE: (searchValue) => ({ ...state, searchValue }),
+ SET_SEARCH_RESULTS: searchResults => ({
+ ...state,
+ searchResults,
+ selectedItem: searchResults.length > 0
+ ? searchResults.findIndex(r => r.uuid === state.selectedItem) >= 0
+ ? state.selectedItem
+ : state.searchValue
+ : state.searchValue
+ }),
+ SET_SEARCH_VALUE: searchValue => ({
+ ...state,
+ searchValue,
+ selectedItem: state.searchValue === state.selectedItem
+ ? searchValue
+ : state.selectedItem
+ }),
SET_SAVED_QUERIES: savedQueries => ({ ...state, savedQueries }),
UPDATE_SAVED_QUERY: searchQuery => ({ ...state, savedQueries: searchQuery }),
+ SET_SELECTED_ITEM: item => ({ ...state, selectedItem: item }),
+ MOVE_UP: () => {
+ let selectedItem = state.selectedItem;
+ if (state.currentView === SearchView.AUTOCOMPLETE) {
+ const idx = state.searchResults.findIndex(r => r.uuid === selectedItem);
+ if (idx > 0) {
+ selectedItem = state.searchResults[idx - 1].uuid;
+ } else {
+ selectedItem = state.searchValue;
+ }
+ }
+ return {
+ ...state,
+ selectedItem
+ };
+ },
+ MOVE_DOWN: () => {
+ let selectedItem = state.selectedItem;
+ if (state.currentView === SearchView.AUTOCOMPLETE) {
+ const idx = state.searchResults.findIndex(r => r.uuid === selectedItem);
+ if (idx >= 0 && idx < state.searchResults.length - 1) {
+ selectedItem = state.searchResults[idx + 1].uuid;
+ } else if (idx < 0 && state.searchResults.length > 0) {
+ selectedItem = state.searchResults[0].uuid;
+ }
+ }
+ return {
+ ...state,
+ selectedItem
+ };
+ },
default: () => state
- });
\ No newline at end of file
+ });
diff --git a/src/store/search-results-panel/search-results-middleware-service.ts b/src/store/search-results-panel/search-results-middleware-service.ts
index 5ccb61b..3806765 100644
--- a/src/store/search-results-panel/search-results-middleware-service.ts
+++ b/src/store/search-results-panel/search-results-middleware-service.ts
@@ -28,7 +28,7 @@ export class SearchResultsMiddlewareService extends DataExplorerMiddlewareServic
const dataExplorer = getDataExplorer(state.dataExplorer, this.getId());
const searchValue = state.searchBar.searchValue;
try {
- const response = await this.services.groupsService.contents(userUuid, getParams(dataExplorer, searchValue));
+ const response = await this.services.groupsService.contents('', getParams(dataExplorer, searchValue));
api.dispatch(updateResources(response.items));
api.dispatch(setItems(response));
} catch {
@@ -72,4 +72,4 @@ const couldNotFetchWorkflows = () =>
snackbarActions.OPEN_SNACKBAR({
message: 'Could not fetch workflows.',
kind: SnackbarKind.ERROR
- });
\ No newline at end of file
+ });
diff --git a/src/views-components/search-bar/search-bar-autocomplete-view.tsx b/src/views-components/search-bar/search-bar-autocomplete-view.tsx
index 69fa459..02ff4c7 100644
--- a/src/views-components/search-bar/search-bar-autocomplete-view.tsx
+++ b/src/views-components/search-bar/search-bar-autocomplete-view.tsx
@@ -21,13 +21,14 @@ const styles: StyleRulesCallback<CssRules> = theme => {
paddingLeft: theme.spacing.unit,
paddingRight: theme.spacing.unit * 2,
},
-
+
};
};
export interface SearchBarAutocompleteViewDataProps {
- searchResults?: GroupContentsResource[];
+ searchResults: GroupContentsResource[];
searchValue?: string;
+ selectedItem: string;
}
export interface SearchBarAutocompleteViewActionProps {
@@ -37,18 +38,23 @@ export interface SearchBarAutocompleteViewActionProps {
type SearchBarAutocompleteViewProps = SearchBarAutocompleteViewDataProps & SearchBarAutocompleteViewActionProps & WithStyles<CssRules>;
export const SearchBarAutocompleteView = withStyles(styles)(
- ({ classes, searchResults, searchValue, navigateTo }: SearchBarAutocompleteViewProps) =>
- <Paper className={classes.searchView}>
- {searchResults && <List component="nav" className={classes.list}>
+ ({ classes, searchResults, searchValue, navigateTo, selectedItem }: SearchBarAutocompleteViewProps) => {
+ console.log(searchValue, selectedItem);
+ return <Paper className={classes.searchView}>
+ <List component="nav" className={classes.list}>
+ <ListItem button className={classes.listItem} selected={!selectedItem || searchValue === selectedItem}>
+ <ListItemText secondary={searchValue}/>
+ </ListItem>
{searchResults.map((item: GroupContentsResource) =>
- <ListItem button key={item.uuid} className={classes.listItem}>
- <ListItemText secondary={getFormattedText(item.name, searchValue)} onClick={() => navigateTo(item.uuid)} />
+ <ListItem button key={item.uuid} className={classes.listItem} selected={item.uuid === selectedItem}>
+ <ListItemText secondary={getFormattedText(item.name, searchValue)}
+ onClick={() => navigateTo(item.uuid)}/>
</ListItem>
)}
- </List>}
- </Paper>
-);
+ </List>
+ </Paper>;
+ });
const getFormattedText = (textToHighlight: string, searchString = '') => {
return <Highlighter searchWords={[searchString]} autoEscape={true} textToHighlight={textToHighlight} />;
-};
\ No newline at end of file
+};
diff --git a/src/views-components/search-bar/search-bar-view.tsx b/src/views-components/search-bar/search-bar-view.tsx
index b3ec7ab..80c3c71 100644
--- a/src/views-components/search-bar/search-bar-view.tsx
+++ b/src/views-components/search-bar/search-bar-view.tsx
@@ -31,6 +31,7 @@ import {
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';
@@ -82,25 +83,47 @@ interface SearchBarViewActionProps {
closeView: () => void;
openSearchView: () => void;
loadRecentQueries: () => string[];
+ moveUp: () => void;
+ moveDown: () => void;
}
type SearchBarViewProps = SearchBarDataProps & SearchBarActionProps & WithStyles<CssRules>;
export const SearchBarView = withStyles(styles)(
(props : SearchBarViewProps) => {
- const { classes, isPopoverOpen, closeView, searchValue, openSearchView, onChange, onSubmit } = props;
+ const { classes, isPopoverOpen } = props;
return (
- <ClickAwayListener onClickAway={closeView}>
+ <ClickAwayListener onClickAway={props.closeView}>
<Paper className={isPopoverOpen ? classes.containerSearchViewOpened : classes.container} >
- <form onSubmit={onSubmit}>
+ <form onSubmit={props.onSubmit}>
<Input
className={classes.input}
- onChange={onChange}
+ onChange={props.onChange}
placeholder="Search"
- value={searchValue}
+ value={props.searchValue}
fullWidth={true}
disableUnderline={true}
- onClick={openSearchView}
+ onClick={props.openSearchView}
+ onKeyDown={e => {
+ if (e.keyCode === KEY_CODE_DOWN) {
+ e.preventDefault();
+ if (!isPopoverOpen) {
+ props.openSearchView();
+ } else {
+ props.moveDown();
+ }
+ } else if (e.keyCode === KEY_CODE_UP) {
+ e.preventDefault();
+ props.moveUp();
+ } else if (e.keyCode === KEY_CODE_ESC) {
+ props.closeView();
+ } else if (e.keyCode === KEY_ENTER) {
+ if (props.selectedItem !== props.searchValue) {
+ e.preventDefault();
+ props.navigateTo(props.selectedItem);
+ }
+ }
+ }}
endAdornment={
<InputAdornment position="end">
<Tooltip title='Search'>
@@ -121,25 +144,24 @@ export const SearchBarView = withStyles(styles)(
);
const getView = (props: SearchBarViewProps) => {
- const { onSetView, closeAdvanceView, loadRecentQueries, savedQueries, deleteSavedQuery, searchValue,
- searchResults, onSearch, navigateTo, editSavedQuery, tags, currentView } = props;
- switch (currentView) {
+ switch (props.currentView) {
case SearchView.AUTOCOMPLETE:
return <SearchBarAutocompleteView
- navigateTo={navigateTo}
- searchResults={searchResults}
- searchValue={searchValue} />;
+ navigateTo={props.navigateTo}
+ searchResults={props.searchResults}
+ searchValue={props.searchValue}
+ selectedItem={props.selectedItem} />;
case SearchView.ADVANCED:
return <SearchBarAdvancedView
- closeAdvanceView={closeAdvanceView}
- tags={tags} />;
+ closeAdvanceView={props.closeAdvanceView}
+ tags={props.tags} />;
default:
return <SearchBarBasicView
- onSetView={onSetView}
- onSearch={onSearch}
- loadRecentQueries={loadRecentQueries}
- savedQueries={savedQueries}
- deleteSavedQuery={deleteSavedQuery}
- editSavedQuery={editSavedQuery} />;
+ onSetView={props.onSetView}
+ onSearch={props.onSearch}
+ loadRecentQueries={props.loadRecentQueries}
+ savedQueries={props.savedQueries}
+ deleteSavedQuery={props.deleteSavedQuery}
+ editSavedQuery={props.editSavedQuery} />;
}
};
diff --git a/src/views-components/search-bar/search-bar.tsx b/src/views-components/search-bar/search-bar.tsx
index 68ffecf..e60b214 100644
--- a/src/views-components/search-bar/search-bar.tsx
+++ b/src/views-components/search-bar/search-bar.tsx
@@ -16,7 +16,7 @@ import {
navigateToItem,
editSavedQuery,
changeData,
- submitData
+ submitData, moveUp, moveDown
} from '~/store/search-bar/search-bar-actions';
import { SearchBarView, SearchBarActionProps, SearchBarDataProps } from '~/views-components/search-bar/search-bar-view';
import { SearchBarAdvanceFormData } from '~/models/search-bar';
@@ -27,6 +27,7 @@ const mapStateToProps = ({ searchBar, form }: RootState): SearchBarDataProps =>
currentView: searchBar.currentView,
isPopoverOpen: searchBar.open,
searchResults: searchBar.searchResults,
+ selectedItem: searchBar.selectedItem,
savedQueries: searchBar.savedQueries,
tags: form.searchBarAdvanceFormName
};
@@ -43,7 +44,9 @@ const mapDispatchToProps = (dispatch: Dispatch): SearchBarActionProps => ({
deleteSavedQuery: (id: number) => dispatch<any>(deleteSavedQuery(id)),
openSearchView: () => dispatch<any>(openSearchView()),
navigateTo: (uuid: string) => dispatch<any>(navigateToItem(uuid)),
- editSavedQuery: (data: SearchBarAdvanceFormData) => dispatch<any>(editSavedQuery(data))
+ editSavedQuery: (data: SearchBarAdvanceFormData) => dispatch<any>(editSavedQuery(data)),
+ moveUp: () => dispatch<any>(moveUp()),
+ moveDown: () => dispatch<any>(moveDown())
});
-export const SearchBar = connect(mapStateToProps, mapDispatchToProps)(SearchBarView);
\ No newline at end of file
+export const SearchBar = connect(mapStateToProps, mapDispatchToProps)(SearchBarView);
-----------------------------------------------------------------------
hooks/post-receive
--
More information about the arvados-commits
mailing list