Cleaning up sheet abstraction to use Ant/flipper-plugin dialogs instead

Summary:
This diff cleans up order remaining dialogs, the ones involved in exporting to file or url. Includes some legacy component cleanup boyscouting, but not too much.

This removes a lot of code where state of the wizard was stored globally, and makes it locally instead.

Other code that was removed involves interaction with the old UI, which allowed import / export to be running in the background as well. (which is no longer needed since we optimised the process)

Reviewed By: timur-valiev

Differential Revision: D30192000

fbshipit-source-id: 13be883c5bf217a3d58b610b78516359e9bd0ebc
This commit is contained in:
Michel Weststrate
2021-10-06 09:08:47 -07:00
committed by Facebook GitHub Bot
parent 9e5575cf69
commit d56375970d
15 changed files with 324 additions and 751 deletions

View File

@@ -1,39 +0,0 @@
/**
* 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 {FlexRow, Button} from '../ui/index';
import {styled, LoadingIndicator, Text} from '../ui';
import React, {Component} from 'react';
import {colors} from '../ui/components/colors';
const Wrapper = styled(FlexRow)({
color: colors.light50,
alignItems: 'center',
marginLeft: 10,
});
type Props = {
msg: string;
onCancel: () => void;
};
export default class CancellableExportStatus extends Component<Props> {
render() {
const {msg, onCancel} = this.props;
return (
<Wrapper>
<LoadingIndicator size={16} />
&nbsp;
<Text>{msg}</Text>
&nbsp;
<Button onClick={onCancel}> Cancel </Button>
</Wrapper>
);
}
}

View File

@@ -9,42 +9,22 @@
import {connect} from 'react-redux';
import React, {Component} from 'react';
import {ShareType} from '../reducers/application';
import {State as Store} from '../reducers';
import {ActiveSheet} from '../reducers/application';
import {selectedPlugins as actionForSelectedPlugins} from '../reducers/plugins';
import {
ACTIVE_SHEET_SHARE_DATA,
setActiveSheet as getActiveSheetAction,
setExportDataToFileActiveSheet as getExportDataToFileActiveSheetAction,
} from '../reducers/application';
import ListView from './ListView';
import {Dispatch, Action} from 'redux';
import {unsetShare} from '../reducers/application';
import {FlexColumn, styled} from '../ui';
import {getExportablePlugins} from '../selectors/connections';
type OwnProps = {
onHide: () => void;
selectedPlugins: Array<string>;
setSelectedPlugins: (plugins: string[]) => void;
};
type StateFromProps = {
share: ShareType | null;
selectedPlugins: Array<string>;
availablePluginsToExport: Array<{id: string; label: string}>;
};
type DispatchFromProps = {
setSelectedPlugins: (payload: Array<string>) => void;
setActiveSheet: (payload: ActiveSheet) => void;
setExportDataToFileActiveSheet: (payload: {
file: string;
closeOnFinish: boolean;
}) => void;
unsetShare: () => void;
};
type Props = OwnProps & StateFromProps & DispatchFromProps;
type Props = OwnProps & StateFromProps;
const Container = styled(FlexColumn)({
maxHeight: 700,
@@ -53,78 +33,27 @@ const Container = styled(FlexColumn)({
class ExportDataPluginSheet extends Component<Props, {}> {
render() {
const {onHide} = this.props;
const onHideWithUnsettingShare = () => {
this.props.unsetShare();
onHide();
};
return (
<Container>
<ListView
type="multiple"
title="Select the plugins for which you want to export the data"
leftPadding={8}
onSubmit={() => {
const {share} = this.props;
if (!share) {
console.error(
'applications.share is undefined, whereas it was expected to be defined',
);
} else {
switch (share.type) {
case 'link':
this.props.setActiveSheet(ACTIVE_SHEET_SHARE_DATA);
break;
case 'file': {
const file = share.file;
if (file) {
this.props.setExportDataToFileActiveSheet({
file,
closeOnFinish: true,
});
} else {
console.error('share.file is undefined');
}
}
}
}
}}
onChange={(selectedArray) => {
this.props.setSelectedPlugins(selectedArray);
}}
elements={this.props.availablePluginsToExport}
selectedElements={new Set(this.props.selectedPlugins)}
onHide={onHideWithUnsettingShare}
onHide={() => {}}
/>
</Container>
);
}
}
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
(state) => {
const availablePluginsToExport = getExportablePlugins(state);
return {
share: state.application.share,
selectedPlugins: state.plugins.selectedPlugins,
availablePluginsToExport,
};
},
(dispatch: Dispatch<Action<any>>) => ({
setSelectedPlugins: (plugins: Array<string>) => {
dispatch(actionForSelectedPlugins(plugins));
},
setActiveSheet: (payload: ActiveSheet) => {
dispatch(getActiveSheetAction(payload));
},
setExportDataToFileActiveSheet: (payload: {
file: string;
closeOnFinish: boolean;
}) => {
dispatch(getExportDataToFileActiveSheetAction(payload));
},
unsetShare: () => {
dispatch(unsetShare());
},
}),
)(ExportDataPluginSheet);
export default connect<StateFromProps, {}, OwnProps, Store>((state) => {
const availablePluginsToExport = getExportablePlugins(state);
return {
availablePluginsToExport,
};
})(ExportDataPluginSheet);

View File

@@ -7,7 +7,6 @@
* @format
*/
import {Button, FlexColumn} from '../ui';
import React, {Component, useContext} from 'react';
import {Radio} from 'antd';
import {updateSettings, Action} from '../reducers/settings';
@@ -27,13 +26,13 @@ import {isEqual, isMatch, isEmpty} from 'lodash';
import restartFlipper from '../utils/restartFlipper';
import LauncherSettingsPanel from '../fb-stubs/LauncherSettingsPanel';
import {reportUsage} from '../utils/metrics';
import {Modal, message} from 'antd';
import {Modal, message, Button} from 'antd';
import {Layout, withTrackingScope, _NuxManagerContext} from 'flipper-plugin';
type OwnProps = {
onHide: () => void;
platform: NodeJS.Platform;
noModal?: boolean;
noModal?: boolean; // used for testing
};
type StateFromProps = {
@@ -246,7 +245,7 @@ class SettingsSheet extends Component<Props, State> {
}));
}}
/>
<FlexColumn style={{paddingLeft: 15, paddingBottom: 10}}>
<Layout.Container style={{paddingLeft: 15, paddingBottom: 10}}>
Theme Selection
<Radio.Group
value={darkMode}
@@ -262,7 +261,7 @@ class SettingsSheet extends Component<Props, State> {
<Radio.Button value="light">Light</Radio.Button>
<Radio.Button value="system">Use System Setting</Radio.Button>
</Radio.Group>
</FlexColumn>
</Layout.Container>
<ToggledSection
label="React Native keyboard shortcuts"
toggled={reactNative.shortcuts.enabled}
@@ -330,21 +329,15 @@ class SettingsSheet extends Component<Props, State> {
const footer = (
<>
<Button compact padded onClick={this.props.onHide}>
Cancel
</Button>
<Button onClick={this.props.onHide}>Cancel</Button>
<Button
disabled={settingsPristine || forcedRestart}
compact
padded
onClick={this.applyChangesWithoutRestart}>
Apply
</Button>
<Button
disabled={settingsPristine}
type="primary"
compact
padded
onClick={this.applyChanges}>
Apply and Restart
</Button>
@@ -388,7 +381,7 @@ function ResetTooltips() {
function ResetLocalState() {
return (
<Button
type="danger"
danger
onClick={() => {
window.localStorage.clear();
message.success('Local storage state cleared');

View File

@@ -9,9 +9,7 @@
import {FlexColumn, Button, styled, Text, FlexRow, Spacer} from '../ui';
import React, {Component} from 'react';
import {setExportStatusComponent, unsetShare} from '../reducers/application';
import {reportPlatformFailures} from '../utils/metrics';
import CancellableExportStatus from './CancellableExportStatus';
import {performance} from 'perf_hooks';
import {Logger} from '../fb-interfaces/Logger';
import {IdlerImpl} from '../utils/Idler';
@@ -24,6 +22,7 @@ import ShareSheetErrorList from './ShareSheetErrorList';
import ShareSheetPendingDialog from './ShareSheetPendingDialog';
import {ReactReduxContext} from 'react-redux';
import {MiddlewareAPI} from '../reducers/index';
import {Modal} from 'antd';
const Container = styled(FlexColumn)({
padding: 20,
@@ -69,39 +68,23 @@ type State = {
kind: 'pending';
};
statusUpdate: string | null;
runInBackground: boolean;
};
export default class ShareSheetExportFile extends Component<Props, State> {
static contextType = ReactReduxContext;
get store(): MiddlewareAPI {
return this.context.store;
}
state: State = {
fetchMetaDataErrors: null,
result: {kind: 'pending'},
statusUpdate: null,
runInBackground: false,
};
idler = new IdlerImpl();
dispatchAndUpdateToolBarStatus(msg: string) {
this.store.dispatch(
setExportStatusComponent(
<CancellableExportStatus
msg={msg}
onCancel={() => {
this.idler.cancel();
this.store.dispatch(unsetShare());
}}
/>,
),
);
get store(): MiddlewareAPI {
return this.context.store;
}
idler = new IdlerImpl();
async componentDidMount() {
const mark = 'shareSheetExportFile';
performance.mark(mark);
@@ -116,28 +99,17 @@ export default class ShareSheetExportFile extends Component<Props, State> {
false,
this.idler,
(msg: string) => {
if (this.state.runInBackground) {
this.dispatchAndUpdateToolBarStatus(msg);
} else {
this.setState({statusUpdate: msg});
}
this.setState({statusUpdate: msg});
},
),
`${EXPORT_FLIPPER_TRACE_EVENT}:UI_FILE`,
);
if (this.state.runInBackground) {
new Notification('Shareable Flipper Export created', {
body: `Saved to ${this.props.file}`,
requireInteraction: true,
});
}
this.setState({
fetchMetaDataErrors,
result: fetchMetaDataErrors
? {error: JSON.stringify(fetchMetaDataErrors) as any, kind: 'error'}
: {kind: 'success'},
});
this.store.dispatch(unsetShare());
this.props.logger.trackTimeSince(mark, 'export:file-success');
} catch (err) {
const result: {
@@ -147,14 +119,10 @@ export default class ShareSheetExportFile extends Component<Props, State> {
kind: 'error',
error: err,
};
if (!this.state.runInBackground) {
// Show the error in UI.
this.setState({result});
} else {
this.store.dispatch(unsetShare());
}
// Show the error in UI.
this.setState({result});
this.props.logger.trackTimeSince(mark, 'export:file-error', result);
throw err;
console.error('Failed to export to file: ', err);
}
}
@@ -164,84 +132,64 @@ export default class ShareSheetExportFile extends Component<Props, State> {
);
return (
<ReactReduxContext.Consumer>
{({store}) => (
<Container>
<FlexColumn>
<Title bold>Data Exported Successfully</Title>
<InfoText>
When sharing your Flipper data, consider that the captured data
might contain sensitive information like access tokens used in
network requests.
</InfoText>
<ShareSheetErrorList
errors={errorArray}
title={title}
type={'warning'}
/>
</FlexColumn>
<FlexRow>
<Spacer />
<Button compact padded onClick={() => this.cancelAndHide(store)}>
Close
</Button>
</FlexRow>
</Container>
)}
</ReactReduxContext.Consumer>
<Container>
<FlexColumn>
<Title bold>Data Exported Successfully</Title>
<InfoText>
When sharing your Flipper data, consider that the captured data
might contain sensitive information like access tokens used in
network requests.
</InfoText>
<ShareSheetErrorList
errors={errorArray}
title={title}
type={'warning'}
/>
</FlexColumn>
<FlexRow>
<Spacer />
<Button compact padded onClick={() => this.cancelAndHide()}>
Close
</Button>
</FlexRow>
</Container>
);
}
renderError(result: {kind: 'error'; error: Error}) {
return (
<ReactReduxContext.Consumer>
{({store}) => (
<Container>
<Title bold>Error</Title>
<ErrorMessage code>
{result.error.message || 'File could not be saved.'}
</ErrorMessage>
<FlexRow>
<Spacer />
<Button compact padded onClick={() => this.cancelAndHide(store)}>
Close
</Button>
</FlexRow>
</Container>
)}
</ReactReduxContext.Consumer>
<Container>
<Title bold>Error</Title>
<ErrorMessage code>
{result.error.message || 'File could not be saved.'}
</ErrorMessage>
<FlexRow>
<Spacer />
<Button compact padded onClick={() => this.cancelAndHide()}>
Close
</Button>
</FlexRow>
</Container>
);
}
renderPending(statusUpdate: string | null) {
return (
<ReactReduxContext.Consumer>
{({store}) => (
<ShareSheetPendingDialog
width={500}
statusUpdate={statusUpdate}
statusMessage="Creating Flipper Export..."
onCancel={() => this.cancelAndHide(store)}
onRunInBackground={() => {
this.setState({runInBackground: true});
if (statusUpdate) {
this.dispatchAndUpdateToolBarStatus(statusUpdate);
}
this.props.onHide();
}}
/>
)}
</ReactReduxContext.Consumer>
<ShareSheetPendingDialog
width={500}
statusUpdate={statusUpdate}
statusMessage="Creating Flipper Export..."
onCancel={() => this.cancelAndHide()}
/>
);
}
cancelAndHide(store: MiddlewareAPI) {
store.dispatch(unsetShare());
cancelAndHide = () => {
this.props.onHide();
this.idler.cancel();
}
};
render() {
renderStatus() {
const {result, statusUpdate} = this.state;
switch (result.kind) {
case 'success':
@@ -252,4 +200,12 @@ export default class ShareSheetExportFile extends Component<Props, State> {
return this.renderPending(statusUpdate);
}
}
render() {
return (
<Modal visible onCancel={this.cancelAndHide} footer={null}>
{this.renderStatus()}
</Modal>
);
}
}

View File

@@ -7,14 +7,9 @@
* @format
*/
import {FlexColumn, Button, styled, Text, FlexRow, Spacer, Input} from '../ui';
import {FlexColumn, styled, Text, FlexRow, Spacer, Input} from '../ui';
import React, {Component} from 'react';
import {ReactReduxContext} from 'react-redux';
import {
setExportStatusComponent,
unsetShare,
setExportURL,
} from '../reducers/application';
import {Logger} from '../fb-interfaces/Logger';
import {IdlerImpl} from '../utils/Idler';
import {
@@ -29,21 +24,16 @@ import {
} from '../utils/exportData';
import ShareSheetErrorList from './ShareSheetErrorList';
import {reportPlatformFailures} from '../utils/metrics';
import CancellableExportStatus from './CancellableExportStatus';
import {performance} from 'perf_hooks';
import ShareSheetPendingDialog from './ShareSheetPendingDialog';
import {getInstance as getLogger} from '../fb-stubs/Logger';
import {resetSupportFormV2State} from '../reducers/supportForm';
import {MiddlewareAPI} from '../reducers/index';
import {getFlipperLib} from 'flipper-plugin';
import {getFlipperLib, Layout} from 'flipper-plugin';
import {Button, Modal} from 'antd';
export const SHARE_FLIPPER_TRACE_EVENT = 'share-flipper-link';
const Container = styled(FlexColumn)({
padding: 20,
width: 500,
});
const Copy = styled(Input)({
marginRight: 0,
marginBottom: 15,
@@ -69,11 +59,9 @@ const ErrorMessage = styled(Text)({
type Props = {
onHide: () => any;
logger: Logger;
closeOnFinish: boolean;
};
type State = {
runInBackground: boolean;
fetchMetaDataErrors: {
[plugin: string]: Error;
} | null;
@@ -88,7 +76,6 @@ export default class ShareSheetExportUrl extends Component<Props, State> {
fetchMetaDataErrors: null,
result: null,
statusUpdate: null,
runInBackground: false,
};
get store(): MiddlewareAPI {
@@ -97,30 +84,12 @@ export default class ShareSheetExportUrl extends Component<Props, State> {
idler = new IdlerImpl();
dispatchAndUpdateToolBarStatus(msg: string) {
this.store.dispatch(
setExportStatusComponent(
<CancellableExportStatus
msg={msg}
onCancel={() => {
this.idler.cancel();
this.store.dispatch(unsetShare());
}}
/>,
),
);
}
async componentDidMount() {
const mark = 'shareSheetExportUrl';
performance.mark(mark);
try {
const statusUpdate = (msg: string) => {
if (this.state.runInBackground) {
this.dispatchAndUpdateToolBarStatus(msg);
} else {
this.setState({statusUpdate: msg});
}
this.setState({statusUpdate: msg});
};
const {serializedString, fetchMetaDataErrors} =
await reportPlatformFailures(
@@ -146,17 +115,13 @@ export default class ShareSheetExportUrl extends Component<Props, State> {
});
const flipperUrl = (result as DataExportResult).flipperUrl;
if (flipperUrl) {
this.store.dispatch(setExportURL(flipperUrl));
if (this.state.runInBackground) {
getFlipperLib().writeTextToClipboard(String(flipperUrl));
new Notification('Shareable Flipper Export created', {
body: 'URL copied to clipboard',
requireInteraction: true,
});
}
getFlipperLib().writeTextToClipboard(String(flipperUrl));
new Notification('Shareable Flipper Export created', {
body: 'URL copied to clipboard',
requireInteraction: true,
});
}
this.setState({fetchMetaDataErrors, result});
this.store.dispatch(unsetShare());
this.store.dispatch(resetSupportFormV2State());
this.props.logger.trackTimeSince(mark, 'export:url-success');
} catch (e) {
@@ -165,62 +130,39 @@ export default class ShareSheetExportUrl extends Component<Props, State> {
error: e,
stacktrace: '',
};
if (!this.state.runInBackground) {
if (e instanceof Error) {
result.error = e.message;
result.stacktrace = e.stack || '';
}
// Show the error in UI.
this.setState({result});
if (e instanceof Error) {
result.error = e.message;
result.stacktrace = e.stack || '';
}
this.store.dispatch(unsetShare());
// Show the error in UI.
this.setState({result});
this.props.logger.trackTimeSince(mark, 'export:url-error', result);
throw e;
console.error('Failed to export to flipper trace', e);
}
}
sheetHidden: boolean = false;
hideSheet = () => {
this.sheetHidden = true;
this.props.onHide();
this.idler.cancel();
};
componentDidUpdate() {
const {result} = this.state;
if (!result || !(result as DataExportResult).flipperUrl) {
return;
}
if (!this.sheetHidden && this.props.closeOnFinish) {
this.hideSheet();
}
}
cancelAndHide = (store: MiddlewareAPI) => () => {
store.dispatch(unsetShare());
this.hideSheet();
cancelAndHide = () => {
this.props.onHide();
this.idler.cancel();
};
renderPending(statusUpdate: string | null) {
return (
<ReactReduxContext.Consumer>
{({store}) => (
<ShareSheetPendingDialog
width={500}
statusUpdate={statusUpdate}
statusMessage="Uploading Flipper Export..."
onCancel={this.cancelAndHide(store)}
onRunInBackground={() => {
this.setState({runInBackground: true});
if (statusUpdate) {
this.dispatchAndUpdateToolBarStatus(statusUpdate);
}
this.props.onHide();
}}
/>
)}
</ReactReduxContext.Consumer>
<Modal visible onCancel={this.cancelAndHide} footer={null}>
<ShareSheetPendingDialog
width={500}
statusUpdate={statusUpdate}
statusMessage="Uploading Flipper Export..."
onCancel={this.cancelAndHide}
/>
</Modal>
);
}
@@ -232,56 +174,54 @@ export default class ShareSheetExportUrl extends Component<Props, State> {
const {title, errorArray} = displayFetchMetadataErrors(fetchMetaDataErrors);
return (
<ReactReduxContext.Consumer>
{({store}) => (
<Container>
<>
<FlexColumn>
{(result as DataExportResult).flipperUrl ? (
<>
<Title bold>Data Upload Successful</Title>
<InfoText>
Flipper's data was successfully uploaded. This URL can be
used to share with other Flipper users. Opening it will
import the data from your export.
</InfoText>
<Copy
value={(result as DataExportResult).flipperUrl}
readOnly
/>
<InfoText>
When sharing your Flipper link, consider that the captured
data might contain sensitve information like access tokens
used in network requests.
</InfoText>
<ShareSheetErrorList
errors={errorArray}
title={title}
type={'warning'}
/>
</>
) : (
<>
<Title bold>
{(result as DataExportError).error_class || 'Error'}
</Title>
<ErrorMessage code>
{(result as DataExportError).error ||
'The data could not be uploaded'}
</ErrorMessage>
</>
)}
</FlexColumn>
<FlexRow>
<Spacer />
<Button compact padded onClick={this.cancelAndHide(store)}>
Close
</Button>
</FlexRow>
</>
</Container>
)}
</ReactReduxContext.Consumer>
<Modal visible onCancel={this.cancelAndHide} footer={null}>
<Layout.Container>
<>
<FlexColumn>
{(result as DataExportResult).flipperUrl ? (
<>
<Title bold>Data Upload Successful</Title>
<InfoText>
Flipper's data was successfully uploaded. This URL can be
used to share with other Flipper users. Opening it will
import the data from your export.
</InfoText>
<Copy
value={(result as DataExportResult).flipperUrl}
readOnly
/>
<InfoText>
When sharing your Flipper link, consider that the captured
data might contain sensitve information like access tokens
used in network requests.
</InfoText>
<ShareSheetErrorList
errors={errorArray}
title={title}
type={'warning'}
/>
</>
) : (
<>
<Title bold>
{(result as DataExportError).error_class || 'Error'}
</Title>
<ErrorMessage code>
{(result as DataExportError).error ||
'The data could not be uploaded'}
</ErrorMessage>
</>
)}
</FlexColumn>
<FlexRow>
<Spacer />
<Button type="primary" onClick={this.cancelAndHide}>
Close
</Button>
</FlexRow>
</>
</Layout.Container>
</Modal>
);
}
}

View File

@@ -7,76 +7,38 @@
* @format
*/
import {
FlexColumn,
styled,
Button,
colors,
Spacer,
FlexRow,
Text,
LoadingIndicator,
} from '../ui';
import {Button, Typography} from 'antd';
import {Layout, Spinner} from 'flipper-plugin';
import React from 'react';
const Container = styled(FlexColumn)({
padding: 20,
});
const Center = styled(FlexColumn)({
alignItems: 'center',
paddingTop: 50,
paddingBottom: 50,
});
const Uploading = styled(Text)({
marginTop: 15,
});
const {Text} = Typography;
export default function (props: {
statusMessage: string;
statusUpdate: string | null;
hideNavButtons?: boolean;
onCancel?: () => void;
onRunInBackground?: () => void;
width?: number;
}) {
return (
<Container style={{width: props.width}}>
<Center>
<LoadingIndicator size={30} />
{props.statusUpdate && props.statusUpdate.length > 0 ? (
<Uploading bold color={colors.macOSTitleBarIcon}>
{props.statusUpdate}
</Uploading>
) : (
<Uploading bold color={colors.macOSTitleBarIcon}>
{props.statusMessage}
</Uploading>
)}
</Center>
{!props.hideNavButtons && props.onCancel && props.onRunInBackground && (
<FlexRow>
<Spacer />
<Layout.Container style={{width: props.width, textAlign: 'center'}}>
<Spinner size={30} />
{props.statusUpdate && props.statusUpdate.length > 0 ? (
<Text strong>{props.statusUpdate}</Text>
) : (
<Text strong>{props.statusMessage}</Text>
)}
{!props.hideNavButtons && props.onCancel && (
<Layout.Right>
<div />
<Button
compact
padded
onClick={() => {
props.onCancel && props.onCancel();
}}>
Cancel
</Button>
<Button
compact
padded
type="primary"
onClick={() => {
props.onRunInBackground && props.onRunInBackground();
}}>
Run In Background
</Button>
</FlexRow>
</Layout.Right>
)}
</Container>
</Layout.Container>
);
}

View File

@@ -1,53 +0,0 @@
/**
* 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 React, {Component} from 'react';
import {setActiveSheet} from '../reducers/application';
import {connect} from 'react-redux';
import {State as Store} from '../reducers';
import {ActiveSheet} from '../reducers/application';
import {Modal} from 'antd';
type OwnProps = {
children: (onHide: () => any) => any;
};
type StateFromProps = {
activeSheet: ActiveSheet;
};
type DispatchFromProps = {
onHideSheet: () => void;
};
type Props = OwnProps & StateFromProps & DispatchFromProps;
class Sheet extends Component<Props, {}> {
render() {
return (
<Modal
visible={!!this.props.activeSheet}
footer={null}
onCancel={this.props.onHideSheet}>
{this.props.children(this.props.onHideSheet)}
</Modal>
);
}
}
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
({application: {activeSheet}}) => ({
activeSheet,
}),
{
onHideSheet: () => setActiveSheet(null),
},
)(Sheet);

View File

@@ -1,62 +0,0 @@
/**
* 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 React, {useCallback} from 'react';
import ShareSheetExportUrl from './ShareSheetExportUrl';
import ExportDataPluginSheet from './ExportDataPluginSheet';
import ShareSheetExportFile from './ShareSheetExportFile';
import Sheet from './Sheet';
import {
ACTIVE_SHEET_SHARE_DATA,
ACTIVE_SHEET_SHARE_DATA_IN_FILE,
ACTIVE_SHEET_SELECT_PLUGINS_TO_EXPORT,
} from '../reducers/application';
import {Logger} from '../fb-interfaces/Logger';
import {useStore} from '../utils/useStore';
export function SheetRenderer({logger}: {logger: Logger}) {
const activeSheet = useStore((state) => state.application.activeSheet);
// MWE: share being grabbed (and stored) here isn't ideal, clean up in the future?
const share = useStore((state) => state.application.share);
const renderSheet = useCallback(
(onHide: () => any) => {
switch (activeSheet) {
case ACTIVE_SHEET_SELECT_PLUGINS_TO_EXPORT:
return <ExportDataPluginSheet onHide={onHide} />;
case ACTIVE_SHEET_SHARE_DATA:
return (
<ShareSheetExportUrl
onHide={onHide}
logger={logger}
closeOnFinish={share != null && share.closeOnFinish}
/>
);
case ACTIVE_SHEET_SHARE_DATA_IN_FILE:
return share && share.type === 'file' ? (
<ShareSheetExportFile
onHide={onHide}
file={share.file}
logger={logger}
/>
) : (
(() => {
console.error('No file provided when calling share sheet.');
return null;
})()
);
default:
return null;
}
},
[activeSheet, logger, share],
);
return <Sheet>{renderSheet}</Sheet>;
}

View File

@@ -20,7 +20,6 @@ test('ShareSheetPendingDialog is rendered with status update', () => {
<Provider store={mockStore}>
<ShareSheetPendingDialog
onCancel={() => {}}
onRunInBackground={() => {}}
statusMessage="wubba lubba dub dub"
statusUpdate="Update"
/>
@@ -35,7 +34,6 @@ test('ShareSheetPendingDialog is rendered without status update', () => {
<Provider store={mockStore}>
<ShareSheetPendingDialog
onCancel={() => {}}
onRunInBackground={() => {}}
statusMessage="wubba lubba dub dub"
statusUpdate={null}
/>

View File

@@ -2,35 +2,60 @@
exports[`ShareSheetPendingDialog is rendered with status update 1`] = `
<div
className="css-1lvu114-View-FlexBox-FlexColumn"
className="css-gzchr8-Container e1hsqii15"
style={
Object {
"textAlign": "center",
"width": undefined,
}
}
>
<div
className="css-1tg2qwm-View-FlexBox-FlexColumn"
className="ant-spin ant-spin-spinning"
>
<div
className="css-hs91vy-LoadingIndicator eq9prj20"
size={30}
/>
<span
className="css-137ad86-Text"
color="#6f6f6f"
aria-label="loading"
className="anticon anticon-loading anticon-spin ant-spin-dot"
role="img"
style={
Object {
"fontSize": 30,
}
}
>
Update
<svg
aria-hidden="true"
data-icon="loading"
fill="currentColor"
focusable="false"
height="1em"
viewBox="0 0 1024 1024"
width="1em"
>
<path
d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"
/>
</svg>
</span>
</div>
<div
className="css-wospjg-View-FlexBox-FlexRow epz0qe20"
<span
className="ant-typography"
style={
Object {
"WebkitLineClamp": undefined,
}
}
>
<div
className="css-t4wmtk-View-FlexBox-Spacer e13mj6h80"
/>
<strong>
Update
</strong>
</span>
<div
className="css-1knrt0j-SandySplitContainer e1hsqii10"
>
<div />
<button
className="ant-btn ant-btn-default"
className="ant-btn"
onClick={[Function]}
type="button"
>
@@ -38,50 +63,66 @@ exports[`ShareSheetPendingDialog is rendered with status update 1`] = `
Cancel
</span>
</button>
<button
className="ant-btn ant-btn-primary"
onClick={[Function]}
type="button"
>
<span>
Run In Background
</span>
</button>
</div>
</div>
`;
exports[`ShareSheetPendingDialog is rendered without status update 1`] = `
<div
className="css-1lvu114-View-FlexBox-FlexColumn"
className="css-gzchr8-Container e1hsqii15"
style={
Object {
"textAlign": "center",
"width": undefined,
}
}
>
<div
className="css-1tg2qwm-View-FlexBox-FlexColumn"
className="ant-spin ant-spin-spinning"
>
<div
className="css-hs91vy-LoadingIndicator eq9prj20"
size={30}
/>
<span
className="css-137ad86-Text"
color="#6f6f6f"
aria-label="loading"
className="anticon anticon-loading anticon-spin ant-spin-dot"
role="img"
style={
Object {
"fontSize": 30,
}
}
>
wubba lubba dub dub
<svg
aria-hidden="true"
data-icon="loading"
fill="currentColor"
focusable="false"
height="1em"
viewBox="0 0 1024 1024"
width="1em"
>
<path
d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"
/>
</svg>
</span>
</div>
<div
className="css-wospjg-View-FlexBox-FlexRow epz0qe20"
<span
className="ant-typography"
style={
Object {
"WebkitLineClamp": undefined,
}
}
>
<div
className="css-t4wmtk-View-FlexBox-Spacer e13mj6h80"
/>
<strong>
wubba lubba dub dub
</strong>
</span>
<div
className="css-1knrt0j-SandySplitContainer e1hsqii10"
>
<div />
<button
className="ant-btn ant-btn-default"
className="ant-btn"
onClick={[Function]}
type="button"
>
@@ -89,15 +130,6 @@ exports[`ShareSheetPendingDialog is rendered without status update 1`] = `
Cancel
</span>
</button>
<button
className="ant-btn ant-btn-primary"
onClick={[Function]}
type="button"
>
<span>
Run In Background
</span>
</button>
</div>
</div>
`;

View File

@@ -8,9 +8,10 @@
*/
import {PluginDetails} from 'flipper-plugin-lib';
import {Layout} from 'flipper-plugin';
import Client from '../../Client';
import {TableBodyRow} from '../../ui/components/table/types';
import React, {Component, Fragment} from 'react';
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {Text, ManagedTable, styled, colors} from '../../ui';
import StatusIndicator from '../../ui/components/StatusIndicator';
@@ -211,7 +212,7 @@ class PluginDebugger extends Component<Props> {
render() {
return (
<Fragment>
<Layout.Container pad>
<InfoText>The table lists all plugins known to Flipper.</InfoText>
<TableContainer>
<ManagedTable
@@ -221,7 +222,7 @@ class PluginDebugger extends Component<Props> {
columnSizes={COLUMNS_SIZES}
/>
</TableContainer>
</Fragment>
</Layout.Container>
);
}
}

View File

@@ -11,33 +11,7 @@
// eslint-disable-next-line flipper/no-electron-remote-imports
import {remote} from 'electron';
import {v1 as uuidv1} from 'uuid';
import {ReactElement} from 'react';
import CancellableExportStatus from '../chrome/CancellableExportStatus';
import {Actions} from './';
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';
export const ACTIVE_SHEET_SIGN_IN: 'SIGN_IN' = 'SIGN_IN';
export const ACTIVE_SHEET_SETTINGS: 'SETTINGS' = 'SETTINGS';
export const ACTIVE_SHEET_DOCTOR: 'DOCTOR' = 'DOCTOR';
export const ACTIVE_SHEET_SHARE_DATA_IN_FILE: '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_CHANGELOG = 'ACTIVE_SHEET_CHANGELOG';
export const ACTIVE_SHEET_CHANGELOG_RECENT_ONLY =
'ACTIVE_SHEET_CHANGELOG_RECENT_ONLY';
/**
* @deprecated this is a weird mechanism, and using imperative dialogs will be simpler
*/
export type ActiveSheet =
| typeof ACTIVE_SHEET_SHARE_DATA
| typeof ACTIVE_SHEET_SHARE_DATA_IN_FILE
| typeof ACTIVE_SHEET_SELECT_PLUGINS_TO_EXPORT
| null;
export type LauncherMsg = {
message: string;
@@ -73,7 +47,6 @@ export type State = {
rightSidebarVisible: boolean;
rightSidebarAvailable: boolean;
windowIsFocused: boolean;
activeSheet: ActiveSheet;
share: ShareType | null;
sessionId: string | null;
serverPorts: ServerPorts;
@@ -96,18 +69,6 @@ export type Action =
type: 'windowIsFocused';
payload: {isFocused: boolean; time: number};
}
| {
type: 'SET_ACTIVE_SHEET';
payload: ActiveSheet;
}
| {
type: typeof ACTIVE_SHEET_SHARE_DATA_IN_FILE;
payload: {file: string; closeOnFinish: boolean};
}
| {
type: typeof ACTIVE_SHEET_SELECT_PLUGINS_TO_EXPORT;
payload: ShareType;
}
| {
type: 'SET_SERVER_PORTS';
payload: {
@@ -129,17 +90,6 @@ export type Action =
message: string;
};
}
| {
type: 'UNSET_SHARE';
}
| {
type: 'SET_EXPORT_STATUS_MESSAGE';
payload: React.ReactNode;
}
| {
type: 'SET_EXPORT_URL';
payload: string;
}
| {
type: 'ADD_STATUS_MSG';
payload: {msg: string; sender: string};
@@ -213,27 +163,6 @@ export default function reducer(
...state,
windowIsFocused: action.payload.isFocused,
};
} else if (action.type === 'SET_ACTIVE_SHEET') {
return {
...state,
activeSheet: action.payload,
};
} else if (action.type === ACTIVE_SHEET_SHARE_DATA_IN_FILE) {
return {
...state,
activeSheet: ACTIVE_SHEET_SHARE_DATA_IN_FILE,
share: {
type: 'file',
file: action.payload.file,
closeOnFinish: action.payload.closeOnFinish,
},
};
} else if (action.type === ACTIVE_SHEET_SELECT_PLUGINS_TO_EXPORT) {
return {
...state,
activeSheet: ACTIVE_SHEET_SELECT_PLUGINS_TO_EXPORT,
share: action.payload,
};
} else if (action.type === 'SET_SERVER_PORTS') {
return {
...state,
@@ -249,23 +178,6 @@ export default function reducer(
...state,
launcherMsg: action.payload,
};
} else if (action.type === 'SET_EXPORT_STATUS_MESSAGE') {
if (state.share) {
const {share} = state;
return {
...state,
share: {...share, statusComponent: action.payload},
};
}
return state;
} else if (action.type === 'UNSET_SHARE') {
return {...state, share: null};
} else if (action.type === 'SET_EXPORT_URL') {
const share = state.share;
if (share && share.type === 'link') {
return {...state, share: {...share, url: action.payload}};
}
return state;
} else if (action.type === 'ADD_STATUS_MSG') {
const {sender, msg} = action.payload;
const statusMsg = statusMessage(sender, msg);
@@ -298,37 +210,6 @@ export const toggleAction = (
payload,
});
export const unsetShare = (): Action => ({
type: UNSET_SHARE,
});
export const setExportStatusComponent = (
payload: ReactElement<typeof CancellableExportStatus>,
): Action => ({
type: SET_EXPORT_STATUS_MESSAGE,
payload,
});
export const setSelectPluginsToExportActiveSheet = (
payload: ShareType,
): Action => ({
type: ACTIVE_SHEET_SELECT_PLUGINS_TO_EXPORT,
payload,
});
export const setExportDataToFileActiveSheet = (payload: {
file: string;
closeOnFinish: boolean;
}): Action => ({
type: ACTIVE_SHEET_SHARE_DATA_IN_FILE,
payload: payload,
});
export const setActiveSheet = (payload: ActiveSheet): Action => ({
type: 'SET_ACTIVE_SHEET',
payload,
});
export const toggleLeftSidebarVisible = (payload?: boolean): Action => ({
type: 'leftSidebarVisible',
payload,
@@ -344,11 +225,6 @@ export const toggleRightSidebarAvailable = (payload?: boolean): Action => ({
payload,
});
export const setExportURL = (result: string): Action => ({
type: 'SET_EXPORT_URL',
payload: result,
});
export const addStatusMessage = (payload: StatusMessageType): Action => ({
type: 'ADD_STATUS_MSG',
payload,

View File

@@ -30,7 +30,6 @@ import {AppInspect} from './appinspect/AppInspect';
import PluginContainer from '../PluginContainer';
import {ContentContainer} from './ContentContainer';
import {Notification} from './notification/Notification';
import {SheetRenderer} from '../chrome/SheetRenderer';
import ChangelogSheet, {hasNewChangesToShow} from '../chrome/ChangelogSheet';
import {getVersionString} from '../utils/versionString';
import config from '../fb-stubs/config';
@@ -179,7 +178,6 @@ export function SandyApp() {
{outOfContentsContainer}
</MainContainer>
</Layout.Left>
<SheetRenderer logger={logger} />
<_PortalsManager />
</Layout.Container>
);

View File

@@ -7,13 +7,14 @@
* @format
*/
import * as React from 'react';
import os from 'os';
import path from 'path';
import electron from 'electron';
import {getInstance as getLogger} from '../fb-stubs/Logger';
import {getInstance, getInstance as getLogger} from '../fb-stubs/Logger';
import {Store, MiddlewareAPI} from '../reducers';
import {DeviceExport} from '../devices/BaseDevice';
import {State as PluginsState} from '../reducers/plugins';
import {selectedPlugins, State as PluginsState} from '../reducers/plugins';
import {PluginNotification} from '../reducers/notifications';
import Client, {ClientExport} from '../Client';
import {getAppVersion} from './info';
@@ -33,14 +34,16 @@ import {
resetSupportFormV2State,
SupportFormRequestDetailsState,
} from '../reducers/supportForm';
import {setSelectPluginsToExportActiveSheet} from '../reducers/application';
import {deconstructClientId} from '../utils/clientUtils';
import {performance} from 'perf_hooks';
import {processMessageQueue} from './messageQueue';
import {getPluginTitle} from './pluginUtils';
import {capture} from './screenshot';
import {uploadFlipperMedia} from '../fb-stubs/user';
import {ClientQuery, Idler} from 'flipper-plugin';
import {ClientQuery, Dialog, Idler} from 'flipper-plugin';
import ShareSheetExportUrl from '../chrome/ShareSheetExportUrl';
import ShareSheetExportFile from '../chrome/ShareSheetExportFile';
import ExportDataPluginSheet from '../chrome/ExportDataPluginSheet';
export const IMPORT_FLIPPER_TRACE_EVENT = 'import-flipper-trace';
export const EXPORT_FLIPPER_TRACE_EVENT = 'export-flipper-trace';
@@ -612,36 +615,54 @@ export function showOpenDialog(store: Store) {
});
}
export function startFileExport(dispatch: Store['dispatch']) {
electron.remote.dialog
.showSaveDialog(
// @ts-ignore This appears to work but isn't allowed by the types
null,
{
title: 'FlipperExport',
defaultPath: path.join(os.homedir(), 'FlipperExport.flipper'),
},
)
.then(async (result: electron.SaveDialogReturnValue) => {
const file = result.filePath;
if (!file) {
return;
}
dispatch(
setSelectPluginsToExportActiveSheet({
type: 'file',
file: file,
closeOnFinish: false,
}),
);
});
export async function startFileExport(dispatch: Store['dispatch']) {
const result = await electron.remote.dialog.showSaveDialog(
// @ts-ignore This appears to work but isn't allowed by the types
null,
{
title: 'FlipperExport',
defaultPath: path.join(os.homedir(), 'FlipperExport.flipper'),
},
);
const file = result.filePath;
if (!file) {
return;
}
const plugins = await selectPlugins();
if (plugins === false) {
return; // cancelled
}
// TODO: no need to put this in the store,
// need to be cleaned up later in combination with SupportForm
dispatch(selectedPlugins(plugins));
Dialog.showModal((onHide) => (
<ShareSheetExportFile onHide={onHide} file={file} logger={getInstance()} />
));
}
export function startLinkExport(dispatch: Store['dispatch']) {
dispatch(
setSelectPluginsToExportActiveSheet({
type: 'link',
closeOnFinish: false,
}),
);
export async function startLinkExport(dispatch: Store['dispatch']) {
const plugins = await selectPlugins();
if (plugins === false) {
return; // cancelled
}
// TODO: no need to put this in the store,
// need to be cleaned up later in combination with SupportForm
dispatch(selectedPlugins(plugins));
Dialog.showModal((onHide) => (
<ShareSheetExportUrl onHide={onHide} logger={getInstance()} />
));
}
async function selectPlugins() {
return await Dialog.select<string[]>({
title: 'Select plugins to export',
defaultValue: [],
renderer: (value, onChange, onCancel) => (
<ExportDataPluginSheet
onHide={onCancel}
selectedPlugins={value}
setSelectedPlugins={onChange}
/>
),
});
}

View File

@@ -237,6 +237,27 @@ export const Dialog = {
});
},
select<T>({
defaultValue,
renderer,
...rest
}: {
defaultValue: T;
renderer: (
value: T,
onChange: (newValue: T) => void,
onCancel: () => void,
) => React.ReactElement;
} & BaseDialogOptions): DialogResult<false | T> {
const handle = Dialog.show<T>({
...rest,
defaultValue,
children: (currentValue, setValue): React.ReactElement =>
renderer(currentValue, setValue, () => handle.close()),
});
return handle;
},
loading({
title,
message,