Merge remote-tracking branch 'jerbob92/master' into develop-test

Conflicts:
	js/app.js
This commit is contained in:
Trevor Squillario 2014-04-05 12:53:24 -04:00
commit 8361a247ba
14 changed files with 768 additions and 85 deletions

View file

@ -33,6 +33,7 @@
<script src="js/plugins/jquery.scrollTo.min.js" type="text/javascript"></script>
<script src="js/plugins/UnityShim.js" type="text/javascript"></script>
<script src="js/plugins/jplayer/jquery.jplayer.min.js" type="text/javascript"></script>
<script src="js/plugins/notifyjs/notify.js" type="text/javascript"></script>
<script src="js/app.js" type="text/javascript"></script>
<script src="js/service.js" type="text/javascript"></script>
<script src="js/utils.js" type="text/javascript"></script>

126
js/app.js
View file

@ -1,6 +1,46 @@
/* Declare app level module */
var JamStash = angular.module('JamStash', ['ngCookies', 'ngRoute', 'ngSanitize']);
//var JamStash = angular.module('JamStash', ['ngCookies', 'ngRoute']);
JamStash.service('globals', function (utils) {
this.settings = {
// Subsonic
/* Demo Server
Username: "android-guest"),
Password: "guest"),
Server: "http://subsonic.org/demo"),
*/
Url: "http://Jamstash.com/beta/#/archive/",
Username: "",
Password: "",
Server: "",
Timeout: 10000,
NotificationTimeout: 20000,
Protocol: "jsonp",
ApplicationName: "Jamstash",
ApiVersion: "1.6.0",
AutoPlaylists: "",
AutoPlaylistSize: 25,
AutoAlbumSize: 15,
// General
HideAZ: false,
ScrollTitle: true,
NotificationSong: true,
NotificationNowPlaying: false,
SaveTrackPosition: false,
ForceFlash: false,
Theme: "Default",
DefaultLibraryLayout: "grid",
AutoPlay: false,
LoopQueue: false,
Repeat: false,
Debug: false
};
this.SavedCollections = [];
this.SavedGenres = [];
this.BaseURL = function () { return this.settings.Server + '/rest'; };
this.BaseParams = function () { return 'u=' + this.settings.Username + '&p=' + this.settings.Password + '&f=' + this.settings.Protocol + '&v=' + this.settings.ApiVersion + '&c=' + this.settings.ApplicationName; };
});
/*
JamStash.config(function ($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist(['/^\s*(https?|file|ms-appx):/', 'self']);
@ -28,26 +68,78 @@ JamStash.config(function ($routeProvider) {
.when('/archive/:artist/:album', { templateUrl: 'js/partials/archive.html', controller: 'ArchiveCtrl' })
.otherwise({ redirectTo: '/index' });
})
.run(['$rootScope', '$location', 'globals', 'utils', function ($rootScope, $location, globals, utils) {
$rootScope.$on("$locationChangeStart", function (event, next, current) {
JamStash.config(function ($httpProvider) {
$httpProvider.interceptors.push(function ($rootScope, $location, $q, globals) {
return {
'request': function (request) {
// if we're not logged-in to the AngularJS app, redirect to login page
//$rootScope.loggedIn = $rootScope.loggedIn || globals.settings.Username;
$rootScope.loggedIn = false;
var path = $location.path().replace(/^\/([^\/]*).*$/, '$1');
if (globals.settings.Username !== "" && globals.settings.Password !== "" && globals.settings.Server !== "" && path != 'archive') {
if (globals.settings.Username != "" && globals.settings.Password != "" && globals.settings.Server != "") {
$rootScope.loggedIn = true;
$.fancybox.close();
}
if (!$rootScope.loggedIn && (path != 'settings' && path != 'archive')) {
var url = '/settings';
$location.path(url);
if (!$rootScope.loggedIn && $location.path() != '/settings' && $location.path() != '/archive') {
$location.path('/settings');
}
return request;
},
'responseError': function (rejection) {
// if we're not logged-in to the web service, redirect to login page
if (rejection.status === 401 && $location.path() != '/settings') {
$rootScope.loggedIn = false;
$location.path('/settings');
}
return $q.reject(rejection);
}
};
});
});
JamStash.service('model', function (utils) {
this.Index = function (name, artist) {
this.name = name;
this.artist = artist;
}
this.Artist = function (id, name) {
this.id = id;
this.name = name;
}
this.Album = function (id, parentid, name, artist, coverartthumb, coverartfull, date, starred, description, url) {
this.id = id;
this.parentid = parentid;
this.name = name;
this.artist = artist;
this.coverartthumb = coverartthumb;
this.coverartfull = coverartfull;
this.date = date;
this.starred = starred;
this.description = description;
this.url = url;
}
this.Song = function (id, parentid, track, name, artist, artistId, album, albumId, coverartthumb, coverartfull, duration, rating, starred, suffix, specs, url, position, description) {
this.id = id;
this.parentid = parentid;
this.track = track;
this.name = name;
this.artist = artist;
this.artistId = artistId;
this.album = album;
this.albumId = albumId;
this.coverartthumb = coverartthumb;
this.coverartfull = coverartfull;
this.duration = duration;
this.time = duration == '' ? '00:00' : utils.secondsToTime(duration);
this.rating = rating;
this.starred = starred;
this.suffix = suffix;
this.specs = specs;
this.url = url;
this.position = position;
this.selected = false;
this.playing = false;
this.description = description;
this.displayName = this.name + " - " + this.album + " - " + this.artist;
}
});
}]);
/*
JamStash.config(function ($locationProvider) {
$locationProvider.html5Mode(true);
})
JamStash.config(function ($httpProvider, globals) {
$httpProvider.defaults.timeout = globals.settings.Timeout;
})
*/

