forked from platypush/platypush
[media.jellyfin] Playlist track move implementation [UI].
This commit is contained in:
parent
1230236ca5
commit
9999025c0a
5 changed files with 145 additions and 28 deletions
|
@ -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 }} —
|
||||
</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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
)
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue