1fd22f56bb4d3fa6d6cd3db01e246d4b81c61a06
[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').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="circle ${(currentResultTrack.preview ? `single-cover" preview="${currentResultTrack.preview}"><i class="material-icons preview_controls white-text">play_arrow</i>` : '">')}<img style="width:56px;" class="circle" 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">error_outline</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="circle" /></td>
480                                 <td>${(currentResultAlbum.explicit_lyrics ? '<i class="material-icons valignicon tiny materialize-red-text tooltipped" data-tooltip="Explicit">error_outline</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="circle" /></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="circle" /></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">error_outline</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">error_outline</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">error_outline</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 = 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">error_outline</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">error_outline</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 circle">' + 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 circle">' + 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="circle ${(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="circle" /></a></td>
821                                 <td>${(currentChartTrack.explicit_lyrics ? '<i class="material-icons valignicon tiny materialize-red-text tooltipped" data-tooltip="Explicit">error_outline</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="circle" 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)
896         if (type == 'track') {
897                 userSettings.filename = userSettings.trackNameTemplate
898                 userSettings.foldername = userSettings.albumNameTemplate
899         } else if (type == 'playlist' || type == 'spotifyplaylist') {
900                 userSettings.filename = userSettings.playlistTrackNameTemplate
901                 userSettings.foldername = userSettings.albumNameTemplate
902         } else if (type == 'album' || type == 'artist'){
903                 userSettings.filename = userSettings.albumTrackNameTemplate
904                 userSettings.foldername = userSettings.albumNameTemplate
905         } else {
906                 M.toast({html: '<i class="material-icons left">error</i> Wrong Type!', displayLength: 5000, classes: 'rounded'})
907                 return false
908         }
909         if (alreadyInQueue(id)) {
910                 M.toast({html: '<i class="material-icons left">playlist_add_check</i> Already in download-queue!', displayLength: 5000, classes: 'rounded'})
911                 return false
912         }
913         if (id.match(/^[0-9]+$/) == null && type != 'spotifyplaylist' && parseInt(id)>0) {
914                 M.toast({html: '<i class="material-icons left">error</i> Wrong ID!', displayLength: 5000, classes: 'rounded'})
915                 return false
916         }
917         socket.emit("download" + type, {id: id, settings: userSettings})
918         M.toast({html: '<i class="material-icons left">add</i>Added to download-queue', displayLength: 5000, classes: 'rounded'})
919 }
920
921 function alreadyInQueue(id) {
922         var alreadyInQueue = false
923         $('#tab_downloads_table_downloads').find('tbody').find('tr').each(function () {
924                 if ($(this).data('deezerid') == `${id}:${userSettings.maxBitrate}`) {
925                         alreadyInQueue = true
926                         return false
927                 }
928         })
929         return alreadyInQueue
930 }
931
932 socket.on('addToQueue', function (data) {
933
934         var tableBody = $('#tab_downloads_table_downloads').find('tbody')
935
936         $(tableBody).append(
937                         `<tr id="${data.queueId}" data-deezerid="${data.id}">
938                         <td class="queueTitle">${data.name}</td>
939                         <td class="queueSize">${data.size}</td>
940                         <td class="queueDownloaded">${data.downloaded}</td>
941                         <td class="queueFailed">${data.failed}</td>
942                         <td><div class="progress"><div class="indeterminate"></div></div></td>
943                         </tr>`)
944
945         var btn_remove = $('<a href="#" class="btn-flat waves-effect"><i class="material-icons">remove</i></a>')
946
947         $(btn_remove).click(function (ev) {
948
949                 ev.preventDefault()
950
951                 socket.emit("cancelDownload", {queueId: data.queueId})
952
953         })
954
955         btn_remove.appendTo(tableBody.children('tr:last')).wrap('<td class="eventBtn center">')
956
957 })
958
959 socket.on("downloadStarted", function (data) {
960         //data.queueId -> queueId of started download
961
962         //Switch progress type indeterminate to determinate
963         $('#' + data.queueId).find('.indeterminate').removeClass('indeterminate').addClass('determinate')
964         $('#' + data.queueId).find('.eventBtn').find('a').html('<i class="material-icons">clear</i>')
965
966 })
967
968 socket.on('updateQueue', function (data) {
969
970         if (data.cancelFlag) {
971                 return
972         }
973
974         $('#' + data.queueId).find('.queueDownloaded').html(data.downloaded)
975         $('#' + data.queueId).find('.queueFailed').html(data.failed)
976
977         if (data.failed == 0 && ((data.downloaded + data.failed) >= data.size)) {
978                 $('#' + data.queueId).find('.eventBtn').html('<i class="material-icons">done</i>')
979                 $('#' + data.queueId).addClass('finished')
980                 M.toast({html: `<i class="material-icons left">done</i>${quoteattr(data.name)} - Completed!`, displayLength: 5000, classes: 'rounded'})
981         } else if (data.downloaded == 0 && ((data.downloaded + data.failed) >= data.size)) {
982                 $('#' + data.queueId).find('.eventBtn').html('<i class="material-icons">error</i>')
983                 $('#' + data.queueId).addClass('error')
984                 M.toast({html: `<i class="material-icons left">error</i>${quoteattr(data.name)} - Failed!`, displayLength: 5000, classes: 'rounded'})
985         } else if ((data.downloaded + data.failed) >= data.size) {
986                 $('#' + data.queueId).find('.eventBtn').html('<i class="material-icons">warning</i>')
987                 $('#' + data.queueId).addClass('error')
988                 M.toast({html: `<i class="material-icons left">warning</i>${quoteattr(data.name)} - Completed with errors!`, displayLength: 5000, classes: 'rounded'})
989         }
990 })
991
992 socket.on("downloadProgress", function (data) {
993         //data.queueId -> id (string)
994         //data.percentage -> float/double, percentage
995         //updated in 1% steps
996         $('#' + data.queueId).find('.determinate').css('width', data.percentage + '%')
997
998 })
999
1000 socket.on("emptyDownloadQueue", function () {
1001         M.toast({html: '<i class="material-icons left">done_all</i>All downloads completed!', displayLength: 5000, classes: 'rounded'})
1002 })
1003
1004 socket.on("cancelDownload", function (data) {
1005         //data.queueId          -> queueId of item which was canceled
1006         $('#' + data.queueId).addClass('animated fadeOutRight').on('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function () {
1007                 $(this).remove()
1008                 if (!data.cleanAll) M.toast({html: '<i class="material-icons left">clear</i>One download removed!', displayLength: 5000, classes: 'rounded'})
1009         })
1010 })
1011
1012 $('#clearTracksTable').click(function (ev) {
1013         $('#tab_downloads_table_downloads').find('tbody').find('.finished, .error').addClass('animated fadeOutRight').on('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function () {
1014                 $(this).remove()
1015         })
1016         return false
1017 })
1018
1019 $('#cancelAllTable').click(function (ev) {
1020         let listOfIDs = $('#tab_downloads_table_downloads').find('tbody').find('tr').map((x,i)=>{
1021                 return $(i).attr('id')
1022         }).get()
1023         socket.emit('cancelAllDownloads', {queueList: listOfIDs})
1024 })
1025
1026 socket.on("cancelAllDownloads", function () {
1027         M.toast({html: '<i class="material-icons left">clear</i>All downloads removed!', displayLength: 5000, classes: 'rounded'})
1028 })
1029
1030 //****************************************************************************************************\\
1031 //******************************************HELPER-FUNCTIONS******************************************\\
1032 //****************************************************************************************************\\
1033 /**
1034  * Replaces special characters with HTML friendly counterparts
1035  * @param s string
1036  * @param preserveCR preserves the new line character
1037  * @returns {string}
1038  */
1039 function quoteattr(s, preserveCR) {
1040   preserveCR = preserveCR ? '&#13;' : '\n'
1041   return ('' + s) /* Forces the conversion to string. */
1042         .replace(/&/g, '&amp;') /* This MUST be the 1st replacement. */
1043     .replace(/'/g, '&apos;') /* The 4 other predefined entities, required. */
1044     .replace(/"/g, '&quot;')
1045     .replace(/</g, '&lt;')
1046     .replace(/>/g, '&gt;')
1047     /*
1048     You may add other replacements here for HTML only
1049     (but it's not necessary).
1050     Or for XML, only if the named entities are defined in its DTD.
1051     */
1052     .replace(/\r\n/g, preserveCR) /* Must be before the next replacement. */
1053     .replace(/[\r\n]/g, preserveCR)
1054
1055 }
1056
1057 function getIDFromLink(link) {
1058         if (link.indexOf('?') > -1) {
1059                 link = link.substring(0, link.indexOf("?"))
1060         }
1061         if ((link.startsWith("http") && link.indexOf('open.spotify.com/') >= 0)){
1062                 return link.slice(link.indexOf("/playlist/")+10)
1063         } else if (link.startsWith("spotify:")){
1064                 return link.slice(link.indexOf("playlist:")+9)
1065         } else {
1066                 return link.substring(link.lastIndexOf("/") + 1)
1067         }
1068 }
1069
1070 function getTypeFromLink(link) {
1071         var type
1072         if (link.indexOf('spotify') > -1){
1073                 type = "spotifyplaylist"
1074         } else  if (link.indexOf('track') > -1) {
1075                 type = "track"
1076         } else if (link.indexOf('playlist') > -1) {
1077                 type = "playlist"
1078         } else if (link.indexOf('album') > -1) {
1079                 type = "album"
1080         } else if (link.indexOf('artist')) {
1081                 type = "artist"
1082         }
1083         return type
1084 }
1085
1086 function generateDownloadLink(url) {
1087         var btn_download = $('<a href="#" class="waves-effect btn-flat"><i class="material-icons">file_download</i></a>')
1088         $(btn_download).click(function (ev) {
1089                 ev.preventDefault()
1090                 addToQueue(url)
1091         })
1092         return btn_download
1093 }
1094
1095 function addPreviewControlsHover(el){
1096         el.hover( function () {
1097                 $(this).css({opacity: 1})
1098         }, function () {
1099                 if (($(this).parent().attr("playing") && preview_stopped) || !$(this).parent().attr("playing")){
1100                         $(this).css({opacity: 0}, 200)
1101                 }
1102         })
1103 }
1104
1105 function addPreviewControlsClick(el){
1106         el.click(function (e) {
1107                 e.preventDefault()
1108                 var icon = (this.tagName == "I" ? $(this) : $(this).children('i'))
1109                 if ($(this).attr("playing")){
1110                         if (preview_track.paused){
1111                                 preview_track.play()
1112                                 preview_stopped = false
1113                                 icon.text("pause")
1114                                 $(preview_track).animate({volume: 1}, 500)
1115                         }else{
1116                                 preview_stopped = true
1117                                 icon.text("play_arrow")
1118                                 $(preview_track).animate({volume: 0}, 250, "swing", ()=>{ preview_track.pause() })
1119                         }
1120                 }else{
1121                         $("*").removeAttr("playing")
1122                         $(this).attr("playing",true)
1123                         $('.preview_controls').text("play_arrow")
1124                         $('.preview_playlist_controls').text("play_arrow")
1125                         $('.preview_controls').css({opacity:0})
1126                         icon.text("pause")
1127                         icon.css({opacity: 1})
1128                         preview_stopped = false
1129                         $(preview_track).animate({volume: 0}, 250, "swing", ()=>{
1130                                 preview_track.pause()
1131                                 $('#preview-track_source').prop("src", $(this).attr("preview"))
1132                                 preview_track.load()
1133                         })
1134                 }
1135         })
1136 }
1137
1138 function convertDuration(duration) {
1139         //convert from seconds only to mm:ss format
1140         var mm, ss
1141         mm = Math.floor(duration / 60)
1142         ss = duration - (mm * 60)
1143         //add leading zero if ss < 0
1144         if (ss < 10) {
1145                 ss = "0" + ss
1146         }
1147         return mm + ":" + ss
1148 }
1149
1150 function sleep(milliseconds) {
1151   var start = new Date().getTime()
1152   for (var i = 0; i < 1e7; i++) {
1153     if ((new Date().getTime() - start) > milliseconds){
1154       break
1155                 }
1156   }
1157 }