Device plugin list

Summary:
Render a plugin list

Will add tests once the entire feature is complete

Reviewed By: cekkaewnumchai

Differential Revision: D24079897

fbshipit-source-id: db7250ff612b67ab18dfcacdcb9c44dab596933d
This commit is contained in:
Michel Weststrate
2020-10-20 03:22:15 -07:00
committed by Facebook GitHub Bot
parent c38a4413dc
commit f14a724fa5
10 changed files with 243 additions and 18 deletions

View File

@@ -16,7 +16,7 @@ import {InfoCircleOutlined} from '@ant-design/icons';
export const LeftSidebar: React.FC = ({children}) => ( export const LeftSidebar: React.FC = ({children}) => (
<Layout.Container borderRight padv="small"> <Layout.Container borderRight padv="small">
<Layout.Vertical>{children}</Layout.Vertical> {children}
</Layout.Container> </Layout.Container>
); );

View File

@@ -15,7 +15,6 @@ import {Logger} from '../fb-interfaces/Logger';
import {LeftRail} from './LeftRail'; import {LeftRail} from './LeftRail';
import {TemporarilyTitlebar} from './TemporarilyTitlebar'; import {TemporarilyTitlebar} from './TemporarilyTitlebar';
import SandyDesignSystem from './SandyDesignSystem';
import {registerStartupTime} from '../App'; import {registerStartupTime} from '../App';
import {useStore, useDispatch} from '../utils/useStore'; import {useStore, useDispatch} from '../utils/useStore';
import {SandyContext} from './SandyContext'; import {SandyContext} from './SandyContext';
@@ -23,6 +22,7 @@ import {ConsoleLogs} from '../chrome/ConsoleLogs';
import {setStaticView} from '../reducers/connections'; import {setStaticView} from '../reducers/connections';
import {toggleLeftSidebarVisible} from '../reducers/application'; import {toggleLeftSidebarVisible} from '../reducers/application';
import {AppInspect} from './appinspect/AppInspect'; import {AppInspect} from './appinspect/AppInspect';
import PluginContainer from '../PluginContainer';
export type ToplevelNavItem = 'appinspect' | 'flipperlogs' | undefined; export type ToplevelNavItem = 'appinspect' | 'flipperlogs' | undefined;
export type ToplevelProps = { export type ToplevelProps = {
@@ -101,7 +101,7 @@ export function SandyApp({logger}: {logger: Logger}) {
logger: logger, logger: logger,
}) })
) : ( ) : (
<SandyDesignSystem /> <PluginContainer logger={logger} />
)} )}
</ContentContainer> </ContentContainer>
<Sidebar <Sidebar

View File

