Refactor main process

This commit is contained in:
Igor Gassmann 2018-01-17 23:13:08 -03:00
parent be0e526dbb
commit fd64ae58fe
16 changed files with 450 additions and 631 deletions

64
src/main/Daemon.js Normal file
View file

@ -0,0 +1,64 @@
/* eslint-disable no-console */
import path from 'path';
import { spawn, execSync } from 'child_process';
export default class Daemon {
static path = process.env.LBRY_DAEMON || path.join(__static, 'daemon/lbrynet-daemon');
subprocess;
handlers;
constructor() {
this.handlers = [];
}
launch() {
// Kill any running daemon
if (process.platform === 'win32') {
try {
execSync('taskkill /im lbrynet-daemon.exe /t /f');
} catch (error) {
console.warn(error.message);
}
} else {
try {
execSync('pkill lbrynet-daemon');
} catch (error) {
console.warn(error.message);
}
}
console.log('Launching daemon:', Daemon.path);
this.subprocess = spawn(Daemon.path);
this.subprocess.stdout.on('data', data => console.log(`Daemon: ${data}`));
this.subprocess.stderr.on('data', data => console.error(`Daemon: ${data}`));
this.subprocess.on('exit', () => this.fire('exit'));
this.subprocess.on('error', error => console.error(`Daemon: ${error}`));
}
quit() {
if (process.platform === 'win32') {
try {
execSync(`taskkill /pid ${this.subprocess.pid} /t /f`);
} catch (error) {
console.error(error.message);
}
} else {
this.subprocess.kill();
}
}
// Follows the publish/subscribe pattern
// Subscribe method
on(event, handler, context = handler) {
this.handlers.push({ event, handler: handler.bind(context) });
}
// Publish method
fire(event, args) {
this.handlers.forEach(topic => {
if (topic.event === event) topic.handler(args);
});
}
}

63
src/main/Tray.js Normal file
View file

@ -0,0 +1,63 @@
import { app, Menu, Tray as ElectronTray } from 'electron';
import path from 'path';
import createWindow from './createWindow';
export default class Tray {
window;
updateAttachedWindow;
tray;
constructor(window, updateAttachedWindow) {
this.window = window;
this.updateAttachedWindow = updateAttachedWindow;
}
create() {
let iconPath;
switch (process.platform) {
case 'darwin': {
iconPath = path.join(__static, '/img/tray/mac/trayTemplate.png');
break;
}
case 'win32': {
iconPath = path.join(__static, '/img/tray/windows/tray.ico');
break;
}
default: {
iconPath = path.join(__static, '/img/tray/default/tray.png');
}
}
this.tray = new ElectronTray(iconPath);
this.tray.on('double-click', () => {
if (!this.window || this.window.isDestroyed()) {
this.window = createWindow();
this.updateAttachedWindow(this.window);
} else {
this.window.show();
this.window.focus();
}
});
this.tray.setToolTip('LBRY App');
const template = [
{
label: `Open ${app.getName()}`,
click: () => {
if (!this.window || this.window.isDestroyed()) {
this.window = createWindow();
this.updateAttachedWindow(this.window);
} else {
this.window.show();
this.window.focus();
}
},
},
{ role: 'quit' },
];
const contextMenu = Menu.buildFromTemplate(template);
this.tray.setContextMenu(contextMenu);
}
}

100
src/main/createWindow.js Normal file
View file

