2016-03-05 09:01:12 +00:00
|
|
|
<template>
|
|
|
|
<ul v-el:menu class="menu song-menu" v-show="shown" tabindex="-1" @contextmenu.prevent
|
|
|
|
@blur="close"
|
|
|
|
:style="{ top: top + 'px', left: left + 'px' }"
|
|
|
|
>
|
|
|
|
<li v-if="onlyOneSongSelected" @click="doPlayback">
|
|
|
|
<span v-if="songs[0].playbackState !== 'playing'">Play</span>
|
|
|
|
<span v-else>Pause</span>
|
|
|
|
</li>
|
2016-03-21 12:38:10 +00:00
|
|
|
<li v-if="onlyOneSongSelected" @click="goToAlbum">
|
|
|
|
<span>Go to Album</span>
|
|
|
|
</li>
|
|
|
|
<li v-if="onlyOneSongSelected" @click="goToArtist">
|
|
|
|
<span>Go to Artist</span>
|
|
|
|
</li>
|
2016-03-05 09:01:12 +00:00
|
|
|
<li class="has-sub">Add To
|
|
|
|
<ul class="menu submenu">
|
|
|
|
<li @click="queueSongsToBottom">Bottom of Queue</li>
|
|
|
|
<li @click="queueSongsToTop">Top of Queue</li>
|
|
|
|
<li class="separator"></li>
|
|
|
|
<li @click="addSongsToFavorite">Favorites</li>
|
|
|
|
<li class="separator"></li>
|
|
|
|
<li v-for="playlist in playlistState.playlists"
|
|
|
|
@click="addSongsToExistingPlaylist(playlist)"
|
|
|
|
>{{ playlist.name }}</li>
|
|
|
|
</ul>
|
|
|
|
</li>
|
|
|
|
<li v-if="isAdmin" @click="openEditForm">Edit</li>
|
|
|
|
</ul>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
|
|
|
import $ from 'jquery';
|
|
|
|
|
|
|
|
import songMenuMethods from '../../mixins/song-menu-methods';
|
|
|
|
|
|
|
|
import queueStore from '../../stores/queue';
|
|
|
|
import userStore from '../../stores/user';
|
|
|
|
import playlistStore from '../../stores/playlist';
|
|
|
|
import playback from '../../services/playback';
|
|
|
|
|
|
|
|
export default {
|
|
|
|
props: ['songs'],
|
|
|
|
mixins: [songMenuMethods],
|
|
|
|
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
playlistState: playlistStore.state,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
computed: {
|
|
|
|
onlyOneSongSelected() {
|
|
|
|
return this.songs.length === 1;
|
|
|
|
},
|
|
|
|
|
|
|
|
isAdmin() {
|
2016-03-18 04:45:12 +00:00
|
|
|
return userStore.current.is_admin;
|
2016-03-05 09:01:12 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
methods: {
|
|
|
|
open(top = 0, left = 0) {
|
|
|
|
if (!this.songs.length) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.top = top;
|
|
|
|
this.left = left;
|
|
|
|
this.shown = true;
|
|
|
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
// Make sure the menu isn't off-screen
|
|
|
|
if (this.$el.getBoundingClientRect().bottom > window.innerHeight) {
|
|
|
|
$(this.$el).css({
|
|
|
|
top: 'auto',
|
|
|
|
bottom: 0,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
$(this.$el).css({
|
|
|
|
top: this.top,
|
|
|
|
bottom: 'auto',
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
this.$els.menu.focus();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Take the right playback action based on the current playback state.
|
|
|
|
*/
|
|
|
|
doPlayback() {
|
|
|
|
switch (this.songs[0].playbackState) {
|
|
|
|
case 'playing':
|
|
|
|
playback.pause();
|
|
|
|
break;
|
|
|
|
case 'paused':
|
|
|
|
playback.resume();
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
if (!queueStore.contains(this.songs[0])) {
|
|
|
|
queueStore.queueAfterCurrent(this.songs[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
playback.play(this.songs[0]);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.close();
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Trigger opening the "Edit Song" form/overlay.
|
|
|
|
*/
|
|
|
|
openEditForm() {
|
|
|
|
if (this.songs.length) {
|
|
|
|
this.$root.showEditSongsForm(this.songs);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.close();
|
|
|
|
},
|
2016-03-21 12:38:10 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Navigate to the song's album view
|
|
|
|
*/
|
|
|
|
goToAlbum() {
|
|
|
|
this.$root.loadAlbum(this.songs[0].album);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Navigate to the song's artist view
|
|
|
|
*/
|
|
|
|
goToArtist() {
|
|
|
|
this.$root.loadArtist(this.songs[0].album.artist);
|
|
|
|
},
|
2016-03-05 09:01:12 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* On component ready(), 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.
|
|
|
|
*/
|
|
|
|
ready() {
|
|
|
|
$(this.$el).find('.has-sub').hover(e => {
|
2016-03-16 03:51:07 +00:00
|
|
|
let $submenu = $(e.target).find('.submenu:first');
|
2016-03-05 09:01:12 +00:00
|
|
|
if (!$submenu.length) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$submenu.show();
|
|
|
|
|
|
|
|
// Make sure the submenu isn't off-screen
|
|
|
|
if ($submenu[0].getBoundingClientRect().bottom > window.innerHeight) {
|
|
|
|
$submenu.css({
|
|
|
|
top: 'auto',
|
|
|
|
bottom: 0,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}, e => {
|
|
|
|
$(e.target).find('.submenu:first').hide().css({
|
|
|
|
top: 0,
|
|
|
|
bottom: 'auto',
|
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
|
|
};
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="sass" scoped>
|
2016-03-13 17:00:32 +00:00
|
|
|
@import "../../../sass/partials/_vars.scss";
|
|
|
|
@import "../../../sass/partials/_mixins.scss";
|
2016-03-05 09:01:12 +00:00
|
|
|
|
|
|
|
.menu {
|
|
|
|
@include context-menu();
|
|
|
|
position: fixed;
|
|
|
|
|
|
|
|
li {
|
|
|
|
position: relative;
|
|
|
|
padding: 4px 12px;
|
|
|
|
cursor: default;
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
background: $colorOrange;
|
|
|
|
color: #fff;
|
|
|
|
}
|
|
|
|
|
|
|
|
&.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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.submenu {
|
|
|
|
position: absolute;
|
|
|
|
display: none;
|
|
|
|
left: 100%;
|
|
|
|
top: 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|