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:
committed by
Facebook GitHub Bot
parent
d5e4b0c360
commit
9763af4c96
@@ -37,6 +37,7 @@ import React from 'react';
|
||||
import ChangelogSheet from './chrome/ChangelogSheet';
|
||||
import PluginManager from './chrome/plugin-manager/PluginManager';
|
||||
import SettingsSheet from './chrome/SettingsSheet';
|
||||
import reloadFlipper from './utils/reloadFlipper';
|
||||
|
||||
export type DefaultKeyboardAction = keyof typeof _buildInMenuEntries;
|
||||
export type TopLevelMenu = 'Edit' | 'View' | 'Window' | 'Help';
|
||||
@@ -294,11 +295,9 @@ function getTemplate(
|
||||
{
|
||||
label: 'Reload',
|
||||
accelerator: 'CmdOrCtrl+R',
|
||||
click: function (_, focusedWindow: electron.BrowserWindow | undefined) {
|
||||
if (focusedWindow) {
|
||||
logger.track('usage', 'reload');
|
||||
focusedWindow.reload();
|
||||
}
|
||||
click: function (_, _focusedWindow: electron.BrowserWindow | undefined) {
|
||||
logger.track('usage', 'reload');
|
||||
reloadFlipper();
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import {NotificationEvents} from './dispatcher/notifications';
|
||||
import {PluginNotification} from './reducers/notifications';
|
||||
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
|
||||
type MainProcessEvents = {
|
||||
@@ -44,7 +45,9 @@ type ChildProcessEvents = {
|
||||
export interface RenderHost {
|
||||
readonly processId: number;
|
||||
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;
|
||||
hasFocus(): boolean;
|
||||
onIpcEvent<Event extends keyof MainProcessEvents>(
|
||||
@@ -55,6 +58,7 @@ export interface RenderHost {
|
||||
event: Event,
|
||||
...args: ChildProcessEvents[Event]
|
||||
): void;
|
||||
shouldUseDarkColors(): boolean;
|
||||
}
|
||||
|
||||
let renderHostInstance: RenderHost | undefined;
|
||||
@@ -82,5 +86,8 @@ if (process.env.NODE_ENV === 'test') {
|
||||
},
|
||||
onIpcEvent() {},
|
||||
sendIpcEvent() {},
|
||||
shouldUseDarkColors() {
|
||||
return false;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -99,11 +99,11 @@ export function FilePathConfigField(props: {
|
||||
.catch((_) => setIsValid(false));
|
||||
}}
|
||||
/>
|
||||
{renderHost.selectDirectory && (
|
||||
{renderHost.showSelectDirectoryDialog && (
|
||||
<FlexColumn
|
||||
onClick={() => {
|
||||
renderHost
|
||||
.selectDirectory?.()
|
||||
.showSelectDirectoryDialog?.()
|
||||
.then((path) => {
|
||||
if (path) {
|
||||
setValue(path);
|
||||
|
||||
@@ -205,7 +205,7 @@ function init() {
|
||||
|
||||
setPersistor(persistor);
|
||||
|
||||
initializeFlipperLibImplementation(store, logger);
|
||||
initializeFlipperLibImplementation(getRenderHostInstance(), store, logger);
|
||||
_setGlobalInteractionReporter((r) => {
|
||||
logger.track('usage', 'interaction', r);
|
||||
if (!isProduction()) {
|
||||
@@ -230,26 +230,20 @@ function init() {
|
||||
sideEffect(
|
||||
store,
|
||||
{name: 'loadTheme', fireImmediately: false, throttleMs: 500},
|
||||
(state) => {
|
||||
const theme = state.settingsState.darkMode;
|
||||
(state) => state.settingsState.darkMode,
|
||||
(theme) => {
|
||||
let shouldUseDarkMode = false;
|
||||
if (theme === 'dark') {
|
||||
shouldUseDarkMode = true;
|
||||
} else if (theme === 'light') {
|
||||
shouldUseDarkMode = false;
|
||||
} else if (theme === 'system') {
|
||||
shouldUseDarkMode = remote.nativeTheme.shouldUseDarkColors;
|
||||
shouldUseDarkMode = getRenderHostInstance().shouldUseDarkColors();
|
||||
}
|
||||
return {
|
||||
shouldUseDarkMode: shouldUseDarkMode,
|
||||
theme: theme,
|
||||
};
|
||||
},
|
||||
(result) => {
|
||||
(
|
||||
document.getElementById('flipper-theme-import') as HTMLLinkElement
|
||||
).href = `themes/${result.shouldUseDarkMode ? 'dark' : 'light'}.css`;
|
||||
getRenderHostInstance().sendIpcEvent('setTheme', result.theme);
|
||||
).href = `themes/${shouldUseDarkMode ? 'dark' : 'light'}.css`;
|
||||
getRenderHostInstance().sendIpcEvent('setTheme', theme);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -272,10 +266,21 @@ function initializeFlipperForElectron() {
|
||||
readTextFromClipboard() {
|
||||
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
|
||||
.showOpenDialog({
|
||||
properties: ['openDirectory', 'showHiddenFiles'],
|
||||
properties: ['openDirectory'],
|
||||
defaultPath,
|
||||
})
|
||||
.then((result: SaveDialogReturnValue & {filePaths: string[]}) => {
|
||||
@@ -305,5 +310,8 @@ function initializeFlipperForElectron() {
|
||||
sendIpcEvent(event, ...args: any[]) {
|
||||
ipcRenderer.send(event, ...args);
|
||||
},
|
||||
shouldUseDarkColors() {
|
||||
return remote.nativeTheme.shouldUseDarkColors;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import ArchivedDevice from '../devices/ArchivedDevice';
|
||||
import {ClientQuery, DeviceOS} from 'flipper-common';
|
||||
import {TestDevice} from './TestDevice';
|
||||
import {createFlipperServerMock} from './createFlipperServerMock';
|
||||
import {getRenderHostInstance} from '../RenderHost';
|
||||
|
||||
export interface AppOptions {
|
||||
plugins?: PluginDefinition[];
|
||||
@@ -89,7 +90,11 @@ export default class MockFlipper {
|
||||
this.unsubscribePluginManager = pluginManager(this._store, this._logger, {
|
||||
runSideEffectsSynchronously: true,
|
||||
});
|
||||
initializeFlipperLibImplementation(this._store, this._logger);
|
||||
initializeFlipperLibImplementation(
|
||||
getRenderHostInstance(),
|
||||
this._store,
|
||||
this._logger,
|
||||
);
|
||||
this._store.dispatch(registerPlugins(plugins ?? []));
|
||||
this._store.dispatch({
|
||||
type: 'SET_FLIPPER_SERVER',
|
||||
|
||||
@@ -11,12 +11,11 @@ import React, {useState} from 'react';
|
||||
import FlexRow from './FlexRow';
|
||||
import Glyph from './Glyph';
|
||||
import Input from './Input';
|
||||
import electron from 'electron';
|
||||
import styled from '@emotion/styled';
|
||||
import {colors} from './colors';
|
||||
import Electron from 'electron';
|
||||
import fs from 'fs';
|
||||
import {Tooltip} from '..';
|
||||
import {getFlipperLib} from 'flipper-plugin';
|
||||
|
||||
const CenteredGlyph = styled(Glyph)({
|
||||
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 {
|
||||
onPathChanged: (evtArgs: {path: string; isValid: boolean}) => void;
|
||||
placeholderText: string;
|
||||
defaultPath: string;
|
||||
showHiddenFiles: boolean;
|
||||
}
|
||||
|
||||
const defaultProps: Props = {
|
||||
onPathChanged: (_) => {},
|
||||
placeholderText: '',
|
||||
defaultPath: '/',
|
||||
showHiddenFiles: false,
|
||||
};
|
||||
|
||||
export default function FileSelector({
|
||||
onPathChanged,
|
||||
placeholderText,
|
||||
|
||||
defaultPath,
|
||||
showHiddenFiles,
|
||||
}: Props) {
|
||||
const [value, setValue] = useState('');
|
||||
const [isValid, setIsValid] = useState(false);
|
||||
const options: Electron.OpenDialogOptions = {
|
||||
properties: [
|
||||
'openFile',
|
||||
...(showHiddenFiles ? strToArr('showHiddenFiles') : []),
|
||||
],
|
||||
defaultPath,
|
||||
};
|
||||
const onChange = (path: string) => {
|
||||
setValue(path);
|
||||
let isNewPathValid = false;
|
||||
@@ -103,11 +87,11 @@ export default function FileSelector({
|
||||
/>
|
||||
<GlyphContainer
|
||||
onClick={() =>
|
||||
electron.remote.dialog
|
||||
.showOpenDialog(options)
|
||||
.then((result: electron.OpenDialogReturnValue) => {
|
||||
if (result && !result.canceled && result.filePaths.length) {
|
||||
onChange(result.filePaths[0]);
|
||||
getFlipperLib()
|
||||
.showOpenDialog?.({defaultPath})
|
||||
.then((path) => {
|
||||
if (path) {
|
||||
onChange(path);
|
||||
}
|
||||
})
|
||||
}>
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
import * as React from 'react';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import electron from 'electron';
|
||||
import {getLogger} from 'flipper-common';
|
||||
import {Store, MiddlewareAPI} from '../reducers';
|
||||
import {DeviceExport} from '../devices/BaseDevice';
|
||||
@@ -24,7 +23,6 @@ import {default as BaseDevice} from '../devices/BaseDevice';
|
||||
import {default as ArchivedDevice} from '../devices/ArchivedDevice';
|
||||
import fs from 'fs';
|
||||
import {v4 as uuidv4} from 'uuid';
|
||||
import {remote, OpenDialogOptions} from 'electron';
|
||||
import {readCurrentRevision} from './packageMetadata';
|
||||
import {tryCatchReportPlatformFailures} from 'flipper-common';
|
||||
import {promisify} from 'util';
|
||||
@@ -45,6 +43,7 @@ import {ClientQuery} from 'flipper-common';
|
||||
import ShareSheetExportUrl from '../chrome/ShareSheetExportUrl';
|
||||
import ShareSheetExportFile from '../chrome/ShareSheetExportFile';
|
||||
import ExportDataPluginSheet from '../chrome/ExportDataPluginSheet';
|
||||
import {getRenderHostInstance} from '../RenderHost';
|
||||
|
||||
export const IMPORT_FLIPPER_TRACE_EVENT = 'import-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) {
|
||||
const options: OpenDialogOptions = {
|
||||
properties: ['openFile'],
|
||||
filters: [{extensions: ['flipper', 'json', 'txt'], name: 'Flipper files'}],
|
||||
};
|
||||
remote.dialog.showOpenDialog(options).then((result) => {
|
||||
const filePaths = result.filePaths;
|
||||
if (filePaths.length > 0) {
|
||||
tryCatchReportPlatformFailures(() => {
|
||||
importFileToStore(filePaths[0], store);
|
||||
}, `${IMPORT_FLIPPER_TRACE_EVENT}:UI`);
|
||||
}
|
||||
});
|
||||
return getRenderHostInstance()
|
||||
.showOpenDialog?.({
|
||||
filter: {extensions: ['flipper', 'json', 'txt'], name: 'Flipper files'},
|
||||
})
|
||||
.then((filePath) => {
|
||||
if (filePath) {
|
||||
tryCatchReportPlatformFailures(() => {
|
||||
importFileToStore(filePath, store);
|
||||
}, `${IMPORT_FLIPPER_TRACE_EVENT}:UI`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function startFileExport(dispatch: Store['dispatch']) {
|
||||
const result = await electron.remote.dialog.showSaveDialog(
|
||||
// @ts-ignore This appears to work but isn't allowed by the types
|
||||
null,
|
||||
{
|
||||
title: 'FlipperExport',
|
||||
defaultPath: path.join(os.homedir(), 'FlipperExport.flipper'),
|
||||
},
|
||||
);
|
||||
const file = result.filePath;
|
||||
const file = await getRenderHostInstance().showSaveDialog?.({
|
||||
title: 'FlipperExport',
|
||||
defaultPath: path.join(os.homedir(), 'FlipperExport.flipper'),
|
||||
});
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -18,8 +18,10 @@ import constants from '../fb-stubs/constants';
|
||||
import {addNotification} from '../reducers/notifications';
|
||||
import {deconstructPluginKey} from 'flipper-common';
|
||||
import {DetailSidebarImpl} from '../sandy-chrome/DetailSidebarImpl';
|
||||
import {RenderHost} from '../RenderHost';
|
||||
|
||||
export function initializeFlipperLibImplementation(
|
||||
renderHost: RenderHost,
|
||||
store: Store,
|
||||
logger: Logger,
|
||||
) {
|
||||
@@ -64,5 +66,8 @@ export function initializeFlipperLibImplementation(
|
||||
);
|
||||
},
|
||||
DetailsSidebarImplementation: DetailSidebarImpl,
|
||||
showSaveDialog: renderHost.showSaveDialog,
|
||||
showOpenDialog: renderHost.showOpenDialog,
|
||||
showSelectDirectoryDialog: renderHost.showSelectDirectoryDialog,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,5 +7,4 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {remote} from 'electron';
|
||||
export default () => remote.getCurrentWindow().reload();
|
||||
export default () => window.location.reload();
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
import {useStore} from './useStore';
|
||||
import {remote} from 'electron';
|
||||
import {getRenderHostInstance} from '../RenderHost';
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
export function useIsDarkMode(): boolean {
|
||||
return useStore((state) => {
|
||||
const darkMode = state.settingsState.darkMode;
|
||||
if (darkMode === 'dark') {
|
||||
return true;
|
||||
} else if (darkMode === 'light') {
|
||||
return false;
|
||||
} else if (darkMode === 'system') {
|
||||
return remote.nativeTheme.shouldUseDarkColors;
|
||||
}
|
||||
const darkMode = useStore((state) => state.settingsState.darkMode);
|
||||
if (darkMode === 'dark') {
|
||||
return true;
|
||||
} else if (darkMode === 'light') {
|
||||
return false;
|
||||
});
|
||||
} else if (darkMode === 'system') {
|
||||
return getRenderHostInstance().shouldUseDarkColors();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,19 @@ export interface FlipperLib {
|
||||
DetailsSidebarImplementation?(
|
||||
props: DetailSidebarProps,
|
||||
): 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;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
import fs from 'fs';
|
||||
// eslint-disable-next-line
|
||||
import electron, {OpenDialogOptions, remote} from 'electron';
|
||||
import {Atom, DataTableManager} from 'flipper-plugin';
|
||||
import {Atom, DataTableManager, getFlipperLib} from 'flipper-plugin';
|
||||
import {createContext} from 'react';
|
||||
import {Header, Request} from '../types';
|
||||
import {message} from 'antd';
|
||||
@@ -137,16 +137,13 @@ export function createNetworkManager(
|
||||
informClientMockChange(routes.get());
|
||||
},
|
||||
importRoutes() {
|
||||
const options: OpenDialogOptions = {
|
||||
properties: ['openFile'],
|
||||
filters: [{extensions: ['json'], name: 'Flipper Route Files'}],
|
||||
};
|
||||
remote.dialog
|
||||
.showOpenDialog(options)
|
||||
.then((result) => {
|
||||
const filePaths = result.filePaths;
|
||||
if (filePaths.length > 0) {
|
||||
fs.readFile(filePaths[0], 'utf8', (err, data) => {
|
||||
getFlipperLib()
|
||||
.showOpenDialog?.({
|
||||
filter: {extensions: ['json'], name: 'Flipper Route Files'},
|
||||
})
|
||||
.then((filePath) => {
|
||||
if (filePath) {
|
||||
fs.readFile(filePath, 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
message.error('Unable to import file');
|
||||
return;
|
||||
@@ -177,17 +174,12 @@ export function createNetworkManager(
|
||||
);
|
||||
},
|
||||
exportRoutes() {
|
||||
remote.dialog
|
||||
.showSaveDialog(
|
||||
// @ts-ignore This appears to work but isn't allowed by the types
|
||||
null,
|
||||
{
|
||||
title: 'Export Routes',
|
||||
defaultPath: 'NetworkPluginRoutesExport.json',
|
||||
},
|
||||
)
|
||||
.then((result: electron.SaveDialogReturnValue) => {
|
||||
const file = result.filePath;
|
||||
getFlipperLib()
|
||||
.showSaveDialog?.({
|
||||
title: 'Export Routes',
|
||||
defaultPath: 'NetworkPluginRoutesExport.json',
|
||||
})
|
||||
.then((file) => {
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user