diff --git a/desktop/app/src/sandy-chrome/DesignComponentDemos.tsx b/desktop/app/src/sandy-chrome/DesignComponentDemos.tsx
index 4cff33a45..36ca9a968 100644
--- a/desktop/app/src/sandy-chrome/DesignComponentDemos.tsx
+++ b/desktop/app/src/sandy-chrome/DesignComponentDemos.tsx
@@ -103,10 +103,10 @@ const demos: PreviewProps[] = [
background: theme.successColor,
}}>
),
- 'bordered padded rounded': (
+ 'bordered pad rounded': (
child
@@ -152,8 +152,8 @@ const demos: PreviewProps[] = [
{aDynamicBox}
),
- 'Using flags: padded center gap={8} (great for toolbars and such)': (
-
+ 'Using flags: pad center gap={8} (great for toolbars and such)': (
+
{aButton}
{someText}
{aBox}
@@ -187,8 +187,8 @@ const demos: PreviewProps[] = [
{aDynamicBox}
),
- 'Using flags: padded center gap={8} (great for toolbars and such)': (
-
+ 'Using flags: pad center gap (great for toolbars and such)': (
+
{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': (
diff --git a/desktop/app/src/sandy-chrome/appinspect/AppInspect.tsx b/desktop/app/src/sandy-chrome/appinspect/AppInspect.tsx
index f10577abd..d3e4bcfd3 100644
--- a/desktop/app/src/sandy-chrome/appinspect/AppInspect.tsx
+++ b/desktop/app/src/sandy-chrome/appinspect/AppInspect.tsx
@@ -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 (
@@ -42,7 +40,7 @@ export function AppInspect() {
App Inspect
-
+
} defaultValue="mysite" />
} type="link" />
@@ -58,36 +56,22 @@ export function AppInspect() {
- Dynamic section
+
+ {selectedDevice ? (
+
+ ) : (
+
+ )}
+
);
}
-function DeviceDropdown() {
+function PluginList() {
return (
-
-
- } style={{fontWeight: 'bold'}}>
- IPhone 11
-
-
- Facebook
-
-
- Instagram
-
- } style={{fontWeight: 'bold'}}>
- Android
-
-
- }>
- } style={{width: '100%'}}>
- Facebook Iphone11
-
-
-
+
+ Plugins
+
);
}
diff --git a/desktop/app/src/sandy-chrome/appinspect/AppSelector.tsx b/desktop/app/src/sandy-chrome/appinspect/AppSelector.tsx
new file mode 100644
index 000000000..4548ee2ff
--- /dev/null
+++ b/desktop/app/src/sandy-chrome/appinspect/AppSelector.tsx
@@ -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 ;
+ case 'Android':
+ return ;
+ case 'Windows':
+ return ;
+ 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 (
+
+
+ {entries.flat()}
+
+ }>
+
+
+
+
+ {client?.query.app ?? ''}
+
+ {selectedDevice?.displayTitle() || 'Available devices'}
+
+
+
+
+
+
+
+ );
+}
+
+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 = (
+
{
+ dispatch(selectDevice(device));
+ }}>
+ {device.displayTitle()}
+
+ );
+ const clientEntries = getAvailableClients(device, clients).map((client) => (
+ {
+ batch(() => {
+ dispatch(selectDevice(device));
+ dispatch(selectClient(client.id));
+ });
+ }}>
+ {client.query.app}
+
+ ));
+ return [deviceEntry, ...clientEntries];
+ });
+ if (uninitializedClients.length) {
+ entries.push([
+
+ Currently connecting...
+ ,
+ ...uninitializedClients.map((client) => (
+
+ {client.client.appName}
+
+ )),
+ ]);
+ }
+ return entries;
+}
diff --git a/desktop/app/src/ui/components/Layout.tsx b/desktop/app/src/ui/components/Layout.tsx
index 52d8863ee..e33569512 100644
--- a/desktop/app/src/ui/components/Layout.tsx
+++ b/desktop/app/src/ui/components/Layout.tsx
@@ -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(
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)(({gap, center}) => ({
flexDirection: 'row',
gap: normalizeSpace(gap, theme.space.small),
alignItems: center ? 'center' : 'stretch',
+ minWidth: 'auto', // corrects 0 on Container
}));
const Vertical = styled(Container)(({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];
};
diff --git a/desktop/app/src/utils/useStore.tsx b/desktop/app/src/utils/useStore.tsx
index 83e61c47c..133ceb716 100644
--- a/desktop/app/src/utils/useStore.tsx
+++ b/desktop/app/src/utils/useStore.tsx
@@ -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(
return useSelector(selector, equalityFn);
}
+export type Dispatch = ReduxDispatch;
+
/**
* Strongly typed useDispatch wrapper for the Flipper redux store.
*/
-export function useDispatch(): Dispatch {
- return useDispatchBase();
+export function useDispatch(): Dispatch {
+ return useDispatchBase() as any;
}