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 {
share: state.application.share,
selectedPlugins: state.plugins.selectedPlugins,
availablePluginsToExport, availablePluginsToExport,
}; };
}, })(ExportDataPluginSheet);
(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.dispatchAndUpdateToolBarStatus(msg);
} else {
this.setState({statusUpdate: msg}); 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,8 +132,6 @@ export default class ShareSheetExportFile extends Component<Props, State> {
); );
return ( return (
<ReactReduxContext.Consumer>
{({store}) => (
<Container> <Container>
<FlexColumn> <FlexColumn>
<Title bold>Data Exported Successfully</Title> <Title bold>Data Exported Successfully</Title>
@@ -182,20 +148,16 @@ export default class ShareSheetExportFile extends Component<Props, State> {
</FlexColumn> </FlexColumn>
<FlexRow> <FlexRow>
<Spacer /> <Spacer />
<Button compact padded onClick={() => this.cancelAndHide(store)}> <Button compact padded onClick={() => this.cancelAndHide()}>
Close Close
</Button> </Button>
</FlexRow> </FlexRow>
</Container> </Container>
)}
</ReactReduxContext.Consumer>
); );
} }
renderError(result: {kind: 'error'; error: Error}) { renderError(result: {kind: 'error'; error: Error}) {
return ( return (
<ReactReduxContext.Consumer>
{({store}) => (
<Container> <Container>
<Title bold>Error</Title> <Title bold>Error</Title>
<ErrorMessage code> <ErrorMessage code>
@@ -203,45 +165,31 @@ export default class ShareSheetExportFile extends Component<Props, State> {
</ErrorMessage> </ErrorMessage>
<FlexRow> <FlexRow>
<Spacer /> <Spacer />
<Button compact padded onClick={() => this.cancelAndHide(store)}> <Button compact padded onClick={() => this.cancelAndHide()}>
Close Close
</Button> </Button>
</FlexRow> </FlexRow>
</Container> </Container>
)}
</ReactReduxContext.Consumer>
); );
} }
renderPending(statusUpdate: string | null) { renderPending(statusUpdate: string | null) {
return ( return (
<ReactReduxContext.Consumer>
{({store}) => (
<ShareSheetPendingDialog <ShareSheetPendingDialog
width={500} width={500}
statusUpdate={statusUpdate} statusUpdate={statusUpdate}
statusMessage="Creating Flipper Export..." statusMessage="Creating Flipper Export..."
onCancel={() => this.cancelAndHide(store)} onCancel={() => this.cancelAndHide()}
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.dispatchAndUpdateToolBarStatus(msg);
} else {
this.setState({statusUpdate: msg}); 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));
if (this.state.runInBackground) {
getFlipperLib().writeTextToClipboard(String(flipperUrl)); getFlipperLib().writeTextToClipboard(String(flipperUrl));
new Notification('Shareable Flipper Export created', { new Notification('Shareable Flipper Export created', {
body: 'URL copied to clipboard', body: 'URL copied to clipboard',
requireInteraction: true, 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. // Show the error in UI.
this.setState({result}); this.setState({result});
}
this.store.dispatch(unsetShare());
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(store)} onCancel={this.cancelAndHide}
onRunInBackground={() => {
this.setState({runInBackground: true});
if (statusUpdate) {
this.dispatchAndUpdateToolBarStatus(statusUpdate);
}
this.props.onHide();
}}
/> />
)} </Modal>
</ReactReduxContext.Consumer>
); );
} }
@@ -232,9 +174,8 @@ 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 ? (
@@ -274,14 +215,13 @@ export default class ShareSheetExportUrl extends Component<Props, State> {
</FlexColumn> </FlexColumn>
<FlexRow> <FlexRow>
<Spacer /> <Spacer />
<Button compact padded onClick={this.cancelAndHide(store)}> <Button type="primary" onClick={this.cancelAndHide}>
Close Close
</Button> </Button>
</FlexRow> </FlexRow>
</> </>
</Container> </Layout.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 ? (
<Uploading bold color={colors.macOSTitleBarIcon}> <Text strong>{props.statusUpdate}</Text>
{props.statusUpdate}
</Uploading>
) : ( ) : (
<Uploading bold color={colors.macOSTitleBarIcon}> <Text strong>{props.statusMessage}</Text>
{props.statusMessage}
</Uploading>
)} )}
</Center> {!props.hideNavButtons && props.onCancel && (
{!props.hideNavButtons && props.onCancel && props.onRunInBackground && ( <Layout.Right>
<FlexRow> <div />
<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,
}
}
> >
<strong>
Update
</strong>
</span>
<div <div
className="css-t4wmtk-View-FlexBox-Spacer e13mj6h80" 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,
}
}
> >
<strong>
wubba lubba dub dub
</strong>
</span>
<div <div
className="css-t4wmtk-View-FlexBox-Spacer e13mj6h80" 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'),
}, },
) );
.then(async (result: electron.SaveDialogReturnValue) => {
const file = result.filePath; const file = result.filePath;
if (!file) { if (!file) {
return; return;
} }
dispatch( const plugins = await selectPlugins();
setSelectPluginsToExportActiveSheet({ if (plugins === false) {
type: 'file', return; // cancelled
file: file, }
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) => (
<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,