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