mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Split loaders for most items on the main page
This commit is contained in:
parent
2756397898
commit
393c58b10a
@ -213,7 +213,7 @@ export const ItemGrid = ({
|
||||
);
|
||||
};
|
||||
|
||||
ItemGrid.Loader = (props: Stylable) => {
|
||||
ItemGrid.Loader = (props: object) => {
|
||||
const { css } = useYoshiki();
|
||||
|
||||
return (
|
||||
|
@ -87,7 +87,7 @@ export const BrowsePage: QueryPage = () => {
|
||||
/>
|
||||
}
|
||||
Render={({ item }) => <LayoutComponent {...itemMap(item)} />}
|
||||
Loader={() => <LayoutComponent.Loader />}
|
||||
Loader={LayoutComponent.Loader}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -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 (
|
||||
|
@ -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,23 +67,21 @@ export const EpisodeBox = ({
|
||||
name,
|
||||
overview,
|
||||
thumbnail,
|
||||
isLoading,
|
||||
href,
|
||||
watchedPercent,
|
||||
watchedStatus,
|
||||
...props
|
||||
}: Stylable &
|
||||
WithLoading<{
|
||||
slug: string;
|
||||
// if show slug is null, disable "Go to show" in the context menu
|
||||
showSlug: string | null;
|
||||
name: string | null;
|
||||
overview: string | null;
|
||||
href: string;
|
||||
thumbnail?: ImageProps["src"] | null;
|
||||
watchedPercent: number | null;
|
||||
watchedStatus: WatchStatusV | null;
|
||||
}>) => {
|
||||
}: Stylable & {
|
||||
slug: string;
|
||||
// if show slug is null, disable "Go to show" in the context menu
|
||||
showSlug: string | null;
|
||||
name: string | null;
|
||||
overview: string | null;
|
||||
href: string;
|
||||
thumbnail?: ImageProps["src"] | null;
|
||||
watchedPercent: number | null;
|
||||
watchedStatus: WatchStatusV | null;
|
||||
}) => {
|
||||
const [moreOpened, setMoreOpened] = useState(false);
|
||||
const { css } = useYoshiki("episodebox");
|
||||
const { t } = useTranslation();
|
||||
@ -126,58 +124,72 @@ 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}
|
||||
status={watchedStatus}
|
||||
isOpen={moreOpened}
|
||||
setOpen={(v) => setMoreOpened(v)}
|
||||
{...css([
|
||||
{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
right: 0,
|
||||
bg: (theme) => theme.darkOverlay,
|
||||
},
|
||||
"more",
|
||||
Platform.OS === "web" && moreOpened && { display: important("flex") },
|
||||
])}
|
||||
/>
|
||||
)}
|
||||
<EpisodesContext
|
||||
slug={slug}
|
||||
showSlug={showSlug}
|
||||
status={watchedStatus}
|
||||
isOpen={moreOpened}
|
||||
setOpen={(v) => setMoreOpened(v)}
|
||||
{...css([
|
||||
{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
right: 0,
|
||||
bg: (theme) => theme.darkOverlay,
|
||||
},
|
||||
"more",
|
||||
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({
|
||||
marginTop: 0,
|
||||
textAlign: "center",
|
||||
})}
|
||||
>
|
||||
{overview}
|
||||
</SubP>
|
||||
)}
|
||||
</Skeleton>
|
||||
<P {...css([{ marginY: 0, textAlign: "center" }, "title"])}>
|
||||
{name ?? t("show.episodeNoMetadata")}
|
||||
</P>
|
||||
<SubP
|
||||
numberOfLines={3}
|
||||
{...css({
|
||||
marginTop: 0,
|
||||
textAlign: "center",
|
||||
})}
|
||||
>
|
||||
{overview}
|
||||
</SubP>
|
||||
</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,
|
||||
|
@ -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";
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -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) ? (
|
||||
Render={({ item }) => {
|
||||
if (item.kind === "episode") {
|
||||
return (
|
||||
<EpisodeBox
|
||||
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" })}
|
||||
/>
|
||||
);
|
||||
}
|
||||
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}
|
||||
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"}
|
||||
/>
|
||||
) : (
|
||||
<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}
|
||||
// TODO: Move this into the ItemList (using getItemSize)
|
||||
// @ts-expect-error This is a web only property
|
||||
{...css({ gridColumnEnd: "span 2" })}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</InfiniteFetch>
|
||||
);
|
||||
}}
|
||||
Loader={({ index }) => (index % 2 ? <EpisodeBox.Loader /> : <ItemGrid.Loader />)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -39,55 +39,10 @@ export const WatchlistList = () => {
|
||||
const { css } = useYoshiki();
|
||||
const account = useAccount();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header title={t("home.watchlist")} />
|
||||
{account ? (
|
||||
<InfiniteFetch
|
||||
query={WatchlistList.query()}
|
||||
layout={{ ...ItemGrid.layout, layout: "horizontal" }}
|
||||
getItemType={(x, i) =>
|
||||
(x.kind === "show" && x.watchStatus?.nextEpisode) || (x.isLoading && i % 2)
|
||||
? "episode"
|
||||
: "item"
|
||||
}
|
||||
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) ? (
|
||||
<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}
|
||||
// TODO: Move this into the ItemList (using getItemSize)
|
||||
// @ts-expect-error This is a web only property
|
||||
{...css({ gridColumnEnd: "span 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}
|
||||
unseenEpisodesCount={x.kind === "show" ? x.watchStatus?.unseenEpisodesCount : null}
|
||||
type={x.kind}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</InfiniteFetch>
|
||||
) : (
|
||||
if (!account) {
|
||||
return (
|
||||
<>
|
||||
<Header title={t("home.watchlist")} />
|
||||
<View {...css({ justifyContent: "center", alignItems: "center" })}>
|
||||
<P>{t("home.watchlistLogin")}</P>
|
||||
<Button
|
||||
@ -96,7 +51,60 @@ export const WatchlistList = () => {
|
||||
{...css({ minWidth: ts(24), margin: ts(2) })}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header title={t("home.watchlist")} />
|
||||
<InfiniteFetch
|
||||
query={WatchlistList.query()}
|
||||
layout={{ ...ItemGrid.layout, layout: "horizontal" }}
|
||||
getItemType={(x, i) =>
|
||||
(x.kind === "show" && x.watchStatus?.nextEpisode) || (x.isLoading && i % 2)
|
||||
? "episode"
|
||||
: "item"
|
||||
}
|
||||
getItemSize={(kind) => (kind === "episode" ? 2 : 1)}
|
||||
empty={t("home.none")}
|
||||
Render={({ item }) => {
|
||||
const episode = item.kind === "show" ? item.watchStatus?.nextEpisode : null;
|
||||
if (episode) {
|
||||
return (
|
||||
<EpisodeBox
|
||||
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
|
||||
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}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
Loader={({ index }) => (index % 2 ? <EpisodeBox.Loader /> : <ItemGrid.Loader />)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user