diff --git a/src/App.js b/src/App.js index c30e127a9..dd774db20 100644 --- a/src/App.js +++ b/src/App.js @@ -13,6 +13,7 @@ import TitleBar from './chrome/TitleBar.js'; import MainSidebar from './chrome/MainSidebar.js'; import BugReporterDialog from './chrome/BugReporterDialog.js'; import ErrorBar from './chrome/ErrorBar.js'; +import ShareSheet from './chrome/ShareSheet.js'; import PluginContainer from './PluginContainer.js'; import Sheet from './chrome/Sheet.js'; import {ipcRenderer, remote} from 'electron'; @@ -20,6 +21,7 @@ import PluginDebugger from './chrome/PluginDebugger.js'; import { ACTIVE_SHEET_BUG_REPORTER, ACTIVE_SHEET_PLUGIN_DEBUGGER, + ACTIVE_SHEET_SHARE_DATA, } from './reducers/application.js'; import type {Logger} from './fb-interfaces/Logger.js'; @@ -68,6 +70,8 @@ export class App extends React.Component { ); } else if (this.props.activeSheet === ACTIVE_SHEET_PLUGIN_DEBUGGER) { return ; + } else if (this.props.activeSheet === ACTIVE_SHEET_SHARE_DATA) { + return ; } else { // contents are added via React.Portal return null; diff --git a/src/MenuBar.js b/src/MenuBar.js index e526db7c9..a43120af4 100644 --- a/src/MenuBar.js +++ b/src/MenuBar.js @@ -8,11 +8,11 @@ import type {FlipperPlugin, FlipperDevicePlugin} from './plugin.js'; import { exportStoreToFile, - exportStore, importFileToStore, IMPORT_FLIPPER_TRACE_EVENT, EXPORT_FLIPPER_TRACE_EVENT, } from './utils/exportData.js'; +import {setActiveSheet, ACTIVE_SHEET_SHARE_DATA} from './reducers/application'; import type {Store} from './reducers/'; import electron from 'electron'; import {GK} from 'flipper'; @@ -20,7 +20,6 @@ import {remote} from 'electron'; const {dialog} = remote; import os from 'os'; import path from 'path'; -import {shareFlipperData} from './fb-stubs/user'; import { reportPlatformFailures, tryCatchReportPlatformFailures, @@ -374,7 +373,7 @@ function getTemplate( label: 'Sharable Link', accelerator: 'CommandOrControl+Shift+E', click: async function(item: Object, focusedWindow: Object) { - shareFlipperData(await exportStore(store)); + store.dispatch(setActiveSheet(ACTIVE_SHEET_SHARE_DATA)); }, }, ], diff --git a/src/chrome/ShareSheet.js b/src/chrome/ShareSheet.js new file mode 100644 index 000000000..376ce9d44 --- /dev/null +++ b/src/chrome/ShareSheet.js @@ -0,0 +1,148 @@ +/** + * 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 { + FlexColumn, + Button, + styled, + colors, + Text, + LoadingIndicator, + Component, + FlexRow, + Spacer, + Input, +} from 'flipper'; +import {shareFlipperData} from '../fb-stubs/user'; +import {exportStore} from '../utils/exportData.js'; +import PropTypes from 'prop-types'; +import {clipboard} from 'electron'; + +const Container = styled(FlexColumn)({ + padding: 20, + width: 500, +}); + +const Center = styled(FlexColumn)({ + alignItems: 'center', + paddingTop: 50, + paddingBottom: 50, +}); + +const Uploading = styled(Text)({ + marginTop: 15, +}); + +const ErrorMessage = styled(Text)({ + display: 'block', + marginTop: 6, + wordBreak: 'break-all', + whiteSpace: 'pre-line', + lineHeight: 1.35, +}); + +const Copy = styled(Input)({ + marginRight: 0, + marginBottom: 15, +}); + +const Title = styled(Text)({ + marginBottom: 6, +}); + +const InfoText = styled(Text)({ + lineHeight: 1.35, + marginBottom: 15, +}); + +type Props = { + onHide: () => mixed, +}; +type State = { + result: + | ?{ + error_class: string, + error: string, + } + | { + flipperUrl: string, + }, +}; + +export default class ShareSheet extends Component { + static contextTypes = { + store: PropTypes.object.isRequired, + }; + + state = { + result: null, + }; + + async componentDidMount() { + const storeData = await exportStore(this.context.store); + const result = await shareFlipperData(storeData); + this.setState({result}); + + if (result.flipperUrl) { + clipboard.writeText(String(result.flipperUrl)); + new window.Notification('Sharable Flipper trace created', { + body: 'URL copied to clipboard', + requireInteraction: true, + }); + } + } + + render() { + return ( + + {this.state.result ? ( + <> + + {this.state.result.flipperUrl ? ( + <> + Data Upload Successful + + Flipper's data was successfully uploaded. This URL can be + used to share with other Flipper users. Opening it will + import the data from your trace. + + + + When sharing your Flipper link, consider that the captured + data might contain sensitve information like access tokens + used in network requests. + + + ) : ( + <> + {this.state.result.error_class || 'Error'} + + {this.state.result.error || + 'The data could not be uploaded'} + + + )} + + + + + + + ) : ( +
+ + + Uploading Flipper trace... + +
+ )} +
+ ); + } +} diff --git a/src/fb-stubs/user.js b/src/fb-stubs/user.js index 3bfff668e..e106ef484 100644 --- a/src/fb-stubs/user.js +++ b/src/fb-stubs/user.js @@ -17,7 +17,23 @@ export function logoutUser(): Promise { return Promise.reject(); } -export async function shareFlipperData(trace: string) { +export async function shareFlipperData( + trace: string, +): Promise< + | { + id: string, + os: 'string', + deviceType: string, + plugins: string[], + fileUrl: string, + flipperUrl: string, + } + | { + error: string, + error_class: string, + stacktrace: string, + }, +> { new window.Notification('Feature not implemented'); return Promise.reject(); } diff --git a/src/reducers/application.js b/src/reducers/application.js index 4a551e45f..892930663 100644 --- a/src/reducers/application.js +++ b/src/reducers/application.js @@ -12,11 +12,13 @@ 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' = 'PLUGIN_DEBUGGER'; +export const ACTIVE_SHEET_SHARE_DATA: 'SHARE_DATA' = 'SHARE_DATA'; export type ActiveSheet = | typeof ACTIVE_SHEET_PLUGIN_SHEET | typeof ACTIVE_SHEET_BUG_REPORTER | typeof ACTIVE_SHEET_PLUGIN_DEBUGGER + | typeof ACTIVE_SHEET_SHARE_DATA | null; export type State = {