/**
* 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,
ExclamationCircleOutlined,
} from '@ant-design/icons';
import {Glyph, Layout, styled} from '../../ui';
import {DeviceOS, theme, useTrackedCallback, useValue} from 'flipper-plugin';
import {batch} from 'react-redux';
import {useDispatch, useStore} from '../../utils/useStore';
import {
canBeDefaultDevice,
getAvailableClients,
selectClient,
selectDevice,
} from '../../reducers/connections';
import BaseDevice from '../../devices/BaseDevice';
import Client from '../../Client';
import {State} from '../../reducers';
import {brandColors, brandIcons, colors} from '../../ui/components/colors';
import {TroubleshootingGuide} from './fb-stubs/TroubleshootingGuide';
import GK from '../../fb-stubs/GK';
const {Text} = Typography;
function getOsIcon(os?: DeviceOS) {
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);
useValue(selectedDevice?.connected, false); // subscribe to future archived state changes
const onSelectDevice = useTrackedCallback(
'select-device',
(device: BaseDevice) => {
batch(() => {
dispatch(selectDevice(device));
dispatch(selectClient(null));
});
},
[],
);
const onSelectApp = useTrackedCallback(
'select-app',
(device: BaseDevice, client: Client) => {
batch(() => {
dispatch(selectDevice(device));
dispatch(selectClient(client.id));
});
},
[],
);
const entries = computeEntries(
devices,
clients,
uninitializedClients,
onSelectDevice,
onSelectApp,
);
const client = clients.find((client) => client.id === selectedApp);
return (
<>
{entries.length ? (
{entries}
}>
{client?.query.app ?? ''}
{selectedDevice?.title || 'Available devices'}
) : (
No devices found
)}
>
);
}
const AppInspectButton = styled(Button)({
background: theme.backgroundTransparentHover,
height: 52,
border: 'none',
fontWeight: 'normal',
flex: `1 1 0`,
overflow: 'hidden', // required for ellipsis
paddingLeft: theme.space.small,
paddingRight: theme.space.small,
textAlign: 'left',
'&:hover, &:focus, &:active': {
background: theme.backgroundTransparentHover,
},
'.ant-typography': {
lineHeight: '20px',
overflow: 'hidden',
textOverflow: 'ellipsis',
},
});
function AppIcon({
appname,
device,
}: {
appname?: string;
device?: BaseDevice | null;
}) {
const invert = appname?.endsWith('Lite') ?? false;
const brandName = appname?.replace(/ Lite$/, '');
const color = brandName
? getColorByApp(brandName)
: theme.backgroundTransparentHover;
const icon = (brandName && (brandIcons as any)[brandName]) ?? device?.icon;
return (
{icon && (
)}
);
}
const AppIconContainer = styled.div({
borderRadius: 4,
width: 36,
height: 36,
padding: 6,
});
function computeEntries(
devices: BaseDevice[],
clients: Client[],
uninitializedClients: State['connections']['uninitializedClients'],
onSelectDevice: (device: BaseDevice) => void,
onSelectApp: (device: BaseDevice, client: Client) => void,
) {
const entries = devices
.filter(
(device) =>
// hide non default devices, unless they have a connected client or plugins
canBeDefaultDevice(device) ||
device.hasDevicePlugins ||
clients.some((c) => c.device === device),
)
.map((device) => {
const deviceEntry = (
{
onSelectDevice(device);
}}>
);
const clientEntries = getAvailableClients(device, clients).map(
(client) => (
{
onSelectApp(device, client);
}}>
),
);
return [deviceEntry, ...clientEntries];
});
if (uninitializedClients.length) {
entries.push([
Currently connecting...
,
...uninitializedClients.map((client) => (
{`${client.appName} (${client.deviceName})`}
)),
]);
}
return entries.flat();
}
function DeviceTitle({device}: {device: BaseDevice}) {
const connected = useValue(device.connected);
const isImported = device.isArchived;
return (
<>{device.title} >
{!connected || isImported ? (
{isImported ? '(Imported)' : '(Offline)'}
) : null}
);
}
function ClientTitle({client}: {client: Client}) {
const connected = useValue(client.connected);
return (
<>{client.query.app} >
{!connected ? (
(Offline)
) : null}
);
}
function getColorByApp(app?: string | null): string {
let iconColor: string | undefined = (brandColors as any)[app!];
if (!iconColor) {
if (!app) {
// Device plugin
iconColor = colors.macOSTitleBarIconBlur;
} else {
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 iconColor;
}