diff --git a/platypush/backend/http/webapp/src/components/elements/Dropdown.vue b/platypush/backend/http/webapp/src/components/elements/Dropdown.vue index 53150e28b2..4d8506f210 100644 --- a/platypush/backend/http/webapp/src/components/elements/Dropdown.vue +++ b/platypush/backend/http/webapp/src/components/elements/Dropdown.vue @@ -175,7 +175,7 @@ export default { }, toggle(event) { - event.stopPropagation() + event?.stopPropagation() this.$emit('click', event) this.visible ? this.close() : this.open() }, diff --git a/platypush/backend/http/webapp/src/components/panels/Media/Browser.vue b/platypush/backend/http/webapp/src/components/panels/Media/Browser.vue index 57df3bd44b..dbb542d82b 100644 --- a/platypush/backend/http/webapp/src/components/panels/Media/Browser.vue +++ b/platypush/backend/http/webapp/src/components/panels/Media/Browser.vue @@ -3,13 +3,16 @@
+ v-if="providersMetadata[name].icon?.class" /> + +
{{ providersMetadata[name].name }} @@ -104,6 +107,15 @@ export default { return acc }, {}) }, + + visibleMediaProviders() { + return Object.entries(this.mediaProviders) + .filter(([provider, component]) => component && (!this.filter || provider.toLowerCase().includes(this.filter.toLowerCase()))) + .reduce((acc, [provider, component]) => { + acc[provider] = component + return acc + }, {}) + }, }, methods: { @@ -124,13 +136,16 @@ export default { }, async refreshMediaProviders() { - const config = await this.request('config.get') + const config = this.$root.config this.mediaProviders = {} // The local File provider is always enabled this.registerMediaProvider('File') if (config.youtube) this.registerMediaProvider('YouTube') + + if (config['media.jellyfin']) + this.registerMediaProvider('Jellyfin') }, onPlaylistChange() { diff --git a/platypush/backend/http/webapp/src/components/panels/Media/Item.vue b/platypush/backend/http/webapp/src/components/panels/Media/Item.vue index 0bd0574b08..1f1b73be5c 100644 --- a/platypush/backend/http/webapp/src/components/panels/Media/Item.vue +++ b/platypush/backend/http/webapp/src/components/panels/Media/Item.vue @@ -43,6 +43,20 @@
{{ formatDateTime(item.created_at, true) }}
+ +
+ +
+ +   + % + + + +   + % + +
@@ -229,5 +243,23 @@ export default { color: $default-fg-2; flex: 1; } + + .ratings { + width: 100%; + font-size: .75em; + opacity: .75; + display: flex; + justify-content: space-between; + + .rating { + display: flex; + align-items: center; + margin-right: 1em; + + i { + margin-right: .25em; + } + } + } } diff --git a/platypush/backend/http/webapp/src/components/panels/Media/MediaImage.vue b/platypush/backend/http/webapp/src/components/panels/Media/MediaImage.vue index 1ece209939..05fd4e9f42 100644 --- a/platypush/backend/http/webapp/src/components/panels/Media/MediaImage.vue +++ b/platypush/backend/http/webapp/src/components/panels/Media/MediaImage.vue @@ -5,6 +5,9 @@
+
+ @@ -119,6 +122,7 @@ export default { background: rgba(0, 0, 0, 0.5); border-radius: 0.25em; color: $default-media-img-fg; + z-index: 2; a { width: 100%; @@ -159,6 +163,7 @@ export default { color: white; padding: 0.25em 0.5em; border-radius: 0.25em; + z-index: 2; } .type-icon { @@ -194,6 +199,7 @@ export default { .image { max-width: 100%; + z-index: 1; } div.image { @@ -229,6 +235,7 @@ div.image { border-radius: 2em; opacity: 0; transition: opacity 0.2s ease-in-out; + z-index: 3; &:hover { opacity: 1; @@ -239,4 +246,14 @@ div.image { color: $default-media-img-fg; } } + +.backdrop { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-size: cover; + filter: blur(5px) brightness(0.5); +} diff --git a/platypush/backend/http/webapp/src/components/panels/Media/Providers/Jellyfin.vue b/platypush/backend/http/webapp/src/components/panels/Media/Providers/Jellyfin.vue new file mode 100644 index 0000000000..c2b37ea6ee --- /dev/null +++ b/platypush/backend/http/webapp/src/components/panels/Media/Providers/Jellyfin.vue @@ -0,0 +1,162 @@ + + + + + diff --git a/platypush/backend/http/webapp/src/components/panels/Media/Providers/Jellyfin/Collections/Movies/Index.vue b/platypush/backend/http/webapp/src/components/panels/Media/Providers/Jellyfin/Collections/Movies/Index.vue new file mode 100644 index 0000000000..bc3dd6ef08 --- /dev/null +++ b/platypush/backend/http/webapp/src/components/panels/Media/Providers/Jellyfin/Collections/Movies/Index.vue @@ -0,0 +1,116 @@ + + + + + diff --git a/platypush/backend/http/webapp/src/components/panels/Media/Providers/Jellyfin/Index.vue b/platypush/backend/http/webapp/src/components/panels/Media/Providers/Jellyfin/Index.vue new file mode 100644 index 0000000000..fa210ab9c8 --- /dev/null +++ b/platypush/backend/http/webapp/src/components/panels/Media/Providers/Jellyfin/Index.vue @@ -0,0 +1,131 @@ + + + + + diff --git a/platypush/backend/http/webapp/src/components/panels/Media/Providers/Jellyfin/common.scss b/platypush/backend/http/webapp/src/components/panels/Media/Providers/Jellyfin/common.scss new file mode 100644 index 0000000000..574375c4ce --- /dev/null +++ b/platypush/backend/http/webapp/src/components/panels/Media/Providers/Jellyfin/common.scss @@ -0,0 +1,62 @@ +@import "@/components/panels/Media/style.scss"; + +.index { + height: 100%; + margin: 2px 0 -2px 0; + + .items { + height: 100%; + display: grid; + gap: 1em; + grid-template-columns: repeat(auto-fill, minmax(20em, 1fr)); + overflow-y: auto; + padding: 1em; + } + + .item { + width: 100%; + height: 15em; + flex: 1; + position: relative; + cursor: pointer; + + &:hover { + @extend .dim; + } + + .image { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; + + img { + width: 100%; + height: 100%; + object-fit: cover; + margin: 0; + } + + i { + font-size: 5em; + } + } + + .name { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + background-color: rgba(0, 0, 0, 0.75); + color: white; + font-size: 1.75em; + font-weight: bold; + } + } +} diff --git a/platypush/backend/http/webapp/src/components/panels/Media/Providers/meta.json b/platypush/backend/http/webapp/src/components/panels/Media/Providers/meta.json index 5b7901cbe9..6d638ebd0d 100644 --- a/platypush/backend/http/webapp/src/components/panels/Media/Providers/meta.json +++ b/platypush/backend/http/webapp/src/components/panels/Media/Providers/meta.json @@ -7,6 +7,13 @@ } }, + "Jellyfin": { + "name": "Jellyfin", + "icon": { + "url": "https://static.platypush.tech/icons/media.jellyfin-64.png" + } + }, + "YouTube": { "name": "YouTube", "icon": { diff --git a/platypush/backend/http/webapp/src/components/panels/Media/style.scss b/platypush/backend/http/webapp/src/components/panels/Media/style.scss index 9a77bfe31b..3e9186881e 100644 --- a/platypush/backend/http/webapp/src/components/panels/Media/style.scss +++ b/platypush/backend/http/webapp/src/components/panels/Media/style.scss @@ -20,6 +20,11 @@ i { font-size: 40px; } + + img { + width: 40px; + height: 40px; + } } } } diff --git a/platypush/backend/http/webapp/src/style/animations.scss b/platypush/backend/http/webapp/src/style/animations.scss index c5b5c4abe6..5f4486d96f 100644 --- a/platypush/backend/http/webapp/src/style/animations.scss +++ b/platypush/backend/http/webapp/src/style/animations.scss @@ -46,6 +46,22 @@ -webkit-animation-name: unfold; } +.dim { + animation-duration: 0.5s; + -webkit-animation-duration: 0.5s; + animation-fill-mode: both; + animation-name: dim; + -webkit-animation-name: dim; +} + +.brighten { + animation-duration: 0.5s; + -webkit-animation-duration: 0.5s; + animation-fill-mode: both; + animation-name: brighten; + -webkit-animation-name: brighten; +} + @keyframes fadeIn { 0% {opacity: 0;} 100% {opacity: 1;} @@ -79,6 +95,16 @@ 100% {transform: scale(1, 1);} } +@keyframes dim { + 0% {filter: brightness(1);} + 100% {filter: brightness(0.5);} +} + +@keyframes brighten { + 0% {filter: brightness(0.5);} + 100% {filter: brightness(1);} +} + .glow { animation-duration: 2s; -webkit-animation-duration: 2s;