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" /> - - + + 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; }