Summary: This diff shows the error screen when one of the following condition is met: - Selected device is not android - No clients are active for selected device - At least there is one client which supports inspector plugin. For this I have added a SupportRequestManager class which loads the error screen or the litho form based on the above condition. The UI is super basic. Will improve the design based on your feedback in the coming diffs. Reviewed By: jknoxville Differential Revision: D17790596 fbshipit-source-id: 2afc917c93c6e7321399633c5bf9d27a703742ee
475 lines
14 KiB
TypeScript
475 lines
14 KiB
TypeScript
/**
|
|
* Copyright 2018-present Facebook.
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
* @format
|
|
*/
|
|
|
|
import config from '../fb-stubs/config';
|
|
import BaseDevice from '../devices/BaseDevice';
|
|
import Client from '../Client';
|
|
import {UninitializedClient} from '../UninitializedClient';
|
|
import {FlipperBasePlugin} from '../plugin';
|
|
import {PluginNotification} from '../reducers/notifications';
|
|
import {ActiveSheet, ACTIVE_SHEET_PLUGINS} from '../reducers/application';
|
|
import {State as Store} from '../reducers';
|
|
import {
|
|
Sidebar,
|
|
FlexBox,
|
|
colors,
|
|
brandColors,
|
|
Text,
|
|
Glyph,
|
|
styled,
|
|
FlexColumn,
|
|
GK,
|
|
FlipperPlugin,
|
|
FlipperDevicePlugin,
|
|
LoadingIndicator,
|
|
} from 'flipper';
|
|
import React, {Component, PureComponent} from 'react';
|
|
import NotificationsHub from '../NotificationsHub';
|
|
import {
|
|
selectPlugin,
|
|
showMoreOrLessPlugins,
|
|
StaticView,
|
|
setStaticView,
|
|
} from '../reducers/connections';
|
|
import {setActiveSheet} from '../reducers/application';
|
|
import UserAccount from './UserAccount';
|
|
import {connect} from 'react-redux';
|
|
import {BackgroundColorProperty} from 'csstype';
|
|
import {
|
|
MAX_MINIMUM_PLUGINS,
|
|
SHOW_REMAINING_PLUGIN_IF_LESS_THAN,
|
|
} from '../Client';
|
|
import {StyledOtherComponent} from 'create-emotion-styled';
|
|
import SupportRequestFormManager from '../fb-stubs/SupportRequestFormManager';
|
|
|
|
const ListItem = styled('div')(({active}: {active?: boolean}) => ({
|
|
paddingLeft: 10,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
marginBottom: 2,
|
|
flexShrink: 0,
|
|
backgroundColor: active ? colors.macOSTitleBarIconSelected : 'none',
|
|
color: active ? colors.white : colors.macOSSidebarSectionItem,
|
|
lineHeight: '25px',
|
|
padding: '0 10px',
|
|
'&[disabled]': {
|
|
color: 'rgba(0, 0, 0, 0.5)',
|
|
},
|
|
}));
|
|
|
|
const SidebarHeader = styled(FlexBox)({
|
|
display: 'block',
|
|
alignItems: 'center',
|
|
padding: 3,
|
|
color: colors.macOSSidebarSectionTitle,
|
|
fontSize: 11,
|
|
fontWeight: 500,
|
|
marginLeft: 7,
|
|
textOverflow: 'ellipsis',
|
|
overflow: 'hidden',
|
|
whiteSpace: 'nowrap',
|
|
flexShrink: 0,
|
|
});
|
|
|
|
const PluginShape = styled(FlexBox)(
|
|
({backgroundColor}: {backgroundColor?: BackgroundColorProperty}) => ({
|
|
marginRight: 5,
|
|
backgroundColor,
|
|
borderRadius: 3,
|
|
flexShrink: 0,
|
|
width: 18,
|
|
height: 18,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
}),
|
|
);
|
|
|
|
const PluginName = styled(Text)(
|
|
(props: {isActive?: boolean; count?: number}) => ({
|
|
minWidth: 0,
|
|
textOverflow: 'ellipsis',
|
|
whiteSpace: 'nowrap',
|
|
overflow: 'hidden',
|
|
display: 'flex',
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
flexGrow: 1,
|
|
'::after': {
|
|
fontSize: 12,
|
|
display: props.count ? 'inline-block' : 'none',
|
|
padding: '0 8px',
|
|
lineHeight: '17px',
|
|
height: 17,
|
|
alignSelf: 'center',
|
|
content: `"${props.count}"`,
|
|
borderRadius: '999em',
|
|
color: props.isActive ? colors.macOSTitleBarIconSelected : colors.white,
|
|
backgroundColor: props.isActive
|
|
? colors.white
|
|
: colors.macOSTitleBarIconSelected,
|
|
fontWeight: 500,
|
|
},
|
|
}),
|
|
);
|
|
|
|
const Plugins = styled(FlexColumn)({
|
|
flexGrow: 1,
|
|
overflow: 'auto',
|
|
});
|
|
|
|
const PluginDebugger = styled(FlexBox)({
|
|
color: colors.blackAlpha50,
|
|
alignItems: 'center',
|
|
padding: 10,
|
|
flexShrink: 0,
|
|
whiteSpace: 'nowrap',
|
|
overflow: 'hidden',
|
|
textOverflow: 'ellipsis',
|
|
});
|
|
|
|
const PluginShowMoreOrLess = styled(ListItem)({
|
|
color: colors.blue,
|
|
fontSize: 10,
|
|
lineHeight: '10px',
|
|
paddingBottom: 5,
|
|
});
|
|
|
|
function PluginIcon({
|
|
isActive,
|
|
backgroundColor,
|
|
name,
|
|
color,
|
|
}: {
|
|
isActive: boolean;
|
|
backgroundColor?: string;
|
|
name: string;
|
|
color: string;
|
|
}) {
|
|
return (
|
|
<PluginShape backgroundColor={backgroundColor}>
|
|
<Glyph size={12} name={name} color={isActive ? colors.white : color} />
|
|
</PluginShape>
|
|
);
|
|
}
|
|
|
|
class PluginSidebarListItem extends Component<{
|
|
onClick: () => void;
|
|
isActive: boolean;
|
|
plugin: typeof FlipperBasePlugin;
|
|
app?: string | null | undefined;
|
|
}> {
|
|
render() {
|
|
const {isActive, plugin} = this.props;
|
|
const app = this.props.app || 'Facebook';
|
|
let iconColor: string | undefined = (brandColors as any)[app];
|
|
|
|
if (!iconColor) {
|
|
const pluginColors = [
|
|
colors.seaFoam,
|
|
colors.teal,
|
|
colors.lime,
|
|
colors.lemon,
|
|
colors.orange,
|
|
colors.tomato,
|
|
colors.cherry,
|
|
colors.pink,
|
|
colors.grape,
|
|
];
|
|
|
|
iconColor = pluginColors[parseInt(app, 36) % pluginColors.length];
|
|
}
|
|
|
|
return (
|
|
<ListItem active={isActive} onClick={this.props.onClick}>
|
|
<PluginIcon
|
|
isActive={isActive}
|
|
name={plugin.icon || 'apps'}
|
|
backgroundColor={iconColor}
|
|
color={colors.white}
|
|
/>
|
|
<PluginName>{plugin.title || plugin.id}</PluginName>
|
|
</ListItem>
|
|
);
|
|
}
|
|
}
|
|
|
|
const Spinner = centerInSidebar(LoadingIndicator);
|
|
|
|
const ErrorIndicator = centerInSidebar(Glyph);
|
|
|
|
function centerInSidebar(
|
|
component: StyledOtherComponent<any, {}, any> | React.ComponentType<any>,
|
|
) {
|
|
return styled(component)({
|
|
marginTop: '10px',
|
|
marginBottom: '10px',
|
|
marginLeft: 'auto',
|
|
marginRight: 'auto',
|
|
});
|
|
}
|
|
|
|
type OwnProps = {};
|
|
|
|
type StateFromProps = {
|
|
numNotifications: number;
|
|
windowIsFocused: boolean;
|
|
selectedDevice: BaseDevice | null | undefined;
|
|
staticView: StaticView;
|
|
selectedPlugin: string | null | undefined;
|
|
selectedApp: string | null | undefined;
|
|
clients: Array<Client>;
|
|
uninitializedClients: Array<{
|
|
client: UninitializedClient;
|
|
deviceId?: string;
|
|
errorMessage?: string;
|
|
}>;
|
|
devicePlugins: Map<string, typeof FlipperDevicePlugin>;
|
|
clientPlugins: Map<string, typeof FlipperPlugin>;
|
|
};
|
|
|
|
type DispatchFromProps = {
|
|
selectPlugin: (payload: {
|
|
selectedPlugin: string | null;
|
|
selectedApp: string | null;
|
|
deepLinkPayload: string | null;
|
|
}) => void;
|
|
setActiveSheet: (activeSheet: ActiveSheet) => void;
|
|
setStaticView: (payload: StaticView) => void;
|
|
showMoreOrLessPlugins: (payload: string) => void;
|
|
};
|
|
|
|
type Props = OwnProps & StateFromProps & DispatchFromProps;
|
|
class MainSidebar extends PureComponent<Props> {
|
|
render() {
|
|
const {
|
|
selectedDevice,
|
|
selectedPlugin,
|
|
staticView,
|
|
selectedApp,
|
|
selectPlugin,
|
|
setStaticView,
|
|
windowIsFocused,
|
|
numNotifications,
|
|
} = this.props;
|
|
let {clients, uninitializedClients} = this.props;
|
|
clients = clients
|
|
.filter(
|
|
(client: Client) =>
|
|
selectedDevice && selectedDevice.supportsOS(client.query.os),
|
|
)
|
|
.sort((a, b) => (a.query.app || '').localeCompare(b.query.app));
|
|
|
|
const byPluginNameOrId = (
|
|
a: typeof FlipperBasePlugin,
|
|
b: typeof FlipperBasePlugin,
|
|
) => ((a.title || a.id) > (b.title || b.id) ? 1 : -1);
|
|
|
|
return (
|
|
<Sidebar
|
|
position="left"
|
|
width={200}
|
|
backgroundColor={
|
|
process.platform === 'darwin' && windowIsFocused ? 'transparent' : ''
|
|
}>
|
|
<Plugins>
|
|
{!GK.get('flipper_disable_notifications') && (
|
|
<ListItem
|
|
active={selectedPlugin === 'notifications'}
|
|
onClick={() =>
|
|
selectPlugin({
|
|
selectedPlugin: 'notifications',
|
|
selectedApp: null,
|
|
deepLinkPayload: null,
|
|
})
|
|
}>
|
|
<PluginIcon
|
|
color={colors.light50}
|
|
name={
|
|
numNotifications > 0
|
|
? NotificationsHub.icon || 'bell'
|
|
: 'bell-null'
|
|
}
|
|
isActive={selectedPlugin === NotificationsHub.id}
|
|
/>
|
|
<PluginName
|
|
count={numNotifications}
|
|
isActive={selectedPlugin === NotificationsHub.id}>
|
|
{NotificationsHub.title}
|
|
</PluginName>
|
|
</ListItem>
|
|
)}
|
|
{GK.get('flipper_support_requests') && (
|
|
<ListItem
|
|
active={
|
|
staticView != null && staticView === SupportRequestFormManager
|
|
}
|
|
onClick={() => setStaticView(SupportRequestFormManager)}>
|
|
<PluginIcon
|
|
color={colors.light50}
|
|
name={'app-dailies'}
|
|
isActive={
|
|
staticView != null && staticView === SupportRequestFormManager
|
|
}
|
|
/>
|
|
<PluginName
|
|
isActive={
|
|
staticView != null && staticView === SupportRequestFormManager
|
|
}>
|
|
Litho Support Request
|
|
</PluginName>
|
|
</ListItem>
|
|
)}
|
|
{selectedDevice && (
|
|
<SidebarHeader>{selectedDevice.title}</SidebarHeader>
|
|
)}
|
|
{selectedDevice &&
|
|
Array.from(this.props.devicePlugins.values())
|
|
.filter(plugin => plugin.supportsDevice(selectedDevice))
|
|
.sort(byPluginNameOrId)
|
|
.map((plugin: typeof FlipperDevicePlugin) => (
|
|
<PluginSidebarListItem
|
|
key={plugin.id}
|
|
isActive={plugin.id === selectedPlugin}
|
|
onClick={() =>
|
|
selectPlugin({
|
|
selectedPlugin: plugin.id,
|
|
selectedApp: null,
|
|
deepLinkPayload: null,
|
|
})
|
|
}
|
|
plugin={plugin}
|
|
/>
|
|
))}
|
|
{clients
|
|
.filter(
|
|
(client: Client) =>
|
|
(selectedDevice &&
|
|
client.query.device_id === selectedDevice.serial) ||
|
|
// Old android sdk versions don't know their device_id
|
|
// Display their plugins under all selected devices until they die out
|
|
client.query.device_id === 'unknown',
|
|
)
|
|
.map((client: Client) => {
|
|
const plugins = Array.from(
|
|
this.props.clientPlugins.values(),
|
|
).filter(
|
|
(p: typeof FlipperPlugin) => client.plugins.indexOf(p.id) > -1,
|
|
);
|
|
|
|
const minShowPluginsCount =
|
|
plugins.length <
|
|
MAX_MINIMUM_PLUGINS + SHOW_REMAINING_PLUGIN_IF_LESS_THAN
|
|
? plugins.length
|
|
: MAX_MINIMUM_PLUGINS;
|
|
|
|
return (
|
|
<React.Fragment key={client.id}>
|
|
<SidebarHeader>{client.query.app}</SidebarHeader>
|
|
{plugins
|
|
.sort((a: typeof FlipperPlugin, b: typeof FlipperPlugin) =>
|
|
client.byClientLRU(plugins.length, a, b),
|
|
)
|
|
.slice(
|
|
0,
|
|
client.showAllPlugins
|
|
? client.plugins.length
|
|
: minShowPluginsCount,
|
|
)
|
|
.map((plugin: typeof FlipperPlugin) => (
|
|
<PluginSidebarListItem
|
|
key={plugin.id}
|
|
isActive={
|
|
plugin.id === selectedPlugin &&
|
|
selectedApp === client.id
|
|
}
|
|
onClick={() =>
|
|
selectPlugin({
|
|
selectedPlugin: plugin.id,
|
|
selectedApp: client.id,
|
|
deepLinkPayload: null,
|
|
})
|
|
}
|
|
plugin={plugin}
|
|
app={client.query.app}
|
|
/>
|
|
))}
|
|
{plugins.length > minShowPluginsCount && (
|
|
<PluginShowMoreOrLess
|
|
onClick={() =>
|
|
this.props.showMoreOrLessPlugins(client.id)
|
|
}>
|
|
{client.showAllPlugins ? 'Show less' : 'Show more'}
|
|
</PluginShowMoreOrLess>
|
|
)}
|
|
</React.Fragment>
|
|
);
|
|
})}
|
|
{uninitializedClients.map(entry => (
|
|
<React.Fragment key={JSON.stringify(entry.client)}>
|
|
<SidebarHeader>{entry.client.appName}</SidebarHeader>
|
|
{entry.errorMessage ? (
|
|
<ErrorIndicator name={'mobile-cross'} size={16} />
|
|
) : (
|
|
<Spinner size={16} />
|
|
)}
|
|
</React.Fragment>
|
|
))}
|
|
</Plugins>
|
|
<PluginDebugger
|
|
onClick={() => this.props.setActiveSheet(ACTIVE_SHEET_PLUGINS)}>
|
|
<Glyph
|
|
name="question-circle"
|
|
size={16}
|
|
variant="outline"
|
|
color={colors.blackAlpha30}
|
|
/>
|
|
Manage Plugins...
|
|
</PluginDebugger>
|
|
{config.showLogin && <UserAccount />}
|
|
</Sidebar>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
|
|
({
|
|
application: {windowIsFocused},
|
|
connections: {
|
|
selectedDevice,
|
|
selectedPlugin,
|
|
selectedApp,
|
|
clients,
|
|
uninitializedClients,
|
|
staticView,
|
|
},
|
|
notifications: {activeNotifications, blacklistedPlugins},
|
|
plugins: {devicePlugins, clientPlugins},
|
|
}) => ({
|
|
numNotifications: (() => {
|
|
const blacklist = new Set(blacklistedPlugins);
|
|
return activeNotifications.filter(
|
|
(n: PluginNotification) => !blacklist.has(n.pluginId),
|
|
).length;
|
|
})(),
|
|
windowIsFocused,
|
|
selectedDevice,
|
|
staticView,
|
|
selectedPlugin,
|
|
selectedApp,
|
|
clients,
|
|
uninitializedClients,
|
|
devicePlugins,
|
|
clientPlugins,
|
|
}),
|
|
{
|
|
selectPlugin,
|
|
setStaticView,
|
|
setActiveSheet,
|
|
showMoreOrLessPlugins,
|
|
},
|
|
)(MainSidebar);
|