mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-31 14:33:50 -04:00
wip: Series details
This commit is contained in:
parent
e1059aceed
commit
8f80d6c96d
@ -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<typeof SeasonP>;
|
|
31
front/src/models/season.ts
Normal file
31
front/src/models/season.ts
Normal file
@ -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<typeof Season>;
|
@ -1 +1,2 @@
|
|||||||
export { MovieDetails } from "./movie";
|
export { MovieDetails } from "./movie";
|
||||||
|
export { SerieDetails } from "./serie";
|
||||||
|
@ -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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<Link href={slug ? `/person/${slug}` : ""} {...props}>
|
|
||||||
<Avatar src={poster} alt={name} size={PersonAvatar.width} fill />
|
|
||||||
<Skeleton>{isLoading || <P {...css({ textAlign: "center" })}>{name}</P>}</Skeleton>
|
|
||||||
{(isLoading || role) && (
|
|
||||||
<Skeleton>{isLoading || <SubP {...css({ textAlign: "center" })}>{role}</SubP>}</Skeleton>
|
|
||||||
)}
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
PersonAvatar.width = 300;
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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 MenuIcon from "@material-symbols/svg-400/rounded/menu-fill.svg";
|
||||||
import type { ComponentType } from "react";
|
import type { ComponentType } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import { rem, useYoshiki } from "yoshiki/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";
|
import { EpisodeLine, episodeDisplayNumber } from "./episode";
|
||||||
|
|
||||||
type SeasonProcessed = Season & { href: string };
|
type SeasonProcessed = Season & { href: string };
|
||||||
@ -63,10 +47,21 @@ export const SeasonHeader = ({
|
|||||||
>
|
>
|
||||||
{seasonNumber}
|
{seasonNumber}
|
||||||
</P>
|
</P>
|
||||||
<H2 {...css({ marginX: ts(1), fontSize: rem(1.5), flexGrow: 1, flexShrink: 1 })}>
|
<H2
|
||||||
|
{...css({
|
||||||
|
marginX: ts(1),
|
||||||
|
fontSize: rem(1.5),
|
||||||
|
flexGrow: 1,
|
||||||
|
flexShrink: 1,
|
||||||
|
})}
|
||||||
|
>
|
||||||
{name ?? t("show.season", { number: seasonNumber })}
|
{name ?? t("show.season", { number: seasonNumber })}
|
||||||
</H2>
|
</H2>
|
||||||
<Menu Trigger={IconButton} icon={MenuIcon} {...tooltip(t("show.jumpToSeason"))}>
|
<Menu
|
||||||
|
Trigger={IconButton}
|
||||||
|
icon={MenuIcon}
|
||||||
|
{...tooltip(t("show.jumpToSeason"))}
|
||||||
|
>
|
||||||
{seasons
|
{seasons
|
||||||
?.filter((x) => x.episodesCount > 0)
|
?.filter((x) => x.episodesCount > 0)
|
||||||
.map((x) => (
|
.map((x) => (
|
||||||
@ -90,7 +85,13 @@ SeasonHeader.Loader = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
<View {...css({ flexDirection: "row", marginX: ts(1), justifyContent: "space-between" })}>
|
<View
|
||||||
|
{...css({
|
||||||
|
flexDirection: "row",
|
||||||
|
marginX: ts(1),
|
||||||
|
justifyContent: "space-between",
|
||||||
|
})}
|
||||||
|
>
|
||||||
<View {...css({ flexDirection: "row", alignItems: "center" })}>
|
<View {...css({ flexDirection: "row", alignItems: "center" })}>
|
||||||
<Skeleton
|
<Skeleton
|
||||||
variant="custom"
|
variant="custom"
|
||||||
@ -101,7 +102,9 @@ SeasonHeader.Loader = () => {
|
|||||||
height: rem(1.5),
|
height: rem(1.5),
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
<Skeleton {...css({ marginX: ts(1), width: rem(12), height: rem(2) })} />
|
<Skeleton
|
||||||
|
{...css({ marginX: ts(1), width: rem(12), height: rem(2) })}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
<IconButton icon={MenuIcon} disabled />
|
<IconButton icon={MenuIcon} disabled />
|
||||||
</View>
|
</View>
|
||||||
@ -110,18 +113,20 @@ SeasonHeader.Loader = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
SeasonHeader.query = (slug: string): QueryIdentifier<Season, SeasonProcessed> => ({
|
SeasonHeader.query = (slug: string): QueryIdentifier<Season> => ({
|
||||||
parser: SeasonP,
|
parser: Season,
|
||||||
path: ["show", slug, "seasons"],
|
path: ["series", slug, "seasons"],
|
||||||
params: {
|
params: {
|
||||||
// Fetch all seasons at one, there won't be hundred of thems anyways.
|
// Fetch all seasons at one, there won't be hundred of thems anyways.
|
||||||
limit: 0,
|
limit: 0,
|
||||||
fields: ["episodesCount"],
|
|
||||||
},
|
},
|
||||||
infinite: {
|
infinite: {
|
||||||
value: true,
|
value: true,
|
||||||
map: (seasons) =>
|
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 = <Props,>({
|
|||||||
divider
|
divider
|
||||||
Header={Header}
|
Header={Header}
|
||||||
headerProps={headerProps}
|
headerProps={headerProps}
|
||||||
getItemType={(item) => (!item || item.firstOfSeason ? "withHeader" : "normal")}
|
getItemType={(item) =>
|
||||||
|
!item || item.firstOfSeason ? "withHeader" : "normal"
|
||||||
|
}
|
||||||
contentContainerStyle={pageStyle}
|
contentContainerStyle={pageStyle}
|
||||||
placeholderCount={5}
|
placeholderCount={5}
|
||||||
Render={({ item }) => {
|
Render={({ item }) => {
|
||||||
@ -161,7 +168,11 @@ export const EpisodeList = <Props,>({
|
|||||||
<>
|
<>
|
||||||
{item.firstOfSeason &&
|
{item.firstOfSeason &&
|
||||||
(sea ? (
|
(sea ? (
|
||||||
<SeasonHeader name={sea.name} seasonNumber={sea.seasonNumber} seasons={seasons} />
|
<SeasonHeader
|
||||||
|
name={sea.name}
|
||||||
|
seasonNumber={sea.seasonNumber}
|
||||||
|
seasons={seasons}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<SeasonHeader.Loader />
|
<SeasonHeader.Loader />
|
||||||
))}
|
))}
|
||||||
|
127
front/src/ui/details/serie.tsx
Normal file
127
front/src/ui/details/serie.tsx
Normal file
@ -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 (
|
||||||
|
<View {...css({ width: percent(100), aspectRatio: width / height })}>
|
||||||
|
<Svg
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
viewBox="0 372.979 612 52.771"
|
||||||
|
fill="black"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<Path d="M0,375.175c68,-5.1,136,-0.85,204,7.948c68,9.052,136,22.652,204,24.777s136,-8.075,170,-12.878l34,-4.973v35.7h-612" />
|
||||||
|
</Svg>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ShowWatchStatusCard = ({
|
||||||
|
watchedPercent,
|
||||||
|
status,
|
||||||
|
nextEpisode,
|
||||||
|
}: ShowWatchStatus) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [focused, setFocus] = useState(false);
|
||||||
|
|
||||||
|
if (!nextEpisode) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SwitchVariant>
|
||||||
|
{({ css }) => (
|
||||||
|
<Container
|
||||||
|
{...css([
|
||||||
|
{
|
||||||
|
marginY: ts(2),
|
||||||
|
borderRadius: 16,
|
||||||
|
overflow: "hidden",
|
||||||
|
borderWidth: ts(0.5),
|
||||||
|
borderStyle: "solid",
|
||||||
|
borderColor: (theme) => theme.background,
|
||||||
|
backgroundColor: (theme) => theme.background,
|
||||||
|
},
|
||||||
|
focused && {
|
||||||
|
...focusReset,
|
||||||
|
borderColor: (theme) => theme.accent,
|
||||||
|
},
|
||||||
|
])}
|
||||||
|
>
|
||||||
|
<H2 {...css({ marginLeft: ts(2) })}>{t("show.nextUp")}</H2>
|
||||||
|
<EpisodeLine
|
||||||
|
{...nextEpisode}
|
||||||
|
showSlug={null}
|
||||||
|
watchedPercent={watchedPercent || null}
|
||||||
|
watchedStatus={status || null}
|
||||||
|
displayNumber={episodeDisplayNumber(nextEpisode)}
|
||||||
|
onHoverIn={() => setFocus(true)}
|
||||||
|
onHoverOut={() => setFocus(false)}
|
||||||
|
onFocus={() => setFocus(true)}
|
||||||
|
onBlur={() => setFocus(false)}
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
)}
|
||||||
|
</SwitchVariant>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ShowHeader = ({ children, slug, ...props }) => {
|
||||||
|
const { css, theme } = useYoshiki();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
{...css(
|
||||||
|
[
|
||||||
|
{ bg: (theme) => theme.background },
|
||||||
|
Platform.OS === "web" && {
|
||||||
|
flexGrow: 1,
|
||||||
|
flexShrink: 1,
|
||||||
|
// @ts-ignore Web only property
|
||||||
|
overflowY: "auto" as any,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
props,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Header kind="serie" slug={slug} />
|
||||||
|
{/* <DetailsCollections type="serie" slug={slug} /> */}
|
||||||
|
{/* <Staff slug={slug} /> */}
|
||||||
|
<SvgWave
|
||||||
|
fill={theme.variant.background}
|
||||||
|
{...css({ flexShrink: 0, flexGrow: 1, display: "flex" })}
|
||||||
|
/>
|
||||||
|
<View {...css({ bg: theme.variant.background })}>
|
||||||
|
<Container>{children}</Container>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export const ShowDetails = () => {
|
||||||
|
const { css, theme } = useYoshiki();
|
||||||
|
const [slug] = useQueryState("slug", undefined!);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View {...css({ bg: theme.variant.background, flex: 1 })}>
|
||||||
|
<EpisodeList
|
||||||
|
slug={slug}
|
||||||
|
season={season}
|
||||||
|
Header={ShowHeader}
|
||||||
|
headerProps={{ slug }}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
@ -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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<View {...css({ width: percent(100), aspectRatio: width / height })}>
|
|
||||||
<Svg width="100%" height="100%" viewBox="0 372.979 612 52.771" fill="black" {...props}>
|
|
||||||
<Path d="M0,375.175c68,-5.1,136,-0.85,204,7.948c68,9.052,136,22.652,204,24.777s136,-8.075,170,-12.878l34,-4.973v35.7h-612" />
|
|
||||||
</Svg>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ShowWatchStatusCard = ({ watchedPercent, status, nextEpisode }: ShowWatchStatus) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [focused, setFocus] = useState(false);
|
|
||||||
|
|
||||||
if (!nextEpisode) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SwitchVariant>
|
|
||||||
{({ css }) => (
|
|
||||||
<Container
|
|
||||||
{...css([
|
|
||||||
{
|
|
||||||
marginY: ts(2),
|
|
||||||
borderRadius: 16,
|
|
||||||
overflow: "hidden",
|
|
||||||
borderWidth: ts(0.5),
|
|
||||||
borderStyle: "solid",
|
|
||||||
borderColor: (theme) => theme.background,
|
|
||||||
backgroundColor: (theme) => theme.background,
|
|
||||||
},
|
|
||||||
focused && {
|
|
||||||
...focusReset,
|
|
||||||
borderColor: (theme) => theme.accent,
|
|
||||||
},
|
|
||||||
])}
|
|
||||||
>
|
|
||||||
<H2 {...css({ marginLeft: ts(2) })}>{t("show.nextUp")}</H2>
|
|
||||||
<EpisodeLine
|
|
||||||
{...nextEpisode}
|
|
||||||
showSlug={null}
|
|
||||||
watchedPercent={watchedPercent || null}
|
|
||||||
watchedStatus={status || null}
|
|
||||||
displayNumber={episodeDisplayNumber(nextEpisode)}
|
|
||||||
onHoverIn={() => setFocus(true)}
|
|
||||||
onHoverOut={() => setFocus(false)}
|
|
||||||
onFocus={() => setFocus(true)}
|
|
||||||
onBlur={() => setFocus(false)}
|
|
||||||
/>
|
|
||||||
</Container>
|
|
||||||
)}
|
|
||||||
</SwitchVariant>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ShowHeader = forwardRef<View, ViewProps & { slug: string }>(function ShowHeader(
|
|
||||||
{ children, slug, ...props },
|
|
||||||
ref,
|
|
||||||
) {
|
|
||||||
const { css, theme } = useYoshiki();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
ref={ref}
|
|
||||||
{...css(
|
|
||||||
[
|
|
||||||
{ bg: (theme) => theme.background },
|
|
||||||
Platform.OS === "web" && {
|
|
||||||
flexGrow: 1,
|
|
||||||
flexShrink: 1,
|
|
||||||
// @ts-ignore Web only property
|
|
||||||
overflowY: "auto" as any,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
props,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Header type="show" query={query(slug)} />
|
|
||||||
<DetailsCollections type="show" slug={slug} />
|
|
||||||
{/* <Staff slug={slug} /> */}
|
|
||||||
<SvgWave
|
|
||||||
fill={theme.variant.background}
|
|
||||||
{...css({ flexShrink: 0, flexGrow: 1, display: "flex" })}
|
|
||||||
/>
|
|
||||||
<View {...css({ bg: theme.variant.background })}>
|
|
||||||
<Container>{children}</Container>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const query = (slug: string): QueryIdentifier<Show> => ({
|
|
||||||
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 (
|
|
||||||
<View {...css({ bg: theme.variant.background, flex: 1 })}>
|
|
||||||
<EpisodeList slug={slug} season={season} Header={ShowHeader} headerProps={{ slug }} />
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
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 } };
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// 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 (
|
|
||||||
// <InfiniteFetch
|
|
||||||
// query={Staff.query(slug)}
|
|
||||||
// horizontal
|
|
||||||
// layout={{ numColumns: 1, size: PersonAvatar.width }}
|
|
||||||
// empty={t("show.staff-none")}
|
|
||||||
// >
|
|
||||||
// {(item, key) => (
|
|
||||||
// <PersonAvatar
|
|
||||||
// key={key}
|
|
||||||
// isLoading={item.isLoading}
|
|
||||||
// slug={item?.slug}
|
|
||||||
// name={item?.name}
|
|
||||||
// role={item?.type ? `${item?.type} (${item?.role})` : item?.role}
|
|
||||||
// poster={item?.poster}
|
|
||||||
// // sx={{ width: { xs: "7rem", lg: "10rem" }, flexShrink: 0, px: 2 }}
|
|
||||||
// />
|
|
||||||
// )}
|
|
||||||
// </InfiniteFetch>
|
|
||||||
// );
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// Staff.query = (slug: string): QueryIdentifier<Person> => ({
|
|
||||||
// parser: PersonP,
|
|
||||||
// path: ["show", slug, "people"],
|
|
||||||
// infinite: true,
|
|
||||||
// });
|
|
Loading…
x
Reference in New Issue
Block a user