Electron: Creating Tray Menu
Tags: react, tutorial, electron, typescript
Published: 2020-08-07 12:13:53


---
title: Electron: Creating Tray Menu
published: true

cover_image: https://res.cloudinary.com/practicaldev/image/fetch/s--HII-pSoO--/c_imagga_scale,f_auto,fl_progressive,h_420,q_auto,w_1000/https://dev-to-uploads.s3.amazonaws.com/i/o546e4gt45o4j2w4xwar.png

tags: react, tutorial, electron, typescript
series: Alarm Widget with Electron + React
---

Heya!

In the previous part we prepared the environment of our application built using Electron and React with Typescript. If you haven't seen it yet, it's better to check [Part 1](https://dev.to/franamorim/tutorial-reminder-widget-with-electron-react-1hj9) first.

### Part 2: Electron: Tray Menu

In this part we will focus on creating the tray system icon, such as its interaction and menu.

The idea is the application as soon as it is executed open an icon in the system tray. 

With the left click we will open a window, in this window we can see every alarm created, and will be able to turn it on and off. We will be able to create new alarms, defining the hour and minutes, giving a name, choosing a sound and also a message. And last we can delete them. :stuck_out_tongue_winking_eye:

With the right click we will have a quick menu, where we can open the main window, turn alarms on and off directly in this menu and we can also turn off the application.

Let's start to code!! :sunglasses:

### The Tray Button

We will create a folder called `electro`n inside the `src` folder, from now on all files that are directed to electron will be created inside this folder. We have also to create our file for the Tray menu, giving the name `TrayMenu.ts`.
Create an `assets` folder under `src`, add an icon for the application, I used a [Clock](https://www.flaticon.com/free-icon/wall-clock_594354?term=clock&page=1&position=1) in case you have no ideas (16x16px).

At this point our project must have the following structure:

![Project Structure](https://dev-to-uploads.s3.amazonaws.com/i/r2dapse35apznry644zj.png)

To create our tray menu, we need to import from Electron the Tray and nativeImage:

`.src/electron/TrayMenu.ts`
```ts
import { app, Tray, Menu, nativeImage } from 'electron';
```

Why this four? :thinking:
* **app** is where the event lifcycle of our applicatin is controled;
* **Tray** is a class used add icons and context menus;
* **Menu** it's a class to create Menu's;
* **nativeImage** will create a native image from a url path, it's necessary to create the tray icon for OSX;

`.src/electron/TrayMenu.ts`
```ts
import { app, Tray, Menu, nativeImage } from 'electron';

export class TrayMenu {
  // Create a variable to store our tray
  // Public: Make it accessible outside of the class;
  // Readonly: Value can't be changed
  public readonly tray: Tray;

  // Path where should we fetch our icon;
  private iconPath: string = '/assets/clock-icon.png';

  constructor() {
    this.tray = new Tray(this.iconPath);
  }
}
```

As you can see the tray constructor accepts a url path, but we want to create our icon with nativeImage and for that we will create a method that will handle the creation.

`.src/electron/TrayMenu.ts`
```ts
import { app, Tray, Menu, nativeImage } from 'electron';

export class TrayMenu {
  // Create a variable to store our tray
  // Public: Make it accessible outside of the class;
  // Readonly: Value can't be changed
  public readonly tray: Tray;

  // Path where should we fetch our icon;
  private iconPath: string = '/assets/clock-icon.png';

  constructor() {
    this.tray = new Tray(this.createNativeImage());
  }

  createNativeImage() {
    // Since we never know where the app is installed,
    // we need to add the app base path to it.
    const path = `${app.getAppPath()}${this.iconPath}`;
    const image = nativeImage.createFromPath(path);
    // Marks the image as a template image.
    image.setTemplateImage(true);
    return image;
  }
}
```

Now we just need to instantiate TrayMenu as soon as the application is ready, and for that we will go to the `main.ts` file to instantiate.

`./src/main.ts`
```ts
import { app, BrowserWindow } from 'electron';
import { TrayMenu } from '@/electron/TrayMenu';

const appElements: any = {
  tray: null,
  windows: []
};

app.on('ready', () => {
  appElements.tray = new TrayMenu();
});
```

Note: You can delete the `createWindow` method in it. 

At the moment we have created a constant to store the application elements that we need to instantiate. But later we will create a Manager for that.

Why do we need this manager?

For example if we do this:
```ts
app.on('ready', () => {
  const tray = new TrayMenu();
});
```
The icon will disappear, as the garbage collector will delete the references that are in this scope.

#### Webpack Optimization

We need to install the `Webpack Copy Plugin` to copy the all assets to our dist folder.

For that run:
```sh
npm install copy-webpack-plugin --save-dev
```

After installing it add the following config to our `webpack.config.js`:

`./webpack.config.js`
```js
const electronConfiguration = {
  ...
  plugins: [
    new CopyPlugin({
      patterns: [
        { from: 'src/assets', to: 'assets' },
      ],
    }),
  ],
};
```

Let's check out our progress,run:
```sh
npm start
```

![Tray](https://dev-to-uploads.s3.amazonaws.com/i/fcrxkaxg0pw5su82m8aw.png)

### The Menu

Just as we did to create `nativeImage` we will create a method to create the menu.

`./src/electron/TrayMenu.ts`
```ts
import { app, Tray, Menu, nativeImage } from 'electron';

export class TrayMenu {

  ...

  constructor() {
    this.tray = new Tray(this.createNativeImage());
    // We need to set the context menu to our tray
    this.tray.setContextMenu(this.createMenu());
  }

  ...

  createMenu(): Menu {
    // This method will create the Menu for the tray
    const contextMenu = Menu.buildFromTemplate([
      {
        label: 'Tokei',
        type: 'normal',
        click: () => { 
          /* Later this will open the Main Window */ 
        }
      },
      {
        label: 'Quit',
        type: 'normal',
        click: () => app.quit()
      }
    ]);
    return contextMenu;
  }
}
```

Explaining better what each Button will do:

* The first one will open the alarms window, when clicking you should open the window but for now it will do nothing;
* The 'Quit' button as the label indicates will be to close the application, for that we will execute a function of the application controller to close it;

If you run our application now and click on the icon, the menu will be shown


![Tray with Menu](https://dev-to-uploads.s3.amazonaws.com/i/9wcm0o0zjwzn35ypdgdj.png)

### Conclusion

Right now we have our application running with an Icon tray and the menu.
I hope you are enjoying it, leave your comment on what I should improve! And if you have any doubts, just comment, I will try to answer quickly. :grin:

Repository: [Tokei](https://github.com/diink-studios/tokei/tree/part-2/the-tray-menu)

[Part 3 - The Alarm Window](https://dev.to/franamorim/alarm-widget-with-electron-react-3-5bca)