app/src/electron/menu/build-default.ts
crusader 41fc9f5e6d ing
2018-10-11 00:38:56 +09:00

331 lines
8.9 KiB
TypeScript

import { Menu, ipcMain, shell, app } from 'electron';
import { ensureDir } from 'fs-extra';
import { ensureItemIds } from './ensure-item-ids';
import { MenuEvent } from '../../commons/type/menu-event';
import { openDirectorySafe } from '../shell';
export function buildDefaultMenu(
): Electron.Menu {
const template = new Array<Electron.MenuItemConstructorOptions>();
const separator: Electron.MenuItemConstructorOptions = { type: 'separator' };
if (__DARWIN__) {
template.push({
label: 'overFlow Scanner',
submenu: [
{
label: 'About overFlow Scanner',
click: emit('show-about'),
id: 'about',
},
separator,
{
label: 'Preferences…',
id: 'preferences',
accelerator: 'CmdOrCtrl+,',
click: emit('show-preferences'),
},
separator,
{
role: 'services',
submenu: [],
},
separator,
{ role: 'hide' },
{ role: 'hideothers' },
{ role: 'unhide' },
separator,
{ role: 'quit' },
],
});
}
const fileMenu: Electron.MenuItemConstructorOptions = {
label: __DARWIN__ ? 'File' : '&File',
submenu: [
{
label: __DARWIN__ ? 'Save…' : 'Save…',
id: 'save',
click: emit('save'),
accelerator: 'CmdOrCtrl+S',
},
separator,
{
label: __DARWIN__ ? 'Export as…' : 'Export as…',
submenu: [
{
label: __DARWIN__ ? 'PNG…' : 'PNG…',
id: 'export-png',
click: emit('show-export-png'),
},
{
label: __DARWIN__ ? 'JPG…' : 'JPG…',
id: 'export-jpg',
click: emit('show-export-jpg'),
enabled: false,
},
{
label: __DARWIN__ ? 'svg…' : 'svg…',
id: 'export-svg',
click: emit('show-export-svg'),
enabled: false,
},
{
label: __DARWIN__ ? 'CSV…' : 'CSV…',
id: 'export-csv',
click: emit('show-export-csv'),
enabled: false,
},
],
},
separator,
{
label: __DARWIN__ ? 'Print…' : 'Print…',
id: 'print',
click: emit('show-print'),
enabled: false,
},
],
};
template.push(fileMenu);
template.push({
label: __DARWIN__ ? 'View' : '&View',
submenu: [
{
label: __DARWIN__ ? 'Toggle Full Screen' : 'Toggle &full screen',
role: 'togglefullscreen',
},
separator,
{
label: __DARWIN__ ? 'Reset Zoom' : 'Reset zoom',
accelerator: 'CmdOrCtrl+0',
click: zoom(ZoomDirection.Reset),
},
{
label: __DARWIN__ ? 'Zoom In' : 'Zoom in',
accelerator: 'CmdOrCtrl+=',
click: zoom(ZoomDirection.In),
},
{
label: __DARWIN__ ? 'Zoom Out' : 'Zoom out',
accelerator: 'CmdOrCtrl+-',
click: zoom(ZoomDirection.Out),
},
separator,
{
label: '&Reload',
id: 'reload-window',
// Ctrl+Alt is interpreted as AltGr on international keyboards and this
// can clash with other shortcuts. We should always use Ctrl+Shift for
// chorded shortcuts, but this menu item is not a user-facing feature
// so we are going to keep this one around and save Ctrl+Shift+R for
// a different shortcut in the future...
accelerator: 'CmdOrCtrl+Alt+R',
click(item: any, focusedWindow: Electron.BrowserWindow) {
if (focusedWindow) {
focusedWindow.reload();
}
},
visible: __RELEASE_CHANNEL__ === 'development',
},
{
id: 'show-devtools',
label: __DARWIN__
? 'Toggle Developer Tools'
: '&Toggle developer tools',
accelerator: (() => {
return __DARWIN__ ? 'Alt+Command+I' : 'Ctrl+Shift+I';
})(),
click(item: any, focusedWindow: Electron.BrowserWindow) {
if (focusedWindow) {
focusedWindow.webContents.toggleDevTools();
}
},
},
],
});
if (__DARWIN__) {
template.push({
role: 'window',
submenu: [
{ role: 'minimize' },
{ role: 'zoom' },
{ role: 'close' },
separator,
{ role: 'front' },
],
});
}
const settingMenu: Electron.MenuItemConstructorOptions = {
label: __DARWIN__ ? 'Discovery' : '&Discovery',
submenu: [
{
label: __DARWIN__ ? 'Setting…' : 'Setting…',
id: 'preferences',
accelerator: 'CmdOrCtrl+,',
click: emit('show-preferences'),
},
{
label: __DARWIN__ ? 'Language…' : 'Language…',
submenu: [
{
label: __DARWIN__ ? 'English…' : 'English…',
id: 'language-english',
click: emit('change-language-english'),
},
{
label: __DARWIN__ ? 'Korean…' : 'Korean…',
id: 'language-korean',
click: emit('change-language-korean'),
},
],
},
],
};
template.push(settingMenu);
const submitIssueItem: Electron.MenuItemConstructorOptions = {
label: __DARWIN__ ? 'Report Issue…' : 'Report issue…',
click() {
shell.openExternal('https://github.com/desktop/desktop/issues/new/choose');
},
};
const contactSupportItem: Electron.MenuItemConstructorOptions = {
label: __DARWIN__ ? 'Contact overFlow Scanner Support…' : '&Contact overFlow Scanner support…',
click() {
shell.openExternal(
`https://github.com/contact?from_desktop_app=1&app_version=${app.getVersion()}`
);
},
};
const showUserGuides: Electron.MenuItemConstructorOptions = {
label: 'Show User Guides',
click() {
shell.openExternal('https://help.github.com/desktop/guides/');
},
};
const helpItems = [
submitIssueItem,
contactSupportItem,
showUserGuides,
];
if (__DARWIN__) {
template.push({
role: 'help',
submenu: helpItems,
});
} else {
template.push({
label: '&Help',
submenu: [
...helpItems,
separator,
{
label: '&About overFlow Scanner',
click: emit('show-about'),
id: 'about',
},
],
});
}
ensureItemIds(template);
return Menu.buildFromTemplate(template);
}
type ClickHandler = (
menuItem: Electron.MenuItem,
browserWindow: Electron.BrowserWindow,
event: Electron.Event
) => void;
/**
* Utility function returning a Click event handler which, when invoked, emits
* the provided menu event over IPC.
*/
function emit(name: MenuEvent): ClickHandler {
return (menuItem, window) => {
if (window) {
window.webContents.send('menu-event', { name });
} else {
ipcMain.emit('menu-event', { name });
}
};
}
enum ZoomDirection {
Reset,
In,
Out,
}
/** The zoom steps that we support, these factors must sorted */
const ZoomInFactors = [1, 1.1, 1.25, 1.5, 1.75, 2];
const ZoomOutFactors = ZoomInFactors.slice().reverse();
/**
* Returns the element in the array that's closest to the value parameter. Note
* that this function will throw if passed an empty array.
*/
function findClosestValue(arr: Array<number>, value: number) {
return arr.reduce((previous, current) => {
return Math.abs(current - value) < Math.abs(previous - value)
? current
: previous;
});
}
/**
* Figure out the next zoom level for the given direction and alert the renderer
* about a change in zoom factor if necessary.
*/
function zoom(direction: ZoomDirection): ClickHandler {
return (menuItem, window) => {
if (!window) {
return;
}
const { webContents } = window;
if (direction === ZoomDirection.Reset) {
webContents.setZoomFactor(1);
webContents.send('zoom-factor-changed', 1);
} else {
webContents.getZoomFactor(rawZoom => {
const zoomFactors =
direction === ZoomDirection.In ? ZoomInFactors : ZoomOutFactors;
// So the values that we get from getZoomFactor are floating point
// precision numbers from chromium that don't always round nicely so
// we'll have to do a little trick to figure out which of our supported
// zoom factors the value is referring to.
const currentZoom = findClosestValue(zoomFactors, rawZoom);
const nextZoomLevel = zoomFactors.find(
f =>
direction === ZoomDirection.In ? f > currentZoom : f < currentZoom
);
// If we couldn't find a zoom level (likely due to manual manipulation
// of the zoom factor in devtools) we'll just snap to the closest valid
// factor we've got.
const newZoom =
nextZoomLevel === undefined ? currentZoom : nextZoomLevel;
webContents.setZoomFactor(newZoom);
webContents.send('zoom-factor-changed', newZoom);
});
}
};
}