Added ability to save bookmarks in Nav Plugin

Summary:
This is a glue commit that glues all the components I've added in the past together.

Favouriting a page (i.e. clicking on the star) adds it as a bookmark.

There's four main parts to make your rreview easier:
1. Add bookmarks and favouriting to all the components that support it, including their parents. (NavigationInfoBox, SearchBar, Timeline)

2. Persist bookmarks using the indexedDB. (index.js)

3. Add saving to db through the SaveBookmarksDialog

4. Various other changes due to a changed architecture. i.e. moving bookmarks from persistedState to state.

Still to come.

1. Removing bookmarks.

2. Pressing enter to save the bookmarks when the SaveBookmarksDialog pops up.

3. Alphabetizing bookmarks? Order seems to jump around.

Reviewed By: jknoxville

Differential Revision: D16518013

fbshipit-source-id: 2e0cef14123c611db43cca360bc66dc4c05b11ed
This commit is contained in:
Benjamin Elo
2019-07-28 12:46:10 -07:00
committed by Facebook Github Bot
parent e4601a89f3
commit 84f9a1d8b5
7 changed files with 107 additions and 37 deletions

View File

@@ -12,14 +12,12 @@ import type {PersistedState} from '../';
function constructPersistedStateMock(): PersistedState {
return {
bookmarks: [],
navigationEvents: [],
};
}
function constructPersistedStateMockWithEvents(): PersistedState {
return {
bookmarks: [],
navigationEvents: [
{
uri: 'mock://this_is_a_mock_uri/mock/1',

View File

@@ -11,7 +11,7 @@ import {DetailSidebar, FlexCenter, styled, colors} from 'flipper';
import type {Bookmark} from '../';
type Props = {|
bookmarks: Array<Bookmark>,
bookmarks: Map<string, Bookmark>,
onNavigate: string => void,
|};
@@ -57,11 +57,11 @@ export default (props: Props) => {
const {bookmarks, onNavigate} = props;
return (
<DetailSidebar>
{bookmarks.length === 0 ? (
{bookmarks.size === 0 ? (
<NoData grow>No Bookmarks</NoData>
) : (
<BookmarksList>
{bookmarks.map(bookmark => (
{[...bookmarks.values()].map(bookmark => (
<div
className="bookmark-container"
role="button"

View File

@@ -11,6 +11,7 @@ import {parseURIParameters} from '../util/uri';
import {IconButton, FavoriteButton} from './';
type Props = {|
isBookmarked: boolean,
uri: ?string,
onNavigate: (query: string) => void,
onFavorite: (query: string) => void,
@@ -52,7 +53,7 @@ const NavigationInfoBoxContainer = styled('div')({
});
export default (props: Props) => {
const {uri} = props;
const {uri, isBookmarked} = props;
if (uri == null) {
return (
<NavigationInfoBoxContainer>
@@ -65,7 +66,7 @@ export default (props: Props) => {
<NavigationInfoBoxContainer>
<div className="icon-container">
<FavoriteButton
highlighted={false}
highlighted={isBookmarked}
size={16}
onClick={() => props.onFavorite(uri)}
/>

View File

@@ -7,12 +7,15 @@
*/
import {Button, FlexColumn, Input, Sheet, styled} from 'flipper';
import {useState} from 'react';
import type {Bookmark} from '../';
type Props = {|
uri: ?string,
shouldShow: boolean,
onHide: ?() => void,
onSubmit: ?(uri: string) => void,
onSubmit: Bookmark => void,
|};
const Container = styled(FlexColumn)({
@@ -45,6 +48,7 @@ const NameInput = styled(Input)({
export default (props: Props) => {
const {shouldShow, onHide, onSubmit, uri} = props;
const [commonName, setCommonName] = useState('');
if (uri == null || !shouldShow) {
return null;
} else {
@@ -54,19 +58,30 @@ export default (props: Props) => {
return (
<Container>
<Title>Save to bookmarks...</Title>
<NameInput placeholder="Name..." />
<NameInput
placeholder="Name..."
value={commonName}
onChange={event => setCommonName(event.target.value)}
/>
<URIContainer>{uri}</URIContainer>
<ButtonContainer>
<Button onClick={() => hide()} compact padded>
<Button
onClick={() => {
hide();
setCommonName('');
}}
compact
padded>
Cancel
</Button>
<Button
type="primary"
onClick={() => {
hide();
if (onSubmit != null) {
onSubmit(uri);
}
onSubmit({uri, commonName});
// The component state is remembered even after unmounting.
// Thus it is necessary to reset the commonName here.
setCommonName('');
}}
compact
padded>

View File

@@ -16,9 +16,12 @@ import {
} from 'flipper';
import {IconButton, FavoriteButton} from './';
import type {Bookmark} from '../';
type Props = {|
onFavorite: (query: string) => void,
onNavigate: (query: string) => void,
bookmarks: Map<string, Bookmark>,
|};
type State = {|
@@ -63,6 +66,8 @@ class SearchBar extends Component<Props, State> {
};
render = () => {
const {bookmarks} = this.props;
const {query} = this.state;
return (
<Toolbar>
<SearchBox>
@@ -79,19 +84,21 @@ class SearchBar extends Component<Props, State> {
<Glyph name="chevron-down" size={12} />
</SearchChevronContainer>
</SearchBox>
<IconContainer>
<IconButton
icon="send"
size={16}
outline={true}
onClick={() => this.navigateTo(this.state.query)}
/>
<FavoriteButton
size={16}
highlighted={false}
onClick={() => this.favorite(this.state.query)}
/>
</IconContainer>
{query.length > 0 ? (
<IconContainer>
<IconButton
icon="send"
size={16}
outline={true}
onClick={() => this.navigateTo(this.state.query)}
/>
<FavoriteButton
size={16}
highlighted={bookmarks.has(query)}
onClick={() => this.favorite(this.state.query)}
/>
</IconContainer>
) : null}
</Toolbar>
);
};

View File

@@ -9,11 +9,13 @@
import {styled} from 'flipper';
import {NavigationInfoBox} from './';
import type {NavigationEvent} from '../';
import type {Bookmark, NavigationEvent} from '../';
type Props = {|
bookmarks: Map<string, Bookmark>,
events: Array<NavigationEvent>,
onNavigate: string => void,
onFavorite: string => void,
|};
const TimelineContainer = styled('div')({
@@ -22,14 +24,16 @@ const TimelineContainer = styled('div')({
});
export default (props: Props) => {
const {bookmarks, events, onNavigate, onFavorite} = props;
return (
<TimelineContainer>
{props.events.map((event: NavigationEvent) => {
{events.map((event: NavigationEvent) => {
return (
<NavigationInfoBox
isBookmarked={event.uri != null ? bookmarks.has(event.uri) : false}
uri={event.uri}
onNavigate={props.onNavigate}
onFavorite={() => {}} // stubbed for now
onNavigate={onNavigate}
onFavorite={onFavorite}
/>
);
})}

View File

@@ -9,12 +9,18 @@
import {FlipperPlugin} from 'flipper';
import {
BookmarksSidebar,
SaveBookmarkDialog,
SearchBar,
Timeline,
ScrollableFlexColumn,
} from './components';
import {readBookmarksFromDB, writeBookmarkToDB} from './util/indexedDB';
type State = {||};
type State = {|
bookmarks: Map<string, Bookmark>,
shouldShowSaveBookmarkDialog: boolean,
saveBookmarkURI: ?string,
|};
export type NavigationEvent = {|
date: ?Date,
@@ -23,12 +29,11 @@ export type NavigationEvent = {|
export type Bookmark = {|
uri: string,
commonName: ?string,
commonName: string,
|};
export type PersistedState = {|
navigationEvents: Array<NavigationEvent>,
bookmarks: Array<Bookmark>,
|};
export default class extends FlipperPlugin<State, {}, PersistedState> {
@@ -39,7 +44,12 @@ export default class extends FlipperPlugin<State, {}, PersistedState> {
static defaultPersistedState: PersistedState = {
navigationEvents: [],
bookmarks: [],
};
state = {
bookmarks: new Map<string, Bookmark>(),
shouldShowSaveBookmarkDialog: false,
saveBookmarkURI: null,
};
static persistedStateReducer = (
@@ -66,6 +76,12 @@ export default class extends FlipperPlugin<State, {}, PersistedState> {
}
};
componentDidMount = () => {
readBookmarksFromDB().then(bookmarks => {
this.setState({bookmarks});
});
};
onKeyboardAction = (action: string) => {
if (action === 'clear') {
this.props.setPersistedState({navigationEvents: []});
@@ -78,16 +94,45 @@ export default class extends FlipperPlugin<State, {}, PersistedState> {
});
};
onFavorite = (uri: string) => {
this.setState({shouldShowSaveBookmarkDialog: true, saveBookmarkURI: uri});
};
addBookmark = (bookmark: Bookmark) => {
const newBookmark = {
uri: bookmark.uri,
commonName:
bookmark.commonName.length > 0 ? bookmark.commonName : bookmark.uri,
};
writeBookmarkToDB(newBookmark);
const newMapRef = this.state.bookmarks;
newMapRef.set(newBookmark.uri, newBookmark);
this.setState({bookmarks: newMapRef});
};
render() {
const {navigationEvents, bookmarks} = this.props.persistedState;
const {bookmarks, shouldShowSaveBookmarkDialog} = this.state;
const {navigationEvents} = this.props.persistedState;
return (
<ScrollableFlexColumn>
<SearchBar
bookmarks={bookmarks}
onNavigate={this.navigateTo}
onFavorite={(query: string) => {}}
onFavorite={this.onFavorite}
/>
<Timeline
bookmarks={bookmarks}
events={navigationEvents}
onNavigate={this.navigateTo}
onFavorite={this.onFavorite}
/>
<Timeline events={navigationEvents} onNavigate={this.navigateTo} />
<BookmarksSidebar bookmarks={bookmarks} onNavigate={this.navigateTo} />
<SaveBookmarkDialog
shouldShow={shouldShowSaveBookmarkDialog}
uri={this.state.saveBookmarkURI}
onHide={() => this.setState({shouldShowSaveBookmarkDialog: false})}
onSubmit={this.addBookmark}
/>
</ScrollableFlexColumn>
);
}