add notification grouping (#48)

This commit is contained in:
Lynn 2017-03-29 17:42:15 -07:00 committed by GitHub
parent 75d1342b28
commit 27d58a94d7
5 changed files with 164 additions and 86 deletions

View File

@ -34,6 +34,9 @@
</body>
<script>
var notfEl = document.getElementById('notf');
var num = 0;
// note: notification will close when clicked
notfEl.addEventListener('click', function() {
var title = document.getElementById('title').value;
var body = document.getElementById('body').value;
@ -41,26 +44,46 @@
var shouldFlash = document.getElementById('flash').checked;
var color = document.getElementById('color').value;
num++;
// notfs with same groupId will replace existing notf.
var groupId = (num % 2).toString();
var notf = new SYM_API.Notification(title, {
body: body,
body: (body + ' num=' + num + ' groupId=' + groupId),
image: imageUrl,
flash: shouldFlash,
color: color || 'white',
data: {
hello: 'hello word'
}
},
groupId: groupId
});
notf.addEventListener('click', function(event) {
notf.addEventListener('click', onclick);
function onclick(event) {
event.target.data.then(function(value) {
alert('notification clicked: ' + value.hello);
})
});
}
notf.addEventListener('close', function() {
notf.addEventListener('close', onclose);
function onclose() {
alert('notification closed');
});
removeEvents();
};
notf.addEventListener('error', onerror);
function onerror(event) {
alert('error=' + event.result);
};
// be sure to remove all events when closed, otherwise leaks
// will occur.
function removeEvents() {
notf.removeEventListener('click', onclick)
notf.removeEventListener('close', onclose)
notf.removeEventListener('error', onerror)
}
});
var badgeCount = 0;

View File

