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