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