Split loaders for most items on the main page

This commit is contained in:
Zoe Roux 2024-05-20 19:36:23 +02:00
parent 2756397898
commit 393c58b10a
No known key found for this signature in database
11 changed files with 169 additions and 162 deletions

View File

@ -213,7 +213,7 @@ export const ItemGrid = ({
);
};
ItemGrid.Loader = (props: Stylable) => {
ItemGrid.Loader = (props: object) => {
const { css } = useYoshiki();
return (

View File

@ -87,7 +87,7 @@ export const BrowsePage: QueryPage = () => {
/>
}
Render={({ item }) => <LayoutComponent {...itemMap(item)} />}
Loader={() => <LayoutComponent.Loader />}
Loader={LayoutComponent.Loader}
/>
);
};

View File

@ -37,7 +37,7 @@ import { percent, px, rem, useYoshiki } from "yoshiki/native";
import { ItemContext } from "../components/context-menus";
import type { Layout } from "../fetch";
import { ItemWatchStatus } from "./grid";
import { Stylable } from "yoshiki";
import type { Stylable } from "yoshiki";
export const ItemList = ({
href,
@ -166,7 +166,7 @@ export const ItemList = ({
);
};
ItemList.Loader = (props: Stylable) => {
ItemList.Loader = (props: object) => {
const { css } = useYoshiki();
return (

View File

@ -42,7 +42,7 @@ import { type ImageStyle, Platform, type PressableProps, View } from "react-nati
import { type Stylable, type Theme, percent, rem, useYoshiki } from "yoshiki/native";
import { ItemProgress } from "../browse/grid";
import { EpisodesContext } from "../components/context-menus";
import type { Layout, WithLoading } from "../fetch";
import type { Layout } from "../fetch";
export const episodeDisplayNumber = (episode: {
seasonNumber?: number | null;
@ -67,13 +67,11 @@ export const EpisodeBox = ({
name,
overview,
thumbnail,
isLoading,
href,
watchedPercent,
watchedStatus,
...props
}: Stylable &
WithLoading<{
}: Stylable & {
slug: string;
// if show slug is null, disable "Go to show" in the context menu
showSlug: string | null;
@ -83,7 +81,7 @@ export const EpisodeBox = ({
thumbnail?: ImageProps["src"] | null;
watchedPercent: number | null;
watchedStatus: WatchStatusV | null;
}>) => {
}) => {
const [moreOpened, setMoreOpened] = useState(false);
const { css } = useYoshiki("episodebox");
const { t } = useTranslation();
@ -126,15 +124,12 @@ export const EpisodeBox = ({
quality="low"
alt=""
gradient={false}
hideLoad={false}
forcedLoading={isLoading}
layout={{ width: percent(100), aspectRatio: 16 / 9 }}
{...(css("poster") as any)}
>
{(watchedPercent || watchedStatus === WatchStatusV.Completed) && (
<ItemProgress watchPercent={watchedPercent ?? 100} />
)}
{slug && watchedStatus !== undefined && (
<EpisodesContext
slug={slug}
showSlug={showSlug}
@ -152,17 +147,10 @@ export const EpisodeBox = ({
Platform.OS === "web" && moreOpened && { display: important("flex") },
])}
/>
)}
</ImageBackground>
<Skeleton {...css({ width: percent(50) })}>
{isLoading || (
<P {...css([{ marginY: 0, textAlign: "center" }, "title"])}>
{name ?? t("show.episodeNoMetadata")}
</P>
)}
</Skeleton>
<Skeleton {...css({ width: percent(75), height: rem(0.8) })}>
{isLoading || (
<SubP
numberOfLines={3}
{...css({
@ -172,12 +160,36 @@ export const EpisodeBox = ({
>
{overview}
</SubP>
)}
</Skeleton>
</Link>
);
};
EpisodeBox.Loader = (props: Stylable) => {
const { css } = useYoshiki();
return (
<View
{...css(
{
alignItems: "center",
},
props,
)}
>
<Image.Loader
layout={{ width: percent(100), aspectRatio: 16 / 9 }}
{...css({
borderColor: (theme) => theme.background,
borderWidth: ts(0.5),
borderStyle: "solid",
})}
/>
<Skeleton {...css({ width: percent(50) })} />
<Skeleton {...css({ width: percent(75), height: rem(0.8) })} />
</View>
);
};
export const EpisodeLine = ({
slug,
showSlug,

View File

@ -26,18 +26,7 @@ import {
SeasonP,
useInfiniteFetch,
} from "@kyoo/models";
import {
H2,
H6,
HR,
IconButton,
Menu,
P,
Skeleton,
tooltip,
ts,
usePageStyle,
} from "@kyoo/primitives";
import { H2, HR, IconButton, Menu, P, Skeleton, tooltip, ts, usePageStyle } from "@kyoo/primitives";
import MenuIcon from "@material-symbols/svg-400/rounded/menu-fill.svg";
import type { ComponentType } from "react";
import { useTranslation } from "react-i18next";

View File

@ -18,7 +18,7 @@
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { type Page, type QueryIdentifier, useInfiniteFetch } from "@kyoo/models";
import { type QueryIdentifier, useInfiniteFetch } from "@kyoo/models";
import { HR, useBreakpointMap } from "@kyoo/primitives";
import { type ContentStyle, FlashList } from "@shopify/flash-list";
import {

View File

@ -18,7 +18,7 @@
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { type Page, type QueryIdentifier, useInfiniteFetch } from "@kyoo/models";
import { type QueryIdentifier, useInfiniteFetch } from "@kyoo/models";
import { HR } from "@kyoo/primitives";
import type { ContentStyle } from "@shopify/flash-list";
import {

View File

@ -75,13 +75,9 @@ export const GenreGrid = ({ genre }: { genre: Genre }) => {
layout={{ ...ItemGrid.layout, layout: "horizontal" }}
placeholderCount={2}
empty={displayEmpty.current ? t("home.none") : undefined}
>
{(x, i) => {
// only display empty list if a loading as been displayed (not durring ssr)
if (x.isLoading) displayEmpty.current = true;
return <ItemGrid key={x.id ?? i} {...itemMap(x)} />;
}}
</InfiniteFetchList>
Render={({ item }) => <ItemGrid {...itemMap(item)} />}
Loader={ItemGrid.Loader}
/>
</>
);
};

View File

@ -39,38 +39,40 @@ export const NewsList = () => {
getItemType={(x, i) => (x.kind === "movie" || (x.isLoading && i % 2) ? "movie" : "episode")}
getItemSize={(kind) => (kind === "episode" ? 2 : 1)}
empty={t("home.none")}
>
{(x, i) =>
x.kind === "movie" || (x.isLoading && i % 2) ? (
<ItemGrid
isLoading={x.isLoading as any}
href={x.href}
slug={x.slug}
name={x.name!}
subtitle={!x.isLoading ? getDisplayDate(x) : undefined}
poster={x.poster}
watchStatus={x.watchStatus?.status || null}
watchPercent={x.watchStatus?.watchedPercent || null}
type={"movie"}
/>
) : (
Render={({ item }) => {
if (item.kind === "episode") {
return (
<EpisodeBox
isLoading={x.isLoading as any}
slug={x.slug}
showSlug={x.kind === "episode" ? x.show!.slug : null}
name={x.kind === "episode" ? `${x.show!.name} ${episodeDisplayNumber(x)}` : undefined}
overview={x.name}
thumbnail={x.thumbnail}
href={x.href}
watchedPercent={x.watchStatus?.watchedPercent || null}
watchedStatus={x.watchStatus?.status || null}
slug={item.slug}
showSlug={item.show!.slug}
name={`${item.show!.name} ${episodeDisplayNumber(item)}`}
overview={item.name}
thumbnail={item.thumbnail}
href={item.href}
watchedPercent={item.watchStatus?.watchedPercent || null}
watchedStatus={item.watchStatus?.status || null}
// TODO: Move this into the ItemList (using getItemSize)
// @ts-expect-error This is a web only property
{...css({ gridColumnEnd: "span 2" })}
/>
)
);
}
</InfiniteFetch>
return (
<ItemGrid
href={item.href}
slug={item.slug}
name={item.name!}
subtitle={getDisplayDate(item)}
poster={item.poster}
watchStatus={item.watchStatus?.status || null}
watchPercent={item.watchStatus?.watchedPercent || null}
unseenEpisodesCount={null}
type={"movie"}
/>
);
}}
Loader={({ index }) => (index % 2 ? <EpisodeBox.Loader /> : <ItemGrid.Loader />)}
/>
</>
);
};

View File

@ -41,9 +41,9 @@ export const VerticalRecommended = () => {
layout={{ ...ItemList.layout, layout: "vertical" }}
fetchMore={false}
nested
>
{(x, i) => <ItemList key={x.id ?? i} {...itemMap(x)} />}
</InfiniteFetch>
Render={({ item }) => <ItemList {...itemMap(item)} />}
Loader={() => <ItemList.Loader />}
/>
</View>
);
};

View File

@ -39,10 +39,25 @@ export const WatchlistList = () => {
const { css } = useYoshiki();
const account = useAccount();
if (!account) {
return (
<>
<Header title={t("home.watchlist")} />
<View {...css({ justifyContent: "center", alignItems: "center" })}>
<P>{t("home.watchlistLogin")}</P>
<Button
text={t("login.login")}
href={"/login"}
{...css({ minWidth: ts(24), margin: ts(2) })}
/>
</View>
</>
);
}
return (
<>
<Header title={t("home.watchlist")} />
{account ? (
<InfiniteFetch
query={WatchlistList.query()}
layout={{ ...ItemGrid.layout, layout: "horizontal" }}
@ -53,50 +68,43 @@ export const WatchlistList = () => {
}
getItemSize={(kind) => (kind === "episode" ? 2 : 1)}
empty={t("home.none")}
>
{(x, i) => {
const episode = x.kind === "show" ? x.watchStatus?.nextEpisode : null;
return (x.kind === "show" && x.watchStatus?.nextEpisode) || (x.isLoading && i % 2) ? (
Render={({ item }) => {
const episode = item.kind === "show" ? item.watchStatus?.nextEpisode : null;
if (episode) {
return (
<EpisodeBox
isLoading={x.isLoading as any}
slug={episode?.slug}
showSlug={x.slug}
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}
slug={episode.slug}
showSlug={item.slug}
name={`${item.name} ${episodeDisplayNumber(episode)}`}
overview={episode.name}
thumbnail={episode.thumbnail ?? item.thumbnail}
href={episode.href}
watchedPercent={item.watchStatus?.watchedPercent || null}
watchedStatus={item.watchStatus?.status || null}
// TODO: Move this into the ItemList (using getItemSize)
// @ts-expect-error This is a web only property
{...css({ gridColumnEnd: "span 2" })}
/>
) : (
);
}
return (
<ItemGrid
isLoading={x.isLoading as any}
href={x.href}
slug={x.slug}
name={x.name!}
subtitle={!x.isLoading ? getDisplayDate(x) : undefined}
poster={x.poster}
watchStatus={x.watchStatus?.status || null}
watchPercent={x.watchStatus?.watchedPercent || null}
unseenEpisodesCount={x.kind === "show" ? x.watchStatus?.unseenEpisodesCount : null}
type={x.kind}
href={item.href}
slug={item.slug}
name={item.name!}
subtitle={getDisplayDate(item)}
poster={item.poster}
watchStatus={item.watchStatus?.status || null}
watchPercent={item.watchStatus?.watchedPercent || null}
unseenEpisodesCount={
(item.kind === "show" && item.watchStatus?.unseenEpisodesCount) || null
}
type={item.kind}
/>
);
}}
</InfiniteFetch>
) : (
<View {...css({ justifyContent: "center", alignItems: "center" })}>
<P>{t("home.watchlistLogin")}</P>
<Button
text={t("login.login")}
href={"/login"}
{...css({ minWidth: ts(24), margin: ts(2) })}
Loader={({ index }) => (index % 2 ? <EpisodeBox.Loader /> : <ItemGrid.Loader />)}
/>
</View>
)}
</>
);
};