// Based mfs only use only one 500 line file instead of ten 50 line files. import { useEffect, useRef, useState } from "react"; import { Helmet } from "react-helmet-async"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import { get } from "@/backend/metadata/tmdb"; import { ThiccContainer } from "@/components/layout/ThinContainer"; import { Divider } from "@/components/utils/Divider"; import { Flare } from "@/components/utils/Flare"; import { conf } from "@/setup/config"; import { Category, Genre, Media, Movie, TVShow, categories, tvCategories, } from "@/utils/discover"; import { SubPageLayout } from "./layouts/SubPageLayout"; import { Icon, Icons } from "../components/Icon"; import { PageTitle } from "./parts/util/PageTitle"; const editorPicks = [ { id: 9342, type: "movie" }, // The Mask of Zorro { id: 293, type: "movie" }, // A River Runs Through It { id: 370172, type: "movie" }, // No Time To Die { id: 661374, type: "movie" }, // The Glass Onion { id: 207, type: "movie" }, // Dead Poets Society { id: 378785, type: "movie" }, // The Best of the Blues Brothers { id: 335984, type: "movie" }, // Blade Runner 2049 { id: 13353, type: "movie" }, // It's the Great Pumpkin, Charlie Brown { id: 27205, type: "movie" }, // Inception { id: 106646, type: "movie" }, // The Wolf of Wall Street { id: 334533, type: "movie" }, // Captain Fantastic { id: 693134, type: "movie" }, // Dune: Part Two { id: 765245, type: "movie" }, // Swan Song { id: 264660, type: "movie" }, // Ex Machina { id: 92591, type: "movie" }, // Bernie { id: 976893, type: "movie" }, // Perfect Days ]; export function Discover() { const { t } = useTranslation(); const [genres, setGenres] = useState([]); const [randomMovie, setRandomMovie] = useState(null); const [genreMovies, setGenreMovies] = useState<{ [genreId: number]: Movie[]; }>({}); const [countdown, setCountdown] = useState(null); const navigate = useNavigate(); const [categoryShows, setCategoryShows] = useState<{ [categoryName: string]: Movie[]; }>({}); const [categoryMovies, setCategoryMovies] = useState<{ [categoryName: string]: Movie[]; }>({}); const [tvGenres, setTVGenres] = useState([]); const [tvShowGenres, setTVShowGenres] = useState<{ [genreId: number]: TVShow[]; }>({}); const carouselRef = useRef(null); const carouselRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}); const gradientRef = useRef(null); const [countdownTimeout, setCountdownTimeout] = useState(null); const [editorPicksData, setEditorPicksData] = useState([]); useEffect(() => { // Function to shuffle array const shuffleArray = (array: any[]) => { for (let i = array.length - 1; i > 0; i -= 1) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } return array; }; const fetchEditorPicks = async () => { try { // Shuffle the editorPicks array const shuffledPicks = shuffleArray([...editorPicks]); const promises = shuffledPicks.map(async (pick) => { const endpoint = pick.type === "movie" ? `/movie/${pick.id}` : `/tv/${pick.id}`; const data = await get(endpoint, { api_key: conf().TMDB_READ_API_KEY, language: "en-US", }); return { ...data, type: pick.type, }; }); const results = await Promise.all(promises); setEditorPicksData(results); } catch (error) { console.error("Error fetching editor picks:", error); } }; fetchEditorPicks(); }, []); useEffect(() => { const fetchMoviesForCategory = async (category: Category) => { try { const data = await get(category.endpoint, { api_key: conf().TMDB_READ_API_KEY, language: "en-US", }); // Shuffle the movies for (let i = data.results.length - 1; i > 0; i -= 1) { const j = Math.floor(Math.random() * (i + 1)); [data.results[i], data.results[j]] = [ data.results[j], data.results[i], ]; } setCategoryMovies((prevCategoryMovies) => ({ ...prevCategoryMovies, [category.name]: data.results, })); } catch (error) { console.error( `Error fetching movies for category ${category.name}:`, error, ); } }; categories.forEach(fetchMoviesForCategory); }, []); useEffect(() => { const fetchShowsForCategory = async (category: Category) => { try { const data = await get(category.endpoint, { api_key: conf().TMDB_READ_API_KEY, language: "en-US", }); // Shuffle the TV shows for (let i = data.results.length - 1; i > 0; i -= 1) { const j = Math.floor(Math.random() * (i + 1)); [data.results[i], data.results[j]] = [ data.results[j], data.results[i], ]; } setCategoryShows((prevCategoryShows) => ({ ...prevCategoryShows, [category.name]: data.results, })); } catch (error) { console.error( `Error fetching movies for category ${category.name}:`, error, ); } }; tvCategories.forEach(fetchShowsForCategory); }, []); // Fetch TV show genres useEffect(() => { const fetchTVGenres = async () => { try { const data = await get("/genre/tv/list", { api_key: conf().TMDB_READ_API_KEY, language: "en-US", }); // Shuffle the array of genres for (let i = data.genres.length - 1; i > 0; i -= 1) { const j = Math.floor(Math.random() * (i + 1)); [data.genres[i], data.genres[j]] = [data.genres[j], data.genres[i]]; } // Fetch only the first 6 TV show genres setTVGenres(data.genres.slice(0, 6)); } catch (error) { console.error("Error fetching TV show genres:", error); } }; fetchTVGenres(); }, []); // Fetch TV shows for each genre useEffect(() => { const fetchTVShowsForGenre = async (genreId: number) => { try { const data = await get("/discover/tv", { api_key: conf().TMDB_READ_API_KEY, with_genres: genreId.toString(), language: "en-US", }); // Shuffle the TV shows for (let i = data.results.length - 1; i > 0; i -= 1) { const j = Math.floor(Math.random() * (i + 1)); [data.results[i], data.results[j]] = [ data.results[j], data.results[i], ]; } setTVShowGenres((prevTVShowGenres) => ({ ...prevTVShowGenres, [genreId]: data.results, })); } catch (error) { console.error(`Error fetching TV shows for genre ${genreId}:`, error); } }; tvGenres.forEach((genre) => fetchTVShowsForGenre(genre.id)); }, [tvGenres]); // Fetch Movie genres useEffect(() => { const fetchGenres = async () => { try { const data = await get("/genre/movie/list", { api_key: conf().TMDB_READ_API_KEY, language: "en-US", }); // Shuffle the array of genres for (let i = data.genres.length - 1; i > 0; i -= 1) { const j = Math.floor(Math.random() * (i + 1)); [data.genres[i], data.genres[j]] = [data.genres[j], data.genres[i]]; } // Fetch only the first 4 genres setGenres(data.genres.slice(0, 4)); } catch (error) { console.error("Error fetching genres:", error); } }; fetchGenres(); }, []); // Fetch movies for each genre useEffect(() => { const fetchMoviesForGenre = async (genreId: number) => { try { const movies: any[] = []; for (let page = 1; page <= 6; page += 1) { // Fetch only 6 pages const data = await get("/discover/movie", { api_key: conf().TMDB_READ_API_KEY, with_genres: genreId.toString(), language: "en-US", page: page.toString(), }); movies.push(...data.results); } // Shuffle the movies for (let i = movies.length - 1; i > 0; i -= 1) { const j = Math.floor(Math.random() * (i + 1)); [movies[i], movies[j]] = [movies[j], movies[i]]; } setGenreMovies((prevGenreMovies) => ({ ...prevGenreMovies, [genreId]: movies, })); } catch (error) { console.error(`Error fetching movies for genre ${genreId}:`, error); } }; genres.forEach((genre) => fetchMoviesForGenre(genre.id)); }, [genres]); function scrollCarousel(categorySlug: string, direction: string) { const carousel = carouselRefs.current[categorySlug]; if (carousel) { const movieElements = carousel.getElementsByTagName("a"); if (movieElements.length > 0) { const movieWidth = movieElements[0].offsetWidth; const visibleMovies = Math.floor(carousel.offsetWidth / movieWidth); const scrollAmount = movieWidth * visibleMovies * 0.69; // Silly number :3 if (direction === "left") { carousel.scrollBy({ left: -scrollAmount, behavior: "smooth" }); } else { carousel.scrollBy({ left: scrollAmount, behavior: "smooth" }); } } } } const [movieWidth, setMovieWidth] = useState( window.innerWidth < 600 ? "150px" : "200px", ); useEffect(() => { const handleResize = () => { setMovieWidth(window.innerWidth < 600 ? "150px" : "200px"); }; window.addEventListener("resize", handleResize); return () => { window.removeEventListener("resize", handleResize); }; }, []); useEffect(() => { if (carouselRef.current && gradientRef.current) { const carouselHeight = carouselRef.current.getBoundingClientRect().height; gradientRef.current.style.top = `${carouselHeight}px`; gradientRef.current.style.bottom = `${carouselHeight}px`; } }, [movieWidth]); const browser = !!window.chrome; // detect chromium browser let isScrolling = false; function handleWheel(e: React.WheelEvent, categorySlug: string) { if (isScrolling) { return; } isScrolling = true; const carousel = carouselRefs.current[categorySlug]; if (carousel && !e.deltaX) { const movieElements = carousel.getElementsByTagName("a"); if (movieElements.length > 0) { if (e.deltaY < 5) { scrollCarousel(categorySlug, "left"); } else { scrollCarousel(categorySlug, "right"); } } } if (browser) { setTimeout(() => { isScrolling = false; }, 345); // disable scrolling after 345 milliseconds for chromium-based browsers } else { // immediately reset isScrolling for non-chromium browsers isScrolling = false; } } const [isHovered, setIsHovered] = useState(false); const toggleHover = (isHovering: boolean) => setIsHovered(isHovering); useEffect(() => { document.body.style.overflow = isHovered ? "hidden" : "auto"; return () => { document.body.style.overflow = "auto"; }; }, [isHovered]); function renderMovies(medias: Media[], category: string, isTVShow = false) { const categorySlug = `${category.toLowerCase().replace(/ /g, "-")}${Math.random()}`; // Convert the category to a slug const displayCategory = category === "Now Playing" ? "In Cinemas" : category === "Editor Picks" // Check for "Editor Picks" specifically ? category : category.includes("Movie") ? `${category}s` : isTVShow ? `${category} Shows` : `${category} Movies`; // https://tailwindcss.com/docs/border-style return (

