Skip to content

Commit

Permalink
Merge branch 'next' of github:HolodexNet/Holodex into next
Browse files Browse the repository at this point in the history
  • Loading branch information
P-man2976 committed Jun 29, 2024
2 parents fb25829 + 9e396a3 commit 97a704c
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 113 deletions.
34 changes: 22 additions & 12 deletions packages/react/src/components/common/Loading.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { cn } from "@/lib/utils";
import { Loader2 } from "lucide-react";
import { DetailedHTMLProps, HTMLAttributes } from "react";
import { DetailedHTMLProps, HTMLAttributes, useEffect, useRef } from "react";
import { ApiError } from "./ApiError";
import { HTTPError } from "@/lib/fetch";
import { Button } from "@/shadcn/ui/button";
import { useTranslation } from "react-i18next";
import { useIntersectionObserver } from "usehooks-ts";

interface LoadingProps {
size: "sm" | "md" | "lg" | "xl";
Expand All @@ -15,12 +16,12 @@ export function Loading(
props: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> &
LoadingProps,
) {
const sizeCN = cn({
"text-sm": props.size === "sm",
"text-lg": props.size === "md",
"text-2xl": props.size === "lg",
"text-4xl": props.size === "xl",
});
// const sizeCN = cn({
// "text-sm": props.size === "sm",
// "text-lg": props.size === "md",
// "text-2xl": props.size === "lg",
// "text-4xl": props.size === "xl",
// });

if (props.error) return <ApiError error={props.error} />;

Expand Down Expand Up @@ -48,18 +49,27 @@ export function VirtuosoLoadingFooter({
isLoading: boolean;
hasNextPage: boolean;
loadMore?: () => void;
autoload?: boolean;
} & DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
}) {
const { t } = useTranslation();
const { loadMore, isLoading, autoload } = context || {};

const { ref, isIntersecting } = useIntersectionObserver({
rootMargin: "-100px",
});

useEffect(() => {
console.log(isIntersecting, isLoading, autoload);
if (autoload && isIntersecting && !isLoading && loadMore) {
loadMore();
}
}, [autoload, isIntersecting, isLoading, loadMore]);

return context?.isLoading ? (
<Loading {...context} />
) : context?.hasNextPage ? (
<Button
variant="outline"
className="mt-4 w-full"
onClick={context.loadMore}
>
<Button ref={ref} variant="primary" className="mt-4" onClick={loadMore}>
{t("component.channelList.loadMore")}
</Button>
) : undefined;
Expand Down
135 changes: 48 additions & 87 deletions packages/react/src/components/video/MainVideoListing.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
import { useMemo } from "react";
import { VideoCard } from "./VideoCard";
import { SkeletonVideoCard } from "./SkeletonVideoCard";
import { WindowVirtualizer } from "virtua";
import { VirtuosoGrid } from "react-virtuoso";
import { cn } from "@/lib/utils";
import { VirtuosoLoadingFooter } from "@/components/common/Loading";

interface MainVideoListingProps {
videos: VideoBase[];
videos?: VideoBase[];
size: VideoCardSize;
className?: string;
fetchNextPage?: () => void;
isLoading?: boolean;
hasNextPage?: boolean;
isFetchingNextPage?: boolean;
containerWidth: number;
}

export function MainVideoListing({
containerWidth,
videos,
size,
className,
Expand All @@ -25,99 +24,61 @@ export function MainVideoListing({
isFetchingNextPage,
isLoading,
}: MainVideoListingProps) {
let countPerRow;
switch (size) {
case "list":
countPerRow = 1;
break;
case "lg":
countPerRow = Math.max(1, Math.floor(containerWidth / 360));
break;
case "md":
countPerRow = Math.max(1, Math.floor(containerWidth / 240));
break;
case "sm":
countPerRow = Math.max(1, Math.floor(containerWidth / 200));
break;
case "xs":
countPerRow = Math.max(1, Math.floor(containerWidth / 180));
break;
default:
countPerRow = 1;
}

const videosGroupedByRow = useMemo(() => {
const out = [];
for (let i = 0; i < videos.length; i += countPerRow) {
out.push(videos.slice(i, i + countPerRow));
}
return out;
}, [videos, countPerRow]);

const listClassName = useMemo(
() =>
cn(
"px-4 py-2 md:px-8",
"grid w-full gap-4 px-4 py-2 @container md:px-8",
{
"@container grid gap-4": size === "lg",
"@container grid gap-2": size === "md",
"@container flex flex-col max-w-screen mx-auto px-4 py-1":
size === "list",
"grid-cols-1": size === "list",
"grid-cols-[repeat(auto-fill,minmax(340px,1fr))]": size === "lg",
"grid-cols-[repeat(auto-fill,minmax(220px,1fr))]": size === "md",
"grid-cols-[repeat(auto-fill,minmax(180px,1fr))]": size === "sm",
"grid-cols-[repeat(auto-fill,minmax(160px,1fr))]": size === "xs",
},
className,
),
[size, className],
);

// const fetchedCountRef = useRef(-1);
// const Footer = () =>
// (isLoading || isFetchingNextPage) && (
// <div className={listClassName}>
// {Array.from({ length: 8 }).map((_, index) => (
// <SkeletonVideoCard key={`placeholder-${index}`} />
// ))}
// </div>
// );

return (
<div className="w-full">
<WindowVirtualizer
onRangeChange={async (_, end) => {
if (
hasNextPage &&
!isFetchingNextPage &&
!isLoading &&
end + 2 > videosGroupedByRow.length //&&
// fetchedCountRef.current < videosGroupedByRow.length
) {
// fetchedCountRef.current = videosGroupedByRow.length;
fetchNextPage?.();
}
}}
>
{videosGroupedByRow.map((videoRow, idx) => {
return (
<div
key={`row-${idx}`}
className={listClassName}
style={{ gridTemplateColumns: `repeat(${countPerRow}, 1fr)` }}
>
{videoRow.map((video) => (
<VideoCard
key={"video-" + video.id}
video={video}
size={size}
/>
))}
</div>
);
})}
{(isLoading || isFetchingNextPage) && (
<div
key={`row-loading`}
className={listClassName}
style={{ gridTemplateColumns: `repeat(${countPerRow}, 1fr)` }}
>
{(isLoading || isFetchingNextPage) &&
Array.from({
length: isLoading ? 24 : isFetchingNextPage ? countPerRow : 0,
}).map((_, index) => (
<SkeletonVideoCard key={`placeholder-${index}`} />
))}
</div>
)}
</WindowVirtualizer>
</div>
<VirtuosoGrid
useWindowScroll
data={isLoading ? ([1, 2, 3, 4, 5, 6] as unknown as VideoBase[]) : videos}
listClassName={listClassName}
itemContent={(index, video) =>
isLoading ? (
<SkeletonVideoCard key={`placeholder-${index}`} />
) : (
<VideoCard key={`video-${video.id}`} video={video} size={size} />
)
}
endReached={async () => {
if (hasNextPage && !isFetchingNextPage && !isLoading) {
await fetchNextPage?.();
}
}}
context={{
size: "sm",
isLoading: !!isLoading || !!isFetchingNextPage,
hasNextPage: !!hasNextPage,
loadMore: fetchNextPage,
}}
components={
hasNextPage && !isLoading
? {
Footer: VirtuosoLoadingFooter,
}
: {}
}
/>
);
}
8 changes: 2 additions & 6 deletions packages/react/src/routes/channel/ChannelVideos.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { useOutletContext } from "react-router-dom";
import { ChannelOutletContext } from "../channel";
import { useChannelVideos } from "@/services/channel.service";
import { MainVideoListing } from "@/components/video/MainVideoListing";
import useMeasure from "react-use-measure";

export default function ChannelVideos({ type }: { type: ChannelVideoType }) {
const { id, channel } = useOutletContext<ChannelOutletContext>();
Expand All @@ -15,18 +14,15 @@ export default function ChannelVideos({ type }: { type: ChannelVideoType }) {
isFetchingNextPage,
} = useChannelVideos(id, type);

const [ref, bounds] = useMeasure({ scroll: false });

return (
<div className="container p-4 md:px-8" ref={ref}>
<div className="container p-4 md:px-8">
<MainVideoListing
isLoading={isPending}
fetchNextPage={fetchNextPage}
hasNextPage={hasNextPage}
isFetchingNextPage={isFetchingNextPage}
videos={videos?.pages.flat() ?? []}
videos={videos?.pages.flat()}
size="md"
containerWidth={bounds.width}
/>
</div>
);
Expand Down
10 changes: 2 additions & 8 deletions packages/react/src/routes/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,16 +95,14 @@ export function Home() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchParams, tab]);

const [ref, bounds] = useMeasure({ scroll: false });

if (!org) return <Navigate to="/org404" />;

return (
<>
<Helmet>
<title>{currentOrg} - Holodex</title>
</Helmet>
<Tabs defaultValue={tab} onValueChange={setTab} ref={ref}>
<Tabs defaultValue={tab} onValueChange={setTab}>
<TabsList className="sticky top-0 z-20 flex h-fit max-w-full justify-start overflow-x-auto bg-base-1 md:px-8">
<TabsTrigger value="live" className="px-2">
<Trans
Expand Down Expand Up @@ -161,16 +159,14 @@ export function Home() {
</TabsList>
<TabsContent value="live">
<MainVideoListing
isLoading={false}
isLoading={liveLoading}
size={cardSize}
containerWidth={bounds.width}
videos={live?.filter(({ status }) => status === "live") ?? []}
/>
<Separator className="mb-4 mt-2 w-full border-base-3 lg:mb-6 lg:mt-4" />
<MainVideoListing
isLoading={liveLoading}
size={cardSize}
containerWidth={bounds.width}
videos={live?.filter(({ status }) => status !== "live") ?? []}
/>
</TabsContent>
Expand All @@ -182,7 +178,6 @@ export function Home() {
fetchNextPage={fetchArchives}
hasNextPage={hasArchiveNextPage}
isFetchingNextPage={isFetchingArchiveNextPage}
containerWidth={bounds.width}
></MainVideoListing>
</TabsContent>
<TabsContent value="clips">
Expand All @@ -193,7 +188,6 @@ export function Home() {
fetchNextPage={fetchClips}
hasNextPage={hasClipsNextPage}
isFetchingNextPage={isFetchingClipsNextPage}
containerWidth={bounds.width}
></MainVideoListing>
</TabsContent>
</Tabs>
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/routes/orgChannels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export default function ChannelsOrg() {
isLoading: isFetchingNextPage,
hasNextPage,
loadMore: fetchChannels,
autoload: true,
}}
components={{
Footer: VirtuosoLoadingFooter,
Expand Down

0 comments on commit 97a704c

Please sign in to comment.