Refactor plugin to make it fast refreshable
Summary: Refactored Navigation plugin to make it fast-refreshable: moved the main component into a separate file and exported all components as named functions. Without these changes every change of UI triggered full reload. Reviewed By: timur-valiev Differential Revision: D29814077 fbshipit-source-id: 5285bdc5f14a5163f9501c0d45a3affefb08fc8e
This commit is contained in:
committed by
Facebook GitHub Bot
parent
a78b6124d7
commit
d782f19001
78
desktop/plugins/public/navigation/Component.tsx
Normal file
78
desktop/plugins/public/navigation/Component.tsx
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* 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 {
|
||||||
|
BookmarksSidebar,
|
||||||
|
SaveBookmarkDialog,
|
||||||
|
SearchBar,
|
||||||
|
Timeline,
|
||||||
|
} from './components';
|
||||||
|
import {
|
||||||
|
appMatchPatternsToAutoCompleteProvider,
|
||||||
|
bookmarksToAutoCompleteProvider,
|
||||||
|
} from './util/autoCompleteProvider';
|
||||||
|
import React, {useMemo} from 'react';
|
||||||
|
import {useValue, usePlugin, Layout} from 'flipper-plugin';
|
||||||
|
import {plugin} from './plugin';
|
||||||
|
|
||||||
|
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 currentURI = useValue(instance.currentURI);
|
||||||
|
const navigationEvents = useValue(instance.navigationEvents);
|
||||||
|
|
||||||
|
const autoCompleteProviders = useMemo(
|
||||||
|
() => [
|
||||||
|
bookmarksToAutoCompleteProvider(bookmarks),
|
||||||
|
appMatchPatternsToAutoCompleteProvider(appMatchPatterns),
|
||||||
|
],
|
||||||
|
[bookmarks, appMatchPatterns],
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Layout.Container grow>
|
||||||
|
<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}
|
||||||
|
/>
|
||||||
|
</Layout.Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* @scarf-info: do not remove, more info: https://fburl.com/scarf */
|
||||||
|
/* @scarf-generated: flipper-plugin index.js.template 0bfa32e5-fb15-4705-81f8-86260a1f3f8e */
|
||||||
@@ -50,7 +50,7 @@ const SheetItemIcon = styled.span({
|
|||||||
padding: 8,
|
padding: 8,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default (props: Props) => {
|
export function AutoCompleteSheet(props: Props) {
|
||||||
const {providers, onHighlighted, onNavigate, query} = props;
|
const {providers, onHighlighted, onNavigate, query} = props;
|
||||||
const lineItems = filterProvidersToLineItems(providers, query, MAX_ITEMS);
|
const lineItems = filterProvidersToLineItems(providers, query, MAX_ITEMS);
|
||||||
lineItems.unshift({uri: query, matchPattern: query, icon: 'send'});
|
lineItems.unshift({uri: query, matchPattern: query, icon: 'send'});
|
||||||
@@ -70,4 +70,4 @@ export default (props: Props) => {
|
|||||||
))}
|
))}
|
||||||
</AutoCompleteSheetContainer>
|
</AutoCompleteSheetContainer>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ const alphabetizeBookmarkCompare = (b1: Bookmark, b2: Bookmark) => {
|
|||||||
return b1.uri < b2.uri ? -1 : b1.uri > b2.uri ? 1 : 0;
|
return b1.uri < b2.uri ? -1 : b1.uri > b2.uri ? 1 : 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default (props: Props) => {
|
export function BookmarksSidebar(props: Props) {
|
||||||
const {bookmarks, onNavigate, onRemove} = props;
|
const {bookmarks, onNavigate, onRemove} = props;
|
||||||
return (
|
return (
|
||||||
<DetailSidebar>
|
<DetailSidebar>
|
||||||
@@ -119,4 +119,4 @@ export default (props: Props) => {
|
|||||||
</Panel>
|
</Panel>
|
||||||
</DetailSidebar>
|
</DetailSidebar>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const FavoriteButtonContainer = styled.div({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default (props: Props) => {
|
export function FavoriteButton(props: Props) {
|
||||||
const {highlighted, onClick, ...iconButtonProps} = props;
|
const {highlighted, onClick, ...iconButtonProps} = props;
|
||||||
return (
|
return (
|
||||||
<FavoriteButtonContainer>
|
<FavoriteButtonContainer>
|
||||||
@@ -42,4 +42,4 @@ export default (props: Props) => {
|
|||||||
<IconButton outline icon="star" onClick={onClick} {...iconButtonProps} />
|
<IconButton outline icon="star" onClick={onClick} {...iconButtonProps} />
|
||||||
</FavoriteButtonContainer>
|
</FavoriteButtonContainer>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -43,23 +43,23 @@ const RippleEffect = styled.div({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const IconButton = styled.div({
|
const IconButtonContainer = styled.div({
|
||||||
':active': {
|
':active': {
|
||||||
animation: `${shrinkAnimation} .25s ease forwards`,
|
animation: `${shrinkAnimation} .25s ease forwards`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function (props: Props) {
|
export function IconButton(props: Props) {
|
||||||
return (
|
return (
|
||||||
<RippleEffect>
|
<RippleEffect>
|
||||||
<IconButton className="icon-button" onClick={props.onClick}>
|
<IconButtonContainer className="icon-button" onClick={props.onClick}>
|
||||||
<Glyph
|
<Glyph
|
||||||
name={props.icon}
|
name={props.icon}
|
||||||
size={props.size}
|
size={props.size}
|
||||||
color={props.color}
|
color={props.color}
|
||||||
variant={props.outline ? 'outline' : 'filled'}
|
variant={props.outline ? 'outline' : 'filled'}
|
||||||
/>
|
/>
|
||||||
</IconButton>
|
</IconButtonContainer>
|
||||||
</RippleEffect>
|
</RippleEffect>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ const buildParameterTable = (parameters: Map<string, string>) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default (props: Props) => {
|
export function NavigationInfoBox(props: Props) {
|
||||||
const {
|
const {
|
||||||
uri,
|
uri,
|
||||||
isBookmarked,
|
isBookmarked,
|
||||||
@@ -238,4 +238,4 @@ export default (props: Props) => {
|
|||||||
</NavigationInfoBoxContainer>
|
</NavigationInfoBoxContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ type Props = {
|
|||||||
onSubmit: (uri: URI) => void;
|
onSubmit: (uri: URI) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default (props: Props) => {
|
export function RequiredParametersDialog(props: Props) {
|
||||||
const {onHide, onSubmit, uri, requiredParameters} = props;
|
const {onHide, onSubmit, uri, requiredParameters} = props;
|
||||||
const {isValid, values, setValuesArray} =
|
const {isValid, values, setValuesArray} =
|
||||||
useRequiredParameterFormValidator(requiredParameters);
|
useRequiredParameterFormValidator(requiredParameters);
|
||||||
@@ -95,4 +95,4 @@ export default (props: Props) => {
|
|||||||
</Layout.Container>
|
</Layout.Container>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ const NameInput = styled(Input)({
|
|||||||
height: 30,
|
height: 30,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default (props: Props) => {
|
export function SaveBookmarkDialog(props: Props) {
|
||||||
const {edit, shouldShow, onHide, onRemove, onSubmit, uri} = props;
|
const {edit, shouldShow, onHide, onRemove, onSubmit, uri} = props;
|
||||||
const [commonName, setCommonName] = useState('');
|
const [commonName, setCommonName] = useState('');
|
||||||
if (uri == null || !shouldShow) {
|
if (uri == null || !shouldShow) {
|
||||||
@@ -114,4 +114,4 @@ export default (props: Props) => {
|
|||||||
</Sheet>
|
</Sheet>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ const SearchInputContainer = styled.div({
|
|||||||
position: 'relative',
|
position: 'relative',
|
||||||
});
|
});
|
||||||
|
|
||||||
class SearchBar extends Component<Props, State> {
|
export class SearchBar extends Component<Props, State> {
|
||||||
state = {
|
state = {
|
||||||
inputFocused: false,
|
inputFocused: false,
|
||||||
autoCompleteSheetOpen: false,
|
autoCompleteSheetOpen: false,
|
||||||
@@ -158,5 +158,3 @@ class SearchBar extends Component<Props, State> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SearchBar;
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ const NoData = styled(FlexCenter)({
|
|||||||
color: theme.textColorSecondary,
|
color: theme.textColorSecondary,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default (props: Props) => {
|
export function Timeline(props: Props) {
|
||||||
const {bookmarks, events, onNavigate, onFavorite} = props;
|
const {bookmarks, events, onNavigate, onFavorite} = props;
|
||||||
const timelineRef = useRef<HTMLDivElement>(null);
|
const timelineRef = useRef<HTMLDivElement>(null);
|
||||||
return events.length === 0 ? (
|
return events.length === 0 ? (
|
||||||
@@ -92,4 +92,4 @@ export default (props: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
</TimelineContainer>
|
</TimelineContainer>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -7,12 +7,12 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export {default as AutoCompleteSheet} from './AutoCompleteSheet';
|
export {AutoCompleteSheet} from './AutoCompleteSheet';
|
||||||
export {default as BookmarksSidebar} from './BookmarksSidebar';
|
export {BookmarksSidebar} from './BookmarksSidebar';
|
||||||
export {default as FavoriteButton} from './FavoriteButton';
|
export {FavoriteButton} from './FavoriteButton';
|
||||||
export {default as IconButton} from './IconButton';
|
export {IconButton} from './IconButton';
|
||||||
export {default as NavigationInfoBox} from './NavigationInfoBox';
|
export {NavigationInfoBox} from './NavigationInfoBox';
|
||||||
export {default as RequiredParametersDialog} from './RequiredParametersDialog';
|
export {RequiredParametersDialog} from './RequiredParametersDialog';
|
||||||
export {default as SaveBookmarkDialog} from './SaveBookmarkDialog';
|
export {SaveBookmarkDialog} from './SaveBookmarkDialog';
|
||||||
export {default as SearchBar} from './SearchBar';
|
export {SearchBar} from './SearchBar';
|
||||||
export {default as Timeline} from './Timeline';
|
export {Timeline} from './Timeline';
|
||||||
|
|||||||
@@ -8,248 +8,5 @@
|
|||||||
* @flow strict-local
|
* @flow strict-local
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {bufferToBlob} from 'flipper';
|
export {plugin, NavigationPlugin} from './plugin';
|
||||||
import {
|
export {Component} from './Component';
|
||||||
BookmarksSidebar,
|
|
||||||
SaveBookmarkDialog,
|
|
||||||
SearchBar,
|
|
||||||
Timeline,
|
|
||||||
RequiredParametersDialog,
|
|
||||||
} from './components';
|
|
||||||
import {
|
|
||||||
removeBookmarkFromDB,
|
|
||||||
readBookmarksFromDB,
|
|
||||||
writeBookmarkToDB,
|
|
||||||
} from './util/indexedDB';
|
|
||||||
import {
|
|
||||||
appMatchPatternsToAutoCompleteProvider,
|
|
||||||
bookmarksToAutoCompleteProvider,
|
|
||||||
} from './util/autoCompleteProvider';
|
|
||||||
import {getAppMatchPatterns} from './util/appMatchPatterns';
|
|
||||||
import {getRequiredParameters, filterOptionalParameters} from './util/uri';
|
|
||||||
import {
|
|
||||||
Bookmark,
|
|
||||||
NavigationEvent,
|
|
||||||
AppMatchPattern,
|
|
||||||
URI,
|
|
||||||
RawNavigationEvent,
|
|
||||||
} from './types';
|
|
||||||
import React, {useMemo} from 'react';
|
|
||||||
import {
|
|
||||||
PluginClient,
|
|
||||||
createState,
|
|
||||||
useValue,
|
|
||||||
usePlugin,
|
|
||||||
Layout,
|
|
||||||
renderReactRoot,
|
|
||||||
} from 'flipper-plugin';
|
|
||||||
|
|
||||||
export type State = {
|
|
||||||
shouldShowSaveBookmarkDialog: boolean;
|
|
||||||
shouldShowURIErrorDialog: boolean;
|
|
||||||
saveBookmarkURI: URI | null;
|
|
||||||
requiredParameters: Array<string>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Events = {
|
|
||||||
nav_event: RawNavigationEvent;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Methods = {
|
|
||||||
navigate_to(params: {url: string}): Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type NavigationPlugin = ReturnType<typeof plugin>;
|
|
||||||
|
|
||||||
export function plugin(client: PluginClient<Events, Methods>) {
|
|
||||||
const bookmarks = createState(new Map<URI, Bookmark>(), {
|
|
||||||
persist: 'bookmarks',
|
|
||||||
});
|
|
||||||
const navigationEvents = createState<NavigationEvent[]>([], {
|
|
||||||
persist: 'navigationEvents',
|
|
||||||
});
|
|
||||||
const appMatchPatterns = createState<AppMatchPattern[]>([], {
|
|
||||||
persist: 'appMatchPatterns',
|
|
||||||
});
|
|
||||||
const currentURI = createState('');
|
|
||||||
const shouldShowSaveBookmarkDialog = createState(false);
|
|
||||||
const saveBookmarkURI = createState<null | string>(null);
|
|
||||||
|
|
||||||
client.onMessage('nav_event', async (payload) => {
|
|
||||||
const navigationEvent: NavigationEvent = {
|
|
||||||
uri: payload.uri === undefined ? null : decodeURIComponent(payload.uri),
|
|
||||||
date: payload.date ? new Date(payload.date) : new Date(),
|
|
||||||
className: payload.class === undefined ? null : payload.class,
|
|
||||||
screenshot: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (navigationEvent.uri) currentURI.set(navigationEvent.uri);
|
|
||||||
|
|
||||||
navigationEvents.update((draft) => {
|
|
||||||
draft.unshift(navigationEvent);
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
currentURI.set(filteredQuery);
|
|
||||||
const params = getRequiredParameters(filteredQuery);
|
|
||||||
if (params.length === 0) {
|
|
||||||
if (client.appName === 'Facebook' && client.device.os === 'iOS') {
|
|
||||||
// use custom navigate_to event for Wilde
|
|
||||||
client.send('navigate_to', {
|
|
||||||
url: filterOptionalParameters(filteredQuery),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
client.device.realDevice.navigateToLocation(
|
|
||||||
filterOptionalParameters(filteredQuery),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
renderReactRoot((unmount) => (
|
|
||||||
<RequiredParametersDialog
|
|
||||||
onHide={unmount}
|
|
||||||
uri={filteredQuery}
|
|
||||||
requiredParameters={params}
|
|
||||||
onSubmit={navigateTo}
|
|
||||||
/>
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onFavorite(uri: string) {
|
|
||||||
shouldShowSaveBookmarkDialog.set(true);
|
|
||||||
saveBookmarkURI.set(uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
function addBookmark(bookmark: Bookmark) {
|
|
||||||
const newBookmark = {
|
|
||||||
uri: bookmark.uri,
|
|
||||||
commonName: bookmark.commonName,
|
|
||||||
};
|
|
||||||
|
|
||||||
bookmarks.update((draft) => {
|
|
||||||
draft.set(newBookmark.uri, newBookmark);
|
|
||||||
});
|
|
||||||
writeBookmarkToDB(newBookmark);
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeBookmark(uri: string) {
|
|
||||||
bookmarks.update((draft) => {
|
|
||||||
draft.delete(uri);
|
|
||||||
});
|
|
||||||
removeBookmarkFromDB(uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
navigateTo,
|
|
||||||
onFavorite,
|
|
||||||
addBookmark,
|
|
||||||
removeBookmark,
|
|
||||||
bookmarks,
|
|
||||||
saveBookmarkURI,
|
|
||||||
shouldShowSaveBookmarkDialog,
|
|
||||||
appMatchPatterns,
|
|
||||||
navigationEvents,
|
|
||||||
currentURI,
|
|
||||||
getAutoCompleteAppMatchPatterns(
|
|
||||||
query: string,
|
|
||||||
bookmarks: Map<string, Bookmark>,
|
|
||||||
appMatchPatterns: AppMatchPattern[],
|
|
||||||
limit: number,
|
|
||||||
): AppMatchPattern[] {
|
|
||||||
const q = query.toLowerCase();
|
|
||||||
const results: AppMatchPattern[] = [];
|
|
||||||
for (const item of appMatchPatterns) {
|
|
||||||
if (
|
|
||||||
!bookmarks.has(item.pattern) &&
|
|
||||||
(item.className.toLowerCase().includes(q) ||
|
|
||||||
item.pattern.toLowerCase().includes(q))
|
|
||||||
) {
|
|
||||||
results.push(item);
|
|
||||||
if (--limit < 1) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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}
|
|
||||||
/>
|
|
||||||
</Layout.Container>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* @scarf-info: do not remove, more info: https://fburl.com/scarf */
|
|
||||||
/* @scarf-generated: flipper-plugin index.js.template 0bfa32e5-fb15-4705-81f8-86260a1f3f8e */
|
|
||||||
|
|||||||
187
desktop/plugins/public/navigation/plugin.tsx
Normal file
187
desktop/plugins/public/navigation/plugin.tsx
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* 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 {bufferToBlob} from 'flipper';
|
||||||
|
import {RequiredParametersDialog} from './components';
|
||||||
|
import {
|
||||||
|
removeBookmarkFromDB,
|
||||||
|
readBookmarksFromDB,
|
||||||
|
writeBookmarkToDB,
|
||||||
|
} from './util/indexedDB';
|
||||||
|
import {} from './util/autoCompleteProvider';
|
||||||
|
import {getAppMatchPatterns} from './util/appMatchPatterns';
|
||||||
|
import {getRequiredParameters, filterOptionalParameters} from './util/uri';
|
||||||
|
import {
|
||||||
|
Bookmark,
|
||||||
|
NavigationEvent,
|
||||||
|
AppMatchPattern,
|
||||||
|
URI,
|
||||||
|
RawNavigationEvent,
|
||||||
|
} from './types';
|
||||||
|
import React from 'react';
|
||||||
|
import {PluginClient, createState, renderReactRoot} from 'flipper-plugin';
|
||||||
|
|
||||||
|
export type State = {
|
||||||
|
shouldShowSaveBookmarkDialog: boolean;
|
||||||
|
shouldShowURIErrorDialog: boolean;
|
||||||
|
saveBookmarkURI: URI | null;
|
||||||
|
requiredParameters: Array<string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Events = {
|
||||||
|
nav_event: RawNavigationEvent;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Methods = {
|
||||||
|
navigate_to(params: {url: string}): Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type NavigationPlugin = ReturnType<typeof plugin>;
|
||||||
|
|
||||||
|
export function plugin(client: PluginClient<Events, Methods>) {
|
||||||
|
const bookmarks = createState(new Map<URI, Bookmark>(), {
|
||||||
|
persist: 'bookmarks',
|
||||||
|
});
|
||||||
|
const navigationEvents = createState<NavigationEvent[]>([], {
|
||||||
|
persist: 'navigationEvents',
|
||||||
|
});
|
||||||
|
const appMatchPatterns = createState<AppMatchPattern[]>([], {
|
||||||
|
persist: 'appMatchPatterns',
|
||||||
|
});
|
||||||
|
const currentURI = createState('');
|
||||||
|
const shouldShowSaveBookmarkDialog = createState(false);
|
||||||
|
const saveBookmarkURI = createState<null | string>(null);
|
||||||
|
|
||||||
|
client.onMessage('nav_event', async (payload) => {
|
||||||
|
const navigationEvent: NavigationEvent = {
|
||||||
|
uri: payload.uri === undefined ? null : decodeURIComponent(payload.uri),
|
||||||
|
date: payload.date ? new Date(payload.date) : new Date(),
|
||||||
|
className: payload.class === undefined ? null : payload.class,
|
||||||
|
screenshot: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (navigationEvent.uri) currentURI.set(navigationEvent.uri);
|
||||||
|
|
||||||
|
navigationEvents.update((draft) => {
|
||||||
|
draft.unshift(navigationEvent);
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
currentURI.set(filteredQuery);
|
||||||
|
const params = getRequiredParameters(filteredQuery);
|
||||||
|
if (params.length === 0) {
|
||||||
|
if (client.appName === 'Facebook' && client.device.os === 'iOS') {
|
||||||
|
// use custom navigate_to event for Wilde
|
||||||
|
client.send('navigate_to', {
|
||||||
|
url: filterOptionalParameters(filteredQuery),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
client.device.realDevice.navigateToLocation(
|
||||||
|
filterOptionalParameters(filteredQuery),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
renderReactRoot((unmount) => (
|
||||||
|
<RequiredParametersDialog
|
||||||
|
onHide={unmount}
|
||||||
|
uri={filteredQuery}
|
||||||
|
requiredParameters={params}
|
||||||
|
onSubmit={navigateTo}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFavorite(uri: string) {
|
||||||
|
shouldShowSaveBookmarkDialog.set(true);
|
||||||
|
saveBookmarkURI.set(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addBookmark(bookmark: Bookmark) {
|
||||||
|
const newBookmark = {
|
||||||
|
uri: bookmark.uri,
|
||||||
|
commonName: bookmark.commonName,
|
||||||
|
};
|
||||||
|
|
||||||
|
bookmarks.update((draft) => {
|
||||||
|
draft.set(newBookmark.uri, newBookmark);
|
||||||
|
});
|
||||||
|
writeBookmarkToDB(newBookmark);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeBookmark(uri: string) {
|
||||||
|
bookmarks.update((draft) => {
|
||||||
|
draft.delete(uri);
|
||||||
|
});
|
||||||
|
removeBookmarkFromDB(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
navigateTo,
|
||||||
|
onFavorite,
|
||||||
|
addBookmark,
|
||||||
|
removeBookmark,
|
||||||
|
bookmarks,
|
||||||
|
saveBookmarkURI,
|
||||||
|
shouldShowSaveBookmarkDialog,
|
||||||
|
appMatchPatterns,
|
||||||
|
navigationEvents,
|
||||||
|
currentURI,
|
||||||
|
getAutoCompleteAppMatchPatterns(
|
||||||
|
query: string,
|
||||||
|
bookmarks: Map<string, Bookmark>,
|
||||||
|
appMatchPatterns: AppMatchPattern[],
|
||||||
|
limit: number,
|
||||||
|
): AppMatchPattern[] {
|
||||||
|
const q = query.toLowerCase();
|
||||||
|
const results: AppMatchPattern[] = [];
|
||||||
|
for (const item of appMatchPatterns) {
|
||||||
|
if (
|
||||||
|
!bookmarks.has(item.pattern) &&
|
||||||
|
(item.className.toLowerCase().includes(q) ||
|
||||||
|
item.pattern.toLowerCase().includes(q))
|
||||||
|
) {
|
||||||
|
results.push(item);
|
||||||
|
if (--limit < 1) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* @scarf-info: do not remove, more info: https://fburl.com/scarf */
|
||||||
|
/* @scarf-generated: flipper-plugin index.js.template 0bfa32e5-fb15-4705-81f8-86260a1f3f8e */
|
||||||
Reference in New Issue
Block a user