Laverna/laverna

View on GitHub
electron.js

Summary

Maintainability
A
1 hr
Test Coverage
'use strict';

const electron        = require('electron'),
    windowStateKeeper = require('electron-window-state'),
    path              = require('path'),
    openUrl           = require('open');

const {app, BrowserWindow, Menu, Tray, protocol} = electron;

let argv        = require('minimist')(process.argv.slice(1)),
    contextMenu = require('electron-context-menu')({}),
    win         = null,
    appHelper;

// Show command line help
if (argv.help || argv.h) {
    console.log('Usage: laverna [options]\r\n');
    console.log('--help', 'Show this help');
    console.log('--dev ', 'Show developer tools automatically on start');
    console.log('--tray', 'Hide to tray on start');
    console.log(
        '--data-dir',
        'Directory where data is stored.',
        'Example: laverna --data-dir=../data'
    );

    return app.quit();
}

// Allow to change the directory where the data is stored
if (argv['data-dir'] && argv['data-dir'].trim()) {
    let dataDir = argv['data-dir'].trim();

    // The same or parent directory
    if (dataDir.search(/^\./) > - 1) {

        // Always store data in a separate directory
        dataDir += (dataDir.search(/^\.{1,2}$/) > -1 ? '/laverna-data' : '');

        dataDir = path.join(__dirname, dataDir);
    }

    app.setPath('userData', path.normalize(dataDir));
}