View file

@ -31,13 +31,13 @@ function SettingsCtrl($rootScope, $scope, $routeParams, $location, utils, global
if ($scope.settings.Password !== '' && globals.settings.Password.substring(0, 4) != 'enc:') { $scope.settings.Password = 'enc:' + utils.HexEncode($scope.settings.Password); }
if ($scope.settings.NotificationSong) {
notifications.requestPermissionIfRequired();
if (!notifications.hasNotificationPermission()) {
if (!notifications.hasNotificationSupport()) {
alert('HTML5 Notifications are not available for your current browser, Sorry :(');
}
}
if ($scope.settings.NotificationNowPlaying) {
notifications.requestPermissionIfRequired();
if (!notifications.hasNotificationPermission()) {
if (!notifications.hasNotificationSupport()) {
alert('HTML5 Notifications are not available for your current browser, Sorry :(');
}
}

1
js/plugins/notifyjs/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
node_modules

View file

@ -0,0 +1,20 @@
{
"globals": {
"define": false,
"Notification": false
},
"browser": true,
"node": true,
"esnext": true,
"curly": true,
"eqeqeq": true,
"immed": true,
"latedef": false,
"newcap": true,
"undef": true,
"strict": true,
"trailing": true,
"white": true,
"indent": false,
"unused": false
}

View file

@ -0,0 +1,8 @@
language: node_js
node_js:
- "0.8"
before_install:
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
addons:
firefox: "22.0"

View file

@ -0,0 +1,9 @@
Copyright (c) 2013 Alex Gibson
http://alxgbsn.co.uk/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction except as noted below, including without limitation the rights to use, copy, modify, merge, publish, distribute, and/or sublicense, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE

View file

@ -0,0 +1,92 @@
Notify.js
=========
[![Build Status](https://travis-ci.org/alexgibson/notify.js.png?branch=master)](https://travis-ci.org/alexgibson/notify.js)
A handy wrapper for using the [Web Notifications API](http://www.w3.org/TR/notifications/). Notify.js aims to simplify requesting user permission and associated Web Notification API events, as well as providing a few extra callbacks and convenience methods.
Installation
---------------------------------------
* Download: [zip](https://github.com/alexgibson/notify.js/zipball/master)
* [Bower](https://github.com/twitter/bower/): `bower install notify.js`
* Git: `git clone https://github.com/alexgibson/notify.js`
Setup
---------
This component can be used as an AMD module, or a global.
To initialize a web notification create a new `Notify` instance, passing the message `title` as well as any other options you wish to use.
```
var myNotification = new Notify('Yo dawg!', {
body: 'This is an awesome notification',
notifyShow: onNotifyShow
});
function onNotifyShow() {
console.log('notification was shown!');
}
```
Then show the notification.
```
myNotification.show();
```
Required parameters
-------------------
* title (string) - notification title
Optional parameters
-------------------
* body: (string) - notification message body
* icon: (string) - path for icon to display in notification
* tag: (string) - unique identifier to stop duplicate notifications
* timeout: (integer) - number of seconds to close the notification automatically
* notifyShow: (function) - callback when notification is shown
* notifyClose: (function) - callback when notification is closed
* notifyClick: (function) - callback when notification is clicked
* notifyError: (function) - callback when notification throws an error
* permissionGranted: (function) - callback when user has granted permission
* permissionDenied: (function) - callback when user has denied permission
Useful methods
--------------
* `Notify.needsPermission()` - (returns boolean) check is permission is needed for the user to receive notifications.
* `Notify.requestPermission()` - requests permission from the user if needed and handles permission callbacks.
* `Notify.isSupported()` - (returns boolean) test for Web Notifications API browser support
A note about Chrome
-------------------
Unlike other browsers that implement the Web Notification API, Chrome does not permit requesting permission on page load (it must be as a result of user interaction, such as a `click` event). You can find out more in the [Chromium bug for this issue](https://code.google.com/p/chromium/issues/detail?id=274284).
Testing
-------
Install [Node](http://nodejs.org). Testing relies on the Karma test-runner, which can be installed globally using the following command.
```
npm install -g karma
```
In the project root, to perform a single pass of the tests using Firefox run:
```
npm test
```
Browser support
---------------------------------------
- Chrome (desktop)
- Safari
- Firefox
- Firefox OS (v1.2+)
- Firefox Mobile (Android)

View file

@ -0,0 +1,27 @@
{
"name": "notify.js",
"description": "A handy wrapper for the Web Notifications API",
"version": "1.1.0",
"author": {
"name": "Alex Gibson",
"email": "alxgbsn@gmail.com"
},
"keywords": ["javascript", "web notifications", "notification", "notify"],
"main": "./notify.js",
"repository": {
"type": "git",
"url": "https://github.com/alexgibson/notify.js.git"
},
"readme": "README.md",
"licenses": [{
"type": "MIT",
"url": "http://opensource.org/licenses/MIT"
}],
"ignore": [
"test",
".jshintrc",
"karma.conf.js",
"package.json",
".travis.yml"
]
}

View file

@ -0,0 +1,69 @@
// Karma configuration
// Generated on Sun Aug 04 2013 21:16:15 GMT+0100 (BST)
// base path, that will be used to resolve files and exclude
basePath = '';
// list of files / patterns to load in the browser
files = [
JASMINE,
JASMINE_ADAPTER,
'notify.js',
'test/*.js',
{ pattern: 'node_modules/sinon/pkg/sinon.js', watched: false, included: true }
];
// list of files to exclude
exclude = [
];
// test results reporter to use
// possible values: 'dots', 'progress', 'junit'
reporters = ['dots'];
// web server port
port = 9876;
// cli runner port
runnerPort = 9100;
// enable / disable colors in the output (reporters and logs)
colors = true;
// level of logging
// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
logLevel = LOG_INFO;
// enable / disable watching file and executing tests whenever any file changes
autoWatch = true;
// Start these browsers, currently available:
// - Chrome
// - ChromeCanary
// - Firefox
// - Opera
// - Safari (only Mac)
// - PhantomJS
// - IE (only Windows)
browsers = ['Firefox'];
// If browser does not capture in given timeout [ms], kill it
captureTimeout = 60000;
// Continuous Integration mode
// if true, it capture browsers, run tests and exit
singleRun = false;

View file

@ -0,0 +1,193 @@
(function (root, factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// AMD environment
define('notify', [], function () {
return factory(root, document);
});
} else {
// Browser environment
root.Notify = factory(root, document);
}
}(this, function (w, d) {
'use strict';
function Notify(title, options) {
this.title = typeof title === 'string' ? title : null;
this.options = {
icon: '',
body: '',
tag: '',
notifyShow: null,
notifyClose: null,
notifyClick: null,
notifyError: null,
permissionGranted: null,
permissionDenied: null,
timeout: null
};
this.permission = null;
if (!Notify.isSupported()) {
return;
}
if (!this.title) {
throw new Error('Notify(): first arg (title) must be a string.');
}
//User defined options for notification content
if (typeof options === 'object') {
for (var i in options) {
if (options.hasOwnProperty(i)) {
this.options[i] = options[i];
}
}
//callback when notification is displayed
if (typeof this.options.notifyShow === 'function') {
this.onShowCallback = this.options.notifyShow;
}
//callback when notification is closed
if (typeof this.options.notifyClose === 'function') {
this.onCloseCallback = this.options.notifyClose;
}
//callback when notification is clicked
if (typeof this.options.notifyClick === 'function') {
this.onClickCallback = this.options.notifyClick;
}
//callback when notification throws error
if (typeof this.options.notifyError === 'function') {
this.onErrorCallback = this.options.notifyError;
}
}
}
// return true if the browser supports HTML5 Notification
Notify.isSupported = function () {
if ('Notification' in w) {
return true;
}
return false;
};
// returns true if the permission is not granted
Notify.needsPermission = function () {
if (Notify.isSupported() && Notification.permission === 'granted') {
return false;
}
return true;
};
// asks the user for permission to display notifications. Then calls the callback functions is supplied.
Notify.requestPermission = function (onPermissionGrantedCallback, onPermissionDeniedCallback) {
if (Notify.isSupported()) {
w.Notification.requestPermission(function (perm) {
switch (perm) {
case 'granted':
if (typeof onPermissionGrantedCallback === 'function') {
onPermissionGrantedCallback();
}
break;
case 'denied':
if (typeof onPermissionDeniedCallback === 'function') {
onPermissionDeniedCallback();
}
break;
}
});
}
};
Notify.prototype.show = function () {
var that = this;
if (!Notify.isSupported()) {
return;
}
this.myNotify = new Notification(this.title, {
'body': this.options.body,
'tag' : this.options.tag,
'icon' : this.options.icon
});
if (this.options.timeout && !isNaN(this.options.timeout)) {
setTimeout(this.close.bind(this), this.options.timeout * 1000);
}
this.myNotify.addEventListener('show', this, false);
this.myNotify.addEventListener('error', this, false);
this.myNotify.addEventListener('close', this, false);
this.myNotify.addEventListener('click', this, false);
};
Notify.prototype.onShowNotification = function (e) {
if (this.onShowCallback) {
this.onShowCallback(e);
}
};
Notify.prototype.onCloseNotification = function () {
if (this.onCloseCallback) {
this.onCloseCallback();
}
this.destroy();
};
Notify.prototype.onClickNotification = function () {
if (this.onClickCallback) {
this.onClickCallback();
}
};
Notify.prototype.onErrorNotification = function () {
if (this.onErrorCallback) {
this.onErrorCallback();
}
this.destroy();
};
Notify.prototype.destroy = function () {
this.myNotify.removeEventListener('show', this, false);
this.myNotify.removeEventListener('error', this, false);
this.myNotify.removeEventListener('close', this, false);
this.myNotify.removeEventListener('click', this, false);
};
Notify.prototype.close = function () {
this.myNotify.close();
};
Notify.prototype.handleEvent = function (e) {
switch (e.type) {
case 'show':
this.onShowNotification(e);
break;
case 'close':
this.onCloseNotification(e);
break;
case 'click':
this.onClickNotification(e);
break;
case 'error':
this.onErrorNotification(e);
break;
}
};
return Notify;
}));

View file

@ -0,0 +1,27 @@
{
"name": "notify.js",
"version": "1.1.0",
"description": "A handy wrapper for the Web Notifications API",
"main": "notify.js",
"devDependencies": {
"karma": "~0.8.5",
"sinon": "~1.7.3"
},
"repository": {
"type": "git",
"url": "https://github.com/alexgibson/notify.js.git"
},
"keywords": [
"web",
"notification",
"nofity"
],
"author": "Alex Gibson",
"license": "MIT",
"bugs": {
"url": "https://github.com/alexgibson/notify.js/issues"
},
"scripts": {
"test": "./node_modules/.bin/karma start --browsers Firefox --single-run"
}
}

View file

@ -0,0 +1,97 @@
describe('instantiation', function () {
it('should create a new Notify instance', function () {
var notification = new Notify('foo');
expect(notification instanceof window.Notify).toBeTruthy();
});
it('should throw an exception if has no title', function () {
expect(function () {
var notification = new Notify();
}).toThrow();
});
});
describe('permission', function () {
it('should check if permission is needed', function () {
expect(Notify.needsPermission()).toBeTruthy();
});
it('should request permission from the user', function () {
spyOn(window.Notification, 'requestPermission');
Notify.requestPermission();
expect(window.Notification.requestPermission).toHaveBeenCalled();
});
});
describe('callbacks', function () {
var callback;
beforeEach(function() {
callback = jasmine.createSpy();
});
it('should fire show callback', function () {
var notification = new Notify('foo', {
notifyShow: callback
});
notification.onShowNotification();
expect(callback).toHaveBeenCalled();
});
it('should fire close callback', function () {
var notification = new Notify('foo', {
notifyClose: callback
});
notification.show();
notification.onCloseNotification();
expect(callback).toHaveBeenCalled();
});
it('should fire click callback', function () {
var notification = new Notify('foo', {
notifyClick: callback
});
notification.onClickNotification();
expect(callback).toHaveBeenCalled();
});
it('should fire error callback', function () {
var notification = new Notify('foo', {
notifyError: callback
});
notification.show();
notification.onErrorNotification();
expect(callback).toHaveBeenCalled();
});
it('should destroy a notification once closed', function () {
var notification = new Notify('foo', {
notifyClose: callback
});
spyOn(notification, 'destroy');
notification.onCloseNotification();
expect(notification.destroy).toHaveBeenCalled();
});
});
describe('timeout', function () {
beforeEach(function () {
jasmine.Clock.useMock();
});
it('should close a notification automatically', function () {
var notification = new Notify('foo', {
timeout: 1
});
spyOn(window.Notification.prototype, 'close');
notification.show();
expect(window.Notification.prototype.close).not.toHaveBeenCalled();
jasmine.Clock.tick(1000);
expect(window.Notification.prototype.close).toHaveBeenCalled();
});
});

View file

@ -133,6 +133,74 @@ JamStash.service('globals', function () {
this.BaseURL = function () { return this.settings.Server + '/rest'; };
this.BaseParams = function () { return 'u=' + this.settings.Username + '&p=' + this.settings.Password + '&f=' + this.settings.Protocol + '&v=' + this.settings.ApiVersion + '&c=' + this.settings.ApplicationName; };
});
JamStash.service('notifications', function ($rootScope, globals) {
var msgIndex = 1;
this.updateMessage = function (msg, autohide) {
if (msg != '') {
var id = msgIndex;
$('#messages').append('<span id=\"msg_' + id + '\" class="message">' + msg + '</span>');
$('#messages').fadeIn();
$("#messages").scrollTo('100%');
var el = '#msg_' + id;
if (autohide) {
setTimeout(function () {
$(el).fadeOut(function () { $(this).remove(); });
}, globals.settings.NotificationTimeout);
} else {
$(el).click(function () {
$(el).fadeOut(function () { $(this).remove(); });
return false;
});
}
msgIndex++;
}
}
this.requestPermissionIfRequired = function () {
if (window.Notify.isSupported() && window.Notify.needsPermission()) {
window.Notify.requestPermission();
}
}
this.hasNotificationPermission = function () {
return (window.Notify.needsPermission() === false);
}
this.hasNotificationSupport = function () {
return window.Notify.isSupported();
}
var notifications = new Array();
this.showNotification = function (pic, title, text, type, bind) {
if (this.hasNotificationPermission()) {
//closeAllNotifications()
var settings = {}
if (bind = '#NextTrack') {
settings.notifyClick = function () {
$rootScope.nextTrack();
this.close();
};
}
if (type == 'text') {
settings.body = text;
settings.icon = pic;
} else if (type == 'html') {
settings.body = text;
}
var notification = new Notify(title, settings);
notifications.push(notification);
setTimeout(function (notWin) {
notWin.close();
}, globals.settings.NotificationTimeout, notification);
notification.show();
} else {
console.log("showNotification: No Permission");
}
}
this.closeAllNotifications = function () {
for (notification in notifications) {
notifications[notification].close();
}
}
});
// Directives
JamStash.directive('layout', function () {
return {
@ -349,6 +417,30 @@ JamStash.directive('ngDownload', function ($compile) {
}
};
});
JamStash.directive('stopEvent', function () {
return {
restrict: 'A',
link: function (scope, element, attr) {
element.bind(attr.stopEvent, function (e) {
e.stopPropagation();
});
}
};
});
JamStash.directive('ngEnter', function () {
return function (scope, element, attrs) {
element.bind("keydown keypress", function (event) {
if (event.which === 13) {
scope.$apply(function () {
scope.$eval(attrs.ngEnter);
});
event.preventDefault();
}
});
};
});
/* Factory */
JamStash.factory('json', function ($http) { // Deferred loading
@ -413,6 +505,16 @@ JamStash.factory('subsonic', function ($http, globals, utils) {
}
};
});
JamStash.factory('json', function ($http) { // Deferred loading
return {
getCollections: function (callback) {
$http.get('js/json_collections.js').success(callback);
},
getChangeLog: function (callback) {
$http.get('js/json_changelog.js').success(callback);
}
}
});
/* Filters */
JamStash.filter('capitalize', function () {
@ -425,65 +527,10 @@ JamStash.filter('musicfolder', function () {
return items.slice(1, items.length);
};
});
JamStash.filter('capitalize', function () {
return function (input, scope) {
return input.substring(0, 1).toUpperCase() + input.substring(1);
}
});
JamStash.service('notifications', function ($rootScope, globals) {
var msgIndex = 1;
this.updateMessage = function (msg, autohide) {
if (msg !== '') {
var id = msgIndex;
$('#messages').append('<span id=\"msg_' + id + '\" class="message">' + msg + '</span>');
$('#messages').fadeIn();
$("#messages").scrollTo('100%');
var el = '#msg_' + id;
if (autohide) {
setTimeout(function () {
$(el).fadeOut(function () { $(this).remove(); });
}, globals.settings.Timeout);
}
$(el).click(function () {
$(el).fadeOut(function () { $(this).remove(); });
return false;
});
msgIndex++;
}
};
this.requestPermissionIfRequired = function () {
if (!this.hasNotificationPermission() && (window.webkitNotifications)) {
window.webkitNotifications.requestPermission();
}
};
this.hasNotificationPermission = function () {
return !!(window.webkitNotifications) && (window.webkitNotifications.checkPermission() === 0);
};
var notifications = [];
this.showNotification = function (pic, title, text, type, bind) {
if (this.hasNotificationPermission()) {
//closeAllNotifications()
var popup;
if (type == 'text') {
popup = window.webkitNotifications.createNotification(pic, title, text);
} else if (type == 'html') {
popup = window.webkitNotifications.createHTMLNotification(text);
}
if (bind == '#NextTrack') {
popup.addEventListener('click', function (bind) {
//$(bind).click();
$rootScope.nextTrack();
this.cancel();
});
}
notifications.push(popup);
setTimeout(function (notWin) {
notWin.cancel();
}, globals.settings.Timeout, popup);
popup.show();
} else {
console.log("showNotification: No Permission");
}
};
this.closeAllNotifications = function () {
for (notification in notifications) {
notifications[notification].cancel();
}
};
});