From 1eb531338f5ae3696fa9d68a4171a73f0107c2f8 Mon Sep 17 00:00:00 2001 From: Factiven Date: Fri, 4 Aug 2023 14:49:35 +0700 Subject: Update v3.8.5 - Merged Beta to Main (#32) * 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 --- components/anime/changeView.js | 107 ++++++++++ components/anime/episode.js | 281 +++++++++++++++++++++++++++ components/anime/infoDetails.js | 203 +++++++++++++++++++ components/anime/mobile/topSection.js | 81 ++++++++ components/anime/viewMode/listMode.js | 39 ++++ components/anime/viewMode/thumbnailDetail.js | 76 ++++++++ components/anime/viewMode/thumbnailOnly.js | 59 ++++++ components/anime/watch/primary/details.js | 177 +++++++++++++++++ components/anime/watch/primarySide.js | 213 ++++++++++++++++++++ components/anime/watch/secondarySide.js | 129 ++++++++++++ 10 files changed, 1365 insertions(+) create mode 100644 components/anime/changeView.js create mode 100644 components/anime/episode.js create mode 100644 components/anime/infoDetails.js create mode 100644 components/anime/mobile/topSection.js create mode 100644 components/anime/viewMode/listMode.js create mode 100644 components/anime/viewMode/thumbnailDetail.js create mode 100644 components/anime/viewMode/thumbnailOnly.js create mode 100644 components/anime/watch/primary/details.js create mode 100644 components/anime/watch/primarySide.js create mode 100644 components/anime/watch/secondarySide.js (limited to 'components/anime') 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 ( +
+
0 + ? episode?.some((item) => item?.title === null) + ? "pointer-events-none" + : "cursor-pointer" + : "pointer-events-none" + } + onClick={() => { + setView(1); + localStorage.setItem("view", 1); + }} + > + + 0 + ? episode?.some((item) => item?.title === null) + ? "fill-[#1c1c22]" + : view === 1 + ? "fill-action" + : "fill-[#3A3A44]" + : "fill-[#1c1c22]" + }`} + rx="3" + > + +
+
0 + ? episode?.some((item) => item?.title === null) + ? "pointer-events-none" + : "cursor-pointer" + : "pointer-events-none" + } + onClick={() => { + setView(2); + localStorage.setItem("view", 2); + }} + > + 0 + ? episode?.some((item) => item?.title === null) + ? "fill-[#1c1c22]" + : view === 2 + ? "fill-action" + : "fill-[#3A3A44]" + : "fill-[#1c1c22]" + }`} + viewBox="0 0 33 20" + > + + + +
+
0 ? `cursor-pointer` : "pointer-events-none" + } + onClick={() => { + setView(3); + localStorage.setItem("view", 3); + }} + > + 0 + ? view === 3 + ? "fill-action" + : "fill-[#3A3A44]" + : "fill-[#1c1c22]" + }`} + viewBox="0 0 33 20" + > + + + + +
+
+ ); +} 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 ( + <> +
+
+
+
+ {info && ( +

+ Episodes +

+ )} + {info?.nextAiringEpisode && ( +
+
+

Next :

+
+ {convertSecondsToTime( + info.nextAiringEpisode.timeUntilAiring + )} +
+
+
+ +
+
+ )} +
+ +
+
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"} + + Switch to {isDub ? "Sub" : "Dub"} + +
+
setVisible(!visible)} + > + + + +
+
+
+
+ {providers && ( +
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"} + + Switch to {isDub ? "Sub" : "Dub"} + +
+ )} + {providers && providers.length > 0 && ( + <> +
+
+ + {/* + Select Providers + */} + +
+ + {totalPages > 1 && ( +
+ + +
+ )} +
+ + )} + + +
+
+ + {/* Episodes */} + {!loading ? ( +
+ {Array.isArray(providers) ? ( + providers.length > 0 ? ( + currentEpisodes.map((episode, index) => { + return ( + + {view === 1 && ( + + )} + {view === 2 && ( + + )} + {view === 3 && ( + + )} + + ); + }) + ) : ( +
+

+ Oops!

It looks like this anime is not available. +

+
+ ) + ) : ( +

{providers.message}

+ )} +
+ ) : ( +
+
+
+
+
+
+
+
+ )} +
+ + ); +} 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 ( + <> +
+
+ {info ? ( + <> +
+ poster anime + + + ) : ( + + )} +
+ +
+
+

+ {info ? ( + info?.title?.romaji || info?.title?.english + ) : ( + + )} +

+ {info ? ( +
+ {info?.episodes && ( +
+ {info?.episodes} Episodes +
+ )} + {info?.startDate?.year && ( +
+ {info?.startDate?.year} +
+ )} + {info?.averageScore && ( +
+ {info?.averageScore}% +
+ )} + {info?.type && ( +
+ {info?.type} +
+ )} + {info?.status && ( +
+ {info?.status} +
+ )} +
+ Sub | EN +
+
+ ) : ( + + )} +
+ {info ? ( +

+ ) : ( + + )} +

+
+ +
+
+ {info?.relations?.edges?.length > 0 && ( +
+ Relations +
+ )} + {info?.relations?.edges?.length > 3 && ( +
setShowAll(!showAll)} + > + {showAll ? "show less" : "show more"} +
+ )} +
+
+ {info?.relations?.edges ? ( + info?.relations?.edges + .slice(0, showAll ? info?.relations?.edges.length : 3) + .map((r, index) => { + const rel = r.node; + return ( + +
+
+ {rel.id} +
+
+
+ {r.relationType} +
+
+ {rel.title.userPreferred || rel.title.romaji} +
+
{rel.type}
+
+
+ + ); + }) + ) : ( + <> + {[1, 2, 3].map((item) => ( +
+ +
+ ))} +
+ +
+ + )} +
+
+ + ); +} 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 ( +
+
+

+ {info?.title?.romaji || info?.title?.english} +

+

+

+ {info?.genres + ?.slice(0, info?.genres?.length > 3 ? info?.genres?.length : 3) + .map((item, index) => ( + + {item} + + ))} +
+ {info && ( +
+
+ +
+ +
+
+
+ )} +
+
+
+ {info && info.status !== "NOT_YET_RELEASED" ? ( + <> +
+ +

{info?.type}

+
+
+ +

{info?.averageScore}%

+
+
+ + {info?.episodes ? ( +

{info?.episodes} Episodes

+ ) : ( +

TBA

+ )} +
+ + ) : ( +
{info && "Not Yet Released"}
+ )} +
+
+
+ ); +} 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 ( +
+ +

Episode {episode.number}

+ {episode.title && ( +

+ "{episode.title}" +

+ )} + + {index !== episode?.length - 1 && } +
+ ); +} 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 ( + +
+
+ Anime Cover + + + Episode {epi?.number} + +
+ + + +
+
+
+ +
+

+ {epi?.title} +

+ {epi?.description && ( +

+ {epi?.description} +

+ )} +
+ + ); +} 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 ( + + + Episode {episode?.number} + + +
+ epi image + + ); +} 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 ( +
+
+
+ {info ? ( + Anime Cover + ) : ( + + )} +
+
+
+

+ Studios +

+
+ {info ? info.studios.edges[0].node.name : } +
+
+
+ { + session ? handlePlan() : handleOpen(); + }} + className={`w-8 h-8 hover:fill-white text-white hover:cursor-pointer ${ + onList ? "fill-white" : "" + }`} + > + + +
+
+
+
+

+ Status +

+
{info ? info.status : }
+
+
+

+ Titles +

+
+ {info ? ( + <> +
{info.title?.romaji || ""}
+
+ {info.title?.english || ""} +
+
{info.title?.native || ""}
+ + ) : ( + + )} +
+
+
+
+
+ {info && + info.genres?.map((item, index) => ( +
+ {item} +
+ ))} +
+
+ {info && ( +

+ )} +

+ {/* {
} */} + {!showComments && ( +
+ +
+ )} + {showComments && ( +
+ {info && url && ( +
+ +
+ )} +
+ )} +
+ ); +} 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 ( + <> + handleClose()}> + {!session && ( +
+

+ Edit your list +

+ +
+ )} +
+
+
+ {!loading ? ( + episodeData && ( + + ) + ) : ( +
+ )} +
+
+ {info && episodeList ? ( +
+
+

+ + {navigation?.playing?.title || info.title?.romaji} + +

+

+ Episode {epiNumber} +

+
+
+
+ + +
+ +
+
+ ) : ( +
+
+
+ +
+
+

+ +

+
+ )} +
+
+
+ + ); +} 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 ( +
+

Up Next

+
+ {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 ( + +
+
+ Anime Cover + + + Episode {item.number} + + {item.id == watchId && ( +
+ + + +
+ )} +
+
+
+

+ {item.title} +

+

+ {item?.description} +

+
+ + ); + }) + ) : ( + episode.map((item) => { + return ( + + Episode {item.number} + + ); + }) + ) + ) : ( + <> + {[1].map((item) => ( + + ))} + + )} +
+
+ ); +} -- cgit v1.2.3