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:
committed by
Facebook GitHub Bot
parent
c38a4413dc
commit
f14a724fa5
@@ -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>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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',
|
||||||
|
});
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export function LaunchEmulatorDialog({
|
|||||||
launchEmulator(name)
|
launchEmulator(name)
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
message.error('Failed to start emulator:' + e);
|
message.error('Failed to start emulator: ' + e);
|
||||||
})
|
})
|
||||||
.finally(onClose);
|
.finally(onClose);
|
||||||
}}>
|
}}>
|
||||||
@@ -84,7 +84,7 @@ export function LaunchEmulatorDialog({
|
|||||||
launchSimulator(device.udid)
|
launchSimulator(device.udid)
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
message.error('Failed to start simulator:' + e);
|
message.error('Failed to start simulator: ' + e);
|
||||||
})
|
})
|
||||||
.finally(onClose);
|
.finally(onClose);
|
||||||
}}>
|
}}>
|
||||||
|
|||||||
@@ -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)',
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user