removed httpVueLoader, moved Vue instances in different files, moved socket in different file, other minor code changes

This commit is contained in:
Roberto Tonino 2020-04-21 22:20:19 +02:00
parent aeb9194127
commit 7b106e97ab
10 changed files with 1040 additions and 1509 deletions

View File

@ -1,15 +1,20 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<head>
<meta charset="utf-8">
<title>deemix</title>
<link rel="stylesheet" type="text/css" href="/public/css/style.css">
<meta name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=0">
</head>
<script>
if ('true' === localStorage.getItem('darkMode')) {
document.documentElement.classList.add('dark-theme')
}
</script>
</head>
<body>
<body>
<aside role="navigation" id="sidebar">
<i class="material-icons side_icon">menu</i>
<i id="main_search_tablink" class="main_tablinks"></i>
@ -159,8 +164,8 @@ <h1>No Tracks found</h1>
<td style="width: 48px; text-align: center;"><img class="rounded coverart"
v-bind:src="track.album.cover_small"></td>
<td class="breakline">{{track.title + (track.title_version ? ' '+track.title_version : '')}}</td>
<td class="breakline clickable" v-on:click="artistView(event)"
v-bind:data-id="track.artist.id">{{track.artist.name}}</td>
<td class="breakline clickable" v-on:click="artistView(event)" v-bind:data-id="track.artist.id">
{{track.artist.name}}</td>
<td class="breakline clickable" v-on:click="albumView(event)" v-bind:data-id="track.album.id">
{{track.album.title}}</td>
<td>{{convertDuration(track.duration)}}</td>
@ -263,16 +268,17 @@ <h1>Settings</h1>
style="width: 125px;height:125px; margin-right: 12px;" />
<div>
<p>You are logged in as <b id="settings_username"></b></p>
<button id="settings_btn_logout">Logout</button>
<button id="settings_btn_logout" @click="logout">Logout</button>
</div>
</div>
<div class="inline-flex">
<input autocomplete="off" type="password" id="login_input_arl" placeholder="ARL" />
<button id="settings_btn_copyArl"><i class="material-icons">assignment</i></button>
<input autocomplete="off" type="password" id="login_input_arl" ref="loginInput" placeholder="ARL" />
<button id="settings_btn_copyArl" @click="copyARLtoClipboard"><i
class="material-icons">assignment</i></button>
</div>
<p><a href="https://notabug.org/RemixDevs/DeezloaderRemix/wiki/Login+via+userToken" target="_blank">How do I
get my own ARL?</a></p>
<p><button style="width:100%;" id="settings_btn_updateArl">Update ARL</button></p>
<p><button id="settings_btn_updateArl" @click="login" style="width:100%;">Update ARL</button></p>
<div id="settings_generic_tab">
<div class="input_group">
<p>Download Path</p>
@ -454,7 +460,7 @@ <h1>Settings</h1>
</div>
</div>
<footer>
<button id="settings_btn_save">Save</button>
<button id="settings_btn_save" @click="saveSettings">Save</button>
</footer>
</div>
@ -474,7 +480,7 @@ <h1>{{ title }}</h1>
<div class="tab">
<template v-for="(item, name, index) in body">
<button v-bind:class="'selective' + (name==currentTab ? 'active' : '')" v-bind:href="'#artist_' + name"
v-on:click="changeTab(name)">{{ name }}</button>
@click="changeTab(name)">{{ name }}</button>
</template>
</div>
@ -506,7 +512,7 @@ <h1>{{ title }}</h1>
</table>
<footer>
<button onclick="backTab()">Back</button>
<button class="back-button">Back</button>
</footer>
</div>
@ -514,13 +520,15 @@ <h1>{{ title }}</h1>
<header
v-bind:style="{ 'background-image': 'linear-gradient(to bottom, transparent 0%, var(--main-background) 100%), url(\''+image+'\')' }">
<h1 class="inline-flex">{{ title }} <i v-if="explicit" class="material-icons">explicit</i></h1>
<h2 class="inline-flex"><span v-if="metadata">{{ metadata }}</span><span class="right" v-if="release_date">{{ release_date }}</span></h2>
<h2 class="inline-flex"><span v-if="metadata">{{ metadata }}</span><span class="right"
v-if="release_date">{{ release_date }}</span></h2>
</header>
<table>
<thead>
<tr>
<th v-for="data in head" v-html="data.title" v-bind:style="{ 'width': data.width ? data.width : 'auto'}"></th>
<th v-for="data in head" v-html="data.title"
v-bind:style="{ 'width': data.width ? data.width : 'auto'}"></th>
<th style="width: 32px"><input v-on:click="toggleAll(event)" class="selectAll" type="checkbox"></th>
</tr>
</thead>
@ -529,7 +537,8 @@ <h2 class="inline-flex"><span v-if="metadata">{{ metadata }}</span><span class=
<tr v-if="track.type == 'track'">
<td><i class="material-icons">play_arrow</i></td>
<td>{{ track.track_position }}</td>
<td class="inline-flex"><i v-if="track.explicit_lyrics" class="material-icons">explicit</i>{{ track.title }} <span
<td class="inline-flex"><i v-if="track.explicit_lyrics"
class="material-icons">explicit</i>{{ track.title }} <span
v-if="track.title_version">{{track.title_version}}</span></td>
<td class="clickable" v-on:click="artistView(event)" v-bind:data-id="track.artist.id">
{{ track.artist.name }}</td>
@ -553,7 +562,7 @@ <h2 class="inline-flex"><span v-if="metadata">{{ metadata }}</span><span class=
<button class="with_icon" v-on:contextmenu="openQualityModal(event)" v-on:click="addToQueue(event)"
v-bind:data-link="selectedLinks()">Download selection<i
class="material-icons">file_download</i></button>
<button onclick="backTab()">Back</button>
<button class="back-button">Back</button>
</footer>
</div>
@ -581,14 +590,13 @@ <h2 class="inline-flex"><span v-if="metadata">{{ metadata }}</span><span class=
<button class="quality-button" data-quality-value="13">Download 360 Reality Audio [LQ]</button><br>
</div>
</div>
</body>
<script type="text/javascript" src="/public/js/vendor/httpVueLoader.js"></script>
<script type="text/javascript" src="/public/js/vendor/socket.io.js"></script>
<script type="text/javascript" src="/public/js/vendor/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="/public/js/vendor/vue.min.js"></script>
<script type="text/javascript" src="/public/js/vendor/lodash.min.js"></script>
<script type="text/javascript" src="/public/js/vendor/toastify.js"></script>
</body>
<script type="text/javascript" src="/public/js/vendor/socket.io.js"></script>
<script type="text/javascript" src="/public/js/vendor/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="/public/js/vendor/vue.min.js"></script>
<script type="text/javascript" src="/public/js/vendor/lodash.min.js"></script>
<script type="text/javascript" src="/public/js/vendor/toastify.js"></script>
<script type="module" src="/public/js/app.js"></script>
<script type="module" src="/public/js/app.js"></script>
</html>

