Merge branch '262-shuffle-feedback' into 'develop'
Resolve "No feedback on the shuffle button" Closes #262 See merge request funkwhale/funkwhale!238
This commit is contained in:
commit
1823554bbc
|
@ -0,0 +1 @@
|
||||||
|
Added feedback on shuffle button (#262)
|
|
@ -1,6 +1,7 @@
|
||||||
<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 @@
|
||||||
<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 {
|
||||||
components: {
|
components: {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
Raven,
|
Raven,
|
||||||
PlaylistModal
|
PlaylistModal,
|
||||||
|
ServiceMessages
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
@ -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 {
|
||||||
}
|
}
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
<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 {
|
||||||
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 @@
|
||||||
: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 {
|
||||||
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 {
|
||||||
...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 {
|
||||||
.ui.feed.icon {
|
.ui.feed.icon {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
.shuffling.loader.inline {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
<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'
|
||||||
|
|
||||||
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 {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
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 {
|
||||||
// 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 @@
|
||||||
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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
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'])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in New Issue