From 2756397898b071a630f26b47ccf84a300950f309 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 20 May 2024 17:54:28 +0200 Subject: [PATCH] Split loaders in the episode list --- front/packages/ui/src/details/episode.tsx | 128 +++++++++++++--------- front/packages/ui/src/details/season.tsx | 88 ++++++++++----- front/packages/ui/src/details/show.tsx | 3 +- front/packages/ui/src/player/index.tsx | 2 +- front/translations/en.json | 3 +- 5 files changed, 142 insertions(+), 82 deletions(-) diff --git a/front/packages/ui/src/details/episode.tsx b/front/packages/ui/src/details/episode.tsx index d9d0af7b..7b8c9365 100644 --- a/front/packages/ui/src/details/episode.tsx +++ b/front/packages/ui/src/details/episode.tsx @@ -32,6 +32,7 @@ import { important, tooltip, ts, + Image, } from "@kyoo/primitives"; import ExpandMore from "@material-symbols/svg-400/rounded/keyboard_arrow_down-fill.svg"; import ExpandLess from "@material-symbols/svg-400/rounded/keyboard_arrow_up-fill.svg"; @@ -43,18 +44,15 @@ import { ItemProgress } from "../browse/grid"; import { EpisodesContext } from "../components/context-menus"; import type { Layout, WithLoading } from "../fetch"; -export const episodeDisplayNumber = ( - episode: { - seasonNumber?: number | null; - episodeNumber?: number | null; - absoluteNumber?: number | null; - }, - def?: string, -) => { +export const episodeDisplayNumber = (episode: { + seasonNumber?: number | null; + episodeNumber?: number | null; + absoluteNumber?: number | null; +}) => { 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; + return "??"; }; export const displayRuntime = (runtime: number | null) => { @@ -187,7 +185,6 @@ export const EpisodeLine = ({ name, thumbnail, overview, - isLoading, id, absoluteNumber, episodeNumber, @@ -198,7 +195,7 @@ export const EpisodeLine = ({ watchedStatus, href, ...props -}: WithLoading<{ +}: { id: string; slug: string; // if show slug is null, disable "Go to show" in the context menu @@ -215,8 +212,7 @@ export const EpisodeLine = ({ watchedPercent: number | null; watchedStatus: WatchStatusV | null; href: string; -}> & - PressableProps & +} & PressableProps & Stylable) => { const [moreOpened, setMoreOpened] = useState(false); const [descriptionExpanded, setDescriptionExpanded] = useState(false); @@ -254,7 +250,6 @@ export const EpisodeLine = ({ quality="low" alt="" gradient={false} - hideLoad={false} layout={{ width: percent(18), aspectRatio: 16 / 9, @@ -293,48 +288,36 @@ export const EpisodeLine = ({ justifyContent: "space-between", })} > - - {isLoading || ( - // biome-ignore lint/a11y/useValidAriaValues: simply use H6 for the style but keep a P -
- {[displayNumber, name ?? t("show.episodeNoMetadata")].join(" · ")} -
- )} -
+ {/* biome-ignore lint/a11y/useValidAriaValues: simply use H6 for the style but keep a P */} +
+ {[displayNumber, name ?? t("show.episodeNoMetadata")].join(" · ")} +
- - {isLoading || ( - - {/* Source https://www.i18next.com/translation-function/formatting#datetime */} - {[ - releaseDate ? t("{{val, datetime}}", { val: releaseDate }) : null, - displayRuntime(runtime), - ] - .filter((item) => item != null) - .join(" · ")} - - )} - - {slug && watchedStatus !== undefined && ( - setMoreOpened(v)} - {...css([ - "more", - { display: "flex", marginLeft: ts(3) }, - Platform.OS === "web" && moreOpened && { display: important("flex") }, - ])} - /> - )} + + {[ + // @ts-ignore Source https://www.i18next.com/translation-function/formatting#datetime + releaseDate ? t("{{val, datetime}}", { val: releaseDate }) : null, + displayRuntime(runtime), + ] + .filter((item) => item != null) + .join(" · ")} + + setMoreOpened(v)} + {...css([ + "more", + { display: "flex", marginLeft: ts(3) }, + Platform.OS === "web" && moreOpened && { display: important("flex") }, + ])} + /> - - - {isLoading ||

{overview}

} -
+ +

{overview}

); }; + +EpisodeLine.Loader = (props: Stylable) => { + const { css } = useYoshiki(); + + return ( + + + + + + + + + + + ); +}; + EpisodeLine.layout = { numColumns: 1, size: 100, diff --git a/front/packages/ui/src/details/season.tsx b/front/packages/ui/src/details/season.tsx index 8150f71e..ab6f09b3 100644 --- a/front/packages/ui/src/details/season.tsx +++ b/front/packages/ui/src/details/season.tsx @@ -26,7 +26,18 @@ import { SeasonP, useInfiniteFetch, } from "@kyoo/models"; -import { H6, HR, IconButton, Menu, P, Skeleton, tooltip, ts, usePageStyle } from "@kyoo/primitives"; +import { + H2, + H6, + 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"; @@ -38,14 +49,12 @@ import { EpisodeLine, episodeDisplayNumber } from "./episode"; type SeasonProcessed = Season & { href: string }; export const SeasonHeader = ({ - isLoading, seasonNumber, name, seasons, }: { - isLoading: boolean; - seasonNumber?: number; - name?: string; + seasonNumber: number; + name: string | null; seasons?: SeasonProcessed[]; }) => { const { css } = useYoshiki(); @@ -63,21 +72,20 @@ export const SeasonHeader = ({ fontSize: rem(1.5), })} > - {isLoading ? : seasonNumber} + {seasonNumber}

-
- {isLoading ? : name} -
+

+ {name ?? t("show.season", { number: seasonNumber })} +

{seasons ?.filter((x) => x.episodesCount > 0) .map((x) => ( ))} @@ -88,6 +96,31 @@ export const SeasonHeader = ({ ); }; +SeasonHeader.Loader = () => { + const { css } = useYoshiki(); + + return ( + + + + + + + + +
+
+ ); +}; + SeasonHeader.query = (slug: string): QueryIdentifier => ({ parser: SeasonP, path: ["show", slug, "seasons"], @@ -130,32 +163,37 @@ export const EpisodeList = ({ headerProps={headerProps} getItemType={(item) => (item.firstOfSeason ? "withHeader" : "normal")} contentContainerStyle={pageStyle} - > - {(item) => { + placeholderCount={5} + Render={({ item }) => { const sea = item?.firstOfSeason ? seasons?.find((x) => x.seasonNumber === item.seasonNumber) : null; return ( <> - {item.firstOfSeason && ( - - )} + {item.firstOfSeason && + (sea ? ( + + ) : ( + + ))} ); }} - + Loader={({ index }) => ( + <> + {index === 0 && } + + + )} + /> ); }; diff --git a/front/packages/ui/src/details/show.tsx b/front/packages/ui/src/details/show.tsx index 741e211a..f981efec 100644 --- a/front/packages/ui/src/details/show.tsx +++ b/front/packages/ui/src/details/show.tsx @@ -79,12 +79,11 @@ export const ShowWatchStatusCard = ({ watchedPercent, status, nextEpisode }: Sho >

{t("show.nextUp")}

setFocus(true)} onHoverOut={() => setFocus(false)} onFocus={() => setFocus(true)} diff --git a/front/packages/ui/src/player/index.tsx b/front/packages/ui/src/player/index.tsx index df35889f..5324ffca 100644 --- a/front/packages/ui/src/player/index.tsx +++ b/front/packages/ui/src/player/index.tsx @@ -53,7 +53,7 @@ const mapData = ( if (!data) return { isLoading: true }; return { isLoading: false, - name: data.type === "movie" ? data.name : `${episodeDisplayNumber(data, "")} ${data.name}`, + name: data.type === "movie" ? data.name : `${episodeDisplayNumber(data)} ${data.name}`, showName: data.type === "movie" ? data.name! : data.show!.name, poster: data.type === "movie" ? data.poster : data.show!.poster, subtitles: info?.subtitles, diff --git a/front/translations/en.json b/front/translations/en.json index bd0ee0f4..fd659f8f 100644 --- a/front/translations/en.json +++ b/front/translations/en.json @@ -39,7 +39,8 @@ "droped": "Mark as dropped", "null": "Mark as not seen" }, - "nextUp": "Next up" + "nextUp": "Next up", + "season": "Season {{number}}" }, "browse": { "sortby": "Sort by {{key}}",