@@ -7,16 +7,27 @@
* @format * @format
*/ */
import React from 'react'; import React, {useEffect, useRef, useState} from 'react';
import {Alert, Button, Input} from 'antd'; import {Alert, Badge, Button, Input, Menu, Tooltip, Typography} from 'antd';
import {LeftSidebar, SidebarTitle, InfoIcon} from '../LeftSidebar'; import {LeftSidebar, SidebarTitle, InfoIcon} from '../LeftSidebar';
import {SettingOutlined, RocketOutlined} from '@ant-design/icons'; import {
import {Layout, Link} from '../../ui'; SettingOutlined,
RocketOutlined,
MailOutlined,
AppstoreOutlined,
} from '@ant-design/icons';
import {Glyph, Layout, Link, styled} from '../../ui';
import {theme} from '../theme'; import {theme} from '../theme';
import {useStore as useReduxStore} from 'react-redux'; import {useStore as useReduxStore} from 'react-redux';
import {showEmulatorLauncher} from './LaunchEmulator'; import {showEmulatorLauncher} from './LaunchEmulator';
import {AppSelector} from './AppSelector'; import {AppSelector} from './AppSelector';
import {useStore} from '../../utils/useStore'; import {useDispatch, useStore} from '../../utils/useStore';
import {getPluginTitle, sortPluginsByName} from '../../utils/pluginUtils';
import {PluginDefinition} from '../../plugin';
import {selectPlugin} from '../../reducers/connections';
const {SubMenu} = Menu;
const {Text} = Typography;
const appTooltip = ( const appTooltip = (
<> <>
@@ -56,7 +67,7 @@ export function AppInspect() {
</Layout.Horizontal> </Layout.Horizontal>
</Layout.Vertical> </Layout.Vertical>
</Layout.Container> </Layout.Container>
<Layout.Container padv={theme.space.small}> <Layout.Container padv={theme.space.large}>
{selectedDevice ? ( {selectedDevice ? (
<PluginList /> <PluginList />
) : ( ) : (
@@ -69,9 +80,214 @@ export function AppInspect() {
} }
function PluginList() { function PluginList() {
// const {selectedApp, selectedDevice} = useStore((state) => state.connections);
return ( return (
<Layout.Container> <Layout.Container>
<SidebarTitle>Plugins</SidebarTitle> <SidebarTitle>Plugins</SidebarTitle>
<Layout.Container padv={theme.space.small} padh={theme.space.tiny}>
<PluginMenu
inlineIndent={8}
onClick={() => {}}
defaultOpenKeys={['device']}
mode="inline">
<DevicePlugins key="device" />
<SubMenu
key="sub1"
title={
<Layout.Right center>
<Text strong>Header</Text>
<Badge count={8}></Badge>
</Layout.Right>
}>
<Menu.Item key="1" icon={<AppstoreOutlined />}>
Option 1
</Menu.Item>
<Menu.Item key="2" icon={<AppstoreOutlined />}>
Option 2
</Menu.Item>
<Menu.Item key="3">Option 3</Menu.Item>
<Menu.Item key="4">Option 4</Menu.Item>
</SubMenu>
<SubMenu
key="sub2"
icon={<AppstoreOutlined />}
title="Navigation Two">
<Menu.Item key="5">Option 5</Menu.Item>
<Menu.Item key="6">Option 6</Menu.Item>
<Menu.Item key="7">Option 7</Menu.Item>
<Menu.Item key="8">Option 8</Menu.Item>
</SubMenu>
<SubMenu
key="sub4"
title={
<span>
<SettingOutlined />
<span>Navigation Three</span>
</span>
}>
<Menu.Item key="9">Option 9</Menu.Item>
<Menu.Item key="10">Option 10</Menu.Item>
<Menu.Item key="11">Option 11</Menu.Item>
<Menu.Item key="12">Option 12</Menu.Item>
</SubMenu>
</PluginMenu>
</Layout.Container>
</Layout.Container> </Layout.Container>
); );
} }
function DevicePlugins(props: any) {
const dispatch = useDispatch();
const {selectedDevice, selectedPlugin} = useStore(
(state) => state.connections,
);
const devicePlugins = useStore((state) => state.plugins.devicePlugins);
if (selectedDevice?.devicePlugins.length === 0) {
return null;
}
return (
<SubMenu {...props} title={<Text strong>Device</Text>}>
{selectedDevice!.devicePlugins
.map((pluginName) => devicePlugins.get(pluginName)!)
.sort(sortPluginsByName)
.map((plugin) => (
<Plugin
key={plugin.id}
isActive={plugin.id === selectedPlugin}
onClick={() => {
dispatch(
selectPlugin({
selectedPlugin: plugin.id,
selectedApp: null,
deepLinkPayload: null,
selectedDevice,
}),
);
}}
plugin={plugin}
/>
))}
</SubMenu>
);
}
const Plugin: React.FC<{
onClick: () => void;
isActive: boolean;
plugin: PluginDefinition;
app?: string | null | undefined;
helpRef?: any;
provided?: any;
onFavorite?: () => void;
starred?: boolean; // undefined means: not starrable
}> = function (props) {
const {isActive, plugin, onFavorite, starred, ...rest} = props;
const domRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const node = domRef.current;
if (isActive && node) {
const rect = node.getBoundingClientRect();
if (rect.top < 0 || rect.bottom > document.documentElement.clientHeight) {
node.scrollIntoView();
}
}
}, [isActive]);
return (
<Menu.Item
{...rest}
active={isActive}
onClick={props.onClick}
disabled={starred === false}>
<Tooltip
placement="right"
title={
<>
{getPluginTitle(plugin)} ({plugin.version})
{plugin.details?.description ? (
<>
<br />
<br />
{plugin.details?.description}
</>
) : (
''
)}
</>
}
mouseEnterDelay={1}>
<Layout.Horizontal center gap={10} ref={domRef}>
<PluginIconWrapper>
<Glyph size={16} name={plugin.icon || 'apps'} color="white" />
</PluginIconWrapper>
{getPluginTitle(plugin)}
</Layout.Horizontal>
</Tooltip>
{/* {starred !== undefined && (!starred || isActive) && (
<ToggleButton
onClick={onFavorite}
toggled={starred}
tooltip="Click to enable / disable this plugin"
/>
)} */}
</Menu.Item>
);
};
const iconStyle = {
color: theme.white,
background: theme.primaryColor,
borderRadius: theme.borderRadius,
width: 24,
height: 24,
};
// TODO: move this largely to themes/base.less to make it the default?
// Dimensions are hardcoded as they correlate strongly
const PluginMenu = styled(Menu)({
border: 'none',
'.ant-menu-inline .ant-menu-item, .ant-menu-inline .ant-menu-submenu-title ': {
width: '100%', // reset to remove weird bonus pixel from ANT
},
'.ant-menu-submenu > .ant-menu-submenu-title, .ant-menu-sub.ant-menu-inline > .ant-menu-item': {
borderRadius: theme.borderRadius,
height: '32px',
lineHeight: '24px',
padding: `4px 32px 4px 8px !important`,
'&:hover': {
color: theme.textColorPrimary,
background: theme.backgroundTransparentHover,
},
'&.ant-menu-item-selected::after': {
border: 'none',
},
'&.ant-menu-item-selected': {
color: theme.white,
background: theme.primaryColor,
border: 'none',
},
},
'.ant-menu-submenu-inline > .ant-menu-submenu-title .ant-menu-submenu-arrow': {
right: 8,
},
'.ant-badge-count': {
color: theme.textColorPrimary,
background: theme.backgroundTransparentHover,
fontWeight: 'bold',
padding: `0 10px`,
boxShadow: 'none',
},
'.ant-menu-item .anticon': {
...iconStyle,
lineHeight: '28px', // WUT?
},
});
const PluginIconWrapper = styled.div({
...iconStyle,
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
});

View File

@@ -107,6 +107,7 @@ const AppInspectButton = styled(Button)({
}); });
const AppIcon = styled.div<{appname?: string}>(({appname}) => ({ const AppIcon = styled.div<{appname?: string}>(({appname}) => ({
borderRadius: 4,
width: 36, width: 36,
height: 36, height: 36,
background: appname background: appname

View File

@@ -12,6 +12,7 @@ import {useStore} from '../utils/useStore';
// Exposes all the variables defined in themes/base.less: // Exposes all the variables defined in themes/base.less:
export const theme = { export const theme = {
white: 'white', // use as counter color for primary
primaryColor: 'var(--flipper-primary-color)', primaryColor: 'var(--flipper-primary-color)',
successColor: 'var(--flipper-success-color)', successColor: 'var(--flipper-success-color)',
errorColor: 'var(--flipper-error-color)', errorColor: 'var(--flipper-error-color)',

View File

@@ -204,12 +204,13 @@ Object.keys(Layout).forEach((key) => {
const SandySplitContainer = styled.div<{ const SandySplitContainer = styled.div<{
flex1: number; flex1: number;
flex2: number; flex2: number;
center?: boolean;
flexDirection: CSSProperties['flexDirection']; flexDirection: CSSProperties['flexDirection'];
}>((props) => ({ }>((props) => ({
display: 'flex', display: 'flex',
flex: 1, flex: 1,
flexDirection: props.flexDirection, flexDirection: props.flexDirection,
alignItems: 'stretch', alignItems: props.center ? 'center' : 'stretch',
'> :first-child': { '> :first-child': {
flexGrow: props.flex1, flexGrow: props.flex1,
flexShrink: props.flex1, flexShrink: props.flex1,

View File

@@ -7,6 +7,8 @@
* @format * @format
*/ */
import {theme} from '../../sandy-chrome/theme';
// Last updated: Jan 30 2016 // Last updated: Jan 30 2016
export const colors = { export const colors = {
@@ -278,5 +280,5 @@ export const brandColors = {
Facebook: '#0D7BED', Facebook: '#0D7BED',
Messenger: '#0088FA', Messenger: '#0088FA',
Instagram: '#E61E68', Instagram: '#E61E68',
Flipper: '#8155cb', Flipper: theme.primaryColor,
}; };

View File

@@ -7,13 +7,16 @@
12 12
], ],
"app-react": [ "app-react": [
12 12,
16
], ],
"apps": [ "apps": [
12 12,
16
], ],
"arrow-right": [ "arrow-right": [
12 12,
16
], ],
"bell-null-outline": [ "bell-null-outline": [
24 24
@@ -336,7 +339,8 @@
16 16
], ],
"code": [ "code": [
12 12,
16
], ],
"undo-outline": [ "undo-outline": [
16 16

View File

@@ -11,7 +11,7 @@
// Based on: https://www.figma.com/file/4e6BMdm2SuZ1L7FSuOPQVC/Flipper?node-id=620%3A84614 // Based on: https://www.figma.com/file/4e6BMdm2SuZ1L7FSuOPQVC/Flipper?node-id=620%3A84614
// Link Text & Icon // Link Text & Icon
@primary-color: @purple-7; @primary-color: #722ED1;
// Success // Success
@success-color: @green-7; @success-color: @green-7;
// Negative // Negative