From 04fef7bd209fc83c5eb9abf0bfeb3ddf169b3efc Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 21 Dec 2025 16:58:14 +0100 Subject: [PATCH] wip: Rework home's watchlist --- front/src/components/items/watchlist-info.tsx | 7 ++- front/src/query/fetch-infinite.tsx | 14 ++++- front/src/query/query.tsx | 6 +- front/src/ui/home/header.tsx | 4 -- front/src/ui/home/watchlist.tsx | 62 ++++++++----------- 5 files changed, 46 insertions(+), 47 deletions(-) diff --git a/front/src/components/items/watchlist-info.tsx b/front/src/components/items/watchlist-info.tsx index 25fb0224..a68f5aac 100644 --- a/front/src/components/items/watchlist-info.tsx +++ b/front/src/components/items/watchlist-info.tsx @@ -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; diff --git a/front/src/query/fetch-infinite.tsx b/front/src/query/fetch-infinite.tsx index 98dc4548..74ea92c0 100644 --- a/front/src/query/fetch-infinite.tsx +++ b/front/src/query/fetch-infinite.tsx @@ -13,10 +13,12 @@ export type Layout = { layout: "grid" | "horizontal" | "vertical"; }; -export const InfiniteFetch = ({ +export const InfiniteFetch = ({ query, placeholderCount = 2, incremental = false, + getItemType, + getItemSizeMult, Render, Loader, layout, @@ -31,6 +33,8 @@ export const InfiniteFetch = ({ 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 = ({ getItemSizeMult(item, idx, type as Type) * size + : undefined + } renderItem={({ item, index }) => item ? : } keyExtractor={(item: any, index) => (item ? item.id : index)} - estimatedItemSize={size} horizontal={layout.layout === "horizontal"} numColumns={layout.layout === "horizontal" ? 1 : numColumns} onEndReached={fetchMore ? () => fetchNextPage() : undefined} diff --git a/front/src/query/query.tsx b/front/src/query/query.tsx index 23ac45d6..8ec29b31 100644 --- a/front/src/query/query.tsx +++ b/front/src/query/query.tsx @@ -301,7 +301,7 @@ export const useMutation = ({ ...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 = ({ ...(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) => { diff --git a/front/src/ui/home/header.tsx b/front/src/ui/home/header.tsx index ff3984c8..f5378b69 100644 --- a/front/src/ui/home/header.tsx +++ b/front/src/ui/home/header.tsx @@ -56,10 +56,6 @@ export const Header = ({ }} {...(css( { - position: "absolute", - top: 0, - left: 0, - right: 0, minHeight: { xs: px(350), sm: px(300), diff --git a/front/src/ui/home/watchlist.tsx b/front/src/ui/home/watchlist.tsx index 73709507..49a574cc 100644 --- a/front/src/ui/home/watchlist.tsx +++ b/front/src/ui/home/watchlist.tsx @@ -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 . - */ - 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 = () => {

{t("home.watchlistLogin")}