diff --git a/src/components/overlays/detailsModal/components/carousels/EpisodeCarousel.tsx b/src/components/overlays/detailsModal/components/carousels/EpisodeCarousel.tsx index b6eeacb6..9fec2494 100644 --- a/src/components/overlays/detailsModal/components/carousels/EpisodeCarousel.tsx +++ b/src/components/overlays/detailsModal/components/carousels/EpisodeCarousel.tsx @@ -45,6 +45,39 @@ export function EpisodeCarousel({ const updateItem = useProgressStore((s) => s.updateItem); const confirmModal = useModal("season-watch-confirm"); + const [canScrollLeft, setCanScrollLeft] = useState(false); + const [canScrollRight, setCanScrollRight] = useState(false); + + const updateScrollState = () => { + if (!carouselRef.current) { + setCanScrollLeft(false); + setCanScrollRight(false); + return; + } + + const { scrollLeft, scrollWidth, clientWidth } = carouselRef.current; + const isAtStart = scrollLeft <= 1; + const isAtEnd = scrollLeft + clientWidth >= scrollWidth - 1; + + setCanScrollLeft(!isAtStart); + setCanScrollRight(!isAtEnd); + }; + + useEffect(() => { + const carousel = carouselRef.current; + if (!carousel) return; + + updateScrollState(); + + carousel.addEventListener("scroll", updateScrollState); + window.addEventListener("resize", updateScrollState); + + return () => { + carousel.removeEventListener("scroll", updateScrollState); + window.removeEventListener("resize", updateScrollState); + }; + }, []); + const handleScroll = (direction: "left" | "right") => { if (!carouselRef.current) return; @@ -530,15 +563,17 @@ export function EpisodeCarousel({ {/* Episodes Carousel */}
{/* Left scroll button */} -
- -
+ {canScrollLeft && ( +
+ +
+ )}
{/* Right scroll button */} -
- -
+ {canScrollRight && ( +
+ +
+ )}
); diff --git a/src/components/player/atoms/Episodes.tsx b/src/components/player/atoms/Episodes.tsx index 2d081980..8b9bf424 100644 --- a/src/components/player/atoms/Episodes.tsx +++ b/src/components/player/atoms/Episodes.tsx @@ -766,6 +766,39 @@ export function EpisodesView({ ], ); + const [canScrollLeft, setCanScrollLeft] = useState(false); + const [canScrollRight, setCanScrollRight] = useState(false); + + const updateScrollState = () => { + if (!carouselRef.current) { + setCanScrollLeft(false); + setCanScrollRight(false); + return; + } + + const { scrollLeft, scrollWidth, clientWidth } = carouselRef.current; + const isAtStart = scrollLeft <= 1; + const isAtEnd = scrollLeft + clientWidth >= scrollWidth - 1; + + setCanScrollLeft(!isAtStart); + setCanScrollRight(!isAtEnd); + }; + + useEffect(() => { + const carousel = carouselRef.current; + if (!carousel) return; + + updateScrollState(); + + carousel.addEventListener("scroll", updateScrollState); + window.addEventListener("resize", updateScrollState); + + return () => { + carousel.removeEventListener("scroll", updateScrollState); + window.removeEventListener("resize", updateScrollState); + }; + }, []); + const handleScroll = (direction: "left" | "right") => { if (!carouselRef.current) return; @@ -916,20 +949,22 @@ export function EpisodesView({ content = (
{/* Horizontal scroll buttons */} -
- -
+ +
+ )}
{/* Right scroll button */} -
- -
+ +
+ )} ); } diff --git a/src/pages/discover/components/CarouselNavButtons.tsx b/src/pages/discover/components/CarouselNavButtons.tsx index bd9d3261..24cbdae3 100644 --- a/src/pages/discover/components/CarouselNavButtons.tsx +++ b/src/pages/discover/components/CarouselNavButtons.tsx @@ -1,3 +1,5 @@ +import { useCallback, useEffect, useState } from "react"; + import { Icon, Icons } from "@/components/Icon"; import { Flare } from "@/components/utils/Flare"; @@ -11,9 +13,12 @@ interface CarouselNavButtonsProps { interface NavButtonProps { direction: "left" | "right"; onClick: () => void; + visible: boolean; } -function NavButton({ direction, onClick }: NavButtonProps) { +function NavButton({ direction, onClick, visible }: NavButtonProps) { + if (!visible) return null; + return ( - - ); + const [canScrollLeft, setCanScrollLeft] = useState(false); + const [canScrollRight, setCanScrollRight] = useState(false); + + const updateScrollState = useCallback(() => { + const element = document.getElementById(`button-carousel-${categoryType}`); + if (!element) { + setCanScrollLeft(false); + setCanScrollRight(false); + return; + } + + const { scrollLeft, scrollWidth, clientWidth } = element; + const isAtStart = scrollLeft <= 1; + const isAtEnd = scrollLeft + clientWidth >= scrollWidth - 1; + + setCanScrollLeft(!isAtStart); + setCanScrollRight(!isAtEnd); + }, [categoryType]); + + useEffect(() => { + const element = document.getElementById(`button-carousel-${categoryType}`); + if (!element) return; + + updateScrollState(); + + element.addEventListener("scroll", updateScrollState); + window.addEventListener("resize", updateScrollState); + + return () => { + element.removeEventListener("scroll", updateScrollState); + window.removeEventListener("resize", updateScrollState); + }; + }, [categoryType, updateScrollState]); + + useEffect(() => { + const timeoutId = setTimeout(() => { + updateScrollState(); + }, 0); + return () => clearTimeout(timeoutId); + }, [categories, categoryType, updateScrollState]); + + const renderScrollButton = (direction: "left" | "right") => { + const shouldShow = direction === "left" ? canScrollLeft : canScrollRight; + + if (!shouldShow && !showAlwaysScroll && !isMobile) return null; + + return ( +
+ +
+ ); + }; return (
- {(showAlwaysScroll || isMobile) && renderScrollButton("left")} + {(showAlwaysScroll || isMobile || canScrollLeft) && + renderScrollButton("left")} - {(showAlwaysScroll || isMobile) && renderScrollButton("right")} + {(showAlwaysScroll || isMobile || canScrollRight) && + renderScrollButton("right")}
); } diff --git a/src/stores/__old/watched/store.ts b/src/stores/__old/watched/store.ts index 7d5739ad..5bc2f6fe 100644 --- a/src/stores/__old/watched/store.ts +++ b/src/stores/__old/watched/store.ts @@ -1,10 +1,10 @@ import { useProgressStore } from "@/stores/progress"; +import { createVersionedStore } from "../migrations"; import { OldData, migrateV2Videos } from "./migrations/v2"; import { migrateV3Videos } from "./migrations/v3"; import { migrateV4Videos } from "./migrations/v4"; import { WatchedStoreData } from "./types"; -import { createVersionedStore } from "../migrations"; export const VideoProgressStore = createVersionedStore() .setKey("video-progress")