build: v1.5.2; fix: spotify favorites playlist not showing in Favorites page until the refresh button is clicked (closes #21); refactor: favorites logic

This commit is contained in:
Roberto Tonino 2020-11-28 22:11:11 +01:00
parent 835b0e9f8d
commit d752efa30f
7 changed files with 172 additions and 169 deletions

File diff suppressed because one or more lines are too long

View File

@ -3,13 +3,13 @@
<h1 class="mb-8 text-5xl"> <h1 class="mb-8 text-5xl">
{{ $t('favorites.title') }} {{ $t('favorites.title') }}
<div <div
@click="reloadTabs" @click="refreshFavorites"
class="inline-block clickable reload-button" class="inline-block clickable"
ref="reloadButton" ref="reloadButton"
role="button" role="button"
aria-label="reload" aria-label="reload"
> >
<i class="material-icons">sync</i> <i class="material-icons" :class="{ spin: isRefreshingFavorites }">sync</i>
</div> </div>
</h1> </h1>
@ -20,10 +20,10 @@
</BaseTabs> </BaseTabs>
<button class="btn btn-primary" v-if="!activeTabEmpty" style="margin-bottom: 2rem" @click="downloadAllOfType"> <button class="btn btn-primary" v-if="!activeTabEmpty" style="margin-bottom: 2rem" @click="downloadAllOfType">
{{ $t('globals.download', { thing: $tc(`globals.listTabs.${activeTab}N`, getTabLenght()) }) }} {{ $t('globals.download', { thing: $tc(`globals.listTabs.${activeTab}N`, getTabLength()) }) }}
</button> </button>
<div class="favorites_tabcontent" :class="{ 'favorites_tabcontent--active': activeTab === 'playlist' }"> <div v-show="activeTab === 'playlist'">
<div v-if="playlists.length == 0"> <div v-if="playlists.length == 0">
<h1>{{ $t('favorites.noPlaylists') }}</h1> <h1>{{ $t('favorites.noPlaylists') }}</h1>
</div> </div>
@ -62,7 +62,7 @@
</div> </div>
</div> </div>
<div class="favorites_tabcontent" :class="{ 'favorites_tabcontent--active': activeTab === 'album' }"> <div v-show="activeTab === 'album'">
<div v-if="albums.length == 0"> <div v-if="albums.length == 0">
<h1>{{ $t('favorites.noAlbums') }}</h1> <h1>{{ $t('favorites.noAlbums') }}</h1>
</div> </div>
@ -81,7 +81,7 @@
</div> </div>
</div> </div>
<div class="favorites_tabcontent" :class="{ 'favorites_tabcontent--active': activeTab === 'artist' }"> <div v-show="activeTab === 'artist'">
<div v-if="artists.length == 0"> <div v-if="artists.length == 0">
<h1>{{ $t('favorites.noArtists') }}</h1> <h1>{{ $t('favorites.noArtists') }}</h1>
</div> </div>
@ -99,7 +99,7 @@
</div> </div>
</div> </div>
<div class="favorites_tabcontent" :class="{ 'favorites_tabcontent--active': activeTab === 'track' }"> <div v-show="activeTab === 'track'">
<div v-if="tracks.length == 0"> <div v-if="tracks.length == 0">
<h1>{{ $t('favorites.noTracks') }}</h1> <h1>{{ $t('favorites.noTracks') }}</h1>
</div> </div>
@ -163,86 +163,70 @@
</div> </div>
</template> </template>
<style lang="scss" scoped>
.favorites_tabcontent {
display: none;
&--active {
display: block;
}
}
.reload-button {
&.spin {
i {
animation: spin 500ms infinite ease-out reverse;
}
}
}
</style>
<script> <script>
import { socket } from '@/utils/socket' import { defineComponent, onMounted, reactive, toRefs, watchEffect, ref, computed, watch } from '@vue/composition-api'
import { sendAddToQueue, aggregateDownloadLinks } from '@/utils/downloads'
import { convertDuration } from '@/utils/utils'
import { toast } from '@/utils/toasts'
import { getFavoritesData } from '@/data/favorites'
import PreviewControls from '@components/globals/PreviewControls.vue' import PreviewControls from '@components/globals/PreviewControls.vue'
import CoverContainer from '@components/globals/CoverContainer.vue' import CoverContainer from '@components/globals/CoverContainer.vue'
import { playPausePreview } from '@components/globals/TheTrackPreview.vue' import { playPausePreview } from '@components/globals/TheTrackPreview.vue'
import { BaseTabs, BaseTab } from '@components/globals/BaseTabs' import { BaseTabs, BaseTab } from '@components/globals/BaseTabs'
export default { import { sendAddToQueue, aggregateDownloadLinks } from '@/utils/downloads'
import { convertDuration } from '@/utils/utils'
import { toast } from '@/utils/toasts'
import { useFavorites } from '@/use/favorites'
export default defineComponent({
components: { components: {
PreviewControls, PreviewControls,
CoverContainer, CoverContainer,
BaseTabs, BaseTabs,
BaseTab BaseTab
}, },
data() { setup(props, ctx) {
return { const state = reactive({
tracks: [],
albums: [],
artists: [],
playlists: [],
spotifyPlaylists: [],
activeTab: 'playlist', activeTab: 'playlist',
tabs: ['playlist', 'album', 'artist', 'track'] tabs: ['playlist', 'album', 'artist', 'track']
})
const {
favoriteArtists,
favoriteAlbums,
favoriteSpotifyPlaylists,
favoritePlaylists,
favoriteTracks,
isRefreshingFavorites,
refreshFavorites
} = useFavorites()
const reloadButton = computed(() => ctx.refs.reloadButton)
watch(isRefreshingFavorites, (newVal, oldVal) => {
// If oldVal is true and newOne is false, it means that a refreshing has just terminated
// because isRefreshingFavorites represents the status of the refresh functionality
const isRefreshingTerminated = oldVal && !newVal
if (!isRefreshingTerminated) return
toast(ctx.root.$t('toasts.refreshFavs'), 'done', true)
})
return {
...toRefs(state),
tracks: favoriteTracks,
albums: favoriteAlbums,
artists: favoriteArtists,
playlists: favoritePlaylists,
spotifyPlaylists: favoriteSpotifyPlaylists,
refreshFavorites,
isRefreshingFavorites
} }
}, },
computed: { computed: {
activeTabEmpty() { activeTabEmpty() {
let toCheck = this.getActiveRelease() let toCheck = this.getActiveRelease()
return toCheck.length === 0 return toCheck?.length === 0
} }
}, },
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: { methods: {
playPausePreview, playPausePreview,
convertDuration, convertDuration,
@ -264,54 +248,9 @@ export default {
addToQueue(e) { addToQueue(e) {
sendAddToQueue(e.currentTarget.dataset.link) 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
},
getActiveRelease(tab = this.activeTab) { getActiveRelease(tab = this.activeTab) {
let toDownload let toDownload
// console.log({ tab, play: this.playlists })
switch (tab) { switch (tab) {
case 'playlist': case 'playlist':
@ -333,11 +272,11 @@ export default {
return toDownload return toDownload
}, },
getTabLenght(tab = this.activeTab) { getTabLength(tab = this.activeTab) {
let total = this[`${tab}s`].length let total = this[`${tab}s`]?.length
// TODO: Add Spotify playlists to downlaod queue as well // TODO: Add Spotify playlists to downlaod queue as well
//if (tab === "playlist") total += this.spotifyPlaylists.length //if (tab === "playlist") total += this.spotifyPlaylists.length
return total return total || 0
}, },
getLovedTracksPlaylist() { getLovedTracksPlaylist() {
let lovedTracks = this.playlists.filter(playlist => { let lovedTracks = this.playlists.filter(playlist => {
@ -351,5 +290,5 @@ export default {
} }
} }
} }
} })
</script> </script>

View File

@ -1,22 +0,0 @@
import { socket } from '@/utils/socket'
let favoritesData = {}
let cached = false
export function getFavoritesData() {
if (cached) {
return favoritesData
} else {
socket.emit('get_favorites_data')
return new Promise((resolve, reject) => {
socket.on('init_favorites', data => {
favoritesData = data
cached = true
socket.off('init_favorites')
resolve(data)
})
})
}
}

View File

@ -6,10 +6,17 @@ const getDefaultState = () => ({
name: '', name: '',
picture: '' picture: ''
}, },
spotifyUser: {
id: localStorage.getItem('spotifyUser'),
name: null,
picture: null
},
clientMode: false clientMode: false
}) })
const state = getDefaultState() const state = () => {
return getDefaultState()
}
const actions = { const actions = {
login({ commit, dispatch }, payload) { login({ commit, dispatch }, payload) {
@ -51,8 +58,11 @@ const actions = {
const getters = { const getters = {
getARL: state => state.arl, getARL: state => state.arl,
getUser: state => state.user, getUser: state => state.user,
getSpotifyUser: state => state.spotifyUser,
getClientMode: state => state.clientMode, getClientMode: state => state.clientMode,
isLoggedIn: state => !!state.arl
isLoggedIn: state => !!state.arl,
isLoggedWithSpotify: state => !!state.spotifyUser.id
} }
const mutations = { const mutations = {

View File

@ -22,6 +22,10 @@
width: 156px; width: 156px;
} }
.spin {
animation: spin 500ms infinite ease-out reverse;
}
@keyframes spin { @keyframes spin {
0% { 0% {
transform: rotate(0deg); transform: rotate(0deg);

79
src/use/favorites.js Normal file
View File

@ -0,0 +1,79 @@
import { ref } from '@vue/composition-api'
import store from '@/store'
import { socket } from '@/utils/socket'
const favoriteArtists = ref([])
const favoriteAlbums = ref([])
const favoriteSpotifyPlaylists = ref([])
const favoritePlaylists = ref([])
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')
if (store.getters.isLoggedWithSpotify) {
socket.emit('update_userSpotifyPlaylists', store.getters.getSpotifyUser.id)
}
}
export function useFavorites() {
return {
favoriteArtists,
favoriteAlbums,
favoriteSpotifyPlaylists,
favoritePlaylists,
favoriteTracks,
isRefreshingFavorites,
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

@ -19,7 +19,3 @@ export function aggregateDownloadLinks(releases) {
return links.join(';') return links.join(';')
} }
// export default {
// sendAddToQueue
// }