appHelper = {

    // The page which will be loaded in the window
    page: 'http://localhost:9000/',

    // Icon of the app
    icon: path.join(__dirname, '/dist/images/icon/',
      process.platform === 'darwin' ? 'IconMenubarTemplate.png' : 'icon-120x120.png'
    ),

    // The main menu
    menu: [
        {
            label   : '&File',
            submenu : [
                {
                    label       : 'Quit',
                    accelerator : 'CmdOrCtrl+Q',
                    click       : function() {
                        app.quit();
                    }
                }
            ]
        },
        {
            label: '&Edit',
            submenu: [
                {
                    label       : 'Undo',
                    accelerator : 'CmdOrCtrl+Z',
                    role        : 'undo'
                },
                {
                    label       : 'Redo',
                    accelerator : 'Shift+CmdOrCtrl+Z',
                    role        : 'redo'
                },
                {type: 'separator'},
                {
                    label       : 'Cut',
                    accelerator : 'CmdOrCtrl+X',
                    role        : 'cut'
                },
                {
                    label       : 'Copy',
                    accelerator : 'CmdOrCtrl+C',
                    role        : 'copy'
                },
                {
                    label       : 'Paste',
                    accelerator : 'CmdOrCtrl+V',
                    role        : 'paste'
                },
                {type: 'separator'},
                {
                    label       : 'Select All',
                    accelerator : 'CmdOrCtrl+A',
                    role        : 'selectall'
                },
            ]
        },
        {
            label: '&View',
            submenu: [
                {
                    label       : 'Reload',
                    accelerator : 'CmdOrCtrl+R',
                    click       : function(item, focusedWindow) {
                        if (focusedWindow) { focusedWindow.reload(); }
                    }
                },
                {
                    label       : 'Toggle Developer Tools',
                    accelerator : process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
                    click       : function(item, focusedWindow) {
                        if (focusedWindow) {
                            focusedWindow.webContents.toggleDevTools();
                        }
                    }
                }
            ]
        },
    ],

    // Tray menu (shown on right click)
    menuTray: [
        {
            label: 'Quit',
            click: function() {
                app.quit();
            }
        }
    ],

    /**
     * Electron is ready.
     */
    onReady: function() {
        this
        .startServer()
        // .interceptProtocol()
        .createWindow()
        .createMenu()
        .createTray()
        .registerEvents();
    },

    startServer() {
        require('./server')(9000);
        return this;
    },

    /**
     * Override HTTP protocol. The reason:
     * In order to make oAuth authentifications to Dropbox/RemoteStorage,
     * we need to serve the app from https? protocol and have relative paths.
     */
    interceptProtocol: function() {
        protocol.interceptHttpProtocol('http', function(req, callback) {

            if (req.url.search(appHelper.page) > - 1) {

                // Remove the domain, path, and hash location
                req.url = req.url.replace(appHelper.page, '');
                req.url = req.url.split('#')[0] || 'index.html';

                // Serve the resource from the file system
                req.url = 'file:///' + path.normalize(__dirname + '/dist/' + req.url);
            }

            callback(req);

        });

        return this;
    },

    /**
     * Create the main window.
     */
    createWindow: function() {

        // Recover window state (width, height, and x&y position)
        this.state = windowStateKeeper('main', {
            width  : 1000,
            height : 600
        });

        // Create new browser window
        win = new BrowserWindow({
            width  : this.state.width,
            height : this.state.height,
            x      : this.state.x,
            y      : this.state.y,

            title           : 'Laverna',
            icon            : this.icon,
            autoHideMenuBar : true,
            backgroundColor : '#00a693',

            webPreferences      : {
                nodeIntegration : true,
                preload         : path.resolve(path.join(__dirname, 'preload.js')),
            },
        });

        if (this.state.isMaximized) {
            win.maximize();
        }

        // Load the app
        win.loadURL(this.page);

        // Show development tools
        if (process.env.NODE_ENV === 'dev' || argv.dev) {
            win.webContents.openDevTools();
        }

        return this;
    },

    /**
     * Create the main menu.
     */
    createMenu: function() {

        // Slightly different menu on OS X
        if (process.platform === 'darwin') {
            this.menu[0] = {
                label   : 'Laverna',
                submenu : this.menu[0].submenu
            };
        }

        let menu = Menu.buildFromTemplate(this.menu);
        Menu.setApplicationMenu(menu);

        return this;
    },

    /**
     * Create tray icon.
     */
    createTray: function() {
        let icon = new Tray(this.icon),
            menu = Menu.buildFromTemplate(this.menuTray);

        icon.setToolTip('Laverna');
        icon.setContextMenu(menu);

        // Auto hide to tray on start
        if (argv.tray) {
            win.hide();
        }

        // Hide the window into tray on click
        icon.on('click', function() {
            if (win.isVisible()) {
                return win.hide();
            }
            win.show();
        });

        return this;
    },

    /**
     * Listen to window events.
     */
    registerEvents: function() {

        // Save window state (width, height, and x&y position)
        win.on('close', function() {
            this.state.saveState(win);
        }.bind(appHelper));

        win.on('closed', function() {
            win = null;
        });

        win.webContents.on('will-navigate', appHelper.onNavigate.bind(appHelper));
        win.webContents.on('new-window', appHelper.onNavigate.bind(appHelper));

        // Disable nodeIntegration
        // win.webContents.on('new-window', appHelper.onNewWindow.bind(appHelper));
    },

    /**
     * Open URLs in an external browser.
     */
    onNavigate: function(e, url) {
        if (url.search(this.page) === -1 &&
            url.search(/(oauth|sign_in)/) === -1 &&
            url.search(/^blob:/) === -1) {

            e.preventDefault();
            return openUrl(url);
        }
    },

    /**
     * Disable nodeIntegration on all pages except for app's.
     */
    onNewWindow: function(e, url) {
        if (url.search(this.page) === -1 &&
            url.search(/^blob:/) === -1) {

            e.preventDefault();

            let extWin = new BrowserWindow({
                width  : 600,
                height : 600,
                autoHideMenuBar     : true,
                webPreferences      : {
                    nodeIntegration : false,
                },
            });

            // Load the app
            extWin.loadURL(url);
            extWin.on('closed', function() { extWin = null; });
        }
    },

};

// Create browser window when Electron is ready
app.on('ready', appHelper.onReady.bind(appHelper));

// Quit when all windows are closed.
app.on('window-all-closed', function() {
    /*
     * On OS X 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('activate', function() {
    /*
     * On OS X it's common to re-create a window in the app when the
     * dock icon is clicked and there are no other windows open.
     */
    if (win === null) {
        appHelper.onReady();
    }
});