Refactor song selection across the app

- Added a new SelectedSongs service. Its purpose is to store an array of selected songs and all behaviour associated with it : adding songs to it, removing them, toggling a song, etc. It also changes each song's selected property.
- Removed selectAll, selectNone, playAll, playFrom, addSongsToQueue and selectSong from main-controller.js. Those are now defined either in queue.js, subsonic.js or archive.js, depending on where they are used. I know it does add some duplication between archive.js and subsonic.js, but this will be addressed in a later commit (maybe by creating a shared service).
- Refactored subsonic-service.js' addToPlaylist() and split it between subsonic-service.js and subsonic.js (like all the other playlist-related functions).
- Moved subsonic-service.js' songsRemoveSelected() to subsonic.js since it only removes songs from the scope.
- Removed $rootScope and utils (unused dependencies) from subsonic-service.js
This commit is contained in:
Hyzual 2015-07-11 14:12:37 +02:00
parent 7e0c5a176a
commit 5d385149f3
16 changed files with 658 additions and 276 deletions

View file

@ -7,7 +7,7 @@
<a href="" class="button" id="action_PlayAlbum" title="Play Album" ng-click="playAll()"><img src="images/play_gl_6x8.png"></a> <a href="" class="button" id="action_PlayAlbum" title="Play Album" ng-click="playAll()"><img src="images/play_gl_6x8.png"></a>
<a href="" class="button" id="action_SelectAll" title="Select All" ng-click="selectAll()">All</a> <a href="" class="button" id="action_SelectAll" title="Select All" ng-click="selectAll()">All</a>
<a href="" class="button" id="action_SelectNone" title="Select None" ng-click="selectNone()">None</a> <a href="" class="button" id="action_SelectNone" title="Select None" ng-click="selectNone()">None</a>
<a href="" class="button" id="action_AddToQueue" title="Add To Queue" ng-click="addSongsToQueue()">+ Queue</a> <a href="" class="button" id="action_AddToQueue" title="Add To Queue" ng-click="addSelectedSongsToQueue()">+ Queue</a>
</div> </div>
<div id="ArchiveContainer" class="lgsection" split> <div id="ArchiveContainer" class="lgsection" split>
<div id="right-component" class="lgcolumn"> <div id="right-component" class="lgcolumn">

View file

@ -3,10 +3,10 @@
* *
* Access Archive.org * Access Archive.org
*/ */
angular.module('jamstash.archive.controller', ['jamstash.archive.service']) angular.module('jamstash.archive.controller', ['jamstash.archive.service', 'jamstash.selectedsongs'])
.controller('ArchiveController', ['$scope', '$rootScope', '$location', '$routeParams', '$http', '$timeout', 'utils', 'globals', 'model', 'notifications', 'player', 'archive', 'json', .controller('ArchiveController', ['$scope', '$rootScope', '$location', '$routeParams', '$http', '$timeout', 'utils', 'globals', 'model', 'notifications', 'player', 'archive', 'json', 'SelectedSongs',
function($scope, $rootScope, $location, $routeParams, $http, $timeout, utils, globals, model, notifications, player, archive, json){ function($scope, $rootScope, $location, $routeParams, $http, $timeout, utils, globals, model, notifications, player, archive, json, SelectedSongs){
'use strict'; 'use strict';
$scope.settings = globals.settings; $scope.settings = globals.settings;
@ -150,18 +150,52 @@ angular.module('jamstash.archive.controller', ['jamstash.archive.service'])
$scope.scrollToTop = function () { $scope.scrollToTop = function () {
$('#Artists').stop().scrollTo('#auto', 400); $('#Artists').stop().scrollTo('#auto', 400);
}; };
$scope.selectAll = function () { $scope.selectAll = function () {
$rootScope.selectAll($scope.song); SelectedSongs.addSongs($scope.song);
}; };
$scope.addSelectedSongsToQueue = function () {
if (SelectedSongs.get().length === 0) {
return;
}
player.addSongs(SelectedSongs.get());
notifications.updateMessage(SelectedSongs.get().length + ' Song(s) Added to Queue', true);
SelectedSongs.reset();
};
$scope.toggleSelection = function (song) {
SelectedSongs.toggle(song);
};
$scope.selectNone = function () { $scope.selectNone = function () {
$rootScope.selectNone($scope.song); SelectedSongs.reset();
}; };
$scope.playAll = function () { $scope.playAll = function () {
$rootScope.playAll($scope.song); player.emptyQueue()
.addSongs($scope.song)
.playFirstSong();
}; };
$scope.playFrom = function (index) { $scope.playFrom = function (index) {
$rootScope.playFrom(index, $scope.song); var songsToPlay = $scope.song.slice(index, $scope.song.length);
player.emptyQueue()
.addSongs(songsToPlay)
.playFirstSong();
}; };
$scope.addSongToQueue = function (song) {
player.addSong(song);
};
$scope.playSong = function (song) {
player.emptyQueue()
.addSong(song)
.playFirstSong();
};
// Hyz: Replace
$scope.removeSong = function (item) { $scope.removeSong = function (item) {
$rootScope.removeSong(item, $scope.song); $rootScope.removeSong(item, $scope.song);
}; };

View file

@ -214,52 +214,7 @@ angular.module('JamStash')
$scope.scrollToTop = function () { $scope.scrollToTop = function () {
$('#left-component').stop().scrollTo('#MusicFolders', 400); $('#left-component').stop().scrollTo('#MusicFolders', 400);
}; };
$rootScope.selectAll = function (songs) {
angular.forEach(songs, function (item, key) {
$scope.selectedSongs.push(item);
item.selected = true;
});
};
$rootScope.selectNone = function (songs) {
angular.forEach(songs, function (item, key) {
$scope.selectedSongs = [];
item.selected = false;
});
};
$rootScope.playAll = function (songs) {
// TODO: Hyz: Replace
player.queue = [];
$rootScope.selectAll(songs);
$rootScope.addSongsToQueue();
var next = player.queue[0];
player.play(next);
};
$rootScope.playFrom = function (index, songs) {
// TODO: Hyz: Replace
var from = songs.slice(index,songs.length);
$scope.selectedSongs = [];
angular.forEach(from, function (item, key) {
$scope.selectedSongs.push(item);
item.selected = true;
});
if ($scope.selectedSongs.length > 0) {
player.queue = [];
$rootScope.addSongsToQueue();
var next = player.queue[0];
player.play(next);
}
};
$rootScope.addSongsToQueue = function () {
// TODO: Hyz: Replace
if ($scope.selectedSongs.length !== 0) {
angular.forEach($scope.selectedSongs, function (item, key) {
player.queue.push(item);
item.selected = false;
});
notifications.updateMessage($scope.selectedSongs.length + ' Song(s) Added to Queue', true);
$scope.selectedSongs.length = 0;
}
};
$rootScope.removeSong = function (item, songs) { $rootScope.removeSong = function (item, songs) {
// TODO: Hyz: Replace // TODO: Hyz: Replace
var index = songs.indexOf(item); var index = songs.indexOf(item);
@ -290,17 +245,6 @@ angular.module('JamStash')
} }
}); });
}; };
$scope.selectedSongs = [];
$scope.selectSong = function (data) {
var i = $scope.selectedSongs.indexOf(data);
if (i >= 0) {
$scope.selectedSongs.splice(i, 1);
data.selected = false;
} else {
$scope.selectedSongs.push(data);
data.selected = true;
}
};
/** /**
* Returns true if the target of this event is an input * Returns true if the target of this event is an input

View file

@ -0,0 +1,76 @@
/**
* jamstash.selectedsongs Module
*
* Manages the list of selected songs accross the app to avoid duplicating
* those functions both in Subsonic and Archive.org contexts
*/
angular.module('jamstash.selectedsongs', ['ngLodash'])
.service('SelectedSongs', SelectedSongs);
SelectedSongs.$inject = ['lodash'];
function SelectedSongs(_) {
'use strict';
var self = this;
_.extend(self, {
add : add,
addSongs: addSongs,
get : get,
remove : remove,
reset : reset,
toggle : toggle
});
var selectedSongs = [];
function add(song) {
song.selected = true;
selectedSongs.push(song);
selectedSongs = _.uniq(selectedSongs);
return self;
}
function addSongs(songs) {
_.forEach(songs, function (song) {
song.selected = true;
});
selectedSongs = _.union(selectedSongs, songs);
return self;
}
function get() {
return selectedSongs;
}
function remove(song) {
var removedSong = _(selectedSongs).remove(function (selectedSong) {
return selectedSong === song;
}).first();
_.set(removedSong, 'selected', false);
return self;
}
function toggle(song) {
if (song.selected) {
self.remove(song);
} else {
self.add(song);
}
return self;
}
function reset() {
_.forEach(selectedSongs, function (song) {
song.selected = false;
});
selectedSongs = [];
return self;
}
}

