diff options
| author | Factiven <[email protected]> | 2023-08-04 14:49:35 +0700 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-08-04 14:49:35 +0700 |
| commit | 1eb531338f5ae3696fa9d68a4171a73f0107c2f8 (patch) | |
| tree | 67afef1c72b39bc1fa0d0f4cff7b0586c4d519c9 /components | |
| parent | Update package.json (diff) | |
| download | moopa-3.8.5.tar.xz moopa-3.8.5.zip | |
Update v3.8.5 - Merged Beta to Main (#32)v3.8.5
* initial commit
* Update_v.3.6.7-beta-v1.2
* Update_v.3.6.7-beta-v1.3
* Update_v.3.6.7-beta-v1.3
> update API
* Fixed mediaList won't update
* added .env disqus shortname
* Update_v3.6.7-beta-v1.4
>Implementing database
* Create main.yml
* Update v3.6.7-beta-v1.5
small patch
* title home page
* Update content.js
* Delete db-test.js
* Update content.js
* Update home page card
* Update v3.7.0
* Update v3.7.1-beta
> migrating backend to main code
> fixed schedule component
* Update v3.8.0
> Added dub options
> Moved schedule backend
* Update v.3.8.1
> Fixed episodes on watch page isn't dubbed
* Update v3.8.1-patch-1
* Update v3.8.1-patch-2
> Another patch for dub
* Update v3.8.2
> Removed prisma configuration for database since it's not stable yet
* Update v3.8.3
> Fixed different provider have same id
* Update v.3.8.3
> Fixed player bug where the controls won't hide after updating anilist progress
* Update v3.8.4-patch-2
* Update v3.8.5
> Update readme.md
> Update .env.example
Diffstat (limited to 'components')
| -rw-r--r-- | components/anime/changeView.js | 107 | ||||
| -rw-r--r-- | components/anime/episode.js | 281 | ||||
| -rw-r--r-- | components/anime/infoDetails.js | 203 | ||||
| -rw-r--r-- | components/anime/mobile/topSection.js | 81 | ||||
| -rw-r--r-- | components/anime/viewMode/listMode.js | 39 | ||||
| -rw-r--r-- | components/anime/viewMode/thumbnailDetail.js | 76 | ||||
| -rw-r--r-- | components/anime/viewMode/thumbnailOnly.js | 59 | ||||
| -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 | ||||
| -rw-r--r-- | components/disqus.js | 2 | ||||
| -rw-r--r-- | components/home/content.js | 23 | ||||
| -rw-r--r-- | components/home/genres.js | 2 | ||||
| -rw-r--r-- | components/home/schedule.js | 4 | ||||
| -rw-r--r-- | components/listEditor.js | 2 | ||||
| -rw-r--r-- | components/videoPlayer.js | 49 |
16 files changed, 1406 insertions, 41 deletions
diff --git a/components/anime/changeView.js b/components/anime/changeView.js new file mode 100644 index 0000000..cab9054 --- /dev/null +++ b/components/anime/changeView.js @@ -0,0 +1,107 @@ +import { useEffect, useState } from "react"; + +export default function ChangeView({ view, setView, episode }) { + // const [view, setView] = useState(1); + // const episode = null; + return ( + <div className="flex gap-3 rounded-sm items-center p-2"> + <div + className={ + episode?.length > 0 + ? episode?.some((item) => item?.title === null) + ? "pointer-events-none" + : "cursor-pointer" + : "pointer-events-none" + } + onClick={() => { + setView(1); + localStorage.setItem("view", 1); + }} + > + <svg + xmlns="http://www.w3.org/2000/svg" + width="31" + height="20" + fill="none" + viewBox="0 0 31 20" + > + <rect + width="31" + height="20" + className={`${ + episode?.length > 0 + ? episode?.some((item) => item?.title === null) + ? "fill-[#1c1c22]" + : view === 1 + ? "fill-action" + : "fill-[#3A3A44]" + : "fill-[#1c1c22]" + }`} + rx="3" + ></rect> + </svg> + </div> + <div + className={ + episode?.length > 0 + ? episode?.some((item) => item?.title === null) + ? "pointer-events-none" + : "cursor-pointer" + : "pointer-events-none" + } + onClick={() => { + setView(2); + localStorage.setItem("view", 2); + }} + > + <svg + xmlns="http://www.w3.org/2000/svg" + width="33" + height="20" + fill="none" + className={`${ + episode?.length > 0 + ? episode?.some((item) => item?.title === null) + ? "fill-[#1c1c22]" + : view === 2 + ? "fill-action" + : "fill-[#3A3A44]" + : "fill-[#1c1c22]" + }`} + viewBox="0 0 33 20" + > + <rect width="33" height="7" y="1" rx="3"></rect> + <rect width="33" height="7" y="12" rx="3"></rect> + </svg> + </div> + <div + className={ + episode?.length > 0 ? `cursor-pointer` : "pointer-events-none" + } + onClick={() => { + setView(3); + localStorage.setItem("view", 3); + }} + > + <svg + xmlns="http://www.w3.org/2000/svg" + width="33" + height="20" + fill="none" + className={`${ + episode?.length > 0 + ? view === 3 + ? "fill-action" + : "fill-[#3A3A44]" + : "fill-[#1c1c22]" + }`} + viewBox="0 0 33 20" + > + <rect width="29" height="4" x="2" y="2" rx="2"></rect> + <rect width="29" height="4" x="2" y="8" rx="2"></rect> + <rect width="16" height="4" x="2" y="14" rx="2"></rect> + </svg> + </div> + </div> + ); +} diff --git a/components/anime/episode.js b/components/anime/episode.js new file mode 100644 index 0000000..c889c25 --- /dev/null +++ b/components/anime/episode.js @@ -0,0 +1,281 @@ +import { useEffect, useState, Fragment } from "react"; +import { ChevronDownIcon, ClockIcon } from "@heroicons/react/20/solid"; +import { convertSecondsToTime } from "../../utils/getTimes"; +import ChangeView from "./changeView"; +import ThumbnailOnly from "./viewMode/thumbnailOnly"; +import ThumbnailDetail from "./viewMode/thumbnailDetail"; +import ListMode from "./viewMode/listMode"; +import axios from "axios"; + +export default function AnimeEpisode({ info, progress }) { + const [providerId, setProviderId] = useState(); // default provider + const [currentPage, setCurrentPage] = useState(1); // for pagination + const [visible, setVisible] = useState(false); // for mobile view + const itemsPerPage = 13; // choose your number of items per page + + const [loading, setLoading] = useState(true); + const [artStorage, setArtStorage] = useState(null); + const [view, setView] = useState(3); + const [isDub, setIsDub] = useState(false); + + const [providers, setProviders] = useState(null); + + useEffect(() => { + setLoading(true); + setProviders(null); + const fetchData = async () => { + try { + const { data: firstResponse } = await axios.get( + `/api/consumet/episode/${info.id}${isDub === true ? "?dub=true" : ""}` + ); + if (firstResponse.data.length > 0) { + const defaultProvider = firstResponse.data?.find( + (x) => x.providerId === "gogoanime" + ); + setProviderId( + defaultProvider?.providerId || firstResponse.data[0].providerId + ); // set to first provider id + } + + setArtStorage(JSON.parse(localStorage.getItem("artplayer_settings"))); + setProviders(firstResponse.data); + setLoading(false); + } catch (error) { + setLoading(false); + setProviders([]); + } + }; + fetchData(); + }, [info.id, isDub]); + + const episodes = + providers?.find((provider) => provider.providerId === providerId) + ?.episodes || []; + + const lastEpisodeIndex = currentPage * itemsPerPage; + const firstEpisodeIndex = lastEpisodeIndex - itemsPerPage; + const currentEpisodes = episodes.slice(firstEpisodeIndex, lastEpisodeIndex); + const totalPages = Math.ceil(episodes.length / itemsPerPage); + + const handleChange = (event) => { + setProviderId(event.target.value); + }; + + const handlePageChange = (pageNumber) => { + setCurrentPage(pageNumber); + }; + + useEffect(() => { + if (episodes?.some((item) => item?.title === null)) { + setView(3); + } + }, [providerId, episodes]); + + 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 lg:gap-10 sm:gap-7 gap-3"> + {info && ( + <h1 className="text-[20px] lg:text-2xl font-bold font-karla"> + Episodes + </h1> + )} + {info?.nextAiringEpisode && ( + <div className="flex items-center gap-2"> + <div className="flex items-center gap-4 text-[10px] xxs:text-sm lg:text-base"> + <h1>Next :</h1> + <div className="px-4 rounded-sm font-karla font-bold bg-white text-black"> + {convertSecondsToTime( + info.nextAiringEpisode.timeUntilAiring + )} + </div> + </div> + <div className="h-6 w-6"> + <ClockIcon /> + </div> + </div> + )} + </div> + + <div className="flex items-center gap-2"> + <div + onClick={() => setIsDub((prev) => !prev)} + className="flex lg:hidden flex-col items-center relative rounded-md bg-secondary py-1.5 px-3 font-karla text-sm hover:ring-1 ring-action cursor-pointer group" + > + {isDub ? "Dub" : "Sub"} + <span className="absolute invisible opacity-0 group-hover:opacity-100 group-hover:scale-100 scale-0 group-hover:-translate-y-7 translate-y-0 group-hover:visible rounded-sm shadow top-0 w-28 bg-secondary text-center transition-all transform duration-200 ease-out"> + Switch to {isDub ? "Sub" : "Dub"} + </span> + </div> + <div + className="lg:hidden bg-secondary p-1 rounded-md cursor-pointer" + onClick={() => setVisible(!visible)} + > + <svg + xmlns="http://www.w3.org/2000/svg" + fill="none" + viewBox="0 0 24 24" + strokeWidth={1.5} + stroke="currentColor" + className="w-6 h-6" + > + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M6.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM12.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM18.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0z" + /> + </svg> + </div> + </div> + </div> + <div + className={`flex lg:flex gap-3 items-center justify-between ${ + visible ? "" : "hidden" + }`} + > + {providers && ( + <div + onClick={() => setIsDub((prev) => !prev)} + className="hidden lg:flex flex-col items-center relative rounded-[3px] bg-secondary py-1 px-3 font-karla text-sm hover:ring-1 ring-action cursor-pointer group" + > + {isDub ? "Dub" : "Sub"} + <span className="absolute invisible opacity-0 group-hover:opacity-100 group-hover:scale-100 scale-0 group-hover:-translate-y-7 translate-y-0 group-hover:visible rounded-sm shadow top-0 w-28 bg-secondary text-center transition-all transform duration-200 ease-out"> + Switch to {isDub ? "Sub" : "Dub"} + </span> + </div> + )} + {providers && providers.length > 0 && ( + <> + <div className="flex gap-3"> + <div className="relative flex gap-2 items-center group"> + <select + title="Providers" + onChange={handleChange} + value={providerId} + className="flex items-center text-sm gap-5 rounded-[3px] bg-secondary py-1 px-3 pr-8 font-karla appearance-none cursor-pointer outline-none focus:ring-1 focus:ring-action group-hover:ring-1 group-hover:ring-action" + > + {providers.map((provider) => ( + <option + key={provider.providerId} + value={provider.providerId} + > + {provider.providerId} + </option> + ))} + </select> + {/* <span className="absolute invisible opacity-0 group-hover:opacity-100 group-hover:scale-100 scale-0 group-hover:-translate-y-7 translate-y-0 group-hover:visible rounded-sm shadow top-0 w-32 bg-secondary text-center transition-all transform duration-200 ease-out"> + Select Providers + </span> */} + <ChevronDownIcon className="absolute right-2 top-1/2 transform -translate-y-1/2 w-5 h-5 pointer-events-none" /> + </div> + + {totalPages > 1 && ( + <div className="relative flex gap-2 items-center"> + <select + title="Pages" + onChange={(e) => + handlePageChange(Number(e.target.value)) + } + className="flex items-center text-sm gap-5 rounded-[3px] bg-secondary py-1 px-3 pr-8 font-karla appearance-none cursor-pointer outline-none focus:ring-1 focus:ring-action hover:ring-1 hover:ring-action" + > + {[...Array(totalPages)].map((_, i) => ( + <option key={i} value={i + 1}> + {i + 1} + </option> + ))} + </select> + <ChevronDownIcon className="absolute right-2 top-1/2 transform -translate-y-1/2 w-5 h-5 pointer-events-none" /> + </div> + )} + </div> + </> + )} + + <ChangeView + view={view} + setView={setView} + episode={currentEpisodes} + /> + </div> + </div> + + {/* Episodes */} + {!loading ? ( + <div + className={ + view === 1 + ? "grid md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-5 lg:gap-8 place-items-center" + : `flex flex-col gap-3` + } + > + {Array.isArray(providers) ? ( + providers.length > 0 ? ( + currentEpisodes.map((episode, index) => { + return ( + <Fragment key={index}> + {view === 1 && ( + <ThumbnailOnly + key={index} + index={index} + info={info} + providerId={providerId} + episode={episode} + artStorage={artStorage} + progress={progress} + dub={isDub} + // image={thumbnail} + /> + )} + {view === 2 && ( + <ThumbnailDetail + key={index} + index={index} + epi={episode} + provider={providerId} + info={info} + artStorage={artStorage} + progress={progress} + dub={isDub} + /> + )} + {view === 3 && ( + <ListMode + key={index} + info={info} + episode={episode} + index={index} + providerId={providerId} + progress={progress} + dub={isDub} + /> + )} + </Fragment> + ); + }) + ) : ( + <div className="h-[20vh] lg:w-full flex-center flex-col gap-5"> + <p className="text-center font-karla font-bold lg:text-lg"> + Oops!<br></br> It looks like this anime is not available. + </p> + </div> + ) + ) : ( + <p>{providers.message}</p> + )} + </div> + ) : ( + <div className="flex justify-center"> + <div className="lds-ellipsis"> + <div></div> + <div></div> + <div></div> + <div></div> + </div> + </div> + )} + </div> + </> + ); +} diff --git a/components/anime/infoDetails.js b/components/anime/infoDetails.js new file mode 100644 index 0000000..0cf233c --- /dev/null +++ b/components/anime/infoDetails.js @@ -0,0 +1,203 @@ +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=" font-inter font-bold text-[36px] text-white line-clamp-1"> + {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 || rel.coverImage.large + } + 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 || rel.title.romaji} + </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 new file mode 100644 index 0000000..4f7c4b3 --- /dev/null +++ b/components/anime/mobile/topSection.js @@ -0,0 +1,81 @@ +import { HeartIcon } from "@heroicons/react/20/solid"; + +import { + TvIcon, + ArrowTrendingUpIcon, + RectangleStackIcon, +} from "@heroicons/react/24/outline"; + +export default function DetailTop({ info, statuses, handleOpen, loading }) { + return ( + <div className="lg:hidden pt-5 w-screen px-5 flex flex-col"> + <div className="h-[250px] flex flex-col gap-1 justify-center"> + <h1 className="font-karla font-extrabold text-lg line-clamp-1 w-[70%]"> + {info?.title?.romaji || info?.title?.english} + </h1> + <p + className="line-clamp-2 text-sm font-light antialiased w-[56%]" + dangerouslySetInnerHTML={{ __html: info?.description }} + /> + <div className="font-light flex gap-1 py-1 flex-wrap font-outfit text-[10px] text-[#ffffff] w-[70%]"> + {info?.genres + ?.slice(0, info?.genres?.length > 3 ? info?.genres?.length : 3) + .map((item, index) => ( + <span + key={index} + className="px-2 py-1 bg-secondary shadow-lg font-outfit font-light rounded-full" + > + <span>{item}</span> + </span> + ))} + </div> + {info && ( + <div className="flex items-center gap-5 pt-3 text-center"> + <div className="flex items-center gap-2 text-center"> + <button + type="button" + className="bg-action px-10 rounded-sm font-karla font-bold" + onClick={() => handleOpen()} + > + {!loading + ? statuses + ? statuses.name + : "Add to List" + : "Loading..."} + </button> + <div className="h-6 w-6"> + <HeartIcon /> + </div> + </div> + </div> + )} + </div> + <div className="bg-secondary rounded-sm xs:h-[30px]"> + <div className="grid grid-cols-3 place-content-center xxs:flex items-center justify-center h-full xxs:gap-10 p-2 text-sm"> + {info && info.status !== "NOT_YET_RELEASED" ? ( + <> + <div className="flex-center flex-col xxs:flex-row gap-2"> + <TvIcon className="w-5 h-5 text-action" /> + <h4 className="font-karla">{info?.type}</h4> + </div> + <div className="flex-center flex-col xxs:flex-row gap-2"> + <ArrowTrendingUpIcon className="w-5 h-5 text-action" /> + <h4>{info?.averageScore}%</h4> + </div> + <div className="flex-center flex-col xxs:flex-row gap-2"> + <RectangleStackIcon className="w-5 h-5 text-action" /> + {info?.episodes ? ( + <h1>{info?.episodes} Episodes</h1> + ) : ( + <h1>TBA</h1> + )} + </div> + </> + ) : ( + <div>{info && "Not Yet Released"}</div> + )} + </div> + </div> + </div> + ); +} diff --git a/components/anime/viewMode/listMode.js b/components/anime/viewMode/listMode.js new file mode 100644 index 0000000..2016262 --- /dev/null +++ b/components/anime/viewMode/listMode.js @@ -0,0 +1,39 @@ +import Link from "next/link"; + +export default function ListMode({ + info, + episode, + index, + providerId, + progress, + dub, +}) { + return ( + <div key={episode.number} className="flex flex-col gap-3 px-2"> + <Link + href={`/en/anime/watch/${info.id}/${providerId}?id=${encodeURIComponent( + episode.id + )}&num=${episode.number}${dub ? `&dub=${dub}` : ""}`} + className={`text-start text-sm lg:text-lg ${ + progress && episode.number <= progress + ? "text-[#5f5f5f]" + : "text-white" + }`} + > + <p>Episode {episode.number}</p> + {episode.title && ( + <p + className={`text-xs lg:text-sm ${ + progress && episode.number <= progress + ? "text-[#5f5f5f]" + : "text-[#b1b1b1]" + } italic`} + > + "{episode.title}" + </p> + )} + </Link> + {index !== episode?.length - 1 && <span className="h-[1px] bg-white" />} + </div> + ); +} diff --git a/components/anime/viewMode/thumbnailDetail.js b/components/anime/viewMode/thumbnailDetail.js new file mode 100644 index 0000000..a085bc7 --- /dev/null +++ b/components/anime/viewMode/thumbnailDetail.js @@ -0,0 +1,76 @@ +import Image from "next/image"; +import Link from "next/link"; + +export default function ThumbnailDetail({ + index, + epi, + info, + provider, + artStorage, + progress, + dub, +}) { + const time = artStorage?.[epi?.id]?.time; + const duration = artStorage?.[epi?.id]?.duration; + let prog = (time / duration) * 100; + if (prog > 90) prog = 100; + + return ( + <Link + key={index} + href={`/en/anime/watch/${info.id}/${provider}?id=${encodeURIComponent( + epi.id + )}&num=${epi.number}${dub ? `&dub=${dub}` : ""}`} + className="flex group h-[110px] lg:h-[160px] w-full rounded-lg transition-all duration-300 ease-out bg-secondary cursor-pointer hover:scale-[1.02] ring-0 hover:ring-1 hover:shadow-lg ring-white" + > + <div className="w-[43%] lg:w-[30%] relative shrink-0 z-40 rounded-lg overflow-hidden shadow-[4px_0px_5px_0px_rgba(0,0,0,0.3)]"> + <div className="relative"> + <Image + src={epi?.image} + alt="Anime Cover" + width={1000} + height={1000} + className="object-cover z-30 rounded-lg h-[110px] lg:h-[160px] brightness-[65%]" + /> + <span + className={`absolute bottom-0 left-0 h-[3px] bg-red-700`} + style={{ + width: + progress && artStorage && epi?.number <= progress + ? "100%" + : artStorage?.[epi?.id] + ? `${prog}%` + : "0%", + }} + /> + <span className="absolute bottom-2 left-2 font-karla font-semibold text-sm lg:text-lg"> + Episode {epi?.number} + </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 + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 20 20" + fill="currentColor" + className="w-5 h-5 invisible group-hover:visible" + > + <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 justify-center gap-3`} + > + <h1 className="font-karla font-bold text-base lg:text-lg xl:text-xl italic line-clamp-1"> + {epi?.title} + </h1> + {epi?.description && ( + <p className="line-clamp-2 text-xs lg:text-md xl:text-lg italic font-outfit font-extralight"> + {epi?.description} + </p> + )} + </div> + </Link> + ); +} diff --git a/components/anime/viewMode/thumbnailOnly.js b/components/anime/viewMode/thumbnailOnly.js new file mode 100644 index 0000000..6063dfc --- /dev/null +++ b/components/anime/viewMode/thumbnailOnly.js @@ -0,0 +1,59 @@ +import Image from "next/image"; +import Link from "next/link"; + +export default function ThumbnailOnly({ + info, + providerId, + episode, + artStorage, + progress, + dub, +}) { + const time = artStorage?.[episode?.id]?.time; + const duration = artStorage?.[episode?.id]?.duration; + let prog = (time / duration) * 100; + if (prog > 90) prog = 100; + return ( + <Link + // key={index} + href={`/en/anime/watch/${info.id}/${providerId}?id=${encodeURIComponent( + episode.id + )}&num=${episode.number}${dub ? `&dub=${dub}` : ""}`} + 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} + </span> + <span + className={`absolute bottom-7 left-0 h-1 bg-red-600`} + style={{ + width: + progress && artStorage && episode?.number <= progress + ? "100%" + : artStorage?.[episode?.id] + ? `${prog}%` + : "0%", + }} + /> + <div className="absolute inset-0 bg-black z-30 opacity-20" /> + <Image + // src={ + // providerId === "animepahe" + // ? `https://img.moopa.live/image-proxy?url=${encodeURIComponent( + // episode.img + // )}&headers=${encodeURIComponent( + // JSON.stringify({ Referer: "https://animepahe.com/" }) + // )}` + // : thumbnail?.img.includes("null") + // ? info.coverImage.large + // : thumbnail?.img || info.coverImage.large + // } + src={episode?.image} + alt="epi image" + width={500} + height={500} + className="object-cover w-full h-[150px] sm:h-[100px] z-20" + /> + </Link> + ); +} 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> + ); +} diff --git a/components/disqus.js b/components/disqus.js index b276995..724bec3 100644 --- a/components/disqus.js +++ b/components/disqus.js @@ -1,7 +1,7 @@ import { DiscussionEmbed } from "disqus-react"; const DisqusComments = ({ post }) => { - const disqusShortname = "your_disqus_shortname"; + const disqusShortname = post.name || "your_disqus_shortname"; const disqusConfig = { url: post.url, identifier: post.id, // Single post id diff --git a/components/home/content.js b/components/home/content.js index 9b2b1a9..9d41fe9 100644 --- a/components/home/content.js +++ b/components/home/content.js @@ -151,7 +151,7 @@ export default function Content({ ids, section, data, og, userName }) { </div> <div id={ids} - className="scroll flex h-full w-full items-center select-none overflow-x-scroll whitespace-nowrap overflow-y-hidden scrollbar-hide lg:gap-8 gap-3 lg:p-10 py-8 px-5 z-30 scroll-smooth" + className="scroll flex h-full w-full select-none overflow-x-scroll overflow-y-hidden scrollbar-hide lg:gap-8 gap-4 lg:p-10 py-8 px-5 z-30 scroll-smooth" onScroll={handleScroll} onMouseDown={handleMouseDown} onMouseUp={handleMouseUp} @@ -165,14 +165,14 @@ export default function Content({ ids, section, data, og, userName }) { return ( <div key={anime.id} - className="flex shrink-0 cursor-pointer items-center" + className="flex flex-col gap-3 shrink-0 cursor-pointer" > <Link href={`/${lang}/anime/${anime.id}`} className="hover:scale-105 hover:shadow-lg group relative duration-300 ease-out" > {ids === "onGoing" && ( - <div className="h-[190px] w-[135px] lg:h-[265px] lg:w-[185px] bg-gradient-to-b from-transparent to-black absolute z-40 rounded-md whitespace-normal font-karla group"> + <div className="h-[190px] lg:h-[265px] w-[135px] lg:w-[185px] bg-gradient-to-b from-transparent to-black absolute z-40 rounded-md whitespace-normal font-karla group"> <div className="flex flex-col items-center h-full justify-end text-center pb-5"> <h1 className="line-clamp-1 w-[70%] text-[10px]"> {anime.title.romaji || anime.title.english} @@ -217,7 +217,7 @@ export default function Content({ ids, section, data, og, userName }) { alt={ anime.title.romaji || anime.title.english || "coverImage" } - width={209} + width={500} height={300} placeholder="blur" blurDataURL={ @@ -229,6 +229,21 @@ export default function Content({ ids, section, data, og, userName }) { className="z-20 h-[190px] w-[135px] lg:h-[265px] lg:w-[185px] object-cover rounded-md brightness-90" /> </Link> + {ids !== "onGoing" && ( + <Link + href={`/en/anime/${anime.id}`} + className="w-[135px] lg:w-[185px] line-clamp-2" + > + <h1 className="font-karla font-semibold xl:text-base text-[15px]"> + {anime.status === "RELEASING" ? ( + <span className="dots bg-green-500" /> + ) : anime.status === "NOT_YET_RELEASED" ? ( + <span className="dots bg-red-500" /> + ) : null} + {anime.title.romaji} + </h1> + </Link> + )} </div> ); })} diff --git a/components/home/genres.js b/components/home/genres.js index ac67260..3eefecd 100644 --- a/components/home/genres.js +++ b/components/home/genres.js @@ -57,7 +57,7 @@ export default function Genres() { <div className="flex xl:justify-center items-center relative"> <div className="bg-gradient-to-r from-primary to-transparent z-40 absolute w-7 h-[200px] left-0" /> <div className="flex lg:gap-8 gap-3 lg:p-10 py-8 px-5 z-30 overflow-y-hidden overflow-x-scroll snap-x snap-proximity scrollbar-none relative"> - <div className="flex lg:gap-10 gap-3"> + <div className="flex lg:gap-10 gap-4"> {g.map((a, index) => ( <Link href={`${lang}/search/anime/?genres=${a.name}`} diff --git a/components/home/schedule.js b/components/home/schedule.js index 4a85143..187fa17 100644 --- a/components/home/schedule.js +++ b/components/home/schedule.js @@ -45,11 +45,11 @@ export default function Schedule({ data, scheduleData, time }) { <div className="rounded mb-5 shadow-md shadow-black"> <div className="overflow-hidden w-full h-[96px] lg:h-[10rem] rounded relative"> <div className="absolute flex flex-col justify-center pl-5 lg:pl-16 rounded z-20 bg-gradient-to-r from-30% from-[#0c0c0c] to-transparent w-full h-full"> - <h1 className="text-xs xl:text-lg">Coming Up Next!</h1> + <h1 className="text-xs lg:text-lg">Coming Up Next!</h1> <div className="w-1/2 lg:w-2/5 hidden lg:block font-medium font-karla leading-[2.9rem] text-white line-clamp-1"> <Link href={`/en/anime/${data.id}`} - className="hover:underline underline-offset-4 decoration-2 lg:text-lg xl:text-2xl 2xl:text-3xl" + className="hover:underline underline-offset-4 decoration-2 lg:text-[1.7vw] " > {data.title.romaji || data.title.english || data.title.native} </Link> diff --git a/components/listEditor.js b/components/listEditor.js index d88f2af..49aa38e 100644 --- a/components/listEditor.js +++ b/components/listEditor.js @@ -3,7 +3,7 @@ import Image from "next/image"; import { toast } from "react-toastify"; const ListEditor = ({ animeId, session, stats, prg, max, image = null }) => { - const [status, setStatus] = useState(stats ?? ""); + const [status, setStatus] = useState(stats ?? "CURRENT"); const [progress, setProgress] = useState(prg ?? 0); const handleSubmit = async (e) => { diff --git a/components/videoPlayer.js b/components/videoPlayer.js index 22e6916..301812a 100644 --- a/components/videoPlayer.js +++ b/components/videoPlayer.js @@ -25,8 +25,7 @@ export default function VideoPlayer({ session, aniId, stats, - op, - ed, + skip, title, poster, proxy, @@ -77,17 +76,13 @@ export default function VideoPlayer({ return { ...(isDefault && { default: true }), html: items.quality === "default" ? "adaptive" : items.quality, - // url: `${proxy}${items.url}`, url: provider === "gogoanime" - ? `https://cors.moopa.my.id/?url=${encodeURIComponent( + ? `https://cors.moopa.workers.dev/?url=${encodeURIComponent( items.url )}${referer ? `&referer=${encodeURIComponent(referer)}` : ""}` : `${proxy}${items.url}`, }; - // url: `https://m3u8proxy.moopa.workers.dev/?url=${encodeURIComponent(items.url)}${ - // referer ? `&referer=${encodeURIComponent(referer)}` : "" - // }`, }); const defSource = source?.find((i) => i?.default === true); @@ -109,19 +104,12 @@ export default function VideoPlayer({ }); const defSub = data?.subtitles.find((i) => i.lang === "English"); - // const thumb = data?.subtitles.find((i) => i.lang === "Thumbnails"); - // setThumbnails(thumb?.url); setDefSub(defSub?.url); - // console.log(subtitle); setSubtitle(subtitle); } - // const defUrl = `https://cors.moopa.my.id/?url=${encodeURIComponent( - // sumber.url - // )}${referer ? `&referer=${encodeURIComponent(referer)}` : ""}`; - setSource(source); } catch (error) { console.error(error); @@ -213,6 +201,8 @@ export default function VideoPlayer({ } }); + let marked = 0; + art.on("video:timeupdate", () => { if (!session) return; const mediaSession = navigator.mediaSession; @@ -228,9 +218,11 @@ export default function VideoPlayer({ if (percentage >= 0.9) { // use >= instead of > - markProgress(aniId, progress, stats); - art.off("video:timeupdate"); - console.log("Video progress marked"); + if (marked < 1) { + marked = 1; + markProgress(aniId, progress, stats); + // console.log("Video progress marked"); + } } }); @@ -243,9 +235,9 @@ export default function VideoPlayer({ }); if ( - op && - currentTime >= op.interval.startTime && - currentTime <= op.interval.endTime + skip?.op && + currentTime >= skip.op.interval.startTime && + currentTime <= skip.op.interval.endTime ) { // Add the layer if it's not already added if (!art.controls["op"]) { @@ -260,14 +252,14 @@ export default function VideoPlayer({ position: "top", html: '<button class="skip-button">Skip Opening</button>', click: function (...args) { - art.seek = op.interval.endTime; + art.seek = skip.op.interval.endTime; }, }); } } else if ( - ed && - currentTime >= ed.interval.startTime && - currentTime <= ed.interval.endTime + skip?.ed && + currentTime >= skip.ed.interval.startTime && + currentTime <= skip.ed.interval.endTime ) { // Add the layer if it's not already added if (!art.controls["ed"]) { @@ -282,7 +274,7 @@ export default function VideoPlayer({ position: "top", html: '<button class="skip-button">Skip Ending</button>', click: function (...args) { - art.seek = ed.interval.endTime; + art.seek = skip.ed.interval.endTime; }, }); } @@ -296,13 +288,6 @@ export default function VideoPlayer({ } } }); - - // art.on("destroy", async () => { - // art.storage.set(id, { - // time: art.currentTime, - // duration: art.duration, - // }); - // }); }} /> )} |