Add an infinite scroll on web and native

This commit is contained in:
Zoe Roux 2022-12-10 14:41:03 +09:00
parent 1cd418991c
commit 47ca25fe1c
6 changed files with 204 additions and 26 deletions

View File

@ -10,6 +10,7 @@
},
"dependencies": {
"@kyoo/ui": "workspace:^",
"@shopify/flash-list": "1.3.1",
"@tanstack/react-query": "^4.19.1",
"babel-plugin-transform-inline-environment-variables": "^0.4.4",
"expo": "^47.0.0",

View File

@ -13,6 +13,7 @@
"typescript": "^4.9.3"
},
"peerDependencies": {
"@shopify/flash-list": "^1.4.0",
"@tanstack/react-query": "*",
"expo-linear-gradient": "*",
"i18next": "*",

View File

@ -0,0 +1,78 @@
/*
* 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 { Page, QueryIdentifier, useInfiniteFetch } from "@kyoo/models";
import { FlashList } from "@shopify/flash-list";
import { ReactElement } from "react";
import { ErrorView, WithLoading } from "./fetch";
export const InfiniteFetch = <Data,>({
query,
placeholderCount = 15,
children,
size,
numColumns,
...props
}: {
query: QueryIdentifier<Data>;
placeholderCount?: number;
numColumns: number;
children: (
item: Data extends Page<infer Item> ? WithLoading<Item> : WithLoading<Data>,
key: string | undefined,
i: number,
) => ReactElement | null;
size: number;
}): JSX.Element | null => {
if (!query.infinite) console.warn("A non infinite query was passed to an InfiniteFetch.");
const { items, error, fetchNextPage, hasNextPage, refetch, isRefetching } =
useInfiniteFetch(query);
if (error) return <ErrorView error={error} />;
return (
<FlashList
renderItem={({ item, index }) =>
children({ isLoading: false, ...item } as any, undefined, index)
}
data={
hasNextPage
? [
...(items || []),
...[
...Array(
items ? numColumns - (items.length % numColumns) + numColumns : placeholderCount,
),
].map((_, i) => ({ id: `gen${i}`, isLoading: true } as Data)),
]
: items
}
keyExtractor={(item: any) => item.id?.toString()}
numColumns={numColumns}
estimatedItemSize={size}
onEndReached={fetchNextPage}
onEndReachedThreshold={0.5}
onRefresh={refetch}
refreshing={isRefetching}
{...props}
/>
);
};

View File

@ -0,0 +1,69 @@
/*
* 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 { Page, QueryIdentifier, useInfiniteFetch } from "@kyoo/models";
import { ReactElement } from "react";
import InfiniteScroll from "react-infinite-scroll-component";
import { useYoshiki } from "yoshiki";
import { ErrorView, WithLoading } from "./fetch";
export const InfiniteFetch = <Data,>({
query,
placeholderCount = 15,
children,
...props
}: {
query: QueryIdentifier<Data>;
placeholderCount?: number;
children: (
item: Data extends Page<infer Item> ? WithLoading<Item> : WithLoading<Data>,
key: string | undefined,
i: number,
) => ReactElement | null;
}): JSX.Element | null => {
if (!query.infinite) console.warn("A non infinite query was passed to an InfiniteFetch.");
const { items, error, fetchNextPage, hasNextPage } = useInfiniteFetch(query);
const { css } = useYoshiki();
if (error) return <ErrorView error={error} />;
return (
<InfiniteScroll
dataLength={items?.length ?? 0}
next={fetchNextPage}
hasMore={hasNextPage!}
loader={[...Array(12)].map((_, i) => children({ isLoading: true } as any, i.toString(), i))}
{...css(
{
display: "flex",
flexWrap: "wrap",
alignItems: "flex-start",
justifyContent: "center",
},
props,
)}
>
{items?.map((item, i) =>
children({ ...item, isLoading: false } as any, (item as any).id?.toString(), i),
)}
</InfiniteScroll>
);
};

View File

@ -18,7 +18,7 @@
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { Page, QueryIdentifier, useFetch, KyooErrors, useInfiniteFetch } from "@kyoo/models";
import { Page, QueryIdentifier, useFetch, KyooErrors } from "@kyoo/models";
import { P } from "@kyoo/primitives";
import { View } from "react-native";
import { useYoshiki } from "yoshiki/native";
@ -54,29 +54,6 @@ export const Fetch = <Data,>({
return <>{data.items.map((item, i) => children({ ...item, isLoading: false } as any, i))}</>;
};
export const InfiniteFetch = <Data,>({
query,
placeholderCount = 15,
children,
}: {
query: QueryIdentifier<Data>;
placeholderCount?: number;
children: (
item: Data extends Page<infer Item> ? WithLoading<Item> : WithLoading<Data>,
i: number,
) => JSX.Element | null;
}): JSX.Element | null => {
if (!query.infinite) console.warn("A non infinite query was passed to an InfiniteFetch.");
const { items, error } = useInfiniteFetch(query);
if (error) return <ErrorView error={error} />;
if (!items)
return (
<>{[...Array(placeholderCount)].map((_, i) => children({ isLoading: true } as any, i))}</>
);
return <>{items.map((item, i) => children({ ...item, isLoading: false } as any, i))}</>;
};
export const ErrorView = ({ error }: { error: KyooErrors }) => {
const { css } = useYoshiki();

View File

@ -2326,6 +2326,7 @@ __metadata:
react-native-svg: ^13.6.0
typescript: ^4.9.3
peerDependencies:
"@shopify/flash-list": ^1.4.0
"@tanstack/react-query": "*"
expo-linear-gradient: "*"
i18next: "*"
@ -3141,6 +3142,20 @@ __metadata:
languageName: node
linkType: hard
"@shopify/flash-list@npm:1.3.1":
version: 1.3.1
resolution: "@shopify/flash-list@npm:1.3.1"
dependencies:
recyclerlistview: 4.1.2
tslib: 2.4.0
peerDependencies:
"@babel/runtime": "*"
react: "*"
react-native: "*"
checksum: 49a82512bf2b3622e1d7f1a86dc2837f7ee8809131fde0ffe0ced41ede001f77bf81741ea5d65b803ad76d669f146f8cd53702daf3387c9f89402b32f9667110
languageName: node
linkType: hard
"@sideway/address@npm:^4.1.3":
version: 4.1.4
resolution: "@sideway/address@npm:4.1.4"
@ -4280,6 +4295,13 @@ __metadata:
languageName: node
linkType: hard
"babel-plugin-transform-inline-environment-variables@npm:^0.4.4":
version: 0.4.4
resolution: "babel-plugin-transform-inline-environment-variables@npm:0.4.4"
checksum: fa361287411301237fd8ce332aff4f8e8ccb8db30e87a2ddc7224c8bf7cd792eda47aca24dc2e09e70bce4c027bc8cbe22f4999056be37a25d2472945df21ef5
languageName: node
linkType: hard
"babel-preset-expo@npm:~9.2.2":
version: 9.2.2
resolution: "babel-preset-expo@npm:9.2.2"
@ -8600,7 +8622,7 @@ __metadata:
languageName: node
linkType: hard
"lodash.debounce@npm:^4.0.8":
"lodash.debounce@npm:4.0.8, lodash.debounce@npm:^4.0.8":
version: 4.0.8
resolution: "lodash.debounce@npm:4.0.8"
checksum: a3f527d22c548f43ae31c861ada88b2637eb48ac6aa3eb56e82d44917971b8aa96fbb37aa60efea674dc4ee8c42074f90f7b1f772e9db375435f6c83a19b3bc6
@ -9636,9 +9658,11 @@ __metadata:
dependencies:
"@babel/core": ^7.19.3
"@kyoo/ui": "workspace:^"
"@shopify/flash-list": 1.3.1
"@tanstack/react-query": ^4.19.1
"@types/react": ~18.0.24
"@types/react-native": ~0.70.6
babel-plugin-transform-inline-environment-variables: ^0.4.4
expo: ^47.0.0
expo-constants: ~14.0.2
expo-linear-gradient: ~12.0.1
@ -10708,7 +10732,7 @@ __metadata:
languageName: node
linkType: hard
"prop-types@npm:^15.6.2, prop-types@npm:^15.8.1":
"prop-types@npm:15.8.1, prop-types@npm:^15.6.2, prop-types@npm:^15.8.1":
version: 15.8.1
resolution: "prop-types@npm:15.8.1"
dependencies:
@ -11196,6 +11220,20 @@ __metadata:
languageName: node
linkType: hard
"recyclerlistview@npm:4.1.2":
version: 4.1.2
resolution: "recyclerlistview@npm:4.1.2"
dependencies:
lodash.debounce: 4.0.8
prop-types: 15.8.1
ts-object-utils: 0.0.5
peerDependencies:
react: ">= 15.2.1"
react-native: ">= 0.30.0"
checksum: e18e36c0783b528b5244ed0b7eabf231c49ec7b0a97c947a6f596900165a4d7bbc3216b23084a8a2cb8d33b98fb38fc4a64254cf0b6431a0d0fa3ca2e7129fbc
languageName: node
linkType: hard
"regenerate-unicode-properties@npm:^10.1.0":
version: 10.1.0
resolution: "regenerate-unicode-properties@npm:10.1.0"
@ -12718,6 +12756,13 @@ __metadata:
languageName: node
linkType: hard
"ts-object-utils@npm:0.0.5":
version: 0.0.5
resolution: "ts-object-utils@npm:0.0.5"
checksum: 83c48fbdaba392fb2c01cea53b267ed5538d2bb44fc6c3eecc10bcfabc1780bfa6ec8569b52bbf0140d9b521d9049d5f15884e12286918244d463d854dbc73cb
languageName: node
linkType: hard
"tsconfig-paths@npm:^3.14.1":
version: 3.14.1
resolution: "tsconfig-paths@npm:3.14.1"
@ -12730,6 +12775,13 @@ __metadata:
languageName: node
linkType: hard
"tslib@npm:2.4.0":
version: 2.4.0
resolution: "tslib@npm:2.4.0"
checksum: 8c4aa6a3c5a754bf76aefc38026134180c053b7bd2f81338cb5e5ebf96fefa0f417bff221592bf801077f5bf990562f6264fecbc42cd3309b33872cb6fc3b113
languageName: node
linkType: hard
"tslib@npm:^1.8.1":
version: 1.14.1
resolution: "tslib@npm:1.14.1"