feat: improve playlists display aside

This commit is contained in:
nieve 2024-08-26 14:55:38 -04:00 committed by Bnyro
parent fa1ee969a7
commit 5266b2d1d9
4 changed files with 399 additions and 265 deletions

@ -1,24 +1,90 @@
<template>
<div ref="scrollable" class="h-screen-sm overflow-x-scroll">
<VideoItem
<div>
<router-link :to="{ path: '/playlist', query: { list: playlistId } }"
><h1 class="font-bold !text-lg hover:underline" v-text="playlist.name"
/></router-link>
<span class="text-sm">
<template v-if="playlist.uploader">
<router-link class="link-secondary" :to="playlist.uploaderUrl" :title="playlist.uploader">
{{ playlist.uploader }}
</router-link>
-
</template>
{{ selectedIndex }} / {{ playlist.videos }}
</span>
</div>
<div ref="scrollable" class="mt-4 max-h-screen-sm overflow-y-auto">
<div
v-for="(related, index) in playlist.relatedStreams"
:key="related.url"
:item="related"
:index="index"
:playlist-id="playlistId"
:prefer-listen="preferListen"
height="94"
width="168"
>
<router-link
class="flex hover:bg-gray-50 .dark:hover:bg-neutral-800"
:class="{ 'bg-gray-200 .dark:bg-neutral-700': index === selectedIndex - 1 }"
:to="{
path: '/watch',
query: {
v: related.url.substr(-11),
...(playlistId && { list: playlistId }),
...(index >= 0 && { index: index + 1 }),
...(preferListen && { listen: 1 }),
},
}"
>
<span class="min-w-5 flex-none text-xs" v-text="index + 1" />
<div class="w-24 flex-none pb-1">
<VideoThumbnail :item="related" />
<div class="relative text-xs">
<!-- shorts thumbnail -->
<span v-if="related.isShort" v-t="'video.shorts'" class="thumbnail-overlay thumbnail-left" />
<span
v-if="related.duration > 0 || (related.duration == 0 && related.isShort)"
class="thumbnail-overlay thumbnail-right px-0.5"
v-text="timeFormat(related.duration)"
/>
<i18n-t
v-else
keypath="video.live"
class="thumbnail-overlay thumbnail-right !bg-red-600"
tag="div"
>
<i class="i-fa6-solid:tower-broadcast w-3" />
</i18n-t>
<span
v-if="related.watched"
v-t="'video.watched'"
class="thumbnail-overlay bottom-5px left-5px px-0.5"
/>
</div>
</div>
<div class="ml-2 flex flex-col">
<span class="text-sm font-semibold leading-4" :title="related.title" v-text="related.title" />
<div class="flex-1">
<router-link
v-if="related.uploaderUrl && related.uploaderName && !hideChannel"
class="link-secondary block overflow-hidden text-xs"
:to="related.uploaderUrl"
:title="related.uploaderName"
>
<span v-text="related.uploaderName" />
<i v-if="related.uploaderVerified" class="i-fa6-solid:check ml-1.5" />
</router-link>
</div>
</div>
</router-link>
</div>
</div>
</template>
<script>
import { nextTick } from "vue";
import VideoItem from "./VideoItem.vue";
import VideoThumbnail from "./VideoThumbnail.vue";
export default {
components: { VideoItem },
components: { VideoThumbnail },
props: {
playlist: {
type: Object,

@ -12,35 +12,14 @@
},
}"
>
<div class="w-full">
<img
loading="lazy"
class="aspect-video w-full rounded-md object-contain"
:src="thumbnail"
:alt="title"
:class="{ 'shorts-img': item.isShort, 'opacity-75': item.watched }"
/>
<!-- progress bar -->
<div class="relative h-1 w-full">
<div
v-if="item.watched && item.duration > 0"
class="absolute bottom-0 left-0 h-1 bg-red-600"
:style="{ width: `clamp(0%, ${(item.currentTime / item.duration) * 100}%, 100%` }"
/>
</div>
</div>
<VideoThumbnail :item="item" />
<div class="relative text-sm">
<span
v-if="item.duration > 0"
class="thumbnail-overlay thumbnail-right"
v-text="timeFormat(item.duration)"
/>
<!-- shorts thumbnail -->
<span v-if="item.isShort" v-t="'video.shorts'" class="thumbnail-overlay thumbnail-left" />
<span
v-else-if="item.duration >= 0"
class="thumbnail-overlay thumbnail-right"
v-if="item.duration > 0 || (item.duration == 0 && item.isShort)"
class="thumbnail-overlay thumbnail-right px-0.5"
v-text="timeFormat(item.duration)"
/>
<i18n-t v-else keypath="video.live" class="thumbnail-overlay thumbnail-right !bg-red-600" tag="div">
@ -166,9 +145,10 @@
import PlaylistAddModal from "./PlaylistAddModal.vue";
import ShareModal from "./ShareModal.vue";
import ConfirmModal from "./ConfirmModal.vue";
import VideoThumbnail from "./VideoThumbnail.vue";
export default {
components: { PlaylistAddModal, ConfirmModal, ShareModal },
components: { PlaylistAddModal, ConfirmModal, ShareModal, VideoThumbnail },
props: {
item: {
type: Object,
@ -268,10 +248,6 @@ export default {
</script>
<style>
.shorts-img {
@apply w-full object-contain;
}
.video-info {
@apply mt-1 text-xs text-gray-600 font-normal;
}

@ -0,0 +1,45 @@
<template>
<div class="w-full">
<img
loading="lazy"
class="aspect-video w-full rounded-md object-contain"
:src="item.thumbnail"
:alt="item.title"
:class="{ 'shorts-img': item.isShort, 'opacity-75': item.watched }"
/>
<!-- progress bar -->
<div class="relative h-1 w-full">
<div
v-if="item.watched && item.duration > 0"
class="absolute bottom-0 left-0 h-1 bg-red-600"
:style="{ width: `clamp(0%, ${(item.currentTime / item.duration) * 100}%, 100%` }"
/>
</div>
</div>
</template>
<script>
export default {
props: {
item: {
type: Object,
default: () => {
return {};
},
},
},
computed: {
title() {
return this.item.dearrow?.titles[0]?.title ?? this.item.title;
},
thumbnail() {
return this.item.dearrow?.thumbnails[0]?.thumbnail ?? this.item.thumbnail;
},
},
};
</script>
<style>
.shorts-img {
@apply w-full object-contain;
}
</style>

@ -17,9 +17,9 @@
<i18n-t keypath="info.next_video_countdown">{{ counter }}</i18n-t>
</ToastComponent>
</Transition>
<div class="flex gap-5 max-lg:flex-col">
<div class="flex-auto">
<div v-show="!video.error">
<div :class="isMobile ? 'flex-col' : 'flex'">
<keep-alive>
<VideoPlayer
ref="videoPlayer"
@ -32,6 +32,7 @@
@navigate-next="navigateNext"
/>
</keep-alive>
<div v-if="video && isMobile">
<ChaptersBar
v-if="video?.chapters?.length > 0 && showChapters"
:mobile-layout="isMobile"
@ -39,6 +40,13 @@
:player-position="currentTime"
@seek="navigate"
/>
<PlaylistVideos
v-if="playlist"
:playlist-id="playlistId"
:playlist="playlist"
:selected-index="index"
:prefer-listen="isListening"
/>
</div>
<!-- video title -->
<div class="mt-2 break-words text-2xl font-bold" v-text="video.title" />
@ -58,7 +66,10 @@
</div>
<div class="flex items-center">
<div class="i-fa6-solid:thumbs-down" />
<strong class="ml-1" v-text="video.dislikes >= 0 ? addCommas(video.dislikes) : '?'" />
<strong
class="ml-1"
v-text="video.dislikes >= 0 ? addCommas(video.dislikes) : '?'"
/>
</div>
</template>
<template v-if="video.likes < 0">
@ -150,7 +161,10 @@
:title="(isListening ? 'Watch ' : 'Listen to ') + video.title"
class="btn flex items-center"
>
<i :class="isListening ? 'i-fa6-solid:tv' : 'i-fa6-solid:headphones'" class="mx-1.5" />
<i
:class="isListening ? 'i-fa6-solid:tv' : 'i-fa6-solid:headphones'"
class="mx-1.5"
/>
</router-link>
</div>
</div>
@ -209,7 +223,13 @@
<hr />
<label for="chkAutoLoop"><strong v-text="`${$t('actions.loop_this_video')}:`" /></label>
<input id="chkAutoLoop" v-model="selectedAutoLoop" class="ml-1.5" type="checkbox" @change="onChange($event)" />
<input
id="chkAutoLoop"
v-model="selectedAutoLoop"
class="ml-1.5"
type="checkbox"
@change="onChange($event)"
/>
<br />
<label for="chkAutoPlay"><strong v-text="`${$t('actions.auto_play_next_video')}:`" /></label>
<select id="chkAutoPlay" v-model="selectedAutoPlay" class="select ml-1.5" @change="onChange($event)">
@ -219,9 +239,29 @@
</select>
<hr />
<div v-if="isMobile">
<a
v-t="`actions.${showRecs ? 'minimize_recommendations' : 'show_recommendations'}`"
class="btn mb-2"
@click="showRecs = !showRecs"
/>
<hr v-show="showRecs" />
<div v-show="showRecs">
<ContentItem
v-for="related in video.relatedStreams"
:key="related.url"
:item="related"
:prefer-listen="isListening"
class="mb-4"
height="94"
width="168"
/>
</div>
<hr class="" />
</div>
<div class="grid grid-cols-1 sm:grid-cols-4 xl:grid-cols-5">
<div class="sm:col-span-3 xl:col-span-4">
<div class="flex flex-col">
<div class="">
<button
v-if="!comments?.disabled"
class="btn mb-2"
@ -233,14 +273,14 @@
"
/>
</div>
<div v-if="!showComments" class="sm:col-span-3 xl:col-span-4"></div>
<div v-else-if="!comments" class="sm:col-span-3 xl:col-span-4">
<div v-if="!showComments" class=""></div>
<div v-else-if="!comments" class="">
<p v-t="'comment.loading'" class="mt-8 text-center"></p>
</div>
<div v-else-if="comments.disabled" class="sm:col-span-3 xl:col-span-4">
<div v-else-if="comments.disabled" class="">
<p v-t="'comment.disabled'" class="mt-8 text-center"></p>
</div>
<div v-else ref="comments" class="sm:col-span-3 xl:col-span-4">
<div v-else ref="comments" class="">
<CommentItem
v-for="comment in comments.comments"
:key="comment.commentId"
@ -250,8 +290,16 @@
:video-id="getVideoId()"
/>
</div>
<div v-if="video" class="order-first sm:order-last">
</div>
</div>
<div v-if="video && !isMobile" class="w-96 flex-none">
<ChaptersBar
v-if="video?.chapters?.length > 0 && showChapters"
:mobile-layout="isMobile"
:chapters="video.chapters"
:player-position="currentTime"
@seek="navigate"
/>
<PlaylistVideos
v-if="playlist"
:playlist-id="playlistId"
@ -276,7 +324,6 @@
width="168"
/>
</div>
<hr class="sm:hidden" />
</div>
</div>
</LoadingIndicatorPage>