Skip to content

Advanced examples

Samuel Leroy edited this page Feb 4, 2022 · 15 revisions

Electron

Use without @electron/remote

This setup will allow you to use this library without @electron/remote. Thanks to jjeff for proposing this solution.

main.js

const { app, ipcMain, BrowserWindow, Menu} = require('electron');
const path = require('path');

function createWindow() {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    frame: false,
    webPreferences: {
      contextIsolation: true,
      preload: path.join(__dirname, 'preload.js'),
    },
  });

  win.loadFile('index.html');
}

app.whenReady().then(() => {
  createWindow();
});

ipcMain.on('request-application-menu', (event) => sendApplicationMenu(event.sender));

ipcMain.on('menu-event', (event, commandId) => {
  getMenuItemByCommandId(commandId)?.click(undefined, BrowserWindow.fromWebContents(event.sender), event.sender);
});

ipcMain.on('window-event', (event, arg) => {
  const window = BrowserWindow.fromWebContents(event.sender);
  switch (arg) {
    case 'minimize':
      window.minimize();
      break;
    case 'maximize':
      window.isMaximized() ? window.unmaximize() : window.maximize();
      break;
    case 'close':
      window.close();
      break;
  }
});

ipcMain.on('window-state', (event) => {
  event.returnValue = BrowserWindow.fromWebContents(event.sender).isMaximized();
});

const sendApplicationMenu = (webContents) => {
  const appMenu = Menu.getApplicationMenu();

  setDefaultRoleAccelerators(appMenu);

  // Strip functions, maps and circular references (for IPC)
  const menu = JSON.parse(JSON.stringify(appMenu, getCircularReplacer()));

  // Send menu structure to renderer
  webContents.send('application-menu', menu);
};

const getCircularReplacer = () => {
  const seen = new WeakSet();
  return (key, value) => {
    if (key === 'commandsMap') return;
    if (typeof value === 'object' && value !== null) {
      if (seen.has(value)) {
        return;
      }
      seen.add(value);
    }
    return value;
  };
};

const getMenuItemByCommandId = (commandId, menu = Menu.getApplicationMenu()) => {
  for (let i = 0; i < menu.items.length; i++) {
    const item = menu.items[i];
    if (item.commandId === commandId) {
      return item;
    } else if (item.submenu) {
      const result = getMenuItemByCommandId(commandId, item.submenu);
      if (result) {
        return result;
      }
    }
  }
};

const setDefaultRoleAccelerators = (menu) => {
  for (let i = 0; i < menu.items.length; i++) {
    const item = menu.items[i];
    if (item.role && item.getDefaultRoleAccelerator) {
      item.defaultRoleAccelerator = item.getDefaultRoleAccelerator();
    }
    if (item.submenu) {
      setDefaultRoleAccelerators(item.submenu);
    }
  }
};

preload.js

const { ipcRenderer } = require('electron');
const Titlebar = require('@6c65726f79/custom-titlebar');

let titlebar;

window.addEventListener('DOMContentLoaded', () => {
  titlebar = new Titlebar({
    onMinimize: () => ipcRenderer.send('window-event', 'minimize'),
    onMaximize: () => ipcRenderer.send('window-event', 'maximize'),
    onClose: () => ipcRenderer.send('window-event', 'close'),
    isMaximized: () => ipcRenderer.sendSync('window-state'),
    menuItemClickHandler: (commandId) => ipcRenderer.send('menu-event', commandId)
  });

  ipcRenderer.send('request-application-menu');
});

ipcRenderer.on('application-menu', (event, appMenu) => {
  titlebar.updateOptions({menu: appMenu});
});

Use the titlebar in all windows

main.js

const { initialize, enable } = require('@electron/remote/main');
const { app, BrowserWindow } = require('electron');
const path = require('path');

initialize();

