[Jellyfin UI] Implemented add to/remove from playlist.

Closes: #414
This commit is contained in:
Fabio Manganiello 2024-11-10 00:29:24 +01:00
parent 2df88c1911
commit d799d50391
Signed by untrusted user: blacklight
GPG key ID: D90FBA7F76362774
5 changed files with 153 additions and 45 deletions

View file

@ -5,19 +5,30 @@
Playlist name
</TextPrompt>
<div class="playlists">
<div class="playlist new-playlist">
<button @click="showNewPlaylist = true">
<i class="fa fa-plus" />
Create new playlist
</button>
<div class="playlists-container">
<div class="header">
<div class="filter">
<input type="text"
placeholder="Filter playlists"
ref="playlistFilter"
@input="filter = $event.target.value">
</div>
<div class="playlist new-playlist">
<button @click="showNewPlaylist = true">
<i class="fa fa-plus" />
Create new playlist
</button>
</div>
</div>
<div class="playlist" v-for="playlist in playlists" :key="playlist.id">
<button @click="addToPlaylist(playlist.id)">
<i class="fa fa-list" />
{{ playlist.name }}
</button>
<div class="playlists">
<div class="playlist" v-for="playlist in sortedPlaylists" :key="playlist.id">
<button @click="addToPlaylist(playlist.id)">
<i class="fa fa-list" />
{{ playlist.name }}
</button>
</div>
</div>
</div>
</div>
@ -41,13 +52,53 @@ export default {
data() {
return {
filter: '',
loading: false,
playlists: [],
showNewPlaylist: false,
}
},
computed: {
pluginName() {
switch (this.item.type) {
case 'youtube':
return 'youtube'
case 'jellyfin':
return 'media.jellyfin'
default:
return null
}
},
sortedPlaylists() {
return this.playlists
.filter((playlist) => playlist.name.toLowerCase().includes(this.filter.toLowerCase()))
.sort((a, b) => a.name.localeCompare(b.name))
},
},
methods: {
checkPlugin() {
if (!this.pluginName) {
this.notify({
title: 'Unsupported item type',
text: `Item type ${this.item.type} does not support playlists`,
warning: true,
image: {
icon: 'exclamation-triangle',
}
})
console.warn(`Unsupported item type: ${this.item.type}`)
return false
}
return true
},
async createPlaylist(name) {
name = name?.trim()
if (!name?.length)
@ -56,23 +107,11 @@ export default {
this.loading = true
try {
const playlist = await this.request('youtube.create_playlist', {
const playlist = await this.request(`${this.pluginName}.create_playlist`, {
name: name,
})
await this.request('youtube.add_to_playlist', {
playlist_id: playlist.id,
video_id: this.item.id || this.item.url,
})
this.$emit('done')
this.notify({
text: 'Playlist created and video added',
image: {
icon: 'check',
}
})
await this.addToPlaylist(playlist.id)
} finally {
this.loading = false
this.showNewPlaylist = false
@ -80,26 +119,30 @@ export default {
},
async refreshPlaylists() {
this.loading = true
if (!this.checkPlugin())
return
this.loading = true
try {
this.playlists = await this.request('youtube.get_playlists')
this.playlists = await this.request(`${this.pluginName}.get_playlists`)
} finally {
this.loading = false
}
},
async addToPlaylist(playlistId) {
this.loading = true
if (!this.checkPlugin())
return
this.loading = true
try {
await this.request('youtube.add_to_playlist', {
await this.request(`${this.pluginName}.add_to_playlist`, {
playlist_id: playlistId,
video_id: this.item.id || this.item.url,
item_ids: [this.item.id || this.item.url],
})
this.notify({
text: 'Video added to playlist',
text: 'Item added to playlist',
image: {
icon: 'check',
}
@ -112,6 +155,15 @@ export default {
},
},
watch: {
loading() {
if (this.loading)
return
this.$nextTick(() => this.$refs.playlistFilter.focus())
},
},
mounted() {
this.refreshPlaylists()
},
@ -119,19 +171,47 @@ export default {
</script>
<style lang="scss" scoped>
$header-height: 6.5em;
.playlist-adder-container {
min-width: 300px;
height: 100%;
width: 30em;
height: fit-content;
max-width: 90vw;
max-height: 70vh;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
.playlists-container {
width: 100%;
height: 100%;
.header {
width: 100%;
height: $header-height;
display: flex;
flex-direction: column;
}
}
.playlists {
width: 100%;
height: calc(100% - #{$header-height});
overflow-y: auto;
}
.filter {
width: 100%;
display: flex;
input[type="text"] {
width: 100%;
margin: 0.5em 0.5em 0.25em 0.5em;
padding: 0.5em;
}
}
.playlist {
button {
width: 100%;

View file

@ -15,6 +15,7 @@
<Media v-bind="componentData.props"
v-on="componentData.on"
:collection="collection"
@add-to-playlist="$emit('add-to-playlist', $event)"
@delete="deleteItem"
@select="select"
@select-collection="selectCollection"

View file

@ -131,8 +131,8 @@ export default {
try {
await this.request('media.jellyfin.playlist_move', {
playlist: this.collection.id,
playlist_item_id: item.playlist_item_id,
playlist_id: this.collection.id,
item_id: item.playlist_item_id,
to_pos: to,
})

View file

@ -92,7 +92,7 @@
@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)"
@remove-from-playlist="removeFromPlaylist"
@select="selectedResult = $event"
@view="$emit('view', $event)"
v-if="mediaItems?.length > 0" />
@ -131,7 +131,6 @@ export default {
'play',
'play-with-opts',
'playlist-move',
'remove-from-playlist',
'select',
'select-collection',
'view',
@ -178,7 +177,11 @@ export default {
mediaItems() {
return (
this.sortedItems?.filter((item) => !['collection', 'artist', 'album'].includes(item.item_type)) ?? []
).sort((a, b) => {
).map((item) => {
item.media_type = item.type
item.type = 'jellyfin'
return item
}).sort((a, b) => {
if (this.view === 'playlist') {
// Skip sorting if this is a playlist
return 0
@ -219,6 +222,20 @@ export default {
},
methods: {
async removeFromPlaylist(item) {
this.loading_ = true
try {
await this.request('media.jellyfin.remove_from_playlist', {
playlist_id: this.collection.id,
item_ids: [item.playlist_item_id],
})
await this.refresh()
} finally {
this.loading_ = false
}
},
async selectArtist() {
const artistId = this.displayedArtist?.id || this.getUrlArgs().artist
if (!artistId?.length)
@ -319,12 +336,22 @@ export default {
break
case 'playlist':
this.items = await this.request(
'media.jellyfin.get_playlist_items',
{
playlist: this.collection.id,
limit: 25000,
}
this.items = this.collection?.item_type === 'playlist' ? (
await this.request(
'media.jellyfin.get_playlist_items',
{
playlist_id: this.collection.id,
limit: 25000,
}
)
) : (
await this.request(
'media.jellyfin.get_items',
{
parent_id: this.collection.id,
limit: 25000,
}
)
)
break

View file

@ -133,7 +133,7 @@ export default {
try {
await this.request('youtube.remove_from_playlist', {
playlist_id: playlistId,
video_id: videoId,
item_ids: [videoId],
})
} finally {
this.loading_ = false