diff --git a/src/chrome/CancellableExportStatus.js b/src/chrome/CancellableExportStatus.js new file mode 100644 index 000000000..84468a1e3 --- /dev/null +++ b/src/chrome/CancellableExportStatus.js @@ -0,0 +1,37 @@ +/** + * Copyright 2018-present Facebook. + * 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 'flipper'; +import {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 => void, +}; + +export default class CancellableExportStatus extends Component { + render() { + const {msg, onCancel} = this.props; + return ( + + +   + {msg} +   + + + ); + } +} diff --git a/src/chrome/ShareSheet.js b/src/chrome/ShareSheet.js index 1dc84b243..7f48d1596 100644 --- a/src/chrome/ShareSheet.js +++ b/src/chrome/ShareSheet.js @@ -17,6 +17,7 @@ import { Spacer, Input, } from 'flipper'; +import {setExportStatusComponent, unsetShare} from '../reducers/application'; import type {Logger} from '../fb-interfaces/Logger.js'; import {Idler} from '../utils/Idler'; import {shareFlipperData} from '../fb-stubs/user'; @@ -25,6 +26,7 @@ import PropTypes from 'prop-types'; import {clipboard} from 'electron'; import ShareSheetErrorList from './ShareSheetErrorList.js'; import {reportPlatformFailures} from '../utils/metrics'; +import CancellableExportStatus from './CancellableExportStatus'; // $FlowFixMe: Missing type defs for node built-in. import {performance} from 'perf_hooks'; export const SHARE_FLIPPER_TRACE_EVENT = 'share-flipper-link'; @@ -71,6 +73,7 @@ type Props = { logger: Logger, }; type State = { + runInBackground: boolean, errorArray: Array, result: | ?{ @@ -92,21 +95,42 @@ export default class ShareSheet extends Component { errorArray: [], result: null, statusUpdate: null, + runInBackground: false, }; idler = new Idler(); + dispatchAndUpdateToolBarStatus(msg: string) { + this.context.store.dispatch( + setExportStatusComponent( + { + this.idler.cancel(); + this.context.store.dispatch(unsetShare()); + }} + />, + ), + ); + } + async componentDidMount() { const mark = 'shareSheetExportUrl'; performance.mark(mark); try { const {serializedString, errorArray} = await reportPlatformFailures( exportStore(this.context.store, this.idler, (msg: string) => { - this.setState({statusUpdate: msg}); + if (this.state.runInBackground) { + this.dispatchAndUpdateToolBarStatus(msg); + } else { + this.setState({statusUpdate: msg}); + } }), `${EXPORT_FLIPPER_TRACE_EVENT}:UI_LINK`, ); + this.context.store.dispatch(unsetShare()); + const result = await reportPlatformFailures( shareFlipperData(serializedString), `${SHARE_FLIPPER_TRACE_EVENT}`, @@ -151,6 +175,19 @@ export default class ShareSheet extends Component { + diff --git a/src/chrome/ShareSheetExportFile.js b/src/chrome/ShareSheetExportFile.js index 2ab46d9c7..c34bcdade 100644 --- a/src/chrome/ShareSheetExportFile.js +++ b/src/chrome/ShareSheetExportFile.js @@ -15,7 +15,9 @@ import { FlexRow, Spacer, } from 'flipper'; +import {setExportStatusComponent, unsetShare} from '../reducers/application'; import {reportPlatformFailures} from '../utils/metrics'; +import CancellableExportStatus from './CancellableExportStatus'; // $FlowFixMe: Missing type defs for node built-in. import {performance} from 'perf_hooks'; import type {Logger} from '../fb-interfaces/Logger.js'; @@ -72,6 +74,7 @@ type State = { error: ?Error, }, statusUpdate: ?string, + runInBackground: boolean, }; export default class ShareSheetExportFile extends Component { @@ -83,10 +86,25 @@ export default class ShareSheetExportFile extends Component { errorArray: [], result: null, statusUpdate: null, + runInBackground: false, }; idler = new Idler(); + dispatchAndUpdateToolBarStatus(msg: string) { + this.context.store.dispatch( + setExportStatusComponent( + { + this.idler.cancel(); + this.context.store.dispatch(unsetShare()); + }} + />, + ), + ); + } + async componentDidMount() { const mark = 'shareSheetExportFile'; performance.mark(mark); @@ -102,11 +120,24 @@ export default class ShareSheetExportFile extends Component { this.context.store, this.idler, (msg: string) => { - this.setState({statusUpdate: msg}); + if (this.state.runInBackground) { + this.dispatchAndUpdateToolBarStatus(msg); + } else { + this.setState({statusUpdate: msg}); + } }, ), `${EXPORT_FLIPPER_TRACE_EVENT}:UI_FILE`, ); + this.context.store.dispatch(unsetShare()); + if (this.state.runInBackground) { + new window.Notification('Sharable Flipper trace created', { + //$FlowFixMe: Already checked that props.file exists + body: `Flipper trace exported to the ${this.props.file}`, + requireInteraction: true, + }); + return; + } this.setState({errorArray, result: {success: true, error: null}}); this.props.logger.trackTimeSince(mark, 'export:file-success'); } catch (err) { @@ -182,6 +213,19 @@ export default class ShareSheetExportFile extends Component { + diff --git a/src/chrome/TitleBar.tsx b/src/chrome/TitleBar.tsx index a1c2c8033..d182a1b53 100644 --- a/src/chrome/TitleBar.tsx +++ b/src/chrome/TitleBar.tsx @@ -5,7 +5,7 @@ * @format */ -import {ActiveSheet, LauncherMsg} from '../reducers/application.js'; +import {ActiveSheet, LauncherMsg, ShareType} from '../reducers/application.js'; import { colors, @@ -75,6 +75,7 @@ type StateFromProps = { downloadingImportData: boolean, launcherMsg: LauncherMsg, flipperRating: number | null, + share: ShareType | null | undefined, }; const VersionText = styled(Text)({ @@ -115,20 +116,36 @@ const Importing = styled(FlexRow)({ marginLeft: 10, }); +function statusMessageComponent( + downloadingImportData: boolean, + statusComponent?: React.ReactElement | undefined, +) { + if (downloadingImportData) { + return ( + + +  Importing data... + + ); + } + if (statusComponent) { + return statusComponent; + } + return; +} + type Props = OwnProps & DispatchFromProps & StateFromProps; -class TitleBar extends React.Component { +class TitleBar extends React.Component { render() { + const {share} = this.props; return ( - {this.props.downloadingImportData && ( - - -  Importing data... - + {statusMessageComponent( + this.props.downloadingImportData, + share != null ? share.statusComponent : undefined, )} - {config.showFlipperRating ? ( ( downloadingImportData, launcherMsg, flipperRating, + share, }, }) => ({ windowIsFocused, @@ -196,6 +214,7 @@ export default connect( downloadingImportData, launcherMsg, flipperRating, + share, }), { setActiveSheet, diff --git a/src/reducers/application.js b/src/reducers/application.js index 662e4e293..78c239353 100644 --- a/src/reducers/application.js +++ b/src/reducers/application.js @@ -7,7 +7,8 @@ import {remote} from 'electron'; import uuidv1 from 'uuid/v1'; - +import {type Element as ReactElement} from 'react'; +import CancellableExportStatus from '../chrome/CancellableExportStatus'; export const ACTIVE_SHEET_PLUGIN_SHEET: 'PLUGIN_SHEET' = 'PLUGIN_SHEET'; export const ACTIVE_SHEET_BUG_REPORTER: 'BUG_REPORTER' = 'BUG_REPORTER'; export const ACTIVE_SHEET_PLUGIN_DEBUGGER: 'PLUGIN_DEBUGGER' = @@ -18,6 +19,9 @@ export const ACTIVE_SHEET_SHARE_DATA: 'SHARE_DATA' = 'SHARE_DATA'; export const ACTIVE_SHEET_SIGN_IN: 'SIGN_IN' = 'SIGN_IN'; 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 type ActiveSheet = | typeof ACTIVE_SHEET_PLUGIN_SHEET @@ -38,12 +42,12 @@ export type ServerPorts = { secure: number, }; -export type ShareType = - | { - type: 'file', - file: string, - } - | {type: 'link'}; +type SubShareType = {type: 'file', file: string} | {type: 'link'}; + +export type ShareType = { + statusComponent?: ReactElement, + ...SubShareType, +}; export type State = { leftSidebarVisible: boolean, @@ -102,6 +106,13 @@ export type Action = payload: { rating: number, }, + } + | { + type: typeof UNSET_SHARE, + } + | { + type: typeof SET_EXPORT_STATUS_MESSAGE, + payload: ReactElement, }; const initialState: () => State = () => ({ @@ -179,6 +190,18 @@ export default function reducer(state: State, action: Action): State { ...state, flipperRating: action.payload.rating, }; + } else if (action.type === 'SET_EXPORT_STATUS_MESSAGE') { + if (state.share) { + const {share} = state; + return { + ...state, + //$FlowFixMe: T48110490, its not able to understand for which case it needs to apply the changes + share: {...share, statusComponent: action.payload}, + }; + } + return state; + } else if (action.type === 'UNSET_SHARE') { + return {...state, share: null}; } else { return state; } @@ -192,6 +215,17 @@ export const toggleAction = ( payload, }); +export const unsetShare = (): Action => ({ + type: UNSET_SHARE, +}); + +export const setExportStatusComponent = ( + payload: ReactElement, +): Action => ({ + type: SET_EXPORT_STATUS_MESSAGE, + payload, +}); + export const setSelectPluginsToExportActiveSheet = ( payload: ShareType, ): Action => ({