mirror of
				https://github.com/TeamPiped/Piped.git
				synced 2025-10-31 20:51:55 +00:00 
			
		
		
		
	Merge pull request #1652 from TeamPiped/content-item
Implement content item component
This commit is contained in:
		
							
								
								
									
										34
									
								
								src/components/ChannelItem.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/components/ChannelItem.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| <template> | ||||
|     <div> | ||||
|         <router-link :to="props.item.url"> | ||||
|             <div class="relative"> | ||||
|                 <img class="w-full" :src="props.item.thumbnail" loading="lazy" /> | ||||
|             </div> | ||||
|             <p> | ||||
|                 <span v-text="props.item.name" /> | ||||
|                 <font-awesome-icon class="ml-1.5" v-if="props.item.verified" icon="check" /> | ||||
|             </p> | ||||
|         </router-link> | ||||
|         <p v-if="props.item.description" v-text="props.item.description" /> | ||||
|         <router-link v-if="props.item.uploaderUrl" class="link" :to="props.item.uploaderUrl"> | ||||
|             <p> | ||||
|                 <span v-text="props.item.uploader" /> | ||||
|                 <font-awesome-icon class="ml-1.5" v-if="props.item.uploaderVerified" icon="check" /> | ||||
|             </p> | ||||
|         </router-link> | ||||
|  | ||||
|         <a v-if="props.item.uploaderName" class="link" v-text="props.item.uploaderName" /> | ||||
|         <template v-if="props.item.videos >= 0"> | ||||
|             <br v-if="props.item.uploaderName" /> | ||||
|             <strong v-text="`${props.item.videos} ${$t('video.videos')}`" /> | ||||
|         </template> | ||||
|  | ||||
|         <br /> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <script setup> | ||||
| const props = defineProps({ | ||||
|     item: Object, | ||||
| }); | ||||
| </script> | ||||
| @@ -53,7 +53,7 @@ | ||||
|             <ContentItem | ||||
|                 v-for="item in contentItems" | ||||
|                 :key="item.url" | ||||
|                 :content-item="item" | ||||
|                 :item="item" | ||||
|                 height="94" | ||||
|                 width="168" | ||||
|                 hide-channel | ||||
|   | ||||
| @@ -1,52 +1,35 @@ | ||||
| <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> | ||||
|     <component :is="compName" :item="item" /> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import VideoItem from "./VideoItem.vue"; | ||||
| <script setup> | ||||
| import { defineAsyncComponent } from "vue"; | ||||
|  | ||||
| export default { | ||||
|     components: { | ||||
|         VideoItem, | ||||
|     }, | ||||
|     props: { | ||||
|         contentItem: { | ||||
|             type: Object, | ||||
|             default: () => { | ||||
|                 return {}; | ||||
|             }, | ||||
|         }, | ||||
|     }, | ||||
|     methods: { | ||||
|         shouldUseVideoItem(item) { | ||||
|             return item.title; | ||||
|         }, | ||||
|     }, | ||||
| }; | ||||
| const props = defineProps({ | ||||
|     item: Object, | ||||
| }); | ||||
|  | ||||
| const VideoItem = defineAsyncComponent(() => import("./VideoItem.vue")); | ||||
| const PlaylistItem = defineAsyncComponent(() => import("./PlaylistItem.vue")); | ||||
| const ChannelItem = defineAsyncComponent(() => import("./ChannelItem.vue")); | ||||
|  | ||||
| var compName; | ||||
|  | ||||
| switch (props.item?.type) { | ||||
|     case "stream": | ||||
|         compName = VideoItem; | ||||
|         break; | ||||
|     case "playlist": | ||||
|         compName = PlaylistItem; | ||||
|         break; | ||||
|     case "channel": | ||||
|         compName = ChannelItem; | ||||
|         break; | ||||
|     default: | ||||
|         console.error("Unexpected item type: " + props.item.type); | ||||
| } | ||||
|  | ||||
| defineExpose({ | ||||
|     compName, | ||||
| }); | ||||
| </script> | ||||
|   | ||||
| @@ -18,7 +18,7 @@ | ||||
|     <hr /> | ||||
|  | ||||
|     <div class="video-grid"> | ||||
|         <VideoItem :is-feed="true" v-for="video in videos" :key="video.url" :video="video" /> | ||||
|         <VideoItem :is-feed="true" v-for="video in videos" :key="video.url" :item="video" /> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
|   | ||||
| @@ -14,7 +14,7 @@ | ||||
|     <hr /> | ||||
|  | ||||
|     <div class="video-grid"> | ||||
|         <VideoItem v-for="video in videos" :key="video.url" :video="video" /> | ||||
|         <VideoItem v-for="video in videos" :key="video.url" :item="video" /> | ||||
|     </div> | ||||
|  | ||||
|     <br /> | ||||
|   | ||||
							
								
								
									
										34
									
								
								src/components/PlaylistItem.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/components/PlaylistItem.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| <template> | ||||
