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