From be6aec2c86301e39bd2096c4ee512886c4534026 Mon Sep 17 00:00:00 2001 From: Pas <74743263+Pasithea0@users.noreply.github.com> Date: Mon, 1 Dec 2025 16:15:45 -0700 Subject: [PATCH] Refactor MediaCard for internal lazy loading and fix intersection logic Moved intersection observer logic for lazy loading images from MediaCarousel into MediaCard, allowing each card to handle its own image loading. Simplified MediaCarousel by removing its intersection observer and related loading state, improving component separation and maintainability. --- src/components/media/MediaCard.tsx | 300 +++++++++--------- .../discover/components/MediaCarousel.tsx | 52 +-- 2 files changed, 152 insertions(+), 200 deletions(-) diff --git a/src/components/media/MediaCard.tsx b/src/components/media/MediaCard.tsx index cd9828b8..763a24c6 100644 --- a/src/components/media/MediaCard.tsx +++ b/src/components/media/MediaCard.tsx @@ -17,23 +17,19 @@ import { MediaBookmarkButton } from "./MediaBookmark"; import { IconPatch } from "../buttons/IconPatch"; import { Icon, Icons } from "../Icon"; -// Intersection Observer Hook +// Simple Intersection Observer Hook function useIntersectionObserver(options: IntersectionObserverInit = {}) { const [isIntersecting, setIsIntersecting] = useState(false); - const [hasIntersected, setHasIntersected] = useState(false); const targetRef = useRef(null); useEffect(() => { const observer = new IntersectionObserver( ([entry]) => { setIsIntersecting(entry.isIntersecting); - if (entry.isIntersecting) { - setHasIntersected(true); - } }, { ...options, - rootMargin: options.rootMargin || "300px 0px", + rootMargin: options.rootMargin || "300px", }, ); @@ -49,7 +45,7 @@ function useIntersectionObserver(options: IntersectionObserverInit = {}) { }; }, [options]); - return { targetRef, isIntersecting, hasIntersected }; + return { targetRef, isIntersecting }; } // Skeleton Component @@ -135,8 +131,8 @@ function MediaCardContent({ const [searchQuery] = useSearchQuery(); - // Intersection observer for lazy loading - const { targetRef } = useIntersectionObserver({ + // Simple intersection observer for lazy loading images + const { targetRef, isIntersecting } = useIntersectionObserver({ rootMargin: "300px", }); @@ -160,158 +156,162 @@ function MediaCardContent({ } return ( - e.key === "Enter" && e.currentTarget.click()} - > - - }> + e.key === "Enter" && e.currentTarget.click()} > -
+ - {series ? ( -
-

+ {series ? ( +

- {t("media.episodeDisplay", { - season: series.season || 1, - episode: series.episode, - })} -

-
- ) : null} - - {percentage !== undefined ? ( - <> -
-
-
-
-
-
+

+ {t("media.episodeDisplay", { + season: series.season || 1, + episode: series.episode, + })} +

- - ) : null} + ) : null} + + {percentage !== undefined ? ( + <> +
+
+
+
+
+
+
+ + ) : null} + + {!closable && ( +
e.preventDefault()} + > + +
+ )} + + {searchQuery.length > 0 && !closable ? ( +
e.preventDefault()}> + +
+ ) : null} + +
+ closable && onClose?.()} + icon={Icons.X} + /> +
+
+ +

+ {media.title} +

+
+ +
{!closable && ( -
e.preventDefault()} - > - +
+
)} - - {searchQuery.length > 0 && !closable ? ( -
e.preventDefault()}> - + {editable && closable && ( +
+
- ) : null} - -
- closable && onClose?.()} - icon={Icons.X} - /> -
-
- -

- {media.title} -

-
- -
- - {!closable && ( -
- -
- )} - {editable && closable && ( -
- -
- )} - - + )} + + +
); } diff --git a/src/pages/discover/components/MediaCarousel.tsx b/src/pages/discover/components/MediaCarousel.tsx index 177b1f42..281b8b62 100644 --- a/src/pages/discover/components/MediaCarousel.tsx +++ b/src/pages/discover/components/MediaCarousel.tsx @@ -15,7 +15,6 @@ import { useDiscoverMedia, useDiscoverOptions, } from "@/pages/discover/hooks/useDiscoverMedia"; -import { useIntersectionObserver } from "@/pages/discover/hooks/useIntersectionObserver"; import { useDiscoverStore } from "@/stores/discover"; import { useProgressStore } from "@/stores/progress"; import { MediaItem } from "@/utils/mediaTypes"; @@ -114,13 +113,6 @@ export function MediaCarousel({ title: item.title || "", })); - // Set up intersection observer for lazy loading - const { targetRef, isIntersecting, hasIntersected } = useIntersectionObserver( - { - rootMargin: "300px", - }, - ); - // Handle provider/genre selection const handleProviderChange = React.useCallback((id: string, name: string) => { setSelectedProviderId(id); @@ -197,7 +189,7 @@ export function MediaCarousel({ content.type, ]); - // Fetch media using our hook - only when carousel has been visible + // Fetch media using our hook const { media, sectionTitle, actualContentType } = useDiscoverMedia({ contentType, mediaType, @@ -207,7 +199,6 @@ export function MediaCarousel({ providerName: selectedProviderName, mediaTitle: selectedRecommendationTitle, isCarouselView: true, - enabled: hasIntersected, }); // Find active button @@ -311,47 +302,8 @@ export function MediaCarousel({ actualContentType, ]); - // Loading state - if (!isIntersecting || !sectionTitle) { - return ( -
}> -
-
-

- {t("discover.carousel.title.loading")} -

-
-
-
-
-
- {Array(10) - .fill(null) - .map((_, index) => ( -
- -
- ))} -
-
-
-
- ); - } - return ( -
}> +