Native Menus not showing OS X Electron - javascript

I used the electron-quick-start to create an Electron app, and I want the only native menu to show to be the 'Edit' menu, with the usual suspects inside.
However, after searching and exhausting all relevant Google results for 'electron menu not working', I'm at a loss.
My current main.js file:
const {app, Menu, BrowserWindow} = require('electron')
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow;
app.setName('mathulator');
function createWindow () {
// Create the browser window.
mainWindow = new BrowserWindow({width: 900, height: 550})
// and load the index.html of the app.
mainWindow.loadURL(`file://${__dirname}/index.html`)
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
const template = [
{
label: 'Mathulator',
submenu: [
{
role: 'quit'
}
]
},
{
label: 'Edit',
submenu: [
{
role: 'undo'
},
{
role: 'redo'
},
{
type: 'separator'
},
{
role: 'cut'
},
{
role: 'copy'
},
{
role: 'paste'
},
{
role: 'pasteandmatchstyle'
},
{
role: 'delete'
},
{
role: 'selectall'
}
]
}
]
mainWindow.setMenu(Menu.buildFromTemplate(template))
// Emitted when the window is closed.
mainWindow.on('closed', function () {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null
})
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)
// Quit when all windows are closed.
app.on('window-all-closed', function () {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', function () {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow()
}
})
I've also packaged it up with electron-packager, to no avail.
I'm running it in the main.js file, which from what I can gather from the masses of either vague or convoluted information around the web, is the main process and therefore one in which I should create the menus.
I also tried doing it in render.js, which I saw suggested. To no avail. It'll either show up with the default electron-quick-start menu, or just a simple menu named after the app, containing one item labelled 'Quit'.
What might I be doing wrong, and what might I have misunderstood from the available information?
Edit: I actually attached the wrong file, tried using Menu.setApplicationMenu() the first time, like so:
const {app, Menu, BrowserWindow} = require('electron')
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow;
app.setName('mathulator');
function createWindow () {
// Create the browser window.
mainWindow = new BrowserWindow({width: 900, height: 550});
// and load the index.html of the app.
mainWindow.loadURL(`file://${__dirname}/index.html`);
// Emitted when the window is closed.
mainWindow.on('closed', function () {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null;
});
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow);
// Quit when all windows are closed.
app.on('window-all-closed', function () {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit();
}
})
app.on('activate', function () {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow();
}
})
const template = [
{
label: 'Mathulator',
submenu: [
{
role: 'quit'
}
]
},
{
label: 'Edit',
submenu: [
{
role: 'undo'
},
{
role: 'redo'
},
{
type: 'separator'
},
{
role: 'cut'
},
{
role: 'copy'
},
{
role: 'paste'
},
{
role: 'pasteandmatchstyle'
},
{
role: 'delete'
},
{
role: 'selectall'
}
]
}
];
Menu.setApplicationMenu(Menu.buildFromTemplate(template));

The issue here is that BrowserWindow.setMenu() is only available on Windows and Linux. On macOS you should use Menu.setApplicationMenu().

Note that on OSX the menu is not on the window itself but on the top of dosktop.
I lost bunch of time trying to troubleshoot why it was not showing up.

As #Vadim Macagon stated in comment, make sure that the call to Menu.setApplicationMenu() is in createWindow(). For me it fixed the problem.

maybe you set LSUIElement to 1, that means an agent app, that is, an app that should not appear in the Dock. Change the LSUIElement to 0, the build app's menu will show up.
electron build config
mac: {
icon: 'build/icons/icon.icons',
extendInfo: {
LSUIElement: 0
}
}
detail of LSUIElement is here
https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/LaunchServicesKeys.html#//apple_ref/doc/uid/20001431-108256

Related

Converting a React + Redux + MUI Menu to Electron