@ -0,0 +1,100 @@
import { app, BrowserWindow, dialog } from 'electron';
import setupBarMenu from './menu/setupBarMenu';
import setupContextMenu from './menu/setupContextMenu';
export default deepLinkingURIArg => {
let windowConfiguration = {
backgroundColor: '#155B4A',
minWidth: 800,
minHeight: 600,
autoHideMenuBar: true,
show: false,
};
// Disable renderer process's webSecurity on development to enable CORS.
windowConfiguration =
process.env.NODE_ENV === 'development'
? {
...windowConfiguration,
webPreferences: {
webSecurity: false,
},
}
: windowConfiguration;
const rendererURL =
process.env.NODE_ENV === 'development'
? `http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}`
: `file://${__dirname}/index.html`;
let window = new BrowserWindow(windowConfiguration);
window.maximize();
window.loadURL(rendererURL);
let deepLinkingURI;
// Protocol handler for win32
if (
!deepLinkingURIArg &&
process.platform === 'win32' &&
String(process.argv[1]).startsWith('lbry')
) {
// Keep only command line / deep linked arguments
// Windows normalizes URIs when they're passed in from other apps. On Windows, this tries to
// restore the original URI that was typed.
// - If the URI has no path, Windows adds a trailing slash. LBRY URIs can't have a slash with no
// path, so we just strip it off.
// - In a URI with a claim ID, like lbry://channel#claimid, Windows interprets the hash mark as
// an anchor and converts it to lbry://channel/#claimid. We remove the slash here as well.
deepLinkingURI = process.argv[1].replace(/\/$/, '').replace('/#', '#');
} else {
deepLinkingURI = deepLinkingURIArg;
}
setupBarMenu();
setupContextMenu(window);
window.on('closed', () => {
window = null;
});
window.on('focus', () => {
window.webContents.send('window-is-focused', null);
});
window.on('unresponsive', () => {
dialog.showMessageBox(
window,
{
type: 'warning',
buttons: ['Wait', 'Quit'],
title: 'LBRY Unresponsive',
defaultId: 1,
message: 'LBRY is not responding. Would you like to quit?',
cancelId: 0,
},
buttonIndex => {
if (buttonIndex === 1) app.quit();
}
);
});
window.once('ready-to-show', () => {
window.show();
});
window.webContents.on('did-finish-load', () => {
window.webContents.send('open-uri-requested', deepLinkingURI, true);
window.webContents.session.setUserAgent(`LBRY/${app.getVersion()}`);
if (process.env.NODE_ENV === 'development') {
window.webContents.openDevTools();
}
});
window.webContents.on('crashed', () => {
window = null;
});
return window;
};

View file

