mirror of
https://github.com/TeamPiped/Piped.git
synced 2024-12-23 14:03:35 +00:00
Merge pull request #1651 from Bnyro/channeltabs
Support for channel tabs
This commit is contained in:
commit
f5d4da7c2b
@ -35,13 +35,25 @@
|
||||
<font-awesome-icon icon="rss" />
|
||||
</a>
|
||||
|
||||
<div class="flex mt-4 mb-2">
|
||||
<button
|
||||
v-for="(tab, index) in tabs"
|
||||
:key="tab.name"
|
||||
class="btn mr-2"
|
||||
@click="loadTab(index)"
|
||||
:class="{ active: selectedTab == index }"
|
||||
>
|
||||
<span v-text="tab.translatedName"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="video-grid">
|
||||
<VideoItem
|
||||
v-for="video in channel.relatedStreams"
|
||||
:key="video.url"
|
||||
:video="video"
|
||||
<ContentItem
|
||||
v-for="item in contentItems"
|
||||
:key="item.url"
|
||||
:content-item="item"
|
||||
height="94"
|
||||
width="168"
|
||||
hide-channel
|
||||
@ -52,17 +64,21 @@
|
||||
|
||||
<script>
|
||||
import ErrorHandler from "./ErrorHandler.vue";
|
||||
import VideoItem from "./VideoItem.vue";
|
||||
import ContentItem from "./ContentItem.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ErrorHandler,
|
||||
VideoItem,
|
||||
ContentItem,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
channel: null,
|
||||
subscribed: false,
|
||||
tabs: [],
|
||||
selectedTab: 0,
|
||||
contentItems: [],
|
||||
tabNextPage: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
@ -111,8 +127,17 @@ export default {
|
||||
.then(() => {
|
||||
if (!this.channel.error) {
|
||||
document.title = this.channel.name + " - Piped";
|
||||
this.contentItems = this.channel.relatedStreams;
|
||||
this.fetchSubscribedStatus();
|
||||
this.updateWatched(this.channel.relatedStreams);
|
||||
this.tabs.push({
|
||||
translatedName: this.$t("video.videos"),
|
||||
});
|
||||
for (let i = 0; i < this.channel.tabs.length; i++) {
|
||||
let tab = this.channel.tabs[i];
|
||||
tab.translatedName = this.getTranslatedTabName(tab.name);
|
||||
this.tabs.push(tab);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -120,16 +145,33 @@ export default {
|
||||
if (this.loading || !this.channel || !this.channel.nextpage) return;
|
||||
if (window.innerHeight + window.scrollY >= document.body.offsetHeight - window.innerHeight) {
|
||||
this.loading = true;
|
||||
this.fetchJson(this.apiUrl() + "/nextpage/channel/" + this.channel.id, {
|
||||
nextpage: this.channel.nextpage,
|
||||
}).then(json => {
|
||||
this.channel.nextpage = json.nextpage;
|
||||
this.loading = false;
|
||||
this.updateWatched(json.relatedStreams);
|
||||
json.relatedStreams.map(stream => this.channel.relatedStreams.push(stream));
|
||||
});
|
||||
if (this.selectedTab == 0) {
|
||||
this.fetchChannelNextPage();
|
||||
} else {
|
||||
this.fetchChannelTabNextPage();
|
||||
}
|
||||
}
|
||||
},
|
||||
fetchChannelNextPage() {
|
||||
this.fetchJson(this.apiUrl() + "/nextpage/channel/" + this.channel.id, {
|
||||
nextpage: this.channel.nextpage,
|
||||
}).then(json => {
|
||||
this.channel.nextpage = json.nextpage;
|
||||
this.loading = false;
|
||||
this.updateWatched(json.relatedStreams);
|
||||
json.relatedStreams.map(stream => this.contentItems.push(stream));
|
||||
});
|
||||
},
|
||||
fetchChannelTabNextPage() {
|
||||
this.fetchJson(this.apiUrl() + "/channels/tabs", {
|
||||
data: this.tabs[this.selectedTab].data,
|
||||
nextpage: this.tabNextPage,
|
||||
}).then(json => {
|
||||
this.tabNextPage = json.nextpage;
|
||||
this.loading = false;
|
||||
json.content.map(item => this.contentItems.push(item));
|
||||
});
|
||||
},
|
||||
subscribeHandler() {
|
||||
if (this.authenticated) {
|
||||
this.fetchJson(this.authApiUrl() + (this.subscribed ? "/unsubscribe" : "/subscribe"), null, {
|
||||
@ -147,6 +189,46 @@ export default {
|
||||
}
|
||||
this.subscribed = !this.subscribed;
|
||||
},
|
||||
getTranslatedTabName(tabName) {
|
||||
let translatedTabName = tabName;
|
||||
switch (tabName) {
|
||||
case "Livestreams":
|
||||
translatedTabName = this.$t("titles.livestreams");
|
||||
break;
|
||||
case "Playlists":
|
||||
translatedTabName = this.$t("titles.playlists");
|
||||
break;
|
||||
case "Channels":
|
||||
translatedTabName = this.$t("titles.channels");
|
||||
break;
|
||||
case "Shorts":
|
||||
translatedTabName = this.$t("video.shorts");
|
||||
break;
|
||||
default:
|
||||
console.error(`Tab name "${tabName}" is not translated yet!`);
|
||||
break;
|
||||
}
|
||||
return translatedTabName;
|
||||
},
|
||||
loadTab(index) {
|
||||
this.selectedTab = index;
|
||||
if (index == 0) {
|
||||
this.contentItems = this.channel.relatedStreams;
|
||||
return;
|
||||
}
|
||||
this.fetchJson(this.apiUrl() + "/channels/tabs", {
|
||||
data: this.tabs[index].data,
|
||||
}).then(tab => {
|
||||
this.contentItems = tab.content;
|
||||
this.tabNextPage = tab.nextpage;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.active {
|
||||
border: 0.1rem outset red;
|
||||
}
|
||||
</style>
|
||||
|
52
src/components/ContentItem.vue
Normal file
52
src/components/ContentItem.vue
Normal file
@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<VideoItem v-if="shouldUseVideoItem(contentItem)" :video="contentItem" height="94" width="168" />
|
||||
<div v-else>
|
||||
<router-link :to="contentItem.url">
|
||||
<div class="relative">
|
||||
<img class="w-full" :src="contentItem.thumbnail" loading="lazy" />
|
||||
</div>
|
||||
<p>
|
||||
<span v-text="contentItem.name" />
|
||||
<font-awesome-icon class="ml-1.5" v-if="contentItem.verified" icon="check" />
|
||||
</p>
|
||||
</router-link>
|
||||
<p v-if="contentItem.description" v-text="contentItem.description" />
|
||||
<router-link v-if="contentItem.uploaderUrl" class="link" :to="contentItem.uploaderUrl">
|
||||
<p>
|
||||
<span v-text="contentItem.uploader" />
|
||||
<font-awesome-icon class="ml-1.5" v-if="contentItem.uploaderVerified" icon="check" />
|
||||
</p>
|
||||
</router-link>
|
||||
|
||||
<a v-if="contentItem.uploaderName" class="link" v-text="contentItem.uploaderName" />
|
||||
<template v-if="contentItem.videos >= 0">
|
||||
<br v-if="contentItem.uploaderName" />
|
||||
<strong v-text="`${contentItem.videos} ${$t('video.videos')}`" />
|
||||
</template>
|
||||
|
||||
<br />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import VideoItem from "./VideoItem.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
VideoItem,
|
||||
},
|
||||
props: {
|
||||
contentItem: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
shouldUseVideoItem(item) {
|
||||
return item.title;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
@ -20,43 +20,17 @@
|
||||
|
||||
<div v-if="results" class="video-grid">
|
||||
<template v-for="result in results.items" :key="result.url">
|
||||
<VideoItem v-if="shouldUseVideoItem(result)" :video="result" height="94" width="168" />
|
||||
<div v-if="!shouldUseVideoItem(result)">
|
||||
<router-link :to="result.url">
|
||||
<div class="relative">
|
||||
<img class="w-full" :src="result.thumbnail" loading="lazy" />
|
||||
</div>
|
||||
<p>
|
||||
<span v-text="result.name" />
|
||||
<font-awesome-icon class="ml-1.5" v-if="result.verified" icon="check" />
|
||||
</p>
|
||||
</router-link>
|
||||
<p v-if="result.description" v-text="result.description" />
|
||||
<router-link v-if="result.uploaderUrl" class="link" :to="result.uploaderUrl">
|
||||
<p>
|
||||
<span v-text="result.uploader" />
|
||||
<font-awesome-icon class="ml-1.5" v-if="result.uploaderVerified" icon="check" />
|
||||
</p>
|
||||
</router-link>
|
||||
|
||||
<a v-if="result.uploaderName" class="link" v-text="result.uploaderName" />
|
||||
<template v-if="result.videos >= 0">
|
||||
<br v-if="result.uploaderName" />
|
||||
<strong v-text="`${result.videos} ${$t('video.videos')}`" />
|
||||
</template>
|
||||
|
||||
<br />
|
||||
</div>
|
||||
<ContentItem :content-item="result" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import VideoItem from "./VideoItem.vue";
|
||||
import ContentItem from "./ContentItem.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
VideoItem,
|
||||
ContentItem,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -124,9 +98,6 @@ export default {
|
||||
});
|
||||
}
|
||||
},
|
||||
shouldUseVideoItem(item) {
|
||||
return item.title;
|
||||
},
|
||||
handleRedirect() {
|
||||
const query = this.$route.query.search_query;
|
||||
const url =
|
||||
|
@ -10,7 +10,9 @@
|
||||
"playlists": "Playlists",
|
||||
"account": "Account",
|
||||
"instance": "Instance",
|
||||
"player": "Player"
|
||||
"player": "Player",
|
||||
"livestreams": "Livestreams",
|
||||
"channels": "Channels"
|
||||
},
|
||||
"player": {
|
||||
"watch_on": "Watch on {0}"
|
||||
|
Loading…
Reference in New Issue
Block a user