From 2c7bc0a952bb568f62f72e24ad5152b6e119ac0c Mon Sep 17 00:00:00 2001 From: Andrey Goncharov Date: Thu, 18 Nov 2021 09:13:48 -0800 Subject: [PATCH] Remove fs.write from plugins Summary: 1. Remove "fs.writeFile" from plugins 2. Remove "showSaveDialog" from "FlipperLib" 3. Add "exportFile" to "FlipperLib" and "RenderHost" As we are going to use Flipper in a browser as well, instead of providing low-level abstraction like "showSaveDialog", we should provide more high-level ones like "exportFile". In browsers it does not make too much sense to expose "showSaveDialog" as there is not way to use the returned file path to write to it. In the end, "exportFile" is going to trigger a file download for browsers and show the save dialog and write to the returned file path for Electron. Reviewed By: mweststrate Differential Revision: D32492149 fbshipit-source-id: 0673119bdb7670a5872f5982c7d82dfc44eb7906 --- .../app/src/electron/initializeElectron.tsx | 12 +++++++++++ .../flipper-plugin/src/plugin/FlipperLib.tsx | 12 +++++------ .../src/test-utils/test-utils.tsx | 1 + desktop/flipper-ui-core/src/RenderHost.tsx | 14 ++++++++++++- .../src/ui/components/FileSelector.tsx | 2 ++ .../src/utils/flipperLibImplementation.tsx | 3 +-- .../request-mocking/NetworkRouteManager.tsx | 20 +------------------ 7 files changed, 36 insertions(+), 28 deletions(-) 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), );