mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-11-16 03:23:12 -05:00
195 lines
5.1 KiB
TypeScript
195 lines
5.1 KiB
TypeScript
/*
|
|
* 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 Collection,
|
|
CollectionP,
|
|
type LibraryItem,
|
|
LibraryItemP,
|
|
type QueryIdentifier,
|
|
type QueryPage,
|
|
getDisplayDate,
|
|
} from "@kyoo/models";
|
|
import {
|
|
Container,
|
|
GradientImageBackground,
|
|
Head,
|
|
P,
|
|
Skeleton,
|
|
ts,
|
|
usePageStyle,
|
|
} from "@kyoo/primitives";
|
|
import { forwardRef } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { Platform, View, type ViewProps } from "react-native";
|
|
import { percent, px, useYoshiki } from "yoshiki/native";
|
|
import { ItemGrid } from "../browse/grid";
|
|
import { Header as ShowHeader, TitleLine } from "../details/header";
|
|
import { SvgWave } from "../details/show";
|
|
import { Fetch } from "../fetch";
|
|
import { InfiniteFetch } from "../fetch-infinite";
|
|
import { ItemDetails } from "../home/recommended";
|
|
import { DefaultLayout } from "../layout";
|
|
|
|
const Header = ({ slug }: { slug: string }) => {
|
|
const { css } = useYoshiki();
|
|
const { t } = useTranslation();
|
|
|
|
return (
|
|
<Fetch query={Header.query(slug)}>
|
|
{({ isLoading, ...data }) => (
|
|
<>
|
|
<Head title={data?.name} description={data?.overview} image={data?.thumbnail?.high} />
|
|
|
|
<GradientImageBackground
|
|
src={data?.thumbnail}
|
|
quality="high"
|
|
alt=""
|
|
containerStyle={ShowHeader.containerStyle}
|
|
>
|
|
<TitleLine
|
|
isLoading={isLoading}
|
|
type={"collection"}
|
|
playHref={null}
|
|
name={data?.name}
|
|
tagline={null}
|
|
date={null}
|
|
rating={null}
|
|
runtime={null}
|
|
poster={data?.poster}
|
|
trailerUrl={null}
|
|
studio={null}
|
|
{...css(ShowHeader.childStyle)}
|
|
/>
|
|
</GradientImageBackground>
|
|
|
|
<Container
|
|
{...css({
|
|
paddingTop: ts(4),
|
|
marginBottom: ts(4),
|
|
})}
|
|
>
|
|
<Skeleton lines={4}>
|
|
{isLoading || (
|
|
<P {...css({ textAlign: "justify" })}>{data.overview ?? t("show.noOverview")}</P>
|
|
)}
|
|
</Skeleton>
|
|
</Container>
|
|
</>
|
|
)}
|
|
</Fetch>
|
|
);
|
|
};
|
|
|
|
Header.query = (slug: string): QueryIdentifier<Collection> => ({
|
|
parser: CollectionP,
|
|
path: ["collections", slug],
|
|
});
|
|
|
|
const CollectionHeader = forwardRef<View, ViewProps & { slug: string }>(function ShowHeader(
|
|
{ children, slug, ...props },
|
|
ref,
|
|
) {
|
|
const { css, theme } = useYoshiki();
|
|
|
|
return (
|
|
<View
|
|
ref={ref}
|
|
{...css(
|
|
[
|
|
{ bg: (theme) => theme.variant.background },
|
|
Platform.OS === "web" && {
|
|
flexGrow: 1,
|
|
flexShrink: 1,
|
|
// @ts-ignore Web only property
|
|
overflowY: "auto" as any,
|
|
},
|
|
],
|
|
props,
|
|
)}
|
|
>
|
|
<Header slug={slug} />
|
|
<SvgWave fill={theme.background} {...css({ flexShrink: 0, flexGrow: 1, display: "flex" })} />
|
|
<View {...css({ bg: theme.background, paddingTop: { xs: ts(8), md: 0 } })}>
|
|
<View
|
|
{...css({
|
|
width: percent(100),
|
|
maxWidth: { xs: percent(100), lg: px(1170) },
|
|
alignSelf: "center",
|
|
})}
|
|
>
|
|
{children}
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
});
|
|
|
|
const query = (slug: string): QueryIdentifier<LibraryItem> => ({
|
|
parser: LibraryItemP,
|
|
path: ["collections", slug, "items"],
|
|
infinite: true,
|
|
params: {
|
|
fields: ["firstEpisode", "episodesCount", "watchStatus"],
|
|
},
|
|
});
|
|
|
|
export const CollectionPage: QueryPage<{ slug: string }> = ({ slug }) => {
|
|
const { css } = useYoshiki();
|
|
const pageStyle = usePageStyle();
|
|
|
|
return (
|
|
<InfiniteFetch
|
|
query={query(slug)}
|
|
placeholderCount={2}
|
|
layout={{ ...ItemDetails.layout, numColumns: { xs: 1, md: 2 } }}
|
|
Header={CollectionHeader}
|
|
headerProps={{ slug }}
|
|
contentContainerStyle={{ padding: 0, paddingHorizontal: 0, ...pageStyle }}
|
|
Render={({ item }) => (
|
|
<ItemDetails
|
|
slug={item.slug}
|
|
type={item.kind}
|
|
name={item.name}
|
|
tagline={"tagline" in item ? item.tagline : null}
|
|
overview={item.overview}
|
|
poster={item.poster}
|
|
subtitle={item.kind !== "collection" ? getDisplayDate(item) : null}
|
|
genres={"genres" in item ? item.genres : null}
|
|
href={item.href}
|
|
playHref={item.kind !== "collection" ? item.playHref : null}
|
|
watchStatus={(item.kind !== "collection" && item.watchStatus?.status) || null}
|
|
unseenEpisodesCount={
|
|
item.kind === "show"
|
|
? (item.watchStatus?.unseenEpisodesCount ?? item.episodesCount!)
|
|
: null
|
|
}
|
|
{...css({ marginX: ItemGrid.layout.gap })}
|
|
/>
|
|
)}
|
|
Loader={ItemDetails.Loader}
|
|
/>
|
|
);
|
|
};
|
|
|
|
CollectionPage.getLayout = { Layout: DefaultLayout, props: { transparent: true } };
|
|
|
|
CollectionPage.getFetchUrls = ({ slug }) => [query(slug), Header.query(slug)];
|