From 8cfe06d53050f0458e5d2f08f1d87d5f3ac4118c Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Mon, 6 Jan 2020 08:47:21 -0800 Subject: [PATCH] New multi app supporting sidebar navigation Summary: This diff changes the sidebar navigation, fixing a bunch of issues: It will be possible to quickly switch again between the same plugins in multiple apps No need to expand-and-check the app dropdown until the app is connected No need for ugly fallback selections if some app connects faster than another one Reviewed By: nikoant Differential Revision: D19272701 fbshipit-source-id: 10f5fab42391014ef4a4a4c91c529d93f8bfb125 --- src/App.tsx | 3 +- src/chrome/mainsidebar/MainSidebar.tsx | 109 +-- src/chrome/mainsidebar/MainSidebar2.tsx | 697 ++++++++++-------- src/chrome/mainsidebar/sidebarUtils.tsx | 124 +++- src/reducers/connections.tsx | 12 +- .../createMockFlipperWithPlugin.tsx | 1 + src/utils/__tests__/messageQueue.node.tsx | 3 + src/utils/icons.js | 2 +- static/icons.json | 5 +- 9 files changed, 540 insertions(+), 416 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 53e60ef87..a0b7ddae4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -135,6 +135,7 @@ export class App extends React.Component { }; render() { + const useNewSidebar = GK.get('flipper_sidebar2'); return ( @@ -143,7 +144,7 @@ export class App extends React.Component { {this.getSheet} {this.props.leftSidebarVisible && - (GK.get('flipper_sidebar2') ? : )} + (useNewSidebar ? : )} {this.props.staticView != null ? ( React.createElement(this.props.staticView, { logger: this.props.logger, diff --git a/src/chrome/mainsidebar/MainSidebar.tsx b/src/chrome/mainsidebar/MainSidebar.tsx index f3f6e2bc5..a846fad0f 100644 --- a/src/chrome/mainsidebar/MainSidebar.tsx +++ b/src/chrome/mainsidebar/MainSidebar.tsx @@ -10,30 +10,24 @@ import BaseDevice from '../../devices/BaseDevice'; import Client from '../../Client'; import {UninitializedClient} from '../../UninitializedClient'; -import {FlipperBasePlugin, sortPluginsByName} from '../../plugin'; +import {sortPluginsByName} from '../../plugin'; import {PluginNotification} from '../../reducers/notifications'; import {ActiveSheet} from '../../reducers/application'; import {State as Store} from '../../reducers'; import { Sidebar, colors, - brandColors, Glyph, styled, - FlexColumn, GK, FlipperPlugin, FlipperDevicePlugin, - LoadingIndicator, Button, - StarButton, - Heading, - Spacer, ArchivedDevice, SmallText, Info, } from 'flipper'; -import React, {Component, PureComponent, Fragment} from 'react'; +import React, {PureComponent, Fragment} from 'react'; import { selectPlugin, starPlugin, @@ -55,6 +49,12 @@ import { PluginsByCategory, PluginName, PluginIcon, + Plugins, + ErrorIndicator, + Spinner, + CategoryName, + PluginSidebarListItem, + NoDevices, } from './sidebarUtils'; const SidebarButton = styled(Button)<{small?: boolean}>(({small}) => ({ @@ -71,78 +71,6 @@ const SidebarButton = styled(Button)<{small?: boolean}>(({small}) => ({ whiteSpace: 'nowrap', })); -const CategoryName = styled(PluginName)({ - color: colors.macOSSidebarSectionTitle, - textTransform: 'uppercase', - fontSize: '0.9em', -}); - -const Plugins = styled(FlexColumn)({ - flexGrow: 1, - overflow: 'auto', -}); - -class PluginSidebarListItem extends Component<{ - onClick: () => void; - isActive: boolean; - plugin: typeof FlipperBasePlugin; - app?: string | null | undefined; - helpRef?: any; - provided?: any; - onFavorite?: () => void; - starred?: boolean; -}> { - render() { - const {isActive, plugin, onFavorite, starred} = 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 ( - - - {plugin.title || plugin.id} - {starred !== undefined && ( - - )} - - ); - } -} - -const Spinner = centerInSidebar(LoadingIndicator); - -const ErrorIndicator = centerInSidebar(Glyph); - -function centerInSidebar(component: any) { - return styled(component)({ - marginTop: '10px', - marginBottom: '10px', - marginLeft: 'auto', - marginRight: 'auto', - }); -} - type OwnProps = {}; type StateFromProps = { @@ -168,6 +96,7 @@ type DispatchFromProps = { selectedPlugin: string | null; selectedApp: string | null; deepLinkPayload: string | null; + selectedDevice: BaseDevice; }) => void; selectClient: typeof selectClient; setActiveSheet: (activeSheet: ActiveSheet) => void; @@ -217,6 +146,7 @@ class MainSidebar extends PureComponent { selectedPlugin: plugin.id, selectedApp: null, deepLinkPayload: null, + selectedDevice, }) } plugin={plugin} @@ -270,16 +200,7 @@ class MainSidebar extends PureComponent { ))} ) : ( - - - - Select a device to get started - + )} @@ -331,7 +252,12 @@ class MainSidebar extends PureComponent { starred: boolean, onFavorite: (pluginId: string) => void, ) { - const {selectedPlugin, selectedApp, selectPlugin} = this.props; + const { + selectedPlugin, + selectedApp, + selectPlugin, + selectedDevice, + } = this.props; return groupPluginsByCategory(plugins).map(([category, plugins]) => ( {category && ( @@ -348,6 +274,7 @@ class MainSidebar extends PureComponent { selectedPlugin: plugin.id, selectedApp: client.id, deepLinkPayload: null, + selectedDevice: selectedDevice!, }) } plugin={plugin} diff --git a/src/chrome/mainsidebar/MainSidebar2.tsx b/src/chrome/mainsidebar/MainSidebar2.tsx index f3f6e2bc5..321f901b1 100644 --- a/src/chrome/mainsidebar/MainSidebar2.tsx +++ b/src/chrome/mainsidebar/MainSidebar2.tsx @@ -10,144 +10,128 @@ import BaseDevice from '../../devices/BaseDevice'; import Client from '../../Client'; import {UninitializedClient} from '../../UninitializedClient'; -import {FlipperBasePlugin, sortPluginsByName} from '../../plugin'; +import {sortPluginsByName} from '../../plugin'; import {PluginNotification} from '../../reducers/notifications'; import {ActiveSheet} from '../../reducers/application'; import {State as Store} from '../../reducers'; import { Sidebar, colors, - brandColors, Glyph, styled, - FlexColumn, GK, FlipperPlugin, FlipperDevicePlugin, - LoadingIndicator, - Button, - StarButton, - Heading, - Spacer, ArchivedDevice, SmallText, Info, + HBox, } from 'flipper'; -import React, {Component, PureComponent, Fragment} from 'react'; +import React, { + PureComponent, + Fragment, + memo, + useCallback, + useState, +} from 'react'; +import NotificationScreen from '../NotificationScreen'; import { selectPlugin, - starPlugin, + starPlugin as starPluginAction, StaticView, setStaticView, - selectClient, getAvailableClients, - getClientById, + canBeDefaultDevice, } from '../../reducers/connections'; import {setActiveSheet} from '../../reducers/application'; import {connect} from 'react-redux'; import SupportRequestFormManager from '../../fb-stubs/SupportRequestFormManager'; import SupportRequestDetails from '../../fb-stubs/SupportRequestDetails'; -import MainSidebarUtils from './MainSidebarUtilsSection'; +import MainSidebarUtilsSection from './MainSidebarUtilsSection'; import { ListItem, - isStaticViewActive, - FlipperPlugins, - PluginsByCategory, PluginName, + Plugins, + CategoryName, PluginIcon, + PluginSidebarListItem, + ErrorIndicator, + NoClients, + Spinner, + NoDevices, + getColorByApp, } from './sidebarUtils'; -const SidebarButton = styled(Button)<{small?: boolean}>(({small}) => ({ - fontWeight: 'bold', - fontSize: small ? 11 : 14, - width: '100%', - overflow: 'hidden', - marginTop: small ? 0 : 20, - pointer: 'cursor', +type FlipperPlugins = typeof FlipperPlugin[]; +type PluginsByCategory = [string, FlipperPlugins][]; + +type SectionLevel = 1 | 2 | 3; + +const SidebarSectionButton = styled('button')<{ + level: SectionLevel; + color: string; +}>(({level, color}) => ({ + fontWeight: level === 3 ? 'normal' : 'bold', + borderRadius: 0, border: 'none', - background: 'none', - padding: 0, - justifyContent: 'left', - whiteSpace: 'nowrap', + background: level === 1 ? colors.sectionHeaderBorder : 'transparent', + textAlign: level === 3 ? 'center' : 'left', + width: '100%', + fontSize: level === 3 ? 11 : 14, + color, + padding: `${level === 3 ? 0 : 8}px 10px 8px 9px`, })); -const CategoryName = styled(PluginName)({ - color: colors.macOSSidebarSectionTitle, - textTransform: 'uppercase', - fontSize: '0.9em', -}); +const SidebarSectionBody = styled('div')<{ + level: SectionLevel; + collapsed: boolean; +}>(({collapsed}) => ({ + flexShrink: 0, + overflow: 'hidden', + maxHeight: collapsed ? 0 : 1000, // might need increase if too many plugins... + transition: 'max-height 0.5s ease', +})); -const Plugins = styled(FlexColumn)({ - flexGrow: 1, - overflow: 'auto', -}); +const SidebarSection: React.FC<{ + defaultCollapsed?: boolean; + title: string | React.ReactNode | ((collapsed: boolean) => React.ReactNode); + level: SectionLevel; + color?: string; +}> = ({children, title, level, color, defaultCollapsed}) => { + const [collapsed, setCollapsed] = useState(!!defaultCollapsed); + color = color || colors.macOSTitleBarIconActive; -class PluginSidebarListItem extends Component<{ - onClick: () => void; - isActive: boolean; - plugin: typeof FlipperBasePlugin; - app?: string | null | undefined; - helpRef?: any; - provided?: any; - onFavorite?: () => void; - starred?: boolean; -}> { - render() { - const {isActive, plugin, onFavorite, starred} = 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 ( - - - {plugin.title || plugin.id} - {starred !== undefined && ( - - )} - - ); - } -} - -const Spinner = centerInSidebar(LoadingIndicator); - -const ErrorIndicator = centerInSidebar(Glyph); - -function centerInSidebar(component: any) { - return styled(component)({ - marginTop: '10px', - marginBottom: '10px', - marginLeft: 'auto', - marginRight: 'auto', - }); -} + return ( + <> + setCollapsed(s => !s)} + level={level} + color={color}> + + {typeof title === 'function' ? title(collapsed) : title} + {level < 3 && ( + + )} + + + + {level === 1 &&
} + {children} + + + ); +}; type OwnProps = {}; type StateFromProps = { numNotifications: number; windowIsFocused: boolean; + devices: BaseDevice[]; selectedDevice: BaseDevice | null | undefined; staticView: StaticView; selectedPlugin: string | null | undefined; @@ -163,132 +147,137 @@ type StateFromProps = { clientPlugins: Map; }; +type SelectPlugin = (payload: { + selectedPlugin: string | null; + selectedApp?: string | null; + deepLinkPayload: string | null; + selectedDevice: BaseDevice; +}) => void; + type DispatchFromProps = { - selectPlugin: (payload: { - selectedPlugin: string | null; - selectedApp: string | null; - deepLinkPayload: string | null; - }) => void; - selectClient: typeof selectClient; + selectPlugin: SelectPlugin; setActiveSheet: (activeSheet: ActiveSheet) => void; setStaticView: (payload: StaticView) => void; - starPlugin: typeof starPlugin; + starPlugin: typeof starPluginAction; }; type Props = OwnProps & StateFromProps & DispatchFromProps; type State = { + showSupportForm: boolean; + showWatchDebugRoot: boolean; showAllPlugins: boolean; }; -class MainSidebar extends PureComponent { +class MainSidebar2 extends PureComponent { state: State = { + showSupportForm: GK.get('support_requests_v2'), + showWatchDebugRoot: GK.get('watch_team_flipper_clientless_access'), showAllPlugins: false, }; + static getDerivedStateFromProps(props: Props, state: State) { + if ( + !state.showSupportForm && + props.staticView === SupportRequestFormManager + ) { + // Show SupportForm option even when GK is false and support form is shown. + // That means the user has used deeplink to open support form. + // Once the variable is true, it will be true for the whole session. + return {showSupportForm: true}; + } + return state; + } render() { - const { - selectedDevice, - selectClient, - selectedPlugin, - selectedApp, - selectPlugin, - uninitializedClients, - } = this.props; - const clients = getAvailableClients(selectedDevice, this.props.clients); - const client: Client | undefined = getClientById(clients, selectedApp); + const devices = this.props.devices + .slice() + .sort((a, b) => a.title.localeCompare(b.title)); + const renderableDevices = devices.filter(canBeDefaultDevice); return ( - {selectedDevice ? ( - <> - - {selectedDevice.title} - - {this.showArchivedDeviceDetails(selectedDevice)} - {selectedDevice.devicePlugins.map(pluginName => { - const plugin = this.props.devicePlugins.get(pluginName)!; - return ( - - selectPlugin({ - selectedPlugin: plugin.id, - selectedApp: null, - deepLinkPayload: null, - }) - } - plugin={plugin} - /> - ); - })} - - ({ - checked: client === c, - label: c.query.app, - type: 'checkbox', - click: () => selectClient(c.id), - }))}> - {clients.length === 0 ? ( - <> - - No clients connected - - ) : !client ? ( - 'Select client' - ) : ( - <> - {client.query.app} - - - )} - - - {this.renderClientPlugins(client)} - {uninitializedClients.map(entry => ( - - {entry.client.appName} - {entry.errorMessage ? ( - - ) : ( - - )} - - ))} - + {renderableDevices.length ? ( + renderableDevices.map(device => this.renderDevice(device)) ) : ( - - - - Select a device to get started - + )} - + ); } - showArchivedDeviceDetails(selectedDevice: BaseDevice) { - if (!selectedDevice.isArchived || !selectedDevice.source) { + renderDevice(device: BaseDevice) { + const { + selectedPlugin, + selectPlugin, + uninitializedClients, + clientPlugins, + starPlugin, + userStarredPlugins, + selectedApp, + selectedDevice, + } = this.props; + const clients = getAvailableClients(device, this.props.clients); + + return ( + + {this.showArchivedDeviceDetails(device)} + + {device.devicePlugins.map(pluginName => { + const plugin = this.props.devicePlugins.get(pluginName)!; + return ( + + selectPlugin({ + selectedPlugin: plugin.id, + selectedApp: null, + deepLinkPayload: null, + selectedDevice: device, + }) + } + plugin={plugin} + /> + ); + })} + + {uninitializedClients.map(entry => ( + + {entry.client.appName} + {entry.errorMessage ? ( + + ) : ( + + )} + + ))} + {clients.length === 0 ? ( + + ) : ( + clients.map(client => ( + + )) + )} + + ); + } + + showArchivedDeviceDetails(device: BaseDevice) { + if (!device.isArchived || !device.source) { return null; } const {staticView, setStaticView} = this.props; @@ -296,18 +285,15 @@ class MainSidebar extends PureComponent { staticView, SupportRequestDetails, ); - const showSupportForm = - GK.get('support_requests_v2') || - isStaticViewActive(staticView, SupportRequestFormManager); return ( <> - {selectedDevice.source ? 'Imported device' : 'Archived device'} + {device.source ? 'Imported device' : 'Archived device'} - {showSupportForm && - (selectedDevice as ArchivedDevice).supportRequestDetails && ( + {this.state.showSupportForm && + (device as ArchivedDevice).supportRequestDetails && ( setStaticView(SupportRequestDetails)}> @@ -325,127 +311,42 @@ class MainSidebar extends PureComponent { ); } - renderPluginsByCategory( - client: Client, - plugins: FlipperPlugins, - starred: boolean, - onFavorite: (pluginId: string) => void, - ) { - const {selectedPlugin, selectedApp, selectPlugin} = this.props; - return groupPluginsByCategory(plugins).map(([category, plugins]) => ( - - {category && ( - - {category} - - )} - {plugins.map(plugin => ( - - selectPlugin({ - selectedPlugin: plugin.id, - selectedApp: client.id, - deepLinkPayload: null, - }) - } - plugin={plugin} - app={client.query.app} - onFavorite={() => onFavorite(plugin.id)} - starred={starred} - /> - ))} - - )); - } - - renderClientPlugins(client?: Client) { - if (!client) { + renderNotificationsEntry() { + if (GK.get('flipper_disable_notifications')) { return null; } - const onFavorite = (plugin: string) => { - this.props.starPlugin({ - selectedApp: client.query.app, - selectedPlugin: plugin, - }); - }; - const allPlugins = Array.from(this.props.clientPlugins.values()).filter( - (p: typeof FlipperPlugin) => client.plugins.indexOf(p.id) > -1, + + const active = isStaticViewActive( + this.props.staticView, + NotificationScreen, ); - const favoritePlugins: FlipperPlugins = getFavoritePlugins( - allPlugins, - this.props.userStarredPlugins[client.query.app], - true, - ); - const showAllPlugins = - this.state.showAllPlugins || - favoritePlugins.length === 0 || - // If the plugin is part of the hidden section, make sure sidebar is expanded - (client.plugins.includes(this.props.selectedPlugin!) && - !favoritePlugins.find( - plugin => plugin.id === this.props.selectedPlugin, - )); return ( - <> - {favoritePlugins.length === 0 ? ( - - Star your favorite plugins! - - ) : ( - <> - {this.renderPluginsByCategory( - client, - favoritePlugins, - true, - onFavorite, - )} - - - this.setState(state => ({ - ...state, - showAllPlugins: !state.showAllPlugins, - })) - }> - {showAllPlugins ? 'Show less' : 'Show more'} - - - - - )} -
- {showAllPlugins - ? this.renderPluginsByCategory( - client, - getFavoritePlugins( - allPlugins, - this.props.userStarredPlugins[client.query.app], - false, - ), - false, - onFavorite, - ) - : null} -
- + this.props.setStaticView(NotificationScreen)} + style={{ + borderTop: `1px solid ${colors.blackAlpha10}`, + }}> + 0 ? 'bell' : 'bell-null'} + isActive={active} + /> + + Notifications + + ); } } +function isStaticViewActive( + current: StaticView, + selected: StaticView, +): boolean { + return current && selected && current === selected; +} + function getFavoritePlugins( allPlugins: FlipperPlugins, starredPlugins: undefined | string[], @@ -481,6 +382,7 @@ export default connect( ({ application: {windowIsFocused}, connections: { + devices, selectedDevice, selectedPlugin, selectedApp, @@ -499,6 +401,7 @@ export default connect( ).length; })(), windowIsFocused, + devices, selectedDevice, staticView, selectedPlugin, @@ -511,9 +414,167 @@ export default connect( }), { selectPlugin, - selectClient, setStaticView, setActiveSheet, - starPlugin, + starPlugin: starPluginAction, }, -)(MainSidebar); +)(MainSidebar2); + +const PluginList = memo(function PluginList({ + client, + device, + clientPlugins, + starPlugin, + userStarredPlugins, + selectedPlugin, + selectedApp, + selectPlugin, +}: { + client: Client; + device: BaseDevice; + clientPlugins: Map; + starPlugin: typeof starPluginAction; + userStarredPlugins: Store['connections']['userStarredPlugins']; + selectedPlugin?: null | string; + selectPlugin: SelectPlugin; + selectedApp?: null | string; +}) { + const onFavorite = useCallback( + (plugin: string) => { + starPlugin({ + selectedApp: client.query.app, + selectedPlugin: plugin, + }); + }, + [client], + ); + + const allPlugins = Array.from(clientPlugins.values()).filter( + (p: typeof FlipperPlugin) => client.plugins.indexOf(p.id) > -1, + ); + const favoritePlugins: FlipperPlugins = getFavoritePlugins( + allPlugins, + userStarredPlugins[client.query.app], + true, + ); + const selectedNonFavoritePlugin = + selectedApp === client.id && + client.plugins.includes(selectedPlugin!) && + !favoritePlugins.find(plugin => plugin.id === selectedPlugin); + const allPluginsStarred = favoritePlugins.length === allPlugins.length; + + return ( + + {favoritePlugins.length === 0 ? ( + + Star your favorite plugins! + + ) : ( + + )} + {!allPluginsStarred && ( + 0 && !selectedNonFavoritePlugin + } + title={collapsed => ( +
+ {collapsed ? 'All plugins…' : 'Show less'} + +
+ )}> + +
+ )} +
+ ); +}); + +const PluginsByCategory = memo(function PluginsByCategory({ + client, + plugins, + starred, + onFavorite, + selectedPlugin, + selectedApp, + selectPlugin, + device, +}: { + client: Client; + device: BaseDevice; + plugins: FlipperPlugins; + starred: boolean; + selectedPlugin?: null | string; + selectedApp?: null | string; + onFavorite: (pluginId: string) => void; + selectPlugin: SelectPlugin; +}) { + return ( + <> + {groupPluginsByCategory(plugins).map(([category, plugins]) => ( + + {category && ( + + {category} + + )} + {plugins.map(plugin => ( + + selectPlugin({ + selectedPlugin: plugin.id, + selectedApp: client.id, + deepLinkPayload: null, + selectedDevice: device, + }) + } + plugin={plugin} + app={client.query.app} + onFavorite={() => onFavorite(plugin.id)} + starred={starred} + /> + ))} + + ))} + + ); +}); diff --git a/src/chrome/mainsidebar/sidebarUtils.tsx b/src/chrome/mainsidebar/sidebarUtils.tsx index d7f8d869b..ce903c048 100644 --- a/src/chrome/mainsidebar/sidebarUtils.tsx +++ b/src/chrome/mainsidebar/sidebarUtils.tsx @@ -7,8 +7,22 @@ * @format */ -import {FlexBox, colors, Text, Glyph, styled, FlipperPlugin} from 'flipper'; -import React from 'react'; +import { + FlexBox, + colors, + Text, + Glyph, + styled, + FlipperPlugin, + FlexColumn, + LoadingIndicator, + FlipperBasePlugin, + StarButton, + brandColors, + Spacer, + Heading, +} from 'flipper'; +import React, {Component} from 'react'; import {StaticView} from '../../reducers/connections'; import {BackgroundColorProperty} from 'csstype'; @@ -96,3 +110,109 @@ export function isStaticViewActive( ): boolean { return current && selected && current === selected; } + +export const CategoryName = styled(PluginName)({ + color: colors.macOSSidebarSectionTitle, + textTransform: 'uppercase', + fontSize: '0.9em', +}); + +export const Plugins = styled(FlexColumn)({ + flexGrow: 1, + overflow: 'auto', +}); + +export const Spinner = centerInSidebar(LoadingIndicator); + +export const ErrorIndicator = centerInSidebar(Glyph); + +export function centerInSidebar(component: any) { + return styled(component)({ + marginTop: '10px', + marginBottom: '10px', + marginLeft: 'auto', + marginRight: 'auto', + }); +} + +export class PluginSidebarListItem extends Component<{ + onClick: () => void; + isActive: boolean; + plugin: typeof FlipperBasePlugin; + app?: string | null | undefined; + helpRef?: any; + provided?: any; + onFavorite?: () => void; + starred?: boolean; +}> { + render() { + const {isActive, plugin, onFavorite, starred} = this.props; + const iconColor = getColorByApp(this.props.app); + + return ( + + + {plugin.title || plugin.id} + {starred !== undefined && ( + + )} + + ); + } +} + +export 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; +} + +export const NoDevices = () => ( + + + + Select a device to get started + +); + +export const NoClients = () => ( + + + No clients connected + +); diff --git a/src/reducers/connections.tsx b/src/reducers/connections.tsx index bf3220166..53f684df3 100644 --- a/src/reducers/connections.tsx +++ b/src/reducers/connections.tsx @@ -87,6 +87,7 @@ export type Action = selectedPlugin: null | string; selectedApp?: null | string; deepLinkPayload: null | string; + selectedDevice?: null | BaseDevice; }; } | { @@ -224,6 +225,10 @@ const reducer = (state: State = INITAL_STATE, action: Actions): State => { case 'SELECT_PLUGIN': { const {payload} = action; const {selectedPlugin, selectedApp} = payload; + const selectedDevice = payload.selectedDevice || state.selectedDevice; + if (!selectDevice) { + console.warn('Trying to select a plugin before a device was selected!'); + } if (selectedPlugin) { performance.mark(`activePlugin-${selectedPlugin}`); } @@ -234,6 +239,10 @@ const reducer = (state: State = INITAL_STATE, action: Actions): State => { selectedApp: selectedApp || null, selectedPlugin, userPreferredPlugin: selectedPlugin || state.userPreferredPlugin, + selectedDevice: selectedDevice!, + userPreferredDevice: selectedDevice + ? selectedDevice.title + : state.userPreferredDevice, }); } @@ -450,6 +459,7 @@ export const preferDevice = (payload: string): Action => ({ export const selectPlugin = (payload: { selectedPlugin: null | string; selectedApp?: null | string; + selectedDevice?: BaseDevice | null; deepLinkPayload: null | string; }): Action => ({ type: 'SELECT_PLUGIN', @@ -517,7 +527,7 @@ export function getClientById( return clients.find(client => client.id === clientId); } -function canBeDefaultDevice(device: BaseDevice) { +export function canBeDefaultDevice(device: BaseDevice) { return !DEFAULT_DEVICE_BLACKLIST.some( blacklistedDevice => device instanceof blacklistedDevice, ); diff --git a/src/test-utils/createMockFlipperWithPlugin.tsx b/src/test-utils/createMockFlipperWithPlugin.tsx index 304eba665..fd756bc59 100644 --- a/src/test-utils/createMockFlipperWithPlugin.tsx +++ b/src/test-utils/createMockFlipperWithPlugin.tsx @@ -111,6 +111,7 @@ export async function createMockFlipperWithPlugin( selectedPlugin: pluginClazz.id, selectedApp: client.query.app, deepLinkPayload: null, + selectedDevice: device, }), ); diff --git a/src/utils/__tests__/messageQueue.node.tsx b/src/utils/__tests__/messageQueue.node.tsx index d32829e6c..0e04c2613 100644 --- a/src/utils/__tests__/messageQueue.node.tsx +++ b/src/utils/__tests__/messageQueue.node.tsx @@ -63,6 +63,7 @@ function selectDeviceLogs(store: Store) { selectedPlugin: 'DeviceLogs', selectedApp: null, deepLinkPayload: null, + selectedDevice: store.getState().connections.selectedDevice!, }), ); } @@ -73,6 +74,7 @@ function selectTestPlugin(store: Store, client: Client) { selectedPlugin: TestPlugin.id, selectedApp: client.query.app, deepLinkPayload: null, + selectedDevice: store.getState().connections.selectedDevice!, }), ); } @@ -279,6 +281,7 @@ test('queue - messages that arrive during processing will be queued', async () = selectedPlugin: TestPlugin.id, selectedApp: client.id, deepLinkPayload: null, + selectedDevice: device, }), ); expect(store.getState().connections.selectedPlugin).toBe('TestPlugin'); diff --git a/src/utils/icons.js b/src/utils/icons.js index 6614b09cc..5f02f0f72 100644 --- a/src/utils/icons.js +++ b/src/utils/icons.js @@ -73,7 +73,7 @@ function buildIconURL(name, size, density) { // Check if that icon actually exists! fetch(url) .then(res => { - if (res.status === 200) { + if (res.status === 200 && !existing.includes(size)) { // the icon exists existing.push(size); existing.sort(); diff --git a/static/icons.json b/static/icons.json index b97ee9299..f3a023376 100644 --- a/static/icons.json +++ b/static/icons.json @@ -81,8 +81,9 @@ 16 ], "chevron-up": [ - 8, - 12 + 12, + 16, + 8 ], "compose": [ 12