Device Dropdown
Summary: This diff adds the device switcher, filling the switcher with apps and devices. Reviewed By: cekkaewnumchai Differential Revision: D24053787 fbshipit-source-id: 4f69835a12eec90a0e5704f71c8ceed5509f61ce
This commit is contained in:
committed by
Facebook GitHub Bot
parent
79ac1ef009
commit
c38a4413dc
@@ -103,10 +103,10 @@ const demos: PreviewProps[] = [
|
||||
background: theme.successColor,
|
||||
}}></Layout.Container>
|
||||
),
|
||||
'bordered padded rounded': (
|
||||
'bordered pad rounded': (
|
||||
<Layout.Container
|
||||
bordered
|
||||
padded
|
||||
pad
|
||||
rounded
|
||||
style={{background: theme.backgroundDefault, width: 200}}>
|
||||
<div style={demoStyle.square}>child</div>
|
||||
@@ -152,8 +152,8 @@ const demos: PreviewProps[] = [
|
||||
{aDynamicBox}
|
||||
</Layout.Horizontal>
|
||||
),
|
||||
'Using flags: padded center gap={8} (great for toolbars and such)': (
|
||||
<Layout.Horizontal padded center gap={8}>
|
||||
'Using flags: pad center gap={8} (great for toolbars and such)': (
|
||||
<Layout.Horizontal pad center gap={8}>
|
||||
{aButton}
|
||||
{someText}
|
||||
{aBox}
|
||||
@@ -187,8 +187,8 @@ const demos: PreviewProps[] = [
|
||||
{aDynamicBox}
|
||||
</Layout.Vertical>
|
||||
),
|
||||
'Using flags: padded center gap={8} (great for toolbars and such)': (
|
||||
<Layout.Vertical padded center gap={8}>
|
||||
'Using flags: pad center gap (great for toolbars and such)': (
|
||||
<Layout.Vertical pad center gap>
|
||||
{aButton}
|
||||
{someText}
|
||||
{aBox}
|
||||
@@ -207,6 +207,11 @@ const demos: PreviewProps[] = [
|
||||
'boolean (false)',
|
||||
'If set, the area of the second child will automatically be made scrollable.',
|
||||
],
|
||||
[
|
||||
'center',
|
||||
'boolean (false)',
|
||||
'If set, all children will use their own height, and they will be centered vertically in the layout. If not set, all children will be stretched to the height of the layout.',
|
||||
],
|
||||
],
|
||||
demos: {
|
||||
'Layout.Top': (
|
||||
|
||||
@@ -8,18 +8,15 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {Button, Dropdown, Menu, Radio, Input} from 'antd';
|
||||
import {Alert, Button, Input} from 'antd';
|
||||
import {LeftSidebar, SidebarTitle, InfoIcon} from '../LeftSidebar';
|
||||
import {
|
||||
AppleOutlined,
|
||||
AndroidOutlined,
|
||||
SettingOutlined,
|
||||
RocketOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import {SettingOutlined, RocketOutlined} from '@ant-design/icons';
|
||||
import {Layout, Link} from '../../ui';
|
||||
import {theme} from '../theme';
|
||||
import {useStore as useReduxStore} from 'react-redux';
|
||||
import {showEmulatorLauncher} from './LaunchEmulator';
|
||||
import {AppSelector} from './AppSelector';
|
||||
import {useStore} from '../../utils/useStore';
|
||||
|
||||
const appTooltip = (
|
||||
<>
|
||||
@@ -34,6 +31,7 @@ const appTooltip = (
|
||||
|
||||
export function AppInspect() {
|
||||
const store = useReduxStore();
|
||||
const selectedDevice = useStore((state) => state.connections.selectedDevice);
|
||||
return (
|
||||
<LeftSidebar>
|
||||
<Layout.Top scrollable>
|
||||
@@ -42,7 +40,7 @@ export function AppInspect() {
|
||||
App Inspect
|
||||
</SidebarTitle>
|
||||
<Layout.Vertical padv="small" padh="medium" gap={theme.space.large}>
|
||||
<DeviceDropdown />
|
||||
<AppSelector />
|
||||
<Input addonAfter={<SettingOutlined />} defaultValue="mysite" />
|
||||
<Layout.Horizontal gap>
|
||||
<Button icon={<SettingOutlined />} type="link" />
|
||||
@@ -58,36 +56,22 @@ export function AppInspect() {
|
||||
</Layout.Horizontal>
|
||||
</Layout.Vertical>
|
||||
</Layout.Container>
|
||||
<Layout.Container>Dynamic section</Layout.Container>
|
||||
<Layout.Container padv={theme.space.small}>
|
||||
{selectedDevice ? (
|
||||
<PluginList />
|
||||
) : (
|
||||
<Alert message="No device or app selected" type="info" />
|
||||
)}
|
||||
</Layout.Container>
|
||||
</Layout.Top>
|
||||
</LeftSidebar>
|
||||
);
|
||||
}
|
||||
|
||||
function DeviceDropdown() {
|
||||
function PluginList() {
|
||||
return (
|
||||
<Radio.Group value={1} size="small">
|
||||
<Dropdown
|
||||
overlay={
|
||||
<Menu>
|
||||
<Menu.Item icon={<AppleOutlined />} style={{fontWeight: 'bold'}}>
|
||||
IPhone 11
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<Radio value={1}>Facebook</Radio>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<Radio value={3}>Instagram</Radio>
|
||||
</Menu.Item>
|
||||
<Menu.Item icon={<AndroidOutlined />} style={{fontWeight: 'bold'}}>
|
||||
Android
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
}>
|
||||
<Button icon={<AppleOutlined />} style={{width: '100%'}}>
|
||||
Facebook Iphone11
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</Radio.Group>
|
||||
<Layout.Container>
|
||||
<SidebarTitle>Plugins</SidebarTitle>
|
||||
</Layout.Container>
|
||||
);
|
||||
}
|
||||
|
||||
162
desktop/app/src/sandy-chrome/appinspect/AppSelector.tsx
Normal file
162
desktop/app/src/sandy-chrome/appinspect/AppSelector.tsx
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* 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 from 'react';
|
||||
import {Button, Dropdown, Menu, Radio, Typography} from 'antd';
|
||||
import {
|
||||
AppleOutlined,
|
||||
AndroidOutlined,
|
||||
WindowsOutlined,
|
||||
CaretDownOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import {Layout, styled} from '../../ui';
|
||||
import {theme} from '../theme';
|
||||
import {batch} from 'react-redux';
|
||||
import {Dispatch, useDispatch, useStore} from '../../utils/useStore';
|
||||
import {
|
||||
canBeDefaultDevice,
|
||||
getAvailableClients,
|
||||
selectClient,
|
||||
selectDevice,
|
||||
} from '../../reducers/connections';
|
||||
import BaseDevice, {OS} from '../../devices/BaseDevice';
|
||||
import {getColorByApp} from '../../chrome/mainsidebar/sidebarUtils';
|
||||
import Client from '../../Client';
|
||||
import {State} from '../../reducers';
|
||||
|
||||
const {Text} = Typography;
|
||||
|
||||
function getOsIcon(os?: OS) {
|
||||
switch (os) {
|
||||
case 'iOS':
|
||||
return <AppleOutlined />;
|
||||
case 'Android':
|
||||
return <AndroidOutlined />;
|
||||
case 'Windows':
|
||||
return <WindowsOutlined />;
|
||||
default:
|
||||
undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function AppSelector() {
|
||||
const dispatch = useDispatch();
|
||||
const {
|
||||
devices,
|
||||
selectedDevice,
|
||||
clients,
|
||||
uninitializedClients,
|
||||
selectedApp,
|
||||
} = useStore((state) => state.connections);
|
||||
const entries = computeEntries(
|
||||
devices,
|
||||
dispatch,
|
||||
clients,
|
||||
uninitializedClients,
|
||||
);
|
||||
const client = clients.find((client) => client.id === selectedApp);
|
||||
|
||||
return (
|
||||
<Radio.Group value={selectedApp} size="small">
|
||||
<Dropdown
|
||||
overlay={
|
||||
<Menu selectedKeys={selectedApp ? [selectedApp] : []}>
|
||||
{entries.flat()}
|
||||
</Menu>
|
||||
}>
|
||||
<AppInspectButton title="Select the device / app to inspect">
|
||||
<Layout.Horizontal gap center>
|
||||
<AppIcon appname={client?.query.app} />
|
||||
<Layout.Vertical>
|
||||
<Text strong>{client?.query.app ?? ''}</Text>
|
||||
<Text>
|
||||
{selectedDevice?.displayTitle() || 'Available devices'}
|
||||
</Text>
|
||||
</Layout.Vertical>
|
||||
<CaretDownOutlined />
|
||||
</Layout.Horizontal>
|
||||
</AppInspectButton>
|
||||
</Dropdown>
|
||||
</Radio.Group>
|
||||
);
|
||||
}
|
||||
|
||||
const AppInspectButton = styled(Button)({
|
||||
background: theme.backgroundTransparentHover,
|
||||
height: 52,
|
||||
border: 'none',
|
||||
width: '100%',
|
||||
fontWeight: 'normal',
|
||||
paddingLeft: theme.space.small,
|
||||
paddingRight: theme.space.small,
|
||||
textAlign: 'left',
|
||||
'&:hover, &:focus, &:active': {
|
||||
background: theme.backgroundTransparentHover,
|
||||
},
|
||||
'.ant-typography': {
|
||||
lineHeight: '20px',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
},
|
||||
});
|
||||
|
||||
const AppIcon = styled.div<{appname?: string}>(({appname}) => ({
|
||||
width: 36,
|
||||
height: 36,
|
||||
background: appname
|
||||
? getColorByApp(appname)
|
||||
: theme.backgroundTransparentHover,
|
||||
}));
|
||||
|
||||
function computeEntries(
|
||||
devices: BaseDevice[],
|
||||
dispatch: Dispatch,
|
||||
clients: Client[],
|
||||
uninitializedClients: State['connections']['uninitializedClients'],
|
||||
) {
|
||||
const entries = devices.filter(canBeDefaultDevice).map((device) => {
|
||||
const deviceEntry = (
|
||||
<Menu.Item
|
||||
icon={getOsIcon(device.os)}
|
||||
key={device.serial}
|
||||
style={{fontWeight: 'bold'}}
|
||||
onClick={() => {
|
||||
dispatch(selectDevice(device));
|
||||
}}>
|
||||
{device.displayTitle()}
|
||||
</Menu.Item>
|
||||
);
|
||||
const clientEntries = getAvailableClients(device, clients).map((client) => (
|
||||
<Menu.Item
|
||||
key={client.id}
|
||||
onClick={() => {
|
||||
batch(() => {
|
||||
dispatch(selectDevice(device));
|
||||
dispatch(selectClient(client.id));
|
||||
});
|
||||
}}>
|
||||
<Radio value={client.id}>{client.query.app}</Radio>
|
||||
</Menu.Item>
|
||||
));
|
||||
return [deviceEntry, ...clientEntries];
|
||||
});
|
||||
if (uninitializedClients.length) {
|
||||
entries.push([
|
||||
<Menu.Item key="connecting" style={{fontWeight: 'bold'}}>
|
||||
Currently connecting...
|
||||
</Menu.Item>,
|
||||
...uninitializedClients.map((client) => (
|
||||
<Menu.Item key={'connecting' + client.client.appName}>
|
||||
{client.client.appName}
|
||||
</Menu.Item>
|
||||
)),
|
||||
]);
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
@@ -29,7 +29,6 @@ type ContainerProps = {
|
||||
borderLeft?: boolean;
|
||||
bordered?: boolean;
|
||||
rounded?: boolean;
|
||||
padded?: boolean;
|
||||
width?: number;
|
||||
height?: number;
|
||||
} & PaddingProps;
|
||||
@@ -46,6 +45,7 @@ const Container = styled.div<ContainerProps>(
|
||||
height,
|
||||
...rest
|
||||
}) => ({
|
||||
minWidth: `0`, // ensures the Container can shrink smaller than it's largest
|
||||
width,
|
||||
height,
|
||||
display: 'flex',
|
||||
@@ -101,6 +101,7 @@ const Horizontal = styled(Container)<DistributionProps>(({gap, center}) => ({
|
||||
flexDirection: 'row',
|
||||
gap: normalizeSpace(gap, theme.space.small),
|
||||
alignItems: center ? 'center' : 'stretch',
|
||||
minWidth: 'auto', // corrects 0 on Container
|
||||
}));
|
||||
|
||||
const Vertical = styled(Container)<DistributionProps>(({gap, center}) => ({
|
||||
@@ -118,6 +119,7 @@ type SplitLayoutProps = {
|
||||
/**
|
||||
* If set, items will be centered over the orthogonal direction, if false (the default) items will be stretched.
|
||||
*/
|
||||
center?: boolean;
|
||||
children: [React.ReactNode, React.ReactNode];
|
||||
};
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
shallowEqual,
|
||||
useDispatch as useDispatchBase,
|
||||
} from 'react-redux';
|
||||
import {Dispatch} from 'redux';
|
||||
import {Dispatch as ReduxDispatch} from 'redux';
|
||||
import {State, Actions} from '../reducers/index';
|
||||
|
||||
/**
|
||||
@@ -27,9 +27,11 @@ export function useStore<Selected>(
|
||||
return useSelector(selector, equalityFn);
|
||||
}
|
||||
|
||||
export type Dispatch = ReduxDispatch<Actions>;
|
||||
|
||||
/**
|
||||
* Strongly typed useDispatch wrapper for the Flipper redux store.
|
||||
*/
|
||||
export function useDispatch(): Dispatch<Actions> {
|
||||
return useDispatchBase();
|
||||
export function useDispatch(): Dispatch {
|
||||
return useDispatchBase() as any;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user