Convert Navigation plugin to Sandy
Summary: Converted the Navigation plugin to Sandy, and updated Locations bookmark accordingly. This is a prerequisite step of supporting the bookmarkswidgetin the new AppInspect tab. Updated LocationsButton accordingly, and overal simplified implementation a bit; locationsbutton now reuses the logic of the NavigationPlugin, rather than reimplemting it. This reduces code duplication and also makes sure the state between plugin and location button stays in sync. Made sure that search providers are derived and cached rather than stored, again simplifying logic That being said, the navigation plugin is buggy, but all these things failed before this diff as well: * No events happening when using iOS, despite the plugin being enabled. But these seems to be a long time know issue, looks like it was never implemented * Not sure if the parameterized bookmarks is working correctly * screenshots not always happening at the right time (but fixed a race condition where the wrong bookmark might get updated) * Locations button doesn't show up if the navigation plugin is supported but not enabled (will try to fix in next diff) Would be great if bnelo12 could do some exploratory testing to verify what ought to be working, but currently isn't. Reviewed By: cekkaewnumchai Differential Revision: D24860757 fbshipit-source-id: e4b56072de8c42af2ada0f5bb022cb9f8c04bb47
This commit is contained in:
committed by
Facebook GitHub Bot
parent
ba541e76dc
commit
661bea1d5b
@@ -7,43 +7,19 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Button, styled} from '../ui';
|
import React, {useCallback, useEffect} from 'react';
|
||||||
import {connect} from 'react-redux';
|
|
||||||
import React, {Component} from 'react';
|
|
||||||
import {State as Store} from '../reducers';
|
|
||||||
// TODO T71355623
|
|
||||||
// eslint-disable-next-line flipper/no-relative-imports-across-packages
|
|
||||||
import {
|
|
||||||
readBookmarksFromDB,
|
|
||||||
writeBookmarkToDB,
|
|
||||||
} from '../../../plugins/navigation/util/indexedDB';
|
|
||||||
// TODO T71355623
|
|
||||||
// eslint-disable-next-line flipper/no-relative-imports-across-packages
|
|
||||||
import {PersistedState as NavPluginState} from '../../../plugins/navigation/types';
|
|
||||||
import BaseDevice from '../devices/BaseDevice';
|
|
||||||
import {State as PluginState} from '../reducers/pluginStates';
|
|
||||||
import {platform} from 'os';
|
import {platform} from 'os';
|
||||||
import {getPluginKey} from '../utils/pluginUtils';
|
import {useValue} from 'flipper-plugin';
|
||||||
|
import {Button, styled} from '../ui';
|
||||||
|
import {useStore} from '../utils/useStore';
|
||||||
|
import {useMemoize} from '../utils/useMemoize';
|
||||||
|
import {State} from '../reducers';
|
||||||
|
|
||||||
type State = {
|
// TODO T71355623
|
||||||
bookmarks: Array<Bookmark>;
|
// eslint-disable-next-line flipper/no-relative-imports-across-packages
|
||||||
hasRetrievedBookmarks: boolean;
|
import type {NavigationPlugin} from '../../../plugins/navigation/index';
|
||||||
retreivingBookmarks: boolean;
|
// eslint-disable-next-line flipper/no-relative-imports-across-packages
|
||||||
};
|
import type {Bookmark} from '../../../plugins/navigation/types';
|
||||||
|
|
||||||
type OwnProps = {};
|
|
||||||
|
|
||||||
type StateFromProps = {
|
|
||||||
currentURI: string | undefined;
|
|
||||||
selectedDevice: BaseDevice | null | undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
type DispatchFromProps = {};
|
|
||||||
|
|
||||||
type Bookmark = {
|
|
||||||
uri: string;
|
|
||||||
commonName: string | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const DropdownButton = styled(Button)({
|
const DropdownButton = styled(Button)({
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
@@ -57,127 +33,105 @@ const shortenText = (text: string, MAX_CHARACTERS = 30): string => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = OwnProps & StateFromProps & DispatchFromProps;
|
const NAVIGATION_PLUGIN_ID = 'Navigation';
|
||||||
class LocationsButton extends Component<Props, State> {
|
|
||||||
state: State = {
|
|
||||||
bookmarks: [],
|
|
||||||
hasRetrievedBookmarks: false,
|
|
||||||
retreivingBookmarks: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount() {
|
export function LocationsButton() {
|
||||||
document.addEventListener('keydown', this.keyDown);
|
const navPlugin = useStore(navPluginStateSelector);
|
||||||
this.updateBookmarks();
|
return navPlugin ? (
|
||||||
}
|
<ActiveLocationsButton navPlugin={navPlugin} />
|
||||||
|
) : (
|
||||||
componentWillUnmount() {
|
<DropdownButton compact={true}>(none)</DropdownButton>
|
||||||
document.removeEventListener('keydown', this.keyDown);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
goToLocation = (location: string) => {
|
|
||||||
const {selectedDevice} = this.props;
|
|
||||||
if (selectedDevice != null) {
|
|
||||||
selectedDevice.navigateToLocation(location);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
keyDown = (e: KeyboardEvent) => {
|
|
||||||
if (
|
|
||||||
((platform() === 'darwin' && e.metaKey) ||
|
|
||||||
(platform() !== 'darwin' && e.ctrlKey)) &&
|
|
||||||
/^\d$/.test(e.key) &&
|
|
||||||
this.state.bookmarks.length >= parseInt(e.key, 10)
|
|
||||||
) {
|
|
||||||
this.goToLocation(this.state.bookmarks[parseInt(e.key, 10) - 1].uri);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
updateBookmarks = () => {
|
|
||||||
readBookmarksFromDB().then((bookmarksMap) => {
|
|
||||||
const bookmarks: Array<Bookmark> = [];
|
|
||||||
bookmarksMap.forEach((bookmark: Bookmark) => {
|
|
||||||
bookmarks.push(bookmark);
|
|
||||||
});
|
|
||||||
this.setState({bookmarks});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {currentURI} = this.props;
|
|
||||||
const {bookmarks} = this.state;
|
|
||||||
|
|
||||||
const dropdown: any[] = [
|
|
||||||
{
|
|
||||||
label: 'Bookmarks',
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
...bookmarks.map((bookmark, i) => {
|
|
||||||
return {
|
|
||||||
click: () => {
|
|
||||||
this.goToLocation(bookmark.uri);
|
|
||||||
},
|
|
||||||
accelerator: i < 9 ? `CmdOrCtrl+${i + 1}` : undefined,
|
|
||||||
label: shortenText(
|
|
||||||
(bookmark.commonName ? bookmark.commonName + ' - ' : '') +
|
|
||||||
bookmark.uri,
|
|
||||||
100,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
|
|
||||||
if (currentURI) {
|
|
||||||
dropdown.push(
|
|
||||||
{type: 'separator'},
|
|
||||||
{
|
|
||||||
label: 'Bookmark Current Location',
|
|
||||||
click: async () => {
|
|
||||||
await writeBookmarkToDB({
|
|
||||||
uri: currentURI,
|
|
||||||
commonName: null,
|
|
||||||
});
|
|
||||||
this.updateBookmarks();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DropdownButton
|
|
||||||
onMouseDown={this.updateBookmarks}
|
|
||||||
compact={true}
|
|
||||||
dropdown={dropdown}>
|
|
||||||
{(currentURI && shortenText(currentURI)) || '(none)'}
|
|
||||||
</DropdownButton>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateFromPluginStatesToProps = (
|
function ActiveLocationsButton({navPlugin}: {navPlugin: NavigationPlugin}) {
|
||||||
pluginStates: PluginState,
|
const currentURI = useValue(navPlugin.currentURI);
|
||||||
selectedDevice: BaseDevice | null,
|
const bookmarks = useValue(navPlugin.bookmarks);
|
||||||
selectedApp: string | null,
|
|
||||||
) => {
|
|
||||||
const pluginKey = getPluginKey(selectedApp, selectedDevice, 'Navigation');
|
|
||||||
let currentURI: string | undefined;
|
|
||||||
if (pluginKey) {
|
|
||||||
const navPluginState = pluginStates[pluginKey] as
|
|
||||||
| NavPluginState
|
|
||||||
| undefined;
|
|
||||||
currentURI = navPluginState && navPluginState.currentURI;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
currentURI,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
|
const keyDown = useCallback(
|
||||||
({connections: {selectedDevice, selectedApp}, pluginStates}) => ({
|
(e: KeyboardEvent) => {
|
||||||
selectedDevice,
|
if (
|
||||||
...mapStateFromPluginStatesToProps(
|
((platform() === 'darwin' && e.metaKey) ||
|
||||||
pluginStates,
|
(platform() !== 'darwin' && e.ctrlKey)) &&
|
||||||
selectedDevice,
|
/^\d$/.test(e.key) &&
|
||||||
selectedApp,
|
bookmarks.size >= parseInt(e.key, 10)
|
||||||
),
|
) {
|
||||||
}),
|
navPlugin.navigateTo(
|
||||||
)(LocationsButton);
|
Array.from(bookmarks.values())[parseInt(e.key, 10) - 1].uri,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[bookmarks, navPlugin],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener('keydown', keyDown);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('keydown', keyDown);
|
||||||
|
};
|
||||||
|
}, [keyDown]);
|
||||||
|
|
||||||
|
const dropdown = useMemoize(computeBookmarkDropdown, [
|
||||||
|
navPlugin,
|
||||||
|
bookmarks,
|
||||||
|
currentURI,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownButton compact={true} dropdown={dropdown}>
|
||||||
|
{(currentURI && shortenText(currentURI)) || '(none)'}
|
||||||
|
</DropdownButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function navPluginStateSelector(state: State) {
|
||||||
|
const {selectedApp, clients} = state.connections;
|
||||||
|
if (!selectedApp) return undefined;
|
||||||
|
const client = clients.find((client) => client.id === selectedApp);
|
||||||
|
if (!client) return undefined;
|
||||||
|
return client.sandyPluginStates.get(NAVIGATION_PLUGIN_ID)?.instanceApi as
|
||||||
|
| undefined
|
||||||
|
| NavigationPlugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeBookmarkDropdown(
|
||||||
|
navPlugin: NavigationPlugin,
|
||||||
|
bookmarks: Map<string, Bookmark>,
|
||||||
|
currentURI: string,
|
||||||
|
) {
|
||||||
|
const dropdown: Electron.MenuItemConstructorOptions[] = [
|
||||||
|
{
|
||||||
|
label: 'Bookmarks',
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
...Array.from(bookmarks.values()).map((bookmark, i) => {
|
||||||
|
return {
|
||||||
|
click: () => {
|
||||||
|
navPlugin.navigateTo(bookmark.uri);
|
||||||
|
},
|
||||||
|
accelerator: i < 9 ? `CmdOrCtrl+${i + 1}` : undefined,
|
||||||
|
label: shortenText(
|
||||||
|
(bookmark.commonName ? bookmark.commonName + ' - ' : '') +
|
||||||
|
bookmark.uri,
|
||||||
|
100,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (currentURI) {
|
||||||
|
dropdown.push(
|
||||||
|
{type: 'separator'},
|
||||||
|
{
|
||||||
|
label: 'Bookmark Current Location',
|
||||||
|
click: () => {
|
||||||
|
navPlugin.addBookmark({
|
||||||
|
uri: currentURI,
|
||||||
|
commonName: null,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return dropdown;
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import {
|
|||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
import RatingButton from './RatingButton';
|
import RatingButton from './RatingButton';
|
||||||
import DevicesButton from './DevicesButton';
|
import DevicesButton from './DevicesButton';
|
||||||
import LocationsButton from './LocationsButton';
|
import {LocationsButton} from './LocationsButton';
|
||||||
import ScreenCaptureButtons from './ScreenCaptureButtons';
|
import ScreenCaptureButtons from './ScreenCaptureButtons';
|
||||||
import AutoUpdateVersion from './AutoUpdateVersion';
|
import AutoUpdateVersion from './AutoUpdateVersion';
|
||||||
import UpdateIndicator from './UpdateIndicator';
|
import UpdateIndicator from './UpdateIndicator';
|
||||||
@@ -45,7 +45,7 @@ import {reportUsage} from '../utils/metrics';
|
|||||||
import FpsGraph from './FpsGraph';
|
import FpsGraph from './FpsGraph';
|
||||||
import NetworkGraph from './NetworkGraph';
|
import NetworkGraph from './NetworkGraph';
|
||||||
import MetroButton from './MetroButton';
|
import MetroButton from './MetroButton';
|
||||||
import {getPluginKey} from '../utils/pluginUtils';
|
import {navPluginStateSelector} from './LocationsButton';
|
||||||
|
|
||||||
const AppTitleBar = styled(FlexRow)<{focused?: boolean}>(({focused}) => ({
|
const AppTitleBar = styled(FlexRow)<{focused?: boolean}>(({focused}) => ({
|
||||||
userSelect: 'none',
|
userSelect: 'none',
|
||||||
@@ -228,25 +228,19 @@ class TitleBar extends React.Component<Props, StateFromProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default connect<StateFromProps, DispatchFromProps, OwnProps, State>(
|
export default connect<StateFromProps, DispatchFromProps, OwnProps, State>(
|
||||||
({
|
(state) => {
|
||||||
application: {
|
const {
|
||||||
windowIsFocused,
|
application: {
|
||||||
leftSidebarVisible,
|
windowIsFocused,
|
||||||
rightSidebarVisible,
|
leftSidebarVisible,
|
||||||
rightSidebarAvailable,
|
rightSidebarVisible,
|
||||||
downloadingImportData,
|
rightSidebarAvailable,
|
||||||
launcherMsg,
|
downloadingImportData,
|
||||||
share,
|
launcherMsg,
|
||||||
},
|
share,
|
||||||
connections: {selectedDevice, selectedApp},
|
},
|
||||||
pluginStates,
|
} = state;
|
||||||
}) => {
|
const navPluginIsActive = !!navPluginStateSelector(state);
|
||||||
const navigationPluginKey = getPluginKey(
|
|
||||||
selectedApp,
|
|
||||||
selectedDevice,
|
|
||||||
'Navigation',
|
|
||||||
);
|
|
||||||
const navPluginIsActive = !!pluginStates[navigationPluginKey];
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
windowIsFocused,
|
windowIsFocused,
|
||||||
|
|||||||
@@ -7,10 +7,12 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {produce, Draft} from 'immer';
|
import {produce, Draft, enableMapSet} from 'immer';
|
||||||
import {useState, useEffect} from 'react';
|
import {useState, useEffect} from 'react';
|
||||||
import {getCurrentPluginInstance} from '../plugin/PluginBase';
|
import {getCurrentPluginInstance} from '../plugin/PluginBase';
|
||||||
|
|
||||||
|
enableMapSet();
|
||||||
|
|
||||||
export type Atom<T> = {
|
export type Atom<T> = {
|
||||||
get(): T;
|
get(): T;
|
||||||
set(newValue: T): void;
|
set(newValue: T): void;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
* @flow strict-local
|
* @flow strict-local
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {FlipperPlugin, FlexColumn, bufferToBlob} from 'flipper';
|
import {bufferToBlob} from 'flipper';
|
||||||
import {
|
import {
|
||||||
BookmarksSidebar,
|
BookmarksSidebar,
|
||||||
SaveBookmarkDialog,
|
SaveBookmarkDialog,
|
||||||
@@ -17,217 +17,227 @@ import {
|
|||||||
RequiredParametersDialog,
|
RequiredParametersDialog,
|
||||||
} from './components';
|
} from './components';
|
||||||
import {
|
import {
|
||||||
removeBookmark,
|
removeBookmarkFromDB,
|
||||||
readBookmarksFromDB,
|
readBookmarksFromDB,
|
||||||
writeBookmarkToDB,
|
writeBookmarkToDB,
|
||||||
} from './util/indexedDB';
|
} from './util/indexedDB';
|
||||||
import {
|
import {
|
||||||
appMatchPatternsToAutoCompleteProvider,
|
appMatchPatternsToAutoCompleteProvider,
|
||||||
bookmarksToAutoCompleteProvider,
|
bookmarksToAutoCompleteProvider,
|
||||||
DefaultProvider,
|
|
||||||
} from './util/autoCompleteProvider';
|
} from './util/autoCompleteProvider';
|
||||||
import {getAppMatchPatterns} from './util/appMatchPatterns';
|
import {getAppMatchPatterns} from './util/appMatchPatterns';
|
||||||
import {getRequiredParameters, filterOptionalParameters} from './util/uri';
|
import {getRequiredParameters, filterOptionalParameters} from './util/uri';
|
||||||
import {
|
import {
|
||||||
State,
|
|
||||||
PersistedState,
|
|
||||||
Bookmark,
|
Bookmark,
|
||||||
NavigationEvent,
|
NavigationEvent,
|
||||||
AppMatchPattern,
|
AppMatchPattern,
|
||||||
|
URI,
|
||||||
|
RawNavigationEvent,
|
||||||
} from './types';
|
} from './types';
|
||||||
import React from 'react';
|
import React, {useMemo} from 'react';
|
||||||
|
import {
|
||||||
|
PluginClient,
|
||||||
|
createState,
|
||||||
|
useValue,
|
||||||
|
usePlugin,
|
||||||
|
Layout,
|
||||||
|
} from 'flipper-plugin';
|
||||||
|
|
||||||
export default class extends FlipperPlugin<State, any, PersistedState> {
|
export type State = {
|
||||||
static defaultPersistedState = {
|
shouldShowSaveBookmarkDialog: boolean;
|
||||||
navigationEvents: [],
|
shouldShowURIErrorDialog: boolean;
|
||||||
bookmarks: new Map<string, Bookmark>(),
|
saveBookmarkURI: URI | null;
|
||||||
currentURI: '',
|
requiredParameters: Array<string>;
|
||||||
bookmarksProvider: DefaultProvider(),
|
};
|
||||||
appMatchPatterns: [],
|
|
||||||
appMatchPatternsProvider: DefaultProvider(),
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
type Events = {
|
||||||
shouldShowSaveBookmarkDialog: false,
|
nav_event: RawNavigationEvent;
|
||||||
saveBookmarkURI: null as string | null,
|
};
|
||||||
shouldShowURIErrorDialog: false,
|
|
||||||
requiredParameters: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
static persistedStateReducer = (
|
type Methods = {
|
||||||
persistedState: PersistedState,
|
navigate_to(params: {url: string}): Promise<void>;
|
||||||
method: string,
|
};
|
||||||
payload: any,
|
|
||||||
) => {
|
|
||||||
switch (method) {
|
|
||||||
case 'nav_event':
|
|
||||||
const navigationEvent: NavigationEvent = {
|
|
||||||
uri:
|
|
||||||
payload.uri === undefined ? null : decodeURIComponent(payload.uri),
|
|
||||||
date: new Date(payload.date) || new Date(),
|
|
||||||
className: payload.class === undefined ? null : payload.class,
|
|
||||||
screenshot: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
export type NavigationPlugin = ReturnType<typeof plugin>;
|
||||||
...persistedState,
|
|
||||||
currentURI:
|
|
||||||
navigationEvent.uri == null
|
|
||||||
? persistedState.currentURI
|
|
||||||
: decodeURIComponent(navigationEvent.uri),
|
|
||||||
navigationEvents: [
|
|
||||||
navigationEvent,
|
|
||||||
...persistedState.navigationEvents,
|
|
||||||
],
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
return persistedState;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
subscribeToNavigationEvents = () => {
|
export function plugin(client: PluginClient<Events, Methods>) {
|
||||||
this.client.subscribe('nav_event', () =>
|
const bookmarks = createState(new Map<URI, Bookmark>(), {
|
||||||
// Wait for view to render and then take a screenshot
|
persist: 'bookmarks',
|
||||||
setTimeout(async () => {
|
});
|
||||||
const device = await this.getDevice();
|
const navigationEvents = createState<NavigationEvent[]>([], {
|
||||||
const screenshot = await device.screenshot();
|
persist: 'navigationEvents',
|
||||||
const blobURL = URL.createObjectURL(bufferToBlob(screenshot));
|
});
|
||||||
this.props.persistedState.navigationEvents[0].screenshot = blobURL;
|
const appMatchPatterns = createState<AppMatchPattern[]>([], {
|
||||||
this.props.setPersistedState({...this.props.persistedState});
|
persist: 'appMatchPatterns',
|
||||||
}, 1000),
|
});
|
||||||
);
|
const currentURI = createState('');
|
||||||
};
|
const shouldShowURIErrorDialog = createState(false);
|
||||||
|
const requiredParameters = createState<string[]>([]);
|
||||||
|
const shouldShowSaveBookmarkDialog = createState(false);
|
||||||
|
const saveBookmarkURI = createState<null | string>(null);
|
||||||
|
|
||||||
componentDidMount() {
|
client.onMessage('nav_event', async (payload) => {
|
||||||
const {selectedApp} = this.props;
|
const navigationEvent: NavigationEvent = {
|
||||||
this.subscribeToNavigationEvents();
|
uri: payload.uri === undefined ? null : decodeURIComponent(payload.uri),
|
||||||
this.getDevice()
|
date: payload.date ? new Date(payload.date) : new Date(),
|
||||||
.then((device) => getAppMatchPatterns(selectedApp, device))
|
className: payload.class === undefined ? null : payload.class,
|
||||||
.then((patterns: Array<AppMatchPattern>) => {
|
screenshot: null,
|
||||||
this.props.setPersistedState({
|
};
|
||||||
appMatchPatterns: patterns,
|
|
||||||
appMatchPatternsProvider: appMatchPatternsToAutoCompleteProvider(
|
if (navigationEvent.uri) currentURI.set(navigationEvent.uri);
|
||||||
patterns,
|
|
||||||
),
|
navigationEvents.update((draft) => {
|
||||||
});
|
draft.unshift(navigationEvent);
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
/* Silently fail here. */
|
|
||||||
});
|
|
||||||
readBookmarksFromDB().then((bookmarks) => {
|
|
||||||
this.props.setPersistedState({
|
|
||||||
bookmarks: bookmarks,
|
|
||||||
bookmarksProvider: bookmarksToAutoCompleteProvider(bookmarks),
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
navigateTo = async (query: string) => {
|
const screenshot: Buffer = await client.device.realDevice.screenshot();
|
||||||
|
const blobURL = URL.createObjectURL(bufferToBlob(screenshot));
|
||||||
|
// this process is async, make sure we update the correct one..
|
||||||
|
const navigationEventIndex = navigationEvents
|
||||||
|
.get()
|
||||||
|
.indexOf(navigationEvent);
|
||||||
|
if (navigationEventIndex !== -1) {
|
||||||
|
navigationEvents.update((draft) => {
|
||||||
|
draft[navigationEventIndex].screenshot = blobURL;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
getAppMatchPatterns(client.appId, client.device.realDevice)
|
||||||
|
.then((patterns) => {
|
||||||
|
appMatchPatterns.set(patterns);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error('[Navigation] Failed to find appMatchPatterns', e);
|
||||||
|
});
|
||||||
|
|
||||||
|
readBookmarksFromDB().then((bookmarksData) => {
|
||||||
|
bookmarks.set(bookmarksData);
|
||||||
|
});
|
||||||
|
|
||||||
|
function navigateTo(query: string) {
|
||||||
const filteredQuery = filterOptionalParameters(query);
|
const filteredQuery = filterOptionalParameters(query);
|
||||||
this.props.setPersistedState({currentURI: filteredQuery});
|
currentURI.set(filteredQuery);
|
||||||
const requiredParameters = getRequiredParameters(filteredQuery);
|
const params = getRequiredParameters(filteredQuery);
|
||||||
if (requiredParameters.length === 0) {
|
if (params.length === 0) {
|
||||||
const device = await this.getDevice();
|
if (client.appName === 'Facebook' && client.device.os === 'iOS') {
|
||||||
if (this.realClient.query.app === 'Facebook' && device.os === 'iOS') {
|
|
||||||
// use custom navigate_to event for Wilde
|
// use custom navigate_to event for Wilde
|
||||||
this.client.send('navigate_to', {
|
client.send('navigate_to', {
|
||||||
url: filterOptionalParameters(filteredQuery),
|
url: filterOptionalParameters(filteredQuery),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
device.navigateToLocation(filterOptionalParameters(filteredQuery));
|
client.device.realDevice.navigateToLocation(
|
||||||
|
filterOptionalParameters(filteredQuery),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
requiredParameters.set(params);
|
||||||
requiredParameters,
|
shouldShowURIErrorDialog.set(true);
|
||||||
shouldShowURIErrorDialog: true,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
onFavorite = (uri: string) => {
|
function onFavorite(uri: string) {
|
||||||
this.setState({shouldShowSaveBookmarkDialog: true, saveBookmarkURI: uri});
|
// TODO: why does this need a dialog?
|
||||||
};
|
shouldShowSaveBookmarkDialog.set(true);
|
||||||
|
saveBookmarkURI.set(uri);
|
||||||
|
}
|
||||||
|
|
||||||
addBookmark = (bookmark: Bookmark) => {
|
function addBookmark(bookmark: Bookmark) {
|
||||||
const newBookmark = {
|
const newBookmark = {
|
||||||
uri: bookmark.uri,
|
uri: bookmark.uri,
|
||||||
commonName: bookmark.commonName,
|
commonName: bookmark.commonName,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bookmarks.update((draft) => {
|
||||||
|
draft.set(newBookmark.uri, newBookmark);
|
||||||
|
});
|
||||||
writeBookmarkToDB(newBookmark);
|
writeBookmarkToDB(newBookmark);
|
||||||
const newMapRef = this.props.persistedState.bookmarks;
|
|
||||||
newMapRef.set(newBookmark.uri, newBookmark);
|
|
||||||
this.props.setPersistedState({
|
|
||||||
bookmarks: newMapRef,
|
|
||||||
bookmarksProvider: bookmarksToAutoCompleteProvider(newMapRef),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
removeBookmark = (uri: string) => {
|
|
||||||
removeBookmark(uri);
|
|
||||||
const newMapRef = this.props.persistedState.bookmarks;
|
|
||||||
newMapRef.delete(uri);
|
|
||||||
this.props.setPersistedState({
|
|
||||||
bookmarks: newMapRef,
|
|
||||||
bookmarksProvider: bookmarksToAutoCompleteProvider(newMapRef),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
saveBookmarkURI,
|
|
||||||
shouldShowSaveBookmarkDialog,
|
|
||||||
shouldShowURIErrorDialog,
|
|
||||||
requiredParameters,
|
|
||||||
} = this.state;
|
|
||||||
const {
|
|
||||||
bookmarks,
|
|
||||||
bookmarksProvider,
|
|
||||||
currentURI,
|
|
||||||
appMatchPatternsProvider,
|
|
||||||
navigationEvents,
|
|
||||||
} = this.props.persistedState;
|
|
||||||
const autoCompleteProviders = [bookmarksProvider, appMatchPatternsProvider];
|
|
||||||
return (
|
|
||||||
<FlexColumn grow>
|
|
||||||
<SearchBar
|
|
||||||
providers={autoCompleteProviders}
|
|
||||||
bookmarks={bookmarks}
|
|
||||||
onNavigate={this.navigateTo}
|
|
||||||
onFavorite={this.onFavorite}
|
|
||||||
uriFromAbove={currentURI}
|
|
||||||
/>
|
|
||||||
<Timeline
|
|
||||||
bookmarks={bookmarks}
|
|
||||||
events={navigationEvents}
|
|
||||||
onNavigate={this.navigateTo}
|
|
||||||
onFavorite={this.onFavorite}
|
|
||||||
/>
|
|
||||||
<BookmarksSidebar
|
|
||||||
bookmarks={bookmarks}
|
|
||||||
onRemove={this.removeBookmark}
|
|
||||||
onNavigate={this.navigateTo}
|
|
||||||
/>
|
|
||||||
<SaveBookmarkDialog
|
|
||||||
shouldShow={shouldShowSaveBookmarkDialog}
|
|
||||||
uri={saveBookmarkURI}
|
|
||||||
onHide={() => this.setState({shouldShowSaveBookmarkDialog: false})}
|
|
||||||
edit={
|
|
||||||
saveBookmarkURI != null ? bookmarks.has(saveBookmarkURI) : false
|
|
||||||
}
|
|
||||||
onSubmit={this.addBookmark}
|
|
||||||
onRemove={this.removeBookmark}
|
|
||||||
/>
|
|
||||||
<RequiredParametersDialog
|
|
||||||
shouldShow={shouldShowURIErrorDialog}
|
|
||||||
onHide={() => this.setState({shouldShowURIErrorDialog: false})}
|
|
||||||
uri={currentURI}
|
|
||||||
requiredParameters={requiredParameters}
|
|
||||||
onSubmit={this.navigateTo}
|
|
||||||
/>
|
|
||||||
</FlexColumn>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeBookmark(uri: string) {
|
||||||
|
bookmarks.update((draft) => {
|
||||||
|
draft.delete(uri);
|
||||||
|
});
|
||||||
|
removeBookmarkFromDB(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
navigateTo,
|
||||||
|
onFavorite,
|
||||||
|
addBookmark,
|
||||||
|
removeBookmark,
|
||||||
|
bookmarks,
|
||||||
|
saveBookmarkURI,
|
||||||
|
shouldShowSaveBookmarkDialog,
|
||||||
|
shouldShowURIErrorDialog,
|
||||||
|
requiredParameters,
|
||||||
|
appMatchPatterns,
|
||||||
|
navigationEvents,
|
||||||
|
currentURI,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Component() {
|
||||||
|
const instance = usePlugin(plugin);
|
||||||
|
const bookmarks = useValue(instance.bookmarks);
|
||||||
|
const appMatchPatterns = useValue(instance.appMatchPatterns);
|
||||||
|
const saveBookmarkURI = useValue(instance.saveBookmarkURI);
|
||||||
|
const shouldShowSaveBookmarkDialog = useValue(
|
||||||
|
instance.shouldShowSaveBookmarkDialog,
|
||||||
|
);
|
||||||
|
const shouldShowURIErrorDialog = useValue(instance.shouldShowURIErrorDialog);
|
||||||
|
const requiredParameters = useValue(instance.requiredParameters);
|
||||||
|
const currentURI = useValue(instance.currentURI);
|
||||||
|
const navigationEvents = useValue(instance.navigationEvents);
|
||||||
|
|
||||||
|
const autoCompleteProviders = useMemo(
|
||||||
|
() => [
|
||||||
|
bookmarksToAutoCompleteProvider(bookmarks),
|
||||||
|
appMatchPatternsToAutoCompleteProvider(appMatchPatterns),
|
||||||
|
],
|
||||||
|
[bookmarks, appMatchPatterns],
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Layout.Container>
|
||||||
|
<SearchBar
|
||||||
|
providers={autoCompleteProviders}
|
||||||
|
bookmarks={bookmarks}
|
||||||
|
onNavigate={instance.navigateTo}
|
||||||
|
onFavorite={instance.onFavorite}
|
||||||
|
uriFromAbove={currentURI}
|
||||||
|
/>
|
||||||
|
<Timeline
|
||||||
|
bookmarks={bookmarks}
|
||||||
|
events={navigationEvents}
|
||||||
|
onNavigate={instance.navigateTo}
|
||||||
|
onFavorite={instance.onFavorite}
|
||||||
|
/>
|
||||||
|
<BookmarksSidebar
|
||||||
|
bookmarks={bookmarks}
|
||||||
|
onRemove={instance.removeBookmark}
|
||||||
|
onNavigate={instance.navigateTo}
|
||||||
|
/>
|
||||||
|
<SaveBookmarkDialog
|
||||||
|
shouldShow={shouldShowSaveBookmarkDialog}
|
||||||
|
uri={saveBookmarkURI}
|
||||||
|
onHide={() => {
|
||||||
|
instance.shouldShowSaveBookmarkDialog.set(false);
|
||||||
|
}}
|
||||||
|
edit={saveBookmarkURI != null ? bookmarks.has(saveBookmarkURI) : false}
|
||||||
|
onSubmit={instance.addBookmark}
|
||||||
|
onRemove={instance.removeBookmark}
|
||||||
|
/>
|
||||||
|
<RequiredParametersDialog
|
||||||
|
shouldShow={shouldShowURIErrorDialog}
|
||||||
|
onHide={() => {
|
||||||
|
instance.shouldShowURIErrorDialog.set(false);
|
||||||
|
}}
|
||||||
|
uri={currentURI}
|
||||||
|
requiredParameters={requiredParameters}
|
||||||
|
onSubmit={instance.navigateTo}
|
||||||
|
/>
|
||||||
|
</Layout.Container>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* @scarf-info: do not remove, more info: https://fburl.com/scarf */
|
/* @scarf-info: do not remove, more info: https://fburl.com/scarf */
|
||||||
|
|||||||
@@ -13,5 +13,8 @@
|
|||||||
"icon": "directions",
|
"icon": "directions",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"email": "beneloca@fb.com"
|
"email": "beneloca@fb.com"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"flipper-plugin": "0.64.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,20 +9,11 @@
|
|||||||
|
|
||||||
export type URI = string;
|
export type URI = string;
|
||||||
|
|
||||||
export type State = {
|
export type RawNavigationEvent = {
|
||||||
shouldShowSaveBookmarkDialog: boolean;
|
date: string | undefined;
|
||||||
shouldShowURIErrorDialog: boolean;
|
uri: URI | undefined;
|
||||||
saveBookmarkURI: URI | null;
|
class: string | undefined;
|
||||||
requiredParameters: Array<string>;
|
screenshot: string | undefined;
|
||||||
};
|
|
||||||
|
|
||||||
export type PersistedState = {
|
|
||||||
bookmarks: Map<URI, Bookmark>;
|
|
||||||
navigationEvents: Array<NavigationEvent>;
|
|
||||||
bookmarksProvider: AutoCompleteProvider;
|
|
||||||
appMatchPatterns: Array<AppMatchPattern>;
|
|
||||||
appMatchPatternsProvider: AutoCompleteProvider;
|
|
||||||
currentURI: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NavigationEvent = {
|
export type NavigationEvent = {
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ export const readBookmarksFromDB: () => Promise<Map<string, Bookmark>> = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const removeBookmark: (uri: string) => Promise<void> = (uri) => {
|
export const removeBookmarkFromDB: (uri: string) => Promise<void> = (uri) => {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
openNavigationPluginDB()
|
openNavigationPluginDB()
|
||||||
.then((db: IDBDatabase) => {
|
.then((db: IDBDatabase) => {
|
||||||
|
|||||||
@@ -532,5 +532,11 @@
|
|||||||
],
|
],
|
||||||
"hourglass": [
|
"hourglass": [
|
||||||
16
|
16
|
||||||
|
],
|
||||||
|
"mobile-outline": [
|
||||||
|
16
|
||||||
|
],
|
||||||
|
"bookmark-outline": [
|
||||||
|
16
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user