started turning js Vue components files in SFCs

This commit is contained in:
Roberto Tonino 2020-06-24 22:54:36 +02:00
parent 2cb0986928
commit 56e7d80363
29 changed files with 2239 additions and 1889 deletions

View File

@ -4,6 +4,8 @@ This is just the WebUI for deemix, it should be used with deemix-pyweb or someth
## What's left to do?
- Use Vue as much as possible
- First step: Single File Components
- Completely remove jQuery dependency
- Make the UI look coherent
- Style buttons

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

4
src/js/EventBus.js Normal file
View File

@ -0,0 +1,4 @@
// https://alligator.io/vuejs/global-event-bus/
import Vue from 'vue'
export default new Vue()

View File

@ -1,18 +1,21 @@
// Global Vue components
import '@components/LoadingPlaceholder.js'
import Vue from 'vue'
// Vue views components
import '@components/artist-tab.js'
import '@components/charts-tab.js'
import '@components/errors-tab.js'
import '@components/favorites-tab.js'
import '@components/home-tab.js'
import '@components/link-analyzer-tab.js'
import '@components/main-search.js'
import '@components/settings-tab.js'
import '@components/tracklist-tab.js'
import TheSidebar from '@components/TheSidebar.vue'
import ArtistTab from '@components/ArtistTab.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 '@components/TestComponent.vue'
// Must be imported before settings tab at the moment
import TrackPreview from '@/js/track-preview.js'
import TheSettingsTab from '@components/TheSettingsTab.vue'
import '@components/main-search.js'
import TracklistTab from '@components/TracklistTab.vue'
import $ from 'jquery'
import { socket } from '@/js/socket.js'
@ -21,11 +24,11 @@ import Downloads from '@/js/downloads.js'
import QualityModal from '@/js/quality-modal.js'
import Tabs from '@/js/tabs.js'
import Search from '@/js/search.js'
import TrackPreview from '@/js/track-preview.js'
/* ===== App initialization ===== */
function startApp() {
mountComponents()
setCurrentUserTheme()
Downloads.init()
@ -35,6 +38,23 @@ function startApp() {
TrackPreview.init()
}
/**
* This funcion is temporary. It will be removed when all components will be as SFC and all their methods will be called
* by using the EventBus.
*/
function mountComponents() {
new Vue({ render: h => h(TheSidebar) }).$mount('#sidebar-placeholder')
new Vue({ render: h => h(ArtistTab) }).$mount('#artist-tab-placeholder')
new Vue({ render: h => h(TheChartsTab) }).$mount('#charts-tab-placeholder')
new Vue({ render: h => h(TheFavoritesTab) }).$mount('#favorites-tab-placeholder')
new Vue({ render: h => h(TheHomeTab) }).$mount('#home-tab-placeholder')
new Vue({ render: h => h(TheLinkAnalyzerTab) }).$mount('#link-analyzer-tab-placeholder')
new Vue({ render: h => h(TheSettingsTab) }).$mount('#settings-tab-placeholder')
new Vue({ render: h => h(TracklistTab) }).$mount('#tracklist-tab-placeholder')
new Vue({ render: h => h(TheAboutTab) }).$mount('#about-tab-placeholder')
new Vue({ render: h => h(TheErrorsTab) }).$mount('#errors-tab-placeholder')
}
function initClient() {
window.clientMode = true
document.querySelector(`#open_downloads_folder`).classList.remove('hide')

View File

@ -0,0 +1,197 @@
<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 QualityModal from '@/js/quality-modal.js'
import { showView, updateSelected } 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) {
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() {
updateSelected(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>

View File

@ -0,0 +1,23 @@
<template>
<div class="loading_placeholder">
<span class="loading_placeholder__text">Loading...</span>
<div class="lds-ring">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
</template>
<script>
export default {
name: 'base-loading-placeholder',
// Without this empty data rollup watcher throws an error
data() {
return {}
}
}
</script>
<style>
</style>

View File

@ -1,13 +0,0 @@
import Vue from 'vue'
Vue.component('loading-placeholder', {
template: `<div class="loading_placeholder">
<span class="loading_placeholder__text">Loading...</span>
<div class="lds-ring">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>`
})

View File

@ -1,17 +0,0 @@
<template>
<div>Test</div>
</template>
<script>
export default {
data: () => ({
test: 'super test'
}),
mounted() {
console.log('abcdefghi')
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,94 @@
<template>
<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>
<script>
export default {
name: 'the-about-tab',
// Without this empty data rollup watcher throws an error
data() {
return {}
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,198 @@
<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 QualityModal from '@/js/quality-modal.js'
import TrackPreview from '@/js/track-preview.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: TrackPreview.playPausePreview,
previewMouseEnter: TrackPreview.previewMouseEnter,
previewMouseLeave: TrackPreview.previewMouseLeave,
convertDuration: Utils.convertDuration,
addToQueue(e) {
e.stopPropagation()
Downloads.sendAddToQueue(e.currentTarget.dataset.link)
},
openQualityModal(e) {
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>

View File

@ -0,0 +1,47 @@
<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 EventBus from '@/js/EventBus'
export default {
name: 'the-errors-tab',
data: () => ({
title: '',
errors: []
}),
methods: {
reset() {
this.title = ''
this.errors = []
},
showErrors(data) {
this.title = data.artist + ' - ' + data.title
this.errors = data.errors
}
},
mounted() {
EventBus.$on('showErrors', this.showErrors)
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,277 @@
<template>
<div id="favorites_tab" class="main_tabcontent">
<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 } from '@/js/tabs.js'
import Downloads from '@/js/downloads.js'
import QualityModal from '@/js/quality-modal.js'
import TrackPreview from '@/js/track-preview.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: TrackPreview.playPausePreview,
previewMouseEnter: TrackPreview.previewMouseEnter,
previewMouseLeave: TrackPreview.previewMouseLeave,
convertDuration: Utils.convertDuration,
addToQueue(e) {
e.stopPropagation()
Downloads.sendAddToQueue(e.currentTarget.dataset.link)
},
openQualityModal(e) {
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>

View File

@ -0,0 +1,103 @@
<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'
import QualityModal from '@/js/quality-modal.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) {
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>

View File

@ -0,0 +1,186 @@
<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>

View File

@ -0,0 +1,665 @@
<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>

View File

@ -0,0 +1,53 @@
<template>
<aside id="sidebar" role="navigation">
<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" data-theme-variant="purple"></div>
<div class="theme_toggler" data-theme-variant="dark"></div>
<div class="theme_toggler theme_toggler--active" data-theme-variant="light"></div>
</div>
</span>
</aside>
</template>
<script>
export default {
name: 'the-sidebar',
// Without this empty data rollup watcher throws an error
data() {
return {}
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,301 @@
<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 QualityModal from '@/js/quality-modal.js'
import TrackPreview from '@/js/track-preview.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: TrackPreview.playPausePreview,
reset() {
console.log('tracklist tab 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) {
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>

View File

@ -1,97 +0,0 @@
import { isEmpty, orderBy } from 'lodash-es'
import Vue from 'vue'
import { socket } from '@/js/socket.js'
import Downloads from '@/js/downloads.js'
import QualityModal from '@/js/quality-modal.js'
import { showView } from '@/js/tabs.js'
const ArtistTab = new Vue({
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) {
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
},
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)
}
}).$mount('#artist_tab')
export default ArtistTab

View File

@ -1,82 +0,0 @@
import Vue from 'vue'
import { socket } from '@/js/socket.js'
import { showView } from '@/js/tabs.js'
import Downloads from '@/js/downloads.js'
import QualityModal from '@/js/quality-modal.js'
import TrackPreview from '@/js/track-preview.js'
import Utils from '@/js/utils.js'
const ChartsTab = new Vue({
data() {
return {
country: '',
id: 0,
countries: [],
chart: []
}
},
methods: {
artistView: showView.bind(null, 'artist'),
albumView: showView.bind(null, 'album'),
playPausePreview: TrackPreview.playPausePreview,
previewMouseEnter: TrackPreview.previewMouseEnter,
previewMouseLeave: TrackPreview.previewMouseLeave,
convertDuration: Utils.convertDuration,
addToQueue(e) {
e.stopPropagation()
Downloads.sendAddToQueue(e.currentTarget.dataset.link)
},
openQualityModal(e) {
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)
}
}).$mount('#charts_tab')
export default ChartsTab

View File

@ -1,20 +0,0 @@
import Vue from 'vue'
const ErrorsTab = new Vue({
data: () => ({
title: '',
errors: []
}),
methods: {
reset(){
this.title = ''
this.errors = []
},
showErrors(data){
this.title = data.artist+" - "+data.title
this.errors = data.errors
}
}
}).$mount('#errors_tab')
export default ErrorsTab

View File

@ -1,91 +0,0 @@
import Vue from 'vue'
import { socket } from '@/js/socket.js'
import { showView } from '@/js/tabs.js'
import Downloads from '@/js/downloads.js'
import QualityModal from '@/js/quality-modal.js'
import TrackPreview from '@/js/track-preview.js'
import Utils from '@/js/utils.js'
import { toast } from '@/js/toasts'
const FavoritesTab = new Vue({
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: TrackPreview.playPausePreview,
previewMouseEnter: TrackPreview.previewMouseEnter,
previewMouseLeave: TrackPreview.previewMouseLeave,
convertDuration: Utils.convertDuration,
addToQueue(e) {
e.stopPropagation()
Downloads.sendAddToQueue(e.currentTarget.dataset.link)
},
openQualityModal(e) {
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)
}
}).$mount('#favorites_tab')
export default FavoritesTab

View File

@ -1,46 +0,0 @@
import Vue from 'vue'
import { socket } from '@/js/socket.js'
import { showView } from '@/js/tabs.js'
import Downloads from '@/js/downloads.js'
import QualityModal from '@/js/quality-modal.js'
const HomeTab = new Vue({
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) {
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)
}
}).$mount('#home_tab')
export default HomeTab

View File

@ -1,79 +0,0 @@
import Vue from 'vue'
import { socket } from '@/js/socket.js'
import { showView } from '@/js/tabs.js'
import Utils from '@/js/utils.js'
const LinkAnalyzerTab = new Vue({
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() {
socket.on('analyze_track', this.showTrack)
socket.on('analyze_album', this.showAlbum)
socket.on('analyze_notSupported', this.notSupported)
}
}).$mount('#analyzer_tab')
export default LinkAnalyzerTab

View File

@ -5,8 +5,12 @@ import Downloads from '@/js/downloads.js'
import QualityModal from '@/js/quality-modal.js'
import TrackPreview from '@/js/track-preview.js'
import Utils from '@/js/utils.js'
import BaseLoadingPlaceholder from '@components/BaseLoadingPlaceholder.vue'
const MainSearch = new Vue({
components: {
BaseLoadingPlaceholder
},
data: {
names: {
TOP_RESULT: 'Top Result',

View File

@ -1,142 +0,0 @@
import Vue from 'vue'
import { toast } from '@/js/toasts.js'
import { socket } from '@/js/socket.js'
import TestComponent from '@components/TestComponent.vue'
const SettingsTab = new Vue({
components: {
TestComponent
},
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: {
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() {
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)
}
}).$mount('#settings_tab')
export default SettingsTab

View File

@ -1,146 +0,0 @@
import { isEmpty } from 'lodash-es'
import Vue from 'vue'
import { socket } from '@/js/socket.js'
import { showView } from '@/js/tabs.js'
import Downloads from '@/js/downloads.js'
import QualityModal from '@/js/quality-modal.js'
import TrackPreview from '@/js/track-preview.js'
import Utils from '@/js/utils.js'
const TracklistTab = new Vue({
data: () => ({
title: '',
metadata: '',
release_date: '',
label: '',
explicit: false,
image: '',
type: '',
link: '',
body: []
}),
methods: {
artistView: showView.bind(null, 'artist'),
albumView: showView.bind(null, 'album'),
playPausePreview: TrackPreview.playPausePreview,
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) {
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() {
socket.on('show_album', this.showAlbum)
socket.on('show_playlist', this.showPlaylist)
socket.on('show_spotifyplaylist', this.showSpotifyPlaylist)
}
}).$mount('#tracklist_tab')
export default TracklistTab

View File

@ -1,11 +1,7 @@
import ArtistTab from '@components/artist-tab.js'
import TracklistTab from '@components/tracklist-tab.js'
import LinkAnalyzerTab from '@components/link-analyzer-tab.js'
import ErrorsTab from '@components/errors-tab.js'
import SettingsTab from '@components/settings-tab.js'
import TrackPreview from '@/js/track-preview.js'
import MainSearch from '@components/main-search.js'
import { socket } from '@/js/socket.js'
import TrackPreview from '@/js/track-preview.js'
import EventBus from '@/js/EventBus'
/* ===== Globals ====== */
window.search_selected = ''
@ -27,12 +23,12 @@ export function showView(viewType, event) {
switch (viewType) {
case 'artist':
ArtistTab.reset()
EventBus.$emit('artistTab:reset')
break
case 'album':
case 'playlist':
case 'spotifyplaylist':
TracklistTab.reset()
EventBus.$emit('tracklistTab:reset')
break
default:
@ -44,12 +40,18 @@ export function showView(viewType, event) {
}
export function showErrors(event) {
ErrorsTab.showErrors(event.data.item)
EventBus.$emit('showErrors', event.data.item)
changeTab(event.target, 'main', 'errors_tab')
}
export function updateSelected(newSelected) {
currentStack.selected = newSelected
}
window.test = showErrors
function analyzeLink(link) {
LinkAnalyzerTab.reset()
EventBus.$emit('linkAnalyzerTab:reset')
socket.emit('analyzeLink', link)
}
@ -211,9 +213,8 @@ function changeTab(sidebarEl, section, tabName) {
}
if (tabName == 'settings_tab' && main_selected != 'settings_tab') {
SettingsTab.settings = { ...SettingsTab.lastSettings }
SettingsTab.spotifyCredentials = { ...SettingsTab.lastCredentials }
SettingsTab.spotifyUser = (' ' + SettingsTab.lastUser).slice(1)
EventBus.$emit('settingsTab:revertSettings')
EventBus.$emit('settingsTab:revertCredentials')
}
document.getElementById(tabName).style.display = 'block'
@ -243,7 +244,7 @@ function showTab(type, id, back = false) {
windows_stack.push({ tab: main_selected })
} else if (!back) {
if (currentStack.type === 'artist') {
currentStack.selected = ArtistTab.getCurrentTab()
EventBus.$emit('artistTab:updateSelected')
}
windows_stack.push(currentStack)
@ -271,11 +272,15 @@ function backTab() {
let { type, id } = data
if (type === 'artist') {
ArtistTab.reset()
if (data.selected) ArtistTab.changeTab(data.selected)
EventBus.$emit('artistTab:reset')
if (data.selected) {
EventBus.$emit('artistTab:changeTab', data.selected)
}
} else {
TracklistTab.reset()
EventBus.$emit('tracklistTab:reset')
}
socket.emit('getTracklist', { type, id })
showTab(type, id, true)
}

View File

@ -15,14 +15,14 @@ function init() {
preview_track.volume = 1
// start playing when track loaded
preview_track.addEventListener('canplay', function () {
preview_track.addEventListener('canplay', function() {
preview_track.play()
preview_stopped = false
$(preview_track).animate({ volume: vol.preview_max_volume / 100 }, 500)
})
// auto fadeout when at the end of the song
preview_track.addEventListener('timeupdate', function () {
preview_track.addEventListener('timeupdate', function() {
if (preview_track.currentTime > preview_track.duration - 1) {
$(preview_track).animate({ volume: 0 }, 800)
preview_stopped = true
@ -37,7 +37,7 @@ function init() {
// on modal closing
function stopStackedTabsPreview() {
if (
$('.preview_playlist_controls').filter(function () {
$('.preview_playlist_controls').filter(function() {
return $(this).attr('playing')
}).length > 0
) {
@ -56,7 +56,15 @@ function previewMouseEnter(e) {
function previewMouseLeave(event) {
const { currentTarget: obj } = event
if (($(obj).parent().attr('playing') && preview_stopped) || !$(obj).parent().attr('playing')) {
if (
($(obj)
.parent()
.attr('playing') &&
preview_stopped) ||
!$(obj)
.parent()
.attr('playing')
) {
$(obj).css({ opacity: 0 }, 200)
}
}