Skip to content

Commit e5cc6cb

Browse files
huntiefacebook-github-bot
authored andcommitted
Configure additional app menu items
Summary: UX polish: Refine RNDT desktop app with new menu items mapping to key features inside the DevTools UI, and adding menu item icons (macOS 26+). **New/changed menu items** - **[New] File > Reload App** - Unify cmd+R logic — explicitly bound to "Reload App". - **[New] File > Reload DevTools** - opt+R — reinstates above - Note: Reloading and force-reloading the debugger-frontend's UI can also be done with cmd+R and cmd+shift+R *from a DevTools-on-DevTools window*. - **[New] File > QuickOpen…** - - **[New] View > Command Palette…** - - **[Changed] View > Toggle Developer Tools** - Shown in debug builds only. Delete Reload and Force Reload items here (per above). - Simplify Edit menu options. **Notes** - Icons are configured on macOS 26+ only, and we've left the `{role: '<name>'}` icon templates alone (should be managed upstream in Electron, although today these icons are missing in some cases). See electron/electron#50609, electron/electron#48909. Changelog: [General][Added] - **React Native DevTools**: Expose new options in the app menu Differential Revision: D94527813
1 parent aee01cf commit e5cc6cb

3 files changed

Lines changed: 182 additions & 29 deletions

File tree

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
const {BrowserWindow, Menu, app, nativeImage, shell} =
12+
// $FlowFixMe[unclear-type] We have no Flow types for the Electron API.
13+
require('electron') as any;
14+
15+
const {isMacOSAtLeast} = require('./utils');
16+
17+
export function configureAppMenu(): void {
18+
const template = [
19+
...(process.platform === 'darwin' ? [{role: 'appMenu'}] : []),
20+
{
21+
label: 'File',
22+
submenu: [
23+
{
24+
label: 'Reload App',
25+
...menuSymbol('arrow.clockwise'),
26+
accelerator:
27+
process.platform === 'darwin' ? 'Command+R' : 'Control+R',
28+
click: () => invokeCommand('inspector-main.reload'),
29+
},
30+
{
31+
label: 'Reload DevTools',
32+
...menuSymbol('none'),
33+
accelerator: process.platform === 'darwin' ? 'Option+R' : 'Alt+R',
34+
click: () => BrowserWindow.getFocusedWindow()?.webContents.reload(),
35+
},
36+
{type: 'separator'},
37+
{
38+
label: 'Quick Open…',
39+
...menuSymbol('doc.text.magnifyingglass'),
40+
accelerator:
41+
process.platform === 'darwin' ? 'Command+P' : 'Control+P',
42+
click: () => invokeCommand('quick-open.show'),
43+
},
44+
{type: 'separator'},
45+
{role: 'close'},
46+
],
47+
},
48+
{
49+
label: 'Edit',
50+
submenu: [
51+
{role: 'undo'},
52+
{role: 'redo'},
53+
{type: 'separator'},
54+
{role: 'cut'},
55+
{role: 'copy'},
56+
{role: 'paste'},
57+
{role: 'selectAll'},
58+
],
59+
},
60+
{
61+
label: 'View',
62+
submenu: [
63+
{
64+
label: 'Command Palette…',
65+
...menuSymbol('filemenu.and.selection'),
66+
accelerator:
67+
process.platform === 'darwin'
68+
? 'Command+Shift+P'
69+
: 'Control+Shift+P',
70+
click: () => invokeCommand('quick-open.show-command-menu'),
71+
},
72+
{type: 'separator'},
73+
// Enable Developer Tools only in development
74+
...(!app.isPackaged
75+
? [{type: 'separator'}, {role: 'toggleDevTools'}]
76+
: []),
77+
{type: 'separator'},
78+
{role: 'resetZoom'},
79+
{role: 'zoomIn'},
80+
{role: 'zoomOut'},
81+
{type: 'separator'},
82+
{role: 'togglefullscreen'},
83+
],
84+
},
85+
{role: 'windowMenu'},
86+
{
87+
role: 'help',
88+
submenu: [
89+
{
90+
label: 'Keyboard Shortcuts',
91+
...menuSymbol('keyboard'),
92+
click: () => invokeCommand('settings.shortcuts'),
93+
},
94+
{type: 'separator'},
95+
{
96+
label: 'React Native Website',
97+
...menuSymbol('text.rectangle.page'),
98+
click: () => shell.openExternal('https://reactnative.dev'),
99+
},
100+
{
101+
label: 'Release Notes',
102+
...menuSymbol('none'),
103+
click: () =>
104+
shell.openExternal(
105+
'https://github.com/facebook/react-native/releases',
106+
),
107+
},
108+
],
109+
},
110+
];
111+
const menu = Menu.buildFromTemplate(template);
112+
Menu.setApplicationMenu(menu);
113+
}
114+
115+
function menuSymbol(symbolName: string): {icon?: unknown} {
116+
if (!isMacOSAtLeast(26)) {
117+
return {};
118+
}
119+
return {
120+
icon:
121+
symbolName === 'none'
122+
? createMenuSpacer()
123+
: nativeImage.createMenuSymbol(symbolName),
124+
};
125+
}
126+
127+
function createMenuSpacer() {
128+
const size = 16;
129+
const buf = Buffer.alloc(size * size * 4);
130+
131+
for (let i = 0; i < buf.length; i += 4) {
132+
buf[i] = 0; // R
133+
buf[i + 1] = 0; // G
134+
buf[i + 2] = 0; // B
135+
buf[i + 3] = 1; // A
136+
}
137+
138+
const spacer = nativeImage.createFromBitmap(buf, {
139+
width: size,
140+
height: size,
141+
});
142+
143+
// On macOS, mark as template so it lives in the same gutter as other
144+
// template icons and adapts to light/dark menu bars.
145+
if (process.platform === 'darwin') {
146+
spacer.setTemplateImage(true);
147+
}
148+
149+
return spacer;
150+
}
151+
152+
function invokeCommand(commandId: string): void {
153+
const win = BrowserWindow.getFocusedWindow();
154+
win.webContents.executeJavaScript(
155+
`(async () => {
156+
const UI = await import('./ui/legacy/legacy.js');
157+
return UI.ActionRegistry.ActionRegistry.instance()
158+
.getAction(${JSON.stringify(commandId)})?.execute();
159+
})()`,
160+
true,
161+
);
162+
}

