mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-30 19:54:16 -04:00
Create watchlist on the homepage
This commit is contained in:
parent
4cf9609153
commit
c87001d91e
@ -30,4 +30,5 @@ export * from "./episode";
|
|||||||
export * from "./season";
|
export * from "./season";
|
||||||
export * from "./watch-info";
|
export * from "./watch-info";
|
||||||
export * from "./watch-status";
|
export * from "./watch-status";
|
||||||
|
export * from "./watchlist";
|
||||||
export * from "./user";
|
export * from "./user";
|
||||||
|
47
front/packages/models/src/resources/watchlist.ts
Normal file
47
front/packages/models/src/resources/watchlist.ts
Normal 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>;
|
@ -105,7 +105,7 @@ export const EpisodeBox = ({
|
|||||||
src={thumbnail}
|
src={thumbnail}
|
||||||
quality="low"
|
quality="low"
|
||||||
alt=""
|
alt=""
|
||||||
graditent={false}
|
gradient={false}
|
||||||
hideLoad={false}
|
hideLoad={false}
|
||||||
forcedLoading={isLoading}
|
forcedLoading={isLoading}
|
||||||
layout={{ width: percent(100), aspectRatio: 16 / 9 }}
|
layout={{ width: percent(100), aspectRatio: 16 / 9 }}
|
||||||
|
@ -28,6 +28,7 @@ import { Recommanded } from "./recommanded";
|
|||||||
import { VerticalRecommanded } from "./vertical";
|
import { VerticalRecommanded } from "./vertical";
|
||||||
import { NewsList } from "./news";
|
import { NewsList } from "./news";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { WatchlistList } from "./watchlist";
|
||||||
|
|
||||||
export const HomePage: QueryPage<{}, Genre> = ({ randomItems }) => {
|
export const HomePage: QueryPage<{}, Genre> = ({ randomItems }) => {
|
||||||
const [isClient, setClient] = useState(false);
|
const [isClient, setClient] = useState(false);
|
||||||
@ -48,6 +49,7 @@ export const HomePage: QueryPage<{}, Genre> = ({ randomItems }) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Fetch>
|
</Fetch>
|
||||||
|
<WatchlistList />
|
||||||
<NewsList />
|
<NewsList />
|
||||||
{randomItems
|
{randomItems
|
||||||
.filter((_, i) => i < 2)
|
.filter((_, i) => i < 2)
|
||||||
@ -72,6 +74,7 @@ HomePage.getLayout = { Layout: DefaultLayout, props: { transparent: true } };
|
|||||||
|
|
||||||
HomePage.getFetchUrls = (_, randomItems) => [
|
HomePage.getFetchUrls = (_, randomItems) => [
|
||||||
Header.query(),
|
Header.query(),
|
||||||
|
WatchlistList.query(),
|
||||||
NewsList.query(),
|
NewsList.query(),
|
||||||
...randomItems.filter((_, i) => i < 6).map((x) => GenreGrid.query(x)),
|
...randomItems.filter((_, i) => i < 6).map((x) => GenreGrid.query(x)),
|
||||||
Recommanded.query(),
|
Recommanded.query(),
|
||||||
|
92
front/packages/ui/src/home/watchlist.tsx
Normal file
92
front/packages/ui/src/home/watchlist.tsx
Normal 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"],
|
||||||
|
},
|
||||||
|
});
|
@ -2,6 +2,7 @@
|
|||||||
"home": {
|
"home": {
|
||||||
"recommanded": "Recommanded",
|
"recommanded": "Recommanded",
|
||||||
"news": "News",
|
"news": "News",
|
||||||
|
"watchlist": "Continue watching",
|
||||||
"info": "See more",
|
"info": "See more",
|
||||||
"none": "No episodes"
|
"none": "No episodes"
|
||||||
},
|
},
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
"home": {
|
"home": {
|
||||||
"recommanded": "Recommandé",
|
"recommanded": "Recommandé",
|
||||||
"news": "Nouveautés",
|
"news": "Nouveautés",
|
||||||
|
"watchlist": "Continuer de regarder",
|
||||||
"info": "Voir plus",
|
"info": "Voir plus",
|
||||||
"none": "Aucun episode"
|
"none": "Aucun episode"
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user