View File

@ -1,69 +1,28 @@
/* ===== Imports ===== */
import * as Utils from './modules/utils.js'
import { MainSearch } from './modules/search-tab/main-search.js'
const socket = io.connect(window.location.href)
const localStorage = window.localStorage
/* ===== Vue components ===== */
import { MainSearch } from './modules/main-search.js'
import { SettingsTab } from './modules/settings-tab.js'
// Toasts stuff
window.toastsWithId = {}
import { resetTracklistTab } from './modules/tracklist-tab.js'
import { resetArtistTab } from './modules/artist-tab.js'
import { toast } from './modules/toasts.js'
import { socket } from './modules/socket.js'
// Settings
window.lastSettings = {}
window.lastCredentials = {}
window.toast = function (msg, icon = null, dismiss = true, id = null) {
if (toastsWithId[id]) {
let toastObj = toastsWithId[id]
let toastDOM = $(`div.toastify[toast_id=${id}]`)
if (msg) {
toastDOM.find('.toast-message').html(msg)
}
if (icon) {
if (icon == 'loading') icon = `<div class="circle-loader"></div>`
else icon = `<i class="material-icons">${icon}</i>`
toastDOM.find('.toast-icon').html(icon)
}
if (dismiss !== null && dismiss) {
setTimeout(function () {
toastObj.hideToast()
delete toastsWithId[id]
}, 3000)
}
} else {
if (icon == null) icon = ''
else if (icon == 'loading') icon = `<div class="circle-loader"></div>`
else icon = `<i class="material-icons">${icon}</i>`
let toastObj = Toastify({
text: `<span class="toast-icon">${icon}</span><span class="toast-message">${msg}</toast>`,
duration: dismiss ? 3000 : 0,
gravity: 'bottom',
position: 'left'
}).showToast()
if (id) {
toastsWithId[id] = toastObj
$(toastObj.toastElement).attr('toast_id', id)
}
}
}
/* ===== Globals ===== */
/* ===== Socketio listeners ===== */
socket.on('toast', data => {
toast(data.msg, data.icon || null, data.dismiss !== undefined ? data.dismiss : true, data.id || null)
})
// Debug messages for socketio
socket.on('message', function (msg) {
console.log(msg)
})
// Login stuff
window.loginButton = function () {
let arl = document.querySelector('#login_input_arl').value
if (arl != '' && arl != localStorage.getItem('arl')) {
socket.emit('login', arl, true)
}
}
socket.on('toast', data => {
toast(data.msg, data.icon || null, data.dismiss !== undefined ? data.dismiss : true, data.id || null)
})
socket.on('logging_in', function () {
toast('Logging in', 'loading', false, 'login-toast')
@ -122,28 +81,35 @@ socket.on('logged_out', function () {
})
/**
* Adds event listeners.
* Adds all event listeners.
* @returns {void}
* @since 0.1.0 (?)
*/
function linkEventListeners() {
document.getElementById('toggle_download_tab').addEventListener('click', toggleDownloadTab)
document.getElementById('modal_quality').addEventListener('click', modalQualityButton)
document.getElementById('settings_btn_updateArl').addEventListener('click', loginButton)
document.getElementById('sidebar').addEventListener('click', handleSidebarClick)
document.getElementById('search_tab').addEventListener('click', handleTabClick)
const backButtons = Array.from(document.getElementsByClassName('back-button'))
backButtons.forEach(button => {
button.addEventListener('click', backTab)
})
document.getElementById('clean_queue').addEventListener('click', () => {
socket.emit('removeFinishedDownloads')
})
document.getElementById('cancel_queue').addEventListener('click', () => {
socket.emit('cancelAllDownloads')
})
}
/**
* App initialization.
* @returns {void}
* @since 0.1.0 (?)
*/
/* ===== App initialization ===== */
function init() {
linkEventListeners()
if ('true' === localStorage.darkMode) {
document.documentElement.classList.add('dark-theme')
}
if (localStorage.getItem('arl')) {
let arl = localStorage.getItem('arl')
@ -217,13 +183,16 @@ window.showTab = function (type, id, back = false) {
document.getElementById(tab).style.display = 'block'
}
window.backTab = function () {
function backTab() {
if (windows_stack.length == 1) {
clickElement('main_' + main_selected + 'link')
document.getElementById(`main_${main_selected}link`).click()
} else {
let tabObj = windows_stack.pop()
if (tabObj.type == 'artist') resetArtistTab()
else resetTracklistTab()
if (tabObj.type == 'artist') {
resetArtistTab()
} else {
resetTracklistTab()
}
socket.emit('getTracklist', { type: tabObj.type, id: tabObj.id })
showTab(tabObj.type, tabObj.id, true)
}
@ -236,7 +205,7 @@ window.backTab = function () {
* @param {Event} event
* @since 0.1.0
*/
window.handleSidebarClick = function (event) {
function handleSidebarClick(event) {
let targetID = event.target.id
switch (targetID) {
@ -267,142 +236,7 @@ window.handleSidebarClick = function (event) {
}
}
document.getElementById('sidebar').addEventListener('click', handleSidebarClick)
/* stackedTabs.js */
var artistTab = new Vue({
el: '#artist_tab',
data: {
currentTab: '',
sortKey: 'release_date',
sortOrder: 'desc',
title: '',
image: '',
type: '',
link: '',
head: null,
body: null
},
methods: {
addToQueue: function (e) {
e.stopPropagation()
sendAddToQueue(e.currentTarget.dataset.link)
},
openQualityModal: function (e) {
e.preventDefault()
openQualityModal(e.currentTarget.dataset.link)
},
moreInfo: function (url, e) {
if (e) e.preventDefault()
showTrackListSelective(url, true)
},
sortBy: function (key) {
if (key == this.sortKey) {
this.sortOrder = this.sortOrder == 'asc' ? 'desc' : 'asc'
} else {
this.sortKey = key
this.sortOrder = 'asc'
}
},
changeTab: function (tab) {
this.currentTab = tab
},
checkNewRelease: function (date) {
var g1 = new Date()
var g2 = new Date(date)
g2.setDate(g2.getDate() + 3)
g1.setHours(0, 0, 0, 0)
if (g1.getTime() <= g2.getTime()) {
return true
} else {
return false
}
}
},
computed: {
showTable() {
if (this.body) return _.orderBy(this.body[this.currentTab], this.sortKey, this.sortOrder)
else return []
}
}
})
var tracklistTab = new Vue({
el: '#tracklist_tab',
data: {
title: '',
metadata: '',
release_date: '',
label: '',
explicit: false,
image: '',
type: '',
link: '',
head: null,
body: []
},
methods: {
addToQueue: function (e) {
e.stopPropagation()
sendAddToQueue(e.currentTarget.dataset.link)
},
openQualityModal: function (e) {
e.preventDefault()
openQualityModal(e.currentTarget.dataset.link)
},
toggleAll: function (e) {
this.body.forEach(item => {
if (item.type == 'track') {
item.selected = e.currentTarget.checked
}
})
},
selectedLinks: function () {
var selected = []
if (this.body) {
this.body.forEach(item => {
if (item.type == 'track' && item.selected) selected.push(item.link)
})
}
return selected.join(';')
},
convertDuration(duration) {
//convert from seconds only to mm:ss format
let mm, ss
mm = Math.floor(duration / 60)
ss = duration - mm * 60
//add leading zero if ss < 0
if (ss < 10) {
ss = '0' + ss
}
return mm + ':' + ss
}
}
})
window.resetArtistTab = function () {
artistTab.title = 'Loading...'
artistTab.image = ''
artistTab.type = ''
artistTab.currentTab = ''
artistTab.sortKey = 'release_date'
artistTab.sortOrder = 'desc'
artistTab.link = ''
artistTab.head = []
artistTab.body = null
}
window.resetTracklistTab = function () {
tracklistTab.title = 'Loading...'
tracklistTab.image = ''
tracklistTab.metadata = ''
tracklistTab.label = ''
tracklistTab.release_date = ''
tracklistTab.explicit = false
tracklistTab.type = ''
tracklistTab.head = []
tracklistTab.body = []
}
window.artistView = function (ev) {
let id = ev.currentTarget.dataset.id
@ -423,71 +257,6 @@ window.playlistView = function (ev) {
showTab('playlist', id)
}
socket.on('show_artist', function (data) {
artistTab.title = data.name
artistTab.image = data.picture_xl
artistTab.type = 'Artist'
artistTab.link = `https://www.deezer.com/artist/${data.id}`
artistTab.currentTab = Object.keys(data.releases)[0]
artistTab.sortKey = 'release_date'
artistTab.sortOrder = 'desc'
artistTab.head = [
{ title: 'Title', sortKey: 'title' },
{ title: 'Release Date', sortKey: 'release_date' },
{ title: '', width: '32px' }
]
if (_.isEmpty(data.releases)) {
artistTab.body = null
} else {
artistTab.body = data.releases
}
})
socket.on('show_album', function (data) {
tracklistTab.type = 'Album'
tracklistTab.link = `https://www.deezer.com/album/${data.id}`
tracklistTab.title = data.title
tracklistTab.explicit = data.explicit_lyrics
tracklistTab.label = data.label
tracklistTab.metadata = `${data.artist.name}${data.tracks.length} songs`
tracklistTab.release_date = data.release_date.substring(0, 10)
tracklistTab.image = data.cover_xl
tracklistTab.head = [
{ title: '<i class="material-icons">music_note</i>', width: '24px' },
{ title: '#' },
{ title: 'Song' },
{ title: 'Artist' },
{ title: '<i class="material-icons">timer</i>', width: '40px' }
]
if (_.isEmpty(data.tracks)) {
tracklistTab.body = null
} else {
tracklistTab.body = data.tracks
}
})
socket.on('show_playlist', function (data) {
tracklistTab.type = 'Playlist'
tracklistTab.link = `https://www.deezer.com/playlist/${data.id}`
tracklistTab.title = data.title
tracklistTab.image = data.picture_xl
tracklistTab.release_date = data.creation_date.substring(0, 10)
tracklistTab.metadata = `by ${data.creator.name}${data.tracks.length} songs`
tracklistTab.head = [
{ title: '<i class="material-icons">music_note</i>', width: '24px' },
{ title: '#' },
{ title: 'Song' },
{ title: 'Artist' },
{ title: 'Album' },
{ title: '<i class="material-icons">timer</i>', width: '40px' }
]
if (_.isEmpty(data.tracks)) {
tracklistTab.body = null
} else {
tracklistTab.body = data.tracks
}
})
/* search.js */
// Load more content when the search page is at the end
$('#content').on('scroll', function () {
@ -523,27 +292,6 @@ window.scrolledSearch = function (type) {
}
}
window.searchUpdate = function (result) {
let next = 0
if (result.next) next = parseInt(result.next.match(/index=(\d*)/)[1])
else next = result.total
if (MainSearch.results[result.type + 'Tab'].total != result.total)
MainSearch.results[result.type + 'Tab'].total = result.total
if (MainSearch.results[result.type + 'Tab'].next != next) {
MainSearch.results[result.type + 'Tab'].next = next
MainSearch.results[result.type + 'Tab'].data = MainSearch.results[result.type + 'Tab'].data.concat(result.data)
}
MainSearch.results[result.type + 'Tab'].loaded = true
}
socket.on('search', function (result) {
searchUpdate(result)
})
window.clickElement = function (button) {
return document.getElementById(button).click()
}
window.sendAddToQueue = function (url, bitrate = null) {
if (url.indexOf(';') != -1) {
urls = url.split(';')
@ -555,7 +303,7 @@ window.sendAddToQueue = function (url, bitrate = null) {
}
}
window.handleTabClick = function (event) {
function handleTabClick(event) {
let targetID = event.target.id
switch (targetID) {
@ -580,8 +328,6 @@ window.handleTabClick = function (event) {
}
}
document.getElementById('search_tab').addEventListener('click', handleTabClick)
// Search section
$('#searchbar').keyup(function (e) {
if (e.keyCode == 13) {
@ -605,82 +351,11 @@ $('#searchbar').keyup(function (e) {
}
})
window.mainSearchHandler = function (result) {
let resetObj = { data: [], next: 0, total: 0, loaded: false }
MainSearch.results.allTab = result
MainSearch.results.query = result.QUERY
MainSearch.results.trackTab = { ...resetObj }
MainSearch.results.albumTab = { ...resetObj }
MainSearch.results.artistTab = { ...resetObj }
MainSearch.results.playlistTab = { ...resetObj }
document.getElementById('search_all_tab').click()
document.getElementById('search_tab_content').style.display = 'block'
document.getElementById('main_search_tablink').click()
}
socket.on('mainSearch', function (result) {
mainSearchHandler(result)
})
/* settings.js */
const SettingsTab = new Vue({
el: '#settings_tab',
data: {
settings: { tags: {} },
spotifyFeatures: {}
},
methods: {
addListeners() {
document.getElementById('settings_btn_save').addEventListener('click', window.saveSettings)
document.getElementById('settings_btn_copyArl').addEventListener('click', window.copyARLtoClipboard)
document.getElementById('settings_btn_logout').addEventListener('click', window.logout)
}
},
mounted() {
this.addListeners()
}
})
socket.on('init_settings', function (settings, credentials) {
loadSettings(settings, credentials)
toast('Settings loaded!', 'settings')
})
socket.on('updateSettings', function (settings, credentials) {
loadSettings(settings, credentials)
toast('Settings updated!', 'settings')
})
window.loadSettings = function (settings, spotifyCredentials) {
lastSettings = { ...settings }
lastCredentials = { ...spotifyCredentials }
SettingsTab.settings = settings
SettingsTab.spotifyFeatures = spotifyCredentials
}
window.saveSettings = function () {
lastSettings = { ...SettingsTab.settings }
lastCredentials = { ...SettingsTab.spotifyFeatures }
socket.emit('saveSettings', lastSettings, lastCredentials)
}
window.copyARLtoClipboard = function () {
$('#login_input_arl').attr('type', 'text')
let copyText = document.querySelector('#login_input_arl')
copyText.select()
copyText.setSelectionRange(0, 99999)
document.execCommand('copy')
$('#login_input_arl').attr('type', 'password')
toast('ARL copied to clipboard', 'assignment')
}
window.logout = function () {
socket.emit('logout')
}
/* downloadList.js */
// Show/Hide Download Tab
window.toggleDownloadTab = function (ev) {
function toggleDownloadTab(ev) {
ev.preventDefault()
let isHidden = document.querySelector('#download_tab_container').classList.toggle('tab_hidden')
@ -753,9 +428,7 @@ window.addToQueue = function (queueItem, current = false) {
}
}
socket.on('addedToQueue', function (queueItem) {
addToQueue(queueItem)
})
socket.on('addedToQueue', addToQueue)
window.downloadAction = function (evt) {
let icon = $(evt.currentTarget).text()
@ -830,14 +503,6 @@ socket.on('removedFinishedDownloads', function () {
queueComplete = []
})
$('#clean_queue').on('click', function () {
socket.emit('removeFinishedDownloads')
})
$('#cancel_queue').on('click', function () {
socket.emit('cancelAllDownloads')
})
socket.on('updateQueue', function (update) {
if (update.uuid && queue.indexOf(update.uuid) > -1) {
if (update.downloaded) {
@ -869,40 +534,41 @@ socket.on('updateQueue', function (update) {
/* modals.js */
// quality modal stuff
var modalQuality = document.getElementById('modal_quality')
var $modalQuality = $(modalQuality)
modalQuality.open = false
window.onclick = function (event) {
if (event.target == modalQuality && modalQuality.open) {
$(modalQuality).addClass('animated fadeOut')
$modalQuality.addClass('animated fadeOut')
}
}
$(modalQuality).on('webkitAnimationEnd', function () {
$modalQuality.on('webkitAnimationEnd', function () {
if (modalQuality.open) {
$(this).removeClass('animated fadeOut')
$(this).css('display', 'none')
$modalQuality.removeClass('animated fadeOut')
$modalQuality.css('display', 'none')
modalQuality.open = false
} else {
$(this).removeClass('animated fadeIn')
$(this).css('display', 'block')
$modalQuality.removeClass('animated fadeIn')
$modalQuality.css('display', 'block')
modalQuality.open = true
}
})
window.openQualityModal = function (link) {
$(modalQuality).data('url', link)
$(modalQuality).css('display', 'block')
$(modalQuality).addClass('animated fadeIn')
$modalQuality.data('url', link)
$modalQuality.css('display', 'block')
$modalQuality.addClass('animated fadeIn')
}
window.modalQualityButton = function (event) {
function modalQualityButton(event) {
if (!event.target.matches('.quality-button')) {
return
}
let bitrate = event.target.dataset.qualityValue
var url = $(modalQuality).data('url')
var url = $modalQuality.data('url')
sendAddToQueue(url, bitrate)
$(modalQuality).addClass('animated fadeOut')
$modalQuality.addClass('animated fadeOut')
}

View File

@ -0,0 +1,90 @@
import { socket } from './socket.js'
export const ArtistTab = new Vue({
el: '#artist_tab',
data: {
currentTab: '',
sortKey: 'release_date',
sortOrder: 'desc',
title: '',
image: '',
type: '',
link: '',
head: null,
body: null
},
methods: {
addToQueue(e) {
e.stopPropagation()
sendAddToQueue(e.currentTarget.dataset.link)
},
openQualityModal(e) {
e.preventDefault()
openQualityModal(e.currentTarget.dataset.link)
},
moreInfo(url, e) {
if (e) e.preventDefault()
showTrackListSelective(url, true)
},
sortBy(key) {
if (key == this.sortKey) {
this.sortOrder = this.sortOrder == 'asc' ? 'desc' : 'asc'
} else {
this.sortKey = key
this.sortOrder = 'asc'
}
},
changeTab(tab) {
this.currentTab = tab
},
checkNewRelease(date) {
var g1 = new Date()
var g2 = new Date(date)
g2.setDate(g2.getDate() + 3)
g1.setHours(0, 0, 0, 0)
if (g1.getTime() <= g2.getTime()) {
return true
} else {
return false
}
}
},
computed: {
showTable() {
if (this.body) return _.orderBy(this.body[this.currentTab], this.sortKey, this.sortOrder)
else return []
}
}
})
export function resetArtistTab() {
ArtistTab.title = 'Loading...'
ArtistTab.image = ''
ArtistTab.type = ''
ArtistTab.currentTab = ''
ArtistTab.sortKey = 'release_date'
ArtistTab.sortOrder = 'desc'
ArtistTab.link = ''
ArtistTab.head = []
ArtistTab.body = null
}
socket.on('show_artist', data => {
ArtistTab.title = data.name
ArtistTab.image = data.picture_xl
ArtistTab.type = 'Artist'
ArtistTab.link = `https://www.deezer.com/artist/${data.id}`
ArtistTab.currentTab = Object.keys(data.releases)[0]
ArtistTab.sortKey = 'release_date'
ArtistTab.sortOrder = 'desc'
ArtistTab.head = [
{ title: 'Title', sortKey: 'title' },
{ title: 'Release Date', sortKey: 'release_date' },
{ title: '', width: '32px' }
]
if (_.isEmpty(data.releases)) {
ArtistTab.body = null
} else {
ArtistTab.body = data.releases
}
})

View File

@ -0,0 +1,110 @@
import { socket } from './socket.js'
export const MainSearch = new Vue({
data: {
names: {
TOP_RESULT: 'Top Result',
TRACK: 'Tracks',
ARTIST: 'Artists',
ALBUM: 'Albums',
PLAYLIST: 'Playlists'
},
results: {
query: '',
allTab: {
ORDER: [],
TOP_RESULT: [],
ALBUM: {},
ARTIST: {},
TRACK: {},
PLAYLIST: {}
},
trackTab: {
data: [],
next: 0,
total: 0,
loaded: false
},
albumTab: {
data: [],
next: 0,
total: 0,
loaded: false
},
artistTab: {
data: [],
next: 0,
total: 0,
loaded: false
},
playlistTab: {
data: [],
next: 0,
total: 0,
loaded: false
}
}
},
methods: {
changeSearchTab(section) {
if (section != 'TOP_RESULT') {
document.getElementById(`search_${section.toLowerCase()}_tab`).click()
}
},
addToQueue: function (e) {
e.stopPropagation()
sendAddToQueue(e.currentTarget.dataset.link)
},
openQualityModal: function (e) {
e.preventDefault()
openQualityModal(e.currentTarget.dataset.link)
},
numberWithDots(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.')
},
convertDuration(duration) {
//convert from seconds only to mm:ss format
let mm, ss
mm = Math.floor(duration / 60)
ss = duration - mm * 60
//add leading zero if ss < 0
if (ss < 10) {
ss = '0' + ss
}
return mm + ':' + ss
}
}
}).$mount('#search_tab')
socket.on('mainSearch', result => {
let resetObj = { data: [], next: 0, total: 0, loaded: false }
MainSearch.results.allTab = result
MainSearch.results.query = result.QUERY
MainSearch.results.trackTab = { ...resetObj }
MainSearch.results.albumTab = { ...resetObj }
MainSearch.results.artistTab = { ...resetObj }
MainSearch.results.playlistTab = { ...resetObj }
document.getElementById('search_all_tab').click()
document.getElementById('search_tab_content').style.display = 'block'
document.getElementById('main_search_tablink').click()
})
socket.on('search', result => {
let next = 0
if (result.next) {
next = parseInt(result.next.match(/index=(\d*)/)[1])
} else {
next = result.total
}
if (MainSearch.results[result.type + 'Tab'].total != result.total) {
MainSearch.results[result.type + 'Tab'].total = result.total
}
if (MainSearch.results[result.type + 'Tab'].next != next) {
MainSearch.results[result.type + 'Tab'].next = next
MainSearch.results[result.type + 'Tab'].data = MainSearch.results[result.type + 'Tab'].data.concat(result.data)
}
MainSearch.results[result.type + 'Tab'].loaded = true
})

View File

@ -1,74 +0,0 @@
export const MainSearch = new Vue({
el: '#search_tab',
data: {
names: {
TOP_RESULT: 'Top Result',
TRACK: 'Tracks',
ARTIST: 'Artists',
ALBUM: 'Albums',
PLAYLIST: 'Playlists'
},
results: {
query: '',
allTab: {
ORDER: [],
TOP_RESULT: [],
ALBUM: {},
ARTIST: {},
TRACK: {},
PLAYLIST: {}
},
trackTab: {
data: [],
next: 0,
total: 0,
loaded: false
},
albumTab: {
data: [],
next: 0,
total: 0,
loaded: false
},
artistTab: {
data: [],
next: 0,
total: 0,
loaded: false
},
playlistTab: {
data: [],
next: 0,
total: 0,
loaded: false
}
}
},
methods: {
changeSearchTab(section) {
if (section != 'TOP_RESULT') clickElement('search_' + section.toLowerCase() + '_tab')
},
addToQueue: function (e) {
e.stopPropagation()
sendAddToQueue(e.currentTarget.dataset.link)
},
openQualityModal: function (e) {
e.preventDefault()
openQualityModal(e.currentTarget.dataset.link)
},
numberWithDots(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.')
},
convertDuration(duration) {
//convert from seconds only to mm:ss format
let mm, ss
mm = Math.floor(duration / 60)
ss = duration - mm * 60
//add leading zero if ss < 0
if (ss < 10) {
ss = '0' + ss
}
return mm + ':' + ss
}
}
})

View File

@ -0,0 +1,61 @@
import { toast } from './toasts.js'
import { socket } from './socket.js'
// Globals
window.lastSettings = {}
window.lastCredentials = {}
export const SettingsTab = new Vue({
data() {
return {
settings: { tags: {} },
spotifyFeatures: {}
}
},
methods: {
copyARLtoClipboard() {
let copyText = this.$refs.loginInput
copyText.setAttribute('type', 'text')
copyText.select()
copyText.setSelectionRange(0, 99999)
document.execCommand('copy')
copyText.setAttribute('type', 'password')
toast('ARL copied to clipboard', 'assignment')
},
saveSettings() {
console.log(socket)
lastSettings = { ...SettingsTab.settings }
lastCredentials = { ...SettingsTab.spotifyFeatures }
socket.emit('saveSettings', lastSettings, lastCredentials)
},
login() {
let arl = this.$refs.loginInput.value
if (arl != '' && arl != localStorage.getItem('arl')) {
socket.emit('login', arl, true)
}
},
logout() {
socket.emit('logout')
}
}
}).$mount('#settings_tab')
socket.on('init_settings', function (settings, credentials) {
loadSettings(settings, credentials)
toast('Settings loaded!', 'settings')
})
socket.on('updateSettings', function (settings, credentials) {
loadSettings(settings, credentials)
toast('Settings updated!', 'settings')
})
function loadSettings(settings, spotifyCredentials) {
lastSettings = { ...settings }
lastCredentials = { ...spotifyCredentials }
SettingsTab.settings = settings
SettingsTab.spotifyFeatures = spotifyCredentials
}

View File

@ -0,0 +1 @@
export const socket = io.connect(window.location.href)

View File

@ -0,0 +1,36 @@
let toastsWithId = {}
export const toast = function (msg, icon = null, dismiss = true, id = null) {
if (toastsWithId[id]) {
let toastObj = toastsWithId[id]
let toastDOM = $(`div.toastify[toast_id=${id}]`)
if (msg) {
toastDOM.find('.toast-message').html(msg)
}
if (icon) {
if (icon == 'loading') icon = `<div class="circle-loader"></div>`
else icon = `<i class="material-icons">${icon}</i>`
toastDOM.find('.toast-icon').html(icon)
}
if (dismiss !== null && dismiss) {
setTimeout(function () {
toastObj.hideToast()
delete toastsWithId[id]
}, 3000)
}
} else {
if (icon == null) icon = ''
else if (icon == 'loading') icon = `<div class="circle-loader"></div>`
else icon = `<i class="material-icons">${icon}</i>`
let toastObj = Toastify({
text: `<span class="toast-icon">${icon}</span><span class="toast-message">${msg}</toast>`,
duration: dismiss ? 3000 : 0,
gravity: 'bottom',
position: 'left'
}).showToast()
if (id) {
toastsWithId[id] = toastObj
$(toastObj.toastElement).attr('toast_id', id)
}
}
}

View File

@ -0,0 +1,111 @@
import { socket } from './socket.js'
export const TracklistTab = new Vue({
el: '#tracklist_tab',
data: {
title: '',
metadata: '',
release_date: '',
label: '',
explicit: false,
image: '',
type: '',
link: '',
head: null,
body: []
},
methods: {
addToQueue: function (e) {
e.stopPropagation()
sendAddToQueue(e.currentTarget.dataset.link)
},
openQualityModal: function (e) {
e.preventDefault()
openQualityModal(e.currentTarget.dataset.link)
},
toggleAll: function (e) {
this.body.forEach(item => {
if (item.type == 'track') {
item.selected = e.currentTarget.checked
}
})
},
selectedLinks: function () {
var selected = []
if (this.body) {
this.body.forEach(item => {
if (item.type == 'track' && item.selected) selected.push(item.link)
})
}
return selected.join(';')
},
convertDuration(duration) {
//convert from seconds only to mm:ss format
let mm, ss
mm = Math.floor(duration / 60)
ss = duration - mm * 60
//add leading zero if ss < 0
if (ss < 10) {
ss = '0' + ss
}
return mm + ':' + ss
}
}
})
export function resetTracklistTab() {
TracklistTab.title = 'Loading...'
TracklistTab.image = ''
TracklistTab.metadata = ''
TracklistTab.label = ''
TracklistTab.release_date = ''
TracklistTab.explicit = false
TracklistTab.type = ''
TracklistTab.head = []
TracklistTab.body = []
}
socket.on('show_album', data => {
TracklistTab.type = 'Album'
TracklistTab.link = `https://www.deezer.com/album/${data.id}`
TracklistTab.title = data.title
TracklistTab.explicit = data.explicit_lyrics
TracklistTab.label = data.label
TracklistTab.metadata = `${data.artist.name}${data.tracks.length} songs`
TracklistTab.release_date = data.release_date.substring(0, 10)
TracklistTab.image = data.cover_xl
TracklistTab.head = [
{ title: '<i class="material-icons">music_note</i>', width: '24px' },
{ title: '#' },
{ title: 'Song' },
{ title: 'Artist' },
{ title: '<i class="material-icons">timer</i>', width: '40px' }
]
if (_.isEmpty(data.tracks)) {
TracklistTab.body = null
} else {
TracklistTab.body = data.tracks
}
})
socket.on('show_playlist', data => {
TracklistTab.type = 'Playlist'
TracklistTab.link = `https://www.deezer.com/playlist/${data.id}`
TracklistTab.title = data.title
TracklistTab.image = data.picture_xl
TracklistTab.release_date = data.creation_date.substring(0, 10)
TracklistTab.metadata = `by ${data.creator.name}${data.tracks.length} songs`
TracklistTab.head = [
{ title: '<i class="material-icons">music_note</i>', width: '24px' },
{ title: '#' },
{ title: 'Song' },
{ title: 'Artist' },
{ title: 'Album' },
{ title: '<i class="material-icons">timer</i>', width: '40px' }
]
if (_.isEmpty(data.tracks)) {
TracklistTab.body = null
} else {
TracklistTab.body = data.tracks
}
})

View File

@ -1,478 +0,0 @@
(function umd(root,factory){
if(typeof module==='object' && typeof exports === 'object' )
module.exports=factory()
else if(typeof define==='function' && define.amd)
define([],factory)
else
root.httpVueLoader=factory()
})(this,function factory() {
'use strict';
var scopeIndex = 0;
StyleContext.prototype = {
withBase: function(callback) {
var tmpBaseElt;
if ( this.component.baseURI ) {
// firefox and chrome need the <base> to be set while inserting or modifying <style> in a document.
tmpBaseElt = document.createElement('base');
tmpBaseElt.href = this.component.baseURI;
var headElt = this.component.getHead();
headElt.insertBefore(tmpBaseElt, headElt.firstChild);
}
callback.call(this);
if ( tmpBaseElt )
this.component.getHead().removeChild(tmpBaseElt);
},
scopeStyles: function(styleElt, scopeName) {
function process() {
var sheet = styleElt.sheet;
var rules = sheet.cssRules;
for ( var i = 0; i < rules.length; ++i ) {
var rule = rules[i];
if ( rule.type !== 1 )
continue;
var scopedSelectors = [];
rule.selectorText.split(/\s*,\s*/).forEach(function(sel) {
scopedSelectors.push(scopeName+' '+sel);
var segments = sel.match(/([^ :]+)(.+)?/);
scopedSelectors.push(segments[1] + scopeName + (segments[2]||''));
});
var scopedRule = scopedSelectors.join(',') + rule.cssText.substr(rule.selectorText.length);
sheet.deleteRule(i);
sheet.insertRule(scopedRule, i);
}
}
try {
// firefox may fail sheet.cssRules with InvalidAccessError
process();
} catch (ex) {
if ( ex instanceof DOMException && ex.code === DOMException.INVALID_ACCESS_ERR ) {
styleElt.sheet.disabled = true;
styleElt.addEventListener('load', function onStyleLoaded() {
styleElt.removeEventListener('load', onStyleLoaded);
// firefox need this timeout otherwise we have to use document.importNode(style, true)
setTimeout(function() {
process();
styleElt.sheet.disabled = false;
});
});
return;
}
throw ex;
}
},
compile: function() {
var hasTemplate = this.template !== null;
var scoped = this.elt.hasAttribute('scoped');
if ( scoped ) {
// no template, no scopable style needed
if ( !hasTemplate )
return;
// firefox does not tolerate this attribute
this.elt.removeAttribute('scoped');
}
this.withBase(function() {
this.component.getHead().appendChild(this.elt);
});
if ( scoped )
this.scopeStyles(this.elt, '['+this.component.getScopeId()+']');
return Promise.resolve();
},
getContent: function() {
return this.elt.textContent;
},
setContent: function(content) {
this.withBase(function() {
this.elt.textContent = content;
});
}
};
function StyleContext(component, elt) {
this.component = component;
this.elt = elt;
}
ScriptContext.prototype = {
getContent: function() {
return this.elt.textContent;
},
setContent: function(content) {
this.elt.textContent = content;
},
compile: function(module) {
var childModuleRequire = function(childURL) {
return httpVueLoader.require(resolveURL(this.component.baseURI, childURL));
}.bind(this);
var childLoader = function(childURL, childName) {
return httpVueLoader(resolveURL(this.component.baseURI, childURL), childName);
}.bind(this);
try {
Function('exports', 'require', 'httpVueLoader', 'module', this.getContent()).call(this.module.exports, this.module.exports, childModuleRequire, childLoader, this.module);
} catch(ex) {
if ( !('lineNumber' in ex) ) {
return Promise.reject(ex);
}
var vueFileData = responseText.replace(/\r?\n/g, '\n');
var lineNumber = vueFileData.substr(0, vueFileData.indexOf(script)).split('\n').length + ex.lineNumber - 1;
throw new (ex.constructor)(ex.message, url, lineNumber);
}
return Promise.resolve(this.module.exports)
.then(httpVueLoader.scriptExportsHandler.bind(this))
.then(function(exports) {
this.module.exports = exports;
}.bind(this));
}
};
function ScriptContext(component, elt) {
this.component = component;
this.elt = elt;
this.module = { exports:{} };
}
TemplateContext.prototype = {
getContent: function() {
return this.elt.innerHTML;
},
setContent: function(content) {
this.elt.innerHTML = content;
},
getRootElt: function() {
var tplElt = this.elt.content || this.elt;
if ( 'firstElementChild' in tplElt )
return tplElt.firstElementChild;
for ( tplElt = tplElt.firstChild; tplElt !== null; tplElt = tplElt.nextSibling )
if ( tplElt.nodeType === Node.ELEMENT_NODE )
return tplElt;
return null;
},
compile: function() {
return Promise.resolve();
}
};
function TemplateContext(component, elt) {
this.component = component;
this.elt = elt;
}
Component.prototype = {
getHead: function() {
return document.head || document.getElementsByTagName('head')[0];
},
getScopeId: function() {
if ( this._scopeId === '' ) {
this._scopeId = 'data-s-' + (scopeIndex++).toString(36);
this.template.getRootElt().setAttribute(this._scopeId, '');
}
return this._scopeId;
},
load: function(componentURL) {
return httpVueLoader.httpRequest(componentURL)
.then(function(responseText) {
this.baseURI = componentURL.substr(0, componentURL.lastIndexOf('/')+1);
var doc = document.implementation.createHTMLDocument('');
// IE requires the <base> to come with <style>
doc.body.innerHTML = (this.baseURI ? '<base href="'+this.baseURI+'">' : '') + responseText;
for ( var it = doc.body.firstChild; it; it = it.nextSibling ) {
switch ( it.nodeName ) {
case 'TEMPLATE':
this.template = new TemplateContext(this, it);
break;
case 'SCRIPT':
this.script = new ScriptContext(this, it);
break;
case 'STYLE':
this.styles.push(new StyleContext(this, it));
break;
}
}
return this;
}.bind(this));
},
_normalizeSection: function(eltCx) {
var p;
if ( eltCx === null || !eltCx.elt.hasAttribute('src') ) {
p = Promise.resolve(null);
} else {
p = httpVueLoader.httpRequest(eltCx.elt.getAttribute('src'))
.then(function(content) {
eltCx.elt.removeAttribute('src');
return content;
});
}
return p
.then(function(content) {
if ( eltCx !== null && eltCx.elt.hasAttribute('lang') ) {
var lang = eltCx.elt.getAttribute('lang');
eltCx.elt.removeAttribute('lang');
return httpVueLoader.langProcessor[lang.toLowerCase()].call(this, content === null ? eltCx.getContent() : content);
}
return content;
}.bind(this))
.then(function(content) {
if ( content !== null )
eltCx.setContent(content);
});
},
normalize: function() {
return Promise.all(Array.prototype.concat(
this._normalizeSection(this.template),
this._normalizeSection(this.script),
this.styles.map(this._normalizeSection)
))
.then(function() {
return this;
}.bind(this));
},
compile: function() {
return Promise.all(Array.prototype.concat(
this.template && this.template.compile(),
this.script && this.script.compile(),
this.styles.map(function(style) { return style.compile(); })
))
.then(function() {
return this;
}.bind(this));
}
};
function Component(name) {
this.name = name;
this.template = null;
this.script = null;
this.styles = [];
this._scopeId = '';
}
function identity(value) {
return value;
}
function parseComponentURL(url) {
var comp = url.match(/(.*?)([^/]+?)\/?(\.vue)?(\?.*|#.*|$)/);
return {
name: comp[2],
url: comp[1] + comp[2] + (comp[3] === undefined ? '/index.vue' : comp[3]) + comp[4]
};
}
function resolveURL(baseURL, url) {
if (url.substr(0, 2) === './' || url.substr(0, 3) === '../') {
return baseURL + url;
}
return url;
}
httpVueLoader.load = function(url, name) {
return function() {
return new Component(name).load(url)
.then(function(component) {
return component.normalize();
})
.then(function(component) {
return component.compile();
})
.then(function(component) {
var exports = component.script !== null ? component.script.module.exports : {};
if ( component.template !== null )
exports.template = component.template.getContent();
if ( exports.name === undefined )
if ( component.name !== undefined )
exports.name = component.name;
exports._baseURI = component.baseURI;
return exports;
});
};
};
httpVueLoader.register = function(Vue, url) {
var comp = parseComponentURL(url);
Vue.component(comp.name, httpVueLoader.load(comp.url));
};
httpVueLoader.install = function(Vue) {
Vue.mixin({
beforeCreate: function () {
var components = this.$options.components;
for ( var componentName in components ) {
if ( typeof(components[componentName]) === 'string' && components[componentName].substr(0, 4) === 'url:' ) {
var comp = parseComponentURL(components[componentName].substr(4));
var componentURL = ('_baseURI' in this.$options) ? resolveURL(this.$options._baseURI, comp.url) : comp.url;
if ( isNaN(componentName) )
components[componentName] = httpVueLoader.load(componentURL, componentName);
else
components[componentName] = Vue.component(comp.name, httpVueLoader.load(componentURL, comp.name));
}
}
}
});
};
httpVueLoader.require = function(moduleName) {
return window[moduleName];
};
httpVueLoader.httpRequest = function(url) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'text';
xhr.onreadystatechange = function() {
if ( xhr.readyState === 4 ) {
if ( xhr.status >= 200 && xhr.status < 300 )
resolve(xhr.responseText);
else
reject(xhr.status);
}
};
xhr.send(null);
});
};
httpVueLoader.langProcessor = {
html: identity,
js: identity,
css: identity
};
httpVueLoader.scriptExportsHandler = identity;
function httpVueLoader(url, name) {
var comp = parseComponentURL(url);
return httpVueLoader.load(comp.url, name);
}
return httpVueLoader;
});