View file

@ -0,0 +1,176 @@
// jscs:disable validateQuoteMarks
describe("SelectedSongs service -", function () {
'use strict';
var SelectedSongs;
beforeEach(function () {
module('jamstash.selectedsongs');
inject(function (_SelectedSongs_) {
SelectedSongs = _SelectedSongs_;
});
});
describe("add() -", function () {
it("Given that there weren't any selected songs and given a song, when I add it to the selected songs, then the song's selected property will be true and the service itself will be returned to allow chaining", function () {
var song = { id: 237 };
var result = SelectedSongs.add(song);
expect(SelectedSongs.get()).toEqual([
{ id: 237, selected: true }
]);
expect(result).toBe(SelectedSongs);
});
it("Given that there were already 2 selected songs and given a different song, when I add it then the third song will be appended to the end of the selected songs", function () {
var firstSong = { id: 6162 },
secondSong = { id: 479 },
thirdSong = { id: 25 };
SelectedSongs.add(firstSong).add(secondSong);
SelectedSongs.add(thirdSong);
expect(SelectedSongs.get()).toEqual([
{ id: 6162, selected: true },
{ id: 479, selected: true },
{ id: 25, selected: true }
]);
});
it("Given that I already added 2 songs, when I try to add again the first song to the selected songs, then the new duplicate song won't be added and the selected songs will only contain the first 2 songs", function () {
var firstSong = { id: 8146 },
secondSong = { id: 4883 };
SelectedSongs.add(firstSong).add(secondSong);
SelectedSongs.add(firstSong);
expect(SelectedSongs.get()).toEqual([
{ id: 8146, selected: true },
{ id: 4883, selected: true }
]);
});
});
describe("addSongs() -", function () {
it("Given that there weren't any selected songs and given an array of 2 songs, when I add them, then the songs will be added to the selected songs, each song's selected property will be true and the service itself will be returned to allow chaining", function () {
var songs = [
{ id: 4236 },
{ id: 8816 }
];
var result = SelectedSongs.addSongs(songs);
expect(SelectedSongs.get()).toEqual([
{ id: 4236, selected: true },
{ id: 8816, selected: true }
]);
expect(result).toBe(SelectedSongs);
});
it("Given that there were already 2 selected songs and given 2 different songs, when I add them, then the songs will be appended to the end of the selected songs", function () {
var firstSongs = [
{ id: 9539 },
{ id: 8253 }
];
SelectedSongs.addSongs(firstSongs);
SelectedSongs.addSongs([
{ id: 9130 },
{ id: 6491 }
]);
expect(SelectedSongs.get()).toEqual([
{ id: 9539, selected: true },
{ id: 8253, selected: true },
{ id: 9130, selected: true },
{ id: 6491, selected: true }
]);
});
it("Given that there were already 2 selected songs and given the same 2 songs, when I try to add them again, then the two duplicate songs won't be added and the selected songs will only contain the first 2 songs", function () {
var firstSongs = [
{ id: 954 },
{ id: 3526 }
];
SelectedSongs.addSongs(firstSongs);
SelectedSongs.addSongs(firstSongs);
expect(SelectedSongs.get()).toEqual([
{ id: 954, selected: true },
{ id: 3526, selected: true }
]);
});
});
describe("remove() -", function () {
it("Given that there were 2 selected songs, when I remove the second song, then the remove song's selected property will be false, the selected songs will be an array containing the first song and the service itself will be returned to allow chaining", function () {
var firstSong = { id: 833 },
secondSong = { id: 8775 };
SelectedSongs.add(firstSong, secondSong);
var result = SelectedSongs.remove(secondSong);
expect(SelectedSongs.get()).toEqual([
{ id: 833, selected: true }
]);
expect(secondSong.selected).toBeFalsy();
expect(result).toBe(SelectedSongs);
});
it("Given that there was only 1 selected song, when I remove it, then the selected songs will be an empty array", function () {
var song = { id: 347 };
SelectedSongs.add(song);
SelectedSongs.remove(song);
expect(song.selected).toBeFalsy();
expect(SelectedSongs.get()).toEqual([]);
});
});
describe("toggle() - ", function () {
it("Given a selected song, when I toggle its selection, then the song will no longer be selected and the service itself will be returned to allow chaining", function () {
var song = { id: 1998, selected: true };
SelectedSongs.add(song);
var result = SelectedSongs.toggle(song);
expect(SelectedSongs.get()).toEqual([]);
expect(song.selected).toBeFalsy();
expect(result).toBe(SelectedSongs);
});
it("Given a song that isn't selected, when I toggle its selection, then the song will be selected", function () {
var song = { id: 6210 };
SelectedSongs.toggle(song);
expect(SelectedSongs.get()).toEqual([
{ id: 6210, selected: true }
]);
});
});
describe("reset() -", function () {
it("Given that there were 2 selected songs, when I reset the selected songs, then each song's selected property will be false, the selected songs will be an empty array and the service itself will be returned to allow chaining", function () {
var firstSong = { id: 8825 },
secondSong = { id: 6034 };
SelectedSongs.add(firstSong).add(secondSong);
var result = SelectedSongs.reset();
expect(SelectedSongs.get()).toEqual([]);
expect(firstSong.selected).toBeFalsy();
expect(secondSong.selected).toBeFalsy();
expect(result).toBe(SelectedSongs);
});
it("Given that there weren't any selected songs, when I reset thems, then the selected songs will be an empty array", function () {
SelectedSongs.reset();
expect(SelectedSongs.get()).toEqual([]);
});
});
});

