Added Download All button in modal
[DeezloaderRemix.git] / app / public / js / main.js
1 // Starting area, boot up the API and proceed to eat memory
2
3 // Variables & constants
4 const socket = io.connect(window.location.href);
5 if(typeof mainApp !== "undefined"){
6         var defaultUserSettings = mainApp.defaultSettings;
7         var defaultDownloadLocation = mainApp.defaultDownloadDir;
8 }
9 let userSettings = [];
10
11 let preview_track = document.getElementById('preview-track');
12 let preview_stopped = true;
13
14 socket.emit("autologin");
15
16 socket.on("message", function(desc){
17         message(desc.title, desc.msg);
18 });
19
20 //Login button
21 $('#modal_login_btn_login').click(function () {
22         $('#modal_login_btn_login').attr("disabled", true);
23         $('#modal_login_btn_login').html("Logging in...");
24         var username = $('#modal_login_input_username').val();
25         var password = $('#modal_login_input_password').val();
26         var autologin = $('#modal_login_input_autologin').prop("checked");
27         //Send to the software
28         socket.emit('login', username, password,autologin);
29 });
30
31 socket.on("autologin",function(username,password){
32         $('#modal_login_input_autologin').prop('checked', true);
33         $('#modal_login_btn_login').attr("disabled", true);
34         $('#modal_login_btn_login').html("Logging in...");
35         $('#modal_login_input_username').val(username);
36         $('#modal_login_input_password').val(password);
37         M.updateTextFields();
38         socket.emit('login', username, password,false);
39 });
40
41 socket.on("login", function (data) {
42         if (!data.error) {
43                 $("#modal_settings_username").html(data.username);
44                 $("#modal_settings_picture").attr("src",data.picture);
45                 $("#side_user").text(data.username);
46                 $("#side_avatar").attr("src",data.picture);
47                 $("#side_email").text(data.email);
48                 $('#initializing').addClass('animated fadeOut').on('webkitAnimationEnd', function () {
49                         $(this).css('display', 'none');
50                         $(this).removeClass('animated fadeOut');
51                 });
52                 // Load top charts list for countries
53                 socket.emit("getChartsCountryList", {selected: userSettings.chartsCountry});
54                 socket.emit("getChartsTrackListByCountry", {country: userSettings.chartsCountry});
55                 // Load personal pubblic playlists
56                 socket.emit("getMePlaylistList", {});
57         }else{
58                         $('#login-res-text').text(data.error);
59                         setTimeout(function(){$('#login-res-text').text("");},3000);
60         }
61         $('#modal_login_btn_login').attr("disabled", false);
62         $('#modal_login_btn_login').html("Login");
63 });
64
65 // Open downloads folder
66 $('#openDownloadsFolder').on('click', function () {
67         if(typeof shell !== "undefined"){
68                 shell.showItemInFolder(userSettings.downloadLocation + path.sep + '.');
69         }else{
70                 alert("For security reasons, this button will do nothing.");
71         }
72 });
73 $('#modal_tags_replayGain').on('click', function() {
74         if ($(this).is(':checked')) {
75         message('Warning','Saving replay gain causes tracks to be quieter for some users.');
76 }
77 })
78 // Do misc stuff on page load
79 $(document).ready(function () {
80         M.AutoInit();
81         preview_track.volume = 0;
82
83         $('.sidenav').sidenav({
84                 edge: 'right'
85         });
86
87         var tabs = M.Tabs.getInstance(document.getElementById("tab-nav"));
88
89         $('.sidenav_tab').click((e)=>{
90                 e.preventDefault;
91                 $(e.currentTarget).addClass("active");
92                 tabs.select($(e.currentTarget).attr('tab-id'));
93                 tabs.updateTabIndicator();
94         })
95
96         $(window).scroll(function () {
97                 if ($(this).scrollTop() > 100) {
98                         $('#btn_scrollToTop a').removeClass('scale-out').addClass('scale-in');
99                 } else {
100                         $('#btn_scrollToTop a').removeClass('scale-in').addClass('scale-out');
101                 }
102         });
103
104         $('#btn_scrollToTop').click(function () {
105                 $('html, body').animate({scrollTop: 0}, 800);
106                 return false;
107         });
108
109         $("#button_refresh_playlist_tab").click(function(){
110                 $("table_personal_playlists").html("");
111                 socket.emit("getMePlaylistList", {});
112         })
113
114         $(preview_track).on('canplay', ()=>{
115                 preview_track.play();
116                 preview_stopped = false;
117                 $(preview_track).animate({volume: 1}, 500);
118         });
119
120         $(preview_track).on('timeupdate', ()=>{
121                 if (preview_track.currentTime > preview_track.duration-1){
122                         $(preview_track).animate({volume: 0}, 800);
123                         preview_stopped = true;
124                         $("*").removeAttr("playing");
125                         $('.preview_controls').text("play_arrow");
126                         $('.preview_playlist_controls').text("play_arrow");
127                 }
128         });
129
130         $('#nightTimeSwitcher').change(function(){
131                 if(this.checked){
132                         document.getElementsByTagName('link')[4].disabled = false;
133                         $("#nightModeSwitch2").html(`<i class="material-icons">brightness_7</i>Disable Night Mode`)
134                 }else{
135                         document.getElementsByTagName('link')[4].disabled = true;
136                         $("#nightModeSwitch2").html(`<i class="material-icons">brightness_2</i>Enable Night Mode`)
137                 }
138                 localStorage.darkMode = this.checked;
139         });
140
141         $('#nightModeSwitch2').click(()=>{
142                 $('#nightTimeSwitcher').prop('checked', !$('#nightTimeSwitcher').prop('checked'))
143                 $('#nightTimeSwitcher').change();
144         })
145
146         if (eval(localStorage.darkMode)){
147                 $('#nightTimeSwitcher').prop('checked', true);
148                 $('#nightTimeSwitcher').change();
149         }else{
150                 $('#nightTimeSwitcher').prop('checked', false);
151                 $('#nightTimeSwitcher').change();
152         }
153
154         $('#downloadChartPlaylist').click(function(){
155                 addToQueue(`https://www.deezer.com/playlist/${$(this).data("id")}`);
156         })
157
158         $('.modal').modal();
159         socket.emit("getUserSettings");
160
161         $('#modal_trackList, #modal_trackListSelective').modal({
162                 onCloseStart: ()=>{
163                         if ($('.preview_playlist_controls').filter(function(){return $(this).attr("playing")}).length > 0){
164                                 $(preview_track).animate({volume: 0}, 800);
165                                 preview_stopped = true;
166                                 $(".preview_playlist_controls").removeAttr("playing");
167                                 $('.preview_playlist_controls').text("play_arrow");
168                         }
169                 }
170         })
171
172         $('input[name=searchMode][type=radio]').change(()=>{
173                 $('#tab_search_form_search').submit();
174         })
175
176         $('#download_all_tracks_selective, #download_all_tracks').click(function(){
177                 addToQueue($(this).attr("data-link"));
178         })
179 });
180
181 // Load settings
182 socket.on('getUserSettings', function (data) {
183         userSettings = data.settings;
184         console.log('Settings refreshed');
185 });
186
187 /**
188  *      Modal Area START
189  */
190
191 // Prevent default behavior of closing button
192 $('.modal-close').click(function (e) {
193         e.preventDefault();
194 });
195
196 // Settings Modal START
197 const $settingsAreaParent = $('#modal_settings');
198
199 // Open settings panel
200 $('#nav_btn_openSettingsModal').click(function () {
201         fillSettingsModal(userSettings);
202 });
203
204 // Save settings button
205 $('#modal_settings_btn_saveSettings').click(function () {
206         let settings = {};
207         // Save
208         settings.userDefined = {
209                 trackNameTemplate: $('#modal_settings_input_trackNameTemplate').val(),
210                 playlistTrackNameTemplate: $('#modal_settings_input_playlistTrackNameTemplate').val(),
211                 albumTrackNameTemplate: $('#modal_settings_input_albumTrackNameTemplate').val(),
212                 albumNameTemplate: $('#modal_settings_input_albumNameTemplate').val(),
213                 coverImageTemplate: $('#modal_settings_input_coverImageTemplate').val(),
214                 artistImageTemplate: $('#modal_settings_input_artistImageTemplate').val(),
215                 createM3UFile: $('#modal_settings_cbox_createM3UFile').is(':checked'),
216                 createArtistFolder: $('#modal_settings_cbox_createArtistFolder').is(':checked'),
217                 createAlbumFolder: $('#modal_settings_cbox_createAlbumFolder').is(':checked'),
218                 createCDFolder: $('#modal_settings_cbox_createCDFolder').is(':checked'),
219                 downloadLocation: $('#modal_settings_input_downloadTracksLocation').val(),
220                 artworkSize: parseInt($('#modal_settings_select_artworkSize').val()),
221                 hifi: $('#modal_settings_cbox_hifi').is(':checked'),
222                 padtrck: $('#modal_settings_cbox_padtrck').is(':checked'),
223                 syncedlyrics: $('#modal_settings_cbox_syncedlyrics').is(':checked'),
224                 numplaylistbyalbum: $('#modal_settings_cbox_numplaylistbyalbum').is(':checked'),
225                 chartsCountry: $('#modal_settings_select_chartsCounrty').val(),
226                 spotifyUser: $('#modal_settings_input_spotifyUser').val(),
227                 saveArtwork: $('#modal_settings_cbox_saveArtwork').is(':checked'),
228                 saveArtworkArtist: $('#modal_settings_cbox_saveArtworkArtist').is(':checked'),
229                 logErrors: $('#modal_settings_cbox_logErrors').is(':checked'),
230                 logSearched: $('#modal_settings_cbox_logSearched').is(':checked'),
231                 queueConcurrency: parseInt($('#modal_settings_number_queueConcurrency').val()),
232                 paddingSize: $('#modal_settings_number_paddingSize').val(),
233                 multitagSeparator: $('#modal_settings_select_multitagSeparator').val(),
234                 maxBitrate: $('#modal_settings_select_maxBitrate').val(),
235                 PNGcovers: $('#modal_settings_cbox_PNGcovers').is(':checked'),
236                 removeAlbumVersion : $('#modal_settings_cbox_removeAlbumVersion').is(':checked'),
237                 dateFormat: $('#modal_settings_select_dateFormat').val(),
238                 dateFormatYear: $('#modal_settings_select_dateFormatYear').val(),
239                 fallbackBitrate : $('#modal_settings_cbox_fallbackBitrate').is(':checked'),
240                 minimizeToTray : $('#modal_settings_cbox_minimizeToTray').is(':checked'),
241                 saveFullArtists : $('#modal_settings_cbox_saveFullArtists').is(':checked'),
242                 removeDupedTags : $('#modal_settings_cbox_removeDupedTags').is(':checked'),
243                 tags: {
244                         title: $('#modal_tags_title').is(':checked'),
245                         artist: $('#modal_tags_artist').is(':checked'),
246                         album: $('#modal_tags_album').is(':checked'),
247                         cover: $('#modal_tags_cover').is(':checked'),
248                         trackNumber: $('#modal_tags_trackNumber').is(':checked'),
249                         trackTotal: $('#modal_tags_trackTotal').is(':checked'),
250                         discNumber: $('#modal_tags_discNumber').is(':checked'),
251                         discTotal: $('#modal_tags_discTotal').is(':checked'),
252                         albumArtist: $('#modal_tags_albumArtist').is(':checked'),
253                         genre: $('#modal_tags_genre').is(':checked'),
254                         year: $('#modal_tags_year').is(':checked'),
255                         date: $('#modal_tags_date').is(':checked'),
256                         explicit: $('#modal_tags_explicit').is(':checked'),
257                         isrc: $('#modal_tags_isrc').is(':checked'),
258                         length: $('#modal_tags_length').is(':checked'),
259                         barcode: $('#modal_tags_barcode').is(':checked'),
260                         bpm: $('#modal_tags_bpm').is(':checked'),
261                         replayGain: $('#modal_tags_replayGain').is(':checked'),
262                         publisher: $('#modal_tags_publisher').is(':checked'),
263                         unsynchronisedLyrics: $('#modal_tags_unsynchronisedLyrics').is(':checked'),
264                         copyright: $('#modal_tags_copyright').is(':checked'),
265                         musicpublisher: $('#modal_tags_musicpublisher').is(':checked'),
266                         composer: $('#modal_tags_composer').is(':checked'),
267                         mixer: $('#modal_tags_mixer').is(':checked'),
268                         author: $('#modal_tags_author').is(':checked'),
269                         writer: $('#modal_tags_writer').is(':checked'),
270                         engineer: $('#modal_tags_engineer').is(':checked'),
271                         producer: $('#modal_tags_producer').is(':checked')
272                 }
273         };
274         // Send updated settings to be saved into config file
275         socket.emit('saveSettings', settings);
276         socket.emit('getUserSettings');
277 });
278
279 // Reset defaults button
280 $('#modal_settings_btn_defaultSettings').click(function () {
281         if(typeof defaultDownloadLocation !== 'undefined'){
282                 defaultUserSettings.downloadLocation = defaultDownloadLocation;
283                 fillSettingsModal(defaultUserSettings);
284         }
285 });
286
287 $('#modal_login_btn_signup').click(function(){
288         if(typeof shell != 'undefined'){
289                 shell.openExternal("https://www.deezer.com/register");
290         }else{
291                 window.open("https://www.deezer.com/register");
292         }
293 });
294
295 $('#modal_settings_btn_logout').click(function () {
296         $('#initializing').css('display', '');
297         $('#initializing').addClass('animated fadeIn').on('webkitAnimationEnd', function () {
298                 $(this).removeClass('animated fadeIn');
299                 $(this).css('display', '');
300         });
301         socket.emit('logout');
302         $('#modal_login_input_username').val("");
303         $('#modal_login_input_password').val("");
304         $('#modal_login_input_autologin').prop("checked",false);
305 });
306
307 // Populate settings fields
308 function fillSettingsModal(settings) {
309         $('#modal_settings_input_trackNameTemplate').val(settings.trackNameTemplate);
310         $('#modal_settings_input_playlistTrackNameTemplate').val(settings.playlistTrackNameTemplate);
311         $('#modal_settings_input_albumTrackNameTemplate').val(settings.albumTrackNameTemplate);
312         $('#modal_settings_input_albumNameTemplate').val(settings.albumNameTemplate);
313         $('#modal_settings_input_coverImageTemplate').val(settings.coverImageTemplate);
314         $('#modal_settings_input_artistImageTemplate').val(settings.artistImageTemplate);
315         $('#modal_settings_cbox_createM3UFile').prop('checked', settings.createM3UFile);
316         $('#modal_settings_cbox_createArtistFolder').prop('checked', settings.createArtistFolder);
317         $('#modal_settings_cbox_createAlbumFolder').prop('checked', settings.createAlbumFolder);
318         $('#modal_settings_cbox_createCDFolder').prop('checked', settings.createCDFolder);
319         $('#modal_settings_cbox_hifi').prop('checked', settings.hifi);
320         $('#modal_settings_cbox_padtrck').prop('checked', settings.padtrck);
321         $('#modal_settings_cbox_syncedlyrics').prop('checked', settings.syncedlyrics);
322         $('#modal_settings_cbox_numplaylistbyalbum').prop('checked', settings.numplaylistbyalbum);
323         $('#modal_settings_input_downloadTracksLocation').val(settings.downloadLocation);
324         $('#modal_settings_select_artworkSize').val(settings.artworkSize).formSelect();
325         $('#modal_settings_select_chartsCounrty').val(settings.chartsCountry).formSelect();
326         $('#modal_settings_input_spotifyUser').val(settings.spotifyUser);
327         $('#modal_settings_cbox_saveArtwork').prop('checked', settings.saveArtwork);
328         $('#modal_settings_cbox_saveArtworkArtist').prop('checked', settings.saveArtworkArtist);
329         $('#modal_settings_cbox_logErrors').prop('checked', settings.logErrors);
330         $('#modal_settings_cbox_logSearched').prop('checked', settings.logSearched);
331         $('#modal_settings_number_queueConcurrency').val(settings.queueConcurrency);
332         $('#modal_settings_number_paddingSize').val(settings.paddingSize);
333         $('#modal_settings_select_multitagSeparator').val(settings.multitagSeparator).formSelect();
334         $('#modal_settings_select_maxBitrate').val(settings.maxBitrate).formSelect();
335         $('#modal_settings_cbox_PNGcovers').prop('checked', settings.PNGcovers);
336         $('#modal_settings_cbox_removeAlbumVersion').prop('checked', settings.removeAlbumVersion);
337         $('#modal_settings_select_dateFormat').val(settings.dateFormat).formSelect();
338         $('#modal_settings_select_dateFormatYear').val(settings.dateFormatYear).formSelect();
339         $('#modal_settings_cbox_fallbackBitrate').prop('checked', settings.fallbackBitrate);
340         $('#modal_settings_cbox_minimizeToTray').prop('checked', settings.minimizeToTray);
341         $('#modal_settings_cbox_saveFullArtists').prop('checked', settings.saveFullArtists);
342         $('#modal_settings_cbox_removeDupedTags').prop('checked', settings.removeDupedTags);
343
344         $('#modal_tags_title').prop('checked', settings.tags.title);
345         $('#modal_tags_artist').prop('checked', settings.tags.artist);
346         $('#modal_tags_album').prop('checked', settings.tags.album);
347         $('#modal_tags_cover').prop('checked', settings.tags.cover);
348         $('#modal_tags_trackNumber').prop('checked', settings.tags.trackNumber);
349         $('#modal_tags_trackTotal').prop('checked', settings.tags.trackTotal);
350         $('#modal_tags_discNumber').prop('checked', settings.tags.discNumber);
351         $('#modal_tags_discTotal').prop('checked', settings.tags.discTotal);
352         $('#modal_tags_albumArtist').prop('checked', settings.tags.albumArtist);
353         $('#modal_tags_genre').prop('checked', settings.tags.genre);
354         $('#modal_tags_year').prop('checked', settings.tags.year);
355         $('#modal_tags_date').prop('checked', settings.tags.date);
356         $('#modal_tags_explicit').prop('checked', settings.tags.explicit);
357         $('#modal_tags_isrc').prop('checked', settings.tags.isrc);
358         $('#modal_tags_length').prop('checked', settings.tags.length);
359         $('#modal_tags_barcode').prop('checked', settings.tags.barcode);
360         $('#modal_tags_bpm').prop('checked', settings.tags.bpm);
361         $('#modal_tags_replayGain').prop('checked', settings.tags.replayGain);
362         $('#modal_tags_publisher').prop('checked', settings.tags.publisher);
363         $('#modal_tags_unsynchronisedLyrics').prop('checked', settings.tags.unsynchronisedLyrics);
364         $('#modal_tags_copyright').prop('checked', settings.tags.copyright);
365         $('#modal_tags_musicpublisher').prop('checked', settings.tags.musicpublisher);
366         $('#modal_tags_composer').prop('checked', settings.tags.composer);
367         $('#modal_tags_mixer').prop('checked', settings.tags.mixer);
368         $('#modal_tags_author').prop('checked', settings.tags.author);
369         $('#modal_tags_writer').prop('checked', settings.tags.writer);
370         $('#modal_tags_engineer').prop('checked', settings.tags.engineer);
371         $('#modal_tags_producer').prop('checked', settings.tags.producer);
372
373         M.updateTextFields()
374 }
375
376
377 //#############################################MODAL_MSG##############################################\\
378 function message(title, message) {
379         $('#modal_msg_title').html(title);
380         $('#modal_msg_message').html(message);
381         $('#modal_msg').modal('open');
382 }
383
384 //****************************************************************************************************\\
385 //************************************************TABS************************************************\\
386 //****************************************************************************************************\\
387
388 //#############################################TAB_SEARCH#############################################\\
389 $('#tab_search_form_search').submit(function (ev) {
390
391         ev.preventDefault();
392
393         var searchString = $('#tab_search_form_search_input_searchString').val().trim();
394         var mode = $('#tab_search_form_search').find('input[name=searchMode]:checked').val();
395
396         if (searchString.length == 0) {
397                 return;
398         }
399
400         $('#tab_search_table_results').find('thead').find('tr').addClass('hide');
401         $('#tab_search_table_results_tbody_results').addClass('hide');
402         $('#tab_search_table_results_tbody_noResults').addClass('hide');
403         $('#tab_search_table_results_tbody_loadingIndicator').removeClass('hide');
404
405         socket.emit("search", {type: mode, text: searchString});
406
407 });
408
409 socket.on('search', function (data) {
410
411         $('#tab_search_table_results_tbody_loadingIndicator').addClass('hide');
412
413         if (data.items.length == 0) {
414                 $('#tab_search_table_results_tbody_noResults').removeClass('hide');
415                 return;
416         }
417
418         if (data.type == 'track') {
419                 showResults_table_track(data.items);
420         } else if (data.type == 'album') {
421                 showResults_table_album(data.items);
422         } else if (data.type == 'artist') {
423                 showResults_table_artist(data.items);
424         } else if (data.type == 'playlist') {
425                 showResults_table_playlist(data.items);
426         }
427         $('#tab_search_table_results_tbody_results').removeClass('hide');
428 });
429
430 function showResults_table_track(tracks) {
431         var tableBody = $('#tab_search_table_results_tbody_results');
432         $(tableBody).html('');
433         $('#tab_search_table_results_thead_track').removeClass('hide');
434         for (var i = 0; i < tracks.length; i++) {
435                 var currentResultTrack = tracks[i];
436                 $(tableBody).append(
437                         `<tr>
438                         <td><a href="#" class="circle ${(currentResultTrack.preview ? `single-cover" preview="${currentResultTrack.preview}"><i class="material-icons preview_controls white-text">play_arrow</i>` : '">')}<img style="width:56px" class="circle" src="${(currentResultTrack.album.cover_small ? currentResultTrack.album.cover_small : "img/noCover.jpg" )}"/></a></td>
439                         <td>${(currentResultTrack.explicit_lyrics ? ' <i class="material-icons valignicon tiny materialize-red-text">error_outline</i>' : '')} ${currentResultTrack.title}</td>
440                         <td><span class="resultArtist resultLink" data-link="${currentResultTrack.artist.link}">${currentResultTrack.artist.name}</span></td>
441                         <td><span class="resultAlbum resultLink" data-link="https://www.deezer.com/album/${currentResultTrack.album.id}">${currentResultTrack.album.title}</span></td>
442                         <td>${convertDuration(currentResultTrack.duration)}</td>
443                         </tr>`);
444                 generateDownloadLink(currentResultTrack.link).appendTo(tableBody.children('tr:last')).wrap('<td>');
445                 addPreviewControlsHover(tableBody.children('tr:last').find('.preview_controls'))
446                 addPreviewControlsClick(tableBody.children('tr:last').find('.single-cover'))
447                 tableBody.children('tr:last').find('.resultArtist').click(function (ev){
448                         ev.preventDefault();
449                         showTrackList($(this).data("link"));
450                 });
451                 tableBody.children('tr:last').find('.resultAlbum').click(function (ev){
452                         ev.preventDefault();
453                         showTrackListSelective($(this).data("link"));
454                 });
455         }
456 }
457
458 function showResults_table_album(albums) {
459         var tableBody = $('#tab_search_table_results_tbody_results');
460         $(tableBody).html('');
461         $('#tab_search_table_results_thead_album').removeClass('hide');
462         for (var i = 0; i < albums.length; i++) {
463                 var currentResultAlbum = albums[i];
464                 $(tableBody).append(
465                                 `<tr>
466                                 <td><img style="width:56px" src="${(currentResultAlbum.cover_small ? currentResultAlbum.cover_small : "img/noCover.jpg")}" class="circle" /></td>
467                                 <td>${(currentResultAlbum.explicit_lyrics ? '<i class="material-icons valignicon tiny materialize-red-text tooltipped" data-tooltip="Explicit">error_outline</i>' : '')} ${currentResultAlbum.title}</td>
468                                 <td><span class="resultArtist resultLink" data-link="${currentResultAlbum.artist.link}">${currentResultAlbum.artist.name}</span></td>
469                                 <td>${currentResultAlbum.nb_tracks}</td>
470                                 <td>${currentResultAlbum.record_type[0].toUpperCase() + currentResultAlbum.record_type.substring(1)}</td>
471                                 </tr>`);
472                 generateShowTracklistSelectiveButton(currentResultAlbum.link).appendTo(tableBody.children('tr:last')).wrap('<td>');
473                 generateDownloadLink(currentResultAlbum.link).appendTo(tableBody.children('tr:last')).wrap('<td>');
474                 tableBody.children('tr:last').find('.resultArtist').click(function (ev){
475                         ev.preventDefault();
476                         showTrackList($(this).data("link"));
477                 });
478         }
479         $('.tooltipped').tooltip({delay: 100});
480 }
481
482 function showResults_table_artist(artists) {
483         var tableBody = $('#tab_search_table_results_tbody_results');
484         $(tableBody).html('');
485         $('#tab_search_table_results_thead_artist').removeClass('hide');
486         for (var i = 0; i < artists.length; i++) {
487                 var currentResultArtist = artists[i];
488                 $(tableBody).append(
489                                 `<tr>
490                                 <td><img style="width:56px" src="${(currentResultArtist.picture_small ? currentResultArtist.picture_small : "img/noCover.jpg")}" class="circle" /></td>
491                                 <td>${currentResultArtist.name}</td>
492                                 <td>${currentResultArtist.nb_album}</td>
493                                 </tr>`);
494                 generateShowTracklistButton(currentResultArtist.link).appendTo(tableBody.children('tr:last')).wrap('<td>');
495                 generateDownloadLink(currentResultArtist.link).appendTo(tableBody.children('tr:last')).wrap('<td>');
496         }
497 }
498
499 function showResults_table_playlist(playlists) {
500         var tableBody = $('#tab_search_table_results_tbody_results');
501         $(tableBody).html('');
502         $('#tab_search_table_results_thead_playlist').removeClass('hide');
503         for (var i = 0; i < playlists.length; i++) {
504                 var currentResultPlaylist = playlists[i];
505                 $(tableBody).append(
506                                 `<tr>
507                                 <td><img style="width:56px" src="${(currentResultPlaylist.picture_small ? currentResultPlaylist.picture_small : "img/noCover.jpg")}" class="circle" /></td>
508                                 <td>${currentResultPlaylist.title}</td>
509                                 <td>${currentResultPlaylist.nb_tracks}</td>
510                                 </tr>`);
511                 generateShowTracklistSelectiveButton(currentResultPlaylist.link).appendTo(tableBody.children('tr:last')).wrap('<td>');
512                 generateDownloadLink(currentResultPlaylist.link).appendTo(tableBody.children('tr:last')).wrap('<td>');
513         }
514         $('.tooltipped').tooltip({delay: 100});
515 }
516
517 function generateShowTracklistSelectiveButton(link) {
518         var btn_showTrackListSelective = $('<a href="#" class="waves-effect btn-flat"><i class="material-icons">list</i></a>');
519         $(btn_showTrackListSelective).click(function (ev){
520                 ev.preventDefault();
521                 showTrackListSelective(link);
522         });
523         return btn_showTrackListSelective;
524 }
525
526 function generateShowTracklistButton(link) {
527         var btn_showTrackList = $('<a href="#" class="waves-effect btn-flat"><i class="material-icons">list</i></a>');
528         $(btn_showTrackList).click(function (ev) {
529                 ev.preventDefault();
530                 showTrackList(link);
531         });
532         return btn_showTrackList;
533 }
534
535 var trackListSelectiveModalApp = new Vue({
536         el: '#modal_trackListSelective',
537         data: {
538                 title: null,
539                 type: null,
540                 link: null,
541                 head: null,
542                 body: []
543         }
544 });
545
546 var trackListModalApp = new Vue({
547         el: '#modal_trackList',
548         data: {
549                 title: null,
550                 type: null,
551                 link: null,
552                 head: null,
553                 body: []
554         }
555 });
556
557 function showTrackListSelective(link) {
558         $('#modal_trackListSelective_table_trackListSelective_tbody_trackListSelective').addClass('hide');
559         $('#modal_trackListSelective_table_trackListSelective_tbody_loadingIndicator').removeClass('hide');
560         $('#modal_trackListSelective').modal('open');
561         socket.emit('getTrackList', {id: getIDFromLink(link), type: getTypeFromLink(link)});
562 }
563
564 $('#download_track_selection').click(function(e){
565         e.preventDefault();
566         var urls = [];
567         $("input:checkbox.trackCheckbox:checked").each(function(){
568                 urls.push($(this).val());
569         });
570         if(urls.length != 0){
571                 for (var ia = 0; ia < urls.length; ia++) {
572                         addToQueue(urls[ia]);
573                 }
574         }
575         $('#modal_trackListSelective').modal('close');
576 });
577
578 function showTrackList(link) {
579         $('#modal_trackList_table_trackList_tbody_trackList').addClass('hide');
580         $('#modal_trackList_table_trackList_tbody_loadingIndicator').removeClass('hide');
581         $('#modal_trackList').modal('open');
582         socket.emit("getTrackList", {id: getIDFromLink(link), type: getTypeFromLink(link)});
583 }
584
585 socket.on("getTrackList", function (data) {
586         //data.err                      -> undefined/err
587         //data.id                        -> passed id
588         //data.response -> API response
589         if (data.err){
590                 trackListSelectiveModalApp.title = "Can't get data"
591                 return;
592         }
593         if (data.response){
594                 var trackList = data.response.data, content = '';
595                 var trackListSelective = data.response.data, content = '';
596                 if (typeof trackList == 'undefined') {
597                         alert('Well, there seems to be a problem with this part of the app. Please notify the developer.');
598                         return;
599                 }
600
601                 // ########################################
602                 if(data.reqType == 'album' || data.reqType == 'playlist'){
603                         var tableBody = $('#modal_trackListSelective_table_trackListSelective_tbody_trackListSelective');
604                 } else {
605                         var tableBody = $('#modal_trackList_table_trackList_tbody_trackList');
606                 }
607                 $(tableBody).html('');
608                 //############################################
609                 if (data.reqType == 'artist') {
610                         trackListModalApp.type = data.reqType;
611                         trackListModalApp.link = `https://www.deezer.com/${data.reqType}/${data.id}`
612                         trackListModalApp.title = 'Album List';
613                         trackListModalApp.head = [
614                                 {title: '#'},
615                                 {title: ''},
616                                 {title: 'Album Title'},
617                                 {title: 'Release Date'},
618                                 {title: 'Record Type'},
619                                 {title: 'Download Album'}
620                         ];
621                         for (var i = 0; i < trackList.length; i++) {
622                                 $(tableBody).append(
623                                         `<tr>
624                                         <td>${(i + 1)}</td>
625                                         <td>${(trackList[i].explicit_lyrics ? '<i class="material-icons valignicon tiny materialize-red-text tooltipped" data-tooltip="Explicit">error_outline</i>' : '')}</td>
626                                         <td><a href="#" class="album_chip" data-link="${trackList[i].link}"><div class="chip"><img src="${trackList[i].cover_small}"/>${trackList[i].title}</div></a></td>
627                                         <td>${trackList[i].release_date}</td>
628                                         <td>${trackList[i].record_type[0].toUpperCase() + trackList[i].record_type.substring(1)}</td>
629                                         </tr>`
630                                 );
631                                 generateDownloadLink(trackList[i].link).appendTo(tableBody.children('tr:last')).wrap('<td>');
632                         }
633                         $('.album_chip').click(function(e){
634                                 showTrackListSelective($(this).data('link'), true);
635                         });
636                 } else if(data.reqType == 'playlist') {
637                         trackListSelectiveModalApp.type = data.reqType;
638                         trackListSelectiveModalApp.link = `https://www.deezer.com/${data.reqType}/${data.id}`
639                         trackListSelectiveModalApp.title = 'Playlist';
640                         trackListSelectiveModalApp.head = [
641                                 {title: '<i class="material-icons">music_note</i>'},
642                                 {title: '#'},
643                                 {title: 'Song'},
644                                 {title: 'Artist'},
645                                 {title: '<i class="material-icons">timer</i>'},
646                                 {title: '<div class="valign-wrapper"><label><input class="selectAll" type="checkbox" id="selectAll"><span></span></label></div>'}
647                         ];
648                         $('.selectAll').prop('checked', false);
649                         for (var i = 0; i < trackList.length; i++) {
650                                 $(tableBody).append(
651                                         `<tr>
652                                         <td><i class="material-icons ${(trackList[i].preview ? `preview_playlist_controls" preview="${trackList[i].preview}"` : 'grey-text"')}>play_arrow</i></td>
653                                         <td>${(i + 1)}</td>
654                                         <td>${(trackList[i].explicit_lyrics ? '<i class="material-icons valignicon tiny materialize-red-text tooltipped" data-tooltip="Explicit">error_outline</i> ' : '')}${trackList[i].title}</td>
655                                         <td>${trackList[i].artist.name}</td>
656                                         <td>${convertDuration(trackList[i].duration)}</td>
657                                         <td>
658                                                 <div class="valign-wrapper">
659                                                 <label>
660                                                 <input class="trackCheckbox valign" type="checkbox" id="trackChk${i}" value="${trackList[i].link}"><span></span>
661                                                 </label>
662                                                 </div>
663                                         </td>
664                                         </tr>`
665                                 );
666                                 addPreviewControlsClick(tableBody.children('tr:last').find('.preview_playlist_controls'));
667                         }
668                 } else if(data.reqType == 'album') {
669                         trackListSelectiveModalApp.type = data.reqType;
670                         trackListSelectiveModalApp.link = `https://www.deezer.com/${data.reqType}/${data.id}`
671                         trackListSelectiveModalApp.title = 'Tracklist';
672                         trackListSelectiveModalApp.head = [
673                                 {title: '<i class="material-icons">music_note</i>'},
674                                 {title: '#'},
675                                 {title: 'Song'},
676                                 {title: 'Artist'},
677                                 {title: '<i class="material-icons">timer</i>'},
678                                 {title: '<div class="valign-wrapper"><label><input class="selectAll" type="checkbox" id="selectAll"><span></span></label></div>'}
679                         ];
680                         $('.selectAll').prop('checked', false);
681                         if (trackList[trackList.length-1].disk_number != 1){
682                                 baseDisc = 0
683                         } else {
684                                 baseDisc =1
685                         };
686                         for (var i = 0; i < trackList.length; i++) {
687                                 discNum = trackList[i].disk_number
688                                 if (discNum != baseDisc){
689                                         $(tableBody).append(`<tr><td colspan="4" style="opacity: 0.54;"><i class="material-icons valignicon tiny">album</i>${discNum}</td></tr>`);
690                                         baseDisc = discNum;
691                                 }
692                                 $(tableBody).append(
693                                         `<tr>
694                                         <td><i class="material-icons ${(trackList[i].preview ? `preview_playlist_controls" preview="${trackList[i].preview}"` : 'grey-text"')}>play_arrow</i></td>
695                                         <td>${trackList[i].track_position}</td>
696                                         <td>${(trackList[i].explicit_lyrics ? '<i class="material-icons valignicon tiny materialize-red-text tooltipped" data-tooltip="Explicit">error_outline</i> ' : '')}${trackList[i].title}</td>
697                                         <td>${trackList[i].artist.name}</td>
698                                         <td>${convertDuration(trackList[i].duration)}</td>
699                                         <td>
700                                                 <div class="valign-wrapper">
701                                                 <label>
702                                                 <input class="trackCheckbox valign" type="checkbox" id="trackChk${i}" value="${trackList[i].link}"><span></span>
703                                                 </label>
704                                                 </div>
705                                         </td>
706                                         </tr>`
707                                 );
708                                 addPreviewControlsClick(tableBody.children('tr:last').find('.preview_playlist_controls'));
709                         }
710                 } else if(data.reqType == 'spotifyplaylist') {
711                         trackListModalApp.type = "Spotify Playlist";
712                         trackListModalApp.link = data.id;
713                         trackListModalApp.title = 'Tracklist';
714                         trackListModalApp.head = [
715                                 {title: '<i class="material-icons">music_note</i>'},
716                                 {title: '#'},
717                                 {title: 'Song'},
718                                 {title: 'Artist'},
719                                 {title: '<i class="material-icons">timer</i>'}
720                         ];
721                         for (var i = 0; i < trackList.length; i++) {
722                                 $(tableBody).append(
723                                         `<tr>
724                                         <td><i class="material-icons ${(trackList[i].preview ? `preview_playlist_controls" preview="${trackList[i].preview}"` : 'grey-text"')}>play_arrow</i></td>
725                                         <td>${(i + 1)}</td>
726                                         <td>${(trackList[i].explicit_lyrics ? '<i class="material-icons valignicon tiny materialize-red-text tooltipped" data-tooltip="Explicit">error_outline</i> ' : '')}${trackList[i].title}</td>
727                                         <td>${trackList[i].artist.name}</td>
728                                         <td>${convertDuration(trackList[i].duration)}</td>
729                                         </tr>`
730                                 );
731                                 addPreviewControlsClick(tableBody.children('tr:last').find('.preview_playlist_controls'));
732                         }
733                 } else {
734                         trackListModalApp.type = null;
735                         trackListModalApp.title = 'Tracklist';
736                         trackListModalApp.head = [
737                                 {title: '<i class="material-icons">music_note</i>'},
738                                 {title: '#'},
739                                 {title: 'Song'},
740                                 {title: 'Artist'},
741                                 {title: '<i class="material-icons">timer</i>'}
742                         ];
743                         for (var i = 0; i < trackList.length; i++) {
744                                 $(tableBody).append(
745                                         `<tr>
746                                         <td><i class="material-icons ${(trackList[i].preview ? `preview_playlist_controls" preview="${trackList[i].preview}"` : 'grey-text"')}>play_arrow</i></td>
747                                         <td>${(i + 1)}</td>
748                                         <td>${(trackList[i].explicit_lyrics ? '<i class="material-icons valignicon tiny materialize-red-text tooltipped" data-tooltip="Explicit">error_outline</i> ' : '')}${trackList[i].title}</td>
749                                         <td>${trackList[i].artist.name}</td>
750                                         <td>${convertDuration(trackList[i].duration)}</td>
751                                         </tr>`
752                                 );
753                                 addPreviewControlsClick(tableBody.children('tr:last').find('.preview_playlist_controls'));
754                         }
755                 }
756                 if(data.reqType == 'album' || data.reqType == 'playlist'){
757                         $('#modal_trackListSelective_table_trackListSelective_tbody_loadingIndicator').addClass('hide');
758                         $('#modal_trackListSelective_table_trackListSelective_tbody_trackListSelective').removeClass('hide');
759                 } else {
760                         $('#modal_trackList_table_trackList_tbody_loadingIndicator').addClass('hide');
761                         $('#modal_trackList_table_trackList_tbody_trackList').removeClass('hide');
762                 }
763                 //$('#modal_trackList_table_trackList_tbody_trackList').html(content);
764         }
765 });
766
767 //#############################################TAB_CHARTS#############################################\\
768 socket.on("getChartsCountryList", function (data) {
769         //data.countries                -> Array
770         //data.countries[0].country -> String (country name)
771         //data.countries[0].picture_small/picture_medium/picture_big -> url to cover
772         for (var i = 0; i < data.countries.length; i++) {
773                 $('#tab_charts_select_country').append('<option value="' + data.countries[i]['country'] + '" data-icon="' + data.countries[i]['picture_small'] + '" class="left circle">' + data.countries[i]['country'] + '</option>');
774                 $('#modal_settings_select_chartsCounrty').append('<option value="' + data.countries[i]['country'] + '" data-icon="' + data.countries[i]['picture_small'] + '" class="left circle">' + data.countries[i]['country'] + '</option>');
775         }
776         $('#tab_charts_select_country').find('option[value="' + data.selected + '"]').attr("selected", true);
777         $('#modal_settings_select_chartsCounrty').find('option[value="' + data.selected + '"]').attr("selected", true);
778         $('select').formSelect();
779 });
780
781 socket.on("setChartsCountry", function (data) {
782         $('#tab_charts_select_country').find('option[value="' + data.selected + '"]').attr("selected", true);
783         $('#modal_settings_select_chartsCounrty').find('option[value="' + data.selected + '"]').attr("selected", true);
784         $('select').formSelect();
785 });
786
787 $('#tab_charts_select_country').on('change', function () {
788         var country = $(this).find('option:selected').val();
789         $('#tab_charts_table_charts_tbody_charts').addClass('hide');
790         $('#tab_charts_table_charts_tbody_loadingIndicator').removeClass('hide');
791         socket.emit("getChartsTrackListByCountry", {country: country});
792 });
793
794 socket.on("getChartsTrackListByCountry", function (data) {
795         //data.playlist         -> Object with Playlist information
796         //data.tracks                   -> Array
797         //data.tracks[0]         -> Object of track 0
798         $("#downloadChartPlaylist").data("id", data.playlist.id)
799         var chartsTableBody = $('#tab_charts_table_charts_tbody_charts'), currentChartTrack;
800         chartsTableBody.html('');
801         for (var i = 0; i < data.tracks.length; i++) {
802                 currentChartTrack = data.tracks[i];
803                 $(chartsTableBody).append(
804                                 `<tr>
805                                 <td>${(i + 1)}</td>
806                                 <td><a href="#" class="circle ${(currentChartTrack.preview ? `single-cover" preview="${currentChartTrack.preview}"><i class="material-icons preview_controls white-text">play_arrow</i>` : '">')}<img style="width:56px" src="${(currentChartTrack.album.cover_small ? currentChartTrack.album.cover_small : "img/noCover.jpg")}" class="circle" /></a></td>
807                                 <td>${(currentChartTrack.explicit_lyrics ? '<i class="material-icons valignicon tiny materialize-red-text tooltipped" data-tooltip="Explicit">error_outline</i> ' : '')}${currentChartTrack.title}</td>
808                                 <td><span class="resultArtist resultLink" data-link="${currentChartTrack.artist.link}">${currentChartTrack.artist.name}</span></td>
809                                 <td><span class="resultAlbum resultLink" data-link="https://www.deezer.com/album/${currentChartTrack.album.id}">${currentChartTrack.album.title}</span></td>
810                                 <td>${convertDuration(currentChartTrack.duration)}</td>
811                                 </tr>`);
812                 generateDownloadLink(currentChartTrack.link).appendTo(chartsTableBody.children('tr:last')).wrap('<td>');
813                 addPreviewControlsHover(chartsTableBody.children('tr:last').find('.preview_controls'))
814                 addPreviewControlsClick(chartsTableBody.children('tr:last').find('.single-cover'))
815                 chartsTableBody.children('tr:last').find('.resultArtist').click(function (ev){
816                         ev.preventDefault();
817                         showTrackList($(this).data("link"));
818                 });
819                 chartsTableBody.children('tr:last').find('.resultAlbum').click(function (ev){
820                         ev.preventDefault();
821                         showTrackListSelective($(this).data("link"));
822                 });
823         }
824         $('#tab_charts_table_charts_tbody_loadingIndicator').addClass('hide');
825         chartsTableBody.removeClass('hide');
826 });
827
828 //#############################################TAB_PLAYLISTS############################################\\
829 socket.on("getMePlaylistList", function (data) {
830         var tableBody = $('#table_personal_playlists');
831         $(tableBody).html('');
832         for (var i = 0; i < data.playlists.length; i++) {
833                 var currentResultPlaylist = data.playlists[i];
834                 $(tableBody).append(
835                                 `<tr>
836                                 <td><img src="${currentResultPlaylist.image}" class="circle" width="56px" /></td>
837                                 <td>${currentResultPlaylist.title}</td>
838                                 <td>${currentResultPlaylist.songs}</td>
839                                 </tr>`);
840                 if (currentResultPlaylist.spotify)
841                         generateShowTracklistButton(currentResultPlaylist.link).appendTo(tableBody.children('tr:last')).wrap('<td>')
842                 else
843                         generateShowTracklistSelectiveButton(currentResultPlaylist.link).appendTo(tableBody.children('tr:last')).wrap('<td>')
844
845                 generateDownloadLink(currentResultPlaylist.link).appendTo(tableBody.children('tr:last')).wrap('<td>');
846         }
847         $('.tooltipped').tooltip({delay: 100});
848 });
849
850 //###############################################TAB_URL##############################################\\
851 $('#tab_url_form_url').submit(function (ev) {
852
853         ev.preventDefault();
854         var urls = $("#song_url").val().split(";");
855         for(var i = 0; i < urls.length; i++){
856                 var url = urls[i];
857                 if (url.length == 0) {
858                         message('Blank URL Field', 'You need to insert an URL to download it!');
859                         return false;
860                 }
861                 //Validate URL
862                 if (url.indexOf('deezer.com/') < 0 && url.indexOf('open.spotify.com/') < 0 && url.indexOf('spotify:') < 0) {
863                         message('Wrong URL', 'The URL seems to be wrong. Please check it and try it again.');
864                         return false;
865                 }
866                 if (url.indexOf('?') > -1) {
867                         url = url.substring(0, url.indexOf("?"));
868                 }
869                 if (url.indexOf('open.spotify.com/') >= 0 ||  url.indexOf('spotify:') >= 0){
870                         if (url.indexOf('user') < 0 || url.indexOf('playlist') < 0){
871                                 message('Playlist not found', 'Deezloader for now can only download Spotify playlists.');
872                                 return false;
873                         }
874                 }
875                 addToQueue(url);
876         }
877 });
878
879 //############################################TAB_DOWNLOADS###########################################\\
880 function addToQueue(url) {
881         var type = getTypeFromLink(url), id = getIDFromLink(url);
882         if (type == 'spotifyplaylist'){
883                 [user, id] = getPlayUserFromURI(url)
884                 userSettings.currentSpotifyUser = user;
885         }
886         if (type == 'track') {
887                 userSettings.filename = userSettings.trackNameTemplate;
888                 userSettings.foldername = userSettings.albumNameTemplate;
889         } else if (type == 'playlist' || type == 'spotifyplaylist') {
890                 userSettings.filename = userSettings.playlistTrackNameTemplate;
891                 userSettings.foldername = userSettings.albumNameTemplate;
892         } else if (type == 'album' || type == 'artist'){
893                 userSettings.filename = userSettings.albumTrackNameTemplate;
894                 userSettings.foldername = userSettings.albumNameTemplate;
895         } else {
896                 M.toast({html: '<i class="material-icons left">error</i> Wrong Type!', displayLength: 5000, classes: 'rounded'});
897                 return false;
898         }
899         if (alreadyInQueue(id)) {
900                 M.toast({html: '<i class="material-icons left">playlist_add_check</i> Already in download-queue!', displayLength: 5000, classes: 'rounded'});
901                 return false;
902         }
903         if (id.match(/^[0-9]+$/) == null && type != 'spotifyplaylist' && parseInt(id)>0) {
904                 M.toast({html: '<i class="material-icons left">error</i> Wrong ID!', displayLength: 5000, classes: 'rounded'});
905                 return false;
906         }
907         socket.emit("download" + type, {id: id, settings: userSettings});
908         M.toast({html: '<i class="material-icons left">add</i>Added to download-queue', displayLength: 5000, classes: 'rounded'});
909 }
910
911 function alreadyInQueue(id) {
912         var alreadyInQueue = false;
913         $('#tab_downloads_table_downloads').find('tbody').find('tr').each(function () {
914                 if ($(this).data('deezerid') == id) {
915                         alreadyInQueue = true;
916                         return false
917                 }
918         });
919         return alreadyInQueue;
920 }
921
922 socket.on('addToQueue', function (data) {
923
924         var tableBody = $('#tab_downloads_table_downloads').find('tbody');
925
926         $(tableBody).append(
927                         `<tr id="${data.queueId}" data-deezerid="${data.id}">
928                         <td class="queueTitle">${data.name}</td>
929                         <td class="queueSize">${data.size}</td>
930                         <td class="queueDownloaded">${data.downloaded}</td>
931                         <td class="queueFailed">${data.failed}</td>
932                         <td><div class="progress"><div class="indeterminate"></div></div></td>
933                         </tr>`);
934
935         var btn_remove = $('<a href="#" class="btn-flat waves-effect"><i class="material-icons">remove</i></a>');
936
937         $(btn_remove).click(function (ev) {
938
939                 ev.preventDefault();
940
941                 socket.emit("cancelDownload", {queueId: data.queueId});
942
943         });
944
945         btn_remove.appendTo(tableBody.children('tr:last')).wrap('<td class="eventBtn center">');
946
947 });
948
949 socket.on("downloadStarted", function (data) {
950         //data.queueId -> queueId of started download
951
952         //Switch progress type indeterminate to determinate
953         $('#' + data.queueId).find('.indeterminate').removeClass('indeterminate').addClass('determinate');
954         $('#' + data.queueId).find('.eventBtn').find('a').html('<i class="material-icons">clear</i>');
955
956 });
957
958 socket.on('updateQueue', function (data) {
959
960         if (data.cancelFlag) {
961                 return;
962         }
963
964         $('#' + data.queueId).find('.queueDownloaded').html(data.downloaded);
965         $('#' + data.queueId).find('.queueFailed').html(data.failed);
966
967         if (data.failed == 0 && ((data.downloaded + data.failed) >= data.size)) {
968                 $('#' + data.queueId).find('.eventBtn').html('<i class="material-icons">done</i>');
969                 $('#' + data.queueId).addClass('finished');
970                 M.toast({html: `<i class="material-icons left">done</i>${quoteattr(data.name)} - Completed!`, displayLength: 5000, classes: 'rounded'})
971         } else if (data.downloaded == 0 && ((data.downloaded + data.failed) >= data.size)) {
972                 $('#' + data.queueId).find('.eventBtn').html('<i class="material-icons">error</i>');
973                 $('#' + data.queueId).addClass('error');
974                 M.toast({html: `<i class="material-icons left">error</i>${quoteattr(data.name)} - Failed!`, displayLength: 5000, classes: 'rounded'})
975         } else if ((data.downloaded + data.failed) >= data.size) {
976                 $('#' + data.queueId).find('.eventBtn').html('<i class="material-icons">warning</i>');
977                 $('#' + data.queueId).addClass('error');
978                 M.toast({html: `<i class="material-icons left">warning</i>${quoteattr(data.name)} - Completed with errors!`, displayLength: 5000, classes: 'rounded'})
979         }
980 });
981
982 socket.on("downloadProgress", function (data) {
983         //data.queueId -> id (string)
984         //data.percentage -> float/double, percentage
985         //updated in 1% steps
986         $('#' + data.queueId).find('.determinate').css('width', data.percentage + '%');
987
988 });
989
990 socket.on("emptyDownloadQueue", function () {
991         M.toast({html: '<i class="material-icons left">done_all</i>All downloads completed!', displayLength: 5000, classes: 'rounded'});
992 });
993
994 socket.on("cancelDownload", function (data) {
995         //data.queueId          -> queueId of item which was canceled
996         $('#' + data.queueId).addClass('animated fadeOutRight').on('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function () {
997                 $(this).remove();
998                 M.toast({html: '<i class="material-icons left">clear</i>One download removed!', displayLength: 5000, classes: 'rounded'})
999         });
1000 });
1001
1002 $('#clearTracksTable').click(function (ev) {
1003         $('#tab_downloads_table_downloads').find('tbody').find('.finished, .error').addClass('animated fadeOutRight').on('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function () {
1004                 $(this).remove();
1005         });
1006         return false;
1007 });
1008
1009 $('#cancelAllTable').click(function (ev) {
1010         let listOfIDs = $('#tab_downloads_table_downloads').find('tbody').find('tr').map((x,i)=>{
1011                 return $(i).attr('id')
1012         }).get();
1013         socket.emit('cancelAllDownloads', {queueList: listOfIDs})
1014 });
1015
1016 //****************************************************************************************************\\
1017 //******************************************HELPER-FUNCTIONS******************************************\\
1018 //****************************************************************************************************\\
1019 /**
1020  * Replaces special characters with HTML friendly counterparts
1021  * @param s string
1022  * @param preserveCR preserves the new line character
1023  * @returns {string}
1024  */
1025 function quoteattr(s, preserveCR) {
1026   preserveCR = preserveCR ? '&#13;' : '\n';
1027   return ('' + s) /* Forces the conversion to string. */
1028         .replace(/&/g, '&amp;') /* This MUST be the 1st replacement. */
1029     .replace(/'/g, '&apos;') /* The 4 other predefined entities, required. */
1030     .replace(/"/g, '&quot;')
1031     .replace(/</g, '&lt;')
1032     .replace(/>/g, '&gt;')
1033     /*
1034     You may add other replacements here for HTML only
1035     (but it's not necessary).
1036     Or for XML, only if the named entities are defined in its DTD.
1037     */
1038     .replace(/\r\n/g, preserveCR) /* Must be before the next replacement. */
1039     .replace(/[\r\n]/g, preserveCR);
1040     ;
1041 }
1042 /**
1043  * Given a spotify playlist URL or URI it returns the username of the owner of the playlist and the ID of the playlist
1044  * @param url URL or URI
1045  * @return string[] Array containing user and playlist id
1046  */
1047 function getPlayUserFromURI(url){
1048         var spotyUser, playlistID;
1049         if ((url.startsWith("http") && url.indexOf('open.spotify.com/') >= 0)){
1050                 if (url.indexOf('user') < 0 || url.indexOf('playlist') < 0){
1051                         message('Playlist not found', 'The URL seems to be wrong. Please check it and try it again.');
1052                         return [false,false];
1053                 }
1054                 if (url.indexOf('?') > -1) {
1055                         url = url.substring(0, url.indexOf("?"));
1056                 }
1057                 spotyUser = url.slice(url.indexOf("/user/")+6);
1058                 spotyUser = spotyUser.substring(0, spotyUser.indexOf("/"));
1059                 playlistID = url.slice(url.indexOf("/playlist/")+10);
1060         } else if (url.startsWith("spotify:")){
1061                 spotyUser = url.slice(url.indexOf("user:")+5);
1062                 spotyUser = spotyUser.substring(0, spotyUser.indexOf(":"));
1063                 playlistID = url.slice(url.indexOf("playlist:")+9);
1064         } else {
1065                 return [false,false];
1066         }
1067         return [spotyUser, playlistID]
1068 }
1069
1070 function getIDFromLink(link) {
1071         return link.substring(link.lastIndexOf("/") + 1);
1072 }
1073
1074 function getTypeFromLink(link) {
1075         var type;
1076         if (link.indexOf('spotify') > -1){
1077                 type = "spotifyplaylist";
1078         } else  if (link.indexOf('track') > -1) {
1079                 type = "track";
1080         } else if (link.indexOf('playlist') > -1) {
1081                 type = "playlist";
1082         } else if (link.indexOf('album') > -1) {
1083                 type = "album";
1084         } else if (link.indexOf('artist')) {
1085                 type = "artist";
1086         }
1087         return type;
1088 }
1089
1090 function generateDownloadLink(url) {
1091         var btn_download = $('<a href="#" class="waves-effect btn-flat"><i class="material-icons">file_download</i></a>');
1092         $(btn_download).click(function (ev) {
1093                 ev.preventDefault();
1094                 addToQueue(url);
1095         });
1096         return btn_download;
1097 }
1098
1099 function addPreviewControlsHover(el){
1100         el.hover( function () {
1101                 $(this).css({opacity: 1});
1102         }, function () {
1103                 if (($(this).parent().attr("playing") && preview_stopped) || !$(this).parent().attr("playing")){
1104                         $(this).css({opacity: 0}, 200);
1105                 }
1106         });
1107 }
1108
1109 function addPreviewControlsClick(el){
1110         el.click(function (e) {
1111                 e.preventDefault();
1112                 var icon = (this.tagName == "I" ? $(this) : $(this).children('i'))
1113                 if ($(this).attr("playing")){
1114                         if (preview_track.paused){
1115                                 preview_track.play();
1116                                 preview_stopped = false;
1117                                 icon.text("pause");
1118                                 $(preview_track).animate({volume: 1}, 500);
1119                         }else{
1120                                 preview_stopped = true;
1121                                 icon.text("play_arrow");
1122                                 $(preview_track).animate({volume: 0}, 250, "swing", ()=>{ preview_track.pause() });
1123                         }
1124                 }else{
1125                         $("*").removeAttr("playing");
1126                         $(this).attr("playing",true);
1127                         $('.preview_controls').text("play_arrow");
1128                         $('.preview_playlist_controls').text("play_arrow");
1129                         $('.preview_controls').css({opacity:0});
1130                         icon.text("pause");
1131                         icon.css({opacity: 1});
1132                         preview_stopped = false;
1133                         $(preview_track).animate({volume: 0}, 250, "swing", ()=>{
1134                                 preview_track.pause();
1135                                 $('#preview-track_source').prop("src", $(this).attr("preview"));
1136                                 preview_track.load();
1137                         });
1138                 }
1139         });
1140 }
1141
1142 function convertDuration(duration) {
1143         //convert from seconds only to mm:ss format
1144         var mm, ss;
1145         mm = Math.floor(duration / 60);
1146         ss = duration - (mm * 60);
1147         //add leading zero if ss < 0
1148         if (ss < 10) {
1149                 ss = "0" + ss;
1150         }
1151         return mm + ":" + ss;
1152 }
1153
1154 function sleep(milliseconds) {
1155   var start = new Date().getTime();
1156   for (var i = 0; i < 1e7; i++) {
1157     if ((new Date().getTime() - start) > milliseconds){
1158       break;
1159     }
1160   }
1161 }