koel/resources/assets/js/components/shared/song-menu.vue

225 lines
5.4 KiB
Vue
Raw Normal View History

2016-03-05 09:01:12 +00:00
<template>
2016-06-25 16:05:24 +00:00
<ul ref="menu" class="menu song-menu" v-show="shown" tabindex="-1" @contextmenu.prevent
@blur="close"
:style="{ top: top+'px', left: left+'px' }"
2016-06-25 16:05:24 +00:00
>
2016-07-07 13:54:20 +00:00
<template v-show="onlyOneSongSelected">
<li @click="doPlayback">
2016-07-08 08:36:58 +00:00
<span v-show="!firstSongPlaying">Play</span>
<span v-show="firstSongPlaying">Pause</span>
2016-07-07 13:54:20 +00:00
</li>
<li @click="viewAlbumDetails(songs[0].album)">Go to Album</li>
<li @click="viewArtistDetails(songs[0].artist)">Go to Artist</li>
</template>
2016-06-25 16:05:24 +00:00
<li class="has-sub">Add To
<ul class="menu submenu">
<li @click="queueSongsAfterCurrent">After Current Song</li>
<li @click="queueSongsToBottom">Bottom of Queue</li>
<li @click="queueSongsToTop">Top of Queue</li>
<li class="separator"></li>
<li @click="addSongsToFavorite">Favorites</li>
2016-07-08 08:36:58 +00:00
<li class="separator" v-show="playlistState.playlists.length"></li>
2016-07-07 13:54:20 +00:00
<li v-for="p in playlistState.playlists" @click="addSongsToExistingPlaylist(p)">{{ p.name }}</li>
2016-06-25 16:05:24 +00:00
</ul>
</li>
2016-07-08 08:36:58 +00:00
<li v-show="isAdmin" @click="openEditForm">Edit</li>
<li v-show="sharedState.allowDownload" @click="download">Download</li>
2016-07-07 13:54:20 +00:00
<!-- somehow v-if doesn't work here -->
<li v-show="copyable && onlyOneSongSelected" @click="copyUrl">Copy Shareable URL</li>
2016-06-25 16:05:24 +00:00
</ul>
2016-03-05 09:01:12 +00:00
</template>
<script>
2017-01-15 16:20:55 +00:00
import { each } from 'lodash'
2016-06-25 16:05:24 +00:00
2017-01-15 16:20:55 +00:00
import songMenuMethods from '../../mixins/song-menu-methods'
2016-11-26 03:25:35 +00:00
import { event, isClipboardSupported, copyText } from '../../utils'
import { sharedStore, songStore, queueStore, userStore, playlistStore } from '../../stores'
import { playback, download } from '../../services'
import router from '../../router'
2016-06-25 16:05:24 +00:00
export default {
name: 'song-menu',
props: ['songs'],
2016-07-10 17:55:20 +00:00
mixins: [songMenuMethods],
2016-06-25 16:05:24 +00:00
2016-11-26 03:25:35 +00:00
data () {
2016-06-25 16:05:24 +00:00
return {
playlistState: playlistStore.state,
sharedState: sharedStore.state,
2016-11-26 03:25:35 +00:00
copyable: isClipboardSupported()
}
2016-06-25 16:05:24 +00:00
},
computed: {
2016-11-26 03:25:35 +00:00
onlyOneSongSelected () {
return this.songs.length === 1
2016-06-25 16:05:24 +00:00
},
2016-11-26 03:25:35 +00:00
firstSongPlaying () {
return this.songs[0] ? this.songs[0].playbackState === 'playing' : false
2016-07-07 13:54:20 +00:00
},
2016-11-26 03:25:35 +00:00
isAdmin () {
return userStore.current.is_admin
}
2016-06-25 16:05:24 +00:00
},
methods: {
2016-11-26 03:25:35 +00:00
open (top = 0, left = 0) {
2016-06-25 16:05:24 +00:00
if (!this.songs.length) {
2016-11-26 03:25:35 +00:00
return
2016-06-25 16:05:24 +00:00
}
2016-11-26 03:25:35 +00:00
this.top = top
this.left = left
this.shown = true
2016-06-25 16:05:24 +00:00
this.$nextTick(() => {
// Make sure the menu isn't off-screen
if (this.$el.getBoundingClientRect().bottom > window.innerHeight) {
2016-12-20 15:44:47 +00:00
this.$el.style.top = 'auto'
this.$el.style.bottom = 0
2016-06-25 16:05:24 +00:00
} else {
2016-12-20 15:44:47 +00:00
this.$el.style.top = this.top
this.$el.style.bottom = 'auto'
2016-06-25 16:05:24 +00:00
}
2016-11-26 03:25:35 +00:00
this.$refs.menu.focus()
})
2016-06-25 16:05:24 +00:00
},
/**
* Take the right playback action based on the current playback state.
*/
2016-11-26 03:25:35 +00:00
doPlayback () {
2016-06-25 16:05:24 +00:00
switch (this.songs[0].playbackState) {
case 'playing':
2016-11-26 03:25:35 +00:00
playback.pause()
break
2016-06-25 16:05:24 +00:00
case 'paused':
2016-11-26 03:25:35 +00:00
playback.resume()
break
2016-06-25 16:05:24 +00:00
default:
2017-01-17 07:02:19 +00:00
queueStore.contains(this.songs[0]) || queueStore.queueAfterCurrent(this.songs[0])
2016-11-26 03:25:35 +00:00
playback.play(this.songs[0])
break
2016-06-25 16:05:24 +00:00
}
2016-11-26 03:25:35 +00:00
this.close()
2016-06-25 16:05:24 +00:00
},
/**
* Trigger opening the "Edit Song" form/overlay.
*/
2016-11-26 03:25:35 +00:00
openEditForm () {
2017-01-17 07:02:19 +00:00
this.songs.length || event.emit('songs:edit', this.songs)
2016-11-26 03:25:35 +00:00
this.close()
2016-06-25 16:05:24 +00:00
},
2016-07-10 17:55:20 +00:00
/**
* Load the album details screen.
*/
2016-11-26 03:25:35 +00:00
viewAlbumDetails (album) {
router.go(`album/${album.id}`)
this.close()
2016-07-10 17:55:20 +00:00
},
/**
* Load the artist details screen.
*/
2016-11-26 03:25:35 +00:00
viewArtistDetails (artist) {
router.go(`artist/${artist.id}`)
this.close()
2016-07-10 17:55:20 +00:00
},
2016-11-26 03:25:35 +00:00
download () {
download.fromSongs(this.songs)
this.close()
2016-06-25 16:05:24 +00:00
},
2016-07-07 13:54:20 +00:00
2016-11-26 03:25:35 +00:00
copyUrl () {
copyText(songStore.getShareableUrl(this.songs[0]))
}
2016-06-25 16:05:24 +00:00
},
/**
* On component mounted(), we use some JavaScript to prepare the submenu triggering.
* With this, we can catch when the submenus shown or hidden, and can make sure
* they don't appear off-screen.
*/
2016-11-26 03:25:35 +00:00
mounted () {
2017-01-15 16:20:55 +00:00
each(this.$el.querySelectorAll('.has-sub'), item => {
2016-12-20 15:44:47 +00:00
const submenu = item.querySelector('.submenu')
if (!submenu) {
2016-11-26 03:25:35 +00:00
return
2016-06-25 16:05:24 +00:00
}
2016-12-20 15:44:47 +00:00
item.addEventListener('mouseenter', e => {
submenu.style.display = 'block'
2016-06-25 16:05:24 +00:00
2016-12-20 15:44:47 +00:00
// Make sure the submenu isn't off-screen
if (submenu.getBoundingClientRect().bottom > window.innerHeight) {
submenu.style.top = 'auto'
submenu.style.bottom = 0
}
})
item.addEventListener('mouseleave', e => {
submenu.style.top = 0
submenu.style.bottom = 'auto'
submenu.style.display = 'none'
2016-11-26 03:25:35 +00:00
})
})
}
}
2016-03-05 09:01:12 +00:00
</script>
<style lang="sass" scoped>
2016-06-25 16:05:24 +00:00
@import "../../../sass/partials/_vars.scss";
@import "../../../sass/partials/_mixins.scss";
.menu {
@include context-menu();
position: fixed;
li {
position: relative;
padding: 4px 12px;
cursor: default;
white-space: nowrap;
&:hover {
background: $colorOrange;
color: #fff;
}
2016-03-05 09:01:12 +00:00
2016-06-25 16:05:24 +00:00
&.separator {
pointer-event: none;
padding: 1px 0;
background: #ccc;
}
&.has-sub {
padding-right: 24px;
&:after {
position: absolute;
right: 12px;
top: 4px;
content: "▸";
width: 16px;
text-align: right;
}
2016-03-05 09:01:12 +00:00
}
2016-06-25 16:05:24 +00:00
}
.submenu {
position: absolute;
display: none;
left: 100%;
top: 0;
}
}
2016-03-05 09:01:12 +00:00
</style>