src/main/menu.js
import { dialog, app, shell, Menu, ipcMain as ipc,
BrowserWindow } from 'electron';
import * as path from 'path';
import { launch, launchNewNotebook } from './launch';
const kernelspecs = require('kernelspecs');
function send(focusedWindow, eventName, obj) {
if (!focusedWindow) {
console.error('renderer window not in focus (are your devtools open?)');
return;
}
focusedWindow.webContents.send(eventName, obj);
}
function createSender(eventName, obj) {
return (item, focusedWindow) => {
send(focusedWindow, eventName, obj);
};
}
export function authAndPublish(item, focusedWindow) {
const win = new BrowserWindow({ show: false,
webPreferences: { zoomFactor: 0.75 } });
if (process.env.AUTHENTICATED) {
send(focusedWindow, 'menu:github:auth');
return;
}
win.webContents.on('dom-ready', () => {
if (win.getURL().indexOf('callback?code=') !== -1) {
win.webContents.executeJavaScript(`
require('electron').ipcRenderer.send('auth', document.body.textContent);
`);
ipc.on('auth', (event, auth) => {
send(focusedWindow, 'menu:github:auth', JSON.parse(auth).access_token);
process.env.AUTHENTICATED = true;
win.close();
return;
});
} else {
win.show();
}
});
win.loadURL('https://oauth.nteract.io/github');
}
export const fileSubMenus = {
new: {
label: '&New',
accelerator: 'CmdOrCtrl+N',
},
open: {
label: '&Open',
click: () => {
const opts = {
title: 'Open a notebook',
filters: [
{ name: 'Notebooks', extensions: ['ipynb'] },
],
properties: [
'openFile',
],
defaultPath: app.getPath('home'),
};
dialog.showOpenDialog(opts, (fname) => {
if (fname) {
launch(fname[0]);
}
});
},
accelerator: 'CmdOrCtrl+O',
},
save: {
label: '&Save',
click: createSender('menu:save'),
accelerator: 'CmdOrCtrl+S',
},
saveAs: {
label: 'Save &As',
click: (item, focusedWindow) => {
const opts = {
title: 'Save Notebook As',
filters: [{ name: 'Notebooks', extensions: ['ipynb'] }],
defaultPath: app.getPath('home'),
};
dialog.showSaveDialog(opts, (filename) => {
if (!filename) {
return;
}
const ext = path.extname(filename) === '' ? '.ipynb' : '';
send(focusedWindow, 'menu:save-as', `${filename}${ext}`);
});
},
accelerator: 'CmdOrCtrl+Shift+S',
},
publish: {
label: '&Publish',
submenu: [
{
label: '&User Gist',
click: authAndPublish,
},
{
label: '&Anonymous Gist',
click: createSender('menu:publish:gist'),
},
],
},
};
export const file = {
label: '&File',
submenu: [
fileSubMenus.new,
fileSubMenus.open,
fileSubMenus.save,
fileSubMenus.saveAs,
fileSubMenus.publish,
],
};
export const edit = {
label: 'Edit',
submenu: [
{
label: 'Cut',
accelerator: 'CmdOrCtrl+X',
role: 'cut',
},
{
label: 'Copy',
accelerator: 'CmdOrCtrl+C',
role: 'copy',
},
{
label: 'Paste',
accelerator: 'CmdOrCtrl+V',
role: 'paste',
},
{
label: 'Select All',
accelerator: 'CmdOrCtrl+A',
role: 'selectall',
},
{
type: 'separator',
},
{
label: 'New Code Cell',
accelerator: 'CmdOrCtrl+Shift+N',
click: createSender('menu:new-code-cell'),
},
{
label: 'Copy Cell',
accelerator: 'CmdOrCtrl+Shift+C',
click: createSender('menu:copy-cell'),
},
{
label: 'Cut Cell',
accelerator: 'CmdOrCtrl+Shift+X',
click: createSender('menu:cut-cell'),
},
{
label: 'Paste Cell',
accelerator: 'CmdOrCtrl+Shift+V',
click: createSender('menu:paste-cell'),
},
],
};
export const cell = {
label: 'Cell',
submenu: [
{
label: 'Run All',
click: createSender('menu:run-all'),
},
{
label: 'Clear All',
click: createSender('menu:clear-all'),
},
],
};
const theme_menu = [
{
label: 'Light',
click: createSender('menu:theme', 'light'),
},
{
label: 'Dark',
click: createSender('menu:theme', 'dark'),
},
{
label: 'Classic',
click: createSender('menu:theme', 'classic'),
},
];
const today = new Date();
const day = today.getDate();
const month = today.getMonth() + 1;
if (month === 12) {
theme_menu.push(
{
label: 'Hohoho',
click: createSender('menu:theme', 'christmas'),
});
} else if (month === 10 && day === 31) {
theme_menu.push({
label: 'Mwaaahahahhah',
click: createSender('menu:theme', 'halloween'),
});
}
export const view = {
label: 'View',
submenu: [
{
label: 'Reload',
accelerator: 'CmdOrCtrl+R',
click: (item, focusedWindow) => {
if (focusedWindow) {
focusedWindow.reload();
}
},
},
{
label: 'Toggle Full Screen',
accelerator: (() => {
if (process.platform === 'darwin') {
return 'Ctrl+Command+F';
}
return 'F11';
})(),
click: (item, focusedWindow) => {
if (focusedWindow) {
focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
}
},
},
{
label: 'Toggle Developer Tools',
accelerator: (() => {
if (process.platform === 'darwin') {
return 'Alt+Command+I';
}
return 'Ctrl+Shift+I';
})(),
click: (item, focusedWindow) => {
if (focusedWindow) {
focusedWindow.toggleDevTools();
}
},
},
{
label: 'Zoom In',
accelerator: 'CmdOrCtrl+=',
click: createSender('menu:zoom-in'),
},
{
label: 'Zoom Out',
accelerator: 'CmdOrCtrl+-',
click: createSender('menu:zoom-out'),
},
{
label: 'Theme',
submenu: theme_menu,
},
],
};
const windowDraft = {
label: 'Window',
role: 'window',
submenu: [
{
label: 'Minimize',
accelerator: 'CmdOrCtrl+M',
role: 'minimize',
},
{
label: 'Close',
accelerator: 'CmdOrCtrl+W',
role: 'close',
},
],
};
if (process.platform === 'darwin') {
windowDraft.submenu.push(
{
type: 'separator',
},
{
label: 'Bring All to Front',
role: 'front',
}
);
}
export const window = windowDraft;
export const help = {
label: 'Help',
role: 'help',
submenu: [
{
label: 'Learn More',
click: () => { shell.openExternal('http://github.com/nteract/nteract'); },
},
],
};
const name = 'nteract';
app.setName(name);
export const named = {
label: name,
submenu: [
{
label: `About ${name}`,
role: 'about',
},
{
type: 'separator',
},
{
label: 'Services',
role: 'services',
submenu: [],
},
{
type: 'separator',
},
{
label: `Hide ${name}`,
accelerator: 'Command+H',
role: 'hide',
},
{
label: 'Hide Others',
accelerator: 'Command+Alt+H',
role: 'hideothers',
},
{
label: 'Show All',
role: 'unhide',
},
{
type: 'separator',
},
{
label: 'Quit',
accelerator: 'Command+Q',
click: () => app.quit(),
},
],
};
export function generateDefaultTemplate() {
const template = [];
if (process.platform === 'darwin') {
template.push(named);
}
template.push(file);
template.push(edit);
template.push(view);
template.push(window);
template.push(help);
return template;
}
export const defaultMenu = Menu.buildFromTemplate(generateDefaultTemplate());
export function loadFullMenu() {
return kernelspecs.findAll().then((kernelSpecs) => {
function generateSubMenu(kernelName) {
return {
label: kernelSpecs[kernelName].spec.display_name,
click: createSender('menu:new-kernel', kernelName),
};
}
const kernelMenuItems = Object.keys(kernelSpecs).map(generateSubMenu);
const newNotebookItems = Object.keys(kernelSpecs)
.map(kernelName => ({
label: kernelSpecs[kernelName].spec.display_name,
click: () => launchNewNotebook(kernelName),
}));
const languageMenu = {
label: '&Language',
submenu: [
{
label: '&Kill Running Kernel',
click: createSender('menu:kill-kernel'),
},
{
label: '&Interrupt Running Kernel',
click: createSender('menu:interrupt-kernel'),
},
{
label: 'Restart Running Kernel',
click: createSender('menu:restart-kernel'),
},
{
label: 'Restart and Clear All Cells',
click: createSender('menu:restart-and-clear-all'),
},
{
type: 'separator',
},
// All the available kernels
...kernelMenuItems,
],
};
const template = [];
if (process.platform === 'darwin') {
template.push(named);
}
const fileWithNew = {
label: '&File',
submenu: [
{
label: '&New',
submenu: newNotebookItems,
},
fileSubMenus.open,
fileSubMenus.save,
fileSubMenus.saveAs,
fileSubMenus.publish,
],
};
template.push(fileWithNew);
template.push(edit);
template.push(cell);
template.push(view);
// Application specific functionality should go before window and help
template.push(languageMenu);
template.push(window);
template.push(help);
const menu = Menu.buildFromTemplate(template);
return menu;
});
}