diff options
Diffstat (limited to 'components/anime/watch')
| -rw-r--r-- | components/anime/watch/primary/details.js | 177 | ||||
| -rw-r--r-- | components/anime/watch/primarySide.js | 213 | ||||
| -rw-r--r-- | components/anime/watch/secondarySide.js | 129 |
3 files changed, 519 insertions, 0 deletions
diff --git a/components/anime/watch/primary/details.js b/components/anime/watch/primary/details.js new file mode 100644 index 0000000..94c3360 --- /dev/null +++ b/components/anime/watch/primary/details.js @@ -0,0 +1,177 @@ +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, + 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]"> + <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="line-clamp-3">{info.title?.romaji || ""}</div> + <div className="line-clamp-3"> + {info.title?.english || ""} + </div> + <div className="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: info?.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 new file mode 100644 index 0000000..49bb1b6 --- /dev/null +++ b/components/anime/watch/primarySide.js @@ -0,0 +1,213 @@ +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"; +import axios from "axios"; + +export default function PrimarySide({ + info, + session, + epiNumber, + setLoading, + navigation, + loading, + providerId, + watchId, + status, + onList, + proxy, + disqus, + setOnList, + episodeList, +}) { + const [episodeData, setEpisodeData] = useState(); + const [open, setOpen] = useState(false); + const [skip, setSkip] = useState(); + + const router = useRouter(); + + useEffect(() => { + setLoading(true); + setEpisodeData(); + setSkip(); + async function fetchData() { + if (info) { + const { data } = await axios.get( + `/api/consumet/source/${providerId}/${watchId}` + ); + + 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((r) => { + if (!r.ok) { + switch (r.status) { + case 404: { + return null; + } + } + } + return r.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(data); + setLoading(false); + } + // setMal(malId); + } + + fetchData(); + }, [providerId, watchId, info]); + + 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 ? ( + episodeData && ( + <VideoPlayer + session={session} + data={episodeData} + provider={providerId} + id={watchId} + progress={epiNumber} + stats={status} + skip={skip} + proxy={proxy} + aniId={info.id} + /> + ) + ) : ( + <div className="aspect-video bg-black" /> + )} + </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" + > + {navigation?.playing?.title || info.title?.romaji} + </Link> + </h1> + <h1 className="text-sm font-karla font-light"> + Episode {epiNumber} + </h1> + </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}` + ); + }} + > + {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}` + ); + }} + > + <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} + 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 new file mode 100644 index 0000000..e3f0224 --- /dev/null +++ b/components/anime/watch/secondarySide.js @@ -0,0 +1,129 @@ +import Skeleton from "react-loading-skeleton"; +import Image from "next/image"; +import Link from "next/link"; + +export default function SecondarySide({ + info, + providerId, + watchId, + episode, + progress, + artStorage, + dub, +}) { + 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 ? ( + episode.some((item) => item.title && item.description) > 0 ? ( + episode.map((item) => { + const time = artStorage?.[item.id]?.time; + const duration = artStorage?.[item.id]?.duration; + let prog = (time / duration) * 100; + if (prog > 90) prog = 100; + 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"> + <Image + src={item.image} + 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-[3px] 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-[70%] h-full select-none p-4 flex flex-col gap-2 ${ + item.id == watchId ? "text-[#7a7a7a]" : "" + }`} + > + <h1 className="font-karla font-bold italic line-clamp-1"> + {item.title} + </h1> + <p className="line-clamp-2 text-xs italic font-outfit font-extralight"> + {item?.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> + ); +} |