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