diff --git a/app/config.js b/app/config.js index 10a7bd5b..1f8ed28d 100644 --- a/app/config.js +++ b/app/config.js @@ -1,13 +1,11 @@ -const {homedir} = require('os'); const {statSync, renameSync, readFileSync, writeFileSync} = require('fs'); -const {resolve} = require('path'); const vm = require('vm'); const {dialog} = require('electron'); -const isDev = require('electron-is-dev'); const gaze = require('gaze'); const Config = require('electron-config'); const notify = require('./notify'); +const _paths = require('./config/paths'); // local storage const winCfg = new Config({ @@ -17,22 +15,8 @@ const winCfg = new Config({ } }); -let configDir = homedir(); -if (isDev) { - // if a local config file exists, use it - try { - const devDir = resolve(__dirname, '..'); - const devConfig = resolve(devDir, '.hyper.js'); - statSync(devConfig); - configDir = devDir; - console.log('using config file:', devConfig); - } catch (err) { - // ignore - } -} - -const path = resolve(configDir, '.hyper.js'); -const pathLegacy = resolve(configDir, '.hyperterm.js'); +const path = _paths.confPath; +const pathLegacy = _paths.pathLegacy; const watchers = []; @@ -115,7 +99,7 @@ exports.init = function () { exec(readFileSync(path, 'utf8')); } catch (err) { console.log('read error', path, err.message); - const defaultConfig = readFileSync(resolve(__dirname, 'config-default.js')); + const defaultConfig = readFileSync(_paths.defaultConfig); try { console.log('attempting to write default config to', path); exec(defaultConfig); @@ -132,7 +116,7 @@ exports.init = function () { exports.getConfigDir = function () { // expose config directory to load plugin from the right place - return configDir; + return _paths.confDir; }; exports.getConfig = function () { diff --git a/app/config-default.js b/app/config/config-default.js similarity index 100% rename from app/config-default.js rename to app/config/config-default.js diff --git a/app/config/paths.js b/app/config/paths.js new file mode 100644 index 00000000..64063420 --- /dev/null +++ b/app/config/paths.js @@ -0,0 +1,36 @@ +// This module exports paths, names, and other metadata that is referenced +const {homedir} = require('os'); +const {statSync} = require('fs'); +const {resolve} = require('path'); +const isDev = require('electron-is-dev'); + +const conf = '.hyper.js'; +const defaultConf = 'config-default.js'; +const legacyConf = '.hyperterm.js'; +const homeDir = homedir(); + +let confPath = resolve(homeDir, conf); +let confDir = homeDir; + +const devDir = resolve(__dirname, '../..'); +const devConfig = resolve(devDir, conf); +const defaultConfig = resolve(__dirname, defaultConf); +const pathLegacy = resolve(homeDir, legacyConf); + +const icon = resolve(__dirname, 'static/icon.png'); + +if (isDev) { + // if a local config file exists, use it + try { + statSync(devConfig); + confPath = devConfig; + confDir = devDir; + console.log('using config file:', confPath); + } catch (err) { + // ignore + } +} + +module.exports = { + pathLegacy, confDir, confPath, conf, defaultConfig, defaultConf, icon +}; diff --git a/app/index.js b/app/index.js index e4456b1c..fe41ee43 100644 --- a/app/index.js +++ b/app/index.js @@ -49,7 +49,7 @@ const isDev = require('electron-is-dev'); // Ours const AutoUpdater = require('./auto-updater'); const toElectronBackgroundColor = require('./utils/to-electron-background-color'); -const createMenu = require('./menu'); +const AppMenu = require('./menus/menu'); const createRPC = require('./rpc'); const notify = require('./notify'); const fetchNotifications = require('./notifications'); @@ -390,13 +390,12 @@ app.on('ready', () => installDevExtensions(isDev).then(() => { } }); - const setupMenu = () => { - const tpl = plugins.decorateMenu(createMenu({ - createWindow, - updatePlugins: () => { + const makeMenu = () => { + const menu = plugins.decorateMenu( + AppMenu(createWindow, () => { plugins.updatePlugins({force: true}); - } - })); + }) + ); // If we're on Mac make a Dock Menu if (process.platform === 'darwin') { @@ -409,12 +408,14 @@ app.on('ready', () => installDevExtensions(isDev).then(() => { app.dock.setMenu(dockMenu); } - Menu.setApplicationMenu(Menu.buildFromTemplate(tpl)); + Menu.setApplicationMenu( + Menu.buildFromTemplate(menu) + ); }; const load = () => { plugins.onApp(app); - setupMenu(); + makeMenu(); }; load(); diff --git a/app/menu.js b/app/menu.js deleted file mode 100644 index cca97857..00000000 --- a/app/menu.js +++ /dev/null @@ -1,384 +0,0 @@ -const os = require('os'); -const path = require('path'); -const {app, shell, dialog} = require('electron'); - -const {accelerators} = require('./accelerators'); -const {getConfigDir} = require('./config'); - -const isMac = process.platform === 'darwin'; -const appName = app.getName(); - -// based on and inspired by -// https://github.com/sindresorhus/anatine/blob/master/menu.js - -module.exports = ({createWindow, updatePlugins}) => { - const osxApplicationMenu = { - // This menu label is overrided by OSX to be the appName - // The label is set to appName here so it matches actual behavior - label: appName, - submenu: [ - { - role: 'about' - }, - { - type: 'separator' - }, - { - label: 'Preferences...', - accelerator: accelerators.preferences, - click() { - const configFile = path.resolve(getConfigDir(), '.hyper.js'); - shell.openItem(configFile); - } - }, - { - type: 'separator' - }, - { - role: 'services', - submenu: [] - }, - { - type: 'separator' - }, - { - role: 'hide' - }, - { - role: 'hideothers' - }, - { - role: 'unhide' - }, - { - type: 'separator' - }, - { - role: 'quit' - } - ] - }; - - const shellOrFileMenu = { - label: isMac ? 'Shell' : 'File', - submenu: [ - { - label: 'New Window', - accelerator: accelerators.newWindow, - click() { - createWindow(); - } - }, - { - label: 'New Tab', - accelerator: accelerators.newTab, - click(item, focusedWindow) { - if (focusedWindow) { - focusedWindow.rpc.emit('termgroup add req'); - } else { - createWindow(); - } - } - }, - { - type: 'separator' - }, - { - label: 'Split Vertically', - accelerator: accelerators.splitVertically, - click(item, focusedWindow) { - if (focusedWindow) { - focusedWindow.rpc.emit('split request vertical'); - } - } - }, - { - label: 'Split Horizontally', - accelerator: accelerators.splitHorizontally, - click(item, focusedWindow) { - if (focusedWindow) { - focusedWindow.rpc.emit('split request horizontal'); - } - } - }, - { - type: 'separator' - }, - { - label: 'Close Session', - accelerator: accelerators.closeSession, - click(item, focusedWindow) { - if (focusedWindow) { - focusedWindow.rpc.emit('termgroup close req'); - } - } - }, - { - label: isMac ? 'Close Window' : 'Quit', - role: 'close', - accelerator: accelerators.closeWindow - } - ] - }; - - const editMenu = { - label: 'Edit', - submenu: [ - { - role: 'undo', - accelerator: accelerators.undo - }, - { - role: 'redo', - accelerator: accelerators.redo - }, - { - type: 'separator' - }, - { - role: 'cut', - accelerator: accelerators.cut - }, - { - role: 'copy', - accelerator: accelerators.copy - }, - { - role: 'paste', - accelerator: accelerators.paste - }, - { - role: 'selectall', - accelerator: accelerators.selectAll - }, - { - type: 'separator' - }, - { - label: 'Clear', - accelerator: accelerators.clear, - click(item, focusedWindow) { - if (focusedWindow) { - focusedWindow.rpc.emit('session clear req'); - } - } - } - ] - }; - - if (!isMac) { - editMenu.submenu.push( - {type: 'separator'}, - { - label: 'Preferences...', - accelerator: accelerators.preferences, - click() { - const configFile = path.resolve(getConfigDir(), '.hyper.js'); - shell.openItem(configFile); - } - } - ); - } - - const viewMenu = { - label: 'View', - submenu: [ - { - label: 'Reload', - accelerator: accelerators.reload, - click(item, focusedWindow) { - if (focusedWindow) { - focusedWindow.rpc.emit('reload'); - } - } - }, - { - label: 'Full Reload', - accelerator: accelerators.fullReload, - click(item, focusedWindow) { - if (focusedWindow) { - focusedWindow.reload(); - } - } - }, - { - label: 'Toggle Developer Tools', - accelerator: accelerators.toggleDevTools, - click(item, focusedWindow) { - if (focusedWindow) { - const webContents = focusedWindow.webContents; - if (webContents.isDevToolsOpened()) { - webContents.closeDevTools(); - } else { - webContents.openDevTools({mode: 'detach'}); - } - } - } - }, - { - type: 'separator' - }, - { - label: 'Reset Zoom Level', - accelerator: accelerators.resetZoom, - click(item, focusedWindow) { - if (focusedWindow) { - focusedWindow.rpc.emit('reset fontSize req'); - } - } - }, - { - label: 'Zoom In', - accelerator: accelerators.zoomIn, - click(item, focusedWindow) { - if (focusedWindow) { - focusedWindow.rpc.emit('increase fontSize req'); - } - } - }, - { - label: 'Zoom Out', - accelerator: accelerators.zoomOut, - click(item, focusedWindow) { - if (focusedWindow) { - focusedWindow.rpc.emit('decrease fontSize req'); - } - } - } - ] - }; - - const pluginsMenu = { - label: 'Plugins', - submenu: [ - { - label: 'Update All Now', - accelerator: accelerators.updatePlugins, - click() { - updatePlugins(); - } - } - ] - }; - - const windowMenu = { - role: 'window', - submenu: [ - { - role: 'minimize', - accelerator: accelerators.minimize - }, - { - role: 'zoom' - }, - { - type: 'separator' - }, - { - label: 'Show Previous Tab', - accelerator: accelerators.showPreviousTab, - click(item, focusedWindow) { - if (focusedWindow) { - focusedWindow.rpc.emit('move left req'); - } - } - }, - { - label: 'Show Next Tab', - accelerator: accelerators.showNextTab, - click(item, focusedWindow) { - if (focusedWindow) { - focusedWindow.rpc.emit('move right req'); - } - } - }, - { - type: 'separator' - }, - { - label: 'Select Next Pane', - accelerator: accelerators.selectNextPane, - click(item, focusedWindow) { - if (focusedWindow) { - focusedWindow.rpc.emit('next pane req'); - } - } - }, - { - label: 'Select Previous Pane', - accelerator: accelerators.selectPreviousPane, - click(item, focusedWindow) { - if (focusedWindow) { - focusedWindow.rpc.emit('prev pane req'); - } - } - }, - { - type: 'separator' - }, - { - role: 'front' - }, - { - role: 'togglefullscreen' - } - ] - }; - - const helpMenu = { - role: 'help', - submenu: [ - { - label: `${appName} Website`, - click() { - shell.openExternal('https://hyper.is'); - } - }, - { - label: 'Report an Issue...', - click() { - const body = ` - - -- - -${app.getName()} ${app.getVersion()} -Electron ${process.versions.electron} -${process.platform} ${process.arch} ${os.release()}`; - - shell.openExternal(`https://github.com/zeit/hyper/issues/new?body=${encodeURIComponent(body)}`); - } - } - ] - }; - - if (!isMac) { - helpMenu.submenu.push( - {type: 'separator'}, - { - role: 'about', - click() { - dialog.showMessageBox({ - title: `About ${appName}`, - message: `${appName} ${app.getVersion()}`, - detail: 'Created by Guillermo Rauch', - icon: path.join(__dirname, 'static/icon.png'), - buttons: [] - }); - } - } - ); - } - - const menu = [].concat( - isMac ? osxApplicationMenu : [], - shellOrFileMenu, - editMenu, - viewMenu, - pluginsMenu, - windowMenu, - helpMenu - ); - - return menu; -}; diff --git a/app/menus/menu.js b/app/menus/menu.js new file mode 100644 index 00000000..c10fa2a4 --- /dev/null +++ b/app/menus/menu.js @@ -0,0 +1,27 @@ +// menus +const viewMenu = require('./menus/view'); +const shellMenu = require('./menus/shell'); +const editMenu = require('./menus/edit'); +const pluginsMenu = require('./menus/plugins'); +const windowMenu = require('./menus/window'); +const helpMenu = require('./menus/help'); +const darwinMenu = require('./menus/darwin'); + +module.exports = (createWindow, updatePlugins) => { + const menu = [].concat( + shellMenu(createWindow), + editMenu(), + viewMenu(), + pluginsMenu(updatePlugins), + windowMenu(), + helpMenu() + ); + + if (process.platform === 'darwin') { + menu.unshift( + darwinMenu() + ); + } + + return menu; +}; diff --git a/app/menus/menus/darwin.js b/app/menus/menus/darwin.js new file mode 100644 index 00000000..cd28ba68 --- /dev/null +++ b/app/menus/menus/darwin.js @@ -0,0 +1,51 @@ +// This menu label is overrided by OSX to be the appName +// The label is set to appName here so it matches actual behavior +const {app, shell} = require('electron'); +const {accelerators} = require('../../accelerators'); +const {confPath} = require('../../config/paths'); + +module.exports = function () { + return { + label: `${app.getName()}`, + submenu: [ + { + role: 'about' + }, + { + type: 'separator' + }, + { + label: 'Preferences...', + accelerator: accelerators.preferences, + click() { + shell.openItem(confPath); + } + }, + { + type: 'separator' + }, + { + role: 'services', + submenu: [] + }, + { + type: 'separator' + }, + { + role: 'hide' + }, + { + role: 'hideothers' + }, + { + role: 'unhide' + }, + { + type: 'separator' + }, + { + role: 'quit' + } + ] + }; +}; diff --git a/app/menus/menus/edit.js b/app/menus/menus/edit.js new file mode 100644 index 00000000..bb6c0a10 --- /dev/null +++ b/app/menus/menus/edit.js @@ -0,0 +1,65 @@ +const {shell} = require('electron'); +const {accelerators} = require('../../accelerators'); +const {confPath} = require('../../config/paths'); + +module.exports = function () { + const submenu = [ + { + role: 'undo', + accelerator: accelerators.undo + }, + { + role: 'redo', + accelerator: accelerators.redo + }, + { + type: 'separator' + }, + { + role: 'cut', + accelerator: accelerators.cut + }, + { + role: 'copy', + accelerator: accelerators.copy + }, + { + role: 'paste', + accelerator: accelerators.paste + }, + { + role: 'selectall', + accelerator: accelerators.selectAll + }, + { + type: 'separator' + }, + { + label: 'Clear Buffer', + accelerator: accelerators.clear, + click(item, focusedWindow) { + if (focusedWindow) { + focusedWindow.rpc.emit('session clear req'); + } + } + } + ]; + + if (process.platform !== 'darwin') { + submenu.push( + {type: 'separator'}, + { + label: 'Preferences...', + accelerator: accelerators.preferences, + click() { + shell.openItem(confPath); + } + } + ); + } + + return { + label: 'Edit', + submenu + }; +}; diff --git a/app/menus/menus/help.js b/app/menus/menus/help.js new file mode 100644 index 00000000..9bb883c6 --- /dev/null +++ b/app/menus/menus/help.js @@ -0,0 +1,50 @@ +const os = require('os'); +const {app, shell, dialog} = require('electron'); +const {icon} = require('../../config/paths'); + +module.exports = function () { + const submenu = [ + { + label: `${app.getName()} Website`, + click() { + shell.openExternal('https://hyper.is'); + } + }, + { + label: 'Report Issue', + click() { + const body = ` + + - + ${app.getName()} ${app.getVersion()} + Electron ${process.versions.electron} + ${process.platform} ${process.arch} ${os.release()}`; + + shell.openExternal(`https://github.com/zeit/hyper/issues/new?body=${encodeURIComponent(body)}`); + } + } + ]; + + if (process.platform !== 'darwin') { + submenu.push( + {type: 'separator'}, + { + role: 'about', + click() { + dialog.showMessageBox({ + title: `About ${app.getName()}`, + message: `${app.getName()} ${app.getVersion()}`, + detail: 'Created by Guillermo Rauch', + icon, + buttons: [] + }); + } + } + ); + } + + return { + role: 'help', + submenu + }; +}; diff --git a/app/menus/menus/plugins.js b/app/menus/menus/plugins.js new file mode 100644 index 00000000..1abc1b53 --- /dev/null +++ b/app/menus/menus/plugins.js @@ -0,0 +1,16 @@ +const {accelerators} = require('../../accelerators'); + +module.exports = function (update) { + return { + label: 'Plugins', + submenu: [ + { + label: 'Update', + accelerator: accelerators.updatePlugins, + click() { + update(); + } + } + ] + }; +}; diff --git a/app/menus/menus/shell.js b/app/menus/menus/shell.js new file mode 100644 index 00000000..c632bbaf --- /dev/null +++ b/app/menus/menus/shell.js @@ -0,0 +1,67 @@ +const {accelerators} = require('../../accelerators'); + +module.exports = function (createWindow) { + const isMac = process.platform === 'darwin'; + + return { + label: isMac ? 'Shell' : 'File', + submenu: [ + { + label: 'New Window', + accelerator: accelerators.newWindow, + click() { + createWindow(); + } + }, + { + label: 'New Tab', + accelerator: accelerators.newTab, + click(item, focusedWindow) { + if (focusedWindow) { + focusedWindow.rpc.emit('termgroup add req'); + } else { + createWindow(); + } + } + }, + { + type: 'separator' + }, + { + label: 'Split Vertically', + accelerator: accelerators.splitVertically, + click(item, focusedWindow) { + if (focusedWindow) { + focusedWindow.rpc.emit('split request vertical'); + } + } + }, + { + label: 'Split Horizontally', + accelerator: accelerators.splitHorizontally, + click(item, focusedWindow) { + if (focusedWindow) { + focusedWindow.rpc.emit('split request horizontal'); + } + } + }, + { + type: 'separator' + }, + { + label: 'Close Session', + accelerator: accelerators.closeSession, + click(item, focusedWindow) { + if (focusedWindow) { + focusedWindow.rpc.emit('termgroup close req'); + } + } + }, + { + label: isMac ? 'Close Window' : 'Quit', + role: 'close', + accelerator: accelerators.closeWindow + } + ] + }; +}; diff --git a/app/menus/menus/view.js b/app/menus/menus/view.js new file mode 100644 index 00000000..d1b98cbe --- /dev/null +++ b/app/menus/menus/view.js @@ -0,0 +1,71 @@ +const {accelerators} = require('../../accelerators'); + +module.exports = function () { + return { + label: 'View', + submenu: [ + { + label: 'Reload', + accelerator: accelerators.reload, + click(item, focusedWindow) { + if (focusedWindow) { + focusedWindow.rpc.emit('reload'); + } + } + }, + { + label: 'Full Reload', + accelerator: accelerators.fullReload, + click(item, focusedWindow) { + if (focusedWindow) { + focusedWindow.reload(); + } + } + }, + { + label: 'Developer Tools', + accelerator: accelerators.toggleDevTools, + click(item, focusedWindow) { + if (focusedWindow) { + const webContents = focusedWindow.webContents; + if (webContents.isDevToolsOpened()) { + webContents.closeDevTools(); + } else { + webContents.openDevTools({mode: 'detach'}); + } + } + } + }, + { + type: 'separator' + }, + { + label: 'Reset Zoom Level', + accelerator: accelerators.resetZoom, + click(item, focusedWindow) { + if (focusedWindow) { + focusedWindow.rpc.emit('reset fontSize req'); + } + } + }, + { + label: 'Zoom In', + accelerator: accelerators.zoomIn, + click(item, focusedWindow) { + if (focusedWindow) { + focusedWindow.rpc.emit('increase fontSize req'); + } + } + }, + { + label: 'Zoom Out', + accelerator: accelerators.zoomOut, + click(item, focusedWindow) { + if (focusedWindow) { + focusedWindow.rpc.emit('decrease fontSize req'); + } + } + } + ] + }; +}; diff --git a/app/menus/menus/window.js b/app/menus/menus/window.js new file mode 100644 index 00000000..b467b051 --- /dev/null +++ b/app/menus/menus/window.js @@ -0,0 +1,78 @@ +const {accelerators} = require('../../accelerators'); + +module.exports = function () { + return { + role: 'window', + submenu: [ + { + role: 'minimize', + accelerator: accelerators.minimize + }, + { + role: 'zoom' + }, + { + type: 'separator' + }, + { + label: 'Select Tab', + submenu: [ + { + label: 'Previous', + accelerator: accelerators.showPreviousTab, + click(item, focusedWindow) { + if (focusedWindow) { + focusedWindow.rpc.emit('move left req'); + } + } + }, + { + label: 'Next', + accelerator: accelerators.showNextTab, + click(item, focusedWindow) { + if (focusedWindow) { + focusedWindow.rpc.emit('move right req'); + } + } + } + ] + }, + { + type: 'separator' + }, + { + label: 'Select Pane', + submenu: [ + { + label: 'Previous', + accelerator: accelerators.selectNextPane, + click(item, focusedWindow) { + if (focusedWindow) { + focusedWindow.rpc.emit('prev pane req'); + } + } + }, + { + label: 'Next', + accelerator: accelerators.selectPreviousPane, + click(item, focusedWindow) { + if (focusedWindow) { + focusedWindow.rpc.emit('next pane req'); + } + } + } + ] + }, + { + type: 'separator' + }, + { + role: 'front' + }, + { + role: 'togglefullscreen', + accelerators: accelerators.enterFullScreen + } + ] + }; +};