Create Electron Menu in TypeScript?
For me the problem was the proper role capitalisation:
{ role: 'forceReload' },
{ role: 'toggleDevTools' }
over
{ role: 'forcereload' },
{ role: 'toggledevtools' }
See docs.
I couldn't get the sample that works in JS in TS. The MenuItemConstructorOptions
is an interface defined in the electron.d.ts
file in the electron package. However I found a workaround by defining the menu entries individually and push them to an empty array. Interestingly the submenu entries inside were accepted and worked without an issue. This is the code that worked:
function createMenu() {
const template = [];
// Edit Menu
template.push({
label: 'Edit',
submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
{ role: 'pasteandmatchstyle' },
{ role: 'delete' },
{ role: 'selectall' }
]
});
// View Menu
template.push({
label: 'View',
submenu: [
{ role: 'reload' },
{ role: 'forcereload' },
{ role: 'toggledevtools' },
{ type: 'separator' },
{ role: 'resetzoom' },
{ role: 'zoomin' },
{ role: 'zoomout' },
{ type: 'separator' },
{ role: 'togglefullscreen' }
]
});
// Windown menu
template.push({
role: 'window',
submenu: [{ role: 'minimize' }, { role: 'close' }]
});
// Help menu
template.push({
role: 'help',
submenu: [
{
label: 'Learn More',
click() {
require('electron').shell.openExternal('https://electron.atom.io');
}
}
]
});
if (process.platform === 'darwin') {
template.unshift({
label: app.getName(),
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'services', submenu: [] },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideothers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' }
]
});
// Edit menu
template[1].submenu.push(
{ type: 'separator' },
{ label: 'Speech', submenu: [{ role: 'startspeaking' }, { role: 'stopspeaking' }] }
);
// Window menu
template[3].submenu = [{ role: 'close' }, { role: 'minimize' }, { role: 'zoom' }, { type: 'separator' }, { role: 'front' }];
}
menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
}
Hope this helps if anybody stumbles over the same issue
I got the same error when I based my menu syntax off Electron's Menu
example, which works fine in JavaScript but upsets TypeScript. Unfortunately the MenuItemConstructorOptions[]
cast for template
as suggested by PhoneixS didn't work for me.
It looks like TypeScript trips on the ternaries in Electron's example and it can't figure out their resulting type. In my case, adding as MenuItemConstructorOptions[]
after the closing parentheses enclosing the ternaries made TypeScript realize they're valid submenus after all.
The full Electron example in valid TypeScript:
import { app, Menu, MenuItemConstructorOptions, shell } from "electron"
const isMac = process.platform === 'darwin'
const menu = Menu.buildFromTemplate(
[
// { role: 'appMenu' }
...(isMac ? [{
label: app.name,
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'services' },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideothers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' }
]
}] : []) as MenuItemConstructorOptions[],
// { role: 'fileMenu' }
{
label: 'File',
submenu: [
isMac ? { role: 'close' } : { role: 'quit' }
] as MenuItemConstructorOptions[]
},
// { role: '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: 'Speech',
submenu: [
{ role: 'startSpeaking' },
{ role: 'stopSpeaking' }
]
}
] : [
{ role: 'delete' },
{ type: 'separator' },
{ role: 'selectAll' }
]) as MenuItemConstructorOptions[]
]
},
// { role: 'viewMenu' }
{
label: 'View',
submenu: [
{ role: 'reload' },
{ role: 'forceReload' },
{ role: 'toggleDevTools' },
{ type: 'separator' },
{ role: 'resetZoom' },
{ role: 'zoomIn' },
{ role: 'zoomOut' },
{ type: 'separator' },
{ role: 'togglefullscreen' }
]
},
// { role: 'windowMenu' }
{
label: 'Window',
submenu: [
{ role: 'minimize' },
{ role: 'zoom' },
...(isMac ? [
{ type: 'separator' },
{ role: 'front' },
{ type: 'separator' },
{ role: 'window' }
] : [
{ role: 'close' }
]) as MenuItemConstructorOptions[]
]
},
{
role: 'help',
submenu: [
{
label: 'Learn More',
click: async () => {
await shell.openExternal('https://electronjs.org')
}
}
]
}
]
)
Menu.setApplicationMenu(menu)
For me was enough to set the type of template const to Electron.MenuItemConstructorOptions[]
.
For example:
const template: Electron.MenuItemConstructorOptions[] = [{
label: 'Edit',
submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
{ role: 'pasteandmatchstyle' },
{ role: 'delete' },
{ role: 'selectall' }
]
},
{
label: 'View',
submenu: [
{ role: 'reload' },
{ role: 'forcereload' },
{ role: 'toggledevtools' },
{ type: 'separator' },
{ role: 'resetzoom' },
{ role: 'zoomin' },
{ role: 'zoomout' },
{ type: 'separator' },
{ role: 'togglefullscreen' }
]
},
{ role: 'window', submenu: [{ role: 'minimize' }, { role: 'close' }] },
{
role: 'help',
submenu: [{
label: 'Learn More',
click() {
require('electron').shell.openExternal('https://electron.atom.io');
}
}]
}
];