mirror of
https://github.com/chenasraf/nextcloud-jukebox.git
synced 2026-05-18 01:39:00 +00:00
feat: remove from queue
This commit is contained in:
12
src/App.vue
12
src/App.vue
@@ -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,
|
||||
|
||||
@@ -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 }) {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user