mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-06-03 13:44:33 -04:00
Add more menus everywhere
This commit is contained in:
parent
c67a11b8b6
commit
e8bd663ad7
@ -67,6 +67,10 @@ export const BaseEpisodeP = withImages(
|
|||||||
*/
|
*/
|
||||||
hls: z.string().transform(imageFn),
|
hls: z.string().transform(imageFn),
|
||||||
}),
|
}),
|
||||||
|
/**
|
||||||
|
* The id of the show containing this episode
|
||||||
|
*/
|
||||||
|
showId: z.string(),
|
||||||
}),
|
}),
|
||||||
"episodes",
|
"episodes",
|
||||||
)
|
)
|
||||||
|
@ -21,6 +21,10 @@
|
|||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
import { px } from "yoshiki/native";
|
import { px } from "yoshiki/native";
|
||||||
|
|
||||||
|
export const important = <T,>(value: T): T => {
|
||||||
|
return `${value} !important` as T;
|
||||||
|
}
|
||||||
|
|
||||||
export const ts = (spacing: number) => {
|
export const ts = (spacing: number) => {
|
||||||
return px(spacing * 8);
|
return px(spacing * 8);
|
||||||
};
|
};
|
||||||
|
@ -19,11 +19,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { KyooImage, WatchStatusV } from "@kyoo/models";
|
import { KyooImage, WatchStatusV } from "@kyoo/models";
|
||||||
import { Link, Skeleton, ts, focusReset, P, SubP, PosterBackground, Icon } from "@kyoo/primitives";
|
import { Link, Skeleton, ts, focusReset, P, SubP, PosterBackground, Icon, important } from "@kyoo/primitives";
|
||||||
import { ImageStyle, View } from "react-native";
|
import { ImageStyle, Platform, View } from "react-native";
|
||||||
import { max, percent, px, rem, Stylable, Theme, useYoshiki } from "yoshiki/native";
|
import { max, percent, px, rem, Stylable, Theme, useYoshiki } from "yoshiki/native";
|
||||||
import { Layout, WithLoading } from "../fetch";
|
import { Layout, WithLoading } from "../fetch";
|
||||||
import Done from "@material-symbols/svg-400/rounded/done-fill.svg";
|
import Done from "@material-symbols/svg-400/rounded/done-fill.svg";
|
||||||
|
import { ItemContext } from "../components/context-menus";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
export const ItemWatchStatus = ({
|
export const ItemWatchStatus = ({
|
||||||
watchStatus,
|
watchStatus,
|
||||||
@ -96,6 +98,7 @@ export const ItemProgress = ({ watchPercent }: { watchPercent: number }) => {
|
|||||||
|
|
||||||
export const ItemGrid = ({
|
export const ItemGrid = ({
|
||||||
href,
|
href,
|
||||||
|
slug,
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
subtitle,
|
subtitle,
|
||||||
@ -107,6 +110,7 @@ export const ItemGrid = ({
|
|||||||
...props
|
...props
|
||||||
}: WithLoading<{
|
}: WithLoading<{
|
||||||
href: string;
|
href: string;
|
||||||
|
slug: string;
|
||||||
name: string;
|
name: string;
|
||||||
subtitle?: string;
|
subtitle?: string;
|
||||||
poster?: KyooImage | null;
|
poster?: KyooImage | null;
|
||||||
@ -116,11 +120,13 @@ export const ItemGrid = ({
|
|||||||
unseenEpisodesCount: number | null;
|
unseenEpisodesCount: number | null;
|
||||||
}> &
|
}> &
|
||||||
Stylable<"text">) => {
|
Stylable<"text">) => {
|
||||||
|
const [moreOpened, setMoreOpened] = useState(false);
|
||||||
const { css } = useYoshiki("grid");
|
const { css } = useYoshiki("grid");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href={href}
|
href={moreOpened ? undefined : href}
|
||||||
|
onLongPress={() => setMoreOpened(true)}
|
||||||
{...css(
|
{...css(
|
||||||
{
|
{
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
@ -132,6 +138,9 @@ export const ItemGrid = ({
|
|||||||
borderWidth: ts(0.5),
|
borderWidth: ts(0.5),
|
||||||
borderStyle: "solid",
|
borderStyle: "solid",
|
||||||
},
|
},
|
||||||
|
more: {
|
||||||
|
display: "none",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
fover: {
|
fover: {
|
||||||
self: focusReset,
|
self: focusReset,
|
||||||
@ -141,6 +150,9 @@ export const ItemGrid = ({
|
|||||||
title: {
|
title: {
|
||||||
textDecorationLine: "underline",
|
textDecorationLine: "underline",
|
||||||
},
|
},
|
||||||
|
more: {
|
||||||
|
display: "flex",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
props,
|
props,
|
||||||
@ -156,6 +168,25 @@ export const ItemGrid = ({
|
|||||||
>
|
>
|
||||||
<ItemWatchStatus watchStatus={watchStatus} unseenEpisodesCount={unseenEpisodesCount} />
|
<ItemWatchStatus watchStatus={watchStatus} unseenEpisodesCount={unseenEpisodesCount} />
|
||||||
{type === "movie" && watchPercent && <ItemProgress watchPercent={watchPercent} />}
|
{type === "movie" && watchPercent && <ItemProgress watchPercent={watchPercent} />}
|
||||||
|
{slug && watchStatus !== undefined && type && type !== "collection" && (
|
||||||
|
<ItemContext
|
||||||
|
type={type}
|
||||||
|
slug={slug}
|
||||||
|
status={watchStatus}
|
||||||
|
isOpen={moreOpened}
|
||||||
|
setOpen={(v) => setMoreOpened(v)}
|
||||||
|
{...css([
|
||||||
|
{
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bg: (theme) => theme.dark.background,
|
||||||
|
},
|
||||||
|
"more",
|
||||||
|
Platform.OS === "web" && moreOpened && { display: important("flex") },
|
||||||
|
])}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</PosterBackground>
|
</PosterBackground>
|
||||||
<Skeleton>
|
<Skeleton>
|
||||||
{isLoading || (
|
{isLoading || (
|
||||||
|
@ -45,6 +45,7 @@ export const itemMap = (
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
isLoading: item.isLoading,
|
isLoading: item.isLoading,
|
||||||
|
slug: item.slug,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
subtitle: item.kind !== ItemKind.Collection ? getDisplayDate(item) : undefined,
|
subtitle: item.kind !== ItemKind.Collection ? getDisplayDate(item) : undefined,
|
||||||
href: item.href,
|
href: item.href,
|
||||||
|
@ -28,15 +28,19 @@ import {
|
|||||||
Heading,
|
Heading,
|
||||||
PosterBackground,
|
PosterBackground,
|
||||||
imageBorderRadius,
|
imageBorderRadius,
|
||||||
|
important,
|
||||||
} from "@kyoo/primitives";
|
} from "@kyoo/primitives";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { View } from "react-native";
|
import { Platform, View } from "react-native";
|
||||||
import { percent, px, rem, useYoshiki } from "yoshiki/native";
|
import { percent, px, rem, useYoshiki } from "yoshiki/native";
|
||||||
import { Layout, WithLoading } from "../fetch";
|
import { Layout, WithLoading } from "../fetch";
|
||||||
import { ItemWatchStatus } from "./grid";
|
import { ItemWatchStatus } from "./grid";
|
||||||
|
import { ItemContext } from "../components/context-menus";
|
||||||
|
|
||||||
export const ItemList = ({
|
export const ItemList = ({
|
||||||
href,
|
href,
|
||||||
|
slug,
|
||||||
|
type,
|
||||||
name,
|
name,
|
||||||
subtitle,
|
subtitle,
|
||||||
thumbnail,
|
thumbnail,
|
||||||
@ -47,6 +51,8 @@ export const ItemList = ({
|
|||||||
...props
|
...props
|
||||||
}: WithLoading<{
|
}: WithLoading<{
|
||||||
href: string;
|
href: string;
|
||||||
|
slug: string;
|
||||||
|
type: "movie" | "show" | "collection";
|
||||||
name: string;
|
name: string;
|
||||||
subtitle?: string;
|
subtitle?: string;
|
||||||
poster?: KyooImage | null;
|
poster?: KyooImage | null;
|
||||||
@ -55,7 +61,7 @@ export const ItemList = ({
|
|||||||
unseenEpisodesCount: number | null;
|
unseenEpisodesCount: number | null;
|
||||||
}>) => {
|
}>) => {
|
||||||
const { css } = useYoshiki();
|
const { css } = useYoshiki();
|
||||||
const [isHovered, setHovered] = useState(0);
|
const [moreOpened, setMoreOpened] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ImageBackground
|
<ImageBackground
|
||||||
@ -63,11 +69,8 @@ export const ItemList = ({
|
|||||||
alt={name}
|
alt={name}
|
||||||
quality="medium"
|
quality="medium"
|
||||||
as={Link}
|
as={Link}
|
||||||
href={href ?? ""}
|
href={moreOpened ? undefined : href}
|
||||||
onFocus={() => setHovered((i) => i + 1)}
|
onLongPress={() => setMoreOpened(true)}
|
||||||
onBlur={() => setHovered((i) => i - 1)}
|
|
||||||
onPressIn={() => setHovered((i) => i + 1)}
|
|
||||||
onPressOut={() => setHovered((i) => i - 1)}
|
|
||||||
containerStyle={{
|
containerStyle={{
|
||||||
borderRadius: px(imageBorderRadius),
|
borderRadius: px(imageBorderRadius),
|
||||||
}}
|
}}
|
||||||
@ -83,38 +86,78 @@ export const ItemList = ({
|
|||||||
borderRadius: px(imageBorderRadius),
|
borderRadius: px(imageBorderRadius),
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
marginX: ItemList.layout.gap,
|
marginX: ItemList.layout.gap,
|
||||||
|
child: {
|
||||||
|
more: {
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fover: {
|
||||||
|
title: {
|
||||||
|
textDecorationLine: "underline",
|
||||||
|
},
|
||||||
|
more: {
|
||||||
|
opacity: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
props,
|
props,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
{...css({
|
{...css({
|
||||||
flexDirection: "column",
|
|
||||||
width: { xs: "50%", lg: "30%" },
|
width: { xs: "50%", lg: "30%" },
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Skeleton {...css({ height: rem(2), alignSelf: "center" })}>
|
<View
|
||||||
{isLoading || (
|
{...css({
|
||||||
<Heading
|
flexDirection: "row",
|
||||||
{...css({
|
justifyContent: "center",
|
||||||
textAlign: "center",
|
})}
|
||||||
fontSize: rem(2),
|
>
|
||||||
letterSpacing: rem(0.002),
|
<Skeleton {...css({ height: rem(2), alignSelf: "center" })}>
|
||||||
fontWeight: "900",
|
{isLoading || (
|
||||||
textTransform: "uppercase",
|
<Heading
|
||||||
textDecorationLine: isHovered ? "underline" : "none",
|
{...css([
|
||||||
})}
|
"title",
|
||||||
>
|
{
|
||||||
{name}
|
textAlign: "center",
|
||||||
</Heading>
|
fontSize: rem(2),
|
||||||
|
letterSpacing: rem(0.002),
|
||||||
|
fontWeight: "900",
|
||||||
|
textTransform: "uppercase",
|
||||||
|
},
|
||||||
|
])}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</Heading>
|
||||||
|
)}
|
||||||
|
</Skeleton>
|
||||||
|
{slug && watchStatus !== undefined && type && type !== "collection" && (
|
||||||
|
<ItemContext
|
||||||
|
type={type}
|
||||||
|
slug={slug}
|
||||||
|
status={watchStatus}
|
||||||
|
isOpen={moreOpened}
|
||||||
|
setOpen={(v) => setMoreOpened(v)}
|
||||||
|
{...css([
|
||||||
|
{
|
||||||
|
// I dont know why marginLeft gets overwritten by the margin: px(2) so we important
|
||||||
|
marginLeft: important(ts(2)),
|
||||||
|
bg: (theme) => theme.darkOverlay,
|
||||||
|
},
|
||||||
|
"more",
|
||||||
|
Platform.OS === "web" && moreOpened && { opacity: important(100) },
|
||||||
|
])}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</Skeleton>
|
</View>
|
||||||
{(isLoading || subtitle) && (
|
{(isLoading || subtitle) && (
|
||||||
<Skeleton {...css({ width: rem(5), alignSelf: "center" })}>
|
<Skeleton {...css({ width: rem(5), alignSelf: "center" })}>
|
||||||
{isLoading || (
|
{isLoading || (
|
||||||
<P
|
<P
|
||||||
{...css({
|
{...css({
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
|
marginRight: ts(4),
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{subtitle}
|
{subtitle}
|
||||||
|
@ -160,6 +160,8 @@ export const CollectionPage: QueryPage<{ slug: string }> = ({ slug }) => {
|
|||||||
{(x) => (
|
{(x) => (
|
||||||
<ItemDetails
|
<ItemDetails
|
||||||
isLoading={x.isLoading as any}
|
isLoading={x.isLoading as any}
|
||||||
|
slug={x.slug}
|
||||||
|
type={x.kind?.toLowerCase() as any}
|
||||||
name={x.name}
|
name={x.name}
|
||||||
tagline={"tagline" in x ? x.tagline : null}
|
tagline={"tagline" in x ? x.tagline : null}
|
||||||
overview={x.overview}
|
overview={x.overview}
|
||||||
|
@ -28,13 +28,17 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|||||||
import { watchListIcon } from "./watchlist-info";
|
import { watchListIcon } from "./watchlist-info";
|
||||||
|
|
||||||
export const EpisodesContext = ({
|
export const EpisodesContext = ({
|
||||||
showSlug,
|
type = "episode",
|
||||||
slug,
|
slug,
|
||||||
|
showSlug,
|
||||||
status,
|
status,
|
||||||
...props
|
...props
|
||||||
}: { showSlug?: string | null; slug: string; status: WatchStatusV | null } & Partial<
|
}: {
|
||||||
ComponentProps<typeof Menu<typeof IconButton>>
|
type?: "show" | "movie" | "episode";
|
||||||
>) => {
|
showSlug?: string | null;
|
||||||
|
slug: string;
|
||||||
|
status: WatchStatusV | null;
|
||||||
|
} & Partial<ComponentProps<typeof Menu<typeof IconButton>>>) => {
|
||||||
const account = useAccount();
|
const account = useAccount();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -42,10 +46,10 @@ export const EpisodesContext = ({
|
|||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationFn: (newStatus: WatchStatusV | null) =>
|
mutationFn: (newStatus: WatchStatusV | null) =>
|
||||||
queryFn({
|
queryFn({
|
||||||
path: ["episode", slug, "watchStatus", newStatus && `?status=${newStatus}`],
|
path: [type, slug, "watchStatus", newStatus && `?status=${newStatus}`],
|
||||||
method: newStatus ? "POST" : "DELETE",
|
method: newStatus ? "POST" : "DELETE",
|
||||||
}),
|
}),
|
||||||
onSettled: async () => await queryClient.invalidateQueries({ queryKey: ["episode", slug] }),
|
onSettled: async () => await queryClient.invalidateQueries({ queryKey: [type, slug] }),
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -73,3 +77,16 @@ export const EpisodesContext = ({
|
|||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ItemContext = ({
|
||||||
|
type,
|
||||||
|
slug,
|
||||||
|
status,
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
type: "movie" | "show";
|
||||||
|
slug: string;
|
||||||
|
status: WatchStatusV | null;
|
||||||
|
} & Partial<ComponentProps<typeof Menu<typeof IconButton>>>) => {
|
||||||
|
return <EpisodesContext type={type} slug={slug} status={status} showSlug={null} {...props} />;
|
||||||
|
};
|
||||||
|
@ -23,6 +23,7 @@ import {
|
|||||||
H6,
|
H6,
|
||||||
ImageBackground,
|
ImageBackground,
|
||||||
ImageProps,
|
ImageProps,
|
||||||
|
important,
|
||||||
Link,
|
Link,
|
||||||
P,
|
P,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
@ -58,6 +59,8 @@ export const displayRuntime = (runtime: number) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const EpisodeBox = ({
|
export const EpisodeBox = ({
|
||||||
|
slug,
|
||||||
|
showSlug,
|
||||||
name,
|
name,
|
||||||
overview,
|
overview,
|
||||||
thumbnail,
|
thumbnail,
|
||||||
@ -68,6 +71,9 @@ export const EpisodeBox = ({
|
|||||||
...props
|
...props
|
||||||
}: Stylable &
|
}: Stylable &
|
||||||
WithLoading<{
|
WithLoading<{
|
||||||
|
slug: string;
|
||||||
|
// if show slug is null, disable "Go to show" in the context menu
|
||||||
|
showSlug: string | null;
|
||||||
name: string | null;
|
name: string | null;
|
||||||
overview: string | null;
|
overview: string | null;
|
||||||
href: string;
|
href: string;
|
||||||
@ -75,12 +81,14 @@ export const EpisodeBox = ({
|
|||||||
watchedPercent: number | null;
|
watchedPercent: number | null;
|
||||||
watchedStatus: WatchStatusV | null;
|
watchedStatus: WatchStatusV | null;
|
||||||
}>) => {
|
}>) => {
|
||||||
|
const [moreOpened, setMoreOpened] = useState(false);
|
||||||
const { css } = useYoshiki("episodebox");
|
const { css } = useYoshiki("episodebox");
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href={href}
|
href={moreOpened ? undefined : href}
|
||||||
|
onLongPress={() => setMoreOpened(true)}
|
||||||
{...css(
|
{...css(
|
||||||
{
|
{
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
@ -90,6 +98,9 @@ export const EpisodeBox = ({
|
|||||||
borderWidth: ts(0.5),
|
borderWidth: ts(0.5),
|
||||||
borderStyle: "solid",
|
borderStyle: "solid",
|
||||||
},
|
},
|
||||||
|
more: {
|
||||||
|
display: "none",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
fover: {
|
fover: {
|
||||||
self: focusReset,
|
self: focusReset,
|
||||||
@ -99,6 +110,9 @@ export const EpisodeBox = ({
|
|||||||
title: {
|
title: {
|
||||||
textDecorationLine: "underline",
|
textDecorationLine: "underline",
|
||||||
},
|
},
|
||||||
|
more: {
|
||||||
|
display: "flex",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
props,
|
props,
|
||||||
@ -117,6 +131,25 @@ export const EpisodeBox = ({
|
|||||||
{(watchedPercent || watchedStatus === WatchStatusV.Completed) && (
|
{(watchedPercent || watchedStatus === WatchStatusV.Completed) && (
|
||||||
<ItemProgress watchPercent={watchedPercent ?? 100} />
|
<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") },
|
||||||
|
])}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</ImageBackground>
|
</ImageBackground>
|
||||||
<Skeleton {...css({ width: percent(50) })}>
|
<Skeleton {...css({ width: percent(50) })}>
|
||||||
{isLoading || (
|
{isLoading || (
|
||||||
@ -177,8 +210,7 @@ export const EpisodeLine = ({
|
|||||||
watchedPercent: number | null;
|
watchedPercent: number | null;
|
||||||
watchedStatus: WatchStatusV | null;
|
watchedStatus: WatchStatusV | null;
|
||||||
href: string;
|
href: string;
|
||||||
}> &
|
}>) => {
|
||||||
Partial<PressableProps>) => {
|
|
||||||
const [moreOpened, setMoreOpened] = useState(false);
|
const [moreOpened, setMoreOpened] = useState(false);
|
||||||
const { css } = useYoshiki("episode-line");
|
const { css } = useYoshiki("episode-line");
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -206,7 +238,7 @@ export const EpisodeLine = ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
props as any,
|
props,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<P {...css({ width: rem(4), flexShrink: 0, m: ts(1), textAlign: "center" })}>
|
<P {...css({ width: rem(4), flexShrink: 0, m: ts(1), textAlign: "center" })}>
|
||||||
@ -277,7 +309,7 @@ export const EpisodeLine = ({
|
|||||||
setOpen={(v) => setMoreOpened(v)}
|
setOpen={(v) => setMoreOpened(v)}
|
||||||
{...css([
|
{...css([
|
||||||
"more",
|
"more",
|
||||||
Platform.OS === "web" && moreOpened && { display: "flex !important" as any },
|
Platform.OS === "web" && moreOpened && { display: important("flex") },
|
||||||
])}
|
])}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -57,6 +57,8 @@ export const NewsList = () => {
|
|||||||
) : (
|
) : (
|
||||||
<EpisodeBox
|
<EpisodeBox
|
||||||
isLoading={x.isLoading as any}
|
isLoading={x.isLoading as any}
|
||||||
|
slug={x.slug}
|
||||||
|
showSlug={x.showId}
|
||||||
name={
|
name={
|
||||||
x.kind === NewsKind.Episode
|
x.kind === NewsKind.Episode
|
||||||
? `${x.show!.name} ${episodeDisplayNumber(x)}`
|
? `${x.show!.name} ${episodeDisplayNumber(x)}`
|
||||||
|
@ -49,9 +49,13 @@ import { Layout, WithLoading } from "../fetch";
|
|||||||
import { InfiniteFetch } from "../fetch-infinite";
|
import { InfiniteFetch } from "../fetch-infinite";
|
||||||
import PlayArrow from "@material-symbols/svg-400/rounded/play_arrow-fill.svg";
|
import PlayArrow from "@material-symbols/svg-400/rounded/play_arrow-fill.svg";
|
||||||
import { ItemGrid, ItemWatchStatus } from "../browse/grid";
|
import { ItemGrid, ItemWatchStatus } from "../browse/grid";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { ItemContext } from "../components/context-menus";
|
||||||
|
|
||||||
export const ItemDetails = ({
|
export const ItemDetails = ({
|
||||||
isLoading,
|
isLoading,
|
||||||
|
slug,
|
||||||
|
type,
|
||||||
name,
|
name,
|
||||||
tagline,
|
tagline,
|
||||||
subtitle,
|
subtitle,
|
||||||
@ -64,6 +68,8 @@ export const ItemDetails = ({
|
|||||||
unseenEpisodesCount,
|
unseenEpisodesCount,
|
||||||
...props
|
...props
|
||||||
}: WithLoading<{
|
}: WithLoading<{
|
||||||
|
slug: string;
|
||||||
|
type: "movie" | "show" | "collection";
|
||||||
name: string;
|
name: string;
|
||||||
tagline: string | null;
|
tagline: string | null;
|
||||||
subtitle: string;
|
subtitle: string;
|
||||||
@ -75,8 +81,9 @@ export const ItemDetails = ({
|
|||||||
watchStatus: WatchStatusV | null;
|
watchStatus: WatchStatusV | null;
|
||||||
unseenEpisodesCount: number | null;
|
unseenEpisodesCount: number | null;
|
||||||
}>) => {
|
}>) => {
|
||||||
const { t } = useTranslation();
|
const [moreOpened, setMoreOpened] = useState(false);
|
||||||
const { css } = useYoshiki("recommanded-card");
|
const { css } = useYoshiki("recommanded-card");
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
@ -88,7 +95,8 @@ export const ItemDetails = ({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
href={href}
|
href={moreOpened ? undefined : href}
|
||||||
|
onLongPress={() => setMoreOpened(true)}
|
||||||
{...css({
|
{...css({
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: 0,
|
top: 0,
|
||||||
@ -149,11 +157,22 @@ export const ItemDetails = ({
|
|||||||
<View
|
<View
|
||||||
{...css({ flexShrink: 1, flexGrow: 1, justifyContent: "flex-end", marginBottom: px(50) })}
|
{...css({ flexShrink: 1, flexGrow: 1, justifyContent: "flex-end", marginBottom: px(50) })}
|
||||||
>
|
>
|
||||||
{(isLoading || tagline) && (
|
<View {...css({ flexDirection: "row-reverse", justifyContent: "space-between" })}>
|
||||||
<Skeleton {...css({ m: ts(1), marginVertical: ts(2) })}>
|
{slug && type && type !== "collection" && watchStatus !== undefined && (
|
||||||
{isLoading || <P {...css({ p: ts(1) })}>{tagline}</P>}
|
<ItemContext
|
||||||
</Skeleton>
|
type={type}
|
||||||
)}
|
slug={slug}
|
||||||
|
status={watchStatus}
|
||||||
|
isOpen={moreOpened}
|
||||||
|
setOpen={(v) => setMoreOpened(v)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{(isLoading || tagline) && (
|
||||||
|
<Skeleton {...css({ m: ts(1), marginVertical: ts(2) })}>
|
||||||
|
{isLoading || <P {...css({ p: ts(1) })}>{tagline}</P>}
|
||||||
|
</Skeleton>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
<ScrollView {...css({ pX: ts(1) })}>
|
<ScrollView {...css({ pX: ts(1) })}>
|
||||||
<Skeleton lines={5} {...css({ height: rem(0.8) })}>
|
<Skeleton lines={5} {...css({ height: rem(0.8) })}>
|
||||||
{isLoading || (
|
{isLoading || (
|
||||||
@ -231,6 +250,8 @@ export const Recommanded = () => {
|
|||||||
{(x) => (
|
{(x) => (
|
||||||
<ItemDetails
|
<ItemDetails
|
||||||
isLoading={x.isLoading as any}
|
isLoading={x.isLoading as any}
|
||||||
|
slug={x.slug}
|
||||||
|
type={x.kind?.toLowerCase() as any}
|
||||||
name={x.name}
|
name={x.name}
|
||||||
tagline={"tagline" in x ? x.tagline : null}
|
tagline={"tagline" in x ? x.tagline : null}
|
||||||
overview={x.overview}
|
overview={x.overview}
|
||||||
|
@ -61,11 +61,14 @@ export const WatchlistList = () => {
|
|||||||
(x.isLoading && i % 2) ? (
|
(x.isLoading && i % 2) ? (
|
||||||
<EpisodeBox
|
<EpisodeBox
|
||||||
isLoading={x.isLoading as any}
|
isLoading={x.isLoading as any}
|
||||||
|
slug={episode?.slug}
|
||||||
|
showSlug={x.slug}
|
||||||
name={episode ? `${x.name} ${episodeDisplayNumber(episode)}` : undefined}
|
name={episode ? `${x.name} ${episodeDisplayNumber(episode)}` : undefined}
|
||||||
overview={episode?.name}
|
overview={episode?.name}
|
||||||
thumbnail={episode?.thumbnail ?? x.thumbnail}
|
thumbnail={episode?.thumbnail ?? x.thumbnail}
|
||||||
href={episode?.href}
|
href={episode?.href}
|
||||||
watchedPercent={x.watchStatus?.watchedPercent || null}
|
watchedPercent={x.watchStatus?.watchedPercent || null}
|
||||||
|
watchedStatus={x.watchStatus?.status || null}
|
||||||
// TODO: Move this into the ItemList (using getItemSize)
|
// TODO: Move this into the ItemList (using getItemSize)
|
||||||
// @ts-expect-error This is a web only property
|
// @ts-expect-error This is a web only property
|
||||||
{...css({ gridColumnEnd: "span 2" })}
|
{...css({ gridColumnEnd: "span 2" })}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user