Merge branch '4.5.0' into develop
This commit is contained in:
commit
ab1954b3ba
48 changed files with 2037 additions and 1020 deletions
|
@ -1,17 +1,10 @@
|
|||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# EditorConfig helps developers define and maintain consistent coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
[*]
|
||||
|
||||
# Change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# We recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
|
|
28
.jshintrc
28
.jshintrc
|
@ -1,38 +1,32 @@
|
|||
{
|
||||
"esnext": true,
|
||||
"bitwise": true,
|
||||
"camelcase": true,
|
||||
"curly": true,
|
||||
"eqeqeq": true,
|
||||
"immed": true,
|
||||
"indent": 2,
|
||||
"esnext": true,
|
||||
"freeze": true,
|
||||
"latedef": true,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"quotmark": "true",
|
||||
"regexp": true,
|
||||
"nonbsp": true,
|
||||
"nonew": true,
|
||||
"notypeof": true,
|
||||
"shadow": false,
|
||||
"strict": true,
|
||||
"undef": true,
|
||||
"unused": true,
|
||||
"strict": true,
|
||||
"trailing": true,
|
||||
"smarttabs": true,
|
||||
"globals": {
|
||||
"_": false,
|
||||
"affix": false,
|
||||
"after": false,
|
||||
"afterEach": false,
|
||||
"angular": false,
|
||||
"before": false,
|
||||
"beforeEach": false,
|
||||
"browser": false,
|
||||
"describe": false,
|
||||
"console": false,
|
||||
"expect": false,
|
||||
"inject": false,
|
||||
"it": false,
|
||||
"jasmine": false,
|
||||
"spyOn": false
|
||||
"installPromiseMatchers": false,
|
||||
"module": false
|
||||
},
|
||||
"browser": true,
|
||||
"jquery": true,
|
||||
"node": true
|
||||
"jasmine": true
|
||||
}
|
||||
|
|
78
Gruntfile.js
78
Gruntfile.js
|
@ -39,7 +39,7 @@ module.exports = function (grunt) {
|
|||
watch: {
|
||||
bower: {
|
||||
files: ['bower.json'],
|
||||
tasks: ['wiredep']
|
||||
tasks: ['wiredep', 'copy:svg']
|
||||
},
|
||||
js: {
|
||||
files: ['<%= yeoman.app %>/**/*.js', '!<%= yeoman.app %>/**/*_test.js'],
|
||||
|
@ -52,12 +52,17 @@ module.exports = function (grunt) {
|
|||
files: ['<%= yeoman.app %>/**/*_test.js'],
|
||||
tasks: ['karma:continuous:run']
|
||||
},
|
||||
svg: {
|
||||
files: ['<%= yeoman.app %>/images/**/*.svg', '!<%= yeoman.app %>/images/sprite/**'],
|
||||
tasks: ['svg_sprite:dist']
|
||||
},
|
||||
gruntfile: {
|
||||
files: ['Gruntfile.js']
|
||||
},
|
||||
livereload: {
|
||||
files: [
|
||||
'<%= yeoman.app %>/**/*.html',
|
||||
'<%= yeoman.app %>/**/*.css',
|
||||
'<%= yeoman.app %>/styles/{,*/}*.css',
|
||||
'<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
|
||||
]
|
||||
|
@ -89,20 +94,6 @@ module.exports = function (grunt) {
|
|||
}
|
||||
}
|
||||
},
|
||||
test: {
|
||||
options: {
|
||||
port: 9001,
|
||||
middleware: function (connect) {
|
||||
return [
|
||||
connect().use(
|
||||
'/bower_components',
|
||||
connect.static('./bower_components')
|
||||
),
|
||||
connect.static(appConfig.app)
|
||||
];
|
||||
}
|
||||
}
|
||||
},
|
||||
coverage: {
|
||||
options: {
|
||||
open: true,
|
||||
|
@ -123,7 +114,7 @@ module.exports = function (grunt) {
|
|||
// Test settings
|
||||
karma: {
|
||||
options: {
|
||||
configFile: './karma.conf.js',
|
||||
configFile: './karma.conf.js'
|
||||
},
|
||||
unit: {
|
||||
singleRun: true,
|
||||
|
@ -161,22 +152,6 @@ module.exports = function (grunt) {
|
|||
}
|
||||
},
|
||||
|
||||
// Make sure code styles are up to par and there are no obvious mistakes
|
||||
jshint: {
|
||||
options: {
|
||||
jshintrc: '.jshintrc',
|
||||
reporter: require('jshint-stylish'),
|
||||
force: true //TODO: while I work on correcting those errors, don't block the build
|
||||
},
|
||||
all: {
|
||||
src: [
|
||||
'Gruntfile.js',
|
||||
'<%= yeoman.app %>/**/*.js',
|
||||
'!<%= yeoman.app %>/vendor/**/*.js'
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// Empties folders to start fresh
|
||||
clean: {
|
||||
dist: {
|
||||
|
@ -218,7 +193,7 @@ module.exports = function (grunt) {
|
|||
|
||||
// Performs rewrites based on filerev and the useminPrepare configuration
|
||||
usemin: {
|
||||
html: ['<%= yeoman.dist %>/{,*/}*.html'],
|
||||
html: ['<%= yeoman.dist %>/**/*.html'],
|
||||
css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
|
||||
js: ['<%= yeoman.dist %>/scripts/*.js'],
|
||||
options: {
|
||||
|
@ -264,6 +239,22 @@ module.exports = function (grunt) {
|
|||
}
|
||||
},
|
||||
|
||||
svg_sprite: {
|
||||
options: {
|
||||
mode: {
|
||||
symbol: {
|
||||
dest: '',
|
||||
sprite: 'jamstash-sprite.svg'
|
||||
}
|
||||
}
|
||||
},
|
||||
dist: {
|
||||
cwd: '<%= yeoman.app %>/images',
|
||||
src: ['**/*.svg', '!sprite/**/*.svg'],
|
||||
dest: '<%= yeoman.app %>/images/sprite'
|
||||
}
|
||||
},
|
||||
|
||||
// Minify our CSS files but do not merge them, we still want to have two
|
||||
cssmin: {
|
||||
styles: {
|
||||
|
@ -271,7 +262,7 @@ module.exports = function (grunt) {
|
|||
expand: true,
|
||||
cwd: '.tmp/styles',
|
||||
src: ['*.css', '!*.min.css'],
|
||||
dest: '<%= yeoman.dist %>/styles',
|
||||
dest: '<%= yeoman.dist %>/styles'
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
@ -309,6 +300,12 @@ module.exports = function (grunt) {
|
|||
],
|
||||
dest: '<%= yeoman.dist %>'
|
||||
},
|
||||
{
|
||||
expand: true,
|
||||
cwd: '<%= yeoman.app %>',
|
||||
src: ['images/sprite/*.svg'],
|
||||
dest: '<%= yeoman.dist %>'
|
||||
},
|
||||
{
|
||||
expand: true,
|
||||
cwd: '<%= yeoman.app %>',
|
||||
|
@ -324,6 +321,13 @@ module.exports = function (grunt) {
|
|||
'bower_components/fancybox/source/*.{png,gif}'
|
||||
],
|
||||
dest: '.tmp/styles'
|
||||
}
|
||||
]
|
||||
},
|
||||
svg: {
|
||||
files: [{
|
||||
src: ['bower_components/open-iconic/sprite/sprite.svg'],
|
||||
dest: '<%= yeoman.app %>/images/sprite/iconic.svg'
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
@ -365,7 +369,7 @@ module.exports = function (grunt) {
|
|||
sftp: {
|
||||
test: {
|
||||
files: {
|
||||
'./': ['<%= yeoman.dist %>/{,*/}*', '<%= yeoman.dist %>/.git*']
|
||||
'./': ['<%= yeoman.dist %>/**/*', '<%= yeoman.dist %>/.git*']
|
||||
},
|
||||
options: {
|
||||
path: '/var/www/jamstash',
|
||||
|
@ -403,8 +407,7 @@ module.exports = function (grunt) {
|
|||
|
||||
grunt.registerTask('test', 'Run unit tests and jshint', function () {
|
||||
return grunt.task.run([
|
||||
'karma:unit',
|
||||
'jshint'
|
||||
'karma:unit'
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -420,6 +423,7 @@ module.exports = function (grunt) {
|
|||
return grunt.task.run([
|
||||
'clean:dist',
|
||||
'wiredep:app',
|
||||
'copy:svg',
|
||||
'useminPrepare',
|
||||
'concat:generated',
|
||||
'copy:dist',
|
||||
|
|
|
@ -23,7 +23,8 @@ angular.module('jamstash.archive.service', ['jamstash.settings.service', 'jamsta
|
|||
};
|
||||
var offset = 0;
|
||||
|
||||
var mapAlbum = function (data) {
|
||||
var archiveService = {
|
||||
mapAlbum: function (data) {
|
||||
var song = data;
|
||||
var coverartthumb, coverartfull, starred, title, album, publisher, avg_rating, downloads, identifier, source, date;
|
||||
var url = globals.archiveUrl + 'details/' + song.identifier;
|
||||
|
@ -45,9 +46,10 @@ angular.module('jamstash.archive.service', ['jamstash.settings.service', 'jamsta
|
|||
description += '<b>Transferer</b>: ' + publisher + '<br />';
|
||||
description += '<b>Rating</b>: ' + avg_rating + '<br />';
|
||||
description += '<b>Downloads</b>: ' + downloads + '<br />';
|
||||
return new model.Album(identifier, null, title, album, '', coverartthumb, coverartfull, $.format.date(new Date(song.publicdate), "yyyy-MM-dd h:mm a"), starred, $sce.trustAsHtml(description), url);
|
||||
};
|
||||
var mapSong = function (key, song, server, dir, identifier, coverart) {
|
||||
return new model.Album(identifier, null, title, album, '', coverartthumb, coverartfull, utils.formatDate(new Date(song.publicdate), "yyyy-MM-dd h:mm a"), starred, $sce.trustAsHtml(description), url);
|
||||
},
|
||||
|
||||
mapSong: function (key, song, server, dir, identifier, coverart) {
|
||||
var url, time, track, title, rating, starred, contenttype, suffix;
|
||||
var specs = '';
|
||||
if (song.format == 'VBR MP3') {
|
||||
|
@ -58,9 +60,8 @@ angular.module('jamstash.archive.service', ['jamstash.settings.service', 'jamsta
|
|||
if (typeof song.length == 'undefined') { time = ' '; } else { time = utils.timeToSeconds(song.length); }
|
||||
return new model.Song(song.md5, identifier, song.track, title, song.creator, '', song.album, '', coverart, coverart, time, '', '', 'mp3', specs, url, 0, '');
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
return {
|
||||
getArtists: function (query) {
|
||||
var deferred = $q.defer();
|
||||
if (globals.settings.Debug) { console.log("LOAD ARCHIVE.ORG COLLECTIONS"); }
|
||||
|
@ -138,7 +139,7 @@ angular.module('jamstash.archive.service', ['jamstash.settings.service', 'jamsta
|
|||
content.album = [];
|
||||
content.song = [];
|
||||
angular.forEach(items, function (item, key) {
|
||||
content.album.push(mapAlbum(item));
|
||||
content.album.push(archiveService.mapAlbum(item));
|
||||
});
|
||||
notifications.updateMessage(content.album.length + ' Items Returned', true);
|
||||
} else {
|
||||
|
@ -178,7 +179,7 @@ angular.module('jamstash.archive.service', ['jamstash.settings.service', 'jamsta
|
|||
var items = data.files;
|
||||
if (action == 'add') {
|
||||
angular.forEach(items, function (item, key) {
|
||||
var song = mapSong(key, item, server, dir, identifier, coverart);
|
||||
var song = archiveService.mapSong(key, item, server, dir, identifier, coverart);
|
||||
if (song) {
|
||||
player.queue.push(song);
|
||||
}
|
||||
|
@ -187,7 +188,7 @@ angular.module('jamstash.archive.service', ['jamstash.settings.service', 'jamsta
|
|||
} else if (action == 'play') {
|
||||
player.queue = [];
|
||||
angular.forEach(items, function (item, key) {
|
||||
var song = mapSong(key, item, server, dir, identifier, coverart);
|
||||
var song = archiveService.mapSong(key, item, server, dir, identifier, coverart);
|
||||
if (song) {
|
||||
player.queue.push(song);
|
||||
}
|
||||
|
@ -199,7 +200,7 @@ angular.module('jamstash.archive.service', ['jamstash.settings.service', 'jamsta
|
|||
content.album = [];
|
||||
content.song = [];
|
||||
angular.forEach(items, function (item, key) {
|
||||
var song = mapSong(key, item, server, dir, identifier, coverart);
|
||||
var song = archiveService.mapSong(key, item, server, dir, identifier, coverart);
|
||||
if (song) {
|
||||
content.song.push(song);
|
||||
}
|
||||
|
@ -214,4 +215,5 @@ angular.module('jamstash.archive.service', ['jamstash.settings.service', 'jamsta
|
|||
return deferred.promise;
|
||||
}
|
||||
};
|
||||
return archiveService;
|
||||
}]);
|
||||
|
|
|
@ -1,4 +1,52 @@
|
|||
describe("archive service", function() {
|
||||
'use strict';
|
||||
|
||||
var archive, mockBackend, mockGlobals, utils,
|
||||
response;
|
||||
|
||||
beforeEach(function() {
|
||||
|
||||
mockGlobals = {
|
||||
archiveUrl: "http://hysterotomy.com/hippolytus/quercitrin?a=chillagite&b=savour#superfecundation"
|
||||
};
|
||||
|
||||
module('jamstash.archive.service', function ($provide) {
|
||||
$provide.value('globals', mockGlobals);
|
||||
$provide.decorator('player', function () {
|
||||
var playerService = jasmine.createSpyObj("player", ["play"]);
|
||||
playerService.queue = [];
|
||||
return playerService;
|
||||
});
|
||||
$provide.decorator('notifications', function () {
|
||||
return jasmine.createSpyObj("notifications", ["updateMessage"]);
|
||||
});
|
||||
$provide.decorator('utils', function () {
|
||||
return jasmine.createSpyObj("utils", ["formatDate"]);
|
||||
});
|
||||
});
|
||||
|
||||
inject(function (_archive_, $httpBackend, _utils_) {
|
||||
archive = _archive_;
|
||||
mockBackend = $httpBackend;
|
||||
utils = _utils_;
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
mockBackend.verifyNoOutstandingExpectation();
|
||||
mockBackend.verifyNoOutstandingRequest();
|
||||
});
|
||||
|
||||
describe("mapAlbum() -", function() {
|
||||
it("Given album data with a publicDate defined, when I map it to an Album, then utils.formatDate will be called", function() {
|
||||
var albumData = {
|
||||
id: 504,
|
||||
publicDate: "2015-03-29T18:22:06.000Z",
|
||||
collection: ['Sternal Daubreelite']
|
||||
};
|
||||
|
||||
archive.mapAlbum(albumData);
|
||||
expect(utils.formatDate).toHaveBeenCalledWith(jasmine.any(Date), "yyyy-MM-dd h:mm a");
|
||||
});
|
||||
});
|
||||
});
|
|
@ -44,7 +44,7 @@
|
|||
<a class="add" href="" title="Add To Play Queue" ng-click="getSongs(o.id, 'add')" stop-event="click"></a>
|
||||
<a class="play" href="" title="Play" ng-click="getSongs(o.id, 'play')" stop-event="click"></a>
|
||||
<a class="download" href="" title="Download"></a>
|
||||
<a href="" title="Favorite" ng-class="{'favorite': o.starred, 'rate': !o.starred}" ng-click="updateFavorite(o)" stop-event="click"></a>
|
||||
<a href="" title="Favorite" ng-class="{'favorite': o.starred, 'rate': !o.starred}" ng-click="toggleStar(o)" stop-event="click"></a>
|
||||
</div>
|
||||
<div class="albumart"><img ng-src="{{o.coverart}}" src="images/albumdefault_50.jpg"></div>
|
||||
<div class="title">{{o.name}}</div>
|
||||
|
|
|
@ -166,6 +166,10 @@ angular.module('jamstash.archive.controller', ['jamstash.archive.service'])
|
|||
$rootScope.removeSong(item, $scope.song);
|
||||
};
|
||||
|
||||
$scope.toggleStar = function (item) {
|
||||
//Do nothing: we aren't logged in archive.org, so we can't star anything there.
|
||||
};
|
||||
|
||||
/* Launch on Startup */
|
||||
//$scope.getArtists();
|
||||
$scope.getAlbums();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('JamStash')
|
||||
.controller('AppController', ['$scope', '$rootScope', '$document', '$window', '$location', '$cookieStore', '$http', 'utils', 'globals', 'model', 'notifications', 'player', 'persistence', 'Page',
|
||||
function($scope, $rootScope, $document, $window, $location, $cookieStore, $http, utils, globals, model, notifications, player, persistence, Page) {
|
||||
.controller('AppController', ['$scope', '$rootScope', '$document', '$window', '$location', '$cookieStore', '$http', 'utils', 'globals', 'model', 'notifications', 'player', 'persistence', 'Page', 'subsonic',
|
||||
function ($scope, $rootScope, $document, $window, $location, $cookieStore, $http, utils, globals, model, notifications, player, persistence, Page, subsonic) {
|
||||
'use strict';
|
||||
|
||||
$rootScope.settings = globals.settings;
|
||||
|
@ -10,7 +10,6 @@ angular.module('JamStash')
|
|||
$rootScope.Genres = [];
|
||||
$rootScope.Messages = [];
|
||||
|
||||
$rootScope.SelectedMusicFolder = "";
|
||||
$rootScope.unity = null;
|
||||
$scope.Page = Page;
|
||||
$rootScope.loggedIn = function () {
|
||||
|
@ -380,27 +379,13 @@ angular.module('JamStash')
|
|||
});
|
||||
};
|
||||
|
||||
$scope.updateFavorite = function (item) {
|
||||
var id = item.id;
|
||||
var starred = item.starred;
|
||||
var url;
|
||||
if (starred) {
|
||||
url = globals.BaseURL() + '/unstar.view?' + globals.BaseParams() + '&id=' + id;
|
||||
item.starred = undefined;
|
||||
} else {
|
||||
url = globals.BaseURL() + '/star.view?' + globals.BaseParams() + '&id=' + id;
|
||||
item.starred = true;
|
||||
}
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: 'GET',
|
||||
dataType: globals.settings.Protocol,
|
||||
timeout: globals.settings.Timeout,
|
||||
success: function () {
|
||||
$scope.toggleStar = function (item) {
|
||||
subsonic.toggleStar(item).then(function (newStarred) {
|
||||
item.starred = newStarred;
|
||||
notifications.updateMessage('Favorite Updated!', true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.toTrusted = function (html) {
|
||||
return $sce.trustAsHtml(html);
|
||||
};
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
describe("Main controller", function() {
|
||||
'use strict';
|
||||
|
||||
var controllerParams, $controller, scope, mockGlobals, player, utils, persistence;
|
||||
var controllerParams, $controller, $q, scope, mockGlobals, player, utils, persistence, subsonic, notifications,
|
||||
deferred;
|
||||
beforeEach(function() {
|
||||
mockGlobals = {
|
||||
settings: {
|
||||
SaveTrackPosition: false,
|
||||
ShowQueue: false,
|
||||
Debug: true,
|
||||
Debug: false,
|
||||
Jukebox: false
|
||||
}
|
||||
};
|
||||
|
@ -30,9 +31,21 @@ describe("Main controller", function() {
|
|||
"saveSettings"
|
||||
]);
|
||||
|
||||
inject(function (_$controller_, $rootScope, _$document_, _$window_, _$location_, _$cookieStore_, _utils_, globals, _model_, _notifications_, _Page_) {
|
||||
// Mock the subsonic service
|
||||
subsonic = jasmine.createSpyObj("subsonic", [
|
||||
"toggleStar"
|
||||
]);
|
||||
|
||||
// Mock the notifications service
|
||||
notifications = jasmine.createSpyObj("notifications", [
|
||||
"updateMessage"
|
||||
]);
|
||||
|
||||
inject(function (_$controller_, $rootScope, _$q_, _$document_, _$window_, _$location_, _$cookieStore_, _utils_, globals, _model_, _Page_) {
|
||||
scope = $rootScope.$new();
|
||||
utils = _utils_;
|
||||
$q = _$q_;
|
||||
deferred = $q.defer();
|
||||
|
||||
spyOn(utils, "switchTheme");
|
||||
|
||||
|
@ -47,37 +60,15 @@ describe("Main controller", function() {
|
|||
utils: utils,
|
||||
globals: globals,
|
||||
model: _model_,
|
||||
notifications: _notifications_,
|
||||
notifications: notifications,
|
||||
player: player,
|
||||
persistence: persistence,
|
||||
Page: _Page_
|
||||
Page: _Page_,
|
||||
subsonic: subsonic
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
xdescribe("updateFavorite -", function() {
|
||||
|
||||
xit("when starring a song, it notifies the user that the star was saved", function() {
|
||||
|
||||
});
|
||||
|
||||
xit("when starring an album, it notifies the user that the star was saved", function() {
|
||||
|
||||
});
|
||||
|
||||
xit("when starring an artist, it notifies the user that the star was saved", function() {
|
||||
|
||||
});
|
||||
|
||||
xit("given that the Subsonic server returns an error, when starring something, it notifies the user with the error message", function() {
|
||||
//TODO: move to higher level
|
||||
});
|
||||
|
||||
xit("given that the Subsonic server is unreachable, when starring something, it notifies the user with the HTTP error code", function() {
|
||||
//TODO: move to higher level
|
||||
});
|
||||
});
|
||||
|
||||
xdescribe("toggleSetting -", function() {
|
||||
|
||||
});
|
||||
|
@ -191,6 +182,34 @@ describe("Main controller", function() {
|
|||
});
|
||||
|
||||
});
|
||||
|
||||
describe("toggleStar() -", function() {
|
||||
beforeEach(function() {
|
||||
subsonic.toggleStar.and.returnValue(deferred.promise);
|
||||
});
|
||||
|
||||
it("Given an artist that was not starred, when I toggle its star, then subsonic service will be called, the artist will be starred and a notification will be displayed", function() {
|
||||
var artist = { id: 4218, starred: false };
|
||||
scope.toggleStar(artist);
|
||||
deferred.resolve(true);
|
||||
scope.$apply();
|
||||
|
||||
expect(subsonic.toggleStar).toHaveBeenCalledWith(artist);
|
||||
expect(artist.starred).toBeTruthy();
|
||||
expect(notifications.updateMessage).toHaveBeenCalledWith('Favorite Updated!', true);
|
||||
});
|
||||
|
||||
it("Given a song that was starred, when I toggle its star, then subsonic service will be called, the song will be starred and a notification will be displayed", function() {
|
||||
var song = { id: 784, starred: true };
|
||||
scope.toggleStar(song);
|
||||
deferred.resolve(false);
|
||||
scope.$apply();
|
||||
|
||||
expect(subsonic.toggleStar).toHaveBeenCalledWith(song);
|
||||
expect(song.starred).toBeFalsy();
|
||||
expect(notifications.updateMessage).toHaveBeenCalledWith('Favorite Updated!', true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("When starting up,", function() {
|
||||
|
|
|
@ -58,7 +58,7 @@ describe("model service", function() {
|
|||
expect(result).toEqual(episodes);
|
||||
});
|
||||
|
||||
it("Given album data without artist info, when I map it to an Album, an Album with an empty artist name will be returned", function() {
|
||||
it("Given album data without artist info, when I map it to an Album, then an Album with an empty artist name will be returned", function() {
|
||||
var albumData = {
|
||||
id: 584,
|
||||
artist: undefined,
|
||||
|
|
|
@ -77,6 +77,19 @@ angular.module('jamstash.persistence', ['angular-locker',
|
|||
locker.forget('Volume');
|
||||
};
|
||||
|
||||
/* Manage selected music folder */
|
||||
this.getSelectedMusicFolder = function () {
|
||||
return locker.get('MusicFolders');
|
||||
};
|
||||
|
||||
this.saveSelectedMusicFolder = function (selectedMusicFolder) {
|
||||
locker.put('MusicFolders', selectedMusicFolder);
|
||||
};
|
||||
|
||||
this.deleteSelectedMusicFolder = function () {
|
||||
locker.forget('MusicFolders');
|
||||
};
|
||||
|
||||
/* Manage user settings */
|
||||
this.getSettings = function () {
|
||||
// If the latest version from changelog.json is newer than the version stored in local storage,
|
||||
|
|
|
@ -154,6 +154,48 @@ describe("Persistence service", function() {
|
|||
expect(locker.forget).toHaveBeenCalledWith('Volume');
|
||||
});
|
||||
|
||||
describe("getSelectedMusicFolder() -", function() {
|
||||
it("Given a previously saved selected music folder in local storage, when I get the saved music folder, then an object containing the id and name of the selected music folder will be returned", function() {
|
||||
fakeStorage = {
|
||||
'MusicFolders': {
|
||||
'id': 74,
|
||||
'name': 'kooliman unhurled'
|
||||
}
|
||||
};
|
||||
|
||||
var selectedMusicFolder = persistence.getSelectedMusicFolder();
|
||||
|
||||
expect(locker.get).toHaveBeenCalledWith('MusicFolders');
|
||||
expect(selectedMusicFolder).toEqual({
|
||||
'id': 74,
|
||||
'name': 'kooliman unhurled'
|
||||
});
|
||||
});
|
||||
|
||||
it("Given that no selected music folder was previously saved in local storage, when I get the saved music folder, then undefined will be returned", function() {
|
||||
var selectedMusicFolder = persistence.getSelectedMusicFolder();
|
||||
|
||||
expect(locker.get).toHaveBeenCalledWith('MusicFolders');
|
||||
expect(selectedMusicFolder).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("saveSelectedMusicFolder() - given an object containing the id and name of the selected music folder, when I save the music folder, then it will be set in local storage", function() {
|
||||
persistence.saveSelectedMusicFolder({
|
||||
id: 41,
|
||||
name: 'parlormaid carcinolytic'
|
||||
});
|
||||
expect(locker.put).toHaveBeenCalledWith('MusicFolders', {
|
||||
id: 41,
|
||||
name: 'parlormaid carcinolytic'
|
||||
});
|
||||
});
|
||||
|
||||
it("deleteSelectedMusicFolder() - when I delete the selected music folder, then it will be erased from local storage", function() {
|
||||
persistence.deleteSelectedMusicFolder();
|
||||
expect(locker.forget).toHaveBeenCalledWith('MusicFolders');
|
||||
});
|
||||
|
||||
describe("getSettings() -", function() {
|
||||
beforeEach(function() {
|
||||
spyOn(persistence, 'upgradeVersion');
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
<div class="itemactions">
|
||||
<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="play" href="" title="Start Playing From This Song" ng-click="playFrom($index)" stop-event="click"></a>-->
|
||||
<a class="play" href="" title="Play this song" ng-click="playSong(o)" stop-event="click"></a>
|
||||
<!--<a class="download" href="" title="Download Song" ng-click="download(o.id)"></a>-->
|
||||
<a href="" title="Favorite" ng-class="{'favorite': o.starred, 'rate': !o.starred}" ng-click="updateFavorite(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>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
<div class="track floatleft" ng-bind-html="o.track"></div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<li class="row song" ng-repeat="o in song" ng-click="selectSong(o)" ng-dblclick="playSong(o)" ng-class="{'selected': o.selected, 'playing': o.playing}">
|
||||
<div class="itemactions">
|
||||
<a class="remove" href="" title="Remove Song" ng-click="removeSongFromQueue(o)" stop-event="click"></a>
|
||||
<a href="" title="Favorite" ng-class="{'favorite': o.starred, 'rate': !o.starred}" ng-click="updateFavorite(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>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
<div class="title floatleft" title="{{o.description}}" ng-bind-html="o.name"></div>
|
||||
|
|
3
app/images/loop-single.svg
Normal file
3
app/images/loop-single.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="8" width="8" version="1.1" viewBox="0 0 8 8">
|
||||
<path d="m6.002 0v1.0015c-1.7031 0.00345-3.4067-0.006907-5.1095 0.0052-0.54821 0.0425-0.96298 0.5856-0.8995 1.1241v0.87368h1.0015v-1.0015h5.0075v1.0015l2.0031-1.5021c-0.6677-0.5008-1.3356-1.0015-2.0031-1.5024zm0.086036 3.1845c-1.0618-0.075578-1.8347 0.96819-1.9789 1.823h-2.1132v-1.0015l-2.003 1.5023 2.003 1.5023v-1.0015h2.404c0.54902 1.1213 2.228 1.3563 3.0632 0.42782 0.45961-0.43735 0.56857-1.0803 0.54177-1.6849v-0.74587c-0.2578 0.0273-0.4787-0.0007-0.5932-0.2906-0.3515-0.3384-0.836-0.5324-1.3237-0.531zm0.066497 0.47534c0.29389-0.085418 0.27048 0.17533 0.25848 0.36687v2.0667c0.16688 0.00839 0.34015-0.017266 0.5028 0.01371 0.12894 0.19311 0.050198 0.52465-0.2269 0.43624h-1.3868c-0.1886-0.1444-0.0957-0.5528 0.1898-0.4499 0.1002-0.0281 0.3133 0.0567 0.3423-0.0426v-1.8196c-0.1923 0.0909-0.367 0.2378-0.5739 0.2842-0.1524-0.206-0.0164-0.4607 0.2048-0.5449 0.1713-0.097 0.3231-0.2382 0.5077-0.3049 0.0603-0.0053 0.1223-0.006 0.1817-0.0058z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1 KiB |
Binary file not shown.
Before Width: | Height: | Size: 241 B |
Binary file not shown.
Before Width: | Height: | Size: 212 B |
673
app/images/sprite/iconic.svg
Normal file
673
app/images/sprite/iconic.svg
Normal file
|
@ -0,0 +1,673 @@
|
|||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<symbol id="account-login" viewBox="0 0 8 8">
|
||||
<path d="M3 0v1h4v5h-4v1h5v-7h-5zm1 2v1h-4v1h4v1l2-1.5-2-1.5z"></path>
|
||||
</symbol>
|
||||
<symbol id="account-logout" viewBox="0 0 8 8">
|
||||
<path d="M3 0v1h4v5h-4v1h5v-7h-5zm-1 2l-2 1.5 2 1.5v-1h4v-1h-4v-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="action-redo" viewBox="0 0 8 8">
|
||||
<path d="M3.5 0c-1.93 0-3.5 1.57-3.5 3.5 0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v.5h-1l2 2 2-2h-1v-.5c0-1.93-1.57-3.5-3.5-3.5z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="action-undo" viewBox="0 0 8 8">
|
||||
<path d="M4.5 0c-1.93 0-3.5 1.57-3.5 3.5v.5h-1l2 2 2-2h-1v-.5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5c0-1.93-1.57-3.5-3.5-3.5z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="align-center" viewBox="0 0 8 8">
|
||||
<path d="M0 0v1h8v-1h-8zm1 2v1h6v-1h-6zm-1 2v1h8v-1h-8zm1 2v1h6v-1h-6z"></path>
|
||||
</symbol>
|
||||
<symbol id="align-left" viewBox="0 0 8 8">
|
||||
<path d="M0 0v1h8v-1h-8zm0 2v1h6v-1h-6zm0 2v1h8v-1h-8zm0 2v1h6v-1h-6z"></path>
|
||||
</symbol>
|
||||
<symbol id="align-right" viewBox="0 0 8 8">
|
||||
<path d="M0 0v1h8v-1h-8zm2 2v1h6v-1h-6zm-2 2v1h8v-1h-8zm2 2v1h6v-1h-6z"></path>
|
||||
</symbol>
|
||||
<symbol id="aperture" viewBox="0 0 8 8">
|
||||
<path d="M4 0c-.69 0-1.336.19-1.906.5l3.219 2.344.719-2.25c-.59-.36-1.281-.594-2.031-.594zm-2.75 1.125c-.76.73-1.25 1.735-1.25 2.875 0 .25.022.489.063.719l3.094-2.219-1.906-1.375zm5.625.125l-1.219 3.75h2.219c.08-.32.125-.65.125-1 0-1.07-.435-2.03-1.125-2.75zm-4.719 3.188l-1.75 1.281c.55 1.13 1.595 1.989 2.875 2.219l-1.125-3.5zm1.563 1.563l.625 1.969c1.33-.11 2.454-.879 3.094-1.969h-3.719z"></path>
|
||||
</symbol>
|
||||
<symbol id="arrow-bottom" viewBox="0 0 8 8">
|
||||
<path d="M2 0v5h-2l2.531 3 2.469-3h-2v-5h-1z" transform="translate(1)"></path>
|
||||
</symbol>
|
||||
<symbol id="arrow-circle-bottom" viewBox="0 0 8 8">
|
||||
<path d="M4 0c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm-1 1h2v3h2l-3 3-3-3h2v-3z"></path>
|
||||
</symbol>
|
||||
<symbol id="arrow-circle-left" viewBox="0 0 8 8">
|
||||
<path d="M4 0c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 1v2h3v2h-3v2l-3-3 3-3z"></path>
|
||||
</symbol>
|
||||
<symbol id="arrow-circle-right" viewBox="0 0 8 8">
|
||||
<path d="M4 0c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 1l3 3-3 3v-2h-3v-2h3v-2z"></path>
|
||||
</symbol>
|
||||
<symbol id="arrow-circle-top" viewBox="0 0 8 8">
|
||||
<path d="M4 0c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 1l3 3h-2v3h-2v-3h-2l3-3z"></path>
|
||||
</symbol>
|
||||
<symbol id="arrow-left" viewBox="0 0 8 8">
|
||||
<path d="M3 0l-3 2.531 3 2.469v-2h5v-1h-5v-2z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="arrow-right" viewBox="0 0 8 8">
|
||||
<path d="M5 0v2h-5v1h5v2l3-2.531-3-2.469z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="arrow-thick-bottom" viewBox="0 0 8 8">
|
||||
<path d="M2 0v5h-2l3.031 3 2.969-3h-2v-5h-2z" transform="translate(1)"></path>
|
||||
</symbol>
|
||||
<symbol id="arrow-thick-left" viewBox="0 0 8 8">
|
||||
<path d="M3 0l-3 3.031 3 2.969v-2h5v-2h-5v-2z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="arrow-thick-right" viewBox="0 0 8 8">
|
||||
<path d="M5 0v2h-5v2h5v2l3-3.031-3-2.969z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="arrow-thick-top" viewBox="0 0 8 8">
|
||||
<path d="M2.969 0l-2.969 3h2v5h2v-5h2l-3.031-3z" transform="translate(1)"></path>
|
||||
</symbol>
|
||||
<symbol id="arrow-top" viewBox="0 0 8 8">
|
||||
<path d="M2.469 0l-2.469 3h2v5h1v-5h2l-2.531-3z" transform="translate(1)"></path>
|
||||
</symbol>
|
||||
<symbol id="audio-spectrum" viewBox="0 0 8 8">
|
||||
<path d="M4 0v8h1v-8h-1zm-2 1v6h1v-6h-1zm4 1v4h1v-4h-1zm-6 1v2h1v-2h-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="audio" viewBox="0 0 8 8">
|
||||
<path d="M1.188 0c-.734.722-1.188 1.748-1.188 2.844 0 1.095.454 2.09 1.188 2.813l.688-.719c-.546-.538-.875-1.269-.875-2.094s.329-1.587.875-2.125l-.688-.719zm5.625 0l-.688.719c.552.552.875 1.289.875 2.125 0 .836-.327 1.554-.875 2.094l.688.719c.732-.72 1.188-1.708 1.188-2.813 0-1.104-.459-2.115-1.188-2.844zm-4.219 1.406c-.362.362-.594.889-.594 1.438 0 .548.232 1.045.594 1.406l.688-.719c-.178-.178-.281-.416-.281-.688 0-.272.103-.54.281-.719l-.688-.719zm2.813 0l-.688.719c.183.183.281.434.281.719s-.099.505-.281.688l.688.719c.357-.357.594-.851.594-1.406 0-.555-.236-1.08-.594-1.438z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="badge" viewBox="0 0 8 8">
|
||||
<path d="M2 0c-1.105 0-2 .895-2 2s.895 2 2 2 2-.895 2-2-.895-2-2-2zm-1 4.813v3.188l1-1 1 1v-3.188c-.31.11-.65.188-1 .188s-.69-.077-1-.188z" transform="translate(2)"></path>
|
||||
</symbol>
|
||||
<symbol id="ban" viewBox="0 0 8 8">
|
||||
<path d="M4 0c-2.203 0-4 1.797-4 4 0 2.203 1.797 4 4 4 2.203 0 4-1.797 4-4 0-2.203-1.797-4-4-4zm0 1c.655 0 1.258.209 1.75.563l-4.188 4.188c-.353-.492-.563-1.095-.563-1.75 0-1.663 1.337-3 3-3zm2.438 1.25c.353.492.563 1.095.563 1.75 0 1.663-1.337 3-3 3-.655 0-1.258-.209-1.75-.563l4.188-4.188z"></path>
|
||||
</symbol>
|
||||
<symbol id="bar-chart" viewBox="0 0 8 8">
|
||||
<path d="M0 0v7h8v-1h-7v-6h-1zm5 0v5h2v-5h-2zm-3 2v3h2v-3h-2z"></path>
|
||||
</symbol>
|
||||
<symbol id="basket" viewBox="0 0 8 8">
|
||||
<path d="M3.969 0c-.127.011-.259.083-.344.188l-2.344 2.813h-1.281v1h1v3.656c0 .18.164.344.344.344h5.313c.18 0 .344-.164.344-.344v-3.656h1v-1h-1.281c-.274-.329-2.387-2.866-2.406-2.875-.105-.09-.216-.136-.344-.125zm.031 1.281l1.438 1.719h-2.875l1.438-1.719zm-1.5 3.719c.28 0 .5.22.5.5v1c0 .28-.22.5-.5.5s-.5-.22-.5-.5v-1c0-.28.22-.5.5-.5zm3 0c.28 0 .5.22.5.5v1c0 .28-.22.5-.5.5s-.5-.22-.5-.5v-1c0-.28.22-.5.5-.5z"></path>
|
||||
</symbol>
|
||||
<symbol id="battery-empty" viewBox="0 0 8 8">
|
||||
<path d="M.094 0c-.06 0-.094.034-.094.094v5.813c0 .06.034.094.094.094h6.813c.06 0 .094-.034.094-.094v-1.906h1v-2h-1v-1.906c0-.06-.034-.094-.094-.094h-6.813zm.906 1h5v4h-5v-4z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="battery-full" viewBox="0 0 8 8">
|
||||
<path d="M.094 0c-.06 0-.094.034-.094.094v5.813c0 .06.034.094.094.094h6.813c.06 0 .094-.034.094-.094v-1.906h1v-2h-1v-1.906c0-.06-.034-.094-.094-.094h-6.813z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="beaker" viewBox="0 0 8 8">
|
||||
<path d="M1.344 0a.502.502 0 0 0 .156 1h.5v1.406c-.088.172-1.194 2.313-1.656 3.094-.153.268-.344.612-.344 1.063 0 .383.139.764.406 1.031.26.26.643.406 1.031.406h5.125c.383 0 .764-.139 1.031-.406.26-.26.406-.643.406-1.031 0-.452-.194-.801-.344-1.063-.463-.78-1.568-2.922-1.656-3.094v-1.406h.5a.5.5 0 1 0 0-1h-5a.5.5 0 0 0-.094 0 .502.502 0 0 0-.063 0zm1.656 1h2v1.625l.063.094s.652 1.233 1.219 2.281h-4.563c.567-1.049 1.219-2.281 1.219-2.281l.063-.094v-1.625z"></path>
|
||||
</symbol>
|
||||
<symbol id="bell" viewBox="0 0 8 8">
|
||||
<path d="M4 0c-1.1 0-2 .9-2 2 0 1.04-.524 1.976-1.344 2.656-.42.34-.656.824-.656 1.344h8c0-.52-.236-1.004-.656-1.344-.82-.68-1.344-1.616-1.344-2.656 0-1.1-.9-2-2-2zm-1 7c0 .55.45 1 1 1s1-.45 1-1h-2z"></path>
|
||||
</symbol>
|
||||
<symbol id="bluetooth" viewBox="0 0 8 8">
|
||||
<path d="M1.5 0v2.5l-.75-.75-.75.75 1.5 1.5-1.5 1.5.75.75.75-.75v2.5h.5l3.5-2.5-2.25-1.531 2.25-1.469-3.5-2.5h-.5zm1 1.5l1.5 1-1.5 1v-2zm0 3l1.5 1-1.5 1v-2z" transform="translate(1)"></path>
|
||||
</symbol>
|
||||
<symbol id="bold" viewBox="0 0 8 8">
|
||||
<path d="M0 0v1c.55 0 1 .45 1 1v4c0 .55-.45 1-1 1v1h5.5c1.38 0 2.5-1.12 2.5-2.5 0-1-.588-1.85-1.438-2.25.27-.34.438-.78.438-1.25 0-1.1-.9-2-2-2h-5zm3 1h1c.55 0 1 .45 1 1s-.45 1-1 1h-1v-2zm0 3h1.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5h-1.5v-3z"></path>
|
||||
</symbol>
|
||||
<symbol id="bolt" viewBox="0 0 8 8">
|
||||
<path d="M3 0l-3 5h2v3l3-5h-2v-3z" transform="translate(1)"></path>
|
||||
</symbol>
|
||||
<symbol id="book" viewBox="0 0 8 8">
|
||||
<path d="M1 0c-.07 0-.127.001-.188.031-.39.08-.701.391-.781.781-.03.06-.031.118-.031.188v5.5c0 .83.67 1.5 1.5 1.5h5.5v-1h-5.5c-.28 0-.5-.22-.5-.5s.22-.5.5-.5h5.5v-5.5c0-.28-.22-.5-.5-.5h-.5v3l-1-1-1 1v-3h-3z"></path>
|
||||
</symbol>
|
||||
<symbol id="bookmark" viewBox="0 0 8 8">
|
||||
<path d="M0 0v8l2-2 2 2v-8h-4z" transform="translate(2)"></path>
|
||||
</symbol>
|
||||
<symbol id="box" viewBox="0 0 8 8">
|
||||
<path d="M0 0v1h8v-1h-8zm0 2v5.906c0 .06.034.094.094.094h7.813c.06 0 .094-.034.094-.094v-5.906h-2.969v1.031h-2.031v-1.031h-3z"></path>
|
||||
</symbol>
|
||||
<symbol id="briefcase" viewBox="0 0 8 8">
|
||||
<path d="M3 0c-.554 0-1 .458-1 1v1h-1.906c-.06 0-.094.034-.094.094v2.406c0 .28.22.5.5.5h7c.28 0 .5-.22.5-.5v-2.406c0-.06-.034-.094-.094-.094h-1.906v-1c0-.542-.446-1-1-1h-2zm0 1h2v1h-2v-1zm-3 4.906v2c0 .06.034.094.094.094h7.813c.06 0 .094-.034.094-.094v-2c-.16.05-.32.094-.5.094h-7c-.18 0-.34-.044-.5-.094z"></path>
|
||||
</symbol>
|
||||
<symbol id="british-pound" viewBox="0 0 8 8">
|
||||
<path d="M3 0c-.619 0-1.159.262-1.5.688-.341.426-.5.986-.5 1.563 0 .692.165 1.245.25 1.75h-1.25v1h1.219c-.112.448-.37.964-1.063 1.656l-.156.125v1.2189999999999999h6v-1h-4.906c.641-.729.982-1.397 1.125-2h1.781v-1h-1.719c-.078-.683-.281-1.242-.281-1.75 0-.394.115-.731.281-.938.166-.207.368-.313.719-.313.394 0 .609.109.75.25.141.141.25.356.25.75h1c0-.576-.165-1.102-.531-1.469-.366-.366-.893-.531-1.469-.531z" transform="translate(1)"></path>
|
||||
</symbol>
|
||||
<symbol id="browser" viewBox="0 0 8 8">
|
||||
<path d="M.344 0a.5.5 0 0 0-.344.5v7a.5.5 0 0 0 .5.5h7a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.5-.5h-7a.5.5 0 0 0-.094 0 .5.5 0 0 0-.063 0zm1.156 1c.28 0 .5.22.5.5s-.22.5-.5.5-.5-.22-.5-.5.22-.5.5-.5zm2 0h3c.28 0 .5.22.5.5s-.22.5-.5.5h-3c-.28 0-.5-.22-.5-.5s.22-.5.5-.5zm-2.5 2h6v4h-6v-4z"></path>
|
||||
</symbol>
|
||||
<symbol id="brush" viewBox="0 0 8 8">
|
||||
<path d="M7.438.031c-.029-.001-.037.016-.063.031l-3.75 2.656c-.04.03-.095.106-.125.156l-.125.25c.719.229 1.271.781 1.5 1.5l.25-.125c.05-.02.126-.075.156-.125l2.656-3.75c.03-.04.04-.116 0-.156l-.406-.406c-.02-.02-.065-.03-.094-.031zm-4.781 3.969c-.73 0-1.313.614-1.313 1.344 0 .99-.544 1.821-1.344 2.281.4.23.864.375 1.344.375 1.48 0 2.656-1.176 2.656-2.656 0-.73-.604-1.344-1.344-1.344z"></path>
|
||||
</symbol>
|
||||
<symbol id="bug" viewBox="0 0 8 8">
|
||||
<path d="M3.5 0c-1.19 0-1.978 1.69-1.188 2.5-.09.07-.196.137-.281.219l-1.313-.656a.5.5 0 0 0-.344-.063.5.5 0 0 0-.094.938l1.156.563c-.09.156-.186.328-.25.5h-.688a.5.5 0 0 0-.094 0 .502.502 0 1 0 .094 1h.5c0 .227.023.445.063.656l-.781.406a.5.5 0 1 0 .438.875l.656-.344c.245.46.59.844 1 1.094.35-.19.625-.439.625-.719v-1.438a.5.5 0 0 0 0-.094v-.813a.5.5 0 0 0 0-.219c.045-.231.254-.406.5-.406.28 0 .5.22.5.5v.875a.5.5 0 0 0 0 .094v.063a.5.5 0 0 0 0 .094v1.344c0 .27.275.497.625.688.41-.245.755-.604 1-1.063l.656.344a.5.5 0 1 0 .438-.875l-.781-.406c.04-.211.063-.429.063-.656h.5a.5.5 0 1 0 0-1h-.688c-.064-.172-.16-.344-.25-.5l1.156-.563a.5.5 0 0 0-.313-.938.5.5 0 0 0-.125.063l-1.313.656c-.086-.082-.191-.149-.281-.219.78-.83.003-2.5-1.188-2.5z"></path>
|
||||
</symbol>
|
||||
<symbol id="bullhorn" viewBox="0 0 8 8">
|
||||
<path d="M6.094 0c-.03 0-.06.022-.094.031v5.969c.033.007.065 0 .094 0h.813c.06 0 .094-.034.094-.094v-5.813c0-.06-.034-.094-.094-.094h-.813zm-1.094.5l-2.906 1.469c-.05.02-.127.031-.188.031h-1.813c-.06 0-.094.034-.094.094v1.813c0 .06.034.094.094.094h.906l1.031 2.719c.11.25.406.36.656.25.25-.11.36-.406.25-.656l-.719-1.781c.033-.136.136-.25.281-.25v-.031l2.5 1.25v-5z"></path>
|
||||
</symbol>
|
||||
<symbol id="calculator" viewBox="0 0 8 8">
|
||||
<path d="M.094 0c-.06 0-.094.034-.094.094v7.813c0 .06.034.094.094.094h6.813c.06 0 .094-.034.094-.094v-7.813c0-.06-.034-.094-.094-.094h-6.813zm.906 1h5v2h-5v-2zm0 3h1v1h-1v-1zm2 0h1v1h-1v-1zm2 0h1v3h-1v-3zm-4 2h1v1h-1v-1zm2 0h1v1h-1v-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="calendar" viewBox="0 0 8 8">
|
||||
<path d="M0 0v2h7v-2h-7zm0 3v4.906c0 .06.034.094.094.094h6.813c.06 0 .094-.034.094-.094v-4.906h-7zm1 1h1v1h-1v-1zm2 0h1v1h-1v-1zm2 0h1v1h-1v-1zm-4 2h1v1h-1v-1zm2 0h1v1h-1v-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="camera-slr" viewBox="0 0 8 8">
|
||||
<path d="M4.094 0c-.06 0-.105.044-.125.094l-.938 1.813c-.02.05-.065.094-.125.094h-1.406c-.83 0-1.5.67-1.5 1.5v4.406c0 .06.034.094.094.094h7.813c.06 0 .094-.034.094-.094v-5.813c0-.06-.034-.094-.094-.094h-.813c-.06 0-.105-.044-.125-.094l-.938-1.813c-.02-.05-.065-.094-.125-.094h-1.813zm-2.594 3c.28 0 .5.22.5.5s-.22.5-.5.5-.5-.22-.5-.5.22-.5.5-.5zm3.5 0c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zm0 1c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="caret-bottom" viewBox="0 0 8 8">
|
||||
<path d="M0 0l4 4 4-4h-8z" transform="translate(0 2)"></path>
|
||||
</symbol>
|
||||
<symbol id="caret-left" viewBox="0 0 8 8">
|
||||
<path d="M4 0l-4 4 4 4v-8z" transform="translate(2)"></path>
|
||||
</symbol>
|
||||
<symbol id="caret-right" viewBox="0 0 8 8">
|
||||
<path d="M0 0v8l4-4-4-4z" transform="translate(2)"></path>
|
||||
</symbol>
|
||||
<symbol id="caret-top" viewBox="0 0 8 8">
|
||||
<path d="M4 0l-4 4h8l-4-4z" transform="translate(0 2)"></path>
|
||||
</symbol>
|
||||
<symbol id="cart" viewBox="0 0 8 8">
|
||||
<path d="M.344 0a.502.502 0 0 0 .156 1h1.5l.094.25.406 1.25.406 1.25c.04.13.204.25.344.25h3.5c.14 0 .304-.12.344-.25l.813-2.531c.04-.12-.016-.219-.156-.219h-4.438l-.375-.719a.5.5 0 0 0-.438-.281h-2a.5.5 0 0 0-.094 0 .502.502 0 0 0-.063 0zm3.156 5c-.276 0-.5.224-.5.5s.224.5.5.5.5-.224.5-.5-.224-.5-.5-.5zm3 0c-.276 0-.5.224-.5.5s.224.5.5.5.5-.224.5-.5-.224-.5-.5-.5z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="chat" viewBox="0 0 8 8">
|
||||
<path d="M0 0v5l1-1h1v-3h3v-1h-5zm3 2v4h4l1 1v-5h-5z"></path>
|
||||
</symbol>
|
||||
<symbol id="check" viewBox="0 0 8 8">
|
||||
<path d="M6.406 0l-.719.688-2.781 2.781-.781-.781-.719-.688-1.406 1.406.688.719 1.5 1.5.719.688.719-.688 3.5-3.5.688-.719-1.406-1.406z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="chevron-bottom" viewBox="0 0 8 8">
|
||||
<path d="M1.5 0l-1.5 1.5 4 4 4-4-1.5-1.5-2.5 2.5-2.5-2.5z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="chevron-left" viewBox="0 0 8 8">
|
||||
<path d="M4 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z" transform="translate(1)"></path>
|
||||
</symbol>
|
||||
<symbol id="chevron-right" viewBox="0 0 8 8">
|
||||
<path d="M1.5 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z" transform="translate(1)"></path>
|
||||
</symbol>
|
||||
<symbol id="chevron-top" viewBox="0 0 8 8">
|
||||
<path d="M4 0l-4 4 1.5 1.5 2.5-2.5 2.5 2.5 1.5-1.5-4-4z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="circle-check" viewBox="0 0 8 8">
|
||||
<path d="M4 0c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm2 1.781l.719.719-3.219 3.219-1.719-1.719.719-.719 1 1 2.5-2.5z"></path>
|
||||
</symbol>
|
||||
<symbol id="circle-x" viewBox="0 0 8 8">
|
||||
<path d="M4 0c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm-1.5 1.781l1.5 1.5 1.5-1.5.719.719-1.5 1.5 1.5 1.5-.719.719-1.5-1.5-1.5 1.5-.719-.719 1.5-1.5-1.5-1.5.719-.719z"></path>
|
||||
</symbol>
|
||||
<symbol id="clipboard" viewBox="0 0 8 8">
|
||||
<path d="M3.5 0c-.28 0-.5.22-.5.5v.5h-.75c-.14 0-.25.11-.25.25v.75h3v-.75c0-.14-.11-.25-.25-.25h-.75v-.5c0-.28-.22-.5-.5-.5zm-3.25 1c-.14 0-.25.11-.25.25v6.5c0 .14.11.25.25.25h6.5c.14 0 .25-.11.25-.25v-6.5c0-.14-.11-.25-.25-.25h-.75v2h-5v-2h-.75z"></path>
|
||||
</symbol>
|
||||
<symbol id="clock" viewBox="0 0 8 8">
|
||||
<path d="M4 0c-2.203 0-4 1.797-4 4 0 2.203 1.797 4 4 4 2.203 0 4-1.797 4-4 0-2.203-1.797-4-4-4zm0 1c1.663 0 3 1.337 3 3s-1.337 3-3 3-3-1.337-3-3 1.337-3 3-3zm-.5 1v2.219l.156.125.5.5.344.375.719-.719-.375-.344-.344-.344v-1.813h-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="cloud-download" viewBox="0 0 8 8">
|
||||
<path d="M4.5 0c-1.21 0-2.27.86-2.5 2-1.1 0-2 .9-2 2 0 .37.111.7.281 1h2.719v-.5c0-.83.67-1.5 1.5-1.5s1.5.67 1.5 1.5v.5h1.906c.05-.16.094-.32.094-.5 0-.65-.42-1.29-1-1.5v-.5c0-1.38-1.12-2.5-2.5-2.5zm-.156 4a.5.5 0 0 0-.344.5v1.5h-1.5l2 2 2-2h-1.5v-1.5a.5.5 0 0 0-.594-.5.5.5 0 0 0-.063 0z"></path>
|
||||
</symbol>
|
||||
<symbol id="cloud-upload" viewBox="0 0 8 8">
|
||||
<path d="M4.5 0c-1.21 0-2.27.86-2.5 2-1.1 0-2 .9-2 2 0 .37.111.7.281 1h2.219l2-2 2 2h1.406c.05-.16.094-.32.094-.5 0-.65-.42-1.29-1-1.5v-.5c0-1.38-1.12-2.5-2.5-2.5zm0 4.5l-2.5 2.5h2v.5a.5.5 0 1 0 1 0v-.5h2l-2.5-2.5z"></path>
|
||||
</symbol>
|
||||
<symbol id="cloud" viewBox="0 0 8 8">
|
||||
<path d="M4.5 0c-1.21 0-2.27.86-2.5 2-1.1 0-2 .9-2 2s.9 2 2 2h4.5c.83 0 1.5-.67 1.5-1.5 0-.65-.42-1.29-1-1.5v-.5c0-1.38-1.12-2.5-2.5-2.5z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="cloudy" viewBox="0 0 8 8">
|
||||
<path d="M2.5 0c-1.38 0-2.5 1.12-2.5 2.5 0 .39.09.743.25 1.063.3-.21.63-.379 1-.469.55-1.25 1.82-2.084 3.25-2.094-.46-.6-1.18-1-2-1zm2 2c-1.21 0-2.27.86-2.5 2-1.1 0-2 .9-2 2s.9 2 2 2h4.5c.83 0 1.5-.67 1.5-1.5 0-.65-.42-1.29-1-1.5v-.5c0-1.38-1.12-2.5-2.5-2.5z"></path>
|
||||
</symbol>
|
||||
<symbol id="code" viewBox="0 0 8 8">
|
||||
<path d="M5 0l-3 6h1l3-6h-1zm-4 1l-1 2 1 2h1l-1-2 1-2h-1zm5 0l1 2-1 2h1l1-2-1-2h-1z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="cog" viewBox="0 0 8 8">
|
||||
<path d="M3.5 0l-.5 1.188c-.1.04-.191.085-.281.125l-1.188-.5-.719.719.5 1.188c-.05.1-.095.181-.125.281l-1.188.5v1l1.188.5c.03.1.075.213.125.313l-.5 1.156.719.719 1.188-.5c.1.05.181.085.281.125l.5 1.188h1l.5-1.188c.1-.03.191-.085.281-.125l1.188.5.719-.719-.5-1.188c.04-.09.085-.181.125-.281l1.188-.5v-1l-1.188-.5c-.03-.09-.075-.191-.125-.281l.469-1.188-.688-.719-1.188.5c-.09-.04-.181-.095-.281-.125l-.5-1.188h-1zm.5 2.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5-1.5-.67-1.5-1.5.67-1.5 1.5-1.5z"></path>
|
||||
</symbol>
|
||||
<symbol id="collapse-down" viewBox="0 0 8 8">
|
||||
<path d="M0 0v2h8v-2h-8zm2 3l2 2 2-2h-4zm-2 4v1h8v-1h-8z"></path>
|
||||
</symbol>
|
||||
<symbol id="collapse-left" viewBox="0 0 8 8">
|
||||
<path d="M0 0v8h1v-8h-1zm6 0v8h2v-8h-2zm-1 2l-2 2 2 2v-4z"></path>
|
||||
</symbol>
|
||||
<symbol id="collapse-right" viewBox="0 0 8 8">
|
||||
<path d="M0 0v8h2v-8h-2zm7 0v8h1v-8h-1zm-4 2v4l2-2-2-2z"></path>
|
||||
</symbol>
|
||||
<symbol id="collapse-up" viewBox="0 0 8 8">
|
||||
<path d="M0 0v1h8v-1h-8zm4 3l-2 2h4l-2-2zm-4 3v2h8v-2h-8z"></path>
|
||||
</symbol>
|
||||
<symbol id="command" viewBox="0 0 8 8">
|
||||
<path d="M1.5 0c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5h.5v1h-.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5v-.5h1v.5c0 .83.67 1.5 1.5 1.5s1.5-.67 1.5-1.5-.67-1.5-1.5-1.5h-.5v-1h.5c.83 0 1.5-.67 1.5-1.5s-.67-1.5-1.5-1.5-1.5.67-1.5 1.5v.5h-1v-.5c0-.83-.67-1.5-1.5-1.5zm0 1c.28 0 .5.22.5.5v.5h-.5c-.28 0-.5-.22-.5-.5s.22-.5.5-.5zm4 0c.28 0 .5.22.5.5s-.22.5-.5.5h-.5v-.5c0-.28.22-.5.5-.5zm-2.5 2h1v1h-1v-1zm-1.5 2h.5v.5c0 .28-.22.5-.5.5s-.5-.22-.5-.5.22-.5.5-.5zm3.5 0h.5c.28 0 .5.22.5.5s-.22.5-.5.5-.5-.22-.5-.5v-.5z"></path>
|
||||
</symbol>
|
||||
<symbol id="comment-square" viewBox="0 0 8 8">
|
||||
<path d="M.094 0c-.06 0-.094.034-.094.094v5.813c0 .06.034.094.094.094h5.906l2 2v-7.906000000000001c0-.06-.034-.094-.094-.094h-7.813z"></path>
|
||||
</symbol>
|
||||
<symbol id="compass" viewBox="0 0 8 8">
|
||||
<path d="M4 0c-2.203 0-4 1.797-4 4 0 2.203 1.797 4 4 4 2.203 0 4-1.797 4-4 0-2.203-1.797-4-4-4zm0 1c1.663 0 3 1.337 3 3s-1.337 3-3 3-3-1.337-3-3 1.337-3 3-3zm2 1l-3 1-1 3 3-1 1-3zm-2 1.5c.28 0 .5.22.5.5s-.22.5-.5.5-.5-.22-.5-.5.22-.5.5-.5z"></path>
|
||||
</symbol>
|
||||
<symbol id="contrast" viewBox="0 0 8 8">
|
||||
<path d="M4 0c-2.203 0-4 1.797-4 4 0 2.203 1.797 4 4 4 2.203 0 4-1.797 4-4 0-2.203-1.797-4-4-4zm0 1c1.663 0 3 1.337 3 3s-1.337 3-3 3v-6z"></path>
|
||||
</symbol>
|
||||
<symbol id="copywriting" viewBox="0 0 8 8">
|
||||
<path d="M0 0v1h8v-1h-8zm0 2v1h5v-1h-5zm0 3v1h8v-1h-8zm0 2v1h6v-1h-6zm7.5 0c-.276 0-.5.224-.5.5s.224.5.5.5.5-.224.5-.5-.224-.5-.5-.5z"></path>
|
||||
</symbol>
|
||||
<symbol id="credit-card" viewBox="0 0 8 8">
|
||||
<path d="M.25 0c-.14 0-.25.11-.25.25v.75h8v-.75c0-.14-.11-.25-.25-.25h-7.5zm-.25 2v3.75c0 .14.11.25.25.25h7.5c.14 0 .25-.11.25-.25v-3.75h-8zm1 2h1v1h-1v-1zm2 0h1v1h-1v-1z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="crop" viewBox="0 0 8 8">
|
||||
<path d="M1 0v1h-1v1h1v5h5v1h1v-1h1v-1h-1v-4.5l1-1-.5-.5-1 1h-4.5v-1h-1zm1 2h3.5l-3.5 3.5v-3.5zm4 .5v3.5h-3.5l3.5-3.5z"></path>
|
||||
</symbol>
|
||||
<symbol id="dashboard" viewBox="0 0 8 8">
|
||||
<path d="M4 0c-2.203 0-4 1.797-4 4 0 2.203 1.797 4 4 4 2.203 0 4-1.797 4-4 0-2.203-1.797-4-4-4zm0 1c1.663 0 3 1.337 3 3s-1.337 3-3 3-3-1.337-3-3 1.337-3 3-3zm0 1c-.276 0-.5.224-.5.5s.224.5.5.5.5-.224.5-.5-.224-.5-.5-.5zm-1.656 1a.5.5 0 0 0-.188.844l.906.906c-.023.085-.063.158-.063.25 0 .552.448 1 1 1s1-.448 1-1-.448-1-1-1c-.092 0-.165.039-.25.063l-.906-.906a.5.5 0 0 0-.438-.156.5.5 0 0 0-.063 0zm3.156 0c-.276 0-.5.224-.5.5s.224.5.5.5.5-.224.5-.5-.224-.5-.5-.5z"></path>
|
||||
</symbol>
|
||||
<symbol id="data-transfer-download" viewBox="0 0 8 8">
|
||||
<path d="M3 0v3h-2l3 3 3-3h-2v-3h-2zm-3 7v1h8v-1h-8z"></path>
|
||||
</symbol>
|
||||
<symbol id="data-transfer-upload" viewBox="0 0 8 8">
|
||||
<path d="M0 0v1h8v-1h-8zm4 2l-3 3h2v3h2v-3h2l-3-3z"></path>
|
||||
</symbol>
|
||||
<symbol id="delete" viewBox="0 0 8 8">
|
||||
<path d="M2 0l-2 3 2 3h6v-6h-6zm1.5.781l1.5 1.5 1.5-1.5.719.719-1.5 1.5 1.5 1.5-.719.719-1.5-1.5-1.5 1.5-.719-.719 1.5-1.5-1.5-1.5.719-.719z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="dial" viewBox="0 0 8 8">
|
||||
<path d="M4 0c-2.201 0-4 1.799-4 4h1c0-1.659 1.341-3 3-3s3 1.341 3 3h1c0-2.201-1.799-4-4-4zm-.594 2.094c-.82.25-1.406 1.006-1.406 1.906 0 1.1.9 2 2 2s2-.9 2-2c0-.9-.586-1.656-1.406-1.906l-.594.875-.594-.875z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="document" viewBox="0 0 8 8">
|
||||
<path d="M0 0v8h7v-4h-4v-4h-3zm4 0v3h3l-3-3zm-3 2h1v1h-1v-1zm0 2h1v1h-1v-1zm0 2h4v1h-4v-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="dollar" viewBox="0 0 8 8">
|
||||
<path d="M2 0v1h-.75c-.686 0-1.25.564-1.25 1.25v.5c0 .678.437 1.242 1.094 1.406l2.563.656c.143.036.344.296.344.438v.5c0 .134-.116.25-.25.25h-2.5c-.116 0-.212-.037-.25-.063v-.938h-1v1c0 .342.203.627.438.781.234.155.518.219.813.219h.75v1h1v-1h.75c.686 0 1.25-.564 1.25-1.25v-.5c0-.678-.437-1.242-1.094-1.406l-2.563-.656c-.143-.036-.344-.296-.344-.438v-.5c0-.134.116-.25.25-.25h2.5c.116 0 .212.037.25.063v.938h1v-1c0-.342-.203-.627-.438-.781-.234-.155-.518-.219-.813-.219h-.75v-1h-1z" transform="translate(1)"></path>
|
||||
</symbol>
|
||||
<symbol id="double-quote-sans-left" viewBox="0 0 8 8">
|
||||
<path d="M0 0v6l3-3v-3h-3zm5 0v6l3-3v-3h-3z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="double-quote-sans-right" viewBox="0 0 8 8">
|
||||
<path d="M3 0l-3 3v3h3v-6zm5 0l-3 3v3h3v-6z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="double-quote-serif-left" viewBox="0 0 8 8">
|
||||
<path d="M3 0c-1.651 0-3 1.349-3 3v3h3v-3h-2c0-1.109.891-2 2-2v-1zm5 0c-1.651 0-3 1.349-3 3v3h3v-3h-2c0-1.109.891-2 2-2v-1z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="double-quote-serif-right" viewBox="0 0 8 8">
|
||||
<path d="M0 0v3h2c0 1.109-.891 2-2 2v1c1.651 0 3-1.349 3-3v-3h-3zm5 0v3h2c0 1.109-.891 2-2 2v1c1.651 0 3-1.349 3-3v-3h-3z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="droplet" viewBox="0 0 8 8">
|
||||
<path d="M3 0l-.344.344c-.11.11-2.656 2.685-2.656 4.875 0 1.65 1.35 3 3 3s3-1.35 3-3c0-2.19-2.546-4.765-2.656-4.875l-.344-.344zm-1.5 4.719c.28 0 .5.22.5.5 0 .55.45 1 1 1 .28 0 .5.22.5.5s-.22.5-.5.5c-1.1 0-2-.9-2-2 0-.28.22-.5.5-.5z" transform="translate(1)"></path>
|
||||
</symbol>
|
||||
<symbol id="eject" viewBox="0 0 8 8">
|
||||
<path d="M4 0l-4 5h8l-4-5zm-4 6v2h8v-2h-8z"></path>
|
||||
</symbol>
|
||||
<symbol id="elevator" viewBox="0 0 8 8">
|
||||
<path d="M3 0l-3 3h6l-3-3zm-3 5l3 3 3-3h-6z" transform="translate(1)"></path>
|
||||
</symbol>
|
||||
<symbol id="ellipses" viewBox="0 0 8 8">
|
||||
<path d="M0 0v2h2v-2h-2zm3 0v2h2v-2h-2zm3 0v2h2v-2h-2z" transform="translate(0 3)"></path>
|
||||
</symbol>
|
||||
<symbol id="envelope-closed" viewBox="0 0 8 8">
|
||||
<path d="M0 0v1l4 2 4-2v-1h-8zm0 2v4h8v-4l-4 2-4-2z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="envelope-open" viewBox="0 0 8 8">
|
||||
<path d="M4 0l-4 2v6h8v-6l-4-2zm0 1.125l3 1.5v1.875l-3 1.5-3-1.5v-1.875l3-1.5zm-2 1.875v1l2 1 2-1v-1h-4z"></path>
|
||||
</symbol>
|
||||
<symbol id="euro" viewBox="0 0 8 8">
|
||||
<path d="M6 0c-1.858 0-3.398 1.278-3.844 3h-1.906l-.25 1h2c0 .345.073.68.156 1h-1.969l-.188 1h2.563c.696 1.185 1.969 2 3.438 2 .734 0 1.407-.215 2-.563v-1.219c-.531.479-1.225.781-2 .781-.888 0-1.671-.392-2.219-1h2.219l.156-1h-2.969c-.113-.317-.188-.643-.188-1h3.344l.156-1h-3.313c.414-1.16 1.507-2 2.813-2 .655 0 1.258.209 1.75.563l.156-1.063c-.57-.313-1.213-.5-1.906-.5z" transform="translate(-1)"></path>
|
||||
</symbol>
|
||||
<symbol id="excerpt" viewBox="0 0 8 8">
|
||||
<path d="M0 0v1h7v-1h-7zm0 2v1h5v-1h-5zm0 2v1h8v-1h-8zm0 2v1h1v-1h-1zm2 0v1h1v-1h-1zm2 0v1h1v-1h-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="expand-down" viewBox="0 0 8 8">
|
||||
<path d="M0 0v1h8v-1h-8zm2 2l2 2 2-2h-4zm-2 4v2h8v-2h-8z"></path>
|
||||
</symbol>
|
||||
<symbol id="expand-left" viewBox="0 0 8 8">
|
||||
<path d="M0 0v8h1v-8h-1zm6 0v8h2v-8h-2zm-4 2v4l2-2-2-2z"></path>
|
||||
</symbol>
|
||||
<symbol id="expand-right" viewBox="0 0 8 8">
|
||||
<path d="M0 0v8h2v-8h-2zm7 0v8h1v-8h-1zm-1 2l-2 2 2 2v-4z"></path>
|
||||
</symbol>
|
||||
<symbol id="expand-up" viewBox="0 0 8 8">
|
||||
<path d="M0 0v2h8v-2h-8zm4 4l-2 2h4l-2-2zm-4 3v1h8v-1h-8z"></path>
|
||||
</symbol>
|
||||
<symbol id="external-link" viewBox="0 0 8 8">
|
||||
<path d="M0 0v8h8v-2h-1v1h-6v-6h1v-1h-2zm4 0l1.5 1.5-2.5 2.5 1 1 2.5-2.5 1.5 1.5v-4h-4z"></path>
|
||||
</symbol>
|
||||
<symbol id="eye" viewBox="0 0 8 8">
|
||||
<path d="M4.031 0c-2.53 0-4.031 3-4.031 3s1.501 3 4.031 3c2.47 0 3.969-3 3.969-3s-1.499-3-3.969-3zm-.031 1c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zm0 1c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1c0-.1-.032-.191-.063-.281-.08.16-.237.281-.438.281-.28 0-.5-.22-.5-.5 0-.2.121-.357.281-.438-.09-.03-.181-.063-.281-.063z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="eyedropper" viewBox="0 0 8 8">
|
||||
<path d="M3.313 0a.5.5 0 0 0-.188.844l.625.625-3.594 3.656-.156.156v2.7190000000000003h2.719l.125-.156 3.656-3.656.625.656a.5.5 0 1 0 .719-.688l-.938-.938.656-.656c.59-.58.59-1.545 0-2.125-.56-.57-1.555-.57-2.125 0l-.656.656-.938-.938a.5.5 0 0 0-.469-.156.5.5 0 0 0-.063 0zm1.156 2.188l1.313 1.313-3.156 3.156-1.281-1.313 3.125-3.156z"></path>
|
||||
</symbol>
|
||||
<symbol id="file" viewBox="0 0 8 8">
|
||||
<path d="M0 0v8h7v-4h-4v-4h-3zm4 0v3h3l-3-3z"></path>
|
||||
</symbol>
|
||||
<symbol id="fire" viewBox="0 0 8 8">
|
||||
<path d="M2 0c1 2-2 3-2 5s2 3 2 3c-.98-1.98 2-3 2-5s-2-3-2-3zm3 3c1 2-2 3-2 5h3c.4 0 1-.5 1-2 0-2-2-3-2-3z"></path>
|
||||
</symbol>
|
||||
<symbol id="flag" viewBox="0 0 8 8">
|
||||
<path d="M0 0v8h1v-8h-1zm2 0v4h2v1h4l-2-1.969 2-2.031h-3v-1h-3z"></path>
|
||||
</symbol>
|
||||
<symbol id="flash" viewBox="0 0 8 8">
|
||||
<path d="M1.5 0l-1.5 3h2l-.656 2h-1.344l1 3 3-3h-1.5l1.5-3h-2l1-2h-1.5z" transform="translate(2)"></path>
|
||||
</symbol>
|
||||
<symbol id="folder" viewBox="0 0 8 8">
|
||||
<path d="M0 0v2h8v-1h-5v-1h-3zm0 3v4.5c0 .28.22.5.5.5h7c.28 0 .5-.22.5-.5v-4.5h-8z"></path>
|
||||
</symbol>
|
||||
<symbol id="fork" viewBox="0 0 8 8">
|
||||
<path d="M1.5 0c-.828 0-1.5.672-1.5 1.5 0 .656.414 1.202 1 1.406v2.188c-.586.204-1 .75-1 1.406 0 .828.672 1.5 1.5 1.5s1.5-.672 1.5-1.5c0-.595-.341-1.101-.844-1.344.09-.09.205-.156.344-.156h2c.823 0 1.5-.677 1.5-1.5v-.594c.586-.204 1-.75 1-1.406 0-.828-.672-1.5-1.5-1.5s-1.5.672-1.5 1.5c0 .656.414 1.202 1 1.406v.594c0 .277-.223.5-.5.5h-2c-.171 0-.346.04-.5.094v-1.188c.586-.204 1-.75 1-1.406 0-.828-.672-1.5-1.5-1.5z"></path>
|
||||
</symbol>
|
||||
<symbol id="fullscreen-enter" viewBox="0 0 8 8">
|
||||
<path d="M0 0v4l1.5-1.5 1.5 1.5 1-1-1.5-1.5 1.5-1.5h-4zm5 4l-1 1 1.5 1.5-1.5 1.5h4v-4l-1.5 1.5-1.5-1.5z"></path>
|
||||
</symbol>
|
||||
<symbol id="fullscreen-exit" viewBox="0 0 8 8">
|
||||
<path d="M1 0l-1 1 1.5 1.5-1.5 1.5h4v-4l-1.5 1.5-1.5-1.5zm3 4v4l1.5-1.5 1.5 1.5 1-1-1.5-1.5 1.5-1.5h-4z"></path>
|
||||
</symbol>
|
||||
<symbol id="globe" viewBox="0 0 8 8">
|
||||
<path d="M4 0c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 1c.333 0 .637.086.938.188-.214.197-.45.383-.406.563.04.18.688.13.688.5 0 .27-.425.346-.125.656.35.35-.636.978-.656 1.438-.03.83.841.969 1.531.969.424 0 .503.195.469.438-.546.758-1.438 1.25-2.438 1.25-.378 0-.729-.09-1.063-.219.224-.442-.313-1.344-.781-1.625-.226-.226-.689-.114-.969-.219-.092-.271-.178-.545-.188-.844.031-.05.081-.094.156-.094.19 0 .454.374.594.344.18-.04-.742-1.313-.313-1.563.2-.12.609.394.469-.156-.12-.51.366-.276.656-.406.26-.11.455-.414.125-.594-.057-.031-.133-.104-.219-.188.45-.27.972-.438 1.531-.438zm2.313 1.094c.184.222.323.481.438.75-.043.065-.083.114-.188.219-.29.27-.327-.212-.438-.313-.13-.11-.638.025-.688-.125-.077-.181.499-.418.875-.531z"></path>
|
||||
</symbol>
|
||||
<symbol id="graph" viewBox="0 0 8 8">
|
||||
<path d="M7.031 0l-3.031 3-1-1-3 3.031 1 1 2-2.031 1 1 4-4-.969-1zm-7.031 7v1h8v-1h-8z"></path>
|
||||
</symbol>
|
||||
<symbol id="grid-four-up" viewBox="0 0 8 8">
|
||||
<path d="M0 0v1h1v-1h-1zm2 0v1h1v-1h-1zm2 0v1h1v-1h-1zm2 0v1h1v-1h-1zm-6 2v1h1v-1h-1zm2 0v1h1v-1h-1zm2 0v1h1v-1h-1zm2 0v1h1v-1h-1zm-6 2v1h1v-1h-1zm2 0v1h1v-1h-1zm2 0v1h1v-1h-1zm2 0v1h1v-1h-1zm-6 2v1h1v-1h-1zm2 0v1h1v-1h-1zm2 0v1h1v-1h-1zm2 0v1h1v-1h-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="grid-three-up" viewBox="0 0 8 8">
|
||||
<path d="M0 0v2h2v-2h-2zm3 0v2h2v-2h-2zm3 0v2h2v-2h-2zm-6 3v2h2v-2h-2zm3 0v2h2v-2h-2zm3 0v2h2v-2h-2zm-6 3v2h2v-2h-2zm3 0v2h2v-2h-2zm3 0v2h2v-2h-2z"></path>
|
||||
</symbol>
|
||||
<symbol id="grid-two-up" viewBox="0 0 8 8">
|
||||
<path d="M0 0v3h3v-3h-3zm5 0v3h3v-3h-3zm-5 5v3h3v-3h-3zm5 0v3h3v-3h-3z"></path>
|
||||
</symbol>
|
||||
<symbol id="hard-drive" viewBox="0 0 8 8">
|
||||
<path d="M.188 0c-.11 0-.188.077-.188.188v3.313c0 .28.22.5.5.5h6c.28 0 .5-.22.5-.5v-3.313c0-.11-.077-.188-.188-.188h-6.625zm-.188 4.906v2.906c0 .11.077.188.188.188h6.625c.11 0 .188-.077.188-.188v-2.906c-.16.05-.32.094-.5.094h-6c-.18 0-.34-.044-.5-.094zm5.5 1.094c.28 0 .5.22.5.5s-.22.5-.5.5-.5-.22-.5-.5.22-.5.5-.5z"></path>
|
||||
</symbol>
|
||||
<symbol id="header" viewBox="0 0 8 8">
|
||||
<path d="M0 0v1h.5c.28 0 .5.22.5.5v4c0 .28-.22.5-.5.5h-.5v1h3v-1h-.5c-.28 0-.5-.22-.5-.5v-1.5h3v1.5c0 .28-.22.5-.5.5h-.5v1h3v-1h-.5c-.28 0-.5-.22-.5-.5v-4c0-.28.22-.5.5-.5h.5v-1h-3v1h.5c.28 0 .5.22.5.5v1.5h-3v-1.5c0-.28.22-.5.5-.5h.5v-1h-3z"></path>
|
||||
</symbol>
|
||||
<symbol id="headphones" viewBox="0 0 8 8">
|
||||
<path d="M4 0c-1.651 0-3 1.349-3 3v1h-.5a.5.5 0 0 0-.5.5v2a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-3.5c0-1.109.891-2 2-2s2 .891 2 2v3.5a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-2a.5.5 0 0 0-.5-.5h-.5v-1c0-1.651-1.349-3-3-3z"></path>
|
||||
</symbol>
|
||||
<symbol id="heart" viewBox="0 0 8 8">
|
||||
<path d="M2 0c-.55 0-1.046.224-1.406.594-.37.36-.594.856-.594 1.406 0 .55.224 1.046.594 1.406l3.406 3.438 3.406-3.438c.37-.37.594-.856.594-1.406 0-.55-.224-1.046-.594-1.406-.36-.37-.856-.594-1.406-.594-.55 0-1.046.224-1.406.594-.37.36-.594.856-.594 1.406 0-.55-.224-1.046-.594-1.406-.36-.37-.856-.594-1.406-.594z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="home" viewBox="0 0 8 8">
|
||||
<path d="M4 0l-4 3h1v4h2v-2h2v2h2v-4.031l1 .031-4-3z"></path>
|
||||
</symbol>
|
||||
<symbol id="image" viewBox="0 0 8 8">
|
||||
<path d="M0 0v8h8v-8h-8zm1 1h6v3l-1-1-1 1 2 2v1h-1l-4-4-1 1v-3z"></path>
|
||||
</symbol>
|
||||
<symbol id="inbox" viewBox="0 0 8 8">
|
||||
<path d="M.188 0c-.11 0-.188.077-.188.188v7.625c0 .11.077.188.188.188h7.625c.11 0 .188-.077.188-.188v-7.625c0-.11-.077-.188-.188-.188h-7.625zm.813 2h6v3h-1l-1 1h-2l-1-1h-1v-3z"></path>
|
||||
</symbol>
|
||||
<symbol id="infinity" viewBox="0 0 8 8">
|
||||
<path d="M2 0c-1.31 0-2 1.01-2 2s.69 2 2 2c.79 0 1.42-.559 2-1.219.58.66 1.19 1.219 2 1.219 1.31 0 2-1.01 2-2s-.69-2-2-2c-.81 0-1.42.559-2 1.219-.57-.66-1.21-1.219-2-1.219zm0 1c.42 0 .884.47 1.344 1-.46.53-.924 1-1.344 1-.74 0-1-.54-1-1 0-.46.26-1 1-1zm4 0c.74 0 1 .54 1 1 0 .46-.26 1-1 1-.43 0-.894-.47-1.344-1 .45-.53.914-1 1.344-1z" transform="translate(0 2)"></path>
|
||||
</symbol>
|
||||
<symbol id="info" viewBox="0 0 8 8">
|
||||
<path d="M3 0c-.552 0-1 .448-1 1s.448 1 1 1 1-.448 1-1-.448-1-1-1zm-1.5 2.5c-.83 0-1.5.67-1.5 1.5h1c0-.28.22-.5.5-.5s.5.22.5.5-1 1.64-1 2.5c0 .86.67 1.5 1.5 1.5s1.5-.67 1.5-1.5h-1c0 .28-.22.5-.5.5s-.5-.22-.5-.5c0-.36 1-1.84 1-2.5 0-.81-.67-1.5-1.5-1.5z" transform="translate(2)"></path>
|
||||
</symbol>
|
||||
<symbol id="italic" viewBox="0 0 8 8">
|
||||
<path d="M2 0v1h1.625l-.063.125-2 5-.344.875h-1.219v1h5v-1h-1.625l.063-.125 2-5 .344-.875h1.219v-1h-5z"></path>
|
||||
</symbol>
|
||||
<symbol id="justify-center" viewBox="0 0 8 8">
|
||||
<path d="M0 0v1h8v-1h-8zm0 2v1h8v-1h-8zm0 2v1h8v-1h-8zm1 2v1h6v-1h-6z"></path>
|
||||
</symbol>
|
||||
<symbol id="justify-left" viewBox="0 0 8 8">
|
||||
<path d="M0 0v1h8v-1h-8zm0 2v1h8v-1h-8zm0 2v1h8v-1h-8zm0 2v1h6v-1h-6z"></path>
|
||||
</symbol>
|
||||
<symbol id="justify-right" viewBox="0 0 8 8">
|
||||
<path d="M0 0v1h8v-1h-8zm0 2v1h8v-1h-8zm0 2v1h8v-1h-8zm2 2v1h6v-1h-6z"></path>
|
||||
</symbol>
|
||||
<symbol id="key" viewBox="0 0 8 8">
|
||||
<path d="M5.5 0c-1.38 0-2.5 1.12-2.5 2.5 0 .16.033.297.063.438l-3.063 3.063v2h3v-2h2v-1l.063-.063c.14.03.277.063.438.063 1.38 0 2.5-1.12 2.5-2.5s-1.12-2.5-2.5-2.5zm.5 1c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="laptop" viewBox="0 0 8 8">
|
||||
<path d="M1.344 0a.5.5 0 0 0-.344.5v3.5h-1v1.5c0 .28.22.5.5.5h6.999999999999999c.28 0 .5-.22.5-.5v-1.5h-1v-3.5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0-.094 0 .5.5 0 0 0-.063 0zm.656 1h4v3h-1v1h-2v-1h-1v-3z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="layers" viewBox="0 0 8 8">
|
||||
<path d="M0 0v4h4v-4h-4zm5 2v3h-3v1h4v-4h-1zm2 2v3h-3v1h4v-4h-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="lightbulb" viewBox="0 0 8 8">
|
||||
<path d="M3.406 0a.5.5 0 0 0-.125.063l-3 1.5a.5.5 0 1 0 .438.875l3-1.5a.5.5 0 0 0-.313-.938zm1 1.5a.5.5 0 0 0-.125.063l-4 2a.5.5 0 1 0 .438.875l4-2a.5.5 0 0 0-.313-.938zm0 2a.5.5 0 0 0-.125.063l-3 1.5a.5.5 0 0 0 .219.938h2a.502.502 0 0 0 .156-1l1.063-.563a.5.5 0 0 0-.313-.938zm-2.563 3.5a.502.502 0 0 0 .156 1h1a.5.5 0 1 0 0-1h-1a.5.5 0 0 0-.094 0 .502.502 0 0 0-.063 0z" transform="translate(1)"></path>
|
||||
</symbol>
|
||||
<symbol id="link-broken" viewBox="0 0 8 8">
|
||||
<path d="M2 0v1h-1v1h2v-2h-1zm3.875.031c-.184.01-.354.03-.531.094-.27.095-.531.25-.75.469l-.438.438a.5.5 0 1 0 .688.688l.438-.438c.101-.101.245-.173.375-.219.352-.126.78-.064 1.063.219.395.389.4 1.037 0 1.438l-1.5 1.5a.5.5 0 1 0 .688.688l1.5-1.5c.78-.78.785-2.041 0-2.813-.279-.279-.606-.452-.969-.531-.181-.039-.379-.041-.563-.031zm-3.594 2.906a.5.5 0 0 0-.188.156l-1.5 1.5c-.78.78-.785 2.041 0 2.813.557.557 1.355.722 2.063.469.27-.095.531-.25.75-.469l.438-.438a.5.5 0 1 0-.688-.688l-.438.438c-.101.101-.245.173-.375.219-.352.126-.78.064-1.063-.219-.395-.389-.4-1.037 0-1.438l1.5-1.5a.5.5 0 0 0-.438-.844.5.5 0 0 0-.063 0zm2.719 3.063v2h1v-1h1v-1h-2z"></path>
|
||||
</symbol>
|
||||
<symbol id="link-intact" viewBox="0 0 8 8">
|
||||
<path d="M5.875.031c-.184.01-.354.03-.531.094-.27.095-.531.25-.75.469a.5.5 0 1 0 .688.688c.101-.101.245-.173.375-.219.352-.126.78-.064 1.063.219.395.389.4 1.037 0 1.438l-1.5 1.5c-.434.434-.799.483-1.063.469-.264-.015-.406-.125-.406-.125a.504.504 0 1 0-.5.875s.34.222.844.25c.504.028 1.197-.165 1.813-.781l1.5-1.5c.78-.78.785-2.041 0-2.813-.279-.279-.606-.452-.969-.531-.181-.039-.379-.041-.563-.031zm-2 2.313c-.501-.019-1.186.155-1.781.75l-1.5 1.5c-.78.78-.785 2.041 0 2.813.557.557 1.355.722 2.063.469.27-.095.531-.25.75-.469a.5.5 0 1 0-.688-.688c-.101.101-.245.173-.375.219-.352.126-.78.064-1.063-.219-.395-.389-.4-1.037 0-1.438l1.5-1.5c.405-.405.752-.448 1.031-.438.279.011.469.094.469.094a.5.5 0 1 0 .438-.875s-.343-.199-.844-.219z"></path>
|
||||
</symbol>
|
||||
<symbol id="list-rich" viewBox="0 0 8 8">
|
||||
<path d="M0 0v3h3v-3h-3zm4 0v1h4v-1h-4zm0 2v1h3v-1h-3zm-4 2v3h3v-3h-3zm4 0v1h4v-1h-4zm0 2v1h3v-1h-3z"></path>
|
||||
</symbol>
|
||||
<symbol id="list" viewBox="0 0 8 8">
|
||||
<path d="M.5 0c-.276 0-.5.224-.5.5s.224.5.5.5.5-.224.5-.5-.224-.5-.5-.5zm1.5 0v1h6v-1h-6zm-1.5 2c-.276 0-.5.224-.5.5s.224.5.5.5.5-.224.5-.5-.224-.5-.5-.5zm1.5 0v1h6v-1h-6zm-1.5 2c-.276 0-.5.224-.5.5s.224.5.5.5.5-.224.5-.5-.224-.5-.5-.5zm1.5 0v1h6v-1h-6zm-1.5 2c-.276 0-.5.224-.5.5s.224.5.5.5.5-.224.5-.5-.224-.5-.5-.5zm1.5 0v1h6v-1h-6z"></path>
|
||||
</symbol>
|
||||
<symbol id="location" viewBox="0 0 8 8">
|
||||
<path d="M8 0l-8 4 3 1 1 3 4-8z"></path>
|
||||
</symbol>
|
||||
<symbol id="lock-locked" viewBox="0 0 8 8">
|
||||
<path d="M3 0c-1.099 0-2 .901-2 2v1h-1v4h6v-4h-1v-1c0-1.099-.901-2-2-2zm0 1c.561 0 1 .439 1 1v1h-2v-1c0-.561.439-1 1-1z" transform="translate(1)"></path>
|
||||
</symbol>
|
||||
<symbol id="lock-unlocked" viewBox="0 0 8 8">
|
||||
<path d="M3 0c-1.099 0-2 .901-2 2h1c0-.561.439-1 1-1 .561 0 1 .439 1 1v2h-4v4h6v-4h-1v-2c0-1.099-.901-2-2-2z" transform="translate(1)"></path>
|
||||
</symbol>
|
||||
<symbol id="loop-circular" viewBox="0 0 8 8">
|
||||
<path d="M4 0c-1.651 0-3 1.349-3 3h-1l1.5 2 1.5-2h-1c0-1.109.891-2 2-2v-1zm2.5 1l-1.5 2h1c0 1.109-.891 2-2 2v1c1.651 0 3-1.349 3-3h1l-1.5-2z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="loop-square" viewBox="0 0 8 8">
|
||||
<path d="M1 0v2h1v-1h4v2h-1l1.5 2.5 1.5-2.5h-1v-3h-6zm.5 2.5l-1.5 2.5h1v3h6v-2h-1v1h-4v-2h1l-1.5-2.5z"></path>
|
||||
</symbol>
|
||||
<symbol id="loop" viewBox="0 0 8 8">
|
||||
<path d="M6 0v1h-5c-.554 0-1 .446-1 1v1h1v-1h5v1l2-1.5-2-1.5zm-4 4l-2 1.5 2 1.5v-1h5c.542 0 1-.458 1-1v-1h-1v1h-5v-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="magnifying-glass" viewBox="0 0 8 8">
|
||||
<path d="M3.5 0c-1.927 0-3.5 1.573-3.5 3.5s1.573 3.5 3.5 3.5c.592 0 1.166-.145 1.656-.406a1 1 0 0 0 .125.125l1 1a1.016 1.016 0 1 0 1.438-1.438l-1-1a1 1 0 0 0-.156-.125c.266-.493.438-1.059.438-1.656 0-1.927-1.573-3.5-3.5-3.5zm0 1c1.387 0 2.5 1.113 2.5 2.5 0 .661-.241 1.273-.656 1.719-.01.011-.021.021-.031.031a1 1 0 0 0-.125.125c-.442.397-1.043.625-1.688.625-1.387 0-2.5-1.113-2.5-2.5s1.113-2.5 2.5-2.5z"></path>
|
||||
</symbol>
|
||||
<symbol id="map-marker" viewBox="0 0 8 8">
|
||||
<path d="M3 0c-1.66 0-3 1.34-3 3 0 2 3 5 3 5s3-3 3-5c0-1.66-1.34-3-3-3zm0 1c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2z" transform="translate(1)"></path>
|
||||
</symbol>
|
||||
<symbol id="map" viewBox="0 0 8 8">
|
||||
<path d="M0 0v8h8v-2.375a.5.5 0 0 0 0-.219v-5.406h-8zm1 1h6v4h-1.5a.5.5 0 0 0-.094 0 .502.502 0 1 0 .094 1h1.5v1h-6v-6zm2.5 1c-.83 0-1.5.67-1.5 1.5 0 1 1.5 2.5 1.5 2.5s1.5-1.5 1.5-2.5c0-.83-.67-1.5-1.5-1.5zm0 1c.28 0 .5.22.5.5s-.22.5-.5.5-.5-.22-.5-.5.22-.5.5-.5z"></path>
|
||||
</symbol>
|
||||
<symbol id="media-pause" viewBox="0 0 8 8">
|
||||
<path d="M0 0v6h2v-6h-2zm4 0v6h2v-6h-2z" transform="translate(1 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="media-play" viewBox="0 0 8 8">
|
||||
<path d="M0 0v6l6-3-6-3z" transform="translate(1 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="media-record" viewBox="0 0 8 8">
|
||||
<path d="M3 0c-1.657 0-3 1.343-3 3s1.343 3 3 3 3-1.343 3-3-1.343-3-3-3z" transform="translate(1 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="media-skip-backward" viewBox="0 0 8 8">
|
||||
<path d="M4 0l-4 3 4 3v-6zm0 3l4 3v-6l-4 3z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="media-skip-forward" viewBox="0 0 8 8">
|
||||
<path d="M0 0v6l4-3-4-3zm4 3v3l4-3-4-3v3z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="media-step-backward" viewBox="0 0 8 8">
|
||||
<path d="M0 0v6h2v-6h-2zm2 3l5 3v-6l-5 3z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="media-step-forward" viewBox="0 0 8 8">
|
||||
<path d="M0 0v6l5-3-5-3zm5 3v3h2v-6h-2v3z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="media-stop" viewBox="0 0 8 8">
|
||||
<path d="M0 0v6h6v-6h-6z" transform="translate(1 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="medical-cross" viewBox="0 0 8 8">
|
||||
<path d="M2 0v2h-2v4h2v2h4v-2h2v-4h-2v-2h-4z"></path>
|
||||
</symbol>
|
||||
<symbol id="menu" viewBox="0 0 8 8">
|
||||
<path d="M0 0v1h8v-1h-8zm0 2.969v1h8v-1h-8zm0 3v1h8v-1h-8z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="microphone" viewBox="0 0 8 8">
|
||||
<path d="M2.906-.031a1 1 0 0 0-.125.031 1 1 0 0 0-.781 1v2a1 1 0 1 0 2 0v-2a1 1 0 0 0-1.094-1.031zm-2.563 2.031a.5.5 0 0 0-.344.5v.5c0 1.476 1.091 2.693 2.5 2.938v1.063h-.5c-.55 0-1 .45-1 1h4c0-.55-.45-1-1-1h-.5v-1.063c1.409-.244 2.5-1.461 2.5-2.938v-.5a.5.5 0 1 0-1 0v.5c0 1.109-.891 2-2 2s-2-.891-2-2v-.5a.5.5 0 0 0-.594-.5.5.5 0 0 0-.063 0z" transform="translate(1)"></path>
|
||||
</symbol>
|
||||
<symbol id="minus" viewBox="0 0 8 8">
|
||||
<path d="M0 0v2h8v-2h-8z" transform="translate(0 3)"></path>
|
||||
</symbol>
|
||||
<symbol id="monitor" viewBox="0 0 8 8">
|
||||
<path d="M.344 0a.5.5 0 0 0-.344.5v5a.5.5 0 0 0 .5.5h2.5v1h-1c-.55 0-1 .45-1 1h6c0-.55-.45-1-1-1h-1v-1h2.5a.5.5 0 0 0 .5-.5v-5a.5.5 0 0 0-.5-.5h-7a.5.5 0 0 0-.094 0 .5.5 0 0 0-.063 0zm.656 1h6v4h-6v-4z"></path>
|
||||
</symbol>
|
||||
<symbol id="moon" viewBox="0 0 8 8">
|
||||
<path d="M2.719 0c-1.58.53-2.719 2.021-2.719 3.781 0 2.21 1.79 4 4 4 1.76 0 3.251-1.17 3.781-2.75-.4.14-.831.25-1.281.25-2.21 0-4-1.79-4-4 0-.44.079-.881.219-1.281z"></path>
|
||||
</symbol>
|
||||
<symbol id="move" viewBox="0 0 8 8">
|
||||
<path d="M3.5 0l-1.5 1.5h1v1.5h-1.5v-1l-1.5 1.5 1.5 1.5v-1h1.5v1.5h-1l1.5 1.5 1.5-1.5h-1v-1.5h1.5v1l1.5-1.5-1.5-1.5v1h-1.5v-1.5h1l-1.5-1.5z"></path>
|
||||
</symbol>
|
||||
<symbol id="musical-note" viewBox="0 0 8 8">
|
||||
<path d="M8 0c-5 0-6 1-6 1v4.093999999999999c-.154-.054-.327-.094-.5-.094-.828 0-1.5.672-1.5 1.5s.672 1.5 1.5 1.5 1.5-.672 1.5-1.5v-3.969c.732-.226 1.99-.438 4-.5v2.063c-.154-.054-.327-.094-.5-.094-.828 0-1.5.672-1.5 1.5s.672 1.5 1.5 1.5 1.5-.672 1.5-1.5v-5.5z"></path>
|
||||
</symbol>
|
||||
<symbol id="paperclip" viewBox="0 0 8 8">
|
||||
<path d="M5 0c-.514 0-1.021.201-1.406.594l-2.781 2.719c-1.07 1.07-1.07 2.805 0 3.875 1.07 1.07 2.805 1.07 3.875 0l1.25-1.25-.688-.688-.906.875-.344.375c-.69.69-1.81.69-2.5 0-.682-.682-.668-1.778 0-2.469l2.781-2.719v-.031c.389-.395 1.037-.4 1.438 0 .388.381.378 1.006 0 1.406l-2.5 2.469c-.095.095-.28.095-.375 0-.095-.095-.095-.28 0-.375l.375-.344.594-.625-.688-.688-.875.875-.094.094c-.485.485-.485 1.265 0 1.75.485.485 1.265.485 1.75 0l2.5-2.438c.78-.78.785-2.041 0-2.813-.39-.39-.893-.594-1.406-.594z"></path>
|
||||
</symbol>
|
||||
<symbol id="pencil" viewBox="0 0 8 8">
|
||||
<path d="M6 0l-1 1 2 2 1-1-2-2zm-2 2l-4 4v2h2l4-4-2-2z"></path>
|
||||
</symbol>
|
||||
<symbol id="people" viewBox="0 0 8 8">
|
||||
<path d="M5.5 0c-.51 0-.949.355-1.219.875.45.54.719 1.275.719 2.125 0 .29-.034.574-.094.844.18.11.374.156.594.156.83 0 1.5-.9 1.5-2s-.67-2-1.5-2zm-3 1c-.828 0-1.5.895-1.5 2s.672 2 1.5 2 1.5-.895 1.5-2-.672-2-1.5-2zm4.75 3.156c-.43.51-1.018.824-1.688.844.27.38.438.844.438 1.344v.656h2v-1.656c0-.52-.31-.968-.75-1.188zm-6.5 1c-.44.22-.75.668-.75 1.188v1.656h5v-1.656c0-.52-.31-.968-.75-1.188-.44.53-1.06.844-1.75.844s-1.31-.314-1.75-.844z"></path>
|
||||
</symbol>
|
||||
<symbol id="person" viewBox="0 0 8 8">
|
||||
<path d="M4 0c-1.105 0-2 1.119-2 2.5s.895 2.5 2 2.5 2-1.119 2-2.5-.895-2.5-2-2.5zm-2.094 5c-1.07.04-1.906.92-1.906 2v1h8v-1c0-1.08-.836-1.96-1.906-2-.54.61-1.284 1-2.094 1-.81 0-1.554-.39-2.094-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="phone" viewBox="0 0 8 8">
|
||||
<path d="M.188 0c-.11 0-.188.077-.188.188v7.625c0 .11.077.188.188.188h4.625c.11 0 .188-.077.188-.188v-7.625c0-.11-.077-.188-.188-.188h-4.625zm.813 1h3v5h-3v-5zm1.5 5.5c.28 0 .5.22.5.5s-.22.5-.5.5-.5-.22-.5-.5.22-.5.5-.5z" transform="translate(1)"></path>
|
||||
</symbol>
|
||||
<symbol id="pie-chart" viewBox="0 0 8 8">
|
||||
<path d="M3.5 0c-.97 0-1.839.391-2.469 1.031l2.969 2.969v-3.969c-.16-.03-.33-.031-.5-.031zm1.5 1.063v3.406l-2.719 2.719c.6.5 1.369.813 2.219.813 1.93 0 3.5-1.57 3.5-3.5 0-1.76-1.31-3.197-3-3.438zm-4.094 1.313c-.55.54-.906 1.285-.906 2.125 0 .95.435 1.804 1.125 2.344l2.156-2.125-2.375-2.344z"></path>
|
||||
</symbol>
|
||||
<symbol id="pin" viewBox="0 0 8 8">
|
||||
<path d="M1.344 0a.502.502 0 0 0 .156 1h.5v2h-1c-.55 0-1 .45-1 1h3v3l.438 1 .563-1v-3h3c0-.55-.45-1-1-1h-1v-2h.5a.5.5 0 1 0 0-1h-4a.5.5 0 0 0-.094 0 .502.502 0 0 0-.063 0z"></path>
|
||||
</symbol>
|
||||
<symbol id="play-circle" viewBox="0 0 8 8">
|
||||
<path d="M4 0c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm-1 2l3 2-3 2v-4z"></path>
|
||||
</symbol>
|
||||
<symbol id="plus" viewBox="0 0 8 8">
|
||||
<path d="M3 0v3h-3v2h3v3h2v-3h3v-2h-3v-3h-2z"></path>
|
||||
</symbol>
|
||||
<symbol id="power-standby" viewBox="0 0 8 8">
|
||||
<path d="M3 0v4h1v-4h-1zm-1.281 1.438l-.375.313c-.803.64-1.344 1.634-1.344 2.75 0 1.929 1.571 3.5 3.5 3.5s3.5-1.571 3.5-3.5c0-1.116-.529-2.11-1.344-2.75l-.375-.313-.625.781.375.313c.585.46.969 1.165.969 1.969 0 1.391-1.109 2.5-2.5 2.5s-2.5-1.109-2.5-2.5c0-.804.361-1.509.938-1.969l.406-.313-.625-.781z"></path>
|
||||
</symbol>
|
||||
<symbol id="print" viewBox="0 0 8 8">
|
||||
<path d="M2 0v2h4v-2h-4zm-1.906 3c-.06 0-.094.034-.094.094v2.813c0 .06.034.094.094.094h.906v-2h6v2h.906c.06 0 .094-.034.094-.094v-2.813c0-.06-.034-.094-.094-.094h-7.813zm1.906 2v3h4v-3h-4z"></path>
|
||||
</symbol>
|
||||
<symbol id="project" viewBox="0 0 8 8">
|
||||
<path d="M0 0v7h1v-7h-1zm7 0v7h1v-7h-1zm-5 1v1h2v-1h-2zm1 2v1h2v-1h-2zm1 2v1h2v-1h-2z"></path>
|
||||
</symbol>
|
||||
<symbol id="pulse" viewBox="0 0 8 8">
|
||||
<path d="M3.25 0l-.469 1.531-.781 2.563-.031-.063-.094-.344h-1.875v1h1.1560000000000001l.375 1.156.469 1.469.469-1.469.781-2.5.781 2.5.406 1.313.531-1.281.594-1.469.125.281h2.3129999999999997v-1h-1.688l-.375-.719-.5-1-.406 1.031-.469 1.188-.844-2.656-.469-1.531z"></path>
|
||||
</symbol>
|
||||
<symbol id="puzzle-piece" viewBox="0 0 8 8">
|
||||
<path d="M3 0c-.28 0-.539.101-.719.281-.18.18-.281.439-.281.719 0 .28.181.479.281.719.03.06.063.161.063.281h-2.344v6h2.344c0-.12-.011-.221-.031-.281-.11-.24-.313-.439-.313-.719 0-.28.101-.539.281-.719.18-.18.439-.281.719-.281.28 0 .539.101.719.281.18.18.281.439.281.719 0 .28-.181.479-.281.719-.03.06-.063.161-.063.281h2.344v-2.344c.12 0 .221.011.281.031.24.11.439.313.719.313.28 0 .539-.101.719-.281.18-.18.281-.439.281-.719 0-.28-.101-.539-.281-.719-.18-.18-.439-.281-.719-.281-.28 0-.479.181-.719.281-.06.03-.161.063-.281.063v-2.344h-2.344c0-.12.011-.221.031-.281.11-.24.313-.439.313-.719 0-.28-.101-.539-.281-.719-.18-.18-.439-.281-.719-.281z"></path>
|
||||
</symbol>
|
||||
<symbol id="question-mark" viewBox="0 0 8 8">
|
||||
<path d="M2.469 0c-.854 0-1.48.256-1.875.656s-.54.901-.594 1.281l1 .125c.036-.26.125-.497.313-.688.188-.19.491-.375 1.156-.375.664 0 1.019.163 1.219.344.199.181.281.405.281.656 0 .833-.313 1.063-.813 1.5-.5.438-1.188 1.083-1.188 2.25v.25h1v-.25c0-.833.344-1.063.844-1.5.5-.438 1.156-1.083 1.156-2.25 0-.479-.168-1.02-.594-1.406-.426-.387-1.071-.594-1.906-.594zm-.5 7v1h1v-1h-1z" transform="translate(2)"></path>
|
||||
</symbol>
|
||||
<symbol id="rain" viewBox="0 0 8 8">
|
||||
<path d="M4.5 0c-1.21 0-2.27.86-2.5 2-1.1 0-2 .9-2 2 0 .52.201 1.015.531 1.375.26-.22.599-.375.969-.375.2 0 .393.055.563.125.17-.64.748-1.125 1.438-1.125s1.268.485 1.438 1.125c.17-.07.362-.125.563-.125.63 0 1.155.388 1.375.938.64-.17 1.125-.747 1.125-1.438 0-.65-.42-1.29-1-1.5v-.5c0-1.38-1.12-2.5-2.5-2.5zm-1.156 5a.5.5 0 0 0-.344.5v2a.5.5 0 1 0 1 0v-2a.5.5 0 0 0-.594-.5.5.5 0 0 0-.063 0zm-2 1a.5.5 0 0 0-.344.5v1a.5.5 0 1 0 1 0v-1a.5.5 0 0 0-.594-.5.5.5 0 0 0-.063 0zm4 0a.5.5 0 0 0-.344.5v1a.5.5 0 1 0 1 0v-1a.5.5 0 0 0-.594-.5.5.5 0 0 0-.063 0z"></path>
|
||||
</symbol>
|
||||
<symbol id="random" viewBox="0 0 8 8">
|
||||
<path d="M6 0v1h-.5c-.354 0-.6.116-.813.375l-1.406 1.75-1.5-1.75v-.031c-.212-.236-.427-.344-.781-.344h-1v1h1c-.037 0 .008-.011.031 0v.031l1.625 1.906-1.625 2.031c.016-.02.019.022 0 .031-.019.009-.068 0-.031 0h-1v1h1c.354 0 .6-.116.813-.375l1.531-1.906 1.625 1.906v.031c.212.236.427.344.781.344h.25v1l2-1.5-2-1.5v1h-.25c.037 0-.008.011-.031 0v-.031l-1.75-2.063 1.5-1.875v-.031c.019-.009.068 0 .031 0h.5v1l2-1.5-2-1.5z"></path>
|
||||
</symbol>
|
||||
<symbol id="reload" viewBox="0 0 8 8">
|
||||
<path d="M4 0c-2.201 0-4 1.799-4 4s1.799 4 4 4c1.104 0 2.092-.456 2.813-1.188l-.688-.688c-.54.548-1.289.875-2.125.875-1.659 0-3-1.341-3-3s1.341-3 3-3c.834 0 1.545.354 2.094.906l-1.094 1.094h3v-3l-1.188 1.188c-.731-.72-1.719-1.188-2.813-1.188z"></path>
|
||||
</symbol>
|
||||
<symbol id="resize-both" viewBox="0 0 8 8">
|
||||
<path d="M4 0l1.656 1.656-4 4-1.656-1.656v4h4l-1.656-1.656 4-4 1.656 1.656v-4h-4z"></path>
|
||||
</symbol>
|
||||
<symbol id="resize-height" viewBox="0 0 8 8">
|
||||
<path d="M2.5 0l-2.5 3h2v2h-2l2.5 3 2.5-3h-2v-2h2l-2.5-3z" transform="translate(1)"></path>
|
||||
</symbol>
|
||||
<symbol id="resize-width" viewBox="0 0 8 8">
|
||||
<path d="M3 0l-3 2.5 3 2.5v-2h2v2l3-2.5-3-2.5v2h-2v-2z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="rss-alt" viewBox="0 0 8 8">
|
||||
<path d="M0 0v2c3.331 0 6 2.669 6 6h2c0-4.409-3.591-8-8-8zm0 3v2c1.67 0 3 1.33 3 3h2c0-2.75-2.25-5-5-5zm0 3v2h2c0-1.11-.89-2-2-2z"></path>
|
||||
</symbol>
|
||||
<symbol id="rss" viewBox="0 0 8 8">
|
||||
<path d="M1 0v1c3.32 0 6 2.68 6 6h1c0-3.86-3.14-7-7-7zm0 2v1c2.221 0 4 1.779 4 4h1c0-2.759-2.241-5-5-5zm0 2v1c1.109 0 2 .891 2 2h1c0-1.651-1.349-3-3-3zm0 2c-.552 0-1 .448-1 1s.448 1 1 1 1-.448 1-1-.448-1-1-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="script" viewBox="0 0 8 8">
|
||||
<path d="M3 0c-.55 0-1 .45-1 1v5.5c0 .28-.22.5-.5.5s-.5-.22-.5-.5v-1.5h-1v2c0 .55.45 1 1 1h5c.55 0 1-.45 1-1v-3h-4v-2.5c0-.28.22-.5.5-.5s.5.22.5.5v1.5h4v-2c0-.55-.45-1-1-1h-4z"></path>
|
||||
</symbol>
|
||||
<symbol id="share-boxed" viewBox="0 0 8 8">
|
||||
<path d="M.75 0c-.402 0-.75.348-.75.75v5.5c0 .402.348.75.75.75h4.5c.402 0 .75-.348.75-.75v-1.25h-1v1h-4v-5h2v-1h-2.25zm5.25 0v1c-2.05 0-3.704 1.544-3.938 3.531.213-.875.999-1.531 1.938-1.531h2v1l2-2-2-2z"></path>
|
||||
</symbol>
|
||||
<symbol id="share" viewBox="0 0 8 8">
|
||||
<path d="M5 0v2c-4 0-5 2.05-5 5 .52-1.98 2-3 4-3h1v2l3-3.156-3-2.844z"></path>
|
||||
</symbol>
|
||||
<symbol id="shield" viewBox="0 0 8 8">
|
||||
<path d="M4 0l-.188.094-3.5 1.469-.313.125v.313c0 1.657.666 3.122 1.469 4.188.401.533.828.969 1.25 1.281.422.313.826.531 1.281.531.455 0 .86-.219 1.281-.531.422-.313.849-.749 1.25-1.281.803-1.065 1.469-2.53 1.469-4.188v-.313l-.313-.125-3.5-1.469-.188-.094zm0 1.094v5.906c-.045 0-.328-.069-.656-.313s-.714-.631-1.063-1.094c-.642-.851-1.137-2.025-1.219-3.281l2.938-1.219z"></path>
|
||||
</symbol>
|
||||
<symbol id="signal" viewBox="0 0 8 8">
|
||||
<path d="M6 0v8h1v-8h-1zm-2 1v7h1v-7h-1zm-2 2v5h1v-5h-1zm-2 2v3h1v-3h-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="signpost" viewBox="0 0 8 8">
|
||||
<path d="M3 0v1h-2l-1 1 1 1h2v5h1v-4h2l1-1-1-1h-2v-2h-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="sort-ascending" viewBox="0 0 8 8">
|
||||
<path d="M2 0v6h-2l2.5 2 2.5-2h-2v-6h-1zm2 0v1h2v-1h-2zm0 2v1h3v-1h-3zm0 2v1h4v-1h-4z"></path>
|
||||
</symbol>
|
||||
<symbol id="sort-descending" viewBox="0 0 8 8">
|
||||
<path d="M2 0v6h-2l2.5 2 2.5-2h-2v-6h-1zm2 0v1h4v-1h-4zm0 2v1h3v-1h-3zm0 2v1h2v-1h-2z"></path>
|
||||
</symbol>
|
||||
<symbol id="spreadsheet" viewBox="0 0 8 8">
|
||||
<path d="M.75 0c-.402 0-.75.348-.75.75v5.5c0 .402.348.75.75.75h6.5c.402 0 .75-.348.75-.75v-5.5c0-.402-.348-.75-.75-.75h-6.5zm.25 1h1v1h-1v-1zm2 0h4v1h-4v-1zm-2 2h1v1h-1v-1zm2 0h4v1h-4v-1zm-2 2h1v1h-1v-1zm2 0h4v1h-4v-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="star" viewBox="0 0 8 8">
|
||||
<path d="M4 0l-1 3h-3l2.5 2-1 3 2.5-2 2.5 2-1-3 2.5-2h-3l-1-3z"></path>
|
||||
</symbol>
|
||||
<symbol id="sun" viewBox="0 0 8 8">
|
||||
<path d="M4 0c-.276 0-.5.224-.5.5s.224.5.5.5.5-.224.5-.5-.224-.5-.5-.5zm-2.5 1c-.276 0-.5.224-.5.5s.224.5.5.5.5-.224.5-.5-.224-.5-.5-.5zm5 0c-.276 0-.5.224-.5.5s.224.5.5.5.5-.224.5-.5-.224-.5-.5-.5zm-2.5 1c-1.105 0-2 .895-2 2s.895 2 2 2 2-.895 2-2-.895-2-2-2zm-3.5 1.5c-.276 0-.5.224-.5.5s.224.5.5.5.5-.224.5-.5-.224-.5-.5-.5zm7 0c-.276 0-.5.224-.5.5s.224.5.5.5.5-.224.5-.5-.224-.5-.5-.5zm-6 2.5c-.276 0-.5.224-.5.5s.224.5.5.5.5-.224.5-.5-.224-.5-.5-.5zm5 0c-.276 0-.5.224-.5.5s.224.5.5.5.5-.224.5-.5-.224-.5-.5-.5zm-2.5 1c-.276 0-.5.224-.5.5s.224.5.5.5.5-.224.5-.5-.224-.5-.5-.5z"></path>
|
||||
</symbol>
|
||||
<symbol id="tablet" viewBox="0 0 8 8">
|
||||
<path d="M.344 0c-.18 0-.344.164-.344.344v7.313c0 .18.164.344.344.344h6.313c.18 0 .344-.164.344-.344v-7.313c0-.18-.164-.344-.344-.344h-6.313zm.656 1h5v5h-5v-5zm2.5 5.5c.28 0 .5.22.5.5s-.22.5-.5.5-.5-.22-.5-.5.22-.5.5-.5z"></path>
|
||||
</symbol>
|
||||
<symbol id="tag" viewBox="0 0 8 8">
|
||||
<path d="M0 0v3l5 5 3-3-5-5h-3zm2 1c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="tags" viewBox="0 0 8 8">
|
||||
<path d="M0 0v2l3 3 1.5-1.5.5-.5-2-2-1-1h-2zm3.406 0l3 3-1.188 1.219.781.781 2-2-3-3h-1.594zm-1.906 1c.28 0 .5.22.5.5s-.22.5-.5.5-.5-.22-.5-.5.22-.5.5-.5z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="target" viewBox="0 0 8 8">
|
||||
<path d="M4 0c-2.203 0-4 1.797-4 4 0 2.203 1.797 4 4 4 2.203 0 4-1.797 4-4 0-2.203-1.797-4-4-4zm0 1c1.663 0 3 1.337 3 3s-1.337 3-3 3-3-1.337-3-3 1.337-3 3-3zm0 1c-1.099 0-2 .901-2 2s.901 2 2 2 2-.901 2-2-.901-2-2-2zm0 1c.558 0 1 .442 1 1s-.442 1-1 1-1-.442-1-1 .442-1 1-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="task" viewBox="0 0 8 8">
|
||||
<path d="M0 0v7h7v-3.594l-1 1v1.594h-5v-5h3.594l1-1h-5.594zm7 0l-3 3-1-1-1 1 2 2 4-4-1-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="terminal" viewBox="0 0 8 8">
|
||||
<path d="M.094 0c-.06 0-.094.034-.094.094v7.813c0 .06.034.094.094.094h7.813c.06 0 .094-.034.094-.094v-7.813c0-.06-.034-.094-.094-.094h-7.813zm1.406.781l1.719 1.719-1.719 1.719-.719-.719 1-1-1-1 .719-.719zm2.5 2.219h3v1h-3v-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="text" viewBox="0 0 8 8">
|
||||
<path d="M0 0v2h.5c0-.55.45-1 1-1h1.5v5.5c0 .28-.22.5-.5.5h-.5v1h4v-1h-.5c-.28 0-.5-.22-.5-.5v-5.5h1.5c.55 0 1 .45 1 1h.5v-2h-8z"></path>
|
||||
</symbol>
|
||||
<symbol id="thumb-down" viewBox="0 0 8 8">
|
||||
<path d="M0 0v4h1v-4h-1zm2 0v4.001c.28 0 .529.101.719.281.18.19 1.151 2.115 1.281 2.375.13.26.386.393.656.313.26-.08.393-.355.313-.625-.08-.26-.469-1.594-.469-1.844s.22-.5.5-.5h1.5c.28 0 .5-.22.5-.5s-1.031-3.188-1.031-3.188c-.08-.18-.259-.313-.469-.313h-3.5z"></path>
|
||||
</symbol>
|
||||
<symbol id="thumb-up" viewBox="0 0 8 8">
|
||||
<path d="M4.438 0c-.19.021-.34.149-.438.344-.13.26-1.101 2.185-1.281 2.375-.19.18-.439.281-.719.281v4.001h3.5c.21 0 .389-.133.469-.313 0 0 1.031-2.908 1.031-3.188 0-.28-.22-.5-.5-.5h-1.5c-.28 0-.5-.25-.5-.5s.389-1.574.469-1.844c.08-.27-.053-.545-.313-.625-.067-.02-.155-.038-.219-.031zm-4.438 3v4h1v-4h-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="timer" viewBox="0 0 8 8">
|
||||
<path d="M2 0v1h1v.031c-1.697.241-3 1.707-3 3.469 0 1.929 1.571 3.5 3.5 3.5s3.5-1.571 3.5-3.5c0-.45-.086-.874-.219-1.25l-.938.344c.107.304.156.596.156.906 0 1.391-1.109 2.5-2.5 2.5s-2.5-1.109-2.5-2.5 1.109-2.5 2.5-2.5c.298 0 .585.051.875.156l.344-.938c-.221-.081-.471-.119-.719-.156v-.063h1v-1h-3zm5 1.125s-3.675 2.8-3.875 3c-.2.2-.2.519 0 .719.2.2.519.2.719 0 .2-.19 3.156-3.719 3.156-3.719z"></path>
|
||||
</symbol>
|
||||
<symbol id="transfer" viewBox="0 0 8 8">
|
||||
<path d="M6 0v1h-6v1h6v1l2-1.5-2-1.5zm-4 4l-2 1.5 2 1.5v-1h6v-1h-6v-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="trash" viewBox="0 0 8 8">
|
||||
<path d="M3 0c-.55 0-1 .45-1 1h-1c-.55 0-1 .45-1 1h7c0-.55-.45-1-1-1h-1c0-.55-.45-1-1-1h-1zm-2 3v4.813c0 .11.077.188.188.188h4.625c.11 0 .188-.077.188-.188v-4.813h-1v3.5c0 .28-.22.5-.5.5s-.5-.22-.5-.5v-3.5h-1v3.5c0 .28-.22.5-.5.5s-.5-.22-.5-.5v-3.5h-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="underline" viewBox="0 0 8 8">
|
||||
<path d="M1 0v4c0 1.1 1.12 2 2.5 2h.5c1.1 0 2-.9 2-2v-4h-1v4c0 .55-.45 1-1 1s-1-.45-1-1v-4h-2zm-1 7v1h7v-1h-7z"></path>
|
||||
</symbol>
|
||||
<symbol id="vertical-align-bottom" viewBox="0 0 8 8">
|
||||
<path d="M.094 0c-.06 0-.094.034-.094.094v4.813c0 .06.034.094.094.094h1.813c.06 0 .094-.034.094-.094v-4.813c0-.06-.034-.094-.094-.094h-1.813zm6 0c-.06 0-.094.034-.094.094v4.813c0 .06.034.094.094.094h1.813c.06 0 .094-.034.094-.094v-4.813c0-.06-.034-.094-.094-.094h-1.813zm-3 2c-.06 0-.094.034-.094.094v2.813c0 .06.034.094.094.094h1.813c.06 0 .094-.034.094-.094v-2.813c0-.06-.034-.094-.094-.094h-1.813zm-3.094 4v1h8v-1h-8z"></path>
|
||||
</symbol>
|
||||
<symbol id="vertical-align-center" viewBox="0 0 8 8">
|
||||
<path d="M.094 0c-.06 0-.094.034-.094.094v1.906h2v-1.906c0-.06-.034-.094-.094-.094h-1.813zm6 0c-.06 0-.094.034-.094.094v1.906h2v-1.906c0-.06-.034-.094-.094-.094h-1.813zm-3 1c-.06 0-.094.034-.094.094v.906h2v-.906c0-.06-.034-.094-.094-.094h-1.813zm-3.094 2v1h8v-1h-8zm0 2v1.906c0 .06.034.094.094.094h1.813c.06 0 .094-.034.094-.094v-1.906h-2zm3 0v.906c0 .06.034.094.094.094h1.813c.06 0 .094-.034.094-.094v-.906h-2zm3 0v1.906c0 .06.034.094.094.094h1.813c.06 0 .094-.034.094-.094v-1.906h-2z"></path>
|
||||
</symbol>
|
||||
<symbol id="vertical-align-top" viewBox="0 0 8 8">
|
||||
<path d="M0 0v1h8v-1h-8zm.094 2c-.06 0-.094.034-.094.094v4.813c0 .06.034.094.094.094h1.813c.06 0 .094-.034.094-.094v-4.813c0-.06-.034-.094-.094-.094h-1.813zm3 0c-.06 0-.094.034-.094.094v2.813c0 .06.034.094.094.094h1.813c.06 0 .094-.034.094-.094v-2.813c0-.06-.034-.094-.094-.094h-1.813zm3 0c-.06 0-.094.034-.094.094v4.813c0 .06.034.094.094.094h1.813c.06 0 .094-.034.094-.094v-4.813c0-.06-.034-.094-.094-.094h-1.813z"></path>
|
||||
</symbol>
|
||||
<symbol id="video" viewBox="0 0 8 8">
|
||||
<path d="M.5 0c-.28 0-.5.22-.5.5v4c0 .28.22.5.5.5h5c.28 0 .5-.22.5-.5v-1.5l1 1h1v-3h-1l-1 1v-1.5c0-.28-.22-.5-.5-.5h-5z" transform="translate(0 1)"></path>
|
||||
</symbol>
|
||||
<symbol id="volume-high" viewBox="0 0 8 8">
|
||||
<path d="M3.344 0l-1.344 2h-2v4h2l1.344 2h.656v-8h-.656zm1.656 1v1c.152 0 .313.026.469.063h.031c.86.215 1.5.995 1.5 1.938 0 .942-.64 1.722-1.5 1.938-.166.041-.338.063-.5.063v1c.258 0 .516-.035.75-.094 1.3-.325 2.25-1.508 2.25-2.906 0-1.398-.95-2.581-2.25-2.906-.234-.059-.492-.094-.75-.094zm0 2v2c.04 0 .134-.002.25-.031.433-.118.75-.507.75-.969 0-.446-.325-.819-.75-.938v-.031c-.005-.001-.025.002-.031 0-.043-.011-.111-.031-.219-.031z"></path>
|
||||
</symbol>
|
||||
<symbol id="volume-low" viewBox="0 0 8 8">
|
||||
<path d="M3.344 0l-1.344 2h-2v4h2l1.344 2h.656v-8h-.656zm1.656 3v2c.04 0 .134-.002.25-.031.433-.118.75-.507.75-.969 0-.446-.325-.819-.75-.938v-.031c-.005-.001-.025.002-.031 0-.043-.011-.111-.031-.219-.031z" transform="translate(1)"></path>
|
||||
</symbol>
|
||||
<symbol id="volume-off" viewBox="0 0 8 8">
|
||||
<path d="M3.344 0l-1.344 2h-2v4h2l1.344 2h.656v-8h-.656z" transform="translate(2)"></path>
|
||||
</symbol>
|
||||
<symbol id="warning" viewBox="0 0 8 8">
|
||||
<path d="M3.094 0c-.06 0-.105.044-.125.094l-2.938 6.813c-.02.05-.031.128-.031.188v.813c0 .06.034.094.094.094h6.813c.06 0 .094-.034.094-.094v-.813c0-.06-.011-.128-.031-.188l-2.938-6.813c-.02-.05-.065-.094-.125-.094h-.813zm-.094 3h1v2h-1v-2zm0 3h1v1h-1v-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="wifi" viewBox="0 0 8 8">
|
||||
<path d="M3.75 0c-1.374 0-2.66.372-3.75 1.063l.531.875c.93-.59 2.033-.938 3.219-.938 1.2 0 2.323.31 3.25.906l.531-.813c-1.093-.703-2.401-1.094-3.781-1.094zm.031 3c-.795 0-1.531.227-2.156.625l.531.844c.475-.302 1.02-.469 1.625-.469.593 0 1.13.177 1.594.469l.531-.844c-.616-.388-1.338-.625-2.125-.625zm-.031 3c-.552 0-1 .448-1 1s.448 1 1 1 1-.448 1-1-.448-1-1-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="wrench" viewBox="0 0 8 8">
|
||||
<path d="M5.5 0c-1.38 0-2.5 1.12-2.5 2.5 0 .32.078.626.188.906l-2.906 2.875c-.39.39-.39 1.016 0 1.406.2.2.459.313.719.313.26 0 .519-.091.719-.281l2.875-2.875c.28.1.586.156.906.156 1.38 0 2.5-1.12 2.5-2.5 0-.16-.032-.297-.063-.438l-.938.938h-2v-2l.938-.938c-.14-.03-.277-.062-.438-.063zm-4.5 6.5c.28 0 .5.22.5.5s-.22.5-.5.5-.5-.22-.5-.5.22-.5.5-.5z"></path>
|
||||
</symbol>
|
||||
<symbol id="x" viewBox="0 0 8 8">
|
||||
<path d="M1.406 0l-1.406 1.406.688.719 1.781 1.781-1.781 1.781-.688.719 1.406 1.406.719-.688 1.781-1.781 1.781 1.781.719.688 1.406-1.406-.688-.719-1.781-1.781 1.781-1.781.688-.719-1.406-1.406-.719.688-1.781 1.781-1.781-1.781-.719-.688z"></path>
|
||||
</symbol>
|
||||
<symbol id="yen" viewBox="0 0 8 8">
|
||||
<path d="M0 0l2.25 3h-2.25v1h3v1h-3v1h3v2h1v-2h3v-1h-3v-1h3v-1h-2.25l2.25-3h-1l-2.313 3h-.375l-2.313-3h-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="zoom-in" viewBox="0 0 8 8">
|
||||
<path d="M3.5 0c-1.927 0-3.5 1.573-3.5 3.5s1.573 3.5 3.5 3.5c.592 0 1.166-.145 1.656-.406a1 1 0 0 0 .094.094l1.031 1.031a1.016 1.016 0 1 0 1.438-1.438l-1.031-1.031a1 1 0 0 0-.125-.094c.266-.493.438-1.059.438-1.656 0-1.927-1.573-3.5-3.5-3.5zm0 1c1.387 0 2.5 1.113 2.5 2.5 0 .587-.196 1.137-.531 1.563-.009.012-.022.02-.031.031a1 1 0 0 0-.063.031 1 1 0 0 0-.281.281 1 1 0 0 0-.063.063c-.422.326-.953.531-1.531.531-1.387 0-2.5-1.113-2.5-2.5s1.113-2.5 2.5-2.5zm-.5 1v1h-1v1h1v1h1v-1h1v-1h-1v-1h-1z"></path>
|
||||
</symbol>
|
||||
<symbol id="zoom-out" viewBox="0 0 8 8">
|
||||
<path d="M3.5 0c-1.927 0-3.5 1.573-3.5 3.5s1.573 3.5 3.5 3.5c.592 0 1.166-.145 1.656-.406a1 1 0 0 0 .094.094l1.031 1.031a1.016 1.016 0 1 0 1.438-1.438l-1.031-1.031a1 1 0 0 0-.125-.094c.266-.493.438-1.059.438-1.656 0-1.927-1.573-3.5-3.5-3.5zm0 1c1.387 0 2.5 1.113 2.5 2.5 0 .587-.196 1.137-.531 1.563-.009.012-.022.02-.031.031a1 1 0 0 0-.063.031 1 1 0 0 0-.281.281 1 1 0 0 0-.063.063c-.422.326-.953.531-1.531.531-1.387 0-2.5-1.113-2.5-2.5s1.113-2.5 2.5-2.5zm-1.5 2v1h3v-1h-3z"></path>
|
||||
</symbol>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 57 KiB |
1
app/images/sprite/jamstash-sprite.svg
Normal file
1
app/images/sprite/jamstash-sprite.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><symbol viewBox="0 0 8 8" id="loop-single"><path d="M6.002 0v1.002C4.3 1.004 2.595.995.892 1.006c-.548.042-.962.585-.9 1.124v.874H.996v-1h5.007v1l2.003-1.502c-.668-.5-1.336-1-2.003-1.502zm.086 3.184c-1.062-.075-1.835.97-1.98 1.823H1.997v-1l-2.003 1.5 2.003 1.504v-1H4.4c.55 1.12 2.228 1.355 3.063.427.46-.437.57-1.08.542-1.685v-.746c-.258.027-.48 0-.593-.29-.352-.34-.836-.533-1.324-.53zm.067.476c.293-.086.27.175.258.367v2.066c.167.01.34-.017.503.014.13.193.05.525-.227.436H5.3c-.188-.144-.096-.552.19-.45.1-.028.313.057.342-.042V4.23c-.192.092-.367.24-.574.285-.152-.206-.016-.46.205-.544.17-.096.323-.238.508-.304.06-.006.122-.006.182-.006z"/></symbol></svg>
|
After Width: | Height: | Size: 782 B |
|
@ -18,6 +18,10 @@
|
|||
<link href="styles/Style.css" rel="stylesheet" type="text/css" data-name="main" />
|
||||
<link href="styles/Mobile.css" rel="stylesheet" type="text/css" data-name="main" />
|
||||
<link href="" rel="stylesheet" type="text/css" data-name="theme" />
|
||||
<!-- build:css(app) styles/concat.min.css -->
|
||||
<link rel="stylesheet" href="player/player.css" />
|
||||
<link rel="stylesheet" href="player/repeat-directive/repeat-directive.css" />
|
||||
<!-- endbuild -->
|
||||
</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)'}">
|
||||
<div id="container">
|
||||
|
@ -87,13 +91,12 @@
|
|||
<script src="bower_components/fancybox/source/jquery.fancybox.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-dateFormat/dist/jquery-dateFormat.js"></script>
|
||||
<script src="bower_components/underscore/underscore.js"></script>
|
||||
<script src="bower_components/angular-underscore/angular-underscore.js"></script>
|
||||
<script src="bower_components/angular-locker/dist/angular-locker.min.js"></script>
|
||||
<script src="bower_components/angular-ui-utils/keypress.js"></script>
|
||||
<!-- endbower -->
|
||||
<script src="vendor/jquery.base64.js"></script>
|
||||
<script src="vendor/jquery.dateFormat-1.0.js"></script>
|
||||
<!-- endbuild -->
|
||||
<!-- our scripts -->
|
||||
<!-- build:js(app) scripts/scripts.min.js -->
|
||||
|
@ -113,6 +116,7 @@
|
|||
<script src="player/player.js"></script>
|
||||
<script src="player/player-directive.js"></script>
|
||||
<script src="player/player-service.js"></script>
|
||||
<script src="player/repeat-directive/repeat-directive.js"></script>
|
||||
<script src="queue/queue.js"></script>
|
||||
<script src="common/filters.js"></script>
|
||||
<script src="common/directives.js"></script>
|
||||
|
|
|
@ -32,7 +32,6 @@ describe("jplayer directive", function() {
|
|||
return $delegate;
|
||||
});
|
||||
$provide.value('globals', mockGlobals);
|
||||
$provide.constant('jamstashVersion', '1.0.0');
|
||||
});
|
||||
|
||||
spyOn($.fn, "jPlayer").and.callThrough();
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
*
|
||||
* Manages the player and playing queue. Use it to play a song, go to next track or add songs to the queue.
|
||||
*/
|
||||
angular.module('jamstash.player.service', ['jamstash.settings.service', 'angular-underscore/utils'])
|
||||
angular.module('jamstash.player.service', ['angular-underscore/utils'])
|
||||
|
||||
.factory('player', ['globals', function (globals) {
|
||||
.factory('player', [function () {
|
||||
'use strict';
|
||||
|
||||
var playerVolume = 1.0;
|
||||
|
@ -18,6 +18,10 @@ angular.module('jamstash.player.service', ['jamstash.settings.service', 'angular
|
|||
pauseSong: false,
|
||||
restartSong: false,
|
||||
loadSong: false,
|
||||
settings: {
|
||||
repeat: "none",
|
||||
repeatValues: ["queue", "song", "none"]
|
||||
},
|
||||
|
||||
play: function (song) {
|
||||
// Find the song's index in the queue, if it's in there
|
||||
|
@ -56,11 +60,11 @@ angular.module('jamstash.player.service', ['jamstash.settings.service', 'angular
|
|||
|
||||
// Called from the player directive at the end of the current song
|
||||
songEnded: function () {
|
||||
if (globals.settings.Repeat) {
|
||||
if (player.settings.repeat === "song") {
|
||||
// repeat current track
|
||||
player.restart();
|
||||
} else if (player.isLastSongPlaying() === true) {
|
||||
if (globals.settings.LoopQueue) {
|
||||
if (player.settings.repeat === "queue") {
|
||||
// Loop to first track in queue
|
||||
player.playFirstSong();
|
||||
}
|
||||
|
@ -101,7 +105,13 @@ angular.module('jamstash.player.service', ['jamstash.settings.service', 'angular
|
|||
},
|
||||
|
||||
shuffleQueue: function () {
|
||||
player.queue = _(player.queue).shuffle();
|
||||
var shuffled = _(player.queue).without(player._playingSong);
|
||||
shuffled = _(shuffled).shuffle();
|
||||
if(player._playingSong !== undefined) {
|
||||
shuffled.unshift(player._playingSong);
|
||||
player._playingIndex = 0;
|
||||
}
|
||||
player.queue = shuffled;
|
||||
return player;
|
||||
},
|
||||
|
||||
|
|
|
@ -1,21 +1,13 @@
|
|||
describe("Player service -", function() {
|
||||
'use strict';
|
||||
|
||||
var player, mockGlobals, firstSong, secondSong, thirdSong, newSong;
|
||||
var player, firstSong, secondSong, thirdSong, newSong;
|
||||
beforeEach(function() {
|
||||
// We redefine globals because in some tests we need to alter the settings
|
||||
mockGlobals = {
|
||||
settings: {
|
||||
Repeat: false,
|
||||
LoopQueue: false
|
||||
}
|
||||
};
|
||||
module('jamstash.player.service', function ($provide) {
|
||||
$provide.value('globals', mockGlobals);
|
||||
});
|
||||
module('jamstash.player.service');
|
||||
inject(function (_player_) {
|
||||
player = _player_;
|
||||
});
|
||||
player.settings.repeat = "none";
|
||||
});
|
||||
|
||||
describe("Given that I have 3 songs in my playing queue,", function() {
|
||||
|
@ -138,6 +130,25 @@ describe("Player service -", function() {
|
|||
expect(player.queue).toEqual([]);
|
||||
});
|
||||
|
||||
it("and given the third song was playing, when I shuffle the playing queue, then the third song will be at the first position and the rest of the queue will be shuffled", function() {
|
||||
player._playingSong = thirdSong;
|
||||
|
||||
player.shuffleQueue();
|
||||
|
||||
expect(player.queue[0]).toBe(thirdSong);
|
||||
expect(player.queue).toContain(firstSong);
|
||||
expect(player.queue).toContain(secondSong);
|
||||
});
|
||||
|
||||
it("and given no song was playing, when I shuffle the playing queue, then the whole queue will be shuffled", function() {
|
||||
player.shuffleQueue();
|
||||
|
||||
expect(player.queue).toContain(firstSong);
|
||||
expect(player.queue).toContain(secondSong);
|
||||
expect(player.queue).toContain(thirdSong);
|
||||
expect(player.queue).not.toContain(undefined);
|
||||
});
|
||||
|
||||
it("when I get the index of the first song, it returns 0", function() {
|
||||
expect(player.indexOfSong(firstSong)).toBe(0);
|
||||
});
|
||||
|
@ -187,9 +198,9 @@ describe("Player service -", function() {
|
|||
expect(player.nextTrack).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("and that the 'Repeat song' setting is true, when the current song ends, it restarts it", function() {
|
||||
it("and that the 'Repeat' setting is set to 'song', when the current song ends, it restarts it", function() {
|
||||
spyOn(player, "restart");
|
||||
mockGlobals.settings.Repeat = true;
|
||||
player.settings.repeat = "song";
|
||||
|
||||
player.songEnded();
|
||||
|
||||
|
@ -201,9 +212,9 @@ describe("Player service -", function() {
|
|||
player._playingIndex = 2;
|
||||
});
|
||||
|
||||
it("if the 'Repeat queue' setting is true, it plays the first song of the queue", function() {
|
||||
it("if the 'Repeat' setting is set to 'queue', it plays the first song of the queue", function() {
|
||||
spyOn(player, "playFirstSong");
|
||||
mockGlobals.settings.LoopQueue = true;
|
||||
player.settings.repeat = "queue";
|
||||
|
||||
player.songEnded();
|
||||
|
||||
|
|
9
app/player/player.css
Normal file
9
app/player/player.css
Normal file
|
@ -0,0 +1,9 @@
|
|||
.icon {
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
display: block;
|
||||
}
|
||||
.icon-wrap {
|
||||
margin: 4px 2px;
|
||||
float: left;
|
||||
}
|
|
@ -27,9 +27,9 @@
|
|||
</ul>
|
||||
<div id="songdetails_controls">
|
||||
<a href="" class="jukebox" title="Jukebox Mode [Beta]" ng-click="toggleSetting('Jukebox')" ng-class="{'hoverSelected': !settings.Jukebox }"></a>
|
||||
<a href="" class="loop" title="Repeat" ng-click="toggleSetting('Repeat')" ng-class="{'hoverSelected': !settings.Repeat }"></a>
|
||||
<jamstash-repeat selected-value="playerSettings.repeat" values="playerSettings.repeatValues"></jamstash-repeat>
|
||||
<a href="" id="action_SaveProgress" class="lock" title="Save Track Position: On" ng-show="settings.SaveTrackPosition"></a>
|
||||
<a title="Favorite" href="" ng-class="{'favorite': getPlayingSong().starred, 'rate': !getPlayingSong().starred}" ng-click="updateFavorite(getPlayingSong())" stop-event="click"></a>
|
||||
<a title="Favorite" href="" ng-class="{'favorite': getPlayingSong().starred, 'rate': !getPlayingSong().starred}" ng-click="toggleStar(getPlayingSong())" stop-event="click"></a>
|
||||
<a href="" id="action_Mute" class="mute" title="Mute"></a>
|
||||
<a href="" id="action_UnMute" class="unmute" title="Unmute" style="display: none;"></a>
|
||||
<!--<div class="jp-volume-bar"><div class="jp-volume-bar-value"></div></div><a href="" id="action_VolumeMax" class="volume" title="Max Volume"></a>-->
|
||||
|
|
|
@ -5,13 +5,15 @@
|
|||
* Also provides the currently playing song's info through the scope so it can be displayed next to
|
||||
* the player controls.
|
||||
*/
|
||||
angular.module('jamstash.player.controller', ['jamstash.player.service', 'jamstash.player.directive'])
|
||||
angular.module('jamstash.player.controller', ['jamstash.player.service', 'jamstash.player.directive', 'jamstash.repeat.directive'])
|
||||
|
||||
.controller('PlayerController', ['$scope', 'player', 'globals',
|
||||
function ($scope, player, globals) {
|
||||
'use strict';
|
||||
|
||||
$scope.getPlayingSong = player.getPlayingSong;
|
||||
$scope.settings = globals.settings;
|
||||
$scope.playerSettings = player.settings;
|
||||
|
||||
$scope.play = function () {
|
||||
if (globals.settings.Jukebox) {
|
||||
|
@ -31,6 +33,4 @@ angular.module('jamstash.player.controller', ['jamstash.player.service', 'jamsta
|
|||
|
||||
$scope.previousTrack = player.previousTrack;
|
||||
$scope.nextTrack = player.nextTrack;
|
||||
|
||||
//TODO: Hyz: updateFavorite - leave in rootScope ?
|
||||
}]);
|
||||
|
|
|
@ -1,40 +1,64 @@
|
|||
describe("Player controller", function() {
|
||||
'use strict';
|
||||
|
||||
var player, scope;
|
||||
var player, scope, mockGlobals;
|
||||
|
||||
beforeEach(function() {
|
||||
// We redefine globals because in some tests we need to alter the settings
|
||||
mockGlobals = {
|
||||
settings: {
|
||||
Jukebox: false
|
||||
}
|
||||
};
|
||||
|
||||
module('jamstash.player.controller');
|
||||
|
||||
inject(function ($controller, $rootScope) {
|
||||
scope = $rootScope.$new();
|
||||
|
||||
player = jasmine.createSpyObj("player", ["getPlayingSong", "previousTrack", "nextTrack"]);
|
||||
player = jasmine.createSpyObj("player", [
|
||||
"getPlayingSong",
|
||||
"previousTrack",
|
||||
"nextTrack",
|
||||
"getRepeatValues",
|
||||
"togglePause"
|
||||
]);
|
||||
|
||||
$controller('PlayerController', {
|
||||
$scope: scope,
|
||||
player: player
|
||||
player: player,
|
||||
globals: mockGlobals
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("When I get the currently playing song, it asks the player service", function() {
|
||||
it("When I play a song, the player service will be called", function() {
|
||||
scope.play();
|
||||
|
||||
expect(player.togglePause).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("when I pause a song, the player service will be called", function() {
|
||||
scope.pause();
|
||||
|
||||
expect(player.togglePause).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("When I get the currently playing song, the player service will be called", function() {
|
||||
scope.getPlayingSong();
|
||||
|
||||
expect(player.getPlayingSong).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("When I get the previous track, it uses the player service", function() {
|
||||
it("When I get the previous track, the player service will be called", function() {
|
||||
scope.previousTrack();
|
||||
|
||||
expect(player.previousTrack).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("When I get the next track, it uses the player service", function() {
|
||||
it("When I get the next track, the player service will be called", function() {
|
||||
scope.nextTrack();
|
||||
|
||||
expect(player.nextTrack).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// TODO: updateFavorite
|
||||
});
|
||||
|
|
6
app/player/repeat-directive/repeat-directive.css
Normal file
6
app/player/repeat-directive/repeat-directive.css
Normal file
|
@ -0,0 +1,6 @@
|
|||
.icon-loop-queue, .icon-loop-single {
|
||||
fill: #fff;
|
||||
}
|
||||
.icon-loop-none {
|
||||
fill: #adadad;
|
||||
}
|
20
app/player/repeat-directive/repeat-directive.html
Normal file
20
app/player/repeat-directive/repeat-directive.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
<a href="javascript:void(0)" class="icon-wrap" ng-click="cycleRepeat()">
|
||||
<span title="Repeat the playing queue" ng-show="selectedValue === 'queue'">
|
||||
<svg class="icon">
|
||||
<title>Repeat the playing queue</title>
|
||||
<use xlink:href="images/sprite/iconic.svg#loop" class="icon-loop-queue"></use>
|
||||
</svg>
|
||||
</span>
|
||||
<span title="Repeat the current song" ng-show="selectedValue === 'song'">
|
||||
<svg class="icon">
|
||||
<title>Repeat the current song</title>
|
||||
<use xlink:href="images/sprite/jamstash-sprite.svg#loop-single" class="icon-loop-single"></use>
|
||||
</svg>
|
||||
</span>
|
||||
<span title="Disable repeat" ng-show="selectedValue === 'none'">
|
||||
<svg class="icon">
|
||||
<title>Disable repeat</title>
|
||||
<use xlink:href="images/sprite/iconic.svg#loop" class="icon-loop-none"></use>
|
||||
</svg>
|
||||
</span>
|
||||
</a>
|
29
app/player/repeat-directive/repeat-directive.js
Normal file
29
app/player/repeat-directive/repeat-directive.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* jamstash.repeat.directive Module
|
||||
*
|
||||
* Triple-state button to toggle between repeating the entire playing queue, the current playing song and disabling repeat
|
||||
*/
|
||||
angular.module('jamstash.repeat.directive', ['jamstash.notifications'])
|
||||
|
||||
.directive('jamstashRepeat', ['notifications', function (notifications) {
|
||||
'use strict';
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: 'player/repeat-directive/repeat-directive.html',
|
||||
replace: true,
|
||||
scope: {
|
||||
selectedValue: '=',
|
||||
values: '='
|
||||
},
|
||||
link: function ($scope) {
|
||||
$scope.$watch('selectedValue', function (newVal) {
|
||||
$scope.selectedIndex = $scope.values.indexOf(newVal);
|
||||
});
|
||||
$scope.cycleRepeat = function () {
|
||||
$scope.selectedIndex = ($scope.selectedIndex + 1) % $scope.values.length;
|
||||
$scope.selectedValue = $scope.values[$scope.selectedIndex];
|
||||
notifications.updateMessage('Repeat ' + $scope.selectedValue, true);
|
||||
};
|
||||
}
|
||||
};
|
||||
}]);
|
69
app/player/repeat-directive/repeat-directive_test.js
Normal file
69
app/player/repeat-directive/repeat-directive_test.js
Normal file
|
@ -0,0 +1,69 @@
|
|||
describe("repeat directive", function() {
|
||||
'use strict';
|
||||
|
||||
var element, scope, isolateScope, notifications, mockGlobals;
|
||||
|
||||
beforeEach(module ('templates'));
|
||||
beforeEach(function() {
|
||||
// We redefine globals because in some tests we need to alter the settings
|
||||
mockGlobals = {
|
||||
settings: {
|
||||
RepeatValues: ["queue", "song", "none"],
|
||||
Repeat: "none"
|
||||
}
|
||||
};
|
||||
|
||||
module('jamstash.repeat.directive', function($provide) {
|
||||
$provide.value('globals', mockGlobals);
|
||||
// Mock the notifications service
|
||||
$provide.decorator('notifications', function () {
|
||||
return jasmine.createSpyObj("notifications", ["updateMessage"]);
|
||||
});
|
||||
});
|
||||
|
||||
inject(function ($rootScope, $compile, _notifications_) {
|
||||
notifications = _notifications_;
|
||||
// Compile the directive
|
||||
scope = $rootScope.$new();
|
||||
scope.settings = mockGlobals.settings;
|
||||
element = '<jamstash-repeat selected-value="settings.Repeat" values="settings.RepeatValues"></jamstash-repeat>';
|
||||
element = $compile(element)(scope);
|
||||
scope.$digest();
|
||||
isolateScope = element.isolateScope();
|
||||
});
|
||||
});
|
||||
|
||||
it("Given that the Repeat setting was set to 'none', when I cycle through the values, then the Repeat setting will be set to 'queue'", function() {
|
||||
isolateScope.cycleRepeat();
|
||||
isolateScope.$apply();
|
||||
|
||||
expect(mockGlobals.settings.Repeat).toBe('queue');
|
||||
});
|
||||
|
||||
it("Given that the Repeat setting was set to 'queue', when I cycle through the values, then the Repeat setting will be set to 'song'", function() {
|
||||
mockGlobals.settings.Repeat = 'queue';
|
||||
isolateScope.$apply();
|
||||
|
||||
isolateScope.cycleRepeat();
|
||||
isolateScope.$apply();
|
||||
|
||||
expect(mockGlobals.settings.Repeat).toBe('song');
|
||||
});
|
||||
|
||||
it("Given that the Repeat setting was set to 'song', when I cycle through the values, then the Repeat setting will be set to 'none", function() {
|
||||
mockGlobals.settings.Repeat = 'song';
|
||||
isolateScope.$apply();
|
||||
|
||||
isolateScope.cycleRepeat();
|
||||
isolateScope.$apply();
|
||||
|
||||
expect(mockGlobals.settings.Repeat).toBe('none');
|
||||
});
|
||||
|
||||
it("When I cycle through the values, then the user will be notified with the new value", function() {
|
||||
isolateScope.cycleRepeat();
|
||||
isolateScope.$apply();
|
||||
|
||||
expect(notifications.updateMessage).toHaveBeenCalledWith('Repeat queue', true);
|
||||
});
|
||||
});
|
|
@ -10,7 +10,7 @@
|
|||
<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)}">
|
||||
<div class="itemactions">
|
||||
<a class="remove" href="" title="Remove Song" ng-click="removeSongFromQueue(o)" stop-event="click"></a>
|
||||
<a href="" title="Favorite" ng-class="{'favorite': o.starred, 'rate': !o.starred}" ng-click="updateFavorite(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>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
<div class="title floatleft" title="{{o.description}}" ng-bind-html="o.name"></div>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* Manages the playing queue. Gives access to the player service's queue-related functions,
|
||||
* like adding, removing and shuffling the queue.
|
||||
*/
|
||||
angular.module('jamstash.queue.controller', ['jamstash.player.service'])
|
||||
angular.module('jamstash.queue.controller', ['jamstash.player.service', 'jamstash.settings.service'])
|
||||
|
||||
.controller('QueueController', ['$scope', 'globals', 'player',
|
||||
function ($scope, globals, player) {
|
||||
|
@ -24,6 +24,8 @@ angular.module('jamstash.queue.controller', ['jamstash.player.service'])
|
|||
|
||||
$scope.shuffleQueue = function () {
|
||||
player.shuffleQueue();
|
||||
//TODO: Hyz: Shouldn't it be in a directive ?
|
||||
$('#SideBar').stop().scrollTo('.header', 400);
|
||||
};
|
||||
|
||||
$scope.addSongToQueue = function (song) {
|
||||
|
@ -63,6 +65,4 @@ angular.module('jamstash.queue.controller', ['jamstash.player.service'])
|
|||
end = ui.item.index();
|
||||
player.queue.splice(end, 0, player.queue.splice(start, 1)[0]);
|
||||
};
|
||||
|
||||
//TODO: Hyz: updateFavorite - leave in rootScope ?
|
||||
}]);
|
||||
|
|
|
@ -36,10 +36,14 @@ describe("Queue controller", function() {
|
|||
expect($.fancybox.close).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("When I shuffle the queue, it calls shuffleQueue in the player service", function() {
|
||||
it("When I shuffle the queue, then the player's shuffleQueue will be called and the queue will be scrolled back to the first element", function() {
|
||||
spyOn(player, "shuffleQueue");
|
||||
spyOn($.fn, 'scrollTo');
|
||||
|
||||
scope.shuffleQueue();
|
||||
|
||||
expect(player.shuffleQueue).toHaveBeenCalled();
|
||||
expect($.fn.scrollTo).toHaveBeenCalledWith('.header', jasmine.any(Number));
|
||||
});
|
||||
|
||||
it("When I add one song to the queue, it calls addSong in the player service", function() {
|
||||
|
|
|
@ -33,9 +33,6 @@
|
|||
<div class="inputwrap"><input type="checkbox" id="AutoPlay" name="AutoPlay" value="1" title="When the Queue has ended, load random songs" ng-model="settings.AutoPlay" /></div>
|
||||
<label for="AutoPlay">Auto Play</label>
|
||||
<div class="clear"></div>
|
||||
<div class="inputwrap"><input type="checkbox" id="LoopQueue" name="LoopQueue" value="1" title="When the Queue has ended, start from beginning" ng-model="settings.LoopQueue" /></div>
|
||||
<label for="LoopQueue">Loop Queue</label>
|
||||
<div class="clear"></div>
|
||||
<div class="inputwrap"><input type="checkbox" id="HideAZ" name="HideAZ" value="1" title="Hide A-Z Artist Picker (Tablet/Touch friendly feature)" ng-model="settings.HideAZ" /></div>
|
||||
<label for="HideAZ">Hide A-Z</label>
|
||||
<div class="clear"></div>
|
||||
|
|
|
@ -71,6 +71,7 @@ angular.module('jamstash.settings.controller', ['jamstash.settings.service', 'ja
|
|||
$rootScope.showIndex = true;
|
||||
}, function (error) {
|
||||
//TODO: Hyz: Duplicate from subsonic.js - requestSongs. Find a way to handle this only once.
|
||||
globals.settings.ApiVersion = error.version;
|
||||
var errorNotif;
|
||||
if (error.subsonicError !== undefined) {
|
||||
errorNotif = error.reason + ' ' + error.subsonicError.message;
|
||||
|
|
|
@ -90,6 +90,24 @@ describe("Settings controller", function() {
|
|||
|
||||
expect(subsonic.ping).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Given the server and Jamstash had different api versions, when I save the settings and the server responds an error, then the ApiVersion setting will be updated with the one sent from the server", function() {
|
||||
scope.settings.Server = 'http://gallotannate.com/tetranychus/puzzlement?a=stoically&b=mantuamaker#marianolatrist';
|
||||
scope.settings.Username = 'Vandervelden';
|
||||
scope.settings.Password = 'PA3DhdfAu0dy';
|
||||
scope.ApiVersion = '1.10.2';
|
||||
subsonic.ping.and.returnValue(deferred.promise);
|
||||
|
||||
scope.save();
|
||||
deferred.reject({
|
||||
reason: 'Error when contacting the Subsonic server.',
|
||||
subsonicError: {code: 30, message: 'Incompatible Subsonic REST protocol version. Server must upgrade.'},
|
||||
version: '1.8.0'
|
||||
});
|
||||
scope.$apply();
|
||||
|
||||
expect(mockGlobals.settings.ApiVersion).toEqual('1.8.0');
|
||||
});
|
||||
});
|
||||
|
||||
it("reset() - When I reset the settings, they will be deleted from the persistence service and will be reloaded with default values", function() {
|
||||
|
|
|
@ -1474,15 +1474,6 @@ ul.songlist li:hover
|
|||
display: block;
|
||||
background: url('../images/lock_stroke_gl_9x12.png') 0 center no-repeat;
|
||||
}
|
||||
#songdetails a.loop
|
||||
{
|
||||
float: left;
|
||||
margin: 4px 2px;
|
||||
height: 9px;
|
||||
width: 12px;
|
||||
display: block;
|
||||
background: url('../images/loop_alt3_w_12x9.png') 0 center no-repeat;
|
||||
}
|
||||
#songdetails a.jukebox
|
||||
{
|
||||
float: left;
|
||||
|
@ -1833,5 +1824,3 @@ legend
|
|||
font-variant: small-caps;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
* Also offers more fine-grained functionality that is not part of Subsonic's API.
|
||||
*/
|
||||
angular.module('jamstash.subsonic.service', ['angular-underscore/utils',
|
||||
'jamstash.settings.service', 'jamstash.utils', 'jamstash.model', 'jamstash.notifications', 'jamstash.player.service'])
|
||||
'jamstash.settings.service', 'jamstash.utils', 'jamstash.model'])
|
||||
|
||||
.factory('subsonic', ['$rootScope', '$http', '$q', 'globals', 'utils', 'map', 'notifications', 'player',
|
||||
function ($rootScope, $http, $q, globals, utils, map, notifications, player) {
|
||||
.factory('subsonic', ['$rootScope', '$http', '$q', 'globals', 'utils', 'map',
|
||||
function ($rootScope, $http, $q, globals, utils, map) {
|
||||
'use strict';
|
||||
|
||||
//TODO: Hyz: Remove when refactored
|
||||
|
@ -31,9 +31,10 @@ angular.module('jamstash.subsonic.service', ['angular-underscore/utils',
|
|||
var offset = 0;
|
||||
var showPlaylist = false;
|
||||
|
||||
return {
|
||||
var subsonicService = {
|
||||
showIndex: $rootScope.showIndex,
|
||||
showPlaylist: showPlaylist,
|
||||
//TODO: Hyz: Do we still need this ? it's only used in the songpreview directive
|
||||
getSongTemplate: function (callback) {
|
||||
var id = '16608';
|
||||
var url = globals.BaseURL() + '/getMusicDirectory.view?' + globals.BaseParams() + '&id=' + id;
|
||||
|
@ -106,6 +107,7 @@ angular.module('jamstash.subsonic.service', ['angular-underscore/utils',
|
|||
} else {
|
||||
if(subsonicResponse.status === 'failed' && subsonicResponse.error !== undefined) {
|
||||
exception.subsonicError = subsonicResponse.error;
|
||||
exception.version = subsonicResponse.version;
|
||||
}
|
||||
deferred.reject(exception);
|
||||
}
|
||||
|
@ -117,28 +119,36 @@ angular.module('jamstash.subsonic.service', ['angular-underscore/utils',
|
|||
},
|
||||
|
||||
ping: function () {
|
||||
return this.subsonicRequest('ping.view');
|
||||
return subsonicService.subsonicRequest('ping.view');
|
||||
},
|
||||
|
||||
getMusicFolders: function () {
|
||||
var exception = {reason: 'No music folder found on the Subsonic server.'};
|
||||
var promise = subsonicService.subsonicRequest('getMusicFolders.view')
|
||||
.then(function (subsonicResponse) {
|
||||
if (subsonicResponse.musicFolders !== undefined && subsonicResponse.musicFolders.musicFolder !== undefined) {
|
||||
return [].concat(subsonicResponse.musicFolders.musicFolder);
|
||||
} else {
|
||||
return $q.reject(exception);
|
||||
}
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
|
||||
getArtists: function (folder) {
|
||||
var exception = {reason: 'No artist found on the Subsonic server.'};
|
||||
// TODO: Hyz: Move loading / saving the music folder to persistence-service
|
||||
if (isNaN(folder) && utils.getValue('MusicFolders')) {
|
||||
var musicFolder = angular.fromJson(utils.getValue('MusicFolders'));
|
||||
folder = musicFolder.id;
|
||||
}
|
||||
var params;
|
||||
if (!isNaN(folder)) {
|
||||
params = {
|
||||
musicFolderId: folder
|
||||
};
|
||||
}
|
||||
var promise = this.subsonicRequest('getIndexes.view', {
|
||||
var promise = subsonicService.subsonicRequest('getIndexes.view', {
|
||||
params: params
|
||||
}).then(function (subsonicResponse) {
|
||||
if(subsonicResponse.indexes !== undefined && (subsonicResponse.indexes.index !== undefined || subsonicResponse.indexes.shortcut !== undefined)) {
|
||||
// Make sure shortcut, index and each index's artist are arrays
|
||||
// because Madsonic will return objects and not arrays if there is only 1 artist
|
||||
// because Madsonic will return an object when there's only one element
|
||||
var formattedResponse = {};
|
||||
formattedResponse.shortcut = [].concat(subsonicResponse.indexes.shortcut);
|
||||
formattedResponse.index = [].concat(subsonicResponse.indexes.index);
|
||||
|
@ -160,10 +170,11 @@ angular.module('jamstash.subsonic.service', ['angular-underscore/utils',
|
|||
var params = {
|
||||
id: id
|
||||
};
|
||||
var promise = this.subsonicRequest('getMusicDirectory.view', {
|
||||
var promise = subsonicService.subsonicRequest('getMusicDirectory.view', {
|
||||
params: params
|
||||
}).then(function (subsonicResponse) {
|
||||
if(subsonicResponse.directory.child !== undefined) {
|
||||
// Make sure this is an array using concat because Madsonic will return an object when there's only one element
|
||||
var childArray = [].concat(subsonicResponse.directory.child);
|
||||
if (childArray.length > 0) {
|
||||
content.song = [];
|
||||
|
@ -199,10 +210,11 @@ angular.module('jamstash.subsonic.service', ['angular-underscore/utils',
|
|||
type: id,
|
||||
offset: offset
|
||||
};
|
||||
var promise = this.subsonicRequest('getAlbumList.view', {
|
||||
var promise = subsonicService.subsonicRequest('getAlbumList.view', {
|
||||
params: params
|
||||
}).then(function (subsonicResponse) {
|
||||
if(subsonicResponse.albumList.album !== undefined) {
|
||||
// Make sure this is an array using concat because Madsonic will return an object when there's only one element
|
||||
var albumArray = [].concat(subsonicResponse.albumList.album);
|
||||
if (albumArray.length > 0) {
|
||||
content.song = [];
|
||||
|
@ -253,52 +265,25 @@ angular.module('jamstash.subsonic.service', ['angular-underscore/utils',
|
|||
});
|
||||
return deferred.promise;
|
||||
},
|
||||
getSongs: function (id, action) {
|
||||
var exception = {reason: 'No songs found on the Subsonic server.'};
|
||||
var promise = this.subsonicRequest('getMusicDirectory.view', {
|
||||
|
||||
getSongs: function (id) {
|
||||
var exception = {reason: 'This directory is empty.'};
|
||||
var promise = subsonicService.subsonicRequest('getMusicDirectory.view', {
|
||||
params: {
|
||||
id: id
|
||||
}
|
||||
}).then(function (subsonicResponse) {
|
||||
if(subsonicResponse.directory.child !== undefined) {
|
||||
var items = [].concat(subsonicResponse.directory.child);
|
||||
if (items.length > 0) {
|
||||
content.selectedAlbum = id;
|
||||
if (action == 'add') {
|
||||
angular.forEach(items, function (item, key) {
|
||||
player.queue.push(map.mapSong(item));
|
||||
// Make sure this is an array using concat because Madsonic will return an object when there's only one element
|
||||
var children = [].concat(subsonicResponse.directory.child);
|
||||
if (children.length > 0) {
|
||||
var allChildren = _(children).partition(function (item) {
|
||||
return item.isDir;
|
||||
});
|
||||
notifications.updateMessage(items.length + ' Song(s) Added to Queue', true);
|
||||
} else if (action == 'play') {
|
||||
player.queue = [];
|
||||
angular.forEach(items, function (item, key) {
|
||||
player.queue.push(map.mapSong(item));
|
||||
});
|
||||
var next = player.queue[0];
|
||||
player.play(next);
|
||||
notifications.updateMessage(items.length + ' Song(s) Added to Queue', true);
|
||||
} else {
|
||||
if (subsonicResponse.directory.id != 'undefined') {
|
||||
var albumId = subsonicResponse.directory.id;
|
||||
var albumName = subsonicResponse.directory.name;
|
||||
if (content.breadcrumb.length > 0) { content.breadcrumb.splice(1, (content.breadcrumb.length - 1)); }
|
||||
content.breadcrumb.push({ 'type': 'album', 'id': albumId, 'name': albumName });
|
||||
}
|
||||
content.song = [];
|
||||
content.album = [];
|
||||
var albums = [];
|
||||
angular.forEach(items, function (item, key) {
|
||||
if (item.isDir) {
|
||||
albums.push(map.mapAlbum(item));
|
||||
} else {
|
||||
content.song.push(map.mapSong(item));
|
||||
}
|
||||
});
|
||||
if (albums.length > 0) {
|
||||
content.album = albums;
|
||||
}
|
||||
}
|
||||
return content;
|
||||
return {
|
||||
directories: map.mapAlbums(allChildren[0]),
|
||||
songs: map.mapSongs(allChildren[1])
|
||||
};
|
||||
}
|
||||
}
|
||||
// We end up here for every else
|
||||
|
@ -307,28 +292,73 @@ angular.module('jamstash.subsonic.service', ['angular-underscore/utils',
|
|||
return promise;
|
||||
},
|
||||
|
||||
// This is used when we add or play a directory, so we recursively get all its contents
|
||||
recursiveGetSongs: function (id) {
|
||||
var deferred = $q.defer();
|
||||
// We first use getSongs() to get the contents of the root directory
|
||||
subsonicService.getSongs(id).then(function (data) {
|
||||
var directories = data.directories;
|
||||
var songs = data.songs;
|
||||
// If there are only songs, we return them immediately: this is a leaf directory and the end of the recursion
|
||||
if (directories.length === 0) {
|
||||
deferred.resolve(songs);
|
||||
} else {
|
||||
// otherwise, for each directory, we call ourselves
|
||||
var promises = [];
|
||||
angular.forEach(directories, function (dir) {
|
||||
var subdirectoryRequest = subsonicService.recursiveGetSongs(dir.id).then(function (data) {
|
||||
// This is where we join all the songs together in a single array
|
||||
return songs.concat(data);
|
||||
});
|
||||
promises.push(subdirectoryRequest);
|
||||
});
|
||||
// since all of this is asynchronous, we need to wait for all the requests to finish by using $q.all()
|
||||
var allRequestsFinished = $q.all(promises).then(function (data) {
|
||||
// and since $q.all() wraps everything in another array, we use flatten() to end up with only one array of songs
|
||||
return _(data).flatten();
|
||||
});
|
||||
deferred.resolve(allRequestsFinished);
|
||||
}
|
||||
}, function () {
|
||||
// Even if getSongs returns an error, we resolve with an empty array. Otherwise one empty directory somewhere
|
||||
// would keep us from playing all the songs of a directory recursively
|
||||
deferred.resolve([]);
|
||||
});
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
search: function (query, type) {
|
||||
if(_([0, 1, 2]).contains(type)) {
|
||||
var promise = this.subsonicRequest('search2.view', {
|
||||
var promise = subsonicService.subsonicRequest('search2.view', {
|
||||
params: {
|
||||
query: query
|
||||
}
|
||||
}).then(function (subsonicResponse) {
|
||||
var searchResult;
|
||||
if (!_.isEmpty(subsonicResponse.searchResult2)) {
|
||||
searchResult = subsonicResponse.searchResult2;
|
||||
} else if (!_.isEmpty(subsonicResponse.search2)) {
|
||||
// We also check search2 because Music Cabinet doesn't respond the same thing
|
||||
// as everyone else...
|
||||
searchResult = subsonicResponse.search2;
|
||||
}
|
||||
if (!_.isEmpty(searchResult)) {
|
||||
// Make sure that song, album and artist are arrays using concat
|
||||
// because Madsonic will return an object when there's only one element
|
||||
switch (type) {
|
||||
case 0:
|
||||
if (subsonicResponse.searchResult2.song !== undefined) {
|
||||
return map.mapSongs([].concat(subsonicResponse.searchResult2.song));
|
||||
if (searchResult.song !== undefined) {
|
||||
return map.mapSongs([].concat(searchResult.song));
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (subsonicResponse.searchResult2.album !== undefined) {
|
||||
return map.mapAlbums([].concat(subsonicResponse.searchResult2.album));
|
||||
if (searchResult.album !== undefined) {
|
||||
return map.mapAlbums([].concat(searchResult.album));
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (subsonicResponse.searchResult2.artist !== undefined) {
|
||||
return [].concat(subsonicResponse.searchResult2.artist);
|
||||
if (searchResult.artist !== undefined) {
|
||||
return [].concat(searchResult.artist);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -353,10 +383,11 @@ angular.module('jamstash.subsonic.service', ['angular-underscore/utils',
|
|||
if (!isNaN(folder)) {
|
||||
params.musicFolderId = folder;
|
||||
}
|
||||
var promise = this.subsonicRequest('getRandomSongs.view', {
|
||||
var promise = subsonicService.subsonicRequest('getRandomSongs.view', {
|
||||
params: params
|
||||
}).then(function (subsonicResponse) {
|
||||
if(subsonicResponse.randomSongs !== undefined) {
|
||||
// Make sure this is an array using concat because Madsonic will return an object when there's only one element
|
||||
var songArray = [].concat(subsonicResponse.randomSongs.song);
|
||||
if (songArray.length > 0) {
|
||||
return map.mapSongs(songArray);
|
||||
|
@ -369,7 +400,7 @@ angular.module('jamstash.subsonic.service', ['angular-underscore/utils',
|
|||
},
|
||||
|
||||
getStarred: function () {
|
||||
var promise = this.subsonicRequest('getStarred.view', { cache: true })
|
||||
var promise = subsonicService.subsonicRequest('getStarred.view', { cache: true })
|
||||
.then(function (subsonicResponse) {
|
||||
if(angular.equals(subsonicResponse.starred, {})) {
|
||||
return $q.reject({reason: 'Nothing is starred on the Subsonic server.'});
|
||||
|
@ -381,9 +412,10 @@ angular.module('jamstash.subsonic.service', ['angular-underscore/utils',
|
|||
},
|
||||
|
||||
getRandomStarredSongs: function () {
|
||||
var promise = this.getStarred()
|
||||
var promise = subsonicService.getStarred()
|
||||
.then(function (starred) {
|
||||
if(starred.song !== undefined) {
|
||||
// Make sure this is an array using concat because Madsonic will return an object when there's only one element
|
||||
var songArray = [].concat(starred.song);
|
||||
if (songArray.length > 0) {
|
||||
// Return random subarray of songs
|
||||
|
@ -399,9 +431,10 @@ angular.module('jamstash.subsonic.service', ['angular-underscore/utils',
|
|||
|
||||
getPlaylists: function () {
|
||||
var exception = {reason: 'No playlist found on the Subsonic server.'};
|
||||
var promise = this.subsonicRequest('getPlaylists.view')
|
||||
var promise = subsonicService.subsonicRequest('getPlaylists.view')
|
||||
.then(function (subsonicResponse) {
|
||||
if(subsonicResponse.playlists.playlist !== undefined) {
|
||||
// Make sure this is an array using concat because Madsonic will return an object when there's only one element
|
||||
var playlistArray = [].concat(subsonicResponse.playlists.playlist);
|
||||
if (playlistArray.length > 0) {
|
||||
var allPlaylists = _(playlistArray).partition(function (item) {
|
||||
|
@ -418,12 +451,13 @@ angular.module('jamstash.subsonic.service', ['angular-underscore/utils',
|
|||
|
||||
getPlaylist: function (id) {
|
||||
var exception = {reason: 'This playlist is empty.'};
|
||||
var promise = this.subsonicRequest('getPlaylist.view', {
|
||||
var promise = subsonicService.subsonicRequest('getPlaylist.view', {
|
||||
params: {
|
||||
id: id
|
||||
}
|
||||
}).then(function (subsonicResponse) {
|
||||
if (subsonicResponse.playlist.entry !== undefined) {
|
||||
// Make sure this is an array using concat because Madsonic will return an object when there's only one element
|
||||
var entryArray = [].concat(subsonicResponse.playlist.entry);
|
||||
if (entryArray.length > 0) {
|
||||
return map.mapSongs(entryArray);
|
||||
|
@ -436,7 +470,7 @@ angular.module('jamstash.subsonic.service', ['angular-underscore/utils',
|
|||
},
|
||||
|
||||
newPlaylist: function (name) {
|
||||
var promise = this.subsonicRequest('createPlaylist.view', {
|
||||
var promise = subsonicService.subsonicRequest('createPlaylist.view', {
|
||||
params: {
|
||||
name: name
|
||||
}
|
||||
|
@ -445,7 +479,7 @@ angular.module('jamstash.subsonic.service', ['angular-underscore/utils',
|
|||
},
|
||||
|
||||
deletePlaylist: function (id) {
|
||||
var promise = this.subsonicRequest('deletePlaylist.view', {
|
||||
var promise = subsonicService.subsonicRequest('deletePlaylist.view', {
|
||||
params: {
|
||||
id: id
|
||||
}
|
||||
|
@ -463,7 +497,7 @@ angular.module('jamstash.subsonic.service', ['angular-underscore/utils',
|
|||
for (var i = 0; i < songs.length; i++) {
|
||||
params.params.songId.push(songs[i].id);
|
||||
}
|
||||
return this.subsonicRequest('createPlaylist.view', params);
|
||||
return subsonicService.subsonicRequest('createPlaylist.view', params);
|
||||
},
|
||||
|
||||
//TODO: Hyz: move to controller
|
||||
|
@ -507,13 +541,14 @@ angular.module('jamstash.subsonic.service', ['angular-underscore/utils',
|
|||
|
||||
getPodcasts: function () {
|
||||
var exception = {reason: 'No podcast found on the Subsonic server.'};
|
||||
var promise = this.subsonicRequest('getPodcasts.view', {
|
||||
var promise = subsonicService.subsonicRequest('getPodcasts.view', {
|
||||
params: {
|
||||
includeEpisodes: false
|
||||
}
|
||||
})
|
||||
.then(function (subsonicResponse) {
|
||||
if (subsonicResponse.podcasts !== undefined && subsonicResponse.podcasts.channel !== undefined) {
|
||||
// Make sure this is an array using concat because Madsonic will return an object when there's only one element
|
||||
var channelArray = [].concat(subsonicResponse.podcasts.channel);
|
||||
if (channelArray.length > 0) {
|
||||
return channelArray;
|
||||
|
@ -527,7 +562,7 @@ angular.module('jamstash.subsonic.service', ['angular-underscore/utils',
|
|||
|
||||
getPodcast: function (id) {
|
||||
var exception = {reason: 'This podcast was not found on the Subsonic server.'};
|
||||
var promise = this.subsonicRequest('getPodcasts.view', {
|
||||
var promise = subsonicService.subsonicRequest('getPodcasts.view', {
|
||||
params: {
|
||||
id: id,
|
||||
includeEpisodes: true
|
||||
|
@ -535,10 +570,12 @@ angular.module('jamstash.subsonic.service', ['angular-underscore/utils',
|
|||
}).then(function (subsonicResponse) {
|
||||
var episodes = [];
|
||||
if (subsonicResponse.podcasts.channel !== undefined) {
|
||||
// Make sure this is an array using concat because Madsonic will return an object when there's only one element
|
||||
var channelArray = [].concat(subsonicResponse.podcasts.channel);
|
||||
if (channelArray.length > 0) {
|
||||
var channel = channelArray[0];
|
||||
if (channel !== null && channel.id === id) {
|
||||
// Make sure this is an array using concat because Madsonic will return an object when there's only one element
|
||||
var episodesArray = [].concat(channel.episode);
|
||||
episodes = _(episodesArray).filter(function (episode) {
|
||||
return episode.status === "completed";
|
||||
|
@ -558,7 +595,7 @@ angular.module('jamstash.subsonic.service', ['angular-underscore/utils',
|
|||
},
|
||||
|
||||
scrobble: function (song) {
|
||||
var promise = this.subsonicRequest('scrobble.view', {
|
||||
var promise = subsonicService.subsonicRequest('scrobble.view', {
|
||||
params: {
|
||||
id: song.id,
|
||||
submisssion: true
|
||||
|
@ -568,7 +605,20 @@ angular.module('jamstash.subsonic.service', ['angular-underscore/utils',
|
|||
return true;
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
|
||||
toggleStar: function (item) {
|
||||
var partialUrl = (item.starred) ? 'unstar.view' : 'star.view';
|
||||
var promise = subsonicService.subsonicRequest(partialUrl, {
|
||||
params: {
|
||||
id: item.id
|
||||
}
|
||||
}).then(function () {
|
||||
return !item.starred;
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
// End subsonic
|
||||
};
|
||||
return subsonicService;
|
||||
}]);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
describe("Subsonic service -", function() {
|
||||
'use strict';
|
||||
|
||||
var subsonic, mockBackend, mockGlobals;
|
||||
var response;
|
||||
var $q, mockBackend, subsonic, mockGlobals,
|
||||
response, url;
|
||||
beforeEach(function() {
|
||||
// We redefine it because in some tests we need to alter the settings
|
||||
mockGlobals = {
|
||||
|
@ -46,9 +46,12 @@ describe("Subsonic service -", function() {
|
|||
});
|
||||
});
|
||||
|
||||
inject(function (_subsonic_, $httpBackend) {
|
||||
installPromiseMatchers();
|
||||
|
||||
inject(function (_subsonic_, $httpBackend, _$q_) {
|
||||
subsonic = _subsonic_;
|
||||
mockBackend = $httpBackend;
|
||||
$q = _$q_;
|
||||
});
|
||||
response = {"subsonic-response": {status: "ok", version: "1.10.2"}};
|
||||
});
|
||||
|
@ -59,40 +62,65 @@ describe("Subsonic service -", function() {
|
|||
});
|
||||
|
||||
describe("subsonicRequest() -", function() {
|
||||
var partialUrl, url;
|
||||
var partialUrl;
|
||||
beforeEach(function() {
|
||||
partialUrl = '/getStarred.view';
|
||||
url ='http://demo.subsonic.com/rest/getStarred.view?'+
|
||||
'c=Jamstash&callback=JSON_CALLBACK&f=jsonp&p=enc:cGFzc3dvcmQ%3D&u=Hyzual&v=1.10.2';
|
||||
});
|
||||
|
||||
it("Given that the Subsonic server is not responding, when I make a request to Subsonic it returns an error object with a message", function() {
|
||||
it("Given that the Subsonic server was not responding, when I make a request to Subsonic, then an error object with a message will be returned", function() {
|
||||
mockBackend.expectJSONP(url).respond(503, 'Service Unavailable');
|
||||
|
||||
var promise = subsonic.subsonicRequest(partialUrl);
|
||||
mockBackend.flush();
|
||||
|
||||
expect(promise).toBeRejectedWith({reason: 'Error when contacting the Subsonic server.', httpError: 503});
|
||||
expect(promise).toBeRejectedWith({
|
||||
reason: 'Error when contacting the Subsonic server.',
|
||||
httpError: 503
|
||||
});
|
||||
});
|
||||
|
||||
it("Given a missing parameter, when I make a request to Subsonic it returns an error object with a message", function() {
|
||||
it("Given a missing parameter, when I make a request to Subsonic, then an error object with a message will be returned", function() {
|
||||
delete mockGlobals.settings.Password;
|
||||
var missingPasswordUrl = 'http://demo.subsonic.com/rest/getStarred.view?'+
|
||||
'c=Jamstash&callback=JSON_CALLBACK&f=jsonp&u=Hyzual&v=1.10.2';
|
||||
var errorResponse = {"subsonic-response" : {
|
||||
"status" : "failed",
|
||||
"version" : "1.10.2",
|
||||
"error" : {"code" : 10,"message" : "Required parameter is missing."}
|
||||
var errorResponse = {'subsonic-response' : {
|
||||
status : 'failed',
|
||||
version : '1.10.2',
|
||||
error : {code : 10, message : 'Required parameter is missing.'}
|
||||
}};
|
||||
mockBackend.expectJSONP(missingPasswordUrl).respond(JSON.stringify(errorResponse));
|
||||
|
||||
var promise = subsonic.subsonicRequest(partialUrl);
|
||||
mockBackend.flush();
|
||||
|
||||
expect(promise).toBeRejectedWith({reason: 'Error when contacting the Subsonic server.', subsonicError: {code: 10, message:'Required parameter is missing.'}});
|
||||
expect(promise).toBeRejectedWith({
|
||||
reason: 'Error when contacting the Subsonic server.',
|
||||
subsonicError: {code: 10, message:'Required parameter is missing.'},
|
||||
version: '1.10.2'
|
||||
});
|
||||
});
|
||||
|
||||
it("Given a partialUrl that does not start with '/', it adds '/' before it and makes a correct request", function() {
|
||||
it("Given that server and Jamstash had different api versions, when I make a request to Subsonic and the server responds an error, then it will return the server's api version in the error object", function() {
|
||||
var errorResponse = {'subsonic-response': {
|
||||
status: 'failed',
|
||||
version: '1.8.0',
|
||||
error: {code: 30, message: 'Incompatible Subsonic REST protocol version. Server must upgrade.'}
|
||||
}};
|
||||
mockBackend.expectJSONP(url).respond(JSON.stringify(errorResponse));
|
||||
|
||||
var promise = subsonic.subsonicRequest(partialUrl);
|
||||
mockBackend.flush();
|
||||
|
||||
expect(promise).toBeRejectedWith({
|
||||
reason: 'Error when contacting the Subsonic server.',
|
||||
subsonicError: {code: 30, message: 'Incompatible Subsonic REST protocol version. Server must upgrade.'},
|
||||
version: '1.8.0'
|
||||
});
|
||||
});
|
||||
|
||||
it("Given a partialUrl that did not start with '/', when I make a request to Subsonic, then a '/' will be added before the partial url", function() {
|
||||
partialUrl = 'getStarred.view';
|
||||
mockBackend.expectJSONP(url).respond(JSON.stringify(response));
|
||||
|
||||
|
@ -100,7 +128,7 @@ describe("Subsonic service -", function() {
|
|||
mockBackend.flush();
|
||||
});
|
||||
|
||||
it("Given $http config params, it does not overwrite them", function() {
|
||||
it("Given $http config params, when I make a request to Subsonic, then the params won't be overwritten", function() {
|
||||
partialUrl = 'scrobble.view';
|
||||
url ='http://demo.subsonic.com/rest/scrobble.view?'+
|
||||
'c=Jamstash&callback=JSON_CALLBACK&f=jsonp&id=75&p=enc:cGFzc3dvcmQ%3D&submission=false&u=Hyzual&v=1.10.2';
|
||||
|
@ -115,7 +143,7 @@ describe("Subsonic service -", function() {
|
|||
mockBackend.flush();
|
||||
});
|
||||
|
||||
it("Given that the global protocol setting is 'json', when I make a request to Subsonic it uses GET and does not use the JSON_CALLBACK parameter", function() {
|
||||
it("Given that the global protocol setting was 'json', when I make a request to Subsonic, then it will use GET and won't use the JSON_CALLBACK parameter", function() {
|
||||
mockGlobals.settings.Protocol = 'json';
|
||||
var getUrl = 'http://demo.subsonic.com/rest/getStarred.view?'+
|
||||
'c=Jamstash&f=json&p=enc:cGFzc3dvcmQ%3D&u=Hyzual&v=1.10.2';
|
||||
|
@ -129,7 +157,6 @@ describe("Subsonic service -", function() {
|
|||
});
|
||||
|
||||
describe("getAlbums() -", function() {
|
||||
var url;
|
||||
beforeEach(function() {
|
||||
url = 'http://demo.subsonic.com/rest/getMusicDirectory.view?'+
|
||||
'c=Jamstash&callback=JSON_CALLBACK&f=jsonp'+'&id=21'+'&p=enc:cGFzc3dvcmQ%3D&u=Hyzual&v=1.10.2';
|
||||
|
@ -182,62 +209,127 @@ describe("Subsonic service -", function() {
|
|||
});
|
||||
});
|
||||
|
||||
//TODO: Hyz: Rename into getDirectory(), because we don't know if there will be songs or other directories in it
|
||||
describe("getSongs() -", function() {
|
||||
var url;
|
||||
beforeEach(function() {
|
||||
var url = 'http://demo.subsonic.com/rest/getMusicDirectory.view?'+
|
||||
url = 'http://demo.subsonic.com/rest/getMusicDirectory.view?'+
|
||||
'c=Jamstash&callback=JSON_CALLBACK&f=jsonp'+'&id=209'+'&p=enc:cGFzc3dvcmQ%3D&u=Hyzual&v=1.10.2';
|
||||
});
|
||||
|
||||
it("Given that there were 2 songs in the given directory id in my library, when I get the songs from that directory, then a promise will be resolved with an array of 2 songs", function() {
|
||||
it("Given a directory containing 2 songs and 1 subdirectory and given its id, when I get the songs from that directory, then a promise will be resolved with an array of 2 songs and an array of 1 subdirectory", function() {
|
||||
response["subsonic-response"].directory = {
|
||||
child: [
|
||||
{ id: 778 },
|
||||
{ id: 614 }
|
||||
{ id: 614 },
|
||||
{ id: 205, isDir: true}
|
||||
]
|
||||
};
|
||||
mockBackend.expectJSONP(url).respond(JSON.stringify(response));
|
||||
|
||||
var promise = subsonic.getSongs(209);
|
||||
//TODO: Hyz: Replace with toBeResolvedWith() when getSongs() is refactored
|
||||
var success = function (data) {
|
||||
expect(data.album).toEqual([]);
|
||||
expect(data.song).toEqual([
|
||||
{ id: 778 },
|
||||
{ id: 614 }
|
||||
]);
|
||||
};
|
||||
promise.then(success);
|
||||
|
||||
mockBackend.flush();
|
||||
|
||||
expect(promise).toBeResolved();
|
||||
expect(promise).toBeResolvedWith({
|
||||
directories: [{ id: 205, isDir: true}],
|
||||
songs: [{id: 778}, {id: 614}]
|
||||
});
|
||||
});
|
||||
|
||||
it("Given that there was only 1 song in the given directory id in my Madsonic library, when I get the songs from that directory, then a promise will be resolved with an array of 1 song", function() {
|
||||
it("Given a directory containing 1 song in my Madsonic library and given its id, when I get the songs from that directory, then a promise will be resolved with an array of 1 song and an empty array of subdirectories", function() {
|
||||
response["subsonic-response"].directory = {
|
||||
child: { id: 402 }
|
||||
};
|
||||
mockBackend.expectJSONP(url).respond(JSON.stringify(response));
|
||||
|
||||
var promise = subsonic.getSongs(209);
|
||||
//TODO: Hyz: Replace with toBeResolvedWith() when getSongs() is refactored
|
||||
var success = function (data) {
|
||||
expect(data.album).toEqual([]);
|
||||
expect(data.song).toEqual([
|
||||
{ id: 402 }
|
||||
]);
|
||||
};
|
||||
promise.then(success);
|
||||
|
||||
mockBackend.flush();
|
||||
|
||||
expect(promise).toBeResolved();
|
||||
expect(promise).toBeResolvedWith({
|
||||
directories: [],
|
||||
songs: [{id: 402}]
|
||||
});
|
||||
});
|
||||
|
||||
it("Given a directory that didn't contain anything and given its id, when I get the songs from that directory, then a promise will be rejected with an error message", function() {
|
||||
response["subsonic-response"].directory = {};
|
||||
mockBackend.expectJSONP(url).respond(JSON.stringify(response));
|
||||
|
||||
var promise = subsonic.getSongs(209);
|
||||
mockBackend.flush();
|
||||
|
||||
expect(promise).toBeRejectedWith({reason: 'This directory is empty.'});
|
||||
});
|
||||
});
|
||||
|
||||
describe("recursiveGetSongs() -", function() {
|
||||
var deferred;
|
||||
beforeEach(function() {
|
||||
deferred = $q.defer();
|
||||
spyOn(subsonic, 'getSongs').and.returnValue(deferred.promise);
|
||||
});
|
||||
it("Given a directory containing 2 songs and a subdirectory itself containing 2 songs and given its id, when I get the songs from that directory, then a promise will be resolved with an array of 4 songs", function() {
|
||||
// Mock getSongs so we are only testing the recursivity
|
||||
var firstDeferred = $q.defer();
|
||||
var secondDeferred = $q.defer();
|
||||
subsonic.getSongs.and.callFake(function (id) {
|
||||
// First call to getSongs
|
||||
if (id === 499) {
|
||||
return firstDeferred.promise;
|
||||
// Second call to getSongs
|
||||
} else if (id === 553) {
|
||||
return secondDeferred.promise;
|
||||
}
|
||||
});
|
||||
|
||||
var promise = subsonic.recursiveGetSongs(499);
|
||||
// On the first call to getSongs, we expect 2 songs and a subdirectory
|
||||
firstDeferred.resolve({
|
||||
directories: [{ id: 553, type: 'byfolder' }],
|
||||
songs: [
|
||||
{ id: 695 },
|
||||
{ id: 227 }
|
||||
]
|
||||
});
|
||||
// On the second call, we expect 2 songs
|
||||
secondDeferred.resolve({
|
||||
directories: [],
|
||||
songs: [
|
||||
{ id: 422 },
|
||||
{ id: 171 }
|
||||
]
|
||||
});
|
||||
|
||||
expect(promise).toBeResolvedWith([
|
||||
{ id: 695 },
|
||||
{ id: 227 },
|
||||
{ id: 422 },
|
||||
{ id: 171 },
|
||||
]);
|
||||
});
|
||||
|
||||
it("Given a directory containing only 2 songs and given its id, when I get the songs from that directory, then a promise will be resolved with an array of 2 songs", function() {
|
||||
var promise = subsonic.recursiveGetSongs(14);
|
||||
deferred.resolve({
|
||||
directories: [],
|
||||
songs: [
|
||||
{ id: 33 }, { id: 595 }
|
||||
]
|
||||
});
|
||||
|
||||
expect(promise).toBeResolvedWith([
|
||||
{ id: 33 }, { id: 595 }
|
||||
]);
|
||||
});
|
||||
|
||||
it("Given a directory that didn't contain anything and given its id, when I get the songs from that directory, then a promise will be resolved with an empty array", function() {
|
||||
var promise = subsonic.recursiveGetSongs(710);
|
||||
deferred.reject({reason: 'This directory is empty.'});
|
||||
|
||||
expect(promise).toBeResolvedWith([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getAlbumListBy() -", function() {
|
||||
var url;
|
||||
beforeEach(function() {
|
||||
url = 'http://demo.subsonic.com/rest/getAlbumList.view?'+
|
||||
'c=Jamstash&callback=JSON_CALLBACK&f=jsonp'+'&offset=0'+'&p=enc:cGFzc3dvcmQ%3D'+'&size=3&type=newest'+'&u=Hyzual&v=1.10.2';
|
||||
|
@ -295,7 +387,7 @@ describe("Subsonic service -", function() {
|
|||
});
|
||||
|
||||
describe("getStarred() -", function() {
|
||||
var url
|
||||
var url;
|
||||
beforeEach(function() {
|
||||
url = 'http://demo.subsonic.com/rest/getStarred.view?'+
|
||||
'c=Jamstash&callback=JSON_CALLBACK&f=jsonp&p=enc:cGFzc3dvcmQ%3D&u=Hyzual&v=1.10.2';
|
||||
|
@ -331,7 +423,6 @@ describe("Subsonic service -", function() {
|
|||
});
|
||||
|
||||
describe("getRandomStarredSongs() -", function() {
|
||||
var url;
|
||||
beforeEach(function() {
|
||||
url = 'http://demo.subsonic.com/rest/getStarred.view?'+
|
||||
'c=Jamstash&callback=JSON_CALLBACK&f=jsonp&p=enc:cGFzc3dvcmQ%3D&u=Hyzual&v=1.10.2';
|
||||
|
@ -393,7 +484,6 @@ describe("Subsonic service -", function() {
|
|||
});
|
||||
|
||||
describe("getRandomSongs() -", function() {
|
||||
var url;
|
||||
beforeEach(function() {
|
||||
url = 'http://demo.subsonic.com/rest/getRandomSongs.view?'+
|
||||
'c=Jamstash&callback=JSON_CALLBACK&f=jsonp&p=enc:cGFzc3dvcmQ%3D'+'&size=3'+'&u=Hyzual&v=1.10.2';
|
||||
|
@ -507,8 +597,83 @@ describe("Subsonic service -", function() {
|
|||
expect(promise).toBeResolvedWith(true);
|
||||
});
|
||||
|
||||
|
||||
describe("toggleStar() -", function() {
|
||||
it("Given an item (can be an artist, an album or a song) that wasn't starred, when I toggle its star, then a promise will be resolved with true", function() {
|
||||
var song = { id: 7748, starred: false };
|
||||
var url = 'http://demo.subsonic.com/rest/star.view?' +
|
||||
'c=Jamstash&callback=JSON_CALLBACK&f=jsonp'+'&id=7748'+'&p=enc:cGFzc3dvcmQ%3D&u=Hyzual&v=1.10.2';
|
||||
mockBackend.expectJSONP(url).respond(JSON.stringify(response));
|
||||
|
||||
var promise = subsonic.toggleStar(song);
|
||||
mockBackend.flush();
|
||||
|
||||
expect(promise).toBeResolvedWith(true);
|
||||
});
|
||||
|
||||
it("Given an item (can be an artist, an album or a song) that was starred, when I toggle its star, then a promise will be resolved with false", function() {
|
||||
var album = { id: 6631, starred: true };
|
||||
var url = 'http://demo.subsonic.com/rest/unstar.view?' +
|
||||
'c=Jamstash&callback=JSON_CALLBACK&f=jsonp'+'&id=6631'+'&p=enc:cGFzc3dvcmQ%3D&u=Hyzual&v=1.10.2';
|
||||
mockBackend.expectJSONP(url).respond(JSON.stringify(response));
|
||||
|
||||
var promise = subsonic.toggleStar(album);
|
||||
mockBackend.flush();
|
||||
|
||||
expect(promise).toBeResolvedWith(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getMusicFolders() -", function() {
|
||||
beforeEach(function() {
|
||||
url = 'http://demo.subsonic.com/rest/getMusicFolders.view?'+
|
||||
'c=Jamstash&callback=JSON_CALLBACK&f=jsonp&p=enc:cGFzc3dvcmQ%3D&u=Hyzual&v=1.10.2';
|
||||
});
|
||||
|
||||
it("Given that there were 2 music folders in my library, when I get the music folders, then a promise will be resolved with an array of 2 music folders", function() {
|
||||
response["subsonic-response"].musicFolders = {
|
||||
musicFolder: [
|
||||
{ id: 80, name: "languageless" },
|
||||
{ id: 38, name: "mala" }
|
||||
]
|
||||
};
|
||||
mockBackend.expectJSONP(url).respond(JSON.stringify(response));
|
||||
|
||||
var promise = subsonic.getMusicFolders();
|
||||
mockBackend.flush();
|
||||
|
||||
expect(promise).toBeResolvedWith([
|
||||
{ id: 80, name: "languageless" },
|
||||
{ id: 38, name: "mala" }
|
||||
]);
|
||||
});
|
||||
|
||||
it("Given that there was 1 music folder in my Madsonic library, when I get the music folders, then a promise will be resolved with an array of 1 music folder", function() {
|
||||
response["subsonic-response"].musicFolders = {
|
||||
musicFolder: {id: 56, name: "dite"}
|
||||
};
|
||||
mockBackend.expectJSONP(url).respond(JSON.stringify(response));
|
||||
|
||||
var promise = subsonic.getMusicFolders();
|
||||
mockBackend.flush();
|
||||
|
||||
expect(promise).toBeResolvedWith([
|
||||
{ id: 56, name: "dite"}
|
||||
]);
|
||||
});
|
||||
|
||||
it("Given that there wasn't any music folder in my library, when I get the music folders, then a promise will be rejected with an error message", function() {
|
||||
response["subsonic-response"].musicFolders = {};
|
||||
mockBackend.expectJSONP(url).respond(JSON.stringify(response));
|
||||
|
||||
var promise = subsonic.getMusicFolders();
|
||||
mockBackend.flush();
|
||||
|
||||
expect(promise).toBeRejectedWith({reason: 'No music folder found on the Subsonic server.'});
|
||||
});
|
||||
});
|
||||
|
||||
describe("getArtists() -", function() {
|
||||
var url;
|
||||
beforeEach(function() {
|
||||
url = 'http://demo.subsonic.com/rest/getIndexes.view?'+
|
||||
'c=Jamstash&callback=JSON_CALLBACK&f=jsonp&p=enc:cGFzc3dvcmQ%3D&u=Hyzual&v=1.10.2';
|
||||
|
@ -542,6 +707,15 @@ describe("Subsonic service -", function() {
|
|||
expect(promise).toBeResolvedWith(response["subsonic-response"].indexes);
|
||||
});
|
||||
|
||||
it("Given a folder id, when I get the artists, then it will be used as parameter in the request", function() {
|
||||
url = 'http://demo.subsonic.com/rest/getIndexes.view?'+
|
||||
'c=Jamstash&callback=JSON_CALLBACK&f=jsonp'+'&musicFolderId=54'+'&p=enc:cGFzc3dvcmQ%3D&u=Hyzual&v=1.10.2';
|
||||
mockBackend.expectJSONP(url).respond(JSON.stringify(response));
|
||||
|
||||
subsonic.getArtists(54);
|
||||
mockBackend.flush();
|
||||
});
|
||||
|
||||
it("Given that there were 2 artist at the top level of my Madsonic library, when I get the artists, then a promise will be resolved with an array of two artist", function() {
|
||||
response["subsonic-response"].indexes = {
|
||||
shortcut: { id: 433, name: "Podcast" },
|
||||
|
@ -689,7 +863,6 @@ describe("Subsonic service -", function() {
|
|||
});
|
||||
|
||||
describe("getPlaylist() -", function() {
|
||||
var url;
|
||||
beforeEach(function() {
|
||||
url = 'http://demo.subsonic.com/rest/getPlaylist.view?'+
|
||||
'c=Jamstash&callback=JSON_CALLBACK&f=jsonp'+'&id=9123'+'&p=enc:cGFzc3dvcmQ%3D&u=Hyzual&v=1.10.2';
|
||||
|
@ -781,7 +954,6 @@ describe("Subsonic service -", function() {
|
|||
});
|
||||
|
||||
describe("getPodcasts() -", function() {
|
||||
var url;
|
||||
beforeEach(function() {
|
||||
url = 'http://demo.subsonic.com/rest/getPodcasts.view?'+
|
||||
'c=Jamstash&callback=JSON_CALLBACK&f=jsonp'+'&includeEpisodes=false'+'&p=enc:cGFzc3dvcmQ%3D&u=Hyzual&v=1.10.2';
|
||||
|
@ -833,7 +1005,6 @@ describe("Subsonic service -", function() {
|
|||
});
|
||||
|
||||
describe("getPodcast() -", function() {
|
||||
var url;
|
||||
beforeEach(function() {
|
||||
url = 'http://demo.subsonic.com/rest/getPodcasts.view?'+
|
||||
'c=Jamstash&callback=JSON_CALLBACK&f=jsonp'+'&id=2695&includeEpisodes=true'+'&p=enc:cGFzc3dvcmQ%3D&u=Hyzual&v=1.10.2';
|
||||
|
@ -933,7 +1104,6 @@ describe("Subsonic service -", function() {
|
|||
});
|
||||
|
||||
describe("search() -", function() {
|
||||
var url;
|
||||
beforeEach(function() {
|
||||
url = 'http://demo.subsonic.com/rest/search2.view?'+
|
||||
'c=Jamstash&callback=JSON_CALLBACK&f=jsonp&p=enc:cGFzc3dvcmQ%3D'+'&query=unintersetingly'+'&u=Hyzual&v=1.10.2';
|
||||
|
@ -967,6 +1137,34 @@ describe("Subsonic service -", function() {
|
|||
]);
|
||||
});
|
||||
|
||||
it("Given that songs containing 'unintersetingly' existed in my Music Cabinet library, when I search for a song that contains 'unintersetingly', then a promise will be resolved with an array of songs", function() {
|
||||
response["subsonic-response"].search2 = {
|
||||
song: [
|
||||
{
|
||||
id: 7907,
|
||||
name: "unintersetingly Caragana"
|
||||
}, {
|
||||
id: 4089,
|
||||
name: "attacolite unintersetingly"
|
||||
}
|
||||
]
|
||||
};
|
||||
mockBackend.expectJSONP(url).respond(JSON.stringify(response));
|
||||
|
||||
var promise = subsonic.search("unintersetingly", 0);
|
||||
mockBackend.flush();
|
||||
|
||||
expect(promise).toBeResolvedWith([
|
||||
{
|
||||
id: 7907,
|
||||
name: "unintersetingly Caragana"
|
||||
}, {
|
||||
id: 4089,
|
||||
name: "attacolite unintersetingly"
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it("Given that only one song containing 'unintersetingly' existed in my Madsonic library, when I search for a song that contains 'unintersetingly', then a promise will be resolved with an array of one song", function() {
|
||||
response["subsonic-response"].searchResult2 = {
|
||||
song: { id: 142, name: "unintersetingly rescue" }
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
<div id="BreadCrumbs" class="floatleft">
|
||||
<div class="crumb"><a ng-click="toggleArtists()" title="Toggle Artists">Artists</a> ></div>
|
||||
<div class="crumb" ng-repeat="o in BreadCrumbs | filter:{type:'artist'}"><a ng-click="getAlbums(o.id, o.name)">{{o.name}}</a> ></div>
|
||||
<div class="crumb" ng-repeat="o in BreadCrumbs | filter:{type:'album'}"><a ng-click="getSongs(o.id, '')">{{o.name}}</a> ></div>
|
||||
<div class="crumb" ng-repeat="o in BreadCrumbs | filter:{type:'album'}"><a ng-click="getSongs('display', o.id, o.name)">{{o.name}}</a> ></div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
@ -49,12 +49,12 @@
|
|||
<div class="clear"></div>
|
||||
<ul class="simplelist songlist noselect">
|
||||
<div class="" ng-repeat="o in album" ng-switch on="o.type">
|
||||
<li class="album" ng-switch-when="byfolder" id="{{o.id}}" ng-class="{'selected': selectedAlbum == o.id, 'albumgrid': settings.DefaultLibraryLayout.id == 'grid'}" ng-click="getSongs(o.id, '')" parentid="{{o.parentid}}">
|
||||
<li class="album" ng-switch-when="byfolder" id="{{o.id}}" ng-class="{'selected': selectedAlbum == o.id, 'albumgrid': settings.DefaultLibraryLayout.id == 'grid'}" ng-click="getSongs('display', o.id, o.name)" parentid="{{o.parentid}}">
|
||||
<div class="itemactions">
|
||||
<a class="add hover" href="" title="Add To Play Queue" ng-click="getSongs(o.id, 'add')" stop-event="click"></a>
|
||||
<a class="play hover" href="" title="Play" ng-click="getSongs(o.id, 'play')" stop-event="click"></a>
|
||||
<a class="add hover" href="" title="Add To Play Queue" ng-click="getSongs('add', o.id, o.name)" stop-event="click"></a>
|
||||
<a class="play hover" href="" title="Play" ng-click="getSongs('play', o.id, o.name)" stop-event="click"></a>
|
||||
<a class="download hover" href="" ng-click="download(o.id)" title="Download" stop-event="click"></a>
|
||||
<a title="Favorite hover" href="" ng-class="{'favorite': o.starred, 'rate': !o.starred}" ng-click="updateFavorite(o)" stop-event="click"></a>
|
||||
<a class="hover" href="" title="Star" ng-class="{'favorite': o.starred, 'rate': !o.starred}" ng-click="toggleStar(o)" stop-event="click"></a>
|
||||
<a class="info hover" href="" title="{{'Created: ' + o.date}}"></a>
|
||||
</div>
|
||||
<div class="albumart"><img ng-src="{{o.coverartthumb}}" src="images/albumdefault_160.jpg"></div>
|
||||
|
@ -86,7 +86,9 @@
|
|||
</ul>
|
||||
<div class="clear"></div>
|
||||
<div id="IndexContainer" class="leftsubsection" ng-show="showIndex">
|
||||
<select id="MusicFolders" class="folders" ng-model="$root.SelectedMusicFolder" ng-options="o.name for o in MusicFolders"></select>
|
||||
<select id="MusicFolders" class="folders" ng-model="SelectedMusicFolder" ng-options="o.name for o in MusicFolders track by o.id">
|
||||
<option value="">All Folders</option>
|
||||
</select>
|
||||
<div id="AZIndex" ng-show="!settings.HideAZ" class="subactionsfixed">
|
||||
<a href="" ng-click="toggleAZ()" stop-event="click">A-Z</a>
|
||||
</div>
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
* Access and use the Subsonic Server. The Controller is in charge of relaying the Service's messages to the user through the
|
||||
* notifications.
|
||||
*/
|
||||
angular.module('jamstash.subsonic.controller', ['jamstash.subsonic.service', 'jamstash.player.service'])
|
||||
angular.module('jamstash.subsonic.controller', ['jamstash.subsonic.service', 'jamstash.player.service', 'jamstash.persistence'])
|
||||
|
||||
.controller('SubsonicController', ['$scope', '$rootScope', '$routeParams', '$window', 'utils', 'globals', 'map', 'subsonic', 'notifications', 'player',
|
||||
function ($scope, $rootScope, $routeParams, $window, utils, globals, map, subsonic, notifications, player) {
|
||||
.controller('SubsonicController', ['$scope', '$rootScope', '$routeParams', '$window', 'utils', 'globals', 'map', 'subsonic', 'notifications', 'player', 'persistence',
|
||||
function ($scope, $rootScope, $routeParams, $window, utils, globals, map, subsonic, notifications, player, persistence) {
|
||||
'use strict';
|
||||
|
||||
$scope.settings = globals.settings;
|
||||
|
@ -141,13 +141,20 @@ angular.module('jamstash.subsonic.controller', ['jamstash.subsonic.service', 'ja
|
|||
}
|
||||
}
|
||||
});
|
||||
$rootScope.$watch("SelectedMusicFolder", function (newValue, oldValue) {
|
||||
|
||||
$scope.$watch("SelectedMusicFolder", function (newValue, oldValue) {
|
||||
if (newValue !== oldValue) {
|
||||
// TODO: Hyz: Move loading / saving the music folder to persistence-service
|
||||
utils.setValue('MusicFolders', angular.toJson(newValue), true);
|
||||
$scope.getArtists(newValue.id);
|
||||
var folderId;
|
||||
if (newValue) {
|
||||
folderId = newValue.id;
|
||||
persistence.saveSelectedMusicFolder(newValue);
|
||||
} else {
|
||||
persistence.deleteSelectedMusicFolder();
|
||||
}
|
||||
$scope.getArtists(folderId);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.searching = {
|
||||
query: "",
|
||||
typeId: globals.settings.DefaultSearchType,
|
||||
|
@ -201,7 +208,12 @@ angular.module('jamstash.subsonic.controller', ['jamstash.subsonic.service', 'ja
|
|||
$scope.song = data.song;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.getArtists = function (folder) {
|
||||
var savedFolder = $scope.SelectedMusicFolder;
|
||||
if (isNaN(folder) && savedFolder) {
|
||||
folder = savedFolder.id;
|
||||
}
|
||||
var promise = subsonic.getArtists(folder);
|
||||
$scope.handleErrors(promise).then(function (data) {
|
||||
$scope.index = data.index;
|
||||
|
@ -214,8 +226,9 @@ angular.module('jamstash.subsonic.controller', ['jamstash.subsonic.service', 'ja
|
|||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.refreshArtists = function () {
|
||||
utils.setValue('MusicFolders', null, true);
|
||||
$scope.SelectedMusicFolder = undefined;
|
||||
$scope.getArtists();
|
||||
$scope.getPlaylists();
|
||||
};
|
||||
|
@ -279,6 +292,7 @@ angular.module('jamstash.subsonic.controller', ['jamstash.subsonic.service', 'ja
|
|||
* @param {String} action the action to be taken with the songs. Must be 'add', 'play' or 'display'
|
||||
* @return {Promise} the original promise passed in first param. That way we can chain it further !
|
||||
*/
|
||||
//TODO: Hyz: Maybe we should move this to a service
|
||||
$scope.requestSongs = function (promise, action) {
|
||||
$scope.handleErrors(promise)
|
||||
.then(function (songs) {
|
||||
|
@ -300,18 +314,33 @@ angular.module('jamstash.subsonic.controller', ['jamstash.subsonic.service', 'ja
|
|||
return promise;
|
||||
};
|
||||
|
||||
$scope.getSongs = function (id, action) {
|
||||
subsonic.getSongs(id, action).then(function (data) {
|
||||
$scope.album = data.album;
|
||||
$scope.song = data.song;
|
||||
$scope.BreadCrumbs = data.breadcrumb;
|
||||
$scope.selectedAutoAlbum = data.selectedAutoAlbum;
|
||||
$scope.selectedArtist = data.selectedArtist;
|
||||
$scope.selectedPlaylist = data.selectedPlaylist;
|
||||
if ($scope.SelectedAlbumSort.id != "default") {
|
||||
$scope.getSongs = function (action, id, name) {
|
||||
var promise;
|
||||
if (action === 'play' || action === 'add') {
|
||||
promise = subsonic.recursiveGetSongs(id);
|
||||
$scope.requestSongs(promise, action);
|
||||
} else if (action === 'display') {
|
||||
promise = subsonic.getSongs(id);
|
||||
$scope.handleErrors(promise).then(function (data) {
|
||||
$scope.album = data.directories;
|
||||
$scope.song = data.songs;
|
||||
if ($scope.BreadCrumbs.length > 0) {
|
||||
$scope.BreadCrumbs.splice(1, ($scope.BreadCrumbs.length - 1));
|
||||
}
|
||||
$scope.BreadCrumbs.push({'type': 'album', 'id': id, 'name': name});
|
||||
$scope.selectedAutoAlbum = null;
|
||||
$scope.selectedArtist = null;
|
||||
$scope.selectedAlbum = id;
|
||||
$scope.selectedAutoPlaylist = null;
|
||||
$scope.selectedPlaylist = null;
|
||||
$scope.selectedPodcast = null;
|
||||
if ($scope.SelectedAlbumSort.id !== "default") {
|
||||
sortSubsonicAlbums($scope.SelectedAlbumSort.id);
|
||||
}
|
||||
}, function (error) {
|
||||
notifications.updateMessage(error.reason, true);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.getRandomStarredSongs = function (action) {
|
||||
|
@ -578,43 +607,19 @@ angular.module('jamstash.subsonic.controller', ['jamstash.subsonic.service', 'ja
|
|||
};
|
||||
|
||||
$scope.getMusicFolders = function () {
|
||||
$.ajax({
|
||||
url: globals.BaseURL() + '/getMusicFolders.view?' + globals.BaseParams(),
|
||||
method: 'GET',
|
||||
dataType: globals.settings.Protocol,
|
||||
timeout: globals.settings.Timeout,
|
||||
success: function (data) {
|
||||
if (data["subsonic-response"].musicFolders.musicFolder !== undefined) {
|
||||
var folders = [];
|
||||
if (data["subsonic-response"].musicFolders.musicFolder.length > 0) {
|
||||
folders = data["subsonic-response"].musicFolders.musicFolder;
|
||||
} else {
|
||||
folders[0] = data["subsonic-response"].musicFolders.musicFolder;
|
||||
}
|
||||
|
||||
folders.unshift({
|
||||
"id": -1,
|
||||
"name": "All Folders"
|
||||
});
|
||||
$rootScope.MusicFolders = folders;
|
||||
if (utils.getValue('MusicFolders')) {
|
||||
var folder = angular.fromJson(utils.getValue('MusicFolders'));
|
||||
var i = 0, index = "";
|
||||
angular.forEach($rootScope.MusicFolders, function (item, key) {
|
||||
if (item.id == folder.id) {
|
||||
index = i;
|
||||
}
|
||||
i++;
|
||||
});
|
||||
$rootScope.SelectedMusicFolder = $rootScope.MusicFolders[index];
|
||||
} else {
|
||||
$rootScope.SelectedMusicFolder = $rootScope.MusicFolders[0];
|
||||
}
|
||||
$scope.$apply();
|
||||
var promise = subsonic.getMusicFolders();
|
||||
$scope.handleErrors(promise).then(function (musicFolders) {
|
||||
var folders = musicFolders;
|
||||
$scope.MusicFolders = folders;
|
||||
var savedFolder = persistence.getSelectedMusicFolder();
|
||||
if (savedFolder) {
|
||||
if (_.findIndex(folders, {id: savedFolder.id}) !== -1) {
|
||||
$scope.SelectedMusicFolder = savedFolder;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the order of playlists through jQuery UI's sortable
|
||||
*/
|
||||
|
@ -627,19 +632,19 @@ angular.module('jamstash.subsonic.controller', ['jamstash.subsonic.service', 'ja
|
|||
$scope.song.splice(end, 0, $scope.song.splice(start, 1)[0]);
|
||||
};
|
||||
$scope.playSong = function (song) {
|
||||
player.play(song);
|
||||
player.emptyQueue().addSong(song).playFirstSong();
|
||||
};
|
||||
$scope.addSongToQueue = function (song) {
|
||||
player.addSong(song);
|
||||
};
|
||||
|
||||
/* Launch on Startup */
|
||||
$scope.getMusicFolders();
|
||||
$scope.getArtists();
|
||||
$scope.getPlaylists();
|
||||
$scope.getGenres();
|
||||
$scope.getPodcasts();
|
||||
$scope.openDefaultSection();
|
||||
$scope.getMusicFolders();
|
||||
if ($routeParams.artistId && $routeParams.albumId) {
|
||||
$scope.getAlbumByTag($routeParams.albumId);
|
||||
} else if ($routeParams.artistId) {
|
||||
|
|
|
@ -2,59 +2,74 @@ describe("Subsonic controller", function() {
|
|||
'use strict';
|
||||
|
||||
var scope, $rootScope, $controller, $window,
|
||||
subsonic, notifications, player, controllerParams, deferred;
|
||||
subsonic, notifications, player, utils, persistence, controllerParams, deferred;
|
||||
|
||||
beforeEach(function() {
|
||||
jasmine.addCustomEqualityTester(angular.equals);
|
||||
|
||||
module('jamstash.subsonic.controller', function ($provide) {
|
||||
// Mock the player service
|
||||
$provide.decorator('player', function($delegate) {
|
||||
module('jamstash.subsonic.controller');
|
||||
|
||||
$delegate.queue = [];
|
||||
$delegate.play = jasmine.createSpy("play");
|
||||
$delegate.playFirstSong = jasmine.createSpy("playFirstSong");
|
||||
return $delegate;
|
||||
});
|
||||
});
|
||||
|
||||
inject(function (_$controller_, _$rootScope_, utils, globals, map, $q, _player_) {
|
||||
inject(function (_$controller_, _$rootScope_, globals, map, $q) {
|
||||
$rootScope = _$rootScope_;
|
||||
scope = $rootScope.$new();
|
||||
deferred = $q.defer();
|
||||
player = _player_;
|
||||
|
||||
$window = jasmine.createSpyObj("$window", [
|
||||
"prompt",
|
||||
"confirm"
|
||||
]);
|
||||
notifications = jasmine.createSpyObj("notifications", ["updateMessage"]);
|
||||
utils = jasmine.createSpyObj("utils", ["getValue"]);
|
||||
persistence = jasmine.createSpyObj("persistence", [
|
||||
"getSelectedMusicFolder",
|
||||
"saveSelectedMusicFolder",
|
||||
"deleteSelectedMusicFolder"
|
||||
]);
|
||||
|
||||
// Mock the subsonic service
|
||||
subsonic = jasmine.createSpyObj("subsonic", [
|
||||
"deletePlaylist",
|
||||
"getAlbums",
|
||||
"getArtists",
|
||||
"getGenres",
|
||||
"getPlaylists",
|
||||
"getPodcasts",
|
||||
"getRandomStarredSongs",
|
||||
"getRandomSongs",
|
||||
"getMusicFolders",
|
||||
"getPlaylist",
|
||||
"newPlaylist",
|
||||
"deletePlaylist",
|
||||
"savePlaylist",
|
||||
"getPlaylists",
|
||||
"getPodcast",
|
||||
"search"
|
||||
"getPodcasts",
|
||||
"getRandomSongs",
|
||||
"getRandomStarredSongs",
|
||||
"getSongs",
|
||||
"newPlaylist",
|
||||
"recursiveGetSongs",
|
||||
"savePlaylist",
|
||||
"search",
|
||||
]);
|
||||
// We make them return different promises and use our deferred variable only when testing
|
||||
// a particular function, so that they stay isolated
|
||||
subsonic.getAlbums.and.returnValue($q.defer().promise);
|
||||
subsonic.getArtists.and.returnValue($q.defer().promise);
|
||||
subsonic.getGenres.and.returnValue($q.defer().promise);
|
||||
subsonic.getMusicFolders.and.returnValue($q.defer().promise);
|
||||
subsonic.getPlaylists.and.returnValue($q.defer().promise);
|
||||
subsonic.getPodcasts.and.returnValue($q.defer().promise);
|
||||
subsonic.getSongs.and.returnValue($q.defer().promise);
|
||||
subsonic.recursiveGetSongs.and.returnValue($q.defer().promise);
|
||||
subsonic.showIndex = false;
|
||||
|
||||
// Mock the player service
|
||||
player = jasmine.createSpyObj("player", [
|
||||
"emptyQueue",
|
||||
"addSong",
|
||||
"addSongs",
|
||||
"play",
|
||||
"playFirstSong"
|
||||
]);
|
||||
player.emptyQueue.and.returnValue(player);
|
||||
player.addSong.and.returnValue(player);
|
||||
player.addSongs.and.returnValue(player);
|
||||
player.queue = [];
|
||||
|
||||
$controller = _$controller_;
|
||||
controllerParams = {
|
||||
$scope: scope,
|
||||
|
@ -65,7 +80,9 @@ describe("Subsonic controller", function() {
|
|||
globals: globals,
|
||||
map: map,
|
||||
subsonic: subsonic,
|
||||
notifications: notifications
|
||||
notifications: notifications,
|
||||
player: player,
|
||||
persistence: persistence
|
||||
};
|
||||
});
|
||||
});
|
||||
|
@ -74,6 +91,139 @@ describe("Subsonic controller", function() {
|
|||
beforeEach(function() {
|
||||
$controller('SubsonicController', controllerParams);
|
||||
scope.selectedPlaylist = null;
|
||||
scope.$apply();
|
||||
});
|
||||
|
||||
describe("getSongs -", function() {
|
||||
beforeEach(function() {
|
||||
scope.BreadCrumbs = [];
|
||||
scope.SelectedAlbumSort= {
|
||||
id: "default"
|
||||
};
|
||||
subsonic.getSongs.and.returnValue(deferred.promise);
|
||||
subsonic.recursiveGetSongs.and.returnValue(deferred.promise);
|
||||
spyOn(scope, "requestSongs").and.returnValue(deferred.promise);
|
||||
});
|
||||
|
||||
it("Given a music directory that contained 3 songs and given its id and name, when I display it, then subsonic-service will be called, the breadcrumbs will be updated and the songs will be published to the scope", function() {
|
||||
scope.getSongs('display', 87, 'Covetous Dadayag');
|
||||
deferred.resolve({
|
||||
directories: [],
|
||||
songs: [
|
||||
{ id: 660 },
|
||||
{ id: 859 },
|
||||
{ id: 545 }
|
||||
]
|
||||
});
|
||||
scope.$apply();
|
||||
|
||||
expect(subsonic.getSongs).toHaveBeenCalledWith(87);
|
||||
expect(scope.album).toEqual([]);
|
||||
expect(scope.song).toEqual([
|
||||
{ id: 660 },
|
||||
{ id: 859 },
|
||||
{ id: 545 }
|
||||
]);
|
||||
expect(scope.BreadCrumbs).toEqual([{
|
||||
type: 'album',
|
||||
id: 87,
|
||||
name: 'Covetous Dadayag'
|
||||
}]);
|
||||
expect(scope.selectedAutoAlbum).toBeNull();
|
||||
expect(scope.selectedArtist).toBeNull();
|
||||
expect(scope.selectedAlbum).toBe(87);
|
||||
expect(scope.selectedAutoPlaylist).toBeNull();
|
||||
expect(scope.selectedPlaylist).toBeNull();
|
||||
expect(scope.selectedPodcast).toBeNull();
|
||||
});
|
||||
|
||||
it("Given that there was a previous level in the breadcrumbs, when I display a music directory, then the album breadcrumb will be added", function() {
|
||||
scope.BreadCrumbs = [
|
||||
{
|
||||
type: 'artist',
|
||||
id: 73,
|
||||
name: 'Evan Mestanza'
|
||||
}
|
||||
];
|
||||
scope.getSongs('display', 883, 'Pitiedly preutilizable');
|
||||
deferred.resolve({
|
||||
directories: [],
|
||||
songs: []
|
||||
});
|
||||
scope.$apply();
|
||||
|
||||
expect(scope.BreadCrumbs).toEqual([
|
||||
{
|
||||
type: 'artist',
|
||||
id: 73,
|
||||
name: 'Evan Mestanza'
|
||||
}, {
|
||||
type: 'album',
|
||||
id: 883,
|
||||
name: 'Pitiedly preutilizable'
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it("Given a music directory that contained 2 songs and 1 subdirectory and given its id and name, when I display it, then subsonic-service will be called, the songs and directory will be published to the scope", function() {
|
||||
scope.getSongs('display', 6, 'Potsander dormilona');
|
||||
deferred.resolve({
|
||||
directories: [{id: 387, type: 'byfolder'}],
|
||||
songs: [
|
||||
{ id: 102 },
|
||||
{ id: 340 }
|
||||
]
|
||||
});
|
||||
scope.$apply();
|
||||
|
||||
expect(scope.album).toEqual([
|
||||
{id: 387, type: 'byfolder'}
|
||||
]);
|
||||
expect(scope.song).toEqual([
|
||||
{ id: 102 },
|
||||
{ id: 340 }
|
||||
]);
|
||||
});
|
||||
|
||||
it("Given a music directory, when I display it, then handleErrors will handle HTTP and Subsonic errors", function() {
|
||||
spyOn(scope, 'handleErrors').and.returnValue(deferred.promise);
|
||||
scope.getSongs('display', 88);
|
||||
expect(scope.handleErrors).toHaveBeenCalledWith(deferred.promise);
|
||||
});
|
||||
|
||||
it("Given a music directory that didn't contain anything, when I display it, then an error notification will be displayed", function() {
|
||||
scope.getSongs('display', 214, 'Upside bunemost');
|
||||
deferred.reject({reason: 'This directory is empty.'});
|
||||
scope.$apply();
|
||||
|
||||
expect(notifications.updateMessage).toHaveBeenCalledWith('This directory is empty.', true);
|
||||
});
|
||||
|
||||
it("Given a music directory that contained 3 songs and given its id, when I add it to the playing queue, then requestSongs() will be called", function() {
|
||||
scope.getSongs('add', 720);
|
||||
deferred.resolve([
|
||||
{ id: 927 },
|
||||
{ id: 598 },
|
||||
{ id: 632 }
|
||||
]);
|
||||
scope.$apply();
|
||||
|
||||
expect(subsonic.recursiveGetSongs).toHaveBeenCalledWith(720);
|
||||
expect(scope.requestSongs).toHaveBeenCalledWith(deferred.promise, 'add');
|
||||
});
|
||||
|
||||
it("Given a music directory that contained 3 songs and given its id, when I play it, then requestSongs() will be called", function() {
|
||||
scope.getSongs('play', 542);
|
||||
deferred.resolve([
|
||||
{ id: 905 },
|
||||
{ id: 181 },
|
||||
{ id: 880 }
|
||||
]);
|
||||
scope.$apply();
|
||||
|
||||
expect(subsonic.recursiveGetSongs).toHaveBeenCalledWith(542);
|
||||
expect(scope.requestSongs).toHaveBeenCalledWith(deferred.promise, 'play');
|
||||
});
|
||||
});
|
||||
|
||||
describe("Given that my library contained 3 songs, ", function() {
|
||||
|
@ -234,23 +384,22 @@ describe("Subsonic controller", function() {
|
|||
deferred.resolve(response);
|
||||
scope.$apply();
|
||||
|
||||
expect(player.queue).toEqual([
|
||||
expect(player.addSongs).toHaveBeenCalledWith([
|
||||
{id: "2548"}, {id: "8986"}, {id: "2986"}
|
||||
]);
|
||||
expect(notifications.updateMessage).toHaveBeenCalledWith('3 Song(s) Added to Queue', true);
|
||||
});
|
||||
|
||||
it("when I play songs, it plays the first selected song, empties the queue and fills it with the selected songs and it notifies the user", function() {
|
||||
player.queue = [{id: "7666"}];
|
||||
|
||||
scope.requestSongs(deferred.promise, 'play');
|
||||
deferred.resolve(response);
|
||||
scope.$apply();
|
||||
|
||||
expect(player.playFirstSong).toHaveBeenCalled();
|
||||
expect(player.queue).toEqual([
|
||||
expect(player.emptyQueue).toHaveBeenCalled();
|
||||
expect(player.addSongs).toHaveBeenCalledWith([
|
||||
{id: "2548"}, {id: "8986"}, {id: "2986"}
|
||||
]);
|
||||
expect(player.playFirstSong).toHaveBeenCalled();
|
||||
expect(notifications.updateMessage).toHaveBeenCalledWith('3 Song(s) Added to Queue', true);
|
||||
});
|
||||
|
||||
|
@ -362,22 +511,48 @@ describe("Subsonic controller", function() {
|
|||
});
|
||||
});
|
||||
|
||||
it("When I call playSong, it calls play in the player service", 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.play).toHaveBeenCalledWith(fakeSong);
|
||||
expect(player.emptyQueue).toHaveBeenCalled();
|
||||
expect(player.addSong).toHaveBeenCalledWith({"id": 3572});
|
||||
expect(player.playFirstSong).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
//TODO: Hyz: all starred
|
||||
|
||||
describe("When I load the artists,", function() {
|
||||
describe("", function() {
|
||||
beforeEach(function() {
|
||||
spyOn(scope, "getArtists");
|
||||
});
|
||||
|
||||
it("Given no previously selected music folder, when I select a music folder, then it will be stored in persistence and the artists will be loaded from subsonic", function() {
|
||||
scope.SelectedMusicFolder = { id: 22, name: "Cascadia" };
|
||||
scope.$apply();
|
||||
|
||||
expect(persistence.saveSelectedMusicFolder).toHaveBeenCalledWith({ id: 22, name: "Cascadia" });
|
||||
expect(scope.getArtists).toHaveBeenCalledWith(22);
|
||||
});
|
||||
|
||||
it("Given a previously selected music folder, when I select the 'All Folders' (undefined) music folder, then the stored value will be deleted from persistence and all the artists will be loaded from subsonic", function() {
|
||||
scope.SelectedMusicFolder = { id: 23, name: "grantable" };
|
||||
scope.$apply();
|
||||
scope.SelectedMusicFolder = undefined;
|
||||
scope.$apply();
|
||||
|
||||
expect(persistence.deleteSelectedMusicFolder).toHaveBeenCalled();
|
||||
expect(scope.getArtists).not.toHaveBeenCalledWith(jasmine.any(Number));
|
||||
});
|
||||
});
|
||||
|
||||
describe("getArtists() -", function() {
|
||||
beforeEach(function() {
|
||||
subsonic.getArtists.and.returnValue(deferred.promise);
|
||||
});
|
||||
|
||||
it("Given that there are songs in the library, it loads the artists and publishes them to the scope", function() {
|
||||
it("Given that there were artists in the library, when I load the artists, then subsonic will be queried an index array containing the artists and a shortcut array containing the shortcuts (such as Podcasts) will be publisehd to the scope", function() {
|
||||
scope.getArtists();
|
||||
deferred.resolve({
|
||||
index: [
|
||||
|
@ -398,7 +573,15 @@ describe("Subsonic controller", function() {
|
|||
]);
|
||||
});
|
||||
|
||||
it("Given that there aren't any songs in the library, when loading indexes, it notifies the user with an error message", function() {
|
||||
it("Given no folder id and given a selected music folder had been set in the scope, when I get the artists, then the selected music folder's id will be used as parameter to subsonic service", function() {
|
||||
scope.SelectedMusicFolder = { id: 62, name: "dollardee" };
|
||||
|
||||
scope.getArtists();
|
||||
|
||||
expect(subsonic.getArtists).toHaveBeenCalledWith(62);
|
||||
});
|
||||
|
||||
it("Given that there weren't any artist in the library, when I load the artists, then a notification with an error message will be displayed", function() {
|
||||
scope.getArtists();
|
||||
deferred.reject({reason: 'No artist found on the Subsonic server.'});
|
||||
scope.$apply();
|
||||
|
@ -408,13 +591,24 @@ describe("Subsonic controller", function() {
|
|||
expect(notifications.updateMessage).toHaveBeenCalledWith('No artist found on the Subsonic server.', true);
|
||||
});
|
||||
|
||||
it("it lets handleErrors handle HTTP and Subsonic errors", function() {
|
||||
it("Given that the server was unreachable, when I get the music folders, then handleErrors() will deal with the error", function() {
|
||||
spyOn(scope, 'handleErrors').and.returnValue(deferred.promise);
|
||||
scope.getArtists();
|
||||
expect(scope.handleErrors).toHaveBeenCalledWith(deferred.promise);
|
||||
});
|
||||
});
|
||||
|
||||
it("refreshArtists() - When I refresh the artists, then the selected music folder will be reset to undefined and the artists and playlists will be loaded", function() {
|
||||
spyOn(scope, "getArtists");
|
||||
spyOn(scope, "getPlaylists");
|
||||
|
||||
scope.refreshArtists();
|
||||
|
||||
expect(scope.SelectedMusicFolder).toBeUndefined();
|
||||
expect(scope.getArtists).toHaveBeenCalled();
|
||||
expect(scope.getPlaylists).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("When I load the playlists,", function() {
|
||||
beforeEach(function() {
|
||||
subsonic.getPlaylists.and.returnValue(deferred.promise);
|
||||
|
@ -490,7 +684,7 @@ describe("Subsonic controller", function() {
|
|||
expect(scope.getPlaylists).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Given no selected playlist, when I try to delete a playlist, an error message will be notified", function() {
|
||||
it("Given no selected playlist, when I try to delete a playlist, an error notification will be displayed", function() {
|
||||
scope.selectedPlaylist = null;
|
||||
|
||||
scope.deletePlaylist();
|
||||
|
@ -522,7 +716,7 @@ describe("Subsonic controller", function() {
|
|||
expect(notifications.updateMessage).toHaveBeenCalledWith('Playlist Updated!', true);
|
||||
});
|
||||
|
||||
it("Given no selected playlist, when I try to save a playlist, an error message will be notified", function() {
|
||||
it("Given no selected playlist, when I try to save a playlist, an error notification will be displayed", function() {
|
||||
scope.selectedPlaylist = null;
|
||||
|
||||
scope.savePlaylist();
|
||||
|
@ -564,6 +758,64 @@ describe("Subsonic controller", function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe("getMusicFolders", function() {
|
||||
beforeEach(function() {
|
||||
subsonic.getMusicFolders.and.returnValue(deferred.promise);
|
||||
});
|
||||
|
||||
it("Given that there were music folders in the library, when I get the music folders, then the folders will be published to the scope", function() {
|
||||
scope.getMusicFolders();
|
||||
deferred.resolve([
|
||||
{ id: 74, name: "scirrhosis"},
|
||||
{ id: 81, name: "drooper"}
|
||||
]);
|
||||
scope.$apply();
|
||||
|
||||
expect(subsonic.getMusicFolders).toHaveBeenCalled();
|
||||
expect(scope.MusicFolders).toEqual([
|
||||
{ id: 74, name: "scirrhosis"},
|
||||
{ id: 81, name: "drooper"}
|
||||
]);
|
||||
});
|
||||
|
||||
describe("Given that there was a selected music folder previously saved in persistence", function() {
|
||||
it("and that it matched one of the music folders returned by subsonic, when I get the music folders, then the scope's selected music folder will be set", function() {
|
||||
persistence.getSelectedMusicFolder.and.returnValue({ id: 79, name: "dismember" });
|
||||
|
||||
scope.getMusicFolders();
|
||||
deferred.resolve([
|
||||
{ id: 93, name: "illish" },
|
||||
{ id: 79, name: "dismember" }
|
||||
]);
|
||||
scope.$apply();
|
||||
|
||||
expect(scope.SelectedMusicFolder).toEqual({ id: 79, name: "dismember" });
|
||||
});
|
||||
|
||||
it("and that it didn't match one of the music folders returned by subsonic, when I get the music folders, then the scope's selected music folder will be undefined", function() {
|
||||
persistence.getSelectedMusicFolder.and.returnValue({ id: 49, name: "metafulminuric" });
|
||||
|
||||
scope.getMusicFolders();
|
||||
deferred.resolve([
|
||||
{ id: 94, name: "dorsiflexor" }
|
||||
]);
|
||||
scope.$apply();
|
||||
|
||||
expect(scope.SelectedMusicFolder).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("Given that there weren't any music folder in the library, when I get the music folders, then handleErrors() will deal with the error", function() {
|
||||
spyOn(scope, 'handleErrors').and.returnValue(deferred.promise);
|
||||
|
||||
scope.getMusicFolders();
|
||||
deferred.reject({reason: 'No music folder found on the Subsonic server.'});
|
||||
scope.$apply();
|
||||
|
||||
expect(scope.handleErrors).toHaveBeenCalledWith(deferred.promise);
|
||||
});
|
||||
});
|
||||
|
||||
describe("search() -", function() {
|
||||
beforeEach(function() {
|
||||
subsonic.search.and.returnValue(deferred.promise);
|
||||
|
|
142
app/vendor/jquery.base64.js
vendored
142
app/vendor/jquery.base64.js
vendored
|
@ -1,142 +0,0 @@
|
|||
|
||||
/**
|
||||
* jQuery BASE64 functions
|
||||
*
|
||||
* <code>
|
||||
* Encodes the given data with base64.
|
||||
* String $.base64Encode ( String str )
|
||||
* <br />
|
||||
* Decodes a base64 encoded data.
|
||||
* String $.base64Decode ( String str )
|
||||
* </code>
|
||||
*
|
||||
* Encodes and Decodes the given data in base64.
|
||||
* This encoding is designed to make binary data survive transport through transport layers that are not 8-bit clean, such as mail bodies.
|
||||
* Base64-encoded data takes about 33% more space than the original data.
|
||||
* This javascript code is used to encode / decode data using base64 (this encoding is designed to make binary data survive transport through transport layers that are not 8-bit clean). Script is fully compatible with UTF-8 encoding. You can use base64 encoded data as simple encryption mechanism.
|
||||
* If you plan using UTF-8 encoding in your project don't forget to set the page encoding to UTF-8 (Content-Type meta tag).
|
||||
* This function orginally get from the WebToolkit and rewrite for using as the jQuery plugin.
|
||||
*
|
||||
* Example
|
||||
* Code
|
||||
* <code>
|
||||
* $.base64Encode("I'm Persian.");
|
||||
* </code>
|
||||
* Result
|
||||
* <code>
|
||||
* "SSdtIFBlcnNpYW4u"
|
||||
* </code>
|
||||
* Code
|
||||
* <code>
|
||||
* $.base64Decode("SSdtIFBlcnNpYW4u");
|
||||
* </code>
|
||||
* Result
|
||||
* <code>
|
||||
* "I'm Persian."
|
||||
* </code>
|
||||
*
|
||||
* @alias Muhammad Hussein Fattahizadeh < muhammad [AT] semnanweb [DOT] com >
|
||||
* @link http://www.semnanweb.com/jquery-plugin/base64.html
|
||||
* @see http://www.webtoolkit.info/
|
||||
* @license http://www.gnu.org/licenses/gpl.html [GNU General Public License]
|
||||
* @param {jQuery} {base64Encode:function(input))
|
||||
* @param {jQuery} {base64Decode:function(input))
|
||||
* @return string
|
||||
*/
|
||||
|
||||
(function($){
|
||||
|
||||
var keyString = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
||||
|
||||
var uTF8Encode = function(string) {
|
||||
string = string.replace(/\x0d\x0a/g, "\x0a");
|
||||
var output = "";
|
||||
for (var n = 0; n < string.length; n++) {
|
||||
var c = string.charCodeAt(n);
|
||||
if (c < 128) {
|
||||
output += String.fromCharCode(c);
|
||||
} else if ((c > 127) && (c < 2048)) {
|
||||
output += String.fromCharCode((c >> 6) | 192);
|
||||
output += String.fromCharCode((c & 63) | 128);
|
||||
} else {
|
||||
output += String.fromCharCode((c >> 12) | 224);
|
||||
output += String.fromCharCode(((c >> 6) & 63) | 128);
|
||||
output += String.fromCharCode((c & 63) | 128);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
var uTF8Decode = function(input) {
|
||||
var string = "";
|
||||
var i = 0;
|
||||
var c = c1 = c2 = 0;
|
||||
while ( i < input.length ) {
|
||||
c = input.charCodeAt(i);
|
||||
if (c < 128) {
|
||||
string += String.fromCharCode(c);
|
||||
i++;
|
||||
} else if ((c > 191) && (c < 224)) {
|
||||
c2 = input.charCodeAt(i+1);
|
||||
string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
|
||||
i += 2;
|
||||
} else {
|
||||
c2 = input.charCodeAt(i+1);
|
||||
c3 = input.charCodeAt(i+2);
|
||||
string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
|
||||
i += 3;
|
||||
}
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
$.extend({
|
||||
base64Encode: function(input) {
|
||||
var output = "";
|
||||
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
|
||||
var i = 0;
|
||||
input = uTF8Encode(input);
|
||||
while (i < input.length) {
|
||||
chr1 = input.charCodeAt(i++);
|
||||
chr2 = input.charCodeAt(i++);
|
||||
chr3 = input.charCodeAt(i++);
|
||||
enc1 = chr1 >> 2;
|
||||
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
|
||||
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
|
||||
enc4 = chr3 & 63;
|
||||
if (isNaN(chr2)) {
|
||||
enc3 = enc4 = 64;
|
||||
} else if (isNaN(chr3)) {
|
||||
enc4 = 64;
|
||||
}
|
||||
output = output + keyString.charAt(enc1) + keyString.charAt(enc2) + keyString.charAt(enc3) + keyString.charAt(enc4);
|
||||
}
|
||||
return output;
|
||||
},
|
||||
base64Decode: function(input) {
|
||||
var output = "";
|
||||
var chr1, chr2, chr3;
|
||||
var enc1, enc2, enc3, enc4;
|
||||
var i = 0;
|
||||
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
|
||||
while (i < input.length) {
|
||||
enc1 = keyString.indexOf(input.charAt(i++));
|
||||
enc2 = keyString.indexOf(input.charAt(i++));
|
||||
enc3 = keyString.indexOf(input.charAt(i++));
|
||||
enc4 = keyString.indexOf(input.charAt(i++));
|
||||
chr1 = (enc1 << 2) | (enc2 >> 4);
|
||||
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
|
||||
chr3 = ((enc3 & 3) << 6) | enc4;
|
||||
output = output + String.fromCharCode(chr1);
|
||||
if (enc3 != 64) {
|
||||
output = output + String.fromCharCode(chr2);
|
||||
}
|
||||
if (enc4 != 64) {
|
||||
output = output + String.fromCharCode(chr3);
|
||||
}
|
||||
}
|
||||
output = uTF8Decode(output);
|
||||
return output;
|
||||
}
|
||||
});
|
||||
})(jQuery);
|
317
app/vendor/jquery.dateFormat-1.0.js
vendored
317
app/vendor/jquery.dateFormat-1.0.js
vendored
|
@ -1,317 +0,0 @@
|
|||
(function (jQuery) {
|
||||
|
||||
var daysInWeek = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
||||
var shortMonthsInYear = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
||||
var longMonthsInYear = ["January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December"];
|
||||
var shortMonthsToNumber = [];
|
||||
shortMonthsToNumber["Jan"] = "01";
|
||||
shortMonthsToNumber["Feb"] = "02";
|
||||
shortMonthsToNumber["Mar"] = "03";
|
||||
shortMonthsToNumber["Apr"] = "04";
|
||||
shortMonthsToNumber["May"] = "05";
|
||||
shortMonthsToNumber["Jun"] = "06";
|
||||
shortMonthsToNumber["Jul"] = "07";
|
||||
shortMonthsToNumber["Aug"] = "08";
|
||||
shortMonthsToNumber["Sep"] = "09";
|
||||
shortMonthsToNumber["Oct"] = "10";
|
||||
shortMonthsToNumber["Nov"] = "11";
|
||||
shortMonthsToNumber["Dec"] = "12";
|
||||
|
||||
jQuery.format = (function () {
|
||||
function strDay(value) {
|
||||
return daysInWeek[parseInt(value, 10)] || value;
|
||||
}
|
||||
|
||||
function strMonth(value) {
|
||||
var monthArrayIndex = parseInt(value, 10) - 1;
|
||||
return shortMonthsInYear[monthArrayIndex] || value;
|
||||
}
|
||||
|
||||
function strLongMonth(value) {
|
||||
var monthArrayIndex = parseInt(value, 10) - 1;
|
||||
return longMonthsInYear[monthArrayIndex] || value;
|
||||
}
|
||||
|
||||
var parseMonth = function (value) {
|
||||
return shortMonthsToNumber[value] || value;
|
||||
};
|
||||
|
||||
var parseTime = function (value) {
|
||||
var retValue = value;
|
||||
var millis = "";
|
||||
if (retValue.indexOf(".") !== -1) {
|
||||
var delimited = retValue.split('.');
|
||||
retValue = delimited[0];
|
||||
millis = delimited[1];
|
||||
}
|
||||
|
||||
var values3 = retValue.split(":");
|
||||
|
||||
if (values3.length === 3) {
|
||||
hour = values3[0];
|
||||
minute = values3[1];
|
||||
second = values3[2];
|
||||
|
||||
return {
|
||||
time: retValue,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
millis: millis
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
time: "",
|
||||
hour: "",
|
||||
minute: "",
|
||||
second: "",
|
||||
millis: ""
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
date: function (value, format) {
|
||||
/*
|
||||
value = new java.util.Date()
|
||||
2009-12-18 10:54:50.546
|
||||
*/
|
||||
try {
|
||||
var date = null;
|
||||
var year = null;
|
||||
var month = null;
|
||||
var dayOfMonth = null;
|
||||
var dayOfWeek = null;
|
||||
var time = null;
|
||||
if (typeof value == "number"){
|
||||
return this.date(new Date(value), format);
|
||||
} else if (typeof value.getFullYear == "function") {
|
||||
year = value.getFullYear();
|
||||
month = value.getMonth() + 1;
|
||||
dayOfMonth = value.getDate();
|
||||
dayOfWeek = value.getDay();
|
||||
time = parseTime(value.toTimeString());
|
||||
} else if (value.search(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.?\d{0,3}[Z\-+]?(\d{2}:?\d{2})?/) != -1) {
|
||||
/* 2009-04-19T16:11:05+02:00 || 2009-04-19T16:11:05Z */
|
||||
var values = value.split(/[T\+-]/);
|
||||
year = values[0];
|
||||
month = values[1];
|
||||
dayOfMonth = values[2];
|
||||
time = parseTime(values[3].split(".")[0]);
|
||||
date = new Date(year, month - 1, dayOfMonth);
|
||||
dayOfWeek = date.getDay();
|
||||
} else {
|
||||
var values = value.split(" ");
|
||||
switch (values.length) {
|
||||
case 6:
|
||||
/* Wed Jan 13 10:43:41 CET 2010 */
|
||||
year = values[5];
|
||||
month = parseMonth(values[1]);
|
||||
dayOfMonth = values[2];
|
||||
time = parseTime(values[3]);
|
||||
date = new Date(year, month - 1, dayOfMonth);
|
||||
dayOfWeek = date.getDay();
|
||||
break;
|
||||
case 2:
|
||||
/* 2009-12-18 10:54:50.546 */
|
||||
var values2 = values[0].split("-");
|
||||
year = values2[0];
|
||||
month = values2[1];
|
||||
dayOfMonth = values2[2];
|
||||
time = parseTime(values[1]);
|
||||
date = new Date(year, month - 1, dayOfMonth);
|
||||
dayOfWeek = date.getDay();
|
||||
break;
|
||||
case 7:
|
||||
/* Tue Mar 01 2011 12:01:42 GMT-0800 (PST) */
|
||||
case 9:
|
||||
/*added by Larry, for Fri Apr 08 2011 00:00:00 GMT+0800 (China Standard Time) */
|
||||
case 10:
|
||||
/* added by Larry, for Fri Apr 08 2011 00:00:00 GMT+0200 (W. Europe Daylight Time) */
|
||||
year = values[3];
|
||||
month = parseMonth(values[1]);
|
||||
dayOfMonth = values[2];
|
||||
time = parseTime(values[4]);
|
||||
date = new Date(year, month - 1, dayOfMonth);
|
||||
dayOfWeek = date.getDay();
|
||||
break;
|
||||
case 1:
|
||||
/* added by Jonny, for 2012-02-07CET00:00:00 (Doctrine Entity -> Json Serializer) */
|
||||
var values2 = values[0].split("");
|
||||
year=values2[0]+values2[1]+values2[2]+values2[3];
|
||||
month= values2[5]+values2[6];
|
||||
dayOfMonth = values2[8]+values2[9];
|
||||
time = parseTime(values2[13]+values2[14]+values2[15]+values2[16]+values2[17]+values2[18]+values2[19]+values2[20])
|
||||
date = new Date(year, month - 1, dayOfMonth);
|
||||
dayOfWeek = date.getDay();
|
||||
break;
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
var pattern = "";
|
||||
var retValue = "";
|
||||
var unparsedRest = "";
|
||||
/*
|
||||
Issue 1 - variable scope issue in format.date
|
||||
Thanks jakemonO
|
||||
*/
|
||||
for (var i = 0; i < format.length; i++) {
|
||||
var currentPattern = format.charAt(i);
|
||||
pattern += currentPattern;
|
||||
unparsedRest = "";
|
||||
switch (pattern) {
|
||||
case "ddd":
|
||||
retValue += strDay(dayOfWeek);
|
||||
pattern = "";
|
||||
break;
|
||||
case "dd":
|
||||
if (format.charAt(i + 1) == "d") {
|
||||
break;
|
||||
}
|
||||
if (String(dayOfMonth).length === 1) {
|
||||
dayOfMonth = '0' + dayOfMonth;
|
||||
}
|
||||
retValue += dayOfMonth;
|
||||
pattern = "";
|
||||
break;
|
||||
case "d":
|
||||
if (format.charAt(i + 1) == "d") {
|
||||
break;
|
||||
}
|
||||
retValue += parseInt(dayOfMonth, 10);
|
||||
pattern = "";
|
||||
break;
|
||||
case "MMMM":
|
||||
retValue += strLongMonth(month);
|
||||
pattern = "";
|
||||
break;
|
||||
case "MMM":
|
||||
if (format.charAt(i + 1) === "M") {
|
||||
break;
|
||||
}
|
||||
retValue += strMonth(month);
|
||||
pattern = "";
|
||||
break;
|
||||
case "MM":
|
||||
if (format.charAt(i + 1) == "M") {
|
||||
break;
|
||||
}
|
||||
if (String(month).length === 1) {
|
||||
month = '0' + month;
|
||||
}
|
||||
retValue += month;
|
||||
pattern = "";
|
||||
break;
|
||||
case "M":
|
||||
if (format.charAt(i + 1) == "M") {
|
||||
break;
|
||||
}
|
||||
retValue += parseInt(month, 10);
|
||||
pattern = "";
|
||||
break;
|
||||
case "yyyy":
|
||||
retValue += year;
|
||||
pattern = "";
|
||||
break;
|
||||
case "yy":
|
||||
if (format.charAt(i + 1) == "y" &&
|
||||
format.charAt(i + 2) == "y") {
|
||||
break;
|
||||
}
|
||||
retValue += String(year).slice(-2);
|
||||
pattern = "";
|
||||
break;
|
||||
case "HH":
|
||||
retValue += time.hour;
|
||||
pattern = "";
|
||||
break;
|
||||
case "hh":
|
||||
/* time.hour is "00" as string == is used instead of === */
|
||||
var hour = (time.hour == 0 ? 12 : time.hour < 13 ? time.hour : time.hour - 12);
|
||||
hour = String(hour).length == 1 ? '0' + hour : hour;
|
||||
retValue += hour;
|
||||
pattern = "";
|
||||
break;
|
||||
case "h":
|
||||
if (format.charAt(i + 1) == "h") {
|
||||
break;
|
||||
}
|
||||
var hour = (time.hour == 0 ? 12 : time.hour < 13 ? time.hour : time.hour - 12);
|
||||
retValue += parseInt(hour, 10);
|
||||
// Fixing issue https://github.com/phstc/jquery-dateFormat/issues/21
|
||||
// retValue = parseInt(retValue, 10);
|
||||
pattern = "";
|
||||
break;
|
||||
case "mm":
|
||||
retValue += time.minute;
|
||||
pattern = "";
|
||||
break;
|
||||
case "ss":
|
||||
/* ensure only seconds are added to the return string */
|
||||
retValue += time.second.substring(0, 2);
|
||||
pattern = "";
|
||||
break;
|
||||
case "SSS":
|
||||
retValue += time.millis.substring(0, 3);
|
||||
pattern = "";
|
||||
break;
|
||||
case "a":
|
||||
retValue += time.hour >= 12 ? "PM" : "AM";
|
||||
pattern = "";
|
||||
break;
|
||||
case " ":
|
||||
retValue += currentPattern;
|
||||
pattern = "";
|
||||
break;
|
||||
case "/":
|
||||
retValue += currentPattern;
|
||||
pattern = "";
|
||||
break;
|
||||
case ":":
|
||||
retValue += currentPattern;
|
||||
pattern = "";
|
||||
break;
|
||||
default:
|
||||
if (pattern.length === 2 && pattern.indexOf("y") !== 0 && pattern != "SS") {
|
||||
retValue += pattern.substring(0, 1);
|
||||
pattern = pattern.substring(1, 2);
|
||||
} else if ((pattern.length === 3 && pattern.indexOf("yyy") === -1)) {
|
||||
pattern = "";
|
||||
} else {
|
||||
unparsedRest = pattern;
|
||||
}
|
||||
}
|
||||
}
|
||||
retValue += unparsedRest;
|
||||
return retValue;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
};
|
||||
}());
|
||||
}(jQuery));
|
||||
|
||||
jQuery.format.date.defaultShortDateFormat = "dd/MM/yyyy";
|
||||
jQuery.format.date.defaultLongDateFormat = "dd/MM/yyyy hh:mm:ss";
|
||||
|
||||
jQuery(document).ready(function () {
|
||||
jQuery(".shortDateFormat").each(function (idx, elem) {
|
||||
if (jQuery(elem).is(":input")) {
|
||||
jQuery(elem).val(jQuery.format.date(jQuery(elem).val(), jQuery.format.date.defaultShortDateFormat));
|
||||
} else {
|
||||
jQuery(elem).text(jQuery.format.date(jQuery(elem).text(), jQuery.format.date.defaultShortDateFormat));
|
||||
}
|
||||
});
|
||||
jQuery(".longDateFormat").each(function (idx, elem) {
|
||||
if (jQuery(elem).is(":input")) {
|
||||
jQuery(elem).val(jQuery.format.date(jQuery(elem).val(), jQuery.format.date.defaultLongDateFormat));
|
||||
} else {
|
||||
jQuery(elem).text(jQuery.format.date(jQuery(elem).text(), jQuery.format.date.defaultLongDateFormat));
|
||||
}
|
||||
});
|
||||
});
|
22
bower.json
22
bower.json
|
@ -19,28 +19,30 @@
|
|||
"archive.org",
|
||||
"music"
|
||||
],
|
||||
"license": "MIT",
|
||||
"license": "GPL-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tsquillario/Jamstash.git"
|
||||
},
|
||||
"main": "app/index.html",
|
||||
"dependencies": {
|
||||
"angular": "~1.2.0",
|
||||
"angular-route": "~1.2.0",
|
||||
"angular-sanitize": "~1.2.0",
|
||||
"angular-cookies": "~1.2.0",
|
||||
"angular-resource": "~1.2.0",
|
||||
"angular": "~1.3.15",
|
||||
"angular-route": "~1.3.15",
|
||||
"angular-sanitize": "~1.3.15",
|
||||
"angular-cookies": "~1.3.15",
|
||||
"angular-resource": "~1.3.15",
|
||||
"jquery": "~2.0.0",
|
||||
"jquery-ui": "~1.10.0",
|
||||
"jplayer": "~2.9.0",
|
||||
"fancybox": "~2.1.4",
|
||||
"notify.js": "<=1.2.2",
|
||||
"jquery.scrollTo": "~1.4.5",
|
||||
"underscore": "~1.7.0",
|
||||
"jquery-dateFormat": "~1.0.2",
|
||||
"underscore": "~1.8.3",
|
||||
"angular-underscore": "~0.5.0",
|
||||
"angular-locker": "~1.2.0",
|
||||
"angular-ui-utils": "bower-keypress"
|
||||
"angular-ui-utils": "bower-keypress",
|
||||
"open-iconic": "~1.1.1"
|
||||
},
|
||||
"overrides": {
|
||||
"fancybox": {
|
||||
|
@ -51,8 +53,8 @@
|
|||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular-mocks": "~1.2.0",
|
||||
"jasmine-promise-matchers": "~0.0.3",
|
||||
"angular-mocks": "~1.3.15",
|
||||
"jasmine-promise-matchers": "~1.1.1",
|
||||
"jasmine-fixture": "~1.2.2"
|
||||
},
|
||||
"ignore": [
|
||||
|
|
|
@ -30,6 +30,7 @@ module.exports = function (config) {
|
|||
'bower_components/fancybox/source/jquery.fancybox.js',
|
||||
'bower_components/notify.js/notify.js',
|
||||
'bower_components/jquery.scrollTo/jquery.scrollTo.js',
|
||||
'bower_components/jquery-dateFormat/dist/jquery-dateFormat.js',
|
||||
'bower_components/underscore/underscore.js',
|
||||
'bower_components/angular-underscore/angular-underscore.js',
|
||||
'bower_components/angular-locker/dist/angular-locker.min.js',
|
||||
|
@ -39,14 +40,21 @@ module.exports = function (config) {
|
|||
'bower_components/jasmine-fixture/dist/jasmine-fixture.js',
|
||||
// endbower
|
||||
'app/**/*.js',
|
||||
'app/**/*_test.js'
|
||||
'app/**/*_test.js',
|
||||
'app/**/*.html'
|
||||
],
|
||||
|
||||
// list of files / patterns to exclude
|
||||
// exclude: ['app/vendor/**/*.js'],
|
||||
|
||||
preprocessors: {
|
||||
'app/**/!(*_test).js': ['coverage']
|
||||
'app/**/!(*_test).js': ['coverage'],
|
||||
'app/**/*.html': ['ng-html2js']
|
||||
},
|
||||
|
||||
ngHtml2JsPreprocessor: {
|
||||
stripPrefix: 'app/',
|
||||
moduleName: 'templates'
|
||||
},
|
||||
|
||||
// web server port
|
||||
|
@ -60,18 +68,7 @@ module.exports = function (config) {
|
|||
// - Safari (only Mac)
|
||||
// - PhantomJS
|
||||
// - IE (only Windows)
|
||||
browsers: [
|
||||
'PhantomJS'
|
||||
],
|
||||
|
||||
// Which plugins to enable
|
||||
plugins: [
|
||||
'karma-chrome-launcher',
|
||||
'karma-phantomjs-launcher',
|
||||
'karma-jasmine',
|
||||
'karma-coverage',
|
||||
'karma-notify-reporter'
|
||||
],
|
||||
browsers: [],
|
||||
|
||||
// Continuous Integration mode
|
||||
// if true, it capture browsers, run tests and exit
|
||||
|
|
43
package.json
43
package.json
|
@ -14,7 +14,7 @@
|
|||
"x37v (https://github.com/x37v)",
|
||||
"Hyzual (https://github.com/Hyzual)"
|
||||
],
|
||||
"license": "MIT",
|
||||
"license": "GPL-2.0",
|
||||
"homepage": "http://jamstash.com",
|
||||
"keywords": [
|
||||
"subsonic",
|
||||
|
@ -31,30 +31,29 @@
|
|||
"grunt": "^0.4.5",
|
||||
"grunt-bump": "^0.3.0",
|
||||
"grunt-contrib-clean": "^0.6.0",
|
||||
"grunt-contrib-concat": "^0.5.0",
|
||||
"grunt-contrib-connect": "^0.8.0",
|
||||
"grunt-contrib-copy": "^0.7.0",
|
||||
"grunt-contrib-cssmin": "^0.10.0",
|
||||
"grunt-contrib-htmlmin": "^0.3.0",
|
||||
"grunt-contrib-imagemin": "^0.9.1",
|
||||
"grunt-contrib-jshint": "^0.10.0",
|
||||
"grunt-contrib-uglify": "^0.6.0",
|
||||
"grunt-contrib-concat": "^0.5.1",
|
||||
"grunt-contrib-connect": "^0.10.1",
|
||||
"grunt-contrib-copy": "^0.8.0",
|
||||
"grunt-contrib-cssmin": "^0.12.2",
|
||||
"grunt-contrib-htmlmin": "^0.4.0",
|
||||
"grunt-contrib-imagemin": "^0.9.4",
|
||||
"grunt-contrib-uglify": "^0.9.1",
|
||||
"grunt-contrib-watch": "^0.6.1",
|
||||
"grunt-filerev": "^2.1.1",
|
||||
"grunt-karma": "^0.9.0",
|
||||
"grunt-filerev": "^2.2.0",
|
||||
"grunt-karma": "^0.10.1",
|
||||
"grunt-notify": "^0.4.1",
|
||||
"grunt-ssh": "^0.12.0",
|
||||
"grunt-usemin": "^2.6.0",
|
||||
"grunt-wiredep": "^1.9.0",
|
||||
"jit-grunt": "^0.9.0",
|
||||
"jshint-stylish": "^1.0.0",
|
||||
"karma": "^0.12.32",
|
||||
"karma-chrome-launcher": "^0.1.5",
|
||||
"karma-coverage": "^0.2.6",
|
||||
"karma-jasmine": "^0.3.0",
|
||||
"grunt-ssh": "^0.12.2",
|
||||
"grunt-svg-sprite": "^1.1.0",
|
||||
"grunt-usemin": "^3.0.0",
|
||||
"grunt-wiredep": "^2.0.0",
|
||||
"jit-grunt": "^0.9.1",
|
||||
"karma": "^0.12.31",
|
||||
"karma-chrome-launcher": "^0.1.7",
|
||||
"karma-coverage": "^0.2.7",
|
||||
"karma-jasmine": "^0.3.5",
|
||||
"karma-ng-html2js-preprocessor": "^0.1.2",
|
||||
"karma-notify-reporter": "^0.1.1",
|
||||
"karma-phantomjs-launcher": "^0.1.4",
|
||||
"time-grunt": "^1.0.0"
|
||||
"time-grunt": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue