/** * 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; }