View file

@ -1,4 +1,4 @@
<li class="row song" ng-repeat="o in song" ng-click="selectSong(o)" ng-dblclick="playFrom($index)" ng-class="{'selected': o.selected, 'playing': o.playing}"> <li class="row song" ng-repeat="o in song" ng-click="toggleSelection(o)" ng-dblclick="playFrom($index)" ng-class="{'selected': o.selected, 'playing': o.playing}">
<div class="itemactions"> <div class="itemactions">
<a class="add" href="" title="Add To Queue" ng-click="addSongToQueue(o)" stop-event="click"></a> <a class="add" href="" title="Add To Queue" ng-click="addSongToQueue(o)" stop-event="click"></a>
<!--<a class="remove" href="" title="Remove Song" ng-click="removeSongFromQueue(o)" stop-event="click"></a>--> <!--<a class="remove" href="" title="Remove Song" ng-click="removeSongFromQueue(o)" stop-event="click"></a>-->

View file

@ -82,13 +82,13 @@
</script> </script>
<!-- build:js({.,app}) scripts/vendor.min.js --> <!-- build:js({.,app}) scripts/vendor.min.js -->
<!-- bower:js --> <!-- bower:js -->
<script src="bower_components/jquery/jquery.js"></script> <script src="bower_components/jquery/dist/jquery.js"></script>
<script src="bower_components/angular/angular.js"></script> <script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-route/angular-route.js"></script> <script src="bower_components/angular-route/angular-route.js"></script>
<script src="bower_components/angular-sanitize/angular-sanitize.js"></script> <script src="bower_components/angular-sanitize/angular-sanitize.js"></script>
<script src="bower_components/angular-cookies/angular-cookies.js"></script> <script src="bower_components/angular-cookies/angular-cookies.js"></script>
<script src="bower_components/angular-resource/angular-resource.js"></script> <script src="bower_components/angular-resource/angular-resource.js"></script>
<script src="bower_components/jquery-ui/ui/jquery-ui.js"></script> <script src="bower_components/jquery-ui/jquery-ui.js"></script>
<script src="bower_components/jplayer/dist/jplayer/jquery.jplayer.js"></script> <script src="bower_components/jplayer/dist/jplayer/jquery.jplayer.js"></script>
<script src="bower_components/fancybox/source/jquery.fancybox.js"></script> <script src="bower_components/fancybox/source/jquery.fancybox.js"></script>
<script src="bower_components/notify.js/notify.js"></script> <script src="bower_components/notify.js/notify.js"></script>
@ -111,6 +111,7 @@
<script src="common/notification-service.js"></script> <script src="common/notification-service.js"></script>
<script src="common/persistence-service.js"></script> <script src="common/persistence-service.js"></script>
<script src="common/main-controller.js"></script> <script src="common/main-controller.js"></script>
<script src="common/selectedsongs-service.js"></script>
<script src="subsonic/subsonic.js"></script> <script src="subsonic/subsonic.js"></script>
<script src="subsonic/subsonic-service.js"></script> <script src="subsonic/subsonic-service.js"></script>
<script src="subsonic/breadcrumbs-directive/breadcrumbs-service.js"></script> <script src="subsonic/breadcrumbs-directive/breadcrumbs-service.js"></script>

View file

@ -7,7 +7,7 @@
<div id="SideQueue"> <div id="SideQueue">
<ul class="simplelist songlist noselect"> <ul class="simplelist songlist noselect">
<div ng-repeat="song in [player.queue] track by $index" class="songs" sortable> <div ng-repeat="song in [player.queue] track by $index" class="songs" sortable>
<li class="row song id{{o.id}}" ng-repeat="o in song" ng-click="selectSong(o)" ng-dblclick="playSong(o)" ng-class="{'selected': o.selected, 'playing': isPlayingSong(o)}"> <li class="row song id{{o.id}}" ng-repeat="o in song" ng-click="toggleSelection(o)" ng-dblclick="playSong(o)" ng-class="{'selected': o.selected, 'playing': isPlayingSong(o)}">
<div class="itemactions"> <div class="itemactions">
<a class="remove" href="" title="Remove Song" ng-click="removeSongFromQueue(o)" stop-event="click"></a> <a class="remove" href="" title="Remove Song" ng-click="removeSongFromQueue(o)" stop-event="click"></a>
<a href="" title="Star" ng-class="{'favorite': o.starred, 'rate': !o.starred}" ng-click="toggleStar(o)" stop-event="click"></a> <a href="" title="Star" ng-class="{'favorite': o.starred, 'rate': !o.starred}" ng-click="toggleStar(o)" stop-event="click"></a>

View file

