forked from nylas/nylas-mail
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmenu-manager.es6
129 lines (115 loc) · 3.8 KB
/
menu-manager.es6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/* eslint global-require: 0 */
/* eslint import/no-dynamic-require: 0 */
import path from 'path';
import fs from 'fs-plus';
import { ipcRenderer } from 'electron';
import { Disposable } from 'event-kit';
import Utils from './flux/models/utils';
import MenuHelpers from './menu-helpers';
export default class MenuManager {
constructor({resourcePath}) {
this.resourcePath = resourcePath;
this.template = [];
this.loadPlatformItems();
NylasEnv.keymaps.onDidReloadKeymap(() => this.update());
NylasEnv.commands.onRegistedCommandsChanged(() => this.update());
}
// Public: Adds the given items to the application menu.
//
// ## Examples
//
// ```coffee
// NylasEnv.menu.add [
// {
// label: 'Hello'
// submenu : [{label: 'World!', command: 'hello:world'}]
// }
// ]
// ```
//
// * `items` An {Array} of menu item {Object}s containing the keys:
// * `label` The {String} menu label.
// * `submenu` An optional {Array} of sub menu items.
// * `command` An optional {String} command to trigger when the item is
// clicked.
//
// Returns a {Disposable} on which `.dispose()` can be called to remove the
// added menu items.
add(items) {
const cloned = Utils.deepClone(items);
for (const item of cloned) {
this.merge(this.template, item);
}
this.update();
return new Disposable(() => this.remove(items));
}
remove(items) {
for (const item of items) {
this.unmerge(this.template, item);
}
return this.update();
}
// Public: Refreshes the currently visible menu.
update = () => {
if (this.pendingUpdateOperation) {
return;
}
this.pendingUpdateOperation = true;
window.requestAnimationFrame(() => {
this.pendingUpdateOperation = false;
MenuHelpers.forEachMenuItem(this.template, (item) => {
if (item.command && item.command.startsWith('application:') === false) {
item.enabled = NylasEnv.commands.listenerCountForCommand(item.command) > 0;
}
if (item.submenu != null) {
item.enabled = !item.submenu.every((subitem) => subitem.enabled === false);
}
if (item.hideWhenDisabled) { item.visible = item.enabled; }
});
return this.sendToBrowserProcess(this.template, NylasEnv.keymaps.getBindingsForAllCommands());
});
}
loadPlatformItems() {
const menusDirPath = path.join(this.resourcePath, 'menus');
const platformMenuPath = fs.resolve(menusDirPath, process.platform, ['json']);
const {menu} = require(platformMenuPath);
return this.add(menu);
}
// Merges an item in a submenu aware way such that new items are always
// appended to the bottom of existing menus where possible.
merge(menu, item) {
return MenuHelpers.merge(menu, item);
}
unmerge(menu, item) {
return MenuHelpers.unmerge(menu, item);
}
// OSX can't handle displaying accelerators for multiple keystrokes.
// If they are sent across, it will stop processing accelerators for the rest
// of the menu items.
filterMultipleKeystroke(keystrokesByCommand) {
if (!keystrokesByCommand) {
return {};
}
const filtered = {};
for (const key of Object.keys(keystrokesByCommand)) {
const bindings = keystrokesByCommand[key];
for (const binding of bindings) {
if (binding.includes(' ')) {
continue;
}
if (!/(cmd|ctrl|shift|alt|mod)/.test(binding) && !/f\d+/.test(binding)) {
continue;
}
if (!filtered[key]) {
filtered[key] = [];
}
filtered[key].push(binding);
}
}
return filtered;
}
sendToBrowserProcess(template, keystrokesByCommand) {
const filtered = this.filterMultipleKeystroke(keystrokesByCommand);
return ipcRenderer.send('update-application-menu', template, filtered);
}
}