[#337] Initial YouTube UI with feed support.

This commit is contained in:
Fabio Manganiello 2023-11-13 02:40:05 +01:00
parent f425e95e7e
commit 96e69811fe
Signed by: blacklight
GPG key ID: D90FBA7F76362774
5 changed files with 276 additions and 3 deletions

View file

@ -0,0 +1,109 @@
<template>
<div class="media-youtube-browser">
<Loading v-if="loading" />
<div class="browser" v-else>
<MediaNav :path="computedPath" @back="$emit('back')" />
<NoToken v-if="!authToken" />
<div class="body" v-else>
<Feed @play="$emit('play', $event)" v-if="selectedView === 'feed'" />
<Index @select="selectView" v-else />
</div>
</div>
</div>
</template>
<script>
import Loading from "@/components/Loading";
import MediaNav from "./Nav";
import MediaProvider from "./Mixin";
import Feed from "./YouTube/Feed";
import Index from "./YouTube/Index";
import NoToken from "./YouTube/NoToken";
export default {
mixins: [MediaProvider],
components: {
Feed,
Index,
Loading,
MediaNav,
NoToken,
},
data() {
return {
youtubeConfig: null,
selectedView: null,
path: [],
}
},
computed: {
authToken() {
return this.youtubeConfig?.auth_token
},
computedPath() {
return [
{
title: 'YouTube',
click: () => this.selectView(null),
icon: {
class: 'fab fa-youtube',
},
},
...this.path,
]
},
},
methods: {
async loadYoutubeConfig() {
this.loading = true
try {
this.youtubeConfig = (await this.request('config.get_plugins')).youtube
} finally {
this.loading = false
}
},
selectView(view) {
this.selectedView = view
if (view?.length) {
this.path = [
{
title: view.slice(0, 1).toUpperCase() + view.slice(1),
},
]
} else {
this.path = []
}
},
},
mounted() {
this.loadYoutubeConfig()
},
}
</script>
<style lang="scss" scoped>
@import "../style.scss";
.media-youtube-browser {
height: 100%;
.browser {
height: 100%;
}
.body {
height: calc(100% - $media-nav-height - 2px);
margin-top: 2px;
overflow: auto;
}
}
</style>

View file

@ -0,0 +1,65 @@
<template>
<div class="media-youtube-feed">
<Loading v-if="loading" />
<NoItems :with-shadow="false" v-else-if="!feed?.length">
No videos found.
</NoItems>
<Results :results="feed"
:sources="{'youtube': true}"
:selected-result="selectedResult"
@select="selectedResult = $event"
@play="$emit('play', $event)"
v-else />
</div>
</template>
<script>
import NoItems from "@/components/elements/NoItems";
import Loading from "@/components/Loading";
import Results from "@/components/panels/Media/Results";
import Utils from "@/Utils";
export default {
emits: ['play'],
mixins: [Utils],
components: {
Loading,
NoItems,
Results,
},
data() {
return {
feed: [],
loading: false,
selectedResult: null,
}
},
methods: {
async loadFeed() {
this.loading = true
try {
this.feed = (await this.request('youtube.get_feed')).map(item => ({
...item,
type: 'youtube',
}))
} finally {
this.loading = false
}
},
},
mounted() {
this.loadFeed()
},
}
</script>
<style lang="scss" scoped>
.media-youtube-feed {
height: 100%;
overflow: auto;
}
</style>

View file

@ -0,0 +1,30 @@
<template>
<div class="youtube-views-browser grid">
<div class="item" @click="$emit('select', 'feed')">
<div class="icon">
<i class="fas fa-rss" />
</div>
<div class="name">Feed</div>
</div>
<div class="item" @click="$emit('select', 'playlists')">
<div class="icon">
<i class="fas fa-list" />
</div>
<div class="name">Playlists</div>
</div>
<div class="item" @click="$emit('select', 'subscriptions')">
<div class="icon">
<i class="fas fa-user" />
</div>
<div class="name">Subscriptions</div>
</div>
</div>
</template>
<script>
export default {
emits: ['select'],
}
</script>

View file

@ -0,0 +1,38 @@
<template>
<div class="no-token">
<div class="title">
No <code>auth_token</code> found in the YouTube configuration.
</div>
<div class="description">
This integration requires an <code>auth_token</code> to be set in the
<code>youtube</code> section of the configuration file in order to
access your playlists and subscriptions.<br/><br/>
Piped auth tokens are currently supported. You can retrieve one through
the following procedure:
<ol>
<li>Login to your configured Piped instance.</li>
<li>Copy the RSS/Atom feed URL on the <i>Feed</i> tab.</li>
<li>Copy the <code>auth_token</code> query parameter from the URL.</li>
<li>
Enter it in the <code>auth_token</code> field in the
<code>youtube</code> section of the configuration file.
</li>
</ol>
</div>
</div>
</template>
<style lang="scss" scoped>
.no-token {
padding: 0.5em;
.title {
font-size: 1.5em;
font-weight: bold;
margin-bottom: 1em;
}
}
</style>

View file

@ -1,12 +1,12 @@
<template> <template>
<div class="media-results"> <div class="media-results" @scroll="onScroll">
<Loading v-if="loading" /> <Loading v-if="loading" />
<NoItems v-else-if="!results?.length" :with-shadow="false"> <NoItems v-else-if="!results?.length" :with-shadow="false">
No search results No search results
</NoItems> </NoItems>
<div class="grid" v-else> <div class="grid" ref="grid" v-else>
<Item v-for="(item, i) in results" <Item v-for="(item, i) in visibleResults"
:key="i" :key="i"
:item="item" :item="item"
:selected="selectedResult === i" :selected="selectedResult === i"
@ -54,6 +54,37 @@ export default {
type: Object, type: Object,
default: () => {}, default: () => {},
}, },
resultIndexStep: {
type: Number,
default: 25,
},
},
data() {
return {
maxResultIndex: this.resultIndexStep,
}
},
computed: {
visibleResults() {
return this.results.slice(0, this.maxResultIndex)
},
},
methods: {
onScroll(e) {
const el = e.target
if (!el)
return
const bottom = (el.scrollHeight - el.scrollTop) <= el.clientHeight + 150
if (!bottom)
return
this.maxResultIndex += this.resultIndexStep
},
}, },
mounted() { mounted() {