@ -4,10 +4,10 @@
* Manages the playing queue. Gives access to the player service's queue-related functions, * Manages the playing queue. Gives access to the player service's queue-related functions,
* like adding, removing and shuffling the queue. * like adding, removing and shuffling the queue.
*/ */
angular.module('jamstash.queue.controller', ['jamstash.player.service', 'jamstash.settings.service']) angular.module('jamstash.queue.controller', ['jamstash.player.service', 'jamstash.settings.service', 'jamstash.selectedsongs'])
.controller('QueueController', ['$scope', 'globals', 'player', .controller('QueueController', ['$scope', 'globals', 'player', 'SelectedSongs',
function ($scope, globals, player) { function ($scope, globals, player, SelectedSongs) {
'use strict'; 'use strict';
$scope.settings = globals.settings; $scope.settings = globals.settings;
$scope.player = player; $scope.player = player;
@ -37,7 +37,7 @@ angular.module('jamstash.queue.controller', ['jamstash.player.service', 'jamstas
}; };
$scope.removeSelectedSongsFromQueue = function () { $scope.removeSelectedSongsFromQueue = function () {
player.removeSongs($scope.selectedSongs); player.removeSongs(SelectedSongs.get());
}; };
$scope.isPlayingSong = function (song) { $scope.isPlayingSong = function (song) {
@ -65,4 +65,8 @@ angular.module('jamstash.queue.controller', ['jamstash.player.service', 'jamstas
end = ui.item.index(); end = ui.item.index();
player.queue.splice(end, 0, player.queue.splice(start, 1)[0]); player.queue.splice(end, 0, player.queue.splice(start, 1)[0]);
}; };
$scope.toggleSelection = function (song) {
SelectedSongs.toggle(song);
};
}]); }]);

View file

@ -2,12 +2,14 @@
describe("Queue controller", function () { describe("Queue controller", function () {
'use strict'; 'use strict';
var player, scope; var player, scope, SelectedSongs;
var song; var song;
beforeEach(function () { beforeEach(function () {
module('jamstash.queue.controller'); module('jamstash.queue.controller');
SelectedSongs = jasmine.createSpyObj("SelectedSongs", ["get"]);
inject(function ($controller, $rootScope, globals, _player_) { inject(function ($controller, $rootScope, globals, _player_) {
scope = $rootScope.$new(); scope = $rootScope.$new();
player = _player_; player = _player_;
@ -15,7 +17,8 @@ describe("Queue controller", function () {
$controller('QueueController', { $controller('QueueController', {
$scope: scope, $scope: scope,
globals: globals, globals: globals,
player: player player: player,
SelectedSongs: SelectedSongs
}); });
}); });
song = { id: 7310 }; song = { id: 7310 };
@ -62,7 +65,7 @@ describe("Queue controller", function () {
it("When I remove all the selected songs from the queue, it calls removeSongs in the player service", function () { it("When I remove all the selected songs from the queue, it calls removeSongs in the player service", function () {
spyOn(player, "removeSongs"); spyOn(player, "removeSongs");
var secondSong = { id: 6791 }; var secondSong = { id: 6791 };
scope.selectedSongs = [song, secondSong]; SelectedSongs.get.and.returnValue([song, secondSong]);
scope.removeSelectedSongsFromQueue(); scope.removeSelectedSongsFromQueue();
expect(player.removeSongs).toHaveBeenCalledWith([song, secondSong]); expect(player.removeSongs).toHaveBeenCalledWith([song, secondSong]);
}); });

View file

@ -7,32 +7,31 @@
angular.module('jamstash.subsonic.service', [ angular.module('jamstash.subsonic.service', [
'ngLodash', 'ngLodash',
'jamstash.settings.service', 'jamstash.settings.service',
'jamstash.utils',
'jamstash.model' 'jamstash.model'
]) ])
.service('subsonic', subsonicService); .service('subsonic', subsonicService);
subsonicService.$inject = [ subsonicService.$inject = [
'$rootScope',
'$http', '$http',
'$q', '$q',
'lodash', 'lodash',
'globals', 'globals',
'utils',
'map' 'map'
]; ];
function subsonicService($rootScope, $http, $q, _, globals, utils, map) { function subsonicService(
$http,
$q,
_,
globals,
map
) {
'use strict'; 'use strict';
var showPlaylist = false;
var self = this; var self = this;
_.extend(self, { _.extend(self, {
// TODO: Hyz: Remove when refactored addToPlaylist : addToPlaylist,
showIndex: $rootScope.showIndex,
showPlaylist: showPlaylist,
deletePlaylist : deletePlaylist, deletePlaylist : deletePlaylist,
getAlbumByTag : getAlbumByTag, getAlbumByTag : getAlbumByTag,
getAlbumListBy : getAlbumListBy, getAlbumListBy : getAlbumListBy,
@ -53,7 +52,6 @@ function subsonicService($rootScope, $http, $q, _, globals, utils, map) {
savePlaylist : savePlaylist, savePlaylist : savePlaylist,
scrobble : scrobble, scrobble : scrobble,
search : search, search : search,
songsRemoveSelected : songsRemoveSelected,
subsonicRequest : subsonicRequest, subsonicRequest : subsonicRequest,
toggleStar : toggleStar toggleStar : toggleStar
}); });
@ -439,37 +437,35 @@ function subsonicService($rootScope, $http, $q, _, globals, utils, map) {
return promise; return promise;
} }
function deletePlaylist(id) { function deletePlaylist(playlistId) {
var promise = self.subsonicRequest('deletePlaylist.view', { var promise = self.subsonicRequest('deletePlaylist.view', {
params: { params: {
id: id id: playlistId
}
});
return promise;
}
function addToPlaylist(playlistId, songs) {
var songIds = _.pluck(songs, 'id');
var promise = self.subsonicRequest('updatePlaylist.view', {
params: {
playlistId : playlistId,
songIdToAdd: songIds
} }
}); });
return promise; return promise;
} }
function savePlaylist(playlistId, songs) { function savePlaylist(playlistId, songs) {
var params = { var songIds = _.pluck(songs, 'id');
var promise = self.subsonicRequest('createPlaylist.view', {
params: { params: {
playlistId: playlistId, playlistId: playlistId,
songId: [] songId : songIds
} }
};
for (var i = 0; i < songs.length; i++) {
params.params.songId.push(songs[i].id);
}
return self.subsonicRequest('createPlaylist.view', params);
}
//TODO: Hyz: move to controller
function songsRemoveSelected(songs) {
var deferred = $q.defer();
angular.forEach(songs, function (item, key) {
var index = content.song.indexOf(item)
content.song.splice(index, 1);
}); });
deferred.resolve(content); return promise;
return deferred.promise;
} }
function getGenres() { function getGenres() {

View file

@ -703,7 +703,7 @@ describe("Subsonic service -", function () {
it("Given that there was only 1 genre in my Madsonic library, when I get the genres, then a promise will be resolved with an array of 1 genre", function () { it("Given that there was only 1 genre in my Madsonic library, when I get the genres, then a promise will be resolved with an array of 1 genre", function () {
response["subsonic-response"].genres = { response["subsonic-response"].genres = {
genre: { genre: {
value: "periople", content: "periople",
songCount: 53, songCount: 53,
artistCount: 7, artistCount: 7,
albumCount: 74 albumCount: 74
@ -968,6 +968,24 @@ describe("Subsonic service -", function () {
}); });
}); });
it("addToPlaylist() - Given a playlist id and an array of songs, when I add those songs to the playlist, then an empty resolved promise will be returned", function () {
var url = 'http://demo.subsonic.com/rest/updatePlaylist.view?' +
'c=Jamstash&callback=JSON_CALLBACK&f=jsonp&p=enc:cGFzc3dvcmQ%3D' + '&playlistId=85' +
'&songIdToAdd=644&songIdToAdd=275&songIdToAdd=383' +
'&u=Hyzual&v=1.10.2';
mockBackend.expectJSONP(url).respond(JSON.stringify(response));
var songs = [
{ id: 644 },
{ id: 275 },
{ id: 383 }
];
var promise = subsonic.addToPlaylist(85, songs);
mockBackend.flush();
expect(promise).toBeResolved();
});
it("newPlaylist() - Given a name, when I create a new playlist, an empty resolved promise will be returned", function () { it("newPlaylist() - Given a name, when I create a new playlist, an empty resolved promise will be returned", function () {
var url = 'http://demo.subsonic.com/rest/createPlaylist.view?' + var url = 'http://demo.subsonic.com/rest/createPlaylist.view?' +
'c=Jamstash&callback=JSON_CALLBACK&f=jsonp' + '&name=Apolloship' + '&p=enc:cGFzc3dvcmQ%3D&u=Hyzual&v=1.10.2'; 'c=Jamstash&callback=JSON_CALLBACK&f=jsonp' + '&name=Apolloship' + '&p=enc:cGFzc3dvcmQ%3D&u=Hyzual&v=1.10.2';

View file

@ -22,7 +22,7 @@
<a href="" class="button" id="action_PlayAlbum" title="Play Album" ng-click="playAll()"><img src="images/play_gl_6x8.png"></a> <a href="" class="button" id="action_PlayAlbum" title="Play Album" ng-click="playAll()"><img src="images/play_gl_6x8.png"></a>
<a href="" class="button" id="action_SelectAll" title="Select All" ng-click="selectAll()">All</a> <a href="" class="button" id="action_SelectAll" title="Select All" ng-click="selectAll()">All</a>
<a href="" class="button" id="action_SelectNone" title="Select None" ng-click="selectNone()">None</a> <a href="" class="button" id="action_SelectNone" title="Select None" ng-click="selectNone()">None</a>
<a href="" class="button" id="action_AddToQueue" title="Add To Queue" ng-click="addSongsToQueue()">+ Queue</a> <a href="" class="button" id="action_AddToQueue" title="Add To Queue" ng-click="addSelectedSongsToQueue()">+ Queue</a>
<a href="" class="button" id="action_AddToPlaylist" title="Add Selected To Playlist" ng-click="loadPlaylistsForMenu()">+ Playlist</a> <a href="" class="button" id="action_AddToPlaylist" title="Add Selected To Playlist" ng-click="loadPlaylistsForMenu()">+ Playlist</a>
<div id="submenu_AddToPlaylist" class="submenu shadow" style="display: none;"> <div id="submenu_AddToPlaylist" class="submenu shadow" style="display: none;">
<a href="" ng-repeat="o in playlistMenu" ng-click="addToPlaylist(o.id)">{{o.name}}</a> <a href="" ng-repeat="o in playlistMenu" ng-click="addToPlaylist(o.id)">{{o.name}}</a>

View file

@ -10,7 +10,8 @@ angular.module('jamstash.subsonic.controller', [
'jamstash.player.service', 'jamstash.player.service',
'jamstash.persistence', 'jamstash.persistence',
'jamstash.breadcrumbs.directive', 'jamstash.breadcrumbs.directive',
'jamstash.breadcrumbs.service' 'jamstash.breadcrumbs.service',
'jamstash.selectedsongs'
]) ])
.controller('SubsonicController', [ .controller('SubsonicController', [
@ -27,6 +28,7 @@ angular.module('jamstash.subsonic.controller', [
'player', 'player',
'persistence', 'persistence',
'breadcrumbs', 'breadcrumbs',
'SelectedSongs',
function ( function (
$scope, $scope,
$rootScope, $rootScope,
@ -40,7 +42,8 @@ angular.module('jamstash.subsonic.controller', [
notifications, notifications,
player, player,
persistence, persistence,
breadcrumbs breadcrumbs,
SelectedSongs
) { ) {
'use strict'; 'use strict';
@ -74,17 +77,57 @@ angular.module('jamstash.subsonic.controller', [
}, },
itemType : 'ss', itemType : 'ss',
playlistMenu : [], playlistMenu : [],
selectedSongs : SelectedSongs.get(),
SelectedAlbumSort: globals.settings.DefaultAlbumSort, SelectedAlbumSort: globals.settings.DefaultAlbumSort,
Server : globals.settings.Server, Server : globals.settings.Server,
settings: globals.settings settings : globals.settings,
addSelectedSongsToQueue: addSelectedSongsToQueue,
addSongToQueue : addSongToQueue,
playAll : playAll,
playFrom : playFrom,
playSong : playSong,
selectAll : selectAll,
selectNone : SelectedSongs.reset,
toggleSelection : SelectedSongs.toggle
}); });
$rootScope.showIndex = subsonic.showIndex; function selectAll() {
$scope.$watch("showIndex", function (newValue, oldValue) { SelectedSongs.addSongs($scope.song);
if (newValue !== oldValue) {
subsonic.showIndex = $rootScope.showIndex;
} }
});
function addSelectedSongsToQueue() {
if (SelectedSongs.get().length === 0) {
return;
}
player.addSongs(SelectedSongs.get());
notifications.updateMessage(SelectedSongs.get().length + ' Song(s) Added to Queue', true);
SelectedSongs.reset();
}
function playAll() {
player.emptyQueue()
.addSongs($scope.song)
.playFirstSong();
}
function playFrom(index) {
var songsToPlay = $scope.song.slice(index, $scope.song.length);
player.emptyQueue()
.addSongs(songsToPlay)
.playFirstSong();
}
function addSongToQueue(song) {
player.addSong(song);
}
function playSong(song) {
player.emptyQueue()
.addSong(song)
.playFirstSong();
}
$rootScope.showIndex = false;
$scope.toggleIndex = function () { $scope.toggleIndex = function () {
if ($rootScope.showIndex) { if ($rootScope.showIndex) {
$rootScope.showIndex = false; $rootScope.showIndex = false;
@ -95,12 +138,7 @@ angular.module('jamstash.subsonic.controller', [
} }
$scope.saveDefaultSection('index'); $scope.saveDefaultSection('index');
}; };
$scope.showPlaylist = subsonic.showPlaylist; $scope.showPlaylist = false;
$scope.$watch("showPlaylist", function (newValue, oldValue) {
if (newValue !== oldValue) {
subsonic.showPlaylist = $scope.showPlaylist;
}
});
$scope.togglePlaylist = function () { $scope.togglePlaylist = function () {
if ($scope.showPlaylist) { if ($scope.showPlaylist) {
$scope.showPlaylist = false; $scope.showPlaylist = false;
@ -111,12 +149,7 @@ angular.module('jamstash.subsonic.controller', [
} }
$scope.saveDefaultSection('playlist'); $scope.saveDefaultSection('playlist');
}; };
$scope.showPodcast = subsonic.showPodcast; $scope.showPodcast = false;
$scope.$watch("showPodcast", function (newValue, oldValue) {
if (newValue !== oldValue) {
subsonic.showPodcast = $scope.showPodcast;
}
});
$scope.togglePodcast = function () { $scope.togglePodcast = function () {
if ($scope.showPodcast) { if ($scope.showPodcast) {
$scope.showPodcast = false; $scope.showPodcast = false;
@ -226,30 +259,14 @@ angular.module('jamstash.subsonic.controller', [
} }
}); });
}; };
$scope.selectAll = function () {
$rootScope.selectAll($scope.song);
};
$scope.selectNone = function () {
$rootScope.selectNone($scope.song);
};
// TODO: Hyz: Replace
$scope.playAll = function () {
$rootScope.playAll($scope.song);
};
// TODO: Hyz: Replace
$scope.playFrom = function (index) {
$rootScope.playFrom(index, $scope.song);
};
// TODO: Hyz: Replace // TODO: Hyz: Replace
$scope.removeSong = function (item) { $scope.removeSong = function (item) {
$rootScope.removeSong(item, $scope.song); $rootScope.removeSong(item, $scope.song);
}; };
// TODO: Hyz: Replace
$scope.songsRemoveSelected = function () { $scope.songsRemoveSelected = function () {
subsonic.songsRemoveSelected($scope.selectedSongs).then(function (data) { $scope.song = _.difference($scope.song, SelectedSongs.get());
$scope.album = data.album;
$scope.song = data.song;
});
}; };
$scope.getArtists = function (folder) { $scope.getArtists = function (folder) {
@ -563,7 +580,10 @@ angular.module('jamstash.subsonic.controller', [
}; };
$scope.deletePlaylist = function () { $scope.deletePlaylist = function () {
if ($scope.selectedPlaylist !== null) { if (! $scope.selectedPlaylist) {
notifications.updateMessage('Please select a playlist to delete.');
return;
}
var ok = $window.confirm('Are you sure you want to delete the selected playlist?'); var ok = $window.confirm('Are you sure you want to delete the selected playlist?');
if (ok) { if (ok) {
var promise = subsonic.deletePlaylist($scope.selectedPlaylist); var promise = subsonic.deletePlaylist($scope.selectedPlaylist);
@ -571,21 +591,30 @@ angular.module('jamstash.subsonic.controller', [
$scope.getPlaylists(); $scope.getPlaylists();
}); });
} }
} else { };
notifications.updateMessage('Please select a playlist to delete.');
$scope.addToPlaylist = function (playlistId) {
if (SelectedSongs.get().length === 0) {
notifications.updateMessage('Please select a song to add to that playlist.');
return;
} }
var promise = subsonic.addToPlaylist(playlistId, SelectedSongs.get());
$scope.handleErrors(promise).then(function () {
SelectedSongs.reset();
notifications.updateMessage('Playlist Updated!', true);
});
}; };
$scope.savePlaylist = function () { $scope.savePlaylist = function () {
if ($scope.selectedPlaylist !== null) { if (! $scope.selectedPlaylist) {
notifications.updateMessage('Please select a playlist to save.');
return;
}
var promise = subsonic.savePlaylist($scope.selectedPlaylist, $scope.song); var promise = subsonic.savePlaylist($scope.selectedPlaylist, $scope.song);
$scope.handleErrors(promise).then(function () { $scope.handleErrors(promise).then(function () {
$scope.getPlaylist('display', $scope.selectedPlaylist); $scope.getPlaylist('display', $scope.selectedPlaylist);
notifications.updateMessage('Playlist Updated!', true); notifications.updateMessage('Playlist Updated!', true);
}); });
} else {
notifications.updateMessage('Please select a playlist to save.');
}
}; };
$scope.loadPlaylistsForMenu = function () { $scope.loadPlaylistsForMenu = function () {
@ -599,31 +628,6 @@ angular.module('jamstash.subsonic.controller', [
}); });
}; };
$scope.addToPlaylist = function (id) {
var songs = [];
if ($scope.selectedSongs.length !== 0) {
angular.forEach($scope.selectedSongs, function (item) {
songs.push(item.id);
});
var runningVersion = utils.parseVersionString(globals.settings.ApiVersion);
var minimumVersion = utils.parseVersionString('1.8.0');
if (utils.checkVersion(runningVersion, minimumVersion)) { // is 1.8.0 or newer
$.ajax({
type: 'GET',
url: globals.BaseURL() + '/updatePlaylist.view?' + globals.BaseParams(),
dataType: globals.settings.Protocol,
timeout: globals.settings.Timeout,
data: { playlistId: id, songIdToAdd: songs },
success: function () {
$scope.selectedSongs.length = 0;
notifications.updateMessage('Playlist Updated!', true);
},
traditional: true // Fixes POST with an array in JQuery 1.4
});
}
}
};
$scope.getGenres = function () { $scope.getGenres = function () {
var promise = subsonic.getGenres(); var promise = subsonic.getGenres();
$scope.handleErrors(promise).then(function (genres) { $scope.handleErrors(promise).then(function (genres) {
@ -708,12 +712,6 @@ angular.module('jamstash.subsonic.controller', [
end = ui.item.index(); end = ui.item.index();
$scope.song.splice(end, 0, $scope.song.splice(start, 1)[0]); $scope.song.splice(end, 0, $scope.song.splice(start, 1)[0]);
}; };
$scope.playSong = function (song) {
player.emptyQueue().addSong(song).playFirstSong();
};
$scope.addSongToQueue = function (song) {
player.addSong(song);
};
/* Launch on Startup */ /* Launch on Startup */
$scope.searching = { $scope.searching = {

View file

@ -3,7 +3,7 @@ describe("Subsonic controller", function () {
'use strict'; 'use strict';
var scope, $rootScope, $controller, $window, var scope, $rootScope, $controller, $window,
_, subsonic, notifications, player, utils, persistence, breadcrumbs, mockGlobals, controllerParams, deferred; _, subsonic, notifications, player, utils, persistence, breadcrumbs, mockGlobals, SelectedSongs, controllerParams, deferred;
beforeEach(function () { beforeEach(function () {
jasmine.addCustomEqualityTester(angular.equals); jasmine.addCustomEqualityTester(angular.equals);
@ -47,9 +47,10 @@ describe("Subsonic controller", function () {
// Mock the subsonic service // Mock the subsonic service
subsonic = jasmine.createSpyObj("subsonic", [ subsonic = jasmine.createSpyObj("subsonic", [
"addToPlaylist",
"deletePlaylist", "deletePlaylist",
"getAlbums",
"getAlbumListBy", "getAlbumListBy",
"getAlbums",
"getArtists", "getArtists",
"getGenres", "getGenres",
"getMusicFolders", "getMusicFolders",
@ -81,6 +82,16 @@ describe("Subsonic controller", function () {
_.forIn(player, _.method('and.returnValue', player)); _.forIn(player, _.method('and.returnValue', player));
player.queue = []; player.queue = [];
SelectedSongs = jasmine.createSpyObj("SelectedSongs", [
"add",
"addSongs",
"get",
"remove",
"reset"
]);
_.forIn(SelectedSongs, _.method('and.returnValue', SelectedSongs));
SelectedSongs.get.and.returnValue([]);
$controller = _$controller_; $controller = _$controller_;
controllerParams = { controllerParams = {
$scope: scope, $scope: scope,
@ -94,7 +105,8 @@ describe("Subsonic controller", function () {
notifications: notifications, notifications: notifications,
player: player, player: player,
persistence: persistence, persistence: persistence,
breadcrumbs: breadcrumbs breadcrumbs: breadcrumbs,
SelectedSongs: SelectedSongs
}; };
}); });
}); });
@ -106,7 +118,99 @@ describe("Subsonic controller", function () {
scope.$apply(); scope.$apply();
}); });
describe("getSongs -", function () { it("selectAll() - Given 2 songs in the scope, when I select all songs, then the SelectedSongs service will be called", function () {
scope.song = [
{ id: 5612 },
{ id: 8206 }
];
scope.selectAll();
expect(SelectedSongs.addSongs).toHaveBeenCalledWith([
{ id: 5612 },
{ id: 8206 }
]);
});
describe("addSelectedSongsToQueue() -", function () {
it("Given 2 selected songs, when I add them to the queue, then the selected songs will be added to the player's queue, the selection will be reset and a notification will be displayed", function () {
SelectedSongs.get.and.returnValue([
{ id: 5485 },
{ id: 71 }
]);
scope.addSelectedSongsToQueue();
expect(player.addSongs).toHaveBeenCalledWith([
{ id: 5485 },
{ id: 71 }
]);
expect(notifications.updateMessage).toHaveBeenCalledWith('2 Song(s) Added to Queue', true);
expect(SelectedSongs.reset).toHaveBeenCalled();
});
it("Given that there were no selected songs, when I try to add them to the queue, then nothing will happen", function () {
SelectedSongs.get.and.returnValue([]);
scope.addSelectedSongsToQueue();
expect(player.addSongs).not.toHaveBeenCalled();
expect(notifications.updateMessage).not.toHaveBeenCalled();
});
});
it("playAll() - Given 2 songs in the scope, when I play all songs, then the player's queue will be emptied, 2 songs will be added to the queue and the first song of the queue will be played", function () {
scope.song = [
{ id: 9397 },
{ id: 2586 }
];
scope.playAll();
expect(player.emptyQueue).toHaveBeenCalled();
expect(player.addSongs).toHaveBeenCalledWith([
{ id: 9397 },
{ id: 2586 }
]);
expect(player.playFirstSong).toHaveBeenCalled();
});
it("playFrom() - Given 3 songs in the scope, when I play all songs from the second, then the player's queue will be emptied, the last 2 songs will be added to the queue and the first song of the queue will be played", function () {
scope.song = [
{ id: 6548 },
{ id: 5069 },
{ id: 4325 }
];
scope.playFrom(1);
expect(player.emptyQueue).toHaveBeenCalled();
expect(player.addSongs).toHaveBeenCalledWith([
{ id: 5069 },
{ id: 4325 }
]);
expect(player.playFirstSong).toHaveBeenCalled();
});
it("addSongToQueue() - Given a song, when I add it to the queue, then the song will be added to the player's queue", function () {
var fakeSong = { id: 4308 };
scope.addSongToQueue(fakeSong);
expect(player.addSong).toHaveBeenCalledWith({ id: 4308 });
});
it("playSong() - Given a song, when I play it, then the player service's queue will be emptied, the song will be added to the queue and played", function () {
var fakeSong = { id: 3572 };
scope.playSong(fakeSong);
expect(player.emptyQueue).toHaveBeenCalled();
expect(player.addSong).toHaveBeenCalledWith({ id: 3572 });
expect(player.playFirstSong).toHaveBeenCalled();
});
describe("getSongs() -", function () {
beforeEach(function () { beforeEach(function () {
subsonic.getSongs.and.returnValue(deferred.promise); subsonic.getSongs.and.returnValue(deferred.promise);
subsonic.recursiveGetSongs.and.returnValue(deferred.promise); subsonic.recursiveGetSongs.and.returnValue(deferred.promise);
@ -521,16 +625,6 @@ describe("Subsonic controller", function () {
}); });
}); });
it("Given a song, when I call playSong, then the player service's queue will be emptied, the song will be added to the queue and played", function () {
var fakeSong = { id: 3572 };
scope.playSong(fakeSong);
expect(player.emptyQueue).toHaveBeenCalled();
expect(player.addSong).toHaveBeenCalledWith({ id: 3572 });
expect(player.playFirstSong).toHaveBeenCalled();
});
// TODO: Hyz: all starred // TODO: Hyz: all starred
describe("", function () { describe("", function () {
@ -689,12 +783,12 @@ describe("Subsonic controller", function () {
expect(scope.getPlaylists).toHaveBeenCalled(); expect(scope.getPlaylists).toHaveBeenCalled();
}); });
describe("When I load the playlists,", function () { describe("getPlaylists() -", function () {
beforeEach(function () { beforeEach(function () {
subsonic.getPlaylists.and.returnValue(deferred.promise); subsonic.getPlaylists.and.returnValue(deferred.promise);
}); });
it("Given that there are playlists in the library, it loads the playlists and publishes them to the scope", function () { it("Given that there are playlists in the library, when I load the playlists, it loads the playlists and publishes them to the scope", function () {
scope.getPlaylists(); scope.getPlaylists();
deferred.resolve({ deferred.resolve({
playlists: [ playlists: [
@ -715,7 +809,7 @@ describe("Subsonic controller", function () {
]); ]);
}); });
it("Given that there aren't any playlists in the library, it publishes an empty array to the scope and does not notify the user with the error message", function () { it("Given that there aren't any playlists in the library, when I load the playlists, it publishes an empty array to the scope and does not notify the user with the error message", function () {
scope.getPlaylists(); scope.getPlaylists();
deferred.reject({ reason: 'No playlist found on the Subsonic server.' }); deferred.reject({ reason: 'No playlist found on the Subsonic server.' });
scope.$apply(); scope.$apply();
@ -766,6 +860,7 @@ describe("Subsonic controller", function () {
}); });
}); });
describe("newPlaylist() -", function () {
it("When I create a playlist, then it will ask for a name, use subsonic-service and reload the playlists", function () { it("When I create a playlist, then it will ask for a name, use subsonic-service and reload the playlists", function () {
$window.prompt.and.returnValue('declassicize'); $window.prompt.and.returnValue('declassicize');
subsonic.newPlaylist.and.returnValue(deferred.promise); subsonic.newPlaylist.and.returnValue(deferred.promise);
@ -787,7 +882,9 @@ describe("Subsonic controller", function () {
expect(subsonic.newPlaylist).not.toHaveBeenCalled(); expect(subsonic.newPlaylist).not.toHaveBeenCalled();
}); });
});
describe("deletePlaylist() -", function () {
it("Given a selected playlist, when I delete that playlist, it will ask for confirmation, use subsonic-service and reload the playlists", function () { it("Given a selected playlist, when I delete that playlist, it will ask for confirmation, use subsonic-service and reload the playlists", function () {
$window.confirm.and.returnValue(true); $window.confirm.and.returnValue(true);
subsonic.deletePlaylist.and.returnValue(deferred.promise); subsonic.deletePlaylist.and.returnValue(deferred.promise);
@ -811,7 +908,41 @@ describe("Subsonic controller", function () {
expect(notifications.updateMessage).toHaveBeenCalledWith('Please select a playlist to delete.'); expect(notifications.updateMessage).toHaveBeenCalledWith('Please select a playlist to delete.');
expect(subsonic.deletePlaylist).not.toHaveBeenCalled(); expect(subsonic.deletePlaylist).not.toHaveBeenCalled();
}); });
});
describe("addToPlaylist() -", function () {
it("Given selected songs and a playlist id, when I add songs to that playlist, then the subsonic-service will be called, the selected songs will be unselected and a notification message will be displayed", function () {
subsonic.addToPlaylist.and.returnValue(deferred.promise);
SelectedSongs.get.and.returnValue([
{ id: 1673 },
{ id: 9126 },
{ id: 6275 }
]);
scope.addToPlaylist(298);
deferred.resolve();
scope.$apply();
expect(subsonic.addToPlaylist).toHaveBeenCalledWith(298, [
{ id: 1673 },
{ id: 9126 },
{ id: 6275 }
]);
expect(SelectedSongs.reset).toHaveBeenCalled();
expect(notifications.updateMessage).toHaveBeenCalledWith('Playlist Updated!', true);
});
it("Given no selected songs and a playlist id, when I try to add songs to to that playlist, then an error notification will be displayed", function () {
scope.selectedSongs = [];
scope.addToPlaylist(70);
expect(notifications.updateMessage).toHaveBeenCalledWith('Please select a song to add to that playlist.');
expect(subsonic.addToPlaylist).not.toHaveBeenCalled();
});
});
describe("savePlaylist() -", function () {
it("Given a selected playlist, when I save that playlist, the displayed songs will be sent to subsonic-service, the playlist will be displayed again and a notification message will be displayed", function () { it("Given a selected playlist, when I save that playlist, the displayed songs will be sent to subsonic-service, the playlist will be displayed again and a notification message will be displayed", function () {
subsonic.savePlaylist.and.returnValue(deferred.promise); subsonic.savePlaylist.and.returnValue(deferred.promise);
spyOn(scope, 'getPlaylist'); spyOn(scope, 'getPlaylist');
@ -843,6 +974,7 @@ describe("Subsonic controller", function () {
expect(notifications.updateMessage).toHaveBeenCalledWith('Please select a playlist to save.'); expect(notifications.updateMessage).toHaveBeenCalledWith('Please select a playlist to save.');
expect(subsonic.savePlaylist).not.toHaveBeenCalled(); expect(subsonic.savePlaylist).not.toHaveBeenCalled();
}); });
});
describe("getPodcasts() -", function () { describe("getPodcasts() -", function () {
beforeEach(function () { beforeEach(function () {

View file

@ -19,13 +19,13 @@ module.exports = function (config) {
// list of files / patterns to load in the browser // list of files / patterns to load in the browser
files: [ files: [
// bower: // bower:
'bower_components/jquery/jquery.js', 'bower_components/jquery/dist/jquery.js',
'bower_components/angular/angular.js', 'bower_components/angular/angular.js',
'bower_components/angular-route/angular-route.js', 'bower_components/angular-route/angular-route.js',
'bower_components/angular-sanitize/angular-sanitize.js', 'bower_components/angular-sanitize/angular-sanitize.js',
'bower_components/angular-cookies/angular-cookies.js', 'bower_components/angular-cookies/angular-cookies.js',
'bower_components/angular-resource/angular-resource.js', 'bower_components/angular-resource/angular-resource.js',
'bower_components/jquery-ui/ui/jquery-ui.js', 'bower_components/jquery-ui/jquery-ui.js',
'bower_components/jplayer/dist/jplayer/jquery.jplayer.js', 'bower_components/jplayer/dist/jplayer/jquery.jplayer.js',
'bower_components/fancybox/source/jquery.fancybox.js', 'bower_components/fancybox/source/jquery.fancybox.js',
'bower_components/notify.js/notify.js', 'bower_components/notify.js/notify.js',