diff --git a/front/src/models/resources/season.ts b/front/src/models/resources/season.ts deleted file mode 100644 index b1f79c49..00000000 --- a/front/src/models/resources/season.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { z } from "zod"; -import { ImagesP, ResourceP } from "../traits"; -import { zdate } from "../utils"; - -export const SeasonP = ResourceP("season").merge(ImagesP).extend({ - /** - * The name of this season. - */ - name: z.string(), - /** - * The number of this season. This can be set to 0 to indicate specials. - */ - seasonNumber: z.number(), - /** - * A quick overview of this season. - */ - overview: z.string().nullable(), - /** - * The starting air date of this season. - */ - startDate: zdate().nullable(), - /** - * The ending date of this season. - */ - endDate: zdate().nullable(), - /** - * The number of episodes available on kyoo of this season. - */ - episodesCount: z.number(), -}); - -/** - * A season of a Show. - */ -export type Season = z.infer; diff --git a/front/src/models/season.ts b/front/src/models/season.ts new file mode 100644 index 00000000..f945ebc9 --- /dev/null +++ b/front/src/models/season.ts @@ -0,0 +1,31 @@ +import { z } from "zod/v4"; +import { KImage } from "./utils/images"; +import { zdate } from "./utils/utils"; + +export const Season = z.object({ + id: z.string(), + slug: z.string(), + seasonNumber: z.number().gte(0), + name: z.string().nullable(), + description: z.string().nullable(), + entryCount: z.number(), + startAir: zdate().nullable(), + endAir: zdate().nullable(), + externalId: z.record( + z.string(), + z.object({ + serieId: z.string(), + season: z.number(), + link: z.string().nullable(), + }), + ), + + poster: KImage.nullable(), + thumbnail: KImage.nullable(), + banner: KImage.nullable(), + + createdAt: zdate(), + updatedAt: zdate(), +}); + +export type Season = z.infer; diff --git a/front/src/ui/details/index.tsx b/front/src/ui/details/index.tsx index b986d106..d7b81206 100644 --- a/front/src/ui/details/index.tsx +++ b/front/src/ui/details/index.tsx @@ -1 +1,2 @@ export { MovieDetails } from "./movie"; +export { SerieDetails } from "./serie"; diff --git a/front/src/ui/details/person.tsx b/front/src/ui/details/person.tsx deleted file mode 100644 index 5a50e1a2..00000000 --- a/front/src/ui/details/person.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 { Avatar, Link, P, Skeleton, SubP } from "@kyoo/primitives"; -import { type Stylable, useYoshiki } from "yoshiki/native"; - -export const PersonAvatar = ({ - slug, - name, - role, - poster, - isLoading, - ...props -}: { - isLoading: boolean; - slug?: string; - name?: string; - role?: string; - poster?: string | null; -} & Stylable) => { - const { css } = useYoshiki(); - - return ( - - - {isLoading ||

{name}

}
- {(isLoading || role) && ( - {isLoading || {role}} - )} - - ); -}; - -PersonAvatar.width = 300; diff --git a/front/src/ui/details/season.tsx b/front/src/ui/details/season.tsx index e3ffdf38..de44d569 100644 --- a/front/src/ui/details/season.tsx +++ b/front/src/ui/details/season.tsx @@ -1,38 +1,22 @@ -/* - * 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 { - type Episode, - EpisodeP, - type QueryIdentifier, - type Season, - SeasonP, - useInfiniteFetch, -} from "@kyoo/models"; -import { H2, HR, IconButton, Menu, P, Skeleton, tooltip, ts, usePageStyle } from "@kyoo/primitives"; import MenuIcon from "@material-symbols/svg-400/rounded/menu-fill.svg"; import type { ComponentType } from "react"; import { useTranslation } from "react-i18next"; import { View } from "react-native"; import { rem, useYoshiki } from "yoshiki/native"; -import { InfiniteFetch } from "../fetch-infinite"; +import { type Episode, type Season, useInfiniteFetch } from "~/models"; +import { + H2, + HR, + IconButton, + Menu, + P, + Skeleton, + tooltip, + ts, + usePageStyle, +} from "~/primitives"; +import type { QueryIdentifier } from "~/query"; +import { InfiniteFetch } from "~/query/fetch-infinite"; import { EpisodeLine, episodeDisplayNumber } from "./episode"; type SeasonProcessed = Season & { href: string }; @@ -63,10 +47,21 @@ export const SeasonHeader = ({ > {seasonNumber}

-

+

{name ?? t("show.season", { number: seasonNumber })}

- + {seasons ?.filter((x) => x.episodesCount > 0) .map((x) => ( @@ -90,7 +85,13 @@ SeasonHeader.Loader = () => { return ( - + { height: rem(1.5), })} /> - + @@ -110,18 +113,20 @@ SeasonHeader.Loader = () => { ); }; -SeasonHeader.query = (slug: string): QueryIdentifier => ({ - parser: SeasonP, - path: ["show", slug, "seasons"], +SeasonHeader.query = (slug: string): QueryIdentifier => ({ + parser: Season, + path: ["series", slug, "seasons"], params: { // Fetch all seasons at one, there won't be hundred of thems anyways. limit: 0, - fields: ["episodesCount"], }, infinite: { value: true, map: (seasons) => - seasons.map((x) => ({ ...x, href: `/show/${slug}?season=${x.seasonNumber}` })), + seasons.map((x) => ({ + ...x, + href: `/show/${slug}?season=${x.seasonNumber}`, + })), }, }); @@ -150,7 +155,9 @@ export const EpisodeList = ({ divider Header={Header} headerProps={headerProps} - getItemType={(item) => (!item || item.firstOfSeason ? "withHeader" : "normal")} + getItemType={(item) => + !item || item.firstOfSeason ? "withHeader" : "normal" + } contentContainerStyle={pageStyle} placeholderCount={5} Render={({ item }) => { @@ -161,7 +168,11 @@ export const EpisodeList = ({ <> {item.firstOfSeason && (sea ? ( - + ) : ( ))} diff --git a/front/src/ui/details/serie.tsx b/front/src/ui/details/serie.tsx new file mode 100644 index 00000000..d6fe6348 --- /dev/null +++ b/front/src/ui/details/serie.tsx @@ -0,0 +1,127 @@ +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Platform, View } from "react-native"; +import Svg, { Path, type SvgProps } from "react-native-svg"; +import { percent, useYoshiki } from "yoshiki/native"; +import { Container, focusReset, H2, SwitchVariant, ts } from "~/primitives"; +import { useQueryState } from "~/utils"; +import { EpisodeLine, episodeDisplayNumber } from "./episode"; +import { Header } from "./header"; +import { EpisodeList } from "./season"; + +export const SvgWave = (props: SvgProps) => { + const { css } = useYoshiki(); + const width = 612; + const height = 52.771; + + return ( + + + + + + ); +}; + +export const ShowWatchStatusCard = ({ + watchedPercent, + status, + nextEpisode, +}: ShowWatchStatus) => { + const { t } = useTranslation(); + const [focused, setFocus] = useState(false); + + if (!nextEpisode) return null; + + return ( + + {({ css }) => ( + theme.background, + backgroundColor: (theme) => theme.background, + }, + focused && { + ...focusReset, + borderColor: (theme) => theme.accent, + }, + ])} + > +

{t("show.nextUp")}

+ setFocus(true)} + onHoverOut={() => setFocus(false)} + onFocus={() => setFocus(true)} + onBlur={() => setFocus(false)} + /> +
+ )} +
+ ); +}; + +const ShowHeader = ({ children, slug, ...props }) => { + const { css, theme } = useYoshiki(); + + return ( + theme.background }, + Platform.OS === "web" && { + flexGrow: 1, + flexShrink: 1, + // @ts-ignore Web only property + overflowY: "auto" as any, + }, + ], + props, + )} + > +
+ {/* */} + {/* */} + + + {children} + + + ); + }, +) + +export const ShowDetails = () => { + const { css, theme } = useYoshiki(); + const [slug] = useQueryState("slug", undefined!); + + return ( + + + + ); +}; diff --git a/front/src/ui/details/show.tsx b/front/src/ui/details/show.tsx deleted file mode 100644 index 4e18e733..00000000 --- a/front/src/ui/details/show.tsx +++ /dev/null @@ -1,159 +0,0 @@ -/* - * 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 { - type QueryIdentifier, - type QueryPage, - type Show, - ShowP, - type ShowWatchStatus, -} from "@kyoo/models"; -import { Container, H2, SwitchVariant, focusReset, ts } from "@kyoo/primitives"; -import { forwardRef, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { Platform, View, type ViewProps } from "react-native"; -import Svg, { Path, type SvgProps } from "react-native-svg"; -import { percent, useYoshiki } from "yoshiki/native"; -import { DefaultLayout } from "../../../packages/ui/src/layoutpackages/ui/src/layout"; -import { DetailsCollections } from "./collection"; -import { EpisodeLine, episodeDisplayNumber } from "./episode"; -import { Header } from "./header"; -import { EpisodeList, SeasonHeader } from "./season"; - -export const SvgWave = (props: SvgProps) => { - const { css } = useYoshiki(); - const width = 612; - const height = 52.771; - - return ( - - - - - - ); -}; - -export const ShowWatchStatusCard = ({ watchedPercent, status, nextEpisode }: ShowWatchStatus) => { - const { t } = useTranslation(); - const [focused, setFocus] = useState(false); - - if (!nextEpisode) return null; - - return ( - - {({ css }) => ( - theme.background, - backgroundColor: (theme) => theme.background, - }, - focused && { - ...focusReset, - borderColor: (theme) => theme.accent, - }, - ])} - > -

{t("show.nextUp")}

- setFocus(true)} - onHoverOut={() => setFocus(false)} - onFocus={() => setFocus(true)} - onBlur={() => setFocus(false)} - /> -
- )} -
- ); -}; - -const ShowHeader = forwardRef(function ShowHeader( - { children, slug, ...props }, - ref, -) { - const { css, theme } = useYoshiki(); - - return ( - theme.background }, - Platform.OS === "web" && { - flexGrow: 1, - flexShrink: 1, - // @ts-ignore Web only property - overflowY: "auto" as any, - }, - ], - props, - )} - > -
- - {/* */} - - - {children} - - - ); -}); - -const query = (slug: string): QueryIdentifier => ({ - parser: ShowP, - path: ["show", slug], - params: { - fields: ["studio", "firstEpisode", "watchStatus"], - }, -}); - -export const ShowDetails: QueryPage<{ slug: string; season: string }> = ({ slug, season }) => { - const { css, theme } = useYoshiki(); - return ( - - - - ); -}; - -ShowDetails.getFetchUrls = ({ slug, season }) => [ - query(slug), - DetailsCollections.query("show", slug), - // ShowStaff.query(slug), - EpisodeList.query(slug, season), - SeasonHeader.query(slug), -]; - -ShowDetails.getLayout = { Layout: DefaultLayout, props: { transparent: true } }; diff --git a/front/src/ui/details/staff.tsx b/front/src/ui/details/staff.tsx deleted file mode 100644 index ca62ab72..00000000 --- a/front/src/ui/details/staff.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 { Person, PersonP, QueryIdentifier } from "@kyoo/models"; -// import { useTranslation } from "react-i18next"; -// import { InfiniteFetch } from "../fetch-infinite"; -// import { PersonAvatar } from "./person"; - -// export const Staff = ({ slug }: { slug: string }) => { -// const { t } = useTranslation(); -// -// return ( -// -// {(item, key) => ( -// -// )} -// -// ); -// }; -// -// Staff.query = (slug: string): QueryIdentifier => ({ -// parser: PersonP, -// path: ["show", slug, "people"], -// infinite: true, -// });