Add bookmarks section to AppInspect
Summary: This diff adds support for the navigation plugin bookmarks to the appinspect tab. Support for path discovery, and path params will be added in a next diff. Features: * click a bookmark and navigate to it * sync bookmark state and uri with navigation plugin * manually enter a path and navigate to it by using <ENTER> Reviewed By: cekkaewnumchai Differential Revision: D24620250 fbshipit-source-id: 14b393a5456b4afeef69444d2120c8f01686e602
This commit is contained in:
committed by
Facebook GitHub Bot
parent
661bea1d5b
commit
5118727cb7
@@ -15,7 +15,6 @@ import {useStore} from '../utils/useStore';
|
||||
import {useMemoize} from '../utils/useMemoize';
|
||||
import {State} from '../reducers';
|
||||
|
||||
// TODO T71355623
|
||||
// eslint-disable-next-line flipper/no-relative-imports-across-packages
|
||||
import type {NavigationPlugin} from '../../../plugins/navigation/index';
|
||||
// eslint-disable-next-line flipper/no-relative-imports-across-packages
|
||||
|
||||
@@ -11,12 +11,18 @@ import React from 'react';
|
||||
import {Alert} from 'antd';
|
||||
import {LeftSidebar, SidebarTitle, InfoIcon} from '../LeftSidebar';
|
||||
import {Layout, Link, styled} from '../../ui';
|
||||
import {NUX, theme} from 'flipper-plugin';
|
||||
import {theme} from 'flipper-plugin';
|
||||
import {AppSelector} from './AppSelector';
|
||||
import {useStore} from '../../utils/useStore';
|
||||
import {PluginList} from './PluginList';
|
||||
import ScreenCaptureButtons from '../../chrome/ScreenCaptureButtons';
|
||||
import MetroButton from '../../chrome/MetroButton';
|
||||
import {BookmarkSection} from './BookmarkSection';
|
||||
import {useMemoize} from '../../utils/useMemoize';
|
||||
import Client from '../../Client';
|
||||
import {State} from '../../reducers';
|
||||
import BaseDevice from '../../devices/BaseDevice';
|
||||
import MetroDevice from '../../devices/MetroDevice';
|
||||
|
||||
const appTooltip = (
|
||||
<>
|
||||
@@ -30,8 +36,23 @@ const appTooltip = (
|
||||
);
|
||||
|
||||
export function AppInspect() {
|
||||
const selectedDevice = useStore((state) => state.connections.selectedDevice);
|
||||
const isArchived = !!selectedDevice?.isArchived;
|
||||
const connections = useStore((state) => state.connections);
|
||||
|
||||
const metroDevice = useMemoize(findMetroDevice, [connections.devices]);
|
||||
const client = useMemoize(findBestClient, [
|
||||
connections.clients,
|
||||
connections.selectedApp,
|
||||
connections.userPreferredApp,
|
||||
]);
|
||||
// // if the selected device is Metro, we want to keep the owner of the selected App as active device if possible
|
||||
const activeDevice = useMemoize(findBestDevice, [
|
||||
client,
|
||||
connections.devices,
|
||||
connections.selectedDevice,
|
||||
metroDevice,
|
||||
connections.userPreferredDevice,
|
||||
]);
|
||||
const isArchived = !!activeDevice?.isArchived;
|
||||
|
||||
return (
|
||||
<LeftSidebar>
|
||||
@@ -42,14 +63,14 @@ export function AppInspect() {
|
||||
</SidebarTitle>
|
||||
<Layout.Container padv="small" padh="medium" gap={theme.space.large}>
|
||||
<AppSelector />
|
||||
{
|
||||
isArchived ? (
|
||||
<Alert
|
||||
message="This device is a snapshot and cannot be interacted with."
|
||||
type="info"
|
||||
/>
|
||||
) : null /* TODO: add bookmarks back T77016599 */
|
||||
}
|
||||
{isArchived ? (
|
||||
<Alert
|
||||
message="This device is a snapshot and cannot be interacted with."
|
||||
type="info"
|
||||
/>
|
||||
) : (
|
||||
<BookmarkSection />
|
||||
)}
|
||||
{!isArchived && (
|
||||
<Toolbar gap>
|
||||
<MetroButton useSandy />
|
||||
@@ -59,8 +80,12 @@ export function AppInspect() {
|
||||
</Layout.Container>
|
||||
</Layout.Container>
|
||||
<Layout.ScrollContainer vertical padv={theme.space.large}>
|
||||
{selectedDevice ? (
|
||||
<PluginList />
|
||||
{activeDevice ? (
|
||||
<PluginList
|
||||
activeDevice={activeDevice}
|
||||
metroDevice={metroDevice}
|
||||
client={client}
|
||||
/>
|
||||
) : (
|
||||
<Alert message="No device or app selected." type="info" />
|
||||
)}
|
||||
@@ -75,3 +100,44 @@ const Toolbar = styled(Layout.Horizontal)({
|
||||
border: 'none',
|
||||
},
|
||||
});
|
||||
|
||||
export function findBestClient(
|
||||
clients: Client[],
|
||||
selectedApp: string | null,
|
||||
userPreferredApp: string | null,
|
||||
): Client | undefined {
|
||||
return clients.find((c) => c.id === (selectedApp || userPreferredApp));
|
||||
}
|
||||
|
||||
export function findMetroDevice(
|
||||
devices: State['connections']['devices'],
|
||||
): MetroDevice | undefined {
|
||||
return devices?.find(
|
||||
(device) => device.os === 'Metro' && !device.isArchived,
|
||||
) as MetroDevice;
|
||||
}
|
||||
|
||||
export function findBestDevice(
|
||||
client: Client | undefined,
|
||||
devices: State['connections']['devices'],
|
||||
selectedDevice: BaseDevice | null,
|
||||
metroDevice: BaseDevice | undefined,
|
||||
userPreferredDevice: string | null,
|
||||
): BaseDevice | undefined {
|
||||
// if not Metro device, use the selected device as metro device
|
||||
const selected = selectedDevice ?? undefined;
|
||||
if (selected !== metroDevice) {
|
||||
return selected;
|
||||
}
|
||||
// if there is an active app, use device owning the app
|
||||
if (client) {
|
||||
return client.deviceSync;
|
||||
}
|
||||
// if no active app, use the preferred device
|
||||
if (userPreferredDevice) {
|
||||
return (
|
||||
devices.find((device) => device.title === userPreferredDevice) ?? selected
|
||||
);
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
80
desktop/app/src/sandy-chrome/appinspect/BookmarkSection.tsx
Normal file
80
desktop/app/src/sandy-chrome/appinspect/BookmarkSection.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import {AutoComplete, Input} from 'antd';
|
||||
import {StarFilled, StarOutlined} from '@ant-design/icons';
|
||||
import {useStore} from '../../utils/useStore';
|
||||
import {NUX, useValue} from 'flipper-plugin';
|
||||
import {navPluginStateSelector} from '../../chrome/LocationsButton';
|
||||
|
||||
// eslint-disable-next-line flipper/no-relative-imports-across-packages
|
||||
import type {NavigationPlugin} from '../../../../plugins/navigation/index';
|
||||
|
||||
export function BookmarkSection() {
|
||||
const navPlugin = useStore(navPluginStateSelector);
|
||||
|
||||
return navPlugin ? (
|
||||
<NUX
|
||||
title="Use bookmarks to directly navigate to a location in the app."
|
||||
placement="right">
|
||||
<BookmarkSectionInput navPlugin={navPlugin} />
|
||||
</NUX>
|
||||
) : null;
|
||||
}
|
||||
|
||||
function BookmarkSectionInput({navPlugin}: {navPlugin: NavigationPlugin}) {
|
||||
const currentURI = useValue(navPlugin.currentURI);
|
||||
const bookmarks = useValue(navPlugin.bookmarks);
|
||||
|
||||
const isBookmarked = useMemo(() => bookmarks.has(currentURI), [
|
||||
bookmarks,
|
||||
currentURI,
|
||||
]);
|
||||
const handleBookmarkClick = useCallback(() => {
|
||||
if (isBookmarked) {
|
||||
navPlugin.removeBookmark(currentURI);
|
||||
} else {
|
||||
navPlugin.addBookmark({
|
||||
uri: currentURI,
|
||||
commonName: null,
|
||||
});
|
||||
}
|
||||
}, [navPlugin, currentURI, isBookmarked]);
|
||||
|
||||
const bookmarkButton = isBookmarked ? (
|
||||
<StarFilled onClick={handleBookmarkClick} />
|
||||
) : (
|
||||
<StarOutlined onClick={handleBookmarkClick} />
|
||||
);
|
||||
|
||||
return (
|
||||
<AutoComplete
|
||||
value={currentURI}
|
||||
onSelect={navPlugin.navigateTo}
|
||||
options={Array.from(bookmarks.values()).map((bookmark) => ({
|
||||
value: bookmark.uri,
|
||||
label: bookmark.commonName
|
||||
? `${bookmark.commonName} - ${bookmark.uri}`
|
||||
: bookmark.uri,
|
||||
}))}>
|
||||
<Input
|
||||
addonAfter={bookmarkButton}
|
||||
defaultValue="<select a bookmark>"
|
||||
value={currentURI}
|
||||
onChange={(e) => {
|
||||
navPlugin.currentURI.set(e.target.value);
|
||||
}}
|
||||
onPressEnter={(e) => {
|
||||
navPlugin.navigateTo(currentURI);
|
||||
}}
|
||||
/>
|
||||
</AutoComplete>
|
||||
);
|
||||
}
|
||||
@@ -23,29 +23,24 @@ import BaseDevice from '../../devices/BaseDevice';
|
||||
import {getFavoritePlugins} from '../../chrome/mainsidebar/sidebarUtils';
|
||||
import {PluginDetails} from 'flipper-plugin-lib';
|
||||
import {useMemoize} from '../../utils/useMemoize';
|
||||
import MetroDevice from '../../devices/MetroDevice';
|
||||
|
||||
const {SubMenu} = Menu;
|
||||
const {Text} = Typography;
|
||||
|
||||
export const PluginList = memo(function PluginList() {
|
||||
export const PluginList = memo(function PluginList({
|
||||
client,
|
||||
activeDevice,
|
||||
metroDevice,
|
||||
}: {
|
||||
client: Client | undefined;
|
||||
activeDevice: BaseDevice | undefined;
|
||||
metroDevice: MetroDevice | undefined;
|
||||
}) {
|
||||
const dispatch = useDispatch();
|
||||
const connections = useStore((state) => state.connections);
|
||||
const plugins = useStore((state) => state.plugins);
|
||||
|
||||
const metroDevice = useMemoize(findMetroDevice, [connections.devices]);
|
||||
const client = useMemoize(findBestClient, [
|
||||
connections.clients,
|
||||
connections.selectedApp,
|
||||
connections.userPreferredApp,
|
||||
]);
|
||||
// // if the selected device is Metro, we want to keep the owner of the selected App as active device if possible
|
||||
const activeDevice = useMemoize(findBestDevice, [
|
||||
client,
|
||||
connections.devices,
|
||||
connections.selectedDevice,
|
||||
metroDevice,
|
||||
connections.userPreferredDevice,
|
||||
]);
|
||||
const {
|
||||
devicePlugins,
|
||||
metroPlugins,
|
||||
@@ -351,45 +346,6 @@ function getPluginTooltip(details: PluginDetails): string {
|
||||
}`;
|
||||
}
|
||||
|
||||
export function findBestClient(
|
||||
clients: Client[],
|
||||
selectedApp: string | null,
|
||||
userPreferredApp: string | null,
|
||||
): Client | undefined {
|
||||
return clients.find((c) => c.id === (selectedApp || userPreferredApp));
|
||||
}
|
||||
|
||||
export function findMetroDevice(
|
||||
devices: State['connections']['devices'],
|
||||
): BaseDevice | undefined {
|
||||
return devices?.find((device) => device.os === 'Metro' && !device.isArchived);
|
||||
}
|
||||
|
||||
export function findBestDevice(
|
||||
client: Client | undefined,
|
||||
devices: State['connections']['devices'],
|
||||
selectedDevice: BaseDevice | null,
|
||||
metroDevice: BaseDevice | undefined,
|
||||
userPreferredDevice: string | null,
|
||||
): BaseDevice | undefined {
|
||||
// if not Metro device, use the selected device as metro device
|
||||
const selected = selectedDevice ?? undefined;
|
||||
if (selected !== metroDevice) {
|
||||
return selected;
|
||||
}
|
||||
// if there is an active app, use device owning the app
|
||||
if (client) {
|
||||
return client.deviceSync;
|
||||
}
|
||||
// if no active app, use the preferred device
|
||||
if (userPreferredDevice) {
|
||||
return (
|
||||
devices.find((device) => device.title === userPreferredDevice) ?? selected
|
||||
);
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
export function computePluginLists(
|
||||
device: BaseDevice | undefined,
|
||||
metroDevice: BaseDevice | undefined,
|
||||
|
||||
@@ -11,12 +11,8 @@ import {
|
||||
createMockFlipperWithPlugin,
|
||||
MockFlipperResult,
|
||||
} from '../../../test-utils/createMockFlipperWithPlugin';
|
||||
import {
|
||||
findBestClient,
|
||||
findBestDevice,
|
||||
findMetroDevice,
|
||||
computePluginLists,
|
||||
} from '../PluginList';
|
||||
import {computePluginLists} from '../PluginList';
|
||||
import {findBestClient, findBestDevice, findMetroDevice} from '../AppInspect';
|
||||
import {FlipperPlugin} from '../../../plugin';
|
||||
import MetroDevice from '../../../devices/MetroDevice';
|
||||
import BaseDevice from '../../../devices/BaseDevice';
|
||||
|
||||
Reference in New Issue
Block a user