Menús multiplataforma en Electron js

- Andrés Cruz

In english
Menús multiplataforma en Electron js

La estructura de los menús no es más que un array de objetos con una estructura predefinida; podemos definir las opciones en el menú de dos tipos, personalizadas y con roles predefinidos como presentaremos en los siguientes apartados.

Un menú clásico luce de la siguiente manera:

const { Menu } = require('electron')

const template = [
   ***
   {
       label: 'About us',
       submenu: [
           ***
           {
               label: "About the app",
               click() {
                   //console.log("Hello world")
                   // TO DO
               }
           },

       ]
   },
]

Cada menú está acompañado de un submenú en el cual indicamos los ítems del menú, que pueden ser personalizados o de roles predefinidos que veremos más adelante; en definitiva y como puedes ver en la estructura anterior, un menú está compuesto de un array, por lo tanto,  podemos colocar tantos ítems en nuestro menú como necesitemos; a su vez, la propiedad de submenus es un array asi que, puedes indicar tantos items como desees.

Volviendo a la clasificación o opciones, tenemos dos:

Opciones personalizadas

La idea principal de los menús es que, podamos incluir opciones personalizadas para poder usar a lo largo de la aplicación;  por ende, se usan como un evento de tipo click en JavaScript asociado a un botón (por ejemplo) pero, en este caso, en vez de un botón, es una opción en un menú:

const { Menu, shell } = require('electron')

const template = [
   {
       role: 'About Electron',
       submenu: [
           {
               label: "About the app",
               click() {
                   shell.openExternal("https://www.electronjs.org")
               }
           },

       ]
   },
]

Con el shell, podemos abrir una página web en un enlace, como si se tratase de un enlace de navegación.

Su uso es muy sencillo, y son las que empleamos cuando queremos usar algún método personalizado.
Roles predefinidos
Con los roles ya contamos con funciones específicas que podemos reutilizar fácilmente:

{ role: 'reload' },
{ role: 'forceReload' },
{ role: 'toggleDevTools' },
{ role: 'resetZoom' },
{ role: 'zoomIn' },
{ role: 'zoomOut' },
{ role: 'togglefullscreen' }

Los cuales permiten:

  • reload, Recargar la página.
  • forceReload, Forzar la recarga de la página (vienen siendo un host reloading).
  • toggleDevTools, Activar la consola de desarrolladores del navegador.
  • resetZoom, Restablecer el zoom.
  • zoomIn, Zoom In, aumentar el zoom.
  • zoomOut, Zoom Out, Reducir el zoom.
  • togglefullscreen, Habilita el modo full screen de la aplicación.

También tenemos el separador para organizar los menús:

{
   type:'separator'
},

Finalmente, un menú luce como el siguiente:

menu
menu

Como puedes ver, al ser una estructura grande, lo mejor es definirlo en un archivo aparte:

menu.js

const { app, ipcMain, Menu, shell, BrowserWindow, globalShortcut } = require('electron')
const { open_file, save_file } = require("./editor-options")
const template = [
   {
       label: 'About us',
       click() {
           shell.openExternal("https://www.electronjs.org")
       }
   },
   {
       label: 'File',
       submenu: [
           {
               label: "Save",
               accelerator: 'CommandOrControl+Shift+S',
               click() {
                   const win = BrowserWindow.getFocusedWindow()
                   win.webContents.send('editorchannel', 'file-save')
               }
           },
           {
               label: "Open",
               accelerator: 'CommandOrControl+Shift+O',
               click() {
                   const win = BrowserWindow.getFocusedWindow()
                   open_file(win)
               }
           },
       ]
   },
   {
       label: 'Style and format',
       submenu: [
           {
               label: 'Bold',
               click() {
                   const win = BrowserWindow.getFocusedWindow()
                   win.webContents.send('editorchannel', 'style-bold')
                   console.log("Negritas")
               }
           },
           {
               label: 'Italic',
               click() {
                   const win = BrowserWindow.getFocusedWindow()
                   win.webContents.send('editor-channel', 'style-italic')
               }
           },
           {
               type: 'separator'
           },
           {
               label: 'H1',
               click() {
                   const win = BrowserWindow.getFocusedWindow()
                   win.webContents.send('editor-channel', 'style-h1')
               }
           },
           {
               label: 'H2',
               click() {
                   const win = BrowserWindow.getFocusedWindow()
                   win.webContents.send('editor-channel', 'style-h2')
               }
           },
       ]
   }
]
if (process.platform == 'win32' || process.platform == 'darwin') {
   template.push(
       {
           label: 'Default',
           submenu: [
               { role: 'reload' },
               { role: 'forceReload' },
               { role: 'toggleDevTools' },
               { role: 'resetZoom' },
               { role: 'zoomIn' },
               { role: 'zoomOut' },
               {
                   type: 'separator'
               },
               { role: 'togglefullscreen' },
           ]
       }
   )
}
ipcMain.on('editor-channel', (event, arg) => {
   console.log("Mensaje recibido del canal 'editor-channel': " + arg)
})
ipcMain.on('file-open', (event, arg) => {
   const win = BrowserWindow.getFocusedWindow()
   open_file(win)
})
ipcMain.on('file-save', (event, arg) => {
   const win = BrowserWindow.getFocusedWindow()
   save_file(win, arg)
})
app.on('ready', () => {
   globalShortcut.register('CommandOrControl+Shift+S', () => {
       const win = BrowserWindow.getFocusedWindow()
       win.webContents.send('editor-channel', 'file-save')
   })
   globalShortcut.register('CommandOrControl+Shift+O', () => {
       const win = BrowserWindow.getFocusedWindow()
       open_file(win)
   })
});
const menu = Menu.buildFromTemplate(template);
module.exports = menu

Para consumirlo, tenemos, desde el archivo principal:

const menu = require('./menu')
***
app.whenReady().then(createWindow)
Menu.setApplicationMenu(menu)

Y al ejecutar un:

npm run start

Tenemos:

menu
menu

Este contenido forma parte tanto de mi libro en Electron como de mi curso de Electron.

Andrés Cruz

Desarrollo con Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz en Udemy

Acepto recibir anuncios de interes sobre este Blog.