Added providers to the auto complete sheet
Summary: Okay so the main changes here are integrating the providers into the auto complete sheet and getting the search bar to work with it also. For instance, in the search bar, I want to update the value string to whatever the user has highlighted in the auto complete sheet, without executing a new query. So thus, I had to create a new state variable in the search bar component for this. I've also moved the custom hook into its own file to keep the component short in size. It had to be mainly rewritten to support providers instead of only bookmarks. Same goes for the entire AutoCompleteSheet component. The bookmarksProvider is stored in the persisted state as to not regenerate every-time on render. It is only updated if the bookmarks are updated which are also now stored in the persistedState for the same reason. Lastly, a DefaultProvider object was also made for the initial persisted state object. Reviewed By: danielbuechele Differential Revision: D16581644 fbshipit-source-id: 88723a4081d96250f723a4cd7b1ade101bf3e8f3
This commit is contained in:
committed by
Facebook Github Bot
parent
f591475f85
commit
ea8a6546c9
@@ -7,17 +7,22 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import NavigationPlugin from '../';
|
import NavigationPlugin from '../';
|
||||||
|
import {DefaultProvider} from '../util/autoCompleteProvider';
|
||||||
|
|
||||||
import type {PersistedState} from '../flow-types';
|
import type {Bookmark, PersistedState, URI} from '../flow-types';
|
||||||
|
|
||||||
function constructPersistedStateMock(): PersistedState {
|
function constructPersistedStateMock(): PersistedState {
|
||||||
return {
|
return {
|
||||||
|
bookmarksProvider: new DefaultProvider(),
|
||||||
|
bookmarks: new Map<URI, Bookmark>(),
|
||||||
navigationEvents: [],
|
navigationEvents: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function constructPersistedStateMockWithEvents(): PersistedState {
|
function constructPersistedStateMockWithEvents(): PersistedState {
|
||||||
return {
|
return {
|
||||||
|
bookmarksProvider: new DefaultProvider(),
|
||||||
|
bookmarks: new Map<URI, Bookmark>(),
|
||||||
navigationEvents: [
|
navigationEvents: [
|
||||||
{
|
{
|
||||||
uri: 'mock://this_is_a_mock_uri/mock/1',
|
uri: 'mock://this_is_a_mock_uri/mock/1',
|
||||||
|
|||||||
@@ -6,22 +6,23 @@
|
|||||||
* @flow strict-local
|
* @flow strict-local
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {styled} from 'flipper';
|
import {Glyph, styled} from 'flipper';
|
||||||
import {useEffect, useState} from 'react';
|
import {useItemNavigation} from '../hooks/autoCompleteSheet';
|
||||||
|
import {filterProvidersToLineItems} from '../util/autoCompleteProvider';
|
||||||
|
|
||||||
import type {Bookmark} from '../flow-types';
|
import type {AutoCompleteProvider} from '../flow-types';
|
||||||
|
|
||||||
type Props = {|
|
type Props = {|
|
||||||
bookmarks: Map<string, Bookmark>,
|
providers: Array<AutoCompleteProvider>,
|
||||||
onHighlighted: string => void,
|
onHighlighted: string => void,
|
||||||
onNavigate: string => void,
|
onNavigate: string => void,
|
||||||
|
query: string,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
const MAX_ITEMS = 5;
|
const MAX_ITEMS = 5;
|
||||||
|
|
||||||
const AutoCompleteSheetContainer = styled('div')({
|
const AutoCompleteSheetContainer = styled('div')({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
overflowY: 'scroll',
|
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: '100%',
|
top: '100%',
|
||||||
backgroundColor: 'white',
|
backgroundColor: 'white',
|
||||||
@@ -44,56 +45,26 @@ const SheetItem = styled('div')({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Menu Item Navigation Hook
|
const SheetItemIcon = styled('span')({
|
||||||
const useItemNavigation = (
|
padding: 8,
|
||||||
bookmarks: Array<Bookmark>,
|
});
|
||||||
onHighlighted: string => void,
|
|
||||||
) => {
|
|
||||||
const [selectedItem, setSelectedItem] = useState(-1);
|
|
||||||
|
|
||||||
const handleKeyPress = ({key}) => {
|
|
||||||
switch (key) {
|
|
||||||
case 'ArrowDown': {
|
|
||||||
const newSelectedItem =
|
|
||||||
selectedItem < MAX_ITEMS - 1 ? selectedItem + 1 : selectedItem;
|
|
||||||
setSelectedItem(newSelectedItem);
|
|
||||||
onHighlighted(bookmarks[newSelectedItem].uri);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'ArrowUp': {
|
|
||||||
const newSelectedItem =
|
|
||||||
selectedItem > 0 ? selectedItem - 1 : selectedItem;
|
|
||||||
setSelectedItem(newSelectedItem);
|
|
||||||
onHighlighted(bookmarks[newSelectedItem].uri);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
window.addEventListener('keydown', handleKeyPress);
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('keydown', handleKeyPress);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return selectedItem;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default (props: Props) => {
|
export default (props: Props) => {
|
||||||
const {bookmarks, onHighlighted, onNavigate} = props;
|
const {providers, onHighlighted, onNavigate, query} = props;
|
||||||
const filteredBookmarks = [...bookmarks.values()].slice(0, MAX_ITEMS);
|
const lineItems = filterProvidersToLineItems(providers, query, MAX_ITEMS);
|
||||||
const selectedItem = useItemNavigation(filteredBookmarks, onHighlighted);
|
lineItems.unshift({uri: query, matchPattern: query, icon: 'send'});
|
||||||
|
const selectedItem = useItemNavigation(lineItems, onHighlighted);
|
||||||
return (
|
return (
|
||||||
<AutoCompleteSheetContainer>
|
<AutoCompleteSheetContainer>
|
||||||
{filteredBookmarks.map((bookmark, idx) => (
|
{lineItems.map((lineItem, idx) => (
|
||||||
<SheetItem
|
<SheetItem
|
||||||
className={idx === selectedItem ? 'selected' : null}
|
className={idx === selectedItem ? 'selected' : null}
|
||||||
key={bookmark.uri}
|
key={idx}
|
||||||
onMouseDown={() => onNavigate(bookmark.uri)}>
|
onMouseDown={() => onNavigate(lineItem.uri)}>
|
||||||
{bookmark.uri}
|
<SheetItemIcon>
|
||||||
|
<Glyph name={lineItem.icon} size={16} variant="outline" />
|
||||||
|
</SheetItemIcon>
|
||||||
|
{lineItem.matchPattern}
|
||||||
</SheetItem>
|
</SheetItem>
|
||||||
))}
|
))}
|
||||||
</AutoCompleteSheetContainer>
|
</AutoCompleteSheetContainer>
|
||||||
|
|||||||
@@ -16,18 +16,20 @@ import {
|
|||||||
} from 'flipper';
|
} from 'flipper';
|
||||||
import {AutoCompleteSheet, IconButton, FavoriteButton} from './';
|
import {AutoCompleteSheet, IconButton, FavoriteButton} from './';
|
||||||
|
|
||||||
import type {Bookmark} from '../flow-types';
|
import type {AutoCompleteProvider, Bookmark} from '../flow-types';
|
||||||
|
|
||||||
type Props = {|
|
type Props = {|
|
||||||
onFavorite: (query: string) => void,
|
onFavorite: (query: string) => void,
|
||||||
onNavigate: (query: string) => void,
|
onNavigate: (query: string) => void,
|
||||||
bookmarks: Map<string, Bookmark>,
|
bookmarks: Map<string, Bookmark>,
|
||||||
|
providers: Array<AutoCompleteProvider>,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
type State = {|
|
type State = {|
|
||||||
query: string,
|
query: string,
|
||||||
inputFocused: boolean,
|
inputFocused: boolean,
|
||||||
autoCompleteSheetOpen: boolean,
|
autoCompleteSheetOpen: boolean,
|
||||||
|
searchInputValue: string,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
const IconContainer = styled('div')({
|
const IconContainer = styled('div')({
|
||||||
@@ -65,73 +67,84 @@ class SearchBar extends Component<Props, State> {
|
|||||||
inputFocused: false,
|
inputFocused: false,
|
||||||
autoCompleteSheetOpen: false,
|
autoCompleteSheetOpen: false,
|
||||||
query: '',
|
query: '',
|
||||||
|
searchInputValue: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
favorite = (query: string) => {
|
favorite = (searchInputValue: string) => {
|
||||||
this.props.onFavorite(query);
|
this.props.onFavorite(searchInputValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
navigateTo = (query: string) => {
|
navigateTo = (searchInputValue: string) => {
|
||||||
this.setState({query});
|
this.setState({query: searchInputValue, searchInputValue});
|
||||||
this.props.onNavigate(query);
|
this.props.onNavigate(searchInputValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
queryInputChanged = (event: SyntheticInputEvent<>) => {
|
queryInputChanged = (event: SyntheticInputEvent<>) => {
|
||||||
this.setState({query: event.target.value});
|
const value = event.target.value;
|
||||||
|
this.setState({query: value, searchInputValue: value});
|
||||||
};
|
};
|
||||||
|
|
||||||
render = () => {
|
render = () => {
|
||||||
const {bookmarks} = this.props;
|
const {bookmarks, providers} = this.props;
|
||||||
const {autoCompleteSheetOpen, inputFocused, query} = this.state;
|
const {
|
||||||
|
autoCompleteSheetOpen,
|
||||||
|
inputFocused,
|
||||||
|
searchInputValue,
|
||||||
|
query,
|
||||||
|
} = this.state;
|
||||||
return (
|
return (
|
||||||
<ToolbarContainer>
|
<ToolbarContainer>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<SearchBox className={inputFocused ? 'drop-shadow' : null}>
|
<SearchBox className={inputFocused ? 'drop-shadow' : null}>
|
||||||
<SearchInputContainer>
|
<SearchInputContainer>
|
||||||
<SearchInput
|
<SearchInput
|
||||||
value={query}
|
value={searchInputValue}
|
||||||
onBlur={() =>
|
onBlur={() =>
|
||||||
this.setState({
|
this.setState({
|
||||||
autoCompleteSheetOpen: false,
|
autoCompleteSheetOpen: false,
|
||||||
inputFocused: false,
|
inputFocused: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
onFocus={() =>
|
onFocus={event => {
|
||||||
|
event.target.select();
|
||||||
this.setState({
|
this.setState({
|
||||||
autoCompleteSheetOpen: true,
|
autoCompleteSheetOpen: true,
|
||||||
inputFocused: true,
|
inputFocused: true,
|
||||||
})
|
});
|
||||||
}
|
}}
|
||||||
onChange={this.queryInputChanged}
|
onChange={this.queryInputChanged}
|
||||||
onKeyPress={e => {
|
onKeyPress={e => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
this.navigateTo(this.state.query);
|
this.navigateTo(this.state.searchInputValue);
|
||||||
e.target.blur();
|
e.target.blur();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
placeholder="Navigate To..."
|
placeholder="Navigate To..."
|
||||||
/>
|
/>
|
||||||
{autoCompleteSheetOpen ? (
|
{autoCompleteSheetOpen && query.length > 0 ? (
|
||||||
<AutoCompleteSheet
|
<AutoCompleteSheet
|
||||||
bookmarks={bookmarks}
|
providers={providers}
|
||||||
onNavigate={this.navigateTo}
|
onNavigate={this.navigateTo}
|
||||||
onHighlighted={newQuery => this.setState({query: newQuery})}
|
onHighlighted={newInputValue =>
|
||||||
|
this.setState({searchInputValue: newInputValue})
|
||||||
|
}
|
||||||
|
query={query}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</SearchInputContainer>
|
</SearchInputContainer>
|
||||||
</SearchBox>
|
</SearchBox>
|
||||||
{query.length > 0 ? (
|
{searchInputValue.length > 0 ? (
|
||||||
<IconContainer>
|
<IconContainer>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon="send"
|
icon="send"
|
||||||
size={16}
|
size={16}
|
||||||
outline={true}
|
outline={true}
|
||||||
onClick={() => this.navigateTo(this.state.query)}
|
onClick={() => this.navigateTo(searchInputValue)}
|
||||||
/>
|
/>
|
||||||
<FavoriteButton
|
<FavoriteButton
|
||||||
size={16}
|
size={16}
|
||||||
highlighted={bookmarks.has(query)}
|
highlighted={bookmarks.has(searchInputValue)}
|
||||||
onClick={() => this.favorite(this.state.query)}
|
onClick={() => this.favorite(searchInputValue)}
|
||||||
/>
|
/>
|
||||||
</IconContainer>
|
</IconContainer>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -9,13 +9,14 @@
|
|||||||
export type URI = string;
|
export type URI = string;
|
||||||
|
|
||||||
export type State = {|
|
export type State = {|
|
||||||
bookmarks: Map<URI, Bookmark>,
|
|
||||||
shouldShowSaveBookmarkDialog: boolean,
|
shouldShowSaveBookmarkDialog: boolean,
|
||||||
saveBookmarkURI: ?URI,
|
saveBookmarkURI: ?URI,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
export type PersistedState = {|
|
export type PersistedState = {|
|
||||||
|
bookmarks: Map<URI, Bookmark>,
|
||||||
navigationEvents: Array<NavigationEvent>,
|
navigationEvents: Array<NavigationEvent>,
|
||||||
|
bookmarksProvider: AutoCompleteProvider,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
export type NavigationEvent = {|
|
export type NavigationEvent = {|
|
||||||
|
|||||||
51
src/plugins/navigation/hooks/autoCompleteSheet.js
Normal file
51
src/plugins/navigation/hooks/autoCompleteSheet.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2018-present Facebook.
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
* @format
|
||||||
|
* @flow strict-local
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {useEffect, useState} from 'react';
|
||||||
|
import type {AutoCompleteLineItem} from '../flow-types';
|
||||||
|
|
||||||
|
export const useItemNavigation = (
|
||||||
|
lineItems: Array<AutoCompleteLineItem>,
|
||||||
|
onHighlighted: string => void,
|
||||||
|
) => {
|
||||||
|
const [selectedItem, setSelectedItem] = useState(0);
|
||||||
|
|
||||||
|
const handleKeyPress = ({key}) => {
|
||||||
|
switch (key) {
|
||||||
|
case 'ArrowDown': {
|
||||||
|
const newSelectedItem =
|
||||||
|
selectedItem < lineItems.length - 1
|
||||||
|
? selectedItem + 1
|
||||||
|
: lineItems.length - 1;
|
||||||
|
setSelectedItem(newSelectedItem);
|
||||||
|
onHighlighted(lineItems[newSelectedItem].uri);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'ArrowUp': {
|
||||||
|
const newSelectedItem =
|
||||||
|
selectedItem > 0 ? selectedItem - 1 : selectedItem;
|
||||||
|
setSelectedItem(newSelectedItem);
|
||||||
|
onHighlighted(lineItems[newSelectedItem].uri);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
setSelectedItem(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('keydown', handleKeyPress);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('keydown', handleKeyPress);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return selectedItem;
|
||||||
|
};
|
||||||
@@ -19,6 +19,10 @@ import {
|
|||||||
readBookmarksFromDB,
|
readBookmarksFromDB,
|
||||||
writeBookmarkToDB,
|
writeBookmarkToDB,
|
||||||
} from './util/indexedDB';
|
} from './util/indexedDB';
|
||||||
|
import {
|
||||||
|
bookmarksToAutoCompleteProvider,
|
||||||
|
DefaultProvider,
|
||||||
|
} from './util/autoCompleteProvider';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
State,
|
State,
|
||||||
@@ -35,10 +39,11 @@ export default class extends FlipperPlugin<State, {}, PersistedState> {
|
|||||||
|
|
||||||
static defaultPersistedState: PersistedState = {
|
static defaultPersistedState: PersistedState = {
|
||||||
navigationEvents: [],
|
navigationEvents: [],
|
||||||
|
bookmarks: new Map<string, Bookmark>(),
|
||||||
|
bookmarksProvider: new DefaultProvider(),
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
bookmarks: new Map<string, Bookmark>(),
|
|
||||||
shouldShowSaveBookmarkDialog: false,
|
shouldShowSaveBookmarkDialog: false,
|
||||||
saveBookmarkURI: null,
|
saveBookmarkURI: null,
|
||||||
};
|
};
|
||||||
@@ -69,7 +74,10 @@ export default class extends FlipperPlugin<State, {}, PersistedState> {
|
|||||||
|
|
||||||
componentDidMount = () => {
|
componentDidMount = () => {
|
||||||
readBookmarksFromDB().then(bookmarks => {
|
readBookmarksFromDB().then(bookmarks => {
|
||||||
this.setState({bookmarks});
|
this.props.setPersistedState({
|
||||||
|
bookmarks: bookmarks,
|
||||||
|
bookmarksProvider: bookmarksToAutoCompleteProvider(bookmarks),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -95,29 +103,38 @@ export default class extends FlipperPlugin<State, {}, PersistedState> {
|
|||||||
commonName:
|
commonName:
|
||||||
bookmark.commonName.length > 0 ? bookmark.commonName : bookmark.uri,
|
bookmark.commonName.length > 0 ? bookmark.commonName : bookmark.uri,
|
||||||
};
|
};
|
||||||
|
|
||||||
writeBookmarkToDB(newBookmark);
|
writeBookmarkToDB(newBookmark);
|
||||||
const newMapRef = this.state.bookmarks;
|
const newMapRef = this.props.persistedState.bookmarks;
|
||||||
newMapRef.set(newBookmark.uri, newBookmark);
|
newMapRef.set(newBookmark.uri, newBookmark);
|
||||||
this.setState({bookmarks: newMapRef});
|
this.props.setPersistedState({
|
||||||
|
bookmarks: newMapRef,
|
||||||
|
bookmarksProvider: bookmarksToAutoCompleteProvider(newMapRef),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
removeBookmark = (uri: string) => {
|
removeBookmark = (uri: string) => {
|
||||||
removeBookmark(uri);
|
removeBookmark(uri);
|
||||||
const newMapRef = this.state.bookmarks;
|
const newMapRef = this.props.persistedState.bookmarks;
|
||||||
newMapRef.delete(uri);
|
newMapRef.delete(uri);
|
||||||
this.setState({bookmarks: newMapRef});
|
this.props.setPersistedState({
|
||||||
|
bookmarks: newMapRef,
|
||||||
|
bookmarksProvider: bookmarksToAutoCompleteProvider(newMapRef),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const {saveBookmarkURI, shouldShowSaveBookmarkDialog} = this.state;
|
||||||
const {
|
const {
|
||||||
bookmarks,
|
bookmarks,
|
||||||
saveBookmarkURI,
|
bookmarksProvider,
|
||||||
shouldShowSaveBookmarkDialog,
|
navigationEvents,
|
||||||
} = this.state;
|
} = this.props.persistedState;
|
||||||
const {navigationEvents} = this.props.persistedState;
|
const autoCompleteProviders = [bookmarksProvider];
|
||||||
return (
|
return (
|
||||||
<ScrollableFlexColumn>
|
<ScrollableFlexColumn>
|
||||||
<SearchBar
|
<SearchBar
|
||||||
|
providers={autoCompleteProviders}
|
||||||
bookmarks={bookmarks}
|
bookmarks={bookmarks}
|
||||||
onNavigate={this.navigateTo}
|
onNavigate={this.navigateTo}
|
||||||
onFavorite={this.onFavorite}
|
onFavorite={this.onFavorite}
|
||||||
|
|||||||
@@ -13,6 +13,12 @@ import type {
|
|||||||
AutoCompleteLineItem,
|
AutoCompleteLineItem,
|
||||||
} from '../flow-types';
|
} from '../flow-types';
|
||||||
|
|
||||||
|
export function DefaultProvider(): AutoCompleteProvider {
|
||||||
|
this.icon = 'caution';
|
||||||
|
this.matchPatterns = new Map<string, URI>();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
export const bookmarksToAutoCompleteProvider: (
|
export const bookmarksToAutoCompleteProvider: (
|
||||||
Map<URI, Bookmark>,
|
Map<URI, Bookmark>,
|
||||||
) => AutoCompleteProvider = bookmarks => {
|
) => AutoCompleteProvider = bookmarks => {
|
||||||
|
|||||||
Reference in New Issue
Block a user