From a960b30fd5da0619e5029e67712b2a2778e4b591 Mon Sep 17 00:00:00 2001 From: Daniel Kos Date: Tue, 30 Oct 2018 08:29:56 +0100 Subject: [PATCH] Add navigation with arrow on basic search view Feature #14364 Arvados-DCO-1.1-Signed-off-by: Daniel Kos --- src/store/search-bar/search-bar-actions.ts | 15 +++-- src/store/search-bar/search-bar-reducer.ts | 65 +++++++++++++++---- .../search-bar-autocomplete-view.tsx | 10 +-- .../search-bar/search-bar-basic-view.tsx | 14 ++-- .../search-bar/search-bar-recent-queries.tsx | 19 ++++-- .../search-bar/search-bar-save-queries.tsx | 20 +++--- .../search-bar/search-bar-view.tsx | 50 ++++++++------ 7 files changed, 124 insertions(+), 69 deletions(-) diff --git a/src/store/search-bar/search-bar-actions.ts b/src/store/search-bar/search-bar-actions.ts index f953a577..9f86e460 100644 --- a/src/store/search-bar/search-bar-actions.ts +++ b/src/store/search-bar/search-bar-actions.ts @@ -26,6 +26,7 @@ export const searchBarActions = unionize({ SET_SEARCH_RESULTS: ofType(), SET_SEARCH_VALUE: ofType(), SET_SAVED_QUERIES: ofType(), + SET_RECENT_QUERIES: ofType(), UPDATE_SAVED_QUERY: ofType(), SET_SELECTED_ITEM: ofType(), MOVE_UP: ofType<{}>(), @@ -49,8 +50,9 @@ export const saveRecentQuery = (query: string) => export const loadRecentQueries = () => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - const recentSearchQueries = services.searchService.getRecentQueries(); - return recentSearchQueries || []; + const recentQueries = services.searchService.getRecentQueries() || []; + dispatch(searchBarActions.SET_RECENT_QUERIES(recentQueries)); + return recentQueries; }; export const searchData = (searchValue: string) => @@ -75,19 +77,18 @@ export const searchAdvanceData = (data: SearchBarAdvanceFormData) => dispatch(navigateToSearchResults); }; -// Todo: create ids for particular searchQuery const saveQuery = (data: SearchBarAdvanceFormData) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - const savedSearchQueries = services.searchService.getSavedQueries(); - const filteredQuery = savedSearchQueries.find(query => query.searchQuery === data.searchQuery); + const savedQueries = services.searchService.getSavedQueries(); if (data.saveQuery && data.searchQuery) { + const filteredQuery = savedQueries.find(query => query.searchQuery === data.searchQuery); if (filteredQuery) { services.searchService.editSavedQueries(data); - dispatch(searchBarActions.UPDATE_SAVED_QUERY(savedSearchQueries)); + dispatch(searchBarActions.UPDATE_SAVED_QUERY(savedQueries)); 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(searchBarActions.SET_SAVED_QUERIES(savedQueries)); dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Query has been successfully saved', hideDuration: 2000, kind: SnackbarKind.SUCCESS })); } } diff --git a/src/store/search-bar/search-bar-reducer.ts b/src/store/search-bar/search-bar-reducer.ts index fd417d37..1c16d999 100644 --- a/src/store/search-bar/search-bar-reducer.ts +++ b/src/store/search-bar/search-bar-reducer.ts @@ -7,6 +7,10 @@ import { GroupContentsResource } from '~/services/groups-service/groups-service' import { SearchBarAdvanceFormData } from '~/models/search-bar'; type SearchResult = GroupContentsResource; +export type SearchBarSelectedItem = { + id: string, + query: string +}; interface SearchBar { currentView: string; @@ -14,7 +18,8 @@ interface SearchBar { searchResults: SearchResult[]; searchValue: string; savedQueries: SearchBarAdvanceFormData[]; - selectedItem: string; + recentQueries: string[]; + selectedItem: SearchBarSelectedItem; } export enum SearchView { @@ -29,7 +34,19 @@ const initialState: SearchBar = { searchResults: [], searchValue: '', savedQueries: [], - selectedItem: '' + recentQueries: [], + selectedItem: { + id: '', + query: '' + } +}; + +const makeSelectedItem = (id: string, query?: string): SearchBarSelectedItem => ({ id, query: query ? query : id }); + +const makeQueryList = (recentQueries: string[], savedQueries: SearchBarAdvanceFormData[]) => { + const recentIds = recentQueries.map((q, idx) => makeSelectedItem(`RQ-${idx}-${q}`, q)); + const savedIds = savedQueries.map((q, idx) => makeSelectedItem(`SQ-${idx}-${q.searchQuery}`, q.searchQuery)); + return recentIds.concat(savedIds); }; export const searchBarReducer = (state = initialState, action: SearchBarActions): SearchBar => @@ -44,30 +61,39 @@ export const searchBarReducer = (state = initialState, action: SearchBarActions) SET_SEARCH_RESULTS: searchResults => ({ ...state, searchResults, - selectedItem: searchResults.length > 0 - ? searchResults.findIndex(r => r.uuid === state.selectedItem) >= 0 - ? state.selectedItem + selectedItem: makeSelectedItem(searchResults.length > 0 + ? searchResults.findIndex(r => r.uuid === state.selectedItem.id) >= 0 + ? state.selectedItem.id : state.searchValue : state.searchValue + ) }), SET_SEARCH_VALUE: searchValue => ({ ...state, searchValue, - selectedItem: state.searchValue === state.selectedItem + selectedItem: makeSelectedItem(state.searchValue === state.selectedItem.id ? searchValue - : state.selectedItem + : state.selectedItem.id) }), SET_SAVED_QUERIES: savedQueries => ({ ...state, savedQueries }), + SET_RECENT_QUERIES: recentQueries => ({ ...state, recentQueries }), UPDATE_SAVED_QUERY: searchQuery => ({ ...state, savedQueries: searchQuery }), - SET_SELECTED_ITEM: item => ({ ...state, selectedItem: item }), + SET_SELECTED_ITEM: item => ({ ...state, selectedItem: makeSelectedItem(item) }), MOVE_UP: () => { let selectedItem = state.selectedItem; if (state.currentView === SearchView.AUTOCOMPLETE) { - const idx = state.searchResults.findIndex(r => r.uuid === selectedItem); + const idx = state.searchResults.findIndex(r => r.uuid === selectedItem.id); if (idx > 0) { - selectedItem = state.searchResults[idx - 1].uuid; + selectedItem = makeSelectedItem(state.searchResults[idx - 1].uuid); } else { - selectedItem = state.searchValue; + selectedItem = makeSelectedItem(state.searchValue); + } + } else if (state.currentView === SearchView.BASIC) { + const items = makeQueryList(state.recentQueries, state.savedQueries); + + const idx = items.findIndex(i => i.id === selectedItem.id); + if (idx > 0) { + selectedItem = items[idx - 1]; } } return { @@ -78,11 +104,22 @@ export const searchBarReducer = (state = initialState, action: SearchBarActions) MOVE_DOWN: () => { let selectedItem = state.selectedItem; if (state.currentView === SearchView.AUTOCOMPLETE) { - const idx = state.searchResults.findIndex(r => r.uuid === selectedItem); + const idx = state.searchResults.findIndex(r => r.uuid === selectedItem.id); if (idx >= 0 && idx < state.searchResults.length - 1) { - selectedItem = state.searchResults[idx + 1].uuid; + selectedItem = makeSelectedItem(state.searchResults[idx + 1].uuid); } else if (idx < 0 && state.searchResults.length > 0) { - selectedItem = state.searchResults[0].uuid; + selectedItem = makeSelectedItem(state.searchResults[0].uuid); + } + } else if (state.currentView === SearchView.BASIC) { + const items = makeQueryList(state.recentQueries, state.savedQueries); + + const idx = items.findIndex(i => i.id === selectedItem.id); + if (idx >= 0 && idx < items.length - 1) { + selectedItem = items[idx + 1]; + } + + if (idx < 0 && items.length > 0) { + selectedItem = items[0]; } } return { 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 02ff4c74..4dab5db0 100644 --- a/src/views-components/search-bar/search-bar-autocomplete-view.tsx +++ b/src/views-components/search-bar/search-bar-autocomplete-view.tsx @@ -6,6 +6,7 @@ import * as React from 'react'; import { Paper, StyleRulesCallback, withStyles, WithStyles, List, ListItem, ListItemText } from '@material-ui/core'; import { GroupContentsResource } from '~/services/groups-service/groups-service'; import Highlighter from "react-highlight-words"; +import { SearchBarSelectedItem } from "~/store/search-bar/search-bar-reducer"; type CssRules = 'searchView' | 'list' | 'listItem'; @@ -20,15 +21,14 @@ const styles: StyleRulesCallback = theme => { listItem: { paddingLeft: theme.spacing.unit, paddingRight: theme.spacing.unit * 2, - }, - + } }; }; export interface SearchBarAutocompleteViewDataProps { searchResults: GroupContentsResource[]; searchValue?: string; - selectedItem: string; + selectedItem: SearchBarSelectedItem; } export interface SearchBarAutocompleteViewActionProps { @@ -42,11 +42,11 @@ export const SearchBarAutocompleteView = withStyles(styles)( console.log(searchValue, selectedItem); return - + {searchResults.map((item: GroupContentsResource) => - + navigateTo(item.uuid)}/> diff --git a/src/views-components/search-bar/search-bar-basic-view.tsx b/src/views-components/search-bar/search-bar-basic-view.tsx index 22dfa829..76d46b36 100644 --- a/src/views-components/search-bar/search-bar-basic-view.tsx +++ b/src/views-components/search-bar/search-bar-basic-view.tsx @@ -7,7 +7,7 @@ import { Paper, StyleRulesCallback, withStyles, WithStyles } from '@material-ui/ import { SearchView } from '~/store/search-bar/search-bar-reducer'; import { SearchBarRecentQueries, - SearchBarRecentQueriesActionProps + SearchBarRecentQueriesActionProps } from '~/views-components/search-bar/search-bar-recent-queries'; import { SearchBarSavedQueries, @@ -33,7 +33,7 @@ const styles: StyleRulesCallback = theme => { }, label: { fontSize: '0.875rem', - padding: `${theme.spacing.unit / 2}px ${theme.spacing.unit}px `, + padding: `${theme.spacing.unit}px ${theme.spacing.unit}px `, color: theme.palette.grey["900"], background: theme.palette.grey["200"] } @@ -50,18 +50,20 @@ export type SearchBarBasicViewActionProps = { type SearchBarBasicViewProps = SearchBarBasicViewDataProps & SearchBarBasicViewActionProps & WithStyles; export const SearchBarBasicView = withStyles(styles)( - ({ classes, onSetView, loadRecentQueries, deleteSavedQuery, savedQueries, onSearch, editSavedQuery }: SearchBarBasicViewProps) => + ({ classes, onSetView, loadRecentQueries, deleteSavedQuery, savedQueries, onSearch, editSavedQuery, selectedItem }: SearchBarBasicViewProps) =>
Recent search queries
+ loadRecentQueries={loadRecentQueries} + selectedItem={selectedItem} />
Saved search queries
+ deleteSavedQuery={deleteSavedQuery} + selectedItem={selectedItem} />
onSetView(SearchView.ADVANCED)}>Advanced search
-); \ No newline at end of file +); diff --git a/src/views-components/search-bar/search-bar-recent-queries.tsx b/src/views-components/search-bar/search-bar-recent-queries.tsx index 3de3ca7a..a3c03e40 100644 --- a/src/views-components/search-bar/search-bar-recent-queries.tsx +++ b/src/views-components/search-bar/search-bar-recent-queries.tsx @@ -5,6 +5,7 @@ import * as React from 'react'; import { withStyles, WithStyles, StyleRulesCallback, List, ListItem, ListItemText } from '@material-ui/core'; import { ArvadosTheme } from '~/common/custom-theme'; +import { SearchBarSelectedItem } from "~/store/search-bar/search-bar-reducer"; type CssRules = 'root' | 'listItem' | 'listItemText'; @@ -22,22 +23,26 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ } }); +export interface SearchBarRecentQueriesDataProps { + selectedItem: SearchBarSelectedItem; +} + export interface SearchBarRecentQueriesActionProps { onSearch: (searchValue: string) => void; loadRecentQueries: () => string[]; } -type SearchBarRecentQueriesProps = SearchBarRecentQueriesActionProps & WithStyles; +type SearchBarRecentQueriesProps = SearchBarRecentQueriesDataProps & SearchBarRecentQueriesActionProps & WithStyles; export const SearchBarRecentQueries = withStyles(styles)( - ({ classes, onSearch, loadRecentQueries }: SearchBarRecentQueriesProps) => + ({ classes, onSearch, loadRecentQueries, selectedItem }: SearchBarRecentQueriesProps) => {loadRecentQueries().map((query, index) => - - onSearch(query)} + + onSearch(query)} className={classes.listItemText} /> )} - ); \ No newline at end of file +
); diff --git a/src/views-components/search-bar/search-bar-save-queries.tsx b/src/views-components/search-bar/search-bar-save-queries.tsx index ccf10a1b..aa62c58f 100644 --- a/src/views-components/search-bar/search-bar-save-queries.tsx +++ b/src/views-components/search-bar/search-bar-save-queries.tsx @@ -7,6 +7,7 @@ import { withStyles, WithStyles, StyleRulesCallback, List, ListItem, ListItemTex import { ArvadosTheme } from '~/common/custom-theme'; import { RemoveIcon, EditSavedQueryIcon } from '~/components/icon/icon'; import { SearchBarAdvanceFormData } from '~/models/search-bar'; +import { SearchBarSelectedItem } from "~/store/search-bar/search-bar-reducer"; type CssRules = 'root' | 'listItem' | 'listItemText' | 'button'; @@ -30,6 +31,7 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ export interface SearchBarSavedQueriesDataProps { savedQueries: SearchBarAdvanceFormData[]; + selectedItem: SearchBarSelectedItem; } export interface SearchBarSavedQueriesActionProps { @@ -38,18 +40,18 @@ export interface SearchBarSavedQueriesActionProps { editSavedQuery: (data: SearchBarAdvanceFormData, id: number) => void; } -type SearchBarSavedQueriesProps = SearchBarSavedQueriesDataProps - & SearchBarSavedQueriesActionProps +type SearchBarSavedQueriesProps = SearchBarSavedQueriesDataProps + & SearchBarSavedQueriesActionProps & WithStyles; export const SearchBarSavedQueries = withStyles(styles)( - ({ classes, savedQueries, onSearch, editSavedQuery, deleteSavedQuery }: SearchBarSavedQueriesProps) => + ({ classes, savedQueries, onSearch, editSavedQuery, deleteSavedQuery, selectedItem }: SearchBarSavedQueriesProps) => - {savedQueries.map((query, index) => - - onSearch(query.searchQuery)} + {savedQueries.map((query, index) => + + onSearch(query.searchQuery)} className={classes.listItemText} /> @@ -65,4 +67,4 @@ export const SearchBarSavedQueries = withStyles(styles)( )} - ); \ 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 80c3c71a..54a3f8f7 100644 --- a/src/views-components/search-bar/search-bar-view.tsx +++ b/src/views-components/search-bar/search-bar-view.tsx @@ -89,6 +89,32 @@ interface SearchBarViewActionProps { type SearchBarViewProps = SearchBarDataProps & SearchBarActionProps & WithStyles; +const handleKeyDown = (e: React.KeyboardEvent, props: SearchBarViewProps) => { + if (e.keyCode === KEY_CODE_DOWN) { + e.preventDefault(); + if (!props.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.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); + } + } + } +}; + export const SearchBarView = withStyles(styles)( (props : SearchBarViewProps) => { const { classes, isPopoverOpen } = props; @@ -104,26 +130,7 @@ export const SearchBarView = withStyles(styles)( fullWidth={true} disableUnderline={true} 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); - } - } - }} + onKeyDown={e => handleKeyDown(e, props)} endAdornment={ @@ -162,6 +169,7 @@ const getView = (props: SearchBarViewProps) => { loadRecentQueries={props.loadRecentQueries} savedQueries={props.savedQueries} deleteSavedQuery={props.deleteSavedQuery} - editSavedQuery={props.editSavedQuery} />; + editSavedQuery={props.editSavedQuery} + selectedItem={props.selectedItem} />; } }; -- 2.30.2