diff --git a/front/packages/models/src/resources/episode.base.ts b/front/packages/models/src/resources/episode.base.ts new file mode 100644 index 00000000..b5949291 --- /dev/null +++ b/front/packages/models/src/resources/episode.base.ts @@ -0,0 +1,75 @@ +/* + * Kyoo - A portable and vast media library solution. + * Copyright (c) Kyoo. + * + * See AUTHORS.md and LICENSE file in the project root for full license information. + * + * Kyoo is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * Kyoo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Kyoo. If not, see . + */ + +import { z } from "zod"; +import { zdate } from "../utils"; +import { withImages, imageFn } from "../traits"; +import { ResourceP } from "../traits/resource"; + +export const BaseEpisodeP = withImages( + ResourceP.extend({ + /** + * The season in witch this episode is in. + */ + seasonNumber: z.number().nullable(), + /** + * The number of this episode in it's season. + */ + episodeNumber: z.number().nullable(), + /** + * The absolute number of this episode. It's an episode number that is not reset to 1 after a new + * season. + */ + absoluteNumber: z.number().nullable(), + /** + * The title of this episode. + */ + name: z.string().nullable(), + /** + * The overview of this episode. + */ + overview: z.string().nullable(), + /** + * How long is this movie? (in minutes). + */ + runtime: z.number().int(), + /** + * The release date of this episode. It can be null if unknown. + */ + releaseDate: zdate().nullable(), + /** + * The links to see a movie or an episode. + */ + links: z.object({ + /** + * The direct link to the unprocessed video (pristine quality). + */ + direct: z.string().transform(imageFn), + /** + * The link to an HLS master playlist containing all qualities available for this video. + */ + hls: z.string().transform(imageFn), + }), + }), + "episodes", +).transform((x) => ({ + ...x, + href: `/watch/${x.slug}`, +})); diff --git a/front/packages/models/src/resources/episode.ts b/front/packages/models/src/resources/episode.ts index 282db100..282bc75d 100644 --- a/front/packages/models/src/resources/episode.ts +++ b/front/packages/models/src/resources/episode.ts @@ -19,64 +19,8 @@ */ import { z } from "zod"; -import { zdate } from "../utils"; -import { withImages, imageFn } from "../traits"; -import { ResourceP } from "../traits/resource"; import { ShowP } from "./show"; - -export const BaseEpisodeP = withImages( - ResourceP.extend({ - /** - * The season in witch this episode is in. - */ - seasonNumber: z.number().nullable(), - - /** - * The number of this episode in it's season. - */ - episodeNumber: z.number().nullable(), - - /** - * The absolute number of this episode. It's an episode number that is not reset to 1 after a new - * season. - */ - absoluteNumber: z.number().nullable(), - - /** - * The title of this episode. - */ - name: z.string().nullable(), - - /** - * The overview of this episode. - */ - overview: z.string().nullable(), - - /** - * The release date of this episode. It can be null if unknown. - */ - releaseDate: zdate().nullable(), - - /** - * The links to see a movie or an episode. - */ - links: z.object({ - /** - * The direct link to the unprocessed video (pristine quality). - */ - direct: z.string().transform(imageFn), - - /** - * The link to an HLS master playlist containing all qualities available for this video. - */ - hls: z.string().transform(imageFn), - }), - }), - "episodes", -).transform((x) => ({ - ...x, - href: `/watch/${x.slug}`, -})); +import { BaseEpisodeP } from "./episode.base"; export const EpisodeP = BaseEpisodeP.and( z.object({ diff --git a/front/packages/models/src/resources/movie.ts b/front/packages/models/src/resources/movie.ts index fdd31656..024adca1 100644 --- a/front/packages/models/src/resources/movie.ts +++ b/front/packages/models/src/resources/movie.ts @@ -51,6 +51,14 @@ export const MovieP = withImages( * /** Is this movie not aired yet or finished? */ status: z.nativeEnum(Status), + /** + * How well this item is rated? (from 0 to 100). + */ + rating: z.number().int().gte(0).lte(100), + /** + * How long is this movie? (in minutes). + */ + runtime: z.number().int(), /** * The date this movie aired. It can also be null if this is unknown. */ diff --git a/front/packages/models/src/resources/news.ts b/front/packages/models/src/resources/news.ts index e7ab99ed..4dc8ef45 100644 --- a/front/packages/models/src/resources/news.ts +++ b/front/packages/models/src/resources/news.ts @@ -20,7 +20,7 @@ import { z } from "zod"; import { MovieP } from "./movie"; -import { BaseEpisodeP } from "./episode"; +import { BaseEpisodeP } from "./episode.base"; import { ResourceP } from "../traits/resource"; import { withImages } from "../traits/images"; diff --git a/front/packages/models/src/resources/show.ts b/front/packages/models/src/resources/show.ts index ee848916..66394ed8 100644 --- a/front/packages/models/src/resources/show.ts +++ b/front/packages/models/src/resources/show.ts @@ -24,6 +24,7 @@ import { withImages, ResourceP } from "../traits"; import { Genre } from "./genre"; import { SeasonP } from "./season"; import { StudioP } from "./studio"; +import { BaseEpisodeP } from "./episode.base"; /** * The enum containing show's status. @@ -61,6 +62,10 @@ export const ShowP = withImages( * Is this show airing, not aired yet or finished? */ status: z.nativeEnum(Status), + /** + * How well this item is rated? (from 0 to 100). + */ + rating: z.number().int().gte(0).lte(100), /** * The date this show started airing. It can be null if this is unknown. */ @@ -85,6 +90,10 @@ export const ShowP = withImages( * The list of seasons of this show. */ seasons: z.array(SeasonP).optional(), + /** + * The first episode of this show + */ + firstEpisode: BaseEpisodeP.optional().nullable(), }), "shows", ) @@ -100,7 +109,7 @@ export const ShowP = withImages( }) .transform((x) => ({ href: `/show/${x.slug}`, - playHref: `/watch/${x.slug}-s1e1`, + playHref: x.firstEpisode ? `/watch/${x.firstEpisode.slug}` : null, ...x, })); diff --git a/front/packages/ui/src/details/header.tsx b/front/packages/ui/src/details/header.tsx index af583539..5e6298f8 100644 --- a/front/packages/ui/src/details/header.tsx +++ b/front/packages/ui/src/details/header.tsx @@ -80,7 +80,7 @@ const TitleLine = ({ ...props }: { isLoading: boolean; - playHref?: string; + playHref?: string | null; name?: string; tagline?: string | null; date?: string | null; @@ -192,17 +192,19 @@ const TitleLine = ({ )} - + {playHref !== null && ( + + )} {trailerUrl && ( => ({ parser: ShowP, path: ["shows", slug], params: { - fields: ["studio"], + fields: ["studio", "firstEpisode"], }, }); diff --git a/front/packages/ui/src/home/header.tsx b/front/packages/ui/src/home/header.tsx index 7c1c59bb..ed3cf668 100644 --- a/front/packages/ui/src/home/header.tsx +++ b/front/packages/ui/src/home/header.tsx @@ -52,7 +52,7 @@ export const Header = ({ thumbnail: KyooImage | null; overview: string | null; tagline: string | null; - link: string; + link: string | null; infoLink: string; }>) => { const { css } = useYoshiki(); @@ -70,14 +70,16 @@ export const Header = ({ >

{name}

- + {link !== null && ( + + )} => ({ parser: LibraryItemP, path: ["items", "random"], + params: { + fields: "firstEpisode", + }, }); diff --git a/front/packages/ui/src/home/recommanded.tsx b/front/packages/ui/src/home/recommanded.tsx index 00e68c8e..c31fa5b4 100644 --- a/front/packages/ui/src/home/recommanded.tsx +++ b/front/packages/ui/src/home/recommanded.tsx @@ -66,7 +66,7 @@ export const ItemDetails = ({ genres: Genre[] | null; overview: string | null; href: string; - playHref: string; + playHref: string | null; }>) => { const { push } = useRouter(); const { css } = useYoshiki("recommanded-card"); @@ -136,13 +136,15 @@ export const ItemDetails = ({ {genres?.map((x) => )} - push(playHref ?? "")} - {...css({ fover: { self: { transform: "scale(1.2)" as any, mX: ts(0.5) } } })} - /> + {playHref !== null && ( + push(playHref ?? "")} + {...css({ fover: { self: { transform: "scale(1.2)" as any, mX: ts(0.5) } } })} + /> + )}
@@ -197,5 +199,6 @@ Recommanded.query = (): QueryIdentifier => ({ params: { sortBy: "random", limit: 6, + fields: "firstEpisode", }, });