Files
flipper/desktop/app/src/PluginContainer.tsx
dependabot[bot] 674f71a426 Bump prettier from 2.2.1 to 2.3.0 in /desktop (#2300)
Summary:
Bumps [prettier](https://github.com/prettier/prettier) from 2.2.1 to 2.3.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/prettier/prettier/releases">prettier's releases</a>.</em></p>
<blockquote>
<h2>2.3.0</h2>
<p><a href="https://github.com/prettier/prettier/compare/2.2.1...2.3.0">diff</a></p>
<p>{emoji:1f517} <a href="https://prettier.io/blog/2021/05/09/2.3.0.html">Release Notes</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/prettier/prettier/blob/main/CHANGELOG.md">prettier's changelog</a>.</em></p>
<blockquote>
<h1>2.3.0</h1>
<p><a href="https://github.com/prettier/prettier/compare/2.2.1...2.3.0">diff</a></p>
<p>{emoji:1f517} <a href="https://prettier.io/blog/2021/05/09/2.3.0.html">Release Notes</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="2afc3b9ae6"><code>2afc3b9</code></a> Release 2.3.0</li>
<li><a href="7cfa9aa89b"><code>7cfa9aa</code></a> Fix pre-commit hook setup command (<a href="https://github-redirect.dependabot.com/prettier/prettier/issues/10710">#10710</a>)</li>
<li><a href="c8c02b4753"><code>c8c02b4</code></a> Build(deps-dev): Bump concurrently from 6.0.2 to 6.1.0 in /website (<a href="https://github-redirect.dependabot.com/prettier/prettier/issues/10834">#10834</a>)</li>
<li><a href="6506e0f50e"><code>6506e0f</code></a> Build(deps-dev): Bump webpack-cli from 4.6.0 to 4.7.0 in /website (<a href="https://github-redirect.dependabot.com/prettier/prettier/issues/10836">#10836</a>)</li>
<li><a href="69fae9c291"><code>69fae9c</code></a> Build(deps): Bump flow-parser from 0.150.0 to 0.150.1 (<a href="https://github-redirect.dependabot.com/prettier/prettier/issues/10839">#10839</a>)</li>
<li><a href="164a6e2351"><code>164a6e2</code></a> Switch CLI to async (<a href="https://github-redirect.dependabot.com/prettier/prettier/issues/10804">#10804</a>)</li>
<li><a href="d3e7e2f634"><code>d3e7e2f</code></a> Build(deps): Bump codecov/codecov-action from v1.4.1 to v1.5.0 (<a href="https://github-redirect.dependabot.com/prettier/prettier/issues/10833">#10833</a>)</li>
<li><a href="9e09845da0"><code>9e09845</code></a> Build(deps): Bump <code>@​angular/compiler</code> from 11.2.12 to 11.2.13 (<a href="https://github-redirect.dependabot.com/prettier/prettier/issues/10838">#10838</a>)</li>
<li><a href="1bfab3d045"><code>1bfab3d</code></a> Build(deps-dev): Bump eslint from 7.25.0 to 7.26.0 (<a href="https://github-redirect.dependabot.com/prettier/prettier/issues/10840">#10840</a>)</li>
<li><a href="387fce4ed8"><code>387fce4</code></a> Minor formatting tweaks (<a href="https://github-redirect.dependabot.com/prettier/prettier/issues/10807">#10807</a>)</li>
<li>Additional commits viewable in <a href="https://github.com/prettier/prettier/compare/2.2.1...2.3.0">compare view</a></li>
</ul>
</details>
<br />

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=prettier&package-manager=npm_and_yarn&previous-version=2.2.1&new-version=2.3.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

 ---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `dependabot rebase` will rebase this PR
- `dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `dependabot merge` will merge this PR after your CI passes on it
- `dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `dependabot cancel merge` will cancel a previously requested merge and block automerging
- `dependabot reopen` will reopen this PR if it is closed
- `dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)

</details>

Pull Request resolved: https://github.com/facebook/flipper/pull/2300

Reviewed By: passy

Differential Revision: D28323849

Pulled By: cekkaewnumchai

fbshipit-source-id: 1842877ccc9a9587af7f0d9ff9432c2075c8ee22
2021-05-11 05:51:56 -07:00

615 lines
16 KiB
TypeScript

/**
* 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 {
FlipperPlugin,
FlipperDevicePlugin,
Props as PluginProps,
PluginDefinition,
} from './plugin';
import {Logger} from './fb-interfaces/Logger';
import BaseDevice from './devices/BaseDevice';
import {pluginKey as getPluginKey} from './reducers/pluginStates';
import Client from './Client';
import {
ErrorBoundary,
FlexColumn,
FlexRow,
colors,
styled,
Glyph,
Label,
VBox,
View,
} from './ui';
import {
StaticView,
setStaticView,
isPluginEnabled,
} from './reducers/connections';
import {switchPlugin} from './reducers/pluginManager';
import React, {PureComponent} from 'react';
import {connect, ReactReduxContext} from 'react-redux';
import {setPluginState} from './reducers/pluginStates';
import {Settings} from './reducers/settings';
import {selectPlugin} from './reducers/connections';
import {State as Store, MiddlewareAPI} from './reducers/index';
import {activateMenuItems} from './MenuBar';
import {Message} from './reducers/pluginMessageQueue';
import {IdlerImpl} from './utils/Idler';
import {processMessageQueue} from './utils/messageQueue';
import {ToggleButton, SmallText, Layout} from './ui';
import {theme, TrackingScope, _SandyPluginRenderer} from 'flipper-plugin';
import {isDevicePluginDefinition, isSandyPlugin} from './utils/pluginUtils';
import {ContentContainer} from './sandy-chrome/ContentContainer';
import {Alert, Typography} from 'antd';
import {InstalledPluginDetails} from 'flipper-plugin-lib';
import semver from 'semver';
import {loadPlugin} from './reducers/pluginManager';
import {produce} from 'immer';
import {reportUsage} from './utils/metrics';
const {Text, Link} = Typography;
const Container = styled(FlexColumn)({
width: 0,
flexGrow: 1,
flexShrink: 1,
backgroundColor: colors.white,
});
export const SidebarContainer = styled(FlexRow)({
backgroundColor: theme.backgroundWash,
height: '100%',
overflow: 'auto',
});
const Waiting = styled(FlexColumn)({
width: '100%',
height: '100%',
flexGrow: 1,
alignItems: 'center',
justifyContent: 'center',
textAlign: 'center',
});
function ProgressBar({progress}: {progress: number}) {
return (
<ProgressBarContainer>
<ProgressBarBar progress={progress} />
</ProgressBarContainer>
);
}
const ProgressBarContainer = styled.div({
border: `1px solid ${colors.cyan}`,
borderRadius: 4,
width: 300,
});
const ProgressBarBar = styled.div<{progress: number}>(({progress}) => ({
background: colors.cyan,
width: `${Math.min(100, Math.round(progress * 100))}%`,
height: 8,
}));
type OwnProps = {
logger: Logger;
isSandy?: boolean;
};
type StateFromProps = {
pluginState: Object;
activePlugin: PluginDefinition | undefined;
target: Client | BaseDevice | null;
pluginKey: string | null;
deepLinkPayload: unknown;
selectedApp: string | null;
isArchivedDevice: boolean;
pendingMessages: Message[] | undefined;
pluginIsEnabled: boolean;
settingsState: Settings;
latestInstalledVersion: InstalledPluginDetails | undefined;
};
type DispatchFromProps = {
selectPlugin: (payload: {
selectedPlugin: string | null;
selectedApp?: string | null;
deepLinkPayload: unknown;
}) => any;
setPluginState: (payload: {pluginKey: string; state: any}) => void;
setStaticView: (payload: StaticView) => void;
enablePlugin: typeof switchPlugin;
loadPlugin: typeof loadPlugin;
};
type Props = StateFromProps & DispatchFromProps & OwnProps;
type State = {
progress: {current: number; total: number};
autoUpdateAlertSuppressed: Set<string>;
};
class PluginContainer extends PureComponent<Props, State> {
static contextType = ReactReduxContext;
constructor(props: Props) {
super(props);
this.reloadPlugin = this.reloadPlugin.bind(this);
}
plugin:
| FlipperPlugin<any, any, any>
| FlipperDevicePlugin<any, any, any>
| null
| undefined;
refChanged = (
ref:
| FlipperPlugin<any, any, any>
| FlipperDevicePlugin<any, any, any>
| null
| undefined,
) => {
// N.B. for Sandy plugins this lifecycle is managed by PluginRenderer
if (this.plugin) {
this.plugin._teardown();
this.plugin = null;
}
if (ref && this.props.target) {
activateMenuItems(ref);
ref._init();
this.props.logger.trackTimeSince(`activePlugin-${ref.constructor.id}`);
this.plugin = ref;
}
};
idler?: IdlerImpl;
pluginBeingProcessed: string | null = null;
state = {
progress: {current: 0, total: 0},
autoUpdateAlertSuppressed: new Set<string>(),
};
get store(): MiddlewareAPI {
return this.context.store;
}
componentWillUnmount() {
if (this.plugin) {
this.plugin._teardown();
this.plugin = null;
}
this.cancelCurrentQueue();
}
componentDidMount() {
this.processMessageQueue();
}
componentDidUpdate() {
this.processMessageQueue();
// make sure deeplinks are propagated
const {deepLinkPayload, target, activePlugin} = this.props;
if (deepLinkPayload && activePlugin && target) {
target.sandyPluginStates
.get(activePlugin.id)
?.triggerDeepLink(deepLinkPayload);
}
}
processMessageQueue() {
const {pluginKey, pendingMessages, activePlugin, pluginIsEnabled, target} =
this.props;
if (pluginKey !== this.pluginBeingProcessed) {
this.pluginBeingProcessed = pluginKey;
this.cancelCurrentQueue();
this.setState((state) =>
produce(state, (draft) => {
draft.progress = {current: 0, total: 0};
}),
);
// device plugins don't have connections so no message queues
if (!activePlugin || isDevicePluginDefinition(activePlugin)) {
return;
}
if (
pluginIsEnabled &&
target instanceof Client &&
activePlugin &&
(isSandyPlugin(activePlugin) || activePlugin.persistedStateReducer) &&
pluginKey &&
pendingMessages?.length
) {
const start = Date.now();
this.idler = new IdlerImpl();
processMessageQueue(
isSandyPlugin(activePlugin)
? target.sandyPluginStates.get(activePlugin.id)!
: activePlugin,
pluginKey,
this.store,
(progress) => {
this.setState((state) =>
produce(state, (draft) => {
draft.progress = progress;
}),
);
},
this.idler,
).then((completed) => {
const duration = Date.now() - start;
this.props.logger.track(
'duration',
'queue-processing-before-plugin-open',
{
completed,
duration,
},
activePlugin.id,
);
});
}
}
}
cancelCurrentQueue() {
if (this.idler && !this.idler.isCancelled()) {
this.idler.cancel();
}
}
render() {
const {activePlugin, pluginKey, target, pendingMessages, pluginIsEnabled} =
this.props;
if (!activePlugin || !target || !pluginKey) {
return null;
}
if (!pluginIsEnabled) {
return this.renderPluginEnabler();
}
if (!pendingMessages || pendingMessages.length === 0) {
return this.renderPlugin();
}
return this.renderPluginLoader();
}
renderPluginEnabler() {
const activePlugin = this.props.activePlugin!;
return (
<View grow>
<Waiting>
<VBox>
<FlexRow>
<Label
style={{
fontSize: '16px',
color: colors.light30,
textTransform: 'uppercase',
}}>
{activePlugin.title}
</Label>
</FlexRow>
</VBox>
<VBox>
<ToggleButton
toggled={false}
onClick={() => {
this.props.enablePlugin({
plugin: activePlugin,
selectedApp: (this.props.target as Client)?.query?.app,
});
}}
large
/>
</VBox>
<VBox>
<SmallText>Click to enable this plugin</SmallText>
</VBox>
</Waiting>
</View>
);
}
renderPluginLoader() {
return (
<View grow>
<Waiting>
<VBox>
<Glyph
name="dashboard"
variant="outline"
size={24}
color={colors.light30}
/>
</VBox>
<VBox>
<Label>
Processing {this.state.progress.total} events for{' '}
{this.props.activePlugin?.id ?? 'plugin'}
</Label>
</VBox>
<VBox>
<ProgressBar
progress={this.state.progress.current / this.state.progress.total}
/>
</VBox>
</Waiting>
</View>
);
}
renderNoPluginActive() {
return (
<View grow>
<Waiting>
<VBox>
<Glyph
name="cup"
variant="outline"
size={24}
color={colors.light30}
/>
</VBox>
<VBox>
<Label>No plugin selected</Label>
</VBox>
</Waiting>
</View>
);
}
reloadPlugin() {
const {loadPlugin, latestInstalledVersion} = this.props;
if (latestInstalledVersion) {
reportUsage(
'plugin-auto-update:alert:reloadClicked',
{
version: latestInstalledVersion.version,
},
latestInstalledVersion.id,
);
loadPlugin({
plugin: latestInstalledVersion,
enable: false,
notifyIfFailed: true,
});
}
}
renderPlugin() {
const {
pluginState,
setPluginState,
activePlugin,
pluginKey,
target,
isArchivedDevice,
selectedApp,
settingsState,
isSandy,
latestInstalledVersion,
} = this.props;
if (!activePlugin || !target || !pluginKey) {
console.warn(`No selected plugin. Rendering empty!`);
return this.renderNoPluginActive();
}
let pluginElement: null | React.ReactElement<any>;
const showUpdateAlert =
latestInstalledVersion &&
activePlugin &&
!this.state.autoUpdateAlertSuppressed.has(
`${latestInstalledVersion.name}@${latestInstalledVersion.version}`,
) &&
semver.gt(latestInstalledVersion.version, activePlugin.version);
if (isSandyPlugin(activePlugin)) {
// Make sure we throw away the container for different pluginKey!
const instance = target.sandyPluginStates.get(activePlugin.id);
if (!instance) {
// happens if we selected a plugin that is not enabled on a specific app or not supported on a specific device.
return this.renderNoPluginActive();
}
pluginElement = (
<_SandyPluginRenderer key={pluginKey} plugin={instance} />
);
} else {
const props: PluginProps<Object> & {
key: string;
ref: (
ref:
| FlipperPlugin<any, any, any>
| FlipperDevicePlugin<any, any, any>
| null
| undefined,
) => void;
} = {
key: pluginKey,
logger: this.props.logger,
selectedApp,
persistedState: activePlugin.defaultPersistedState
? {
...activePlugin.defaultPersistedState,
...pluginState,
}
: pluginState,
setStaticView: (payload: StaticView) =>
this.props.setStaticView(payload),
setPersistedState: (state) => setPluginState({pluginKey, state}),
target,
deepLinkPayload: this.props.deepLinkPayload,
selectPlugin: (pluginID: string, deepLinkPayload: unknown) => {
const {target} = this.props;
// check if plugin will be available
if (
target instanceof Client &&
target.plugins.some((p) => p === pluginID)
) {
this.props.selectPlugin({
selectedPlugin: pluginID,
deepLinkPayload,
});
return true;
} else if (target instanceof BaseDevice) {
this.props.selectPlugin({
selectedPlugin: pluginID,
deepLinkPayload,
});
return true;
} else {
return false;
}
},
ref: this.refChanged,
isArchivedDevice,
settingsState,
};
pluginElement = (
<TrackingScope scope={'plugin:' + activePlugin.id}>
{React.createElement(activePlugin, props)}
</TrackingScope>
);
}
return isSandy ? (
<Layout.Top>
<div>
{showUpdateAlert && (
<Alert
message={
<Text>
Plugin "{activePlugin.title}" v
{latestInstalledVersion?.version} is downloaded and ready to
install. <Link onClick={this.reloadPlugin}>Reload</Link> to
start using the new version.
</Text>
}
type="info"
onClose={() =>
this.setState((state) =>
produce(state, (draft) => {
draft.autoUpdateAlertSuppressed.add(
`${latestInstalledVersion?.name}@${latestInstalledVersion?.version}`,
);
}),
)
}
style={{marginBottom: theme.space.large}}
showIcon
closable
/>
)}
</div>
<Layout.Right>
<ErrorBoundary
heading={`Plugin "${
activePlugin.title || 'Unknown'
}" encountered an error during render`}>
<ContentContainer>{pluginElement}</ContentContainer>
</ErrorBoundary>
<SidebarContainer id="detailsSidebar" />
</Layout.Right>
</Layout.Top>
) : (
<React.Fragment>
<Container key="plugin">
<ErrorBoundary
heading={`Plugin "${
activePlugin.title || 'Unknown'
}" encountered an error during render`}>
{pluginElement}
</ErrorBoundary>
</Container>
<SidebarContainer id="detailsSidebar" />
</React.Fragment>
);
}
}
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
({
connections: {
selectedPlugin,
selectedDevice,
selectedApp,
clients,
deepLinkPayload,
enabledPlugins,
enabledDevicePlugins,
},
pluginStates,
plugins: {devicePlugins, clientPlugins, installedPlugins},
pluginMessageQueue,
settingsState,
}) => {
let pluginKey = null;
let target = null;
let activePlugin: PluginDefinition | undefined;
let pluginIsEnabled = false;
if (selectedPlugin) {
activePlugin = devicePlugins.get(selectedPlugin);
if (selectedDevice && activePlugin) {
target = selectedDevice;
pluginKey = getPluginKey(selectedDevice.serial, activePlugin.id);
} else {
target =
clients.find((client: Client) => client.id === selectedApp) || null;
activePlugin = clientPlugins.get(selectedPlugin);
if (activePlugin && target) {
pluginKey = getPluginKey(target.id, activePlugin.id);
}
}
pluginIsEnabled =
activePlugin !== undefined &&
isPluginEnabled(
enabledPlugins,
enabledDevicePlugins,
selectedApp,
activePlugin.id,
);
}
const isArchivedDevice = !selectedDevice
? false
: selectedDevice.isArchived;
if (isArchivedDevice) {
pluginIsEnabled = true;
}
const pendingMessages = pluginKey
? pluginMessageQueue[pluginKey]
: undefined;
const s: StateFromProps = {
pluginState: pluginStates[pluginKey as string],
activePlugin: activePlugin,
target,
deepLinkPayload,
pluginKey,
isArchivedDevice,
selectedApp: selectedApp || null,
pendingMessages,
pluginIsEnabled,
settingsState,
latestInstalledVersion: installedPlugins.get(
activePlugin?.packageName ?? '',
),
};
return s;
},
{
setPluginState,
selectPlugin,
setStaticView,
enablePlugin: switchPlugin,
loadPlugin,
},
)(PluginContainer);