Decouple open/save dialogs, reload, shouldUseDarkColors from Electron

Summary: Per title. Less imports from Electron.

Reviewed By: timur-valiev, aigoncharov

Differential Revision: D31923504

fbshipit-source-id: dc7557cf7c88c0c8168ba22f7dca7b3e2d339a09
This commit is contained in:
Michel Weststrate
2021-10-26 12:06:18 -07:00
committed by Facebook GitHub Bot
parent d5e4b0c360
commit 9763af4c96
12 changed files with 106 additions and 103 deletions

View File

@@ -37,6 +37,7 @@ import React from 'react';
import ChangelogSheet from './chrome/ChangelogSheet'; import ChangelogSheet from './chrome/ChangelogSheet';
import PluginManager from './chrome/plugin-manager/PluginManager'; import PluginManager from './chrome/plugin-manager/PluginManager';
import SettingsSheet from './chrome/SettingsSheet'; import SettingsSheet from './chrome/SettingsSheet';
import reloadFlipper from './utils/reloadFlipper';
export type DefaultKeyboardAction = keyof typeof _buildInMenuEntries; export type DefaultKeyboardAction = keyof typeof _buildInMenuEntries;
export type TopLevelMenu = 'Edit' | 'View' | 'Window' | 'Help'; export type TopLevelMenu = 'Edit' | 'View' | 'Window' | 'Help';
@@ -294,11 +295,9 @@ function getTemplate(
{ {
label: 'Reload', label: 'Reload',
accelerator: 'CmdOrCtrl+R', accelerator: 'CmdOrCtrl+R',
click: function (_, focusedWindow: electron.BrowserWindow | undefined) { click: function (_, _focusedWindow: electron.BrowserWindow | undefined) {
if (focusedWindow) { logger.track('usage', 'reload');
logger.track('usage', 'reload'); reloadFlipper();
focusedWindow.reload();
}
}, },
}, },
{ {

View File

@@ -10,6 +10,7 @@
import {NotificationEvents} from './dispatcher/notifications'; import {NotificationEvents} from './dispatcher/notifications';
import {PluginNotification} from './reducers/notifications'; import {PluginNotification} from './reducers/notifications';
import type {NotificationConstructorOptions} from 'electron'; import type {NotificationConstructorOptions} from 'electron';
import {FlipperLib} from 'flipper-plugin';
// Events that are emitted from the main.ts ovr the IPC process bridge in Electron // Events that are emitted from the main.ts ovr the IPC process bridge in Electron
type MainProcessEvents = { type MainProcessEvents = {
@@ -44,7 +45,9 @@ type ChildProcessEvents = {
export interface RenderHost { export interface RenderHost {
readonly processId: number; readonly processId: number;
readTextFromClipboard(): string | undefined; readTextFromClipboard(): string | undefined;
selectDirectory?(defaultPath?: string): Promise<string | undefined>; showSaveDialog?: FlipperLib['showSaveDialog'];
showOpenDialog?: FlipperLib['showOpenDialog'];
showSelectDirectoryDialog?(defaultPath?: string): Promise<string | undefined>;
registerShortcut(shortCut: string, callback: () => void): void; registerShortcut(shortCut: string, callback: () => void): void;
hasFocus(): boolean; hasFocus(): boolean;
onIpcEvent<Event extends keyof MainProcessEvents>( onIpcEvent<Event extends keyof MainProcessEvents>(
@@ -55,6 +58,7 @@ export interface RenderHost {
event: Event, event: Event,
...args: ChildProcessEvents[Event] ...args: ChildProcessEvents[Event]
): void; ): void;
shouldUseDarkColors(): boolean;
} }
let renderHostInstance: RenderHost | undefined; let renderHostInstance: RenderHost | undefined;
@@ -82,5 +86,8 @@ if (process.env.NODE_ENV === 'test') {
}, },
onIpcEvent() {}, onIpcEvent() {},
sendIpcEvent() {}, sendIpcEvent() {},
shouldUseDarkColors() {
return false;
},
}); });
} }

View File

