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