I am attempting to convert a web-based React + Redux + MUI app to Electron. The app has a main AppBar with multiple dropdown menus, with menu items which hook into the app's Redux store. So a fairly typical set-up for these technologies in partnership, but I'm struggling to understand how to translate this (if it's possible) to an Electron application menu.
So if I have a MUI MenuItem with a typical onClick handler, like this:
const [soneState, setSomeState] = useState();
const handleOnClick = (e) => {
const val = e.target.value;
console.log(`The value is ${val}`);
setSomeState(val);
}
What would be the equivalent for an Electron Menu? Assuming also that I am leveraging the Redux store, rather than local component state. A number of the handlers in the menu communicate with an Express server via fetch. I have been reading up on Electron inter-process communication via the contextBridge, but I'm not sure which side the Electron Menu comes in that equation. Can it leverage both the Redux store as well as talking to the Main process? I assume I can't make fetch calls from the Menu?
OK I think I figured it out...
I can use the pattern described in the official docs here to create an API in my preload.js, which the app menu can then call into to send messages to the render process. So in preload.js I have:
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('api', {
loadSomeFile: (callback) => ipcRenderer.on('load-some-file', callback),
})
Then I define my menu like this:
const { app, Menu, MenuItem, dialog, ipcMain } = require('electron');
module.exports = (window) => {
return Menu.buildFromTemplate([
{
label: 'File',
submenu: [
{
label: 'About',
},
{
label: 'Preferences',
},
{
type: 'separator'
},
{
label: 'Load Some File',
click() {
dialog.showOpenDialog({
properties: ['openFile']
})
.then((fileObj) => {
window.webContents.send('load-some-file', fileObj);
})
}
},
{
type: 'separator'
},
{
label: 'Exit',
click() {
app.quit()
}
}
]
},
])
}
And in my main.js I have:
const { app, BrowserWindow, Menu, } = require('electron');
const path = require("path");
const mainMenu = require("./app/main-menu");
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false,
contextIsolation: true
}
});
win.loadURL("http://localhost:8080");
return win;
}
app.whenReady().then(() => {
const win = createWindow();
Menu.setApplicationMenu(mainMenu(win));
});
Most of what I already have in my React/Redux/MUI frontend code can remain unchanged. But I need to access the api from the window object on the frontend, and listen to events coming from the Main process (like from the app menu).
window.api.loadSomeFile((event, data) => {
console.log(data);
});
Tested it and it works well. Very little to actually change in my code.

getting multiple windows in electronJS which has the same browserwindow instance to display different results

I am trying to send data from a main window to multiple windows in electronJS.
How my app works is there is a main window with many selections. On clicking each selection, a new window will open, and the window will show data that is related to that selection.
The problem I am facing is that the data does not persist and keeps getting updated each time I click a new selection.
For example:
Main window: selection A,B,C,D
clicks on A
New window 1 pops up, displays data for A
clicks on B
New window 2 pops up, displays data for B. New window 1 displays data for B.
clicks on C
New window 3 pops up, displays data for C. New window 1 & 2 displays data for C.
I think the issue has got to do with the browserwindow instance, but I'm not quite sure how to fix it, because I won't know how many new windows would be opened. Is there a way to 'freeze' the data at the point in time for each instance?
I include my code for both my renderers and main.js. Data is passed from renderer(index) to main and then to renderer(instrument)
main.js
// main.js
// Modules to control application life and create native browser window
const { app, BrowserWindow, ipcMain } = require('electron')
const createWindow = () => {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 1000,
height: 1000,
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
nodeIntegrationInWorker: true
}
})
// and load the index.html of the app.
mainWindow.loadFile('index.html')
// Open the DevTools.
mainWindow.webContents.openDevTools()
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
createWindow()
ipcMain.on('open-instrument-window', (event, instrument) => {
openNewInstrumentWindow()
})
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
const openNewInstrumentWindow = () => {
const instrumentWindow = new BrowserWindow({
width: 1000,
height: 1000,
title: 'Instrument details',
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
nodeIntegrationInWorker: true
}
})
instrumentWindow.loadFile('instrument.html')
instrumentWindow.webContents.openDevTools()
// this is where the data is send over to the renderer instrument
ipcMain.on('a', (event, payload) => {
instrumentWindow.webContents.send('your-relayed-message', payload);
});
}
this is my renderer index code. I have extracted the relevant portion. ipcrenderer is used twice, first to open the new window, second is to send the data over to renderer instrument.
const searchResults = document.getElementsByClassName('search-result');
for (let i = 0; i < searchResults.length; i++) {
searchResults[i].onclick = () => {
ipcRenderer.send('open-instrument-window')
ipcRenderer.send('a', instruments[i]);
}
}
this is my renderer instrument code.
const getInstrumentDetail = (exchange) => {
axios.get(`http://url${exchange}`)
.then(response => {
let instrument = response.data;
document.getElementById('instrument-name').innerHTML = instrument.name;
})
}
ipcRenderer.on('your-relayed-message', (event, payload) => {
getInstrumentDetail(payload['exchange']);
});
Appreciate any tips or guidance to point me in the right direction.

Memory leak when emitting an event after menu click in Electron app

