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:
Michel Weststrate
2020-10-20 03:22:15 -07:00
committed by Facebook GitHub Bot
parent 79ac1ef009
commit c38a4413dc
5 changed files with 198 additions and 43 deletions

View File

@@ -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': (

View File

@@ -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>
);
}

View 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;
}

View File

@@ -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];
};

View File

@@ -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;
}