{displayCategory}

{ carouselRefs.current[categorySlug] = el; }} onMouseEnter={() => toggleHover(true)} onMouseLeave={() => toggleHover(false)} onWheel={(e) => handleWheel(e, categorySlug)} > {medias .filter((media, index, self) => { return ( index === self.findIndex( (m) => m.id === media.id && m.title === media.title, ) ); }) .slice(0, 20) .map((media) => ( navigate( `/media/tmdb-${isTVShow ? "tv" : "movie"}-${media.id}-${ isTVShow ? media.name : media.title }`, ) } className="text-center relative mt-3 mx-[0.285em] mb-3 transition-transform hover:scale-105 duration-[0.45s]" style={{ flex: `0 0 ${movieWidth}` }} // Set a fixed width for each movie > {media.poster_path

{isTVShow ? (media.name?.length ?? 0) > 32 ? `${media.name?.slice(0, 32)}...` : media.name : (media.title?.length ?? 0) > 32 ? `${media.title?.slice(0, 32)}...` : media.title}

))}
); } const handleRandomMovieClick = () => { const allMovies = Object.values(genreMovies).flat(); // Flatten all movie arrays const uniqueTitles = new Set(); // Use a Set to store unique titles allMovies.forEach((movie) => uniqueTitles.add(movie.title)); // Add each title to the Set const uniqueTitlesArray = Array.from(uniqueTitles); // Convert the Set back to an array const randomIndex = Math.floor(Math.random() * uniqueTitlesArray.length); const selectedMovie = allMovies.find( (movie) => movie.title === uniqueTitlesArray[randomIndex], ); if (selectedMovie) { setRandomMovie(selectedMovie); if (countdown !== null && countdown > 0) { // Clear the countdown setCountdown(null); if (countdownTimeout) { clearTimeout(countdownTimeout); setCountdownTimeout(null); setRandomMovie(null); } } else { setCountdown(5); // Schedule navigation after 5 seconds const timeoutId = setTimeout(() => { navigate( `/media/tmdb-movie-${selectedMovie.id}-${selectedMovie.title}`, ); }, 5000); setCountdownTimeout(timeoutId); } } }; useEffect(() => { let countdownInterval: NodeJS.Timeout; if (countdown !== null && countdown > 0) { countdownInterval = setInterval(() => { setCountdown((prevCountdown) => prevCountdown !== null ? prevCountdown - 1 : prevCountdown, ); }, 1000); } return () => { clearInterval(countdownInterval); }; }, [countdown]); return (
{/* Hide scrollbar lmao */}

{t("global.pages.discover")}

{randomMovie && (

Now Playing {randomMovie.title}{" "} in {countdown}

)} {/* Editor Picks Section */}
{editorPicksData.length > 0 && (
{renderMovies(editorPicksData, "Editor Picks")}
)}
{categories.map((category) => (
{renderMovies(categoryMovies[category.name] || [], category.name)}
))} {genres.map((genre) => (
{renderMovies(genreMovies[genre.id] || [], genre.name)}
))}

Shows

{tvCategories.map((category) => (
{renderMovies( categoryShows[category.name] || [], category.name, true, )}
))} {tvGenres.map((genre) => (
{renderMovies(tvShowGenres[genre.id] || [], genre.name, true)}
))}
); }