packages/debugger-shell/src/electron/MainInstanceEntryPoint.js

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
* @format
99
*/
1010

11+
import {configureAppMenu} from './AppMenu.js';
1112
import SettingsStore from './SettingsStore.js';
1213

1314
const path = require('path');
1415
const util = require('util');
1516

1617
// $FlowFixMe[unclear-type] We have no Flow types for the Electron API.
17-
const {BrowserWindow, Menu, app, shell, ipcMain} = require('electron') as any;
18+
const {BrowserWindow, app, shell, ipcMain} = require('electron') as any;
1819

1920
const appSettings = new SettingsStore();
2021
const windowMetadata = new WeakMap<
@@ -102,34 +103,6 @@ function handleLaunchArgs(argv: string[]) {
102103
frontendWindow.focus();
103104
}
104105

105-
function configureAppMenu() {
106-
const template = [
107-
...(process.platform === 'darwin' ? [{role: 'appMenu'}] : []),
108-
{role: 'fileMenu'},
109-
{role: 'editMenu'},
110-
{role: 'viewMenu'},
111-
{role: 'windowMenu'},
112-
{
113-
role: 'help',
114-
submenu: [
115-
{
116-
label: 'React Native Website',
117-
click: () => shell.openExternal('https://reactnative.dev'),
118-
},
119-
{
120-
label: 'Release Notes',
121-
click: () =>
122-
shell.openExternal(
123-
'https://github.com/facebook/react-native/releases',
124-
),
125-
},
126-
],
127-
},
128-
];
129-
const menu = Menu.buildFromTemplate(template);
130-
Menu.setApplicationMenu(menu);
131-
}
132-
133106
function getSavedWindowPosition(
134107
windowKey: string,
135108
): ?{width: number, height: number, x?: number, y?: number} {
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
/** Equivalent of Swift's `if #available(macOS 26, *)`. */
12+
export function isMacOSAtLeast(major: number): boolean {
13+
return (
14+
process.platform === 'darwin' &&
15+
// $FlowFixMe[prop-missing]
16+
Number.parseInt(process.getSystemVersion().split('.')[0], 10) >= major
17+
);
18+
}

0 commit comments

Comments
 (0)