Move app/src (mostly) to flipper-ui-core/src
Summary: This diff moves all UI code from app/src to app/flipper-ui-core. That is now slightly too much (e.g. node deps are not removed yet), but from here it should be easier to move things out again, as I don't want this diff to be open for too long to avoid too much merge conflicts. * But at least flipper-ui-core is Electron free :) * Killed all cross module imports as well, as they where now even more in the way * Some unit test needed some changes, most not too big (but emotion hashes got renumbered in the snapshots, feel free to ignore that) * Found some files that were actually meaningless (tsconfig in plugins, WatchTools files, that start generating compile errors, removed those Follow up work: * make flipper-ui-core configurable, and wire up flipper-server-core in Electron instead of here * remove node deps (aigoncharov) * figure out correct place to load GKs, plugins, make intern requests etc., and move to the correct module * clean up deps Reviewed By: aigoncharov Differential Revision: D32427722 fbshipit-source-id: 14fe92e1ceb15b9dcf7bece367c8ab92df927a70
This commit is contained in:
committed by
Facebook GitHub Bot
parent
54b7ce9308
commit
7e50c0466a
@@ -0,0 +1,331 @@
|
||||
/**
|
||||
* 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 {Layout, theme} from 'flipper-plugin';
|
||||
import {LoadingIndicator, TableRows, ManagedTable, Glyph} from '../../ui';
|
||||
import React, {useCallback, useState, useEffect} from 'react';
|
||||
import {reportPlatformFailures, reportUsage} from 'flipper-common';
|
||||
import reloadFlipper from '../../utils/reloadFlipper';
|
||||
import {registerInstalledPlugins} from '../../reducers/plugins';
|
||||
import {
|
||||
UpdateResult,
|
||||
getInstalledPlugins,
|
||||
getUpdatablePlugins,
|
||||
removePlugin,
|
||||
UpdatablePluginDetails,
|
||||
InstalledPluginDetails,
|
||||
} from 'flipper-plugin-lib';
|
||||
import {installPluginFromNpm} from 'flipper-plugin-lib';
|
||||
import {State as AppState} from '../../reducers';
|
||||
import {connect} from 'react-redux';
|
||||
import {Dispatch, Action} from 'redux';
|
||||
import PluginPackageInstaller from './PluginPackageInstaller';
|
||||
import {Toolbar} from 'flipper-plugin';
|
||||
import {Alert, Button, Input, Tooltip, Typography} from 'antd';
|
||||
|
||||
const {Text, Link} = Typography;
|
||||
|
||||
const TAG = 'PluginInstaller';
|
||||
|
||||
const columnSizes = {
|
||||
name: '25%',
|
||||
version: '10%',
|
||||
description: 'flex',
|
||||
install: '15%',
|
||||
};
|
||||
|
||||
const columns = {
|
||||
name: {
|
||||
value: 'Name',
|
||||
},
|
||||
version: {
|
||||
value: 'Version',
|
||||
},
|
||||
description: {
|
||||
value: 'Description',
|
||||
},
|
||||
install: {
|
||||
value: '',
|
||||
},
|
||||
};
|
||||
|
||||
type PropsFromState = {
|
||||
installedPlugins: Map<string, InstalledPluginDetails>;
|
||||
};
|
||||
|
||||
type DispatchFromProps = {
|
||||
refreshInstalledPlugins: () => void;
|
||||
};
|
||||
|
||||
type OwnProps = {
|
||||
autoHeight: boolean;
|
||||
};
|
||||
|
||||
type Props = OwnProps & PropsFromState & DispatchFromProps;
|
||||
|
||||
const defaultProps: OwnProps = {
|
||||
autoHeight: false,
|
||||
};
|
||||
|
||||
const PluginInstaller = function ({
|
||||
refreshInstalledPlugins,
|
||||
installedPlugins,
|
||||
autoHeight,
|
||||
}: Props) {
|
||||
const [restartRequired, setRestartRequired] = useState(false);
|
||||
const [query, setQuery] = useState('');
|
||||
|
||||
const onInstall = useCallback(async () => {
|
||||
refreshInstalledPlugins();
|
||||
setRestartRequired(true);
|
||||
}, [refreshInstalledPlugins]);
|
||||
|
||||
const rows = useNPMSearch(query, onInstall, installedPlugins);
|
||||
const restartApp = useCallback(() => {
|
||||
reloadFlipper();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Layout.Container gap height={500}>
|
||||
{restartRequired && (
|
||||
<Alert
|
||||
onClick={restartApp}
|
||||
type="error"
|
||||
message="To apply the changes, Flipper needs to reload. Click here to reload!"
|
||||
style={{cursor: 'pointer'}}
|
||||
/>
|
||||
)}
|
||||
<Toolbar>
|
||||
<Input.Search
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
value={query}
|
||||
placeholder="Search Flipper plugins..."
|
||||
/>
|
||||
</Toolbar>
|
||||
<ManagedTable
|
||||
rowLineHeight={28}
|
||||
floating={false}
|
||||
multiline
|
||||
columnSizes={columnSizes}
|
||||
columns={columns}
|
||||
highlightableRows={false}
|
||||
highlightedRows={new Set()}
|
||||
autoHeight={autoHeight}
|
||||
rows={rows}
|
||||
horizontallyScrollable
|
||||
/>
|
||||
<PluginPackageInstaller onInstall={onInstall} />
|
||||
</Layout.Container>
|
||||
);
|
||||
};
|
||||
|
||||
function InstallButton(props: {
|
||||
name: string;
|
||||
version: string;
|
||||
onInstall: () => void;
|
||||
updateStatus: UpdateResult;
|
||||
}) {
|
||||
type InstallAction =
|
||||
| {kind: 'Install'; error?: string}
|
||||
| {kind: 'Waiting'}
|
||||
| {kind: 'Remove'; error?: string}
|
||||
| {kind: 'Update'; error?: string};
|
||||
|
||||
const catchError =
|
||||
(actionKind: 'Install' | 'Remove' | 'Update', fn: () => Promise<void>) =>
|
||||
async () => {
|
||||
try {
|
||||
await fn();
|
||||
} catch (err) {
|
||||
console.error(
|
||||
`Installation process of kind ${actionKind} failed with:`,
|
||||
err,
|
||||
);
|
||||
setAction({kind: actionKind, error: err.toString()});
|
||||
}
|
||||
};
|
||||
|
||||
const mkInstallCallback = (action: 'Install' | 'Update') =>
|
||||
catchError(action, async () => {
|
||||
reportUsage(
|
||||
action === 'Install' ? `${TAG}:install` : `${TAG}:update`,
|
||||
undefined,
|
||||
props.name,
|
||||
);
|
||||
setAction({kind: 'Waiting'});
|
||||
|
||||
await installPluginFromNpm(props.name);
|
||||
|
||||
props.onInstall();
|
||||
setAction({kind: 'Remove'});
|
||||
});
|
||||
|
||||
const performInstall = useCallback(mkInstallCallback('Install'), [
|
||||
props.name,
|
||||
props.version,
|
||||
]);
|
||||
|
||||
const performUpdate = useCallback(mkInstallCallback('Update'), [
|
||||
props.name,
|
||||
props.version,
|
||||
]);
|
||||
|
||||
const performRemove = useCallback(
|
||||
catchError('Remove', async () => {
|
||||
reportUsage(`${TAG}:remove`, undefined, props.name);
|
||||
setAction({kind: 'Waiting'});
|
||||
await removePlugin(props.name);
|
||||
props.onInstall();
|
||||
setAction({kind: 'Install'});
|
||||
}),
|
||||
[props.name],
|
||||
);
|
||||
|
||||
const [action, setAction] = useState<InstallAction>(
|
||||
props.updateStatus.kind === 'update-available'
|
||||
? {kind: 'Update'}
|
||||
: props.updateStatus.kind === 'not-installed'
|
||||
? {kind: 'Install'}
|
||||
: {kind: 'Remove'},
|
||||
);
|
||||
|
||||
if (action.kind === 'Waiting') {
|
||||
return <LoadingIndicator size={16} />;
|
||||
}
|
||||
if ((action.kind === 'Install' || action.kind === 'Remove') && action.error) {
|
||||
}
|
||||
const button = (
|
||||
<Button
|
||||
size="small"
|
||||
type={action.kind !== 'Remove' ? 'primary' : undefined}
|
||||
onClick={() => {
|
||||
switch (action.kind) {
|
||||
case 'Install':
|
||||
reportPlatformFailures(performInstall(), `${TAG}:install`);
|
||||
break;
|
||||
case 'Remove':
|
||||
reportPlatformFailures(performRemove(), `${TAG}:remove`);
|
||||
break;
|
||||
case 'Update':
|
||||
reportPlatformFailures(performUpdate(), `${TAG}:update`);
|
||||
break;
|
||||
}
|
||||
}}>
|
||||
{action.kind}
|
||||
</Button>
|
||||
);
|
||||
|
||||
if (action.error) {
|
||||
const glyph = (
|
||||
<Glyph color={theme.warningColor} size={16} name="caution-triangle" />
|
||||
);
|
||||
return (
|
||||
<Layout.Horizontal gap>
|
||||
<Tooltip
|
||||
placement="leftBottom"
|
||||
title={`Something went wrong: ${action.error}`}
|
||||
children={glyph}
|
||||
/>
|
||||
{button}
|
||||
</Layout.Horizontal>
|
||||
);
|
||||
} else {
|
||||
return button;
|
||||
}
|
||||
}
|
||||
|
||||
function useNPMSearch(
|
||||
query: string,
|
||||
onInstall: () => void,
|
||||
installedPlugins: Map<string, InstalledPluginDetails>,
|
||||
): TableRows {
|
||||
useEffect(() => {
|
||||
reportUsage(`${TAG}:open`);
|
||||
}, []);
|
||||
|
||||
const [searchResults, setSearchResults] = useState<UpdatablePluginDetails[]>(
|
||||
[],
|
||||
);
|
||||
|
||||
const createRow = useCallback(
|
||||
(h: UpdatablePluginDetails) => ({
|
||||
key: h.name,
|
||||
columns: {
|
||||
name: {
|
||||
value: <Text ellipsis>{h.name.replace(/^flipper-plugin-/, '')}</Text>,
|
||||
},
|
||||
version: {
|
||||
value: <Text ellipsis>{h.version}</Text>,
|
||||
align: 'flex-end' as 'flex-end',
|
||||
},
|
||||
description: {
|
||||
value: (
|
||||
<Layout.Horizontal center gap>
|
||||
<Text ellipsis>{h.description}</Text>
|
||||
<Link href={`https://yarnpkg.com/en/package/${h.name}`}>
|
||||
<Glyph
|
||||
color={theme.textColorActive}
|
||||
name="info-circle"
|
||||
size={16}
|
||||
/>
|
||||
</Link>
|
||||
</Layout.Horizontal>
|
||||
),
|
||||
},
|
||||
install: {
|
||||
value: (
|
||||
<InstallButton
|
||||
name={h.name}
|
||||
version={h.version}
|
||||
onInstall={onInstall}
|
||||
updateStatus={h.updateStatus}
|
||||
/>
|
||||
),
|
||||
align: 'center' as 'center',
|
||||
},
|
||||
},
|
||||
}),
|
||||
[onInstall],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
let canceled = false;
|
||||
const updatablePlugins = await reportPlatformFailures(
|
||||
getUpdatablePlugins(query),
|
||||
`${TAG}:queryIndex`,
|
||||
);
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
setSearchResults(updatablePlugins);
|
||||
// Clean up: if query changes while we're searching, abandon results.
|
||||
return () => {
|
||||
canceled = true;
|
||||
};
|
||||
})();
|
||||
}, [query, installedPlugins]);
|
||||
|
||||
const rows = searchResults.map(createRow);
|
||||
return rows;
|
||||
}
|
||||
|
||||
PluginInstaller.defaultProps = defaultProps;
|
||||
|
||||
export default connect<PropsFromState, DispatchFromProps, OwnProps, AppState>(
|
||||
({plugins: {installedPlugins}}) => ({
|
||||
installedPlugins,
|
||||
}),
|
||||
(dispatch: Dispatch<Action<any>>) => ({
|
||||
refreshInstalledPlugins: async () => {
|
||||
const plugins = await getInstalledPlugins();
|
||||
dispatch(registerInstalledPlugins(plugins));
|
||||
},
|
||||
}),
|
||||
)(PluginInstaller);
|
||||
Reference in New Issue
Block a user