mirror of
				https://github.com/zoriya/Kyoo.git
				synced 2025-10-31 10:37:13 -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,13 +67,11 @@ export const EpisodeBox = ({ | ||||
| 	name, | ||||
| 	overview, | ||||
| 	thumbnail, | ||||
| 	isLoading, | ||||
| 	href, | ||||
| 	watchedPercent, | ||||
| 	watchedStatus, | ||||
| 	...props | ||||
| }: Stylable & | ||||
| 	WithLoading<{ | ||||
| }: Stylable & { | ||||
| 	slug: string; | ||||
| 	// if show slug is null, disable "Go to show" in the context menu
 | ||||
| 	showSlug: string | null; | ||||
| @ -83,7 +81,7 @@ export const EpisodeBox = ({ | ||||
| 	thumbnail?: ImageProps["src"] | null; | ||||
| 	watchedPercent: number | null; | ||||
| 	watchedStatus: WatchStatusV | null; | ||||
| 	}>) => { | ||||
| }) => { | ||||
| 	const [moreOpened, setMoreOpened] = useState(false); | ||||
| 	const { css } = useYoshiki("episodebox"); | ||||
| 	const { t } = useTranslation(); | ||||
| @ -126,15 +124,12 @@ 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} | ||||
| @ -152,17 +147,10 @@ export const EpisodeBox = ({ | ||||
| 						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({ | ||||
| @ -172,12 +160,36 @@ export const EpisodeBox = ({ | ||||
| 			> | ||||
| 				{overview} | ||||
| 			</SubP> | ||||
| 				)} | ||||
| 			</Skeleton> | ||||
| 		</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) ? ( | ||||
| 						<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} | ||||
| 							type={"movie"} | ||||
| 						/> | ||||
| 					) : ( | ||||
| 				Render={({ item }) => { | ||||
| 					if (item.kind === "episode") { | ||||
| 						return ( | ||||
| 							<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} | ||||
| 								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" })} | ||||
| 							/> | ||||
| 					) | ||||
| 						); | ||||
| 					} | ||||
| 			</InfiniteFetch> | ||||
| 					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={null} | ||||
| 							type={"movie"} | ||||
| 						/> | ||||
| 					); | ||||
| 				}} | ||||
| 				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,10 +39,25 @@ export const WatchlistList = () => { | ||||
| 	const { css } = useYoshiki(); | ||||
| 	const account = useAccount(); | ||||
| 
 | ||||
| 	if (!account) { | ||||
| 		return ( | ||||
| 			<> | ||||
| 				<Header title={t("home.watchlist")} /> | ||||
| 				<View {...css({ justifyContent: "center", alignItems: "center" })}> | ||||
| 					<P>{t("home.watchlistLogin")}</P> | ||||
| 					<Button | ||||
| 						text={t("login.login")} | ||||
| 						href={"/login"} | ||||
| 						{...css({ minWidth: ts(24), margin: ts(2) })} | ||||
| 					/> | ||||
| 				</View> | ||||
| 			</> | ||||
| 		); | ||||
| 	} | ||||
| 
 | ||||
| 	return ( | ||||
| 		<> | ||||
| 			<Header title={t("home.watchlist")} /> | ||||
| 			{account ? ( | ||||
| 			<InfiniteFetch | ||||
| 				query={WatchlistList.query()} | ||||
| 				layout={{ ...ItemGrid.layout, layout: "horizontal" }} | ||||
| @ -53,50 +68,43 @@ export const WatchlistList = () => { | ||||
| 				} | ||||
| 				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) ? ( | ||||
| 				Render={({ item }) => { | ||||
| 					const episode = item.kind === "show" ? item.watchStatus?.nextEpisode : null; | ||||
| 					if (episode) { | ||||
| 						return ( | ||||
| 							<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} | ||||
| 								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 | ||||
| 								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} | ||||
| 							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} | ||||
| 						/> | ||||
| 					); | ||||
| 				}} | ||||
| 				</InfiniteFetch> | ||||
| 			) : ( | ||||
| 				<View {...css({ justifyContent: "center", alignItems: "center" })}> | ||||
| 					<P>{t("home.watchlistLogin")}</P> | ||||
| 					<Button | ||||
| 						text={t("login.login")} | ||||
| 						href={"/login"} | ||||
| 						{...css({ minWidth: ts(24), margin: ts(2) })} | ||||
| 				Loader={({ index }) => (index % 2 ? <EpisodeBox.Loader /> : <ItemGrid.Loader />)} | ||||
| 			/> | ||||
| 				</View> | ||||
| 			)} | ||||
| 		</> | ||||
| 	); | ||||
| }; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user