I'm currently building a desktop application with Electron and React.
Right now I'm adding a menu feature which toggles the dark mode of the app. In my React app, I'm using a hook which toggles the dark mode. I want to trigger that React hook right after the user has clicked on the menu item.
This is what I've done so far:
menu.ts:
buildDefaultTemplate() {
const templateDefault = [
{
label: '&File',
submenu: [
{
label: '&Open',
accelerator: 'Ctrl+O',
},
{
label: '&Toggle Dark Mode',
accelerator: 'Ctrl+T',
click: () => {
this.mainWindow.webContents.send('toggle-dark-mode', {
message: 'Toggle successful!',
});
},
},
{
label: '&Close',
accelerator: 'Ctrl+W',
click: () => {
this.mainWindow.close();
},
},
],
},
]
Dashboard.tsx:
export default function Dashboard(): ReactElement {
const { username } = os.userInfo();
const { toggleColorMode } = useColorMode();
useEffect(() => {
ipcRenderer.on('toggle-dark-mode', () => {
toggleColorMode();
});
}, [toggleColorMode]);
It works fine toggling it. But after repeating the operation a number of times, I get this warning: MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 toggle-dark-mode listeners added to [EventEmitter]. Use emitter.setMaxListeners() to increase limit
I've seen a similar post but the answer wasn't satisfying. The suggestion there was to simply stop listening to events, which I think would be difficult in my case.
I'm looking for a way to unsubscribe to the event after the toggle has been successful.
Try setting up the toggle-dark-mode event handler once when you start your Electron app.
Your code doesn't need to be in the ready event even.

How to quit application using command line arguments inside a Task

I am using electron 5.0.0 and I am trying to use windows JumpList and the Task category to quit my electron application.
{
program: process.execPath,
arguments: '--new-window',
iconPath: process.execPath,
iconIndex: 0,
title: 'New Window',
description: 'Create a new window'
}
])
I am trying to modify the example code from the electron website and i need to change the arguments
"arguments String - The command line arguments when program is executed."
I know windows has built in arguments like --new-window
So my question is does windows have something that will quit the application or do i need to make a custom argument if so how would i go about doing that
I want it to have the same functionality of skype see image
EDIT:
I tried using second-instance event but it does not seem to be called when the user clicks on the task
app.setUserTasks([
{
program: process.execPath,
arguments: '--force-quit',
iconPath: process.execPath,
iconIndex: 0,
title: 'Force Quit App',
description: 'This will close the app instead of minimizing it.'
}
])
app.on('second-instance', (e, argv)=>{
console.log("secinst" + argv)
if(argv === '--force-quit'){
win.destroy();
}
})
If you set tasks like this:
app.setUserTasks([
{
program: process.execPath,
arguments: '--force-quit',
iconPath: process.execPath,
iconIndex: 0,
title: 'Force Quit App',
description: 'This will close the app instead of minimizing it.'
}
])
When clicked, this will launch a new instance of your application with the command line argument --force-quit. You should handle that argument.
Your use case makes sense only if you allow a single instance of your application to be running. You need to get argv from the second-instance event.
const { app } = require('electron')
let myWindow = null
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
app.quit()
} else {
app.on('second-instance', (event, argv, workingDirectory) => {
// Someone tried to run a second instance
const forceQuit = argv.indexOf("--force-quit") > -1;
if (forceQuit) app.quit()
})
// Create myWindow, load the rest of the app, etc...
app.on('ready', () => {
})
}

Why does a packaged Electron app fail due to Menu.getApplicationMenu() returning null while running it with electron . is fine?

I get a strange error when running a packaged Electron app on Windows 10.
When I press Ok on this error, the application boots up, but without a menu. If I run the same application using electron . it works perfectly, it has a menu.
The error occurs here:
var filemenu = Menu.getApplicationMenu().items[0].submenu;
filemenu.items[0].visible = false;
filemenu.append(new MenuItem({ label: 'Build Project', click: function () { buildProject(); } }));
I'm trying to edit the default File menu and add a "Build Project" item.
This is my first attempt to package an Electron app so I welcome any feedback as to what went wrong?
It seems that the default menu is not added to the app when in production.
The solution is to check if you are in development:
https://www.npmjs.com/package/electron-is-dev
// Check if we are in development
var isDev = require('electron-is-dev');
If in production, you have to construct the menu from scratch.
if(isDev){
// In development
// modify existing menu
}else{
// In production
// construct menu from scratch
var template = [
{
label: "File",
submenu: [
{
label: "Exit",
click: function () { quit(); }
}
]
},
{
label: "Project",
submenu: [
{
label: "Delete",
click: function () { deleteProject(); }
},
{
label: "Build",
click: function () { buildProject(); }
}
]
}
];
// build menu from template
var menu = Menu.buildFromTemplate(template);
// set menu for main window
mainWindow.setMenu(menu);
};

Categories