From 8c623867bd965f0192c370979a26dc8bf9727e82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20B=C3=BCchele?= Date: Thu, 19 Sep 2019 02:31:33 -0700 Subject: [PATCH] PluginManager Summary: Adding the plugin installer to the plugin sheet as a second tab Reviewed By: passy Differential Revision: D17450842 fbshipit-source-id: 211c9f15ed2614a1dd46d974b86f50c825f81fb0 --- src/App.tsx | 12 +- src/chrome/MainSidebar.tsx | 4 +- src/chrome/PluginDebugger.tsx | 34 +-- src/chrome/PluginInstaller.tsx | 22 +- src/chrome/PluginManager.js | 404 --------------------------------- src/chrome/PluginManager.tsx | 45 ++++ src/reducers/application.tsx | 8 +- 7 files changed, 65 insertions(+), 464 deletions(-) delete mode 100644 src/chrome/PluginManager.js create mode 100644 src/chrome/PluginManager.tsx diff --git a/src/App.tsx b/src/App.tsx index cace5329b..e40085510 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -19,24 +19,22 @@ import ShareSheetExportFile from './chrome/ShareSheetExportFile'; import PluginContainer from './PluginContainer'; import Sheet from './chrome/Sheet'; import {ipcRenderer, remote} from 'electron'; -import PluginDebugger from './chrome/PluginDebugger'; import { ActiveSheet, ShareType, ACTIVE_SHEET_BUG_REPORTER, - ACTIVE_SHEET_PLUGIN_DEBUGGER, + ACTIVE_SHEET_PLUGINS, ACTIVE_SHEET_SHARE_DATA, ACTIVE_SHEET_SIGN_IN, ACTIVE_SHEET_SHARE_DATA_IN_FILE, ACTIVE_SHEET_SELECT_PLUGINS_TO_EXPORT, ACTIVE_SHEET_PLUGIN_SHEET, - ACTIVE_SHEET_PLUGIN_INSTALLER, } from './reducers/application'; import {Logger} from './fb-interfaces/Logger'; import BugReporter from './fb-stubs/BugReporter'; import {State as Store} from './reducers/index'; import {StaticView} from './reducers/connections'; -import PluginInstaller from './chrome/PluginInstaller'; +import PluginManager from './chrome/PluginManager'; const version = remote.app.getVersion(); type OwnProps = { @@ -80,16 +78,14 @@ export class App extends React.Component { onHide={onHide} /> ); - case ACTIVE_SHEET_PLUGIN_DEBUGGER: - return ; + case ACTIVE_SHEET_PLUGINS: + return ; case ACTIVE_SHEET_SHARE_DATA: return ; case ACTIVE_SHEET_SIGN_IN: return ; case ACTIVE_SHEET_SELECT_PLUGINS_TO_EXPORT: return ; - case ACTIVE_SHEET_PLUGIN_INSTALLER: - return ; case ACTIVE_SHEET_SHARE_DATA_IN_FILE: return ( { ))} this.props.setActiveSheet('PLUGIN_DEBUGGER')}> + onClick={() => this.props.setActiveSheet(ACTIVE_SHEET_PLUGINS)}> any; -}; +type OwnProps = {}; const COLUMNS = { lamp: { @@ -304,17 +284,7 @@ class PluginDebugger extends Component { ); } - return ( - - Plugin Status - {content} - - - - - ); + return content; } } diff --git a/src/chrome/PluginInstaller.tsx b/src/chrome/PluginInstaller.tsx index 30423b894..5ac820829 100644 --- a/src/chrome/PluginInstaller.tsx +++ b/src/chrome/PluginInstaller.tsx @@ -41,12 +41,6 @@ type PluginDefinition = { description: string; }; -const Container = styled(FlexColumn)({ - width: 600, - height: 400, - background: colors.white, -}); - const EllipsisText = styled(Text)({ overflow: 'hidden', textOverflow: 'ellipsis', @@ -75,6 +69,14 @@ const columns = { }, }; +const Container = styled(FlexColumn)({ + height: 300, + backgroundColor: colors.white, + borderRadius: 4, + overflow: 'hidden', + border: `1px solid ${colors.macOSTitleBarButtonBorder}`, +}); + const RestartBar = styled(FlexColumn)({ backgroundColor: colors.red, color: colors.white, @@ -84,7 +86,7 @@ const RestartBar = styled(FlexColumn)({ textAlign: 'center', }); -export default function(props: {onHide: () => any}) { +export default function() { const [restartRequired, setRestartRequired] = useState(false); const [query, setQuery] = useState(''); const rows = useNPMSearch(setRestartRequired, query, setQuery); @@ -120,10 +122,6 @@ export default function(props: {onHide: () => any}) { highlightedRows={new Set()} rows={rows} /> - - - - ); } @@ -210,7 +208,7 @@ function useNPMSearch( (h: PluginDefinition) => ({ key: h.name, columns: { - name: {value: {h.name}}, + name: {value: {h.name}}, version: { value: {h.version}, align: 'flex-end' as 'flex-end', diff --git a/src/chrome/PluginManager.js b/src/chrome/PluginManager.js deleted file mode 100644 index 0a9efcd7f..000000000 --- a/src/chrome/PluginManager.js +++ /dev/null @@ -1,404 +0,0 @@ -/** - * 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 { - PureComponent, - Button, - FlexColumn, - FlexBox, - Text, - LoadingIndicator, - ButtonGroup, - colors, - Glyph, - FlexRow, - styled, - Searchable, -} from 'flipper'; -const {spawn} = require('child_process'); -const path = require('path'); -const {app, shell} = require('electron').remote; - -const FLIPPER_PLUGIN_PATH = path.join(app.getPath('home'), '.flipper'); -const DYNAMIC_PLUGINS = JSON.parse(window.process.env.PLUGINS || '[]'); - -type NPMModule = { - name: string, - version: string, - description?: string, - error?: Object, -}; - -type Status = - | 'installed' - | 'outdated' - | 'install' - | 'remove' - | 'update' - | 'uninstalled' - | 'uptodate'; - -type PluginT = { - name: string, - version?: string, - description?: string, - status: Status, - managed?: boolean, - entry?: string, - rootDir?: string, -}; - -type Props = { - searchTerm: string, -}; -type State = { - plugins: { - [name: string]: PluginT, - }, - restartRequired: boolean, - searchCompleted: boolean, -}; - -const Container = styled(FlexBox)({ - width: '100%', - flexGrow: 1, - background: colors.light02, - overflowY: 'scroll', -}); - -const Title = styled(Text)({ - fontWeight: 500, -}); - -const Plugin = styled(FlexColumn)({ - backgroundColor: colors.white, - borderRadius: 4, - padding: 15, - margin: '0 15px 25px', - boxShadow: '0 1px 2px rgba(0,0,0,0.05)', -}); - -const SectionTitle = styled('span')({ - fontWeight: 'bold', - fontSize: 24, - margin: 15, - marginLeft: 20, -}); - -const Loading = styled(FlexBox)({ - padding: 50, - alignItems: 'center', - justifyContent: 'center', -}); - -const RestartRequired = styled(FlexBox)({ - textAlign: 'center', - justifyContent: 'center', - fontWeight: 500, - color: colors.white, - padding: 12, - backgroundColor: colors.green, - cursor: 'pointer', -}); - -const TitleRow = styled(FlexRow)({ - alignItems: 'center', - marginBottom: 10, - fontSize: '1.1em', -}); - -const Description = styled(FlexRow)({ - marginBottom: 15, - lineHeight: '130%', -}); - -const PluginGlyph = styled(Glyph)({ - marginRight: 5, -}); - -const PluginLoading = styled(LoadingIndicator)({ - marginLeft: 5, - marginTop: 5, -}); - -const getLatestVersion = (name: string): Promise => { - return fetch(`http://registry.npmjs.org/${name}/latest`).then(res => - res.json(), - ); -}; - -const getPluginList = (): Promise> => { - return fetch( - 'http://registry.npmjs.org/-/v1/search?text=keywords:flipper&size=250', - ) - .then(res => res.json()) - .then(res => res.objects.map(o => o.package)); -}; - -const sortByName = (a: PluginT, b: PluginT): 1 | -1 => - a.name > b.name ? 1 : -1; - -const INSTALLED = ['installed', 'outdated', 'uptodate']; - -class PluginItem extends PureComponent< - { - plugin: PluginT, - onChangeState: (action: Status) => void, - }, - { - working: boolean, - }, -> { - state = { - working: false, - }; - - npmAction = (action: Status) => { - const {name, status: initialStatus} = this.props.plugin; - this.setState({working: true}); - const npm = spawn('npm', [action, name], { - cwd: FLIPPER_PLUGIN_PATH, - }); - - npm.stderr.on('data', e => { - console.error(e.toString()); - }); - - npm.on('close', code => { - this.setState({working: false}); - const newStatus = action === 'remove' ? 'uninstalled' : 'uptodate'; - this.props.onChangeState(code !== 0 ? initialStatus : newStatus); - }); - }; - - render() { - const { - entry, - status, - version, - description, - managed, - name, - rootDir, - } = this.props.plugin; - - return ( - - - - {name} -   - {version} - - {description && {description}} - - {managed ? ( - - This plugin is not managed by Flipper, but loaded from{' '} - - {rootDir} - - - ) : ( - - {status === 'outdated' && ( - - )} - {INSTALLED.includes(status) ? ( - - ) : ( - - )} - - - )} - {this.state.working && } - - - ); - } -} - -class PluginManager extends PureComponent { - state = { - plugins: DYNAMIC_PLUGINS.reduce((acc, plugin) => { - acc[plugin.name] = { - ...plugin, - managed: !(plugin.entry, '').startsWith(FLIPPER_PLUGIN_PATH), - status: 'installed', - }; - return acc; - }, {}), - restartRequired: false, - searchCompleted: false, - }; - - componentDidMount() { - Promise.all( - Object.keys(this.state.plugins) - .filter(name => this.state.plugins[name].managed) - .map(getLatestVersion), - ).then((res: Array) => { - const updates = {}; - res.forEach(plugin => { - if ( - plugin.error == null && - this.state.plugins[plugin.name].version !== plugin.version - ) { - updates[plugin.name] = { - ...plugin, - ...this.state.plugins[plugin.name], - status: 'outdated', - }; - } - }); - this.setState({ - plugins: { - ...this.state.plugins, - ...updates, - }, - }); - }); - - getPluginList().then(pluginList => { - const plugins = {...this.state.plugins}; - pluginList.forEach(plugin => { - if (plugins[plugin.name] != null) { - plugins[plugin.name] = { - ...plugin, - ...plugins[plugin.name], - status: - plugin.version === plugins[plugin.name].version - ? 'uptodate' - : 'outdated', - }; - } else { - plugins[plugin.name] = { - ...plugin, - status: 'uninstalled', - }; - } - }); - this.setState({ - plugins, - searchCompleted: true, - }); - }); - } - - onChangePluginState = (name: string, status: Status) => { - this.setState({ - plugins: { - ...this.state.plugins, - [name]: { - ...this.state.plugins[name], - status, - }, - }, - restartRequired: true, - }); - }; - - relaunch() { - app.relaunch(); - app.exit(0); - } - - render() { - // $FlowFixMe - const plugins: Array = Object.values(this.state.plugins); - const availablePlugins = plugins.filter( - ({status}) => !INSTALLED.includes(status), - ); - return ( - - - {this.state.restartRequired && ( - - -   Restart Required: Click to Restart - - )} - Installed Plugins - {plugins - .filter( - ({status, name}) => - INSTALLED.includes(status) && - name.indexOf(this.props.searchTerm) > -1, - ) - .sort(sortByName) - .map((plugin: PluginT) => ( - - this.onChangePluginState(plugin.name, action) - } - /> - ))} - Available Plugins - {availablePlugins - .filter(({name}) => name.indexOf(this.props.searchTerm) > -1) - .sort(sortByName) - .map((plugin: PluginT) => ( - - this.onChangePluginState(plugin.name, action) - } - /> - ))} - {!this.state.searchCompleted && ( - - - - )} - - - ); - } -} - -const SearchablePluginManager = Searchable(PluginManager); - -export default class extends PureComponent<{}> { - render() { - return ( - - - - ); - } -} diff --git a/src/chrome/PluginManager.tsx b/src/chrome/PluginManager.tsx new file mode 100644 index 000000000..626990938 --- /dev/null +++ b/src/chrome/PluginManager.tsx @@ -0,0 +1,45 @@ +/** + * 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 React, {useState} from 'react'; +import {FlexColumn, Button, styled, Tab, Tabs, TabsContainer} from 'flipper'; +import PluginDebugger from './PluginDebugger'; +import PluginInstaller from './PluginInstaller'; + +const Container = styled(FlexColumn)({ + padding: 15, + width: 700, +}); + +const Row = styled(FlexColumn)({ + alignItems: 'flex-end', +}); + +type Tabs = 'Plugin Status' | 'Install Plugins'; + +export default function(props: {onHide: () => any}) { + const [tab, setTab] = useState('Plugin Status'); + return ( + + + void}> + + + + {tab === 'Plugin Status' && } + {tab === 'Install Plugins' && } + + + + + + ); +} diff --git a/src/reducers/application.tsx b/src/reducers/application.tsx index 5e4439ae9..f0b930fe9 100644 --- a/src/reducers/application.tsx +++ b/src/reducers/application.tsx @@ -12,8 +12,7 @@ import CancellableExportStatus from '../chrome/CancellableExportStatus'; import {Actions} from './'; export const ACTIVE_SHEET_PLUGIN_SHEET: 'PLUGIN_SHEET' = 'PLUGIN_SHEET'; export const ACTIVE_SHEET_BUG_REPORTER: 'BUG_REPORTER' = 'BUG_REPORTER'; -export const ACTIVE_SHEET_PLUGIN_DEBUGGER: 'PLUGIN_DEBUGGER' = - 'PLUGIN_DEBUGGER'; +export const ACTIVE_SHEET_PLUGINS: 'PLUGINS' = 'PLUGINS'; export const ACTIVE_SHEET_SELECT_PLUGINS_TO_EXPORT: 'SELECT_PLUGINS_TO_EXPORT' = 'SELECT_PLUGINS_TO_EXPORT'; export const ACTIVE_SHEET_SHARE_DATA: 'SHARE_DATA' = 'SHARE_DATA'; @@ -23,18 +22,15 @@ export const ACTIVE_SHEET_SHARE_DATA_IN_FILE: 'SHARE_DATA_IN_FILE' = export const SET_EXPORT_STATUS_MESSAGE: 'SET_EXPORT_STATUS_MESSAGE' = 'SET_EXPORT_STATUS_MESSAGE'; export const UNSET_SHARE: 'UNSET_SHARE' = 'UNSET_SHARE'; -export const ACTIVE_SHEET_PLUGIN_INSTALLER: 'ACTIVE_SHEET_PLUGIN_INSTALLER' = - 'ACTIVE_SHEET_PLUGIN_INSTALLER'; export type ActiveSheet = | typeof ACTIVE_SHEET_PLUGIN_SHEET | typeof ACTIVE_SHEET_BUG_REPORTER - | typeof ACTIVE_SHEET_PLUGIN_DEBUGGER + | typeof ACTIVE_SHEET_PLUGINS | typeof ACTIVE_SHEET_SHARE_DATA | typeof ACTIVE_SHEET_SIGN_IN | typeof ACTIVE_SHEET_SHARE_DATA_IN_FILE | typeof ACTIVE_SHEET_SELECT_PLUGINS_TO_EXPORT - | typeof ACTIVE_SHEET_PLUGIN_INSTALLER | null; export type LauncherMsg = {