|     <div> | ||||
|         <router-link :to="props.item.url"> | ||||
|             <div class="relative"> | ||||
|                 <img class="w-full" :src="props.item.thumbnail" loading="lazy" /> | ||||
|             </div> | ||||
|             <p> | ||||
|                 <span v-text="props.item.name" /> | ||||
|                 <font-awesome-icon class="ml-1.5" v-if="props.item.verified" icon="check" /> | ||||
|             </p> | ||||
|         </router-link> | ||||
|         <p v-if="props.item.description" v-text="props.item.description" /> | ||||
|         <router-link v-if="props.item.uploaderUrl" class="link" :to="props.item.uploaderUrl"> | ||||
|             <p> | ||||
|                 <span v-text="props.item.uploader" /> | ||||
|                 <font-awesome-icon class="ml-1.5" v-if="props.item.uploaderVerified" icon="check" /> | ||||
|             </p> | ||||
|         </router-link> | ||||
|  | ||||
|         <a v-if="props.item.uploaderName" class="link" v-text="props.item.uploaderName" /> | ||||
|         <template v-if="props.item.videos >= 0"> | ||||
|             <br v-if="props.item.uploaderName" /> | ||||
|             <strong v-text="`${props.item.videos} ${$t('video.videos')}`" /> | ||||
|         </template> | ||||
|  | ||||
|         <br /> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <script setup> | ||||
| const props = defineProps({ | ||||
|     item: Object, | ||||
| }); | ||||
| </script> | ||||
| @@ -32,7 +32,7 @@ | ||||
|             <VideoItem | ||||
|                 v-for="(video, index) in playlist.relatedStreams" | ||||
|                 :key="video.url" | ||||
|                 :video="video" | ||||
|                 :item="video" | ||||
|                 :index="index" | ||||
|                 :playlist-id="$route.query.list" | ||||
|                 :admin="admin" | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
|         <VideoItem | ||||
|             v-for="(related, index) in playlist.relatedStreams" | ||||
|             :key="related.url" | ||||
|             :video="related" | ||||
|             :item="related" | ||||
|             :index="index" | ||||
|             :playlist-id="playlistId" | ||||
|             height="94" | ||||
|   | ||||
| @@ -20,7 +20,7 @@ | ||||
|  | ||||
|     <div v-if="results" class="video-grid"> | ||||
|         <template v-for="result in results.items" :key="result.url"> | ||||
|             <ContentItem :content-item="result" /> | ||||
|             <ContentItem :item="result" height="94" width="168" /> | ||||
|         </template> | ||||
|     </div> | ||||
| </template> | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
|     <hr /> | ||||
|  | ||||
|     <div class="video-grid"> | ||||
|         <VideoItem v-for="video in videos" :key="video.url" :video="video" height="118" width="210" /> | ||||
|         <VideoItem v-for="video in videos" :key="video.url" :item="video" height="118" width="210" /> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
|             :to="{ | ||||
|                 path: '/watch', | ||||
|                 query: { | ||||
|                     v: video.url.substr(-11), | ||||
|                     v: item.url.substr(-11), | ||||
|                     ...(playlistId && { list: playlistId }), | ||||
|                     ...(index >= 0 && { index: index + 1 }), | ||||
|                 }, | ||||
| @@ -12,36 +12,36 @@ | ||||
|         > | ||||
|             <img | ||||
|                 class="w-full" | ||||
|                 :src="video.thumbnail" | ||||
|                 :alt="video.title" | ||||
|                 :class="{ 'shorts-img': video.isShort }" | ||||
|                 :src="item.thumbnail" | ||||
|                 :alt="item.title" | ||||
|                 :class="{ 'shorts-img': item.isShort }" | ||||
|                 loading="lazy" | ||||
|             /> | ||||
|             <div class="relative text-sm"> | ||||
|                 <span | ||||
|                     class="thumbnail-overlay thumbnail-right" | ||||
|                     v-if="video.duration > 0" | ||||
|                     v-text="timeFormat(video.duration)" | ||||
|                     v-if="item.duration > 0" | ||||
|                     v-text="timeFormat(item.duration)" | ||||
|                 /> | ||||
|                 <!-- shorts thumbnail --> | ||||
|                 <span class="thumbnail-overlay thumbnail-left" v-if="video.isShort" v-t="'video.shorts'" /> | ||||
|                 <span class="thumbnail-overlay thumbnail-left" v-if="item.isShort" v-t="'video.shorts'" /> | ||||
|                 <span | ||||
|                     class="thumbnail-overlay thumbnail-right" | ||||
|                     v-else-if="video.duration >= 60" | ||||
|                     v-text="timeFormat(video.duration)" | ||||
|                     v-else-if="item.duration >= 60" | ||||
|                     v-text="timeFormat(item.duration)" | ||||
|                 /> | ||||
|                 <i18n-t v-else keypath="video.live" class="thumbnail-overlay thumbnail-right !bg-red-600" tag="div"> | ||||
|                     <font-awesome-icon class="w-3" :icon="['fas', 'broadcast-tower']" /> | ||||
|                 </i18n-t> | ||||
|                 <span v-if="video.watched" class="thumbnail-overlay bottom-5px left-5px" v-t="'video.watched'" /> | ||||
|                 <span v-if="item.watched" class="thumbnail-overlay bottom-5px left-5px" v-t="'video.watched'" /> | ||||
|             </div> | ||||
|  | ||||
|             <div> | ||||
|                 <p | ||||
|                     style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical" | ||||
|                     class="my-2 overflow-hidden flex link" | ||||
|                     :title="video.title" | ||||
|                     v-text="video.title" | ||||
|                     :title="item.title" | ||||
|                     v-text="item.title" | ||||
|                 /> | ||||
|             </div> | ||||
|         </router-link> | ||||
| @@ -51,14 +51,14 @@ | ||||
|                 :to="{ | ||||
|                     path: '/watch', | ||||
|                     query: { | ||||
|                         v: video.url.substr(-11), | ||||
|                         v: item.url.substr(-11), | ||||
|                         ...(playlistId && { list: playlistId }), | ||||
|                         ...(index >= 0 && { index: index + 1 }), | ||||
|                         listen: '1', | ||||
|                     }, | ||||
|                 }" | ||||
|                 :aria-label="'Listen to ' + video.title" | ||||
|                 :title="'Listen to ' + video.title" | ||||
|                 :aria-label="'Listen to ' + item.title" | ||||
|                 :title="'Listen to ' + item.title" | ||||
|             > | ||||
|                 <font-awesome-icon icon="headphones" /> | ||||
|             </router-link> | ||||
| @@ -69,20 +69,20 @@ | ||||
|                 v-if="admin" | ||||
|                 :title="$t('actions.remove_from_playlist')" | ||||
|                 ref="removeButton" | ||||
|                 @click="removeVideo(video.url.substr(-11))" | ||||
|                 @click="removeVideo(item.url.substr(-11))" | ||||
|             > | ||||
|                 <font-awesome-icon icon="circle-minus" /> | ||||
|             </button> | ||||
|             <PlaylistAddModal v-if="showModal" :video-id="video.url.substr(-11)" @close="showModal = !showModal" /> | ||||
|             <PlaylistAddModal v-if="showModal" :video-id="item.url.substr(-11)" @close="showModal = !showModal" /> | ||||
|         </div> | ||||
|  | ||||
|         <div class="flex"> | ||||
|             <router-link :to="video.uploaderUrl"> | ||||
|             <router-link :to="item.uploaderUrl"> | ||||
|                 <img | ||||
|                     v-if="video.uploaderAvatar" | ||||
|                     :src="video.uploaderAvatar" | ||||
|                     v-if="item.uploaderAvatar" | ||||
|                     :src="item.uploaderAvatar" | ||||
|                     loading="lazy" | ||||
|                     :alt="video.uploaderName" | ||||
|                     :alt="item.uploaderName" | ||||
|                     class="rounded-full mr-0.5 mt-0.5 w-32px h-32px" | ||||
|                     width="68" | ||||
|                     height="68" | ||||
| @@ -91,22 +91,22 @@ | ||||
|  | ||||
|             <div class="w-[calc(100%-32px-1rem)]"> | ||||
|                 <router-link | ||||
|                     v-if="video.uploaderUrl && video.uploaderName && !hideChannel" | ||||
|                     v-if="item.uploaderUrl && item.uploaderName && !hideChannel" | ||||
|                     class="link-secondary overflow-hidden block" | ||||
|                     :to="video.uploaderUrl" | ||||
|                     :title="video.uploaderName" | ||||
|                     :to="item.uploaderUrl" | ||||
|                     :title="item.uploaderName" | ||||
|                 > | ||||
|                     <span v-text="video.uploaderName" /> | ||||
|                     <font-awesome-icon class="ml-1.5" v-if="video.uploaderVerified" icon="check" /> | ||||
|                     <span v-text="item.uploaderName" /> | ||||
|                     <font-awesome-icon class="ml-1.5" v-if="item.uploaderVerified" icon="check" /> | ||||
|                 </router-link> | ||||
|  | ||||
|                 <strong v-if="video.views >= 0 || video.uploadedDate" class="text-sm"> | ||||
|                     <span v-if="video.views >= 0"> | ||||
|                 <strong v-if="item.views >= 0 || item.uploadedDate" class="text-sm"> | ||||
|                     <span v-if="item.views >= 0"> | ||||
|                         <font-awesome-icon icon="eye" /> | ||||
|                         <span class="pl-0.5" v-text="`${numberFormat(video.views)} •`" /> | ||||
|                         <span class="pl-0.5" v-text="`${numberFormat(item.views)} •`" /> | ||||
|                     </span> | ||||
|                     <span v-if="video.uploaded > 0" class="pl-0.5" v-text="timeAgo(video.uploaded)" /> | ||||
|                     <span v-else-if="video.uploadedDate" class="pl-0.5" v-text="video.uploadedDate" /> | ||||
|                     <span v-if="item.uploaded > 0" class="pl-0.5" v-text="timeAgo(item.uploaded)" /> | ||||
|                     <span v-else-if="item.uploadedDate" class="pl-0.5" v-text="item.uploadedDate" /> | ||||
|                 </strong> | ||||
|             </div> | ||||
|         </div> | ||||
| @@ -124,7 +124,7 @@ import PlaylistAddModal from "./PlaylistAddModal.vue"; | ||||
|  | ||||
| export default { | ||||
|     props: { | ||||
|         video: { | ||||
|         item: { | ||||
|             type: Object, | ||||
|             default: () => { | ||||
|                 return {}; | ||||
| @@ -174,7 +174,7 @@ export default { | ||||
|             if (!this.isFeed || !this.getPreferenceBoolean("hideWatched", false)) return; | ||||
|  | ||||
|             const objectStore = window.db.transaction("watch_history", "readonly").objectStore("watch_history"); | ||||
|             const request = objectStore.get(this.video.url.substr(-11)); | ||||
|             const request = objectStore.get(this.item.url.substr(-11)); | ||||
|             request.onsuccess = event => { | ||||
|                 const video = event.target.result; | ||||
|                 if (video && (video.currentTime ?? 0) > video.duration * 0.9) { | ||||
|   | ||||
| @@ -200,10 +200,10 @@ | ||||
|                 /> | ||||
|                 <hr v-show="showRecs" /> | ||||
|                 <div v-show="showRecs"> | ||||
|                     <VideoItem | ||||
|                     <ContentItem | ||||
|                         v-for="related in video.relatedStreams" | ||||
|                         :key="related.url" | ||||
|                         :video="related" | ||||
|                         :item="related" | ||||
|                         height="94" | ||||
|                         width="168" | ||||
|                     /> | ||||
| @@ -216,7 +216,7 @@ | ||||
|  | ||||
| <script> | ||||
| import VideoPlayer from "./VideoPlayer.vue"; | ||||
| import VideoItem from "./VideoItem.vue"; | ||||
| import ContentItem from "./ContentItem.vue"; | ||||
| import ErrorHandler from "./ErrorHandler.vue"; | ||||
| import CommentItem from "./CommentItem.vue"; | ||||
| import ChaptersBar from "./ChaptersBar.vue"; | ||||
| @@ -228,7 +228,7 @@ export default { | ||||
|     name: "App", | ||||
|     components: { | ||||
|         VideoPlayer, | ||||
|         VideoItem, | ||||
|         ContentItem, | ||||
|         ErrorHandler, | ||||
|         CommentItem, | ||||
|         ChaptersBar, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Kavin
					Kavin