Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: tray & app menu item "Check for Updates..." #62

Merged
merged 1 commit into from
Aug 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
58 changes: 58 additions & 0 deletions main/app-menu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const { Menu, MenuItem, ipcMain } = require('electron')
const { ipcMainEvents } = require('./ipc')

function setupAppMenu (/** @type {import('./typings').Context} */ ctx) {
// Add "Check for updates..." item to the Application menu on MacOS
// GitHub issues tracking the work to add this menu item on other platforms:
// - Windows: https://github.com/filecoin-project/filecoin-station/issues/63
// - Linux: https://github.com/filecoin-project/filecoin-station/issues/64
if (process.platform !== 'darwin') return

const menu = Menu.getApplicationMenu()
if (!menu) return

menu.items[0].submenu?.insert(1, new MenuItem({
id: 'checkForUpdates',
label: 'Check For Updates...',
click: () => { ctx.manualCheckForUpdates() }
}))
menu.items[0].submenu?.insert(2, new MenuItem({
id: 'checkingForUpdates',
label: 'Checking For Updates',
enabled: false,
visible: false
}))

Menu.setApplicationMenu(menu)
setupIpcEventListeners(menu)
}

/**
* @param {Electron.Menu} appMenu
*/
function setupIpcEventListeners (appMenu) {
ipcMain.on(ipcMainEvents.UPDATE_CHECK_STARTED, () => {
getItemById('checkForUpdates').visible = false
getItemById('checkingForUpdates').visible = true
})

ipcMain.on(ipcMainEvents.UPDATE_CHECK_FINISHED, () => {
getItemById('checkForUpdates').visible = true
getItemById('checkingForUpdates').visible = false
})

/**
juliangruber marked this conversation as resolved.
Show resolved Hide resolved
* Get an item from the main app menu or fail with a useful error message.
* @param {string} id
* @returns {Electron.MenuItem}
*/
function getItemById (id) {
const item = appMenu.getMenuItemById(id)
if (!item) throw new Error(`Unknown app menu item id: ${id}`)
return item
}
juliangruber marked this conversation as resolved.
Show resolved Hide resolved
}

module.exports = {
setupAppMenu
}
52 changes: 52 additions & 0 deletions main/dialog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const { dialog } = require('electron')
const { IS_MAC } = require('./consts')

module.exports = {
showDialogSync
}

/**
* NOTE: always send the buttons in the order [OK, Cancel, ...Actions].
* See this post for more interesting information about the topic:
* https://medium.muz.li/ok-key-and-cancel-key-which-one-should-be-set-up-on-the-left-4780e86c16eb
*
* @param {import('electron').MessageBoxSyncOptions & {title: string}} args
* @returns
*/
function showDialogSync ({
title,
message,
type = 'info',
buttons = ['OK', 'Cancel'],
...opts
}) {
const options = {
type,
buttons,
title,
message,
...opts
}

if (IS_MAC) {
options.message = title
options.detail = message
}

const isInverse = !IS_MAC

if (isInverse) {
options.buttons.reverse()
}

if (buttons.length > 1) {
options.defaultId = isInverse ? buttons.length - 1 : 0
options.cancelId = isInverse ? buttons.length - 2 : 1
}

const selected = dialog.showMessageBoxSync(options)

return isInverse
? buttons.length - selected - 1
: selected
}
7 changes: 5 additions & 2 deletions main/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ const setupUI = require('./ui')
const setupTray = require('./tray')
const setupUpdater = require('./updater')
const saturnNode = require('./saturn-node')
const setupIpc = require('./ipc')
const { setupIpcMain } = require('./ipc')
const { setupAppMenu } = require('./app-menu')

const inTest = (process.env.NODE_ENV === 'test')

Expand All @@ -34,6 +35,7 @@ if (!app.requestSingleInstanceLock() && !inTest) {

/** @type {import('./typings').Context} */
const ctx = {
manualCheckForUpdates: () => { throw new Error('never get here') },
showUI: () => { throw new Error('never get here') },
loadWebUIFromDist: serve({ directory: path.resolve(__dirname, '../renderer/dist') })
}
Expand All @@ -49,9 +51,10 @@ async function run () {
try {
// Interface
await setupTray(ctx)
await setupAppMenu(ctx)
await setupUI(ctx)
await setupUpdater(ctx)
await setupIpc()
await setupIpcMain()

await saturnNode.setup(ctx)
} catch (e) {
Expand Down
12 changes: 11 additions & 1 deletion main/ipc.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ const { ipcMain } = require('electron')

const saturnNode = require('./saturn-node')

module.exports = function () {
const ipcMainEvents = Object.freeze({
UPDATE_CHECK_STARTED: 'station:update-check:started',
UPDATE_CHECK_FINISHED: 'station:update-check:finished'
})

function setupIpcMain () {
ipcMain.handle('saturn:isRunning', saturnNode.isRunning)
ipcMain.handle('saturn:isReady', saturnNode.isReady)
ipcMain.handle('saturn:start', saturnNode.start)
Expand All @@ -12,3 +17,8 @@ module.exports = function () {
ipcMain.handle('saturn:getFilAddress', saturnNode.getFilAddress)
ipcMain.handle('saturn:setFilAddress', (_event, address) => saturnNode.setFilAddress(address))
}

module.exports = {
setupIpcMain,
ipcMainEvents
}
42 changes: 41 additions & 1 deletion main/tray.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const { Menu, Tray, shell, app } = require('electron')
const { Menu, Tray, shell, app, ipcMain } = require('electron')
const path = require('path')
const { IS_MAC, STATION_VERSION } = require('./consts')
const { ipcMainEvents } = require('./ipc')

// Be warned, this one is pretty ridiculous:
// Tray must be global or it will break due to.. GC.
Expand All @@ -23,6 +24,17 @@ module.exports = function (/** @type {import('./typings').Context} */ ctx) {
label: `Filecoin Station v${STATION_VERSION}`,
click: () => { shell.openExternal(`https://github.com/filecoin-project/filecoin-station/releases/v${STATION_VERSION}`) }
},
{
id: 'checkForUpdates',
label: 'Check for Updates...',
click: () => { ctx.manualCheckForUpdates() }
},
{
id: 'checkingForUpdates',
label: 'Checking for Updates',
enabled: false,
visible: false
},
{ type: 'separator' },
{
id: 'showUi',
Expand All @@ -38,4 +50,32 @@ module.exports = function (/** @type {import('./typings').Context} */ ctx) {
])
tray.setToolTip('Filecoin Station')
tray.setContextMenu(contextMenu)

setupIpcEventListeners(contextMenu)
}

/**
* @param {Electron.Menu} contextMenu
*/
function setupIpcEventListeners (contextMenu) {
ipcMain.on(ipcMainEvents.UPDATE_CHECK_STARTED, () => {
getItemById('checkForUpdates').visible = false
getItemById('checkingForUpdates').visible = true
})

ipcMain.on(ipcMainEvents.UPDATE_CHECK_FINISHED, () => {
getItemById('checkForUpdates').visible = true
getItemById('checkingForUpdates').visible = false
})

/**
* Get an item from the Tray menu or fail with a useful error message.
* @param {string} id
* @returns {Electron.MenuItem}
*/
function getItemById (id) {
const item = contextMenu.getMenuItemById(id)
if (!item) throw new Error(`Unknown tray menu item id: ${id}`)
return item
}
}
1 change: 1 addition & 0 deletions main/typings.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export interface Context {
showUI: () => void
loadWebUIFromDist: import('electron-serve').loadURL
manualCheckForUpdates: () => void
}