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