electron/lib/browser/api/menu-item-roles.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

386 lines
9.8 KiB
TypeScript
Raw Normal View History

import { app, BrowserWindow, session, webContents, WebContents, MenuItemConstructorOptions } from 'electron/main';
2016-06-22 22:21:45 +00:00
const isMac = process.platform === 'darwin';
const isWindows = process.platform === 'win32';
const isLinux = process.platform === 'linux';
2020-07-06 20:24:54 +00:00
type RoleId = 'about' | 'close' | 'copy' | 'cut' | 'delete' | 'forcereload' | 'front' | 'help' | 'hide' | 'hideothers' | 'minimize' |
'paste' | 'pasteandmatchstyle' | 'quit' | 'redo' | 'reload' | 'resetzoom' | 'selectall' | 'services' | 'recentdocuments' | 'clearrecentdocuments' |
'showsubstitutions' | 'togglesmartquotes' | 'togglesmartdashes' | 'toggletextreplacement' | 'startspeaking' | 'stopspeaking' |
'toggledevtools' | 'togglefullscreen' | 'undo' | 'unhide' | 'window' | 'zoom' | 'zoomin' | 'zoomout' | 'togglespellchecker' |
'appmenu' | 'filemenu' | 'editmenu' | 'viewmenu' | 'windowmenu' | 'sharemenu'
2020-07-06 20:24:54 +00:00
interface Role {
label: string;
accelerator?: string;
checked?: boolean;
2020-07-06 20:24:54 +00:00
windowMethod?: ((window: BrowserWindow) => void);
webContentsMethod?: ((webContents: WebContents) => void);
appMethod?: () => void;
registerAccelerator?: boolean;
nonNativeMacOSRole?: boolean;
submenu?: MenuItemConstructorOptions[];
}
export const roleList: Record<RoleId, Role> = {
2016-06-22 20:15:20 +00:00
about: {
get label () {
return isLinux ? 'About' : `About ${app.name}`;
},
...((isWindows || isLinux) && { appMethod: () => app.showAboutPanel() })
2016-06-22 17:29:49 +00:00
},
2016-06-22 20:15:20 +00:00
close: {
label: isMac ? 'Close Window' : 'Close',
2016-06-22 22:26:17 +00:00
accelerator: 'CommandOrControl+W',
2020-07-06 20:24:54 +00:00
windowMethod: w => w.close()
2016-06-22 20:15:20 +00:00
},
copy: {
label: 'Copy',
2016-06-22 22:26:17 +00:00
accelerator: 'CommandOrControl+C',
2020-07-06 20:24:54 +00:00
webContentsMethod: wc => wc.copy(),
registerAccelerator: false
2016-06-22 17:29:49 +00:00
},
cut: {
label: 'Cut',
2016-06-22 22:26:17 +00:00
accelerator: 'CommandOrControl+X',
2020-07-06 20:24:54 +00:00
webContentsMethod: wc => wc.cut(),
registerAccelerator: false
2016-06-22 17:29:49 +00:00
},
2016-06-22 20:15:20 +00:00
delete: {
label: 'Delete',
2020-07-06 20:24:54 +00:00
webContentsMethod: wc => wc.delete()
2016-06-22 17:29:49 +00:00
},
2017-02-02 19:18:35 +00:00
forcereload: {
label: 'Force Reload',
accelerator: 'Shift+CmdOrCtrl+R',
nonNativeMacOSRole: true,
2020-07-06 20:24:54 +00:00
windowMethod: (window: BrowserWindow) => {
2017-02-02 19:18:35 +00:00
window.webContents.reloadIgnoringCache();
}
},
2016-06-22 20:15:20 +00:00
front: {
label: 'Bring All to Front'
2016-06-22 17:29:49 +00:00
},
2016-06-22 20:15:20 +00:00
help: {
label: 'Help'
2016-06-22 17:29:49 +00:00
},
2016-06-22 20:15:20 +00:00
hide: {
get label () {
return `Hide ${app.name}`;
2016-06-22 20:15:20 +00:00
},
accelerator: 'Command+H'
},
hideothers: {
label: 'Hide Others',
accelerator: 'Command+Alt+H'
2016-06-22 17:29:49 +00:00
},
minimize: {
label: 'Minimize',
2016-06-22 22:26:17 +00:00
accelerator: 'CommandOrControl+M',
2020-07-06 20:24:54 +00:00
windowMethod: w => w.minimize()
2016-06-22 17:29:49 +00:00
},
2016-06-22 20:15:20 +00:00
paste: {
label: 'Paste',
2016-06-22 22:26:17 +00:00
accelerator: 'CommandOrControl+V',
2020-07-06 20:24:54 +00:00
webContentsMethod: wc => wc.paste(),
registerAccelerator: false
2016-06-22 17:29:49 +00:00
},
2016-06-22 20:15:20 +00:00
pasteandmatchstyle: {
label: 'Paste and Match Style',
accelerator: isMac ? 'Cmd+Option+Shift+V' : 'Shift+CommandOrControl+V',
2020-07-06 20:24:54 +00:00
webContentsMethod: wc => wc.pasteAndMatchStyle(),
registerAccelerator: false
2016-06-22 17:29:49 +00:00
},
quit: {
get label () {
2016-06-22 22:26:17 +00:00
switch (process.platform) {
case 'darwin': return `Quit ${app.name}`;
2016-06-22 22:26:17 +00:00
case 'win32': return 'Exit';
default: return 'Quit';
}
2016-06-22 17:29:49 +00:00
},
accelerator: isWindows ? undefined : 'CommandOrControl+Q',
2020-07-06 20:24:54 +00:00
appMethod: () => app.quit()
2016-06-22 17:29:49 +00:00
},
2016-06-22 20:15:20 +00:00
redo: {
label: 'Redo',
accelerator: isWindows ? 'Control+Y' : 'Shift+CommandOrControl+Z',
2020-07-06 20:24:54 +00:00
webContentsMethod: wc => wc.redo()
2016-06-22 20:15:20 +00:00
},
2016-11-15 18:49:51 +00:00
reload: {
label: 'Reload',
accelerator: 'CmdOrCtrl+R',
2017-02-02 19:18:35 +00:00
nonNativeMacOSRole: true,
2020-07-06 20:24:54 +00:00
windowMethod: w => w.reload()
2016-11-15 18:49:51 +00:00
},
2016-08-08 17:09:45 +00:00
resetzoom: {
label: 'Actual Size',
accelerator: 'CommandOrControl+0',
2017-02-02 19:18:35 +00:00
nonNativeMacOSRole: true,
2020-07-06 20:24:54 +00:00
webContentsMethod: (webContents: WebContents) => {
webContents.zoomLevel = 0;
2016-08-08 17:09:45 +00:00
}
},
2016-06-22 20:15:20 +00:00
selectall: {
label: 'Select All',
2016-06-22 22:26:17 +00:00
accelerator: 'CommandOrControl+A',
2020-07-06 20:24:54 +00:00
webContentsMethod: wc => wc.selectAll()
2016-06-22 20:15:20 +00:00
},
services: {
label: 'Services'
},
2017-11-21 00:31:21 +00:00
recentdocuments: {
label: 'Open Recent'
},
2017-11-21 00:31:21 +00:00
clearrecentdocuments: {
label: 'Clear Menu'
},
showsubstitutions: {
label: 'Show Substitutions'
},
togglesmartquotes: {
label: 'Smart Quotes'
},
togglesmartdashes: {
label: 'Smart Dashes'
},
toggletextreplacement: {
label: 'Text Replacement'
},
startspeaking: {
label: 'Start Speaking'
},
stopspeaking: {
label: 'Stop Speaking'
},
2016-11-15 18:52:03 +00:00
toggledevtools: {
label: 'Toggle Developer Tools',
accelerator: isMac ? 'Alt+Command+I' : 'Ctrl+Shift+I',
2017-02-02 19:18:35 +00:00
nonNativeMacOSRole: true,
webContentsMethod: wc => {
const bw = wc.getOwnerBrowserWindow();
if (bw) bw.webContents.toggleDevTools();
}
2016-11-15 18:52:03 +00:00
},
2016-06-22 17:29:49 +00:00
togglefullscreen: {
label: 'Toggle Full Screen',
accelerator: isMac ? 'Control+Command+F' : 'F11',
2020-07-06 20:24:54 +00:00
windowMethod: (window: BrowserWindow) => {
2016-06-22 20:48:26 +00:00
window.setFullScreen(!window.isFullScreen());
}
2016-06-22 17:47:25 +00:00
},
2016-06-22 20:15:20 +00:00
undo: {
label: 'Undo',
2016-06-22 22:26:17 +00:00
accelerator: 'CommandOrControl+Z',
2020-07-06 20:24:54 +00:00
webContentsMethod: wc => wc.undo()
2016-06-22 20:15:20 +00:00
},
unhide: {
label: 'Show All'
2016-06-22 17:47:25 +00:00
},
window: {
label: 'Window'
2016-06-22 20:09:39 +00:00
},
zoom: {
label: 'Zoom'
2016-08-08 17:09:45 +00:00
},
zoomin: {
label: 'Zoom In',
accelerator: 'CommandOrControl+Plus',
2017-02-02 19:18:35 +00:00
nonNativeMacOSRole: true,
2020-07-06 20:24:54 +00:00
webContentsMethod: (webContents: WebContents) => {
webContents.zoomLevel += 0.5;
2016-08-08 17:09:45 +00:00
}
},
zoomout: {
label: 'Zoom Out',
accelerator: 'CommandOrControl+-',
2017-02-02 19:18:35 +00:00
nonNativeMacOSRole: true,
2020-07-06 20:24:54 +00:00
webContentsMethod: (webContents: WebContents) => {
webContents.zoomLevel -= 0.5;
2016-08-08 17:09:45 +00:00
}
},
togglespellchecker: {
label: 'Check Spelling While Typing',
get checked () {
const wc = webContents.getFocusedWebContents();
const ses = wc ? wc.session : session.defaultSession;
return ses.spellCheckerEnabled;
},
nonNativeMacOSRole: true,
webContentsMethod: (wc: WebContents) => {
const ses = wc ? wc.session : session.defaultSession;
ses.spellCheckerEnabled = !ses.spellCheckerEnabled;
}
},
// App submenu should be used for Mac only
appmenu: {
get label () {
return app.name;
},
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'services' },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideOthers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' }
]
},
// File submenu
filemenu: {
label: 'File',
submenu: [
isMac ? { role: 'close' } : { role: 'quit' }
]
},
// Edit submenu
2017-12-28 05:22:39 +00:00
editmenu: {
label: 'Edit',
submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
...(isMac ? [
{ role: 'pasteAndMatchStyle' },
{ role: 'delete' },
{ role: 'selectAll' },
{ type: 'separator' },
{
label: 'Substitutions',
submenu: [
{ role: 'showSubstitutions' },
{ type: 'separator' },
{ role: 'toggleSmartQuotes' },
{ role: 'toggleSmartDashes' },
{ role: 'toggleTextReplacement' }
]
},
{
label: 'Speech',
submenu: [
{ role: 'startSpeaking' },
{ role: 'stopSpeaking' }
]
}
2020-07-06 20:24:54 +00:00
] as MenuItemConstructorOptions[] : [
{ role: 'delete' },
{ type: 'separator' },
{ role: 'selectAll' }
2020-07-06 20:24:54 +00:00
] as MenuItemConstructorOptions[])
]
},
// View submenu
viewmenu: {
label: 'View',
submenu: [
{ role: 'reload' },
{ role: 'forceReload' },
{ role: 'toggleDevTools' },
{ type: 'separator' },
{ role: 'resetZoom' },
{ role: 'zoomIn' },
{ role: 'zoomOut' },
{ type: 'separator' },
{ role: 'togglefullscreen' }
]
},
// Window submenu
2017-12-28 05:22:39 +00:00
windowmenu: {
label: 'Window',
submenu: [
{ role: 'minimize' },
{ role: 'zoom' },
...(isMac ? [
{ type: 'separator' },
{ role: 'front' }
2020-07-06 20:24:54 +00:00
] as MenuItemConstructorOptions[] : [
{ role: 'close' }
2020-07-06 20:24:54 +00:00
] as MenuItemConstructorOptions[])
]
},
// Share submenu
sharemenu: {
label: 'Share',
submenu: []
2016-06-22 17:29:49 +00:00
}
};
const hasRole = (role: keyof typeof roleList) => {
return Object.hasOwn(roleList, role);
};
2020-07-06 20:24:54 +00:00
const canExecuteRole = (role: keyof typeof roleList) => {
if (!hasRole(role)) return false;
if (!isMac) return true;
2017-02-02 19:18:35 +00:00
// macOS handles all roles natively except for a few
2020-07-06 20:24:54 +00:00
return roleList[role].nonNativeMacOSRole;
2016-08-08 17:09:45 +00:00
};
export function getDefaultType (role: RoleId) {
if (shouldOverrideCheckStatus(role)) return 'checkbox';
return 'normal';
}
2020-07-06 20:24:54 +00:00
export function getDefaultLabel (role: RoleId) {
return hasRole(role) ? roleList[role].label : '';
2020-07-06 20:24:54 +00:00
}
export function getCheckStatus (role: RoleId) {
if (hasRole(role)) return roleList[role].checked;
}
export function shouldOverrideCheckStatus (role: RoleId) {
return hasRole(role) && Object.hasOwn(roleList[role], 'checked');
}
2020-07-06 20:24:54 +00:00
export function getDefaultAccelerator (role: RoleId) {
if (hasRole(role)) return roleList[role].accelerator;
2020-07-06 20:24:54 +00:00
}
2016-06-22 20:48:26 +00:00
2020-07-06 20:24:54 +00:00
export function shouldRegisterAccelerator (role: RoleId) {
const hasRoleRegister = hasRole(role) && roleList[role].registerAccelerator !== undefined;
2020-07-06 20:24:54 +00:00
return hasRoleRegister ? roleList[role].registerAccelerator : true;
}
2020-07-06 20:24:54 +00:00
export function getDefaultSubmenu (role: RoleId) {
if (!hasRole(role)) return;
2020-07-06 20:24:54 +00:00
let { submenu } = roleList[role];
2017-03-29 19:27:59 +00:00
// remove null items from within the submenu
if (Array.isArray(submenu)) {
submenu = submenu.filter((item) => item != null);
}
2017-03-29 19:27:59 +00:00
return submenu;
2020-07-06 20:24:54 +00:00
}
2020-07-06 20:24:54 +00:00
export function execute (role: RoleId, focusedWindow: BrowserWindow, focusedWebContents: WebContents) {
2016-08-08 17:09:45 +00:00
if (!canExecuteRole(role)) return false;
2016-06-22 20:48:26 +00:00
2020-07-06 20:24:54 +00:00
const { appMethod, webContentsMethod, windowMethod } = roleList[role];
2016-06-22 20:48:26 +00:00
if (appMethod) {
2020-07-06 20:24:54 +00:00
appMethod();
2016-06-22 20:48:26 +00:00
return true;
}
2016-06-22 21:14:32 +00:00
if (windowMethod && focusedWindow != null) {
2020-07-06 20:24:54 +00:00
windowMethod(focusedWindow);
2016-06-22 21:14:32 +00:00
return true;
}
if (webContentsMethod && focusedWebContents != null) {
2020-07-06 20:24:54 +00:00
webContentsMethod(focusedWebContents);
2016-06-22 21:14:32 +00:00
return true;
2016-06-22 20:48:26 +00:00
}
return false;
2020-07-06 20:24:54 +00:00
}