function createWindow() {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    frame: false,
    webPreferences: {
      nativeWindowOpen: true,
      preload: path.join(__dirname, 'preload.js'),
    },
  });

  enable(win.webContents);
  setWindowOpenHandler(win);

  win.loadFile('index.html');
}

app.whenReady().then(() => {
  createWindow();
});

const setWindowOpenHandler = (window) => {
  window.webContents.setWindowOpenHandler(({ url }) => {
    return {
      action: 'allow',
      overrideBrowserWindowOptions: {
        frame: false,
        webPreferences: {
          nativeWindowOpen: true,
          preload: path.join(__dirname, 'preload.js'),
        },
      },
    };
  });

  window.webContents.on('did-create-window', (childWindow) => {
    enable(childWindow.webContents);
    setWindowOpenHandler(childWindow);
  });
};

preload.js

const { Menu, BrowserWindow, webContents, getCurrentWindow } = require('@electron/remote');
const Titlebar = require('@6c65726f79/custom-titlebar');

const currentWindow = getCurrentWindow();
let titlebar;

currentWindow.webContents.once('dom-ready', () => {
  titlebar = new Titlebar({
    menu: Menu.getApplicationMenu(),
    browserWindow: currentWindow,
    onMinimize: () => currentWindow.minimize(),
    onMaximize: () => (currentWindow.isMaximized() ? currentWindow.unmaximize() : currentWindow.maximize()),
    onClose: () => currentWindow.close(),
    isMaximized: () => currentWindow.isMaximized()
  });
});

Use with Window Controls Overlay

This setup will allow you to use Window Controls Overlay in Electron 16+. The system's default window controls will be used on all platforms.

main.js

const { initialize, enable } = require('@electron/remote/main');
const { app, BrowserWindow } = require('electron');
const path = require('path');

initialize();

function createWindow () {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    titleBarStyle: "hidden",
    titleBarOverlay: {
      color: 'black',
      symbolColor: 'white',
    },
    webPreferences: {
      contextIsolation: true,
      preload: path.join(__dirname, 'preload.js')
    }
  })

  enable(win.webContents);
  
  win.loadFile('index.html');
}

app.whenReady().then(() => {
  createWindow();
})

preload.js

const { Menu, getCurrentWindow } = require('@electron/remote');
const Titlebar = require('@6c65726f79/custom-titlebar');
const { platform } = require('process');

const currentWindow = getCurrentWindow();
let titlebar;

currentWindow.webContents.once('dom-ready', () => {
  titlebar = new Titlebar({
    menu: Menu.getApplicationMenu(),
    backgroundColor: 'black',
    unfocusEffect: false,
    platform: platform,
    browserWindow: currentWindow,
    windowControlsOverlay: true
  });
});

Use the system's traffic lights on macOS

This setup will allow you to use the system's default window controls on macOS. This has no effect on Windows.

main.js

const { initialize, enable } = require('@electron/remote/main');
const { app, BrowserWindow } = require('electron');
const path = require('path');

initialize();

function createWindow () {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    titleBarStyle: 'hidden',
    webPreferences: {
      contextIsolation: true,
      preload: path.join(__dirname, 'preload.js')
    }
  })

  enable(win.webContents);
  
  win.loadFile('index.html');
}

app.whenReady().then(() => {
  createWindow();
})

preload.js

const { Menu, getCurrentWindow } = require('@electron/remote');
const Titlebar = require('@6c65726f79/custom-titlebar');
const { platform } = require('process');

const currentWindow = getCurrentWindow();
let titlebar;

currentWindow.webContents.once('dom-ready', () => {
  titlebar = new Titlebar({
    hideControlsOnDarwin: true,
    platform: platform,
    height: platform == 'darwin' ? 22 : 30,
    onMinimize: () => currentWindow.minimize(),
    onMaximize: () => currentWindow.isMaximized() ? currentWindow.unmaximize() : currentWindow.maximize(),
    onClose: () => currentWindow.close(),
    isMaximized: () => currentWindow.isMaximized()
  });
});