mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-06-01 04:34:50 -04:00
Add season header
This commit is contained in:
parent
3b84161ec5
commit
67deef897f
@ -37,26 +37,26 @@ const kyooUrl =
|
|||||||
Platform.OS !== "web"
|
Platform.OS !== "web"
|
||||||
? process.env.PUBLIC_BACK_URL
|
? process.env.PUBLIC_BACK_URL
|
||||||
: typeof window === "undefined"
|
: typeof window === "undefined"
|
||||||
? process.env.KYOO_URL ?? "http://localhost:5000"
|
? process.env.KYOO_URL ?? "http://localhost:5000"
|
||||||
: "/api";
|
: "/api";
|
||||||
|
|
||||||
export let kyooApiUrl: string | null = kyooUrl || null;
|
export let kyooApiUrl: string | null = kyooUrl || null;
|
||||||
|
|
||||||
export const setApiUrl = (apiUrl: string) => {
|
export const setApiUrl = (apiUrl: string) => {
|
||||||
kyooApiUrl = apiUrl;
|
kyooApiUrl = apiUrl;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const queryFn = async <Data,>(
|
export const queryFn = async <Data,>(
|
||||||
context:
|
context:
|
||||||
| QueryFunctionContext
|
| QueryFunctionContext
|
||||||
| {
|
| {
|
||||||
path: (string | false | undefined | null)[];
|
path: (string | false | undefined | null)[];
|
||||||
body?: object;
|
body?: object;
|
||||||
method: "GET" | "POST" | "DELETE";
|
method: "GET" | "POST" | "DELETE";
|
||||||
authenticated?: boolean;
|
authenticated?: boolean;
|
||||||
apiUrl?: string;
|
apiUrl?: string;
|
||||||
abortSignal?: AbortSignal;
|
abortSignal?: AbortSignal;
|
||||||
},
|
},
|
||||||
type?: z.ZodType<Data>,
|
type?: z.ZodType<Data>,
|
||||||
token?: string | null,
|
token?: string | null,
|
||||||
): Promise<Data> => {
|
): Promise<Data> => {
|
||||||
@ -72,8 +72,8 @@ export const queryFn = async <Data,>(
|
|||||||
"path" in context
|
"path" in context
|
||||||
? context.path.filter((x) => x)
|
? context.path.filter((x) => x)
|
||||||
: context.pageParam
|
: context.pageParam
|
||||||
? [context.pageParam]
|
? [context.pageParam]
|
||||||
: (context.queryKey.filter((x, i) => x && i) as string[]),
|
: (context.queryKey.filter((x, i) => x && i) as string[]),
|
||||||
)
|
)
|
||||||
.join("/")
|
.join("/")
|
||||||
.replace("/?", "?");
|
.replace("/?", "?");
|
||||||
@ -105,7 +105,13 @@ export const queryFn = async <Data,>(
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
data = { errors: [error] } as KyooErrors;
|
data = { errors: [error] } as KyooErrors;
|
||||||
}
|
}
|
||||||
console.log(`Invalid response (${"method" in context && context.method ? context.method : "GET"} ${path}):`, data, resp.status);
|
console.log(
|
||||||
|
`Invalid response (${
|
||||||
|
"method" in context && context.method ? context.method : "GET"
|
||||||
|
} ${path}):`,
|
||||||
|
data,
|
||||||
|
resp.status,
|
||||||
|
);
|
||||||
throw data as KyooErrors;
|
throw data as KyooErrors;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +130,11 @@ export const queryFn = async <Data,>(
|
|||||||
const parsed = await type.safeParseAsync(data);
|
const parsed = await type.safeParseAsync(data);
|
||||||
if (!parsed.success) {
|
if (!parsed.success) {
|
||||||
console.log("Parse error: ", parsed.error);
|
console.log("Parse error: ", parsed.error);
|
||||||
throw { errors: ["Invalid response from kyoo. Possible version mismatch between the server and the application."] } as KyooErrors;
|
throw {
|
||||||
|
errors: [
|
||||||
|
"Invalid response from kyoo. Possible version mismatch between the server and the application.",
|
||||||
|
],
|
||||||
|
} as KyooErrors;
|
||||||
}
|
}
|
||||||
return parsed.data;
|
return parsed.data;
|
||||||
};
|
};
|
||||||
@ -141,11 +151,11 @@ export const createQueryClient = () =>
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export type QueryIdentifier<T = unknown> = {
|
export type QueryIdentifier<T = unknown, Ret = T> = {
|
||||||
parser: z.ZodType<T, z.ZodTypeDef, any>;
|
parser: z.ZodType<T, z.ZodTypeDef, any>;
|
||||||
path: (string | undefined)[];
|
path: (string | undefined)[];
|
||||||
params?: { [query: string]: boolean | number | string | string[] | undefined };
|
params?: { [query: string]: boolean | number | string | string[] | undefined };
|
||||||
infinite?: boolean;
|
infinite?: boolean | { value: true; map?: (x: T[]) => Ret[] };
|
||||||
/**
|
/**
|
||||||
* A custom get next function if the infinite query is not a page.
|
* A custom get next function if the infinite query is not a page.
|
||||||
*/
|
*/
|
||||||
@ -159,7 +169,7 @@ export type QueryPage<Props = {}> = ComponentType<Props> & {
|
|||||||
| { Layout: QueryPage<{ page: ReactElement }>; props: object };
|
| { Layout: QueryPage<{ page: ReactElement }>; props: object };
|
||||||
};
|
};
|
||||||
|
|
||||||
const toQueryKey = <Data,>(query: QueryIdentifier<Data>) => {
|
const toQueryKey = <Data, Ret>(query: QueryIdentifier<Data, Ret>) => {
|
||||||
const prefix = Platform.OS !== "web" ? [kyooApiUrl] : [""];
|
const prefix = Platform.OS !== "web" ? [kyooApiUrl] : [""];
|
||||||
|
|
||||||
if (query.params) {
|
if (query.params) {
|
||||||
@ -184,8 +194,8 @@ export const useFetch = <Data,>(query: QueryIdentifier<Data>) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useInfiniteFetch = <Data,>(
|
export const useInfiniteFetch = <Data, Ret>(
|
||||||
query: QueryIdentifier<Data>,
|
query: QueryIdentifier<Data, Ret>,
|
||||||
options?: Partial<UseInfiniteQueryOptions<Data[], KyooErrors>>,
|
options?: Partial<UseInfiniteQueryOptions<Data[], KyooErrors>>,
|
||||||
) => {
|
) => {
|
||||||
if (query.getNext) {
|
if (query.getNext) {
|
||||||
@ -196,7 +206,7 @@ export const useInfiniteFetch = <Data,>(
|
|||||||
getNextPageParam: query.getNext,
|
getNextPageParam: query.getNext,
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
return { ...ret, items: ret.data?.pages.flatMap((x) => x) };
|
return { ...ret, items: ret.data?.pages.flatMap((x) => x) as unknown as Ret[] | undefined };
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
const ret = useInfiniteQuery<Page<Data>, KyooErrors>({
|
const ret = useInfiniteQuery<Page<Data>, KyooErrors>({
|
||||||
@ -204,7 +214,14 @@ export const useInfiniteFetch = <Data,>(
|
|||||||
queryFn: (ctx) => queryFn(ctx, Paged(query.parser)),
|
queryFn: (ctx) => queryFn(ctx, Paged(query.parser)),
|
||||||
getNextPageParam: (page: Page<Data>) => page?.next || undefined,
|
getNextPageParam: (page: Page<Data>) => page?.next || undefined,
|
||||||
});
|
});
|
||||||
return { ...ret, items: ret.data?.pages.flatMap((x) => x.items) };
|
const items = ret.data?.pages.flatMap((x) => x.items);
|
||||||
|
return {
|
||||||
|
...ret,
|
||||||
|
items:
|
||||||
|
items && typeof query.infinite === "object" && query.infinite.map
|
||||||
|
? query.infinite.map(items)
|
||||||
|
: (items as unknown as Ret[] | undefined),
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchQuery = async (queries: QueryIdentifier[], authToken?: string | null) => {
|
export const fetchQuery = async (queries: QueryIdentifier[], authToken?: string | null) => {
|
||||||
|
@ -18,14 +18,83 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Episode, EpisodeP, QueryIdentifier } from "@kyoo/models";
|
import {
|
||||||
import { Container } from "@kyoo/primitives";
|
Episode,
|
||||||
import { Stylable } from "yoshiki/native";
|
EpisodeP,
|
||||||
|
QueryIdentifier,
|
||||||
|
Season,
|
||||||
|
SeasonP,
|
||||||
|
useInfiniteFetch,
|
||||||
|
} from "@kyoo/models";
|
||||||
|
import { Skeleton, H6, HR, P, ts, Menu, IconButton, tooltip } from "@kyoo/primitives";
|
||||||
|
import { rem, useYoshiki } from "yoshiki/native";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import { InfiniteFetch } from "../fetch-infinite";
|
import { InfiniteFetch } from "../fetch-infinite";
|
||||||
import { episodeDisplayNumber, EpisodeLine } from "./episode";
|
import { episodeDisplayNumber, EpisodeLine } from "./episode";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { ComponentType } from "react";
|
import { ComponentType } from "react";
|
||||||
|
import MenuIcon from "@material-symbols/svg-400/rounded/menu-fill.svg";
|
||||||
|
|
||||||
|
export const SeasonHeader = ({
|
||||||
|
isLoading,
|
||||||
|
seasonNumber,
|
||||||
|
name,
|
||||||
|
seasons,
|
||||||
|
slug,
|
||||||
|
}: {
|
||||||
|
isLoading: boolean;
|
||||||
|
seasonNumber?: number;
|
||||||
|
name?: string;
|
||||||
|
seasons?: Season[];
|
||||||
|
slug: string;
|
||||||
|
}) => {
|
||||||
|
const { css } = useYoshiki();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View id={`season-${seasonNumber}`}>
|
||||||
|
<View {...css({ flexDirection: "row", marginX: ts(1) })}>
|
||||||
|
<P
|
||||||
|
{...css({
|
||||||
|
width: rem(4),
|
||||||
|
flexShrink: 0,
|
||||||
|
marginX: ts(1),
|
||||||
|
textAlign: "center",
|
||||||
|
fontSize: rem(1.5),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{isLoading ? <Skeleton variant="filltext" /> : seasonNumber}
|
||||||
|
</P>
|
||||||
|
<H6
|
||||||
|
aria-level={2}
|
||||||
|
{...css({ marginX: ts(1), fontSize: rem(1.5), flexGrow: 1, flexShrink: 1 })}
|
||||||
|
>
|
||||||
|
{isLoading ? <Skeleton /> : name}
|
||||||
|
</H6>
|
||||||
|
<Menu Trigger={IconButton} icon={MenuIcon} {...tooltip(t("show.jumpToSeason"))}>
|
||||||
|
{seasons?.map((x) => (
|
||||||
|
<Menu.Item
|
||||||
|
key={x.seasonNumber}
|
||||||
|
label={`${x.seasonNumber}: ${x.name}`}
|
||||||
|
href={`/show/${slug}?season=${x.seasonNumber}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
</View>
|
||||||
|
<HR />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
SeasonHeader.query = (slug: string): QueryIdentifier<Season> => ({
|
||||||
|
parser: SeasonP,
|
||||||
|
path: ["shows", slug, "seasons"],
|
||||||
|
params: {
|
||||||
|
// Fetch all seasons at one, there won't be hundred of thems anyways.
|
||||||
|
limit: 0,
|
||||||
|
},
|
||||||
|
infinite: true,
|
||||||
|
});
|
||||||
|
|
||||||
export const EpisodeList = <Props,>({
|
export const EpisodeList = <Props,>({
|
||||||
slug,
|
slug,
|
||||||
@ -39,6 +108,9 @@ export const EpisodeList = <Props,>({
|
|||||||
headerProps: Props;
|
headerProps: Props;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { items: seasons, error } = useInfiniteFetch(SeasonHeader.query(slug));
|
||||||
|
|
||||||
|
if (error) console.error("Could not fetch seasons", error);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InfiniteFetch
|
<InfiniteFetch
|
||||||
@ -50,53 +122,48 @@ export const EpisodeList = <Props,>({
|
|||||||
Header={Header}
|
Header={Header}
|
||||||
headerProps={headerProps}
|
headerProps={headerProps}
|
||||||
>
|
>
|
||||||
{(item) => (
|
{(item) => {
|
||||||
<EpisodeLine
|
const sea = item ? seasons?.find((x) => x.seasonNumber === item.seasonNumber) : null;
|
||||||
{...item}
|
return (
|
||||||
displayNumber={item.isLoading ? undefined! : episodeDisplayNumber(item)!}
|
<>
|
||||||
/>
|
{item.firstOfSeason && (
|
||||||
)}
|
<SeasonHeader
|
||||||
|
isLoading={!sea}
|
||||||
|
name={sea?.name}
|
||||||
|
seasonNumber={sea?.seasonNumber}
|
||||||
|
seasons={seasons}
|
||||||
|
slug={slug}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<EpisodeLine
|
||||||
|
{...item}
|
||||||
|
displayNumber={item.isLoading ? undefined! : episodeDisplayNumber(item)!}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
</InfiniteFetch>
|
</InfiniteFetch>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
EpisodeList.query = (slug: string, season: string | number): QueryIdentifier<Episode> => ({
|
EpisodeList.query = (
|
||||||
|
slug: string,
|
||||||
|
season: string | number,
|
||||||
|
): QueryIdentifier<Episode, Episode & { firstOfSeason?: boolean }> => ({
|
||||||
parser: EpisodeP,
|
parser: EpisodeP,
|
||||||
path: ["shows", slug, "episode"],
|
path: ["shows", slug, "episode"],
|
||||||
params: {
|
params: {
|
||||||
seasonNumber: season,
|
seasonNumber: season ? `gte:${season}` : undefined,
|
||||||
|
},
|
||||||
|
infinite: {
|
||||||
|
value: true,
|
||||||
|
map: (episodes) => {
|
||||||
|
let currentSeason: number | null = null;
|
||||||
|
return episodes.map((x) => {
|
||||||
|
if (x.seasonNumber === currentSeason) return x;
|
||||||
|
currentSeason = x.seasonNumber;
|
||||||
|
return { ...x, firstOfSeason: true };
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
infinite: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const SeasonTab = ({
|
|
||||||
slug,
|
|
||||||
season,
|
|
||||||
...props
|
|
||||||
}: { slug: string; season: number | string } & Stylable) => {
|
|
||||||
// TODO: handle absolute number only shows (without seasons)
|
|
||||||
return null;
|
|
||||||
return (
|
|
||||||
<View>
|
|
||||||
<Container>
|
|
||||||
{/* <Tabs value={season} onChange={(_, i) => setSeason(i)} aria-label="List of seasons"> */}
|
|
||||||
{/* {seasons */}
|
|
||||||
{/* ? seasons.map((x) => ( */}
|
|
||||||
{/* <Tab */}
|
|
||||||
{/* key={x.seasonNumber} */}
|
|
||||||
{/* label={x.name} */}
|
|
||||||
{/* value={x.seasonNumber} */}
|
|
||||||
{/* component={Link} */}
|
|
||||||
{/* to={{ query: { ...router.query, season: x.seasonNumber } }} */}
|
|
||||||
{/* shallow */}
|
|
||||||
{/* replace */}
|
|
||||||
{/* /> */}
|
|
||||||
{/* )) */}
|
|
||||||
{/* : [...Array(3)].map((_, i) => ( */}
|
|
||||||
{/* <Tab key={i} label={<Skeleton width="5rem" />} value={i + 1} disabled /> */}
|
|
||||||
{/* ))} */}
|
|
||||||
{/* </Tabs> */}
|
|
||||||
</Container>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
@ -22,7 +22,7 @@ import { QueryIdentifier, QueryPage, Show, ShowP } from "@kyoo/models";
|
|||||||
import { Platform, View, ViewProps } from "react-native";
|
import { Platform, View, ViewProps } from "react-native";
|
||||||
import { percent, useYoshiki } from "yoshiki/native";
|
import { percent, useYoshiki } from "yoshiki/native";
|
||||||
import { DefaultLayout } from "../layout";
|
import { DefaultLayout } from "../layout";
|
||||||
import { EpisodeList } from "./season";
|
import { EpisodeList, SeasonHeader } from "./season";
|
||||||
import { Header } from "./header";
|
import { Header } from "./header";
|
||||||
import Svg, { Path, SvgProps } from "react-native-svg";
|
import Svg, { Path, SvgProps } from "react-native-svg";
|
||||||
import { Container } from "@kyoo/primitives";
|
import { Container } from "@kyoo/primitives";
|
||||||
@ -75,7 +75,6 @@ const ShowHeader = forwardRef<View, ViewProps & { slug: string }>(function ShowH
|
|||||||
fill={theme.variant.background}
|
fill={theme.variant.background}
|
||||||
{...css({ flexShrink: 0, flexGrow: 1, display: "flex" })}
|
{...css({ flexShrink: 0, flexGrow: 1, display: "flex" })}
|
||||||
/>
|
/>
|
||||||
{/* <SeasonTab slug={slug} season={season} /> */}
|
|
||||||
<View {...css({ bg: theme.variant.background })}>
|
<View {...css({ bg: theme.variant.background })}>
|
||||||
<Container>{children}</Container>
|
<Container>{children}</Container>
|
||||||
</View>
|
</View>
|
||||||
@ -104,6 +103,7 @@ ShowDetails.getFetchUrls = ({ slug, season }) => [
|
|||||||
query(slug),
|
query(slug),
|
||||||
// ShowStaff.query(slug),
|
// ShowStaff.query(slug),
|
||||||
EpisodeList.query(slug, season),
|
EpisodeList.query(slug, season),
|
||||||
|
SeasonHeader.query(slug),
|
||||||
];
|
];
|
||||||
|
|
||||||
ShowDetails.getLayout = { Layout: DefaultLayout, props: { transparent: true } };
|
ShowDetails.getLayout = { Layout: DefaultLayout, props: { transparent: true } };
|
||||||
|
@ -24,7 +24,7 @@ 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 } from "./fetch";
|
||||||
|
|
||||||
export const InfiniteFetch = <Data, Props>({
|
export const InfiniteFetch = <Data, Props, _>({
|
||||||
query,
|
query,
|
||||||
placeholderCount = 15,
|
placeholderCount = 15,
|
||||||
incremental = false,
|
incremental = false,
|
||||||
@ -37,7 +37,7 @@ export const InfiniteFetch = <Data, Props>({
|
|||||||
headerProps,
|
headerProps,
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
query: QueryIdentifier<Data>;
|
query: QueryIdentifier<_, Data>;
|
||||||
placeholderCount?: number;
|
placeholderCount?: number;
|
||||||
layout: Layout;
|
layout: Layout;
|
||||||
horizontal?: boolean;
|
horizontal?: boolean;
|
||||||
@ -49,7 +49,7 @@ export const InfiniteFetch = <Data, Props>({
|
|||||||
incremental?: boolean;
|
incremental?: boolean;
|
||||||
divider?: boolean | ComponentType;
|
divider?: boolean | ComponentType;
|
||||||
Header?: ComponentType<Props & { children: JSX.Element }> | ReactElement;
|
Header?: ComponentType<Props & { children: JSX.Element }> | ReactElement;
|
||||||
headerProps?: Props
|
headerProps?: Props;
|
||||||
}): JSX.Element | null => {
|
}): JSX.Element | null => {
|
||||||
if (!query.infinite) console.warn("A non infinite query was passed to an InfiniteFetch.");
|
if (!query.infinite) console.warn("A non infinite query was passed to an InfiniteFetch.");
|
||||||
|
|
||||||
@ -69,15 +69,14 @@ export const InfiniteFetch = <Data, Props>({
|
|||||||
return <EmptyView message={empty} />;
|
return <EmptyView message={empty} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (incremental)
|
if (incremental) items ??= oldItems.current;
|
||||||
items ??= oldItems.current;
|
|
||||||
const count = items ? numColumns - (items.length % numColumns) : placeholderCount;
|
const count = items ? numColumns - (items.length % numColumns) : placeholderCount;
|
||||||
const placeholders = [...Array(count === 0 ? numColumns : count)].map(
|
const placeholders = [...Array(count === 0 ? numColumns : count)].map(
|
||||||
(_, i) => ({ id: `gen${i}`, isLoading: true } as Data),
|
(_, i) => ({ id: `gen${i}`, isLoading: true }) as Data,
|
||||||
);
|
);
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (headerProps && !isValidElement(Header)) Header = <Header {...headerProps} />
|
if (headerProps && !isValidElement(Header)) Header = <Header {...headerProps} />;
|
||||||
return (
|
return (
|
||||||
<FlashList
|
<FlashList
|
||||||
renderItem={({ item, index }) => children({ isLoading: false, ...item } as any, index)}
|
renderItem={({ item, index }) => children({ isLoading: false, ...item } as any, index)}
|
||||||
|
@ -66,7 +66,8 @@ const InfiniteScroll = <Props,>({
|
|||||||
{
|
{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "flex-start",
|
alignItems: "flex-start",
|
||||||
overflow: "auto",
|
overflowX: "hidden",
|
||||||
|
overflowY: "auto",
|
||||||
},
|
},
|
||||||
layout == "vertical" && {
|
layout == "vertical" && {
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
@ -101,7 +102,7 @@ const InfiniteScroll = <Props,>({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const InfiniteFetch = <Data,>({
|
export const InfiniteFetch = <Data, _>({
|
||||||
query,
|
query,
|
||||||
incremental = false,
|
incremental = false,
|
||||||
placeholderCount = 15,
|
placeholderCount = 15,
|
||||||
@ -113,7 +114,7 @@ export const InfiniteFetch = <Data,>({
|
|||||||
Header,
|
Header,
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
query: QueryIdentifier<Data>;
|
query: QueryIdentifier<_, Data>;
|
||||||
incremental?: boolean;
|
incremental?: boolean;
|
||||||
placeholderCount?: number;
|
placeholderCount?: number;
|
||||||
layout: Layout;
|
layout: Layout;
|
||||||
|
@ -10,7 +10,8 @@
|
|||||||
"noOverview": "No overview available",
|
"noOverview": "No overview available",
|
||||||
"episode-none": "There is no episodes in this season",
|
"episode-none": "There is no episodes in this season",
|
||||||
"episodeNoMetadata": "No metadata available",
|
"episodeNoMetadata": "No metadata available",
|
||||||
"tags": "Tags"
|
"tags": "Tags",
|
||||||
|
"jumpToSeason": "Jump to season"
|
||||||
},
|
},
|
||||||
"browse": {
|
"browse": {
|
||||||
"sortby": "Sort by {{key}}",
|
"sortby": "Sort by {{key}}",
|
||||||
|
@ -10,7 +10,8 @@
|
|||||||
"noOverview": "Aucune description disponible",
|
"noOverview": "Aucune description disponible",
|
||||||
"episode-none": "Il n'y a pas d'épisodes dans cette saison",
|
"episode-none": "Il n'y a pas d'épisodes dans cette saison",
|
||||||
"episodeNoMetadata": "Aucune metadonnée disponible",
|
"episodeNoMetadata": "Aucune metadonnée disponible",
|
||||||
"tags": "Tags"
|
"tags": "Tags",
|
||||||
|
"jumpToSeason": "Aller sur une saison"
|
||||||
},
|
},
|
||||||
"browse": {
|
"browse": {
|
||||||
"sortby": "Trier par {{key}}",
|
"sortby": "Trier par {{key}}",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user