@@ -99,11 +99,11 @@ export function FilePathConfigField(props: {
.catch((_) => setIsValid(false)); .catch((_) => setIsValid(false));
}} }}
/> />
{renderHost.selectDirectory && ( {renderHost.showSelectDirectoryDialog && (
<FlexColumn <FlexColumn
onClick={() => { onClick={() => {
renderHost renderHost
.selectDirectory?.() .showSelectDirectoryDialog?.()
.then((path) => { .then((path) => {
if (path) { if (path) {
setValue(path); setValue(path);

View File

@@ -205,7 +205,7 @@ function init() {
setPersistor(persistor); setPersistor(persistor);
initializeFlipperLibImplementation(store, logger); initializeFlipperLibImplementation(getRenderHostInstance(), store, logger);
_setGlobalInteractionReporter((r) => { _setGlobalInteractionReporter((r) => {
logger.track('usage', 'interaction', r); logger.track('usage', 'interaction', r);
if (!isProduction()) { if (!isProduction()) {
@@ -230,26 +230,20 @@ function init() {
sideEffect( sideEffect(
store, store,
{name: 'loadTheme', fireImmediately: false, throttleMs: 500}, {name: 'loadTheme', fireImmediately: false, throttleMs: 500},
(state) => { (state) => state.settingsState.darkMode,
const theme = state.settingsState.darkMode; (theme) => {
let shouldUseDarkMode = false; let shouldUseDarkMode = false;
if (theme === 'dark') { if (theme === 'dark') {
shouldUseDarkMode = true; shouldUseDarkMode = true;
} else if (theme === 'light') { } else if (theme === 'light') {
shouldUseDarkMode = false; shouldUseDarkMode = false;
} else if (theme === 'system') { } else if (theme === 'system') {
shouldUseDarkMode = remote.nativeTheme.shouldUseDarkColors; shouldUseDarkMode = getRenderHostInstance().shouldUseDarkColors();
} }
return {
shouldUseDarkMode: shouldUseDarkMode,
theme: theme,
};
},
(result) => {
( (
document.getElementById('flipper-theme-import') as HTMLLinkElement document.getElementById('flipper-theme-import') as HTMLLinkElement
).href = `themes/${result.shouldUseDarkMode ? 'dark' : 'light'}.css`; ).href = `themes/${shouldUseDarkMode ? 'dark' : 'light'}.css`;
getRenderHostInstance().sendIpcEvent('setTheme', result.theme); getRenderHostInstance().sendIpcEvent('setTheme', theme);
}, },
); );
} }
@@ -272,10 +266,21 @@ function initializeFlipperForElectron() {
readTextFromClipboard() { readTextFromClipboard() {
return clipboard.readText(); return clipboard.readText();
}, },
selectDirectory(defaultPath = path.resolve('/')) { async showSaveDialog(options) {
return (await remote.dialog.showSaveDialog(options))?.filePath;
},
async showOpenDialog({filter, defaultPath}) {
const result = await remote.dialog.showOpenDialog({
defaultPath,
properties: ['openFile'],
filters: filter ? [filter] : undefined,
});
return result.filePaths?.[0];
},
showSelectDirectoryDialog(defaultPath = path.resolve('/')) {
return remote.dialog return remote.dialog
.showOpenDialog({ .showOpenDialog({
properties: ['openDirectory', 'showHiddenFiles'], properties: ['openDirectory'],
defaultPath, defaultPath,
}) })
.then((result: SaveDialogReturnValue & {filePaths: string[]}) => { .then((result: SaveDialogReturnValue & {filePaths: string[]}) => {
@@ -305,5 +310,8 @@ function initializeFlipperForElectron() {
sendIpcEvent(event, ...args: any[]) { sendIpcEvent(event, ...args: any[]) {
ipcRenderer.send(event, ...args); ipcRenderer.send(event, ...args);
}, },
shouldUseDarkColors() {
return remote.nativeTheme.shouldUseDarkColors;
},
}); });
} }

View File

@@ -28,6 +28,7 @@ import ArchivedDevice from '../devices/ArchivedDevice';
import {ClientQuery, DeviceOS} from 'flipper-common'; import {ClientQuery, DeviceOS} from 'flipper-common';
import {TestDevice} from './TestDevice'; import {TestDevice} from './TestDevice';
import {createFlipperServerMock} from './createFlipperServerMock'; import {createFlipperServerMock} from './createFlipperServerMock';
import {getRenderHostInstance} from '../RenderHost';
export interface AppOptions { export interface AppOptions {
plugins?: PluginDefinition[]; plugins?: PluginDefinition[];
@@ -89,7 +90,11 @@ export default class MockFlipper {
this.unsubscribePluginManager = pluginManager(this._store, this._logger, { this.unsubscribePluginManager = pluginManager(this._store, this._logger, {
runSideEffectsSynchronously: true, runSideEffectsSynchronously: true,
}); });
initializeFlipperLibImplementation(this._store, this._logger); initializeFlipperLibImplementation(
getRenderHostInstance(),
this._store,
this._logger,
);
this._store.dispatch(registerPlugins(plugins ?? [])); this._store.dispatch(registerPlugins(plugins ?? []));
this._store.dispatch({ this._store.dispatch({
type: 'SET_FLIPPER_SERVER', type: 'SET_FLIPPER_SERVER',

View File

@@ -11,12 +11,11 @@ import React, {useState} from 'react';
import FlexRow from './FlexRow'; import FlexRow from './FlexRow';
import Glyph from './Glyph'; import Glyph from './Glyph';
import Input from './Input'; import Input from './Input';
import electron from 'electron';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import {colors} from './colors'; import {colors} from './colors';
import Electron from 'electron';
import fs from 'fs'; import fs from 'fs';
import {Tooltip} from '..'; import {Tooltip} from '..';
import {getFlipperLib} from 'flipper-plugin';
const CenteredGlyph = styled(Glyph)({ const CenteredGlyph = styled(Glyph)({
margin: 'auto', margin: 'auto',
@@ -41,40 +40,25 @@ const FileInputBox = styled(Input)<{isValid: boolean}>(({isValid}) => ({
}, },
})); }));
function strToArr<T extends string>(item: T): T[] {
return [item];
}
export interface Props { export interface Props {
onPathChanged: (evtArgs: {path: string; isValid: boolean}) => void; onPathChanged: (evtArgs: {path: string; isValid: boolean}) => void;
placeholderText: string; placeholderText: string;
defaultPath: string; defaultPath: string;
showHiddenFiles: boolean;
} }
const defaultProps: Props = { const defaultProps: Props = {
onPathChanged: (_) => {}, onPathChanged: (_) => {},
placeholderText: '', placeholderText: '',
defaultPath: '/', defaultPath: '/',
showHiddenFiles: false,
}; };
export default function FileSelector({ export default function FileSelector({
onPathChanged, onPathChanged,
placeholderText, placeholderText,
defaultPath, defaultPath,
showHiddenFiles,
}: Props) { }: Props) {
const [value, setValue] = useState(''); const [value, setValue] = useState('');
const [isValid, setIsValid] = useState(false); const [isValid, setIsValid] = useState(false);
const options: Electron.OpenDialogOptions = {
properties: [
'openFile',
...(showHiddenFiles ? strToArr('showHiddenFiles') : []),
],
defaultPath,
};
const onChange = (path: string) => { const onChange = (path: string) => {
setValue(path); setValue(path);
let isNewPathValid = false; let isNewPathValid = false;
@@ -103,11 +87,11 @@ export default function FileSelector({
/> />
<GlyphContainer <GlyphContainer
onClick={() => onClick={() =>
electron.remote.dialog getFlipperLib()
.showOpenDialog(options) .showOpenDialog?.({defaultPath})
.then((result: electron.OpenDialogReturnValue) => { .then((path) => {
if (result && !result.canceled && result.filePaths.length) { if (path) {
onChange(result.filePaths[0]); onChange(path);
} }
}) })
}> }>

View File

@@ -10,7 +10,6 @@
import * as React from 'react'; 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 {getLogger} from 'flipper-common'; import {getLogger} from 'flipper-common';
import {Store, MiddlewareAPI} from '../reducers'; import {Store, MiddlewareAPI} from '../reducers';
import {DeviceExport} from '../devices/BaseDevice'; import {DeviceExport} from '../devices/BaseDevice';
@@ -24,7 +23,6 @@ import {default as BaseDevice} from '../devices/BaseDevice';
import {default as ArchivedDevice} from '../devices/ArchivedDevice'; import {default as ArchivedDevice} from '../devices/ArchivedDevice';
import fs from 'fs'; import fs from 'fs';
import {v4 as uuidv4} from 'uuid'; import {v4 as uuidv4} from 'uuid';
import {remote, OpenDialogOptions} from 'electron';
import {readCurrentRevision} from './packageMetadata'; import {readCurrentRevision} from './packageMetadata';
import {tryCatchReportPlatformFailures} from 'flipper-common'; import {tryCatchReportPlatformFailures} from 'flipper-common';
import {promisify} from 'util'; import {promisify} from 'util';
@@ -45,6 +43,7 @@ import {ClientQuery} from 'flipper-common';
import ShareSheetExportUrl from '../chrome/ShareSheetExportUrl'; import ShareSheetExportUrl from '../chrome/ShareSheetExportUrl';
import ShareSheetExportFile from '../chrome/ShareSheetExportFile'; import ShareSheetExportFile from '../chrome/ShareSheetExportFile';
import ExportDataPluginSheet from '../chrome/ExportDataPluginSheet'; import ExportDataPluginSheet from '../chrome/ExportDataPluginSheet';
import {getRenderHostInstance} from '../RenderHost';
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';
@@ -602,30 +601,24 @@ export const importFileToStore = (file: string, store: Store) => {
}; };
export function showOpenDialog(store: Store) { export function showOpenDialog(store: Store) {
const options: OpenDialogOptions = { return getRenderHostInstance()
properties: ['openFile'], .showOpenDialog?.({
filters: [{extensions: ['flipper', 'json', 'txt'], name: 'Flipper files'}], filter: {extensions: ['flipper', 'json', 'txt'], name: 'Flipper files'},
}; })
remote.dialog.showOpenDialog(options).then((result) => { .then((filePath) => {
const filePaths = result.filePaths; if (filePath) {
if (filePaths.length > 0) { tryCatchReportPlatformFailures(() => {
tryCatchReportPlatformFailures(() => { importFileToStore(filePath, store);
importFileToStore(filePaths[0], store); }, `${IMPORT_FLIPPER_TRACE_EVENT}:UI`);
}, `${IMPORT_FLIPPER_TRACE_EVENT}:UI`); }
} });
});
} }
export async function startFileExport(dispatch: Store['dispatch']) { export async function startFileExport(dispatch: Store['dispatch']) {
const result = await electron.remote.dialog.showSaveDialog( const file = await getRenderHostInstance().showSaveDialog?.({
// @ts-ignore This appears to work but isn't allowed by the types title: 'FlipperExport',
null, defaultPath: path.join(os.homedir(), 'FlipperExport.flipper'),
{ });
title: 'FlipperExport',
defaultPath: path.join(os.homedir(), 'FlipperExport.flipper'),
},
);
const file = result.filePath;
if (!file) { if (!file) {
return; return;
} }

View File

@@ -18,8 +18,10 @@ import constants from '../fb-stubs/constants';
import {addNotification} from '../reducers/notifications'; import {addNotification} from '../reducers/notifications';
import {deconstructPluginKey} from 'flipper-common'; import {deconstructPluginKey} from 'flipper-common';
import {DetailSidebarImpl} from '../sandy-chrome/DetailSidebarImpl'; import {DetailSidebarImpl} from '../sandy-chrome/DetailSidebarImpl';
import {RenderHost} from '../RenderHost';
export function initializeFlipperLibImplementation( export function initializeFlipperLibImplementation(
renderHost: RenderHost,
store: Store, store: Store,
logger: Logger, logger: Logger,
) { ) {
@@ -64,5 +66,8 @@ export function initializeFlipperLibImplementation(
); );
}, },
DetailsSidebarImplementation: DetailSidebarImpl, DetailsSidebarImplementation: DetailSidebarImpl,
showSaveDialog: renderHost.showSaveDialog,
showOpenDialog: renderHost.showOpenDialog,
showSelectDirectoryDialog: renderHost.showSelectDirectoryDialog,
}); });
} }

View File

@@ -7,5 +7,4 @@
* @format * @format
*/ */
import {remote} from 'electron'; export default () => window.location.reload();
export default () => remote.getCurrentWindow().reload();

View File

@@ -8,7 +8,7 @@
*/ */
import {useStore} from './useStore'; import {useStore} from './useStore';
import {remote} from 'electron'; import {getRenderHostInstance} from '../RenderHost';
/** /**
* This hook returns whether dark mode is currently being used. * This hook returns whether dark mode is currently being used.
@@ -16,15 +16,13 @@ import {remote} from 'electron';
* which will provide colors that reflect the theme * which will provide colors that reflect the theme
*/ */
export function useIsDarkMode(): boolean { export function useIsDarkMode(): boolean {
return useStore((state) => { const darkMode = useStore((state) => state.settingsState.darkMode);
const darkMode = state.settingsState.darkMode; if (darkMode === 'dark') {
if (darkMode === 'dark') { return true;
return true; } else if (darkMode === 'light') {
} else if (darkMode === 'light') {
return false;
} else if (darkMode === 'system') {
return remote.nativeTheme.shouldUseDarkColors;
}
return false; return false;
}); } else if (darkMode === 'system') {
return getRenderHostInstance().shouldUseDarkColors();
}
return false;
} }

View File

@@ -35,6 +35,19 @@ export interface FlipperLib {
DetailsSidebarImplementation?( DetailsSidebarImplementation?(
props: DetailSidebarProps, props: DetailSidebarProps,
): React.ReactElement | null; ): React.ReactElement | null;
showSaveDialog?(options: {
defaultPath?: string;
message?: string;
title?: string;
}): Promise<string | undefined>;
showOpenDialog?(options: {
defaultPath?: string;
filter?: {
extensions: string[];
name: string;
};
}): Promise<string | undefined>;
showSelectDirectoryDialog?(defaultPath?: string): Promise<string | undefined>;
} }
export let flipperLibInstance: FlipperLib | undefined; export let flipperLibInstance: FlipperLib | undefined;

View File

@@ -10,7 +10,7 @@
import fs from 'fs'; import fs from 'fs';
// eslint-disable-next-line // eslint-disable-next-line
import electron, {OpenDialogOptions, remote} from 'electron'; import electron, {OpenDialogOptions, remote} from 'electron';
import {Atom, DataTableManager} from 'flipper-plugin'; import {Atom, DataTableManager, getFlipperLib} from 'flipper-plugin';
import {createContext} from 'react'; import {createContext} from 'react';
import {Header, Request} from '../types'; import {Header, Request} from '../types';
import {message} from 'antd'; import {message} from 'antd';
@@ -137,16 +137,13 @@ export function createNetworkManager(
informClientMockChange(routes.get()); informClientMockChange(routes.get());
}, },
importRoutes() { importRoutes() {
const options: OpenDialogOptions = { getFlipperLib()
properties: ['openFile'], .showOpenDialog?.({
filters: [{extensions: ['json'], name: 'Flipper Route Files'}], filter: {extensions: ['json'], name: 'Flipper Route Files'},
}; })
remote.dialog .then((filePath) => {
.showOpenDialog(options) if (filePath) {
.then((result) => { fs.readFile(filePath, 'utf8', (err, data) => {
const filePaths = result.filePaths;
if (filePaths.length > 0) {
fs.readFile(filePaths[0], 'utf8', (err, data) => {
if (err) { if (err) {
message.error('Unable to import file'); message.error('Unable to import file');
return; return;
@@ -177,17 +174,12 @@ export function createNetworkManager(
); );
}, },
exportRoutes() { exportRoutes() {
remote.dialog getFlipperLib()
.showSaveDialog( .showSaveDialog?.({
// @ts-ignore This appears to work but isn't allowed by the types title: 'Export Routes',
null, defaultPath: 'NetworkPluginRoutesExport.json',
{ })
title: 'Export Routes', .then((file) => {
defaultPath: 'NetworkPluginRoutesExport.json',
},
)
.then((result: electron.SaveDialogReturnValue) => {
const file = result.filePath;
if (!file) { if (!file) {
return; return;
} }