changed directory structure of js and Vue files; added plugin for translation (can try it in settings)
This commit is contained in:
@@ -1,32 +0,0 @@
|
||||
<template>
|
||||
<div style="height: inherit;">
|
||||
<BaseLoadingPlaceholder id="start_app_placeholder" text="Connecting to the server..." />
|
||||
|
||||
<TheSidebar />
|
||||
<TheMainContent />
|
||||
|
||||
<TheTrackPreview />
|
||||
<TheQualityModal />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TheSidebar from '@components/TheSidebar.vue'
|
||||
import TheMainContent from '@components/TheMainContent.vue'
|
||||
import TheTrackPreview from '@components/TheTrackPreview.vue'
|
||||
import TheQualityModal from '@components/TheQualityModal.vue'
|
||||
import BaseLoadingPlaceholder from '@components/BaseLoadingPlaceholder.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TheSidebar,
|
||||
TheMainContent,
|
||||
TheTrackPreview,
|
||||
TheQualityModal,
|
||||
BaseLoadingPlaceholder
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -1,4 +0,0 @@
|
||||
// https://alligator.io/vuejs/global-event-bus/
|
||||
import Vue from 'vue'
|
||||
|
||||
export default new Vue()
|
||||
150
src/js/app.js
150
src/js/app.js
@@ -1,150 +0,0 @@
|
||||
import Vue from 'vue'
|
||||
|
||||
// Object is needed for vue proxy (what does this mean?)
|
||||
window.vol = {
|
||||
preview_max_volume: 100
|
||||
}
|
||||
|
||||
import App from '@/js/App.vue'
|
||||
|
||||
import $ from 'jquery'
|
||||
import { socket } from '@/js/socket.js'
|
||||
import { toast } from '@/js/toasts.js'
|
||||
import { init as initTabs } from '@/js/tabs.js'
|
||||
|
||||
/* ===== App initialization ===== */
|
||||
|
||||
function startApp() {
|
||||
mountApp()
|
||||
initTabs()
|
||||
}
|
||||
|
||||
function mountApp() {
|
||||
// TODO Remove the App instance from the window when deemix will be a complete Vue App
|
||||
window.App = new Vue({
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
||||
}
|
||||
|
||||
function initClient() {
|
||||
window.clientMode = true
|
||||
document.querySelector(`#open_downloads_folder`).classList.remove('hide')
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', startApp)
|
||||
window.addEventListener('pywebviewready', initClient)
|
||||
|
||||
/* ===== Socketio listeners ===== */
|
||||
|
||||
// Debug messages for socketio
|
||||
socket.on('message', function(msg) {
|
||||
console.log(msg)
|
||||
})
|
||||
|
||||
socket.on('logging_in', function() {
|
||||
toast('Logging in', 'loading', false, 'login-toast')
|
||||
})
|
||||
|
||||
socket.on('init_autologin', function() {
|
||||
let arl = localStorage.getItem('arl')
|
||||
let accountNum = localStorage.getItem('accountNum')
|
||||
if (arl) {
|
||||
arl = arl.trim()
|
||||
if (accountNum != 0) {
|
||||
socket.emit('login', arl, true, accountNum)
|
||||
} else {
|
||||
socket.emit('login', arl)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
socket.on('logged_in', function(data) {
|
||||
switch (data.status) {
|
||||
case 1:
|
||||
case 3:
|
||||
toast('Logged in', 'done', true, 'login-toast')
|
||||
if (data.arl) {
|
||||
localStorage.setItem('arl', data.arl)
|
||||
$('#login_input_arl').val(data.arl)
|
||||
}
|
||||
$('#open_login_prompt').hide()
|
||||
if (data.user) {
|
||||
$('#settings_username').text(data.user.name)
|
||||
$('#settings_picture').attr(
|
||||
'src',
|
||||
`https://e-cdns-images.dzcdn.net/images/user/${data.user.picture}/125x125-000000-80-0-0.jpg`
|
||||
)
|
||||
// $('#logged_in_info').show()
|
||||
document.getElementById('logged_in_info').classList.remove('hide')
|
||||
}
|
||||
document.getElementById('home_not_logged_in').classList.add('hide')
|
||||
break
|
||||
case 2:
|
||||
toast('Already logged in', 'done', true, 'login-toast')
|
||||
if (data.user) {
|
||||
$('#settings_username').text(data.user.name)
|
||||
$('#settings_picture').attr(
|
||||
'src',
|
||||
`https://e-cdns-images.dzcdn.net/images/user/${data.user.picture}/125x125-000000-80-0-0.jpg`
|
||||
)
|
||||
// $('#logged_in_info').show()
|
||||
document.getElementById('logged_in_info').classList.remove('hide')
|
||||
}
|
||||
document.getElementById('home_not_logged_in').classList.add('hide')
|
||||
break
|
||||
case 0:
|
||||
toast("Couldn't log in", 'close', true, 'login-toast')
|
||||
localStorage.removeItem('arl')
|
||||
$('#login_input_arl').val('')
|
||||
$('#open_login_prompt').show()
|
||||
document.getElementById('logged_in_info').classList.add('hide')
|
||||
// $('#logged_in_info').hide()
|
||||
$('#settings_username').text('Not Logged')
|
||||
$('#settings_picture').attr('src', `https://e-cdns-images.dzcdn.net/images/user/125x125-000000-80-0-0.jpg`)
|
||||
document.getElementById('home_not_logged_in').classList.remove('hide')
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
socket.on('logged_out', function() {
|
||||
toast('Logged out', 'done', true, 'login-toast')
|
||||
localStorage.removeItem('arl')
|
||||
$('#login_input_arl').val('')
|
||||
$('#open_login_prompt').show()
|
||||
document.getElementById('logged_in_info').classList.add('hide')
|
||||
$('#settings_username').text('Not Logged')
|
||||
$('#settings_picture').attr('src', `https://e-cdns-images.dzcdn.net/images/user/125x125-000000-80-0-0.jpg`)
|
||||
document.getElementById('home_not_logged_in').classList.remove('hide')
|
||||
})
|
||||
|
||||
socket.on('cancellingCurrentItem', function(uuid) {
|
||||
toast('Cancelling current item.', 'loading', false, 'cancelling_' + uuid)
|
||||
})
|
||||
|
||||
socket.on('currentItemCancelled', function(uuid) {
|
||||
toast('Current item cancelled.', 'done', true, 'cancelling_' + uuid)
|
||||
})
|
||||
|
||||
socket.on('startAddingArtist', function(data) {
|
||||
toast(`Adding ${data.name} albums to queue`, 'loading', false, 'artist_' + data.id)
|
||||
})
|
||||
|
||||
socket.on('finishAddingArtist', function(data) {
|
||||
toast(`Added ${data.name} albums to queue`, 'done', true, 'artist_' + data.id)
|
||||
})
|
||||
|
||||
socket.on('startConvertingSpotifyPlaylist', function(id) {
|
||||
toast('Converting spotify tracks to deezer tracks', 'loading', false, 'spotifyplaylist_' + id)
|
||||
})
|
||||
|
||||
socket.on('finishConvertingSpotifyPlaylist', function(id) {
|
||||
toast('Spotify playlist converted', 'done', true, 'spotifyplaylist_' + id)
|
||||
})
|
||||
|
||||
socket.on('errorMessage', function(error) {
|
||||
toast(error, 'error')
|
||||
})
|
||||
|
||||
socket.on('alreadyInQueue', function(data) {
|
||||
toast(`${data.title} is already in queue!`, 'playlist_add_check')
|
||||
})
|
||||
@@ -1,196 +0,0 @@
|
||||
<template>
|
||||
<div id="artist_tab" class="main_tabcontent fixed_footer image_header">
|
||||
<header
|
||||
class="inline-flex"
|
||||
:style="{
|
||||
'background-image':
|
||||
'linear-gradient(to bottom, transparent 0%, var(--main-background) 100%), url(\'' + image + '\')'
|
||||
}"
|
||||
>
|
||||
<h1>{{ title }}</h1>
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@contextmenu.prevent="openQualityModal"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="link"
|
||||
class="fab right"
|
||||
>
|
||||
<i class="material-icons">get_app</i>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="tab">
|
||||
<template v-for="(item, name, index) in body">
|
||||
<button
|
||||
:class="'selective' + (name == currentTab ? ' active' : '')"
|
||||
:href="'#artist_' + name"
|
||||
@click="changeTab(name)"
|
||||
>
|
||||
{{ name }}
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
v-for="data in head"
|
||||
@click="data.sortKey ? sortBy(data.sortKey) : null"
|
||||
:style="{ width: data.width ? data.width : 'auto' }"
|
||||
:class="{
|
||||
'sort-asc': data.sortKey == sortKey && sortOrder == 'asc',
|
||||
'sort-desc': data.sortKey == sortKey && sortOrder == 'desc',
|
||||
sortable: data.sortKey,
|
||||
clickable: data.sortKey
|
||||
}"
|
||||
>
|
||||
{{ data.title }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="release in showTable">
|
||||
<td class="inline-flex clickable" @click="albumView" :data-id="release.id">
|
||||
<img
|
||||
class="rounded coverart"
|
||||
:src="release.cover_small"
|
||||
style="margin-right: 16px; width: 56px; height: 56px;"
|
||||
/>
|
||||
<i v-if="release.explicit_lyrics" class="material-icons explicit_icon">
|
||||
explicit
|
||||
</i>
|
||||
{{ release.title }}
|
||||
<i v-if="checkNewRelease(release.release_date)" class="material-icons" style="color:#FF7300;">
|
||||
fiber_new
|
||||
</i>
|
||||
</td>
|
||||
<td>{{ release.release_date }}</td>
|
||||
<td
|
||||
@click.stop="addToQueue"
|
||||
@contextmenu.prevent="openQualityModal"
|
||||
:data-link="release.link"
|
||||
class="clickable"
|
||||
>
|
||||
<i class="material-icons">
|
||||
file_download
|
||||
</i>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<footer>
|
||||
<button class="back-button">Back</button>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isEmpty, orderBy } from 'lodash-es'
|
||||
import { socket } from '@/js/socket.js'
|
||||
import Downloads from '@/js/downloads.js'
|
||||
import { showView } from '@/js/tabs.js'
|
||||
import EventBus from '@/js/EventBus'
|
||||
|
||||
export default {
|
||||
name: 'artist-tab',
|
||||
data() {
|
||||
return {
|
||||
currentTab: '',
|
||||
sortKey: 'release_date',
|
||||
sortOrder: 'desc',
|
||||
title: '',
|
||||
image: '',
|
||||
type: '',
|
||||
link: '',
|
||||
head: null,
|
||||
body: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
albumView: showView.bind(null, 'album'),
|
||||
reset() {
|
||||
this.title = 'Loading...'
|
||||
this.image = ''
|
||||
this.type = ''
|
||||
this.currentTab = ''
|
||||
this.sortKey = 'release_date'
|
||||
this.sortOrder = 'desc'
|
||||
this.link = ''
|
||||
this.head = []
|
||||
this.body = null
|
||||
},
|
||||
addToQueue(e) {
|
||||
e.stopPropagation()
|
||||
Downloads.sendAddToQueue(e.currentTarget.dataset.link)
|
||||
},
|
||||
openQualityModal(e) {
|
||||
this.$root.$emit('QualityModal:open', e.currentTarget.dataset.link)
|
||||
},
|
||||
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
|
||||
},
|
||||
getCurrentTab() {
|
||||
return this.currentTab
|
||||
},
|
||||
updateSelected() {
|
||||
window.currentStack.selected = this.currentTab
|
||||
},
|
||||
checkNewRelease(date) {
|
||||
let g1 = new Date()
|
||||
let g2 = new Date(date)
|
||||
g2.setDate(g2.getDate() + 3)
|
||||
g1.setHours(0, 0, 0, 0)
|
||||
|
||||
return g1.getTime() <= g2.getTime()
|
||||
},
|
||||
showArtist(data) {
|
||||
const { name, picture_xl, id, releases } = data
|
||||
|
||||
this.title = name
|
||||
this.image = picture_xl
|
||||
this.type = 'Artist'
|
||||
this.link = `https://www.deezer.com/artist/${id}`
|
||||
if (this.currentTab === '') this.currentTab = Object.keys(releases)[0]
|
||||
this.sortKey = 'release_date'
|
||||
this.sortOrder = 'desc'
|
||||
this.head = [
|
||||
{ title: 'Title', sortKey: 'title' },
|
||||
{ title: 'Release Date', sortKey: 'release_date' },
|
||||
{ title: '', width: '32px' }
|
||||
]
|
||||
if (isEmpty(releases)) {
|
||||
this.body = null
|
||||
} else {
|
||||
this.body = releases
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showTable() {
|
||||
if (this.body) return orderBy(this.body[this.currentTab], this.sortKey, this.sortOrder)
|
||||
else return []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
socket.on('show_artist', this.showArtist)
|
||||
|
||||
EventBus.$on('artistTab:reset', this.reset)
|
||||
EventBus.$on('artistTab:updateSelected', this.updateSelected)
|
||||
EventBus.$on('artistTab:changeTab', this.changeTab)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -1,26 +0,0 @@
|
||||
<template functional>
|
||||
<div :id="props.id" class="loading_placeholder">
|
||||
<span class="loading_placeholder__text">{{ props.text }}</span>
|
||||
<div class="lds-ring">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
text: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'Loading...'
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
required: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,82 +0,0 @@
|
||||
<template functional>
|
||||
<div id="about_tab" class="main_tabcontent">
|
||||
<h2 class="page_heading">About</h2>
|
||||
<p>
|
||||
This app uses the <a href="https://deemix.app" target="_blank">deemix</a> library, you can use this
|
||||
library to make your own UI for deemix.</br>
|
||||
Here's the <a href="https://notabug.org/RemixDev/deemix" target="_blank">official repo</a> for the
|
||||
library.
|
||||
</p>
|
||||
<p>
|
||||
Thanks to rtonno, uhwot and lollilol for helping me with this project.<br>
|
||||
Also thanks to BasCurtiz and <a href="http://linktr.ee/scarvimane" target="_blank">scarvimane</a> for
|
||||
making the icon.
|
||||
</p>
|
||||
<p>
|
||||
Stay up to date with the updates by following the <a href="https://t.me/RemixDevNews"
|
||||
target="_blank">news channel</a> on Telegram.
|
||||
</p>
|
||||
<br />
|
||||
<h1>Bug Reports</h1>
|
||||
<p>
|
||||
If you have questions or problems with the app, search for a solution in the
|
||||
<a href="https://www.reddit.com/r/deemix" target="_blank">subreddit</a> first and then, if you don't
|
||||
find anything
|
||||
you can make a post with your issue on the subreddit.
|
||||
</p>
|
||||
<p>
|
||||
Before reporting a bug make sure you're running the latest version of the app and that the thing you
|
||||
want
|
||||
to report is actually a bug and not something that's wrong only on your end.<br />
|
||||
Make sure the bug is reproducible on another machines and also <b>DO NOT</b> report a bug if it's been
|
||||
already reported.
|
||||
</p>
|
||||
<p>
|
||||
<b>DO NOT</b> open issues for asking questions, there is a subreddit for that.
|
||||
</p>
|
||||
<br />
|
||||
<h2>Donations</h2>
|
||||
<h3>You want to contribute to this project? You can do that <b>in different ways!</b></h3>
|
||||
<p>
|
||||
If you're fluent in python you could try to make a new UI for the app using the base library, or fix
|
||||
bugs in the library with a pull request on the <a href="https://notabug.org/RemixDev/deemix"
|
||||
target="_blank">repo</a>.<br>
|
||||
I accept features as well, but no complex things, as they can be implementend directly in the app and
|
||||
not the library.</p>
|
||||
<p>
|
||||
If you're fluent in another programming language you could try to port deemix into other programming
|
||||
languages!<br>
|
||||
You need help understanding the code? Just hit RemixDev up on Telegram or Reddit.</p>
|
||||
<p>If you know JavaScript, HTML or CSS you could contribute to the <a
|
||||
href="https://notabug.org/RemixDev/deemix-webui" target="_blank">webui</a>.</p>
|
||||
<p>
|
||||
If you find some bugs you can report them in the repo, just make sure your bug isn't something that
|
||||
only
|
||||
affects you and it can be reproducible by other users as well.<br>
|
||||
Duplicate bug reports will be closed, so keep an eye out on that.</p>
|
||||
<hr>
|
||||
<h3>You want to contribute monetarily? You could make a donation!</h3>
|
||||
<p>
|
||||
If you can donate you can do that with this links.<br>
|
||||
You shoud remember that <b>this is a free project</b> and <b>you should support the artists you
|
||||
love</b>
|
||||
before supporting the developers.<br>
|
||||
Don't feel obligated to donate, I appreciate you anyway!</p>
|
||||
<p>
|
||||
<b>PayPal:</b> <a href="https://paypal.me/RemixDev" target="_blank">PayPal.me/RemixDev</a><br>
|
||||
<b>Bitcoin:</b> 1sdNymSJrMBWyHM4u2m9uco5nv6uV4Qs1<br>
|
||||
<b>Ethereum:</b> 0x1d2aa67e671485CD4062289772B662e0A6Ff976c
|
||||
</p>
|
||||
<br />
|
||||
<h2>License</h2>
|
||||
<p>
|
||||
<a rel="license" href="https://www.gnu.org/licenses/gpl-3.0.en.html" target="_blank">
|
||||
<img alt="GNU General Public License" style="border-width:0"
|
||||
src="https://www.gnu.org/graphics/gplv3-127x51.png" />
|
||||
</a><br />
|
||||
This work is licensed under a <a rel="license" href="https://www.gnu.org/licenses/gpl-3.0.en.html"
|
||||
target="_blank">GNU General Public License 3.0</a>.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
<template>
|
||||
<div id="charts_tab" class="main_tabcontent">
|
||||
<h2 class="page_heading">Charts</h2>
|
||||
<div v-if="country === ''" id="charts_selection">
|
||||
<div class="release_grid charts_grid">
|
||||
<!-- Ugly af -->
|
||||
<template v-for="release in countries">
|
||||
<div
|
||||
role="button"
|
||||
:aria-label="release.title"
|
||||
v-if="release.title === 'Worldwide'"
|
||||
class="release clickable"
|
||||
@click="getTrackList"
|
||||
:data-title="release.title"
|
||||
:data-id="release.id"
|
||||
:key="release.id"
|
||||
>
|
||||
<img class="rounded coverart" :src="release.picture_medium" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-for="release in countries">
|
||||
<div
|
||||
role="button"
|
||||
:aria-label="release.title"
|
||||
v-if="release.title !== 'Worldwide'"
|
||||
class="release clickable"
|
||||
@click="getTrackList"
|
||||
:data-title="release.title"
|
||||
:data-id="release.id"
|
||||
:key="release.id"
|
||||
>
|
||||
<img class="rounded coverart" :src="release.picture_medium" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else id="charts_table">
|
||||
<button @click="changeCountry">Change Country</button>
|
||||
<button
|
||||
@contextmenu.prevent="openQualityModal"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="'https://www.deezer.com/playlist/' + id"
|
||||
>
|
||||
Download Chart
|
||||
</button>
|
||||
<table class="table table--charts">
|
||||
<tbody>
|
||||
<tr v-for="track in chart" class="track_row">
|
||||
<td class="top-tracks-position" :class="{ first: track.position === 1 }">
|
||||
{{ track.position }}
|
||||
</td>
|
||||
<td class="table__icon table__icon--big">
|
||||
<a
|
||||
href="#"
|
||||
@click="playPausePreview"
|
||||
class="rounded"
|
||||
:class="{ 'single-cover': track.preview }"
|
||||
:data-preview="track.preview"
|
||||
>
|
||||
<i
|
||||
@mouseenter="previewMouseEnter"
|
||||
@mouseleave="previewMouseLeave"
|
||||
v-if="track.preview"
|
||||
class="material-icons preview_controls"
|
||||
>
|
||||
play_arrow
|
||||
</i>
|
||||
<img class="rounded coverart" :src="track.album.cover_small" />
|
||||
</a>
|
||||
</td>
|
||||
<td class="table__cell--large breakline">
|
||||
{{
|
||||
track.title +
|
||||
(track.title_version && track.title.indexOf(track.title_version) == -1
|
||||
? ' ' + track.title_version
|
||||
: '')
|
||||
}}
|
||||
</td>
|
||||
<td
|
||||
class="table__cell--medium table__cell--center breakline clickable"
|
||||
@click="artistView"
|
||||
:data-id="track.artist.id"
|
||||
>
|
||||
{{ track.artist.name }}
|
||||
</td>
|
||||
<td
|
||||
class="table__cell--medium table__cell--center breakline clickable"
|
||||
@click="albumView"
|
||||
:data-id="track.album.id"
|
||||
>
|
||||
{{ track.album.title }}
|
||||
</td>
|
||||
<td class="table__cell--small table__cell--center">
|
||||
{{ convertDuration(track.duration) }}
|
||||
</td>
|
||||
<td
|
||||
class="table__cell--download"
|
||||
@contextmenu.prevent="openQualityModal"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="track.link"
|
||||
role="button"
|
||||
aria-label="download"
|
||||
>
|
||||
<i class="material-icons">get_app</i>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { socket } from '@/js/socket.js'
|
||||
import { showView } from '@/js/tabs.js'
|
||||
import Downloads from '@/js/downloads.js'
|
||||
import Utils from '@/js/utils.js'
|
||||
|
||||
export default {
|
||||
name: 'the-charts-tab',
|
||||
data() {
|
||||
return {
|
||||
country: '',
|
||||
id: 0,
|
||||
countries: [],
|
||||
chart: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
artistView: showView.bind(null, 'artist'),
|
||||
albumView: showView.bind(null, 'album'),
|
||||
playPausePreview(e) {
|
||||
EventBus.$emit('trackPreview:playPausePreview', e)
|
||||
},
|
||||
previewMouseEnter(e) {
|
||||
EventBus.$emit('trackPreview:previewMouseEnter', e)
|
||||
},
|
||||
previewMouseLeave(e) {
|
||||
EventBus.$emit('trackPreview:previewMouseLeave', e)
|
||||
},
|
||||
convertDuration: Utils.convertDuration,
|
||||
addToQueue(e) {
|
||||
e.stopPropagation()
|
||||
Downloads.sendAddToQueue(e.currentTarget.dataset.link)
|
||||
},
|
||||
openQualityModal(e) {
|
||||
this.$root.$emit('QualityModal:open', e.currentTarget.dataset.link)
|
||||
},
|
||||
getTrackList(event) {
|
||||
document.getElementById('content').scrollTo(0, 0)
|
||||
|
||||
const {
|
||||
currentTarget: {
|
||||
dataset: { title }
|
||||
},
|
||||
currentTarget: {
|
||||
dataset: { id }
|
||||
}
|
||||
} = event
|
||||
|
||||
this.country = title
|
||||
localStorage.setItem('chart', this.country)
|
||||
this.id = id
|
||||
socket.emit('getChartTracks', this.id)
|
||||
},
|
||||
setTracklist(data) {
|
||||
this.chart = data
|
||||
},
|
||||
changeCountry() {
|
||||
this.country = ''
|
||||
this.id = 0
|
||||
},
|
||||
initCharts(data) {
|
||||
this.countries = data
|
||||
this.country = localStorage.getItem('chart') || ''
|
||||
|
||||
if (!this.country) return
|
||||
|
||||
let i = 0
|
||||
for (; i < this.countries.length; i++) {
|
||||
if (this.countries[i].title == this.country) break
|
||||
}
|
||||
|
||||
if (i !== this.countries.length) {
|
||||
this.id = this.countries[i].id
|
||||
socket.emit('getChartTracks', this.id)
|
||||
} else {
|
||||
this.country = ''
|
||||
localStorage.setItem('chart', this.country)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
socket.on('init_charts', this.initCharts)
|
||||
socket.on('setChartTracks', this.setTracklist)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -1,72 +0,0 @@
|
||||
<template>
|
||||
<section id="content" @scroll="handleContentScroll" ref="content">
|
||||
<div id="container">
|
||||
<ArtistTab />
|
||||
<TheChartsTab />
|
||||
<TheFavoritesTab />
|
||||
<TheErrorsTab />
|
||||
<TheHomeTab />
|
||||
<TheLinkAnalyzerTab />
|
||||
<TheAboutTab />
|
||||
<TheSettingsTab />
|
||||
<TheMainSearch :scrolled-search-type="newScrolled" />
|
||||
<TracklistTab />
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ArtistTab from '@components/ArtistTab.vue'
|
||||
import TracklistTab from '@components/TracklistTab.vue'
|
||||
|
||||
import TheChartsTab from '@components/TheChartsTab.vue'
|
||||
import TheFavoritesTab from '@components/TheFavoritesTab.vue'
|
||||
import TheErrorsTab from '@components/TheErrorsTab.vue'
|
||||
import TheHomeTab from '@components/TheHomeTab.vue'
|
||||
import TheLinkAnalyzerTab from '@components/TheLinkAnalyzerTab.vue'
|
||||
import TheAboutTab from '@components/TheAboutTab.vue'
|
||||
import TheSettingsTab from '@components/TheSettingsTab.vue'
|
||||
import TheMainSearch from '@components/TheMainSearch.vue'
|
||||
|
||||
import { debounce } from '@/js/utils.js'
|
||||
import EventBus from '@/js/EventBus.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ArtistTab,
|
||||
TheChartsTab,
|
||||
TheFavoritesTab,
|
||||
TheErrorsTab,
|
||||
TheHomeTab,
|
||||
TheLinkAnalyzerTab,
|
||||
TheAboutTab,
|
||||
TheSettingsTab,
|
||||
TheMainSearch,
|
||||
TracklistTab
|
||||
},
|
||||
data: () => ({
|
||||
newScrolled: null
|
||||
}),
|
||||
methods: {
|
||||
handleContentScroll: debounce(async function() {
|
||||
if (this.$refs.content.scrollTop + this.$refs.content.clientHeight < this.$refs.content.scrollHeight) return
|
||||
|
||||
if (
|
||||
main_selected !== 'search_tab' ||
|
||||
['track_search', 'album_search', 'artist_search', 'playlist_search'].indexOf(window.search_selected) === -1
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
this.newScrolled = window.search_selected.split('_')[0]
|
||||
|
||||
await this.$nextTick()
|
||||
|
||||
this.newScrolled = null
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -1,351 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
id="download_tab_container"
|
||||
class="tab_hidden"
|
||||
@transitionend="$refs.container.style.transition = ''"
|
||||
ref="container"
|
||||
>
|
||||
<div id="download_tab_drag_handler" @mousedown.prevent="startDrag" ref="dragHandler"></div>
|
||||
<i
|
||||
id="toggle_download_tab"
|
||||
class="material-icons download_bar_icon"
|
||||
@click.prevent="toggleDownloadTab"
|
||||
ref="toggler"
|
||||
></i>
|
||||
<div id="queue_buttons">
|
||||
<i id="open_downloads_folder" class="material-icons download_bar_icon hide" @click="openDownloadsFolder">
|
||||
folder_open
|
||||
</i>
|
||||
<i id="clean_queue" class="material-icons download_bar_icon" @click="cleanQueue">clear_all</i>
|
||||
<i id="cancel_queue" class="material-icons download_bar_icon" @click="cancelQueue">delete_sweep</i>
|
||||
</div>
|
||||
<div id="download_list" @click="handleListClick" ref="list"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import $ from 'jquery'
|
||||
import { socket } from '@/js/socket.js'
|
||||
import { toast } from '@/js/toasts.js'
|
||||
|
||||
const tabMinWidth = 250
|
||||
const tabMaxWidth = 500
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
cachedTabWidth: parseInt(localStorage.getItem('downloadTabWidth')) || 300,
|
||||
queue: [],
|
||||
queueList: {},
|
||||
queueComplete: []
|
||||
}),
|
||||
mounted() {
|
||||
socket.on('startDownload', this.startDownload)
|
||||
socket.on('init_downloadQueue', this.initQueue)
|
||||
socket.on('addedToQueue', this.addToQueue)
|
||||
socket.on('updateQueue', this.updateQueue)
|
||||
socket.on('removedFromQueue', this.removeFromQueue)
|
||||
socket.on('finishDownload', this.finishDownload)
|
||||
socket.on('removedAllDownloads', this.removeAllDownloads)
|
||||
socket.on('removedFinishedDownloads', this.removedFinishedDownloads)
|
||||
|
||||
// Check if download tab has slim entries
|
||||
if ('true' === localStorage.getItem('slimDownloads')) {
|
||||
this.$refs.list.classList.add('slim')
|
||||
}
|
||||
|
||||
if ('true' === localStorage.getItem('downloadTabOpen')) {
|
||||
this.$refs.container.classList.remove('tab_hidden')
|
||||
|
||||
this.setTabWidth(this.cachedTabWidth)
|
||||
}
|
||||
|
||||
document.addEventListener('mouseup', () => {
|
||||
document.removeEventListener('mousemove', this.handleDrag)
|
||||
})
|
||||
|
||||
window.addEventListener('beforeunload', () => {
|
||||
localStorage.setItem('downloadTabWidth', this.cachedTabWidth)
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
setTabWidth(newWidth) {
|
||||
if (undefined === newWidth) {
|
||||
this.$refs.container.style.width = ''
|
||||
this.$refs.list.style.width = ''
|
||||
} else {
|
||||
this.$refs.container.style.width = newWidth + 'px'
|
||||
this.$refs.list.style.width = newWidth + 'px'
|
||||
}
|
||||
},
|
||||
handleListClick(event) {
|
||||
const { target } = event
|
||||
|
||||
if (!target.matches('.queue_icon[data-uuid]')) {
|
||||
return
|
||||
}
|
||||
|
||||
let icon = target.innerText
|
||||
let uuid = $(target).data('uuid')
|
||||
|
||||
switch (icon) {
|
||||
case 'remove':
|
||||
socket.emit('removeFromQueue', uuid)
|
||||
break
|
||||
default:
|
||||
}
|
||||
},
|
||||
initQueue(data) {
|
||||
const { queue: initQueue, queueComplete: initQueueComplete, currentItem, queueList: initQueueList } = data
|
||||
|
||||
if (initQueueComplete.length) {
|
||||
initQueueComplete.forEach(item => {
|
||||
initQueueList[item].init = true
|
||||
this.addToQueue(initQueueList[item])
|
||||
})
|
||||
}
|
||||
|
||||
if (currentItem) {
|
||||
initQueueList[currentItem].init = true
|
||||
this.addToQueue(initQueueList[currentItem], true)
|
||||
}
|
||||
|
||||
initQueue.forEach(item => {
|
||||
initQueueList[item].init = true
|
||||
this.addToQueue(initQueueList[item])
|
||||
})
|
||||
},
|
||||
addToQueue(queueItem, current = false) {
|
||||
this.queueList[queueItem.uuid] = queueItem
|
||||
|
||||
if (queueItem.downloaded + queueItem.failed == queueItem.size) {
|
||||
if (this.queueComplete.indexOf(queueItem.uuid) == -1) {
|
||||
this.queueComplete.push(queueItem.uuid)
|
||||
}
|
||||
} else {
|
||||
if (this.queue.indexOf(queueItem.uuid) == -1) {
|
||||
this.queue.push(queueItem.uuid)
|
||||
}
|
||||
}
|
||||
|
||||
let queueDOM = document.getElementById('download_' + queueItem.uuid)
|
||||
|
||||
if (typeof queueDOM == 'undefined' || queueDOM == null) {
|
||||
$(this.$refs.list).append(
|
||||
`<div class="download_object" id="download_${queueItem.uuid}" data-deezerid="${queueItem.id}">
|
||||
<div class="download_info">
|
||||
<img width="75px" class="rounded coverart" src="${queueItem.cover}" alt="Cover ${queueItem.title}"/>
|
||||
<div class="download_info_data">
|
||||
<span class="download_line">${queueItem.title}</span> <span class="download_slim_separator"> - </span>
|
||||
<span class="secondary-text">${queueItem.artist}</span>
|
||||
</div>
|
||||
<div class="download_info_status">
|
||||
<span class="download_line"><span class="queue_downloaded">${queueItem.downloaded + queueItem.failed}</span>/${
|
||||
queueItem.size
|
||||
}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="download_bar">
|
||||
<div class="progress"><div id="bar_${queueItem.uuid}" class="indeterminate"></div></div>
|
||||
<i class="material-icons queue_icon" data-uuid="${queueItem.uuid}">remove</i>
|
||||
</div>
|
||||
</div>`
|
||||
)
|
||||
}
|
||||
|
||||
if (queueItem.progress > 0 || current) {
|
||||
this.startDownload(queueItem.uuid)
|
||||
}
|
||||
|
||||
$('#bar_' + queueItem.uuid).css('width', queueItem.progress + '%')
|
||||
|
||||
if (queueItem.failed >= 1 && $('#download_' + queueItem.uuid + ' .queue_failed').length == 0) {
|
||||
$('#download_' + queueItem.uuid + ' .download_info_status').append(
|
||||
`<span class="secondary-text inline-flex"><span class="download_slim_separator">(</span><span class="queue_failed_button inline-flex"><span class="queue_failed">${queueItem.failed}</span><i class="material-icons">error_outline</i></span><span class="download_slim_separator">)</span></span>`
|
||||
)
|
||||
}
|
||||
|
||||
if (queueItem.downloaded + queueItem.failed == queueItem.size) {
|
||||
let resultIcon = $('#download_' + queueItem.uuid).find('.queue_icon')
|
||||
|
||||
if (queueItem.failed == 0) {
|
||||
resultIcon.text('done')
|
||||
} else {
|
||||
let failedButton = $('#download_' + queueItem.uuid).find('.queue_failed_button')
|
||||
|
||||
resultIcon.addClass('clickable')
|
||||
failedButton.addClass('clickable')
|
||||
|
||||
resultIcon.bind('click', { item: queueItem }, this.showErrorsTab)
|
||||
failedButton.bind('click', { item: queueItem }, this.showErrorsTab)
|
||||
|
||||
if (queueItem.failed >= queueItem.size) {
|
||||
resultIcon.text('error')
|
||||
} else {
|
||||
resultIcon.text('warning')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!queueItem.init) {
|
||||
toast(`${queueItem.title} added to queue`, 'playlist_add_check')
|
||||
}
|
||||
},
|
||||
updateQueue(update) {
|
||||
// downloaded and failed default to false?
|
||||
const { uuid, downloaded, failed, progress, error, data } = update
|
||||
|
||||
if (uuid && this.queue.indexOf(uuid) > -1) {
|
||||
if (downloaded) {
|
||||
this.queueList[uuid].downloaded++
|
||||
$('#download_' + uuid + ' .queue_downloaded').text(
|
||||
this.queueList[uuid].downloaded + this.queueList[uuid].failed
|
||||
)
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
this.queueList[uuid].failed++
|
||||
$('#download_' + uuid + ' .queue_downloaded').text(
|
||||
this.queueList[uuid].downloaded + this.queueList[uuid].failed
|
||||
)
|
||||
if (this.queueList[uuid].failed == 1 && $('#download_' + uuid + ' .queue_failed').length == 0) {
|
||||
$('#download_' + uuid + ' .download_info_status').append(
|
||||
`<span class="secondary-text inline-flex"><span class="download_slim_separator">(</span><span class="queue_failed_button inline-flex"><span class="queue_failed">1</span> <i class="material-icons">error_outline</i></span><span class="download_slim_separator">)</span></span>`
|
||||
)
|
||||
} else {
|
||||
$('#download_' + uuid + ' .queue_failed').text(this.queueList[uuid].failed)
|
||||
}
|
||||
|
||||
this.queueList[uuid].errors.push({ message: error, data: data })
|
||||
}
|
||||
|
||||
if (progress) {
|
||||
this.queueList[uuid].progress = progress
|
||||
$('#bar_' + uuid).css('width', progress + '%')
|
||||
}
|
||||
}
|
||||
},
|
||||
removeFromQueue(uuid) {
|
||||
let index = this.queue.indexOf(uuid)
|
||||
|
||||
if (index > -1) {
|
||||
this.queue.splice(index, 1)
|
||||
$(`#download_${this.queueList[uuid].uuid}`).remove()
|
||||
delete this.queueList[uuid]
|
||||
}
|
||||
},
|
||||
removeAllDownloads(currentItem) {
|
||||
this.queueComplete = []
|
||||
|
||||
if (currentItem == '') {
|
||||
this.queue = []
|
||||
this.queueList = {}
|
||||
$(listEl).html('')
|
||||
} else {
|
||||
this.queue = [currentItem]
|
||||
let tempQueueItem = this.queueList[currentItem]
|
||||
this.queueList = {}
|
||||
this.queueList[currentItem] = tempQueueItem
|
||||
|
||||
$('.download_object').each(function(index) {
|
||||
if ($(this).attr('id') != 'download_' + currentItem) $(this).remove()
|
||||
})
|
||||
}
|
||||
},
|
||||
removedFinishedDownloads() {
|
||||
this.queueComplete.forEach(item => {
|
||||
$('#download_' + item).remove()
|
||||
})
|
||||
|
||||
this.queueComplete = []
|
||||
},
|
||||
toggleDownloadTab(clickEvent) {
|
||||
this.setTabWidth()
|
||||
|
||||
this.$refs.container.style.transition = 'all 250ms ease-in-out'
|
||||
|
||||
// Toggle returns a Boolean based on the action it performed
|
||||
let isHidden = this.$refs.container.classList.toggle('tab_hidden')
|
||||
|
||||
if (!isHidden) {
|
||||
this.setTabWidth(this.cachedTabWidth)
|
||||
}
|
||||
|
||||
localStorage.setItem('downloadTabOpen', !isHidden)
|
||||
},
|
||||
cleanQueue() {
|
||||
socket.emit('removeFinishedDownloads')
|
||||
},
|
||||
cancelQueue() {
|
||||
socket.emit('cancelAllDownloads')
|
||||
},
|
||||
finishDownload(uuid) {
|
||||
if (this.queue.indexOf(uuid) > -1) {
|
||||
toast(`${this.queueList[uuid].title} finished downloading.`, 'done')
|
||||
|
||||
$('#bar_' + uuid).css('width', '100%')
|
||||
|
||||
let resultIcon = $('#download_' + uuid).find('.queue_icon')
|
||||
|
||||
if (this.queueList[uuid].failed == 0) {
|
||||
resultIcon.text('done')
|
||||
} else {
|
||||
let failedButton = $('#download_' + uuid).find('.queue_failed_button')
|
||||
|
||||
resultIcon.addClass('clickable')
|
||||
failedButton.addClass('clickable')
|
||||
|
||||
resultIcon.bind('click', { item: this.queueList[uuid] }, this.showErrorsTab)
|
||||
failedButton.bind('click', { item: this.queueList[uuid] }, this.showErrorsTab)
|
||||
|
||||
if (this.queueList[uuid].failed >= this.queueList[uuid].size) {
|
||||
resultIcon.text('error')
|
||||
} else {
|
||||
resultIcon.text('warning')
|
||||
}
|
||||
}
|
||||
|
||||
let index = this.queue.indexOf(uuid)
|
||||
if (index > -1) {
|
||||
this.queue.splice(index, 1)
|
||||
this.queueComplete.push(uuid)
|
||||
}
|
||||
|
||||
if (this.queue.length <= 0) {
|
||||
toast('All downloads completed!', 'done_all')
|
||||
}
|
||||
}
|
||||
},
|
||||
openDownloadsFolder() {
|
||||
if (window.clientMode) {
|
||||
socket.emit('openDownloadsFolder')
|
||||
}
|
||||
},
|
||||
handleDrag(event) {
|
||||
let newWidth = window.innerWidth - event.pageX + 2
|
||||
|
||||
if (newWidth < tabMinWidth) {
|
||||
newWidth = tabMinWidth
|
||||
} else if (newWidth > tabMaxWidth) {
|
||||
newWidth = tabMaxWidth
|
||||
}
|
||||
|
||||
this.cachedTabWidth = newWidth
|
||||
this.setTabWidth(newWidth)
|
||||
},
|
||||
startDrag() {
|
||||
document.addEventListener('mousemove', this.handleDrag)
|
||||
},
|
||||
startDownload(uuid) {
|
||||
$('#bar_' + uuid)
|
||||
.removeClass('indeterminate')
|
||||
.addClass('determinate')
|
||||
},
|
||||
showErrorsTab(clickEvent) {
|
||||
this.$root.$emit('showTabErrors', clickEvent.data.item, clickEvent.target)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -1,52 +0,0 @@
|
||||
<template>
|
||||
<div id="errors_tab" class="main_tabcontent">
|
||||
<h1>Errors for {{ title }}</h1>
|
||||
<table>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Artist</th>
|
||||
<th>Title</th>
|
||||
<th>Error</th>
|
||||
</tr>
|
||||
<tr v-for="error in errors">
|
||||
<td>{{ error.data.id }}</td>
|
||||
<td>{{ error.data.artist }}</td>
|
||||
<td>{{ error.data.title }}</td>
|
||||
<td>{{ error.message }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { changeTab } from '@/js/tabs.js'
|
||||
|
||||
import EventBus from '@/js/EventBus'
|
||||
|
||||
export default {
|
||||
name: 'the-errors-tab',
|
||||
data: () => ({
|
||||
title: '',
|
||||
errors: []
|
||||
}),
|
||||
methods: {
|
||||
reset() {
|
||||
this.title = ''
|
||||
this.errors = []
|
||||
},
|
||||
showErrors(data, eventTarget) {
|
||||
this.title = data.artist + ' - ' + data.title
|
||||
this.errors = data.errors
|
||||
|
||||
changeTab(eventTarget, 'main', 'errors_tab')
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
EventBus.$on('showTabErrors', this.showErrors)
|
||||
this.$root.$on('showTabErrors', this.showErrors)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -1,310 +0,0 @@
|
||||
<template>
|
||||
<div id="favorites_tab" class="main_tabcontent" @click="handleFavoritesTabClick">
|
||||
<h2 class="page_heading">
|
||||
Favorites
|
||||
<div
|
||||
@click="reloadTabs"
|
||||
class="clickable reload-button reload-button--inline"
|
||||
ref="reloadButton"
|
||||
role="button"
|
||||
aria-label="reload"
|
||||
>
|
||||
<i class="material-icons">sync</i>
|
||||
</div>
|
||||
</h2>
|
||||
<div class="section-tabs">
|
||||
<div class="section-tabs__tab favorites_tablinks" id="favorites_playlist_tab">Playlists</div>
|
||||
<div class="section-tabs__tab favorites_tablinks" id="favorites_album_tab">Albums</div>
|
||||
<div class="section-tabs__tab favorites_tablinks" id="favorites_artist_tab">Artists</div>
|
||||
<div class="section-tabs__tab favorites_tablinks" id="favorites_track_tab">Tracks</div>
|
||||
</div>
|
||||
|
||||
<div id="playlist_favorites" class="favorites_tabcontent">
|
||||
<div v-if="playlists.length == 0">
|
||||
<h1>No Playlists found</h1>
|
||||
</div>
|
||||
<div class="release_grid" v-if="playlists.length > 0 || spotifyPlaylists > 0">
|
||||
<div v-for="release in playlists" class="release clickable" @click="playlistView" :data-id="release.id">
|
||||
<div class="cover_container">
|
||||
<img aria-hidden="true" class="rounded coverart" :src="release.picture_medium" />
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@contextmenu.prevent="openQualityModal"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="release.link"
|
||||
class="download_overlay"
|
||||
>
|
||||
<i class="material-icons">get_app</i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.title }}</p>
|
||||
<p class="secondary-text">{{ 'by ' + release.creator.name + ' - ' + release.nb_tracks + ' tracks' }}</p>
|
||||
</div>
|
||||
<div
|
||||
v-for="release in spotifyPlaylists"
|
||||
class="release clickable"
|
||||
@click="spotifyPlaylistView"
|
||||
:data-id="release.id"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img aria-hidden="true" class="rounded coverart" :src="release.picture_medium" />
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@contextmenu.prevent="openQualityModal"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="release.link"
|
||||
class="download_overlay"
|
||||
>
|
||||
<i class="material-icons">get_app</i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.title }}</p>
|
||||
<p class="secondary-text">{{ 'by ' + release.creator.name + ' - ' + release.nb_tracks + ' tracks' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="album_favorites" class="favorites_tabcontent">
|
||||
<div v-if="albums.length == 0">
|
||||
<h1>No Favorite Albums found</h1>
|
||||
</div>
|
||||
<div class="release_grid" v-if="albums.length > 0">
|
||||
<div v-for="release in albums" class="release clickable" @click="albumView" :data-id="release.id">
|
||||
<div class="cover_container">
|
||||
<img aria-hidden="true" class="rounded coverart" :src="release.cover_medium" />
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@contextmenu.prevent="openQualityModal"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="release.link"
|
||||
class="download_overlay"
|
||||
>
|
||||
<i class="material-icons">get_app</i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.title }}</p>
|
||||
<p class="secondary-text">{{ 'by ' + release.artist.name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="artist_favorites" class="favorites_tabcontent">
|
||||
<div v-if="artists.length == 0">
|
||||
<h1>No Favorite Artist found</h1>
|
||||
</div>
|
||||
<div class="release_grid" v-if="artists.length > 0">
|
||||
<div v-for="release in artists" class="release clickable" @click="artistView" :data-id="release.id">
|
||||
<div class="cover_container">
|
||||
<img aria-hidden="true" class="circle coverart" :src="release.picture_medium" />
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@contextmenu.prevent="openQualityModal"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="release.link"
|
||||
class="download_overlay"
|
||||
>
|
||||
<i class="material-icons">get_app</i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="track_favorites" class="favorites_tabcontent">
|
||||
<div v-if="tracks.length == 0">
|
||||
<h1>No Favorite Tracks found</h1>
|
||||
</div>
|
||||
<table v-if="tracks.length > 0" class="table">
|
||||
<tr v-for="track in tracks" class="track_row">
|
||||
<td class="top-tracks-position" :class="{ first: track.position === 1 }">
|
||||
{{ track.position }}
|
||||
</td>
|
||||
<td>
|
||||
<a
|
||||
href="#"
|
||||
class="rounded"
|
||||
:class="{ 'single-cover': !!track.preview }"
|
||||
@click="playPausePreview"
|
||||
:data-preview="track.preview"
|
||||
>
|
||||
<i
|
||||
@mouseenter="previewMouseEnter"
|
||||
@mouseleave="previewMouseLeave"
|
||||
v-if="track.preview"
|
||||
class="material-icons preview_controls"
|
||||
>
|
||||
play_arrow
|
||||
</i>
|
||||
<img class="rounded coverart" :src="track.album.cover_small" />
|
||||
</a>
|
||||
</td>
|
||||
<td class="table__cell--large breakline">
|
||||
{{
|
||||
track.title +
|
||||
(track.title_version && track.title.indexOf(track.title_version) == -1 ? ' ' + track.title_version : '')
|
||||
}}
|
||||
</td>
|
||||
<td
|
||||
class="table__cell--medium table__cell--center breakline clickable"
|
||||
@click="artistView"
|
||||
:data-id="track.artist.id"
|
||||
>
|
||||
{{ track.artist.name }}
|
||||
</td>
|
||||
<td
|
||||
class="table__cell--medium table__cell--center breakline clickable"
|
||||
@click="albumView"
|
||||
:data-id="track.album.id"
|
||||
>
|
||||
{{ track.album.title }}
|
||||
</td>
|
||||
<td class="table__cell--small">
|
||||
{{ convertDuration(track.duration) }}
|
||||
</td>
|
||||
<td
|
||||
class="table__cell--download clickable"
|
||||
@contextmenu.prevent="openQualityModal"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="track.link"
|
||||
role="button"
|
||||
aria-label="download"
|
||||
>
|
||||
<div class="table__cell-content table__cell-content--vertical-center">
|
||||
<i class="material-icons">get_app</i>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { socket } from '@/js/socket.js'
|
||||
import { showView, changeTab } from '@/js/tabs.js'
|
||||
import Downloads from '@/js/downloads.js'
|
||||
import Utils from '@/js/utils.js'
|
||||
import { toast } from '@/js/toasts'
|
||||
|
||||
export default {
|
||||
name: 'the-favorites-tab',
|
||||
data() {
|
||||
return {
|
||||
tracks: [],
|
||||
albums: [],
|
||||
artists: [],
|
||||
playlists: [],
|
||||
spotifyPlaylists: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
artistView: showView.bind(null, 'artist'),
|
||||
albumView: showView.bind(null, 'album'),
|
||||
playlistView: showView.bind(null, 'playlist'),
|
||||
spotifyPlaylistView: showView.bind(null, 'spotifyplaylist'),
|
||||
playPausePreview(e) {
|
||||
EventBus.$emit('trackPreview:playPausePreview', e)
|
||||
},
|
||||
previewMouseEnter(e) {
|
||||
EventBus.$emit('trackPreview:previewMouseEnter', e)
|
||||
},
|
||||
previewMouseLeave(e) {
|
||||
EventBus.$emit('trackPreview:previewMouseLeave', e)
|
||||
},
|
||||
convertDuration: Utils.convertDuration,
|
||||
handleFavoritesTabClick(event) {
|
||||
const {
|
||||
target,
|
||||
target: { id }
|
||||
} = event
|
||||
let selectedTab = null
|
||||
|
||||
switch (id) {
|
||||
case 'favorites_playlist_tab':
|
||||
selectedTab = 'playlist_favorites'
|
||||
break
|
||||
case 'favorites_album_tab':
|
||||
selectedTab = 'album_favorites'
|
||||
break
|
||||
case 'favorites_artist_tab':
|
||||
selectedTab = 'artist_favorites'
|
||||
break
|
||||
case 'favorites_track_tab':
|
||||
selectedTab = 'track_favorites'
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if (!selectedTab) return
|
||||
|
||||
changeTab(target, 'favorites', selectedTab)
|
||||
},
|
||||
addToQueue(e) {
|
||||
e.stopPropagation()
|
||||
Downloads.sendAddToQueue(e.currentTarget.dataset.link)
|
||||
},
|
||||
openQualityModal(e) {
|
||||
this.$root.$emit('QualityModal:open', e.currentTarget.dataset.link)
|
||||
},
|
||||
updated_userSpotifyPlaylists(data) {
|
||||
this.spotifyPlaylists = data
|
||||
},
|
||||
updated_userPlaylists(data) {
|
||||
this.playlists = data
|
||||
},
|
||||
updated_userAlbums(data) {
|
||||
this.albums = data
|
||||
},
|
||||
updated_userArtist(data) {
|
||||
this.artists = data
|
||||
},
|
||||
updated_userTracks(data) {
|
||||
this.tracks = data
|
||||
},
|
||||
reloadTabs() {
|
||||
this.$refs.reloadButton.classList.add('spin')
|
||||
socket.emit('update_userFavorites')
|
||||
if (localStorage.getItem('spotifyUser'))
|
||||
socket.emit('update_userSpotifyPlaylists', localStorage.getItem('spotifyUser'))
|
||||
},
|
||||
updated_userFavorites(data) {
|
||||
const { tracks, albums, artists, playlists } = data
|
||||
this.tracks = tracks
|
||||
this.albums = albums
|
||||
this.artists = artists
|
||||
this.playlists = playlists
|
||||
|
||||
// Removing animation class only when the animation has completed an iteration
|
||||
// Prevents animation ugly stutter
|
||||
this.$refs.reloadButton.addEventListener(
|
||||
'animationiteration',
|
||||
() => {
|
||||
this.$refs.reloadButton.classList.remove('spin')
|
||||
toast('Refresh completed!', 'done', true)
|
||||
},
|
||||
{ once: true }
|
||||
)
|
||||
},
|
||||
initFavorites(data) {
|
||||
this.updated_userFavorites(data)
|
||||
document.getElementById('favorites_playlist_tab').click()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
socket.on('init_favorites', this.initFavorites)
|
||||
socket.on('updated_userFavorites', this.updated_userFavorites)
|
||||
socket.on('updated_userSpotifyPlaylists', this.updated_userSpotifyPlaylists)
|
||||
socket.on('updated_userPlaylists', this.updated_userPlaylists)
|
||||
socket.on('updated_userAlbums', this.updated_userAlbums)
|
||||
socket.on('updated_userArtist', this.updated_userArtist)
|
||||
socket.on('updated_userTracks', this.updated_userTracks)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -1,102 +0,0 @@
|
||||
<template>
|
||||
<div id="home_tab" class="main_tabcontent">
|
||||
<h2 class="page_heading">Welcome to deemix</h2>
|
||||
<section id="home_not_logged_in" class="home_section" ref="notLogged">
|
||||
<p id="home_not_logged_text">You need to log into your deezer account before you can start downloading.</p>
|
||||
<button type="button" name="button" @click="openSettings">Open Settings</button>
|
||||
</section>
|
||||
<section v-if="playlists.length" class="home_section">
|
||||
<h3 class="section_heading">Popular playlists</h3>
|
||||
<div class="release_grid">
|
||||
<div v-for="release in playlists" class="release clickable" @click="playlistView" :data-id="release.id">
|
||||
<div class="cover_container">
|
||||
<img aria-hidden="true" class="rounded coverart" :src="release.picture_medium" />
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@contextmenu.prevent="openQualityModal"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="release.link"
|
||||
class="download_overlay"
|
||||
>
|
||||
<i class="material-icons">get_app</i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.title }}</p>
|
||||
<p class="secondary-text">{{ 'by ' + release.user.name + ' - ' + release.nb_tracks + ' tracks' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section v-if="albums.length" class="home_section">
|
||||
<h3 class="section_heading">Most streamed albums</h3>
|
||||
<div class="release_grid">
|
||||
<div v-for="release in albums" class="release clickable" @click="albumView" :data-id="release.id">
|
||||
<div class="cover_container">
|
||||
<img aria-hidden="true" class="rounded coverart" :src="release.cover_medium" />
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@contextmenu.prevent="openQualityModal"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="release.link"
|
||||
class="download_overlay"
|
||||
>
|
||||
<i class="material-icons">get_app</i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.title }}</p>
|
||||
<p class="secondary-text">{{ 'by ' + release.artist.name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { socket } from '@/js/socket.js'
|
||||
import { showView } from '@/js/tabs.js'
|
||||
import Downloads from '@/js/downloads.js'
|
||||
|
||||
export default {
|
||||
name: 'the-home-tab',
|
||||
data() {
|
||||
return {
|
||||
playlists: [],
|
||||
albums: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
artistView: showView.bind(null, 'artist'),
|
||||
albumView: showView.bind(null, 'album'),
|
||||
playlistView: showView.bind(null, 'playlist'),
|
||||
openSettings() {
|
||||
document.getElementById('main_settings_tablink').click()
|
||||
},
|
||||
addToQueue(e) {
|
||||
Downloads.sendAddToQueue(e.currentTarget.dataset.link)
|
||||
},
|
||||
openQualityModal(e) {
|
||||
this.$root.$emit('QualityModal:open', e.currentTarget.dataset.link)
|
||||
},
|
||||
initHome(data) {
|
||||
const {
|
||||
playlists: { data: playlistData },
|
||||
albums: { data: albumData }
|
||||
} = data
|
||||
|
||||
this.playlists = playlistData
|
||||
this.albums = albumData
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (localStorage.getItem('arl')) {
|
||||
this.$refs.notLogged.classList.add('hide')
|
||||
}
|
||||
|
||||
socket.on('init_home', this.initHome)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -1,186 +0,0 @@
|
||||
<template>
|
||||
<div id="analyzer_tab" class="main_tabcontent image_header">
|
||||
<h2 class="page_heading">Link Analyzer</h2>
|
||||
<div v-if="link == ''">
|
||||
<p>
|
||||
You can use this section to find out more information about the link you are trying to download<br />This is
|
||||
usefull if you're trying to download some tracks that are not available in your country and want to know where
|
||||
they are available
|
||||
</p>
|
||||
</div>
|
||||
<div v-else-if="link == 'error'">
|
||||
<h2>This link is not supported</h2>
|
||||
<p>Seems like this link is not yet supported, try analyzing another one.</p>
|
||||
</div>
|
||||
<div v-else>
|
||||
<header
|
||||
class="inline-flex"
|
||||
:style="{
|
||||
'background-image':
|
||||
'linear-gradient(to bottom, transparent 0%, var(--main-background) 100%), url(\'' + image + '\')'
|
||||
}"
|
||||
>
|
||||
<div>
|
||||
<h1>{{ title }}</h1>
|
||||
<h2 v-if="type == 'track'">
|
||||
by <span class="clickable" @click="artistView" :data-id="data.artist.id">{{ data.artist.name }}</span> • in
|
||||
<span class="clickable" @click="albumView" :data-id="data.album.id">{{ data.album.title }}</span>
|
||||
</h2>
|
||||
<h2 v-else-if="type == 'album'">
|
||||
by <span class="clickable" @click="artistView" :data-id="data.artist.id">{{ data.artist.name }}</span> •
|
||||
{{ data.nb_tracks }} tracks
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@contextmenu.prevent="openQualityModal"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="link"
|
||||
class="fab right"
|
||||
>
|
||||
<i class="material-icons">get_app</i>
|
||||
</div>
|
||||
</header>
|
||||
<table class="table">
|
||||
<tr v-if="data.id">
|
||||
<td>ID</td>
|
||||
<td>{{ data.id }}</td>
|
||||
</tr>
|
||||
<tr v-if="data.isrc">
|
||||
<td>ISRC</td>
|
||||
<td>{{ data.isrc }}</td>
|
||||
</tr>
|
||||
<tr v-if="data.upc">
|
||||
<td>UPC</td>
|
||||
<td>{{ data.upc }}</td>
|
||||
</tr>
|
||||
<tr v-if="data.duration">
|
||||
<td>Duration</td>
|
||||
<td>{{ convertDuration(data.duration) }}</td>
|
||||
</tr>
|
||||
<tr v-if="data.disk_number">
|
||||
<td>Disk Number</td>
|
||||
<td>{{ data.disk_number }}</td>
|
||||
</tr>
|
||||
<tr v-if="data.track_position">
|
||||
<td>Track Number</td>
|
||||
<td>{{ data.track_position }}</td>
|
||||
</tr>
|
||||
<tr v-if="data.release_date">
|
||||
<td>Release Date</td>
|
||||
<td>{{ data.release_date }}</td>
|
||||
</tr>
|
||||
<tr v-if="data.bpm">
|
||||
<td>BPM</td>
|
||||
<td>{{ data.bpm }}</td>
|
||||
</tr>
|
||||
<tr v-if="data.label">
|
||||
<td>Label</td>
|
||||
<td>{{ data.label }}</td>
|
||||
</tr>
|
||||
<tr v-if="data.record_type">
|
||||
<td>Record Type</td>
|
||||
<td>{{ data.record_type }}</td>
|
||||
</tr>
|
||||
<tr v-if="data.genres && data.genres.data.length">
|
||||
<td>Genres</td>
|
||||
<td>{{ data.genres.data.map(x => x.name).join('; ') }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div v-if="type == 'album'">
|
||||
<button @click="albumView" :data-id="id">Tracklist</button>
|
||||
</div>
|
||||
<div v-if="countries.length">
|
||||
<p v-for="country in countries">{{ country[0] }} - {{ country[1] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { socket } from '@/js/socket.js'
|
||||
import { showView } from '@/js/tabs.js'
|
||||
import Utils from '@/js/utils.js'
|
||||
import EventBus from '@/js/EventBus'
|
||||
|
||||
export default {
|
||||
name: 'the-link-analyzer-tab',
|
||||
data() {
|
||||
return {
|
||||
title: '',
|
||||
subtitle: '',
|
||||
image: '',
|
||||
data: {},
|
||||
type: '',
|
||||
link: '',
|
||||
id: '0',
|
||||
countries: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
artistView: showView.bind(null, 'artist'),
|
||||
albumView: showView.bind(null, 'album'),
|
||||
convertDuration: Utils.convertDuration,
|
||||
reset() {
|
||||
this.title = 'Loading...'
|
||||
this.subtitle = ''
|
||||
this.image = ''
|
||||
this.data = {}
|
||||
this.type = ''
|
||||
this.link = ''
|
||||
this.countries = []
|
||||
},
|
||||
showTrack(data) {
|
||||
const {
|
||||
title,
|
||||
title_version,
|
||||
album: { cover_xl },
|
||||
link,
|
||||
available_countries,
|
||||
id
|
||||
} = data
|
||||
|
||||
this.title = title + (title_version && title.indexOf(title_version) == -1 ? ' ' + title_version : '')
|
||||
this.image = cover_xl
|
||||
this.type = 'track'
|
||||
this.link = link
|
||||
this.id = id
|
||||
|
||||
available_countries.forEach(cc => {
|
||||
let temp = []
|
||||
let chars = [...cc].map(c => c.charCodeAt() + 127397)
|
||||
temp.push(String.fromCodePoint(...chars))
|
||||
temp.push(Utils.COUNTRIES[cc])
|
||||
this.countries.push(temp)
|
||||
})
|
||||
|
||||
this.data = data
|
||||
},
|
||||
showAlbum(data) {
|
||||
const { title, cover_xl, link, id } = data
|
||||
|
||||
this.title = title
|
||||
this.image = cover_xl
|
||||
this.type = 'album'
|
||||
this.link = link
|
||||
this.data = data
|
||||
this.id = id
|
||||
},
|
||||
notSupported() {
|
||||
this.link = 'error'
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
EventBus.$on('linkAnalyzerTab:reset', this.reset)
|
||||
|
||||
socket.on('analyze_track', this.showTrack)
|
||||
socket.on('analyze_album', this.showAlbum)
|
||||
socket.on('analyze_notSupported', this.notSupported)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -1,21 +0,0 @@
|
||||
<template>
|
||||
<main id="main_content">
|
||||
<TheMiddleSection />
|
||||
<TheDownloadTab />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TheMiddleSection from '@components/TheMiddleSection.vue'
|
||||
import TheDownloadTab from '@components/TheDownloadTab.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TheMiddleSection,
|
||||
TheDownloadTab
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -1,702 +0,0 @@
|
||||
<template>
|
||||
<div id="search_tab" class="main_tabcontent" @click="handleSearchTabClick">
|
||||
<div :class="{ hide: results.query != '' }">
|
||||
<h2>Start searching!</h2>
|
||||
<p>
|
||||
You can search a track, a whole album, an artist, a playlist.... everything! You can also paste a Deezer link
|
||||
</p>
|
||||
</div>
|
||||
<div v-show="results.query !== ''">
|
||||
<ul class="section-tabs">
|
||||
<li class="section-tabs__tab search_tablinks" id="search_all_tab">All</li>
|
||||
<li class="section-tabs__tab search_tablinks" id="search_track_tab">Tracks</li>
|
||||
<li class="section-tabs__tab search_tablinks" id="search_album_tab">Album</li>
|
||||
<li class="section-tabs__tab search_tablinks" id="search_artist_tab">Artist</li>
|
||||
<li class="section-tabs__tab search_tablinks" id="search_playlist_tab">Playlist</li>
|
||||
</ul>
|
||||
<div id="search_tab_content">
|
||||
<!-- ### Main Search Tab ### -->
|
||||
<div id="main_search" class="search_tabcontent">
|
||||
<template v-for="section in results.allTab.ORDER">
|
||||
<section
|
||||
v-if="
|
||||
(section != 'TOP_RESULT' && results.allTab[section].data.length > 0) ||
|
||||
results.allTab[section].length > 0
|
||||
"
|
||||
class="search_section"
|
||||
>
|
||||
<h2
|
||||
@click="changeSearchTab(section)"
|
||||
class="search_header"
|
||||
:class="{ top_result_header: section === 'TOP_RESULT' }"
|
||||
>
|
||||
{{ names[section] }}
|
||||
</h2>
|
||||
<!-- Top result -->
|
||||
<div
|
||||
v-if="section == 'TOP_RESULT'"
|
||||
class="top_result clickable"
|
||||
@click="handleClickTopResult"
|
||||
:data-id="results.allTab.TOP_RESULT[0].id"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img
|
||||
aria-hidden="true"
|
||||
:src="results.allTab.TOP_RESULT[0].picture"
|
||||
:class="(results.allTab.TOP_RESULT[0].type == 'artist' ? 'circle' : 'rounded') + ' coverart'"
|
||||
/>
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@contextmenu.prevent="openQualityModal"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="results.allTab.TOP_RESULT[0].link"
|
||||
class="download_overlay"
|
||||
>
|
||||
<i class="material-icons">get_app</i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info_box">
|
||||
<p class="primary-text">{{ results.allTab.TOP_RESULT[0].title }}</p>
|
||||
<p class="secondary-text">
|
||||
{{
|
||||
results.allTab.TOP_RESULT[0].type == 'artist'
|
||||
? numberWithDots(results.allTab.TOP_RESULT[0].nb_fan) + ' fans'
|
||||
: 'by ' +
|
||||
results.allTab.TOP_RESULT[0].artist +
|
||||
' - ' +
|
||||
results.allTab.TOP_RESULT[0].nb_song +
|
||||
' tracks'
|
||||
}}
|
||||
</p>
|
||||
<span class="tag">{{
|
||||
results.allTab.TOP_RESULT[0].type.charAt(0).toUpperCase() +
|
||||
results.allTab.TOP_RESULT[0].type.substring(1)
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="section == 'TRACK'">
|
||||
<table class="table table--tracks">
|
||||
<tbody>
|
||||
<tr v-for="track in results.allTab.TRACK.data.slice(0, 6)">
|
||||
<td class="table__icon" aria-hidden="true">
|
||||
<img
|
||||
class="rounded coverart"
|
||||
:src="
|
||||
'https://e-cdns-images.dzcdn.net/images/cover/' +
|
||||
track.ALB_PICTURE +
|
||||
'/32x32-000000-80-0-0.jpg'
|
||||
"
|
||||
/>
|
||||
</td>
|
||||
<td class="table__cell table__cell--large breakline">
|
||||
<div class="table__cell-content table__cell-content--vertical-center">
|
||||
<i v-if="track.EXPLICIT_LYRICS == 1" class="material-icons explicit_icon">
|
||||
explicit
|
||||
</i>
|
||||
{{ track.SNG_TITLE + (track.VERSION ? ' ' + track.VERSION : '') }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="table__cell table__cell--medium table__cell--center breakline">
|
||||
<span
|
||||
class="clickable"
|
||||
@click="artistView"
|
||||
:data-id="artist.ART_ID"
|
||||
v-for="artist in track.ARTISTS"
|
||||
>{{ artist.ART_NAME }}
|
||||
</span>
|
||||
</td>
|
||||
<td
|
||||
class="table__cell--medium table__cell--center breakline clickable"
|
||||
@click="albumView"
|
||||
:data-id="track.ALB_ID"
|
||||
>
|
||||
{{ track.ALB_TITLE }}
|
||||
</td>
|
||||
<td class="table__cell table__cell--center">
|
||||
{{ convertDuration(track.DURATION) }}
|
||||
</td>
|
||||
<td
|
||||
class="table__cell--download table__cell--center clickable"
|
||||
@contextmenu.prevent="openQualityModal"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="'https://www.deezer.com/track/' + track.SNG_ID"
|
||||
role="button"
|
||||
aria-label="download"
|
||||
>
|
||||
<i class="material-icons">
|
||||
get_app
|
||||
</i>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-else-if="section == 'ARTIST'" class="release_grid firstrow_only">
|
||||
<div
|
||||
v-for="release in results.allTab.ARTIST.data.slice(0, 10)"
|
||||
class="release clickable"
|
||||
@click="artistView"
|
||||
:data-id="release.ART_ID"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img
|
||||
aria-hidden="true"
|
||||
class="circle coverart"
|
||||
:src="
|
||||
'https://e-cdns-images.dzcdn.net/images/artist/' +
|
||||
release.ART_PICTURE +
|
||||
'/156x156-000000-80-0-0.jpg'
|
||||
"
|
||||
/>
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@contextmenu.prevent="openQualityModal"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="'https://deezer.com/artist/' + release.ART_ID"
|
||||
class="download_overlay"
|
||||
>
|
||||
<i class="material-icons">get_app</i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.ART_NAME }}</p>
|
||||
<p class="secondary-text">{{ numberWithDots(release.NB_FAN) + ' fans' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="section == 'ALBUM'" class="release_grid firstrow_only">
|
||||
<div
|
||||
v-for="release in results.allTab.ALBUM.data.slice(0, 10)"
|
||||
class="release clickable"
|
||||
@click="albumView"
|
||||
:data-id="release.ALB_ID"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img
|
||||
aria-hidden="true"
|
||||
class="rounded coverart"
|
||||
:src="
|
||||
'https://e-cdns-images.dzcdn.net/images/cover/' +
|
||||
release.ALB_PICTURE +
|
||||
'/156x156-000000-80-0-0.jpg'
|
||||
"
|
||||
/>
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@contextmenu.prevent="openQualityModal"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="'https://deezer.com/album/' + release.ALB_ID"
|
||||
class="download_overlay"
|
||||
>
|
||||
<i class="material-icons">get_app</i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="primary-text inline-flex">
|
||||
<i
|
||||
v-if="[1, 4].indexOf(release.EXPLICIT_ALBUM_CONTENT.EXPLICIT_LYRICS_STATUS) != -1"
|
||||
class="material-icons explicit_icon"
|
||||
>explicit</i
|
||||
>
|
||||
{{ release.ALB_TITLE }}
|
||||
</p>
|
||||
<p class="secondary-text">{{ release.ART_NAME + ' - ' + release.NUMBER_TRACK + ' tracks' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="section == 'PLAYLIST'" class="release_grid firstrow_only">
|
||||
<div
|
||||
v-for="release in results.allTab.PLAYLIST.data.slice(0, 10)"
|
||||
class="release clickable"
|
||||
@click="playlistView"
|
||||
:data-id="release.PLAYLIST_ID"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img
|
||||
aria-hidden="true"
|
||||
class="rounded coverart"
|
||||
:src="
|
||||
'https://e-cdns-images.dzcdn.net/images/' +
|
||||
release.PICTURE_TYPE +
|
||||
'/' +
|
||||
release.PLAYLIST_PICTURE +
|
||||
'/156x156-000000-80-0-0.jpg'
|
||||
"
|
||||
/>
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@contextmenu.prevent="openQualityModal"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="'https://deezer.com/playlist/' + release.PLAYLIST_ID"
|
||||
class="download_overlay"
|
||||
>
|
||||
<i class="material-icons">get_app</i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.TITLE }}</p>
|
||||
<p class="secondary-text">{{ release.NB_SONG + ' tracks' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
<div
|
||||
v-if="
|
||||
results.allTab.ORDER.every(section =>
|
||||
section == 'TOP_RESULT' ? results.allTab[section].length == 0 : results.allTab[section].data.length == 0
|
||||
)
|
||||
"
|
||||
>
|
||||
<h1>No results</h1>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ### Track Search Tab ### -->
|
||||
<div id="track_search" class="search_tabcontent">
|
||||
<base-loading-placeholder v-if="!results.trackTab.loaded"></base-loading-placeholder>
|
||||
<div v-else-if="results.trackTab.data.length == 0">
|
||||
<h1>No Tracks found</h1>
|
||||
</div>
|
||||
<table class="table table--tracks" v-if="results.trackTab.data.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">Title</th>
|
||||
<th>Artists</th>
|
||||
<th>Album</th>
|
||||
<th>
|
||||
<i class="material-icons">
|
||||
timer
|
||||
</i>
|
||||
</th>
|
||||
<th style="width: 56px;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="track in results.trackTab.data">
|
||||
<td class="table__icon table__icon--big">
|
||||
<a
|
||||
href="#"
|
||||
@click="playPausePreview"
|
||||
:class="'rounded' + (track.preview ? ' single-cover' : '')"
|
||||
:data-preview="track.preview"
|
||||
>
|
||||
<i
|
||||
@mouseenter="previewMouseEnter"
|
||||
@mouseleave="previewMouseLeave"
|
||||
v-if="track.preview"
|
||||
class="material-icons preview_controls"
|
||||
>
|
||||
play_arrow
|
||||
</i>
|
||||
<img class="rounded coverart" :src="track.album.cover_small" />
|
||||
</a>
|
||||
</td>
|
||||
<td class="table__cell table__cell--large breakline">
|
||||
<div class="table__cell-content table__cell-content--vertical-center">
|
||||
<i v-if="track.explicit_lyrics" class="material-icons explicit_icon">
|
||||
explicit
|
||||
</i>
|
||||
{{
|
||||
track.title +
|
||||
(track.title_version && track.title.indexOf(track.title_version) == -1
|
||||
? ' ' + track.title_version
|
||||
: '')
|
||||
}}
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="table__cell table__cell--medium table__cell--center breakline clickable"
|
||||
@click="artistView"
|
||||
:data-id="track.artist.id"
|
||||
>
|
||||
{{ track.artist.name }}
|
||||
</td>
|
||||
<td
|
||||
class="table__cell table__cell--medium table__cell--center breakline clickable"
|
||||
@click="albumView"
|
||||
:data-id="track.album.id"
|
||||
>
|
||||
{{ track.album.title }}
|
||||
</td>
|
||||
<td class="table__cell table__cell--small table__cell--center">
|
||||
{{ convertDuration(track.duration) }}
|
||||
</td>
|
||||
<td
|
||||
class="table__cell--download table__cell--center clickable"
|
||||
@contextmenu.prevent="openQualityModal"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="track.link"
|
||||
role="button"
|
||||
aria-label="download"
|
||||
>
|
||||
<i class="material-icons">
|
||||
get_app
|
||||
</i>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- ### Album Search Tab ### -->
|
||||
<div id="album_search" class="search_tabcontent">
|
||||
<base-loading-placeholder v-if="!results.albumTab.loaded"></base-loading-placeholder>
|
||||
<div v-else-if="results.albumTab.data.length == 0">
|
||||
<h1>No Albums found</h1>
|
||||
</div>
|
||||
<div class="release_grid" v-if="results.albumTab.data.length > 0">
|
||||
<div
|
||||
v-for="release in results.albumTab.data"
|
||||
class="release clickable"
|
||||
@click="albumView"
|
||||
:data-id="release.id"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img aria-hidden="true" class="rounded coverart" :src="release.cover_medium" />
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@contextmenu.prevent="openQualityModal"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="release.link"
|
||||
class="download_overlay"
|
||||
>
|
||||
<i class="material-icons">get_app</i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="primary-text inline-flex">
|
||||
<i v-if="release.explicit_lyrics" class="material-icons explicit_icon">explicit</i>
|
||||
{{ release.title }}
|
||||
</p>
|
||||
<p class="secondary-text">{{ 'by ' + release.artist.name + ' - ' + release.nb_tracks + ' tracks' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ### Artist Search Tab ### -->
|
||||
<div id="artist_search" class="search_tabcontent">
|
||||
<base-loading-placeholder v-if="!results.artistTab.loaded"></base-loading-placeholder>
|
||||
<div v-else-if="results.artistTab.data.length == 0">
|
||||
<h1>No Artists found</h1>
|
||||
</div>
|
||||
<div class="release_grid" v-if="results.artistTab.data.length > 0">
|
||||
<div
|
||||
v-for="release in results.artistTab.data"
|
||||
class="release clickable"
|
||||
@click="artistView"
|
||||
:data-id="release.id"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img aria-hidden="true" class="circle coverart" :src="release.picture_medium" />
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@contextmenu.prevent="openQualityModal"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="release.link"
|
||||
class="download_overlay"
|
||||
>
|
||||
<i class="material-icons">get_app</i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.name }}</p>
|
||||
<p class="secondary-text">{{ release.nb_album + ' releases' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ### Playlist Search Tab ### -->
|
||||
<div id="playlist_search" class="search_tabcontent">
|
||||
<base-loading-placeholder v-if="!results.playlistTab.loaded"></base-loading-placeholder>
|
||||
<div v-else-if="results.playlistTab.data.length == 0">
|
||||
<h1>No Playlists found</h1>
|
||||
</div>
|
||||
<div class="release_grid" v-if="results.playlistTab.data.length > 0">
|
||||
<div
|
||||
v-for="release in results.playlistTab.data"
|
||||
class="release clickable"
|
||||
@click="playlistView"
|
||||
:data-id="release.id"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img aria-hidden="true" class="rounded coverart" :src="release.picture_medium" />
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@contextmenu.prevent="openQualityModal"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="release.link"
|
||||
class="download_overlay"
|
||||
>
|
||||
<i class="material-icons">get_app</i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.title }}</p>
|
||||
<p class="secondary-text">{{ 'by ' + release.user.name + ' - ' + release.nb_tracks + ' tracks' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { socket } from '@/js/socket.js'
|
||||
import { showView } from '@/js/tabs.js'
|
||||
import Downloads from '@/js/downloads.js'
|
||||
import Utils from '@/js/utils.js'
|
||||
import BaseLoadingPlaceholder from '@components/BaseLoadingPlaceholder.vue'
|
||||
|
||||
import { changeTab } from '@/js/tabs.js'
|
||||
import EventBus from '@/js/EventBus.js'
|
||||
|
||||
export default {
|
||||
name: 'the-main-search-tab',
|
||||
components: {
|
||||
BaseLoadingPlaceholder
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
props: {
|
||||
scrolledSearchType: {
|
||||
type: String,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
EventBus.$on('mainSearch:checkLoadMoreContent', this.checkLoadMoreContent)
|
||||
|
||||
this.$root.$on('mainSearch:showNewResults', this.showNewResults)
|
||||
socket.on('mainSearch', this.handleMainSearch)
|
||||
socket.on('search', this.handleSearch)
|
||||
},
|
||||
methods: {
|
||||
artistView: showView.bind(null, 'artist'),
|
||||
albumView: showView.bind(null, 'album'),
|
||||
playlistView: showView.bind(null, 'playlist'),
|
||||
playPausePreview(e) {
|
||||
EventBus.$emit('trackPreview:playPausePreview', e)
|
||||
},
|
||||
previewMouseEnter(e) {
|
||||
EventBus.$emit('trackPreview:previewMouseEnter', e)
|
||||
},
|
||||
previewMouseLeave(e) {
|
||||
EventBus.$emit('trackPreview:previewMouseLeave', e)
|
||||
},
|
||||
handleSearchTabClick(event) {
|
||||
const {
|
||||
target,
|
||||
target: { id }
|
||||
} = event
|
||||
let selectedTab = null
|
||||
|
||||
switch (id) {
|
||||
case 'search_all_tab':
|
||||
selectedTab = 'main_search'
|
||||
break
|
||||
case 'search_track_tab':
|
||||
selectedTab = 'track_search'
|
||||
break
|
||||
case 'search_album_tab':
|
||||
selectedTab = 'album_search'
|
||||
break
|
||||
case 'search_artist_tab':
|
||||
selectedTab = 'artist_search'
|
||||
break
|
||||
case 'search_playlist_tab':
|
||||
selectedTab = 'playlist_search'
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if (!selectedTab) return
|
||||
|
||||
changeTab(target, 'search', selectedTab)
|
||||
},
|
||||
handleClickTopResult(event) {
|
||||
let topResultType = this.results.allTab.TOP_RESULT[0].type
|
||||
|
||||
switch (topResultType) {
|
||||
case 'artist':
|
||||
this.artistView(event)
|
||||
break
|
||||
case 'album':
|
||||
this.albumView(event)
|
||||
break
|
||||
case 'playlist':
|
||||
this.playlistView(event)
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
},
|
||||
showNewResults(term, mainSelected) {
|
||||
if (term !== this.results.query || mainSelected == 'search_tab') {
|
||||
document.getElementById('search_tab_content').style.display = 'none'
|
||||
socket.emit('mainSearch', { term })
|
||||
|
||||
// Showing loading placeholder
|
||||
document.getElementById('content').style.display = 'none'
|
||||
document.getElementById('search_placeholder').classList.toggle('loading_placeholder--hidden')
|
||||
} else {
|
||||
document.getElementById('search_tab_content').style.display = 'block'
|
||||
document.getElementById('main_search_tablink').click()
|
||||
}
|
||||
},
|
||||
checkLoadMoreContent(searchSelected) {
|
||||
if (this.results[searchSelected.split('_')[0] + 'Tab'].data.length !== 0) return
|
||||
|
||||
this.search(searchSelected.split('_')[0])
|
||||
},
|
||||
changeSearchTab(section) {
|
||||
if (section === 'TOP_RESULT') return
|
||||
|
||||
let tabID
|
||||
|
||||
// Using the switch beacuse it's tricky to find refernces of the belo IDs
|
||||
switch (section) {
|
||||
case 'TRACK':
|
||||
tabID = 'search_track_tab'
|
||||
break
|
||||
case 'ALBUM':
|
||||
tabID = 'search_album_tab'
|
||||
break
|
||||
case 'ARTIST':
|
||||
tabID = 'search_artist_tab'
|
||||
break
|
||||
case 'PLAYLIST':
|
||||
tabID = 'search_playlist_tab'
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
document.getElementById(tabID).click()
|
||||
},
|
||||
addToQueue(e) {
|
||||
Downloads.sendAddToQueue(e.currentTarget.dataset.link)
|
||||
},
|
||||
openQualityModal(e) {
|
||||
this.$root.$emit('QualityModal:open', e.currentTarget.dataset.link)
|
||||
},
|
||||
numberWithDots: Utils.numberWithDots,
|
||||
convertDuration: Utils.convertDuration,
|
||||
search(type) {
|
||||
socket.emit('search', {
|
||||
term: this.results.query,
|
||||
type: type,
|
||||
start: this.results[type + 'Tab'].next,
|
||||
nb: 30
|
||||
})
|
||||
},
|
||||
scrolledSearch(type) {
|
||||
let currentTab = type + 'Tab'
|
||||
|
||||
if (this.results[currentTab].next < this.results[currentTab].total) {
|
||||
socket.emit('search', {
|
||||
term: this.results.query,
|
||||
type: type,
|
||||
start: this.results[currentTab].next,
|
||||
nb: 30
|
||||
})
|
||||
}
|
||||
},
|
||||
handleMainSearch(result) {
|
||||
// Hiding loading placeholder
|
||||
document.getElementById('content').style.display = ''
|
||||
document.getElementById('search_placeholder').classList.toggle('loading_placeholder--hidden')
|
||||
|
||||
let resetObj = { data: [], next: 0, total: 0, loaded: false }
|
||||
|
||||
this.results.allTab = result
|
||||
this.results.trackTab = { ...resetObj }
|
||||
this.results.albumTab = { ...resetObj }
|
||||
this.results.artistTab = { ...resetObj }
|
||||
this.results.playlistTab = { ...resetObj }
|
||||
|
||||
if (this.results.query == '') document.getElementById('search_all_tab').click()
|
||||
|
||||
this.results.query = result.QUERY
|
||||
document.getElementById('search_tab_content').style.display = 'block'
|
||||
document.getElementById('main_search_tablink').click()
|
||||
},
|
||||
handleSearch(result) {
|
||||
const { next: nextResult, total, type, data } = result
|
||||
|
||||
let currentTab = type + 'Tab'
|
||||
let next = 0
|
||||
|
||||
if (nextResult) {
|
||||
next = parseInt(nextResult.match(/index=(\d*)/)[1])
|
||||
} else {
|
||||
next = total
|
||||
}
|
||||
|
||||
if (this.results[currentTab].total != total) {
|
||||
this.results[currentTab].total = total
|
||||
}
|
||||
|
||||
if (this.results[currentTab].next != next) {
|
||||
this.results[currentTab].next = next
|
||||
this.results[currentTab].data = this.results[currentTab].data.concat(data)
|
||||
}
|
||||
|
||||
this.results[currentTab].loaded = true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
scrolledSearchType(newType) {
|
||||
if (!newType) return
|
||||
|
||||
this.scrolledSearch(newType)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -1,31 +0,0 @@
|
||||
<template>
|
||||
<div id="middle_section">
|
||||
<TheSearchBar />
|
||||
<TheContent />
|
||||
|
||||
<div id="search_placeholder" class="loading_placeholder loading_placeholder--hidden">
|
||||
<span class="loading_placeholder__text">Searching...</span>
|
||||
<div class="lds-ring">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TheContent from '@components/TheContent.vue'
|
||||
import TheSearchBar from '@components/TheSearchBar.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TheContent,
|
||||
TheSearchBar
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -1,80 +0,0 @@
|
||||
<template>
|
||||
<div id="modal_quality" class="smallmodal" v-show="open" @click="tryToDownloadTrack($event)" ref="modal">
|
||||
<div class="smallmodal-content">
|
||||
<button class="quality-button" data-quality-value="9">Download FLAC</button>
|
||||
<button class="quality-button" data-quality-value="3">Download MP3 320kbps</button>
|
||||
<button class="quality-button" data-quality-value="1">Download MP3 128kbps</button>
|
||||
<button class="quality-button" data-quality-value="15">Download 360 Reality Audio [HQ]</button>
|
||||
<button class="quality-button" data-quality-value="14">Download 360 Reality Audio [MQ]</button>
|
||||
<button class="quality-button" data-quality-value="13">Download 360 Reality Audio [LQ]</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style>
|
||||
.smallmodal {
|
||||
position: fixed;
|
||||
z-index: 1250;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: hsla(0, 0%, 0%, 0.4);
|
||||
animation-duration: 0.3s;
|
||||
}
|
||||
|
||||
.smallmodal-content {
|
||||
background-color: transparent;
|
||||
margin: auto;
|
||||
width: var(--modal-content-width);
|
||||
position: relative;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.smallmodal-content button {
|
||||
width: 100%;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import Downloads from '@/js/downloads.js'
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
open: false,
|
||||
url: ''
|
||||
}),
|
||||
mounted() {
|
||||
this.$root.$on('QualityModal:open', this.openModal)
|
||||
|
||||
this.$refs.modal.addEventListener('webkitAnimationEnd', this.handleAnimationEnd)
|
||||
},
|
||||
methods: {
|
||||
tryToDownloadTrack(event) {
|
||||
const { target } = event
|
||||
|
||||
this.$refs.modal.classList.add('animated', 'fadeOut')
|
||||
|
||||
// If true, the click did not happen on a button but outside
|
||||
if (!target.matches('.quality-button')) return
|
||||
|
||||
Downloads.sendAddToQueue(this.url, target.dataset.qualityValue)
|
||||
},
|
||||
openModal(link) {
|
||||
this.url = link
|
||||
this.open = true
|
||||
this.$refs.modal.classList.add('animated', 'fadeIn')
|
||||
},
|
||||
handleAnimationEnd(event) {
|
||||
const { animationName } = event
|
||||
|
||||
this.$refs.modal.classList.remove('animated', animationName)
|
||||
|
||||
if (animationName === 'fadeIn') return
|
||||
|
||||
this.open = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,57 +0,0 @@
|
||||
<template>
|
||||
<header id="search">
|
||||
<div class="search__icon">
|
||||
<i class="material-icons">search</i>
|
||||
</div>
|
||||
<input
|
||||
id="searchbar"
|
||||
autocomplete="off"
|
||||
type="search"
|
||||
name="searchbar"
|
||||
value=""
|
||||
placeholder="Search what you want (or just paste a link)"
|
||||
autofocus
|
||||
ref="searchbar"
|
||||
@keyup="handleSearchBarKeyup($event)"
|
||||
/>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isValidURL } from '@/js/utils.js'
|
||||
import Downloads from '@/js/downloads.js'
|
||||
|
||||
import EventBus from '@/js/EventBus.js'
|
||||
import { socket } from '@/js/socket.js'
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
handleSearchBarKeyup(keyEvent) {
|
||||
// Enter key
|
||||
if (keyEvent.keyCode !== 13) return
|
||||
|
||||
let term = this.$refs.searchbar.value
|
||||
|
||||
if (isValidURL(term)) {
|
||||
if (keyEvent.ctrlKey) {
|
||||
this.$root.$emit('QualityModal:open', term)
|
||||
} else {
|
||||
if (main_selected === 'analyzer_tab') {
|
||||
EventBus.$emit('linkAnalyzerTab:reset')
|
||||
socket.emit('analyzeLink', term)
|
||||
} else {
|
||||
Downloads.sendAddToQueue(term)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (term === '') return
|
||||
|
||||
this.$root.$emit('mainSearch:showNewResults', term, main_selected)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -1,665 +0,0 @@
|
||||
<template>
|
||||
<div id="settings_tab" class="main_tabcontent fixed_footer">
|
||||
<h2 class="page_heading">Settings</h2>
|
||||
|
||||
<div id="logged_in_info" ref="loggedInInfo">
|
||||
<img id="settings_picture" src="" alt="Profile Picture" ref="userpicture" class="circle" />
|
||||
<p>You are logged in as <strong id="settings_username" ref="username"></strong></p>
|
||||
<button id="settings_btn_logout" @click="logout">Logout</button>
|
||||
<select v-if="accounts.length" id="family_account" v-model="accountNum" @change="changeAccount">
|
||||
<option v-for="(account, i) in accounts" :value="i.toString()">{{ account.BLOG_NAME }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<h3 class="settings-group__header settings-group__header--with-icon">
|
||||
<i class="material-icons">person</i>Login
|
||||
</h3>
|
||||
<div class="inline-flex">
|
||||
<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>
|
||||
<a href="https://notabug.org/RemixDevs/DeezloaderRemix/wiki/Login+via+userToken" target="_blank">
|
||||
How do I get my own ARL?
|
||||
</a>
|
||||
<button id="settings_btn_updateArl" @click="login" style="width:100%;">Update ARL</button>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<h3 class="settings-group__header settings-group__header--with-icon">
|
||||
<i class="material-icons">web</i>Appearance
|
||||
</h3>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="changeSlimDownloads" />
|
||||
<span class="checkbox_text">Slim download tab</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<h3 class="settings-group__header settings-group__header--with-icon">
|
||||
<i class="material-icons">folder</i>Download Path
|
||||
</h3>
|
||||
<input type="text" v-model="settings.downloadLocation" />
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<h3 class="settings-group__header settings-group__header--with-icon">
|
||||
<i class="material-icons">font_download</i>Templates
|
||||
</h3>
|
||||
|
||||
<p>Trackname template</p>
|
||||
<input type="text" v-model="settings.tracknameTemplate" />
|
||||
|
||||
<p>Album track template</p>
|
||||
<input type="text" v-model="settings.albumTracknameTemplate" />
|
||||
|
||||
<p>Playlist track template</p>
|
||||
<input type="text" v-model="settings.playlistTracknameTemplate" />
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<h3 class="settings-group__header settings-group__header--with-icon">
|
||||
<i class="material-icons">create_new_folder</i>Folders
|
||||
</h3>
|
||||
<div class="settings-container">
|
||||
<div class="settings-container__third">
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.createPlaylistFolder" />
|
||||
<span class="checkbox_text">Create folder for playlist</span>
|
||||
</label>
|
||||
<div class="input_group" v-if="settings.createPlaylistFolder">
|
||||
<p class="input_group_text">Playlist folder template</p>
|
||||
<input type="text" v-model="settings.playlistNameTemplate" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-container__third">
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.createArtistFolder" />
|
||||
<span class="checkbox_text">Create folder for artist</span>
|
||||
</label>
|
||||
|
||||
<div class="input_group" v-if="settings.createArtistFolder">
|
||||
<p class="input_group_text">Artist folder template</p>
|
||||
<input type="text" v-model="settings.artistNameTemplate" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-container__third">
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.createAlbumFolder" />
|
||||
<span class="checkbox_text">Create folder for album</span>
|
||||
</label>
|
||||
|
||||
<div class="input_group" v-if="settings.createAlbumFolder">
|
||||
<p class="input_group_text">Album folder template</p>
|
||||
<input type="text" v-model="settings.albumNameTemplate" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.createCDFolder" />
|
||||
<span class="checkbox_text">Create folder for CDs</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.createStructurePlaylist" />
|
||||
<span class="checkbox_text">Create folder structure for playlists</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.createSingleFolder" />
|
||||
<span class="checkbox_text">Create folder structure for singles</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<h3 class="settings-group__header settings-group__header--with-icon">
|
||||
<i class="material-icons">title</i>Track titles
|
||||
</h3>
|
||||
|
||||
<div class="settings-container">
|
||||
<div class="settings-container__third settings-container__third--only-checkbox">
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.padTracks" />
|
||||
<span class="checkbox_text">Pad tracks</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="settings-container__third">
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">Overwrite padding size</p>
|
||||
<input type="number" v-model="settings.paddingSize" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-container__third">
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">Illegal Character replacer</p>
|
||||
<input type="text" v-model="settings.illegalCharacterReplacer" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<h3 class="settings-group__header settings-group__header--with-icon">
|
||||
<i class="material-icons">get_app</i>Downloads
|
||||
</h3>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">Concurrent Downloads</p>
|
||||
<input type="number" v-model.number="settings.queueConcurrency" />
|
||||
</div>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">Preferred Bitrate</p>
|
||||
<select v-model="settings.maxBitrate">
|
||||
<option value="9">FLAC 1411kbps</option>
|
||||
<option value="3">MP3 320kbps</option>
|
||||
<option value="1">MP3 128kbps</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">Should I overwrite the files?</p>
|
||||
<select v-model="settings.overwriteFile">
|
||||
<option value="y">Yes, overwrite the file</option>
|
||||
<option value="n">No, don't overwrite the file</option>
|
||||
<option value="t">Overwrite only the tags</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="settings-container">
|
||||
<div class="settings-container__third settings-container__third--only-checkbox">
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.fallbackBitrate" />
|
||||
<span class="checkbox_text">Bitrate fallback</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.fallbackSearch" />
|
||||
<span class="checkbox_text">Search fallback</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="settings-container__third settings-container__third--only-checkbox">
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.logErrors" />
|
||||
<span class="checkbox_text">Create log file for errors</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.logSearched" />
|
||||
<span class="checkbox_text">Create log file for searched tracks</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="settings-container__third settings-container__third--only-checkbox">
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.syncedLyrics" />
|
||||
<span class="checkbox_text">Create .lyr files (Sync Lyrics)</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.createM3U8File" />
|
||||
<span class="checkbox_text">Create playlist file</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input_group" v-if="settings.createM3U8File">
|
||||
<p class="input_group_text">Playlist filename template</p>
|
||||
<input type="text" v-model="settings.playlistFilenameTemplate" />
|
||||
</div>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.saveDownloadQueue" />
|
||||
<span class="checkbox_text">Save download queue when closing the app</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<h3 class="settings-group__header settings-group__header--with-icon">
|
||||
<i class="material-icons">album</i>Album covers
|
||||
</h3>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.saveArtwork" />
|
||||
<span class="checkbox_text">Save covers</span>
|
||||
</label>
|
||||
|
||||
<div class="input_group" v-if="settings.saveArtwork">
|
||||
<p class="input_group_text">Cover name template</p>
|
||||
<input type="text" v-model="settings.coverImageTemplate" />
|
||||
</div>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.saveArtworkArtist" />
|
||||
<span class="checkbox_text">Save artist image</span>
|
||||
</label>
|
||||
|
||||
<div class="input_group" v-if="settings.saveArtworkArtist">
|
||||
<p class="input_group_text">Artist image name template</p>
|
||||
<input type="text" v-model="settings.artistImageTemplate" />
|
||||
</div>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">Local artwork size</p>
|
||||
<input type="number" min="100" max="1800" step="100" v-model.number="settings.localArtworkSize" />
|
||||
</div>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">Embedded artwork size</p>
|
||||
<input type="number" min="100" max="1800" step="100" v-model.number="settings.embeddedArtworkSize" />
|
||||
</div>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.PNGcovers" />
|
||||
<span class="checkbox_text">Save images as png</span>
|
||||
</label>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">JPEG image quality</p>
|
||||
<input type="number" min="1" max="100" v-model.number="settings.jpegImageQuality" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<h3 class="settings-group__header settings-group__header--with-icon">
|
||||
<i class="material-icons" style="width: 1em; height: 1em;">bookmarks</i>Which tags to save
|
||||
</h3>
|
||||
|
||||
<div class="settings-container">
|
||||
<div class="settings-container__half">
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.title" />
|
||||
<span class="checkbox_text">Title</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.artist" />
|
||||
<span class="checkbox_text">Artists</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.album" />
|
||||
<span class="checkbox_text">Album</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.cover" />
|
||||
<span class="checkbox_text">Cover</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.trackNumber" />
|
||||
<span class="checkbox_text">Track Number</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.trackTotal" />
|
||||
<span class="checkbox_text">Track Total</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.discNumber" />
|
||||
<span class="checkbox_text">Disc Number</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.discTotal" />
|
||||
<span class="checkbox_text">Disc Total</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.albumArtist" />
|
||||
<span class="checkbox_text">Album Artist</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.genre" />
|
||||
<span class="checkbox_text">Genre</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.year" />
|
||||
<span class="checkbox_text">Year</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.date" />
|
||||
<span class="checkbox_text">Date</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="settings-container__half">
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.explicit" />
|
||||
<span class="checkbox_text">Explicit Lyrics</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.isrc" />
|
||||
<span class="checkbox_text">ISRC</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.length" />
|
||||
<span class="checkbox_text">Track Length</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.barcode" />
|
||||
<span class="checkbox_text">Album Barcode (UPC)</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.bpm" />
|
||||
<span class="checkbox_text">BPM</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.replayGain" />
|
||||
<span class="checkbox_text">Replay Gain</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.label" />
|
||||
<span class="checkbox_text">Album Label</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.lyrics" />
|
||||
<span class="checkbox_text">Unsynchronized Lyrics</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.copyright" />
|
||||
<span class="checkbox_text">Copyright</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.composer" />
|
||||
<span class="checkbox_text">Composer</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.involvedPeople" />
|
||||
<span class="checkbox_text">Involved People</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<h3 class="settings-group__header settings-group__header--with-icon"><i class="material-icons">list</i>Other</h3>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.savePlaylistAsCompilation" />
|
||||
<span class="checkbox_text">Save playlists as compilation</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.useNullSeparator" />
|
||||
<span class="checkbox_text">Use null separator</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.saveID3v1" />
|
||||
<span class="checkbox_text">Save ID3v1 as well</span>
|
||||
</label>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">How would you like to separate your artists?</p>
|
||||
<select v-model="settings.tags.multitagSeparator">
|
||||
<option value="default">Using standard specification</option>
|
||||
<option value="andFeat">Using & and feat.</option>
|
||||
<option value=" & ">Using " & "</option>
|
||||
<option value=",">Using ","</option>
|
||||
<option value=", ">Using ", "</option>
|
||||
<option value="/">Using "/"</option>
|
||||
<option value=" / ">Using "/ "</option>
|
||||
<option value=";">Using ";"</option>
|
||||
<option value="; ">Using "; "</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.albumVariousArtists" />
|
||||
<span class="checkbox_text">Keep "Various Artists" in the Album Artists</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.removeAlbumVersion" />
|
||||
<span class="checkbox_text">Remove "album version" from track title</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.removeDuplicateArtists" />
|
||||
<span class="checkbox_text">Remove combinations of artists</span>
|
||||
</label>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">Date format for FLAC files</p>
|
||||
<select v-model="settings.dateFormat">
|
||||
<option value="Y-M-D">YYYY-MM-DD</option>
|
||||
<option value="Y-D-M">YYYY-DD-MM</option>
|
||||
<option value="D-M-Y">DD-MM-YYYY</option>
|
||||
<option value="M-D-Y">MM-DD-YYYY</option>
|
||||
<option value="Y">YYYY</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">What should I do with featured artists?</p>
|
||||
<select v-model="settings.featuredToTitle">
|
||||
<option value="0">Nothing</option>
|
||||
<option value="1">Remove it from the title</option>
|
||||
<option value="3">Remove it from the title and the album title</option>
|
||||
<option value="2">Move it to the title</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">Title casing</p>
|
||||
<select v-model="settings.titleCasing">
|
||||
<option value="nothing">Keep unchanged</option>
|
||||
<option value="lower">lowercase</option>
|
||||
<option value="upper">UPPERCASE</option>
|
||||
<option value="start">Start Of Each Word</option>
|
||||
<option value="sentence">Like a sentence</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">Artist casing</p>
|
||||
<select v-model="settings.artistCasing">
|
||||
<option value="nothing">Keep unchanged</option>
|
||||
<option value="lower">lowercase</option>
|
||||
<option value="upper">UPPERCASE</option>
|
||||
<option value="start">Start Of Each Word</option>
|
||||
<option value="sentence">Like a sentence</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">Preview Volume</p>
|
||||
<input
|
||||
type="range"
|
||||
@change="updateMaxVolume"
|
||||
min="0"
|
||||
max="100"
|
||||
step="1"
|
||||
class="slider"
|
||||
v-model.number="previewVolume.preview_max_volume"
|
||||
/>
|
||||
<span>{{ previewVolume.preview_max_volume }}%</span>
|
||||
</div>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">Command to execute after download</p>
|
||||
<p class="secondary-text">Leave blank for no action</p>
|
||||
<input type="text" v-model="settings.executeCommand" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<h3 class="settings-group__header settings-group__header--with-icon">
|
||||
<svg id="spotify_icon" enable-background="new 0 0 24 24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="m12 24c6.624 0 12-5.376 12-12s-5.376-12-12-12-12 5.376-12 12 5.376 12 12 12zm4.872-6.344v.001c-.807 0-3.356-2.828-10.52-1.36-.189.049-.436.126-.576.126-.915 0-1.09-1.369-.106-1.578 3.963-.875 8.013-.798 11.467 1.268.824.526.474 1.543-.265 1.543zm1.303-3.173c-.113-.03-.08.069-.597-.203-3.025-1.79-7.533-2.512-11.545-1.423-.232.063-.358.126-.576.126-1.071 0-1.355-1.611-.188-1.94 4.716-1.325 9.775-.552 13.297 1.543.392.232.547.533.547.953-.005.522-.411.944-.938.944zm-13.627-7.485c4.523-1.324 11.368-.906 15.624 1.578 1.091.629.662 2.22-.498 2.22l-.001-.001c-.252 0-.407-.063-.625-.189-3.443-2.056-9.604-2.549-13.59-1.436-.175.048-.393.125-.625.125-.639 0-1.127-.499-1.127-1.142 0-.657.407-1.029.842-1.155z"
|
||||
/>
|
||||
</svg>
|
||||
Spotify Features
|
||||
</h3>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">Spotify clientID</p>
|
||||
<input type="text" v-model="spotifyFeatures.clientId" />
|
||||
</div>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">Spotify Client Secret</p>
|
||||
<input type="password" v-model="spotifyFeatures.clientSecret" />
|
||||
</div>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">Spotify username</p>
|
||||
<input type="text" v-model="spotifyUser" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<button @click="resetSettings">Reset to Default</button>
|
||||
<button @click="saveSettings">Save</button>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { toast } from '@/js/toasts.js'
|
||||
import { socket } from '@/js/socket.js'
|
||||
import EventBus from '@/js/EventBus'
|
||||
|
||||
export default {
|
||||
name: 'the-settings-tab',
|
||||
data: () => ({
|
||||
settings: { tags: {} },
|
||||
lastSettings: {},
|
||||
spotifyFeatures: {},
|
||||
lastCredentials: {},
|
||||
defaultSettings: {},
|
||||
lastUser: '',
|
||||
spotifyUser: '',
|
||||
slimDownloads: false,
|
||||
previewVolume: window.vol,
|
||||
accountNum: 0,
|
||||
accounts: []
|
||||
}),
|
||||
computed: {
|
||||
changeSlimDownloads: {
|
||||
get() {
|
||||
return this.slimDownloads
|
||||
},
|
||||
set(wantSlimDownloads) {
|
||||
this.slimDownloads = wantSlimDownloads
|
||||
document.getElementById('download_list').classList.toggle('slim', wantSlimDownloads)
|
||||
localStorage.setItem('slimDownloads', wantSlimDownloads)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
revertSettings() {
|
||||
this.settings = { ...this.lastSettings }
|
||||
},
|
||||
revertCredentials() {
|
||||
this.spotifyCredentials = { ...this.lastCredentials }
|
||||
this.spotifyUser = (' ' + this.lastUser).slice(1)
|
||||
},
|
||||
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')
|
||||
},
|
||||
updateMaxVolume() {
|
||||
localStorage.setItem('previewVolume', this.previewVolume.preview_max_volume)
|
||||
},
|
||||
saveSettings() {
|
||||
this.lastSettings = { ...this.settings }
|
||||
this.lastCredentials = { ...this.spotifyFeatures }
|
||||
let changed = false
|
||||
if (this.lastUser != this.spotifyUser) {
|
||||
// force cloning without linking
|
||||
this.lastUser = (' ' + this.spotifyUser).slice(1)
|
||||
localStorage.setItem('spotifyUser', this.lastUser)
|
||||
changed = true
|
||||
}
|
||||
|
||||
socket.emit('saveSettings', this.lastSettings, this.lastCredentials, changed ? this.lastUser : false)
|
||||
},
|
||||
loadSettings(settings, spotifyCredentials, defaults = null) {
|
||||
if (defaults) {
|
||||
this.defaultSettings = { ...defaults }
|
||||
}
|
||||
|
||||
this.lastSettings = { ...settings }
|
||||
this.lastCredentials = { ...spotifyCredentials }
|
||||
this.settings = settings
|
||||
this.spotifyFeatures = spotifyCredentials
|
||||
},
|
||||
login() {
|
||||
let arl = this.$refs.loginInput.value.trim()
|
||||
if (arl != '' && arl != localStorage.getItem('arl')) {
|
||||
socket.emit('login', arl, true, this.accountNum)
|
||||
}
|
||||
},
|
||||
changeAccount() {
|
||||
socket.emit('changeAccount', this.accountNum)
|
||||
},
|
||||
accountChanged(user, accountNum) {
|
||||
this.$refs.username.innerText = user.name
|
||||
this.$refs.userpicture.src = `https://e-cdns-images.dzcdn.net/images/user/${user.picture}/125x125-000000-80-0-0.jpg`
|
||||
this.accountNum = accountNum
|
||||
localStorage.setItem('accountNum', this.accountNum)
|
||||
},
|
||||
initAccounts(accounts) {
|
||||
this.accounts = accounts
|
||||
},
|
||||
logout() {
|
||||
socket.emit('logout')
|
||||
},
|
||||
initSettings(settings, credentials, defaults) {
|
||||
this.loadSettings(settings, credentials, defaults)
|
||||
toast('Settings loaded!', 'settings')
|
||||
},
|
||||
updateSettings(settings, credentials) {
|
||||
this.loadSettings(settings, credentials)
|
||||
toast('Settings updated!', 'settings')
|
||||
},
|
||||
resetSettings() {
|
||||
this.settings = { ...this.defaultSettings }
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
EventBus.$on('settingsTab:revertSettings', this.revertSettings)
|
||||
EventBus.$on('settingsTab:revertCredentials', this.revertCredentials)
|
||||
|
||||
this.$refs.loggedInInfo.classList.add('hide')
|
||||
|
||||
if (localStorage.getItem('arl')) {
|
||||
this.$refs.loginInput.value = localStorage.getItem('arl').trim()
|
||||
}
|
||||
if (localStorage.getItem('accountNum')) {
|
||||
this.accountNum = localStorage.getItem('accountNum')
|
||||
}
|
||||
|
||||
let spotifyUser = localStorage.getItem('spotifyUser')
|
||||
|
||||
if (spotifyUser) {
|
||||
this.lastUser = spotifyUser
|
||||
this.spotifyUser = spotifyUser
|
||||
socket.emit('update_userSpotifyPlaylists', spotifyUser)
|
||||
}
|
||||
|
||||
this.changeSlimDownloads = 'true' === localStorage.getItem('slimDownloads')
|
||||
|
||||
let volume = parseInt(localStorage.getItem('previewVolume'))
|
||||
if (isNaN(volume)) {
|
||||
volume = 80
|
||||
localStorage.setItem('previewVolume', volume)
|
||||
}
|
||||
window.vol.preview_max_volume = volume
|
||||
|
||||
socket.on('init_settings', this.initSettings)
|
||||
socket.on('updateSettings', this.updateSettings)
|
||||
socket.on('accountChanged', this.accountChanged)
|
||||
socket.on('familyAccounts', this.initAccounts)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -1,185 +0,0 @@
|
||||
<template>
|
||||
<aside id="sidebar" role="navigation" @click="handleSidebarClick">
|
||||
<span id="main_home_tablink" class="main_tablinks" role="link" aria-label="home">
|
||||
<i class="material-icons side_icon">home</i>
|
||||
<span class="main_tablinks_text">Home</span>
|
||||
</span>
|
||||
<span id="main_search_tablink" class="main_tablinks" role="link" aria-label="search">
|
||||
<i class="material-icons side_icon">search</i>
|
||||
<span class="main_tablinks_text">Search</span>
|
||||
</span>
|
||||
<span id="main_charts_tablink" class="main_tablinks" role="link" aria-label="charts">
|
||||
<i class="material-icons side_icon">bubble_chart</i>
|
||||
<span class="main_tablinks_text">Charts</span>
|
||||
</span>
|
||||
<span id="main_favorites_tablink" class="main_tablinks" role="link" aria-label="favorites">
|
||||
<i class="material-icons side_icon">album</i>
|
||||
<span class="main_tablinks_text">Favorites</span>
|
||||
</span>
|
||||
<span id="main_analyzer_tablink" class="main_tablinks" role="link" aria-label="link analyzer">
|
||||
<i class="material-icons side_icon">link</i>
|
||||
<span class="main_tablinks_text">Link Analyzer</span>
|
||||
</span>
|
||||
<span id="main_settings_tablink" class="main_tablinks" role="link" aria-label="settings">
|
||||
<i class="material-icons side_icon">settings</i>
|
||||
<span class="main_tablinks_text">Settings</span>
|
||||
</span>
|
||||
<span id="main_about_tablink" class="main_tablinks" role="link" aria-label="info">
|
||||
<i class="material-icons side_icon">info</i>
|
||||
<span class="main_tablinks_text">About</span>
|
||||
</span>
|
||||
<span id="theme_selector" class="main_tablinks" role="link" aria-label="theme selector">
|
||||
<i class="material-icons side_icon side_icon--theme">palette</i>
|
||||
<div id="theme_togglers">
|
||||
<div
|
||||
class="theme_toggler theme_toggler--purple"
|
||||
:class="{ 'theme_toggler--active': activeTheme === 'purple' }"
|
||||
@click="changeTheme('purple')"
|
||||
/>
|
||||
<div
|
||||
class="theme_toggler theme_toggler--dark"
|
||||
:class="{ 'theme_toggler--active': activeTheme === 'dark' }"
|
||||
@click="changeTheme('dark')"
|
||||
/>
|
||||
<div
|
||||
class="theme_toggler theme_toggler--light"
|
||||
:class="{ 'theme_toggler--active': activeTheme === 'light' }"
|
||||
@click="changeTheme('light')"
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
<div id="network-status" :class="{ online: appOnline, offline: !appOnline }">
|
||||
<i v-if="appOnline" class="material-icons">wifi</i>
|
||||
<i v-else class="material-icons">
|
||||
<!-- wifi_off icon not working, maybe need to include it? -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M24 .01c0-.01 0-.01 0 0L0 0v24h24V.01zM0 0h24v24H0V0zm0 0h24v24H0V0z" fill="none" />
|
||||
<path
|
||||
d="M22.99 9C19.15 5.16 13.8 3.76 8.84 4.78l2.52 2.52c3.47-.17 6.99 1.05 9.63 3.7l2-2zm-4 4c-1.29-1.29-2.84-2.13-4.49-2.56l3.53 3.53.96-.97zM2 3.05L5.07 6.1C3.6 6.82 2.22 7.78 1 9l1.99 2c1.24-1.24 2.67-2.16 4.2-2.77l2.24 2.24C7.81 10.89 6.27 11.73 5 13v.01L6.99 15c1.36-1.36 3.14-2.04 4.92-2.06L18.98 20l1.27-1.26L3.29 1.79 2 3.05zM9 17l3 3 3-3c-1.65-1.66-4.34-1.66-6 0z"
|
||||
/>
|
||||
</svg>
|
||||
</i>
|
||||
</div>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
#network-status {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
margin-top: auto;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
#network-status.online i.material-icons {
|
||||
color: hsl(151, 100%, 31%);
|
||||
}
|
||||
|
||||
#network-status.offline i.material-icons svg {
|
||||
fill: red;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { changeTab } from '@/js/tabs.js'
|
||||
|
||||
export default {
|
||||
name: 'the-sidebar',
|
||||
data() {
|
||||
return {
|
||||
appOnline: null,
|
||||
activeTheme: 'light'
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
/* === Online status handling === */
|
||||
this.appOnline = navigator.onLine
|
||||
|
||||
window.addEventListener('online', () => {
|
||||
this.appOnline = true
|
||||
})
|
||||
|
||||
window.addEventListener('offline', () => {
|
||||
this.appOnline = false
|
||||
})
|
||||
|
||||
/* === Current theme handling === */
|
||||
this.activeTheme = localStorage.getItem('selectedTheme') || 'light'
|
||||
},
|
||||
methods: {
|
||||
changeTheme(newTheme) {
|
||||
if (newTheme === this.activeTheme) return
|
||||
|
||||
this.activeTheme = newTheme
|
||||
document.documentElement.setAttribute('data-theme', newTheme)
|
||||
localStorage.setItem('selectedTheme', newTheme)
|
||||
|
||||
// Animating everything to have a smoother theme switch
|
||||
document.querySelectorAll('*').forEach(el => {
|
||||
el.style.transition = 'all 200ms ease-in-out'
|
||||
})
|
||||
|
||||
document.documentElement.addEventListener('transitionend', function transitionHandler() {
|
||||
document.querySelectorAll('*').forEach(el => {
|
||||
el.style.transition = ''
|
||||
})
|
||||
|
||||
document.documentElement.removeEventListener('transitionend', transitionHandler)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* Handles click Event on the sidebar and changes tab
|
||||
* according to clicked icon.
|
||||
* Uses event delegation
|
||||
* @param {Event} event
|
||||
*/
|
||||
handleSidebarClick(event) {
|
||||
const { target } = event
|
||||
|
||||
const wantToChangeTab = target.matches('.main_tablinks') || target.parentElement.matches('.main_tablinks')
|
||||
|
||||
if (!wantToChangeTab) return
|
||||
|
||||
let sidebarEl = target.matches('.main_tablinks') ? target : target.parentElement
|
||||
let targetID = sidebarEl.id
|
||||
let selectedTab = null
|
||||
|
||||
switch (targetID) {
|
||||
case 'main_search_tablink':
|
||||
selectedTab = 'search_tab'
|
||||
break
|
||||
case 'main_home_tablink':
|
||||
selectedTab = 'home_tab'
|
||||
break
|
||||
case 'main_charts_tablink':
|
||||
selectedTab = 'charts_tab'
|
||||
break
|
||||
case 'main_favorites_tablink':
|
||||
selectedTab = 'favorites_tab'
|
||||
break
|
||||
case 'main_analyzer_tablink':
|
||||
selectedTab = 'analyzer_tab'
|
||||
break
|
||||
case 'main_settings_tablink':
|
||||
selectedTab = 'settings_tab'
|
||||
break
|
||||
case 'main_about_tablink':
|
||||
selectedTab = 'about_tab'
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if (!selectedTab) return
|
||||
|
||||
changeTab(sidebarEl, 'main', selectedTab)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
<template>
|
||||
<audio id="preview-track" @canplay="onCanPlay" @timeupdate="onTimeUpdate" ref="preview">
|
||||
<source id="preview-track_source" src="" type="audio/mpeg" />
|
||||
</audio>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import $ from 'jquery'
|
||||
import EventBus from '@/js/EventBus'
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
previewStopped: false
|
||||
}),
|
||||
mounted() {
|
||||
this.$refs.preview.volume = 1
|
||||
|
||||
EventBus.$on('trackPreview:playPausePreview', this.playPausePreview)
|
||||
EventBus.$on('trackPreview:stopStackedTabsPreview', this.stopStackedTabsPreview)
|
||||
EventBus.$on('trackPreview:previewMouseEnter', this.previewMouseEnter)
|
||||
EventBus.$on('trackPreview:previewMouseLeave', this.previewMouseLeave)
|
||||
},
|
||||
methods: {
|
||||
async onCanPlay() {
|
||||
await this.$refs.preview.play()
|
||||
|
||||
this.previewStopped = false
|
||||
$(this.$refs.preview).animate({ volume: vol.preview_max_volume / 100 }, 500)
|
||||
},
|
||||
onTimeUpdate() {
|
||||
// Prevents first time entering in this function
|
||||
if (isNaN(this.$refs.preview.duration)) return
|
||||
if (this.$refs.preview.currentTime <= this.$refs.preview.duration - 1) return
|
||||
|
||||
$(this.$refs.preview).animate({ volume: 0 }, 800)
|
||||
|
||||
this.previewStopped = true
|
||||
|
||||
$('a[playing] > .preview_controls').css({ opacity: 0 })
|
||||
$('*').removeAttr('playing')
|
||||
$('.preview_controls').text('play_arrow')
|
||||
$('.preview_playlist_controls').text('play_arrow')
|
||||
},
|
||||
playPausePreview(e) {
|
||||
e.preventDefault()
|
||||
|
||||
const { currentTarget: obj } = event
|
||||
|
||||
var $icon = obj.tagName == 'I' ? $(obj) : $(obj).children('i')
|
||||
|
||||
if ($(obj).attr('playing')) {
|
||||
if (this.$refs.preview.paused) {
|
||||
this.$refs.preview.play()
|
||||
this.previewStopped = false
|
||||
|
||||
$icon.text('pause')
|
||||
|
||||
$(this.$refs.preview).animate({ volume: vol.preview_max_volume / 100 }, 500)
|
||||
} else {
|
||||
this.previewStopped = true
|
||||
|
||||
$icon.text('play_arrow')
|
||||
|
||||
$(this.$refs.preview).animate({ volume: 0 }, 250, 'swing', () => {
|
||||
this.$refs.preview.pause()
|
||||
})
|
||||
}
|
||||
} else {
|
||||
$('*').removeAttr('playing')
|
||||
$(obj).attr('playing', true)
|
||||
|
||||
$('.preview_controls').text('play_arrow')
|
||||
$('.preview_playlist_controls').text('play_arrow')
|
||||
$('.preview_controls').css({ opacity: 0 })
|
||||
|
||||
$icon.text('pause')
|
||||
$icon.css({ opacity: 1 })
|
||||
|
||||
this.previewStopped = false
|
||||
|
||||
$(this.$refs.preview).animate({ volume: 0 }, 250, 'swing', () => {
|
||||
this.$refs.preview.pause()
|
||||
$('#preview-track_source').prop('src', $(obj).data('preview'))
|
||||
this.$refs.preview.load()
|
||||
})
|
||||
}
|
||||
},
|
||||
stopStackedTabsPreview() {
|
||||
if (
|
||||
$('.preview_playlist_controls').filter(function() {
|
||||
return $(this).attr('playing')
|
||||
}).length > 0
|
||||
) {
|
||||
$(this.$refs.preview).animate({ volume: 0 }, 800)
|
||||
this.previewStopped = true
|
||||
$('.preview_playlist_controls').removeAttr('playing')
|
||||
$('.preview_playlist_controls').text('play_arrow')
|
||||
}
|
||||
},
|
||||
previewMouseEnter(e) {
|
||||
$(e.currentTarget).css({ opacity: 1 })
|
||||
},
|
||||
previewMouseLeave(event) {
|
||||
const { currentTarget: obj } = event
|
||||
|
||||
if (
|
||||
($(obj)
|
||||
.parent()
|
||||
.attr('playing') &&
|
||||
this.previewStopped) ||
|
||||
!$(obj)
|
||||
.parent()
|
||||
.attr('playing')
|
||||
) {
|
||||
$(obj).css({ opacity: 0 }, 200)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -1,300 +0,0 @@
|
||||
<template>
|
||||
<div id="tracklist_tab" class="main_tabcontent fixed_footer image_header">
|
||||
<header
|
||||
: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_icon explicit_icon--right">explicit</i>
|
||||
</h1>
|
||||
<h2 class="inline-flex">
|
||||
<span v-if="metadata">{{ metadata }}</span
|
||||
><span class="right" v-if="release_date">{{ release_date }}</span>
|
||||
</h2>
|
||||
</header>
|
||||
|
||||
<table class="table table--tracklist">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<i class="material-icons">music_note</i>
|
||||
</th>
|
||||
<th>#</th>
|
||||
<th>Song</th>
|
||||
<th>Artist</th>
|
||||
<th v-if="type == 'Playlist'">Album</th>
|
||||
<th>
|
||||
<i class="material-icons">timer</i>
|
||||
</th>
|
||||
<th class="table__icon table__cell--center clickable">
|
||||
<input @click="toggleAll" class="selectAll" type="checkbox" />
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-if="type !== 'Spotify Playlist'">
|
||||
<template v-for="track in body">
|
||||
<tr v-if="track.type == 'track'">
|
||||
<td class="table__cell--x-small table__cell--center">
|
||||
<div class="table__cell-content table__cell-content--vertical-center">
|
||||
<i
|
||||
class="material-icons"
|
||||
:class="{ preview_playlist_controls: track.preview, disabled: !track.preview }"
|
||||
v-on="{ click: track.preview ? playPausePreview : false }"
|
||||
:data-preview="track.preview"
|
||||
>
|
||||
play_arrow
|
||||
</i>
|
||||
</div>
|
||||
</td>
|
||||
<td class="table__cell--small table__cell--center track_position">
|
||||
{{ type === 'Album' ? track.track_position : body.indexOf(track) + 1 }}
|
||||
</td>
|
||||
<td class="table__cell--large table__cell--with-icon">
|
||||
<div class="table__cell-content table__cell-content--vertical-center">
|
||||
<i v-if="track.explicit_lyrics" class="material-icons explicit_icon">
|
||||
explicit
|
||||
</i>
|
||||
{{
|
||||
track.title +
|
||||
(track.title_version && track.title.indexOf(track.title_version) == -1
|
||||
? ' ' + track.title_version
|
||||
: '')
|
||||
}}
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="table__cell--medium table__cell--center clickable"
|
||||
@click="artistView"
|
||||
:data-id="track.artist.id"
|
||||
>
|
||||
{{ track.artist.name }}
|
||||
</td>
|
||||
<td
|
||||
v-if="type == 'Playlist'"
|
||||
class="table__cell--medium table__cell--center clickable"
|
||||
@click="albumView"
|
||||
:data-id="track.album.id"
|
||||
>
|
||||
{{ track.album.title }}
|
||||
</td>
|
||||
<td
|
||||
class="table__cell--center"
|
||||
:class="{ 'table__cell--small': type === 'Album', 'table__cell--x-small': type === 'Playlist' }"
|
||||
>
|
||||
{{ convertDuration(track.duration) }}
|
||||
</td>
|
||||
<td class="table__icon table__cell--center">
|
||||
<input class="clickable" type="checkbox" v-model="track.selected" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-else-if="track.type == 'disc_separator'" class="table__row-no-highlight" style="opacity: 0.54;">
|
||||
<td>
|
||||
<div class="table__cell-content table__cell-content--vertical-center" style="opacity: 0.54;">
|
||||
<i class="material-icons">album</i>
|
||||
</div>
|
||||
</td>
|
||||
<td class="table__cell--center">
|
||||
{{ track.number }}
|
||||
</td>
|
||||
<td colspan="4"></td>
|
||||
</tr>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<tr v-for="(track, i) in body">
|
||||
<td>
|
||||
<i
|
||||
v-if="track.preview_url"
|
||||
@click="playPausePreview"
|
||||
:class="'material-icons' + (track.preview_url ? ' preview_playlist_controls' : '')"
|
||||
:data-preview="track.preview_url"
|
||||
>play_arrow</i
|
||||
>
|
||||
<i v-else class="material-icons disabled">play_arrow</i>
|
||||
</td>
|
||||
<td>{{ i + 1 }}</td>
|
||||
<td class="inline-flex">
|
||||
<i v-if="track.explicit" class="material-icons explicit_icon">explicit</i>
|
||||
{{ track.name }}
|
||||
</td>
|
||||
<td>{{ track.artists[0].name }}</td>
|
||||
<td>{{ track.album.name }}</td>
|
||||
<td>{{ convertDuration(Math.floor(track.duration_ms / 1000)) }}</td>
|
||||
<td><input class="clickable" type="checkbox" v-model="track.selected" /></td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
<span v-if="label" style="opacity: 0.40;margin-top: 8px;display: inline-block;font-size: 13px;">{{ label }}</span>
|
||||
<footer>
|
||||
<button @contextmenu.prevent="openQualityModal" @click.stop="addToQueue" :data-link="link">
|
||||
Download {{ type }}
|
||||
</button>
|
||||
<button
|
||||
class="with_icon"
|
||||
@contextmenu.prevent="openQualityModal"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="selectedLinks()"
|
||||
>
|
||||
Download selection<i class="material-icons">file_download</i>
|
||||
</button>
|
||||
<button class="back-button">Back</button>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isEmpty } from 'lodash-es'
|
||||
import { socket } from '@/js/socket.js'
|
||||
import { showView } from '@/js/tabs.js'
|
||||
import Downloads from '@/js/downloads.js'
|
||||
import Utils from '@/js/utils.js'
|
||||
import EventBus from '@/js/EventBus'
|
||||
|
||||
export default {
|
||||
name: 'tracklist-tab',
|
||||
data: () => ({
|
||||
title: '',
|
||||
metadata: '',
|
||||
release_date: '',
|
||||
label: '',
|
||||
explicit: false,
|
||||
image: '',
|
||||
type: '',
|
||||
link: '',
|
||||
body: []
|
||||
}),
|
||||
methods: {
|
||||
artistView: showView.bind(null, 'artist'),
|
||||
albumView: showView.bind(null, 'album'),
|
||||
playPausePreview(e) {
|
||||
EventBus.$emit('trackPreview:playPausePreview', e)
|
||||
},
|
||||
reset() {
|
||||
this.title = 'Loading...'
|
||||
this.image = ''
|
||||
this.metadata = ''
|
||||
this.label = ''
|
||||
this.release_date = ''
|
||||
this.explicit = false
|
||||
this.type = ''
|
||||
this.body = []
|
||||
},
|
||||
addToQueue(e) {
|
||||
Downloads.sendAddToQueue(e.currentTarget.dataset.link)
|
||||
},
|
||||
openQualityModal(e) {
|
||||
this.$root.$emit('QualityModal:open', e.currentTarget.dataset.link)
|
||||
},
|
||||
toggleAll(e) {
|
||||
this.body.forEach(item => {
|
||||
if (item.type == 'track') {
|
||||
item.selected = e.currentTarget.checked
|
||||
}
|
||||
})
|
||||
},
|
||||
selectedLinks() {
|
||||
var selected = []
|
||||
if (this.body) {
|
||||
this.body.forEach(item => {
|
||||
if (item.type == 'track' && item.selected)
|
||||
selected.push(this.type == 'Spotify Playlist' ? item.uri : item.link)
|
||||
})
|
||||
}
|
||||
return selected.join(';')
|
||||
},
|
||||
convertDuration: Utils.convertDuration,
|
||||
showAlbum(data) {
|
||||
const {
|
||||
id: albumID,
|
||||
title: albumTitle,
|
||||
explicit_lyrics,
|
||||
label: albumLabel,
|
||||
artist: { name: artistName },
|
||||
tracks: albumTracks,
|
||||
tracks: { length: numberOfTracks },
|
||||
release_date,
|
||||
cover_xl
|
||||
} = data
|
||||
|
||||
this.type = 'Album'
|
||||
this.link = `https://www.deezer.com/album/${albumID}`
|
||||
this.title = albumTitle
|
||||
this.explicit = explicit_lyrics
|
||||
this.label = albumLabel
|
||||
this.metadata = `${artistName} • ${numberOfTracks} songs`
|
||||
this.release_date = release_date.substring(0, 10)
|
||||
this.image = cover_xl
|
||||
|
||||
if (isEmpty(albumTracks)) {
|
||||
this.body = null
|
||||
} else {
|
||||
this.body = albumTracks
|
||||
}
|
||||
},
|
||||
showPlaylist(data) {
|
||||
const {
|
||||
id: playlistID,
|
||||
title: playlistTitle,
|
||||
picture_xl: playlistCover,
|
||||
creation_date,
|
||||
creator: { name: creatorName },
|
||||
tracks: playlistTracks,
|
||||
tracks: { length: numberOfTracks }
|
||||
} = data
|
||||
|
||||
this.type = 'Playlist'
|
||||
this.link = `https://www.deezer.com/playlist/${playlistID}`
|
||||
this.title = playlistTitle
|
||||
this.image = playlistCover
|
||||
this.release_date = creation_date.substring(0, 10)
|
||||
this.metadata = `by ${creatorName} • ${numberOfTracks} songs`
|
||||
|
||||
if (isEmpty(playlistTracks)) {
|
||||
this.body = null
|
||||
} else {
|
||||
this.body = playlistTracks
|
||||
}
|
||||
},
|
||||
showSpotifyPlaylist(data) {
|
||||
const {
|
||||
uri: playlistURI,
|
||||
name: playlistName,
|
||||
images,
|
||||
images: { length: numberOfImages },
|
||||
owner: { display_name: ownerName },
|
||||
tracks: playlistTracks,
|
||||
tracks: { length: numberOfTracks }
|
||||
} = data
|
||||
|
||||
this.type = 'Spotify Playlist'
|
||||
this.link = playlistURI
|
||||
this.title = playlistName
|
||||
this.image = numberOfImages
|
||||
? images[0].url
|
||||
: 'https://e-cdns-images.dzcdn.net/images/cover/d41d8cd98f00b204e9800998ecf8427e/1000x1000-000000-80-0-0.jpg'
|
||||
this.release_date = ''
|
||||
this.metadata = `by ${ownerName} • ${numberOfTracks} songs`
|
||||
|
||||
if (isEmpty(playlistTracks)) {
|
||||
this.body = null
|
||||
} else {
|
||||
this.body = playlistTracks
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
EventBus.$on('tracklistTab:reset', this.reset)
|
||||
|
||||
socket.on('show_album', this.showAlbum)
|
||||
socket.on('show_playlist', this.showPlaylist)
|
||||
socket.on('show_spotifyplaylist', this.showSpotifyPlaylist)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -1,11 +0,0 @@
|
||||
import { socket } from '@/js/socket.js'
|
||||
|
||||
function sendAddToQueue(url, bitrate = null) {
|
||||
if (url != '') {
|
||||
socket.emit('addToQueue', { url: url, bitrate: bitrate }, () => {})
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
sendAddToQueue
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
export const socket = io.connect(window.location.href)
|
||||
|
||||
socket.on('connect', () => {
|
||||
document.getElementById('start_app_placeholder').classList.add('loading_placeholder--hidden')
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
import { socket } from '@/js/socket.js'
|
||||
import EventBus from '@/js/EventBus'
|
||||
import { socket } from '@/utils/socket'
|
||||
import EventBus from '@/utils/EventBus'
|
||||
|
||||
/* ===== Globals ====== */
|
||||
window.search_selected = ''
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import Toastify from 'toastify-js'
|
||||
import $ from 'jquery'
|
||||
|
||||
import { socket } from './socket.js'
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
socket.on('toast', data => {
|
||||
const { msg, icon, dismiss, id } = data
|
||||
|
||||
toast(msg, icon || null, dismiss !== undefined ? dismiss : true, id || null)
|
||||
})
|
||||
316
src/js/utils.js
316
src/js/utils.js
@@ -1,316 +0,0 @@
|
||||
export function isValidURL(text) {
|
||||
let lowerCaseText = text.toLowerCase()
|
||||
|
||||
if (lowerCaseText.startsWith('http')) {
|
||||
if (lowerCaseText.indexOf('deezer.com') >= 0 || lowerCaseText.indexOf('open.spotify.com') >= 0) {
|
||||
return true
|
||||
}
|
||||
} else if (lowerCaseText.startsWith('spotify:')) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function 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 convertDurationSeparated(duration) {
|
||||
let hh, mm, ss
|
||||
mm = Math.floor(duration / 60)
|
||||
hh = Math.floor(mm / 60)
|
||||
ss = duration - mm * 60
|
||||
mm -= hh * 60
|
||||
return [hh, mm, ss]
|
||||
}
|
||||
|
||||
export function numberWithDots(x) {
|
||||
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.')
|
||||
}
|
||||
|
||||
// On scroll event, returns currentTarget = null
|
||||
// Probably on other events too
|
||||
export function debounce(func, wait, immediate) {
|
||||
var timeout
|
||||
return function() {
|
||||
var context = this
|
||||
var args = arguments
|
||||
var later = function() {
|
||||
timeout = null
|
||||
if (!immediate) func.apply(context, args)
|
||||
}
|
||||
var callNow = immediate && !timeout
|
||||
clearTimeout(timeout)
|
||||
timeout = setTimeout(later, wait)
|
||||
if (callNow) func.apply(context, args)
|
||||
}
|
||||
}
|
||||
|
||||
export const COUNTRIES = {
|
||||
AF: 'Afghanistan',
|
||||
AX: '\u00c5land Islands',
|
||||
AL: 'Albania',
|
||||
DZ: 'Algeria',
|
||||
AS: 'American Samoa',
|
||||
AD: 'Andorra',
|
||||
AO: 'Angola',
|
||||
AI: 'Anguilla',
|
||||
AQ: 'Antarctica',
|
||||
AG: 'Antigua and Barbuda',
|
||||
AR: 'Argentina',
|
||||
AM: 'Armenia',
|
||||
AW: 'Aruba',
|
||||
AU: 'Australia',
|
||||
AT: 'Austria',
|
||||
AZ: 'Azerbaijan',
|
||||
BS: 'Bahamas',
|
||||
BH: 'Bahrain',
|
||||
BD: 'Bangladesh',
|
||||
BB: 'Barbados',
|
||||
BY: 'Belarus',
|
||||
BE: 'Belgium',
|
||||
BZ: 'Belize',
|
||||
BJ: 'Benin',
|
||||
BM: 'Bermuda',
|
||||
BT: 'Bhutan',
|
||||
BO: 'Bolivia, Plurinational State of',
|
||||
BQ: 'Bonaire, Sint Eustatius and Saba',
|
||||
BA: 'Bosnia and Herzegovina',
|
||||
BW: 'Botswana',
|
||||
BV: 'Bouvet Island',
|
||||
BR: 'Brazil',
|
||||
IO: 'British Indian Ocean Territory',
|
||||
BN: 'Brunei Darussalam',
|
||||
BG: 'Bulgaria',
|
||||
BF: 'Burkina Faso',
|
||||
BI: 'Burundi',
|
||||
KH: 'Cambodia',
|
||||
CM: 'Cameroon',
|
||||
CA: 'Canada',
|
||||
CV: 'Cape Verde',
|
||||
KY: 'Cayman Islands',
|
||||
CF: 'Central African Republic',
|
||||
TD: 'Chad',
|
||||
CL: 'Chile',
|
||||
CN: 'China',
|
||||
CX: 'Christmas Island',
|
||||
CC: 'Cocos (Keeling) Islands',
|
||||
CO: 'Colombia',
|
||||
KM: 'Comoros',
|
||||
CG: 'Congo',
|
||||
CD: 'Congo, the Democratic Republic of the',
|
||||
CK: 'Cook Islands',
|
||||
CR: 'Costa Rica',
|
||||
CI: "C\u00f4te d'Ivoire",
|
||||
HR: 'Croatia',
|
||||
CU: 'Cuba',
|
||||
CW: 'Cura\u00e7ao',
|
||||
CY: 'Cyprus',
|
||||
CZ: 'Czech Republic',
|
||||
DK: 'Denmark',
|
||||
DJ: 'Djibouti',
|
||||
DM: 'Dominica',
|
||||
DO: 'Dominican Republic',
|
||||
EC: 'Ecuador',
|
||||
EG: 'Egypt',
|
||||
SV: 'El Salvador',
|
||||
GQ: 'Equatorial Guinea',
|
||||
ER: 'Eritrea',
|
||||
EE: 'Estonia',
|
||||
ET: 'Ethiopia',
|
||||
FK: 'Falkland Islands (Malvinas)',
|
||||
FO: 'Faroe Islands',
|
||||
FJ: 'Fiji',
|
||||
FI: 'Finland',
|
||||
FR: 'France',
|
||||
GF: 'French Guiana',
|
||||
PF: 'French Polynesia',
|
||||
TF: 'French Southern Territories',
|
||||
GA: 'Gabon',
|
||||
GM: 'Gambia',
|
||||
GE: 'Georgia',
|
||||
DE: 'Germany',
|
||||
GH: 'Ghana',
|
||||
GI: 'Gibraltar',
|
||||
GR: 'Greece',
|
||||
GL: 'Greenland',
|
||||
GD: 'Grenada',
|
||||
GP: 'Guadeloupe',
|
||||
GU: 'Guam',
|
||||
GT: 'Guatemala',
|
||||
GG: 'Guernsey',
|
||||
GN: 'Guinea',
|
||||
GW: 'Guinea-Bissau',
|
||||
GY: 'Guyana',
|
||||
HT: 'Haiti',
|
||||
HM: 'Heard Island and McDonald Islands',
|
||||
VA: 'Holy See (Vatican City State)',
|
||||
HN: 'Honduras',
|
||||
HK: 'Hong Kong',
|
||||
HU: 'Hungary',
|
||||
IS: 'Iceland',
|
||||
IN: 'India',
|
||||
ID: 'Indonesia',
|
||||
IR: 'Iran, Islamic Republic of',
|
||||
IQ: 'Iraq',
|
||||
IE: 'Ireland',
|
||||
IM: 'Isle of Man',
|
||||
IL: 'Israel',
|
||||
IT: 'Italy',
|
||||
JM: 'Jamaica',
|
||||
JP: 'Japan',
|
||||
JE: 'Jersey',
|
||||
JO: 'Jordan',
|
||||
KZ: 'Kazakhstan',
|
||||
KE: 'Kenya',
|
||||
KI: 'Kiribati',
|
||||
KP: "Korea, Democratic People's Republic of",
|
||||
KR: 'Korea, Republic of',
|
||||
KW: 'Kuwait',
|
||||
KG: 'Kyrgyzstan',
|
||||
LA: "Lao People's Democratic Republic",
|
||||
LV: 'Latvia',
|
||||
LB: 'Lebanon',
|
||||
LS: 'Lesotho',
|
||||
LR: 'Liberia',
|
||||
LY: 'Libya',
|
||||
LI: 'Liechtenstein',
|
||||
LT: 'Lithuania',
|
||||
LU: 'Luxembourg',
|
||||
MO: 'Macao',
|
||||
MK: 'Macedonia, the Former Yugoslav Republic of',
|
||||
MG: 'Madagascar',
|
||||
MW: 'Malawi',
|
||||
MY: 'Malaysia',
|
||||
MV: 'Maldives',
|
||||
ML: 'Mali',
|
||||
MT: 'Malta',
|
||||
MH: 'Marshall Islands',
|
||||
MQ: 'Martinique',
|
||||
MR: 'Mauritania',
|
||||
MU: 'Mauritius',
|
||||
YT: 'Mayotte',
|
||||
MX: 'Mexico',
|
||||
FM: 'Micronesia, Federated States of',
|
||||
MD: 'Moldova, Republic of',
|
||||
MC: 'Monaco',
|
||||
MN: 'Mongolia',
|
||||
ME: 'Montenegro',
|
||||
MS: 'Montserrat',
|
||||
MA: 'Morocco',
|
||||
MZ: 'Mozambique',
|
||||
MM: 'Myanmar',
|
||||
NA: 'Namibia',
|
||||
NR: 'Nauru',
|
||||
NP: 'Nepal',
|
||||
NL: 'Netherlands',
|
||||
NC: 'New Caledonia',
|
||||
NZ: 'New Zealand',
|
||||
NI: 'Nicaragua',
|
||||
NE: 'Niger',
|
||||
NG: 'Nigeria',
|
||||
NU: 'Niue',
|
||||
NF: 'Norfolk Island',
|
||||
MP: 'Northern Mariana Islands',
|
||||
NO: 'Norway',
|
||||
OM: 'Oman',
|
||||
PK: 'Pakistan',
|
||||
PW: 'Palau',
|
||||
PS: 'Palestine, State of',
|
||||
PA: 'Panama',
|
||||
PG: 'Papua New Guinea',
|
||||
PY: 'Paraguay',
|
||||
PE: 'Peru',
|
||||
PH: 'Philippines',
|
||||
PN: 'Pitcairn',
|
||||
PL: 'Poland',
|
||||
PT: 'Portugal',
|
||||
PR: 'Puerto Rico',
|
||||
QA: 'Qatar',
|
||||
RE: 'R\u00e9union',
|
||||
RO: 'Romania',
|
||||
RU: 'Russian Federation',
|
||||
RW: 'Rwanda',
|
||||
BL: 'Saint Barth\u00e9lemy',
|
||||
SH: 'Saint Helena, Ascension and Tristan da Cunha',
|
||||
KN: 'Saint Kitts and Nevis',
|
||||
LC: 'Saint Lucia',
|
||||
MF: 'Saint Martin (French part)',
|
||||
PM: 'Saint Pierre and Miquelon',
|
||||
VC: 'Saint Vincent and the Grenadines',
|
||||
WS: 'Samoa',
|
||||
SM: 'San Marino',
|
||||
ST: 'Sao Tome and Principe',
|
||||
SA: 'Saudi Arabia',
|
||||
SN: 'Senegal',
|
||||
RS: 'Serbia',
|
||||
SC: 'Seychelles',
|
||||
SL: 'Sierra Leone',
|
||||
SG: 'Singapore',
|
||||
SX: 'Sint Maarten (Dutch part)',
|
||||
SK: 'Slovakia',
|
||||
SI: 'Slovenia',
|
||||
SB: 'Solomon Islands',
|
||||
SO: 'Somalia',
|
||||
ZA: 'South Africa',
|
||||
GS: 'South Georgia and the South Sandwich Islands',
|
||||
SS: 'South Sudan',
|
||||
ES: 'Spain',
|
||||
LK: 'Sri Lanka',
|
||||
SD: 'Sudan',
|
||||
SR: 'Suriname',
|
||||
SJ: 'Svalbard and Jan Mayen',
|
||||
SZ: 'Swaziland',
|
||||
SE: 'Sweden',
|
||||
CH: 'Switzerland',
|
||||
SY: 'Syrian Arab Republic',
|
||||
TW: 'Taiwan, Province of China',
|
||||
TJ: 'Tajikistan',
|
||||
TZ: 'Tanzania, United Republic of',
|
||||
TH: 'Thailand',
|
||||
TL: 'Timor-Leste',
|
||||
TG: 'Togo',
|
||||
TK: 'Tokelau',
|
||||
TO: 'Tonga',
|
||||
TT: 'Trinidad and Tobago',
|
||||
TN: 'Tunisia',
|
||||
TR: 'Turkey',
|
||||
TM: 'Turkmenistan',
|
||||
TC: 'Turks and Caicos Islands',
|
||||
TV: 'Tuvalu',
|
||||
UG: 'Uganda',
|
||||
UA: 'Ukraine',
|
||||
AE: 'United Arab Emirates',
|
||||
GB: 'United Kingdom',
|
||||
US: 'United States',
|
||||
UM: 'United States Minor Outlying Islands',
|
||||
UY: 'Uruguay',
|
||||
UZ: 'Uzbekistan',
|
||||
VU: 'Vanuatu',
|
||||
VE: 'Venezuela, Bolivarian Republic of',
|
||||
VN: 'Viet Nam',
|
||||
VG: 'Virgin Islands, British',
|
||||
VI: 'Virgin Islands, U.S.',
|
||||
WF: 'Wallis and Futuna',
|
||||
EH: 'Western Sahara',
|
||||
YE: 'Yemen',
|
||||
ZM: 'Zambia',
|
||||
ZW: 'Zimbabwe'
|
||||
}
|
||||
|
||||
export default {
|
||||
isValidURL,
|
||||
convertDuration,
|
||||
convertDurationSeparated,
|
||||
numberWithDots,
|
||||
debounce,
|
||||
COUNTRIES
|
||||
}
|
||||
Reference in New Issue
Block a user