workflow: reorganized components' folder structure
This commit is contained in:
235
src/components/pages/About.vue
Normal file
235
src/components/pages/About.vue
Normal file
@@ -0,0 +1,235 @@
|
||||
<template>
|
||||
<div id="about_tab" class="main_tabcontent" ref="root">
|
||||
<h2 class="page_heading">{{ $t('sidebar.about') }}</h2>
|
||||
<ul>
|
||||
<li>
|
||||
{{ $t('about.updates.currentVersion') }}:
|
||||
<span>{{ current || $t('about.updates.versionNotAvailable') }}</span>
|
||||
</li>
|
||||
<li>{{ $t('about.updates.deemixVersion') }}: {{ deemixVersion }}</li>
|
||||
<li v-if="updateAvailable && latest">{{ $t('about.updates.updateAvailable', { version: latest }) }}</li>
|
||||
</ul>
|
||||
|
||||
<ul>
|
||||
<li v-html="$t('about.usesLibrary')"></li>
|
||||
<li v-html="$t('about.thanks')"></li>
|
||||
<li v-html="$t('about.upToDate')"></li>
|
||||
</ul>
|
||||
|
||||
<h2>{{ $t('about.titles.usefulLinks') }}</h2>
|
||||
<ul class="no-dots">
|
||||
<li>
|
||||
<a href="https://deemix.app" target="_blank">🌍 {{ $t('about.officialWebsite') }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://codeberg.org/RemixDev/deemix" target="_blank">🚀 {{ $t('about.officialRepo') }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://codeberg.org/RemixDev/deemix-webui" target="_blank">💻 {{ $t('about.officialWebuiRepo') }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.reddit.com/r/deemix" target="_blank">🤖 {{ $t('about.officialSubreddit') }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://t.me/RemixDevNews" target="_blank">📰 {{ $t('about.newsChannel') }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2>
|
||||
{{ $t('about.titles.bugReports') }}
|
||||
<span class="subheading">
|
||||
{{ $t('about.subtitles.bugReports') }}
|
||||
</span>
|
||||
</h2>
|
||||
<ul>
|
||||
<li v-html="$t('about.questions')"></li>
|
||||
<li>
|
||||
{{ $t('about.beforeReporting') }}
|
||||
</li>
|
||||
<li v-html="$t('about.beSure')"></li>
|
||||
<li>
|
||||
{{ $t('about.duplicateReports') }}
|
||||
</li>
|
||||
<li v-html="$t('about.dontOpenIssues')"></li>
|
||||
</ul>
|
||||
|
||||
<h2>
|
||||
{{ $t('about.titles.contributing') }}
|
||||
<span class="subheading">
|
||||
{{ $t('about.subtitles.contributing') }}
|
||||
</span>
|
||||
</h2>
|
||||
<ul>
|
||||
<li v-html="$t('about.newUI')"></li>
|
||||
<li>
|
||||
{{ $t('about.acceptFeatures') }}
|
||||
</li>
|
||||
<li v-html="$t('about.contributeWebUI')"></li>
|
||||
<li>
|
||||
{{ $t('about.otherLanguages') }}
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('about.understandingCode') }}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2>
|
||||
{{ $t('about.titles.donations') }}
|
||||
<span class="subheading">
|
||||
{{ $t('about.subtitles.donations') }}
|
||||
</span>
|
||||
</h2>
|
||||
<ul>
|
||||
<li v-html="$t('about.itsFree')"></li>
|
||||
<li>
|
||||
{{ $t('about.notObligated') }}
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
<i v-html="paypal" />
|
||||
<strong>PayPal:</strong>
|
||||
<a href="https://paypal.me/RemixDev" target="_blank">PayPal.me/RemixDev</a>
|
||||
</li>
|
||||
<li>
|
||||
<i class="ethereum" v-html="ethereum" />
|
||||
<strong>Ethereum:</strong> 0x1d2aa67e671485CD4062289772B662e0A6Ff976c
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2>{{ $t('about.titles.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>
|
||||
</p>
|
||||
<p v-html="$t('about.lincensedUnder')"></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
li,
|
||||
p,
|
||||
a {
|
||||
letter-spacing: 0.4px;
|
||||
font-size: 20px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
i {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
i /deep/ svg {
|
||||
fill: white;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.ethereum /deep/ svg {
|
||||
fill: var(--foreground);
|
||||
}
|
||||
|
||||
:link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#about_tab {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-transform: capitalize;
|
||||
|
||||
&:not(.page_heading) {
|
||||
font-size: 2rem;
|
||||
border-bottom: 1px solid hsla(0, 0%, 20%, 0.25);
|
||||
padding: {
|
||||
top: 2rem;
|
||||
bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.subheading {
|
||||
display: block;
|
||||
font-size: 0.5em;
|
||||
margin-top: 0.5em;
|
||||
font-weight: normal;
|
||||
opacity: 0.8;
|
||||
text-transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
ul {
|
||||
li {
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
h2 + & {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
ul + & {
|
||||
margin-top: 1.25rem;
|
||||
}
|
||||
|
||||
&.no-dots {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
&:not(.no-dots) {
|
||||
list-style-type: none;
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '—';
|
||||
position: absolute;
|
||||
left: -30px;
|
||||
opacity: 0.25;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { socket } from '@/utils/socket'
|
||||
import paypal from '@/assets/paypal.svg'
|
||||
import ethereum from '@/assets/ethereum.svg'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
paypal,
|
||||
ethereum,
|
||||
current: null,
|
||||
latest: null,
|
||||
updateAvailable: false,
|
||||
deemixVersion: null
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters(['getAboutInfo'])
|
||||
},
|
||||
methods: {
|
||||
initUpdate(data) {
|
||||
const { currentCommit, latestCommit, updateAvailable, deemixVersion } = data
|
||||
this.current = currentCommit
|
||||
this.latest = latestCommit
|
||||
this.updateAvailable = updateAvailable
|
||||
this.deemixVersion = deemixVersion
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initUpdate(this.getAboutInfo)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
186
src/components/pages/Artist.vue
Normal file
186
src/components/pages/Artist.vue
Normal file
@@ -0,0 +1,186 @@
|
||||
<template>
|
||||
<div id="artist_tab" class="main_tabcontent fixed_footer image_header" ref="root">
|
||||
<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" @click.stop="addToQueue" :data-link="link" class="fab right">
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="tab">
|
||||
<button
|
||||
v-for="(item, name) in body"
|
||||
:key="name"
|
||||
class="selective"
|
||||
:class="{ active: name === currentTab }"
|
||||
:href="'#artist_' + name"
|
||||
@click="changeTab(name)"
|
||||
>
|
||||
{{ $tc(`globals.listTabs.${name}`, 2) }}
|
||||
</button>
|
||||
</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
|
||||
}"
|
||||
>
|
||||
<!-- Need to change this behaviour for translations -->
|
||||
{{ data.title }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="release in showTable" :key="release.id">
|
||||
<router-link tag="td" class="inline-flex clickable" :to="{ name: 'Album', params: { 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>
|
||||
</router-link>
|
||||
<td>{{ release.release_date }}</td>
|
||||
<td>{{ release.nb_song }}</td>
|
||||
<td @click.stop="addToQueue" :data-link="release.link" class="clickable">
|
||||
<i class="material-icons" :title="$t('globals.download_hint')"> file_download </i>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<footer>
|
||||
<button class="back-button" @click="$router.back()">{{ $t('globals.back') }}</button>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isEmpty, orderBy } from 'lodash-es'
|
||||
import { socket } from '@/utils/socket'
|
||||
import Downloads from '@/utils/downloads'
|
||||
import EventBus from '@/utils/EventBus'
|
||||
|
||||
export default {
|
||||
name: 'artist-tab',
|
||||
data() {
|
||||
return {
|
||||
currentTab: '',
|
||||
sortKey: 'release_date',
|
||||
sortOrder: 'desc',
|
||||
title: '',
|
||||
image: '',
|
||||
type: '',
|
||||
link: '',
|
||||
head: null,
|
||||
body: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
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)
|
||||
},
|
||||
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
|
||||
},
|
||||
updateSelected() {
|
||||
// Last tab opened logic
|
||||
},
|
||||
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) {
|
||||
this.reset()
|
||||
|
||||
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: this.$tc('globals.listTabs.title', 1), sortKey: 'title' },
|
||||
{ title: this.$t('globals.listTabs.releaseDate'), sortKey: 'release_date' },
|
||||
{ title: this.$tc('globals.listTabs.track', 2), sortKey: 'nb_song' },
|
||||
{ title: '', width: '32px' }
|
||||
]
|
||||
if (isEmpty(releases)) {
|
||||
this.body = null
|
||||
} else {
|
||||
this.body = releases
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showTable() {
|
||||
if (this.body) {
|
||||
if (this.sortKey == 'nb_song')
|
||||
return orderBy(
|
||||
this.body[this.currentTab],
|
||||
function (o) {
|
||||
return new Number(o.nb_song)
|
||||
},
|
||||
this.sortOrder
|
||||
)
|
||||
else return orderBy(this.body[this.currentTab], this.sortKey, this.sortOrder)
|
||||
} else return []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
socket.on('show_artist', this.showArtist)
|
||||
|
||||
EventBus.$on('artistTab:updateSelected', this.updateSelected)
|
||||
EventBus.$on('artistTab:changeTab', this.changeTab)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
199
src/components/pages/Charts.vue
Normal file
199
src/components/pages/Charts.vue
Normal file
@@ -0,0 +1,199 @@
|
||||
<template>
|
||||
<div id="charts_tab" class="main_tabcontent" ref="root">
|
||||
<h2 class="page_heading">{{ $t('charts.title') }}</h2>
|
||||
<div v-if="country === ''" id="charts_selection">
|
||||
<div class="release_grid charts_grid">
|
||||
<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="onChangeCountry">{{ $t('charts.changeCountry') }}</button>
|
||||
<button @click.stop="addToQueue" :data-link="'https://www.deezer.com/playlist/' + id">
|
||||
{{ $t('charts.download') }}
|
||||
</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"
|
||||
:title="$t('globals.play_hint')"
|
||||
>
|
||||
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>
|
||||
<router-link
|
||||
tag="td"
|
||||
class="table__cell table__cell--medium table__cell--center breakline clickable"
|
||||
:to="{ name: 'Artist', params: { id: track.artist.id } }"
|
||||
>
|
||||
{{ track.artist.name }}
|
||||
</router-link>
|
||||
<router-link
|
||||
tag="td"
|
||||
class="table__cell--medium table__cell--center breakline clickable"
|
||||
:to="{ name: 'Album', params: { id: track.album.id } }"
|
||||
>
|
||||
{{ track.album.title }}
|
||||
</router-link>
|
||||
<td class="table__cell--small table__cell--center">
|
||||
{{ convertDuration(track.duration) }}
|
||||
</td>
|
||||
<td
|
||||
class="table__cell--download"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="track.link"
|
||||
role="button"
|
||||
aria-label="download"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { socket } from '@/utils/socket'
|
||||
import { sendAddToQueue } from '@/utils/downloads'
|
||||
import { convertDuration } from '@/utils/utils'
|
||||
|
||||
import { getChartsData } from '@/data/charts'
|
||||
|
||||
import EventBus from '@/utils/EventBus'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
country: '',
|
||||
id: 0,
|
||||
countries: [],
|
||||
chart: []
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
socket.on('setChartTracks', this.setTracklist)
|
||||
this.$on('hook:destroyed', () => {
|
||||
socket.off('setChartTracks')
|
||||
})
|
||||
|
||||
const chartsData = await getChartsData()
|
||||
|
||||
this.initCharts(chartsData)
|
||||
},
|
||||
methods: {
|
||||
convertDuration,
|
||||
playPausePreview(e) {
|
||||
EventBus.$emit('trackPreview:playPausePreview', e)
|
||||
},
|
||||
previewMouseEnter(e) {
|
||||
EventBus.$emit('trackPreview:previewMouseEnter', e)
|
||||
},
|
||||
previewMouseLeave(e) {
|
||||
EventBus.$emit('trackPreview:previewMouseLeave', e)
|
||||
},
|
||||
addToQueue(e) {
|
||||
e.stopPropagation()
|
||||
sendAddToQueue(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
|
||||
},
|
||||
onChangeCountry() {
|
||||
this.country = ''
|
||||
this.id = 0
|
||||
},
|
||||
initCharts(chartsData) {
|
||||
this.countries = chartsData
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
39
src/components/pages/Errors.vue
Normal file
39
src/components/pages/Errors.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<div id="errors_tab" class="main_tabcontent">
|
||||
<h1>{{ $t('errors.title', { name: title }) }}</h1>
|
||||
|
||||
<table class="table table--tracklist">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>{{ $tc('globals.listTabs.artist', 1) }}</th>
|
||||
<th>{{ $tc('globals.listTabs.title', 1) }}</th>
|
||||
<th>{{ $tc('globals.listTabs.error', 1) }}</th>
|
||||
</tr>
|
||||
<tr v-for="error in errors" :key="error.data.id">
|
||||
<td>{{ error.data.id }}</td>
|
||||
<td>{{ error.data.artist }}</td>
|
||||
<td>{{ error.data.title }}</td>
|
||||
<td>{{ error.errid ? $t(`errors.ids.${error.errid}`) : error.message }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
...mapGetters(['getErrors']),
|
||||
title() {
|
||||
return `${this.getErrors.artist} - ${this.getErrors.title}`
|
||||
},
|
||||
errors() {
|
||||
return this.getErrors.errors
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
342
src/components/pages/Favorites.vue
Normal file
342
src/components/pages/Favorites.vue
Normal file
@@ -0,0 +1,342 @@
|
||||
<template>
|
||||
<div id="favorites_tab" class="main_tabcontent">
|
||||
<h2 class="page_heading">
|
||||
{{ $t('favorites.title') }}
|
||||
<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"
|
||||
:class="{ active: activeTab === tab }"
|
||||
@click="activeTab = tab"
|
||||
v-for="tab in tabs"
|
||||
:key="tab"
|
||||
>
|
||||
{{ $tc(`globals.listTabs.${tab}`, 2) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="favorites_tabcontent" :class="{ 'favorites_tabcontent--active': activeTab === 'playlist' }">
|
||||
<div v-if="playlists.length == 0">
|
||||
<h1>{{ $t('favorites.noPlaylists') }}</h1>
|
||||
</div>
|
||||
<div class="release_grid" v-if="playlists.length > 0 || spotifyPlaylists > 0">
|
||||
<router-link
|
||||
tag="div"
|
||||
v-for="release in playlists"
|
||||
:key="release.id"
|
||||
class="release clickable"
|
||||
:to="{ name: 'Playlist', params: { id: release.id } }"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img aria-hidden="true" class="rounded coverart" :src="release.picture_medium" />
|
||||
<button
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="release.link"
|
||||
class="download_overlay"
|
||||
tabindex="0"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</button>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.title }}</p>
|
||||
<p class="secondary-text">
|
||||
{{
|
||||
`${$t('globals.by', { artist: release.creator.name })} - ${$tc(
|
||||
'globals.listTabs.trackN',
|
||||
release.nb_tracks
|
||||
)}`
|
||||
}}
|
||||
</p>
|
||||
</router-link>
|
||||
<router-link
|
||||
tag="div"
|
||||
v-for="release in spotifyPlaylists"
|
||||
:key="release.id"
|
||||
class="release clickable"
|
||||
:to="{ name: 'Spotify Playlist', params: { id: release.id } }"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img aria-hidden="true" class="rounded coverart" :src="release.picture_medium" />
|
||||
<button
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="release.link"
|
||||
class="download_overlay"
|
||||
tabindex="0"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</button>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.title }}</p>
|
||||
<p class="secondary-text">
|
||||
{{
|
||||
`${$t('globals.by', { artist: release.creator.name })} - ${$tc(
|
||||
'globals.listTabs.trackN',
|
||||
release.nb_tracks
|
||||
)}`
|
||||
}}
|
||||
</p>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="favorites_tabcontent" :class="{ 'favorites_tabcontent--active': activeTab === 'album' }">
|
||||
<div v-if="albums.length == 0">
|
||||
<h1>{{ $t('favorites.noAlbums') }}</h1>
|
||||
</div>
|
||||
<div class="release_grid" v-if="albums.length > 0">
|
||||
<router-link
|
||||
tag="div"
|
||||
class="release clickable"
|
||||
v-for="release in albums"
|
||||
:key="release.id"
|
||||
:to="{ name: 'Album', params: { id: release.id } }"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img aria-hidden="true" class="rounded coverart" :src="release.cover_medium" />
|
||||
<button
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="release.link"
|
||||
class="download_overlay"
|
||||
tabindex="0"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</button>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.title }}</p>
|
||||
<p class="secondary-text">{{ `${$t('globals.by', { artist: release.artist.name })}` }}</p>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="favorites_tabcontent" :class="{ 'favorites_tabcontent--active': activeTab === 'artist' }">
|
||||
<div v-if="artists.length == 0">
|
||||
<h1>{{ $t('favorites.noArtists') }}</h1>
|
||||
</div>
|
||||
<div class="release_grid" v-if="artists.length > 0">
|
||||
<router-link
|
||||
tag="div"
|
||||
class="release clickable"
|
||||
v-for="release in artists"
|
||||
:key="release.id"
|
||||
:to="{ name: 'Artist', params: { id: release.id } }"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img aria-hidden="true" class="circle coverart" :src="release.picture_medium" />
|
||||
<button
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="release.link"
|
||||
class="download_overlay"
|
||||
tabindex="0"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</button>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.name }}</p>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="favorites_tabcontent" :class="{ 'favorites_tabcontent--active': activeTab === 'track' }">
|
||||
<div v-if="tracks.length == 0">
|
||||
<h1>{{ $t('favorites.noTracks') }}</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"
|
||||
:title="$t('globals.play_hint')"
|
||||
>
|
||||
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>
|
||||
<router-link
|
||||
tag="td"
|
||||
class="table__cell table__cell--medium table__cell--center breakline clickable"
|
||||
:to="{ name: 'Artist', params: { id: track.artist.id } }"
|
||||
>
|
||||
{{ track.artist.name }}
|
||||
</router-link>
|
||||
<router-link
|
||||
tag="td"
|
||||
class="table__cell--medium table__cell--center breakline clickable"
|
||||
:to="{ name: 'Album', params: { id: track.album.id } }"
|
||||
>
|
||||
{{ track.album.title }}
|
||||
</router-link>
|
||||
<td class="table__cell--small">
|
||||
{{ convertDuration(track.duration) }}
|
||||
</td>
|
||||
<td
|
||||
class="table__cell--download clickable"
|
||||
@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" :title="$t('globals.download_hint')">get_app</i>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.favorites_tabcontent {
|
||||
display: none;
|
||||
|
||||
&--active {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { socket } from '@/utils/socket'
|
||||
import { sendAddToQueue } from '@/utils/downloads'
|
||||
import { convertDuration } from '@/utils/utils'
|
||||
import { toast } from '@/utils/toasts'
|
||||
|
||||
import { getFavoritesData } from '@/data/favorites'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
tracks: [],
|
||||
albums: [],
|
||||
artists: [],
|
||||
playlists: [],
|
||||
spotifyPlaylists: [],
|
||||
activeTab: 'playlist',
|
||||
tabs: ['playlist', 'album', 'artist', 'track']
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
const favoritesData = await getFavoritesData()
|
||||
|
||||
// TODO Change with isLoggedIn vuex getter
|
||||
if (Object.entries(favoritesData).length === 0) return
|
||||
|
||||
this.setFavorites(favoritesData)
|
||||
},
|
||||
mounted() {
|
||||
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)
|
||||
|
||||
this.$on('hook:destroyed', () => {
|
||||
socket.off('updated_userFavorites')
|
||||
socket.off('updated_userSpotifyPlaylists')
|
||||
socket.off('updated_userPlaylists')
|
||||
socket.off('updated_userAlbums')
|
||||
socket.off('updated_userArtist')
|
||||
socket.off('updated_userTracks')
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
playPausePreview(e) {
|
||||
EventBus.$emit('trackPreview:playPausePreview', e)
|
||||
},
|
||||
previewMouseEnter(e) {
|
||||
EventBus.$emit('trackPreview:previewMouseEnter', e)
|
||||
},
|
||||
previewMouseLeave(e) {
|
||||
EventBus.$emit('trackPreview:previewMouseLeave', e)
|
||||
},
|
||||
convertDuration,
|
||||
addToQueue(e) {
|
||||
sendAddToQueue(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) {
|
||||
this.setFavorites(data)
|
||||
|
||||
// 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(this.$t('toasts.refreshFavs'), 'done', true)
|
||||
},
|
||||
{ once: true }
|
||||
)
|
||||
},
|
||||
setFavorites(data) {
|
||||
const { tracks, albums, artists, playlists } = data
|
||||
|
||||
this.tracks = tracks
|
||||
this.albums = albums
|
||||
this.artists = artists
|
||||
this.playlists = playlists
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
123
src/components/pages/Home.vue
Normal file
123
src/components/pages/Home.vue
Normal file
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<div id="home_tab" class="main_tabcontent" ref="root">
|
||||
<h2 class="page_heading">{{ $t('globals.welcome') }}</h2>
|
||||
|
||||
<section class="home_section" ref="notLogged" v-if="!isLoggedIn">
|
||||
<p id="home_not_logged_text">{{ $t('home.needTologin') }}</p>
|
||||
<router-link tag="button" name="button" :to="{ name: 'Settings' }">
|
||||
{{ $t('home.openSettings') }}
|
||||
</router-link>
|
||||
</section>
|
||||
|
||||
<section v-if="playlists.length" class="home_section">
|
||||
<h3 class="section_heading">{{ $t('home.sections.popularPlaylists') }}</h3>
|
||||
<div class="release_grid">
|
||||
<router-link
|
||||
tag="div"
|
||||
v-for="release in playlists"
|
||||
:key="release.id"
|
||||
class="release clickable"
|
||||
:to="{ name: 'Playlist', params: { id: release.id } }"
|
||||
@keyup.enter.native="$router.push({ name: 'Playlist', params: { id: release.id } })"
|
||||
tabindex="0"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img aria-hidden="true" class="rounded coverart" :src="release.picture_medium" />
|
||||
<button
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="release.link"
|
||||
class="download_overlay"
|
||||
tabindex="0"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</button>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.title }}</p>
|
||||
<p class="secondary-text">
|
||||
{{
|
||||
`${$t('globals.by', { artist: release.user.name })} - ${$tc(
|
||||
'globals.listTabs.trackN',
|
||||
release.nb_tracks
|
||||
)}`
|
||||
}}
|
||||
</p>
|
||||
</router-link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-if="albums.length" class="home_section">
|
||||
<h3 class="section_heading">{{ $t('home.sections.popularAlbums') }}</h3>
|
||||
<div class="release_grid">
|
||||
<router-link
|
||||
tag="div"
|
||||
v-for="release in albums"
|
||||
:key="release.id"
|
||||
class="release clickable"
|
||||
:to="{ name: 'Album', params: { id: release.id } }"
|
||||
@keyup.enter.native="$router.push({ name: 'Album', params: { id: release.id } })"
|
||||
:data-id="release.id"
|
||||
tabindex="0"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img aria-hidden="true" class="rounded coverart" :src="release.cover_medium" />
|
||||
<button
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="release.link"
|
||||
class="download_overlay"
|
||||
tabindex="0"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</button>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.title }}</p>
|
||||
<p class="secondary-text">{{ `${$t('globals.by', { artist: release.artist.name })}` }}</p>
|
||||
</router-link>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
import { sendAddToQueue } from '@/utils/downloads'
|
||||
import { getHomeData } from '@/data/home'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
playlists: [],
|
||||
albums: []
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
const homeData = await getHomeData()
|
||||
|
||||
this.initHome(homeData)
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['isLoggedIn'])
|
||||
},
|
||||
methods: {
|
||||
addToQueue(e) {
|
||||
sendAddToQueue(e.currentTarget.dataset.link)
|
||||
},
|
||||
initHome(data) {
|
||||
const {
|
||||
playlists: { data: playlistData },
|
||||
albums: { data: albumData }
|
||||
} = data
|
||||
|
||||
this.playlists = playlistData
|
||||
this.albums = albumData
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
222
src/components/pages/LinkAnalyzer.vue
Normal file
222
src/components/pages/LinkAnalyzer.vue
Normal file
@@ -0,0 +1,222 @@
|
||||
<template>
|
||||
<div id="analyzer_tab" class="main_tabcontent image_header" ref="root">
|
||||
<h2 class="page_heading page_heading--capitalize">{{ $t('sidebar.linkAnalyzer') }}</h2>
|
||||
|
||||
<div v-if="link === ''">
|
||||
<p>
|
||||
{{ $t('linkAnalyzer.info') }}
|
||||
</p>
|
||||
<p>
|
||||
{{ $t('linkAnalyzer.useful') }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-else-if="link === 'error'">
|
||||
<h2>{{ $t('linkAnalyzer.linkNotSupported') }}</h2>
|
||||
<p>{{ $t('linkAnalyzer.linkNotSupportedYet') }}</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'">
|
||||
<i18n path="globals.by" tag="span">
|
||||
<router-link
|
||||
tag="span"
|
||||
place="artist"
|
||||
class="clickable"
|
||||
:to="{ name: 'Artist', params: { id: data.artist.id } }"
|
||||
>
|
||||
{{ data.artist.name }}
|
||||
</router-link>
|
||||
</i18n>
|
||||
•
|
||||
<i18n path="globals.in" tag="span">
|
||||
<router-link
|
||||
tag="span"
|
||||
place="album"
|
||||
class="clickable"
|
||||
:to="{ name: 'Album', params: { id: data.album.id } }"
|
||||
>
|
||||
{{ data.album.title }}
|
||||
</router-link>
|
||||
</i18n>
|
||||
</h2>
|
||||
<h2 v-else-if="type === 'album'">
|
||||
<i18n path="globals.by" tag="span">
|
||||
<router-link
|
||||
tag="span"
|
||||
place="artist"
|
||||
class="clickable"
|
||||
:to="{ name: 'Artist', params: { id: data.artist.id } }"
|
||||
>
|
||||
{{ data.artist.name }}
|
||||
</router-link>
|
||||
</i18n>
|
||||
{{ ` • ${$tc('globals.listTabs.trackN', data.nb_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" :title="$t('globals.download_hint')">get_app</i>
|
||||
</div>
|
||||
</header>
|
||||
<table class="table">
|
||||
<tr v-if="data.id">
|
||||
<td>{{ $t('linkAnalyzer.table.id') }}</td>
|
||||
<td>{{ data.id }}</td>
|
||||
</tr>
|
||||
<tr v-if="data.isrc">
|
||||
<td>{{ $t('linkAnalyzer.table.isrc') }}</td>
|
||||
<td>{{ data.isrc }}</td>
|
||||
</tr>
|
||||
<tr v-if="data.upc">
|
||||
<td>{{ $t('linkAnalyzer.table.upc') }}</td>
|
||||
<td>{{ data.upc }}</td>
|
||||
</tr>
|
||||
<tr v-if="data.duration">
|
||||
<td>{{ $t('linkAnalyzer.table.duration') }}</td>
|
||||
<td>{{ convertDuration(data.duration) }}</td>
|
||||
</tr>
|
||||
<tr v-if="data.disk_number">
|
||||
<td>{{ $t('linkAnalyzer.table.diskNumber') }}</td>
|
||||
<td>{{ data.disk_number }}</td>
|
||||
</tr>
|
||||
<tr v-if="data.track_position">
|
||||
<td>{{ $t('linkAnalyzer.table.trackNumber') }}</td>
|
||||
<td>{{ data.track_position }}</td>
|
||||
</tr>
|
||||
<tr v-if="data.release_date">
|
||||
<td>{{ $t('linkAnalyzer.table.releaseDate') }}</td>
|
||||
<td>{{ data.release_date }}</td>
|
||||
</tr>
|
||||
<tr v-if="data.bpm">
|
||||
<td>{{ $t('linkAnalyzer.table.bpm') }}</td>
|
||||
<td>{{ data.bpm }}</td>
|
||||
</tr>
|
||||
<tr v-if="data.label">
|
||||
<td>{{ $t('linkAnalyzer.table.label') }}</td>
|
||||
<td>{{ data.label }}</td>
|
||||
</tr>
|
||||
<tr v-if="data.record_type">
|
||||
<td>{{ $t('linkAnalyzer.table.recordType') }}</td>
|
||||
<td>{{ $tc(`globals.listTabs.${data.record_type}`, 1) }}</td>
|
||||
</tr>
|
||||
<tr v-if="data.genres && data.genres.data.length">
|
||||
<td>{{ $t('linkAnalyzer.table.genres') }}</td>
|
||||
<td>{{ data.genres.data.map(x => x.name).join('; ') }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div v-if="type == 'album'">
|
||||
<router-link tag="button" :to="{ name: 'Album', params: { id } }">
|
||||
{{ $t('linkAnalyzer.table.tracklist') }}
|
||||
</router-link>
|
||||
</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 '@/utils/socket'
|
||||
import { convertDuration } from '@/utils/utils'
|
||||
import { COUNTRIES } from '@/utils/countries'
|
||||
import EventBus from '@/utils/EventBus'
|
||||
import { sendAddToQueue } from '@/utils/downloads'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
link: '',
|
||||
title: '',
|
||||
subtitle: '',
|
||||
image: '',
|
||||
data: {},
|
||||
type: '',
|
||||
id: '0',
|
||||
countries: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
convertDuration,
|
||||
reset() {
|
||||
this.title = 'Loading...'
|
||||
this.subtitle = ''
|
||||
this.image = ''
|
||||
this.data = {}
|
||||
this.type = ''
|
||||
this.link = ''
|
||||
this.countries = []
|
||||
},
|
||||
showTrack(data) {
|
||||
this.reset()
|
||||
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(COUNTRIES[cc])
|
||||
this.countries.push(temp)
|
||||
})
|
||||
|
||||
this.data = data
|
||||
},
|
||||
showAlbum(data) {
|
||||
this.reset()
|
||||
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'
|
||||
},
|
||||
addToQueue(e) {
|
||||
sendAddToQueue(e.currentTarget.dataset.link)
|
||||
}
|
||||
},
|
||||
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>
|
||||
282
src/components/pages/Search.vue
Normal file
282
src/components/pages/Search.vue
Normal file
@@ -0,0 +1,282 @@
|
||||
<template>
|
||||
<div id="search_tab" class="main_tabcontent" ref="root">
|
||||
<div v-show="!showSearchTab">
|
||||
<h2>{{ $t('search.startSearching') }}</h2>
|
||||
<p>{{ $t('search.description') }}</p>
|
||||
</div>
|
||||
|
||||
<div v-show="showSearchTab">
|
||||
<ul class="section-tabs">
|
||||
<li
|
||||
class="section-tabs__tab"
|
||||
v-for="tab in tabs"
|
||||
:key="tab.name"
|
||||
@click="currentTab = tab"
|
||||
:class="{ active: currentTab.name === tab.name }"
|
||||
>
|
||||
{{ tab.name }}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<keep-alive>
|
||||
<component
|
||||
:is="currentTab.component"
|
||||
:results="results"
|
||||
@add-to-queue="addToQueue"
|
||||
@change-search-tab="changeSearchTab"
|
||||
></component>
|
||||
</keep-alive>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseLoadingPlaceholder from '@components/globals/BaseLoadingPlaceholder.vue'
|
||||
import ResultsAll from '@components/search/ResultsAll.vue'
|
||||
import ResultsAlbums from '@components/search/ResultsAlbums.vue'
|
||||
import ResultsArtists from '@components/search/ResultsArtists.vue'
|
||||
import ResultsPlaylists from '@components/search/ResultsPlaylists.vue'
|
||||
import ResultsTracks from '@components/search/ResultsTracks.vue'
|
||||
|
||||
import { socket } from '@/utils/socket'
|
||||
import { sendAddToQueue } from '@/utils/downloads'
|
||||
import { numberWithDots, convertDuration } from '@/utils/utils'
|
||||
import EventBus from '@/utils/EventBus'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BaseLoadingPlaceholder
|
||||
},
|
||||
data() {
|
||||
const $t = this.$t.bind(this)
|
||||
const $tc = this.$tc.bind(this)
|
||||
|
||||
return {
|
||||
currentTab: {
|
||||
name: '',
|
||||
component: {}
|
||||
},
|
||||
tabs: [
|
||||
{
|
||||
name: $t('globals.listTabs.all'),
|
||||
searchType: 'all',
|
||||
component: ResultsAll
|
||||
},
|
||||
{
|
||||
name: $tc('globals.listTabs.track', 2),
|
||||
searchType: 'track',
|
||||
component: ResultsTracks
|
||||
},
|
||||
{
|
||||
name: $tc('globals.listTabs.album', 2),
|
||||
searchType: 'album',
|
||||
component: ResultsAlbums
|
||||
},
|
||||
{
|
||||
name: $tc('globals.listTabs.artist', 2),
|
||||
searchType: 'artist',
|
||||
component: ResultsArtists
|
||||
},
|
||||
{
|
||||
name: $tc('globals.listTabs.playlist', 2),
|
||||
searchType: 'playlist',
|
||||
component: ResultsPlaylists
|
||||
}
|
||||
],
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showSearchTab() {
|
||||
return this.results.query !== ''
|
||||
},
|
||||
loadedTabs() {
|
||||
const loaded = []
|
||||
|
||||
for (const resultKey in this.results) {
|
||||
if (this.results.hasOwnProperty(resultKey)) {
|
||||
const result = this.results[resultKey]
|
||||
|
||||
if (result.loaded) {
|
||||
loaded.push(resultKey.replace(/Tab/g, ''))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return loaded
|
||||
}
|
||||
},
|
||||
props: {
|
||||
performScrolledSearch: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.currentTab = this.tabs[0]
|
||||
},
|
||||
mounted() {
|
||||
EventBus.$on('mainSearch:checkLoadMoreContent', this.checkLoadMoreContent)
|
||||
this.$root.$on('mainSearch:showNewResults', this.checkIfShowNewResults)
|
||||
this.$root.$on('mainSearch:updateResults', this.checkIfUpdateResults)
|
||||
|
||||
socket.on('mainSearch', this.handleMainSearch)
|
||||
socket.on('search', this.handleSearch)
|
||||
},
|
||||
methods: {
|
||||
changeSearchTab(sectionName) {
|
||||
sectionName = sectionName.toLowerCase()
|
||||
|
||||
let newTab = this.tabs.find(tab => {
|
||||
return tab.searchType === sectionName
|
||||
})
|
||||
|
||||
if (!newTab) {
|
||||
console.error(`No tab ${sectionName} found`)
|
||||
return
|
||||
}
|
||||
|
||||
window.scrollTo(0, 0)
|
||||
this.currentTab = newTab
|
||||
},
|
||||
checkIfShowNewResults(term, mainSelected) {
|
||||
let needToPerformNewSearch = term !== this.results.query /* || mainSelected == 'search_tab' */
|
||||
|
||||
if (needToPerformNewSearch) {
|
||||
this.showNewResults(term)
|
||||
}
|
||||
},
|
||||
checkIfUpdateResults(term) {
|
||||
let needToUpdateSearch = term === this.results.query && this.currentTab.searchType !== 'all'
|
||||
|
||||
if (needToUpdateSearch) {
|
||||
let resetObj = { data: [], next: 0, total: 0, loaded: false }
|
||||
this.results[this.currentTab.searchType + 'Tab'] = { ...resetObj }
|
||||
this.search(this.currentTab.searchType)
|
||||
}
|
||||
},
|
||||
showNewResults(term) {
|
||||
socket.emit('mainSearch', { term })
|
||||
|
||||
// Showing loading placeholder
|
||||
this.$root.$emit('updateSearchLoadingState', true)
|
||||
this.currentTab = this.tabs[0]
|
||||
},
|
||||
checkLoadMoreContent(searchSelected) {
|
||||
if (this.results[searchSelected.split('_')[0] + 'Tab'].data.length !== 0) return
|
||||
|
||||
this.search(searchSelected.split('_')[0])
|
||||
},
|
||||
addToQueue(e) {
|
||||
sendAddToQueue(e.currentTarget.dataset.link)
|
||||
},
|
||||
numberWithDots,
|
||||
convertDuration,
|
||||
search(type) {
|
||||
socket.emit('search', {
|
||||
term: this.results.query,
|
||||
type,
|
||||
start: this.results[`${type}Tab`].next,
|
||||
nb: 30
|
||||
})
|
||||
},
|
||||
scrolledSearch() {
|
||||
if (this.currentTab.searchType === 'all') return
|
||||
|
||||
let currentTab = `${this.currentTab.searchType}Tab`
|
||||
|
||||
if (this.results[currentTab].next < this.results[currentTab].total) {
|
||||
this.search(this.currentTab.searchType)
|
||||
}
|
||||
},
|
||||
handleMainSearch(result) {
|
||||
// Hiding loading placeholder
|
||||
this.$root.$emit('updateSearchLoadingState', false)
|
||||
|
||||
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 }
|
||||
this.results.query = result.QUERY
|
||||
},
|
||||
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
|
||||
},
|
||||
isTabLoaded(tab) {
|
||||
return this.loadedTabs.indexOf(tab.searchType) !== -1 || tab.searchType === 'all'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
performScrolledSearch(needToSearch) {
|
||||
if (!needToSearch) return
|
||||
|
||||
this.scrolledSearch(needToSearch)
|
||||
},
|
||||
currentTab(newTab) {
|
||||
if (this.isTabLoaded(newTab)) return
|
||||
|
||||
this.search(newTab.searchType)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
862
src/components/pages/Settings.vue
Normal file
862
src/components/pages/Settings.vue
Normal file
@@ -0,0 +1,862 @@
|
||||
<template>
|
||||
<div id="settings_tab" class="main_tabcontent fixed_footer" ref="root">
|
||||
<h2 class="page_heading">{{ $t('settings.title') }}</h2>
|
||||
|
||||
<div id="logged_in_info" v-if="isLoggedIn" ref="loggedInInfo">
|
||||
<img id="settings_picture" :src="pictureHref" alt="Profile Picture" ref="userpicture" class="circle" />
|
||||
|
||||
<i18n path="settings.login.loggedIn" tag="p">
|
||||
<strong place="username" id="settings_username" ref="username">{{ user.name || 'not logged' }}</strong>
|
||||
</i18n>
|
||||
|
||||
<button id="settings_btn_logout" @click="logout">{{ $t('settings.login.logout') }}</button>
|
||||
|
||||
<select v-if="accounts.length" id="family_account" v-model="accountNum" @change="changeAccount">
|
||||
<option v-for="(account, i) in accounts" :key="account" :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>{{ $t('settings.login.title') }}
|
||||
</h3>
|
||||
<div class="inline-flex">
|
||||
<input
|
||||
autocomplete="off"
|
||||
type="password"
|
||||
:value="arl"
|
||||
id="login_input_arl"
|
||||
ref="loginInput"
|
||||
placeholder="ARL"
|
||||
/>
|
||||
<button id="settings_btn_copyArl" class="only_icon" @click="copyARLtoClipboard">
|
||||
<i class="material-icons">assignment</i>
|
||||
</button>
|
||||
</div>
|
||||
<a href="https://codeberg.org/RemixDev/deemix/wiki/Getting-your-own-ARL" target="_blank">
|
||||
{{ $t('settings.login.arl.question') }}
|
||||
</a>
|
||||
<a id="settings_btn_applogin" v-if="clientMode" href="#" @click="appLogin">
|
||||
{{ $t('settings.login.login') }}
|
||||
</a>
|
||||
<button id="settings_btn_updateArl" @click="login" style="width: 100%">
|
||||
{{ $t('settings.login.arl.update') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<h3 class="settings-group__header settings-group__header--with-icon">
|
||||
<i class="material-icons">language</i>{{ $t('settings.languages') }}
|
||||
</h3>
|
||||
<div>
|
||||
<span
|
||||
v-for="locale in locales"
|
||||
:key="locale"
|
||||
class="locale-flag"
|
||||
:class="{ 'locale-flag--current': currentLocale === locale }"
|
||||
@click="changeLocale(locale)"
|
||||
v-html="flags[locale]"
|
||||
:title="locale"
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<h3 class="settings-group__header settings-group__header--with-icon">
|
||||
<i class="material-icons">web</i>{{ $t('settings.appearance.title') }}
|
||||
</h3>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="changeSlimDownloads" />
|
||||
<span class="checkbox_text">{{ $t('settings.appearance.slimDownloadTab') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<h3 class="settings-group__header settings-group__header--with-icon">
|
||||
<i class="material-icons">folder</i>{{ $t('settings.downloadPath.title') }}
|
||||
</h3>
|
||||
<div class="inline-flex">
|
||||
<input autocomplete="off" type="text" v-model="settings.downloadLocation" />
|
||||
<button id="select_downloads_folder" v-if="clientMode" class="only_icon" @click="selectDownloadFolder">
|
||||
<i class="material-icons">folder</i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<h3 class="settings-group__header settings-group__header--with-icon">
|
||||
<i class="material-icons">font_download</i>{{ $t('settings.templates.title') }}
|
||||
</h3>
|
||||
|
||||
<p>{{ $t('settings.templates.tracknameTemplate') }}</p>
|
||||
<input type="text" v-model="settings.tracknameTemplate" />
|
||||
|
||||
<p>{{ $t('settings.templates.albumTracknameTemplate') }}</p>
|
||||
<input type="text" v-model="settings.albumTracknameTemplate" />
|
||||
|
||||
<p>{{ $t('settings.templates.playlistTracknameTemplate') }}</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>{{ $t('settings.folders.title') }}
|
||||
</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">{{ $t('settings.folders.createPlaylistFolder') }}</span>
|
||||
</label>
|
||||
<div class="input_group" v-if="settings.createPlaylistFolder">
|
||||
<p class="input_group_text">{{ $t('settings.folders.playlistNameTemplate') }}</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">{{ $t('settings.folders.createArtistFolder') }}</span>
|
||||
</label>
|
||||
|
||||
<div class="input_group" v-if="settings.createArtistFolder">
|
||||
<p class="input_group_text">{{ $t('settings.folders.artistNameTemplate') }}</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">{{ $t('settings.folders.createAlbumFolder') }}</span>
|
||||
</label>
|
||||
|
||||
<div class="input_group" v-if="settings.createAlbumFolder">
|
||||
<p class="input_group_text">{{ $t('settings.folders.albumNameTemplate') }}</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">{{ $t('settings.folders.createCDFolder') }}</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.createStructurePlaylist" />
|
||||
<span class="checkbox_text">{{ $t('settings.folders.createStructurePlaylist') }}</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.createSingleFolder" />
|
||||
<span class="checkbox_text">{{ $t('settings.folders.createSingleFolder') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<h3 class="settings-group__header settings-group__header--with-icon">
|
||||
<i class="material-icons">title</i>{{ $t('settings.trackTitles.title') }}
|
||||
</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">{{ $t('settings.trackTitles.padTracks') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="settings-container__third">
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">{{ $t('settings.trackTitles.paddingSize') }}</p>
|
||||
<input max="10" type="number" v-model="settings.paddingSize" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-container__third">
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">{{ $t('settings.trackTitles.illegalCharacterReplacer') }}</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>{{ $t('settings.downloads.title') }}
|
||||
</h3>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">{{ $t('settings.downloads.queueConcurrency') }}</p>
|
||||
<input type="number" min="1" v-model.number="settings.queueConcurrency" />
|
||||
</div>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">{{ $t('settings.downloads.maxBitrate.title') }}</p>
|
||||
<select v-model="settings.maxBitrate">
|
||||
<option value="9">{{ $t('settings.downloads.maxBitrate.9') }}</option>
|
||||
<option value="3">{{ $t('settings.downloads.maxBitrate.3') }}</option>
|
||||
<option value="1">{{ $t('settings.downloads.maxBitrate.1') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">{{ $t('settings.downloads.overwriteFile.title') }}</p>
|
||||
<select v-model="settings.overwriteFile">
|
||||
<option value="y">{{ $t('settings.downloads.overwriteFile.y') }}</option>
|
||||
<option value="n">{{ $t('settings.downloads.overwriteFile.n') }}</option>
|
||||
<option value="e">{{ $t('settings.downloads.overwriteFile.e') }}</option>
|
||||
<option value="b">{{ $t('settings.downloads.overwriteFile.b') }}</option>
|
||||
<option value="t">{{ $t('settings.downloads.overwriteFile.t') }}</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">{{ $t('settings.downloads.fallbackBitrate') }}</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.fallbackSearch" />
|
||||
<span class="checkbox_text">{{ $t('settings.downloads.fallbackSearch') }}</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">{{ $t('settings.downloads.logErrors') }}</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.logSearched" />
|
||||
<span class="checkbox_text">{{ $t('settings.downloads.logSearched') }}</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">{{ $t('settings.downloads.syncedLyrics') }}</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.createM3U8File" />
|
||||
<span class="checkbox_text">{{ $t('settings.downloads.createM3U8File') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input_group" v-if="settings.createM3U8File">
|
||||
<p class="input_group_text">{{ $t('settings.downloads.playlistFilenameTemplate') }}</p>
|
||||
<input type="text" v-model="settings.playlistFilenameTemplate" />
|
||||
</div>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.saveDownloadQueue" />
|
||||
<span class="checkbox_text">{{ $t('settings.downloads.saveDownloadQueue') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<h3 class="settings-group__header settings-group__header--with-icon">
|
||||
<i class="material-icons">album</i>{{ $t('settings.covers.title') }}
|
||||
</h3>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.saveArtwork" />
|
||||
<span class="checkbox_text">{{ $t('settings.covers.saveArtwork') }}</span>
|
||||
</label>
|
||||
|
||||
<div class="input_group" v-if="settings.saveArtwork">
|
||||
<p class="input_group_text">{{ $t('settings.covers.coverImageTemplate') }}</p>
|
||||
<input type="text" v-model="settings.coverImageTemplate" />
|
||||
</div>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.saveArtworkArtist" />
|
||||
<span class="checkbox_text">{{ $t('settings.covers.saveArtworkArtist') }}</span>
|
||||
</label>
|
||||
|
||||
<div class="input_group" v-if="settings.saveArtworkArtist">
|
||||
<p class="input_group_text">{{ $t('settings.covers.artistImageTemplate') }}</p>
|
||||
<input type="text" v-model="settings.artistImageTemplate" />
|
||||
</div>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">{{ $t('settings.covers.localArtworkSize') }}</p>
|
||||
<input type="number" min="100" max="10000" step="100" v-model.number="settings.localArtworkSize" />
|
||||
<p v-if="settings.localArtworkSize > 1200" class="input_group_text" style="opacity: 0.75; color: #ffcc22">
|
||||
⚠️ {{ $t('settings.covers.imageSizeWarning') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">{{ $t('settings.covers.embeddedArtworkSize') }}</p>
|
||||
<input type="number" min="100" max="10000" step="100" v-model.number="settings.embeddedArtworkSize" />
|
||||
<p v-if="settings.embeddedArtworkSize > 1200" class="input_group_text" style="opacity: 0.75; color: #ffcc22">
|
||||
⚠️ {{ $t('settings.covers.imageSizeWarning') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">{{ $t('settings.covers.localArtworkFormat.title') }}</p>
|
||||
<select v-model="settings.localArtworkFormat">
|
||||
<option value="jpg">{{ $t('settings.covers.localArtworkFormat.jpg') }}</option>
|
||||
<option value="png">{{ $t('settings.covers.localArtworkFormat.png') }}</option>
|
||||
<option value="jpg,png">{{ $t('settings.covers.localArtworkFormat.both') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.embeddedArtworkPNG" />
|
||||
<span class="checkbox_text">{{ $t('settings.covers.embeddedArtworkPNG') }}</span>
|
||||
</label>
|
||||
<p v-if="settings.embeddedArtworkPNG" style="opacity: 0.75; color: #ffcc22">
|
||||
⚠️ {{ $t('settings.covers.embeddedPNGWarning') }}
|
||||
</p>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.coverDescriptionUTF8" />
|
||||
<span class="checkbox_text">{{ $t('settings.covers.coverDescriptionUTF8') }}</span>
|
||||
</label>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">{{ $t('settings.covers.jpegImageQuality') }}</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>{{ $t('settings.tags.head') }}
|
||||
</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">{{ $t('settings.tags.title') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.artist" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.artist') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.album" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.album') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.cover" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.cover') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.trackNumber" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.trackNumber') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.trackTotal" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.trackTotal') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.discNumber" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.discNumber') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.discTotal" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.discTotal') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.albumArtist" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.albumArtist') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.genre" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.genre') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.year" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.year') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.date" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.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">{{ $t('settings.tags.explicit') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.isrc" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.isrc') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.length" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.length') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.barcode" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.barcode') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.bpm" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.bpm') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.replayGain" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.replayGain') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.label" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.label') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.lyrics" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.lyrics') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.syncedLyrics" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.syncedLyrics') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.copyright" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.copyright') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.composer" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.composer') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.involvedPeople" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.involvedPeople') }}</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>{{ $t('settings.other.title') }}
|
||||
</h3>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.savePlaylistAsCompilation" />
|
||||
<span class="checkbox_text">{{ $t('settings.other.savePlaylistAsCompilation') }}</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.useNullSeparator" />
|
||||
<span class="checkbox_text">{{ $t('settings.other.useNullSeparator') }}</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.saveID3v1" />
|
||||
<span class="checkbox_text">{{ $t('settings.other.saveID3v1') }}</span>
|
||||
</label>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">{{ $t('settings.other.multiArtistSeparator.title') }}</p>
|
||||
<select v-model="settings.tags.multiArtistSeparator">
|
||||
<option value="nothing">{{ $t('settings.other.multiArtistSeparator.nothing') }}</option>
|
||||
<option value="default">{{ $t('settings.other.multiArtistSeparator.default') }}</option>
|
||||
<option value="andFeat">{{ $t('settings.other.multiArtistSeparator.andFeat') }}</option>
|
||||
<option value=" & ">{{ $t('settings.other.multiArtistSeparator.using', { separator: ' & ' }) }}</option>
|
||||
<option value=",">{{ $t('settings.other.multiArtistSeparator.using', { separator: ',' }) }}</option>
|
||||
<option value=", ">{{ $t('settings.other.multiArtistSeparator.using', { separator: ', ' }) }}</option>
|
||||
<option value="/">{{ $t('settings.other.multiArtistSeparator.using', { separator: '/' }) }}</option>
|
||||
<option value=" / ">{{ $t('settings.other.multiArtistSeparator.using', { separator: ' / ' }) }}</option>
|
||||
<option value=";">{{ $t('settings.other.multiArtistSeparator.using', { separator: ';' }) }}</option>
|
||||
<option value="; ">{{ $t('settings.other.multiArtistSeparator.using', { separator: '; ' }) }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.singleAlbumArtist" />
|
||||
<span class="checkbox_text">{{ $t('settings.other.singleAlbumArtist') }}</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.albumVariousArtists" />
|
||||
<span class="checkbox_text">{{ $t('settings.other.albumVariousArtists') }}</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.removeAlbumVersion" />
|
||||
<span class="checkbox_text">{{ $t('settings.other.removeAlbumVersion') }}</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.removeDuplicateArtists" />
|
||||
<span class="checkbox_text">{{ $t('settings.other.removeDuplicateArtists') }}</span>
|
||||
</label>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">{{ $t('settings.other.dateFormat.title') }}</p>
|
||||
<select v-model="settings.dateFormat">
|
||||
<option value="Y-M-D">
|
||||
{{
|
||||
`${$t('settings.other.dateFormat.year')}-${$t('settings.other.dateFormat.month')}-${$t(
|
||||
'settings.other.dateFormat.day'
|
||||
)}`
|
||||
}}
|
||||
</option>
|
||||
<option value="Y-D-M">
|
||||
{{
|
||||
`${$t('settings.other.dateFormat.year')}-${$t('settings.other.dateFormat.day')}-${$t(
|
||||
'settings.other.dateFormat.month'
|
||||
)}`
|
||||
}}
|
||||
</option>
|
||||
<option value="D-M-Y">
|
||||
{{
|
||||
`${$t('settings.other.dateFormat.day')}-${$t('settings.other.dateFormat.month')}-${$t(
|
||||
'settings.other.dateFormat.year'
|
||||
)}`
|
||||
}}
|
||||
</option>
|
||||
<option value="M-D-Y">
|
||||
{{
|
||||
`${$t('settings.other.dateFormat.month')}-${$t('settings.other.dateFormat.day')}-${$t(
|
||||
'settings.other.dateFormat.year'
|
||||
)}`
|
||||
}}
|
||||
</option>
|
||||
<option value="Y">{{ $t('settings.other.dateFormat.year') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">{{ $t('settings.other.featuredToTitle.title') }}</p>
|
||||
<select v-model="settings.featuredToTitle">
|
||||
<option value="0">{{ $t('settings.other.featuredToTitle.0') }}</option>
|
||||
<option value="1">{{ $t('settings.other.featuredToTitle.1') }}</option>
|
||||
<option value="3">{{ $t('settings.other.featuredToTitle.3') }}</option>
|
||||
<option value="2">{{ $t('settings.other.featuredToTitle.2') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">{{ $t('settings.other.titleCasing') }}</p>
|
||||
<select v-model="settings.titleCasing">
|
||||
<option value="nothing">{{ $t('settings.other.casing.nothing') }}</option>
|
||||
<option value="lower">{{ $t('settings.other.casing.lower') }}</option>
|
||||
<option value="upper">{{ $t('settings.other.casing.upper') }}</option>
|
||||
<option value="start">{{ $t('settings.other.casing.start') }}</option>
|
||||
<option value="sentence">{{ $t('settings.other.casing.sentence') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">{{ $t('settings.other.artistCasing') }}</p>
|
||||
<select v-model="settings.artistCasing">
|
||||
<option value="nothing">{{ $t('settings.other.casing.nothing') }}</option>
|
||||
<option value="lower">{{ $t('settings.other.casing.lower') }}</option>
|
||||
<option value="upper">{{ $t('settings.other.casing.upper') }}</option>
|
||||
<option value="start">{{ $t('settings.other.casing.start') }}</option>
|
||||
<option value="sentence">{{ $t('settings.other.casing.sentence') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">{{ $t('settings.other.previewVolume') }}</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">{{ $t('settings.other.executeCommand.title') }}</p>
|
||||
<p class="secondary-text">{{ $t('settings.other.executeCommand.description') }}</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>
|
||||
{{ $t('settings.spotify.title') }}
|
||||
</h3>
|
||||
<a href="https://codeberg.org/RemixDev/deemix/wiki/Enabling-Spotify-Features" target="_blank">
|
||||
{{ $t('settings.spotify.question') }}
|
||||
</a>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">{{ $t('settings.spotify.clientID') }}</p>
|
||||
<input type="text" v-model="spotifyFeatures.clientId" />
|
||||
</div>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">{{ $t('settings.spotify.clientSecret') }}</p>
|
||||
<input type="password" v-model="spotifyFeatures.clientSecret" />
|
||||
</div>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">{{ $t('settings.spotify.username') }}</p>
|
||||
<input type="text" v-model="spotifyUser" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<button @click="resetSettings">{{ $t('settings.reset') }}</button>
|
||||
<button @click="saveSettings">{{ $t('settings.save') }}</button>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
#logged_in_info {
|
||||
height: 250px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.locale-flag {
|
||||
width: 60px;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
&.locale-flag--current {
|
||||
svg {
|
||||
filter: brightness(1);
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 40px !important;
|
||||
height: 40px !important;
|
||||
filter: brightness(0.5);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
|
||||
import { getSettingsData } from '@/data/settings'
|
||||
|
||||
import { toast } from '@/utils/toasts'
|
||||
import { socket } from '@/utils/socket'
|
||||
import { flags } from '@/utils/flags'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
flags,
|
||||
currentLocale: this.$i18n.locale,
|
||||
locales: this.$i18n.availableLocales,
|
||||
settings: {
|
||||
tags: {}
|
||||
},
|
||||
lastSettings: {},
|
||||
spotifyFeatures: {},
|
||||
lastCredentials: {},
|
||||
defaultSettings: {},
|
||||
lastUser: '',
|
||||
spotifyUser: '',
|
||||
slimDownloads: false,
|
||||
previewVolume: window.vol,
|
||||
accountNum: 0,
|
||||
accounts: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
arl: 'getARL',
|
||||
user: 'getUser',
|
||||
isLoggedIn: 'isLoggedIn',
|
||||
clientMode: 'getClientMode'
|
||||
}),
|
||||
needToWait() {
|
||||
return Object.keys(this.getSettings).length === 0
|
||||
},
|
||||
changeSlimDownloads: {
|
||||
get() {
|
||||
return this.slimDownloads
|
||||
},
|
||||
set(wantSlimDownloads) {
|
||||
this.slimDownloads = wantSlimDownloads
|
||||
document.getElementById('download_list').classList.toggle('slim', wantSlimDownloads)
|
||||
localStorage.setItem('slimDownloads', wantSlimDownloads)
|
||||
}
|
||||
},
|
||||
pictureHref() {
|
||||
// Default image: https://e-cdns-images.dzcdn.net/images/user/125x125-000000-80-0-0.jpg
|
||||
return `https://e-cdns-images.dzcdn.net/images/user/${this.user.picture}/125x125-000000-80-0-0.jpg`
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
const { settingsData, defaultSettingsData, spotifyCredentials } = await getSettingsData()
|
||||
|
||||
this.defaultSettings = defaultSettingsData
|
||||
this.initSettings(settingsData, spotifyCredentials)
|
||||
|
||||
let storedAccountNum = localStorage.getItem('accountNum')
|
||||
|
||||
if (storedAccountNum) {
|
||||
this.accountNum = storedAccountNum
|
||||
}
|
||||
|
||||
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('updateSettings', this.updateSettings)
|
||||
socket.on('accountChanged', this.accountChanged)
|
||||
socket.on('familyAccounts', this.initAccounts)
|
||||
socket.on('downloadFolderSelected', this.downloadFolderSelected)
|
||||
socket.on('applogin_arl', this.loggedInViaDeezer)
|
||||
|
||||
this.$on('hook:destroyed', () => {
|
||||
socket.off('updateSettings')
|
||||
socket.off('accountChanged')
|
||||
socket.off('familyAccounts')
|
||||
socket.off('downloadFolderSelected')
|
||||
socket.off('applogin_arl')
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
dispatchARL: 'setARL'
|
||||
}),
|
||||
revertSettings() {
|
||||
this.settings = JSON.parse(JSON.stringify(this.lastSettings))
|
||||
},
|
||||
revertCredentials() {
|
||||
this.spotifyCredentials = JSON.parse(JSON.stringify(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(this.$t('settings.toasts.ARLcopied'), 'assignment')
|
||||
},
|
||||
changeLocale(newLocale) {
|
||||
this.$i18n.locale = newLocale
|
||||
this.currentLocale = newLocale
|
||||
localStorage.setItem('locale', newLocale)
|
||||
},
|
||||
updateMaxVolume() {
|
||||
localStorage.setItem('previewVolume', this.previewVolume.preview_max_volume)
|
||||
},
|
||||
saveSettings() {
|
||||
this.lastSettings = JSON.parse(JSON.stringify(this.settings))
|
||||
this.lastCredentials = JSON.parse(JSON.stringify(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)
|
||||
},
|
||||
selectDownloadFolder() {
|
||||
socket.emit('selectDownloadFolder')
|
||||
},
|
||||
downloadFolderSelected(folder) {
|
||||
this.$set(this.settings, 'downloadLocation', folder)
|
||||
},
|
||||
loadSettings(data) {
|
||||
this.lastSettings = JSON.parse(JSON.stringify(data))
|
||||
this.settings = JSON.parse(JSON.stringify(data))
|
||||
},
|
||||
loadCredentials(credentials) {
|
||||
this.lastCredentials = JSON.parse(JSON.stringify(credentials))
|
||||
this.spotifyFeatures = JSON.parse(JSON.stringify(credentials))
|
||||
},
|
||||
loggedInViaDeezer(arl) {
|
||||
this.dispatchARL({ arl })
|
||||
socket.emit('login', arl, true, this.accountNum)
|
||||
// this.login()
|
||||
},
|
||||
login() {
|
||||
let newArl = this.$refs.loginInput.value.trim()
|
||||
|
||||
if (newArl && newArl !== this.arl) {
|
||||
socket.emit('login', newArl, true, this.accountNum)
|
||||
}
|
||||
},
|
||||
appLogin(e) {
|
||||
socket.emit('applogin')
|
||||
},
|
||||
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) {
|
||||
// this.loadDefaultSettings()
|
||||
this.loadSettings(settings)
|
||||
this.loadCredentials(credentials)
|
||||
|
||||
toast(this.$t('settings.toasts.init'), 'settings')
|
||||
},
|
||||
updateSettings(newSettings, newCredentials) {
|
||||
this.loadSettings(newSettings)
|
||||
this.loadCredentials(newCredentials)
|
||||
|
||||
toast(this.$t('settings.toasts.update'), 'settings')
|
||||
},
|
||||
resetSettings() {
|
||||
this.settings = JSON.parse(JSON.stringify(this.defaultSettings))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
306
src/components/pages/Tracklist.vue
Normal file
306
src/components/pages/Tracklist.vue
Normal file
@@ -0,0 +1,306 @@
|
||||
<template>
|
||||
<div id="tracklist_tab" class="main_tabcontent fixed_footer image_header" ref="root">
|
||||
<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>{{ $tc('globals.listTabs.title', 1) }}</th>
|
||||
<th>{{ $tc('globals.listTabs.artist', 1) }}</th>
|
||||
<th v-if="type === 'playlist'">{{ $tc('globals.listTabs.album', 1) }}</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 !== 'spotifyPlaylist'">
|
||||
<template v-for="(track, index) in body">
|
||||
<tr v-if="track.type == 'track'" @click="selectRow(index, 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"
|
||||
:title="$t('globals.play_hint')"
|
||||
>
|
||||
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>
|
||||
<router-link
|
||||
tag="td"
|
||||
class="table__cell--medium table__cell--center clickable"
|
||||
:to="{ name: 'Artist', params: { id: track.artist.id } }"
|
||||
>
|
||||
{{ track.artist.name }}
|
||||
</router-link>
|
||||
<router-link
|
||||
tag="td"
|
||||
v-if="type === 'playlist'"
|
||||
class="table__cell--medium table__cell--center clickable"
|
||||
:to="{ name: 'Album', params: { id: track.album.id } }"
|
||||
>
|
||||
{{ track.album.title }}
|
||||
</router-link>
|
||||
<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"
|
||||
:title="$t('globals.play_hint')"
|
||||
>
|
||||
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.4; margin-top: 8px; display: inline-block; font-size: 13px">{{ label }}</span>
|
||||
<footer>
|
||||
<button @click.stop="addToQueue" :data-link="link">
|
||||
{{ `${$t('globals.download', { thing: $tc(`globals.listTabs.${type}`, 1) })}` }}
|
||||
</button>
|
||||
<button class="with_icon" @click.stop="addToQueue" :data-link="selectedLinks()">
|
||||
{{ $t('tracklist.downloadSelection') }}<i class="material-icons">file_download</i>
|
||||
</button>
|
||||
<button class="back-button" @click="$router.back()">{{ $t('globals.back') }}</button>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isEmpty } from 'lodash-es'
|
||||
import { socket } from '@/utils/socket'
|
||||
import Downloads from '@/utils/downloads'
|
||||
import Utils from '@/utils/utils'
|
||||
import EventBus from '@/utils/EventBus'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
title: '',
|
||||
metadata: '',
|
||||
release_date: '',
|
||||
label: '',
|
||||
explicit: false,
|
||||
image: '',
|
||||
type: 'empty',
|
||||
link: '',
|
||||
body: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
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 = 'empty'
|
||||
this.body = []
|
||||
},
|
||||
addToQueue(e) {
|
||||
Downloads.sendAddToQueue(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 == 'spotifyPlaylist' ? item.uri : item.link)
|
||||
})
|
||||
}
|
||||
return selected.join(';')
|
||||
},
|
||||
convertDuration: Utils.convertDuration,
|
||||
showAlbum(data) {
|
||||
this.reset()
|
||||
|
||||
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} • ${this.$tc('globals.listTabs.trackN', numberOfTracks)}`
|
||||
this.release_date = release_date.substring(0, 10)
|
||||
this.image = cover_xl
|
||||
|
||||
if (isEmpty(albumTracks)) {
|
||||
this.body = null
|
||||
} else {
|
||||
this.body = albumTracks
|
||||
}
|
||||
},
|
||||
showPlaylist(data) {
|
||||
this.reset()
|
||||
|
||||
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 = `${this.$t('globals.by', { artist: creatorName })} • ${this.$tc(
|
||||
'globals.listTabs.trackN',
|
||||
numberOfTracks
|
||||
)}`
|
||||
|
||||
if (isEmpty(playlistTracks)) {
|
||||
this.body = null
|
||||
} else {
|
||||
this.body = playlistTracks
|
||||
}
|
||||
},
|
||||
showSpotifyPlaylist(data) {
|
||||
this.reset()
|
||||
|
||||
const {
|
||||
uri: playlistURI,
|
||||
name: playlistName,
|
||||
images,
|
||||
images: { length: numberOfImages },
|
||||
owner: { display_name: ownerName },
|
||||
tracks: playlistTracks,
|
||||
tracks: { length: numberOfTracks }
|
||||
} = data
|
||||
|
||||
this.type = 'spotifyPlaylist'
|
||||
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 = `${this.$t('globals.by', { artist: ownerName })} • ${this.$tc(
|
||||
'globals.listTabs.trackN',
|
||||
numberOfTracks
|
||||
)}`
|
||||
|
||||
if (isEmpty(playlistTracks)) {
|
||||
this.body = null
|
||||
} else {
|
||||
this.body = playlistTracks
|
||||
}
|
||||
},
|
||||
selectRow(index, track) {
|
||||
track.selected = !track.selected
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
EventBus.$on('tracklistTab:selectRow', this.selectRow)
|
||||
|
||||
socket.on('show_album', this.showAlbum)
|
||||
socket.on('show_playlist', this.showPlaylist)
|
||||
socket.on('show_spotifyplaylist', this.showSpotifyPlaylist)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
Reference in New Issue
Block a user