Skip to content
Snippets Groups Projects
Commit 1823554b authored by Eliot Berriot's avatar Eliot Berriot
Browse files

Merge branch '262-shuffle-feedback' into 'develop'

Resolve "No feedback on the shuffle button"

Closes #262

See merge request funkwhale/funkwhale!238
parents 2fb8f033 5009a5d0
No related branches found
No related tags found
No related merge requests found
Added feedback on shuffle button (#262)
<template> <template>
<div id="app"> <div id="app">
<sidebar></sidebar> <sidebar></sidebar>
<service-messages v-if="messages.length > 0" />
<router-view :key="$route.fullPath"></router-view> <router-view :key="$route.fullPath"></router-view>
<div class="ui fitted divider"></div> <div class="ui fitted divider"></div>
<div id="footer" class="ui vertical footer segment"> <div id="footer" class="ui vertical footer segment">
...@@ -44,9 +45,11 @@ ...@@ -44,9 +45,11 @@
<script> <script>
import axios from 'axios' import axios from 'axios'
import _ from 'lodash' import _ from 'lodash'
import {mapState} from 'vuex'
import Sidebar from '@/components/Sidebar' import Sidebar from '@/components/Sidebar'
import Raven from '@/components/Raven' import Raven from '@/components/Raven'
import ServiceMessages from '@/components/ServiceMessages'
import PlaylistModal from '@/components/playlists/PlaylistModal' import PlaylistModal from '@/components/playlists/PlaylistModal'
...@@ -55,7 +58,8 @@ export default { ...@@ -55,7 +58,8 @@ export default {
components: { components: {
Sidebar, Sidebar,
Raven, Raven,
PlaylistModal PlaylistModal,
ServiceMessages
}, },
data () { data () {
return { return {
...@@ -80,6 +84,9 @@ export default { ...@@ -80,6 +84,9 @@ export default {
} }
}, },
computed: { computed: {
...mapState({
messages: state => state.ui.messages
}),
version () { version () {
if (!this.nodeinfo) { if (!this.nodeinfo) {
return null return null
...@@ -115,6 +122,14 @@ html, body { ...@@ -115,6 +122,14 @@ html, body {
} }
transform: none !important; transform: none !important;
} }
.service-messages {
position: fixed;
bottom: 1em;
left: 1em;
@include media(">desktop") {
left: 350px;
}
}
.main-pusher { .main-pusher {
padding: 1.5rem 0; padding: 1.5rem 0;
} }
......
<template>
<div class="service-messages">
<message v-for="message in displayedMessages" :key="String(message.date)" :class="['large', getLevel(message)]">
<p>{{ message.content }}</p>
</message>
</div>
</template>
<script>
import {mapState} from 'vuex'
export default {
data () {
return {
date: new Date(),
interval: null
}
},
created () {
this.setupInterval()
},
destroyed () {
if (this.interval) {
clearInterval(this.interval)
}
},
computed: {
...mapState({
messages: state => state.ui.messages,
displayDuration: state => state.ui.messageDisplayDuration
}),
displayedMessages () {
let now = this.date
let interval = this.displayDuration
let toDisplay = this.messages.filter(m => {
return now - m.date <= interval
})
return toDisplay.slice(0, 3)
}
},
methods: {
setupInterval () {
if (this.interval) {
return
}
let self = this
this.interval = setInterval(() => {
if (self.displayedMessages.length === 0) {
clearInterval(self.interval)
this.interval = null
}
self.date = new Date()
}, 1000)
},
getLevel (message) {
return message.level || 'info'
}
},
watch: {
messages: {
handler (v) {
if (v.length > 0 && !this.interval) {
this.setupInterval()
}
},
deep: true
}
}
}
</script>
<style>
.service-messages {
z-index: 9999;
margin-left: 1em;
min-width: 20em;
max-width: 40em;
}
.service-messages .message:last-child {
margin-bottom: 0;
}
</style>
...@@ -124,19 +124,28 @@ export default { ...@@ -124,19 +124,28 @@ export default {
add () { add () {
let self = this let self = this
this.getPlayableTracks().then((tracks) => { this.getPlayableTracks().then((tracks) => {
self.$store.dispatch('queue/appendMany', {tracks: tracks}) self.$store.dispatch('queue/appendMany', {tracks: tracks}).then(() => self.addMessage(tracks))
}) })
}, },
addNext (next) { addNext (next) {
let self = this let self = this
let wasEmpty = this.$store.state.queue.tracks.length === 0 let wasEmpty = this.$store.state.queue.tracks.length === 0
this.getPlayableTracks().then((tracks) => { this.getPlayableTracks().then((tracks) => {
self.$store.dispatch('queue/appendMany', {tracks: tracks, index: self.$store.state.queue.currentIndex + 1}) self.$store.dispatch('queue/appendMany', {tracks: tracks, index: self.$store.state.queue.currentIndex + 1}).then(() => self.addMessage(tracks))
let goNext = next && !wasEmpty let goNext = next && !wasEmpty
if (goNext) { if (goNext) {
self.$store.dispatch('queue/next') self.$store.dispatch('queue/next')
} }
}) })
},
addMessage (tracks) {
if (tracks.length < 1) {
return
}
this.$store.commit('ui/addMessage', {
content: this.$t('{% tracks %} tracks were added to your queue.', {tracks: tracks.length}),
date: new Date()
})
} }
} }
} }
......
...@@ -113,7 +113,8 @@ ...@@ -113,7 +113,8 @@
:disabled="queue.tracks.length === 0" :disabled="queue.tracks.length === 0"
:title="$t('Shuffle your queue')" :title="$t('Shuffle your queue')"
class="two wide column control"> class="two wide column control">
<i @click="shuffle()" :class="['ui', 'random', 'secondary', {'disabled': queue.tracks.length === 0}, 'icon']" ></i> <div v-if="isShuffling" class="ui inline shuffling inverted small active loader"></div>
<i v-else @click="shuffle()" :class="['ui', 'random', 'secondary', {'disabled': queue.tracks.length === 0}, 'icon']" ></i>
</div> </div>
<div class="one wide column"></div> <div class="one wide column"></div>
<div <div
...@@ -158,6 +159,7 @@ export default { ...@@ -158,6 +159,7 @@ export default {
data () { data () {
let defaultAmbiantColors = [[46, 46, 46], [46, 46, 46], [46, 46, 46], [46, 46, 46]] let defaultAmbiantColors = [[46, 46, 46], [46, 46, 46], [46, 46, 46], [46, 46, 46]]
return { return {
isShuffling: false,
renderAudio: true, renderAudio: true,
sliderVolume: this.volume, sliderVolume: this.volume,
Track: Track, Track: Track,
...@@ -173,9 +175,24 @@ export default { ...@@ -173,9 +175,24 @@ export default {
...mapActions({ ...mapActions({
togglePlay: 'player/togglePlay', togglePlay: 'player/togglePlay',
clean: 'queue/clean', clean: 'queue/clean',
shuffle: 'queue/shuffle',
updateProgress: 'player/updateProgress' updateProgress: 'player/updateProgress'
}), }),
shuffle () {
if (this.isShuffling) {
return
}
let self = this
this.isShuffling = true
setTimeout(() => {
self.$store.dispatch('queue/shuffle', () => {
self.isShuffling = false
self.$store.commit('ui/addMessage', {
content: self.$t('Queue shuffled!'),
date: new Date()
})
})
}, 100)
},
next () { next () {
let self = this let self = this
this.$store.dispatch('queue/next').then(() => { this.$store.dispatch('queue/next').then(() => {
...@@ -402,5 +419,8 @@ export default { ...@@ -402,5 +419,8 @@ export default {
.ui.feed.icon { .ui.feed.icon {
margin: 0; margin: 0;
} }
.shuffling.loader.inline {
margin: 0;
}
</style> </style>
<template>
<div class="ui message">
<div class="content">
<slot></slot>
</div>
<i class="close icon"></i>
</div>
</template>
<script>
import $ from 'jquery'
export default {
mounted () {
let self = this
$(this.$el).find('.close.icon').on('click', function () {
$(self.$el).transition('fade', 125)
})
$(this.$el).on('click', function () {
$(self.$el).transition('fade', 125)
})
}
}
</script>
<style scoped>
.ui.message .content {
padding-right: 0.5em;
cursor: pointer;
}
.ui.message .content :first-child {
margin-top: 0;
}
.ui.message .content :last-child {
margin-bottom: 0;
}
</style>
...@@ -12,4 +12,8 @@ import DangerousButton from '@/components/common/DangerousButton' ...@@ -12,4 +12,8 @@ import DangerousButton from '@/components/common/DangerousButton'
Vue.component('dangerous-button', DangerousButton) Vue.component('dangerous-button', DangerousButton)
import Message from '@/components/common/Message'
Vue.component('message', Message)
export default {} export default {}
...@@ -72,16 +72,20 @@ export default { ...@@ -72,16 +72,20 @@ export default {
} }
}, },
appendMany ({state, dispatch}, {tracks, index}) { appendMany ({state, dispatch}, {tracks, index, callback}) {
logger.default.info('Appending many tracks to the queue', tracks.map(e => { return e.title })) logger.default.info('Appending many tracks to the queue', tracks.map(e => { return e.title }))
if (state.tracks.length === 0) { if (state.tracks.length === 0) {
index = 0 index = 0
} else { } else {
index = index || state.tracks.length index = index || state.tracks.length
} }
tracks.forEach((t) => { let total = tracks.length
dispatch('append', {track: t, index: index, skipPlay: true}) tracks.forEach((t, i) => {
let p = dispatch('append', {track: t, index: index, skipPlay: true})
index += 1 index += 1
if (callback && i + 1 === total) {
p.then(callback)
}
}) })
dispatch('resume') dispatch('resume')
}, },
...@@ -148,13 +152,17 @@ export default { ...@@ -148,13 +152,17 @@ export default {
// so we replay automatically on next track append // so we replay automatically on next track append
commit('ended', true) commit('ended', true)
}, },
shuffle ({dispatch, commit, state}) { shuffle ({dispatch, commit, state}, callback) {
let toKeep = state.tracks.slice(0, state.currentIndex + 1) let toKeep = state.tracks.slice(0, state.currentIndex + 1)
let toShuffle = state.tracks.slice(state.currentIndex + 1) let toShuffle = state.tracks.slice(state.currentIndex + 1)
let shuffled = toKeep.concat(_.shuffle(toShuffle)) let shuffled = toKeep.concat(_.shuffle(toShuffle))
commit('player/currentTime', 0, {root: true}) commit('player/currentTime', 0, {root: true})
commit('tracks', []) commit('tracks', [])
dispatch('appendMany', {tracks: shuffled}) let params = {tracks: shuffled}
if (callback) {
params.callback = callback
}
dispatch('appendMany', params)
} }
} }
} }
...@@ -2,11 +2,20 @@ ...@@ -2,11 +2,20 @@
export default { export default {
namespaced: true, namespaced: true,
state: { state: {
lastDate: new Date() lastDate: new Date(),
maxMessages: 100,
messageDisplayDuration: 10000,
messages: []
}, },
mutations: { mutations: {
computeLastDate: (state) => { computeLastDate: (state) => {
state.lastDate = new Date() state.lastDate = new Date()
},
addMessage (state, message) {
state.messages.push(message)
if (state.messages.length > state.maxMessages) {
state.messages.shift()
}
} }
} }
} }
import store from '@/store/ui'
import { testAction } from '../../utils'
describe('store/ui', () => {
describe('mutations', () => {
it('addMessage', () => {
const state = {maxMessages: 100, messages: []}
store.mutations.addMessage(state, 'hello')
expect(state.messages).to.deep.equal(['hello'])
})
it('addMessage', () => {
const state = {maxMessages: 1, messages: ['hello']}
store.mutations.addMessage(state, 'world')
expect(state.messages).to.deep.equal(['world'])
})
})
})
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment