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",
},
});