@ -1,510 +1,127 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
// Module imports // Module imports
import path from 'path';
import url from 'url';
import Jayson from 'jayson';
import SemVer from 'semver';
import https from 'https';
import keytar from 'keytar-prebuild'; import keytar from 'keytar-prebuild';
import ChildProcess from 'child_process'; import SemVer from 'semver';
import assert from 'assert'; import url from 'url';
import { app, BrowserWindow, globalShortcut, ipcMain, Menu, Tray, dialog } from 'electron'; import https from 'https';
import mainMenu from './menu/mainMenu'; import { shell, app, ipcMain, dialog } from 'electron';
import contextMenu from './menu/contextMenu'; import Daemon from './Daemon';
import Tray from './Tray';
import createWindow from './createWindow';
const localVersion = app.getVersion(); // Keep a global reference, if you don't, they will be closed automatically when the JavaScript
// object is garbage collected.
// Debug configs
const isDevelopment = process.env.NODE_ENV === 'development';
// Misc constants
const latestReleaseAPIURL = 'https://api.github.com/repos/lbryio/lbry-app/releases/latest';
const daemonPath = process.env.LBRY_DAEMON || path.join(__static, 'daemon/lbrynet-daemon');
const rendererURL = isDevelopment
? `http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}`
: `file://${__dirname}/index.html`;
const client = Jayson.client.http({
host: 'localhost',
port: 5279,
path: '/',
timeout: 1000,
});
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let rendererWindow; let rendererWindow;
// Also keep the daemon subprocess alive // eslint-disable-next-line no-unused-vars
let daemonSubprocess; let tray;
let daemon;
// This is set to true right before we try to shut the daemon subprocess -- let isQuitting;
// if it dies when we didn't ask it to shut down, we want to alert the user.
let daemonStopRequested = false;
// When a quit is attempted, we cancel the quit, do some preparations, then const updateRendererWindow = window => {
// this is set to true and app.quit() is called again to quit for real. rendererWindow = window;
let readyToQuit = false; };
// If we receive a URI to open from an external app but there's no window to const installExtensions = async () => {
// sendCredits it to, it's cached in this variable. // eslint-disable-next-line import/no-extraneous-dependencies,global-require
let openURI = null; const installer = require('electron-devtools-installer');
// eslint-disable-next-line import/no-extraneous-dependencies,global-require
const devtronExtension = require('devtron');
const forceDownload = !!process.env.UPGRADE_EXTENSIONS;
const extensions = ['REACT_DEVELOPER_TOOLS', 'REDUX_DEVTOOLS'];
// Set this to true to minimize on clicking close return Promise.all(
// false for normal action extensions.map(
let minimize = true; name => installer.default(installer[name], forceDownload),
devtronExtension.install()
// Keep the tray also, it is getting GC'd if put in createTray() )
let tray = null; ).catch(console.log);
};
function processRequestedURI(URI) {
// Windows normalizes URIs when they're passed in from other apps. On Windows,
// this function tries to restore the original URI that was typed.
// - If the URI has no path, Windows adds a trailing slash. LBRY URIs
// can't have a slash with no path, so we just strip it off.
// - In a URI with a claim ID, like lbry://channel#claimid, Windows
// interprets the hash mark as an anchor and converts it to
// lbry://channel/#claimid. We remove the slash here as well.
// On Linux and Mac, we just return the URI as given.
if (process.platform === 'win32') {
return URI.replace(/\/$/, '').replace('/#', '#');
}
return URI;
}
/*
* Replacement for Electron's shell.openItem. The Electron version doesn't
* reliably work from the main process, and we need to be able to run it
* when no windows are open.
*/
function openItem(fullPath) {
const subprocOptions = {
detached: true,
stdio: 'ignore',
};
let child;
if (process.platform === 'darwin') {
child = ChildProcess.spawn('open', [fullPath], subprocOptions);
} else if (process.platform === 'linux') {
child = ChildProcess.spawn('xdg-open', [fullPath], subprocOptions);
} else if (process.platform === 'win32') {
child = ChildProcess.spawn(fullPath, Object.assign({}, subprocOptions, { shell: true }));
}
// Causes child process reference to be garbage collected, allowing main process to exit
child.unref();
}
/*
* Quits by first killing the daemon, the calling quitting for real.
*/
export function safeQuit() {
minimize = false;
app.quit();
}
function getMenuTemplate() {
function getToggleItem() {
if (rendererWindow.isVisible() && rendererWindow.isFocused()) {
return {
label: 'Hide LBRY App',
click: () => rendererWindow.hide(),
};
}
return {
label: 'Show LBRY App',
click: () => rendererWindow.show(),
};
}
return [
getToggleItem(),
{
label: 'Quit',
click: () => safeQuit(),
},
];
}
// This needs to be done as for linux the context menu doesn't update automatically(docs)
function updateTray() {
const trayContextMenu = Menu.buildFromTemplate(getMenuTemplate());
if (tray) {
tray.setContextMenu(trayContextMenu);
} else {
console.log('How did update tray get called without a tray?');
}
}
function createWindow() {
// Disable renderer process's webSecurity on development to enable CORS.
let windowConfiguration = {
backgroundColor: '#155B4A',
minWidth: 800,
minHeight: 600,
autoHideMenuBar: true,
};
windowConfiguration = isDevelopment
? {
...windowConfiguration,
webPreferences: {
webSecurity: false,
},
}
: windowConfiguration;
let window = new BrowserWindow(windowConfiguration);
window.webContents.session.setUserAgent(`LBRY/${localVersion}`);
window.maximize();
if (isDevelopment) {
window.webContents.openDevTools();
}
window.loadURL(rendererURL);
if (openURI) {
// We stored and received a URI that an external app requested before we had a window object
window.webContents.on('did-finish-load', () => {
window.webContents.send('open-uri-requested', openURI, true);
});
}
window.webContents.on('crashed', () => {
safeQuit();
});
window.removeAllListeners();
window.on('close', event => {
if (minimize) {
event.preventDefault();
window.hide();
}
});
window.on('closed', () => {
window = null;
});
window.on('hide', () => {
// Checks what to show in the tray icon menu
if (minimize) updateTray();
});
window.on('show', () => {
// Checks what to show in the tray icon menu
if (minimize) updateTray();
});
window.on('blur', () => {
// Checks what to show in the tray icon menu
if (minimize) updateTray();
// Unregisters Alt+F4 shortcut
globalShortcut.unregister('Alt+F4');
});
window.on('focus', () => {
// Checks what to show in the tray icon menu
if (minimize) updateTray();
// Registers shortcut for closing(quitting) the app
globalShortcut.register('Alt+F4', () => safeQuit());
window.webContents.send('window-is-focused', null);
});
window.on('unresponsive', () => {
dialog.showMessageBox(
window,
{
type: 'warning',
buttons: ['Wait', 'Quit'],
title: 'LBRY Unresponsive',
defaultId: 1,
message: 'LBRY is not responding. Would you like to quit?',
cancelId: 0,
},
buttonIndex => {
if (buttonIndex === 1) safeQuit();
}
);
});
mainMenu();
return window;
}
function createTray() {
// Minimize to tray logic follows:
// Set the tray icon
let iconPath;
if (process.platform === 'darwin') {
// Using @2x for mac retina screens so the icon isn't blurry
// file name needs to include "Template" at the end for dark menu bar
iconPath = path.join(__static, '/img/fav/macTemplate@2x.png');
} else {
iconPath = path.join(__static, '/img/fav/32x32.png');
}
tray = new Tray(iconPath);
tray.setToolTip('LBRY App');
tray.setTitle('LBRY');
tray.on('double-click', () => {
rendererWindow.show();
});
}
function handleOpenURIRequested(URI) {
if (!rendererWindow) {
// Window not created yet, so store up requested URI for when it is
openURI = processRequestedURI(URI);
} else {
if (rendererWindow.isMinimized()) {
rendererWindow.restore();
} else if (!rendererWindow.isVisible()) {
rendererWindow.show();
}
rendererWindow.focus();
rendererWindow.webContents.send('open-uri-requested', processRequestedURI(URI));
}
}
/*
* Quits without any preparation. When a quit is requested (either through the
* interface or through app.quit()), we abort the quit, try to shut down the daemon,
* and then call this to quit for real.
*/
function quitNow() {
readyToQuit = true;
safeQuit();
}
function handleDaemonSubprocessExited() {
console.log('The daemon has exited.');
daemonSubprocess = null;
if (!daemonStopRequested) {
// We didn't request to stop the daemon, so display a
// warning and schedule a quit.
//
// TODO: maybe it would be better to restart the daemon?
if (rendererWindow) {
console.log('Did not request daemon stop, so quitting in 5 seconds.');
rendererWindow.loadURL(`file://${__static}/warning.html`);
setTimeout(quitNow, 5000);
} else {
console.log('Did not request daemon stop, so quitting.');
quitNow();
}
}
}
function launchDaemon() {
assert(!daemonSubprocess, 'Tried to launch daemon twice');
console.log('Launching daemon:', daemonPath);
daemonSubprocess = ChildProcess.spawn(daemonPath);
// Need to handle the data event instead of attaching to
// process.stdout because the latter doesn't work. I believe on
// windows it buffers stdout and we don't get any meaningful output
daemonSubprocess.stdout.on('data', buf => {
console.log(String(buf).trim());
});
daemonSubprocess.stderr.on('data', buf => {
console.log(String(buf).trim());
});
daemonSubprocess.on('exit', handleDaemonSubprocessExited);
}
const isSecondaryInstance = app.makeSingleInstance(argv => {
if (argv.length >= 2) {
handleOpenURIRequested(argv[1]); // This will handle restoring and focusing the window
} else if (rendererWindow) {
if (rendererWindow.isMinimized()) {
rendererWindow.restore();
} else if (!rendererWindow.isVisible()) {
rendererWindow.show();
}
rendererWindow.focus();
}
});
if (isSecondaryInstance) {
// We're not in the original process, so quit
quitNow();
}
function launchDaemonIfNotRunning() {
// Check if the daemon is already running. If we get
// an error its because its not running
console.log('Checking for lbrynet daemon');
client.request('status', [], err => {
if (err) {
console.log('lbrynet daemon needs to be launched');
launchDaemon();
} else {
console.log('lbrynet daemon is already running');
}
});
}
// Taken from webtorrent-desktop
function checkLinuxTraySupport(cb) {
// Check that we're on Ubuntu (or another debian system) and that we have
// libappindicator1.
ChildProcess.exec('dpkg --get-selections libappindicator1', (err, stdout) => {
if (err) return cb(err);
// Unfortunately there's no cleaner way, as far as I can tell, to check
// whether a debian package is installed:
if (stdout.endsWith('\tinstall\n')) {
return cb(null);
}
return cb(new Error('debian package not installed'));
});
}
// When a quit is attempted, this is called. It attempts to shutdown the daemon,
// then calls quitNow() to quit for real.
function shutdownDaemonAndQuit(evenIfNotStartedByApp = false) {
function doShutdown() {
console.log('Shutting down daemon');
daemonStopRequested = true;
client.request('daemon_stop', [], err => {
if (err) {
console.log(`received error when stopping lbrynet-daemon. Error message: ${err.message}\n`);
console.log('You will need to manually kill the daemon.');
} else {
console.log('Successfully stopped daemon via RPC call.');
quitNow();
}
});
}
if (daemonSubprocess) {
doShutdown();
} else if (!evenIfNotStartedByApp) {
console.log('Not killing lbrynet-daemon because app did not start it');
quitNow();
} else {
doShutdown();
}
// Is it safe to start the installer before the daemon finishes running?
// If not, we should wait until the daemon is closed before we start the install.
}
if (isDevelopment) {
import('devtron')
.then(({ install }) => {
install();
console.log('Added Extension: Devtron');
})
.catch(error => {
console.error(error);
});
import('electron-devtools-installer')
.then(({ default: installExtension, REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS }) => {
app.on('ready', () => {
[REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS].forEach(extension => {
installExtension(extension)
.then(name => console.log(`Added Extension: ${name}`))
.catch(err => console.log('An error occurred: ', err));
});
});
})
.catch(error => {
console.error(error);
});
}
app.setAsDefaultProtocolClient('lbry'); app.setAsDefaultProtocolClient('lbry');
app.setName('LBRY');
app.on('ready', () => { app.on('ready', async () => {
launchDaemonIfNotRunning(); daemon = new Daemon();
if (process.platform === 'linux') { daemon.on('exit', () => {
checkLinuxTraySupport(err => { daemon = null;
if (!err) createTray(); if (!isQuitting) {
else minimize = false; dialog.showErrorBox(
}); 'Daemon has Exited',
} else { 'The daemon may have encountered an unexpected error, or another daemon instance is already running.'
createTray(); );
app.quit();
}
});
daemon.launch();
if (process.env.NODE_ENV === 'development') {
await installExtensions();
} }
rendererWindow = createWindow(); rendererWindow = createWindow();
}); tray = new Tray(rendererWindow, updateRendererWindow);
tray.create();
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('before-quit', event => {
if (!readyToQuit) {
// We need to shutdown the daemon before we're ready to actually quit. This
// event will be triggered re-entrantly once preparation is done.
event.preventDefault();
shutdownDaemonAndQuit();
} else {
console.log('Quitting.');
}
}); });
app.on('activate', () => { app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the // On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open. // dock icon is clicked and there are no other windows open.
if (rendererWindow === null) { if (!rendererWindow) rendererWindow = createWindow();
createWindow();
}
}); });
if (process.platform === 'darwin') { app.on('will-quit', () => {
app.on('open-url', (event, URI) => { isQuitting = true;
handleOpenURIRequested(URI); if (daemon) daemon.quit();
});
// https://electronjs.org/docs/api/app#event-will-finish-launching
app.on('will-finish-launching', () => {
// Protocol handler for macOS
app.on('open-url', (event, URL) => {
event.preventDefault();
if (rendererWindow && !rendererWindow.isDestroyed()) {
rendererWindow.webContents.send('open-uri-requested', URL);
rendererWindow.show();
rendererWindow.focus();
} else {
rendererWindow = createWindow(URL);
}
}); });
} else if (process.argv.length >= 2) { });
handleOpenURIRequested(process.argv[1]);
} app.on('window-all-closed', () => {
// Subscribe to event so the app doesn't quit when closing the window.
});
ipcMain.on('upgrade', (event, installerPath) => { ipcMain.on('upgrade', (event, installerPath) => {
app.on('quit', () => { app.on('quit', () => {
console.log('Launching upgrade installer at', installerPath); console.log('Launching upgrade installer at', installerPath);
// This gets triggered called after *all* other quit-related events, so // This gets triggered called after *all* other quit-related events, so
// we'll only get here if we're fully prepared and quitting for real. // we'll only get here if we're fully prepared and quitting for real.
openItem(installerPath); shell.openItem(installerPath);
}); });
if (rendererWindow) {
rendererWindow.loadURL(`file://${__static}/upgrade.html`);
}
shutdownDaemonAndQuit(true);
// wait for daemon to shut down before upgrading
// what to do if no shutdown in a long time? // what to do if no shutdown in a long time?
console.log('Update downloaded to', installerPath); console.log('Update downloaded to', installerPath);
console.log( console.log(
'The app will close, and you will be prompted to install the latest version of LBRY.' 'The app will close, and you will be prompted to install the latest version of LBRY.'
); );
console.log('After the install is complete, please reopen the app.'); console.log('After the install is complete, please reopen the app.');
app.quit();
}); });
ipcMain.on('version-info-requested', () => { ipcMain.on('version-info-requested', () => {
function formatRc(ver) { function formatRc(ver) {
// Adds dash if needed to make RC suffix semver friendly // Adds dash if needed to make RC suffix SemVer friendly
return ver.replace(/([^-])rc/, '$1-rc'); return ver.replace(/([^-])rc/, '$1-rc');
} }
let result = ''; const localVersion = app.getVersion();
const latestReleaseAPIURL = 'https://api.github.com/repos/lbryio/lbry-app/releases/latest';
const opts = { const opts = {
headers: { headers: {
'User-Agent': `LBRY/${localVersion}`, 'User-Agent': `LBRY/${localVersion}`,
}, },
}; };
let result = '';
const req = https.get(Object.assign(opts, url.parse(latestReleaseAPIURL)), res => { const req = https.get(Object.assign(opts, url.parse(latestReleaseAPIURL)), res => {
res.on('data', data => { res.on('data', data => {
@ -549,8 +166,33 @@ ipcMain.on('set-auth-token', (event, token) => {
}); });
process.on('uncaughtException', error => { process.on('uncaughtException', error => {
console.error(error); dialog.showErrorBox('Error Encountered', `Caught error: ${error}`);
safeQuit(); isQuitting = true;
if (daemon) daemon.quit();
app.exit(1);
}); });
export { contextMenu }; // Force single instance application
const isSecondInstance = app.makeSingleInstance(argv => {
// Protocol handler for win32
// argv: An array of the second instances (command line / deep linked) arguments
let URI;
if (process.platform === 'win32' && String(argv[1]).startsWith('lbry')) {
// Keep only command line / deep linked arguments
URI = argv[1].replace(/\/$/, '').replace('/#', '#');
}
if (rendererWindow && !rendererWindow.isDestroyed()) {
rendererWindow.webContents.send('open-uri-requested', URI);
rendererWindow.show();
rendererWindow.focus();
} else {
rendererWindow = createWindow(URI);
}
});
if (isSecondInstance) {
app.exit();
}

View file

@ -1,20 +0,0 @@
import { Menu } from 'electron';
const contextMenuTemplate = [{ role: 'cut' }, { role: 'copy' }, { role: 'paste' }];
export default (win, posX, posY, showDevItems) => {
const template = contextMenuTemplate.slice();
if (showDevItems) {
template.push({
type: 'separator',
});
template.push({
label: 'Inspect Element',
click() {
win.inspectElement(posX, posY);
},
});
}
Menu.buildFromTemplate(template).popup(win);
};

View file

@ -1,138 +0,0 @@
import { app, Menu, shell } from 'electron';
import { safeQuit } from '../index';
const baseTemplate = [
{
label: 'File',
submenu: [
{
label: 'Quit',
accelerator: 'CommandOrControl+Q',
click: () => safeQuit(),
},
],
},
{
label: 'Edit',
submenu: [
{
role: 'undo',
},
{
role: 'redo',
},
{
type: 'separator',
},
{
role: 'cut',
},
{
role: 'copy',
},
{
role: 'paste',
},
{
role: 'selectall',
},
],
},
{
label: 'View',
submenu: [
{
role: 'reload',
},
{
label: 'Developer',
submenu: [
{
role: 'forcereload',
},
{
role: 'toggledevtools',
},
],
},
{
type: 'separator',
},
{
role: 'togglefullscreen',
},
],
},
{
role: 'help',
submenu: [
{
label: 'Learn More',
click(item, focusedWindow) {
if (focusedWindow) {
focusedWindow.webContents.send('open-menu', '/help');
}
},
},
{
label: 'Frequently Asked Questions',
click() {
shell.openExternal('https://lbry.io/faq');
},
},
{
type: 'separator',
},
{
label: 'Report Issue',
click() {
shell.openExternal('https://lbry.io/faq/contributing#report-a-bug');
},
},
{
type: 'separator',
},
{
label: 'Developer API Guide',
click() {
shell.openExternal('https://lbry.io/quickstart');
},
},
],
},
];
const macOSAppMenuTemplate = {
label: app.getName(),
submenu: [
{
role: 'about',
},
{
type: 'separator',
},
{
role: 'hide',
},
{
role: 'hideothers',
},
{
role: 'unhide',
},
{
type: 'separator',
},
{
role: 'quit',
},
],
};
export default () => {
const template = baseTemplate.slice();
if (process.platform === 'darwin') {
template.unshift(macOSAppMenuTemplate);
}
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
};

View file

@ -0,0 +1,89 @@
import { app, Menu, shell } from 'electron';
export default () => {
const template = [
{
label: 'Edit',
submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
],
},
{
label: 'View',
submenu: [
{ role: 'reload' },
{
label: 'Developer',
submenu: [{ role: 'forcereload' }, { role: 'toggledevtools' }],
},
{ type: 'separator' },
{ role: 'togglefullscreen' },
],
},
{
role: 'window',
submenu: [{ role: 'minimize' }, { role: 'close' }],
},
{
role: 'help',
submenu: [
{
label: 'Learn More',
click: (menuItem, browserWindow) => {
if (browserWindow) {
browserWindow.webContents.send('open-menu', '/help');
} else {
shell.openExternal('https://lbry.io/faq');
}
},
},
{
label: 'Frequently Asked Questions',
click: () => {
shell.openExternal('https://lbry.io/faq');
},
},
{ type: 'separator' },
{
label: 'Report Issue',
click: () => {
shell.openExternal('https://github.com/lbryio/lbry-app/issues/new');
},
},
{ type: 'separator' },
{
label: 'Developer API Guide',
click: () => {
shell.openExternal('https://lbry.io/quickstart');
},
},
],
},
];
const darwinTemplateAddition = {
label: app.getName(),
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'services', submenu: [] },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideothers' },
{ type: 'separator' },
{ role: 'quit' },
],
};
if (process.platform === 'darwin') {
template.unshift(darwinTemplateAddition);
}
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
};

View file

@ -0,0 +1,26 @@
// @flow
import { Menu, BrowserWindow } from 'electron';
export default (rendererWindow: BrowserWindow) => {
rendererWindow.webContents.on('context-menu', (e, params) => {
const { x, y } = params;
const template = [{ role: 'cut' }, { role: 'copy' }, { role: 'paste' }];
const developmentTemplateAddition = [
{ type: 'separator' },
{
label: 'Inspect element',
click: () => {
rendererWindow.inspectElement(x, y);
},
},
];
if (process.env.NODE_ENV === 'development') {
template.push(...developmentTemplateAddition);
}
Menu.buildFromTemplate(template).popup();
});
};

View file

@ -1,3 +1,4 @@
/* eslint-disable react/jsx-filename-extension */
import amplitude from 'amplitude-js'; import amplitude from 'amplitude-js';
import App from 'component/app'; import App from 'component/app';
import SnackBar from 'component/snackBar'; import SnackBar from 'component/snackBar';
@ -5,7 +6,6 @@ import SplashScreen from 'component/splash';
import * as ACTIONS from 'constants/action_types'; import * as ACTIONS from 'constants/action_types';
import { ipcRenderer, remote, shell } from 'electron'; import { ipcRenderer, remote, shell } from 'electron';
import lbry from 'lbry'; import lbry from 'lbry';
/* eslint-disable react/jsx-filename-extension */
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
@ -17,13 +17,6 @@ import 'scss/all.scss';
import store from 'store'; import store from 'store';
import app from './app'; import app from './app';
const { contextMenu } = remote.require('./main.js');
window.addEventListener('contextmenu', event => {
contextMenu(remote.getCurrentWindow(), event.x, event.y, app.env === 'development');
event.preventDefault();
});
ipcRenderer.on('open-uri-requested', (event, uri, newSession) => { ipcRenderer.on('open-uri-requested', (event, uri, newSession) => {
if (uri && uri.startsWith('lbry://')) { if (uri && uri.startsWith('lbry://')) {
if (uri.startsWith('lbry://?verify=')) { if (uri.startsWith('lbry://?verify=')) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -1,7 +1,7 @@
const Path = require('path'); const path = require('path');
const FlowFlowPlugin = require('./flowtype-plugin'); const FlowFlowPlugin = require('./flowtype-plugin');
const ELECTRON_RENDERER_PROCESS_ROOT = Path.resolve(__dirname, 'src/renderer/'); const ELECTRON_RENDERER_PROCESS_ROOT = path.resolve(__dirname, 'src/renderer/');
module.exports = { module.exports = {
// This rule is temporarily necessary until https://github.com/electron-userland/electron-webpack/issues/60 is fixed. // This rule is temporarily necessary until https://github.com/electron-userland/electron-webpack/issues/60 is fixed.