mirror of
https://github.com/finos/SymphonyElectron.git
synced 2024-11-23 01:16:24 -06:00
857 lines
26 KiB
JavaScript
857 lines
26 KiB
JavaScript
'use strict';
|
|
//
|
|
// code here adapted from https://www.npmjs.com/package/electron-notify
|
|
// made following changes:
|
|
// - place notification in corner of screen
|
|
// - notification color
|
|
// - notification flash/blink
|
|
// - custom design for symphony notification style
|
|
// - if screen added/removed or size change then close all notifications
|
|
//
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
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, isNodeEnv } = require('../utils/misc');
|
|
const log = require('../log.js');
|
|
const logLevels = require('../enums/logLevels.js');
|
|
const i18n = require('../translation/i18n');
|
|
// maximum number of notifications that can be queued, after limit is
|
|
// reached then error func callback will be invoked.
|
|
const MAX_QUEUE_SIZE = 30;
|
|
|
|
let AnimationQueue = require('./AnimationQueue.js');
|
|
|
|
// Array of windows with currently showing notifications
|
|
let activeNotifications = [];
|
|
|
|
// Recycle windows
|
|
let inactiveWindows = [];
|
|
|
|
// If we cannot show all notifications, queue them
|
|
let notificationQueue = [];
|
|
|
|
// To prevent executing mutliple animations at once
|
|
let animationQueue = new AnimationQueue();
|
|
|
|
// To prevent double-close notification window
|
|
let closedNotifications = {};
|
|
|
|
// Give each notification a unique id
|
|
let latestID = 0;
|
|
|
|
let nextInsertPos = {};
|
|
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
|
|
startCorner: 'upper-right',
|
|
width: 380,
|
|
height: 100,
|
|
borderRadius: 5,
|
|
displayTime: 5000,
|
|
animationSteps: 5,
|
|
animationStepMs: 5,
|
|
animateInParallel: true,
|
|
pathToModule: '',
|
|
logging: true,
|
|
defaultStyleContainer: {
|
|
display: 'flex',
|
|
justifyContent: 'center',
|
|
backgroundColor: '#f0f0f0',
|
|
overflow: 'hidden',
|
|
position: 'relative',
|
|
lineHeight: '15px',
|
|
boxSizing: 'border-box'
|
|
},
|
|
defaultStyleHeader: {
|
|
width: 245,
|
|
minWidth: 230,
|
|
margin: 'auto',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
alignItems: 'flex-start',
|
|
},
|
|
defaultStyleImageContainer: {
|
|
alignItems: 'center',
|
|
display: 'flex',
|
|
},
|
|
defaultStyleImage: {
|
|
height: 43,
|
|
borderRadius: 4,
|
|
width: 43
|
|
},
|
|
defaultStyleClose: {
|
|
width: 16,
|
|
height: 80,
|
|
display: 'flex',
|
|
margin: 'auto',
|
|
opacity: 0.54,
|
|
fontSize: 12,
|
|
color: '#CCC'
|
|
},
|
|
defaultStyleTitle: {
|
|
fontFamily: 'sans-serif',
|
|
fontSize: 14,
|
|
fontWeight: 700,
|
|
color: '#4a4a4a',
|
|
overflow: 'hidden',
|
|
display: '-webkit-box',
|
|
webkitLineClamp: 1,
|
|
webkitBoxOrient: 'vertical',
|
|
},
|
|
defaultStyleCompany: {
|
|
fontFamily: 'sans-serif',
|
|
fontSize: 11,
|
|
color: '#adadad',
|
|
overflow: 'hidden',
|
|
filter: 'brightness(70%)',
|
|
display: '-webkit-box',
|
|
webkitLineClamp: 1,
|
|
webkitBoxOrient: 'vertical',
|
|
},
|
|
defaultStyleText: {
|
|
fontFamily: 'sans-serif',
|
|
fontSize: 12,
|
|
color: '#4a4a4a',
|
|
marginTop: 5,
|
|
overflow: 'hidden',
|
|
display: '-webkit-box',
|
|
webkitLineClamp: 3,
|
|
webkitBoxOrient: 'vertical',
|
|
cursor: 'default',
|
|
textOverflow: 'ellipsis',
|
|
width: '100%',
|
|
overflowWrap: 'break-word',
|
|
},
|
|
defaultStyleLogoContainer: {
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
},
|
|
defaultStyleLogo: {
|
|
marginLeft: '5px',
|
|
opacity: 0.6,
|
|
width: '43px',
|
|
},
|
|
defaultWindow: {
|
|
alwaysOnTop: true,
|
|
skipTaskbar: true,
|
|
resizable: false,
|
|
show: false,
|
|
frame: false,
|
|
transparent: true,
|
|
acceptFirstMouse: true,
|
|
webPreferences: {
|
|
preload: path.join(__dirname, 'electron-notify-preload.js'),
|
|
sandbox: sandboxed,
|
|
nodeIntegration: isNodeEnv,
|
|
devTools: false
|
|
}
|
|
}
|
|
};
|
|
|
|
if (app.isReady()) {
|
|
setup();
|
|
} else {
|
|
app.on('ready', setup);
|
|
}
|
|
|
|
/**
|
|
* Method to update notification config
|
|
* @param customConfig
|
|
*/
|
|
function updateConfig(customConfig) {
|
|
// Fetching user preferred notification position from config
|
|
if (customConfig.position) {
|
|
config = Object.assign(config, {startCorner: customConfig.position});
|
|
|
|
calcDimensions();
|
|
}
|
|
|
|
// Fetching user preferred notification screen from config
|
|
if (customConfig.display) {
|
|
displayId = customConfig.display;
|
|
}
|
|
// Reposition active notification on config changes
|
|
setupConfig();
|
|
moveOneDown(0)
|
|
.then(() => {
|
|
log.send(logLevels.INFO, 'updateConfig: repositioned '+ activeNotifications.length +' active notification');
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Method to setup the notification configuration
|
|
*/
|
|
function setup() {
|
|
setupConfig();
|
|
|
|
// if display added/removed/changed then re-run setup and remove all existing
|
|
// notifications.
|
|
electron.screen.on('display-added', setupConfig);
|
|
electron.screen.on('display-removed', setupConfig);
|
|
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 {
|
|
fs.statSync(templatePath).isFile();
|
|
} catch (err) {
|
|
log.send(logLevels.ERROR, 'electron-notify: Could not find template ("' + templatePath + '").');
|
|
}
|
|
config.templatePath = 'file://' + templatePath;
|
|
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;
|
|
|
|
let firstPosX, firstPosY;
|
|
switch (config.startCorner) {
|
|
case 'upper-right':
|
|
firstPosX = config.corner.x - config.totalWidth;
|
|
firstPosY = config.corner.y;
|
|
break;
|
|
case 'lower-right':
|
|
firstPosX = config.corner.x - config.totalWidth;
|
|
firstPosY = config.corner.y - config.totalHeight;
|
|
break;
|
|
case 'lower-left':
|
|
firstPosX = config.corner.x;
|
|
firstPosY = config.corner.y - config.totalHeight;
|
|
break;
|
|
case 'upper-left':
|
|
default:
|
|
firstPosX = config.corner.x;
|
|
firstPosY = config.corner.y;
|
|
break;
|
|
}
|
|
|
|
// Calc pos of first notification:
|
|
config.firstPos = {
|
|
x: firstPosX,
|
|
y: firstPosY
|
|
};
|
|
|
|
// Set nextInsertPos
|
|
nextInsertPos.x = config.firstPos.x;
|
|
nextInsertPos.y = config.firstPos.y
|
|
}
|
|
|
|
/**
|
|
* Setup the notification config
|
|
*/
|
|
function setupConfig() {
|
|
|
|
log.send(logLevels.INFO, `Either a display was added / removed / the metrics were changed`);
|
|
|
|
// This feature only applies to windows
|
|
if (!isMac) {
|
|
let screens = electron.screen.getAllDisplays();
|
|
if (screens && screens.length >= 0) {
|
|
externalDisplay = screens.find((screen) => {
|
|
let screenId = screen.id.toString();
|
|
return screenId === displayId;
|
|
});
|
|
}
|
|
}
|
|
|
|
let display = externalDisplay ? externalDisplay : electron.screen.getPrimaryDisplay();
|
|
config.corner = {};
|
|
config.corner.x = display.workArea.x;
|
|
config.corner.y = display.workArea.y;
|
|
|
|
// update corner x/y based on corner of screen where notf should appear
|
|
const workAreaWidth = display.workAreaSize.width;
|
|
const workAreaHeight = display.workAreaSize.height;
|
|
switch (config.startCorner) {
|
|
case 'upper-right':
|
|
config.corner.x += workAreaWidth;
|
|
break;
|
|
case 'lower-right':
|
|
config.corner.x += workAreaWidth;
|
|
config.corner.y += workAreaHeight;
|
|
break;
|
|
case 'lower-left':
|
|
config.corner.y += workAreaHeight;
|
|
break;
|
|
case 'upper-left':
|
|
default:
|
|
// no change needed
|
|
break;
|
|
}
|
|
|
|
calcDimensions();
|
|
|
|
// Maximum amount of Notifications we can show:
|
|
config.maxVisibleNotifications = Math.floor(display.workAreaSize.height / config.totalHeight);
|
|
}
|
|
|
|
/**
|
|
* Notifies the user
|
|
* @param notification
|
|
* @returns {*}
|
|
*/
|
|
function notify(notification) {
|
|
// Is it an object and only one argument?
|
|
if (arguments.length === 1 && typeof notification === 'object') {
|
|
let notf = Object.assign({}, notification);
|
|
// Use object instead of supplied parameters
|
|
notf.id = latestID;
|
|
incrementId();
|
|
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) {
|
|
|
|
if (notificationQueue.length >= MAX_QUEUE_SIZE) {
|
|
if (typeof notificationObj.onErrorFunc === 'function') {
|
|
setTimeout(function() {
|
|
notificationObj.onErrorFunc({
|
|
id: notificationObj.id,
|
|
error: 'max notification queue size reached: ' + MAX_QUEUE_SIZE
|
|
});
|
|
log.send(logLevels.INFO, 'showNotification: max notification queue size reached: ' + MAX_QUEUE_SIZE);
|
|
}, 0);
|
|
}
|
|
resolve();
|
|
return;
|
|
}
|
|
|
|
// check if tag id provided. should replace existing notification
|
|
// if has same grouping id.
|
|
let tag = notificationObj.tag;
|
|
if (tag) {
|
|
// first check queued notifications
|
|
for(let i = 0; i < notificationQueue.length; i++) {
|
|
if (tag === notificationQueue[ i ].tag) {
|
|
let existingNotfObj = notificationQueue[ i ];
|
|
// be sure to call close event for existing, so it gets
|
|
// cleaned up.
|
|
if (typeof existingNotfObj.onCloseFunc === 'function') {
|
|
existingNotfObj.onCloseFunc({
|
|
event: 'close',
|
|
id: notificationObj.id
|
|
});
|
|
}
|
|
// update with new notf
|
|
notificationQueue[ i ] = notificationObj;
|
|
resolve();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// next check notfs being shown
|
|
for(let i = 0; i < activeNotifications.length; i++) {
|
|
if (activeNotifications[ i ] && windowExists(activeNotifications[ i ])) {
|
|
let existingNotfyObj = activeNotifications[ i ].notfyObj;
|
|
if (existingNotfyObj && tag === existingNotfyObj.tag) {
|
|
let notificationWindow = activeNotifications[ i ];
|
|
|
|
// be sure to call close event for existing, so it gets
|
|
// cleaned up.
|
|
if (notificationWindow.electronNotifyOnCloseFunc) {
|
|
notificationWindow.electronNotifyOnCloseFunc({
|
|
event: 'close',
|
|
id: existingNotfyObj.id
|
|
});
|
|
delete notificationWindow.electronNotifyOnCloseFunc;
|
|
}
|
|
setNotificationContents(notificationWindow, notificationObj);
|
|
resolve();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Can we show it?
|
|
if (activeNotifications.length < config.maxVisibleNotifications) {
|
|
// Get inactiveWindow or create new:
|
|
getWindow().then(function(notificationWindow) {
|
|
// Move window to position
|
|
calcInsertPos();
|
|
setWindowPosition(notificationWindow, nextInsertPos.x, nextInsertPos.y);
|
|
|
|
let updatedNotfWindow = setNotificationContents(notificationWindow, notificationObj);
|
|
|
|
activeNotifications.push(updatedNotfWindow);
|
|
|
|
resolve(updatedNotfWindow);
|
|
})
|
|
} else {
|
|
// Add to notificationQueue
|
|
notificationQueue.push(notificationObj);
|
|
resolve();
|
|
}
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Sets the HTML notification contents along with other options
|
|
* @param notfWindow
|
|
* @param notfObj
|
|
* @returns {*}
|
|
*/
|
|
function setNotificationContents(notfWindow, notfObj) {
|
|
|
|
// Display time per notification basis.
|
|
let displayTime = notfObj.displayTime ? notfObj.displayTime : config.displayTime;
|
|
|
|
let browserWindows = BrowserWindow.getAllWindows();
|
|
const mainWindow = browserWindows.find((window) => { return window.winName === 'main' });
|
|
if (mainWindow && windowExists(mainWindow)) {
|
|
if (mainWindow.isAlwaysOnTop()) {
|
|
notfWindow.setAlwaysOnTop(true);
|
|
}
|
|
}
|
|
|
|
if (notfWindow.displayTimer) {
|
|
clearTimeout(notfWindow.displayTimer);
|
|
}
|
|
|
|
const updatedNotificationWindow = notfWindow;
|
|
|
|
updatedNotificationWindow.notfyObj = notfObj;
|
|
|
|
let timeoutId;
|
|
let closeFunc = buildCloseNotification(notfWindow, notfObj, function() {
|
|
return timeoutId
|
|
});
|
|
let closeNotificationSafely = buildCloseNotificationSafely(closeFunc);
|
|
|
|
if (!notfObj.sticky) {
|
|
timeoutId = setTimeout(function() {
|
|
closeNotificationSafely('timeout');
|
|
}, displayTime);
|
|
updatedNotificationWindow.displayTimer = timeoutId;
|
|
}
|
|
|
|
// Trigger onShowFunc if existent
|
|
if (notfObj.onShowFunc) {
|
|
notfObj.onShowFunc({
|
|
event: 'show',
|
|
id: notfObj.id,
|
|
closeNotification: closeNotificationSafely
|
|
})
|
|
}
|
|
|
|
// Save onClickFunc in notification window
|
|
if (notfObj.onClickFunc) {
|
|
updatedNotificationWindow.electronNotifyOnClickFunc = notfObj.onClickFunc
|
|
} else {
|
|
delete updatedNotificationWindow.electronNotifyOnClickFunc;
|
|
}
|
|
|
|
if (notfObj.onCloseFunc) {
|
|
updatedNotificationWindow.electronNotifyOnCloseFunc = notfObj.onCloseFunc
|
|
} else {
|
|
delete updatedNotificationWindow.electronNotifyOnCloseFunc;
|
|
}
|
|
// Set strings about i18n
|
|
const translation = {};
|
|
translation.close = i18n.getMessageFor('Close');
|
|
|
|
const windowId = notfWindow.id;
|
|
|
|
// Set contents, ...
|
|
updatedNotificationWindow.webContents.send('electron-notify-set-contents',
|
|
Object.assign({ windowId: windowId, i18n: translation}, notfObj));
|
|
// Show window
|
|
updatedNotificationWindow.showInactive();
|
|
|
|
return updatedNotificationWindow;
|
|
}
|
|
|
|
/**
|
|
* Closes the notification
|
|
* @param notificationWindow
|
|
* @param notificationObj
|
|
* @param getTimeoutId
|
|
* @returns {Function}
|
|
*/
|
|
function buildCloseNotification(notificationWindow, notificationObj, getTimeoutId) {
|
|
return function(event) {
|
|
|
|
// safety check to prevent from using an
|
|
// already destroyed notification window
|
|
if (!notificationWindow || typeof notificationWindow.isDestroyed !== 'function' || notificationWindow.isDestroyed()) {
|
|
return new Promise(function(exitEarly) { exitEarly() })
|
|
}
|
|
|
|
if (closedNotifications[notificationObj.id]) {
|
|
delete closedNotifications[notificationObj.id];
|
|
return new Promise(function(exitEarly) { exitEarly() });
|
|
}
|
|
closedNotifications[notificationObj.id] = true;
|
|
|
|
|
|
if (notificationWindow.electronNotifyOnCloseFunc) {
|
|
notificationWindow.electronNotifyOnCloseFunc({
|
|
event: event,
|
|
id: notificationObj.id
|
|
});
|
|
// ToDo: fix this: shouldn't delete method on arg
|
|
/* eslint-disable */
|
|
delete notificationWindow.electronNotifyOnCloseFunc;
|
|
/* eslint-enable */
|
|
}
|
|
|
|
// reset content
|
|
notificationWindow.webContents.send('electron-notify-reset');
|
|
if (getTimeoutId && typeof getTimeoutId === 'function') {
|
|
let timeoutId = getTimeoutId();
|
|
clearTimeout(timeoutId);
|
|
}
|
|
|
|
// Recycle window
|
|
let pos = activeNotifications.indexOf(notificationWindow);
|
|
activeNotifications.splice(pos, 1);
|
|
inactiveWindows.push(notificationWindow);
|
|
|
|
// Hide notification
|
|
notificationWindow.hide();
|
|
|
|
checkForQueuedNotifications();
|
|
|
|
// Move notifications down
|
|
return moveOneDown(pos);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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({
|
|
func: closeFunc,
|
|
args: [ reason || 'closedByAPI' ]
|
|
});
|
|
}
|
|
}
|
|
|
|
ipc.on('electron-notify-close', function (event, winId, notificationObj) {
|
|
log.send(logLevels.INFO, `Closing notification for ${winId}}`);
|
|
let closeFunc = buildCloseNotification(BrowserWindow.fromId(winId), notificationObj);
|
|
buildCloseNotificationSafely(closeFunc)('close');
|
|
});
|
|
|
|
ipc.on('electron-notify-click', function (event, winId, notificationObj) {
|
|
log.send(logLevels.INFO, `Notification click event triggered for ${winId}}`);
|
|
let notificationWindow = BrowserWindow.fromId(winId);
|
|
if (notificationWindow && notificationWindow.electronNotifyOnClickFunc) {
|
|
let closeFunc = buildCloseNotification(notificationWindow, notificationObj);
|
|
notificationWindow.electronNotifyOnClickFunc({
|
|
event: 'click',
|
|
id: notificationObj.id,
|
|
closeNotification: buildCloseNotificationSafely(closeFunc)
|
|
});
|
|
delete notificationWindow.electronNotifyOnClickFunc;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Checks for queued notifications and add them
|
|
* to AnimationQueue if possible
|
|
*/
|
|
function checkForQueuedNotifications() {
|
|
if (notificationQueue.length > 0 &&
|
|
activeNotifications.length < config.maxVisibleNotifications) {
|
|
// Add new notification to animationQueue
|
|
animationQueue.push({
|
|
func: showNotification,
|
|
args: [ notificationQueue.shift() ]
|
|
})
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
return
|
|
}
|
|
// Build array with index of affected notifications
|
|
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 = asyncMap; // Best performance
|
|
if (config.animateInParallel === false) {
|
|
asyncFunc = asyncMapSeries // Sluggish
|
|
}
|
|
asyncFunc(notificationPosArray, moveNotificationAnimation, function() {
|
|
resolve()
|
|
})
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Moves the notification animation
|
|
* @param i
|
|
* @param done
|
|
*/
|
|
function moveNotificationAnimation(i, done) {
|
|
// Get notification to move
|
|
let notificationWindow = activeNotifications[i];
|
|
|
|
// Calc new y position
|
|
let newY;
|
|
switch(config.startCorner) {
|
|
case 'upper-right':
|
|
case 'upper-left':
|
|
newY = config.corner.y + (config.totalHeight * i);
|
|
break;
|
|
default:
|
|
case 'lower-right':
|
|
case 'lower-left':
|
|
newY = config.corner.y - (config.totalHeight * (i + 1));
|
|
break;
|
|
}
|
|
|
|
// Get startPos, calc step size and start animationInterval
|
|
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);
|
|
done(null, 'done');
|
|
return;
|
|
}
|
|
// Move one step down
|
|
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 && windowExists(browserWin)) {
|
|
browserWin.setPosition(parseInt(posX, 10), parseInt(posY, 10))
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find next possible insert position (on top)
|
|
*/
|
|
function calcInsertPos() {
|
|
if (activeNotifications.length < config.maxVisibleNotifications) {
|
|
switch(config.startCorner) {
|
|
case 'upper-right':
|
|
case 'upper-left':
|
|
nextInsertPos.y = config.corner.y + (config.totalHeight * activeNotifications.length);
|
|
break;
|
|
|
|
default:
|
|
case 'lower-right':
|
|
case 'lower-left':
|
|
nextInsertPos.y = config.corner.y - (config.totalHeight * (activeNotifications.length + 1));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
// Are there still inactiveWindows?
|
|
if (inactiveWindows.length > 0) {
|
|
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.winName = 'notification-window';
|
|
notificationWindow.setVisibleOnAllWorkspaces(true);
|
|
notificationWindow.loadURL(getTemplatePath());
|
|
notificationWindow.webContents.on('did-finish-load', function() {
|
|
// Done
|
|
notificationWindow.webContents.send('electron-notify-load-config', config);
|
|
resolve(notificationWindow)
|
|
});
|
|
|
|
notificationWindow.once('closed', cleanUpActiveNotification);
|
|
}
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Once a minute, remove inactive windows to free up memory used.
|
|
*/
|
|
setInterval(cleanUpInactiveWindow, 60000);
|
|
|
|
function cleanUpActiveNotification(event) {
|
|
|
|
if (!event || !event.sender) {
|
|
return null;
|
|
}
|
|
|
|
let pos = activeNotifications.indexOf(event.sender);
|
|
if (pos !== -1) {
|
|
activeNotifications.splice(pos, 1);
|
|
return moveOneDown(pos);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Cleans up inactive windows
|
|
*/
|
|
function cleanUpInactiveWindow() {
|
|
inactiveWindows.forEach(function(window) {
|
|
if (window && windowExists(window)) {
|
|
window.close();
|
|
}
|
|
});
|
|
inactiveWindows = [];
|
|
}
|
|
|
|
/**
|
|
* Closes all the notification windows
|
|
* and cleans up the reference variables
|
|
*/
|
|
function closeAll() {
|
|
resetAnimationQueue();
|
|
const notificationWin = Object.assign([], activeNotifications);
|
|
for (let activeNotification of notificationWin) {
|
|
if (activeNotification && windowExists(activeNotification)) {
|
|
activeNotification.close();
|
|
}
|
|
}
|
|
|
|
nextInsertPos = {};
|
|
activeNotifications = [];
|
|
notificationQueue = [];
|
|
cleanUpInactiveWindow();
|
|
}
|
|
|
|
/**
|
|
* Resets the animation queue instance
|
|
*/
|
|
function resetAnimationQueue() {
|
|
animationQueue = new AnimationQueue();
|
|
}
|
|
|
|
/**
|
|
* Start a new timer to close the notification
|
|
* @param event
|
|
* @param winId
|
|
* @param notificationObj
|
|
*/
|
|
function onMouseLeave(event, winId, notificationObj) {
|
|
if (winId) {
|
|
log.send(logLevels.INFO, `Mouse was removed from the notification ${winId}`);
|
|
const notificationWindow = BrowserWindow.fromId(winId);
|
|
if (notificationWindow && windowExists(notificationWindow)) {
|
|
notificationWindow.displayTimer = setTimeout(function () {
|
|
let closeFunc = buildCloseNotification(BrowserWindow.fromId(winId), notificationObj);
|
|
buildCloseNotificationSafely(closeFunc)('close');
|
|
}, 3000);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clears the timer for a specific notification window
|
|
* @param event
|
|
* @param winId
|
|
*/
|
|
function onMouseOver(event, winId) {
|
|
if (winId) {
|
|
log.send(logLevels.INFO, `Mouse hover on notification ${winId}`);
|
|
const notificationWindow = BrowserWindow.fromId(winId);
|
|
if (notificationWindow) {
|
|
clearTimeout(notificationWindow.displayTimer);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verifies if the passed window is valid and exists
|
|
* @param browserWindow {Electron.BrowserWindow}
|
|
*/
|
|
function windowExists(browserWindow) {
|
|
return !!browserWindow && typeof browserWindow.isDestroyed === 'function' && !browserWindow.isDestroyed();
|
|
}
|
|
|
|
// capturing mouse events
|
|
ipc.on('electron-notify-mouseleave', onMouseLeave);
|
|
ipc.on('electron-notify-mouseover', onMouseOver);
|
|
|
|
|
|
module.exports.notify = notify;
|
|
module.exports.updateConfig = updateConfig;
|
|
module.exports.reset = setupConfig;
|
|
module.exports.closeAll = closeAll;
|
|
module.exports.resetAnimationQueue = resetAnimationQueue;
|