diff --git a/desktop/app/src/chrome/PluginActions.tsx b/desktop/app/src/chrome/PluginActions.tsx new file mode 100644 index 000000000..2e5bb7712 --- /dev/null +++ b/desktop/app/src/chrome/PluginActions.tsx @@ -0,0 +1,126 @@ +/** + * 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 { + DownloadOutlined, + LoadingOutlined, + PlusOutlined, +} from '@ant-design/icons'; +import {Alert, Button} from 'antd'; +import { + BundledPluginDetails, + DownloadablePluginDetails, +} from 'flipper-plugin-lib'; +import React, {useMemo} from 'react'; +import {useCallback} from 'react'; +import {useDispatch, useSelector} from 'react-redux'; +import {PluginDefinition} from '../plugin'; +import {startPluginDownload} from '../reducers/pluginDownloads'; +import {loadPlugin, switchPlugin} from '../reducers/pluginManager'; +import { + getActiveClient, + getPluginDownloadStatusMap, +} from '../selectors/connections'; +import {Layout} from '../ui'; +import {ActivePluginListItem} from '../utils/pluginUtils'; + +export function PluginActions({ + activePlugin, + type, +}: { + activePlugin: ActivePluginListItem; + type: 'link' | 'primary'; +}) { + switch (activePlugin.status) { + case 'disabled': { + return ; + } + case 'uninstalled': { + return ; + } + case 'unavailable': { + return type === 'primary' ? ( + + ) : null; + } + default: + return null; + } +} + +function EnableButton({ + plugin, + type, +}: { + plugin: PluginDefinition; + type: 'link' | 'primary'; +}) { + const dispatch = useDispatch(); + const client = useSelector(getActiveClient); + const enableOrDisablePlugin = useCallback(() => { + dispatch(switchPlugin({plugin, selectedApp: client?.query?.app})); + }, [dispatch, plugin, client]); + return ( + + ); +} + +function UnavailabilityAlert({reason}: {reason: string}) { + return ( + + + + ); +} + +function InstallButton({ + plugin, + type = 'primary', +}: { + plugin: DownloadablePluginDetails | BundledPluginDetails; + type: 'link' | 'primary'; +}) { + const dispatch = useDispatch(); + const installPlugin = useCallback(() => { + if (plugin.isBundled) { + dispatch(loadPlugin({plugin, enable: true, notifyIfFailed: true})); + } else { + dispatch(startPluginDownload({plugin, startedByUser: true})); + } + }, [plugin, dispatch]); + const downloads = useSelector(getPluginDownloadStatusMap); + const downloadStatus = useMemo( + () => downloads.get(plugin.id), + [downloads, plugin], + ); + return ( + + ); +} diff --git a/desktop/app/src/chrome/fb-stubs/PluginInfo.tsx b/desktop/app/src/chrome/fb-stubs/PluginInfo.tsx index b5611d3d4..22489bdaf 100644 --- a/desktop/app/src/chrome/fb-stubs/PluginInfo.tsx +++ b/desktop/app/src/chrome/fb-stubs/PluginInfo.tsx @@ -7,94 +7,46 @@ * @format */ -import React, {useCallback, useMemo} from 'react'; -import {Label, ToggleButton, SmallText, styled, Layout} from '../../ui'; -import {useDispatch, useStore} from '../../utils/useStore'; -import {switchPlugin} from '../../reducers/pluginManager'; -import {isPluginEnabled} from '../../reducers/connections'; -import {theme} from 'flipper-plugin'; -import {PluginDefinition} from '../../plugin'; +import React from 'react'; import {useSelector} from 'react-redux'; -import {getActiveClient} from '../../selectors/connections'; +import {getActivePlugin} from '../../selectors/connections'; +import {ActivePluginListItem} from '../../utils/pluginUtils'; +import {Layout} from '../../ui'; +import {CenteredContainer} from '../../sandy-chrome/CenteredContainer'; +import {Typography} from 'antd'; +import {PluginActions} from '../PluginActions'; +import {CoffeeOutlined} from '@ant-design/icons'; -const Waiting = styled(Layout.Container)({ - width: '100%', - height: '100%', - flexGrow: 1, - alignItems: 'center', - justifyContent: 'center', - textAlign: 'center', -}); +const {Text, Title} = Typography; export function PluginInfo() { - const pluginId = useStore((state) => state.connections.selectedPlugin); - const enabledPlugins = useStore((state) => state.connections.enabledPlugins); - const enabledDevicePlugins = useStore( - (state) => state.connections.enabledDevicePlugins, - ); - const activeClient = useSelector(getActiveClient); - const clientPlugins = useStore((state) => state.plugins.clientPlugins); - const devicePlugins = useStore((state) => state.plugins.devicePlugins); - const selectedClientId = activeClient?.id ?? null; - const selectedApp = activeClient?.query.app ?? null; - const disabledPlugin = useMemo( - () => - pluginId && - !isPluginEnabled( - enabledPlugins, - enabledDevicePlugins, - selectedClientId, - pluginId, - ) - ? clientPlugins.get(pluginId) ?? devicePlugins.get(pluginId) - : undefined, - [ - pluginId, - enabledPlugins, - enabledDevicePlugins, - selectedClientId, - clientPlugins, - devicePlugins, - ], - ); - return disabledPlugin ? ( - - ) : null; + const activePlugin = useSelector(getActivePlugin); + if (activePlugin) { + return ; + } else { + return null; + } } -function PluginEnabler({ - plugin, - selectedApp, +function PluginMarketplace({ + activePlugin, }: { - plugin: PluginDefinition; - selectedApp: string | null; + activePlugin: ActivePluginListItem; }) { - const dispatch = useDispatch(); - const enablePlugin = useCallback(() => { - dispatch(switchPlugin({plugin, selectedApp: selectedApp ?? undefined})); - }, [dispatch, plugin, selectedApp]); return ( - - - - - - - - - - - - Click to enable this plugin - - - + + + + + Plugin '{activePlugin.details.title}' is {activePlugin.status} + + {activePlugin.status === 'unavailable' ? ( + {activePlugin.reason}. + ) : null} + + + + + ); } diff --git a/desktop/app/src/sandy-chrome/appinspect/__tests__/PluginList.spec.tsx b/desktop/app/src/sandy-chrome/appinspect/__tests__/PluginList.spec.tsx index abfd26b29..aae5a07ee 100644 --- a/desktop/app/src/sandy-chrome/appinspect/__tests__/PluginList.spec.tsx +++ b/desktop/app/src/sandy-chrome/appinspect/__tests__/PluginList.spec.tsx @@ -244,7 +244,7 @@ describe('basic getActiveDevice with metro present', () => { unavailablePlugins: [ [ gateKeepedPlugin, - "This plugin is only available to members of gatekeeper 'not for you'", + "Plugin 'Gatekeeped Plugin' is only available to members of gatekeeper 'not for you'", ], [ unsupportedDevicePlugin.details, diff --git a/desktop/app/src/utils/pluginUtils.tsx b/desktop/app/src/utils/pluginUtils.tsx index f894b772e..fa28e2417 100644 --- a/desktop/app/src/utils/pluginUtils.tsx +++ b/desktop/app/src/utils/pluginUtils.tsx @@ -301,18 +301,21 @@ export function computePluginLists( // process problematic plugins plugins.disabledPlugins.forEach((plugin) => { - unavailablePlugins.push([plugin, 'Plugin is disabled by configuration']); + unavailablePlugins.push([ + plugin, + `Plugin '${plugin.title}' is disabled by configuration`, + ]); }); plugins.gatekeepedPlugins.forEach((plugin) => { unavailablePlugins.push([ plugin, - `This plugin is only available to members of gatekeeper '${plugin.gatekeeper}'`, + `Plugin '${plugin.title}' is only available to members of gatekeeper '${plugin.gatekeeper}'`, ]); }); plugins.failedPlugins.forEach(([plugin, error]) => { unavailablePlugins.push([ plugin, - `Flipper failed to load this plugin: '${error}'`, + `Plugin '${plugin.title}' failed to load: '${error}'`, ]); });