Last PR for Hacktoberfest!

Sarika Hanif
Oct 26 · 6 min read

Hacktoberfest is almost done and I finally submitted 4 PRs!

For my last pull request, I stumbled across a repository called time-to-leave. It’s an application that logs work hours, estimates the time that you should leave and sends a tray notification. I thought how it was built was pretty cool. It uses the electron framework, which I have never heard of before. It piqued my interest and I did some research. It’s pretty much an open-source framework that you can develop GUI applications using HTML, CSS, and JavaScript. It’s cross platform, allowing apps to be built and run on mac, windows and linux. A lot of other companies use it like slack and discord.

For the issue that I chose, I had a big misunderstanding of what was needed to be done. Initially, the issue was phrased as “Issue #55 introduced the ability to punch from tray, but it is always enabled. It should follow the behaviour of the button on the main window, which is disabled if the day is a non-working day or if all entries have already been punched.” When they mentioned tray, I assumed that it was the tray notification.

I went ahead and submitted a PR that stopped the notification using the validation mentioned in the issue. Turns out, that wasn’t the problem!

The actual issue was that they introduced the functionality to also punch (log) work hours into the system, by right clicking on the tray icon in the system tray and selecting Punch time(Oof! So many face palms on my part 🤦🏽‍♀️).

However, the functionality didn’t follow the same validation rules as the main punch button on the application. This allowed the user to record their hours at any time.

Now this issue was challenging. I didn’t know anything about using electron or tray menus (clearly). I ended up doing a lot of research, I found that the tray was built by using Electron’s main process module. This is loaded in the main.js file. The code to create a generic tray looks like this:

const { app, Menu, Tray } = require('electron')

let tray = null
app.on('ready', () => {
tray = new Tray('/path/to/my/icon')
const contextMenu = Menu.buildFromTemplate([
{ label: 'Item1', type: 'radio' },
{ label: 'Item2', type: 'radio' },
{ label: 'Item3', type: 'radio', checked: true },
{ label: 'Item4', type: 'radio' }
])
tray.setToolTip('This is my application.')
tray.setContextMenu(contextMenu)
})

I first looked over how the tray was set up. I noticed that it used an array of options for constructing menu items. Going through the docs, I found that it had an enabled option. Great!

In the calendar.js file, there were two pieces of code that disabled the main punch button.

These were the validations that were applied to the punch time button on the applications page. This needed to be applied to the tray menu item as well. At first, I thought that I could make a function that would return either true or false based on the validation condition. The code for the tray menu would end up looking something like this:

var contextMenu = Menu.buildFromTemplate([
{
label: 'Punch time', enabled: win.webContents.executeJavaScript('trayValidation()') ? true : false, click: function () {
...
}

This approach did not work, I got errors about an unhandled promise. I also tried using modules.export = {} on the function, but it also gave me errors. I figured the best way was to somehow export a boolean from the renderer to the main. After researching, I came across the ipcMain.on() and the ipcRender.send() function. This is a way to communicate between the main process and renderer processes. The arguments are a channel that is any string and the message to be sent. This was perfect because when the button was disabled, I could send a false value to the tray menu item.

Now initially, I thought that I needed an observer to listen to changes in the attributes of the main punch time button and ended up using this block of code:

document.addEventListener('DOMContentLoaded', function () {
var target = document.getElementById('punch-button');
observer.observe(target, {attributes: true});
});
var observer = new MutationObserver(function (mutationRecords) {
mutationRecords.forEach(function (mutation) {
if (mutation.attributeName == 'disabled' && mutation.target.disabled == true) {
ipcRenderer.send('TOGGLE_TRAY_PUNCH_TIME', false);
} else {
ipcRenderer.send('TOGGLE_TRAY_PUNCH_TIME', true);
}
});
});

Looking back, all of this was unnecessary. There was already code that disables the punch button, I didn’t need to observe the buttons attributes as it changed. Referring back to the code that disabled the punch time button, all I had to do was append ipcRenderer.send() to it.

From here I tried checking the console log to see if the values were being received in the main.

//main.jsipcMain.on('TOGGLE_TRAY_PUNCH_TIME', function(_event, arg) {     console.log(arg);
});

Nothing showed up. It turns out that the console.log message within main appears on the terminal and the console.log message in the renderer appears in the toggle developer tools in the menu (if added to menu items).

At this point I was receiving the arguments being passed. I then added contextMenu[0].enabled = arg; within ipcMain.on(), but it didn’t work. I found this to be strange. It would set the argument correctly when the program ran, but it didn’t update the tray menu item as the argument value changed. After searching around I found that Electron’s menu is created at the start of the application and can’t be modified. This meant that my other option was to rebuild the menu whenever the value changed. This would work, because the menu items are only displayed when the user right clicks on the tray icon.

I ended up slightly modifying the tray, by just creating an array of objects and calling the buildFromTemplate function in ipcMain.on().

This seemed to do the trick! The feature now works.

I had some trouble with rebasing my branch. There were some changes made to the master branch and I had to update my code. From rebasing my code the original PR was hard to read with all the changes commited to it. I was asked to create a new PR instead. There were some changes requested like changing the channel name used for the ipcMain.on() and ipcRenderer(). Also, there were some linter issues to be corrected. Now the PR is under review. I hope it passes!

Overall, Hacktoberfest has helped me overcome my fear of contributing to open source projects. It has helped me explore technologies that I haven’t heard of before. Even from issues that that I decided not to move forward with, I’ve learned how to set up different environments and explore new tech through(like the mongo shell and executing scripts) recreating the bug/issue or trying to solve the problem.

I’m looking forward to trying this again next year! ✌🏼

Sarika Hanif

Written by

Open Source Student

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade