diff options
Diffstat (limited to 'components/anime')
| -rw-r--r-- | components/anime/charactersCard.js | 160 | ||||
| -rw-r--r-- | components/anime/episode.js | 114 | ||||
| -rw-r--r-- | components/anime/infoDetails.js | 204 | ||||
| -rw-r--r-- | components/anime/mobile/topSection.js | 179 | ||||
| -rw-r--r-- | components/anime/viewMode/thumbnailDetail.js | 10 | ||||
| -rw-r--r-- | components/anime/viewMode/thumbnailOnly.js | 4 | ||||
| -rw-r--r-- | components/anime/viewSelector.js (renamed from components/anime/changeView.js) | 6 | ||||
| -rw-r--r-- | components/anime/watch/primary/details.js | 185 | ||||
| -rw-r--r-- | components/anime/watch/primarySide.js | 276 | ||||
| -rw-r--r-- | components/anime/watch/secondarySide.js | 134 |
10 files changed, 198 insertions, 1074 deletions
diff --git a/components/anime/charactersCard.js b/components/anime/charactersCard.js index abff2ba..6c9197a 100644 --- a/components/anime/charactersCard.js +++ b/components/anime/charactersCard.js @@ -3,79 +3,91 @@ import Image from "next/image"; import { useState } from "react"; export default function Characters({ info }) { + const [showAll, setShowAll] = useState(false); - const [showAll, setShowAll] = useState(false); - - return ( - <div> - <div className="flex items-center justify-between lg:gap-3 px-5 z-40 "> - <h1 className="font-karla text-[20px] font-bold">Characters</h1> - {info?.length > 6 && ( - <div className="cursor-pointer font-karla" onClick={() => setShowAll(!showAll)}> - {showAll ? "show less" : "show more"} - </div> - )} - </div> - {/* for bigger device */} - <div className="hidden md:grid w-full grid-cols-1 gap-[10px] md:gap-4 md:grid-cols-3 md:pt-7 md:pb-5 px-3 md:px-5 pt-4"> - {info.slice(0, showAll ? info.length : 6).map((item, index) => { - return <a key={index} className="md:hover:scale-[1.02] snap-start hover:shadow-lg scale-100 transition-transform duration-200 ease-out w-full cursor-default"> - <div className="text-gray-300 space-x-4 col-span-1 flex w-full h-24 bg-secondary rounded-md overflow-hidden"> - <div className="relative h-full w-20"> - <Image - draggable={false} - src={ - item.node.image.large || - item.node.image.medium - } - width={500} - height={300} - alt={ - item.node.name.userPreferred || - item.node.name.full || - "Character Image" - } - className="h-full object-cover" - /> - </div> - <div className="py-2 flex flex-col justify-between"> - <p className="font-semibold">{item.node.name.full || item.node.name.userPreferred}</p> - <p>{item.role}</p> - </div> - </div> - </a> - })} - </div> - {/* for smaller devices */} - <div className="flex md:hidden h-full w-full select-none overflow-x-scroll overflow-y-hidden scrollbar-hide gap-4 pt-8 pb-4 px-5 z-30"> - {info.slice(0, showAll ? info.length : 6).map((item, index) => { - return <div key={index} className="flex flex-col gap-3 shrink-0 cursor-pointer"> - <a className="hover:scale-105 hover:shadow-lg duration-300 ease-out group relative"> - <div className="h-[190px] w-[135px] rounded-md z-30"> - <Image - draggable={false} - src={ - item.node.image.large || - item.node.image.medium - } - alt={ - item.node.name.userPreferred || - item.node.name.full || - "Character Image" - } - width={500} - height={300} - className="z-20 h-[190px] w-[135px] object-cover rounded-md brightness-90" - /> - </div> - </a> - <a className="w-[135px] lg:w-[185px] line-clamp-2"> - <h1 className="font-karla font-semibold text-[15px]">{item.node.name.full || item.node.name.userPreferred}</h1> - <h1 className="font-karla float-right italic text-[12px]">~{item.role}</h1> - </a> - </div> - })} + return ( + <div> + <div className="flex items-center justify-between lg:gap-3 px-5 z-40 "> + <h1 className="font-karla text-[20px] font-bold">Characters</h1> + {info?.length > 6 && ( + <div + className="cursor-pointer font-karla" + onClick={() => setShowAll(!showAll)} + > + {showAll ? "show less" : "show more"} + </div> + )} + </div> + {/* for bigger device */} + <div className="hidden md:grid w-full grid-cols-1 gap-[10px] md:gap-4 md:grid-cols-3 md:pt-7 md:pb-5 px-3 md:px-5 pt-4"> + {info.slice(0, showAll ? info.length : 6).map((item, index) => { + return ( + <a + key={index} + className="md:hover:scale-[1.02] snap-start hover:shadow-lg scale-100 transition-transform duration-200 ease-out w-full cursor-default" + > + <div className="text-gray-300 space-x-4 col-span-1 flex w-full h-24 bg-secondary rounded-md overflow-hidden"> + <div className="relative h-full w-20"> + <Image + draggable={false} + src={item.node.image.large || item.node.image.medium} + width={500} + height={300} + alt={ + item.node.name.userPreferred || + item.node.name.full || + "Character Image" + } + className="h-full object-cover" + /> + </div> + <div className="py-2 flex flex-col justify-between"> + <p className="font-semibold"> + {item.node.name.full || item.node.name.userPreferred} + </p> + <p>{item.role}</p> + </div> + </div> + </a> + ); + })} + </div> + {/* for smaller devices */} + <div className="flex md:hidden h-full w-full select-none overflow-x-scroll overflow-y-hidden scrollbar-hide gap-4 pt-8 pb-4 px-5 z-30"> + {info.slice(0, showAll ? info.length : 6).map((item, index) => { + return ( + <div + key={index} + className="flex flex-col gap-3 shrink-0 cursor-pointer" + > + <a className="hover:scale-105 hover:shadow-lg duration-300 ease-out group relative"> + <div className="h-[190px] w-[135px] rounded-md z-30"> + <Image + draggable={false} + src={item.node.image.large || item.node.image.medium} + alt={ + item.node.name.userPreferred || + item.node.name.full || + "Character Image" + } + width={500} + height={300} + className="z-20 h-[190px] w-[135px] object-cover rounded-md brightness-90" + /> + </div> + </a> + <a className="w-[135px] lg:w-[185px] line-clamp-2"> + <h1 className="font-karla font-semibold text-[15px]"> + {item.node.name.full || item.node.name.userPreferred} + </h1> + <h1 className="font-karla float-right italic text-[12px]"> + ~{item.role} + </h1> + </a> </div> - </div> - ); -}
\ No newline at end of file + ); + })} + </div> + </div> + ); +} diff --git a/components/anime/episode.js b/components/anime/episode.js index b2f4bd7..e6420a7 100644 --- a/components/anime/episode.js +++ b/components/anime/episode.js @@ -1,10 +1,10 @@ import { useEffect, useState, Fragment } from "react"; import { ChevronDownIcon } from "@heroicons/react/20/solid"; -import ChangeView from "./changeView"; +import ViewSelector from "./viewSelector"; import ThumbnailOnly from "./viewMode/thumbnailOnly"; import ThumbnailDetail from "./viewMode/thumbnailDetail"; import ListMode from "./viewMode/listMode"; -import { convertSecondsToTime } from "../../utils/getTimes"; +import { toast } from "react-toastify"; export default function AnimeEpisode({ info, @@ -93,8 +93,9 @@ export default function AnimeEpisode({ !mapProviders || mapProviders?.every( (item) => + item?.img?.includes("https://s4.anilist.co/") || item?.image?.includes("https://s4.anilist.co/") || - item?.image === null + item?.img === null ) ) { setView(3); @@ -152,27 +153,106 @@ export default function AnimeEpisode({ } }, [providerId, artStorage, info.id, session?.user?.name]); + let debounceTimeout; + + const handleRefresh = async () => { + try { + setLoading(true); + clearTimeout(debounceTimeout); + debounceTimeout = setTimeout(async () => { + const res = await fetch( + `/api/v2/episode/${info.id}?releasing=${ + info.status === "RELEASING" ? "true" : "false" + }${isDub ? "&dub=true" : ""}&refresh=true` + ); + if (!res.ok) { + console.log(res); + toast.error("Something went wrong", { + position: "bottom-left", + autoClose: 3000, + hideProgressBar: true, + theme: "colored", + }); + setProviders([]); + setLoading(false); + } else { + const data = await res.json(); + const getMap = data.find((i) => i?.map === true); + let allProvider = data; + + if (getMap) { + allProvider = data.filter((i) => { + if (i?.providerId === "gogoanime" && i?.map !== true) { + return null; + } + return i; + }); + setMapProviders(getMap?.episodes); + } + + if (allProvider.length > 0) { + const defaultProvider = allProvider.find( + (x) => x.providerId === "gogoanime" || x.providerId === "9anime" + ); + setProviderId( + defaultProvider?.providerId || allProvider[0].providerId + ); // set to first provider id + } + + setView(Number(localStorage.getItem("view")) || 3); + setArtStorage(JSON.parse(localStorage.getItem("artplayer_settings"))); + setProviders(allProvider); + setLoading(false); + } + }, 1000); + } catch (err) { + console.log(err); + toast.error("Something went wrong", { + position: "bottom-left", + autoClose: 3000, + hideProgressBar: true, + theme: "colored", + }); + } + }; + return ( <> <div className="flex flex-col gap-5 px-3"> <div className="flex lg:flex-row flex-col gap-5 lg:gap-0 justify-between "> <div className="flex justify-between"> - <div className="flex items-center md:gap-5"> + <div className="flex items-center gap-4 md:gap-5"> {info && ( <h1 className="text-[20px] lg:text-2xl font-bold font-karla"> Episodes </h1> )} - {info.nextAiringEpisode?.timeUntilAiring && ( - <p className="hidden md:block bg-gray-100 text-gray-900 rounded-md px-2 font-karla font-medium"> - Ep {info.nextAiringEpisode.episode}{" "} - <span className="animate-pulse">{">>"}</span>{" "} - <span className="font-bold"> - {convertSecondsToTime( - info.nextAiringEpisode.timeUntilAiring - )}{" "} + {info?.status !== "NOT_YET_RELEASED" && ( + <button + type="button" + onClick={() => { + handleRefresh(); + setProviders(null); + setMapProviders(null); + }} + className="relative flex flex-col items-center w-5 h-5 group" + > + <span className="absolute pointer-events-none z-40 opacity-0 -translate-y-8 group-hover:-translate-y-10 group-hover:opacity-100 font-karla shadow-tersier shadow-md whitespace-nowrap bg-secondary px-2 py-1 rounded transition-all duration-200 ease-out"> + Refresh Episodes </span> - </p> + <svg + fill="currentColor" + viewBox="0 0 20 20" + xmlns="http://www.w3.org/2000/svg" + aria-hidden="true" + > + <path + clipRule="evenodd" + fillRule="evenodd" + d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z" + /> + </svg> + </button> )} </div> @@ -267,7 +347,7 @@ export default function AnimeEpisode({ </> )} - <ChangeView + <ViewSelector view={view} setView={setView} episode={currentEpisodes} @@ -301,7 +381,7 @@ export default function AnimeEpisode({ key={index} index={index} info={info} - image={mapData?.image} + image={mapData?.img || mapData?.image} providerId={providerId} episode={episode} artStorage={artStorage} @@ -312,7 +392,7 @@ export default function AnimeEpisode({ {view === 2 && ( <ThumbnailDetail key={index} - image={mapData?.image} + image={mapData?.img || mapData?.image} title={mapData?.title} description={mapData?.description} index={index} @@ -346,7 +426,7 @@ export default function AnimeEpisode({ </div> ) ) : ( - <p>{providers.message}</p> + <p>{providers?.message}</p> )} </div> ) : ( diff --git a/components/anime/infoDetails.js b/components/anime/infoDetails.js deleted file mode 100644 index 8200bfa..0000000 --- a/components/anime/infoDetails.js +++ /dev/null @@ -1,204 +0,0 @@ -import Image from "next/image"; -import Link from "next/link"; -import Skeleton from "react-loading-skeleton"; - -export default function DesktopDetails({ - info, - statuses, - handleOpen, - loading, - color, - setShowAll, - showAll, -}) { - return ( - <> - <div className="hidden lg:flex gap-8 w-full flex-nowrap"> - <div className="shrink-0 lg:h-[250px] lg:w-[180px] w-[115px] h-[164px] relative"> - {info ? ( - <> - <div className="bg-image lg:h-[250px] lg:w-[180px] w-[115px] h-[164px] bg-opacity-30 absolute backdrop-blur-lg z-10 -top-7" /> - <Image - src={info.coverImage.extraLarge || info.coverImage.large} - priority={true} - alt="poster anime" - height={700} - width={700} - className="object-cover lg:h-[250px] lg:w-[180px] w-[115px] h-[164px] z-20 absolute rounded-md -top-7" - /> - <button - type="button" - className="bg-action flex-center z-20 h-[20px] w-[180px] absolute bottom-0 rounded-sm font-karla font-bold" - onClick={() => handleOpen()} - > - {!loading - ? statuses - ? statuses.name - : "Add to List" - : "Loading..."} - </button> - </> - ) : ( - <Skeleton className="h-[250px] w-[180px]" /> - )} - </div> - - <div className="hidden lg:flex w-full flex-col gap-5 h-[250px]"> - <div className="flex flex-col gap-2"> - <h1 - className="title font-inter font-bold text-[36px] text-white line-clamp-1" - title={info?.title?.romaji || info?.title?.english} - > - {info ? ( - info?.title?.romaji || info?.title?.english - ) : ( - <Skeleton width={450} /> - )} - </h1> - {info ? ( - <div className="flex gap-6"> - {info?.episodes && ( - <div - className={`dynamic-text rounded-md px-2 font-karla font-bold`} - style={color} - > - {info?.episodes} Episodes - </div> - )} - {info?.startDate?.year && ( - <div - className={`dynamic-text rounded-md px-2 font-karla font-bold`} - style={color} - > - {info?.startDate?.year} - </div> - )} - {info?.averageScore && ( - <div - className={`dynamic-text rounded-md px-2 font-karla font-bold`} - style={color} - > - {info?.averageScore}% - </div> - )} - {info?.type && ( - <div - className={`dynamic-text rounded-md px-2 font-karla font-bold`} - style={color} - > - {info?.type} - </div> - )} - {info?.status && ( - <div - className={`dynamic-text rounded-md px-2 font-karla font-bold`} - style={color} - > - {info?.status} - </div> - )} - <div - className={`dynamic-text rounded-md px-2 font-karla font-bold`} - style={color} - > - Sub | EN - </div> - </div> - ) : ( - <Skeleton width={240} height={32} /> - )} - </div> - {info ? ( - <p - dangerouslySetInnerHTML={{ __html: info?.description }} - className="overflow-y-scroll scrollbar-thin pr-2 scrollbar-thumb-secondary scrollbar-thumb-rounded-lg h-[140px]" - /> - ) : ( - <Skeleton className="h-[130px]" /> - )} - </div> - </div> - - <div> - <div className="flex gap-5 items-center"> - {info?.relations?.edges?.length > 0 && ( - <div className="p-3 lg:p-0 text-[20px] lg:text-2xl font-bold font-karla"> - Relations - </div> - )} - {info?.relations?.edges?.length > 3 && ( - <div - className="cursor-pointer" - onClick={() => setShowAll(!showAll)} - > - {showAll ? "show less" : "show more"} - </div> - )} - </div> - <div - className={`w-screen lg:w-full flex gap-5 overflow-x-scroll snap-x scroll-px-5 scrollbar-none lg:grid lg:grid-cols-3 justify-items-center lg:pt-7 lg:pb-5 px-3 lg:px-4 pt-4 rounded-xl`} - > - {info?.relations?.edges ? ( - info?.relations?.edges - .slice(0, showAll ? info?.relations?.edges.length : 3) - .map((r, index) => { - const rel = r.node; - return ( - <Link - key={rel.id} - href={ - rel.type === "ANIME" || - rel.type === "OVA" || - rel.type === "MOVIE" || - rel.type === "SPECIAL" || - rel.type === "ONA" - ? `/en/anime/${rel.id}` - : `/en/manga/${rel.id}` - } - className={`lg:hover:scale-[1.02] snap-start hover:shadow-lg scale-100 transition-transform duration-200 ease-out w-full ${ - rel.type === "MUSIC" ? "pointer-events-none" : "" - }`} - > - <div - key={rel.id} - className="w-[400px] lg:w-full h-[126px] bg-secondary flex rounded-md" - > - <div className="w-[90px] bg-image rounded-l-md shrink-0"> - <Image - src={rel.coverImage.extraLarge} - alt={rel.id} - height={500} - width={500} - className="object-cover h-full w-full shrink-0 rounded-l-md" - /> - </div> - <div className="h-full grid px-3 items-center"> - <div className="text-action font-outfit font-bold"> - {r.relationType} - </div> - <div className="font-outfit font-thin line-clamp-2"> - {rel.title.userPreferred} - </div> - <div className={``}>{rel.type}</div> - </div> - </div> - </Link> - ); - }) - ) : ( - <> - {[1, 2, 3].map((item) => ( - <div key={item} className="w-full hidden lg:block"> - <Skeleton className="h-[126px]" /> - </div> - ))} - <div className="w-full lg:hidden"> - <Skeleton className="h-[126px]" /> - </div> - </> - )} - </div> - </div> - </> - ); -} diff --git a/components/anime/mobile/topSection.js b/components/anime/mobile/topSection.js index 4420d24..8db1465 100644 --- a/components/anime/mobile/topSection.js +++ b/components/anime/mobile/topSection.js @@ -1,188 +1,15 @@ -import { - ArrowUpCircleIcon, - MagnifyingGlassIcon, -} from "@heroicons/react/24/solid"; - -import { - ArrowLeftIcon, - PlayIcon, - PlusIcon, - ShareIcon, - UserIcon, -} from "@heroicons/react/24/solid"; +import { PlayIcon, PlusIcon, ShareIcon } from "@heroicons/react/24/solid"; import Image from "next/image"; import { useRouter } from "next/router"; -import { useSearch } from "../../../lib/hooks/isOpenState"; import { useEffect, useState } from "react"; import { convertSecondsToTime } from "../../../utils/getTimes"; import Link from "next/link"; -import { signIn } from "next-auth/react"; import InfoChip from "./reused/infoChip"; import Description from "./reused/description"; - -const getScrollPosition = (el = window) => ({ - x: el.pageXOffset !== undefined ? el.pageXOffset : el.scrollLeft, - y: el.pageYOffset !== undefined ? el.pageYOffset : el.scrollTop, -}); - -export function NewNavbar({ info, session, scrollP = 200, toTop = false }) { - const router = useRouter(); - const [scrollPosition, setScrollPosition] = useState(); - const { isOpen, setIsOpen } = useSearch(); - - useEffect(() => { - const handleScroll = () => { - setScrollPosition(getScrollPosition()); - }; - - // Add a scroll event listener when the component mounts - window.addEventListener("scroll", handleScroll); - - // Clean up the event listener when the component unmounts - return () => { - window.removeEventListener("scroll", handleScroll); - }; - }, []); - return ( - <> - <nav - className={`fixed z-[200] top-0 py-3 px-5 w-full ${ - scrollPosition?.y >= scrollP - ? "bg-tersier shadow-tersier shadow-sm" - : "" - } transition-all duration-200 ease-linear`} - > - <div className="flex items-center justify-between max-w-screen-2xl mx-auto"> - <div className="flex w-full items-center gap-4"> - {info ? ( - <> - <button - type="button" - className="flex-center w-7 h-7 text-white" - onClick={() => { - // router.back(); - router.push("/en"); - }} - > - <ArrowLeftIcon className="w-full h-full" /> - </button> - <span - className={`font-inter font-semibold w-[50%] line-clamp-1 select-none ${ - scrollPosition?.y >= scrollP + 80 - ? "opacity-100" - : "opacity-0" - } transition-all duration-200 ease-linear`} - > - {info.title.romaji} - </span> - </> - ) : ( - // <></> - <Link - href={"/en"} - className="flex-center text-white font-outfit text-2xl font-semibold" - > - moopa - </Link> - )} - </div> - <div className="flex items-center gap-4"> - <button - type="button" - onClick={() => setIsOpen(true)} - className="flex-center w-[26px] h-[26px]" - > - <svg - xmlns="http://www.w3.org/2000/svg" - width="32" - height="32" - viewBox="0 0 24 24" - > - <path - fill="none" - stroke="currentColor" - strokeLinecap="round" - strokeLinejoin="round" - strokeWidth="2" - d="M15 15l6 6m-11-4a7 7 0 110-14 7 7 0 010 14z" - ></path> - </svg> - </button> - {/* <div - className="bg-white" - // title={sessions ? "Go to Profile" : "Login With AniList"} - > */} - {session ? ( - <div className="w-7 h-7 relative flex flex-col items-center group"> - <button - type="button" - onClick={() => - router.push(`/en/profile/${session?.user.name}`) - } - className="rounded-full bg-white/30 overflow-hidden" - > - <Image - src={session?.user.image.large} - alt="avatar" - width={50} - height={50} - className="w-full h-full object-cover" - /> - </button> - <div className="hidden absolute z-50 w-28 text-center -bottom-20 text-white shadow-2xl opacity-0 bg-secondary p-1 py-2 rounded-md font-karla font-light invisible group-hover:visible group-hover:opacity-100 duration-300 transition-all md:grid place-items-center gap-1"> - <Link - href={`/en/profile/${session?.user.name}`} - className="hover:text-action" - > - Profile - </Link> - <div - onClick={() => signOut("AniListProvider")} - className="hover:text-action" - > - Log out - </div> - </div> - </div> - ) : ( - <button - type="button" - onClick={() => signIn("AniListProvider")} - title="Login With AniList" - className="w-7 h-7 bg-white/30 rounded-full overflow-hidden" - > - <UserIcon className="w-full h-full translate-y-2" /> - </button> - )} - {/* </div> */} - </div> - </div> - </nav> - {toTop && ( - <button - type="button" - onClick={() => { - window.scrollTo({ - top: 0, - behavior: "smooth", - }); - }} - className={`${ - scrollPosition?.y >= 180 - ? "-translate-x-6 opacity-100" - : "translate-x-[100%] opacity-0" - } transform transition-all duration-300 ease-in-out fixed bottom-24 lg:bottom-14 right-0 z-[500]`} - > - <ArrowUpCircleIcon className="w-10 h-10 text-white" /> - </button> - )} - </> - ); -} +import { NewNavbar } from "@/components/shared/NavBar"; export default function DetailTop({ info, - session, statuses, handleOpen, watchUrl, @@ -217,7 +44,7 @@ export default function DetailTop({ return ( <div className="gap-6 w-full px-3 pt-4 md:pt-10 flex flex-col items-center justify-center"> - <NewNavbar info={info} session={session} /> + <NewNavbar info={info} /> {/* MAIN */} <div className="flex flex-col md:flex-row w-full items-center md:items-end gap-5 pt-12"> diff --git a/components/anime/viewMode/thumbnailDetail.js b/components/anime/viewMode/thumbnailDetail.js index db18651..2abfd0b 100644 --- a/components/anime/viewMode/thumbnailDetail.js +++ b/components/anime/viewMode/thumbnailDetail.js @@ -32,8 +32,8 @@ export default function ThumbnailDetail({ <Image src={image || ""} alt={`Episode ${epi?.number} Thumbnail`} - width={1000} - height={1000} + width={420} + height={236} className="object-cover z-30 rounded-lg h-[110px] lg:h-[160px] brightness-[65%]" /> )} @@ -41,7 +41,7 @@ export default function ThumbnailDetail({ className={`absolute bottom-0 left-0 h-[2px] bg-red-700`} style={{ width: - progress && artStorage && epi?.number <= progress + progress || (artStorage && epi?.number <= progress) ? "100%" : artStorage?.[epi?.id] ? `${prog}%` @@ -49,7 +49,7 @@ export default function ThumbnailDetail({ }} /> <span className="absolute bottom-2 left-2 font-karla font-semibold text-sm lg:text-lg"> - Episode {epi?.number} + Episode {epi?.number || 0} </span> <div className="z-[9999] absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 scale-[1.5]"> <svg @@ -68,7 +68,7 @@ export default function ThumbnailDetail({ className={`w-[70%] h-full select-none p-4 flex flex-col justify-center gap-3`} > <h1 className="font-karla font-bold text-base lg:text-lg xl:text-xl italic line-clamp-1"> - {title || `Episode ${epi?.number}`} + {title || `Episode ${epi?.number || 0}`} </h1> {description && ( <p className="line-clamp-2 text-xs lg:text-md xl:text-lg italic font-outfit font-extralight"> diff --git a/components/anime/viewMode/thumbnailOnly.js b/components/anime/viewMode/thumbnailOnly.js index 69cd8c3..7259beb 100644 --- a/components/anime/viewMode/thumbnailOnly.js +++ b/components/anime/viewMode/thumbnailOnly.js @@ -23,7 +23,7 @@ export default function ThumbnailOnly({ className="transition-all duration-200 ease-out lg:hover:scale-105 hover:ring-1 hover:ring-white cursor-pointer bg-secondary shrink-0 relative w-full h-[180px] sm:h-[130px] subpixel-antialiased rounded-md overflow-hidden" > <span className="absolute text-sm z-40 bottom-1 left-2 font-karla font-semibold text-white"> - Episode {episode?.number} + Episode {episode?.number || 0} </span> <span className={`absolute bottom-7 left-0 h-[2px] bg-red-600`} @@ -40,7 +40,7 @@ export default function ThumbnailOnly({ {image && ( <Image src={image || ""} - alt="epi image" + alt={`Episode ${episode?.number} Thumbnail`} width={500} height={500} className="object-cover w-full h-[150px] sm:h-[100px] z-20 brightness-75" diff --git a/components/anime/changeView.js b/components/anime/viewSelector.js index 75ebdff..f114a8b 100644 --- a/components/anime/changeView.js +++ b/components/anime/viewSelector.js @@ -1,4 +1,4 @@ -export default function ChangeView({ view, setView, episode, map }) { +export default function ViewSelector({ view, setView, episode, map }) { return ( <div className="flex gap-3 rounded-sm items-center p-2"> <div @@ -6,6 +6,7 @@ export default function ChangeView({ view, setView, episode, map }) { episode?.length > 0 ? map?.every( (item) => + item?.img?.includes("https://s4.anilist.co/") || item?.image?.includes("https://s4.anilist.co/") || item.title === null ) || !map @@ -32,6 +33,7 @@ export default function ChangeView({ view, setView, episode, map }) { episode?.length > 0 ? map?.every( (item) => + item?.img?.includes("https://s4.anilist.co/") || item?.image?.includes("https://s4.anilist.co/") || item.title === null ) || !map @@ -50,6 +52,7 @@ export default function ChangeView({ view, setView, episode, map }) { episode?.length > 0 ? map?.every( (item) => + item?.img?.includes("https://s4.anilist.co/") || item?.image?.includes("https://s4.anilist.co/") || item.title === null ) || !map @@ -71,6 +74,7 @@ export default function ChangeView({ view, setView, episode, map }) { episode?.length > 0 ? map?.every( (item) => + item?.img?.includes("https://s4.anilist.co/") || item?.image?.includes("https://s4.anilist.co/") || item.title === null ) || !map diff --git a/components/anime/watch/primary/details.js b/components/anime/watch/primary/details.js deleted file mode 100644 index f092879..0000000 --- a/components/anime/watch/primary/details.js +++ /dev/null @@ -1,185 +0,0 @@ -import { useEffect, useState } from "react"; -import { useAniList } from "../../../../lib/anilist/useAnilist"; -import Skeleton from "react-loading-skeleton"; -import DisqusComments from "../../../disqus"; -import Image from "next/image"; - -export default function Details({ - info, - session, - epiNumber, - description, - id, - onList, - setOnList, - handleOpen, - disqus, -}) { - const [showComments, setShowComments] = useState(false); - const { markPlanning } = useAniList(session); - const [url, setUrl] = useState(null); - - function handlePlan() { - if (onList === false) { - markPlanning(info.id); - setOnList(true); - } - } - - useEffect(() => { - const url = window.location.href; - setShowComments(false); - setUrl(url); - }, [id]); - - return ( - <div className="flex flex-col gap-2"> - <div className="px-4 pt-7 pb-4 h-full flex"> - <div className="aspect-[9/13] h-[240px]"> - {info ? ( - <Image - src={info.coverImage.extraLarge} - alt="Anime Cover" - width={1000} - height={1000} - priority - className="object-cover aspect-[9/13] h-[240px] rounded-md" - /> - ) : ( - <Skeleton height={240} /> - )} - </div> - <div - className="grid w-full pl-5 gap-3 h-[240px]" - data-episode={info?.episodes || "0"} - > - <div className="grid grid-cols-2 gap-1 items-center"> - <h2 className="text-sm font-light font-roboto text-[#878787]"> - Studios - </h2> - <div className="row-start-2"> - {info ? info.studios.edges[0].node.name : <Skeleton width={80} />} - </div> - <div className="hidden xxs:grid col-start-2 place-content-end relative"> - <div> - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - strokeWidth={1.5} - stroke="currentColor" - onClick={() => { - session ? handlePlan() : handleOpen(); - }} - className={`w-8 h-8 hover:fill-white text-white hover:cursor-pointer ${ - onList ? "fill-white" : "" - }`} - > - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M17.593 3.322c1.1.128 1.907 1.077 1.907 2.185V21L12 17.25 4.5 21V5.507c0-1.108.806-2.057 1.907-2.185a48.507 48.507 0 0111.186 0z" - /> - </svg> - </div> - </div> - </div> - <div className="grid gap-1 items-center"> - <h2 className="text-sm font-light font-roboto text-[#878787]"> - Status - </h2> - <div>{info ? info.status : <Skeleton width={75} />}</div> - </div> - <div className="grid gap-1 items-center overflow-y-hidden"> - <h2 className="text-sm font-light font-roboto text-[#878787]"> - Titles - </h2> - <div className="grid grid-flow-dense grid-cols-2 gap-2 h-full w-full"> - {info ? ( - <> - <div className="title-rm line-clamp-3"> - {info.title?.romaji || ""} - </div> - <div className="title-en line-clamp-3"> - {info.title?.english || ""} - </div> - <div className="title-nt line-clamp-3"> - {info.title?.native || ""} - </div> - </> - ) : ( - <Skeleton width={200} height={50} /> - )} - </div> - </div> - </div> - </div> - <div className="flex flex-wrap gap-3 px-4 pt-3"> - {info && - info.genres?.map((item, index) => ( - <div - key={index} - className="border border-action text-gray-100 py-1 px-2 rounded-md font-karla text-sm" - > - {item} - </div> - ))} - </div> - <div className={`bg-secondary rounded-md mt-3 mx-3`}> - {info && ( - <p - dangerouslySetInnerHTML={{ __html: description }} - className={`p-5 text-sm font-light font-roboto text-[#e4e4e4] `} - /> - )} - </div> - {/* {<div className="mt-5 px-5"></div>} */} - {!showComments && ( - <div className="w-full flex justify-center py-2 font-karla px-3 lg:px-0"> - <button - onClick={() => setShowComments(true)} - className={ - showComments - ? "hidden" - : "flex-center gap-2 h-10 bg-secondary rounded w-full lg:w-[50%]" - } - > - Load Disqus{" "} - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - strokeWidth="1.5" - stroke="currentColor" - className="w-5 h-5" - > - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M20.25 8.511c.884.284 1.5 1.128 1.5 2.097v4.286c0 1.136-.847 2.1-1.98 2.193-.34.027-.68.052-1.02.072v3.091l-3-3c-1.354 0-2.694-.055-4.02-.163a2.115 2.115 0 01-.825-.242m9.345-8.334a2.126 2.126 0 00-.476-.095 48.64 48.64 0 00-8.048 0c-1.131.094-1.976 1.057-1.976 2.192v4.286c0 .837.46 1.58 1.155 1.951m9.345-8.334V6.637c0-1.621-1.152-3.026-2.76-3.235A48.455 48.455 0 0011.25 3c-2.115 0-4.198.137-6.24.402-1.608.209-2.76 1.614-2.76 3.235v6.226c0 1.621 1.152 3.026 2.76 3.235.577.075 1.157.14 1.74.194V21l4.155-4.155" - /> - </svg> - </button> - </div> - )} - {showComments && ( - <div> - {info && url && ( - <div className="mt-5 px-5"> - <DisqusComments - key={id} - post={{ - id: id, - title: info.title.romaji, - url: url, - episode: epiNumber, - name: disqus, - }} - /> - </div> - )} - </div> - )} - </div> - ); -} diff --git a/components/anime/watch/primarySide.js b/components/anime/watch/primarySide.js deleted file mode 100644 index a3d9f4f..0000000 --- a/components/anime/watch/primarySide.js +++ /dev/null @@ -1,276 +0,0 @@ -import { useEffect, useState } from "react"; -import { ChevronDownIcon } from "@heroicons/react/20/solid"; -import { ForwardIcon } from "@heroicons/react/24/solid"; -import { useRouter } from "next/router"; -import { signIn } from "next-auth/react"; -import Details from "./primary/details"; -import VideoPlayer from "../../videoPlayer"; -import Link from "next/link"; -import Skeleton from "react-loading-skeleton"; -import Modal from "../../modal"; -import AniList from "../../media/aniList"; - -export default function PrimarySide({ - info, - session, - epiNumber, - navigation, - providerId, - watchId, - onList, - proxy, - disqus, - setOnList, - episodeList, - timeWatched, - dub, -}) { - const [episodeData, setEpisodeData] = useState(); - const [open, setOpen] = useState(false); - const [skip, setSkip] = useState(); - - const [loading, setLoading] = useState(true); - - const router = useRouter(); - - useEffect(() => { - setLoading(true); - async function fetchData() { - if (info) { - const anify = await fetch("/api/v2/source", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - source: - providerId === "gogoanime" && !watchId.startsWith("/") - ? "consumet" - : "anify", - providerId: providerId, - watchId: watchId, - episode: epiNumber, - id: info.id, - sub: dub ? "dub" : "sub", - }), - }).then((res) => res.json()); - - const skip = await fetch( - `https://api.aniskip.com/v2/skip-times/${info.idMal}/${parseInt( - epiNumber - )}?types[]=ed&types[]=mixed-ed&types[]=mixed-op&types[]=op&types[]=recap&episodeLength=` - ).then((res) => { - if (!res.ok) { - switch (res.status) { - case 404: { - return null; - } - } - } - return res.json(); - }); - - const op = - skip?.results?.find((item) => item.skipType === "op") || null; - const ed = - skip?.results?.find((item) => item.skipType === "ed") || null; - - setSkip({ op, ed }); - - setEpisodeData(anify); - setLoading(false); - } - } - - fetchData(); - return () => { - setEpisodeData(); - setSkip(); - }; - }, [providerId, watchId, info]); - - useEffect(() => { - const mediaSession = navigator.mediaSession; - if (!mediaSession) return; - - const now = navigation?.playing; - const poster = now?.image || info?.bannerImage; - const title = now?.title || info?.title?.romaji; - - const artwork = poster - ? [{ src: poster, sizes: "512x512", type: "image/jpeg" }] - : undefined; - - mediaSession.metadata = new MediaMetadata({ - title: title, - artist: `Moopa ${ - title === info?.title?.romaji - ? "- Episode " + epiNumber - : `- ${info?.title?.romaji || info?.title?.english}` - }`, - artwork, - }); - }, [navigation, info, epiNumber]); - - function handleOpen() { - setOpen(true); - document.body.style.overflow = "hidden"; - } - - function handleClose() { - setOpen(false); - document.body.style.overflow = "auto"; - } - - return ( - <> - <Modal open={open} onClose={() => handleClose()}> - {!session && ( - <div className="flex-center flex-col gap-5 px-10 py-5 bg-secondary rounded-md"> - <h1 className="text-md font-extrabold font-karla"> - Edit your list - </h1> - <button - className="flex items-center bg-[#363642] rounded-md text-white p-1" - onClick={() => signIn("AniListProvider")} - > - <h1 className="px-1 font-bold font-karla">Login with AniList</h1> - <div className="scale-[60%] pb-[1px]"> - <AniList /> - </div> - </button> - </div> - )} - </Modal> - <div className="w-full h-full"> - <div key={watchId} className="w-full aspect-video bg-black"> - {!loading ? ( - navigation && episodeData?.sources?.length !== 0 ? ( - <VideoPlayer - session={session} - info={info} - data={episodeData} - provider={providerId} - id={watchId} - progress={epiNumber} - skip={skip} - proxy={proxy} - aniId={info.id} - aniTitle={info.title?.romaji || info.title?.english} - track={navigation} - timeWatched={timeWatched} - dub={dub} - /> - ) : ( - <p className="h-full flex-center"> - Video is not available, please try other providers - </p> - ) - ) : ( - <div className="flex-center aspect-video bg-black"> - <div className="lds-ellipsis"> - <div></div> - <div></div> - <div></div> - <div></div> - </div> - </div> - )} - </div> - <div className="flex flex-col divide-y divide-white/20"> - {info && episodeList ? ( - <div className="flex items-center justify-between py-3 px-3"> - <div className="flex flex-col gap-2 w-[60%]"> - <h1 className="text-xl font-outfit font-semibold line-clamp-1"> - <Link - href={`/en/anime/${info.id}`} - className="hover:underline" - title={navigation?.playing?.title || info.title?.romaji} - > - {navigation?.playing?.title || info.title?.romaji} - </Link> - </h1> - <h3 className="text-sm font-karla font-light"> - Episode {epiNumber} - </h3> - </div> - <div className="flex gap-4 items-center justify-end"> - <div className="relative"> - <select - className="flex items-center gap-5 rounded-[3px] bg-secondary py-1 px-3 pr-8 font-karla appearance-none cursor-pointer" - value={epiNumber} - onChange={(e) => { - const selectedEpisode = episodeList.find( - (episode) => episode.number === parseInt(e.target.value) - ); - router.push( - `/en/anime/watch/${info.id}/${providerId}?id=${ - selectedEpisode.id - }&num=${selectedEpisode.number}${ - dub ? `&dub=${dub}` : "" - }` - ); - }} - > - {episodeList.map((episode) => ( - <option key={episode.number} value={episode.number}> - Episode {episode.number} - </option> - ))} - </select> - <ChevronDownIcon className="absolute right-2 top-1/2 transform -translate-y-1/2 w-5 h-5 pointer-events-none" /> - </div> - <button - disabled={!navigation?.next} - className={`${ - !navigation?.next ? "pointer-events-none" : "" - }relative group`} - onClick={() => { - router.push( - `/en/anime/watch/${info.id}/${providerId}?id=${ - navigation?.next.id - }&num=${navigation?.next.number}${ - dub ? `&dub=${dub}` : "" - }` - ); - }} - > - <span className="absolute z-[9999] -left-11 -top-14 p-2 shadow-xl rounded-md transform transition-all whitespace-nowrap bg-secondary lg:group-hover:block group-hover:opacity-1 hidden font-karla font-bold"> - Next Episode - </span> - <ForwardIcon - className={`w-6 h-6 ${ - !navigation?.next ? "text-[#282828]" : "" - }`} - /> - </button> - </div> - </div> - ) : ( - <div className="py-3 px-4"> - <div className="text-xl font-outfit font-semibold line-clamp-2"> - <div className="inline hover:underline"> - <Skeleton width={240} /> - </div> - </div> - <h4 className="text-sm font-karla font-light"> - <Skeleton width={75} /> - </h4> - </div> - )} - <Details - info={info} - session={session} - description={navigation?.playing?.description || info?.description} - epiNumber={epiNumber} - id={watchId} - onList={onList} - setOnList={setOnList} - handleOpen={handleOpen} - disqus={disqus} - /> - </div> - </div> - </> - ); -} diff --git a/components/anime/watch/secondarySide.js b/components/anime/watch/secondarySide.js deleted file mode 100644 index c9ef684..0000000 --- a/components/anime/watch/secondarySide.js +++ /dev/null @@ -1,134 +0,0 @@ -import Skeleton from "react-loading-skeleton"; -import Image from "next/image"; -import Link from "next/link"; - -export default function SecondarySide({ - info, - map, - providerId, - watchId, - episode, - artStorage, - dub, -}) { - const progress = info.mediaListEntry?.progress; - return ( - <div className="lg:w-[35%] shrink-0 w-screen"> - <h1 className="text-xl font-karla pl-4 pb-5 font-semibold">Up Next</h1> - <div className="flex flex-col gap-5 lg:pl-5 py-2 scrollbar-thin px-2 scrollbar-thumb-[#313131] scrollbar-thumb-rounded-full"> - {episode && episode.length > 0 ? ( - map?.some((item) => item.title && item.description) > 0 ? ( - episode.map((item) => { - const time = artStorage?.[item.id]?.timeWatched; - const duration = artStorage?.[item.id]?.duration; - let prog = (time / duration) * 100; - if (prog > 90) prog = 100; - - const mapData = map?.find((i) => i.number === item.number); - return ( - <Link - href={`/en/anime/watch/${ - info.id - }/${providerId}?id=${encodeURIComponent(item.id)}&num=${ - item.number - }${dub ? `&dub=${dub}` : ""}`} - key={item.id} - className={`bg-secondary flex w-full h-[110px] rounded-lg scale-100 transition-all duration-300 ease-out ${ - item.id == watchId - ? "pointer-events-none ring-1 ring-action" - : "cursor-pointer hover:scale-[1.02] ring-0 hover:ring-1 hover:shadow-lg ring-white" - }`} - > - <div className="w-[43%] lg:w-[40%] h-[110px] relative rounded-lg z-40 shrink-0 overflow-hidden shadow-[4px_0px_5px_0px_rgba(0,0,0,0.3)]"> - <div className="relative"> - {/* {mapData?.image && ( */} - <Image - src={mapData?.image || info?.coverImage?.extraLarge} - alt="Anime Cover" - width={1000} - height={1000} - className={`object-cover z-30 rounded-lg h-[110px] ${ - item.id == watchId - ? "brightness-[30%]" - : "brightness-75" - }`} - /> - {/* )} */} - <span - className={`absolute bottom-0 left-0 h-[2px] bg-red-700`} - style={{ - width: - progress && artStorage && item?.number <= progress - ? "100%" - : artStorage?.[item?.id] - ? `${prog}%` - : "0", - }} - /> - <span className="absolute bottom-2 left-2 font-karla font-bold text-sm"> - Episode {item?.number} - </span> - {item.id == watchId && ( - <div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 scale-[1.5]"> - <svg - xmlns="http://www.w3.org/2000/svg" - viewBox="0 0 20 20" - fill="currentColor" - className="w-5 h-5" - > - <path d="M6.3 2.841A1.5 1.5 0 004 4.11V15.89a1.5 1.5 0 002.3 1.269l9.344-5.89a1.5 1.5 0 000-2.538L6.3 2.84z" /> - </svg> - </div> - )} - </div> - </div> - <div - className={`w-full h-full overflow-x-hidden select-none p-4 flex flex-col gap-2 ${ - item.id == watchId ? "text-[#7a7a7a]" : "" - }`} - > - <h1 className="font-karla font-bold italic line-clamp-1"> - {mapData?.title} - </h1> - <p className="line-clamp-2 text-xs italic font-outfit font-extralight"> - {mapData?.description} - </p> - </div> - </Link> - ); - }) - ) : ( - episode.map((item) => { - return ( - <Link - href={`/en/anime/watch/${ - info.id - }/${providerId}?id=${encodeURIComponent(item.id)}&num=${ - item.number - }${dub ? `&dub=${dub}` : ""}`} - key={item.id} - className={`bg-secondary flex-center w-full h-[50px] rounded-lg scale-100 transition-all duration-300 ease-out ${ - item.id == watchId - ? "pointer-events-none ring-1 ring-action text-[#5d5d5d]" - : "cursor-pointer hover:scale-[1.02] ring-0 hover:ring-1 hover:shadow-lg ring-white" - }`} - > - Episode {item.number} - </Link> - ); - }) - ) - ) : ( - <> - {[1].map((item) => ( - <Skeleton - key={item} - className="bg-secondary flex w-full h-[110px] rounded-lg scale-100 transition-all duration-300 ease-out" - /> - ))} - </> - )} - </div> - </div> - ); -} |