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