forked from platypush/platypush
[#337] Initial YouTube UI with feed support.
This commit is contained in:
parent
f425e95e7e
commit
96e69811fe
5 changed files with 276 additions and 3 deletions
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -1,12 +1,12 @@
|
|||
<template>
|
||||
<div class="media-results">
|
||||
<div class="media-results" @scroll="onScroll">
|
||||
<Loading v-if="loading" />
|
||||
<NoItems v-else-if="!results?.length" :with-shadow="false">
|
||||
No search results
|
||||
</NoItems>
|
||||
|
||||
<div class="grid" v-else>
|
||||
<Item v-for="(item, i) in results"
|
||||
<div class="grid" ref="grid" v-else>
|
||||
<Item v-for="(item, i) in visibleResults"
|
||||
:key="i"
|
||||
:item="item"
|
||||
:selected="selectedResult === i"
|
||||
|
@ -54,6 +54,37 @@ export default {
|
|||
type: Object,
|
||||
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() {
|
||||
|
|
Loading…
Reference in a new issue