Merge pull request #246 from pR0Ps/bugfix/update-and-ui-fixes
Mouse and media key fixes
This commit is contained in:
commit
2afc90ef06
17 changed files with 2683 additions and 1978 deletions
|
@ -25,6 +25,9 @@ module.exports = function (grunt) {
|
||||||
dist: 'dist'
|
dist: 'dist'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Serve static files
|
||||||
|
var serveStatic = require('serve-static');
|
||||||
|
|
||||||
// Paths to ssh config & private key
|
// Paths to ssh config & private key
|
||||||
var sshConfigFile = '.ssh/testServer.json';
|
var sshConfigFile = '.ssh/testServer.json';
|
||||||
var sshKeyFile = '.ssh/test-server-key/';
|
var sshKeyFile = '.ssh/test-server-key/';
|
||||||
|
@ -87,9 +90,9 @@ module.exports = function (grunt) {
|
||||||
return [
|
return [
|
||||||
connect().use(
|
connect().use(
|
||||||
'/bower_components',
|
'/bower_components',
|
||||||
connect.static('./bower_components')
|
serveStatic('./bower_components')
|
||||||
),
|
),
|
||||||
connect.static(appConfig.app)
|
serveStatic(appConfig.app)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ angular.module('JamStash', [
|
||||||
'ngRoute',
|
'ngRoute',
|
||||||
'ngSanitize',
|
'ngSanitize',
|
||||||
'ngLodash',
|
'ngLodash',
|
||||||
'ui.keypress',
|
|
||||||
'jamstash.subsonic.controller',
|
'jamstash.subsonic.controller',
|
||||||
'jamstash.archive.controller',
|
'jamstash.archive.controller',
|
||||||
'jamstash.player.controller',
|
'jamstash.player.controller',
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('JamStash').directive('sortable', function () {
|
angular.module('JamStash')
|
||||||
|
.directive('sortable', function () {
|
||||||
return {
|
return {
|
||||||
link: function (scope, elm, attrs) {
|
link: function (scope, elm, attrs) {
|
||||||
elm.sortable({
|
elm.sortable({
|
||||||
|
@ -46,30 +47,6 @@ angular.module('JamStash').directive('sortable', function () {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}])
|
}])
|
||||||
.directive('stopEvent', function () {
|
|
||||||
return {
|
|
||||||
restrict: 'A',
|
|
||||||
link: function (scope, element, attr) {
|
|
||||||
element.bind(attr.stopEvent, function (e) {
|
|
||||||
e.stopPropagation();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.directive('ngEnter', function () {
|
|
||||||
return {
|
|
||||||
scope: { onEnter: '&' },
|
|
||||||
link: function (scope, element) {
|
|
||||||
console.log(scope);
|
|
||||||
element.bind("keydown keypress", function (event) {
|
|
||||||
if (event.which === 13) {
|
|
||||||
scope.onEnter();
|
|
||||||
scope.$apply();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.directive('ngDownload', ['$compile', function ($compile) {
|
.directive('ngDownload', ['$compile', function ($compile) {
|
||||||
return {
|
return {
|
||||||
restrict: 'E',
|
restrict: 'E',
|
||||||
|
@ -92,16 +69,20 @@ angular.module('JamStash').directive('sortable', function () {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}])
|
}])
|
||||||
.directive('stopEvent', function () {
|
.directive('stopEvent', ['lodash', function (_) {
|
||||||
return {
|
return {
|
||||||
restrict: 'A',
|
restrict: 'A',
|
||||||
link: function (scope, element, attr) {
|
link: function (scope, element, attr) {
|
||||||
element.bind(attr.stopEvent, function (e) {
|
if (attr && attr.stopEvent) {
|
||||||
e.stopPropagation();
|
_.forEach(attr.stopEvent.split(','), function (eventName) {
|
||||||
});
|
element.bind(eventName, function (e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})
|
}])
|
||||||
.directive('ngEnter', function () {
|
.directive('ngEnter', function () {
|
||||||
return function (scope, element, attrs) {
|
return function (scope, element, attrs) {
|
||||||
element.bind("keydown keypress", function (event) {
|
element.bind("keydown keypress", function (event) {
|
||||||
|
@ -114,22 +95,4 @@ angular.module('JamStash').directive('sortable', function () {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
})
|
|
||||||
.directive("ngMsgs", function() {
|
|
||||||
/* Not Using */
|
|
||||||
return {
|
|
||||||
restrict: 'E',
|
|
||||||
transclude : false,
|
|
||||||
scope: {
|
|
||||||
msgs: "="
|
|
||||||
},
|
|
||||||
template: '<span id="msg_{{$index}}" class="message">{{ item }}</span>',
|
|
||||||
link: function (scope, elm, attrs) {
|
|
||||||
scope.$watch(scope.Messages, function () {
|
|
||||||
var content = $compile((template)(scope));
|
|
||||||
elm.append(content);
|
|
||||||
$(elm).parent().fadeIn();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
});
|
|
@ -154,9 +154,9 @@ angular.module('JamStash')
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$(document).on("click", ".message", function(){
|
$(document).on("click", ".message", function(){
|
||||||
$(this).remove();
|
$(this).remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Global Functions
|
// Global Functions
|
||||||
window.onbeforeunload = function () {
|
window.onbeforeunload = function () {
|
||||||
|
@ -171,39 +171,51 @@ angular.module('JamStash')
|
||||||
$(this).fadeOut(function () { $(this).remove(); });
|
$(this).fadeOut(function () { $(this).remove(); });
|
||||||
return false;
|
return false;
|
||||||
})
|
})
|
||||||
$document.keydown(function (e) {
|
|
||||||
$scope.scrollToIndex(e);
|
// Shortcut processing
|
||||||
|
$(document).keydown(function (e) {
|
||||||
|
$scope.processKeyEvent(e);
|
||||||
});
|
});
|
||||||
$scope.scrollToIndex = function (e) {
|
$scope.scrollToIndex = function (e) {
|
||||||
var source = e.target.id;
|
$scope.processKeyEvent(e);
|
||||||
if (e.target.tagName.toUpperCase() != 'INPUT') {
|
return true;
|
||||||
var unicode = e.charCode ? e.charCode : e.keyCode;
|
};
|
||||||
if (globals.settings.Debug) { console.log('Keycode Triggered: ' + unicode); }
|
$scope.processKeyEvent = function (e) {
|
||||||
if (unicode == 49) { // 1
|
if (e.isDefaultPrevented() ||
|
||||||
$('#action_Queue').click();
|
e.repeat ||
|
||||||
} else if (unicode == 50) {
|
e.altKey || e.metaKey || e.ctrlKey ||
|
||||||
$('#action_Library').click();
|
(e.target && _.contains(['input', 'textarea', 'select'], e.target.tagName.toLowerCase()))) {
|
||||||
} else if (unicode == 51) {
|
return;
|
||||||
$('#action_Archive').click();
|
}
|
||||||
} else if (unicode == 52) {
|
|
||||||
$('#action_Settings').click();
|
var key = e.key;
|
||||||
} else if (unicode == 53) {
|
if (globals.settings.Debug) { console.log('Key pressed: ' + key); }
|
||||||
} else if (unicode == 54) { // 6
|
if (key == "Esc" || key == "Escape") {
|
||||||
|
$rootScope.hideQueue();
|
||||||
|
} else if (key == " " || key == "Space") {
|
||||||
|
player.togglePause();
|
||||||
|
} else if (key == "ArrowLeft" || key == "Left") {
|
||||||
|
player.previousTrack();
|
||||||
|
} else if (key == "ArrowRight" || key == "Right") {
|
||||||
|
player.nextTrack();
|
||||||
|
} else if (key == "-" || key == "_") {
|
||||||
|
persistence.saveVolume(player.turnVolumeDown());
|
||||||
|
} else if (key == "=" || key == "+") {
|
||||||
|
persistence.saveVolume(player.turnVolumeUp());
|
||||||
|
} else if (/^[a-z]$/i.test(key) && $('#tabLibrary').is(':visible')) {
|
||||||
|
if (/^[x-z]$/i.test(key)) {
|
||||||
|
key = 'x-z';
|
||||||
}
|
}
|
||||||
if (unicode >= 65 && unicode <= 90 && $('#tabLibrary').is(':visible')) { // a-z
|
var el = '#' + key.toUpperCase();
|
||||||
var key = utils.findKeyForCode(unicode);
|
if ($(el).length > 0) {
|
||||||
if (key == 'x' || key == 'y' || key == 'z') {
|
$('#left-component').stop().scrollTo(el, 400);
|
||||||
key = 'x-z';
|
|
||||||
}
|
|
||||||
var el = '#' + key.toUpperCase();
|
|
||||||
if ($(el).length > 0) {
|
|
||||||
$('#left-component').stop().scrollTo(el, 400);
|
|
||||||
}
|
|
||||||
} else if (unicode == 36 && $('#tabLibrary').is(':visible')) { // home
|
|
||||||
$('#left-component').stop().scrollTo('#MusicFolders', 400);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
else{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$scope.$apply();
|
||||||
|
e.preventDefault();
|
||||||
};
|
};
|
||||||
$scope.scrollToIndexName = function (index) {
|
$scope.scrollToIndexName = function (index) {
|
||||||
var el = '#' + index;
|
var el = '#' + index;
|
||||||
|
@ -246,82 +258,6 @@ angular.module('JamStash')
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the target of this event is an input
|
|
||||||
* @param {jQuery event} event
|
|
||||||
* @return {Boolean}
|
|
||||||
*/
|
|
||||||
function isTargetInput (event) {
|
|
||||||
return (event && event.target.tagName === "INPUT");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* We define player-related methods here instead of in player controller
|
|
||||||
in order to bind keypresses to <body> and have global shortcuts.
|
|
||||||
We also check the event so we don't do anything if it's on an input */
|
|
||||||
$scope.togglePause = function (event) {
|
|
||||||
if(!isTargetInput(event)) {
|
|
||||||
if(globals.settings.Jukebox) {
|
|
||||||
$scope.sendToJukebox('stop');
|
|
||||||
} else {
|
|
||||||
player.togglePause();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.turnVolumeUp = function (event) {
|
|
||||||
if(!isTargetInput(event)) {
|
|
||||||
var volume = player.turnVolumeUp();
|
|
||||||
persistence.saveVolume(volume);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.turnVolumeDown = function (event) {
|
|
||||||
if(!isTargetInput(event)) {
|
|
||||||
var volume = player.turnVolumeDown();
|
|
||||||
persistence.saveVolume(volume);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.nextTrack = function (event) {
|
|
||||||
if(!isTargetInput(event)) {
|
|
||||||
player.nextTrack();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
$scope.previousTrack = function (event) {
|
|
||||||
if(!isTargetInput(event)) {
|
|
||||||
player.previousTrack();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$rootScope.addToJukebox = function (id) {
|
|
||||||
if (globals.settings.Debug) { console.log("LOAD JUKEBOX"); }
|
|
||||||
$.ajax({
|
|
||||||
url: globals.BaseURL() + '/jukeboxControl.view?' + globals.BaseParams() + '&action=set&id=' + id,
|
|
||||||
method: 'GET',
|
|
||||||
dataType: globals.settings.Protocol,
|
|
||||||
timeout: globals.settings.Timeout,
|
|
||||||
success: function (data) {
|
|
||||||
/*
|
|
||||||
if (data["subsonic-response"].podcasts.channel !== undefined) {
|
|
||||||
}
|
|
||||||
deferred.resolve(podcasts);
|
|
||||||
*/
|
|
||||||
$.get(globals.BaseURL() + '/jukeboxControl.view?' + globals.BaseParams() + '&action=start');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
$rootScope.sendToJukebox = function (action) {
|
|
||||||
if (globals.settings.Debug) { console.log("SEND JUKEBOX " + action); }
|
|
||||||
$.ajax({
|
|
||||||
url: globals.BaseURL() + '/jukeboxControl.view?' + globals.BaseParams() + '&action=' + action,
|
|
||||||
method: 'GET',
|
|
||||||
dataType: globals.settings.Protocol,
|
|
||||||
timeout: globals.settings.Timeout,
|
|
||||||
success: function (data) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.toggleStar = function (item) {
|
$scope.toggleStar = function (item) {
|
||||||
subsonic.toggleStar(item).then(function (newStarred) {
|
subsonic.toggleStar(item).then(function (newStarred) {
|
||||||
item.starred = newStarred;
|
item.starred = newStarred;
|
||||||
|
|
|
@ -3,7 +3,7 @@ describe("Main controller", function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var controllerParams, $controller, $q, scope, mockGlobals, player, utils, persistence, subsonic, notifications,
|
var controllerParams, $controller, $q, scope, mockGlobals, player, utils, persistence, subsonic, notifications,
|
||||||
deferred;
|
deferred, mockKeypress;
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
mockGlobals = {
|
mockGlobals = {
|
||||||
settings: {
|
settings: {
|
||||||
|
@ -18,6 +18,16 @@ describe("Main controller", function () {
|
||||||
$provide.value('globals', mockGlobals);
|
$provide.value('globals', mockGlobals);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Mock a keypress to the application
|
||||||
|
mockKeypress = function(scope, key, target){
|
||||||
|
scope.processKeyEvent({
|
||||||
|
key: key,
|
||||||
|
target: target,
|
||||||
|
isDefaultPrevented: function(){},
|
||||||
|
preventDefault: function(){}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Mock the player service
|
// Mock the player service
|
||||||
player = jasmine.createSpyObj("player", [
|
player = jasmine.createSpyObj("player", [
|
||||||
"togglePause",
|
"togglePause",
|
||||||
|
@ -78,10 +88,6 @@ describe("Main controller", function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
xdescribe("toggleSetting -", function () {
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("", function () {
|
describe("", function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
$controller('AppController', controllerParams);
|
$controller('AppController', controllerParams);
|
||||||
|
@ -99,24 +105,16 @@ describe("Main controller", function () {
|
||||||
expect(scope.showQueue).toHaveBeenCalled();
|
expect(scope.showQueue).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("When I toggle pause,", function () {
|
describe("When I toggle pause using the keyboard shortcut,", function () {
|
||||||
it("given that we're using the Jukebox mode, it sends a 'stop' command to the jukebox", function () {
|
it("it toggles pause on the player service", function () {
|
||||||
mockGlobals.settings.Jukebox = true;
|
mockKeypress(scope, ' ');
|
||||||
spyOn(scope, "sendToJukebox");
|
|
||||||
|
|
||||||
scope.togglePause();
|
|
||||||
expect(scope.sendToJukebox).toHaveBeenCalledWith('stop');
|
|
||||||
});
|
|
||||||
|
|
||||||
it("it toggles pause using the player service", function () {
|
|
||||||
scope.togglePause();
|
|
||||||
expect(player.togglePause).toHaveBeenCalled();
|
expect(player.togglePause).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("When I turn the volume up, it sets the player's volume up and saves it using the persistence service", function () {
|
it("When I turn the volume up, it sets the player's volume up and saves it using the persistence service", function () {
|
||||||
player.turnVolumeUp.and.returnValue(0.6);
|
player.turnVolumeUp.and.returnValue(0.6);
|
||||||
scope.turnVolumeUp();
|
mockKeypress(scope, '+');
|
||||||
|
|
||||||
expect(player.turnVolumeUp).toHaveBeenCalled();
|
expect(player.turnVolumeUp).toHaveBeenCalled();
|
||||||
expect(persistence.saveVolume).toHaveBeenCalledWith(0.6);
|
expect(persistence.saveVolume).toHaveBeenCalledWith(0.6);
|
||||||
|
@ -124,52 +122,49 @@ describe("Main controller", function () {
|
||||||
|
|
||||||
it("When I turn the volume down, it sets the player's volume down and saves it using the persistence service", function () {
|
it("When I turn the volume down, it sets the player's volume down and saves it using the persistence service", function () {
|
||||||
player.turnVolumeDown.and.returnValue(0.4);
|
player.turnVolumeDown.and.returnValue(0.4);
|
||||||
scope.turnVolumeDown();
|
mockKeypress(scope, '-');
|
||||||
|
|
||||||
expect(player.turnVolumeDown).toHaveBeenCalled();
|
expect(player.turnVolumeDown).toHaveBeenCalled();
|
||||||
expect(persistence.saveVolume).toHaveBeenCalledWith(0.4);
|
expect(persistence.saveVolume).toHaveBeenCalledWith(0.4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("When I go to the next track, it calls next track on the player", function () {
|
it("When I go to the next track, it calls next track on the player", function () {
|
||||||
scope.nextTrack();
|
mockKeypress(scope, 'ArrowRight');
|
||||||
expect(player.nextTrack).toHaveBeenCalled();
|
expect(player.nextTrack).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("When I go to the previous track, it calls previous track on the player", function () {
|
it("When I go to the previous track, it calls previous track on the player", function () {
|
||||||
scope.previousTrack();
|
mockKeypress(scope, 'ArrowLeft');
|
||||||
expect(player.previousTrack).toHaveBeenCalled();
|
expect(player.previousTrack).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Given that I am targeting an input,", function () {
|
describe("Given that I am targeting an input,", function () {
|
||||||
var event;
|
var target = { 'tagName': "iNPUt" } ;
|
||||||
beforeEach(function () {
|
|
||||||
event = { target: { tagName: "INPUT" } };
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when I use a shortcut to toggle pause, it doesn't do anything", function () {
|
it("when I use a shortcut to toggle pause, it doesn't do anything", function () {
|
||||||
scope.togglePause(event);
|
mockKeypress(scope, ' ', target);
|
||||||
expect(player.togglePause).not.toHaveBeenCalled();
|
expect(player.togglePause).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when I use a shortcut to turn the volume up, it doesn't do anything", function () {
|
it("when I use a shortcut to turn the volume up, it doesn't do anything", function () {
|
||||||
scope.turnVolumeUp(event);
|
mockKeypress(scope, '+', target);
|
||||||
expect(player.turnVolumeUp).not.toHaveBeenCalled();
|
expect(player.turnVolumeUp).not.toHaveBeenCalled();
|
||||||
expect(persistence.saveVolume).not.toHaveBeenCalled();
|
expect(persistence.saveVolume).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when I use a shortcut to turn the volume down, it doesn't do anything", function () {
|
it("when I use a shortcut to turn the volume down, it doesn't do anything", function () {
|
||||||
scope.turnVolumeDown(event);
|
mockKeypress(scope, '-', target);
|
||||||
expect(player.turnVolumeDown).not.toHaveBeenCalled();
|
expect(player.turnVolumeDown).not.toHaveBeenCalled();
|
||||||
expect(persistence.saveVolume).not.toHaveBeenCalled();
|
expect(persistence.saveVolume).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when I use a shortcut to go to the next track, it doesn't do anything", function () {
|
it("when I use a shortcut to go to the next track, it doesn't do anything", function () {
|
||||||
scope.nextTrack(event);
|
mockKeypress(scope, 'RightArrow', target);
|
||||||
expect(player.nextTrack).not.toHaveBeenCalled();
|
expect(player.nextTrack).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when I use a shortcut to go to the previous track, it doesn't do anything", function () {
|
it("when I use a shortcut to go to the previous track, it doesn't do anything", function () {
|
||||||
scope.previousTrack(event);
|
mockKeypress(scope, 'LeftArrow', target);
|
||||||
expect(player.previousTrack).not.toHaveBeenCalled();
|
expect(player.previousTrack).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<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}">
|
<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,dblclick"></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,dblclick"></a>-->
|
||||||
<a class="play" href="" title="Play this song" ng-click="playSong(o)" stop-event="click"></a>
|
<a class="play" href="" title="Play this song" ng-click="playSong(o)" stop-event="click,dblclick"></a>
|
||||||
<!--<a class="download" href="" title="Download Song" ng-click="download(o.id)"></a>-->
|
<!--<a class="download" href="" title="Download Song" ng-click="download(o.id)" stop-event="click,dblclick"></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,dblclick"></a>
|
||||||
<div class="clear"></div>
|
<div class="clear"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="track floatleft" ng-bind-html="o.track"></div>
|
<div class="track floatleft" ng-bind-html="o.track"></div>
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
<link rel="stylesheet" href="subsonic/breadcrumbs-directive/breadcrumbs-directive.css" />
|
<link rel="stylesheet" href="subsonic/breadcrumbs-directive/breadcrumbs-directive.css" />
|
||||||
<!-- endbuild -->
|
<!-- endbuild -->
|
||||||
</head>
|
</head>
|
||||||
<body ui-keypress="{'32 179': 'togglePause($event)', '43 61 187': 'turnVolumeUp($event)', '45 95 189': 'turnVolumeDown($event)'}" ui-keydown="{'right 176': 'nextTrack($event)', 'left 177': 'previousTrack($event)'}">
|
<body>
|
||||||
<div id="container">
|
<div id="container">
|
||||||
<div id="header">
|
<div id="header">
|
||||||
<div id="messages">
|
<div id="messages">
|
||||||
|
@ -84,20 +84,19 @@
|
||||||
<!-- build:js({.,app}) scripts/vendor.min.js -->
|
<!-- build:js({.,app}) scripts/vendor.min.js -->
|
||||||
<!-- bower:js -->
|
<!-- bower:js -->
|
||||||
<script src="bower_components/jquery/dist/jquery.js"></script>
|
<script src="bower_components/jquery/dist/jquery.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/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/jquery-ui.js"></script>
|
|
||||||
<script src="bower_components/jplayer/dist/jplayer/jquery.jplayer.js"></script>
|
|
||||||
<script src="bower_components/jquery-mousewheel/jquery.mousewheel.js"></script>
|
<script src="bower_components/jquery-mousewheel/jquery.mousewheel.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>
|
||||||
<script src="bower_components/jquery.scrollTo/jquery.scrollTo.js"></script>
|
<script src="bower_components/jquery.scrollTo/jquery.scrollTo.js"></script>
|
||||||
<script src="bower_components/jquery-dateFormat/dist/jquery-dateFormat.js"></script>
|
<script src="bower_components/jquery-dateFormat/dist/jquery-dateFormat.js"></script>
|
||||||
<script src="bower_components/angular-locker/dist/angular-locker.min.js"></script>
|
<script src="bower_components/angular-locker/dist/angular-locker.min.js"></script>
|
||||||
<script src="bower_components/angular-ui-utils/keypress.js"></script>
|
|
||||||
<script src="bower_components/ng-lodash/build/ng-lodash.js"></script>
|
<script src="bower_components/ng-lodash/build/ng-lodash.js"></script>
|
||||||
<script src="bower_components/angular-ui-sortable/sortable.js"></script>
|
<script src="bower_components/angular-ui-sortable/sortable.js"></script>
|
||||||
<!-- endbower -->
|
<!-- endbower -->
|
||||||
|
|
|
@ -85,7 +85,7 @@ angular.module('jamstash.player.directive', ['jamstash.player.service', 'jamstas
|
||||||
var p = event.jPlayer.status.currentPercentAbsolute;
|
var p = event.jPlayer.status.currentPercentAbsolute;
|
||||||
var isPlaying = !event.jPlayer.status.paused;
|
var isPlaying = !event.jPlayer.status.paused;
|
||||||
if (!scope.scrobbled && p > 30 && isPlaying) {
|
if (!scope.scrobbled && p > 30 && isPlaying) {
|
||||||
if (globals.settings.Debug) { console.log('LAST.FM SCROBBLE - Percent Played: ' + p); }
|
if (globals.settings.Debug) { console.log('Scrobbling - Percent Played: ' + p); }
|
||||||
subsonic.scrobble(scope.currentSong);
|
subsonic.scrobble(scope.currentSong);
|
||||||
scope.scrobbled = true;
|
scope.scrobbled = true;
|
||||||
}
|
}
|
||||||
|
@ -123,7 +123,7 @@ angular.module('jamstash.player.directive', ['jamstash.player.service', 'jamstas
|
||||||
$player.jPlayer('setMedia', media);
|
$player.jPlayer('setMedia', media);
|
||||||
if (globals.settings.Jukebox) {
|
if (globals.settings.Jukebox) {
|
||||||
$player.jPlayer('mute', true);
|
$player.jPlayer('mute', true);
|
||||||
scope.addToJukebox(newSong.id);
|
subsonic.addToJukebox(newSong);
|
||||||
}
|
}
|
||||||
if (playerService.loadSong === true || globals.settings.Jukebox) {
|
if (playerService.loadSong === true || globals.settings.Jukebox) {
|
||||||
// Do not play, only load
|
// Do not play, only load
|
||||||
|
@ -152,10 +152,9 @@ angular.module('jamstash.player.directive', ['jamstash.player.service', 'jamstas
|
||||||
scope.$watch(function () {
|
scope.$watch(function () {
|
||||||
return playerService.pauseSong;
|
return playerService.pauseSong;
|
||||||
}, function (newVal) {
|
}, function (newVal) {
|
||||||
if(newVal === true) {
|
$player.jPlayer(newVal ? 'pause' : 'play');
|
||||||
$player.jPlayer('pause');
|
if(globals.settings.Jukebox){
|
||||||
} else {
|
subsonic.sendToJukebox(newVal ? 'stop' : 'start');
|
||||||
$player.jPlayer('play');
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -76,11 +76,12 @@ describe("jplayer directive", function () {
|
||||||
|
|
||||||
it("if the global setting Jukebox is true, it mutes jPlayer and adds the song to subsonic's Jukebox", function () {
|
it("if the global setting Jukebox is true, it mutes jPlayer and adds the song to subsonic's Jukebox", function () {
|
||||||
mockGlobals.settings.Jukebox = true;
|
mockGlobals.settings.Jukebox = true;
|
||||||
scope.addToJukebox = jasmine.createSpy("addToJukebox");
|
spyOn(subsonic, "addToJukebox")
|
||||||
|
|
||||||
scope.$apply();
|
scope.$apply();
|
||||||
expect($player.jPlayer).toHaveBeenCalledWith('mute', true);
|
expect($player.jPlayer).toHaveBeenCalledWith('mute', true);
|
||||||
expect(scope.addToJukebox).toHaveBeenCalledWith(playingSong.id);
|
expect(subsonic.addToJukebox).toHaveBeenCalledWith(
|
||||||
|
jasmine.objectContaining({ id: playingSong.id}))
|
||||||
});
|
});
|
||||||
|
|
||||||
it("if the player service's loadSong flag is true, it does not play the song, it displays the player controls and sets the player to the song's supplied position", function () {
|
it("if the player service's loadSong flag is true, it does not play the song, it displays the player controls and sets the player to the song's supplied position", function () {
|
||||||
|
@ -142,6 +143,7 @@ describe("jplayer directive", function () {
|
||||||
describe("", function () {
|
describe("", function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
$.fn.jPlayer.and.stub();
|
$.fn.jPlayer.and.stub();
|
||||||
|
spyOn(subsonic, "sendToJukebox")
|
||||||
});
|
});
|
||||||
|
|
||||||
it("When the player service's restartSong flag is true, it restarts the current song, resets the restart flag to false and resets the scrobbled flag to false", function () {
|
it("When the player service's restartSong flag is true, it restarts the current song, resets the restart flag to false and resets the scrobbled flag to false", function () {
|
||||||
|
@ -170,6 +172,25 @@ describe("jplayer directive", function () {
|
||||||
expect($player.jPlayer).toHaveBeenCalledWith('play');
|
expect($player.jPlayer).toHaveBeenCalledWith('play');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("When the player service's pauseSong is true and jukebox is enabled, 'stop' is sent to the jukebox", function () {
|
||||||
|
mockGlobals.settings.Jukebox = true;
|
||||||
|
playerService.pauseSong = true;
|
||||||
|
scope.$apply();
|
||||||
|
|
||||||
|
expect(subsonic.sendToJukebox).toHaveBeenCalledWith('stop');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Given that the current song is paused and jukebox is enabled, 'start' is sent to the jukebox when it's unpaused", function () {
|
||||||
|
mockGlobals.settings.Jukebox = true;
|
||||||
|
|
||||||
|
playerService.pauseSong = true;
|
||||||
|
scope.$apply();
|
||||||
|
playerService.pauseSong = false;
|
||||||
|
scope.$apply();
|
||||||
|
|
||||||
|
expect(subsonic.sendToJukebox).toHaveBeenCalledWith('start');
|
||||||
|
});
|
||||||
|
|
||||||
it("When the player service's volume changes, it sets jPlayer's volume", function () {
|
it("When the player service's volume changes, it sets jPlayer's volume", function () {
|
||||||
playerService.getVolume.and.returnValue(0.2034);
|
playerService.getVolume.and.returnValue(0.2034);
|
||||||
scope.$apply();
|
scope.$apply();
|
||||||
|
|
|
@ -15,22 +15,8 @@ angular.module('jamstash.player.controller', ['jamstash.player.service', 'jamsta
|
||||||
$scope.settings = globals.settings;
|
$scope.settings = globals.settings;
|
||||||
$scope.playerSettings = player.settings;
|
$scope.playerSettings = player.settings;
|
||||||
|
|
||||||
$scope.play = function () {
|
$scope.play = player.togglePause;
|
||||||
if (globals.settings.Jukebox) {
|
$scope.pause = player.togglePause;
|
||||||
$scope.sendToJukebox('start');
|
|
||||||
} else {
|
|
||||||
player.togglePause();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.pause = function () {
|
|
||||||
if (globals.settings.Jukebox) {
|
|
||||||
$scope.sendToJukebox('stop');
|
|
||||||
} else {
|
|
||||||
player.togglePause();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.previousTrack = player.previousTrack;
|
$scope.previousTrack = player.previousTrack;
|
||||||
$scope.nextTrack = player.nextTrack;
|
$scope.nextTrack = player.nextTrack;
|
||||||
}]);
|
}]);
|
||||||
|
|
|
@ -46,14 +46,14 @@
|
||||||
href=""
|
href=""
|
||||||
title="Remove Song"
|
title="Remove Song"
|
||||||
ng-click="vm.removeSongFromQueue(song)"
|
ng-click="vm.removeSongFromQueue(song)"
|
||||||
stop-event="click"
|
stop-event="click,dblclick"
|
||||||
></a>
|
></a>
|
||||||
<a
|
<a
|
||||||
href=""
|
href=""
|
||||||
title="Star"
|
title="Star"
|
||||||
ng-class="{'favorite': song.starred, 'rate': ! song.starred}"
|
ng-class="{'favorite': song.starred, 'rate': ! song.starred}"
|
||||||
ng-click="vm.toggleStar(song)"
|
ng-click="vm.toggleStar(song)"
|
||||||
stop-event="click"
|
stop-event="click,dblclick"
|
||||||
></a>
|
></a>
|
||||||
<div class="clear"></div>
|
<div class="clear"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -114,9 +114,7 @@
|
||||||
<h3 class="title">Keyboard Shortcuts</h3>
|
<h3 class="title">Keyboard Shortcuts</h3>
|
||||||
<ul class="preferences">
|
<ul class="preferences">
|
||||||
<li><em>Esc</em> Hide Queue</li>
|
<li><em>Esc</em> Hide Queue</li>
|
||||||
<li><em>[1-6]</em> Switch to corresponding tab</li>
|
|
||||||
<li><em>[a-z]</em> Use to Quickly Browse to an Artist</li>
|
<li><em>[a-z]</em> Use to Quickly Browse to an Artist</li>
|
||||||
<li><em>Home</em> Scroll to Top of Artist List</li>
|
|
||||||
<li><em>Spacebar</em> Play/Pause</li>
|
<li><em>Spacebar</em> Play/Pause</li>
|
||||||
<li><em>→</em> Next Track</li>
|
<li><em>→</em> Next Track</li>
|
||||||
<li><em>←</em> Previous Track</li>
|
<li><em>←</em> Previous Track</li>
|
||||||
|
|
|
@ -53,7 +53,9 @@ function subsonicService(
|
||||||
scrobble : scrobble,
|
scrobble : scrobble,
|
||||||
search : search,
|
search : search,
|
||||||
subsonicRequest : subsonicRequest,
|
subsonicRequest : subsonicRequest,
|
||||||
toggleStar : toggleStar
|
toggleStar : toggleStar,
|
||||||
|
addToJukebox : addToJukebox,
|
||||||
|
sendToJukebox : sendToJukebox
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Hyz: Remove when refactored
|
// TODO: Hyz: Remove when refactored
|
||||||
|
@ -570,4 +572,27 @@ function subsonicService(
|
||||||
});
|
});
|
||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addToJukebox(song) {
|
||||||
|
if (globals.settings.Debug) { console.log("Load Jukebox"); }
|
||||||
|
var promise = self.subsonicRequest('jukeboxControl.view', {
|
||||||
|
params: {
|
||||||
|
action: 'set',
|
||||||
|
id: song.id
|
||||||
|
}
|
||||||
|
}).then(function () {
|
||||||
|
self.sendToJukebox('start');
|
||||||
|
});
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendToJukebox(action) {
|
||||||
|
if (globals.settings.Debug) { console.log("Send Jukebox " + action); }
|
||||||
|
var promise = self.subsonicRequest('jukeboxControl.view', {
|
||||||
|
params: {
|
||||||
|
action: action
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,20 +26,19 @@
|
||||||
},
|
},
|
||||||
"main": "app/index.html",
|
"main": "app/index.html",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"jquery": "~2.1.4",
|
||||||
|
"jquery-ui": "~1.11.4",
|
||||||
|
"jplayer": "~2.9.2",
|
||||||
"angular": "~1.4.1",
|
"angular": "~1.4.1",
|
||||||
"angular-route": "~1.4.1",
|
"angular-route": "~1.4.1",
|
||||||
"angular-sanitize": "~1.4.1",
|
"angular-sanitize": "~1.4.1",
|
||||||
"angular-cookies": "~1.4.1",
|
"angular-cookies": "~1.4.1",
|
||||||
"angular-resource": "~1.4.1",
|
"angular-resource": "~1.4.1",
|
||||||
"jquery": "~2.1.4",
|
|
||||||
"jquery-ui": "~1.11.4",
|
|
||||||
"jplayer": "~2.9.2",
|
|
||||||
"fancybox": "~2.1.4",
|
"fancybox": "~2.1.4",
|
||||||
"notify.js": "<=1.2.2",
|
"notify.js": "<=1.2.2",
|
||||||
"jquery.scrollTo": "~1.4.5",
|
"jquery.scrollTo": "~1.4.5",
|
||||||
"jquery-dateFormat": "~1.0.2",
|
"jquery-dateFormat": "~1.0.2",
|
||||||
"angular-locker": "~2.0.1",
|
"angular-locker": "~2.0.1",
|
||||||
"angular-ui-utils": "bower-keypress",
|
|
||||||
"open-iconic": "~1.1.1",
|
"open-iconic": "~1.1.1",
|
||||||
"ng-lodash": "~0.2.3",
|
"ng-lodash": "~0.2.3",
|
||||||
"angular-ui-sortable": "~0.13.4"
|
"angular-ui-sortable": "~0.13.4"
|
||||||
|
|
|
@ -20,20 +20,19 @@ module.exports = function (config) {
|
||||||
files: [
|
files: [
|
||||||
// bower:
|
// bower:
|
||||||
'bower_components/jquery/dist/jquery.js',
|
'bower_components/jquery/dist/jquery.js',
|
||||||
|
'bower_components/jquery-ui/jquery-ui.js',
|
||||||
|
'bower_components/jplayer/dist/jplayer/jquery.jplayer.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/jquery-ui.js',
|
|
||||||
'bower_components/jplayer/dist/jplayer/jquery.jplayer.js',
|
|
||||||
'bower_components/jquery-mousewheel/jquery.mousewheel.js',
|
'bower_components/jquery-mousewheel/jquery.mousewheel.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',
|
||||||
'bower_components/jquery.scrollTo/jquery.scrollTo.js',
|
'bower_components/jquery.scrollTo/jquery.scrollTo.js',
|
||||||
'bower_components/jquery-dateFormat/dist/jquery-dateFormat.js',
|
'bower_components/jquery-dateFormat/dist/jquery-dateFormat.js',
|
||||||
'bower_components/angular-locker/dist/angular-locker.min.js',
|
'bower_components/angular-locker/dist/angular-locker.min.js',
|
||||||
'bower_components/angular-ui-utils/keypress.js',
|
|
||||||
'bower_components/ng-lodash/build/ng-lodash.js',
|
'bower_components/ng-lodash/build/ng-lodash.js',
|
||||||
'bower_components/angular-ui-sortable/sortable.js',
|
'bower_components/angular-ui-sortable/sortable.js',
|
||||||
'bower_components/angular-mocks/angular-mocks.js',
|
'bower_components/angular-mocks/angular-mocks.js',
|
||||||
|
|
4232
package-lock.json
generated
4232
package-lock.json
generated
File diff suppressed because it is too large
Load diff
41
package.json
41
package.json
|
@ -28,32 +28,33 @@
|
||||||
"main": "app/index.html",
|
"main": "app/index.html",
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"grunt": "^0.4.5",
|
"grunt": "^1.0.1",
|
||||||
"grunt-bump": "^0.3.1",
|
"grunt-bump": "^0.8.0",
|
||||||
"grunt-contrib-clean": "^0.6.0",
|
"grunt-contrib-clean": "^1.0.0",
|
||||||
"grunt-contrib-concat": "^0.5.1",
|
"grunt-contrib-concat": "^1.0.1",
|
||||||
"grunt-contrib-connect": "^0.10.1",
|
"grunt-contrib-connect": "^1.0.2",
|
||||||
"grunt-contrib-copy": "^0.8.0",
|
"grunt-contrib-copy": "^1.0.0",
|
||||||
"grunt-contrib-cssmin": "^0.12.3",
|
"grunt-contrib-cssmin": "^1.0.2",
|
||||||
"grunt-contrib-htmlmin": "^0.4.0",
|
"grunt-contrib-htmlmin": "^2.0.0",
|
||||||
"grunt-contrib-imagemin": "^1.0.1",
|
"grunt-contrib-imagemin": "^1.0.1",
|
||||||
"grunt-contrib-uglify": "^0.9.1",
|
"grunt-contrib-uglify": "^2.0.0",
|
||||||
"grunt-contrib-watch": "^0.6.1",
|
"grunt-contrib-watch": "^1.0.0",
|
||||||
"grunt-filerev": "^2.3.1",
|
"grunt-filerev": "^2.3.1",
|
||||||
"grunt-karma": "^0.10.1",
|
"grunt-karma": "^2.0.0",
|
||||||
"grunt-notify": "^0.4.1",
|
"grunt-notify": "^0.4.1",
|
||||||
"grunt-ssh": "^0.12.3",
|
"grunt-ssh": "^0.12.3",
|
||||||
"grunt-svg-sprite": "^1.1.2",
|
"grunt-svg-sprite": "^1.1.2",
|
||||||
"grunt-usemin": "^3.0.0",
|
"grunt-usemin": "^3.0.0",
|
||||||
"grunt-wiredep": "^2.0.0",
|
"grunt-wiredep": "^3.0.1",
|
||||||
"jasmine-core": "^2.6.1",
|
"jasmine-core": "^2.5.2",
|
||||||
"jit-grunt": "^0.9.1",
|
"jit-grunt": "^0.10.0",
|
||||||
"karma": "^0.12.32",
|
"karma": "^1.3.0",
|
||||||
"karma-chrome-launcher": "^0.1.12",
|
"karma-chrome-launcher": "^2.0.0",
|
||||||
"karma-coverage": "^0.3.1",
|
"karma-coverage": "^1.1.1",
|
||||||
"karma-jasmine": "^0.3.5",
|
"karma-jasmine": "^1.0.2",
|
||||||
"karma-ng-html2js-preprocessor": "^0.1.2",
|
"karma-ng-html2js-preprocessor": "^1.0.0",
|
||||||
"karma-notify-reporter": "^0.1.1",
|
"karma-notify-reporter": "^1.0.1",
|
||||||
|
"serve-static": "^1.13.1",
|
||||||
"time-grunt": "^1.2.1"
|
"time-grunt": "^1.2.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue