Upgrade electron version
Summary: CHANGELOG: Upgrade electron to 18.2.0. In Electron 18.2.0 we no longer have access to `remote`. Instead, we are recommended to implement IPC communications. We re-implement `remote` methods used before as IPC commands. To support type-safe execution of the commands, we create electron IPC clients on both sides: the main process and renderer process. We also move the main menu creation to the main process and track its usage via sending IPC messages to the renderer process where the logging happens. Reviewed By: mweststrate Differential Revision: D36593625 fbshipit-source-id: 6dcf531461ef2edceb9cac372a650f84f3370953
This commit is contained in:
committed by
Facebook GitHub Bot
parent
2fe7e73175
commit
92cdb81096
@@ -15,27 +15,28 @@ import {
|
|||||||
_LoggerContext,
|
_LoggerContext,
|
||||||
} from 'flipper-plugin';
|
} from 'flipper-plugin';
|
||||||
// eslint-disable-next-line no-restricted-imports,flipper/no-electron-remote-imports
|
// eslint-disable-next-line no-restricted-imports,flipper/no-electron-remote-imports
|
||||||
import {
|
import {ipcRenderer, SaveDialogReturnValue, clipboard, shell} from 'electron';
|
||||||
ipcRenderer,
|
|
||||||
remote,
|
|
||||||
SaveDialogReturnValue,
|
|
||||||
clipboard,
|
|
||||||
shell,
|
|
||||||
} from 'electron';
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import {setupMenuBar} from './setupMenuBar';
|
import {setupMenuBarTracking} from './setupMenuBar';
|
||||||
import {FlipperServer, FlipperServerConfig} from 'flipper-common';
|
import {FlipperServer, FlipperServerConfig} from 'flipper-common';
|
||||||
import type {Icon, RenderHost} from 'flipper-ui-core';
|
import type {Icon, RenderHost} from 'flipper-ui-core';
|
||||||
import {getLocalIconUrl} from '../utils/icons';
|
import {getLocalIconUrl} from '../utils/icons';
|
||||||
import {getCPUUsage} from 'process';
|
import {getCPUUsage} from 'process';
|
||||||
|
import {ElectronIpcClientRenderer} from '../electronIpc';
|
||||||
|
|
||||||
export function initializeElectron(
|
export async function initializeElectron(
|
||||||
flipperServer: FlipperServer,
|
flipperServer: FlipperServer,
|
||||||
flipperServerConfig: FlipperServerConfig,
|
flipperServerConfig: FlipperServerConfig,
|
||||||
|
electronIpcClient: ElectronIpcClientRenderer,
|
||||||
) {
|
) {
|
||||||
const execPath = process.execPath || remote.process.execPath;
|
const electronProcess = await electronIpcClient.send('getProcess');
|
||||||
|
const electronTheme = await electronIpcClient.send('getNativeTheme');
|
||||||
|
|
||||||
|
const execPath = process.execPath || electronProcess.execPath;
|
||||||
const isProduction = !/node_modules[\\/]electron[\\/]/.test(execPath);
|
const isProduction = !/node_modules[\\/]electron[\\/]/.test(execPath);
|
||||||
|
|
||||||
|
setupMenuBarTracking(electronIpcClient);
|
||||||
|
|
||||||
function restart(update: boolean = false) {
|
function restart(update: boolean = false) {
|
||||||
if (isProduction) {
|
if (isProduction) {
|
||||||
if (update) {
|
if (update) {
|
||||||
@@ -44,11 +45,11 @@ export function initializeElectron(
|
|||||||
.splice(0, 1)
|
.splice(0, 1)
|
||||||
.filter((arg) => arg !== '--no-launcher' && arg !== '--no-updater'),
|
.filter((arg) => arg !== '--no-launcher' && arg !== '--no-updater'),
|
||||||
};
|
};
|
||||||
remote.app.relaunch(options);
|
electronIpcClient.send('relaunch', options);
|
||||||
} else {
|
} else {
|
||||||
remote.app.relaunch();
|
electronIpcClient.send('relaunch');
|
||||||
}
|
}
|
||||||
remote.app.exit();
|
electronIpcClient.send('exit');
|
||||||
} else {
|
} else {
|
||||||
// Relaunching the process with the standard way doesn't work in dev mode.
|
// Relaunching the process with the standard way doesn't work in dev mode.
|
||||||
// So instead we're sending a signal to dev server to kill the current instance of electron and launch new.
|
// So instead we're sending a signal to dev server to kill the current instance of electron and launch new.
|
||||||
@@ -59,7 +60,7 @@ export function initializeElectron(
|
|||||||
}
|
}
|
||||||
|
|
||||||
FlipperRenderHostInstance = {
|
FlipperRenderHostInstance = {
|
||||||
processId: remote.process.pid,
|
processId: electronProcess.pid,
|
||||||
isProduction,
|
isProduction,
|
||||||
readTextFromClipboard() {
|
readTextFromClipboard() {
|
||||||
return clipboard.readText();
|
return clipboard.readText();
|
||||||
@@ -68,10 +69,11 @@ export function initializeElectron(
|
|||||||
clipboard.writeText(text);
|
clipboard.writeText(text);
|
||||||
},
|
},
|
||||||
async showSaveDialog(options) {
|
async showSaveDialog(options) {
|
||||||
return (await remote.dialog.showSaveDialog(options))?.filePath;
|
return (await electronIpcClient.send('showSaveDialog', options))
|
||||||
|
?.filePath;
|
||||||
},
|
},
|
||||||
async showOpenDialog({filter, defaultPath}) {
|
async showOpenDialog({filter, defaultPath}) {
|
||||||
const result = await remote.dialog.showOpenDialog({
|
const result = await electronIpcClient.send('showOpenDialog', {
|
||||||
defaultPath,
|
defaultPath,
|
||||||
properties: ['openFile'],
|
properties: ['openFile'],
|
||||||
filters: filter ? [filter] : undefined,
|
filters: filter ? [filter] : undefined,
|
||||||
@@ -79,8 +81,8 @@ export function initializeElectron(
|
|||||||
return result.filePaths?.[0];
|
return result.filePaths?.[0];
|
||||||
},
|
},
|
||||||
showSelectDirectoryDialog(defaultPath = path.resolve('/')) {
|
showSelectDirectoryDialog(defaultPath = path.resolve('/')) {
|
||||||
return remote.dialog
|
return electronIpcClient
|
||||||
.showOpenDialog({
|
.send('showOpenDialog', {
|
||||||
properties: ['openDirectory'],
|
properties: ['openDirectory'],
|
||||||
defaultPath,
|
defaultPath,
|
||||||
})
|
})
|
||||||
@@ -104,7 +106,7 @@ export function initializeElectron(
|
|||||||
encoding = 'utf-8',
|
encoding = 'utf-8',
|
||||||
multi,
|
multi,
|
||||||
} = {}) => {
|
} = {}) => {
|
||||||
let {filePaths} = await remote.dialog.showOpenDialog({
|
let {filePaths} = await electronIpcClient.send('showOpenDialog', {
|
||||||
defaultPath,
|
defaultPath,
|
||||||
properties: [
|
properties: [
|
||||||
'openFile',
|
'openFile',
|
||||||
@@ -138,7 +140,7 @@ export function initializeElectron(
|
|||||||
return multi ? descriptors : descriptors[0];
|
return multi ? descriptors : descriptors[0];
|
||||||
}) as RenderHost['importFile'],
|
}) as RenderHost['importFile'],
|
||||||
async exportFile(data, {defaultPath, encoding = 'utf-8'} = {}) {
|
async exportFile(data, {defaultPath, encoding = 'utf-8'} = {}) {
|
||||||
const {filePath} = await remote.dialog.showSaveDialog({
|
const {filePath} = await electronIpcClient.send('showSaveDialog', {
|
||||||
defaultPath,
|
defaultPath,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -153,7 +155,8 @@ export function initializeElectron(
|
|||||||
shell.openExternal(url);
|
shell.openExternal(url);
|
||||||
},
|
},
|
||||||
hasFocus() {
|
hasFocus() {
|
||||||
return remote.getCurrentWindow().isFocused();
|
// eslint-disable-next-line node/no-sync
|
||||||
|
return electronIpcClient.sendSync('getCurrentWindowState').isFocused;
|
||||||
},
|
},
|
||||||
onIpcEvent(event, callback) {
|
onIpcEvent(event, callback) {
|
||||||
ipcRenderer.on(event, (_ev, ...args: any[]) => {
|
ipcRenderer.on(event, (_ev, ...args: any[]) => {
|
||||||
@@ -164,7 +167,7 @@ export function initializeElectron(
|
|||||||
ipcRenderer.send(event, ...args);
|
ipcRenderer.send(event, ...args);
|
||||||
},
|
},
|
||||||
shouldUseDarkColors() {
|
shouldUseDarkColors() {
|
||||||
return remote.nativeTheme.shouldUseDarkColors;
|
return electronTheme.shouldUseDarkColors;
|
||||||
},
|
},
|
||||||
restartFlipper(update: boolean = false) {
|
restartFlipper(update: boolean = false) {
|
||||||
restart(update);
|
restart(update);
|
||||||
@@ -203,8 +206,6 @@ export function initializeElectron(
|
|||||||
return getCPUUsage().percentCPUUsage;
|
return getCPUUsage().percentCPUUsage;
|
||||||
},
|
},
|
||||||
} as RenderHost;
|
} as RenderHost;
|
||||||
|
|
||||||
setupMenuBar();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDefaultPluginsIndex() {
|
function getDefaultPluginsIndex() {
|
||||||
|
|||||||
@@ -7,232 +7,16 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Deliberate use of remote in this context.
|
|
||||||
/* eslint-disable no-restricted-properties, no-restricted-imports */
|
|
||||||
import electron, {MenuItemConstructorOptions, webFrame} from 'electron';
|
|
||||||
import {getLogger} from 'flipper-common';
|
import {getLogger} from 'flipper-common';
|
||||||
import {_buildInMenuEntries, _wrapInteractionHandler} from 'flipper-plugin';
|
import {_buildInMenuEntries, _wrapInteractionHandler} from 'flipper-plugin';
|
||||||
|
import {ElectronIpcClientRenderer} from '../electronIpc';
|
||||||
|
|
||||||
export function setupMenuBar() {
|
export function setupMenuBarTracking(
|
||||||
const template = getTemplate(electron.remote.app);
|
electronIpcClient: ElectronIpcClientRenderer,
|
||||||
// create actual menu instance
|
) {
|
||||||
const applicationMenu = electron.remote.Menu.buildFromTemplate(template);
|
electronIpcClient.on('menuItemAction', ({action, menu, label}) => {
|
||||||
// update menubar
|
if (action === 'click') {
|
||||||
electron.remote.Menu.setApplicationMenu(applicationMenu);
|
getLogger().track('usage', 'menuItemClick', {menu, label});
|
||||||
}
|
|
||||||
|
|
||||||
function trackMenuItems(menu: string, items: MenuItemConstructorOptions[]) {
|
|
||||||
items.forEach((item) => {
|
|
||||||
if (item.label && item.click) {
|
|
||||||
item.click = _wrapInteractionHandler(
|
|
||||||
item.click,
|
|
||||||
'MenuItem',
|
|
||||||
'onClick',
|
|
||||||
'flipper:menu:' + menu,
|
|
||||||
item.label,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTemplate(app: electron.App): Array<MenuItemConstructorOptions> {
|
|
||||||
const viewMenu: MenuItemConstructorOptions[] = [
|
|
||||||
{
|
|
||||||
label: 'Reload',
|
|
||||||
accelerator: 'CmdOrCtrl+R',
|
|
||||||
click: function (_, _focusedWindow: electron.BrowserWindow | undefined) {
|
|
||||||
try {
|
|
||||||
getLogger().track('usage', 'reload');
|
|
||||||
} catch (e) {
|
|
||||||
// Ignore track failures (which can happen if we try to reload from a broken state)
|
|
||||||
console.warn('Could not track reload', e);
|
|
||||||
}
|
|
||||||
window.location.reload();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Toggle Full Screen',
|
|
||||||
accelerator: (function () {
|
|
||||||
if (process.platform === 'darwin') {
|
|
||||||
return 'Ctrl+Command+F';
|
|
||||||
} else {
|
|
||||||
return 'F11';
|
|
||||||
}
|
|
||||||
})(),
|
|
||||||
click: function (_, focusedWindow: electron.BrowserWindow | undefined) {
|
|
||||||
if (focusedWindow) {
|
|
||||||
focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Actual Size',
|
|
||||||
accelerator: (function () {
|
|
||||||
return 'CmdOrCtrl+0';
|
|
||||||
})(),
|
|
||||||
click: function (_, _focusedWindow: electron.BrowserWindow | undefined) {
|
|
||||||
webFrame.setZoomFactor(1);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Zoom In',
|
|
||||||
accelerator: (function () {
|
|
||||||
return 'CmdOrCtrl+=';
|
|
||||||
})(),
|
|
||||||
click: function (_, _focusedWindow: electron.BrowserWindow | undefined) {
|
|
||||||
webFrame.setZoomFactor(webFrame.getZoomFactor() + 0.25);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Zoom Out',
|
|
||||||
accelerator: (function () {
|
|
||||||
return 'CmdOrCtrl+-';
|
|
||||||
})(),
|
|
||||||
click: function (_, _focusedWindow: electron.BrowserWindow | undefined) {
|
|
||||||
webFrame.setZoomFactor(webFrame.getZoomFactor() - 0.25);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Toggle Developer Tools',
|
|
||||||
accelerator: (function () {
|
|
||||||
if (process.platform === 'darwin') {
|
|
||||||
return 'Alt+Command+I';
|
|
||||||
} else {
|
|
||||||
return 'Ctrl+Shift+I';
|
|
||||||
}
|
|
||||||
})(),
|
|
||||||
click: function (_, focusedWindow: electron.BrowserWindow | undefined) {
|
|
||||||
if (focusedWindow) {
|
|
||||||
// @ts-ignore: https://github.com/electron/electron/issues/7832
|
|
||||||
focusedWindow.toggleDevTools();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
trackMenuItems('view', viewMenu);
|
|
||||||
|
|
||||||
const template: MenuItemConstructorOptions[] = [
|
|
||||||
{
|
|
||||||
label: 'Edit',
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
label: 'Undo',
|
|
||||||
accelerator: 'CmdOrCtrl+Z',
|
|
||||||
role: 'undo',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Redo',
|
|
||||||
accelerator: 'Shift+CmdOrCtrl+Z',
|
|
||||||
role: 'redo',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'separator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Cut',
|
|
||||||
accelerator: 'CmdOrCtrl+X',
|
|
||||||
role: 'cut',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Copy',
|
|
||||||
accelerator: 'CmdOrCtrl+C',
|
|
||||||
role: 'copy',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Paste',
|
|
||||||
accelerator: 'CmdOrCtrl+V',
|
|
||||||
role: 'paste',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Select All',
|
|
||||||
accelerator: 'CmdOrCtrl+A',
|
|
||||||
role: 'selectAll',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'View',
|
|
||||||
submenu: viewMenu,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Window',
|
|
||||||
role: 'window',
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
label: 'Minimize',
|
|
||||||
accelerator: 'CmdOrCtrl+M',
|
|
||||||
role: 'minimize',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Close',
|
|
||||||
accelerator: 'CmdOrCtrl+W',
|
|
||||||
role: 'close',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
if (process.platform === 'darwin') {
|
|
||||||
const name = app.name;
|
|
||||||
template.unshift({
|
|
||||||
label: name,
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
label: 'About ' + name,
|
|
||||||
role: 'about',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'separator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Services',
|
|
||||||
role: 'services',
|
|
||||||
submenu: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'separator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Hide ' + name,
|
|
||||||
accelerator: 'Command+H',
|
|
||||||
role: 'hide',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Hide Others',
|
|
||||||
accelerator: 'Command+Shift+H',
|
|
||||||
role: 'hideOthers',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Show All',
|
|
||||||
role: 'unhide',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'separator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Quit',
|
|
||||||
accelerator: 'Command+Q',
|
|
||||||
click: function () {
|
|
||||||
app.quit();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
const windowMenu = template.find(function (m) {
|
|
||||||
return m.role === 'window';
|
|
||||||
});
|
|
||||||
if (windowMenu) {
|
|
||||||
(windowMenu.submenu as MenuItemConstructorOptions[]).push(
|
|
||||||
{
|
|
||||||
type: 'separator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Bring All to Front',
|
|
||||||
role: 'front',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return template;
|
|
||||||
}
|
|
||||||
|
|||||||
85
desktop/app/src/electronIpc.tsx
Normal file
85
desktop/app/src/electronIpc.tsx
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-imports
|
||||||
|
import type {
|
||||||
|
NativeTheme,
|
||||||
|
SaveDialogReturnValue,
|
||||||
|
SaveDialogOptions,
|
||||||
|
OpenDialogOptions,
|
||||||
|
OpenDialogReturnValue,
|
||||||
|
} from 'electron';
|
||||||
|
import {ipcRenderer} from 'electron/renderer';
|
||||||
|
|
||||||
|
export interface ElectronIpcAsyncCommands {
|
||||||
|
getPath: (path: 'app' | 'home' | 'temp' | 'desktop') => Promise<string>;
|
||||||
|
getProcess: () => Promise<{execPath: string; pid: number}>;
|
||||||
|
relaunch: (options?: {args: string[]}) => Promise<void>;
|
||||||
|
showSaveDialog: (
|
||||||
|
options: SaveDialogOptions,
|
||||||
|
) => Promise<SaveDialogReturnValue>;
|
||||||
|
showOpenDialog: (
|
||||||
|
options: OpenDialogOptions,
|
||||||
|
) => Promise<OpenDialogReturnValue>;
|
||||||
|
getNativeTheme: () => Promise<
|
||||||
|
Pick<
|
||||||
|
NativeTheme,
|
||||||
|
| 'inForcedColorsMode'
|
||||||
|
| 'shouldUseDarkColors'
|
||||||
|
| 'shouldUseHighContrastColors'
|
||||||
|
| 'shouldUseInvertedColorScheme'
|
||||||
|
| 'themeSource'
|
||||||
|
>
|
||||||
|
>;
|
||||||
|
quit: () => Promise<void>;
|
||||||
|
exit: () => Promise<void>;
|
||||||
|
getApp: () => Promise<{
|
||||||
|
name: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ElectronIpcSyncCommands {
|
||||||
|
getCurrentWindowState: () => {
|
||||||
|
isFocused: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ElectronIpcEvents {
|
||||||
|
menuItemAction: {
|
||||||
|
menu: 'view' | 'root';
|
||||||
|
label: string;
|
||||||
|
action: 'click';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ElectronIpcClientRenderer {
|
||||||
|
on<T extends keyof ElectronIpcEvents>(
|
||||||
|
eventName: T,
|
||||||
|
cb: (data: ElectronIpcEvents[T]) => void,
|
||||||
|
) {
|
||||||
|
ipcRenderer.on(eventName, (_event, data) => cb(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
send<T extends keyof ElectronIpcAsyncCommands>(
|
||||||
|
eventName: T,
|
||||||
|
...args: Parameters<ElectronIpcAsyncCommands[T]>
|
||||||
|
): ReturnType<ElectronIpcAsyncCommands[T]> extends Promise<any>
|
||||||
|
? ReturnType<ElectronIpcAsyncCommands[T]>
|
||||||
|
: never {
|
||||||
|
return ipcRenderer.invoke(eventName, ...args) as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendSync<T extends keyof ElectronIpcSyncCommands>(
|
||||||
|
eventName: T,
|
||||||
|
...args: Parameters<ElectronIpcSyncCommands[T]>
|
||||||
|
): ReturnType<ElectronIpcSyncCommands[T]> {
|
||||||
|
// eslint-disable-next-line node/no-sync
|
||||||
|
return ipcRenderer.sendSync(eventName, ...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,8 +14,6 @@ import {
|
|||||||
_setGlobalInteractionReporter,
|
_setGlobalInteractionReporter,
|
||||||
_LoggerContext,
|
_LoggerContext,
|
||||||
} from 'flipper-plugin';
|
} from 'flipper-plugin';
|
||||||
// eslint-disable-next-line no-restricted-imports,flipper/no-electron-remote-imports
|
|
||||||
import {remote} from 'electron';
|
|
||||||
import {createFlipperServer, FlipperServerState} from 'flipper-frontend-core';
|
import {createFlipperServer, FlipperServerState} from 'flipper-frontend-core';
|
||||||
import {
|
import {
|
||||||
FlipperServerImpl,
|
FlipperServerImpl,
|
||||||
@@ -39,16 +37,18 @@ import constants from './fb-stubs/constants';
|
|||||||
import {initializeElectron} from './electron/initializeElectron';
|
import {initializeElectron} from './electron/initializeElectron';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
|
import {ElectronIpcClientRenderer} from './electronIpc';
|
||||||
|
|
||||||
enableMapSet();
|
enableMapSet();
|
||||||
|
|
||||||
async function getEmbeddedFlipperServer(
|
async function getEmbeddedFlipperServer(
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
|
electronIpcClient: ElectronIpcClientRenderer,
|
||||||
): Promise<FlipperServer> {
|
): Promise<FlipperServer> {
|
||||||
const app = remote.app;
|
const execPath =
|
||||||
const execPath = process.execPath || remote.process.execPath;
|
process.execPath || (await electronIpcClient.send('getProcess')).execPath;
|
||||||
const appPath = app.getAppPath();
|
const appPath = await electronIpcClient.send('getPath', 'app');
|
||||||
const staticPath = getStaticDir();
|
const staticPath = getStaticDir(appPath);
|
||||||
const isProduction = !/node_modules[\\/]electron[\\/]/.test(execPath);
|
const isProduction = !/node_modules[\\/]electron[\\/]/.test(execPath);
|
||||||
const env = process.env;
|
const env = process.env;
|
||||||
const environmentInfo = await getEnvironmentInfo(
|
const environmentInfo = await getEnvironmentInfo(
|
||||||
@@ -84,11 +84,11 @@ async function getEmbeddedFlipperServer(
|
|||||||
gatekeepers: getGatekeepers(environmentInfo.os.unixname),
|
gatekeepers: getGatekeepers(environmentInfo.os.unixname),
|
||||||
paths: {
|
paths: {
|
||||||
appPath,
|
appPath,
|
||||||
homePath: app.getPath('home'),
|
homePath: await electronIpcClient.send('getPath', 'home'),
|
||||||
execPath,
|
execPath,
|
||||||
staticPath,
|
staticPath,
|
||||||
tempPath: app.getPath('temp'),
|
tempPath: await electronIpcClient.send('getPath', 'temp'),
|
||||||
desktopPath: app.getPath('desktop'),
|
desktopPath: await electronIpcClient.send('getPath', 'desktop'),
|
||||||
},
|
},
|
||||||
launcherSettings: await loadLauncherSettings(),
|
launcherSettings: await loadLauncherSettings(),
|
||||||
processConfig: loadProcessConfig(env),
|
processConfig: loadProcessConfig(env),
|
||||||
@@ -116,10 +116,19 @@ async function start() {
|
|||||||
const logger = createDelegatedLogger();
|
const logger = createDelegatedLogger();
|
||||||
setLoggerInstance(logger);
|
setLoggerInstance(logger);
|
||||||
|
|
||||||
const flipperServer: FlipperServer = await getEmbeddedFlipperServer(logger);
|
const electronIpcClient = new ElectronIpcClientRenderer();
|
||||||
|
|
||||||
|
const flipperServer: FlipperServer = await getEmbeddedFlipperServer(
|
||||||
|
logger,
|
||||||
|
electronIpcClient,
|
||||||
|
);
|
||||||
const flipperServerConfig = await flipperServer.exec('get-config');
|
const flipperServerConfig = await flipperServer.exec('get-config');
|
||||||
|
|
||||||
initializeElectron(flipperServer, flipperServerConfig);
|
await initializeElectron(
|
||||||
|
flipperServer,
|
||||||
|
flipperServerConfig,
|
||||||
|
electronIpcClient,
|
||||||
|
);
|
||||||
|
|
||||||
setProcessState(flipperServerConfig.settings);
|
setProcessState(flipperServerConfig.settings);
|
||||||
|
|
||||||
@@ -142,7 +151,7 @@ start().catch((e) => {
|
|||||||
'Failed to start Flipper desktop: ' + e;
|
'Failed to start Flipper desktop: ' + e;
|
||||||
});
|
});
|
||||||
|
|
||||||
function getStaticDir() {
|
function getStaticDir(appPath: string) {
|
||||||
let _staticPath = path.resolve(__dirname, '..', '..', 'static');
|
let _staticPath = path.resolve(__dirname, '..', '..', 'static');
|
||||||
// fs.existSync used here, as fs-extra doesn't resovle properly in the app.asar
|
// fs.existSync used here, as fs-extra doesn't resovle properly in the app.asar
|
||||||
/* eslint-disable node/no-sync*/
|
/* eslint-disable node/no-sync*/
|
||||||
@@ -150,8 +159,8 @@ function getStaticDir() {
|
|||||||
// True in unit tests
|
// True in unit tests
|
||||||
return _staticPath;
|
return _staticPath;
|
||||||
}
|
}
|
||||||
if (remote && fs.existsSync(remote.app.getAppPath())) {
|
if (fs.existsSync(appPath)) {
|
||||||
_staticPath = path.join(remote.app.getAppPath());
|
_staticPath = path.join(appPath);
|
||||||
}
|
}
|
||||||
if (!fs.existsSync(_staticPath)) {
|
if (!fs.existsSync(_staticPath)) {
|
||||||
throw new Error('Static path does not exist: ' + _staticPath);
|
throw new Error('Static path does not exist: ' + _staticPath);
|
||||||
|
|||||||
@@ -68,12 +68,13 @@
|
|||||||
"description": "Mobile development tool",
|
"description": "Mobile development tool",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/eslint-parser": "^7.17.0",
|
"@babel/eslint-parser": "^7.17.0",
|
||||||
|
"@jest-runner/electron": "^3.0.1",
|
||||||
"@types/jest": "^26.0.24",
|
"@types/jest": "^26.0.24",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.22.0",
|
"@typescript-eslint/eslint-plugin": "^5.22.0",
|
||||||
"@typescript-eslint/parser": "^5.22.0",
|
"@typescript-eslint/parser": "^5.22.0",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"electron": "11.2.3",
|
"electron": "18.2.0",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^7.32.0",
|
||||||
"eslint-config-fbjs": "^3.1.1",
|
"eslint-config-fbjs": "^3.1.1",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
@@ -113,25 +114,25 @@
|
|||||||
"privileged": true,
|
"privileged": true,
|
||||||
"productName": "Flipper",
|
"productName": "Flipper",
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
|
"@jest-runner/electron/electron": "18.2.0",
|
||||||
"adbkit-logcat": "^2.0.1",
|
"adbkit-logcat": "^2.0.1",
|
||||||
"node-forge": "^1.0.6",
|
"minimist": "1.2.6",
|
||||||
"minimist": "1.2.6"
|
"node-forge": "^1.0.6"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "cross-env NODE_ENV=production ./ts-node scripts/build-release.tsx $@",
|
"build": "cross-env NODE_ENV=production ./ts-node scripts/build-release.tsx $@",
|
||||||
"build-plugin": "./ts-node scripts/build-plugin.tsx",
|
"build-plugin": "./ts-node scripts/build-plugin.tsx",
|
||||||
"build:dev": "cross-env NODE_ENV=development ./ts-node scripts/build-release.tsx $@",
|
"build:dev": "cross-env NODE_ENV=development ./ts-node scripts/build-release.tsx $@",
|
||||||
"build:eslint": "cd eslint-plugin-flipper && yarn build",
|
"build:eslint": "cd eslint-plugin-flipper && yarn build",
|
||||||
|
"build:flipper-server": "yarn build:tsc && ./ts-node scripts/build-flipper-server-release.tsx",
|
||||||
"build:themes": "lessc --js themes/light.less static/themes/light.css && lessc --js themes/dark.less static/themes/dark.css",
|
"build:themes": "lessc --js themes/light.less static/themes/light.css && lessc --js themes/dark.less static/themes/dark.css",
|
||||||
"build:tsc": "tsc -b tsc-root/tsconfig.json && ./ts-node ./scripts/compute-package-checksum.tsx -d ./babel-transformer -o ./lib/checksum.txt",
|
"build:tsc": "tsc -b tsc-root/tsconfig.json && ./ts-node ./scripts/compute-package-checksum.tsx -d ./babel-transformer -o ./lib/checksum.txt",
|
||||||
"build:flipper-server": "yarn build:tsc && ./ts-node scripts/build-flipper-server-release.tsx",
|
|
||||||
"bump-versions": "./ts-node scripts/bump-versions.tsx",
|
"bump-versions": "./ts-node scripts/bump-versions.tsx",
|
||||||
"bundle-all-plugins": "./ts-node scripts/bundle-all-plugins.tsx",
|
"bundle-all-plugins": "./ts-node scripts/bundle-all-plugins.tsx",
|
||||||
"predev-server": "yarn build:tsc",
|
|
||||||
"dev-server": "cross-env NODE_ENV=development ./ts-node scripts/start-dev-server.tsx",
|
"dev-server": "cross-env NODE_ENV=development ./ts-node scripts/start-dev-server.tsx",
|
||||||
"preflipper-server": "yarn build:tsc",
|
"docs": "cd ../website && yarn start",
|
||||||
"flipper-server": "cross-env NODE_ENV=development ./ts-node scripts/start-flipper-server-dev.tsx",
|
|
||||||
"fix": "eslint . --fix --ext .js,.ts,.tsx",
|
"fix": "eslint . --fix --ext .js,.ts,.tsx",
|
||||||
|
"flipper-server": "cross-env NODE_ENV=development ./ts-node scripts/start-flipper-server-dev.tsx",
|
||||||
"lint": "yarn lint:eslint && yarn lint:tsc && yarn tsc-plugins",
|
"lint": "yarn lint:eslint && yarn lint:tsc && yarn tsc-plugins",
|
||||||
"lint:eslint": "eslint . --ext .js,.ts,.tsx",
|
"lint:eslint": "eslint . --ext .js,.ts,.tsx",
|
||||||
"lint:tsc": "tsc && tsc -p tsc-root/tsconfig.json --noemit",
|
"lint:tsc": "tsc && tsc -p tsc-root/tsconfig.json --noemit",
|
||||||
@@ -139,10 +140,13 @@
|
|||||||
"open-dist": "open ../dist/mac/Flipper.app --args --launcher=false --inspect=9229",
|
"open-dist": "open ../dist/mac/Flipper.app --args --launcher=false --inspect=9229",
|
||||||
"postinstall": "patch-package && yarn --cwd plugins install --mutex network:30331 && yarn tsc -b pkg-lib/tsconfig.json && ./ts-node scripts/generate-plugin-entry-points.tsx && yarn build:tsc && yarn build:themes",
|
"postinstall": "patch-package && yarn --cwd plugins install --mutex network:30331 && yarn tsc -b pkg-lib/tsconfig.json && ./ts-node scripts/generate-plugin-entry-points.tsx && yarn build:tsc && yarn build:themes",
|
||||||
"prebuild": "yarn build:tsc && yarn rm-dist && yarn build:themes",
|
"prebuild": "yarn build:tsc && yarn rm-dist && yarn build:themes",
|
||||||
|
"predev-server": "yarn build:tsc",
|
||||||
|
"preflipper-server": "yarn build:tsc",
|
||||||
"preinstall": "node scripts/prepare-watchman-config.js && yarn config set ignore-engines",
|
"preinstall": "node scripts/prepare-watchman-config.js && yarn config set ignore-engines",
|
||||||
"prelint:eslint": "yarn build:eslint",
|
"prelint:eslint": "yarn build:eslint",
|
||||||
"pretest": "yarn build:tsc",
|
"pretest": "yarn build:tsc",
|
||||||
"publish-packages": "./ts-node scripts/publish-packages.tsx",
|
"publish-packages": "./ts-node scripts/publish-packages.tsx",
|
||||||
|
"get-electron-cache-directory": "./ts-node scripts/get-electron-cache-directory.tsx",
|
||||||
"reset": "yarn rm-dist && yarn rm-temp && yarn rm-metro-cache && yarn cache clean && yarn rm-bundle && yarn rm-modules",
|
"reset": "yarn rm-dist && yarn rm-temp && yarn rm-metro-cache && yarn cache clean && yarn rm-bundle && yarn rm-modules",
|
||||||
"resolve-plugin-dir": "./ts-node scripts/resolve-plugin-dir.tsx",
|
"resolve-plugin-dir": "./ts-node scripts/resolve-plugin-dir.tsx",
|
||||||
"rm-bundle": "rimraf static/main.bundle.* **/dist/bundle.* **/lib **/*.tsbuildinfo",
|
"rm-bundle": "rimraf static/main.bundle.* **/dist/bundle.* **/lib **/*.tsbuildinfo",
|
||||||
@@ -154,7 +158,6 @@
|
|||||||
"start": "yarn dev-server --inspect=9229",
|
"start": "yarn dev-server --inspect=9229",
|
||||||
"start:break": "yarn dev-server --inspect-brk=9229",
|
"start:break": "yarn dev-server --inspect-brk=9229",
|
||||||
"start:no-bundled-plugins": "yarn start --no-bundled-plugins",
|
"start:no-bundled-plugins": "yarn start --no-bundled-plugins",
|
||||||
"docs": "cd ../website && yarn start",
|
|
||||||
"test": "cross-env TZ=Pacific/Pohnpei jest",
|
"test": "cross-env TZ=Pacific/Pohnpei jest",
|
||||||
"test:debug": "yarn build:tsc && cross-env TZ=Pacific/Pohnpei node --inspect node_modules/.bin/jest --runInBand",
|
"test:debug": "yarn build:tsc && cross-env TZ=Pacific/Pohnpei node --inspect node_modules/.bin/jest --runInBand",
|
||||||
"tsc-plugins": "./ts-node scripts/tsc-plugins.tsx",
|
"tsc-plugins": "./ts-node scripts/tsc-plugins.tsx",
|
||||||
|
|||||||
46
desktop/scripts/get-electron-cache-directory.tsx
Normal file
46
desktop/scripts/get-electron-cache-directory.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
import yargs from 'yargs';
|
||||||
|
import * as crypto from 'crypto';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as url from 'url';
|
||||||
|
|
||||||
|
const argv = yargs
|
||||||
|
.usage('$0 [args]')
|
||||||
|
.options({
|
||||||
|
electronVersion: {
|
||||||
|
key: 'electronVersion',
|
||||||
|
alias: 'v',
|
||||||
|
type: 'string',
|
||||||
|
demandOption: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.help().argv;
|
||||||
|
|
||||||
|
// https://github.com/electron/get/blob/1671db2120142d7850260b098db72b0ef5ee988c/src/Cache.ts#L23
|
||||||
|
const getCacheDirectory = (downloadUrl: string): string => {
|
||||||
|
const parsedDownloadUrl = url.parse(downloadUrl);
|
||||||
|
const {search, hash, pathname, ...rest} = parsedDownloadUrl;
|
||||||
|
const strippedUrl = url.format({
|
||||||
|
...rest,
|
||||||
|
pathname: path.dirname(pathname || 'electron'),
|
||||||
|
});
|
||||||
|
|
||||||
|
return crypto.createHash('sha256').update(strippedUrl).digest('hex');
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUrl = ({electronVersion}: {electronVersion: string}) => {
|
||||||
|
// It is going to be stripped to https://github.com/electron/electron/releases/download/v${electronVersion}, so it is going to be the same for all platforms
|
||||||
|
const url = `https://github.com/electron/electron/releases/download/v${electronVersion}/electron-v${electronVersion}-linux-x64.zip`;
|
||||||
|
return getCacheDirectory(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
const folderName = getUrl(argv);
|
||||||
|
console.log(folderName);
|
||||||
95
desktop/static/electronIpcMain.tsx
Normal file
95
desktop/static/electronIpcMain.tsx
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-imports
|
||||||
|
import electron, {ipcMain, BrowserWindow} from 'electron';
|
||||||
|
// eslint-disable-next-line flipper/no-relative-imports-across-packages
|
||||||
|
import type {
|
||||||
|
ElectronIpcAsyncCommands,
|
||||||
|
ElectronIpcEvents,
|
||||||
|
ElectronIpcSyncCommands,
|
||||||
|
} from '../app/src/electronIpc';
|
||||||
|
|
||||||
|
export class ElectronIpcClientMain {
|
||||||
|
constructor(private readonly window: BrowserWindow) {
|
||||||
|
this.handleAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleAll() {
|
||||||
|
for (const command of Object.keys(this.commandsAsync)) {
|
||||||
|
ipcMain.handle(command, (_event, params) =>
|
||||||
|
this.commandsAsync[command as keyof ElectronIpcAsyncCommands](params),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line node/no-sync
|
||||||
|
for (const command of Object.keys(this.commandsSync)) {
|
||||||
|
ipcMain.on(command, (event) => {
|
||||||
|
event.returnValue =
|
||||||
|
// eslint-disable-next-line node/no-sync
|
||||||
|
this.commandsSync[command as keyof ElectronIpcSyncCommands]();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
send<T extends keyof ElectronIpcEvents>(
|
||||||
|
eventName: T,
|
||||||
|
data: ElectronIpcEvents[T],
|
||||||
|
) {
|
||||||
|
this.window.webContents.send(eventName, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly commandsAsync: ElectronIpcAsyncCommands = {
|
||||||
|
exit: async () => electron.app.exit(),
|
||||||
|
quit: async () => electron.app.quit(),
|
||||||
|
getPath: async (pathName) => {
|
||||||
|
switch (pathName) {
|
||||||
|
case 'app': {
|
||||||
|
return electron.app.getAppPath();
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return electron.app.getPath(pathName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getProcess: async () => {
|
||||||
|
const {pid, execPath} = process;
|
||||||
|
return {pid, execPath};
|
||||||
|
},
|
||||||
|
relaunch: async (options) => electron.app.relaunch(options),
|
||||||
|
showSaveDialog: (options) => electron.dialog.showSaveDialog(options),
|
||||||
|
showOpenDialog: (options) => electron.dialog.showOpenDialog(options),
|
||||||
|
getNativeTheme: async () => {
|
||||||
|
const {
|
||||||
|
themeSource,
|
||||||
|
shouldUseDarkColors,
|
||||||
|
shouldUseHighContrastColors,
|
||||||
|
shouldUseInvertedColorScheme,
|
||||||
|
inForcedColorsMode,
|
||||||
|
} = electron.nativeTheme;
|
||||||
|
return {
|
||||||
|
themeSource,
|
||||||
|
shouldUseDarkColors,
|
||||||
|
shouldUseHighContrastColors,
|
||||||
|
shouldUseInvertedColorScheme,
|
||||||
|
inForcedColorsMode,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getApp: async () => {
|
||||||
|
const {name} = electron.app;
|
||||||
|
return {name};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly commandsSync: ElectronIpcSyncCommands = {
|
||||||
|
getCurrentWindowState: () => {
|
||||||
|
const isFocused = this.window.isFocused();
|
||||||
|
return {isFocused};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -32,6 +32,8 @@ import delegateToLauncher from './launcher';
|
|||||||
import yargs from 'yargs';
|
import yargs from 'yargs';
|
||||||
import {promisify} from 'util';
|
import {promisify} from 'util';
|
||||||
import process from 'process';
|
import process from 'process';
|
||||||
|
import {setupMenuBar} from './setupMenuBar';
|
||||||
|
import {ElectronIpcClientMain} from './electronIpcMain';
|
||||||
|
|
||||||
const VERSION: string = (global as any).__VERSION__;
|
const VERSION: string = (global as any).__VERSION__;
|
||||||
|
|
||||||
@@ -347,14 +349,12 @@ function createWindow(config: Config) {
|
|||||||
? path.join(__dirname, 'icons/app_64x64.png')
|
? path.join(__dirname, 'icons/app_64x64.png')
|
||||||
: undefined,
|
: undefined,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
enableRemoteModule: true,
|
|
||||||
backgroundThrottling: false,
|
backgroundThrottling: false,
|
||||||
webSecurity: false,
|
webSecurity: false,
|
||||||
scrollBounce: true,
|
scrollBounce: true,
|
||||||
experimentalFeatures: true,
|
experimentalFeatures: true,
|
||||||
nodeIntegration: true,
|
nodeIntegration: true,
|
||||||
webviewTag: true,
|
webviewTag: true,
|
||||||
nativeWindowOpen: true,
|
|
||||||
contextIsolation: false,
|
contextIsolation: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -410,6 +410,9 @@ function createWindow(config: Config) {
|
|||||||
slashes: true,
|
slashes: true,
|
||||||
});
|
});
|
||||||
win.loadURL(entryUrl);
|
win.loadURL(entryUrl);
|
||||||
|
|
||||||
|
const electronIpcClient = new ElectronIpcClientMain(win);
|
||||||
|
setupMenuBar(electronIpcClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
function processConfig(config: Config) {
|
function processConfig(config: Config) {
|
||||||
|
|||||||
238
desktop/static/setupMenuBar.tsx
Normal file
238
desktop/static/setupMenuBar.tsx
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-imports
|
||||||
|
import electron, {MenuItemConstructorOptions, webFrame} from 'electron';
|
||||||
|
import {ElectronIpcClientMain} from './electronIpcMain';
|
||||||
|
|
||||||
|
export function setupMenuBar(electronIpcClient: ElectronIpcClientMain) {
|
||||||
|
function trackMenuItems(
|
||||||
|
menu: 'view' | 'root',
|
||||||
|
items: MenuItemConstructorOptions[],
|
||||||
|
) {
|
||||||
|
items.forEach((item) => {
|
||||||
|
if (item.label && item.click) {
|
||||||
|
electronIpcClient.send('menuItemAction', {
|
||||||
|
menu,
|
||||||
|
label: item.label,
|
||||||
|
action: 'click',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const template = getTemplate(trackMenuItems);
|
||||||
|
// create actual menu instance
|
||||||
|
const applicationMenu = electron.Menu.buildFromTemplate(template);
|
||||||
|
// update menubar
|
||||||
|
electron.Menu.setApplicationMenu(applicationMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTemplate(
|
||||||
|
trackMenuItems: (
|
||||||
|
menu: 'view' | 'root',
|
||||||
|
items: MenuItemConstructorOptions[],
|
||||||
|
) => void,
|
||||||
|
): Array<MenuItemConstructorOptions> {
|
||||||
|
const viewMenu: MenuItemConstructorOptions[] = [
|
||||||
|
{
|
||||||
|
label: 'Reload',
|
||||||
|
accelerator: 'CmdOrCtrl+R',
|
||||||
|
click: function (_, _focusedWindow: electron.BrowserWindow | undefined) {
|
||||||
|
window.location.reload();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Toggle Full Screen',
|
||||||
|
accelerator: (function () {
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
return 'Ctrl+Command+F';
|
||||||
|
} else {
|
||||||
|
return 'F11';
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
click: function (_, focusedWindow: electron.BrowserWindow | undefined) {
|
||||||
|
if (focusedWindow) {
|
||||||
|
focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Actual Size',
|
||||||
|
accelerator: (function () {
|
||||||
|
return 'CmdOrCtrl+0';
|
||||||
|
})(),
|
||||||
|
click: function (_, _focusedWindow: electron.BrowserWindow | undefined) {
|
||||||
|
webFrame.setZoomFactor(1);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Zoom In',
|
||||||
|
accelerator: (function () {
|
||||||
|
return 'CmdOrCtrl+=';
|
||||||
|
})(),
|
||||||
|
click: function (_, _focusedWindow: electron.BrowserWindow | undefined) {
|
||||||
|
webFrame.setZoomFactor(webFrame.getZoomFactor() + 0.25);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Zoom Out',
|
||||||
|
accelerator: (function () {
|
||||||
|
return 'CmdOrCtrl+-';
|
||||||
|
})(),
|
||||||
|
click: function (_, _focusedWindow: electron.BrowserWindow | undefined) {
|
||||||
|
webFrame.setZoomFactor(webFrame.getZoomFactor() - 0.25);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Toggle Developer Tools',
|
||||||
|
accelerator: (function () {
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
return 'Alt+Command+I';
|
||||||
|
} else {
|
||||||
|
return 'Ctrl+Shift+I';
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
click: function (_, focusedWindow: electron.BrowserWindow | undefined) {
|
||||||
|
if (focusedWindow) {
|
||||||
|
// @ts-ignore: https://github.com/electron/electron/issues/7832
|
||||||
|
focusedWindow.toggleDevTools();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
trackMenuItems('view', viewMenu);
|
||||||
|
|
||||||
|
const template: MenuItemConstructorOptions[] = [
|
||||||
|
{
|
||||||
|
label: 'Edit',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: 'Undo',
|
||||||
|
accelerator: 'CmdOrCtrl+Z',
|
||||||
|
role: 'undo',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Redo',
|
||||||
|
accelerator: 'Shift+CmdOrCtrl+Z',
|
||||||
|
role: 'redo',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Cut',
|
||||||
|
accelerator: 'CmdOrCtrl+X',
|
||||||
|
role: 'cut',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Copy',
|
||||||
|
accelerator: 'CmdOrCtrl+C',
|
||||||
|
role: 'copy',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Paste',
|
||||||
|
accelerator: 'CmdOrCtrl+V',
|
||||||
|
role: 'paste',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Select All',
|
||||||
|
accelerator: 'CmdOrCtrl+A',
|
||||||
|
role: 'selectAll',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'View',
|
||||||
|
submenu: viewMenu,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Window',
|
||||||
|
role: 'window',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: 'Minimize',
|
||||||
|
accelerator: 'CmdOrCtrl+M',
|
||||||
|
role: 'minimize',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Close',
|
||||||
|
accelerator: 'CmdOrCtrl+W',
|
||||||
|
role: 'close',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
const name = electron.app.name;
|
||||||
|
template.unshift({
|
||||||
|
label: name,
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: 'About ' + name,
|
||||||
|
role: 'about',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Services',
|
||||||
|
role: 'services',
|
||||||
|
submenu: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Hide ' + name,
|
||||||
|
accelerator: 'Command+H',
|
||||||
|
role: 'hide',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Hide Others',
|
||||||
|
accelerator: 'Command+Shift+H',
|
||||||
|
role: 'hideOthers',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Show All',
|
||||||
|
role: 'unhide',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Quit',
|
||||||
|
accelerator: 'Command+Q',
|
||||||
|
click: function () {
|
||||||
|
electron.app.quit();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const windowMenu = template.find(function (m) {
|
||||||
|
return m.role === 'window';
|
||||||
|
});
|
||||||
|
if (windowMenu) {
|
||||||
|
(windowMenu.submenu as MenuItemConstructorOptions[]).push(
|
||||||
|
{
|
||||||
|
type: 'separator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Bring All to Front',
|
||||||
|
role: 'front',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trackMenuItems('root', template);
|
||||||
|
|
||||||
|
return template;
|
||||||
|
}
|
||||||
1874
desktop/yarn.lock
1874
desktop/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user