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