Merge branch 'feature/store-persistence' into 'develop'
Feature/store persistence See merge request funkwhale/funkwhale!35
This commit is contained in:
commit
f5a4faa568
|
@ -3,3 +3,4 @@ DJANGO_SETTINGS_MODULE=config.settings.test
|
|||
|
||||
# -- recommended but optional:
|
||||
python_files = tests.py test_*.py *_tests.py
|
||||
testpatsh = tests
|
||||
|
|
|
@ -23,7 +23,8 @@
|
|||
"vue-resource": "^1.3.4",
|
||||
"vue-router": "^2.3.1",
|
||||
"vuedraggable": "^2.14.1",
|
||||
"vuex": "^3.0.1"
|
||||
"vuex": "^3.0.1",
|
||||
"vuex-persistedstate": "^2.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^6.7.2",
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
import logger from '@/logging'
|
||||
export default {
|
||||
get (key, d) {
|
||||
let v = localStorage.getItem(key)
|
||||
if (v === null) {
|
||||
return d
|
||||
} else {
|
||||
try {
|
||||
return JSON.parse(v).value
|
||||
} catch (e) {
|
||||
logger.default.error('Removing unparsable cached value for key ' + key)
|
||||
this.remove(key)
|
||||
return d
|
||||
}
|
||||
}
|
||||
},
|
||||
set (key, value) {
|
||||
return localStorage.setItem(key, JSON.stringify({value: value}))
|
||||
},
|
||||
|
||||
remove (key) {
|
||||
return localStorage.removeItem(key)
|
||||
},
|
||||
|
||||
clear () {
|
||||
localStorage.clear()
|
||||
}
|
||||
|
||||
}
|
|
@ -62,7 +62,7 @@
|
|||
{{ track.artist.name }}
|
||||
</td>
|
||||
<td>
|
||||
<template v-if="favoriteTracks.objects[track.id]">
|
||||
<template v-if="$store.getters['favorites/isFavorite'](track.id)">
|
||||
<i class="pink heart icon"></i>
|
||||
</template
|
||||
</td>
|
||||
|
@ -94,7 +94,6 @@
|
|||
import {mapState, mapActions} from 'vuex'
|
||||
|
||||
import Player from '@/components/audio/Player'
|
||||
import favoriteTracks from '@/favorites/tracks'
|
||||
import Logo from '@/components/Logo'
|
||||
import SearchBar from '@/components/audio/SearchBar'
|
||||
import backend from '@/audio/backend'
|
||||
|
@ -112,8 +111,7 @@ export default {
|
|||
},
|
||||
data () {
|
||||
return {
|
||||
backend: backend,
|
||||
favoriteTracks
|
||||
backend: backend
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
v-if="currentTrack"
|
||||
:key="(currentIndex, currentTrack.id)"
|
||||
:is-current="true"
|
||||
:start-time="$store.state.player.currentTime"
|
||||
:autoplay="$store.state.player.playing"
|
||||
:track="currentTrack">
|
||||
</audio-track>
|
||||
|
||||
|
@ -127,7 +129,7 @@
|
|||
@keydown.ctrl.right.prevent.exact="next"
|
||||
@keydown.ctrl.down.prevent.exact="$store.commit('player/incrementVolume', -0.1)"
|
||||
@keydown.ctrl.up.prevent.exact="$store.commit('player/incrementVolume', 0.1)"
|
||||
@keydown.f.prevent.exact="favoriteTracks.toggle(currentTrack.id)"
|
||||
@keydown.f.prevent.exact="$store.dispatch('favorites/toggle', currentTrack.id)"
|
||||
@keydown.l.prevent.exact="$store.commit('player/toggleLooping')"
|
||||
@keydown.s.prevent.exact="shuffle"
|
||||
/>
|
||||
|
@ -139,7 +141,6 @@
|
|||
import {mapState, mapGetters, mapActions} from 'vuex'
|
||||
import GlobalEvents from '@/components/utils/global-events'
|
||||
|
||||
import favoriteTracks from '@/favorites/tracks'
|
||||
import Track from '@/audio/track'
|
||||
import AudioTrack from '@/components/audio/Track'
|
||||
import TrackFavoriteIcon from '@/components/favorites/TrackFavoriteIcon'
|
||||
|
@ -154,8 +155,7 @@ export default {
|
|||
data () {
|
||||
return {
|
||||
sliderVolume: this.volume,
|
||||
Track: Track,
|
||||
favoriteTracks
|
||||
Track: Track
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
|
|
|
@ -18,6 +18,7 @@ const SEARCH_URL = config.API_URL + 'search?query={query}'
|
|||
|
||||
export default {
|
||||
mounted () {
|
||||
let self = this
|
||||
jQuery(this.$el).search({
|
||||
type: 'category',
|
||||
minCharacters: 3,
|
||||
|
@ -26,7 +27,7 @@ export default {
|
|||
},
|
||||
apiSettings: {
|
||||
beforeXHR: function (xhrObject) {
|
||||
xhrObject.setRequestHeader('Authorization', this.$store.getters['auth/header'])
|
||||
xhrObject.setRequestHeader('Authorization', self.$store.getters['auth/header'])
|
||||
return xhrObject
|
||||
},
|
||||
onResponse: function (initialResponse) {
|
||||
|
|
|
@ -22,7 +22,9 @@ import url from '@/utils/url'
|
|||
export default {
|
||||
props: {
|
||||
track: {type: Object},
|
||||
isCurrent: {type: Boolean, default: false}
|
||||
isCurrent: {type: Boolean, default: false},
|
||||
startTime: {type: Number, default: 0},
|
||||
autoplay: {type: Boolean, default: false}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
|
@ -57,8 +59,11 @@ export default {
|
|||
|
||||
},
|
||||
loaded: function () {
|
||||
if (this.isCurrent && this.autoplay) {
|
||||
this.$store.commit('player/duration', this.$refs.audio.duration)
|
||||
if (this.isCurrent) {
|
||||
if (this.startTime) {
|
||||
this.setCurrentTime(this.startTime)
|
||||
}
|
||||
this.$store.commit('player/playing', true)
|
||||
this.$refs.audio.play()
|
||||
}
|
||||
|
@ -72,8 +77,9 @@ export default {
|
|||
if (this.looping === 1) {
|
||||
this.setCurrentTime(0)
|
||||
this.$refs.audio.play()
|
||||
}
|
||||
} else {
|
||||
this.$store.dispatch('player/trackEnded', this.track)
|
||||
}
|
||||
},
|
||||
setCurrentTime (t) {
|
||||
if (t < 0 | t > this.duration) {
|
||||
|
|
|
@ -119,7 +119,6 @@ export default {
|
|||
self.results = response.data
|
||||
self.nextLink = response.data.next
|
||||
self.previousLink = response.data.previous
|
||||
self.$store.commit('favorites/count', response.data.count)
|
||||
self.results.results.forEach((track) => {
|
||||
self.$store.commit('favorites/track', {id: track.id, value: true})
|
||||
})
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<button @click="$store.dispatch('favorites/set', {id: track.id, value: !isFavorite})" v-if="button" :class="['ui', 'pink', {'inverted': isFavorite}, {'favorited': isFavorite}, 'button']">
|
||||
<button @click="$store.dispatch('favorites/toggle', track.id)" v-if="button" :class="['ui', 'pink', {'inverted': isFavorite}, {'favorited': isFavorite}, 'button']">
|
||||
<i class="heart icon"></i>
|
||||
<template v-if="isFavorite">
|
||||
In favorites
|
||||
|
@ -8,23 +8,16 @@
|
|||
Add to favorites
|
||||
</template>
|
||||
</button>
|
||||
<i v-else @click="$store.dispatch('favorites/set', {id: track.id, value: !isFavorite})" :class="['favorite-icon', 'heart', {'pink': isFavorite}, {'favorited': isFavorite}, 'link', 'icon']" :title="title"></i>
|
||||
<i v-else @click="$store.dispatch('favorites/toggle', track.id)" :class="['favorite-icon', 'heart', {'pink': isFavorite}, {'favorited': isFavorite}, 'link', 'icon']" :title="title"></i>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapState} from 'vuex'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
track: {type: Object},
|
||||
button: {type: Boolean, default: false}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
favorites: state => {
|
||||
return state.favorites.tracks
|
||||
}
|
||||
}),
|
||||
title () {
|
||||
if (this.isFavorite) {
|
||||
return 'Remove from favorites'
|
||||
|
|
|
@ -65,7 +65,7 @@ export default {
|
|||
},
|
||||
apiSettings: {
|
||||
beforeXHR: function (xhrObject, s) {
|
||||
xhrObject.setRequestHeader('Authorization', this.$store.getters['auth/header'])
|
||||
xhrObject.setRequestHeader('Authorization', self.$store.getters['auth/header'])
|
||||
return xhrObject
|
||||
},
|
||||
onResponse: function (initialResponse) {
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import Vue from 'vue'
|
||||
import config from '@/config'
|
||||
import logger from '@/logging'
|
||||
import cache from '@/cache'
|
||||
import router from '@/router'
|
||||
// import favoriteTracks from '@/favorites/tracks'
|
||||
|
||||
const LOGIN_URL = config.API_URL + 'token/'
|
||||
const USER_PROFILE_URL = config.API_URL + 'users/users/me/'
|
||||
|
@ -46,9 +44,7 @@ export default {
|
|||
return resource.save({}, credentials).then(response => {
|
||||
logger.default.info('Successfully logged in as', credentials.username)
|
||||
commit('token', response.data.token)
|
||||
cache.set('token', response.data.token)
|
||||
commit('username', credentials.username)
|
||||
cache.set('username', credentials.username)
|
||||
commit('authenticated', true)
|
||||
dispatch('fetchProfile')
|
||||
// Redirect to a specified route
|
||||
|
@ -59,7 +55,6 @@ export default {
|
|||
})
|
||||
},
|
||||
logout ({commit}) {
|
||||
cache.clear()
|
||||
commit('authenticated', false)
|
||||
commit('profile', null)
|
||||
logger.default.info('Log out, goodbye!')
|
||||
|
@ -67,8 +62,8 @@ export default {
|
|||
},
|
||||
check ({commit, dispatch, state}) {
|
||||
logger.default.info('Checking authentication...')
|
||||
var jwt = cache.get('token')
|
||||
var username = cache.get('username')
|
||||
var jwt = state.token
|
||||
var username = state.username
|
||||
if (jwt) {
|
||||
commit('authenticated', true)
|
||||
commit('username', username)
|
||||
|
|
|
@ -14,16 +14,16 @@ export default {
|
|||
mutations: {
|
||||
track: (state, {id, value}) => {
|
||||
if (value) {
|
||||
if (state.tracks.indexOf(id) === -1) {
|
||||
state.tracks.push(id)
|
||||
}
|
||||
} else {
|
||||
let i = state.tracks.indexOf(id)
|
||||
if (i > -1) {
|
||||
state.tracks.splice(i, 1)
|
||||
}
|
||||
}
|
||||
},
|
||||
count: (state, value) => {
|
||||
state.count = value
|
||||
state.count = state.tracks.length
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
|
@ -35,29 +35,25 @@ export default {
|
|||
set ({commit, state}, {id, value}) {
|
||||
commit('track', {id, value})
|
||||
if (value) {
|
||||
commit('count', state.count + 1)
|
||||
let resource = Vue.resource(FAVORITES_URL)
|
||||
resource.save({}, {'track': id}).then((response) => {
|
||||
logger.default.info('Successfully added track to favorites')
|
||||
}, (response) => {
|
||||
logger.default.info('Error while adding track to favorites')
|
||||
commit('track', {id, value: !value})
|
||||
commit('count', state.count - 1)
|
||||
})
|
||||
} else {
|
||||
commit('count', state.count - 1)
|
||||
let resource = Vue.resource(REMOVE_URL)
|
||||
resource.delete({}, {'track': id}).then((response) => {
|
||||
logger.default.info('Successfully removed track from favorites')
|
||||
}, (response) => {
|
||||
logger.default.info('Error while removing track from favorites')
|
||||
commit('track', {id, value: !value})
|
||||
commit('count', state.count + 1)
|
||||
})
|
||||
}
|
||||
},
|
||||
toggle ({getters, dispatch}, id) {
|
||||
dispatch('set', {id, value: getters['isFavorite'](id)})
|
||||
dispatch('set', {id, value: !getters['isFavorite'](id)})
|
||||
},
|
||||
fetch ({dispatch, state, commit}, url) {
|
||||
// will fetch favorites by batches from API to have them locally
|
||||
|
@ -68,7 +64,6 @@ export default {
|
|||
response.data.results.forEach(result => {
|
||||
commit('track', {id: result.track, value: true})
|
||||
})
|
||||
commit('count', state.tracks.length)
|
||||
if (response.data.next) {
|
||||
dispatch('fetch', response.data.next)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import createPersistedState from 'vuex-persistedstate'
|
||||
|
||||
import favorites from './favorites'
|
||||
import auth from './auth'
|
||||
|
@ -16,5 +17,84 @@ export default new Vuex.Store({
|
|||
queue,
|
||||
radios,
|
||||
player
|
||||
},
|
||||
plugins: [
|
||||
createPersistedState({
|
||||
key: 'auth',
|
||||
paths: ['auth'],
|
||||
filter: (mutation) => {
|
||||
return mutation.type.startsWith('auth/')
|
||||
}
|
||||
}),
|
||||
createPersistedState({
|
||||
key: 'radios',
|
||||
paths: ['radios'],
|
||||
filter: (mutation) => {
|
||||
return mutation.type.startsWith('radios/')
|
||||
}
|
||||
}),
|
||||
createPersistedState({
|
||||
key: 'player',
|
||||
paths: [
|
||||
'player.looping',
|
||||
'player.playing',
|
||||
'player.volume',
|
||||
'player.duration',
|
||||
'player.errored'],
|
||||
filter: (mutation) => {
|
||||
return mutation.type.startsWith('player/') && mutation.type !== 'player/currentTime'
|
||||
}
|
||||
}),
|
||||
createPersistedState({
|
||||
key: 'progress',
|
||||
paths: ['player.currentTime'],
|
||||
filter: (mutation) => {
|
||||
let delay = 10
|
||||
return mutation.type === 'player/currentTime' && parseInt(mutation.payload) % delay === 0
|
||||
},
|
||||
reducer: (state) => {
|
||||
return {
|
||||
player: {
|
||||
currentTime: state.player.currentTime
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
createPersistedState({
|
||||
key: 'queue',
|
||||
filter: (mutation) => {
|
||||
return mutation.type.startsWith('queue/')
|
||||
},
|
||||
reducer: (state) => {
|
||||
return {
|
||||
queue: {
|
||||
currentIndex: state.queue.currentIndex,
|
||||
tracks: state.queue.tracks.map(track => {
|
||||
// we keep only valuable fields to make the cache lighter and avoid
|
||||
// cyclic value serialization errors
|
||||
let artist = {
|
||||
id: track.artist.id,
|
||||
mbid: track.artist.mbid,
|
||||
name: track.artist.name
|
||||
}
|
||||
return {
|
||||
id: track.id,
|
||||
title: track.title,
|
||||
mbid: track.mbid,
|
||||
album: {
|
||||
id: track.album.id,
|
||||
title: track.album.title,
|
||||
mbid: track.album.mbid,
|
||||
cover: track.album.cover,
|
||||
artist: artist
|
||||
},
|
||||
artist: artist,
|
||||
files: track.files
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
]
|
||||
})
|
||||
|
|
|
@ -111,11 +111,6 @@ export default {
|
|||
}
|
||||
},
|
||||
next ({state, dispatch, commit, rootState}) {
|
||||
if (rootState.player.looping === 1) {
|
||||
// we loop on the same track, this is handled directly on the track
|
||||
// component, so we do nothing.
|
||||
return logger.default.info('Looping on the same track')
|
||||
}
|
||||
if (rootState.player.looping === 2 && state.currentIndex >= state.tracks.length - 1) {
|
||||
logger.default.info('Going back to the beginning of the queue')
|
||||
return dispatch('currentIndex', 0)
|
||||
|
@ -130,6 +125,8 @@ export default {
|
|||
},
|
||||
currentIndex ({commit, state, rootState, dispatch}, index) {
|
||||
commit('ended', false)
|
||||
commit('player/currentTime', 0, {root: true})
|
||||
commit('player/playing', true, {root: true})
|
||||
commit('player/errored', false, {root: true})
|
||||
commit('currentIndex', index)
|
||||
if (state.tracks.length - index <= 2 && rootState.radios.running) {
|
||||
|
|
Loading…
Reference in New Issue