feat: remove from queue

This commit is contained in:
2025-06-09 01:17:13 +03:00
parent a96f23c870
commit a31afe998d
4 changed files with 71 additions and 21 deletions

View File

@@ -82,11 +82,7 @@
<SkipNext :size="20" />
</template>
</NcButton>
<QueuePopover
:shown="showQueue"
:queue="queue"
@close="showQueue = false"
@play="handlePlay">
<QueuePopover :shown="showQueue" :queue="queue" @close="showQueue = false">
<template #trigger>
<NcButton variant="tertiary" aria-label="Queue" size="normal" @click="toggleQueue">
<template #icon>
@@ -188,11 +184,6 @@
showQueue.value = !showQueue.value
}
const onPlayFromQueue = (media: Media) => {
playback.play(media)
showQueue.value = false
}
function formatTime(seconds: number): string {
const m = Math.floor(seconds / 60)
const s = Math.floor(seconds % 60)
@@ -211,7 +202,6 @@
isPlaying: computed(() => playback.isPlaying.value),
showQueue,
toggleQueue,
onPlayFromQueue,
formattedCurrentTime,
formattedDuration,
isRouterLoading,

View File

@@ -2,7 +2,6 @@
<NcListItem :active="isActive" :name="media.title || 'Untitled'" @click.prevent="onPlay" :bold="false">
<template #icon>
<img v-if="media.albumArt" :src="media.albumArt" alt="Cover" class="cover" width="44" height="44" />
<!-- fallback if no album art -->
<Music v-else :size="44" />
</template>
@@ -11,30 +10,35 @@
</template>
<template #actions>
<NcActionButton @click.stop="onPlay">
<slot name="actions-start" />
<NcActionButton v-if="!disablePlay" @click.stop="onPlay">
<template #icon>
<Play :size="20" />
</template>
Play
</NcActionButton>
<NcActionButton @click.stop="onPlayNext">
<NcActionButton v-if="!disablePlayNext" @click.stop="onPlayNext">
<template #icon>
<SkipNext :size="20" />
</template>
Play Next
</NcActionButton>
<NcActionButton @click.stop="onAddToQueue">
<NcActionButton v-if="!disableAddToQueue" @click.stop="onAddToQueue">
<template #icon>
<PlaylistPlus :size="20" />
</template>
Add to Queue
</NcActionButton>
<slot name="actions-end" />
</template>
</NcListItem>
</template>
<script lang="ts">
import { defineComponent, computed, type PropType } from 'vue'
import { type Media } from '@/models/media'
@@ -59,12 +63,26 @@ export default defineComponent({
type: String,
required: true,
},
disablePlay: {
type: Boolean,
default: false,
},
disablePlayNext: {
type: Boolean,
default: false,
},
disableAddToQueue: {
type: Boolean,
default: false,
},
},
components: {
NcActionButton,
NcListItem,
Music,
Play, SkipNext, PlaylistPlus
Play,
SkipNext,
PlaylistPlus,
},
emits: ['play'],
setup(props, { emit }) {

View File

@@ -4,13 +4,21 @@
<slot name="trigger" />
</template>
<template #default>
<div class="queue-container" tabindex="0" role="dialog" aria-labelledby="queue-popover-title" ref="popoverRef">
<h2 id="queue-popover-title" class="popover-title">Playback Queue</h2>
<div v-if="queue.length > 0" class="queue-list">
<MediaListItem v-for="(media, index) in queue" :key="media.id" :media="media" mediaType="track"
@play="onPlay" />
@play="onPlay(media)" disable-play-next disable-add-to-queue>
<template #actions-end>
<NcActionButton @click.stop="onRemove(media)">
<template #icon>
<Delete :size="20" />
</template>
Remove from Queue
</NcActionButton>
</template>
</MediaListItem>
</div>
<p v-else class="empty-message">The queue is empty.</p>
</div>
@@ -23,11 +31,14 @@
import { defineComponent, ref, onMounted, onBeforeUnmount, type PropType } from 'vue'
import NcPopover from '@nextcloud/vue/components/NcPopover'
import MediaListItem from '@/components/media/MediaListItem.vue'
import NcActionButton from '@nextcloud/vue/components/NcActionButton'
import Delete from '@icons/Delete.vue'
import { type Media } from '@/models/media'
import playback from '@/composables/usePlayback'
export default defineComponent({
name: 'QueuePopover',
components: { NcPopover, MediaListItem },
components: { NcPopover, MediaListItem, NcActionButton, Delete },
props: {
shown: Boolean,
queue: {
@@ -35,12 +46,12 @@ export default defineComponent({
required: true,
},
},
emits: ['close', 'play'],
emits: ['close'],
setup(_, { emit }) {
const popoverRef = ref<HTMLElement | null>(null)
const onClose = () => emit('close')
const onPlay = (media: Media) => emit('play', media)
const onPlay = (media: Media) => playback.playFromQueue(media)
const handleClickOutside = (event: MouseEvent) => {
if (popoverRef.value && !popoverRef.value.contains(event.target as Node)) {
@@ -56,9 +67,17 @@ export default defineComponent({
document.removeEventListener('mousedown', handleClickOutside)
})
const onRemove = (media: Media) => {
playback.removeFromQueue(media)
if (popoverRef.value) {
popoverRef.value.focus() // Keep focus on the popover after removing an item
}
}
return {
onClose,
onPlay,
onRemove,
popoverRef,
}
},

View File

@@ -91,6 +91,18 @@ function addToQueue(media: Media | Media[]) {
}
}
function removeFromQueue(media: Media) {
const index = queue.value.findIndex(item => item.id === media.id)
if (index !== -1) {
queue.value.splice(index, 1)
if (currentIndex.value >= index) {
currentIndex.value = Math.max(currentIndex.value - 1, -1)
}
} else {
console.warn('Media not found in queue:', media)
}
}
function addAsNext(media: Media) {
if (currentIndex.value >= 0) {
queue.value.splice(currentIndex.value + 1, 0, media)
@@ -115,6 +127,15 @@ function overwriteQueue(newQueue: Media[], startIndex = 0) {
}
}
function playFromQueue(media: Media) {
const index = queue.value.findIndex(item => item.id === media.id)
if (index !== -1) {
playIndex(index)
} else {
console.warn('Media not found in queue:', media)
}
}
function setSeek(percent: number) {
const newTime = (Number(percent) / 100) * duration.value
audio.currentTime = newTime
@@ -154,6 +175,8 @@ function usePlayback() {
queue,
currentIndex,
addToQueue,
removeFromQueue,
playFromQueue,
addAsNext,
clearQueue,
overwriteQueue,