2022-04-15 14:24:30 +00:00
|
|
|
<template>
|
|
|
|
<div
|
|
|
|
v-show="showing"
|
|
|
|
v-koel-clickaway="close"
|
|
|
|
v-koel-focus
|
2022-05-09 12:25:19 +00:00
|
|
|
class="add-to"
|
2022-04-15 14:24:30 +00:00
|
|
|
data-test="add-to-menu"
|
2022-05-09 12:25:19 +00:00
|
|
|
tabindex="0"
|
|
|
|
@keydown.esc="close"
|
2022-04-15 14:24:30 +00:00
|
|
|
>
|
|
|
|
<section class="existing-playlists">
|
2022-04-15 17:00:08 +00:00
|
|
|
<p>Add {{ pluralize(songs.length, 'song') }} to</p>
|
2022-04-15 14:24:30 +00:00
|
|
|
|
|
|
|
<ul>
|
|
|
|
<template v-if="config.queue">
|
2022-04-30 20:57:04 +00:00
|
|
|
<template v-if="queue.length">
|
2022-05-09 12:25:19 +00:00
|
|
|
<li
|
|
|
|
v-if="currentSong"
|
|
|
|
class="queue-after-current"
|
|
|
|
data-testid="queue-after-current"
|
|
|
|
tabindex="0"
|
|
|
|
@click="queueSongsAfterCurrent"
|
|
|
|
>
|
2022-04-30 20:57:04 +00:00
|
|
|
After Current Song
|
|
|
|
</li>
|
2022-05-09 12:25:19 +00:00
|
|
|
<li class="bottom-queue" data-testid="queue-bottom" tabindex="0" @click="queueSongsToBottom">
|
|
|
|
Bottom of Queue
|
|
|
|
</li>
|
|
|
|
<li class="top-queue" data-testid="queue-top" tabindex="0" @click="queueSongsToTop">Top of Queue</li>
|
2022-04-30 20:57:04 +00:00
|
|
|
</template>
|
2022-05-09 12:25:19 +00:00
|
|
|
<li v-else data-testid="queue" tabindex="0" @click="queueSongsToBottom">Queue</li>
|
2022-04-15 14:24:30 +00:00
|
|
|
</template>
|
|
|
|
|
|
|
|
<li
|
2022-05-09 12:25:19 +00:00
|
|
|
v-if="config.favorites"
|
2022-04-15 14:24:30 +00:00
|
|
|
class="favorites"
|
2022-05-09 12:25:19 +00:00
|
|
|
data-testid="add-to-favorites"
|
2022-04-15 14:24:30 +00:00
|
|
|
tabindex="0"
|
2022-05-09 12:25:19 +00:00
|
|
|
@click="addSongsToFavorite"
|
2022-04-15 14:24:30 +00:00
|
|
|
>
|
|
|
|
Favorites
|
|
|
|
</li>
|
|
|
|
|
|
|
|
<template v-if="config.playlists">
|
|
|
|
<li
|
2022-05-09 12:25:19 +00:00
|
|
|
v-for="playlist in playlists"
|
2022-04-15 14:24:30 +00:00
|
|
|
:key="playlist.id"
|
|
|
|
class="playlist"
|
2022-05-09 12:25:19 +00:00
|
|
|
data-testid="add-to-playlist"
|
2022-04-15 14:24:30 +00:00
|
|
|
tabindex="0"
|
2022-05-09 12:25:19 +00:00
|
|
|
@click="addSongsToExistingPlaylist(playlist)"
|
2022-04-15 14:24:30 +00:00
|
|
|
>
|
|
|
|
{{ playlist.name }}
|
|
|
|
</li>
|
|
|
|
</template>
|
|
|
|
</ul>
|
|
|
|
</section>
|
|
|
|
|
2022-05-09 12:25:19 +00:00
|
|
|
<section v-if="config.newPlaylist" class="new-playlist" data-testid="new-playlist">
|
2022-04-15 14:24:30 +00:00
|
|
|
<p>or create a new playlist</p>
|
|
|
|
|
|
|
|
<form class="form-save form-simple form-new-playlist" @submit.prevent="createNewPlaylistFromSongs">
|
|
|
|
<input
|
2022-05-09 12:25:19 +00:00
|
|
|
v-model="newPlaylistName"
|
|
|
|
data-testid="new-playlist-name"
|
|
|
|
placeholder="Playlist name"
|
2022-04-15 14:24:30 +00:00
|
|
|
required
|
|
|
|
type="text"
|
2022-05-09 12:25:19 +00:00
|
|
|
@keyup.esc.prevent="close"
|
2022-04-15 14:24:30 +00:00
|
|
|
>
|
2022-05-09 12:25:19 +00:00
|
|
|
<Btn title="Save" type="submit">⏎</Btn>
|
2022-04-15 14:24:30 +00:00
|
|
|
</form>
|
|
|
|
</section>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
2022-04-15 17:00:08 +00:00
|
|
|
<script lang="ts" setup>
|
2022-04-25 16:13:18 +00:00
|
|
|
import { computed, defineAsyncComponent, nextTick, ref, toRef, toRefs, watch } from 'vue'
|
2022-04-24 17:58:12 +00:00
|
|
|
import { alerts, pluralize } from '@/utils'
|
2022-04-30 20:57:04 +00:00
|
|
|
import { playlistStore, queueStore } from '@/stores'
|
2022-04-15 17:00:08 +00:00
|
|
|
import { useSongMenuMethods } from '@/composables'
|
2022-04-30 20:57:04 +00:00
|
|
|
import router from '@/router'
|
2022-04-15 14:24:30 +00:00
|
|
|
|
2022-04-21 18:39:18 +00:00
|
|
|
const Btn = defineAsyncComponent(() => import('@/components/ui/Btn.vue'))
|
2022-04-15 17:00:08 +00:00
|
|
|
|
|
|
|
const props = defineProps<{ songs: Song[], showing: Boolean, config: AddToMenuConfig }>()
|
|
|
|
const { songs, showing, config } = toRefs(props)
|
|
|
|
|
|
|
|
const newPlaylistName = ref('')
|
2022-04-30 20:57:04 +00:00
|
|
|
const queue = toRef(queueStore.state, 'songs')
|
|
|
|
const currentSong = toRef(queueStore.state, 'current')
|
2022-04-25 16:13:18 +00:00
|
|
|
|
|
|
|
const allPlaylists = toRef(playlistStore.state, 'playlists')
|
|
|
|
const playlists = computed(() => allPlaylists.value.filter(playlist => !playlist.is_smart))
|
2022-04-15 17:00:08 +00:00
|
|
|
|
|
|
|
const emit = defineEmits(['closing'])
|
|
|
|
const close = () => emit('closing')
|
|
|
|
|
|
|
|
const {
|
|
|
|
queueSongsAfterCurrent,
|
|
|
|
queueSongsToBottom,
|
|
|
|
queueSongsToTop,
|
|
|
|
addSongsToFavorite,
|
|
|
|
addSongsToExistingPlaylist
|
2022-04-20 10:20:09 +00:00
|
|
|
} = useSongMenuMethods(songs, close)
|
2022-04-15 17:00:08 +00:00
|
|
|
|
|
|
|
watch(songs, () => songs.value.length || close())
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Save the selected songs as a playlist.
|
|
|
|
* As of current we don't have selective save.
|
|
|
|
*/
|
|
|
|
const createNewPlaylistFromSongs = async () => {
|
|
|
|
newPlaylistName.value = newPlaylistName.value.trim()
|
|
|
|
|
|
|
|
if (!newPlaylistName.value) {
|
|
|
|
return
|
2022-04-15 14:24:30 +00:00
|
|
|
}
|
2022-04-15 17:00:08 +00:00
|
|
|
|
|
|
|
const playlist = await playlistStore.store(newPlaylistName.value, songs.value)
|
|
|
|
newPlaylistName.value = ''
|
2022-04-24 17:58:12 +00:00
|
|
|
|
|
|
|
alerts.success(`Playlist "${playlist.name}" created.`)
|
|
|
|
|
2022-04-15 17:00:08 +00:00
|
|
|
// Activate the new playlist right away
|
|
|
|
await nextTick()
|
|
|
|
router.go(`playlist/${playlist.id}`)
|
|
|
|
|
|
|
|
close()
|
|
|
|
}
|
2022-04-15 14:24:30 +00:00
|
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
.add-to {
|
|
|
|
@include context-menu();
|
|
|
|
|
|
|
|
width: 100%;
|
|
|
|
max-width: 225px;
|
|
|
|
padding: .75rem;
|
|
|
|
|
|
|
|
> * + * {
|
|
|
|
margin-top: 1rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
p {
|
|
|
|
margin-bottom: .5rem;
|
|
|
|
font-size: .9rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
.new-playlist {
|
|
|
|
margin-top: .5rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
ul {
|
|
|
|
max-height: 12rem;
|
|
|
|
overflow-y: scroll;
|
|
|
|
-webkit-overflow-scrolling: touch;
|
|
|
|
|
|
|
|
> li + li {
|
|
|
|
margin-top: .3rem;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
li {
|
|
|
|
height: 2.25rem;
|
|
|
|
line-height: 2.25rem;
|
|
|
|
padding: 0 .75rem;
|
|
|
|
white-space: nowrap;
|
|
|
|
overflow: hidden;
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
border-radius: 3px;
|
|
|
|
background: rgba(255, 255, 255, .05);
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
background: var(--color-highlight);
|
|
|
|
color: var(--color-text-primary);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
&::before {
|
|
|
|
display: block;
|
|
|
|
content: " ";
|
|
|
|
width: 0;
|
|
|
|
height: 0;
|
|
|
|
border-left: 10px solid transparent;
|
|
|
|
border-right: 10px solid transparent;
|
|
|
|
border-bottom: 10px solid var(--color-bg-secondary);
|
|
|
|
position: absolute;
|
|
|
|
top: -7px;
|
|
|
|
left: calc(50% - 10px);
|
|
|
|
}
|
|
|
|
|
|
|
|
form {
|
|
|
|
width: 100%;
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
input[type="text"] {
|
|
|
|
width: 100%;
|
|
|
|
border-radius: 5px 0 0 5px;
|
|
|
|
height: 28px;
|
|
|
|
}
|
|
|
|
|
|
|
|
button[type="submit"] {
|
|
|
|
margin-top: 0;
|
|
|
|
border-radius: 0 5px 5px 0 !important;
|
|
|
|
height: 28px;
|
|
|
|
line-height: 28px;
|
|
|
|
padding-top: 0;
|
|
|
|
padding-bottom: 0;
|
|
|
|
margin-left: -2px !important;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|