@ -54,11 +54,11 @@ function setContents(notificationObj) {
audio.play()
}
} catch (e) {
log('electron-notify: ERROR could not find sound file: ' + notificationObj.sound.replace('file://', ''), e, e.stack)
log('electron-notify: ERROR could not find sound file: ' + notificationObj.sound.replace('file://', ''), e, e.stack);
}
}
let notiDoc = window.document
let notiDoc = window.document;
let container = notiDoc.getElementById('container');
@ -78,34 +78,35 @@ function setContents(notificationObj) {
}
// Title
let titleDoc = notiDoc.getElementById('title')
titleDoc.innerHTML = notificationObj.title || ''
let titleDoc = notiDoc.getElementById('title');
titleDoc.innerHTML = notificationObj.title || '';
// message
let messageDoc = notiDoc.getElementById('message')
messageDoc.innerHTML = notificationObj.text || ''
let messageDoc = notiDoc.getElementById('message');
messageDoc.innerHTML = notificationObj.text || '';
// Image
let imageDoc = notiDoc.getElementById('image')
let imageDoc = notiDoc.getElementById('image');
if (notificationObj.image) {
imageDoc.src = notificationObj.image
imageDoc.src = notificationObj.image;
} else {
setStyleOnDomElement({ display: 'none'}, imageDoc)
setStyleOnDomElement({ display: 'none'}, imageDoc);
}
const winId = notificationObj.windowId;
// Close button
let closeButton = notiDoc.getElementById('close')
closeButton.addEventListener('click', function(clickEvent) {
let closeButton = notiDoc.getElementById('close');
// note: use onclick because we only want one handler, for case
// when content gets overwritten by notf with same groupId
closeButton.onclick = function(clickEvent) {
clickEvent.stopPropagation()
ipc.send('electron-notify-close', winId, notificationObj)
})
}
// URL
container.addEventListener('click', function() {
ipc.send('electron-notify-click', winId, notificationObj)
})
container.onclick = function() {
ipc.send('electron-notify-click', winId, notificationObj);
}
}
function setStyleOnDomElement(styleObj, domElement) {

View File

@ -229,20 +229,6 @@ function setupConfig() {
function notify(notification) {
if (notificationQueue.length >= MAX_QUEUE_SIZE) {
var id = latestID;
incrementId();
if (typeof notification.onErrorFunc === 'function') {
setTimeout(function() {
notification.onErrorFunc({
id: id,
error: 'max notification queue size reached: ' + MAX_QUEUE_SIZE
});
}, 0);
}
return id;
}
// Is it an object and only one argument?
if (arguments.length === 1 && typeof notification === 'object') {
let notf = Object.assign({}, notification);
@ -265,6 +251,54 @@ function incrementId() {
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
});
}, 0);
}
resolve();
return;
}
// check if group id provided. should replace existing notification
// if has same grouping id.
let groupId = notificationObj.groupId;
if (groupId) {
// first check waiting items
for(let i = 0; i < notificationQueue.length; i++) {
if (groupId === notificationQueue[ i ].groupId) {
notificationQueue[ i ] = notificationObj;
resolve();
return;
}
}
// next check items being shown
for(let i = 0; i < activeNotifications.length; i++) {
if (groupId === activeNotifications[ i ].groupId) {
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: notificationObj.id
});
delete notificationWindow.electronNotifyOnCloseFunc;
}
setNotificationContents(notificationWindow, notificationObj)
resolve();
return;
}
}
}
// Can we show it?
if (activeNotifications.length < config.maxVisibleNotifications) {
// Get inactiveWindow or create new:
@ -273,62 +307,76 @@ function showNotification(notificationObj) {
calcInsertPos()
setWindowPosition(notificationWindow, nextInsertPos.x, nextInsertPos.y)
// Add to activeNotifications
activeNotifications.push(notificationWindow)
let updatedNotfWindow = setNotificationContents(notificationWindow, notificationObj);
// Display time per notification basis.
let displayTime = notificationObj.displayTime ? notificationObj.displayTime : config.displayTime
activeNotifications.push(updatedNotfWindow);
// Set timeout to hide notification
let timeoutId
let closeFunc = buildCloseNotification(notificationWindow, notificationObj, function() {
return timeoutId
})
let closeNotificationSafely = buildCloseNotificationSafely(closeFunc)
timeoutId = setTimeout(function() {
closeNotificationSafely('timeout')
}, displayTime)
// Trigger onShowFunc if existent
if (notificationObj.onShowFunc) {
notificationObj.onShowFunc({
event: 'show',
id: notificationObj.id,
closeNotification: closeNotificationSafely
})
}
var updatedNotificationWindow = notificationWindow;
// Save onClickFunc in notification window
if (notificationObj.onClickFunc) {
updatedNotificationWindow.electronNotifyOnClickFunc = notificationObj.onClickFunc
} else {
delete updatedNotificationWindow.electronNotifyOnClickFunc
}
if (notificationObj.onCloseFunc) {
updatedNotificationWindow.electronNotifyOnCloseFunc = notificationObj.onCloseFunc
} else {
delete updatedNotificationWindow.electronNotifyOnCloseFunc
}
const windowId = notificationWindow.id;
// Set contents, ...
updatedNotificationWindow.webContents.send('electron-notify-set-contents',
Object.assign({ windowId: windowId}, notificationObj));
// Show window
updatedNotificationWindow.showInactive();
resolve(updatedNotificationWindow)
resolve(updatedNotfWindow);
})
} else {
// Add to notificationQueue
notificationQueue.push(notificationObj)
resolve()
notificationQueue.push(notificationObj);
resolve();
}
})
}
function setNotificationContents(notfWindow, notfObj) {
// Display time per notification basis.
let displayTime = notfObj.displayTime ? notfObj.displayTime : config.displayTime;
if (notfWindow.displayTimer) {
clearTimeout(notfWindow.displayTimer);
}
// Set timeout to hide notification
let timeoutId;
let closeFunc = buildCloseNotification(notfWindow, notfObj, function() {
return timeoutId
});
let closeNotificationSafely = buildCloseNotificationSafely(closeFunc);
timeoutId = setTimeout(function() {
closeNotificationSafely('timeout');
}, displayTime);
var updatedNotificationWindow = notfWindow;
updatedNotificationWindow.displayTimer = timeoutId;
updatedNotificationWindow.groupId = notfObj.groupId;
// 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;
}
const windowId = notfWindow.id;
// Set contents, ...
updatedNotificationWindow.webContents.send('electron-notify-set-contents',
Object.assign({ windowId: windowId}, notfObj));
// Show window
updatedNotificationWindow.showInactive();
return updatedNotificationWindow;
}
// Close notification function
function buildCloseNotification(notificationWindow, notificationObj, getTimeoutId) {
return function(event) {

View File

@ -17,6 +17,7 @@ class Notify {
image: options.image || options.icon,
flash: options.flash,
color: options.color,
groupId: options.groupId,
onShowFunc: onShow.bind(this),
onClickFunc: onClick.bind(this),
onCloseFunc: onClose.bind(this),
@ -35,6 +36,7 @@ class Notify {
function onClick(arg) {
if (arg.id === this._id) {
this.emitter.emit('click');
arg.closeNotification();
}
}
@ -80,7 +82,7 @@ class Notify {
removeEventListener(event, cb) {
if (event && typeof cb === 'function') {
this.emitter.removeEventListener(event, cb);
this.emitter.removeListener(event, cb);
}
}

View File

@ -24,6 +24,9 @@ class Notify {
* icon {string} url of image to show in notification
* flash {bool} true if notification should flash (default false)
* color {string} background color for notification
* groupId {string} non-empty string to unique identify notf, if another
* notification arrives with same groupId then it's content will
* replace existing notification.
* data {object} arbitrary object to be stored with notification
* }
*/
@ -41,7 +44,8 @@ class Notify {
static get permission() {}
/**
* returns data object passed in via constructor options
* returns data object passed in via constructor options, return a
* promise that will be fullfilled with the data.
*/
get data() {}