diff --git a/src/app/results/Channel.tsx b/src/app/results/Channel.tsx index aa4d407..16b64de 100644 --- a/src/app/results/Channel.tsx +++ b/src/app/results/Channel.tsx @@ -1,3 +1,5 @@ +"use client"; + import { Component } from "@/typings/component"; import { ChannelResult as ChannelProps } from "@/client/typings/search"; diff --git a/src/app/results/Playlist.tsx b/src/app/results/Playlist.tsx new file mode 100644 index 0000000..3a91f02 --- /dev/null +++ b/src/app/results/Playlist.tsx @@ -0,0 +1,71 @@ +"use client"; + +import { PlaylistResult as PlaylistProps } from "@/client/typings/search"; +import { Component } from "@/typings/component"; +import { Card, CardBody } from "@nextui-org/card"; +import { Image } from "@nextui-org/image"; +import NextLink from "next/link"; +import NextImage from "next/image"; +import { useMemo } from "react"; +import { Link } from "@nextui-org/link"; + +export const Playlist: Component<{ data: PlaylistProps }> = ({ data }) => { + const url = `/playlist/${data.id}`; + const channelUrl = `/channel/${data.author.id}`; + + const videoSize = 200; + const aspectRatio = 16 / 9; + + const [width, height] = useMemo(() => { + return [videoSize * aspectRatio, videoSize]; + }, [videoSize]); + + return ( + + + + + + + + {data.numberOfVideos} videos + + + + + + {data.title} + + + + {data.author.name} + + + + + {data.videos && ( + + {data.videos.map((video) => { + return {video.title}; + })} + + )} + + + + + + ); +}; diff --git a/src/app/results/Search.tsx b/src/app/results/Search.tsx index b22bf7f..e3676e4 100644 --- a/src/app/results/Search.tsx +++ b/src/app/results/Search.tsx @@ -11,6 +11,7 @@ import { Container } from "@/components/Container"; import { LoadingPage } from "@/components/LoadingPage"; import { Button } from "@nextui-org/button"; import { Video } from "./Video"; +import { Playlist } from "./Playlist"; export const Search: Component = () => { const searchParams = useSearchParams(); @@ -60,8 +61,8 @@ export const Search: Component = () => { case "video": return ; - default: - break; + case "playlist": + return ; } })} diff --git a/src/app/results/Video.tsx b/src/app/results/Video.tsx index d7d117e..9667559 100644 --- a/src/app/results/Video.tsx +++ b/src/app/results/Video.tsx @@ -1,3 +1,5 @@ +"use client"; + import { Component } from "@/typings/component"; import { VideoResult as VideoProps } from "@/client/typings/search"; import { Card, CardBody } from "@nextui-org/card"; diff --git a/src/client/adapters/invidious/transformer.ts b/src/client/adapters/invidious/transformer.ts index 90c82cd..d4a389c 100644 --- a/src/client/adapters/invidious/transformer.ts +++ b/src/client/adapters/invidious/transformer.ts @@ -18,6 +18,7 @@ export default class Transformer { ): string | null { const thumbnail = thumbnails.find( (thumbnail) => + thumbnail.quality == "maxresdefault" || thumbnail.quality == "default" || thumbnail.quality == "medium" || thumbnail.quality == "middle" @@ -88,7 +89,24 @@ export default class Transformer { id: result.authorId }, id: result.playlistId, - numberOfVideos: result.videoCount + numberOfVideos: result.videoCount, + thumbnail: result.playlistThumbnail, + videos: result.videos.map((video) => { + const thumbnail = Transformer.findBestThumbnail( + video.videoThumbnails + ); + if (thumbnail === null) + throw new Error( + `Invidious: Missing thumbnail for video with id ${video.videoId}` + ); + + return { + title: video.title, + id: video.videoId, + duration: video.lengthSeconds * 1000, + thumbnail: thumbnail + }; + }) }; return playlist; diff --git a/src/client/adapters/invidious/typings/search/index.ts b/src/client/adapters/invidious/typings/search/index.ts index 18e6584..d32eed4 100644 --- a/src/client/adapters/invidious/typings/search/index.ts +++ b/src/client/adapters/invidious/typings/search/index.ts @@ -37,16 +37,18 @@ export const PlaylistResultModel = z.object({ authorUrl: z.string(), authorVerified: z.boolean(), videoCount: z.number(), - videos: z.object({ - title: z.string(), - videoId: z.string(), - lengthSeconds: z.number(), - videoThumbnails: ThumbnailModel.array() - }) + videos: z + .object({ + title: z.string(), + videoId: z.string(), + lengthSeconds: z.number(), + videoThumbnails: ThumbnailModel.array() + }) + .array() }); export const SearchModel = z - .union([VideoResultModel, ChannelResultModel, PlaylistResultModel]) + .union([PlaylistResultModel, VideoResultModel, ChannelResultModel]) .array(); type Search = z.infer; diff --git a/src/client/adapters/piped/transformer.ts b/src/client/adapters/piped/transformer.ts index b06df70..90870db 100644 --- a/src/client/adapters/piped/transformer.ts +++ b/src/client/adapters/piped/transformer.ts @@ -84,6 +84,7 @@ export default class Transformer { name: result.uploaderName, id: channelId }, + thumbnail: result.thumbnail, id: result.url, numberOfVideos: result.videos }; diff --git a/src/client/typings/search/index.ts b/src/client/typings/search/index.ts index 452ba9c..52c729f 100644 --- a/src/client/typings/search/index.ts +++ b/src/client/typings/search/index.ts @@ -21,6 +21,13 @@ export interface PlaylistResult { id: string; }; numberOfVideos: number; + thumbnail: string; + videos?: { + title: string; + id: string; + duration: number; + thumbnail: string; + }[]; } export type SearchResults = (VideoResult | ChannelResult | PlaylistResult)[]; diff --git a/src/components/Search.tsx b/src/components/Search.tsx index da1700c..34bbefe 100644 --- a/src/components/Search.tsx +++ b/src/components/Search.tsx @@ -46,7 +46,7 @@ export const Search: Component<{ initialQueryValue?: string }> = ({ return ( submit(searchQuery)}>
+ {data.numberOfVideos} videos +