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.

Acepto recibir anuncios de interes sobre este Blog.

Vamos a aprender a manejar los menus multiplataforma en Electron.js.

- Andrés Cruz

In english

) )