mirror of
https://github.com/zoriya/Kyoo.git
synced 2026-01-19 02:15:59 -05:00
wip: Rework home's watchlist
This commit is contained in:
parent
556b9031a4
commit
04fef7bd20
@ -4,7 +4,7 @@ import BookmarkRemove from "@material-symbols/svg-400/rounded/bookmark_remove.sv
|
||||
import Bookmark from "@material-symbols/svg-400/rounded/bookmark-fill.svg";
|
||||
import type { ComponentProps } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { Serie } from "~/models";
|
||||
import type { Movie, Serie } from "~/models";
|
||||
import { IconButton, Menu, tooltip } from "~/primitives";
|
||||
import { useAccount } from "~/providers/account-context";
|
||||
import { useMutation } from "~/query";
|
||||
@ -49,9 +49,10 @@ export const WatchListInfo = ({
|
||||
path: [kind, slug, "watchStatus"],
|
||||
compute: (newStatus: WatchStatus | null) => ({
|
||||
method: newStatus ? "POST" : "DELETE",
|
||||
params: newStatus ? { status: newStatus } : undefined,
|
||||
body: newStatus ? { status: newStatus } : undefined,
|
||||
}),
|
||||
invalidate: [kind, slug],
|
||||
invalidate: ["api", `${kind}s`, slug],
|
||||
// optimistic is a pain to do because shows queries often have query params
|
||||
});
|
||||
if (mutation.isPending) status = mutation.variables;
|
||||
|
||||
|
||||
@ -13,10 +13,12 @@ export type Layout = {
|
||||
layout: "grid" | "horizontal" | "vertical";
|
||||
};
|
||||
|
||||
export const InfiniteFetch = <Data,>({
|
||||
export const InfiniteFetch = <Data, Type extends string = string>({
|
||||
query,
|
||||
placeholderCount = 2,
|
||||
incremental = false,
|
||||
getItemType,
|
||||
getItemSizeMult,
|
||||
Render,
|
||||
Loader,
|
||||
layout,
|
||||
@ -31,6 +33,8 @@ export const InfiniteFetch = <Data,>({
|
||||
placeholderCount?: number;
|
||||
layout: Layout;
|
||||
horizontal?: boolean;
|
||||
getItemType?: (item: Data, index: number) => Type;
|
||||
getItemSizeMult?: (item: Data, index: number, type: Type) => number;
|
||||
Render: (props: { item: Data; index: number }) => ReactElement | null;
|
||||
Loader: (props: { index: number }) => ReactElement | null;
|
||||
Empty?: JSX.Element;
|
||||
@ -74,11 +78,17 @@ export const InfiniteFetch = <Data,>({
|
||||
<LegendList
|
||||
data={data}
|
||||
recycleItems
|
||||
getItemType={getItemType}
|
||||
estimatedItemSize={getItemSizeMult ? undefined : size}
|
||||
getEstimatedItemSize={
|
||||
getItemSizeMult
|
||||
? (idx, item, type) => getItemSizeMult(item, idx, type as Type) * size
|
||||
: undefined
|
||||
}
|
||||
renderItem={({ item, index }) =>
|
||||
item ? <Render index={index} item={item} /> : <Loader index={index} />
|
||||
}
|
||||
keyExtractor={(item: any, index) => (item ? item.id : index)}
|
||||
estimatedItemSize={size}
|
||||
horizontal={layout.layout === "horizontal"}
|
||||
numColumns={layout.layout === "horizontal" ? 1 : numColumns}
|
||||
onEndReached={fetchMore ? () => fetchNextPage() : undefined}
|
||||
|
||||
@ -301,7 +301,7 @@ export const useMutation = <T = void, QueryRet = void>({
|
||||
...queryParams
|
||||
}: MutationParams & {
|
||||
compute?: (param: T) => MutationParams;
|
||||
optimistic?: (param: T) => QueryRet;
|
||||
optimistic?: (param: T, previous?: QueryRet) => QueryRet | undefined;
|
||||
invalidate: string[] | null;
|
||||
}) => {
|
||||
const { apiUrl, authToken } = useContext(AccountContext);
|
||||
@ -324,13 +324,15 @@ export const useMutation = <T = void, QueryRet = void>({
|
||||
...(invalidate && optimistic
|
||||
? {
|
||||
onMutate: async (params) => {
|
||||
const next = optimistic(params);
|
||||
const queryKey = toQueryKey({ apiUrl, path: invalidate });
|
||||
await queryClient.cancelQueries({
|
||||
queryKey,
|
||||
});
|
||||
|
||||
const previous = queryClient.getQueryData(queryKey);
|
||||
const next = optimistic(params, previous as QueryRet);
|
||||
queryClient.setQueryData(queryKey, next);
|
||||
|
||||
return { previous, next };
|
||||
},
|
||||
onError: (_, __, context) => {
|
||||
|
||||
@ -56,10 +56,6 @@ export const Header = ({
|
||||
}}
|
||||
{...(css(
|
||||
{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
minHeight: {
|
||||
xs: px(350),
|
||||
sm: px(300),
|
||||
|
||||
@ -1,33 +1,14 @@
|
||||
/*
|
||||
* 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 { useTranslation } from "react-i18next";
|
||||
import { View } from "react-native";
|
||||
import { useYoshiki } from "yoshiki/native";
|
||||
import { EntryBox, entryDisplayNumber } from "~/components/entries";
|
||||
import { ItemGrid } from "~/components/items";
|
||||
import type { Show } from "~/models";
|
||||
import { getDisplayDate } from "~/utils";
|
||||
import { Button, P, ts } from "~/primitives";
|
||||
import { Show } from "~/models";
|
||||
import { Button, Link, P, ts } from "~/primitives";
|
||||
import { useAccount } from "~/providers/account-context";
|
||||
import { InfiniteFetch, type QueryIdentifier } from "~/query";
|
||||
import { getDisplayDate } from "~/utils";
|
||||
import { EmptyView } from "../errors";
|
||||
import { Header } from "./genre";
|
||||
|
||||
export const WatchlistList = () => {
|
||||
@ -42,8 +23,9 @@ export const WatchlistList = () => {
|
||||
<View {...css({ justifyContent: "center", alignItems: "center" })}>
|
||||
<P>{t("home.watchlistLogin")}</P>
|
||||
<Button
|
||||
text={t("login.login")}
|
||||
as={Link}
|
||||
href={"/login"}
|
||||
text={t("login.login")}
|
||||
{...css({ minWidth: ts(24), margin: ts(2) })}
|
||||
/>
|
||||
</View>
|
||||
@ -58,10 +40,12 @@ export const WatchlistList = () => {
|
||||
query={WatchlistList.query()}
|
||||
layout={{ ...ItemGrid.layout, layout: "horizontal" }}
|
||||
getItemType={(x, i) =>
|
||||
(x?.kind === "serie" && x.nextEntry) || (!x && i % 2) ? "episode" : "item"
|
||||
(x?.kind === "serie" && x.nextEntry) || (!x && i % 2)
|
||||
? "episode"
|
||||
: "item"
|
||||
}
|
||||
getItemSize={(kind) => (kind === "episode" ? 2 : 1)}
|
||||
empty={t("home.none")}
|
||||
getItemSizeMult={(_, __, kind) => (kind === "episode" ? 2 : 1)}
|
||||
Empty={<EmptyView message={t("home.none")} />}
|
||||
Render={({ item }) => {
|
||||
const entry = item.kind === "serie" ? item.nextEntry : null;
|
||||
if (entry) {
|
||||
@ -74,9 +58,6 @@ export const WatchlistList = () => {
|
||||
thumbnail={entry.thumbnail ?? item.thumbnail}
|
||||
href={entry.href ?? "#"}
|
||||
watchedPercent={entry.watchStatus?.percent || null}
|
||||
// TODO: Move this into the ItemList (using getItemSize)
|
||||
// @ts-expect-error This is a web only property
|
||||
{...css({ gridColumnEnd: "span 2" })}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -88,13 +69,23 @@ export const WatchlistList = () => {
|
||||
name={item.name!}
|
||||
subtitle={getDisplayDate(item)}
|
||||
poster={item.poster}
|
||||
watchStatus={item.watchStatus?.status || null}
|
||||
watchPercent={item.kind === "movie" && item.watchStatus ? item.watchStatus.percent : null}
|
||||
watchStatus={
|
||||
item.kind !== "collection"
|
||||
? (item.watchStatus?.status ?? null)
|
||||
: null
|
||||
}
|
||||
watchPercent={
|
||||
item.kind === "movie" && item.watchStatus
|
||||
? item.watchStatus.percent
|
||||
: null
|
||||
}
|
||||
unseenEpisodesCount={null}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
Loader={({ index }) => (index % 2 ? <EntryBox.Loader /> : <ItemGrid.Loader />)}
|
||||
Loader={({ index }) =>
|
||||
index % 2 ? <EntryBox.Loader /> : <ItemGrid.Loader />
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@ -103,10 +94,9 @@ export const WatchlistList = () => {
|
||||
WatchlistList.query = (): QueryIdentifier<Show> => ({
|
||||
parser: Show,
|
||||
infinite: true,
|
||||
path: ["api", "watchlist"],
|
||||
path: ["api", "profiles", "me", "watchlist"],
|
||||
params: {
|
||||
// Limit the initial numbers of items
|
||||
limit: 10,
|
||||
fields: ["watchStatus", "nextEntry"],
|
||||
with: ["nextEntry"],
|
||||
},
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user