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 {connect} from 'react-redux';
import React, {Component} from 'react'; import React, {Component} from 'react';
import {ShareType} from '../reducers/application';
import {State as Store} from '../reducers'; 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 ListView from './ListView';
import {Dispatch, Action} from 'redux';
import {unsetShare} from '../reducers/application';
import {FlexColumn, styled} from '../ui'; import {FlexColumn, styled} from '../ui';
import {getExportablePlugins} from '../selectors/connections'; import {getExportablePlugins} from '../selectors/connections';
type OwnProps = { type OwnProps = {
onHide: () => void; onHide: () => void;
selectedPlugins: Array<string>;
setSelectedPlugins: (plugins: string[]) => void;
}; };
type StateFromProps = { type StateFromProps = {
share: ShareType | null;
selectedPlugins: Array<string>;
availablePluginsToExport: Array<{id: string; label: string}>; availablePluginsToExport: Array<{id: string; label: string}>;
}; };
type DispatchFromProps = { type Props = OwnProps & StateFromProps;
setSelectedPlugins: (payload: Array<string>) => void;
setActiveSheet: (payload: ActiveSheet) => void;
setExportDataToFileActiveSheet: (payload: {
file: string;
closeOnFinish: boolean;
}) => void;
unsetShare: () => void;
};
type Props = OwnProps & StateFromProps & DispatchFromProps;
const Container = styled(FlexColumn)({ const Container = styled(FlexColumn)({
maxHeight: 700, maxHeight: 700,
@@ -53,78 +33,27 @@ const Container = styled(FlexColumn)({
class ExportDataPluginSheet extends Component<Props, {}> { class ExportDataPluginSheet extends Component<Props, {}> {
render() { render() {
const {onHide} = this.props;
const onHideWithUnsettingShare = () => {
this.props.unsetShare();
onHide();
};
return ( return (
<Container> <Container>
<ListView <ListView
type="multiple" type="multiple"
title="Select the plugins for which you want to export the data" title="Select the plugins for which you want to export the data"
leftPadding={8} 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) => { onChange={(selectedArray) => {
this.props.setSelectedPlugins(selectedArray); this.props.setSelectedPlugins(selectedArray);
}} }}
elements={this.props.availablePluginsToExport} elements={this.props.availablePluginsToExport}
selectedElements={new Set(this.props.selectedPlugins)} selectedElements={new Set(this.props.selectedPlugins)}
onHide={onHideWithUnsettingShare} onHide={() => {}}
/> />
</Container> </Container>
); );
} }
} }
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>( export default connect<StateFromProps, {}, OwnProps, Store>((state) => {
(state) => { const availablePluginsToExport = getExportablePlugins(state);
const availablePluginsToExport = getExportablePlugins(state); return {
return { availablePluginsToExport,
share: state.application.share, };
selectedPlugins: state.plugins.selectedPlugins, })(ExportDataPluginSheet);
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);

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,35 +2,60 @@
exports[`ShareSheetPendingDialog is rendered with status update 1`] = ` exports[`ShareSheetPendingDialog is rendered with status update 1`] = `
<div <div
className="css-1lvu114-View-FlexBox-FlexColumn" className="css-gzchr8-Container e1hsqii15"
style={ style={
Object { Object {
"textAlign": "center",
"width": undefined, "width": undefined,
} }
} }
> >
<div <div
className="css-1tg2qwm-View-FlexBox-FlexColumn" className="ant-spin ant-spin-spinning"
> >
<div
className="css-hs91vy-LoadingIndicator eq9prj20"
size={30}
/>
<span <span
className="css-137ad86-Text" aria-label="loading"
color="#6f6f6f" 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> </span>
</div> </div>
<div <span
className="css-wospjg-View-FlexBox-FlexRow epz0qe20" className="ant-typography"
style={
Object {
"WebkitLineClamp": undefined,
}
}
> >
<div <strong>
className="css-t4wmtk-View-FlexBox-Spacer e13mj6h80" Update
/> </strong>
</span>
<div
className="css-1knrt0j-SandySplitContainer e1hsqii10"
>
<div />
<button <button
className="ant-btn ant-btn-default" className="ant-btn"
onClick={[Function]} onClick={[Function]}
type="button" type="button"
> >
@@ -38,50 +63,66 @@ exports[`ShareSheetPendingDialog is rendered with status update 1`] = `
Cancel Cancel
</span> </span>
</button> </button>
<button
className="ant-btn ant-btn-primary"
onClick={[Function]}
type="button"
>
<span>
Run In Background
</span>
</button>
</div> </div>
</div> </div>
`; `;
exports[`ShareSheetPendingDialog is rendered without status update 1`] = ` exports[`ShareSheetPendingDialog is rendered without status update 1`] = `
<div <div
className="css-1lvu114-View-FlexBox-FlexColumn" className="css-gzchr8-Container e1hsqii15"
style={ style={
Object { Object {
"textAlign": "center",
"width": undefined, "width": undefined,
} }
} }
> >
<div <div
className="css-1tg2qwm-View-FlexBox-FlexColumn" className="ant-spin ant-spin-spinning"
> >
<div
className="css-hs91vy-LoadingIndicator eq9prj20"
size={30}
/>
<span <span
className="css-137ad86-Text" aria-label="loading"
color="#6f6f6f" 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> </span>
</div> </div>
<div <span
className="css-wospjg-View-FlexBox-FlexRow epz0qe20" className="ant-typography"
style={
Object {
"WebkitLineClamp": undefined,
}
}
> >
<div <strong>
className="css-t4wmtk-View-FlexBox-Spacer e13mj6h80" wubba lubba dub dub
/> </strong>
</span>
<div
className="css-1knrt0j-SandySplitContainer e1hsqii10"
>
<div />
<button <button
className="ant-btn ant-btn-default" className="ant-btn"
onClick={[Function]} onClick={[Function]}
type="button" type="button"
> >
@@ -89,15 +130,6 @@ exports[`ShareSheetPendingDialog is rendered without status update 1`] = `
Cancel Cancel
</span> </span>
</button> </button>
<button
className="ant-btn ant-btn-primary"
onClick={[Function]}
type="button"
>
<span>
Run In Background
</span>
</button>
</div> </div>
</div> </div>
`; `;

View File

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

View File

@@ -11,33 +11,7 @@
// eslint-disable-next-line flipper/no-electron-remote-imports // eslint-disable-next-line flipper/no-electron-remote-imports
import {remote} from 'electron'; import {remote} from 'electron';
import {v1 as uuidv1} from 'uuid'; import {v1 as uuidv1} from 'uuid';
import {ReactElement} from 'react';
import CancellableExportStatus from '../chrome/CancellableExportStatus';
import {Actions} from './'; 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 = { export type LauncherMsg = {
message: string; message: string;
@@ -73,7 +47,6 @@ export type State = {
rightSidebarVisible: boolean; rightSidebarVisible: boolean;
rightSidebarAvailable: boolean; rightSidebarAvailable: boolean;
windowIsFocused: boolean; windowIsFocused: boolean;
activeSheet: ActiveSheet;
share: ShareType | null; share: ShareType | null;
sessionId: string | null; sessionId: string | null;
serverPorts: ServerPorts; serverPorts: ServerPorts;
@@ -96,18 +69,6 @@ export type Action =
type: 'windowIsFocused'; type: 'windowIsFocused';
payload: {isFocused: boolean; time: number}; 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'; type: 'SET_SERVER_PORTS';
payload: { payload: {
@@ -129,17 +90,6 @@ export type Action =
message: string; message: string;
}; };
} }
| {
type: 'UNSET_SHARE';
}
| {
type: 'SET_EXPORT_STATUS_MESSAGE';
payload: React.ReactNode;
}
| {
type: 'SET_EXPORT_URL';
payload: string;
}
| { | {
type: 'ADD_STATUS_MSG'; type: 'ADD_STATUS_MSG';
payload: {msg: string; sender: string}; payload: {msg: string; sender: string};
@@ -213,27 +163,6 @@ export default function reducer(
...state, ...state,
windowIsFocused: action.payload.isFocused, 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') { } else if (action.type === 'SET_SERVER_PORTS') {
return { return {
...state, ...state,
@@ -249,23 +178,6 @@ export default function reducer(
...state, ...state,
launcherMsg: action.payload, 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') { } else if (action.type === 'ADD_STATUS_MSG') {
const {sender, msg} = action.payload; const {sender, msg} = action.payload;
const statusMsg = statusMessage(sender, msg); const statusMsg = statusMessage(sender, msg);
@@ -298,37 +210,6 @@ export const toggleAction = (
payload, 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 => ({ export const toggleLeftSidebarVisible = (payload?: boolean): Action => ({
type: 'leftSidebarVisible', type: 'leftSidebarVisible',
payload, payload,
@@ -344,11 +225,6 @@ export const toggleRightSidebarAvailable = (payload?: boolean): Action => ({
payload, payload,
}); });
export const setExportURL = (result: string): Action => ({
type: 'SET_EXPORT_URL',
payload: result,
});
export const addStatusMessage = (payload: StatusMessageType): Action => ({ export const addStatusMessage = (payload: StatusMessageType): Action => ({
type: 'ADD_STATUS_MSG', type: 'ADD_STATUS_MSG',
payload, payload,

View File

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

View File

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