Create watchlist on the homepage

This commit is contained in:
Zoe Roux 2023-12-06 14:41:39 +01:00
parent 4cf9609153
commit c87001d91e
7 changed files with 146 additions and 1 deletions

View File

@ -30,4 +30,5 @@ export * from "./episode";
export * from "./season";
export * from "./watch-info";
export * from "./watch-status";
export * from "./watchlist";
export * from "./user";

View File

@ -0,0 +1,47 @@
/*
* 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 { MovieP } from "./movie";
import { ShowP } from "./show";
/**
* The type of item, ether a show, a movie or an episode.
*/
export enum WatchlistKind {
Show = "Show",
Movie = "Movie",
}
export const WatchlistP = z.union([
/*
* Either a show
*/
ShowP.and(z.object({ kind: z.literal(WatchlistKind.Show) })),
/*
* Or a Movie
*/
MovieP.and(z.object({ kind: z.literal(WatchlistKind.Movie) })),
]);
/**
* A item in the user's watchlist.
*/
export type Watchlist = z.infer<typeof WatchlistP>;

View File

@ -105,7 +105,7 @@ export const EpisodeBox = ({
src={thumbnail}
quality="low"
alt=""
graditent={false}
gradient={false}
hideLoad={false}
forcedLoading={isLoading}
layout={{ width: percent(100), aspectRatio: 16 / 9 }}

View File

@ -28,6 +28,7 @@ import { Recommanded } from "./recommanded";
import { VerticalRecommanded } from "./vertical";
import { NewsList } from "./news";
import { useEffect, useState } from "react";
import { WatchlistList } from "./watchlist";
export const HomePage: QueryPage<{}, Genre> = ({ randomItems }) => {
const [isClient, setClient] = useState(false);
@ -48,6 +49,7 @@ export const HomePage: QueryPage<{}, Genre> = ({ randomItems }) => {
/>
)}
</Fetch>
<WatchlistList />
<NewsList />
{randomItems
.filter((_, i) => i < 2)
@ -72,6 +74,7 @@ HomePage.getLayout = { Layout: DefaultLayout, props: { transparent: true } };
HomePage.getFetchUrls = (_, randomItems) => [
Header.query(),
WatchlistList.query(),
NewsList.query(),
...randomItems.filter((_, i) => i < 6).map((x) => GenreGrid.query(x)),
Recommanded.query(),

View File

@ -0,0 +1,92 @@
/*
* 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 {
QueryIdentifier,
Watchlist,
WatchlistKind,
WatchlistP,
getDisplayDate,
} from "@kyoo/models";
import { useYoshiki } from "yoshiki/native";
import { ItemGrid } from "../browse/grid";
import { InfiniteFetch } from "../fetch-infinite";
import { useTranslation } from "react-i18next";
import { Header } from "./genre";
import { EpisodeBox, episodeDisplayNumber } from "../details/episode";
export const WatchlistList = () => {
const { t } = useTranslation();
const { css } = useYoshiki();
return (
<>
<Header title={t("home.watchlist")} />
<InfiniteFetch
query={WatchlistList.query()}
layout={{ ...ItemGrid.layout, layout: "horizontal" }}
getItemType={(x, i) =>
(x.kind === WatchlistKind.Show && x.watchStatus?.nextEpisode) || (x.isLoading && i % 2)
? "episode"
: "item"
}
empty={t("home.none")}
>
{(x, i) => {
const episode = x.kind === WatchlistKind.Show ? x.watchStatus?.nextEpisode : null;
return (x.kind === WatchlistKind.Show && x.watchStatus?.nextEpisode) ||
(x.isLoading && i % 2) ? (
<EpisodeBox
isLoading={x.isLoading as any}
name={episode ? `${x.name} ${episodeDisplayNumber(episode)}` : undefined}
overview={episode?.name}
thumbnail={episode?.thumbnail ?? x.thumbnail}
href={episode?.href}
watchedPercent={x.watchStatus?.watchedPercent || null}
watchedStatus={x.watchStatus?.status || null}
// TODO: support this on mobile too
// @ts-expect-error This is a web only property
{...css({ gridColumnEnd: "span 2" })}
/>
) : (
<ItemGrid
isLoading={x.isLoading as any}
href={x.href}
name={x.name!}
subtitle={!x.isLoading ? getDisplayDate(x) : undefined}
poster={x.poster}
/>
);
}}
</InfiniteFetch>
</>
);
};
WatchlistList.query = (): QueryIdentifier<Watchlist> => ({
parser: WatchlistP,
infinite: true,
path: ["watchlist"],
params: {
// Limit the inital numbers of items
limit: 10,
fields: ["watchStatus"],
},
});

View File

@ -2,6 +2,7 @@
"home": {
"recommanded": "Recommanded",
"news": "News",
"watchlist": "Continue watching",
"info": "See more",
"none": "No episodes"
},

View File

@ -2,6 +2,7 @@
"home": {
"recommanded": "Recommandé",
"news": "Nouveautés",
"watchlist": "Continuer de regarder",
"info": "Voir plus",
"none": "Aucun episode"
},