[#414] Added support for photo collections in Jellyfin UI.

This commit is contained in:
Fabio Manganiello 2024-10-15 22:15:49 +02:00
parent 3ffb061e2a
commit 7b8d92b120
Signed by untrusted user: blacklight
GPG key ID: D90FBA7F76362774
4 changed files with 116 additions and 12 deletions

View file

@ -2,11 +2,11 @@
<div
class="item media-item"
:class="{selected: selected, 'list': listView}"
@click.right.prevent="$refs.dropdown.toggle()"
@click.right="onContextClick"
v-if="!hidden">
<div class="thumbnail" v-if="!listView">
<MediaImage :item="item" @play="$emit('play')" @select="$emit('select')" />
<MediaImage :item="item" @play="$emit('play')" @select="onMediaSelect" />
</div>
<div class="body">
@ -29,10 +29,12 @@
<span class="actions">
<Dropdown title="Actions" icon-class="fa fa-ellipsis-h" ref="dropdown">
<DropdownItem icon-class="fa fa-play" text="Play" @input="$emit('play')"
v-if="item.type !== 'torrent'" />
v-if="item.type !== 'torrent' && item.item_type !== 'photo'" />
<DropdownItem icon-class="fa fa-play" text="Play (With Cache)"
@input="$emit('play-with-opts', {item: item, opts: {cache: true}})"
v-if="item.type === 'youtube'" />
<DropdownItem icon-class="fa fa-eye" text="View" @input="showPhoto = true"
v-if="item.item_type === 'photo'" />
<DropdownItem icon-class="fa fa-download" text="Download" @input="$emit('download')"
v-if="(item.type === 'torrent' || item.type === 'youtube') && item.item_type !== 'channel' && item.item_type !== 'playlist'" />
<DropdownItem icon-class="fa fa-volume-high" text="Download Audio" @input="$emit('download-audio')"
@ -74,6 +76,12 @@
</span>
</div>
</div>
<div class="photo-container" v-if="item.item_type === 'photo' && showPhoto">
<Modal :title="item.title || item.name" :visible="true" @close="showPhoto = false">
<img :src="item.url" ref="image" @load="onImgLoad" />
</Modal>
</div>
</div>
</template>
@ -82,11 +90,18 @@ import Dropdown from "@/components/elements/Dropdown";
import DropdownItem from "@/components/elements/DropdownItem";
import Icons from "./icons.json";
import MediaImage from "./MediaImage";
import Modal from "@/components/Modal";
import Utils from "@/Utils";
export default {
components: {Dropdown, DropdownItem, MediaImage},
mixins: [Utils],
components: {
Dropdown,
DropdownItem,
MediaImage,
Modal,
},
emits: [
'add-to-playlist',
'download',
@ -130,8 +145,39 @@ export default {
},
},
methods: {
onContextClick(e) {
if (this.item?.item_type === 'photo') {
return
}
e.preventDefault()
this.$refs.dropdown.toggle()
},
onImgLoad() {
const width = this.$refs.image.naturalWidth
const height = this.$refs.image.naturalHeight
if (width > height) {
this.$refs.image.classList.add('horizontal')
} else {
this.$refs.image.classList.add('vertical')
}
},
onMediaSelect() {
if (this.item?.item_type === 'photo') {
this.showPhoto = true
} else {
this.$emit('select')
}
},
},
data() {
return {
showPhoto: false,
typeIcons: Icons,
}
},
@ -329,5 +375,31 @@ export default {
}
}
}
.photo-container {
:deep(.modal) {
.body {
max-width: 95vh;
max-height: 90vh;
padding: 0;
}
img {
&.horizontal {
width: 100%;
height: auto;
max-width: 95vh;
max-height: 100%;
}
&.vertical {
width: auto;
height: 100%;
max-width: 100%;
max-height: 90vh;
}
}
}
}
}
</style>

View file

@ -1,12 +1,12 @@
<template>
<div class="image-container"
:class="{ 'with-image': !!item?.image }">
<div class="play-overlay" @click="$emit(clickEvent, item)" v-if="hasPlay">
:class="{ 'with-image': !!item?.image, 'photo': item?.item_type === 'photo' }">
<div class="play-overlay" @click.stop="onItemClick" v-if="hasPlay || item?.item_type === 'photo'">
<i :class="overlayIconClass" />
</div>
<div class="backdrop" v-if="item?.image"
:style="{ backgroundImage: `url(${item.image})` }" />
<div class="backdrop" v-if="item?.image || item?.preview_url"
:style="{ backgroundImage: `url(${item.image || item.preview_url})` }" />
<span class="icon type-icon" v-if="typeIcons[item?.type]">
<a :href="item.url" target="_blank" v-if="item.url">
@ -17,6 +17,7 @@
</span>
<img class="image" :src="imgUrl" :alt="item.title" v-if="imgUrl" />
<div class="image" v-else>
<div class="inner">
<i :class="iconClass" />
@ -68,6 +69,7 @@ export default {
case 'channel':
case 'playlist':
case 'folder':
case 'photo':
return 'select'
default:
return 'play'
@ -88,6 +90,10 @@ export default {
},
imgUrl() {
if (this.item?.item_type === 'photo') {
return this.item?.preview_url || this.item?.url
}
let img = this.item?.image
if (!img) {
img = this.item?.images?.[0]?.url
@ -103,11 +109,19 @@ export default {
this.item?.item_type === 'folder'
) {
return 'fas fa-folder-open'
} else if (this.item?.item_type === 'photo') {
return 'fas fa-eye'
}
return 'fas fa-play'
},
},
methods: {
onItemClick() {
this.$emit(this.clickEvent, this.item)
},
},
}
</script>

View file

@ -67,7 +67,25 @@ export default {
},
mediaItems() {
return this.sortedItems?.filter((item) => item.item_type !== 'collection') ?? []
const items = this.sortedItems?.filter((item) => item.item_type !== 'collection') ?? []
if (this.collection && !this.collection.collection_type) {
return items.sort((a, b) => {
if (a.created_at && b.created_at)
return (new Date(a.created_at)) < (new Date(b.created_at))
if (a.created_at)
return -1
if (b.created_at)
return 1
let names = [a.name || a.title || '', b.name || b.title || '']
return names[0].localeCompare(names[1])
})
}
return items
},
},
@ -120,7 +138,7 @@ export default {
(
await this.request('media.jellyfin.get_items', {
parent_id: this.collection.id,
limit: 5000,
limit: 25000,
})
) : (await this.request('media.jellyfin.get_collections')).map((collection) => ({
...collection,

View file

@ -212,7 +212,7 @@ export default {
'media.jellyfin.get_items',
{
parent_id: this.collection.id,
limit: 5000,
limit: 25000,
}
)
).map((item) => {
@ -234,7 +234,7 @@ export default {
'media.jellyfin.get_items',
{
parent_id: this.collection.id,
limit: 5000,
limit: 25000,
}
)
break