Added LocationsButton when Navigation Plugin is active
Summary: Here I've added the LocationsButton to the TitleBar in Flipper. This allows the user to navigate to saved bookmarks, or display the current page URI without ever opening the Navigation Plugin (Except to add bookmarks). The challenge of this diff was having a TitleBar child be controlled by a plugin. The LocationsButton pulls bookmarks straight from the database whenever a mouseDown event is called on the button. (The Electron popup menu does not respond to props changes, so the menu is opened on mouse up and getting the bookmarks from the database occurs on mouse down... This seems to work fine). The nav plugin on the Android side will now also send a welcome message alerting the app to created a persisted state for the navigation plugin, which shows the button in the TitleBar. Let me know if I can answer any questions. Reviewed By: danielbuechele Differential Revision: D16786330 fbshipit-source-id: afc95348d9b7ec4ee041f42bb4d022f58c6bb969
This commit is contained in:
committed by
Facebook Github Bot
parent
0f270c9f48
commit
1ae3b90019
@@ -8,27 +8,55 @@
|
||||
import {Button, styled} from 'flipper';
|
||||
import {connect} from 'react-redux';
|
||||
import React, {Component} from 'react';
|
||||
import {State} from '../reducers';
|
||||
|
||||
import {State as Store} from '../reducers';
|
||||
import {readBookmarksFromDB} from '../plugins/navigation/util/indexedDB.js';
|
||||
import {State as NavPluginState} from '../plugins/navigation/flow-types';
|
||||
import BaseDevice from '../devices/BaseDevice';
|
||||
import {State as PluginState} from 'src/reducers/pluginStates';
|
||||
|
||||
type OwnProps = {
|
||||
locations: Array<string>;
|
||||
selectedLocation?: string;
|
||||
type State = {
|
||||
bookmarks: Array<Bookmark>;
|
||||
};
|
||||
|
||||
type OwnProps = {};
|
||||
|
||||
type StateFromProps = {
|
||||
currentURI: string;
|
||||
selectedDevice: BaseDevice | null | undefined;
|
||||
};
|
||||
|
||||
type DispatchFromProps = {};
|
||||
|
||||
type Bookmark = {
|
||||
uri: string;
|
||||
commonName: string;
|
||||
};
|
||||
|
||||
const DropdownButton = styled(Button)({
|
||||
fontSize: 11,
|
||||
});
|
||||
|
||||
const shortenText = (text: String, MAX_CHARACTERS = 30): String => {
|
||||
if (text.length <= MAX_CHARACTERS) {
|
||||
return text;
|
||||
} else {
|
||||
return (
|
||||
text
|
||||
.split('')
|
||||
.slice(0, MAX_CHARACTERS)
|
||||
.join('') + '...'
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
type Props = OwnProps & StateFromProps & DispatchFromProps;
|
||||
class LocationsButton extends Component<Props> {
|
||||
class LocationsButton extends Component<Props, State> {
|
||||
state = {
|
||||
bookmarks: [],
|
||||
hasRetrievedBookmarks: false,
|
||||
retreivingBookmarks: false,
|
||||
};
|
||||
|
||||
goToLocation = (location: string) => {
|
||||
const {selectedDevice} = this.props;
|
||||
if (selectedDevice != null) {
|
||||
@@ -36,27 +64,64 @@ class LocationsButton extends Component<Props> {
|
||||
}
|
||||
};
|
||||
|
||||
updateBookmarks = () => {
|
||||
readBookmarksFromDB().then(bookmarksMap => {
|
||||
const bookmarks: Array<Bookmark> = [];
|
||||
bookmarksMap.forEach((bookmark: Bookmark) => {
|
||||
bookmarks.push(bookmark);
|
||||
});
|
||||
this.setState({bookmarks});
|
||||
});
|
||||
};
|
||||
|
||||
componentDidMount = () => {
|
||||
this.updateBookmarks();
|
||||
};
|
||||
|
||||
render() {
|
||||
const {locations, selectedLocation} = this.props;
|
||||
const {currentURI} = this.props;
|
||||
const {bookmarks} = this.state;
|
||||
return (
|
||||
<DropdownButton
|
||||
onMouseDown={this.updateBookmarks}
|
||||
compact={true}
|
||||
dropdown={locations.map(location => {
|
||||
return {
|
||||
click: () => {
|
||||
this.goToLocation(location);
|
||||
},
|
||||
label: location,
|
||||
};
|
||||
})}>
|
||||
{selectedLocation || '(none)'}
|
||||
dropdown={[
|
||||
{
|
||||
label: 'Bookmarks',
|
||||
enabled: false,
|
||||
},
|
||||
...bookmarks.map(bookmark => {
|
||||
return {
|
||||
click: () => {
|
||||
this.goToLocation(bookmark.uri);
|
||||
},
|
||||
label: shortenText(
|
||||
bookmark.commonName + ' - ' + bookmark.uri,
|
||||
100,
|
||||
),
|
||||
};
|
||||
}),
|
||||
]}>
|
||||
{(currentURI && shortenText(currentURI)) || '(none)'}
|
||||
</DropdownButton>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect<StateFromProps, DispatchFromProps, OwnProps, State>(
|
||||
({connections: {selectedDevice}}) => ({
|
||||
const mapStateFromPluginStatesToProps = (pluginStates: PluginState) => {
|
||||
const navPluginState: NavPluginState =
|
||||
pluginStates[
|
||||
Object.keys(pluginStates).find(key => /#Navigation$/.test(key))
|
||||
];
|
||||
const currentURI = navPluginState && navPluginState.currentURI;
|
||||
return {
|
||||
currentURI,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
|
||||
({connections: {selectedDevice}, pluginStates}) => ({
|
||||
selectedDevice,
|
||||
...mapStateFromPluginStatesToProps(pluginStates),
|
||||
}),
|
||||
)(LocationsButton);
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
colors,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
ButtonGroupChain,
|
||||
FlexRow,
|
||||
Spacer,
|
||||
styled,
|
||||
@@ -28,6 +29,7 @@ import {
|
||||
import {connect} from 'react-redux';
|
||||
import RatingButton from './RatingButton';
|
||||
import DevicesButton from './DevicesButton';
|
||||
import LocationsButton from './LocationsButton';
|
||||
import ScreenCaptureButtons from './ScreenCaptureButtons';
|
||||
import AutoUpdateVersion from './AutoUpdateVersion';
|
||||
import UpdateIndicator from './UpdateIndicator';
|
||||
@@ -78,6 +80,7 @@ type StateFromProps = {
|
||||
launcherMsg: LauncherMsg;
|
||||
flipperRating: number | null;
|
||||
share: ShareType | null | undefined;
|
||||
navPluginIsActive: boolean;
|
||||
};
|
||||
|
||||
const VersionText = styled(Text)({
|
||||
@@ -139,10 +142,18 @@ function statusMessageComponent(
|
||||
type Props = OwnProps & DispatchFromProps & StateFromProps;
|
||||
class TitleBar extends React.Component<Props, StateFromProps> {
|
||||
render() {
|
||||
const {share} = this.props;
|
||||
const {navPluginIsActive, share} = this.props;
|
||||
return (
|
||||
<AppTitleBar focused={this.props.windowIsFocused} className="toolbar">
|
||||
<DevicesButton />
|
||||
{navPluginIsActive ? (
|
||||
<ButtonGroupChain iconSize={14}>
|
||||
<DevicesButton />
|
||||
<LocationsButton />
|
||||
</ButtonGroupChain>
|
||||
) : (
|
||||
<DevicesButton />
|
||||
)}
|
||||
|
||||
<ScreenCaptureButtons />
|
||||
{statusMessageComponent(
|
||||
this.props.downloadingImportData,
|
||||
@@ -206,6 +217,7 @@ export default connect<StateFromProps, DispatchFromProps, OwnProps, State>(
|
||||
flipperRating,
|
||||
share,
|
||||
},
|
||||
pluginStates,
|
||||
}) => ({
|
||||
windowIsFocused,
|
||||
leftSidebarVisible,
|
||||
@@ -215,6 +227,9 @@ export default connect<StateFromProps, DispatchFromProps, OwnProps, State>(
|
||||
launcherMsg,
|
||||
flipperRating,
|
||||
share,
|
||||
navPluginIsActive: Object.keys(pluginStates).some(key =>
|
||||
/#Navigation$/.test(key),
|
||||
),
|
||||
}),
|
||||
{
|
||||
setActiveSheet,
|
||||
|
||||
@@ -13,6 +13,7 @@ import type {Bookmark, PersistedState, URI} from '../flow-types';
|
||||
|
||||
function constructPersistedStateMock(): PersistedState {
|
||||
return {
|
||||
currentURI: '',
|
||||
appMatchPatterns: [],
|
||||
appMatchPatternsProvider: new DefaultProvider(),
|
||||
bookmarksProvider: new DefaultProvider(),
|
||||
@@ -23,6 +24,7 @@ function constructPersistedStateMock(): PersistedState {
|
||||
|
||||
function constructPersistedStateMockWithEvents(): PersistedState {
|
||||
return {
|
||||
currentURI: '',
|
||||
appMatchPatterns: [],
|
||||
appMatchPatternsProvider: new DefaultProvider(),
|
||||
bookmarksProvider: new DefaultProvider(),
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
export type URI = string;
|
||||
|
||||
export type State = {|
|
||||
currentURI: string,
|
||||
shouldShowSaveBookmarkDialog: boolean,
|
||||
shouldShowURIErrorDialog: boolean,
|
||||
saveBookmarkURI: ?URI,
|
||||
@@ -22,6 +21,7 @@ export type PersistedState = {|
|
||||
bookmarksProvider: AutoCompleteProvider,
|
||||
appMatchPatterns: Array<AppMatchPattern>,
|
||||
appMatchPatternsProvider: AutoCompleteProvider,
|
||||
currentURI: string,
|
||||
|};
|
||||
|
||||
export type NavigationEvent = {|
|
||||
|
||||
@@ -44,6 +44,7 @@ export default class extends FlipperPlugin<State, {}, PersistedState> {
|
||||
static defaultPersistedState: PersistedState = {
|
||||
navigationEvents: [],
|
||||
bookmarks: new Map<string, Bookmark>(),
|
||||
currentURI: '',
|
||||
bookmarksProvider: new DefaultProvider(),
|
||||
appMatchPatterns: [],
|
||||
appMatchPatternsProvider: new DefaultProvider(),
|
||||
@@ -53,7 +54,6 @@ export default class extends FlipperPlugin<State, {}, PersistedState> {
|
||||
shouldShowSaveBookmarkDialog: false,
|
||||
saveBookmarkURI: null,
|
||||
shouldShowURIErrorDialog: false,
|
||||
currentURI: '',
|
||||
requiredParameters: [],
|
||||
};
|
||||
|
||||
@@ -66,6 +66,8 @@ export default class extends FlipperPlugin<State, {}, PersistedState> {
|
||||
case 'nav_event':
|
||||
return {
|
||||
...persistedState,
|
||||
currentURI:
|
||||
payload.uri == null ? persistedState.currentURI : payload.uri,
|
||||
navigationEvents: [
|
||||
{
|
||||
uri: payload.uri === undefined ? null : payload.uri,
|
||||
@@ -110,7 +112,7 @@ export default class extends FlipperPlugin<State, {}, PersistedState> {
|
||||
};
|
||||
|
||||
navigateTo = (query: string) => {
|
||||
this.setState({currentURI: query});
|
||||
this.props.setPersistedState({currentURI: query});
|
||||
const requiredParameters = getRequiredParameters(query);
|
||||
if (requiredParameters.length === 0) {
|
||||
this.getDevice().then(device => {
|
||||
@@ -156,7 +158,6 @@ export default class extends FlipperPlugin<State, {}, PersistedState> {
|
||||
|
||||
render() {
|
||||
const {
|
||||
currentURI,
|
||||
saveBookmarkURI,
|
||||
shouldShowSaveBookmarkDialog,
|
||||
shouldShowURIErrorDialog,
|
||||
@@ -165,6 +166,7 @@ export default class extends FlipperPlugin<State, {}, PersistedState> {
|
||||
const {
|
||||
bookmarks,
|
||||
bookmarksProvider,
|
||||
currentURI,
|
||||
appMatchPatternsProvider,
|
||||
navigationEvents,
|
||||
} = this.props.persistedState;
|
||||
|
||||
@@ -163,6 +163,10 @@ const Icon = styled(Glyph)(({hasText}) => ({
|
||||
}));
|
||||
|
||||
type Props = {
|
||||
/**
|
||||
* onMouseUp handler.
|
||||
*/
|
||||
onMouseDown?: (event: SyntheticMouseEvent<>) => any,
|
||||
/**
|
||||
* onClick handler.
|
||||
*/
|
||||
@@ -248,7 +252,12 @@ class Button extends React.Component<
|
||||
|
||||
_ref = React.createRef();
|
||||
|
||||
onMouseDown = () => this.setState({active: true, wasClosed: false});
|
||||
onMouseDown = (e: SyntheticMouseEvent<>) => {
|
||||
this.setState({active: true, wasClosed: false});
|
||||
if (this.props.onMouseDown != null) {
|
||||
this.props.onMouseDown(e);
|
||||
}
|
||||
};
|
||||
onMouseUp = () => {
|
||||
if (this.props.disabled === true) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user