menuItems.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777
  1. const {
  2. app,
  3. BrowserWindow,
  4. ipcMain: ipc,
  5. Menu,
  6. shell,
  7. dialog
  8. } = require('electron');
  9. const fs = require('fs');
  10. const path = require('path');
  11. const Windows = require('./windows');
  12. const Settings = require('./settings');
  13. const log = require('./utils/logger').create('menuItems');
  14. const swarmLog = require('./utils/logger').create('swarm');
  15. const updateChecker = require('./updateChecker');
  16. const ethereumNode = require('./ethereumNode.js');
  17. const ClientBinaryManager = require('./clientBinaryManager');
  18. import {
  19. setLanguage,
  20. toggleSwarm,
  21. toggleSwarmOnStart
  22. } from './core/settings/actions';
  23. import { changeNetwork, changeSyncMode } from './core/nodes/actions';
  24. import { SwarmState } from './core/settings/reducer';
  25. import swarmNode from './swarmNode.js';
  26. // Make easier to return values for specific systems
  27. const switchForSystem = function(options) {
  28. if (process.platform in options) {
  29. return options[process.platform];
  30. } else if ('default' in options) {
  31. return options.default;
  32. }
  33. return null;
  34. };
  35. // create menu
  36. // null -> null
  37. const createMenu = function(webviews) {
  38. webviews = webviews || [];
  39. const menu = Menu.buildFromTemplate(menuTempl(webviews));
  40. Menu.setApplicationMenu(menu);
  41. };
  42. const restartNode = function(newType, newNetwork, syncMode, webviews) {
  43. newNetwork = newNetwork || ethereumNode.network;
  44. log.info('Switch node', newType, newNetwork);
  45. store.dispatch(changeNetwork(newNetwork));
  46. return ethereumNode
  47. .restart(newType, newNetwork, syncMode)
  48. .then(() => {
  49. Windows.getByType('main').load(global.interfaceAppUrl);
  50. createMenu(webviews);
  51. log.info('Node switch successful.');
  52. })
  53. .catch(err => {
  54. log.error('Error switching node', err);
  55. });
  56. };
  57. const changeNodeNetwork = function(network, webviews) {
  58. store.dispatch(changeNetwork(network));
  59. Settings.saveUserData('network', network);
  60. restartNode(ethereumNode.type, network, ethereumNode.syncMode, webviews);
  61. createMenu(webviews);
  62. };
  63. const changeNodeSyncMode = function(syncMode, webviews) {
  64. store.dispatch(changeSyncMode(syncMode));
  65. Settings.saveUserData('syncmode', syncMode);
  66. restartNode(ethereumNode.type, ethereumNode.network, syncMode, webviews);
  67. createMenu(webviews);
  68. };
  69. const startMining = webviews => {
  70. ethereumNode
  71. .send('miner_start', [1])
  72. .then(ret => {
  73. log.info('miner_start', ret.result);
  74. if (ret.result) {
  75. global.mining = true;
  76. createMenu(webviews);
  77. }
  78. })
  79. .catch(err => {
  80. log.error('miner_start', err);
  81. });
  82. };
  83. const stopMining = webviews => {
  84. ethereumNode
  85. .send('miner_stop', [1])
  86. .then(ret => {
  87. log.info('miner_stop', ret.result);
  88. if (ret.result) {
  89. global.mining = false;
  90. createMenu(webviews);
  91. }
  92. })
  93. .catch(err => {
  94. log.error('miner_stop', err);
  95. });
  96. };
  97. // create a menu template
  98. // null -> obj
  99. let menuTempl = function(webviews) {
  100. const menu = [];
  101. webviews = webviews || [];
  102. // APP
  103. const fileMenu = [];
  104. if (process.platform === 'darwin') {
  105. fileMenu.push(
  106. {
  107. label: i18n.t('mist.applicationMenu.app.about', {
  108. app: Settings.appName
  109. }),
  110. click() {
  111. Windows.createPopup('about');
  112. }
  113. },
  114. {
  115. label: i18n.t('mist.applicationMenu.app.checkForUpdates'),
  116. click() {
  117. updateChecker.runVisibly();
  118. }
  119. },
  120. {
  121. label: i18n.t('mist.applicationMenu.app.checkForNodeUpdates'),
  122. click() {
  123. // remove skipVersion
  124. fs.writeFileSync(
  125. path.join(Settings.userDataPath, 'skippedNodeVersion.json'),
  126. '' // write no version
  127. );
  128. // true = will restart after updating and user consent
  129. ClientBinaryManager.init(true);
  130. }
  131. },
  132. {
  133. type: 'separator'
  134. },
  135. {
  136. label: i18n.t('mist.applicationMenu.app.services', {
  137. app: Settings.appName
  138. }),
  139. role: 'services',
  140. submenu: []
  141. },
  142. {
  143. type: 'separator'
  144. },
  145. {
  146. label: i18n.t('mist.applicationMenu.app.hide', {
  147. app: Settings.appName
  148. }),
  149. accelerator: 'Command+H',
  150. role: 'hide'
  151. },
  152. {
  153. label: i18n.t('mist.applicationMenu.app.hideOthers', {
  154. app: Settings.appName
  155. }),
  156. accelerator: 'Command+Alt+H',
  157. role: 'hideothers'
  158. },
  159. {
  160. label: i18n.t('mist.applicationMenu.app.showAll', {
  161. app: Settings.appName
  162. }),
  163. role: 'unhide'
  164. },
  165. {
  166. type: 'separator'
  167. }
  168. );
  169. }
  170. fileMenu.push({
  171. label: i18n.t('mist.applicationMenu.app.quit', {
  172. app: Settings.appName
  173. }),
  174. accelerator: 'CommandOrControl+Q',
  175. click() {
  176. app.quit();
  177. }
  178. });
  179. menu.push({
  180. label: i18n.t('mist.applicationMenu.app.label', {
  181. app: Settings.appName
  182. }),
  183. submenu: fileMenu
  184. });
  185. let swarmUpload = [];
  186. if (global.mode !== 'wallet') {
  187. swarmUpload.push(
  188. {
  189. type: 'separator'
  190. },
  191. {
  192. label: i18n.t('mist.applicationMenu.file.swarmUpload'),
  193. accelerator: 'Shift+CommandOrControl+U',
  194. enabled: store.getState().settings.swarmState == SwarmState.Enabled,
  195. click() {
  196. const focusedWindow = BrowserWindow.getFocusedWindow();
  197. const paths = dialog.showOpenDialog(focusedWindow, {
  198. properties: ['openFile', 'openDirectory']
  199. });
  200. if (paths && paths.length === 1) {
  201. const isDir = fs.lstatSync(paths[0]).isDirectory();
  202. const defaultPath = path.join(paths[0], 'index.html');
  203. const uploadConfig = {
  204. path: paths[0],
  205. kind: isDir ? 'directory' : 'file',
  206. defaultFile: fs.existsSync(defaultPath) ? '/index.html' : null
  207. };
  208. swarmNode
  209. .upload(uploadConfig)
  210. .then(hash => {
  211. focusedWindow.webContents.executeJavaScript(`
  212. Tabs.update('browser', {$set: {
  213. url: 'bzz://${hash}',
  214. redirect: 'bzz://${hash}'
  215. }});
  216. LocalStore.set('selectedTab', 'browser');
  217. `);
  218. swarmLog.info('Hash uploaded:', hash);
  219. })
  220. .catch(e => swarmLog.error(e));
  221. }
  222. }
  223. }
  224. );
  225. }
  226. menu.push({
  227. label: i18n.t('mist.applicationMenu.file.label'),
  228. submenu: [
  229. {
  230. label: i18n.t('mist.applicationMenu.file.newAccount'),
  231. accelerator: 'CommandOrControl+N',
  232. click() {
  233. Windows.createPopup('requestAccount');
  234. }
  235. },
  236. {
  237. label: i18n.t('mist.applicationMenu.file.importPresale'),
  238. accelerator: 'CommandOrControl+I',
  239. enabled: ethereumNode.isMainNetwork,
  240. click() {
  241. Windows.createPopup('importAccount');
  242. }
  243. },
  244. {
  245. type: 'separator'
  246. },
  247. {
  248. label: i18n.t('mist.applicationMenu.file.backup'),
  249. submenu: [
  250. {
  251. label: i18n.t('mist.applicationMenu.file.backupKeyStore'),
  252. click() {
  253. let userPath = Settings.userHomePath;
  254. // eth
  255. if (ethereumNode.isEth) {
  256. if (process.platform === 'win32') {
  257. userPath = `${Settings.appDataPath}\\Web3\\keys`;
  258. } else {
  259. userPath += '/.web3/keys';
  260. }
  261. // geth
  262. } else {
  263. if (process.platform === 'darwin') {
  264. userPath += '/Library/Ethereum/keystore';
  265. }
  266. if (
  267. process.platform === 'freebsd' ||
  268. process.platform === 'linux' ||
  269. process.platform === 'sunos'
  270. ) {
  271. userPath += '/.ethereum/keystore';
  272. }
  273. if (process.platform === 'win32') {
  274. userPath = `${Settings.appDataPath}\\Ethereum\\keystore`;
  275. }
  276. }
  277. shell.showItemInFolder(userPath);
  278. }
  279. },
  280. {
  281. label: i18n.t('mist.applicationMenu.file.backupMist'),
  282. click() {
  283. shell.openItem(Settings.userDataPath);
  284. }
  285. }
  286. ]
  287. },
  288. ...swarmUpload
  289. ]
  290. });
  291. // EDIT
  292. menu.push({
  293. label: i18n.t('mist.applicationMenu.edit.label'),
  294. submenu: [
  295. {
  296. label: i18n.t('mist.applicationMenu.edit.undo'),
  297. accelerator: 'CommandOrControl+Z',
  298. role: 'undo'
  299. },
  300. {
  301. label: i18n.t('mist.applicationMenu.edit.redo'),
  302. accelerator: 'Shift+CommandOrControl+Z',
  303. role: 'redo'
  304. },
  305. {
  306. type: 'separator'
  307. },
  308. {
  309. label: i18n.t('mist.applicationMenu.edit.cut'),
  310. accelerator: 'CommandOrControl+X',
  311. role: 'cut'
  312. },
  313. {
  314. label: i18n.t('mist.applicationMenu.edit.copy'),
  315. accelerator: 'CommandOrControl+C',
  316. role: 'copy'
  317. },
  318. {
  319. label: i18n.t('mist.applicationMenu.edit.paste'),
  320. accelerator: 'CommandOrControl+V',
  321. role: 'paste'
  322. },
  323. {
  324. label: i18n.t('mist.applicationMenu.edit.selectAll'),
  325. accelerator: 'CommandOrControl+A',
  326. role: 'selectall'
  327. }
  328. ]
  329. });
  330. // LANGUAGE (VIEW)
  331. const switchLang = langCode => (menuItem, browserWindow) => {
  332. store.dispatch(setLanguage(langCode, browserWindow));
  333. };
  334. const currentLanguage = Settings.language;
  335. const languageMenu = Object.keys(i18n.options.resources)
  336. .filter(langCode => langCode !== 'dev')
  337. .map(langCode => {
  338. const menuItem = {
  339. label: i18n.t(`mist.applicationMenu.view.langCodes.${langCode}`),
  340. type: 'checkbox',
  341. checked: langCode === currentLanguage,
  342. click: switchLang(langCode)
  343. };
  344. return menuItem;
  345. });
  346. languageMenu.unshift(
  347. {
  348. label: i18n.t('mist.applicationMenu.view.default'),
  349. click: switchLang(i18n.getBestMatchedLangCode(app.getLocale()))
  350. },
  351. {
  352. type: 'separator'
  353. }
  354. );
  355. // VIEW
  356. menu.push({
  357. label: i18n.t('mist.applicationMenu.view.label'),
  358. submenu: [
  359. {
  360. label: i18n.t('mist.applicationMenu.view.fullscreen'),
  361. accelerator: switchForSystem({
  362. darwin: 'Command+Control+F',
  363. default: 'F11'
  364. }),
  365. click() {
  366. const mainWindow = Windows.getByType('main');
  367. mainWindow.window.setFullScreen(!mainWindow.window.isFullScreen());
  368. }
  369. },
  370. {
  371. label: i18n.t('mist.applicationMenu.view.languages'),
  372. submenu: languageMenu
  373. }
  374. ]
  375. });
  376. // DEVELOP
  377. const devToolsMenu = [];
  378. let devtToolsSubMenu;
  379. let curWindow;
  380. // change for wallet
  381. if (Settings.uiMode === 'mist') {
  382. devtToolsSubMenu = [
  383. {
  384. label: i18n.t('mist.applicationMenu.develop.devToolsMistUI'),
  385. accelerator: 'Alt+CommandOrControl+I',
  386. click() {
  387. curWindow = BrowserWindow.getFocusedWindow();
  388. if (curWindow) {
  389. curWindow.toggleDevTools();
  390. }
  391. }
  392. },
  393. {
  394. type: 'separator'
  395. }
  396. ];
  397. // add webviews
  398. webviews.forEach(webview => {
  399. devtToolsSubMenu.push({
  400. label: i18n.t('mist.applicationMenu.develop.devToolsWebview', {
  401. webview: webview.name
  402. }),
  403. click() {
  404. Windows.getByType('main').send(
  405. 'uiAction_toggleWebviewDevTool',
  406. webview._id
  407. );
  408. }
  409. });
  410. });
  411. // wallet
  412. } else {
  413. devtToolsSubMenu = [
  414. {
  415. label: i18n.t('mist.applicationMenu.develop.devToolsWalletUI'),
  416. accelerator: 'Alt+CommandOrControl+I',
  417. click() {
  418. curWindow = BrowserWindow.getFocusedWindow();
  419. if (curWindow) {
  420. curWindow.toggleDevTools();
  421. }
  422. }
  423. }
  424. ];
  425. }
  426. devToolsMenu.push({
  427. label: i18n.t('mist.applicationMenu.develop.devTools'),
  428. submenu: devtToolsSubMenu
  429. });
  430. if (Settings.uiMode === 'mist') {
  431. devToolsMenu.push({
  432. label: i18n.t('mist.applicationMenu.develop.openRemix'),
  433. enabled: true,
  434. click() {
  435. Windows.createPopup('remix');
  436. }
  437. });
  438. }
  439. devToolsMenu.push({
  440. label: i18n.t('mist.applicationMenu.develop.runTests'),
  441. enabled: Settings.uiMode === 'mist',
  442. click() {
  443. Windows.getByType('main').send('uiAction_runTests', 'webview');
  444. }
  445. });
  446. devToolsMenu.push({
  447. label: i18n.t('mist.applicationMenu.develop.logFiles'),
  448. click() {
  449. try {
  450. const shown = shell.showItemInFolder(
  451. path.join(Settings.userDataPath, 'logs', 'all.log')
  452. );
  453. if (!shown) {
  454. shell.showItemInFolder(
  455. path.join(Settings.userDataPath, 'logs', 'all.log.0')
  456. );
  457. }
  458. } catch (error) {
  459. log.error(error);
  460. }
  461. }
  462. });
  463. // add node switching menu
  464. devToolsMenu.push({
  465. type: 'separator'
  466. });
  467. // add node switch
  468. if (process.platform === 'darwin' || process.platform === 'win32') {
  469. const nodeSubmenu = [];
  470. const ethClient = ClientBinaryManager.getClient('eth');
  471. const gethClient = ClientBinaryManager.getClient('geth');
  472. if (gethClient) {
  473. nodeSubmenu.push({
  474. label: `Geth ${gethClient.version}`,
  475. checked: ethereumNode.isOwnNode && ethereumNode.isGeth,
  476. enabled: ethereumNode.isOwnNode,
  477. type: 'checkbox',
  478. click() {
  479. restartNode('geth', null, 'fast', webviews);
  480. }
  481. });
  482. }
  483. if (ethClient) {
  484. nodeSubmenu.push({
  485. label: `Eth ${ethClient.version} (C++)`,
  486. checked: ethereumNode.isOwnNode && ethereumNode.isEth,
  487. enabled: ethereumNode.isOwnNode,
  488. // enabled: false,
  489. type: 'checkbox',
  490. click() {
  491. restartNode('eth');
  492. }
  493. });
  494. }
  495. devToolsMenu.push({
  496. label: i18n.t('mist.applicationMenu.develop.ethereumNode'),
  497. submenu: nodeSubmenu
  498. });
  499. }
  500. // add network switch
  501. devToolsMenu.push({
  502. label: i18n.t('mist.applicationMenu.develop.network'),
  503. submenu: [
  504. {
  505. label: i18n.t('mist.applicationMenu.develop.mainNetwork'),
  506. accelerator: 'CommandOrControl+Alt+1',
  507. checked: store.getState().nodes.network === 'main',
  508. enabled: store.getState().nodes.network !== 'private',
  509. type: 'checkbox',
  510. click() {
  511. changeNodeNetwork('main', webviews);
  512. }
  513. },
  514. {
  515. label: 'Ropsten - Test network',
  516. accelerator: 'CommandOrControl+Alt+2',
  517. checked: store.getState().nodes.network === 'ropsten',
  518. enabled: store.getState().nodes.network !== 'private',
  519. type: 'checkbox',
  520. click() {
  521. changeNodeNetwork('ropsten', webviews);
  522. }
  523. },
  524. {
  525. label: 'Rinkeby - Test network',
  526. accelerator: 'CommandOrControl+Alt+3',
  527. checked: store.getState().nodes.network === 'rinkeby',
  528. enabled: store.getState().nodes.network !== 'private',
  529. type: 'checkbox',
  530. click() {
  531. changeNodeNetwork('rinkeby', webviews);
  532. }
  533. }
  534. // {
  535. // label: 'Solo network',
  536. // accelerator: 'CommandOrControl+Alt+4',
  537. // checked: ethereumNode.isOwnNode && ethereumNode.isDevNetwork,
  538. // enabled: ethereumNode.isOwnNode,
  539. // type: 'checkbox',
  540. // click() {
  541. // restartNode(ethereumNode.type, 'dev');
  542. // }
  543. // }
  544. ]
  545. });
  546. // add sync mode switch
  547. devToolsMenu.push({
  548. label: i18n.t('mist.applicationMenu.develop.syncMode'),
  549. submenu: [
  550. {
  551. label: i18n.t('mist.applicationMenu.develop.syncModeLight'),
  552. enabled: ethereumNode.isOwnNode && !ethereumNode.isDevNetwork,
  553. checked: store.getState().nodes.local.syncMode === 'light',
  554. type: 'checkbox',
  555. click() {
  556. changeNodeSyncMode('light', webviews);
  557. }
  558. },
  559. {
  560. label: i18n.t('mist.applicationMenu.develop.syncModeFast'),
  561. enabled: ethereumNode.isOwnNode && !ethereumNode.isDevNetwork,
  562. checked: store.getState().nodes.local.syncMode === 'fast',
  563. type: 'checkbox',
  564. click() {
  565. changeNodeSyncMode('fast', webviews);
  566. }
  567. },
  568. {
  569. label: i18n.t('mist.applicationMenu.develop.syncModeFull'),
  570. enabled: ethereumNode.isOwnNode,
  571. checked: store.getState().nodes.local.syncMode === 'full',
  572. type: 'checkbox',
  573. click() {
  574. changeNodeSyncMode('full', webviews);
  575. }
  576. },
  577. {
  578. label: i18n.t('mist.applicationMenu.develop.syncModeNoSync'),
  579. enabled: ethereumNode.isOwnNode && !ethereumNode.isDevNetwork,
  580. checked: store.getState().nodes.local.syncMode === 'nosync',
  581. type: 'checkbox',
  582. click() {
  583. changeNodeSyncMode('nosync', webviews);
  584. }
  585. }
  586. ]
  587. });
  588. // Enables mining menu: only in Solo mode and Ropsten network (testnet)
  589. if (
  590. ethereumNode.isOwnNode &&
  591. (ethereumNode.isTestNetwork || ethereumNode.isDevNetwork)
  592. ) {
  593. devToolsMenu.push(
  594. {
  595. type: 'separator'
  596. },
  597. {
  598. label: global.mining
  599. ? i18n.t('mist.applicationMenu.develop.stopMining')
  600. : i18n.t('mist.applicationMenu.develop.startMining'),
  601. accelerator: 'CommandOrControl+Shift+M',
  602. enabled: true,
  603. click() {
  604. if (global.mining) {
  605. stopMining(webviews);
  606. } else {
  607. startMining(webviews);
  608. }
  609. }
  610. }
  611. );
  612. }
  613. if (global.mode !== 'wallet') {
  614. devToolsMenu.push(
  615. {
  616. type: 'separator'
  617. },
  618. {
  619. label: i18n.t('mist.applicationMenu.develop.enableSwarm'),
  620. enabled: true,
  621. checked: [SwarmState.Enabling, SwarmState.Enabled].includes(
  622. global.store.getState().settings.swarmState
  623. ),
  624. type: 'checkbox',
  625. click() {
  626. store.dispatch(toggleSwarm());
  627. }
  628. }
  629. );
  630. }
  631. menu.push({
  632. label:
  633. (global.mining ? '⛏ ' : '') +
  634. i18n.t('mist.applicationMenu.develop.label'),
  635. submenu: devToolsMenu
  636. });
  637. // WINDOW
  638. menu.push({
  639. label: i18n.t('mist.applicationMenu.window.label'),
  640. role: 'window',
  641. submenu: [
  642. {
  643. label: i18n.t('mist.applicationMenu.window.minimize'),
  644. accelerator: 'CommandOrControl+M',
  645. role: 'minimize'
  646. },
  647. {
  648. label: i18n.t('mist.applicationMenu.window.close'),
  649. accelerator: 'CommandOrControl+W',
  650. role: 'close'
  651. },
  652. {
  653. type: 'separator'
  654. },
  655. {
  656. label: i18n.t('mist.applicationMenu.window.toFront'),
  657. role: 'front'
  658. }
  659. ]
  660. });
  661. // HELP
  662. const helpMenu = [];
  663. if (
  664. process.platform === 'freebsd' ||
  665. process.platform === 'linux' ||
  666. process.platform === 'sunos' ||
  667. process.platform === 'win32'
  668. ) {
  669. helpMenu.push(
  670. {
  671. label: i18n.t('mist.applicationMenu.app.about', {
  672. app: Settings.appName
  673. }),
  674. click() {
  675. Windows.createPopup('about');
  676. }
  677. },
  678. {
  679. label: i18n.t('mist.applicationMenu.app.checkForUpdates'),
  680. click() {
  681. updateChecker.runVisibly();
  682. }
  683. }
  684. );
  685. }
  686. helpMenu.push(
  687. {
  688. label: i18n.t('mist.applicationMenu.help.mistWiki'),
  689. click() {
  690. shell.openExternal('https://github.com/ethereum/mist/wiki');
  691. }
  692. },
  693. {
  694. label: i18n.t('mist.applicationMenu.help.gitter'),
  695. click() {
  696. shell.openExternal('https://gitter.im/ethereum/mist');
  697. }
  698. },
  699. {
  700. label: i18n.t('mist.applicationMenu.help.reportBug'),
  701. click() {
  702. shell.openExternal('https://github.com/ethereum/mist/issues');
  703. }
  704. }
  705. );
  706. menu.push({
  707. label: i18n.t('mist.applicationMenu.help.label'),
  708. role: 'help',
  709. submenu: helpMenu
  710. });
  711. return menu;
  712. };
  713. module.exports = createMenu;