Parse new fields in the front

This commit is contained in:
Zoe Roux 2023-11-01 23:05:34 +01:00
parent 5489f601d2
commit f872deffb8
9 changed files with 135 additions and 89 deletions

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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}`,
}));

View File

@ -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({

View File

@ -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.
*/

View File

@ -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";

View File

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

View File

@ -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 = ({
</Skeleton>
)}
<View {...css({ flexDirection: "row" })}>
<IconFab
icon={PlayArrow}
as={Link}
href={playHref}
color={{ xs: theme.user.colors.black, md: theme.colors.black }}
{...css({
bg: theme.user.accent,
fover: { self: { bg: theme.user.accent } },
})}
{...tooltip(t("show.play"))}
/>
{playHref !== null && (
<IconFab
icon={PlayArrow}
as={Link}
href={playHref}
color={{ xs: theme.user.colors.black, md: theme.colors.black }}
{...css({
bg: theme.user.accent,
fover: { self: { bg: theme.user.accent } },
})}
{...tooltip(t("show.play"))}
/>
)}
{trailerUrl && (
<IconButton
icon={Theaters}

View File

@ -85,7 +85,7 @@ const query = (slug: string): QueryIdentifier<Show> => ({
parser: ShowP,
path: ["shows", slug],
params: {
fields: ["studio"],
fields: ["studio", "firstEpisode"],
},
});

View File

@ -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 = ({
>
<H1>{name}</H1>
<View {...css({ flexDirection: "row" })}>
<IconFab
icon={PlayArrow}
aria-label={t("show.play")}
as={Link}
href={link ?? "#"}
{...tooltip(t("show.play"))}
{...css({ marginRight: ts(1) })}
/>
{link !== null && (
<IconFab
icon={PlayArrow}
aria-label={t("show.play")}
as={Link}
href={link ?? "#"}
{...tooltip(t("show.play"))}
{...css({ marginRight: ts(1) })}
/>
)}
<IconButton
icon={Info}
aria-label={t("home.info")}
@ -96,4 +98,7 @@ export const Header = ({
Header.query = (): QueryIdentifier<LibraryItem> => ({
parser: LibraryItemP,
path: ["items", "random"],
params: {
fields: "firstEpisode",
},
});

View File

@ -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 = ({
<ScrollView horizontal>
{genres?.map((x) => <Chip key={x} label={x} {...css({ mX: ts(0.5) })} />)}
</ScrollView>
<IconFab
icon={PlayArrow}
size={20}
as={Pressable}
onPress={() => push(playHref ?? "")}
{...css({ fover: { self: { transform: "scale(1.2)" as any, mX: ts(0.5) } } })}
/>
{playHref !== null && (
<IconFab
icon={PlayArrow}
size={20}
as={Pressable}
onPress={() => push(playHref ?? "")}
{...css({ fover: { self: { transform: "scale(1.2)" as any, mX: ts(0.5) } } })}
/>
)}
</View>
</View>
</Link>
@ -197,5 +199,6 @@ Recommanded.query = (): QueryIdentifier<LibraryItem> => ({
params: {
sortBy: "random",
limit: 6,
fields: "firstEpisode",
},
});