Disable empty genre list during ssr

This commit is contained in:
Zoe Roux 2023-10-21 21:54:25 +02:00
parent 11712b5b13
commit fc598838c4
6 changed files with 85 additions and 56 deletions

View File

@ -22,7 +22,7 @@ import { Page, QueryIdentifier, useInfiniteFetch } from "@kyoo/models";
import { useBreakpointMap, HR } from "@kyoo/primitives"; import { useBreakpointMap, HR } from "@kyoo/primitives";
import { FlashList } from "@shopify/flash-list"; import { FlashList } from "@shopify/flash-list";
import { ComponentType, isValidElement, ReactElement, useRef } from "react"; import { ComponentType, isValidElement, ReactElement, useRef } from "react";
import { EmptyView, ErrorView, Layout, WithLoading } from "./fetch"; import { EmptyView, ErrorView, Layout, WithLoading, addHeader } from "./fetch";
export const InfiniteFetch = <Data, Props, _>({ export const InfiniteFetch = <Data, Props, _>({
query, query,
@ -33,7 +33,7 @@ export const InfiniteFetch = <Data, Props, _>({
empty, empty,
divider = false, divider = false,
Header, Header,
headerProps, headerProps: hprops,
getItemType, getItemType,
...props ...props
}: { }: {
@ -48,7 +48,7 @@ export const InfiniteFetch = <Data, Props, _>({
empty?: string | JSX.Element; empty?: string | JSX.Element;
incremental?: boolean; incremental?: boolean;
divider?: boolean | ComponentType; divider?: boolean | ComponentType;
Header?: ComponentType<Props & { children: JSX.Element }> | ReactElement; Header?: ComponentType<Props & { children: JSX.Element; empty: boolean }> | ReactElement;
headerProps?: Props; headerProps?: Props;
getItemType?: (item: Data, index: number) => string | number; getItemType?: (item: Data, index: number) => string | number;
}): JSX.Element | null => { }): JSX.Element | null => {
@ -65,9 +65,13 @@ export const InfiniteFetch = <Data, Props, _>({
if (incremental && items) oldItems.current = items; if (incremental && items) oldItems.current = items;
if (error) return <ErrorView error={error} />; if (error) return <ErrorView error={error} />;
// @ts-ignore
const headerProps: Props & { empty: boolean } = hprops
? { ...hprops, empty: items?.length === 0 }
: { empty: items?.length === 0 };
if (empty && items && items.length === 0) { if (empty && items && items.length === 0) {
if (typeof empty !== "string") return empty; if (typeof empty !== "string") return addHeader(Header, empty, headerProps);
return <EmptyView message={empty} />; return addHeader(Header, <EmptyView message={empty} />, headerProps);
} }
if (incremental) items ??= oldItems.current; if (incremental) items ??= oldItems.current;

View File

@ -30,7 +30,7 @@ import {
useRef, useRef,
} from "react"; } from "react";
import { Stylable, useYoshiki, ysMap } from "yoshiki"; import { Stylable, useYoshiki, ysMap } from "yoshiki";
import { EmptyView, ErrorView, Layout, WithLoading } from "./fetch"; import { EmptyView, ErrorView, Layout, WithLoading, addHeader } from "./fetch";
const InfiniteScroll = <Props,>({ const InfiniteScroll = <Props,>({
children, children,
@ -139,7 +139,7 @@ export const InfiniteFetch = <Data, _, HeaderProps>({
empty, empty,
divider: Divider = false, divider: Divider = false,
Header, Header,
headerProps, headerProps: hprops,
getItemType, getItemType,
...props ...props
}: { }: {
@ -165,10 +165,14 @@ export const InfiniteFetch = <Data, _, HeaderProps>({
}); });
if (incremental && items) oldItems.current = items; if (incremental && items) oldItems.current = items;
// @ts-ignore
const headerProps: HeaderProps & { empty: boolean } = hprops
? { ...hprops, empty: items?.length === 0 }
: { empty: items?.length === 0 };
if (error) return addHeader(Header, <ErrorView error={error} />, headerProps); if (error) return addHeader(Header, <ErrorView error={error} />, headerProps);
if (empty && items && items.length === 0) { if (empty && items && items.length === 0) {
if (typeof empty !== "string") return empty; if (typeof empty !== "string") return addHeader(Header, empty, headerProps);
return <EmptyView message={empty} />; return addHeader(Header, <EmptyView message={empty} />, headerProps);
} }
return ( return (
@ -196,20 +200,3 @@ export const InfiniteFetch = <Data, _, HeaderProps>({
</InfiniteScroll> </InfiniteScroll>
); );
}; };
const addHeader = <Props,>(
Header: ComponentType<{ children: JSX.Element } & Props> | ReactElement | undefined,
children: ReactElement,
headerProps?: Props,
) => {
if (!Header) return children;
return !isValidElement(Header) ? (
// @ts-ignore
<Header {...(headerProps ?? {})}>{children}</Header>
) : (
<>
{Header}
{children}
</>
);
};

View File

@ -20,6 +20,7 @@
import { Page, QueryIdentifier, useFetch, KyooErrors } from "@kyoo/models"; import { Page, QueryIdentifier, useFetch, KyooErrors } from "@kyoo/models";
import { Breakpoint, P } from "@kyoo/primitives"; import { Breakpoint, P } from "@kyoo/primitives";
import { ComponentType, ReactElement, isValidElement } from "react";
import { View } from "react-native"; import { View } from "react-native";
import { useYoshiki } from "yoshiki/native"; import { useYoshiki } from "yoshiki/native";
@ -130,3 +131,20 @@ export const EmptyView = ({ message }: { message: string }) => {
</View> </View>
); );
}; };
export const addHeader = <Props,>(
Header: ComponentType<{ children: JSX.Element } & Props> | ReactElement | undefined,
children: ReactElement,
headerProps?: Props,
) => {
if (!Header) return children;
return !isValidElement(Header) ? (
// @ts-ignore
<Header {...(headerProps ?? {})}>{children}</Header>
) : (
<>
{Header}
{children}
</>
);
};

View File

@ -23,45 +23,63 @@ import {
ItemKind, ItemKind,
LibraryItem, LibraryItem,
LibraryItemP, LibraryItemP,
Page,
Paged,
QueryIdentifier, QueryIdentifier,
getDisplayDate, getDisplayDate,
} from "@kyoo/models"; } from "@kyoo/models";
import { H3, IconButton, ts } from "@kyoo/primitives"; import { H3, IconButton, ts } from "@kyoo/primitives";
import { useRef } from "react"; import { ReactElement, forwardRef, useRef } from "react";
import { ScrollView, View } from "react-native"; import { View } from "react-native";
import { useYoshiki } from "yoshiki/native"; import { useYoshiki } from "yoshiki/native";
import { Fetch } from "../fetch";
import { ItemGrid } from "../browse/grid"; import { ItemGrid } from "../browse/grid";
import ChevronLeft from "@material-symbols/svg-400/rounded/chevron_left-fill.svg"; import ChevronLeft from "@material-symbols/svg-400/rounded/chevron_left-fill.svg";
import ChevronRight from "@material-symbols/svg-400/rounded/chevron_right-fill.svg"; import ChevronRight from "@material-symbols/svg-400/rounded/chevron_right-fill.svg";
import { InfiniteFetch } from "../fetch-infinite"; import { InfiniteFetch } from "../fetch-infinite";
import { useTranslation } from "react-i18next";
export const GenreGrid = ({ genre }: { genre: Genre }) => { const Header = forwardRef<
const ref = useRef<ScrollView>(null); View,
{ empty: boolean; displayEmpty: boolean; genre: Genre; children: ReactElement }
>(function Header({ empty, displayEmpty, genre, children }, ref) {
const { css } = useYoshiki(); const { css } = useYoshiki();
return ( return (
<View> <View ref={ref}>
{!(empty && !displayEmpty) && (
<View {...css({ marginX: ts(1), flexDirection: "row", justifyContent: "space-between" })}> <View {...css({ marginX: ts(1), flexDirection: "row", justifyContent: "space-between" })}>
<H3>{genre}</H3> <H3>{genre}</H3>
<View {...css({ flexDirection: "row" })}> <View {...css({ flexDirection: "row" })}>
<IconButton <IconButton
icon={ChevronLeft} icon={ChevronLeft}
onPress={() => ref.current?.scrollTo({ x: 0, animated: true })} // onPress={() => ref.current?.scrollTo({ x: 0, animated: true })}
/> />
<IconButton <IconButton
icon={ChevronRight} icon={ChevronRight}
onPress={() => ref.current?.scrollTo({ x: 0, animated: true })} // onPress={() => ref.current?.scrollTo({ x: 0, animated: true })}
/> />
</View> </View>
</View> </View>
)}
{children}
</View>
);
});
export const GenreGrid = ({ genre }: { genre: Genre }) => {
const displayEmpty = useRef(false);
const { t } = useTranslation();
return (
<InfiniteFetch <InfiniteFetch
query={GenreGrid.query(genre)} query={GenreGrid.query(genre)}
layout={{ ...ItemGrid.layout, layout: "horizontal" }} layout={{ ...ItemGrid.layout, layout: "horizontal" }}
empty={displayEmpty.current ? t("home.none") : undefined}
Header={Header}
headerProps={{ genre, displayEmpty: displayEmpty.current }}
> >
{(x, i) => ( {(x, i) => {
// only display empty list if a loading as been displayed (not durring ssr)
if (x.isLoading) displayEmpty.current = true;
return (
<ItemGrid <ItemGrid
key={x.id ?? i} key={x.id ?? i}
isLoading={x.isLoading as any} isLoading={x.isLoading as any}
@ -72,9 +90,9 @@ export const GenreGrid = ({ genre }: { genre: Genre }) => {
} }
poster={x.poster} poster={x.poster}
/> />
)} );
}}
</InfiniteFetch> </InfiniteFetch>
</View>
); );
}; };

View File

@ -1,7 +1,8 @@
{ {
"home": { "home": {
"recommanded": "Recommanded", "recommanded": "Recommanded",
"info": "See more" "info": "See more",
"none": "No episodes"
}, },
"show": { "show": {
"play": "Play", "play": "Play",

View File

@ -1,7 +1,8 @@
{ {
"home": { "home": {
"recommanded": "Recommandé", "recommanded": "Recommandé",
"info": "Voir plus" "info": "Voir plus",
"none": "Aucun episode"
}, },
"show": { "show": {
"play": "Lecture", "play": "Lecture",