diff --git a/desktop/app/src/electron/initializeElectron.tsx b/desktop/app/src/electron/initializeElectron.tsx index 91eb4c4f2..1b32a2373 100644 --- a/desktop/app/src/electron/initializeElectron.tsx +++ b/desktop/app/src/electron/initializeElectron.tsx @@ -111,6 +111,18 @@ export function initializeElectron() { return undefined; }); }, + async exportFile(data, {defaultPath} = {}) { + const {filePath} = await remote.dialog.showSaveDialog({ + defaultPath, + }); + + if (!filePath) { + return; + } + + await fs.promises.writeFile(filePath, data); + return filePath; + }, openLink(url: string) { shell.openExternal(url); }, diff --git a/desktop/flipper-plugin/src/plugin/FlipperLib.tsx b/desktop/flipper-plugin/src/plugin/FlipperLib.tsx index 062bc967f..5b9c0bb0f 100644 --- a/desktop/flipper-plugin/src/plugin/FlipperLib.tsx +++ b/desktop/flipper-plugin/src/plugin/FlipperLib.tsx @@ -35,11 +35,6 @@ export interface FlipperLib { DetailsSidebarImplementation?( props: DetailSidebarProps, ): React.ReactElement | null; - showSaveDialog?(options: { - defaultPath?: string; - message?: string; - title?: string; - }): Promise; showOpenDialog?(options: { defaultPath?: string; filter?: { @@ -47,7 +42,12 @@ export interface FlipperLib { name: string; }; }): Promise; - showSelectDirectoryDialog?(defaultPath?: string): Promise; + exportFile( + data: string, + options?: { + defaultPath?: string; + }, + ): Promise; paths: { homePath: string; appPath: string; diff --git a/desktop/flipper-plugin/src/test-utils/test-utils.tsx b/desktop/flipper-plugin/src/test-utils/test-utils.tsx index 85e1e410a..afcf4732d 100644 --- a/desktop/flipper-plugin/src/test-utils/test-utils.tsx +++ b/desktop/flipper-plugin/src/test-utils/test-utils.tsx @@ -379,6 +379,7 @@ export function createMockFlipperLib(options?: StartPluginOptions): FlipperLib { writeTextToClipboard: jest.fn(), openLink: jest.fn(), showNotification: jest.fn(), + exportFile: jest.fn(), paths: { appPath: process.cwd(), homePath: `/dev/null`, diff --git a/desktop/flipper-ui-core/src/RenderHost.tsx b/desktop/flipper-ui-core/src/RenderHost.tsx index 90a187b9d..86770ed29 100644 --- a/desktop/flipper-ui-core/src/RenderHost.tsx +++ b/desktop/flipper-ui-core/src/RenderHost.tsx @@ -57,9 +57,18 @@ export interface RenderHost { readonly isProduction: boolean; readTextFromClipboard(): string | undefined; writeTextToClipboard(text: string): void; - showSaveDialog?: FlipperLib['showSaveDialog']; + /** + * @deprecated + * TODO: Remove in favor of "exportFile" + */ + showSaveDialog?(options: { + defaultPath?: string; + message?: string; + title?: string; + }): Promise; showOpenDialog?: FlipperLib['showOpenDialog']; showSelectDirectoryDialog?(defaultPath?: string): Promise; + exportFile: FlipperLib['exportFile']; /** * @returns * A callback to unregister the shortcut @@ -97,6 +106,9 @@ if (process.env.NODE_ENV === 'test') { return ''; }, writeTextToClipboard() {}, + async exportFile() { + return undefined; + }, registerShortcut() { return () => undefined; }, diff --git a/desktop/flipper-ui-core/src/ui/components/FileSelector.tsx b/desktop/flipper-ui-core/src/ui/components/FileSelector.tsx index 3f593b646..3989e4be6 100644 --- a/desktop/flipper-ui-core/src/ui/components/FileSelector.tsx +++ b/desktop/flipper-ui-core/src/ui/components/FileSelector.tsx @@ -52,6 +52,8 @@ const defaultProps: Props = { defaultPath: '/', }; +// TODO: Should we render null in browsers for FileSelector? +// Do we even need it after decapitation? Every plugin should be using FlipperLib.exportFile which shows a save dialog every time. export default function FileSelector({ onPathChanged, placeholderText, diff --git a/desktop/flipper-ui-core/src/utils/flipperLibImplementation.tsx b/desktop/flipper-ui-core/src/utils/flipperLibImplementation.tsx index fe28f02b7..89b284a78 100644 --- a/desktop/flipper-ui-core/src/utils/flipperLibImplementation.tsx +++ b/desktop/flipper-ui-core/src/utils/flipperLibImplementation.tsx @@ -60,9 +60,8 @@ export function initializeFlipperLibImplementation( ); }, DetailsSidebarImplementation: DetailSidebarImpl, - showSaveDialog: renderHost.showSaveDialog, + exportFile: renderHost.exportFile, showOpenDialog: renderHost.showOpenDialog, - showSelectDirectoryDialog: renderHost.showSelectDirectoryDialog, paths: { appPath: renderHost.paths.appPath, homePath: renderHost.paths.homePath, diff --git a/desktop/plugins/public/network/request-mocking/NetworkRouteManager.tsx b/desktop/plugins/public/network/request-mocking/NetworkRouteManager.tsx index 1b77b1e6d..1dfcb443a 100644 --- a/desktop/plugins/public/network/request-mocking/NetworkRouteManager.tsx +++ b/desktop/plugins/public/network/request-mocking/NetworkRouteManager.tsx @@ -173,27 +173,9 @@ export function createNetworkManager( }, exportRoutes() { getFlipperLib() - .showSaveDialog?.({ - title: 'Export Routes', + .exportFile(JSON.stringify(Object.values(routes.get()), null, 2), { defaultPath: 'NetworkPluginRoutesExport.json', }) - .then((file) => { - if (!file) { - return; - } - fs.writeFile( - file, - JSON.stringify(Object.values(routes.get()), null, 2), - 'utf8', - (err) => { - if (err) { - message.error('Failed to store mock routes: ' + err); - } else { - message.info('Successfully exported mock routes'); - } - }, - ); - }) .catch((e) => console.error('[network] exportRoutes saving failed:', e), );