Merge branch 'restful-refactor'

This commit is contained in:
Roberto Tonino 2021-05-05 20:43:34 +02:00
commit 1b063ac24c
24 changed files with 633 additions and 638 deletions

View File

@ -7,5 +7,6 @@
"trailingComma": "none",
"printWidth": 120,
"arrowParens": "avoid",
"vueIndentScriptAndStyle": false
}
"vueIndentScriptAndStyle": false,
"endOfLine": "lf"
}

View File

@ -17,7 +17,6 @@
"@vue/composition-api": "^1.0.0-beta.21",
"flag-icon-css": "^3.5.0",
"lodash-es": "^4.17.15",
"socket.io-client": "^3.1.0",
"svg-country-flags": "^1.2.9",
"toastify-js": "^1.9.3",
"vue": "^2.6.12",

File diff suppressed because one or more lines are too long

View File

@ -28,10 +28,6 @@ export default {
find: 'vue',
replacement: 'vue/dist/vue.esm'
},
{
find: 'socket.io-client',
replacement: 'socket.io-client/dist/socket.io.min'
},
{
find: '@',
replacement: __dirname + '/src'

View File

@ -69,7 +69,8 @@ export default {
// ConfirmModal
},
mounted() {
socket.on('connect', () => {
socket.addEventListener('open', (event) => {
console.log("Connected to WebSocket")
this.isSocketConnected = true
})
}

View File

@ -19,18 +19,42 @@ import router from '@/router'
import store from '@/store'
import { socket } from '@/utils/socket'
import { fetchData } from '@/utils/api'
import { toast } from '@/utils/toasts'
import { isValidURL } from '@/utils/utils'
import { sendAddToQueue } from '@/utils/downloads'
/* ===== App initialization ===== */
function startApp() {
async function startApp() {
new Vue({
store,
router,
i18n,
render: h => h(App)
}).$mount('#app')
const connectResponse = await (await fetch('connect')).json()
store.dispatch('setAppInfo', connectResponse.update)
if (connectResponse.autologin) {
console.info('Autologin')
let arl = localStorage.getItem('arl')
const accountNum = localStorage.getItem('accountNum')
if (arl) {
arl = arl.trim()
let result
if (accountNum !== 0) {
result = await fetchData('login', { arl, force: true, child: accountNum || 0 })
} else {
result = await fetchData('login', { arl })
}
loggedIn(result)
}
}
}
function initClient() {
@ -52,7 +76,7 @@ document.addEventListener('paste', pasteEvent => {
if (router.currentRoute.name === 'Link Analyzer') {
socket.emit('analyzeLink', pastedText)
} else {
if (pastedText.indexOf("\n") != -1) pastedText = pastedText.replace(/\n/g, ';');
if (pastedText.indexOf('\n') != -1) pastedText = pastedText.replace(/\n/g, ';')
sendAddToQueue(pastedText)
}
} else {
@ -82,30 +106,11 @@ function setClientModeKeyBindings() {
/* ===== Socketio listeners ===== */
// Debug messages for socketio
socket.on('message', function(msg) {
socket.on('message', function (msg) {
console.log(msg)
})
socket.on('logging_in', function() {
toast(i18n.t('toasts.loggingIn'), 'loading', false, 'login-toast')
})
socket.on('init_autologin', function() {
let arl = localStorage.getItem('arl')
let accountNum = localStorage.getItem('accountNum')
if (arl) {
arl = arl.trim()
if (accountNum != 0) {
socket.emit('login', arl, true, accountNum)
} else {
socket.emit('login', arl)
}
}
})
socket.on('logged_in', function(data) {
function loggedIn(data) {
const { status, user } = data
switch (status) {
@ -138,6 +143,15 @@ socket.on('logged_in', function(data) {
// $('#settings_picture').attr('src', `https://e-cdns-images.dzcdn.net/images/user/125x125-000000-80-0-0.jpg`)
// document.getElementById('home_not_logged_in').classList.remove('hide')
}
}
/*
socket.on('logging_in', function() {
toast(i18n.t('toasts.loggingIn'), 'loading', false, 'login-toast')
})
socket.on('logged_in', function(data) {
})
socket.on('logged_out', function() {
@ -145,40 +159,41 @@ socket.on('logged_out', function() {
store.dispatch('logout')
})
*/
socket.on('restoringQueue', function() {
socket.on('restoringQueue', function () {
toast(i18n.t('toasts.restoringQueue'), 'loading', false, 'restoring_queue')
})
socket.on('cancellingCurrentItem', function(uuid) {
socket.on('cancellingCurrentItem', function (uuid) {
toast(i18n.t('toasts.cancellingCurrentItem'), 'loading', false, 'cancelling_' + uuid)
})
socket.on('currentItemCancelled', function(uuid) {
socket.on('currentItemCancelled', function (uuid) {
toast(i18n.t('toasts.currentItemCancelled'), 'done', true, 'cancelling_' + uuid)
})
socket.on('startAddingArtist', function(data) {
socket.on('startAddingArtist', function (data) {
toast(i18n.t('toasts.startAddingArtist', { artist: data.name }), 'loading', false, 'artist_' + data.id)
})
socket.on('finishAddingArtist', function(data) {
socket.on('finishAddingArtist', function (data) {
toast(i18n.t('toasts.finishAddingArtist', { artist: data.name }), 'done', true, 'artist_' + data.id)
})
socket.on('startConvertingSpotifyPlaylist', function(id) {
socket.on('startConvertingSpotifyPlaylist', function (id) {
toast(i18n.t('toasts.startConvertingSpotifyPlaylist'), 'loading', false, 'spotifyplaylist_' + id)
})
socket.on('finishConvertingSpotifyPlaylist', function(id) {
socket.on('finishConvertingSpotifyPlaylist', function (id) {
toast(i18n.t('toasts.finishConvertingSpotifyPlaylist'), 'done', true, 'spotifyplaylist_' + id)
})
socket.on('errorMessage', function(error) {
socket.on('errorMessage', function (error) {
toast(error, 'error')
})
socket.on('queueError', function(queueItem) {
socket.on('queueError', function (queueItem) {
if (queueItem.errid) {
toast(queueItem.link + ' - ' + i18n.t(`errors.ids.${queueItem.errid}`), 'error')
} else {
@ -186,18 +201,18 @@ socket.on('queueError', function(queueItem) {
}
})
socket.on('alreadyInQueue', function(data) {
socket.on('alreadyInQueue', function (data) {
toast(i18n.t('toasts.alreadyInQueue', { item: data.title }), 'playlist_add_check')
})
socket.on('loginNeededToDownload', function(data) {
socket.on('loginNeededToDownload', function (data) {
toast(i18n.t('toasts.loginNeededToDownload'), 'report')
})
socket.on('startGeneratingItems', function(data) {
socket.on('startGeneratingItems', function (data) {
toast(i18n.t('toasts.startGeneratingItems', { n: data.total }), 'loading', false, 'batch_' + data.uuid)
})
socket.on('finishGeneratingItems', function(data) {
socket.on('finishGeneratingItems', function (data) {
toast(i18n.t('toasts.finishGeneratingItems', { n: data.total }), 'done', true, 'batch_' + data.uuid)
})

View File

@ -113,23 +113,20 @@
import { computed, defineComponent, reactive, toRefs } from '@vue/composition-api'
import { links } from '@/data/sidebar'
import { socket } from '@/utils/socket'
import { useTheme } from '@/use/theme'
import deemixIcon from '@/assets/deemix-icon.svg'
export default defineComponent({
setup(props, ctx) {
const state = reactive({
activeTablink: 'home',
updateAvailable: false,
links
})
const { THEMES, currentTheme } = useTheme()
/* === Add update notification near info === */
socket.on('updateAvailable', () => {
state.updateAvailable = true
})
const updateAvailable = computed(() => ctx.root.$store.state.appInfo.updateAvailable)
ctx.root.$router.afterEach((to, from) => {
const linkInSidebar = state.links.find(link => link.routerName === to.name)
@ -141,6 +138,7 @@ export default defineComponent({
return {
...toRefs(state),
updateAvailable,
THEMES,
currentTheme,
isSlim: computed(() => ctx.root.$store.getters.getSlimSidebar),

View File

@ -26,9 +26,7 @@
<li v-html="$t('about.thanks')"></li>
<i18n path="about.upToDate.text" tag="li">
<template #newsChannel>
<a href="https://t.me/RemixDevNews" target="_blank">{{
$t('about.upToDate.newsChannel')
}}</a>
<a href="https://t.me/RemixDevNews" target="_blank">{{ $t('about.upToDate.newsChannel') }}</a>
</template>
</i18n>
</ul>
@ -42,9 +40,7 @@
<a href="https://git.rip/RemixDev/deemix" target="_blank">🚀 {{ $t('about.officialRepo') }}</a>
</li>
<li>
<a href="https://git.rip/RemixDev/deemix-webui" target="_blank">
💻 {{ $t('about.officialWebuiRepo') }}
</a>
<a href="https://git.rip/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>
@ -63,9 +59,7 @@
<ul>
<i18n path="about.questions.text" tag="li">
<template #subreddit>
<a href="https://www.reddit.com/r/deemix" target="_blank">{{
$t('about.questions.subreddit')
}}</a>
<a href="https://www.reddit.com/r/deemix" target="_blank">{{ $t('about.questions.subreddit') }}</a>
</template>
</i18n>
<li>
@ -241,7 +235,7 @@ ul {
</style>
<script>
import { defineComponent, ref, reactive, toRefs, onMounted, computed } from '@vue/composition-api'
import { computed, defineComponent, onMounted, reactive, toRefs } from '@vue/composition-api'
import { useOnline } from '@/use/online'

View File

@ -92,7 +92,6 @@ import { orderBy } from 'lodash-es'
import { BaseTabs, BaseTab } from '@components/globals/BaseTabs'
import { socket } from '@/utils/socket'
import { sendAddToQueue } from '@/utils/downloads'
import { checkNewRelease } from '@/utils/dates'
import { formatArtistData, getArtistData } from '@/data/artist'

View File

@ -6,38 +6,38 @@
<div class="release-grid">
<div
v-for="release in countries"
role="button"
:aria-label="release.title"
class="w-40 h-40 release clickable"
@click="getTrackList"
:data-title="release.title"
:data-id="release.id"
:key="release.id"
:aria-label="release.title"
:data-id="release.id"
:data-title="release.title"
class="w-40 h-40 release clickable"
role="button"
@click="getTrackList"
>
<img class="w-full rounded coverart" :src="release.picture_medium" />
<img :src="release.picture_medium" class="w-full rounded coverart" />
</div>
</div>
</div>
<div v-else>
<button class="btn btn-primary" @click="onChangeCountry">{{ $t('charts.changeCountry') }}</button>
<button class="btn btn-primary" @click.stop="addToQueue" :data-link="'https://www.deezer.com/playlist/' + id">
<button :data-link="'https://www.deezer.com/playlist/' + id" class="btn btn-primary" @click.stop="addToQueue">
{{ $t('charts.download') }}
</button>
<table class="table table--charts">
<tbody>
<tr v-for="track in chart" class="track_row">
<td class="p-3 text-center cursor-default" :class="{ first: track.position === 1 }">
<td :class="{ first: track.position === 1 }" class="p-3 text-center cursor-default">
{{ track.position }}
</td>
<td class="table__icon table__icon--big">
<span
@click="playPausePreview"
class="relative inline-block rounded cursor-pointer"
:data-preview="track.preview"
class="relative inline-block rounded cursor-pointer"
@click="playPausePreview"
>
<PreviewControls v-if="track.preview" />
<img class="rounded coverart" :src="track.album.cover_small" />
<img :src="track.album.cover_small" class="rounded coverart" />
</span>
</td>
<td class="table__cell--large">
@ -47,16 +47,16 @@
}}
</td>
<router-link
tag="td"
class="table__cell table__cell--medium table__cell--center clickable"
:to="{ name: 'Artist', params: { id: track.artist.id } }"
class="table__cell table__cell--medium table__cell--center clickable"
tag="td"
>
{{ track.artist.name }}
</router-link>
<router-link
tag="td"
class="table__cell--medium table__cell--center clickable"
:to="{ name: 'Album', params: { id: track.album.id } }"
class="table__cell--medium table__cell--center clickable"
tag="td"
>
{{ track.album.title }}
</router-link>
@ -64,15 +64,15 @@
{{ convertDuration(track.duration) }}
</td>
<td
class="cursor-pointer group"
@click.stop="addToQueue"
:data-link="track.link"
role="button"
aria-label="download"
class="cursor-pointer group"
role="button"
@click.stop="addToQueue"
>
<i
class="transition-colors duration-150 ease-in-out material-icons group-hover:text-primary"
:title="$t('globals.download_hint')"
class="transition-colors duration-150 ease-in-out material-icons group-hover:text-primary"
>
get_app
</i>
@ -85,14 +85,14 @@
</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 { getChartsData, getChartTracks } from '@/data/charts'
import PreviewControls from '@components/globals/PreviewControls.vue'
import { playPausePreview } from '@components/globals/TheTrackPreview.vue'
import { fetchData } from '@/utils/api'
export default {
components: {
@ -116,12 +116,12 @@ export default {
}
},
async created() {
socket.on('setChartTracks', this.setTracklist)
this.$on('hook:destroyed', () => {
socket.off('setChartTracks')
})
// socket.on('setChartTracks', this.setTracklist)
// this.$on('hook:destroyed', () => {
// socket.off('setChartTracks')
// })
let chartsData = await getChartsData()
let { data: chartsData } = await getChartsData()
let worldwideChart
chartsData = chartsData.filter(item => {
@ -147,17 +147,13 @@ export default {
const {
currentTarget: {
dataset: { title }
},
currentTarget: {
dataset: { id }
dataset: { title, id }
}
} = event
this.country = title
localStorage.setItem('chart', this.country)
this.id = id
socket.emit('getChartTracks', this.id)
},
setTracklist(data) {
this.chart = data
@ -185,6 +181,15 @@ export default {
localStorage.setItem('chart', this.country)
}
}
},
watch: {
id(newId) {
const isActualChart = newId !== 0
if (isActualChart) {
getChartTracks(newId).then(response => this.setTracklist(response.result))
}
}
}
}
</script>

View File

@ -3,13 +3,13 @@
<h1 class="mb-8 text-5xl">
{{ $t('favorites.title') }}
<div
@click="refreshFavorites"
class="inline-block clickable"
ref="reloadButton"
role="button"
aria-label="reload"
class="inline-block clickable"
role="button"
@click="refreshFavorites"
>
<i class="material-icons" :class="{ spin: isRefreshingFavorites }">sync</i>
<i :class="{ spin: isRefreshingFavorites }" class="material-icons">sync</i>
</div>
</h1>
@ -19,7 +19,7 @@
</BaseTab>
</BaseTabs>
<button class="btn btn-primary" v-if="!activeTabEmpty" style="margin-bottom: 2rem" @click="downloadAllOfType">
<button v-if="!activeTabEmpty" class="btn btn-primary" style="margin-bottom: 2rem" @click="downloadAllOfType">
{{ $t('globals.download', { thing: $tc(`globals.listTabs.${activeTab}N`, getTabLength()) }) }}
</button>
@ -27,10 +27,10 @@
<div v-if="playlists.length == 0">
<h1>{{ $t('favorites.noPlaylists') }}</h1>
</div>
<div class="release-grid" v-if="playlists.length > 0 || spotifyPlaylists > 0">
<div class="release" v-for="release in playlists" :key="release.id">
<router-link tag="div" class="cursor-pointer" :to="{ name: 'Playlist', params: { id: release.id } }">
<CoverContainer is-rounded :cover="release.picture_medium" :link="release.link" @click.stop="addToQueue" />
<div v-if="playlists.length > 0 || spotifyPlaylists > 0" class="release-grid">
<div v-for="release in playlists" :key="release.id" class="release">
<router-link :to="{ name: 'Playlist', params: { id: release.id } }" class="cursor-pointer" tag="div">
<CoverContainer :cover="release.picture_medium" :link="release.link" is-rounded @click.stop="addToQueue" />
<p class="primary-text">{{ release.title }}</p>
</router-link>
@ -44,9 +44,9 @@
</p>
</div>
<div class="release" v-for="release in spotifyPlaylists" :key="release.id">
<router-link tag="div" class="cursor-pointer" :to="{ name: 'Spotify Playlist', params: { id: release.id } }">
<CoverContainer is-rounded :cover="release.picture_medium" :link="release.link" @click.stop="addToQueue" />
<div v-for="release in spotifyPlaylists" :key="release.id" class="release">
<router-link :to="{ name: 'Spotify Playlist', params: { id: release.id } }" class="cursor-pointer" tag="div">
<CoverContainer :cover="release.picture_medium" :link="release.link" is-rounded @click.stop="addToQueue" />
<p class="primary-text">{{ release.title }}</p>
</router-link>
@ -66,15 +66,15 @@
<div v-if="albums.length == 0">
<h1>{{ $t('favorites.noAlbums') }}</h1>
</div>
<div class="release-grid" v-if="albums.length > 0">
<div v-if="albums.length > 0" class="release-grid">
<router-link
tag="div"
class="release clickable"
v-for="release in albums"
:key="release.id"
:to="{ name: 'Album', params: { id: release.id } }"
class="release clickable"
tag="div"
>
<CoverContainer is-rounded :cover="release.cover_medium" :link="release.link" @click.stop="addToQueue" />
<CoverContainer :cover="release.cover_medium" :link="release.link" is-rounded @click.stop="addToQueue" />
<p class="primary-text">{{ release.title }}</p>
<p class="secondary-text">{{ `${$t('globals.by', { artist: release.artist.name })}` }}</p>
</router-link>
@ -85,15 +85,15 @@
<div v-if="artists.length == 0">
<h1>{{ $t('favorites.noArtists') }}</h1>
</div>
<div class="release-grid" v-if="artists.length > 0">
<div v-if="artists.length > 0" class="release-grid">
<router-link
tag="div"
class="release clickable"
v-for="release in artists"
:key="release.id"
:to="{ name: 'Artist', params: { id: release.id } }"
class="release clickable"
tag="div"
>
<CoverContainer is-circle :cover="release.picture_medium" :link="release.link" @click.stop="addToQueue" />
<CoverContainer :cover="release.picture_medium" :link="release.link" is-circle @click.stop="addToQueue" />
<p class="primary-text">{{ release.name }}</p>
</router-link>
</div>
@ -105,17 +105,17 @@
</div>
<table v-if="tracks.length > 0" class="table">
<tr v-for="track in tracks" class="track_row">
<td class="p-3 text-center cursor-default" :class="{ first: track.position === 1 }">
<td :class="{ first: track.position === 1 }" class="p-3 text-center cursor-default">
{{ track.position }}
</td>
<td>
<span
:data-preview="track.preview"
class="relative inline-block rounded cursor-pointer"
@click="playPausePreview"
:data-preview="track.preview"
>
<PreviewControls v-if="track.preview" />
<img class="rounded coverart" :src="track.album.cover_small" />
<img :src="track.album.cover_small" class="rounded coverart" />
</span>
</td>
<td class="table__cell--large">
@ -125,16 +125,16 @@
}}
</td>
<router-link
tag="td"
class="table__cell table__cell--medium table__cell--center clickable"
:to="{ name: 'Artist', params: { id: track.artist.id } }"
class="table__cell table__cell--medium table__cell--center clickable"
tag="td"
>
{{ track.artist.name }}
</router-link>
<router-link
tag="td"
class="table__cell--medium table__cell--center clickable"
:to="{ name: 'Album', params: { id: track.album.id } }"
class="table__cell--medium table__cell--center clickable"
tag="td"
>
{{ track.album.title }}
</router-link>
@ -142,16 +142,16 @@
{{ convertDuration(track.duration) }}
</td>
<td
class="cursor-pointer group"
@click.stop="addToQueue"
:data-link="track.link"
role="button"
aria-label="download"
class="cursor-pointer group"
role="button"
@click.stop="addToQueue"
>
<div class="table__cell-content table__cell-content--vertical-center">
<i
class="transition-colors duration-150 ease-in-out material-icons group-hover:text-primary"
:title="$t('globals.download_hint')"
class="transition-colors duration-150 ease-in-out material-icons group-hover:text-primary"
>
get_app
</i>
@ -164,14 +164,14 @@
</template>
<script>
import { defineComponent, onMounted, reactive, toRefs, watchEffect, ref, computed, watch } from '@vue/composition-api'
import { defineComponent, reactive, toRefs, watch } from '@vue/composition-api'
import PreviewControls from '@components/globals/PreviewControls.vue'
import CoverContainer from '@components/globals/CoverContainer.vue'
import { playPausePreview } from '@components/globals/TheTrackPreview.vue'
import { BaseTabs, BaseTab } from '@components/globals/BaseTabs'
import { BaseTab, BaseTabs } from '@components/globals/BaseTabs'
import { sendAddToQueue, aggregateDownloadLinks } from '@/utils/downloads'
import { aggregateDownloadLinks, sendAddToQueue } from '@/utils/downloads'
import { convertDuration } from '@/utils/utils'
import { toast } from '@/utils/toasts'
import { useFavorites } from '@/use/favorites'
@ -197,7 +197,8 @@ export default defineComponent({
isRefreshingFavorites,
refreshFavorites
} = useFavorites()
const reloadButton = computed(() => ctx.refs.reloadButton)
refreshFavorites({ isInitial: true })
watch(isRefreshingFavorites, (newVal, oldVal) => {
// If oldVal is true and newOne is false, it means that a refreshing has just terminated

View File

@ -783,6 +783,7 @@ import { copyToClipboard } from '@/utils/utils'
import BaseAccordion from '@/components/globals/BaseAccordion.vue'
import TemplateVariablesList from '@components/settings/TemplateVariablesList.vue'
import { fetchData, sendToServer } from '@/utils/api'
export default {
components: {
@ -825,7 +826,7 @@ export default {
get() {
return this.previewVolume
},
set: debounce(function(value) {
set: debounce(function (value) {
this.setPreviewVolume(value)
}, 20)
},
@ -949,16 +950,17 @@ export default {
this.lastCredentials = JSON.parse(JSON.stringify(credentials))
this.spotifyFeatures = JSON.parse(JSON.stringify(credentials))
},
loggedInViaDeezer(arl) {
async loggedInViaDeezer(arl) {
this.dispatchARL({ arl })
socket.emit('login', arl, true, this.accountNum)
// this.login()
// const res = await fetchData('login', { arl, force: true, child: this.accountNum })
},
login() {
async login() {
let newArl = this.$refs.loginInput.value.trim()
if (newArl && newArl !== this.arl) {
socket.emit('login', newArl, true, this.accountNum)
const res = await fetchData('login', { arl: newArl, force: true, child: this.accountNum })
this.loggedInViaDeezer(res.arl)
}
},
appLogin(e) {
@ -969,9 +971,7 @@ export default {
},
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.$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)

View File

@ -1,5 +1,5 @@
<template>
<div class="relative fixed-footer bg-background-main image-header" ref="root">
<div ref="root" class="relative fixed-footer bg-background-main image-header">
<header
:style="{
'background-image':
@ -30,7 +30,7 @@
<i class="material-icons">timer</i>
</th>
<th class="table__icon table__cell--center clickable">
<input @click="toggleAll" class="selectAll" type="checkbox" />
<input class="selectAll" type="checkbox" @click="toggleAll" />
</th>
</tr>
</thead>
@ -41,15 +41,15 @@
<td class="table__cell--x-small table__cell--center">
<div class="table__cell-content table__cell-content--vertical-center">
<i
class="material-icons"
v-on="{ click: track.preview ? playPausePreview : false }"
:class="{
preview_playlist_controls: track.preview,
'cursor-pointer': track.preview,
disabled: !track.preview
}"
v-on="{ click: track.preview ? playPausePreview : false }"
:data-preview="track.preview"
:title="$t('globals.play_hint')"
class="material-icons"
>
play_arrow
</i>
@ -70,28 +70,28 @@
</div>
</td>
<router-link
tag="td"
class="table__cell--medium table__cell--center clickable"
:to="{ name: 'Artist', params: { id: track.artist.id } }"
class="table__cell--medium table__cell--center clickable"
tag="td"
>
{{ 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 } }"
class="table__cell--medium table__cell--center clickable"
tag="td"
>
{{ track.album.title }}
</router-link>
<td
class="table__cell--center"
:class="{ 'table__cell--small': type === 'album', 'table__cell--x-small': type === 'playlist' }"
class="table__cell--center"
>
{{ convertDuration(track.duration) }}
</td>
<td class="table__icon table__cell--center">
<input class="clickable" type="checkbox" v-model="track.selected" />
<input v-model="track.selected" class="clickable" type="checkbox" />
</td>
</tr>
<tr v-else-if="track.type == 'disc_separator'" class="table__row-no-highlight" style="opacity: 0.54">
@ -112,14 +112,14 @@
<td>
<i
v-if="track.preview_url"
@click="playPausePreview"
class="material-icons"
:class="{
preview_playlist_controls: track.preview_url,
'cursor-pointer': track.preview_url
}"
:data-preview="track.preview_url"
:title="$t('globals.play_hint')"
class="material-icons"
@click="playPausePreview"
>
play_arrow
</i>
@ -133,17 +133,17 @@
<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>
<td><input v-model="track.selected" class="clickable" type="checkbox" /></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 class="bg-background-main">
<button class="mr-2 btn btn-primary" @click.stop="addToQueue" :data-link="link">
<button :data-link="link" class="mr-2 btn btn-primary" @click.stop="addToQueue">
{{ `${$t('globals.download', { thing: $tc(`globals.listTabs.${type}`, 1) })}` }}
</button>
<button class="flex items-center btn btn-primary" @click.stop="addToQueue" :data-link="selectedLinks()">
<button :data-link="selectedLinks()" class="flex items-center btn btn-primary" @click.stop="addToQueue">
{{ $t('tracklist.downloadSelection') }}<i class="ml-2 material-icons">file_download</i>
</button>
</footer>
@ -156,6 +156,7 @@ import { socket } from '@/utils/socket'
import { sendAddToQueue } from '@/utils/downloads'
import Utils from '@/utils/utils'
import { playPausePreview } from '@components/globals/TheTrackPreview.vue'
import EventBus from '@/utils/EventBus'
export default {
data() {
@ -172,9 +173,9 @@ export default {
}
},
mounted() {
socket.on('show_album', this.showAlbum)
socket.on('show_playlist', this.showPlaylist)
socket.on('show_spotifyplaylist', this.showSpotifyPlaylist)
EventBus.$on('showAlbum', this.showAlbum)
EventBus.$on('showPlaylist', this.showPlaylist)
EventBus.$on('showSpotifyPlaylist', this.showSpotifyPlaylist)
},
methods: {
playPausePreview,
@ -193,17 +194,17 @@ export default {
},
toggleAll(e) {
this.body.forEach(item => {
if (item.type == 'track') {
if (item.type === 'track') {
item.selected = e.currentTarget.checked
}
})
},
selectedLinks() {
var selected = []
const selected = []
if (this.body) {
this.body.forEach(item => {
if (item.type == 'track' && item.selected)
selected.push(this.type == 'spotifyPlaylist' ? item.uri : item.link)
if (item.type === 'track' && item.selected)
selected.push(this.type === 'spotifyPlaylist' ? item.uri : item.link)
})
}
return selected.join(';')
@ -305,4 +306,3 @@ export default {
}
}
</script>

View File

@ -1,5 +1,5 @@
import { socket } from '@/utils/socket'
import { getPropertyWithFallback } from '@/utils/utils'
import { fetchData } from '@/utils/api'
export function formatArtistData(artistData) {
return {
@ -36,15 +36,8 @@ function formatArtistReleases(artistReleases) {
}
export function getArtistData(artistID) {
socket.emit('getTracklist', {
return fetchData('getTracklist', {
type: 'artist',
id: artistID
})
return new Promise((resolve, reject) => {
socket.on('show_artist', data => {
socket.off('show_artist')
resolve(data)
})
})
}

View File

@ -1,22 +1,9 @@
import { socket } from '@/utils/socket'
let chartsData = {}
let cached = false
import { fetchData } from '@/utils/api'
export function getChartsData() {
if (cached) {
return chartsData
} else {
socket.emit('get_charts_data')
return new Promise((resolve, reject) => {
socket.on('init_charts', data => {
chartsData = data
cached = true
socket.off('init_charts')
resolve(data)
})
})
}
return fetchData('getCharts')
}
export function getChartTracks(chartId) {
return fetchData('getChartTracks', { id: chartId })
}

View File

@ -1,22 +1,17 @@
import { socket } from '@/utils/socket'
import { fetchData } from '@/utils/api'
let homeData = {}
let cached = false
export function getHomeData() {
export async function getHomeData() {
if (cached) {
return homeData
} else {
socket.emit('get_home_data')
const data = await fetchData('getHome')
return new Promise((resolve, reject) => {
socket.on('init_home', data => {
homeData = data
cached = true
homeData = data
cached = true
socket.off('init_home')
resolve(data)
})
})
return data
}
}

View File

@ -7,8 +7,5 @@
"@components/*": ["./components/*"]
}
},
"typeAcquisition": {
"include": ["socket.io-client"]
},
"exclude": ["assets/**/*", "styles/**/*"]
}

View File

@ -1,6 +1,5 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import { socket } from '@/utils/socket'
// Pages
import About from '@components/pages/About.vue'
@ -15,6 +14,8 @@ import LinkAnalyzer from '@components/pages/LinkAnalyzer.vue'
import Search from '@components/pages/Search.vue'
import Settings from '@components/pages/Settings.vue'
import Tracklist from '@components/pages/Tracklist.vue'
import { fetchData } from '@/utils/api'
import EventBus from '@/utils/EventBus'
Vue.use(VueRouter)
@ -125,44 +126,51 @@ const router = new VueRouter({
})
router.beforeEach((to, from, next) => {
let getTracklistParams = null
switch (to.name) {
case 'Tracklist':
getTracklistParams = {
type: to.params.type,
id: to.params.id
}
case 'Tracklist': {
// const getTracklistParams = {
// type: to.params.type,
// id: to.params.id
// }
console.warn('This should never happen.')
break
case 'Album':
getTracklistParams = {
}
case 'Album': {
const getTracklistParams = {
type: 'album',
id: to.params.id
}
fetchData('getTracklist', getTracklistParams).then(albumData => {
EventBus.$emit('showAlbum', albumData)
})
break
case 'Playlist':
getTracklistParams = {
}
case 'Playlist': {
const getTracklistParams = {
type: 'playlist',
id: to.params.id
}
fetchData('getTracklist', getTracklistParams).then(playlistData => {
EventBus.$emit('showPlaylist', playlistData)
})
break
case 'Spotify Playlist':
getTracklistParams = {
}
case 'Spotify Playlist': {
const getTracklistParams = {
type: 'spotifyplaylist',
id: to.params.id
}
fetchData('getTracklist', getTracklistParams).then(spotifyPlaylistData => {
EventBus.$emit('showSpotifyPlaylist', spotifyPlaylistData)
})
break
}
default:
break
}
if (getTracklistParams) {
socket.emit('getTracklist', getTracklistParams)
}
next()
})
export default router

View File

@ -1,7 +1,7 @@
import { ref } from '@vue/composition-api'
import store from '@/store'
import { socket } from '@/utils/socket'
import { fetchData } from '@/utils/api'
const favoriteArtists = ref([])
const favoriteAlbums = ref([])
@ -11,22 +11,35 @@ const favoriteTracks = ref([])
const isRefreshingFavorites = ref(false)
if (store.getters.isLoggedIn) {
refreshFavorites({ isInitial: true })
}
function refreshFavorites({ isInitial = false }) {
if (!isInitial) {
isRefreshingFavorites.value = true
}
socket.emit('get_favorites_data')
fetchData('getUserFavorites').then(setAllFavorites).catch(console.error)
if (store.getters.isLoggedWithSpotify) {
socket.emit('update_userSpotifyPlaylists', store.getters.getSpotifyUser.id)
fetchData('getUserSpotifyPlaylists', {
spotifyUser: store.getters.getSpotifyUser.id
})
.then(({ data: spotifyPlaylists }) => {
favoriteSpotifyPlaylists.value = spotifyPlaylists
})
.catch(console.error)
}
}
function setAllFavorites(data) {
const { tracks, albums, artists, playlists } = data
isRefreshingFavorites.value = false
favoriteArtists.value = artists
favoriteAlbums.value = albums
favoritePlaylists.value = playlists
favoriteTracks.value = tracks
}
export function useFavorites() {
return {
favoriteArtists,
@ -38,42 +51,3 @@ export function useFavorites() {
refreshFavorites
}
}
function setAllFavorites(data) {
const { tracks, albums, artists, playlists } = data
favoriteArtists.value = artists
favoriteAlbums.value = albums
favoritePlaylists.value = playlists
favoriteTracks.value = tracks
}
socket.on('updated_userFavorites', data => {
setAllFavorites(data)
// Commented out because the corresponding emit function is never called at the moment
// therefore isRefreshingFavorites is never set to true
// isRefreshingFavorites.value = false
})
socket.on('init_favorites', data => {
setAllFavorites(data)
isRefreshingFavorites.value = false
})
socket.on('updated_userSpotifyPlaylists', data => {
favoriteSpotifyPlaylists.value = data
})
socket.on('updated_userSpotifyPlaylists', data => {
favoriteSpotifyPlaylists.value = data
})
socket.on('updated_userPlaylists', data => {
favoritePlaylists.value = data
})
socket.on('updated_userAlbums', data => {
favoriteAlbums.value = data
})
socket.on('updated_userArtist', data => {
favoriteArtists.value = data
})
socket.on('updated_userTracks', data => {
favoriteTracks.value = data
})

View File

@ -1,15 +1,11 @@
import { ref } from '@vue/composition-api'
import { socket } from '@/utils/socket'
import { fetchData } from '@/utils/api'
const searchResult = ref({})
function performMainSearch(searchTerm) {
socket.emit('mainSearch', { term: searchTerm })
socket.on('mainSearch', data => {
fetchData('mainSearch', { term: searchTerm }).then(data => {
searchResult.value = data
socket.off('mainSearch')
})
}

View File

@ -1,20 +1,16 @@
import { ref } from '@vue/composition-api'
import { socket } from '@/utils/socket'
import { fetchData } from '@/utils/api'
const result = ref({})
function performSearch({ term, type, start = 0, nb = 30 }) {
socket.emit('search', {
fetchData('search', {
term,
type,
start,
nb
})
socket.on('search', data => {
}).then(data => {
result.value = data
socket.off('search')
})
}

21
src/utils/api.js Normal file
View File

@ -0,0 +1,21 @@
export function fetchData(key, data = {}) {
const url = new URL(`${window.location.origin}/api/${key}`)
Object.keys(data).forEach(key => {
url.searchParams.append(key, data[key])
})
return fetch(url.href)
.then(response => response.json())
.catch(() => {})
}
export function sendToServer(key, data) {
const url = new URL(`${window.location.origin}/api/${key}`)
Object.keys(data).forEach(key => {
url.searchParams.append(key, data[key])
})
fetch(url.href).catch(console.error)
}

View File

@ -1,13 +1,13 @@
import { socket } from '@/utils/socket'
import { sendToServer } from '@/utils/api'
/**
* @param {string} url
* @param {number} bitrate
* @param {number|null} bitrate
*/
export function sendAddToQueue(url, bitrate = null) {
if (!url) throw new Error('No URL given to sendAddToQueue function!')
socket.emit('addToQueue', { url, bitrate }, () => {})
sendToServer('addToQueue', { url, bitrate })
}
export function aggregateDownloadLinks(releases) {

View File

@ -1,8 +1,35 @@
import store from '@/store'
import io from 'socket.io-client'
class CustomSocket extends WebSocket {
constructor(args) {
super(args)
this.listeners = {}
}
emit(key, data) {
if (this.readyState !== WebSocket.OPEN) return false
export const socket = io.connect('/')
this.send(JSON.stringify({ key, data }))
}
on(key, cb) {
if (Object.keys(this.listeners).indexOf(key) == -1){
console.log('on:', key)
this.listeners[key] = cb
socket.on('init_update', data => {
store.dispatch('setAppInfo', data)
})
this.addEventListener('message', event => {
const messageData = JSON.parse(event.data)
if (messageData.key === key) {
cb(messageData.data)
}
})
}
}
off(key) {
if (Object.keys(this.listeners).indexOf(key) != -1){
console.log('off:', key)
this.removeEventListener('message', this.listeners[key])
delete this.listeners[key]
}
}
}
export const socket = new CustomSocket('ws://' + location.host + '/')