[media.jellyfin] Playlist track move implementation [UI].

This commit is contained in:
Fabio Manganiello 2024-11-09 16:24:53 +01:00
parent 1230236ca5
commit 9999025c0a
Signed by untrusted user: blacklight
GPG key ID: D90FBA7F76362774
5 changed files with 145 additions and 28 deletions

View file

@ -1,5 +1,5 @@
<template>
<div
<div ref="item"
class="item media-item"
:class="{selected: selected, 'list': listView}"
@click.right="onContextClick"
@ -22,11 +22,15 @@
{{ item.track_number }}
</span>
<span class="artist" v-if="playlistView && item.artist">
{{ item.artist.name ?? item.artist }} &nbsp;&mdash;&nbsp;
</span>
<div class="artist-and-title">
<span class="artist" v-if="playlistView && item.artist">
{{ item.artist.name ?? item.artist }}
</span>
{{item.title || item.name}}
<span class="title">
{{item.title || item.name}}
</span>
</div>
</div>
<div class="right side" :class="{'col-1': !listView, 'col-2': listView }">
@ -129,7 +133,7 @@ export default {
},
playlist: {
type: String,
type: [Object, String],
},
selected: {
@ -326,10 +330,22 @@ export default {
}
.artist {
font-size: 0.9em;
font-weight: 300;
opacity: .75;
letter-spacing: 0.065em;
}
.artist-and-title {
width: 100%;
display: flex;
flex-direction: column;
.title {
font-size: 1em;
padding: 0;
}
}
}
.side {

View file

@ -47,6 +47,11 @@ export default {
return []
}
if (this.collection?.item_type === 'playlist') {
// Don't sort playlists
return this.items
}
return [...this.items].sort((a, b) => {
const attr = this.sort.attr
const desc = this.sort.desc

View file

@ -11,6 +11,7 @@
@delete="$emit('delete', $event)"
@play="$emit('play', $event)"
@play-with-opts="$emit('play-with-opts', $event)"
@playlist-move="playlistMove"
@remove-from-playlist="$emit('remove-from-playlist', $event)"
@select="selectedResult = $event; $emit('select', $event)"
@select-collection="selectCollection"
@ -124,6 +125,23 @@ export default {
this.selectedResult = index
},
async playlistMove(event) {
const { item, to } = event
this.loading_ = true
try {
await this.request('media.jellyfin.playlist_move', {
playlist: this.collection.id,
playlist_item_id: item.playlist_item_id,
to_pos: to,
})
await this.refresh()
} finally {
this.loading_ = false
}
},
async init() {
const args = this.getUrlArgs()
let collection = args?.collection

View file

@ -89,6 +89,7 @@
:show-date="false"
@add-to-playlist="$emit('add-to-playlist', $event)"
@download="$emit('download', $event)"
@move="$emit('playlist-move', $event)"
@play="$emit('play', $event)"
@play-with-opts="$emit('play-with-opts', $event)"
@remove-from-playlist="$emit('remove-from-playlist', $event)"
@ -129,6 +130,7 @@ export default {
'download',
'play',
'play-with-opts',
'playlist-move',
'remove-from-playlist',
'select',
'select-collection',
@ -177,6 +179,11 @@ export default {
return (
this.sortedItems?.filter((item) => !['collection', 'artist', 'album'].includes(item.item_type)) ?? []
).sort((a, b) => {
if (this.view === 'playlist') {
// Skip sorting if this is a playlist
return 0
}
if (this.view === 'album') {
if (a.track_number && b.track_number) {
if (a.track_number !== b.track_number) {
@ -313,9 +320,9 @@ export default {
case 'playlist':
this.items = await this.request(
'media.jellyfin.get_items',
'media.jellyfin.get_playlist_items',
{
parent_id: this.collection.id,
playlist: this.collection.id,
limit: 25000,
}
)

View file

@ -2,25 +2,54 @@
<div class="media-results" :class="{'list': listView}">
<Loading v-if="loading" />
<div class="grid" ref="grid" v-if="results?.length" @scroll="onScroll">
<Item v-for="(item, i) in visibleResults"
:key="i"
:hidden="!!Object.keys(sources || {}).length && !sources[item.type]"
:index="i"
:item="item"
:list-view="listView"
:playlist="playlist"
:selected="selectedResult === i"
:show-date="showDate"
@add-to-playlist="$emit('add-to-playlist', item)"
@open-channel="$emit('open-channel', item)"
@remove-from-playlist="$emit('remove-from-playlist', item)"
@select="$emit('select', i)"
@play="$emit('play', item)"
@play-with-opts="$emit('play-with-opts', $event)"
@view="$emit('view', item)"
@download="$emit('download', item)"
@download-audio="$emit('download-audio', item)"
/>
<div class="item-container" v-for="(item, i) in visibleResults" :key="i" ref="item">
<div class="droppable-container"
:class="{'dragover': dragOverIndex === i}"
:ref="'droppable-' + i"
v-if="playlistView && draggedIndex != null && i > draggedIndex" />
<Item :item="item"
:index="i"
:list-view="listView"
:playlist="playlist"
:selected="selectedResult === i"
:show-date="showDate"
@add-to-playlist="$emit('add-to-playlist', item)"
@open-channel="$emit('open-channel', item)"
@remove-from-playlist="$emit('remove-from-playlist', item)"
@select="$emit('select', i)"
@play="$emit('play', item)"
@play-with-opts="$emit('play-with-opts', $event)"
@view="$emit('view', item)"
@download="$emit('download', item)"
@download-audio="$emit('download-audio', item)"
@vue:mounted="itemsRef[i] = $event.el"
@vue:unmounted="delete itemsRef[i]"
/>
<Draggable :element="itemsRef[i]"
@drag="draggedIndex = i"
v-if="playlistView" />
<Droppable :element="itemsRef[i]"
@dragenter="dragOverIndex = i"
@dragleave="dragOverIndex = null"
@dragover="dragOverIndex = i"
@drop="onMove(i)" />
<div class="droppable-container"
:class="{'dragover': dragOverIndex === i}"
:ref="'droppable-' + i"
v-if="playlistView && draggedIndex != null && i < draggedIndex" />
<Droppable :element="$refs['droppable-' + i]?.[0]"
@dragenter="dragOverIndex = i"
@dragleave="dragOverIndex = null"
@dragover="dragOverIndex = i"
@drop="onMove(i)"
v-if="playlistView && draggedIndex != null && i !== draggedIndex" />
</div>
</div>
<Modal ref="infoModal" title="Media info" @close="$emit('select', null)">
@ -38,17 +67,28 @@
</template>
<script>
import Draggable from "@/components/elements/Draggable"
import Droppable from "@/components/elements/Droppable"
import Info from "@/components/panels/Media/Info";
import Item from "./Item";
import Loading from "@/components/Loading";
import Modal from "@/components/Modal";
export default {
components: {Info, Item, Loading, Modal},
components: {
Draggable,
Droppable,
Info,
Item,
Loading,
Modal,
},
emits: [
'add-to-playlist',
'download',
'download-audio',
'move',
'open-channel',
'play',
'play-with-opts',
@ -109,11 +149,18 @@ export default {
data() {
return {
draggedIndex: null,
dragOverIndex: null,
itemsRef: {},
maxResultIndex: this.resultIndexStep,
}
},
computed: {
playlistView() {
return this.playlist != null && this.listView
},
visibleResults() {
let results = this.results
.filter((item) => {
@ -131,6 +178,20 @@ export default {
},
methods: {
onMove(toPos) {
if (this.draggedIndex == null)
return
const item = this.results[this.draggedIndex]
this.$emit('move', {
from: this.draggedIndex,
to: toPos,
item: item,
})
this.draggedIndex = null
},
onScroll(e) {
const el = e.target
if (!el)
@ -197,5 +258,15 @@ export default {
}
}
}
.droppable-container {
background: $selected-fg;
box-shadow: $scrollbar-track-shadow;
&.dragover {
height: 0.5em;
background: $active-glow-bg-2;
}
}
}
</style>