mirror of
https://github.com/finos/SymphonyElectron.git
synced 2025-02-25 18:55:29 -06:00
Merge branch 'master' into electron-17
# Conflicts: # js/main.js # js/windowMgr.js
This commit is contained in:
@@ -16,7 +16,7 @@ let throttleActivity;
|
||||
function activityDetection() {
|
||||
// Get system idle status and idle time from PaulCBetts package
|
||||
if (systemIdleTime.getIdleTime() < maxIdleTime) {
|
||||
return {isUserIdle: false, systemIdleTime: systemIdleTime.getIdleTime()};
|
||||
return { isUserIdle: false, systemIdleTime: systemIdleTime.getIdleTime() };
|
||||
}
|
||||
|
||||
// If idle for more than 4 mins, monitor system idle status every second
|
||||
@@ -65,7 +65,7 @@ function monitorUserActivity() {
|
||||
function sendActivity() {
|
||||
let systemActivity = activityDetection();
|
||||
if (systemActivity && !systemActivity.isUserIdle && systemActivity.systemIdleTime) {
|
||||
send({systemIdleTime: systemActivity.systemIdleTime});
|
||||
send({ systemIdleTime: systemActivity.systemIdleTime });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,11 @@ function send(data) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the activity's window
|
||||
* @param period
|
||||
* @param win
|
||||
*/
|
||||
function setActivityWindow(period, win) {
|
||||
maxIdleTime = period;
|
||||
activityWindow = win;
|
||||
@@ -95,6 +100,5 @@ module.exports = {
|
||||
send: send,
|
||||
setActivityWindow: setActivityWindow,
|
||||
activityDetection: activityDetection,
|
||||
monitorUserActivity: monitorUserActivity, // Exporting this for unit test
|
||||
initiateActivityDetection: initiateActivityDetection
|
||||
};
|
||||
monitorUserActivity: monitorUserActivity, // Exporting this for unit tests
|
||||
};
|
||||
@@ -10,6 +10,10 @@ const maxCount = 1e8;
|
||||
const log = require('./log.js');
|
||||
const logLevels = require('./enums/logLevels.js');
|
||||
|
||||
/**
|
||||
* Shows the badge count
|
||||
* @param count
|
||||
*/
|
||||
function show(count) {
|
||||
if (typeof count !== 'number') {
|
||||
log.send(logLevels.WARN, 'badgeCount: invalid func arg, must be a number: ' + count);
|
||||
@@ -37,6 +41,11 @@ function show(count) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the data url
|
||||
* @param dataUrl
|
||||
* @param count
|
||||
*/
|
||||
function setDataUrl(dataUrl, count) {
|
||||
const mainWindow = windowMgr.getMainWindow();
|
||||
if (mainWindow && dataUrl && count) {
|
||||
@@ -50,4 +59,4 @@ function setDataUrl(dataUrl, count) {
|
||||
module.exports = {
|
||||
show: show,
|
||||
setDataUrl: setDataUrl
|
||||
}
|
||||
};
|
||||
|
||||
209
js/config.js
209
js/config.js
@@ -4,16 +4,25 @@ const electron = require('electron');
|
||||
const app = electron.app;
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const AppDirectory = require('appdirectory');
|
||||
const omit = require('lodash.omit');
|
||||
|
||||
const isDevEnv = require('./utils/misc.js').isDevEnv;
|
||||
const isMac = require('./utils/misc.js').isMac;
|
||||
const getRegistry = require('./utils/getRegistry.js');
|
||||
const log = require('./log.js');
|
||||
const logLevels = require('./enums/logLevels.js');
|
||||
|
||||
const configFileName = 'Symphony.config';
|
||||
const dirs = new AppDirectory('Symphony');
|
||||
|
||||
// cached config when first reading files. initially undefined and will be
|
||||
// updated when read from disk.
|
||||
let userConfig;
|
||||
let globalConfig;
|
||||
|
||||
let ignoreSettings = ['minimizeOnClose', 'launchOnStartup', 'alwaysOnTop', 'url'];
|
||||
|
||||
/**
|
||||
* Tries to read given field from user config file, if field doesn't exist
|
||||
* then tries reading from global config. User config is stord in directory:
|
||||
@@ -29,17 +38,22 @@ let globalConfig;
|
||||
*/
|
||||
function getConfigField(fieldName) {
|
||||
return getUserConfigField(fieldName)
|
||||
.then(function(value) {
|
||||
// got value from user config
|
||||
return value;
|
||||
}, function () {
|
||||
// failed to get value from user config, so try global config
|
||||
return getGlobalConfigField(fieldName);
|
||||
});
|
||||
.then((value) => {
|
||||
// got value from user config
|
||||
return value;
|
||||
}, () => {
|
||||
// failed to get value from user config, so try global config
|
||||
return getGlobalConfigField(fieldName);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a specific user config value for a field
|
||||
* @param fieldName
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function getUserConfigField(fieldName) {
|
||||
return readUserConfig().then(function(config) {
|
||||
return readUserConfig().then((config) => {
|
||||
if (typeof fieldName === 'string' && fieldName in config) {
|
||||
return config[fieldName];
|
||||
}
|
||||
@@ -48,16 +62,25 @@ function getUserConfigField(fieldName) {
|
||||
});
|
||||
}
|
||||
|
||||
function readUserConfig() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
/**
|
||||
* Reads the user config file and returns all the attributes
|
||||
* @param customConfigPath
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function readUserConfig(customConfigPath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (userConfig) {
|
||||
resolve(userConfig);
|
||||
return;
|
||||
}
|
||||
|
||||
let configPath = path.join(app.getPath('userData'), configFileName);
|
||||
let configPath = customConfigPath;
|
||||
|
||||
fs.readFile(configPath, 'utf8', function(err, data) {
|
||||
if (!configPath) {
|
||||
configPath = path.join(app.getPath('userData'), configFileName);
|
||||
}
|
||||
|
||||
fs.readFile(configPath, 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
reject('cannot open user config file: ' + configPath + ', error: ' + err);
|
||||
} else {
|
||||
@@ -74,8 +97,13 @@ function readUserConfig() {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a specific user config value for a field
|
||||
* @param fieldName
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function getGlobalConfigField(fieldName) {
|
||||
return readGlobalConfig().then(function(config) {
|
||||
return readGlobalConfig().then((config) => {
|
||||
if (typeof fieldName === 'string' && fieldName in config) {
|
||||
return config[fieldName];
|
||||
}
|
||||
@@ -93,7 +121,7 @@ function getGlobalConfigField(fieldName) {
|
||||
* installed app). for dev env, the file is read directly from packed asar file.
|
||||
*/
|
||||
function readGlobalConfig() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (globalConfig) {
|
||||
resolve(globalConfig);
|
||||
return;
|
||||
@@ -113,7 +141,7 @@ function readGlobalConfig() {
|
||||
configPath = path.join(execPath, isMac ? '..' : '', globalConfigFileName);
|
||||
}
|
||||
|
||||
fs.readFile(configPath, 'utf8', function(err, data) {
|
||||
fs.readFile(configPath, 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
reject('cannot open global config file: ' + configPath + ', error: ' + err);
|
||||
} else {
|
||||
@@ -124,12 +152,12 @@ function readGlobalConfig() {
|
||||
reject('can not parse config file data: ' + data + ', error: ' + err);
|
||||
}
|
||||
getRegistry('PodUrl')
|
||||
.then(function(url) {
|
||||
globalConfig.url = url;
|
||||
resolve(globalConfig);
|
||||
}).catch(function () {
|
||||
resolve(globalConfig);
|
||||
});
|
||||
.then((url) => {
|
||||
globalConfig.url = url;
|
||||
resolve(globalConfig);
|
||||
}).catch(() => {
|
||||
resolve(globalConfig);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -143,21 +171,27 @@ function readGlobalConfig() {
|
||||
*/
|
||||
function updateConfigField(fieldName, newValue) {
|
||||
return readUserConfig()
|
||||
.then(function(config) {
|
||||
return saveUserConfig(fieldName, newValue, config);
|
||||
},
|
||||
function() {
|
||||
// in case config doesn't exist, can't read or is corrupted.
|
||||
// add configVersion - just in case in future we need to provide
|
||||
// upgrade capabilities.
|
||||
return saveUserConfig(fieldName, newValue, {
|
||||
configVersion: '1.0.0'
|
||||
.then((config) => {
|
||||
return saveUserConfig(fieldName, newValue, config);
|
||||
}, () => {
|
||||
// in case config doesn't exist, can't read or is corrupted.
|
||||
// add configVersion - just in case in future we need to provide
|
||||
// upgrade capabilities.
|
||||
return saveUserConfig(fieldName, newValue, {
|
||||
configVersion: '1.0.0'
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves an updated value to the user config
|
||||
* @param fieldName
|
||||
* @param newValue
|
||||
* @param oldConfig
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function saveUserConfig(fieldName, newValue, oldConfig) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let configPath = path.join(app.getPath('userData'), configFileName);
|
||||
|
||||
if (!oldConfig || !fieldName) {
|
||||
@@ -182,17 +216,124 @@ function saveUserConfig(fieldName, newValue, oldConfig) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the existing user config settings by removing
|
||||
* 'minimizeOnClose', 'launchOnStartup', 'url' and 'alwaysOnTop'
|
||||
* @param {Object} oldUserConfig the old user config object
|
||||
*/
|
||||
function updateUserConfig(oldUserConfig) {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
// create a new object from the old user config
|
||||
// by ommitting the user related settings from
|
||||
// the old user config
|
||||
let newUserConfig = omit(oldUserConfig, ignoreSettings);
|
||||
let newUserConfigString = JSON.stringify(newUserConfig, null, 2);
|
||||
|
||||
// get the user config path
|
||||
let userConfigFile;
|
||||
if (isMac) {
|
||||
userConfigFile = path.join(dirs.userConfig(), configFileName);
|
||||
} else {
|
||||
userConfigFile = path.join(app.getPath('userData'), configFileName);
|
||||
}
|
||||
|
||||
if (!userConfigFile) {
|
||||
return reject('user config file doesn\'t exist');
|
||||
}
|
||||
|
||||
// write the new user config changes to the user config file
|
||||
fs.writeFileSync(userConfigFile, newUserConfigString, 'utf-8');
|
||||
|
||||
return resolve();
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Manipulates user config on windows
|
||||
* @param {String} perUserInstall - Is a flag to determine if we are installing for an individual user
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function updateUserConfigWin(perUserInstall) {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
// we get the user config path using electron
|
||||
const userConfigFile = path.join(app.getPath('userData'), configFileName);
|
||||
|
||||
// if it's not a per user installation or if the
|
||||
// user config file doesn't exist, we simple move on
|
||||
if (!perUserInstall || !fs.existsSync(userConfigFile)) {
|
||||
log.send(logLevels.WARN, 'config: Could not find the user config file!');
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
// In case the file exists, we remove it so that all the
|
||||
// values are fetched from the global config
|
||||
// https://perzoinc.atlassian.net/browse/ELECTRON-126
|
||||
readUserConfig(userConfigFile).then((data) => {
|
||||
resolve(updateUserConfig(data));
|
||||
}).catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Manipulates user config on macOS
|
||||
* @param {String} globalConfigPath - The global config path from installer
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function updateUserConfigMac() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const userConfigFile = path.join(dirs.userConfig(), configFileName);
|
||||
|
||||
// if user config file does't exist, just use the global config settings
|
||||
// i.e. until an user makes changes manually using the menu items
|
||||
if (!fs.existsSync(userConfigFile)) {
|
||||
log.send(logLevels.WARN, 'config: Could not find the user config file!');
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
// In case the file exists, we remove it so that all the
|
||||
// values are fetched from the global config
|
||||
// https://perzoinc.atlassian.net/browse/ELECTRON-126
|
||||
readUserConfig(userConfigFile).then((data) => {
|
||||
resolve(updateUserConfig(data));
|
||||
}).catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the cached config
|
||||
*/
|
||||
function clearCachedConfigs() {
|
||||
userConfig = null;
|
||||
globalConfig = null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getConfigField,
|
||||
updateConfigField,
|
||||
|
||||
configFileName,
|
||||
|
||||
getConfigField,
|
||||
|
||||
updateConfigField,
|
||||
updateUserConfigWin,
|
||||
updateUserConfigMac,
|
||||
|
||||
// items below here are only exported for testing, do NOT use!
|
||||
saveUserConfig,
|
||||
clearCachedConfigs
|
||||
|
||||
};
|
||||
|
||||
@@ -12,23 +12,32 @@
|
||||
// renderer process, this will have to do. See github issue posted here to
|
||||
// electron: https://github.com/electron/electron/issues/9312
|
||||
|
||||
var { ipcRenderer } = require('electron');
|
||||
const { ipcRenderer } = require('electron');
|
||||
|
||||
|
||||
var nextId = 0;
|
||||
var includes = [].includes;
|
||||
let nextId = 0;
|
||||
let includes = [].includes;
|
||||
|
||||
function getNextId() {
|
||||
return ++nextId;
|
||||
}
|
||||
|
||||
// |options.type| can not be empty and has to include 'window' or 'screen'.
|
||||
/**
|
||||
* Checks if the options and their types are valid
|
||||
* @param options |options.type| can not be empty and has to include 'window' or 'screen'.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isValid(options) {
|
||||
return ((options != null ? options.types : undefined) != null) && Array.isArray(options.types);
|
||||
return ((options !== null ? options.types : undefined) !== null) && Array.isArray(options.types);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the sources for capturing screens / windows
|
||||
* @param options
|
||||
* @param callback
|
||||
* @returns {*}
|
||||
*/
|
||||
function getSources(options, callback) {
|
||||
var captureScreen, captureWindow, id;
|
||||
let captureScreen, captureWindow, id;
|
||||
if (!isValid(options)) {
|
||||
return callback(new Error('Invalid options'));
|
||||
}
|
||||
@@ -36,33 +45,34 @@ function getSources(options, callback) {
|
||||
captureScreen = includes.call(options.types, 'screen');
|
||||
|
||||
let updatedOptions = options;
|
||||
if (updatedOptions.thumbnailSize == null) {
|
||||
if (!updatedOptions.thumbnailSize) {
|
||||
updatedOptions.thumbnailSize = {
|
||||
width: 150,
|
||||
height: 150
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
id = getNextId();
|
||||
ipcRenderer.send('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', captureWindow, captureScreen, updatedOptions.thumbnailSize, id);
|
||||
|
||||
return ipcRenderer.once('ELECTRON_RENDERER_DESKTOP_CAPTURER_RESULT_' + id, function (event, sources) {
|
||||
var source;
|
||||
callback(null, (function () {
|
||||
var i, len, results
|
||||
return ipcRenderer.once('ELECTRON_RENDERER_DESKTOP_CAPTURER_RESULT_' + id, function(event, sources) {
|
||||
let source;
|
||||
callback(null, (function() {
|
||||
let i, len, results;
|
||||
results = [];
|
||||
for (i = 0, len = sources.length; i < len; i++) {
|
||||
source = sources[i]
|
||||
source = sources[i];
|
||||
results.push({
|
||||
id: source.id,
|
||||
name: source.name,
|
||||
thumbnail: source.thumbnail
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return results
|
||||
return results;
|
||||
|
||||
}()));
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = getSources;
|
||||
module.exports = getSources;
|
||||
@@ -11,14 +11,14 @@ const logLevels = require('../enums/logLevels.js');
|
||||
* @param {String} url Url that failed
|
||||
* @param {String} errorDesc Description of error
|
||||
* @param {Number} errorCode Error code
|
||||
* @param {callback} retryCallback Callback when user clicks reload
|
||||
* @param {function} retryCallback Callback when user clicks reload
|
||||
*/
|
||||
function showLoadFailure(win, url, errorDesc, errorCode, retryCallback) {
|
||||
let msg;
|
||||
if (url) {
|
||||
msg = 'Error loading URL:\n' + url;
|
||||
} else {
|
||||
msg = 'Error loading window'
|
||||
msg = 'Error loading window';
|
||||
}
|
||||
if (errorDesc) {
|
||||
msg += '\n\n' + errorDesc;
|
||||
@@ -29,7 +29,7 @@ function showLoadFailure(win, url, errorDesc, errorCode, retryCallback) {
|
||||
|
||||
electron.dialog.showMessageBox(win, {
|
||||
type: 'error',
|
||||
buttons: [ 'Reload', 'Ignore' ],
|
||||
buttons: ['Reload', 'Ignore'],
|
||||
defaultId: 0,
|
||||
cancelId: 1,
|
||||
noLink: true,
|
||||
@@ -38,7 +38,7 @@ function showLoadFailure(win, url, errorDesc, errorCode, retryCallback) {
|
||||
}, response);
|
||||
|
||||
log.send(logLevels.WARNING, 'Load failure msg: ' + errorDesc +
|
||||
' errorCode: ' + errorCode + ' for url:' + url);
|
||||
' errorCode: ' + errorCode + ' for url:' + url);
|
||||
|
||||
// async handle of user input
|
||||
function response(buttonId) {
|
||||
@@ -53,11 +53,11 @@ function showLoadFailure(win, url, errorDesc, errorCode, retryCallback) {
|
||||
* Show message indicating network connectivity has been lost.
|
||||
* @param {BrowserWindow} win Window to host dialog
|
||||
* @param {String} url Url that failed
|
||||
* @param {callback} retryCallback Callback when user clicks reload
|
||||
* @param {function} retryCallback Callback when user clicks reload
|
||||
*/
|
||||
function showNetworkConnectivityError(win, url, retryCallback) {
|
||||
var errorDesc = 'Network connectivity has been lost, check your internet connection.';
|
||||
let errorDesc = 'Network connectivity has been lost, check your internet connection.';
|
||||
showLoadFailure(win, url, errorDesc, 0, retryCallback);
|
||||
}
|
||||
|
||||
module.exports = { showLoadFailure, showNetworkConnectivityError };
|
||||
module.exports = { showLoadFailure, showNetworkConnectivityError };
|
||||
@@ -19,12 +19,13 @@ local.ipcRenderer.on('downloadProgress', () => {
|
||||
|
||||
/**
|
||||
* Open file in default app.
|
||||
* @param id
|
||||
*/
|
||||
function openFile(id) {
|
||||
let fileIndex = local.downloadItems.findIndex((item) => {
|
||||
return item._id === id
|
||||
return item._id === id;
|
||||
});
|
||||
if (fileIndex !== -1){
|
||||
if (fileIndex !== -1) {
|
||||
let openResponse = remote.shell.openExternal(`file:///${local.downloadItems[fileIndex].savedPath}`);
|
||||
if (!openResponse) {
|
||||
remote.dialog.showErrorBox("File not found", 'The file you are trying to open cannot be found in the specified path.');
|
||||
@@ -34,10 +35,11 @@ function openFile(id) {
|
||||
|
||||
/**
|
||||
* Show downloaded file in explorer or finder.
|
||||
* @param id
|
||||
*/
|
||||
function showInFinder(id) {
|
||||
let showFileIndex = local.downloadItems.findIndex((item) => {
|
||||
return item._id === id
|
||||
return item._id === id;
|
||||
});
|
||||
if (showFileIndex !== -1) {
|
||||
let showResponse = remote.shell.showItemInFolder(local.downloadItems[showFileIndex].savedPath);
|
||||
@@ -47,6 +49,10 @@ function showInFinder(id) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the document object model
|
||||
* @param arg
|
||||
*/
|
||||
function createDOM(arg) {
|
||||
|
||||
if (arg && arg._id) {
|
||||
@@ -149,6 +155,9 @@ function createDOM(arg) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate the download manager
|
||||
*/
|
||||
function initiate() {
|
||||
let mainFooter = document.getElementById('footer');
|
||||
let mainDownloadDiv = document.getElementById('download-manager-footer');
|
||||
@@ -159,7 +168,7 @@ function initiate() {
|
||||
|
||||
let ulFind = document.getElementById('download-main');
|
||||
|
||||
if (!ulFind){
|
||||
if (!ulFind) {
|
||||
let uList = document.createElement('ul');
|
||||
uList.id = 'download-main';
|
||||
mainDownloadDiv.appendChild(uList);
|
||||
@@ -167,7 +176,7 @@ function initiate() {
|
||||
|
||||
let closeSpanFind = document.getElementById('close-download-bar');
|
||||
|
||||
if (!closeSpanFind){
|
||||
if (!closeSpanFind) {
|
||||
let closeSpan = document.createElement('span');
|
||||
closeSpan.id = 'close-download-bar';
|
||||
closeSpan.classList.add('close-download-bar');
|
||||
@@ -1,7 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
var keyMirror = require('keymirror');
|
||||
let keyMirror = require('keymirror');
|
||||
|
||||
/**
|
||||
* Set of APIs exposed to the remote object
|
||||
* @type {Object}
|
||||
*/
|
||||
const cmds = keyMirror({
|
||||
isOnline: null,
|
||||
registerLogger: null,
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
var keyMirror = require('keymirror');
|
||||
let keyMirror = require('keymirror');
|
||||
|
||||
/**
|
||||
* The different log levels
|
||||
* @type {Object}
|
||||
*/
|
||||
module.exports = keyMirror({
|
||||
ERROR: null,
|
||||
CONFLICT: null,
|
||||
|
||||
59
js/log.js
59
js/log.js
@@ -1,16 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
const getCmdLineArg = require('./utils/getCmdLineArg.js')
|
||||
const getCmdLineArg = require('./utils/getCmdLineArg.js');
|
||||
const { isDevEnv } = require('./utils/misc');
|
||||
const logLevels = require('./enums/logLevels.js');
|
||||
|
||||
const MAX_LOG_QUEUE_LENGTH = 100;
|
||||
|
||||
let electronLog;
|
||||
|
||||
class Logger {
|
||||
|
||||
constructor() {
|
||||
// browser window that has registered a logger
|
||||
this.logWindow = null;
|
||||
|
||||
// holds log messages received before logger has been registered.
|
||||
this.logQueue = [];
|
||||
|
||||
// Initializes the local logger
|
||||
if (isDevEnv) {
|
||||
initializeLocalLogger();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -25,6 +35,10 @@ class Logger {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDevEnv) {
|
||||
logLocally(level, details);
|
||||
}
|
||||
|
||||
let logMsg = {
|
||||
level: level,
|
||||
details: details,
|
||||
@@ -45,18 +59,22 @@ class Logger {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a window instance for the remote object
|
||||
* @param win
|
||||
*/
|
||||
setLogWindow(win) {
|
||||
this.logWindow = win;
|
||||
|
||||
if (this.logWindow) {
|
||||
var logMsg = {};
|
||||
let logMsg = {};
|
||||
|
||||
if (Array.isArray(this.logQueue)) {
|
||||
logMsg.msgs = this.logQueue;
|
||||
}
|
||||
|
||||
// configure desired log level and send pending log msgs
|
||||
let logLevel = getCmdLineArg(process.argv, '--logLevel=');
|
||||
let logLevel = getCmdLineArg(process.argv, '--logLevel=', false);
|
||||
if (logLevel) {
|
||||
let level = logLevel.split('=')[1];
|
||||
if (level) {
|
||||
@@ -64,7 +82,7 @@ class Logger {
|
||||
}
|
||||
}
|
||||
|
||||
if (getCmdLineArg(process.argv, '--enableConsoleLogging')) {
|
||||
if (getCmdLineArg(process.argv, '--enableConsoleLogging', false)) {
|
||||
logMsg.showInConsole = true;
|
||||
}
|
||||
|
||||
@@ -77,11 +95,40 @@ class Logger {
|
||||
}
|
||||
}
|
||||
|
||||
var loggerInstance = new Logger();
|
||||
let loggerInstance = new Logger();
|
||||
|
||||
/**
|
||||
* Initializes the electron logger for local logging
|
||||
*/
|
||||
function initializeLocalLogger() {
|
||||
// eslint-disable-next-line global-require
|
||||
electronLog = require('electron-log');
|
||||
electronLog.transports.file.level = 'debug';
|
||||
electronLog.transports.file.format = '{h}:{i}:{s}:{ms} {text}';
|
||||
electronLog.transports.file.maxSize = 10 * 1024 * 1024;
|
||||
electronLog.transports.file.appName = 'Symphony';
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs locally using the electron-logger
|
||||
* @param level
|
||||
* @param message
|
||||
*/
|
||||
function logLocally(level, message) {
|
||||
switch (level) {
|
||||
case logLevels.ERROR: electronLog.error(message); break;
|
||||
case logLevels.CONFLICT: electronLog.error(message); break;
|
||||
case logLevels.WARN: electronLog.warn(message); break;
|
||||
case logLevels.ACTION: electronLog.warn(message); break;
|
||||
case logLevels.INFO: electronLog.info(message); break;
|
||||
case logLevels.DEBUG: electronLog.debug(message); break;
|
||||
default: electronLog.debug(message);
|
||||
}
|
||||
}
|
||||
|
||||
// Logger class is only exposed for testing purposes.
|
||||
module.exports = {
|
||||
Logger: Logger,
|
||||
send: loggerInstance.send.bind(loggerInstance),
|
||||
setLogWindow: loggerInstance.setLogWindow.bind(loggerInstance)
|
||||
}
|
||||
};
|
||||
|
||||
126
js/main.js
126
js/main.js
@@ -1,12 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
// Third Party Dependencies
|
||||
const electron = require('electron');
|
||||
const app = electron.app;
|
||||
const nodeURL = require('url');
|
||||
const squirrelStartup = require('electron-squirrel-startup');
|
||||
const AutoLaunch = require('auto-launch');
|
||||
const urlParser = require('url');
|
||||
const { getConfigField } = require('./config.js');
|
||||
|
||||
// Local Dependencies
|
||||
const {getConfigField, updateUserConfigWin, updateUserConfigMac} = require('./config.js');
|
||||
const { isMac, isDevEnv } = require('./utils/misc.js');
|
||||
const protocolHandler = require('./protocolHandler');
|
||||
const getCmdLineArg = require('./utils/getCmdLineArg.js');
|
||||
@@ -48,10 +51,22 @@ if (!isDevEnv && shouldQuit) {
|
||||
app.quit();
|
||||
}
|
||||
|
||||
var symphonyAutoLauncher = new AutoLaunch({
|
||||
name: 'Symphony',
|
||||
path: process.execPath,
|
||||
});
|
||||
let symphonyAutoLauncher;
|
||||
|
||||
if (isMac) {
|
||||
symphonyAutoLauncher = new AutoLaunch({
|
||||
name: 'Symphony',
|
||||
mac: {
|
||||
useLaunchAgent: true,
|
||||
},
|
||||
path: process.execPath,
|
||||
});
|
||||
} else {
|
||||
symphonyAutoLauncher = new AutoLaunch({
|
||||
name: 'Symphony',
|
||||
path: process.execPath,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will be called when Electron has finished
|
||||
@@ -60,11 +75,18 @@ var symphonyAutoLauncher = new AutoLaunch({
|
||||
*/
|
||||
app.on('ready', setupThenOpenMainWindow);
|
||||
|
||||
app.on('window-all-closed', function () {
|
||||
/**
|
||||
* Is triggered when all the windows are closed
|
||||
* In which case we quit the app
|
||||
*/
|
||||
app.on('window-all-closed', function() {
|
||||
app.quit();
|
||||
});
|
||||
|
||||
app.on('activate', function () {
|
||||
/**
|
||||
* Is triggered when the app is up & running
|
||||
*/
|
||||
app.on('activate', function() {
|
||||
if (windowMgr.isMainWindow(null)) {
|
||||
setupThenOpenMainWindow();
|
||||
} else {
|
||||
@@ -77,52 +99,88 @@ app.on('activate', function () {
|
||||
// and registry keys in windows
|
||||
app.setAsDefaultProtocolClient('symphony');
|
||||
|
||||
// This event is emitted only on macOS
|
||||
// at this moment, support for windows
|
||||
// is in pipeline (https://github.com/electron/electron/pull/8052)
|
||||
app.on('open-url', function (event, url) {
|
||||
/**
|
||||
* This event is emitted only on macOS
|
||||
* at this moment, support for windows
|
||||
* is in pipeline (https://github.com/electron/electron/pull/8052)
|
||||
*/
|
||||
app.on('open-url', function(event, url) {
|
||||
handleProtocolAction(url);
|
||||
});
|
||||
|
||||
/**
|
||||
* Sets up the app (to handle various things like config changes, protocol handling etc.)
|
||||
* and opens the main window
|
||||
*/
|
||||
function setupThenOpenMainWindow() {
|
||||
|
||||
processProtocolAction(process.argv);
|
||||
|
||||
isAppAlreadyOpen = true;
|
||||
|
||||
// allows installer to launch app and set auto startup mode then
|
||||
// immediately quit.
|
||||
// allows installer to launch app and set appropriate global / user config params.
|
||||
let hasInstallFlag = getCmdLineArg(process.argv, '--install', true);
|
||||
let perUserInstall = getCmdLineArg(process.argv, '--peruser', true);
|
||||
if (!isMac && hasInstallFlag) {
|
||||
getConfigField('launchOnStartup')
|
||||
.then(setStartup)
|
||||
.then(app.quit)
|
||||
.catch(app.quit);
|
||||
.then(setStartup)
|
||||
.then(() => updateUserConfigWin(perUserInstall))
|
||||
.then(app.quit)
|
||||
.catch(app.quit);
|
||||
return;
|
||||
}
|
||||
|
||||
// allows mac installer to overwrite user config
|
||||
if (isMac && hasInstallFlag) {
|
||||
// This value is being sent from post install script
|
||||
// as the app is launched as a root user we don't get
|
||||
// access to the config file
|
||||
let launchOnStartup = process.argv[3];
|
||||
// We wire this in via the post install script
|
||||
// to get the config file path where the app is installed
|
||||
let appGlobalConfigPath = process.argv[2];
|
||||
setStartup(launchOnStartup)
|
||||
.then(() => updateUserConfigMac(appGlobalConfigPath))
|
||||
.then(app.quit)
|
||||
.catch(app.quit);
|
||||
return;
|
||||
}
|
||||
|
||||
getUrlAndCreateMainWindow();
|
||||
|
||||
// Event that fixes the remote desktop issue in Windows
|
||||
// by repositioning the browser window
|
||||
electron.screen.on('display-removed', windowMgr.verifyDisplays);
|
||||
}
|
||||
|
||||
function setStartup(lStartup){
|
||||
/**
|
||||
* Sets Symphony on startup
|
||||
* @param lStartup
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function setStartup(lStartup) {
|
||||
return symphonyAutoLauncher.isEnabled()
|
||||
.then(function(isEnabled){
|
||||
if (!isEnabled && lStartup) {
|
||||
return symphonyAutoLauncher.enable();
|
||||
}
|
||||
.then(function(isEnabled) {
|
||||
if (!isEnabled && lStartup) {
|
||||
return symphonyAutoLauncher.enable();
|
||||
}
|
||||
|
||||
if (isEnabled && !lStartup) {
|
||||
return symphonyAutoLauncher.disable();
|
||||
}
|
||||
if (isEnabled && !lStartup) {
|
||||
return symphonyAutoLauncher.disable();
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for the url argument, processes it
|
||||
* and creates the main window
|
||||
*/
|
||||
function getUrlAndCreateMainWindow() {
|
||||
// for dev env allow passing url argument
|
||||
if (isDevEnv) {
|
||||
let url = getCmdLineArg(process.argv, '--url=')
|
||||
let url = getCmdLineArg(process.argv, '--url=', false);
|
||||
if (url) {
|
||||
windowMgr.createMainWindow(url.substr(6));
|
||||
return;
|
||||
@@ -130,12 +188,16 @@ function getUrlAndCreateMainWindow() {
|
||||
}
|
||||
|
||||
getConfigField('url')
|
||||
.then(createWin).catch(function (err) {
|
||||
.then(createWin).catch(function(err) {
|
||||
let title = 'Error loading configuration';
|
||||
electron.dialog.showErrorBox(title, title + ': ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a window
|
||||
* @param urlFromConfig
|
||||
*/
|
||||
function createWin(urlFromConfig) {
|
||||
let protocol = '';
|
||||
// add https protocol if none found.
|
||||
@@ -143,7 +205,7 @@ function createWin(urlFromConfig) {
|
||||
if (!parsedUrl.protocol) {
|
||||
protocol = 'https';
|
||||
}
|
||||
var url = nodeURL.format({
|
||||
let url = nodeURL.format({
|
||||
protocol: protocol,
|
||||
slahes: true,
|
||||
pathname: parsedUrl.href
|
||||
@@ -179,7 +241,7 @@ function processProtocolAction(argv) {
|
||||
return;
|
||||
}
|
||||
|
||||
let protocolUri = getCmdLineArg(argv, 'symphony://');
|
||||
let protocolUri = getCmdLineArg(argv, 'symphony://', false);
|
||||
|
||||
if (protocolUri) {
|
||||
|
||||
@@ -194,6 +256,10 @@ function processProtocolAction(argv) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a protocol action based on the current state of the app
|
||||
* @param uri
|
||||
*/
|
||||
function handleProtocolAction(uri) {
|
||||
if (!isAppAlreadyOpen) {
|
||||
// app is opened by the protocol url, cache the protocol url to be used later
|
||||
|
||||
@@ -9,7 +9,7 @@ const electron = require('electron');
|
||||
const windowMgr = require('./windowMgr.js');
|
||||
const log = require('./log.js');
|
||||
const logLevels = require('./enums/logLevels');
|
||||
const activityDetection = require('./activityDetection/activityDetection');
|
||||
const activityDetection = require('./activityDetection');
|
||||
const badgeCount = require('./badgeCount.js');
|
||||
const protocolHandler = require('./protocolHandler');
|
||||
const configureNotification = require('./notify/settings/configure-notification-position');
|
||||
@@ -30,7 +30,7 @@ function isValidWindow(event) {
|
||||
if (!checkValidWindow) {
|
||||
return true;
|
||||
}
|
||||
var result = false;
|
||||
let result = false;
|
||||
if (event && event.sender) {
|
||||
// validate that event sender is from window we created
|
||||
const browserWin = electron.BrowserWindow.fromWebContents(event.sender);
|
||||
@@ -107,7 +107,7 @@ electron.ipcMain.on(apiName, (event, arg) => {
|
||||
|
||||
// expose these methods primarily for testing...
|
||||
module.exports = {
|
||||
shouldCheckValidWindow: function (shouldCheck) {
|
||||
shouldCheckValidWindow: function(shouldCheck) {
|
||||
checkValidWindow = shouldCheck;
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -1,14 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
const log = require('./log.js');
|
||||
const logLevels = require('./enums/logLevels.js')
|
||||
const logLevels = require('./enums/logLevels.js');
|
||||
|
||||
// once a minute
|
||||
setInterval(gatherMemory, 1000 * 60);
|
||||
|
||||
/**
|
||||
* Gathers system memory and logs it to the remote system
|
||||
*/
|
||||
function gatherMemory() {
|
||||
var memory = process.getProcessMemoryInfo();
|
||||
var details =
|
||||
let memory = process.getProcessMemoryInfo();
|
||||
let details =
|
||||
'workingSetSize: ' + memory.workingSetSize +
|
||||
' peakWorkingSetSize: ' + memory.peakWorkingSetSize +
|
||||
' privatesBytes: ' + memory.privatesBytes +
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
'use strict';
|
||||
var cm = require('electron-context-menu');
|
||||
|
||||
/**
|
||||
* Creates & applies Right Click Context Menu based on
|
||||
* electron-context-menu library o all windows.
|
||||
* Unless activated on edittable field, Reload option is shown.
|
||||
* Enabled Cut/Copy/Paste/Delete/Select all on text.
|
||||
* Enabled Save Image on images
|
||||
* Enabled Copy Link on href Link
|
||||
* Inspect Element is not enabled.
|
||||
*/
|
||||
function contextMenu(browserWindow){
|
||||
cm({
|
||||
browserWindow,
|
||||
|
||||
prepend: (params) => [
|
||||
{
|
||||
role: 'reload',
|
||||
enabled: params.isEditable === false,
|
||||
visible: params.isEditable === false
|
||||
},
|
||||
{
|
||||
role: 'undo',
|
||||
enabled: params.isEditable && params.editFlags.canUndu,
|
||||
visible: params.isEditable
|
||||
},
|
||||
{
|
||||
role: 'redo',
|
||||
enabled: params.isEditable && params.editFlags.canRedo,
|
||||
visible: params.isEditable
|
||||
}
|
||||
],
|
||||
append: (params) => [
|
||||
{
|
||||
role: 'delete',
|
||||
enabled: params.isEditable && params.editFlags.canDelete,
|
||||
visible: params.isEditable
|
||||
},
|
||||
{
|
||||
role: 'selectall',
|
||||
enabled: params.isEditable && params.editFlags.canSelectAll,
|
||||
visible: params.isEditable
|
||||
}
|
||||
],
|
||||
|
||||
showInspectElement: false
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = contextMenu;
|
||||
@@ -4,27 +4,36 @@ const electron = require('electron');
|
||||
const { getConfigField, updateConfigField } = require('../config.js');
|
||||
const AutoLaunch = require('auto-launch');
|
||||
const isMac = require('../utils/misc.js').isMac;
|
||||
const childProcess = require('child_process');
|
||||
const log = require('../log.js');
|
||||
const logLevels = require('../enums/logLevels.js');
|
||||
const eventEmitter = require('../eventEmitter');
|
||||
|
||||
var minimizeOnClose = false;
|
||||
var launchOnStartup = false;
|
||||
var isAlwaysOnTop = false;
|
||||
let minimizeOnClose = false;
|
||||
let launchOnStartup = false;
|
||||
let isAlwaysOnTop = false;
|
||||
|
||||
setCheckboxValues();
|
||||
|
||||
var symphonyAutoLauncher = new AutoLaunch({
|
||||
name: 'Symphony',
|
||||
path: process.execPath,
|
||||
});
|
||||
let launchAgentPath = '~/Library/LaunchAgents/com.symphony.symphony-desktop.agent.plist';
|
||||
let symphonyAutoLauncher;
|
||||
|
||||
const template = [
|
||||
{
|
||||
label: 'Edit',
|
||||
submenu: [
|
||||
if (isMac) {
|
||||
symphonyAutoLauncher = new AutoLaunch({
|
||||
name: 'Symphony',
|
||||
mac: {
|
||||
useLaunchAgent: true,
|
||||
},
|
||||
path: process.execPath,
|
||||
});
|
||||
} else {
|
||||
symphonyAutoLauncher = new AutoLaunch({
|
||||
name: 'Symphony',
|
||||
path: process.execPath,
|
||||
});
|
||||
}
|
||||
|
||||
const template = [{
|
||||
label: 'Edit',
|
||||
submenu: [
|
||||
{ role: 'undo' },
|
||||
{ role: 'redo' },
|
||||
{ type: 'separator' },
|
||||
@@ -34,252 +43,214 @@ const template = [
|
||||
{ role: 'pasteandmatchstyle' },
|
||||
{ role: 'delete' },
|
||||
{ role: 'selectall' }
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'View',
|
||||
submenu: [{
|
||||
label: 'Reload',
|
||||
accelerator: 'CmdOrCtrl+R',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.reload();
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'View',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Reload',
|
||||
accelerator: 'CmdOrCtrl+R',
|
||||
click (item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.reload();
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Toggle Developer Tools',
|
||||
accelerator: isMac ? 'Alt+Command+I' : 'Ctrl+Shift+I',
|
||||
click (item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.webContents.toggleDevTools();
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
role: 'resetzoom'
|
||||
},
|
||||
{
|
||||
role: 'zoomin'
|
||||
},
|
||||
{
|
||||
role: 'zoomout'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
role: 'togglefullscreen'
|
||||
label: 'Toggle Developer Tools',
|
||||
accelerator: isMac ? 'Alt+Command+I' : 'Ctrl+Shift+I',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.webContents.toggleDevTools();
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
role: 'window',
|
||||
submenu: [
|
||||
{
|
||||
role: 'minimize'
|
||||
},
|
||||
{
|
||||
role: 'close'
|
||||
}
|
||||
]
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
role: 'help',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Learn More',
|
||||
click () { electron.shell.openExternal('https://www.symphony.com') }
|
||||
}
|
||||
]
|
||||
role: 'resetzoom'
|
||||
},
|
||||
{
|
||||
role: 'zoomin'
|
||||
},
|
||||
{
|
||||
role: 'zoomout'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
role: 'togglefullscreen'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
role: 'window',
|
||||
submenu: [{
|
||||
role: 'minimize'
|
||||
},
|
||||
{
|
||||
role: 'close'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
role: 'help',
|
||||
submenu: [{
|
||||
label: 'Learn More',
|
||||
click() { electron.shell.openExternal('https://www.symphony.com'); }
|
||||
}]
|
||||
}
|
||||
];
|
||||
|
||||
function getTemplate(app) {
|
||||
if (isMac && template[0].label !== app.getName()) {
|
||||
template.unshift({
|
||||
label: app.getName(),
|
||||
submenu: [
|
||||
{
|
||||
role: 'about'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
role: 'services',
|
||||
submenu: []
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
role: 'hide'
|
||||
},
|
||||
{
|
||||
role: 'hideothers'
|
||||
},
|
||||
{
|
||||
role: 'unhide'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
role: 'quit'
|
||||
}
|
||||
submenu: [{
|
||||
role: 'about'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
role: 'services',
|
||||
submenu: []
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
role: 'hide'
|
||||
},
|
||||
{
|
||||
role: 'hideothers'
|
||||
},
|
||||
{
|
||||
role: 'unhide'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
role: 'quit'
|
||||
}
|
||||
]
|
||||
});
|
||||
// Edit menu.
|
||||
template[1].submenu.push(
|
||||
{
|
||||
type: 'separator'
|
||||
// Edit menu.
|
||||
template[1].submenu.push({
|
||||
type: 'separator'
|
||||
}, {
|
||||
label: 'Speech',
|
||||
submenu: [{
|
||||
role: 'startspeaking'
|
||||
},
|
||||
{
|
||||
label: 'Speech',
|
||||
submenu: [
|
||||
{
|
||||
role: 'startspeaking'
|
||||
},
|
||||
{
|
||||
role: 'stopspeaking'
|
||||
}
|
||||
]
|
||||
role: 'stopspeaking'
|
||||
}
|
||||
)
|
||||
// Window menu.
|
||||
template[3].submenu = [
|
||||
{
|
||||
label: 'Close',
|
||||
accelerator: 'CmdOrCtrl+W',
|
||||
role: 'close'
|
||||
},
|
||||
{
|
||||
label: 'Minimize',
|
||||
accelerator: 'CmdOrCtrl+M',
|
||||
role: 'minimize'
|
||||
},
|
||||
{
|
||||
label: 'Zoom',
|
||||
role: 'zoom'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Bring All to Front',
|
||||
role: 'front'
|
||||
}
|
||||
]
|
||||
]
|
||||
});
|
||||
// Window menu.
|
||||
template[3].submenu = [{
|
||||
label: 'Close',
|
||||
accelerator: 'CmdOrCtrl+W',
|
||||
role: 'close'
|
||||
},
|
||||
{
|
||||
label: 'Minimize',
|
||||
accelerator: 'CmdOrCtrl+M',
|
||||
role: 'minimize'
|
||||
},
|
||||
{
|
||||
label: 'Zoom',
|
||||
role: 'zoom'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Bring All to Front',
|
||||
role: 'front'
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
var index = 2;
|
||||
if (isMac && template[0].label !== app.getName()){
|
||||
let index = 2;
|
||||
if (isMac && template[0].label !== app.getName()) {
|
||||
index = 3;
|
||||
}
|
||||
|
||||
// Window menu -> launchOnStartup.
|
||||
template[index].submenu.push(
|
||||
{
|
||||
label: 'Auto Launch On Startup',
|
||||
type: 'checkbox',
|
||||
checked: launchOnStartup,
|
||||
click: function (item) {
|
||||
if (item.checked){
|
||||
if (isMac){
|
||||
// TODO: Need to change this implementation to AutoLaunch once they fix this issue ->
|
||||
// https://github.com/Teamwork/node-auto-launch/issues/28
|
||||
childProcess.exec(`launchctl load ${launchAgentPath}`, (err) => {
|
||||
if (err){
|
||||
let title = 'Error setting AutoLaunch configuration';
|
||||
log.send(logLevels.ERROR, 'MenuTemplate: ' + title + ': process error ' + err);
|
||||
electron.dialog.showErrorBox(title, 'Please try reinstalling the application');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
symphonyAutoLauncher.enable()
|
||||
.catch(function (err) {
|
||||
let title = 'Error setting AutoLaunch configuration';
|
||||
log.send(logLevels.ERROR, 'MenuTemplate: ' + title + ': auto launch error ' + err);
|
||||
electron.dialog.showErrorBox(title, title + ': ' + err);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (isMac){
|
||||
// TODO: Need to change this implementation to AutoLaunch once they fix this issue ->
|
||||
// https://github.com/Teamwork/node-auto-launch/issues/28
|
||||
childProcess.exec(`launchctl unload ${launchAgentPath}`, (err) => {
|
||||
if (err){
|
||||
let title = 'Error disabling AutoLaunch configuration';
|
||||
log.send(logLevels.ERROR, 'MenuTemplate: ' + title + ': process error ' + err);
|
||||
electron.dialog.showErrorBox(title, 'Please try reinstalling the application');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
symphonyAutoLauncher.disable()
|
||||
.catch(function (err) {
|
||||
let title = 'Error setting AutoLaunch configuration';
|
||||
log.send(logLevels.ERROR, 'MenuTemplate: ' + title + ': auto launch error ' + err);
|
||||
electron.dialog.showErrorBox(title, title + ': ' + err);
|
||||
});
|
||||
}
|
||||
}
|
||||
launchOnStartup = item.checked;
|
||||
updateConfigField('launchOnStartup', launchOnStartup);
|
||||
template[index].submenu.push({
|
||||
label: 'Auto Launch On Startup',
|
||||
type: 'checkbox',
|
||||
checked: launchOnStartup,
|
||||
click: function(item) {
|
||||
if (item.checked) {
|
||||
symphonyAutoLauncher.enable()
|
||||
.catch(function(err) {
|
||||
let title = 'Error setting AutoLaunch configuration';
|
||||
log.send(logLevels.ERROR, 'MenuTemplate: ' + title + ': auto launch error ' + err);
|
||||
electron.dialog.showErrorBox(title, title + ': ' + err);
|
||||
});
|
||||
} else {
|
||||
symphonyAutoLauncher.disable()
|
||||
.catch(function(err) {
|
||||
let title = 'Error setting AutoLaunch configuration';
|
||||
log.send(logLevels.ERROR, 'MenuTemplate: ' + title + ': auto launch error ' + err);
|
||||
electron.dialog.showErrorBox(title, title + ': ' + err);
|
||||
});
|
||||
}
|
||||
launchOnStartup = item.checked;
|
||||
updateConfigField('launchOnStartup', launchOnStartup);
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
// Window menu -> alwaysOnTop.
|
||||
template[index].submenu.push(
|
||||
{
|
||||
label: 'Always on top',
|
||||
type: 'checkbox',
|
||||
checked: isAlwaysOnTop,
|
||||
click: (item) => {
|
||||
isAlwaysOnTop = item.checked;
|
||||
eventEmitter.emit('isAlwaysOnTop', isAlwaysOnTop);
|
||||
updateConfigField('alwaysOnTop', isAlwaysOnTop);
|
||||
}
|
||||
template[index].submenu.push({
|
||||
label: 'Always on top',
|
||||
type: 'checkbox',
|
||||
checked: isAlwaysOnTop,
|
||||
click: (item) => {
|
||||
isAlwaysOnTop = item.checked;
|
||||
eventEmitter.emit('isAlwaysOnTop', isAlwaysOnTop);
|
||||
updateConfigField('alwaysOnTop', isAlwaysOnTop);
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
// Window menu -> minimizeOnClose.
|
||||
// Window menu -> minimizeOnClose.
|
||||
// ToDo: Add behavior on Close.
|
||||
template[index].submenu.push(
|
||||
{
|
||||
label: 'Minimize on Close',
|
||||
type: 'checkbox',
|
||||
checked: minimizeOnClose,
|
||||
click: function (item) {
|
||||
minimizeOnClose = item.checked;
|
||||
updateConfigField('minimizeOnClose', minimizeOnClose);
|
||||
}
|
||||
template[index].submenu.push({
|
||||
label: 'Minimize on Close',
|
||||
type: 'checkbox',
|
||||
checked: minimizeOnClose,
|
||||
click: function(item) {
|
||||
minimizeOnClose = item.checked;
|
||||
updateConfigField('minimizeOnClose', minimizeOnClose);
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
if (!isMac){
|
||||
template[index].submenu.push(
|
||||
{
|
||||
label: 'Quit Symphony',
|
||||
click: function () {
|
||||
app.quit();
|
||||
}
|
||||
if (!isMac) {
|
||||
template[index].submenu.push({
|
||||
label: 'Quit Symphony',
|
||||
click: function() {
|
||||
app.quit();
|
||||
}
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
function setCheckboxValues(){
|
||||
/**
|
||||
* Sets the checkbox values for different menu items
|
||||
* based on configuration
|
||||
*/
|
||||
function setCheckboxValues() {
|
||||
getConfigField('minimizeOnClose').then(function(mClose) {
|
||||
minimizeOnClose = mClose;
|
||||
}).catch(function (err){
|
||||
}).catch(function(err) {
|
||||
let title = 'Error loading configuration';
|
||||
log.send(logLevels.ERROR, 'MenuTemplate: error getting config field minimizeOnClose, error: ' + err);
|
||||
electron.dialog.showErrorBox(title, title + ': ' + err);
|
||||
@@ -287,7 +258,7 @@ function setCheckboxValues(){
|
||||
|
||||
getConfigField('launchOnStartup').then(function(lStartup) {
|
||||
launchOnStartup = lStartup;
|
||||
}).catch(function (err){
|
||||
}).catch(function(err) {
|
||||
let title = 'Error loading configuration';
|
||||
log.send(logLevels.ERROR, 'MenuTemplate: error getting config field launchOnStartup, error: ' + err);
|
||||
electron.dialog.showErrorBox(title, title + ': ' + err);
|
||||
@@ -296,7 +267,7 @@ function setCheckboxValues(){
|
||||
getConfigField('alwaysOnTop').then(function(mAlwaysOnTop) {
|
||||
isAlwaysOnTop = mAlwaysOnTop;
|
||||
eventEmitter.emit('isAlwaysOnTop', isAlwaysOnTop);
|
||||
}).catch(function (err){
|
||||
}).catch(function(err) {
|
||||
let title = 'Error loading configuration';
|
||||
log.send(logLevels.ERROR, 'MenuTemplate: error getting config field alwaysOnTop, error: ' + err);
|
||||
electron.dialog.showErrorBox(title, title + ': ' + err);
|
||||
@@ -304,7 +275,7 @@ function setCheckboxValues(){
|
||||
|
||||
getConfigField('notificationSettings').then(function(notfObject) {
|
||||
eventEmitter.emit('notificationSettings', notfObject);
|
||||
}).catch(function (err){
|
||||
}).catch(function(err) {
|
||||
let title = 'Error loading configuration';
|
||||
log.send(logLevels.ERROR, 'MenuTemplate: error getting config field notificationSettings, error: ' + err);
|
||||
electron.dialog.showErrorBox(title, title + ': ' + err);
|
||||
@@ -312,11 +283,11 @@ function setCheckboxValues(){
|
||||
|
||||
}
|
||||
|
||||
function getMinimizeOnClose(){
|
||||
function getMinimizeOnClose() {
|
||||
return minimizeOnClose;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getTemplate : getTemplate,
|
||||
getMinimizeOnClose : getMinimizeOnClose
|
||||
getTemplate: getTemplate,
|
||||
getMinimizeOnClose: getMinimizeOnClose
|
||||
};
|
||||
@@ -3,13 +3,21 @@
|
||||
const log = require('../log.js');
|
||||
const logLevels = require('../enums/logLevels.js');
|
||||
|
||||
// One animation at a time
|
||||
/**
|
||||
* Manages one animation at a time
|
||||
* @param options
|
||||
* @constructor
|
||||
*/
|
||||
const AnimationQueue = function(options) {
|
||||
this.options = options;
|
||||
this.queue = [];
|
||||
this.running = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Pushes each animation to a queue
|
||||
* @param object
|
||||
*/
|
||||
AnimationQueue.prototype.push = function(object) {
|
||||
if (this.running) {
|
||||
this.queue.push(object);
|
||||
@@ -17,8 +25,12 @@ AnimationQueue.prototype.push = function(object) {
|
||||
this.running = true;
|
||||
setTimeout(this.animate.bind(this, object), 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Animates an animation that is part of the queue
|
||||
* @param object
|
||||
*/
|
||||
AnimationQueue.prototype.animate = function(object) {
|
||||
object.func.apply(null, object.args)
|
||||
.then(function() {
|
||||
@@ -37,10 +49,13 @@ AnimationQueue.prototype.animate = function(object) {
|
||||
' with stack trace:' + err.stack);
|
||||
/* eslint-enable no-console */
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears the queue
|
||||
*/
|
||||
AnimationQueue.prototype.clear = function() {
|
||||
this.queue = [];
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = AnimationQueue;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
//
|
||||
// BrowserWindow preload script use to create notifications window for
|
||||
@@ -9,6 +9,10 @@
|
||||
const electron = require('electron');
|
||||
const ipc = electron.ipcRenderer;
|
||||
|
||||
/**
|
||||
* Sets style for a notification
|
||||
* @param config
|
||||
*/
|
||||
function setStyle(config) {
|
||||
// Style it
|
||||
let notiDoc = window.document;
|
||||
@@ -20,14 +24,14 @@ function setStyle(config) {
|
||||
let close = notiDoc.getElementById('close');
|
||||
|
||||
// Default style
|
||||
setStyleOnDomElement(config.defaultStyleContainer, container)
|
||||
setStyleOnDomElement(config.defaultStyleContainer, container);
|
||||
|
||||
let style = {
|
||||
height: config.height,
|
||||
width: config.width,
|
||||
borderRadius: config.borderRadius + 'px'
|
||||
}
|
||||
setStyleOnDomElement(style, container)
|
||||
};
|
||||
setStyleOnDomElement(style, container);
|
||||
|
||||
setStyleOnDomElement(config.defaultStyleHeader, header);
|
||||
|
||||
@@ -40,6 +44,11 @@ function setStyle(config) {
|
||||
setStyleOnDomElement(config.defaultStyleClose, close);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets contents for a notification
|
||||
* @param event
|
||||
* @param notificationObj
|
||||
*/
|
||||
function setContents(event, notificationObj) {
|
||||
// sound
|
||||
if (notificationObj.sound) {
|
||||
@@ -49,7 +58,7 @@ function setContents(event, notificationObj) {
|
||||
// Won't check remote files e.g. http://
|
||||
if (notificationObj.sound.match(/^file:/) !== null
|
||||
|| notificationObj.sound.match(/^\//) !== null) {
|
||||
let audio = new window.Audio(notificationObj.sound)
|
||||
let audio = new window.Audio(notificationObj.sound);
|
||||
audio.play()
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -102,15 +111,20 @@ function setContents(event, notificationObj) {
|
||||
// note: use onclick because we only want one handler, for case
|
||||
// when content gets overwritten by notf with same tag
|
||||
closeButton.onclick = function(clickEvent) {
|
||||
clickEvent.stopPropagation()
|
||||
clickEvent.stopPropagation();
|
||||
ipc.send('electron-notify-close', winId, notificationObj)
|
||||
}
|
||||
};
|
||||
|
||||
container.onclick = function() {
|
||||
ipc.send('electron-notify-click', winId, notificationObj);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets style on a notification for a DOM element
|
||||
* @param styleObj
|
||||
* @param domElement
|
||||
*/
|
||||
function setStyleOnDomElement(styleObj, domElement) {
|
||||
try {
|
||||
let styleAttr = Object.keys(styleObj);
|
||||
@@ -124,22 +138,30 @@ function setStyleOnDomElement(styleObj, domElement) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the config
|
||||
* @param event
|
||||
* @param conf
|
||||
*/
|
||||
function loadConfig(event, conf) {
|
||||
setStyle(conf || {})
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the notification window
|
||||
*/
|
||||
function reset() {
|
||||
let notiDoc = window.document
|
||||
let container = notiDoc.getElementById('container')
|
||||
let closeButton = notiDoc.getElementById('close')
|
||||
let notiDoc = window.document;
|
||||
let container = notiDoc.getElementById('container');
|
||||
let closeButton = notiDoc.getElementById('close');
|
||||
|
||||
// Remove event listener
|
||||
let newContainer = container.cloneNode(true)
|
||||
container.parentNode.replaceChild(newContainer, container)
|
||||
let newCloseButton = closeButton.cloneNode(true)
|
||||
let newContainer = container.cloneNode(true);
|
||||
container.parentNode.replaceChild(newContainer, container);
|
||||
let newCloseButton = closeButton.cloneNode(true);
|
||||
closeButton.parentNode.replaceChild(newCloseButton, closeButton)
|
||||
}
|
||||
|
||||
ipc.on('electron-notify-set-contents', setContents)
|
||||
ipc.on('electron-notify-load-config', loadConfig)
|
||||
ipc.on('electron-notify-reset', reset)
|
||||
ipc.on('electron-notify-set-contents', setContents);
|
||||
ipc.on('electron-notify-load-config', loadConfig);
|
||||
ipc.on('electron-notify-reset', reset);
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
<p id="message"></p>
|
||||
<div id="close">
|
||||
<svg fill="#000000" height="16" viewBox="0 0 24 24" width="16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"></path>
|
||||
<path d="M0 0h24v24H0z" fill="none"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
//
|
||||
// code here adapted from https://www.npmjs.com/package/electron-notify
|
||||
// made following changes:
|
||||
@@ -10,12 +10,13 @@
|
||||
//
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const async = require('async');
|
||||
const electron = require('electron');
|
||||
const asyncMap = require('async.map');
|
||||
const asyncMapSeries = require('async.mapseries');
|
||||
const app = electron.app;
|
||||
const BrowserWindow = electron.BrowserWindow;
|
||||
const ipc = electron.ipcMain;
|
||||
const { isMac } = require('../utils/misc');
|
||||
const { isMac, isNodeEnv } = require('../utils/misc');
|
||||
const log = require('../log.js');
|
||||
const logLevels = require('../enums/logLevels.js');
|
||||
|
||||
@@ -48,6 +49,8 @@ let externalDisplay;
|
||||
// user selected display id for notification
|
||||
let displayId;
|
||||
|
||||
let sandboxed = false;
|
||||
|
||||
let config = {
|
||||
// corner to put notifications
|
||||
// upper-right, upper-left, lower-right, lower-left
|
||||
@@ -131,17 +134,11 @@ let config = {
|
||||
acceptFirstMouse: true,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'electron-notify-preload.js'),
|
||||
sandbox: true,
|
||||
nodeIntegration: false
|
||||
sandbox: sandboxed,
|
||||
nodeIntegration: isNodeEnv
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// function setConfig(customConfig) {
|
||||
// Object.assign(customConfig, config);
|
||||
//
|
||||
// calcDimensions();
|
||||
// }
|
||||
};
|
||||
|
||||
if (app.isReady()) {
|
||||
setup();
|
||||
@@ -149,7 +146,10 @@ if (app.isReady()) {
|
||||
app.on('ready', setup);
|
||||
}
|
||||
|
||||
// Method to update notification config
|
||||
/**
|
||||
* Method to update notification config
|
||||
* @param customConfig
|
||||
*/
|
||||
function updateConfig(customConfig) {
|
||||
// Fetching user preferred notification position from config
|
||||
if (customConfig.position) {
|
||||
@@ -164,6 +164,9 @@ function updateConfig(customConfig) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to setup the notification configuration
|
||||
*/
|
||||
function setup() {
|
||||
setupConfig();
|
||||
|
||||
@@ -174,6 +177,10 @@ function setup() {
|
||||
electron.screen.on('display-metrics-changed', setupConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the notification template path
|
||||
* @returns {string|*}
|
||||
*/
|
||||
function getTemplatePath() {
|
||||
let templatePath = path.join(__dirname, 'electron-notify.html');
|
||||
try {
|
||||
@@ -185,12 +192,15 @@ function getTemplatePath() {
|
||||
return config.templatePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the dimensions of the screen
|
||||
*/
|
||||
function calcDimensions() {
|
||||
const vertSpaceBetweenNotf = 8;
|
||||
|
||||
// Calc totalHeight & totalWidth
|
||||
config.totalHeight = config.height + vertSpaceBetweenNotf;
|
||||
config.totalWidth = config.width
|
||||
config.totalWidth = config.width;
|
||||
|
||||
let firstPosX, firstPosY;
|
||||
switch (config.startCorner) {
|
||||
@@ -217,13 +227,16 @@ function calcDimensions() {
|
||||
config.firstPos = {
|
||||
x: firstPosX,
|
||||
y: firstPosY
|
||||
}
|
||||
};
|
||||
|
||||
// Set nextInsertPos
|
||||
nextInsertPos.x = config.firstPos.x
|
||||
nextInsertPos.x = config.firstPos.x;
|
||||
nextInsertPos.y = config.firstPos.y
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the notification config
|
||||
*/
|
||||
function setupConfig() {
|
||||
closeAll();
|
||||
|
||||
@@ -270,6 +283,11 @@ function setupConfig() {
|
||||
config.maxVisibleNotifications = config.maxVisibleNotifications > 5 ? 5 : config.maxVisibleNotifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the user
|
||||
* @param notification
|
||||
* @returns {*}
|
||||
*/
|
||||
function notify(notification) {
|
||||
// Is it an object and only one argument?
|
||||
if (arguments.length === 1 && typeof notification === 'object') {
|
||||
@@ -280,17 +298,25 @@ function notify(notification) {
|
||||
animationQueue.push({
|
||||
func: showNotification,
|
||||
args: [ notf ]
|
||||
})
|
||||
});
|
||||
return notf.id
|
||||
}
|
||||
log.send(logLevels.ERROR, 'electron-notify: ERROR notify() only accepts a single object with notification parameters.');
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the notification
|
||||
*/
|
||||
function incrementId() {
|
||||
latestID++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the notification to the user
|
||||
* @param notificationObj
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function showNotification(notificationObj) {
|
||||
return new Promise(function(resolve) {
|
||||
|
||||
@@ -346,7 +372,7 @@ function showNotification(notificationObj) {
|
||||
});
|
||||
delete notificationWindow.electronNotifyOnCloseFunc;
|
||||
}
|
||||
setNotificationContents(notificationWindow, notificationObj)
|
||||
setNotificationContents(notificationWindow, notificationObj);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
@@ -358,8 +384,8 @@ function showNotification(notificationObj) {
|
||||
// Get inactiveWindow or create new:
|
||||
getWindow().then(function(notificationWindow) {
|
||||
// Move window to position
|
||||
calcInsertPos()
|
||||
setWindowPosition(notificationWindow, nextInsertPos.x, nextInsertPos.y)
|
||||
calcInsertPos();
|
||||
setWindowPosition(notificationWindow, nextInsertPos.x, nextInsertPos.y);
|
||||
|
||||
let updatedNotfWindow = setNotificationContents(notificationWindow, notificationObj);
|
||||
|
||||
@@ -375,6 +401,12 @@ function showNotification(notificationObj) {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the HTML notification contents along with other options
|
||||
* @param notfWindow
|
||||
* @param notfObj
|
||||
* @returns {*}
|
||||
*/
|
||||
function setNotificationContents(notfWindow, notfObj) {
|
||||
|
||||
// Display time per notification basis.
|
||||
@@ -384,7 +416,7 @@ function setNotificationContents(notfWindow, notfObj) {
|
||||
clearTimeout(notfWindow.displayTimer);
|
||||
}
|
||||
|
||||
var updatedNotificationWindow = notfWindow;
|
||||
const updatedNotificationWindow = notfWindow;
|
||||
|
||||
updatedNotificationWindow.notfyObj = notfObj;
|
||||
|
||||
@@ -434,7 +466,13 @@ function setNotificationContents(notfWindow, notfObj) {
|
||||
return updatedNotificationWindow;
|
||||
}
|
||||
|
||||
// Close notification function
|
||||
/**
|
||||
* Closes the notification
|
||||
* @param notificationWindow
|
||||
* @param notificationObj
|
||||
* @param getTimeoutId
|
||||
* @returns {Function}
|
||||
*/
|
||||
function buildCloseNotification(notificationWindow, notificationObj, getTimeoutId) {
|
||||
return function(event) {
|
||||
if (closedNotifications[notificationObj.id]) {
|
||||
@@ -456,7 +494,7 @@ function buildCloseNotification(notificationWindow, notificationObj, getTimeoutI
|
||||
}
|
||||
|
||||
// reset content
|
||||
notificationWindow.webContents.send('electron-notify-reset')
|
||||
notificationWindow.webContents.send('electron-notify-reset');
|
||||
if (getTimeoutId && typeof getTimeoutId === 'function') {
|
||||
let timeoutId = getTimeoutId();
|
||||
clearTimeout(timeoutId);
|
||||
@@ -477,8 +515,13 @@ function buildCloseNotification(notificationWindow, notificationObj, getTimeoutI
|
||||
}
|
||||
}
|
||||
|
||||
// Always add to animationQueue to prevent erros (e.g. notification
|
||||
// got closed while it was moving will produce an error)
|
||||
/**
|
||||
* Adds an active notification the close notification queue
|
||||
* Always add to animationQueue to prevent erros (e.g. notification
|
||||
* got closed while it was moving will produce an error)
|
||||
* @param closeFunc
|
||||
* @returns {Function}
|
||||
*/
|
||||
function buildCloseNotificationSafely(closeFunc) {
|
||||
return function(reason) {
|
||||
animationQueue.push({
|
||||
@@ -506,10 +549,10 @@ ipc.on('electron-notify-click', function (event, winId, notificationObj) {
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Checks for queued notifications and add them
|
||||
* to AnimationQueue if possible
|
||||
*/
|
||||
/**
|
||||
* Checks for queued notifications and add them
|
||||
* to AnimationQueue if possible
|
||||
*/
|
||||
function checkForQueuedNotifications() {
|
||||
if (notificationQueue.length > 0 &&
|
||||
activeNotifications.length < config.maxVisibleNotifications) {
|
||||
@@ -521,27 +564,27 @@ function checkForQueuedNotifications() {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Moves the notifications one position down,
|
||||
* starting with notification at startPos
|
||||
*
|
||||
* @param {int} startPos
|
||||
*/
|
||||
/**
|
||||
* Moves the notifications one position down,
|
||||
* starting with notification at startPos
|
||||
* @param startPos
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function moveOneDown(startPos) {
|
||||
return new Promise(function(resolve) {
|
||||
if (startPos >= activeNotifications || startPos === -1) {
|
||||
resolve()
|
||||
resolve();
|
||||
return
|
||||
}
|
||||
// Build array with index of affected notifications
|
||||
let notificationPosArray = []
|
||||
let notificationPosArray = [];
|
||||
for (let i = startPos; i < activeNotifications.length; i++) {
|
||||
notificationPosArray.push(i)
|
||||
}
|
||||
// Start to animate all notifications at once or in parallel
|
||||
let asyncFunc = async.map // Best performance
|
||||
let asyncFunc = asyncMap; // Best performance
|
||||
if (config.animateInParallel === false) {
|
||||
asyncFunc = async.mapSeries // Sluggish
|
||||
asyncFunc = asyncMapSeries // Sluggish
|
||||
}
|
||||
asyncFunc(notificationPosArray, moveNotificationAnimation, function() {
|
||||
resolve()
|
||||
@@ -549,6 +592,11 @@ function moveOneDown(startPos) {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the notification animation
|
||||
* @param i
|
||||
* @param done
|
||||
*/
|
||||
function moveNotificationAnimation(i, done) {
|
||||
// Get notification to move
|
||||
let notificationWindow = activeNotifications[i];
|
||||
@@ -568,32 +616,38 @@ function moveNotificationAnimation(i, done) {
|
||||
}
|
||||
|
||||
// Get startPos, calc step size and start animationInterval
|
||||
let startY = notificationWindow.getPosition()[1]
|
||||
let step = (newY - startY) / config.animationSteps
|
||||
let curStep = 1
|
||||
let startY = notificationWindow.getPosition()[1];
|
||||
let step = (newY - startY) / config.animationSteps;
|
||||
let curStep = 1;
|
||||
let animationInterval = setInterval(function() {
|
||||
// Abort condition
|
||||
if (curStep === config.animationSteps) {
|
||||
setWindowPosition(notificationWindow, config.firstPos.x, newY);
|
||||
clearInterval(animationInterval)
|
||||
clearInterval(animationInterval);
|
||||
done(null, 'done');
|
||||
return;
|
||||
}
|
||||
// Move one step down
|
||||
setWindowPosition(notificationWindow, config.firstPos.x, startY + curStep * step)
|
||||
setWindowPosition(notificationWindow, config.firstPos.x, startY + curStep * step);
|
||||
curStep++
|
||||
}, config.animationStepMs)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the window's position
|
||||
* @param browserWin
|
||||
* @param posX
|
||||
* @param posY
|
||||
*/
|
||||
function setWindowPosition(browserWin, posX, posY) {
|
||||
if (!browserWin.isDestroyed()) {
|
||||
browserWin.setPosition(parseInt(posX, 10), parseInt(posY, 10))
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Find next possible insert position (on top)
|
||||
*/
|
||||
/**
|
||||
* Find next possible insert position (on top)
|
||||
*/
|
||||
function calcInsertPos() {
|
||||
if (activeNotifications.length < config.maxVisibleNotifications) {
|
||||
switch(config.startCorner) {
|
||||
@@ -611,35 +665,38 @@ function calcInsertPos() {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Get a window to display a notification. Use inactiveWindows or
|
||||
* create a new window
|
||||
* @return {Window}
|
||||
*/
|
||||
/**
|
||||
* Get a window to display a notification. Use inactiveWindows or
|
||||
* create a new window
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function getWindow() {
|
||||
return new Promise(function(resolve) {
|
||||
let notificationWindow
|
||||
let notificationWindow;
|
||||
// Are there still inactiveWindows?
|
||||
if (inactiveWindows.length > 0) {
|
||||
notificationWindow = inactiveWindows.pop()
|
||||
notificationWindow = inactiveWindows.pop();
|
||||
resolve(notificationWindow)
|
||||
} else {
|
||||
// Or create a new window
|
||||
let windowProperties = config.defaultWindow
|
||||
windowProperties.width = config.width
|
||||
windowProperties.height = config.height
|
||||
notificationWindow = new BrowserWindow(windowProperties)
|
||||
notificationWindow.setVisibleOnAllWorkspaces(true)
|
||||
notificationWindow.loadURL(getTemplatePath())
|
||||
let windowProperties = config.defaultWindow;
|
||||
windowProperties.width = config.width;
|
||||
windowProperties.height = config.height;
|
||||
notificationWindow = new BrowserWindow(windowProperties);
|
||||
notificationWindow.setVisibleOnAllWorkspaces(true);
|
||||
notificationWindow.loadURL(getTemplatePath());
|
||||
notificationWindow.webContents.on('did-finish-load', function() {
|
||||
// Done
|
||||
notificationWindow.webContents.send('electron-notify-load-config', config)
|
||||
notificationWindow.webContents.send('electron-notify-load-config', config);
|
||||
resolve(notificationWindow)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes all the notifications and windows
|
||||
*/
|
||||
function closeAll() {
|
||||
// Clear out animation Queue and close windows
|
||||
animationQueue.clear();
|
||||
@@ -665,10 +722,13 @@ function closeAll() {
|
||||
}
|
||||
|
||||
/**
|
||||
/* once a minute, remove inactive windows to free up memory used.
|
||||
* Once a minute, remove inactive windows to free up memory used.
|
||||
*/
|
||||
setInterval(cleanUpInactiveWindow, 60000);
|
||||
|
||||
/**
|
||||
* Cleans up inactive windows
|
||||
*/
|
||||
function cleanUpInactiveWindow() {
|
||||
inactiveWindows.forEach(function(window) {
|
||||
window.close();
|
||||
@@ -676,6 +736,6 @@ function cleanUpInactiveWindow() {
|
||||
inactiveWindows = [];
|
||||
}
|
||||
|
||||
module.exports.notify = notify
|
||||
module.exports.updateConfig = updateConfig
|
||||
module.exports.reset = setupConfig
|
||||
module.exports.notify = notify;
|
||||
module.exports.updateConfig = updateConfig;
|
||||
module.exports.reset = setupConfig;
|
||||
|
||||
@@ -51,6 +51,10 @@ class Notify {
|
||||
|
||||
this._data = options.data || null;
|
||||
|
||||
/**
|
||||
* Handles on show event
|
||||
* @param arg
|
||||
*/
|
||||
function onShow(arg) {
|
||||
if (arg.id === this._id) {
|
||||
log.send(logLevels.INFO, 'showing notification, id=' + this._id);
|
||||
@@ -61,6 +65,10 @@ class Notify {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles on click event
|
||||
* @param arg
|
||||
*/
|
||||
function onClick(arg) {
|
||||
if (arg.id === this._id) {
|
||||
log.send(logLevels.INFO, 'clicking notification, id=' + this._id);
|
||||
@@ -70,6 +78,10 @@ class Notify {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles on close event
|
||||
* @param arg
|
||||
*/
|
||||
function onClose(arg) {
|
||||
if (arg.id === this._id || arg.event === 'close-all') {
|
||||
log.send(logLevels.INFO, 'closing notification, id=' + this._id);
|
||||
@@ -80,6 +92,10 @@ class Notify {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles on error event
|
||||
* @param arg
|
||||
*/
|
||||
function onError(arg) {
|
||||
if (arg.id === this._id) {
|
||||
// don't raise error event if handler doesn't exist, node
|
||||
@@ -95,7 +111,7 @@ class Notify {
|
||||
}
|
||||
|
||||
/**
|
||||
* close notification
|
||||
* Closes notification
|
||||
*/
|
||||
close() {
|
||||
if (typeof this._closeNotification === 'function') {
|
||||
@@ -105,7 +121,7 @@ class Notify {
|
||||
}
|
||||
|
||||
/**
|
||||
* always allow showing notifications.
|
||||
* Always allow showing notifications.
|
||||
* @return {string} 'granted'
|
||||
*/
|
||||
static get permission() {
|
||||
@@ -113,14 +129,14 @@ class Notify {
|
||||
}
|
||||
|
||||
/**
|
||||
* returns data object passed in via constructor options
|
||||
* Returns data object passed in via constructor options
|
||||
*/
|
||||
get data() {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
/**
|
||||
* add event listeners for 'click', 'close', 'show', 'error' events
|
||||
* Adds event listeners for 'click', 'close', 'show', 'error' events
|
||||
*
|
||||
* @param {String} event event to listen for
|
||||
* @param {func} cb callback invoked when event occurs
|
||||
@@ -132,7 +148,7 @@ class Notify {
|
||||
}
|
||||
|
||||
/**
|
||||
* remove event listeners for 'click', 'close', 'show', 'error' events
|
||||
* Removes event listeners for 'click', 'close', 'show', 'error' events
|
||||
*
|
||||
* @param {String} event event to stop listening for.
|
||||
* @param {func} cb callback associated with original addEventListener
|
||||
@@ -144,7 +160,7 @@ class Notify {
|
||||
}
|
||||
|
||||
/**
|
||||
* removes all event listeners
|
||||
* Removes all event listeners
|
||||
*/
|
||||
removeAllEvents() {
|
||||
this.destroy();
|
||||
@@ -168,10 +184,10 @@ class Notify {
|
||||
*/
|
||||
function Queue(emitter) {
|
||||
/**
|
||||
* Cache emitter on.
|
||||
* @api private
|
||||
*/
|
||||
var cache = emitter.on;
|
||||
* Cache emitter on.
|
||||
* @api private
|
||||
*/
|
||||
const cache = emitter.on;
|
||||
let modifiedEmitter = emitter;
|
||||
/**
|
||||
* Emit event and store it if no
|
||||
@@ -180,7 +196,7 @@ function Queue(emitter) {
|
||||
*
|
||||
* .queue('message', 'hi');
|
||||
*
|
||||
* @param {String} event
|
||||
* @param {String} topic
|
||||
*/
|
||||
modifiedEmitter.queue = function(topic) {
|
||||
this._queue = this._queue || {};
|
||||
@@ -191,18 +207,18 @@ function Queue(emitter) {
|
||||
(this._queue[topic] = this._queue[topic] || [])
|
||||
.push([].slice.call(arguments, 1));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Listen on the given `event` with `fn`.
|
||||
*
|
||||
* @param {String} event
|
||||
* @param {Function} fn
|
||||
* @return {Emitter}
|
||||
* @return {Event}
|
||||
*/
|
||||
modifiedEmitter.on = modifiedEmitter.addEventListener = function(topic, fn) {
|
||||
this._queue = this._queue || {};
|
||||
var topics = this._queue[topic];
|
||||
const topics = this._queue[topic];
|
||||
cache.apply(this, arguments);
|
||||
|
||||
if (!this._callbacks) {
|
||||
@@ -211,7 +227,9 @@ function Queue(emitter) {
|
||||
this._callbacks[topic] = true;
|
||||
|
||||
if (topics) {
|
||||
for(var i = 0, l = topics.length; i < l; i++) {
|
||||
let i = 0;
|
||||
const l = topics.length;
|
||||
for(; i < l; i++) {
|
||||
fn.apply(this, topics[i]);
|
||||
}
|
||||
delete this._queue[topic];
|
||||
|
||||
@@ -9,7 +9,9 @@ let selectedDisplay;
|
||||
|
||||
renderSettings();
|
||||
|
||||
// Method that renders the data from user config
|
||||
/**
|
||||
* Method that renders the data from user config
|
||||
*/
|
||||
function renderSettings() {
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
@@ -33,6 +35,9 @@ function renderSettings() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the configuration and closes the alert
|
||||
*/
|
||||
function updateAndClose() {
|
||||
ipc.send('update-config', {position: selectedPosition, display: selectedDisplay});
|
||||
ipc.send('close-alert');
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<label class="label">Monitor</label>
|
||||
<div id="screens" class="main">
|
||||
<label>Notification shown on Monitor: </label>
|
||||
<select class="selector" id="screen-selector">
|
||||
<select class="selector" id="screen-selector" title="position">
|
||||
</select>
|
||||
</div>
|
||||
<label class="label">Position</label>
|
||||
|
||||
@@ -17,6 +17,7 @@ let configurationWindow;
|
||||
let screens;
|
||||
let position;
|
||||
let display;
|
||||
let sandboxed = false;
|
||||
|
||||
let windowConfig = {
|
||||
width: 460,
|
||||
@@ -27,7 +28,7 @@ let windowConfig = {
|
||||
resizable: false,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'configure-notification-position-preload.js'),
|
||||
sandbox: true,
|
||||
sandbox: sandboxed,
|
||||
nodeIntegration: false
|
||||
}
|
||||
};
|
||||
@@ -40,6 +41,9 @@ app.on('ready', () => {
|
||||
electron.screen.on('display-removed', updateScreens);
|
||||
});
|
||||
|
||||
/**
|
||||
* Update all the screens
|
||||
*/
|
||||
function updateScreens() {
|
||||
screens = electron.screen.getAllDisplays();
|
||||
|
||||
@@ -49,6 +53,10 @@ function updateScreens() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the template path
|
||||
* @returns {string}
|
||||
*/
|
||||
function getTemplatePath() {
|
||||
let templatePath = path.join(__dirname, 'configure-notification-position.html');
|
||||
try {
|
||||
@@ -59,6 +67,10 @@ function getTemplatePath() {
|
||||
return 'file://' + templatePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the configuration window for a specific window
|
||||
* @param windowName
|
||||
*/
|
||||
function openConfigurationWindow(windowName) {
|
||||
let allWindows = BrowserWindow.getAllWindows();
|
||||
allWindows = allWindows.find((window) => { return window.winName === windowName });
|
||||
@@ -93,6 +105,9 @@ function openConfigurationWindow(windowName) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys a window
|
||||
*/
|
||||
function destroyWindow() {
|
||||
configurationWindow = null;
|
||||
}
|
||||
|
||||
@@ -20,9 +20,26 @@ const apiName = apiEnums.apiName;
|
||||
const getMediaSources = require('../desktopCapturer/getSources');
|
||||
const crashReporter = require('../crashReporter');
|
||||
|
||||
require('../downloadManager/downloadManager');
|
||||
require('../downloadManager');
|
||||
|
||||
const nodeURL = require('url');
|
||||
// bug in electron preventing us from using spellchecker in pop outs
|
||||
// https://github.com/electron/electron/issues/4025
|
||||
// so loading the spellchecker in try catch so that we don't
|
||||
// block other method from loading
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
try {
|
||||
/* eslint-disable global-require */
|
||||
const SpellCheckerHelper = require('../spellChecker').SpellCheckHelper;
|
||||
/* eslint-enable global-require */
|
||||
// Method to initialize spell checker
|
||||
const spellChecker = new SpellCheckerHelper();
|
||||
spellChecker.initializeSpellChecker();
|
||||
} catch (err) {
|
||||
/* eslint-disable no-console */
|
||||
console.error('unable to load the spell checker module, hence, skipping the spell check feature ' + err);
|
||||
/* eslint-enable no-console */
|
||||
}
|
||||
});
|
||||
|
||||
// hold ref so doesn't get GC'ed
|
||||
const local = {
|
||||
@@ -56,25 +73,6 @@ function createAPI() {
|
||||
return;
|
||||
}
|
||||
|
||||
// bug in electron is preventing using event 'will-navigate' from working
|
||||
// in sandboxed environment. https://github.com/electron/electron/issues/8841
|
||||
// so in the mean time using this code below to block clicking on A tags.
|
||||
// A tags are allowed if they include href='_blank', this cause 'new-window'
|
||||
// event to be received which is handled properly in windowMgr.js
|
||||
window.addEventListener('beforeunload', function(event) {
|
||||
var newUrl = document.activeElement && document.activeElement.href;
|
||||
if (newUrl) {
|
||||
var currHostName = window.location.hostname;
|
||||
var parsedNewUrl = nodeURL.parse(newUrl);
|
||||
var parsedNewUrlHostName = parsedNewUrl && parsedNewUrl.hostname;
|
||||
if (currHostName !== parsedNewUrlHostName) {
|
||||
/* eslint-disable no-param-reassign */
|
||||
event.returnValue = 'false';
|
||||
/* eslint-enable no-param-reassign */
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// note: window.open from main window (if in the same domain) will get
|
||||
// api access. window.open in another domain will be opened in the default
|
||||
// browser (see: handler for event 'new-window' in windowMgr.js)
|
||||
@@ -85,14 +83,14 @@ function createAPI() {
|
||||
window.ssf = {
|
||||
getVersionInfo: function() {
|
||||
return new Promise(function(resolve) {
|
||||
var appName = remote.app.getName();
|
||||
var appVer = remote.app.getVersion();
|
||||
let appName = remote.app.getName();
|
||||
let appVer = remote.app.getVersion();
|
||||
|
||||
const verInfo = {
|
||||
containerIdentifier: appName,
|
||||
containerVer: appVer,
|
||||
apiVer: '1.0.0'
|
||||
}
|
||||
};
|
||||
resolve(verInfo);
|
||||
});
|
||||
},
|
||||
@@ -116,9 +114,9 @@ function createAPI() {
|
||||
|
||||
/**
|
||||
* provides api to allow user to capture portion of screen, see api
|
||||
* details in screenSnipper/ScreenSnippet.js
|
||||
* details in screenSnipper/index.js
|
||||
*/
|
||||
ScreenSnippet: remote.require('./screenSnippet/ScreenSnippet.js').ScreenSnippet,
|
||||
ScreenSnippet: remote.require('./screenSnippet/index.js').ScreenSnippet,
|
||||
|
||||
/**
|
||||
* Provides API to crash the renderer process that calls this function
|
||||
@@ -188,7 +186,7 @@ function createAPI() {
|
||||
* this registration func is invoked then the protocolHandler callback
|
||||
* will be immediately called.
|
||||
*/
|
||||
registerProtocolHandler: function (protocolHandler) {
|
||||
registerProtocolHandler: function(protocolHandler) {
|
||||
if (typeof protocolHandler === 'function') {
|
||||
|
||||
local.processProtocolAction = protocolHandler;
|
||||
@@ -344,4 +342,4 @@ function createAPI() {
|
||||
window.addEventListener('online', updateOnlineStatus, false);
|
||||
|
||||
updateOnlineStatus();
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,10 @@ function setProtocolUrl(uri) {
|
||||
protocolUrl = uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* gets the protocol url set against an instance
|
||||
* @returns {*}
|
||||
*/
|
||||
function getProtocolUrl() {
|
||||
return protocolUrl;
|
||||
}
|
||||
|
||||
@@ -10,10 +10,12 @@ const path = require('path');
|
||||
const { isMac, isDevEnv } = require('../utils/misc.js');
|
||||
const log = require('../log.js');
|
||||
const logLevels = require('../enums/logLevels.js');
|
||||
const eventEmitter = require('.././eventEmitter');
|
||||
|
||||
// static ref to child process, only allow one screen snippet at time, so
|
||||
// hold ref to prev, so can kill before starting next snippet.
|
||||
let child;
|
||||
let isAlwaysOnTop;
|
||||
|
||||
/**
|
||||
* Captures a user selected portion of the monitor and returns jpeg image
|
||||
@@ -47,21 +49,32 @@ class ScreenSnippet {
|
||||
// utilize Mac OSX built-in screencapture tool which has been
|
||||
// available since OSX ver 10.2.
|
||||
captureUtil = '/usr/sbin/screencapture';
|
||||
captureUtilArgs = [ '-i', '-s', '-t', 'jpg', outputFileName ];
|
||||
captureUtilArgs = ['-i', '-s', '-t', 'jpg', outputFileName];
|
||||
} else {
|
||||
// use custom built windows screen capture tool
|
||||
if (isDevEnv) {
|
||||
// for dev env pick up tool from node nodules
|
||||
captureUtil =
|
||||
path.join(__dirname,
|
||||
'../../node_modules/screen-snippet/bin/Release/ScreenSnippet.exe');
|
||||
'../../node_modules/screen-snippet/bin/Release/ScreenSnippet.exe');
|
||||
} else {
|
||||
// for production gets installed next to exec.
|
||||
let execPath = path.dirname(app.getPath('exe'));
|
||||
captureUtil = path.join(execPath, 'ScreenSnippet.exe');
|
||||
}
|
||||
|
||||
captureUtilArgs = [ outputFileName ];
|
||||
// Method to verify and disable always on top property
|
||||
// as an issue with the ScreenSnippet.exe not being on top
|
||||
// of the electron wrapper
|
||||
const windows = electron.BrowserWindow.getAllWindows();
|
||||
if (windows && windows.length > 0) {
|
||||
isAlwaysOnTop = windows[ 0 ].isAlwaysOnTop();
|
||||
if (isAlwaysOnTop) {
|
||||
eventEmitter.emit('isAlwaysOnTop', false);
|
||||
}
|
||||
}
|
||||
|
||||
captureUtilArgs = [outputFileName];
|
||||
}
|
||||
|
||||
log.send(logLevels.INFO, 'ScreenSnippet: starting screen capture util: ' + captureUtil + ' with args=' + captureUtilArgs);
|
||||
@@ -72,6 +85,10 @@ class ScreenSnippet {
|
||||
}
|
||||
|
||||
child = childProcess.execFile(captureUtil, captureUtilArgs, (error) => {
|
||||
// Method to reset always on top feature
|
||||
if (isAlwaysOnTop) {
|
||||
eventEmitter.emit('isAlwaysOnTop', true);
|
||||
}
|
||||
// will be called when child process exits.
|
||||
if (error && error.killed) {
|
||||
// processs was killed, just resolve with no data.
|
||||
@@ -84,9 +101,15 @@ class ScreenSnippet {
|
||||
}
|
||||
}
|
||||
|
||||
// this function was moved outside of class since class is exposed to web
|
||||
// client via preload API, we do NOT want web client to be able to call this
|
||||
// method - then they could read any file on the disk!
|
||||
/**
|
||||
* this function was moved outside of class since class is exposed to web
|
||||
* client via preload API, we do NOT want web client to be able to call this
|
||||
* method - then they could read any file on the disk!
|
||||
* @param outputFileName
|
||||
* @param resolve
|
||||
* @param reject
|
||||
* @param childProcessErr
|
||||
*/
|
||||
function readResult(outputFileName, resolve, reject, childProcessErr) {
|
||||
fs.readFile(outputFileName, (readErr, data) => {
|
||||
if (readErr) {
|
||||
@@ -120,8 +143,7 @@ function readResult(outputFileName, resolve, reject, childProcessErr) {
|
||||
});
|
||||
} catch (error) {
|
||||
reject(createError(error));
|
||||
}
|
||||
finally {
|
||||
} finally {
|
||||
// remove tmp file (async)
|
||||
fs.unlink(outputFileName, function(removeErr) {
|
||||
// note: node complains if calling async
|
||||
@@ -136,14 +158,24 @@ function readResult(outputFileName, resolve, reject, childProcessErr) {
|
||||
}
|
||||
|
||||
/* eslint-disable class-methods-use-this */
|
||||
/**
|
||||
* Create an error object with the ERROR level
|
||||
* @param msg
|
||||
* @returns {Error}
|
||||
*/
|
||||
function createError(msg) {
|
||||
var err = new Error(msg);
|
||||
let err = new Error(msg);
|
||||
err.type = 'ERROR';
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an error object with the WARN level
|
||||
* @param msg
|
||||
* @returns {Error}
|
||||
*/
|
||||
function createWarn(msg) {
|
||||
var err = new Error(msg);
|
||||
let err = new Error(msg);
|
||||
err.type = 'WARN';
|
||||
return err;
|
||||
}
|
||||
@@ -153,4 +185,4 @@ module.exports = {
|
||||
ScreenSnippet: ScreenSnippet,
|
||||
// note: readResult only exposed for testing purposes
|
||||
readResult: readResult
|
||||
}
|
||||
};
|
||||
71
js/spellChecker/index.js
Normal file
71
js/spellChecker/index.js
Normal file
@@ -0,0 +1,71 @@
|
||||
const { remote } = require('electron');
|
||||
const { MenuItem } = remote;
|
||||
const { isMac } = require('./../utils/misc');
|
||||
const { SpellCheckHandler, ContextMenuListener, ContextMenuBuilder } = require('electron-spellchecker');
|
||||
|
||||
class SpellCheckHelper {
|
||||
|
||||
/**
|
||||
* A constructor to create an instance of the spell checker
|
||||
*/
|
||||
constructor() {
|
||||
this.spellCheckHandler = new SpellCheckHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to initialize spell checker
|
||||
*/
|
||||
initializeSpellChecker() {
|
||||
this.spellCheckHandler.attachToInput();
|
||||
|
||||
// This is only for window as in mac the
|
||||
// language is switched w.r.t to the current system language.
|
||||
//
|
||||
// In windows we need to implement RxJS observable
|
||||
// in order to switch language dynamically
|
||||
if (!isMac) {
|
||||
this.spellCheckHandler.switchLanguage('en-US');
|
||||
}
|
||||
|
||||
const contextMenuBuilder = new ContextMenuBuilder(this.spellCheckHandler, null, false, SpellCheckHelper.processMenu);
|
||||
this.contextMenuListener = new ContextMenuListener((info) => {
|
||||
contextMenuBuilder.showPopupMenu(info);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to add default menu items to the
|
||||
* menu that was generated by ContextMenuBuilder
|
||||
*
|
||||
* This method will be invoked by electron-spellchecker
|
||||
* before showing the context menu
|
||||
*
|
||||
* @param menu
|
||||
* @returns menu
|
||||
*/
|
||||
static processMenu(menu) {
|
||||
|
||||
let isLink = false;
|
||||
menu.items.map((item) => {
|
||||
if (item.label === 'Copy Link'){
|
||||
isLink = true;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
if (!isLink){
|
||||
menu.append(new MenuItem({ type: 'separator' }));
|
||||
menu.append(new MenuItem({
|
||||
role: 'reload',
|
||||
accelerator: 'CmdOrCtrl+R',
|
||||
label: 'Reload'
|
||||
}));
|
||||
}
|
||||
return menu;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
SpellCheckHelper: SpellCheckHelper
|
||||
};
|
||||
@@ -7,7 +7,7 @@ const logLevels = require('../enums/logLevels.js');
|
||||
* Search given argv for argName using exact match or starts with.
|
||||
* @param {Array} argv Array of strings
|
||||
* @param {String} argName Arg name to search for.
|
||||
* @param {bool} exactMatch If true then look for exact match otherwise
|
||||
* @param {Boolean} exactMatch If true then look for exact match otherwise
|
||||
* try finding arg that starts with argName.
|
||||
* @return {String} If found, returns the arg, otherwise null.
|
||||
*/
|
||||
@@ -26,4 +26,5 @@ function getCmdLineArg(argv, argName, exactMatch) {
|
||||
|
||||
return null;
|
||||
}
|
||||
module.exports = getCmdLineArg
|
||||
|
||||
module.exports = getCmdLineArg;
|
||||
|
||||
@@ -7,12 +7,11 @@
|
||||
* @return {String} guid value in string
|
||||
*/
|
||||
function getGuid() {
|
||||
const guid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,
|
||||
function(c) {
|
||||
var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,
|
||||
function (c) {
|
||||
let r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
return guid;
|
||||
}
|
||||
|
||||
module.exports = getGuid;
|
||||
|
||||
@@ -5,17 +5,17 @@ const { isMac } = require('./misc.js');
|
||||
const log = require('../log.js');
|
||||
const logLevels = require('../enums/logLevels.js');
|
||||
|
||||
var Registry = require('winreg');
|
||||
var symphonyRegistryHKCU = new Registry({
|
||||
let Registry = require('winreg');
|
||||
let symphonyRegistryHKCU = new Registry({
|
||||
hive: Registry.HKCU,
|
||||
key: symphonyRegistry
|
||||
});
|
||||
|
||||
var symphonyRegistryHKLM = new Registry({
|
||||
let symphonyRegistryHKLM = new Registry({
|
||||
key: symphonyRegistry
|
||||
});
|
||||
|
||||
var symphonyRegistryHKLM6432 = new Registry({
|
||||
let symphonyRegistryHKLM6432 = new Registry({
|
||||
key: symphonyRegistry.replace('\\Software','\\Software\\WOW6432Node')
|
||||
});
|
||||
|
||||
@@ -24,24 +24,24 @@ var symphonyRegistryHKLM6432 = new Registry({
|
||||
* that are intended to be used as global (or default) value for all users
|
||||
* running this app.
|
||||
*/
|
||||
var getRegistry = function (name) {
|
||||
var promise = new Promise(function(resolve, reject) {
|
||||
let getRegistry = function (name) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
if (isMac) {
|
||||
reject('registry is not supported for mac osx.');
|
||||
return;
|
||||
}
|
||||
|
||||
//Try to get registry on HKEY_CURRENT_USER
|
||||
symphonyRegistryHKCU.get( name, function( err1, reg1 ) {
|
||||
if (!err1 && reg1 !==null && reg1.value) {
|
||||
symphonyRegistryHKCU.get(name, function (err1, reg1) {
|
||||
if (!err1 && reg1 !== null && reg1.value) {
|
||||
log.send(logLevels.WARN, 'getRegistry: Cannot find ' + name + ' Registry. Using HKCU');
|
||||
resolve(reg1.value);
|
||||
return;
|
||||
}
|
||||
|
||||
//Try to get registry on HKEY_LOCAL_MACHINE
|
||||
symphonyRegistryHKLM.get( name, function( err2, reg2 ) {
|
||||
if ( !err2 && reg2!==null && reg2.value) {
|
||||
symphonyRegistryHKLM.get(name, function (err2, reg2) {
|
||||
if (!err2 && reg2 !== null && reg2.value) {
|
||||
log.send(logLevels.WARN, 'getRegistry: Cannot find ' + name + ' Registry. Using HKLM');
|
||||
resolve(reg2.value);
|
||||
return;
|
||||
@@ -49,18 +49,16 @@ var getRegistry = function (name) {
|
||||
|
||||
// Try to get registry on HKEY_LOCAL_MACHINE in case 32bit app installed on 64bit system.
|
||||
// winreg does not merge keys as normally windows does.
|
||||
symphonyRegistryHKLM6432.get( name, function( err3, reg3 ) {
|
||||
if ( !err3 && reg3!==null && reg3.value) {
|
||||
symphonyRegistryHKLM6432.get(name, function (err3, reg3) {
|
||||
if (!err3 && reg3 !== null && reg3.value) {
|
||||
resolve(reg3.value);
|
||||
} else{
|
||||
} else {
|
||||
reject('Cannot find PodUrl Registry. Using default url.');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
module.exports = getRegistry
|
||||
module.exports = getRegistry;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const electron = require('electron');
|
||||
|
||||
@@ -6,8 +6,8 @@ const electron = require('electron');
|
||||
/**
|
||||
* Returns true if given rectangle is contained within the workArea of at
|
||||
* least one of the screens.
|
||||
* @param {x: Number, y: Number, width: Number, height: Number} rect
|
||||
* @return {Boolean} true if condition in desc is met.
|
||||
* @param {Object} rect - ex:- {x: Number, y: Number, width: Number, height: Number}
|
||||
* @return {Boolean} true if condition in desc is met.
|
||||
*/
|
||||
function isInDisplayBounds(rect) {
|
||||
if (!rect) {
|
||||
|
||||
@@ -5,7 +5,10 @@ const isDevEnv = process.env.ELECTRON_DEV ?
|
||||
|
||||
const isMac = (process.platform === 'darwin');
|
||||
|
||||
const isNodeEnv = !!process.env.NODE_ENV;
|
||||
|
||||
module.exports = {
|
||||
isDevEnv: isDevEnv,
|
||||
isMac: isMac
|
||||
isMac: isMac,
|
||||
isNodeEnv: isNodeEnv
|
||||
};
|
||||
|
||||
192
js/windowMgr.js
192
js/windowMgr.js
@@ -7,7 +7,6 @@ const path = require('path');
|
||||
const nodeURL = require('url');
|
||||
const querystring = require('querystring');
|
||||
const filesize = require('filesize');
|
||||
const {dialog} = require('electron');
|
||||
|
||||
const { getTemplate, getMinimizeOnClose } = require('./menus/menuTemplate.js');
|
||||
const loadErrors = require('./dialogs/showLoadError.js');
|
||||
@@ -20,6 +19,7 @@ const eventEmitter = require('./eventEmitter');
|
||||
|
||||
const throttle = require('./utils/throttle.js');
|
||||
const { getConfigField, updateConfigField } = require('./config.js');
|
||||
const { isMac, isNodeEnv } = require('./utils/misc');
|
||||
|
||||
const crashReporter = require('./crashReporter');
|
||||
|
||||
@@ -39,6 +39,7 @@ let boundsChangeWindow;
|
||||
let alwaysOnTop = false;
|
||||
let position = 'lower-right';
|
||||
let display;
|
||||
let sandboxed = false;
|
||||
|
||||
// note: this file is built using browserify in prebuild step.
|
||||
const preloadMainScript = path.join(__dirname, 'preload/_preloadMain.js');
|
||||
@@ -46,18 +47,36 @@ const preloadMainScript = path.join(__dirname, 'preload/_preloadMain.js');
|
||||
const MIN_WIDTH = 300;
|
||||
const MIN_HEIGHT = 600;
|
||||
|
||||
/**
|
||||
* Adds a window key
|
||||
* @param key
|
||||
* @param browserWin
|
||||
*/
|
||||
function addWindowKey(key, browserWin) {
|
||||
windows[key] = browserWin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a window key
|
||||
* @param key
|
||||
*/
|
||||
function removeWindowKey(key) {
|
||||
delete windows[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the parsed url
|
||||
* @param url
|
||||
* @returns {Url}
|
||||
*/
|
||||
function getParsedUrl(url) {
|
||||
return nodeURL.parse(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the main window
|
||||
* @param initialUrl
|
||||
*/
|
||||
function createMainWindow(initialUrl) {
|
||||
getConfigField('mainWinPos').then(
|
||||
function (bounds) {
|
||||
@@ -70,6 +89,11 @@ function createMainWindow(initialUrl) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the main window with bounds
|
||||
* @param initialUrl
|
||||
* @param initialBounds
|
||||
*/
|
||||
function doCreateMainWindow(initialUrl, initialBounds) {
|
||||
let url = initialUrl;
|
||||
let key = getGuid();
|
||||
@@ -95,9 +119,10 @@ function doCreateMainWindow(initialUrl, initialBounds) {
|
||||
minHeight: MIN_HEIGHT,
|
||||
alwaysOnTop: false,
|
||||
webPreferences: {
|
||||
sandbox: true,
|
||||
nodeIntegration: false,
|
||||
sandbox: sandboxed,
|
||||
nodeIntegration: isNodeEnv,
|
||||
preload: preloadMainScript,
|
||||
nativeWindowOpen: true
|
||||
}
|
||||
};
|
||||
|
||||
@@ -212,7 +237,7 @@ function doCreateMainWindow(initialUrl, initialBounds) {
|
||||
|
||||
function destroyAllWindows() {
|
||||
let keys = Object.keys(windows);
|
||||
for (var i = 0, len = keys.length; i < len; i++) {
|
||||
for (let i = 0, len = keys.length; i < len; i++) {
|
||||
let winKey = keys[i];
|
||||
removeWindowKey(winKey);
|
||||
}
|
||||
@@ -244,6 +269,9 @@ function doCreateMainWindow(initialUrl, initialBounds) {
|
||||
// bug in electron is preventing this from working in sandboxed evt...
|
||||
// https://github.com/electron/electron/issues/8841
|
||||
mainWindow.webContents.on('will-navigate', function(event, willNavUrl) {
|
||||
if (!sandboxed) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
openUrlInDefaultBrower(willNavUrl);
|
||||
});
|
||||
@@ -277,7 +305,7 @@ function doCreateMainWindow(initialUrl, initialBounds) {
|
||||
let height = newWinOptions.height || MIN_HEIGHT;
|
||||
|
||||
// try getting x and y position from query parameters
|
||||
var query = newWinParsedUrl && querystring.parse(newWinParsedUrl.query);
|
||||
let query = newWinParsedUrl && querystring.parse(newWinParsedUrl.query);
|
||||
if (query && query.x && query.y) {
|
||||
let newX = Number.parseInt(query.x, 10);
|
||||
let newY = Number.parseInt(query.y, 10);
|
||||
@@ -346,13 +374,18 @@ function doCreateMainWindow(initialUrl, initialBounds) {
|
||||
}
|
||||
});
|
||||
|
||||
contextMenu(mainWindow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event before-quit emitted by electron
|
||||
*/
|
||||
app.on('before-quit', function () {
|
||||
willQuitApp = true;
|
||||
});
|
||||
|
||||
/**
|
||||
* Saves the main window bounds
|
||||
*/
|
||||
function saveMainWinBounds() {
|
||||
let newBounds = getWindowSizeAndPosition(mainWindow);
|
||||
|
||||
@@ -361,10 +394,19 @@ function saveMainWinBounds() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the main window
|
||||
* @returns {*}
|
||||
*/
|
||||
function getMainWindow() {
|
||||
return mainWindow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a window's size and position
|
||||
* @param window
|
||||
* @returns {*}
|
||||
*/
|
||||
function getWindowSizeAndPosition(window) {
|
||||
if (window) {
|
||||
let newPos = window.getPosition();
|
||||
@@ -384,14 +426,28 @@ function getWindowSizeAndPosition(window) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the main window
|
||||
*/
|
||||
function showMainWindow() {
|
||||
mainWindow.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if a window is the main window
|
||||
* @param win
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isMainWindow(win) {
|
||||
return mainWindow === win;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the window and a key has a window
|
||||
* @param win
|
||||
* @param winKey
|
||||
* @returns {*}
|
||||
*/
|
||||
function hasWindow(win, winKey) {
|
||||
if (win instanceof BrowserWindow) {
|
||||
let browserWin = windows[winKey];
|
||||
@@ -401,6 +457,10 @@ function hasWindow(win, winKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if a user is online
|
||||
* @param status
|
||||
*/
|
||||
function setIsOnline(status) {
|
||||
isOnline = status;
|
||||
}
|
||||
@@ -448,6 +508,10 @@ function sendChildWinBoundsChange(window) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens an external url in the system's default browser
|
||||
* @param urlToOpen
|
||||
*/
|
||||
function openUrlInDefaultBrower(urlToOpen) {
|
||||
if (urlToOpen) {
|
||||
electron.shell.openExternal(urlToOpen);
|
||||
@@ -479,6 +543,119 @@ eventEmitter.on('notificationSettings', (notificationSettings) => {
|
||||
display = notificationSettings.display;
|
||||
});
|
||||
|
||||
/**
|
||||
* Method that gets invoked when an external display
|
||||
* is removed using electron 'display-removed' event.
|
||||
*/
|
||||
function verifyDisplays() {
|
||||
|
||||
// This is only for Windows, macOS handles this by itself
|
||||
if (!mainWindow || isMac){
|
||||
return;
|
||||
}
|
||||
|
||||
const bounds = mainWindow.getBounds();
|
||||
if (bounds) {
|
||||
let isXAxisValid = true;
|
||||
let isYAxisValid = true;
|
||||
|
||||
// checks to make sure the x,y are valid pairs
|
||||
if ((bounds.x === undefined && (bounds.y || bounds.y === 0))){
|
||||
isXAxisValid = false;
|
||||
}
|
||||
if ((bounds.y === undefined && (bounds.x || bounds.x === 0))){
|
||||
isYAxisValid = false;
|
||||
}
|
||||
|
||||
if (!isXAxisValid && !isYAxisValid){
|
||||
return;
|
||||
}
|
||||
|
||||
let externalDisplay = checkExternalDisplay(bounds);
|
||||
|
||||
// If external window doesn't exists, reposition main window
|
||||
if (!externalDisplay) {
|
||||
repositionMainWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that verifies if wrapper exists in any of the available
|
||||
* external display by comparing the app bounds with the display bounds
|
||||
* if not exists returns false otherwise true
|
||||
* @param appBounds {Electron.Rectangle} - current electron wrapper bounds
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function checkExternalDisplay(appBounds) {
|
||||
const x = appBounds.x;
|
||||
const y = appBounds.y;
|
||||
const width = appBounds.width;
|
||||
const height = appBounds.height;
|
||||
const factor = 0.2;
|
||||
const screen = electron.screen;
|
||||
|
||||
// Loops through all the available displays and
|
||||
// verifies if the wrapper exists within the display bounds
|
||||
// returns false if not exists otherwise true
|
||||
return !!screen.getAllDisplays().find(({bounds}) => {
|
||||
|
||||
const leftMost = x + (width * factor);
|
||||
const topMost = y + (height * factor);
|
||||
const rightMost = x + width - (width * factor);
|
||||
const bottomMost = y + height - (height * factor);
|
||||
|
||||
if (leftMost < bounds.x || topMost < bounds.y) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !(rightMost > bounds.x + bounds.width || bottomMost > bounds.y + bounds.height);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that resets the main window bounds when an external display
|
||||
* was removed and if the wrapper was contained within that bounds
|
||||
*/
|
||||
function repositionMainWindow() {
|
||||
const screen = electron.screen;
|
||||
|
||||
const {workArea} = screen.getPrimaryDisplay();
|
||||
const bounds = workArea;
|
||||
|
||||
if (!bounds) {
|
||||
return;
|
||||
}
|
||||
|
||||
const windowWidth = Math.round(bounds.width * 0.6);
|
||||
const windowHeight = Math.round(bounds.height * 0.8);
|
||||
|
||||
// Calculating the center of the primary display
|
||||
// to place the wrapper
|
||||
const centerX = bounds.x + bounds.width / 2.0;
|
||||
const centerY = bounds.y + bounds.height / 2.0;
|
||||
const x = Math.round(centerX - (windowWidth / 2.0));
|
||||
const y = Math.round(centerY - (windowHeight / 2.0));
|
||||
|
||||
let rectangle = {x, y, width: windowWidth, height: windowHeight};
|
||||
|
||||
// resetting the main window bounds
|
||||
if (mainWindow){
|
||||
if (!mainWindow.isVisible()) {
|
||||
mainWindow.show();
|
||||
}
|
||||
|
||||
if (mainWindow.isMinimized()) {
|
||||
mainWindow.restore();
|
||||
}
|
||||
|
||||
mainWindow.focus();
|
||||
mainWindow.flashFrame(false);
|
||||
mainWindow.setBounds(rectangle, true);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createMainWindow: createMainWindow,
|
||||
getMainWindow: getMainWindow,
|
||||
@@ -487,5 +664,6 @@ module.exports = {
|
||||
hasWindow: hasWindow,
|
||||
setIsOnline: setIsOnline,
|
||||
activate: activate,
|
||||
setBoundsChangeWindow: setBoundsChangeWindow
|
||||
setBoundsChangeWindow: setBoundsChangeWindow,
|
||||
verifyDisplays: verifyDisplays
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user