From 2793498e0b3c1478ff9e64f2128758d48eb8ef10 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 5 Jan 2018 13:13:30 -0800 Subject: [PATCH] Decaffeinate ApplicationMenu --- src/main-process/application-menu.coffee | 161 ---------------- src/main-process/application-menu.js | 225 +++++++++++++++++++++++ 2 files changed, 225 insertions(+), 161 deletions(-) delete mode 100644 src/main-process/application-menu.coffee create mode 100644 src/main-process/application-menu.js diff --git a/src/main-process/application-menu.coffee b/src/main-process/application-menu.coffee deleted file mode 100644 index 35bc7d66c..000000000 --- a/src/main-process/application-menu.coffee +++ /dev/null @@ -1,161 +0,0 @@ -{app, Menu} = require 'electron' -_ = require 'underscore-plus' -MenuHelpers = require '../menu-helpers' - -# Used to manage the global application menu. -# -# It's created by {AtomApplication} upon instantiation and used to add, remove -# and maintain the state of all menu items. -module.exports = -class ApplicationMenu - constructor: (@version, @autoUpdateManager) -> - @windowTemplates = new WeakMap() - @setActiveTemplate(@getDefaultTemplate()) - @autoUpdateManager.on 'state-changed', (state) => @showUpdateMenuItem(state) - - # Public: Updates the entire menu with the given keybindings. - # - # window - The BrowserWindow this menu template is associated with. - # template - The Object which describes the menu to display. - # keystrokesByCommand - An Object where the keys are commands and the values - # are Arrays containing the keystroke. - update: (window, template, keystrokesByCommand) -> - @translateTemplate(template, keystrokesByCommand) - @substituteVersion(template) - @windowTemplates.set(window, template) - @setActiveTemplate(template) if window is @lastFocusedWindow - - setActiveTemplate: (template) -> - unless _.isEqual(template, @activeTemplate) - @activeTemplate = template - @menu = Menu.buildFromTemplate(_.deepClone(template)) - Menu.setApplicationMenu(@menu) - - @showUpdateMenuItem(@autoUpdateManager.getState()) - - # Register a BrowserWindow with this application menu. - addWindow: (window) -> - @lastFocusedWindow ?= window - - focusHandler = => - @lastFocusedWindow = window - if template = @windowTemplates.get(window) - @setActiveTemplate(template) - - window.on 'focus', focusHandler - window.once 'closed', => - @lastFocusedWindow = null if window is @lastFocusedWindow - @windowTemplates.delete(window) - window.removeListener 'focus', focusHandler - - @enableWindowSpecificItems(true) - - # Flattens the given menu and submenu items into an single Array. - # - # menu - A complete menu configuration object for atom-shell's menu API. - # - # Returns an Array of native menu items. - flattenMenuItems: (menu) -> - items = [] - for index, item of menu.items or {} - items.push(item) - items = items.concat(@flattenMenuItems(item.submenu)) if item.submenu - items - - # Flattens the given menu template into an single Array. - # - # template - An object describing the menu item. - # - # Returns an Array of native menu items. - flattenMenuTemplate: (template) -> - items = [] - for item in template - items.push(item) - items = items.concat(@flattenMenuTemplate(item.submenu)) if item.submenu - items - - # Public: Used to make all window related menu items are active. - # - # enable - If true enables all window specific items, if false disables all - # window specific items. - enableWindowSpecificItems: (enable) -> - for item in @flattenMenuItems(@menu) - item.enabled = enable if item.metadata?.windowSpecific - return - - # Replaces VERSION with the current version. - substituteVersion: (template) -> - if (item = _.find(@flattenMenuTemplate(template), ({label}) -> label is 'VERSION')) - item.label = "Version #{@version}" - - # Sets the proper visible state the update menu items - showUpdateMenuItem: (state) -> - checkForUpdateItem = _.find(@flattenMenuItems(@menu), ({label}) -> label is 'Check for Update') - checkingForUpdateItem = _.find(@flattenMenuItems(@menu), ({label}) -> label is 'Checking for Update') - downloadingUpdateItem = _.find(@flattenMenuItems(@menu), ({label}) -> label is 'Downloading Update') - installUpdateItem = _.find(@flattenMenuItems(@menu), ({label}) -> label is 'Restart and Install Update') - - return unless checkForUpdateItem? and checkingForUpdateItem? and downloadingUpdateItem? and installUpdateItem? - - checkForUpdateItem.visible = false - checkingForUpdateItem.visible = false - downloadingUpdateItem.visible = false - installUpdateItem.visible = false - - switch state - when 'idle', 'error', 'no-update-available' - checkForUpdateItem.visible = true - when 'checking' - checkingForUpdateItem.visible = true - when 'downloading' - downloadingUpdateItem.visible = true - when 'update-available' - installUpdateItem.visible = true - - # Default list of menu items. - # - # Returns an Array of menu item Objects. - getDefaultTemplate: -> - [ - label: "Atom" - submenu: [ - {label: "Check for Update", metadata: {autoUpdate: true}} - {label: 'Reload', accelerator: 'Command+R', click: => @focusedWindow()?.reload()} - {label: 'Close Window', accelerator: 'Command+Shift+W', click: => @focusedWindow()?.close()} - {label: 'Toggle Dev Tools', accelerator: 'Command+Alt+I', click: => @focusedWindow()?.toggleDevTools()} - {label: 'Quit', accelerator: 'Command+Q', click: -> app.quit()} - ] - ] - - focusedWindow: -> - _.find global.atomApplication.getAllWindows(), (atomWindow) -> atomWindow.isFocused() - - # Combines a menu template with the appropriate keystroke. - # - # template - An Object conforming to atom-shell's menu api but lacking - # accelerator and click properties. - # keystrokesByCommand - An Object where the keys are commands and the values - # are Arrays containing the keystroke. - # - # Returns a complete menu configuration object for atom-shell's menu API. - translateTemplate: (template, keystrokesByCommand) -> - template.forEach (item) => - item.metadata ?= {} - if item.command - item.accelerator = @acceleratorForCommand(item.command, keystrokesByCommand) - item.click = -> global.atomApplication.sendCommand(item.command, item.commandDetail) - item.metadata.windowSpecific = true unless /^application:/.test(item.command, item.commandDetail) - @translateTemplate(item.submenu, keystrokesByCommand) if item.submenu - template - - # Determine the accelerator for a given command. - # - # command - The name of the command. - # keystrokesByCommand - An Object where the keys are commands and the values - # are Arrays containing the keystroke. - # - # Returns a String containing the keystroke in a format that can be interpreted - # by Electron to provide nice icons where available. - acceleratorForCommand: (command, keystrokesByCommand) -> - firstKeystroke = keystrokesByCommand[command]?[0] - MenuHelpers.acceleratorForKeystroke(firstKeystroke) diff --git a/src/main-process/application-menu.js b/src/main-process/application-menu.js new file mode 100644 index 000000000..26dcd1941 --- /dev/null +++ b/src/main-process/application-menu.js @@ -0,0 +1,225 @@ +const {app, Menu} = require('electron') +const _ = require('underscore-plus') +const MenuHelpers = require('../menu-helpers') + +// Used to manage the global application menu. +// +// It's created by {AtomApplication} upon instantiation and used to add, remove +// and maintain the state of all menu items. +module.exports = +class ApplicationMenu { + constructor (version, autoUpdateManager) { + this.version = version + this.autoUpdateManager = autoUpdateManager + this.windowTemplates = new WeakMap() + this.setActiveTemplate(this.getDefaultTemplate()) + this.autoUpdateManager.on('state-changed', state => this.showUpdateMenuItem(state)) + } + + // Public: Updates the entire menu with the given keybindings. + // + // window - The BrowserWindow this menu template is associated with. + // template - The Object which describes the menu to display. + // keystrokesByCommand - An Object where the keys are commands and the values + // are Arrays containing the keystroke. + update (window, template, keystrokesByCommand) { + this.translateTemplate(template, keystrokesByCommand) + this.substituteVersion(template) + this.windowTemplates.set(window, template) + if (window === this.lastFocusedWindow) return this.setActiveTemplate(template) + } + + setActiveTemplate (template) { + if (!_.isEqual(template, this.activeTemplate)) { + this.activeTemplate = template + this.menu = Menu.buildFromTemplate(_.deepClone(template)) + Menu.setApplicationMenu(this.menu) + } + + return this.showUpdateMenuItem(this.autoUpdateManager.getState()) + } + + // Register a BrowserWindow with this application menu. + addWindow (window) { + if (this.lastFocusedWindow == null) this.lastFocusedWindow = window + + const focusHandler = () => { + this.lastFocusedWindow = window + const template = this.windowTemplates.get(window) + if (template) this.setActiveTemplate(template) + } + + window.on('focus', focusHandler) + window.once('closed', () => { + if (window === this.lastFocusedWindow) this.lastFocusedWindow = null + this.windowTemplates.delete(window) + window.removeListener('focus', focusHandler) + }) + + this.enableWindowSpecificItems(true) + } + + // Flattens the given menu and submenu items into an single Array. + // + // menu - A complete menu configuration object for atom-shell's menu API. + // + // Returns an Array of native menu items. + flattenMenuItems (menu) { + const object = menu.items || {} + let items = [] + for (let index in object) { + const item = object[index] + items.push(item) + if (item.submenu) items = items.concat(this.flattenMenuItems(item.submenu)) + } + return items + } + + // Flattens the given menu template into an single Array. + // + // template - An object describing the menu item. + // + // Returns an Array of native menu items. + flattenMenuTemplate (template) { + let items = [] + for (let item of template) { + items.push(item) + if (item.submenu) items = items.concat(this.flattenMenuTemplate(item.submenu)) + } + return items + } + + // Public: Used to make all window related menu items are active. + // + // enable - If true enables all window specific items, if false disables all + // window specific items. + enableWindowSpecificItems (enable) { + for (let item of this.flattenMenuItems(this.menu)) { + if (item.metadata && item.metadata.windowSpecific) item.enabled = enable + } + } + + // Replaces VERSION with the current version. + substituteVersion (template) { + let item = this.flattenMenuTemplate(template).find(({label}) => label === 'VERSION') + if (item) item.label = `Version ${this.version}` + } + + // Sets the proper visible state the update menu items + showUpdateMenuItem (state) { + const items = this.flattenMenuItems(this.menu) + const checkForUpdateItem = items.find(({label}) => label === 'Check for Update') + const checkingForUpdateItem = items.find(({label}) => label === 'Checking for Update') + const downloadingUpdateItem = items.find(({label}) => label === 'Downloading Update') + const installUpdateItem = items.find(({label}) => label === 'Restart and Install Update') + + if (!checkForUpdateItem || !checkingForUpdateItem || + !downloadingUpdateItem || !installUpdateItem) return + + checkForUpdateItem.visible = false + checkingForUpdateItem.visible = false + downloadingUpdateItem.visible = false + installUpdateItem.visible = false + + switch (state) { + case 'idle': + case 'error': + case 'no-update-available': + checkForUpdateItem.visible = true + break + case 'checking': + checkingForUpdateItem.visible = true + break + case 'downloading': + downloadingUpdateItem.visible = true + break + case 'update-available': + installUpdateItem.visible = true + break + } + } + + // Default list of menu items. + // + // Returns an Array of menu item Objects. + getDefaultTemplate () { + return [{ + label: 'Atom', + submenu: [ + { + label: 'Check for Update', + metadata: {autoUpdate: true} + }, + { + label: 'Reload', + accelerator: 'Command+R', + click: () => { + const window = this.focusedWindow() + if (window) window.reload() + } + }, + { + label: 'Close Window', + accelerator: 'Command+Shift+W', + click: () => { + const window = this.focusedWindow() + if (window) window.close() + } + }, + { + label: 'Toggle Dev Tools', + accelerator: 'Command+Alt+I', + click: () => { + const window = this.focusedWindow() + if (window) window.toggleDevTools() + } + }, + { + label: 'Quit', + accelerator: 'Command+Q', + click: () => app.quit() + } + ] + }] + } + + focusedWindow () { + return global.atomApplication.getAllWindows().find(window => window.isFocused()) + } + + // Combines a menu template with the appropriate keystroke. + // + // template - An Object conforming to atom-shell's menu api but lacking + // accelerator and click properties. + // keystrokesByCommand - An Object where the keys are commands and the values + // are Arrays containing the keystroke. + // + // Returns a complete menu configuration object for atom-shell's menu API. + translateTemplate (template, keystrokesByCommand) { + template.forEach(item => { + if (item.metadata == null) item.metadata = {} + if (item.command) { + item.accelerator = this.acceleratorForCommand(item.command, keystrokesByCommand) + item.click = () => global.atomApplication.sendCommand(item.command, item.commandDetail) + if (!/^application:/.test(item.command, item.commandDetail)) { + item.metadata.windowSpecific = true + } + } + if (item.submenu) this.translateTemplate(item.submenu, keystrokesByCommand) + }) + return template + } + + // Determine the accelerator for a given command. + // + // command - The name of the command. + // keystrokesByCommand - An Object where the keys are commands and the values + // are Arrays containing the keystroke. + // + // Returns a String containing the keystroke in a format that can be interpreted + // by Electron to provide nice icons where available. + acceleratorForCommand (command, keystrokesByCommand) { + const firstKeystroke = keystrokesByCommand[command] && keystrokesByCommand[command][0] + return MenuHelpers.acceleratorForKeystroke(firstKeystroke) + } +}