(undefined);
@@ -87,6 +87,10 @@ export const Skeleton = ({
variant === "round" && {
borderRadius: 9999999,
},
+ variant === "fill" && {
+ width: percent(100),
+ height: percent(100),
+ },
],
props,
)}
diff --git a/front/packages/primitives/src/themes/catppuccin.ts b/front/packages/primitives/src/themes/catppuccin.ts
index 7c025a7e..665245dd 100644
--- a/front/packages/primitives/src/themes/catppuccin.ts
+++ b/front/packages/primitives/src/themes/catppuccin.ts
@@ -41,7 +41,7 @@ export const catppuccin: ThemeBuilder = {
subtext: "#6c6f85",
},
variant: {
- background: "#dc8a78",
+ background: "#e6e9ef",
accent: "#d20f39",
divider: "#dd7878",
heading: "#4c4f69",
diff --git a/front/packages/ui/src/browse/index.tsx b/front/packages/ui/src/browse/index.tsx
index 3d23d0eb..2fc1fcea 100644
--- a/front/packages/ui/src/browse/index.tsx
+++ b/front/packages/ui/src/browse/index.tsx
@@ -93,7 +93,7 @@ export const BrowsePage: QueryPage<{ slug?: string }> = ({ slug }) => {
placeholderCount={15}
layout={LayoutComponent.layout}
>
- {(item, key) => }
+ {(item) => }
>
);
diff --git a/front/packages/ui/src/details/episode.tsx b/front/packages/ui/src/details/episode.tsx
new file mode 100644
index 00000000..1629aac4
--- /dev/null
+++ b/front/packages/ui/src/details/episode.tsx
@@ -0,0 +1,115 @@
+/*
+ * 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 .
+ */
+
+import { H6, Image, Link, P, Skeleton, ts } from "@kyoo/primitives";
+import { useTranslation } from "react-i18next";
+import { View } from "react-native";
+import { Layout, WithLoading } from "../fetch";
+import { percent, rem, Stylable, useYoshiki, vw } from "yoshiki/native";
+
+export const episodeDisplayNumber = (
+ episode: {
+ seasonNumber?: number | null;
+ episodeNumber?: number | null;
+ absoluteNumber?: number | null;
+ },
+ def?: string,
+) => {
+ if (typeof episode.seasonNumber === "number" && typeof episode.episodeNumber === "number")
+ return `S${episode.seasonNumber}:E${episode.episodeNumber}`;
+ if (episode.absoluteNumber) return episode.absoluteNumber.toString();
+ return def;
+};
+
+export const EpisodeBox = ({
+ name,
+ overview,
+ thumbnail,
+ isLoading,
+ ...props
+}: WithLoading<{
+ name: string;
+ overview: string;
+ thumbnail?: string | null;
+}> &
+ Stylable) => {
+ return (
+
+
+ {isLoading || {name}
}
+ {isLoading || {overview}
}
+
+ );
+};
+
+export const EpisodeLine = ({
+ slug,
+ displayNumber,
+ name,
+ thumbnail,
+ overview,
+ isLoading,
+ ...props
+}: WithLoading<{
+ slug: string;
+ displayNumber: string;
+ name: string;
+ overview: string;
+ thumbnail?: string | null;
+}> &
+ Stylable) => {
+ const { css } = useYoshiki();
+ const { t } = useTranslation();
+
+ return (
+
+
+ {isLoading ? : displayNumber}
+
+
+
+ {isLoading || {name ?? t("show.episodeNoMetadata")}
}
+ {isLoading || {overview}
}
+
+
+ );
+};
+EpisodeLine.layout = {
+ numColumns: 1,
+ size: 100, //vw(18) / (16 / 9) + ts(2),
+} satisfies Layout;
diff --git a/front/packages/ui/src/details/header.tsx b/front/packages/ui/src/details/header.tsx
index e281b503..62281668 100644
--- a/front/packages/ui/src/details/header.tsx
+++ b/front/packages/ui/src/details/header.tsx
@@ -224,6 +224,7 @@ const Description = ({
theme.user.paragraph,
})}
>
diff --git a/front/packages/ui/src/details/index.tsx b/front/packages/ui/src/details/index.tsx
index bd6594d1..febaaa6d 100644
--- a/front/packages/ui/src/details/index.tsx
+++ b/front/packages/ui/src/details/index.tsx
@@ -19,3 +19,4 @@
*/
export { MovieDetails } from "./movie";
+export { ShowDetails } from "./show";
diff --git a/front/packages/ui/src/details/season.tsx b/front/packages/ui/src/details/season.tsx
new file mode 100644
index 00000000..988c7afb
--- /dev/null
+++ b/front/packages/ui/src/details/season.tsx
@@ -0,0 +1,103 @@
+/*
+ * 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 .
+ */
+
+import { Episode, EpisodeP, QueryIdentifier, Season } from "@kyoo/models";
+import { Container, SwitchVariant } from "@kyoo/primitives";
+import Svg, { SvgProps, Path } from "react-native-svg";
+import { Stylable } from "yoshiki/native";
+import { View } from "react-native";
+import { InfiniteFetch } from "../fetch-infinite";
+import { episodeDisplayNumber, EpisodeLine } from "./episode";
+import { useTranslation } from "react-i18next";
+
+const EpisodeGrid = ({ slug, season }: { slug: string; season: string | number }) => {
+ const { t } = useTranslation();
+
+ return (
+
+ {(item) => (
+
+ )}
+
+ );
+};
+
+EpisodeGrid.query = (slug: string, season: string | number): QueryIdentifier => ({
+ parser: EpisodeP,
+ path: ["shows", slug, "episode"],
+ params: {
+ seasonNumber: season,
+ },
+ infinite: true,
+});
+
+const SvgWave = (props: SvgProps) => (
+
+);
+
+export const SeasonTab = ({
+ slug,
+ season,
+ ...props
+}: { slug: string; season: number | string } & Stylable) => {
+ // TODO: handle absolute number only shows (without seasons)
+ return (
+
+ {({ css, theme }) => (
+
+
+ theme.background }, props)}>
+
+ {/* setSeason(i)} aria-label="List of seasons"> */}
+ {/* {seasons */}
+ {/* ? seasons.map((x) => ( */}
+ {/* */}
+ {/* )) */}
+ {/* : [...Array(3)].map((_, i) => ( */}
+ {/* } value={i + 1} disabled /> */}
+ {/* ))} */}
+ {/* */}
+
+
+
+
+ )}
+
+ );
+};
diff --git a/front/packages/ui/src/details/show.tsx b/front/packages/ui/src/details/show.tsx
new file mode 100644
index 00000000..70dabb70
--- /dev/null
+++ b/front/packages/ui/src/details/show.tsx
@@ -0,0 +1,54 @@
+/*
+ * 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 .
+ */
+
+import { QueryIdentifier, QueryPage, Show, ShowP } from "@kyoo/models";
+import { Platform, ScrollView } from "react-native";
+import { useYoshiki } from "yoshiki/native";
+import { TransparentLayout } from "../layout";
+import { SeasonTab } from "./season";
+import { Header } from "./header";
+
+const query = (slug: string): QueryIdentifier => ({
+ parser: ShowP,
+ path: ["shows", slug],
+ params: {
+ fields: ["genres", "studio"],
+ },
+});
+
+export const ShowDetails: QueryPage<{ slug: string; season: string }> = ({ slug, season }) => {
+ const { css } = useYoshiki();
+
+ return (
+
+
+ {/* */}
+
+
+ );
+};
+
+ShowDetails.getFetchUrls = ({ slug, season = 1 }) => [
+ query(slug),
+ // ShowStaff.query(slug),
+ // EpisodeGrid.query(slug, season),
+];
+
+ShowDetails.getLayout = TransparentLayout;
diff --git a/front/packages/ui/src/fetch-infinite.tsx b/front/packages/ui/src/fetch-infinite.tsx
index 86120a09..c4854fec 100644
--- a/front/packages/ui/src/fetch-infinite.tsx
+++ b/front/packages/ui/src/fetch-infinite.tsx
@@ -19,10 +19,10 @@
*/
import { Page, QueryIdentifier, useInfiniteFetch } from "@kyoo/models";
-import { useBreakpointMap } from "@kyoo/primitives";
+import { useBreakpointMap, HR } from "@kyoo/primitives";
import { FlashList } from "@shopify/flash-list";
import { ReactElement } from "react";
-import { ErrorView, Layout, WithLoading } from "./fetch";
+import { EmptyView, ErrorView, Layout, WithLoading } from "./fetch";
export const InfiniteFetch = ({
query,
@@ -30,6 +30,8 @@ export const InfiniteFetch = ({
horizontal = false,
children,
layout,
+ empty,
+ divider = false,
...props
}: {
query: QueryIdentifier;
@@ -38,9 +40,10 @@ export const InfiniteFetch = ({
horizontal?: boolean;
children: (
item: Data extends Page ? WithLoading- : WithLoading,
- key: string | undefined,
i: number,
) => ReactElement | null;
+ empty?: string | JSX.Element;
+ divider?: boolean | JSX.Element;
}): JSX.Element | null => {
if (!query.infinite) console.warn("A non infinite query was passed to an InfiniteFetch.");
@@ -49,12 +52,19 @@ export const InfiniteFetch = ({
useInfiniteFetch(query);
if (error) return ;
+ if (empty && items && items.length === 0) {
+ if (typeof empty !== "string") return empty;
+ return ;
+ }
return (
- children({ isLoading: false, ...item } as any, undefined, index)
- }
+ renderItem={({ item, index }) => (
+ <>
+ {(divider === true && index !== 0) ?
: divider}
+ {children({ isLoading: false, ...item } as any, index)}
+ >
+ )}
data={
hasNextPage
? [
diff --git a/front/packages/ui/src/fetch-infinite.web.tsx b/front/packages/ui/src/fetch-infinite.web.tsx
index 72881fc3..44f0d859 100644
--- a/front/packages/ui/src/fetch-infinite.web.tsx
+++ b/front/packages/ui/src/fetch-infinite.web.tsx
@@ -19,16 +19,17 @@
*/
import { Page, QueryIdentifier, useInfiniteFetch } from "@kyoo/models";
-import { ReactElement, useRef } from "react";
+import { HR } from "@kyoo/primitives";
+import { Fragment, ReactElement, useRef } from "react";
import { Stylable, useYoshiki } from "yoshiki";
-import { ErrorView, Layout, WithLoading } from "./fetch";
+import { EmptyView, ErrorView, Layout, WithLoading } from "./fetch";
const InfiniteScroll = ({
children,
loader,
layout = "vertical",
loadMore,
- hasMore,
+ hasMore = true,
isFetching,
...props
}: {
@@ -91,6 +92,8 @@ export const InfiniteFetch = ({
children,
layout,
horizontal = false,
+ empty,
+ divider = false,
...props
}: {
query: QueryIdentifier;
@@ -99,9 +102,10 @@ export const InfiniteFetch = ({
horizontal?: boolean;
children: (
item: Data extends Page ? WithLoading- : WithLoading,
- key: string | undefined,
i: number,
) => ReactElement | null;
+ empty?: string | JSX.Element;
+ divider?: boolean | JSX.Element;
}): JSX.Element | null => {
if (!query.infinite) console.warn("A non infinite query was passed to an InfiniteFetch.");
@@ -109,6 +113,10 @@ export const InfiniteFetch = ({
const grid = layout.numColumns !== 1;
if (error) return ;
+ if (empty && items && items.length === 0) {
+ if (typeof empty !== "string") return empty;
+ return ;
+ }
return (
({
loadMore={fetchNextPage}
hasMore={hasNextPage!}
isFetching={isFetching}
- loader={[...Array(12)].map((_, i) => children({ isLoading: true } as any, i.toString(), i))}
+ loader={[...Array(12)].map((_, i) => (
+
+ {(divider === true && i !== 0) ?
: divider}
+ {children({ isLoading: true } as any, i)}
+
+ ))}
{...props}
>
- {items?.map((item, i) =>
- children({ ...item, isLoading: false } as any, (item as any).id?.toString(), i),
- )}
+ {items?.map((item, i) => (
+
+ {(divider === true && i !== 0) ?
: divider}
+ {children({ ...item, isLoading: false } as any, i)}
+
+ ))}
);
};
diff --git a/front/packages/ui/src/fetch.tsx b/front/packages/ui/src/fetch.tsx
index e912551c..7aa689e6 100644
--- a/front/packages/ui/src/fetch.tsx
+++ b/front/packages/ui/src/fetch.tsx
@@ -77,3 +77,19 @@ export const ErrorView = ({ error }: { error: KyooErrors }) => {
);
};
+
+export const EmptyView = ({ message }: { message: string }) => {
+ const { css } = useYoshiki();
+
+ return (
+
+ theme.heading })}>{message}
+
+ );
+};
diff --git a/front/packages/ui/src/index.ts b/front/packages/ui/src/index.ts
index fb01a8a7..56c22c59 100644
--- a/front/packages/ui/src/index.ts
+++ b/front/packages/ui/src/index.ts
@@ -20,4 +20,4 @@
export * from "./navbar";
export { BrowsePage } from "./browse";
-export { MovieDetails } from "./details";
+export { MovieDetails, ShowDetails } from "./details";
diff --git a/front/packages/ui/tsconfig.json b/front/packages/ui/tsconfig.json
index cafc1ac7..75de2740 100755
--- a/front/packages/ui/tsconfig.json
+++ b/front/packages/ui/tsconfig.json
@@ -15,11 +15,7 @@
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
- "incremental": true,
- "baseUrl": ".",
- "paths": {
- "~/*": ["src/*"]
- }
+ "incremental": true
},
"include": ["**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]