aboutsummaryrefslogtreecommitdiff
path: root/components
diff options
context:
space:
mode:
authorFactiven <[email protected]>2023-09-25 00:44:40 +0700
committerGitHub <[email protected]>2023-09-25 00:44:40 +0700
commit1a85c2571690ba592ac5183d5eadaf9846fe532b (patch)
tree3f3552c00cd49c0eeab5275275cf5cf5666e5027 /components
parentDelete .github/workflows/deploy.yml (diff)
downloadmoopa-1a85c2571690ba592ac5183d5eadaf9846fe532b.tar.xz
moopa-1a85c2571690ba592ac5183d5eadaf9846fe532b.zip
Update v4.1.0 (#79)v4.1.0
* Update v4.1.0 * Update pages/_app.js
Diffstat (limited to 'components')
-rw-r--r--components/anime/charactersCard.js160
-rw-r--r--components/anime/episode.js114
-rw-r--r--components/anime/infoDetails.js204
-rw-r--r--components/anime/mobile/topSection.js179
-rw-r--r--components/anime/viewMode/thumbnailDetail.js10
-rw-r--r--components/anime/viewMode/thumbnailOnly.js4
-rw-r--r--components/anime/viewSelector.js (renamed from components/anime/changeView.js)6
-rw-r--r--components/anime/watch/primarySide.js276
-rw-r--r--components/home/content.js19
-rw-r--r--components/home/staticNav.js168
-rw-r--r--components/id/player/Artplayer.js59
-rw-r--r--components/id/player/VideoPlayerId.js181
-rw-r--r--components/layout.js63
-rw-r--r--components/manga/info/topSection.js1
-rw-r--r--components/modal.js2
-rw-r--r--components/navbar.js128
-rw-r--r--components/shared/MobileNav.js8
-rw-r--r--components/shared/NavBar.js265
-rw-r--r--components/shared/bugReport.js200
-rw-r--r--components/shared/footer.js (renamed from components/footer.js)12
-rw-r--r--components/videoPlayer.js412
-rw-r--r--components/watch/player/artplayer.js325
-rw-r--r--components/watch/player/component/controls/quality.js15
-rw-r--r--components/watch/player/component/controls/subtitle.js3
-rw-r--r--components/watch/player/component/overlay.js57
-rw-r--r--components/watch/player/playerComponent.js478
-rw-r--r--components/watch/player/utils/getZoroSource.js0
-rw-r--r--components/watch/primary/details.js (renamed from components/anime/watch/primary/details.js)28
-rw-r--r--components/watch/secondary/episodeLists.js (renamed from components/anime/watch/secondarySide.js)27
29 files changed, 1591 insertions, 1813 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/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/home/content.js b/components/home/content.js
index c869f6b..9dd4408 100644
--- a/components/home/content.js
+++ b/components/home/content.js
@@ -305,6 +305,7 @@ export default function Content({
anime.image ||
anime.coverImage?.extraLarge ||
anime.coverImage?.large ||
+ anime?.coverImage ||
"https://cdn.discordapp.com/attachments/986579286397964290/1058415946945003611/gray_pfp.png"
}
alt={
@@ -336,7 +337,7 @@ export default function Content({
<p className="absolute z-40 text-center w-[86px] lg:w-[110px] top-1 -right-2 lg:top-[5.5px] lg:-right-2 font-karla text-sm lg:text-base">
Episode{" "}
<span className="text-white">
- {anime?.episodeNumber}
+ {anime?.currentEpisode || anime?.episodeNumber}
</span>
</p>
</Fragment>
@@ -377,16 +378,6 @@ export default function Content({
className="flex flex-col gap-2 shrink-0 cursor-pointer relative group/item"
>
<div className="absolute flex flex-col gap-1 z-40 top-1 right-1 transition-all duration-200 ease-out opacity-0 group-hover/item:opacity-100 scale-90 group-hover/item:scale-100 group-hover/item:visible invisible ">
- {/* <button
- type="button"
- className="flex flex-col items-center group/delete relative"
- onClick={() => removeItem(i.watchId)}
- >
- <XMarkIcon className="w-6 h-6 shrink-0 bg-primary p-1 rounded-full hover:text-action scale-100 hover:scale-105 transition-all duration-200 ease-out" />
- <span className="absolute font-karla bg-secondary shadow-black shadow-2xl py-1 px-2 whitespace-nowrap text-white text-sm rounded-md right-7 -bottom-[2px] z-40 duration-300 transition-all ease-out group-hover/delete:visible group-hover/delete:scale-100 group-hover/delete:translate-x-0 group-hover/delete:opacity-100 opacity-0 translate-x-10 scale-50 invisible">
- Remove from history
- </span>
- </button> */}
<HistoryOptions
remove={removeItem}
watchId={i.watchId}
@@ -443,10 +434,10 @@ export default function Content({
{i?.image && (
<Image
src={i?.image}
- width="0"
- height="0"
+ width={320}
+ height={180}
alt="Episode Thumbnail"
- className="w-fit group-hover:scale-[1.02] duration-300 ease-out z-10"
+ className="w-full object-cover group-hover:scale-[1.02] duration-300 ease-out z-10"
/>
)}
</Link>
diff --git a/components/home/staticNav.js b/components/home/staticNav.js
deleted file mode 100644
index 3f43461..0000000
--- a/components/home/staticNav.js
+++ /dev/null
@@ -1,168 +0,0 @@
-import { signIn, signOut, useSession } from "next-auth/react";
-import { getCurrentSeason } from "../../utils/getTimes";
-import Link from "next/link";
-// import { } from "@heroicons/react/24/solid";
-import { useSearch } from "../../lib/hooks/isOpenState";
-import Image from "next/image";
-import { UserIcon } from "@heroicons/react/20/solid";
-import { useRouter } from "next/router";
-
-export default function Navigasi() {
- const { data: sessions, status } = useSession();
- const year = new Date().getFullYear();
- const season = getCurrentSeason();
-
- const router = useRouter();
-
- const { setIsOpen } = useSearch();
-
- return (
- <>
- {/* NAVBAR PC */}
- <div className="flex items-center justify-center w-full">
- <div className="flex w-full items-center justify-between px-4 lg:w-[90%] lg:pt-7">
- <div className="flex items-center lg:gap-16">
- <Link
- href="/en/"
- className=" font-outfit lg:text-[40px] text-[30px] font-bold text-[#FF7F57]"
- >
- moopa
- </Link>
- <ul className="hidden items-center gap-10 pt-2 font-outfit text-[14px] lg:flex">
- <li>
- <Link
- href={`/en/search/anime?season=${season}&year=${year}`}
- className="hover:text-action/80 transition-all duration-150 ease-linear"
- >
- This Season
- </Link>
- </li>
- <li>
- <Link
- href="/en/search/manga"
- className="hover:text-action/80 transition-all duration-150 ease-linear"
- >
- Manga
- </Link>
- </li>
- <li>
- <Link
- href="/en/search/anime"
- className="hover:text-action/80 transition-all duration-150 ease-linear"
- >
- Anime
- </Link>
- </li>
- <li>
- <Link
- href="/en/schedule"
- className="hover:text-action/80 transition-all duration-150 ease-linear"
- >
- Schedule
- </Link>
- </li>
-
- {status === "loading" ? (
- <li>Loading...</li>
- ) : (
- <>
- {!sessions && (
- <li>
- <button
- onClick={() => signIn("AniListProvider")}
- className="hover:text-action/80 transition-all duration-150 ease-linear"
- // className="px-2 py-1 ring-1 ring-action font-bold font-karla rounded-md"
- >
- Sign In
- </button>
- </li>
- )}
- {sessions && (
- <li className="text-center">
- <Link
- href={`/en/profile/${sessions?.user.name}`}
- className="hover:text-action/80 transition-all duration-150 ease-linear"
- >
- My List
- </Link>
- </li>
- )}
- </>
- )}
- </ul>
- </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"}
- > */}
- {sessions ? (
- <div className="w-8 h-8 relative flex flex-col items-center group">
- <button
- type="button"
- onClick={() =>
- router.push(`/en/profile/${sessions?.user.name}`)
- }
- className="rounded-full bg-white/30 overflow-hidden"
- >
- <Image
- src={sessions?.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/${sessions?.user.name}`}
- className="hover:text-action"
- >
- Profile
- </Link>
- <div
- onClick={() => signOut("AniListProvider")}
- className="hover:text-action cursor-pointer"
- >
- 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 text-white/50" />
- </button>
- )}
- {/* </div> */}
- </div>
- </div>
- </div>
- </>
- );
-}
diff --git a/components/id/player/Artplayer.js b/components/id/player/Artplayer.js
deleted file mode 100644
index e209433..0000000
--- a/components/id/player/Artplayer.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import { useEffect, useRef } from "react";
-import Artplayer from "artplayer";
-
-export default function Player({ option, res, getInstance, ...rest }) {
- const artRef = useRef();
-
- useEffect(() => {
- const art = new Artplayer({
- ...option,
- container: artRef.current,
- fullscreen: true,
- hotkey: true,
- lock: true,
- setting: true,
- playbackRate: true,
- autoOrientation: true,
- pip: true,
- theme: "#f97316",
- controls: [
- {
- name: "fast-rewind",
- position: "right",
- html: '<svg class="hi-solid hi-rewind inline-block w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M8.445 14.832A1 1 0 0010 14v-2.798l5.445 3.63A1 1 0 0017 14V6a1 1 0 00-1.555-.832L10 8.798V6a1 1 0 00-1.555-.832l-6 4a1 1 0 000 1.664l6 4z"/></svg>',
- tooltip: "Backward 5s",
- click: function () {
- art.backward = 5;
- },
- },
- {
- name: "fast-forward",
- position: "right",
- html: '<svg class="hi-solid hi-fast-forward inline-block w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M4.555 5.168A1 1 0 003 6v8a1 1 0 001.555.832L10 11.202V14a1 1 0 001.555.832l6-4a1 1 0 000-1.664l-6-4A1 1 0 0010 6v2.798l-5.445-3.63z"/></svg>',
- tooltip: "Forward 5s",
- click: function () {
- art.forward = 5;
- },
- },
- ],
- });
-
- art.events.proxy(document, "keydown", (event) => {
- if (event.key === "f" || event.key === "F") {
- art.fullscreen = !art.fullscreen;
- }
- });
-
- if (getInstance && typeof getInstance === "function") {
- getInstance(art);
- }
-
- return () => {
- if (art && art.destroy) {
- art.destroy(false);
- }
- };
- }, []);
-
- return <div ref={artRef} {...rest}></div>;
-}
diff --git a/components/id/player/VideoPlayerId.js b/components/id/player/VideoPlayerId.js
deleted file mode 100644
index 1168313..0000000
--- a/components/id/player/VideoPlayerId.js
+++ /dev/null
@@ -1,181 +0,0 @@
-import Player from "./Artplayer";
-import { useEffect, useState } from "react";
-import { useAniList } from "../../../lib/anilist/useAnilist";
-
-export default function VideoPlayerId({
- data,
- id,
- progress,
- session,
- aniId,
- stats,
- op,
- ed,
- title,
- poster,
-}) {
- const [url, setUrl] = useState("");
- const [source, setSource] = useState([]);
- const { markProgress } = useAniList(session);
-
- const [resolution, setResolution] = useState("auto");
-
- useEffect(() => {
- const resol = localStorage.getItem("quality");
- if (resol) {
- setResolution(resol);
- }
-
- async function compiler() {
- try {
- const source = data.map((i) => {
- return {
- url: `${i.episode}`,
- html: `${i.size}p`,
- };
- });
-
- const defSource = source.find(
- (i) =>
- i?.html === "1080p" ||
- i?.html === "720p" ||
- i?.html === "480p" ||
- i?.html === "360p"
- );
-
- if (defSource) {
- setUrl(defSource.url);
- }
-
- setSource(source);
- } catch (error) {
- console.error(error);
- }
- }
- compiler();
- }, [data, resolution]);
-
- return (
- <>
- {url && (
- <Player
- key={`${url}`}
- option={{
- url: `${url}`,
- quality: source,
- title: `${title}`,
- autoplay: true,
- screenshot: true,
- poster: poster ? poster : "",
- }}
- res={resolution}
- quality={source}
- style={{
- width: "100%",
- height: "100%",
- margin: "0 auto 0",
- }}
- getInstance={(art) => {
- art.on("ready", () => {
- const seek = art.storage.get(id);
- const seekTime = seek?.time || 0;
- const duration = art.duration;
- const percentage = seekTime / duration;
-
- if (percentage >= 0.9) {
- art.currentTime = 0;
- console.log("Video started from the beginning");
- } else {
- art.currentTime = seekTime;
- }
- });
-
- art.on("video:timeupdate", () => {
- if (!session) return;
- const mediaSession = navigator.mediaSession;
- const currentTime = art.currentTime;
- const duration = art.duration;
- const percentage = currentTime / duration;
-
- mediaSession.setPositionState({
- duration: art.duration,
- playbackRate: art.playbackRate,
- position: art.currentTime,
- });
-
- if (percentage >= 0.9) {
- // use >= instead of >
- markProgress(aniId, progress, stats);
- art.off("video:timeupdate");
- console.log("Video progress marked");
- }
- });
-
- art.on("video:timeupdate", () => {
- var currentTime = art.currentTime;
- // console.log(art.currentTime);
- art.storage.set(id, {
- time: art.currentTime,
- duration: art.duration,
- });
-
- if (
- op &&
- currentTime >= op.interval.startTime &&
- currentTime <= op.interval.endTime
- ) {
- // Add the layer if it's not already added
- if (!art.controls["op"]) {
- // Remove the other control if it's already added
- if (art.controls["ed"]) {
- art.controls.remove("ed");
- }
-
- // Add the control
- art.controls.add({
- name: "op",
- position: "top",
- html: '<button class="skip-button">Skip Opening</button>',
- click: function (...args) {
- art.seek = op.interval.endTime;
- },
- });
- }
- } else if (
- ed &&
- currentTime >= ed.interval.startTime &&
- currentTime <= ed.interval.endTime
- ) {
- // Add the layer if it's not already added
- if (!art.controls["ed"]) {
- // Remove the other control if it's already added
- if (art.controls["op"]) {
- art.controls.remove("op");
- }
-
- // Add the control
- art.controls.add({
- name: "ed",
- position: "top",
- html: '<button class="skip-button">Skip Ending</button>',
- click: function (...args) {
- art.seek = ed.interval.endTime;
- },
- });
- }
- } else {
- // Remove the controls if they're added
- if (art.controls["op"]) {
- art.controls.remove("op");
- }
- if (art.controls["ed"]) {
- art.controls.remove("ed");
- }
- }
- });
- }}
- />
- )}
- </>
- );
-}
diff --git a/components/layout.js b/components/layout.js
deleted file mode 100644
index 49850c9..0000000
--- a/components/layout.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import Navbar from "./navbar";
-import Footer from "./footer";
-import { useEffect, useState } from "react";
-
-function Layout(props) {
- const [isAtTop, setIsAtTop] = useState(true);
- const [isScrollingDown, setIsScrollingDown] = useState(false);
-
- useEffect(() => {
- const handleScroll = () => {
- const scrollY = window.scrollY;
-
- if (scrollY <= 200) {
- setIsAtTop(true);
- setIsScrollingDown(false);
- } else if (scrollY > lastScrollY) {
- setIsAtTop(false);
- setIsScrollingDown(true);
- } else {
- setIsAtTop(false);
- setIsScrollingDown(false);
- }
-
- lastScrollY = scrollY;
- };
-
- let lastScrollY = window.scrollY;
-
- window.addEventListener("scroll", handleScroll);
-
- return () => {
- window.removeEventListener("scroll", handleScroll);
- };
- }, []);
-
- return (
- <>
- <main
- className={`flex h-auto bg-[#121212] text-white flex-col ${props.className}`}
- >
- {/* PC/Tablet */}
- <Navbar
- className={`absolute z-50 hidden w-full duration-500 lg:fixed lg:top-0 lg:block lg:transition-all ${
- isAtTop
- ? `px-2 pt-2 transition-all duration-1000 ${props.navTop}`
- : isScrollingDown
- ? "lg:h-16 lg:translate-y-[-100%] lg:shadow-sm lg:bg-[#0c0d10] "
- : "lg:h-16 lg:translate-y-0 lg:shadow-sm lg:bg-[#0c0d10]"
- }`}
- />
-
- {/* Mobile */}
- <Navbar
- className={`absolute z-50 w-full duration-300 lg:fixed lg:top-0 lg:hidden lg:transition-all`}
- />
- <div className="grid items-center justify-center">{props.children}</div>
- <Footer />
- </main>
- </>
- );
-}
-
-export default Layout;
diff --git a/components/manga/info/topSection.js b/components/manga/info/topSection.js
index 40b5a37..45d5f11 100644
--- a/components/manga/info/topSection.js
+++ b/components/manga/info/topSection.js
@@ -28,6 +28,7 @@ export default function TopSection({ info, firstEp, setCookie }) {
src={info.coverImage}
width={500}
height={500}
+ priority
alt="cover image"
className="hidden md:block object-cover h-[10rem] xs:h-[14rem] lg:h-[22rem] rounded-sm shadow-lg shadow-[#1b1b1f] bg-[#34343b]/20"
/>
diff --git a/components/modal.js b/components/modal.js
index 78b76d7..5d6d0cc 100644
--- a/components/modal.js
+++ b/components/modal.js
@@ -2,7 +2,7 @@ export default function Modal({ open, onClose, children }) {
return (
<div
onClick={onClose}
- className={`fixed z-50 inset-0 flex justify-center items-center transition-colors ${
+ className={`fixed z-[999] inset-0 flex justify-center items-center transition-colors ${
open ? "visible bg-black bg-opacity-50 backdrop-blur-sm" : "invisible"
}`}
>
diff --git a/components/navbar.js b/components/navbar.js
deleted file mode 100644
index 0bb254f..0000000
--- a/components/navbar.js
+++ /dev/null
@@ -1,128 +0,0 @@
-import React, { useState, useEffect } from "react";
-import Link from "next/link";
-import { useSession, signIn, signOut } from "next-auth/react";
-import Image from "next/image";
-import { parseCookies } from "nookies";
-import MobileNav from "./shared/MobileNav";
-
-function Navbar(props) {
- const { data: session, status } = useSession();
- const [isVisible, setIsVisible] = useState(false);
- const [fade, setFade] = useState(false);
-
- const [lang, setLang] = useState("en");
- const [cookie, setCookies] = useState(null);
-
- const handleShowClick = () => {
- setIsVisible(true);
- setFade(true);
- };
-
- const handleHideClick = () => {
- setIsVisible(false);
- setFade(false);
- };
-
- useEffect(() => {
- let lang = null;
- if (!cookie) {
- const cookie = parseCookies();
- lang = cookie.lang || null;
- setCookies(cookie);
- }
- if (lang === "en" || lang === null) {
- setLang("en");
- } else if (lang === "id") {
- setLang("id");
- }
- }, []);
-
- // console.log(session.user?.image);
-
- return (
- <header className={`${props.className}`}>
- <div className="flex h-16 w-auto items-center justify-between px-5 lg:mx-auto lg:w-[80%] lg:px-0 text-[#dbdcdd]">
- <div className="pb-2 font-outfit text-4xl font-semibold lg:block text-white">
- <Link href={`/${lang}/`}>moopa</Link>
- </div>
-
- <MobileNav sessions={session} />
-
- <nav className="left-0 top-[-100%] hidden w-auto items-center gap-10 px-5 lg:flex">
- <ul className="hidden gap-10 font-roboto text-md lg:flex items-center relative">
- <li>
- <Link
- href={`/${lang}/`}
- className="p-2 transition-all duration-100 hover:text-orange-600"
- >
- home
- </Link>
- </li>
- <li>
- <Link
- href={`/${lang}/about`}
- className="p-2 transition-all duration-100 hover:text-orange-600"
- >
- about
- </Link>
- </li>
- <li>
- <Link
- href={`/${lang}/search/anime`}
- className="p-2 transition-all duration-100 hover:text-orange-600"
- >
- search
- </Link>
- </li>
- {status === "loading" ? (
- <li>Loading...</li>
- ) : (
- <>
- {!session && (
- <li>
- <button
- onClick={() => signIn("AniListProvider")}
- className="ring-1 ring-action font-karla font-bold px-2 py-1 rounded-md"
- >
- Sign in
- </button>
- </li>
- )}
- {session && (
- <li className="flex items-center justify-center group ">
- <button>
- <Image
- src={session?.user.image.large}
- alt="imagine"
- width={500}
- height={500}
- className="object-cover h-10 w-10 rounded-full"
- />
- </button>
- <div className="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 grid place-items-center gap-1">
- <Link
- href={`/${lang}/profile/${session?.user.name}`}
- className="hover:text-action"
- >
- Profile
- </Link>
- <button
- onClick={() => signOut("AniListProvider")}
- className="hover:text-action"
- >
- Log out
- </button>
- </div>
- {/* My List */}
- </li>
- )}
- </>
- )}
- </ul>
- </nav>
- </div>
- </header>
- );
-}
-
-export default Navbar;
diff --git a/components/shared/MobileNav.js b/components/shared/MobileNav.js
index 6dd1e64..d0f29c2 100644
--- a/components/shared/MobileNav.js
+++ b/components/shared/MobileNav.js
@@ -1,12 +1,12 @@
import { MagnifyingGlassIcon } from "@heroicons/react/20/solid";
-import { CalendarIcon, ClockIcon, HomeIcon } from "@heroicons/react/24/outline";
-import { signIn, signOut } from "next-auth/react";
+import { CalendarIcon, HomeIcon } from "@heroicons/react/24/outline";
+import { signIn, signOut, useSession } from "next-auth/react";
import Image from "next/image";
import Link from "next/link";
-import { useRouter } from "next/router";
import { useState } from "react";
-export default function MobileNav({ sessions, hideProfile = false }) {
+export default function MobileNav({ hideProfile = false }) {
+ const { data: sessions } = useSession();
const [isVisible, setIsVisible] = useState(false);
const handleShowClick = () => {
diff --git a/components/shared/NavBar.js b/components/shared/NavBar.js
new file mode 100644
index 0000000..42fcff0
--- /dev/null
+++ b/components/shared/NavBar.js
@@ -0,0 +1,265 @@
+import { useSearch } from "@/lib/hooks/isOpenState";
+import { getCurrentSeason } from "@/utils/getTimes";
+import { ArrowLeftIcon, ArrowUpCircleIcon } from "@heroicons/react/20/solid";
+import { UserIcon } from "@heroicons/react/24/solid";
+import { signIn, signOut, useSession } from "next-auth/react";
+import Image from "next/image";
+import Link from "next/link";
+import { useRouter } from "next/router";
+import { useEffect, useState } from "react";
+
+const getScrollPosition = (el = window) => ({
+ x: el.pageXOffset !== undefined ? el.pageXOffset : el.scrollLeft,
+ y: el.pageYOffset !== undefined ? el.pageYOffset : el.scrollTop,
+});
+
+export function NewNavbar({
+ info,
+ scrollP = 200,
+ toTop = false,
+ withNav = false,
+ paddingY = "py-3",
+ home = false,
+ back = false,
+ manga = false,
+ shrink = false,
+}) {
+ const { data: session } = useSession();
+ const router = useRouter();
+ const [scrollPosition, setScrollPosition] = useState();
+ const { setIsOpen } = useSearch();
+
+ const year = new Date().getFullYear();
+ const season = getCurrentSeason();
+
+ 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={`${home ? "" : "fixed"} z-[200] top-0 px-5 w-full ${
+ scrollPosition?.y >= scrollP
+ ? home
+ ? ""
+ : `bg-tersier shadow-tersier shadow-sm ${
+ shrink ? "py-1" : `${paddingY}`
+ }`
+ : `${paddingY}`
+ } transition-all duration-200 ease-linear`}
+ >
+ <div
+ className={`flex items-center justify-between mx-auto ${
+ home ? "lg:max-w-[90%] gap-10" : "max-w-screen-2xl"
+ }`}
+ >
+ <div
+ className={`flex items-center ${
+ withNav ? `${home ? "" : "w-[20%]"} gap-8` : " w-full gap-4"
+ }`}
+ >
+ {info ? (
+ <>
+ <button
+ type="button"
+ className="flex-center w-7 h-7 text-white"
+ onClick={() => {
+ back
+ ? router.back()
+ : manga
+ ? router.push("/en/search/manga")
+ : 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 font-outfit font-semibold pb-2 ${
+ home ? "text-4xl text-action" : "text-white text-3xl"
+ }`}
+ >
+ moopa
+ </Link>
+ )}
+ </div>
+
+ {withNav && (
+ <ul
+ className={`hidden w-full items-center gap-10 pt-2 font-outfit text-[14px] lg:pt-0 lg:flex ${
+ home ? "justify-start" : "justify-center"
+ }`}
+ >
+ <li>
+ <Link
+ href={`/en/search/anime?season=${season}&year=${year}`}
+ className="hover:text-action/80 transition-all duration-150 ease-linear"
+ >
+ This Season
+ </Link>
+ </li>
+ <li>
+ <Link
+ href="/en/search/manga"
+ className="hover:text-action/80 transition-all duration-150 ease-linear"
+ >
+ Manga
+ </Link>
+ </li>
+ <li>
+ <Link
+ href="/en/search/anime"
+ className="hover:text-action/80 transition-all duration-150 ease-linear"
+ >
+ Anime
+ </Link>
+ </li>
+ <li>
+ <Link
+ href="/en/schedule"
+ className="hover:text-action/80 transition-all duration-150 ease-linear"
+ >
+ Schedule
+ </Link>
+ </li>
+
+ {!session && (
+ <li>
+ <button
+ onClick={() => signIn("AniListProvider")}
+ className="hover:text-action/80 transition-all duration-150 ease-linear"
+ // className="px-2 py-1 ring-1 ring-action font-bold font-karla rounded-md"
+ >
+ Sign In
+ </button>
+ </li>
+ )}
+ {session && (
+ <li className="text-center">
+ <Link
+ href={`/en/profile/${session?.user.name}`}
+ className="hover:text-action/80 transition-all duration-150 ease-linear"
+ >
+ My List
+ </Link>
+ </li>
+ )}
+ </ul>
+ )}
+
+ <div className="flex w-[20%] justify-end 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>
+ <button
+ type="button"
+ onClick={() => signOut("AniListProvider")}
+ className="hover:text-action"
+ >
+ Log out
+ </button>
+ </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-1" />
+ </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>
+ )}
+ </>
+ );
+}
diff --git a/components/shared/bugReport.js b/components/shared/bugReport.js
new file mode 100644
index 0000000..9b99016
--- /dev/null
+++ b/components/shared/bugReport.js
@@ -0,0 +1,200 @@
+import { Fragment, useState } from "react";
+import { Dialog, Listbox, Transition } from "@headlessui/react";
+import { CheckIcon, ChevronDownIcon } from "@heroicons/react/20/solid";
+import { toast } from "react-toastify";
+
+const severityOptions = [
+ { id: 1, name: "Low" },
+ { id: 2, name: "Medium" },
+ { id: 3, name: "High" },
+ { id: 4, name: "Critical" },
+];
+
+const BugReportForm = ({ isOpen, setIsOpen }) => {
+ const [bugDescription, setBugDescription] = useState("");
+ const [severity, setSeverity] = useState(severityOptions[0]);
+
+ function closeModal() {
+ setIsOpen(false);
+ setBugDescription("");
+ setSeverity(severityOptions[0]);
+ }
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+
+ const bugReport = {
+ desc: bugDescription,
+ severity: severity.name,
+ url: window.location.href,
+ createdAt: new Date().toISOString(),
+ };
+
+ try {
+ const res = await fetch("/api/v2/admin/bug-report", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ data: bugReport,
+ }),
+ });
+
+ const json = await res.json();
+ toast.success(json.message, {
+ hideProgressBar: true,
+ theme: "colored",
+ });
+ closeModal();
+ } catch (err) {
+ console.log(err);
+ toast.error("Something went wrong: " + err.message, {
+ hideProgressBar: true,
+ theme: "colored",
+ });
+ }
+ };
+
+ return (
+ <>
+ <Transition appear show={isOpen} as={Fragment}>
+ <Dialog as="div" className="relative z-[200]" onClose={closeModal}>
+ <Transition.Child
+ as={Fragment}
+ enter="ease-out duration-300"
+ enterFrom="opacity-0"
+ enterTo="opacity-100"
+ leave="ease-in duration-200"
+ leaveFrom="opacity-100"
+ leaveTo="opacity-0"
+ >
+ <div className="fixed inset-0 bg-black bg-opacity-90" />
+ </Transition.Child>
+
+ <div className="fixed inset-0 overflow-y-auto">
+ <div className="flex min-h-full items-center justify-center p-4 ">
+ <Transition.Child
+ as={Fragment}
+ enter="ease-out duration-300"
+ enterFrom="opacity-0 scale-95"
+ enterTo="opacity-100 scale-100"
+ leave="ease-in duration-200"
+ leaveFrom="opacity-100 scale-100"
+ leaveTo="opacity-0 scale-95"
+ >
+ <Dialog.Panel className="w-full max-w-md transition-all">
+ <div className="bg-secondary p-6 rounded-lg shadow-xl">
+ <h2 className={`text-action text-2xl font-semibold mb-4`}>
+ Report a Bug
+ </h2>
+ <form onSubmit={handleSubmit}>
+ <div className="space-y-4">
+ <div>
+ <label
+ htmlFor="bugDescription"
+ className={`block text-txt text-sm font-medium mb-2`}
+ >
+ Bug Description
+ </label>
+ <textarea
+ id="bugDescription"
+ name="bugDescription"
+ rows="4"
+ className={`w-full bg-image text-txt rounded-md border border-txt focus:ring-action focus:border-action transition duration-300 focus:outline-none py-2 px-3`}
+ placeholder="Describe the bug you encountered..."
+ value={bugDescription}
+ onChange={(e) => setBugDescription(e.target.value)}
+ required
+ ></textarea>
+ </div>
+ <Listbox value={severity} onChange={setSeverity}>
+ <div className="relative mt-1">
+ <label
+ htmlFor="severity"
+ className={`block text-txt text-sm font-medium mb-2`}
+ >
+ Severity
+ </label>
+ <Listbox.Button
+ type="button"
+ className="relative w-full cursor-pointer hover:shadow-xl hover:scale-[1.01] transition-all rounded-lg bg-image py-2 pl-3 pr-10 text-left shadow-md sm:text-base duration-300"
+ >
+ <span className="block truncate text-white font-semibold">
+ {severity.name}
+ </span>
+ <span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
+ <ChevronDownIcon
+ className="h-5 w-5 text-gray-400"
+ aria-hidden="true"
+ />
+ </span>
+ </Listbox.Button>
+ <Transition
+ as={Fragment}
+ leave="transition ease-in duration-100"
+ leaveFrom="opacity-100"
+ leaveTo="opacity-0"
+ >
+ <Listbox.Options className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-image py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
+ {severityOptions.map((person, personIdx) => (
+ <Listbox.Option
+ key={personIdx}
+ className={({ active }) =>
+ `relative cursor-default select-none py-2 pl-10 pr-4 ${
+ active
+ ? "bg-secondary/50 text-white"
+ : "text-gray-400"
+ }`
+ }
+ value={person}
+ >
+ {({ selected }) => (
+ <>
+ <span
+ className={`block truncate ${
+ selected
+ ? "font-medium text-white"
+ : "font-normal"
+ }`}
+ >
+ {person.name}
+ </span>
+ {selected ? (
+ <span className="absolute inset-y-0 left-0 flex items-center pl-3 text-action">
+ <CheckIcon
+ className="h-5 w-5"
+ aria-hidden="true"
+ />
+ </span>
+ ) : null}
+ </>
+ )}
+ </Listbox.Option>
+ ))}
+ </Listbox.Options>
+ </Transition>
+ </div>
+ </Listbox>
+ </div>
+ <div className="mt-4">
+ <button
+ type="submit"
+ className={`w-full bg-action text-white py-2 px-4 rounded-md font-semibold hover:bg-action/80 focus:ring focus:ring-action focus:outline-none transition duration-300`}
+ >
+ Submit Bug Report
+ </button>
+ </div>
+ </form>
+ </div>
+ </Dialog.Panel>
+ </Transition.Child>
+ </div>
+ </div>
+ </Dialog>
+ </Transition>
+ </>
+ );
+};
+
+export default BugReportForm;
diff --git a/components/footer.js b/components/shared/footer.js
index ca5a21f..91af5a8 100644
--- a/components/footer.js
+++ b/components/shared/footer.js
@@ -2,6 +2,7 @@ import Link from "next/link";
import { useEffect, useState } from "react";
import { useRouter } from "next/router";
import { parseCookies, setCookie } from "nookies";
+import Image from "next/image";
function Footer() {
const [year] = useState(new Date().getFullYear());
@@ -46,7 +47,7 @@ function Footer() {
return (
<footer className="flex-col w-full">
<div className="text-[#dbdcdd] z-40 bg-[#0c0d10] lg:flex lg:h-[12rem] w-full lg:items-center lg:justify-between">
- <div className="mx-auto flex w-[85%] lg:w-[95%] xl:w-[80%] flex-col space-y-10 py-6 lg:flex-row lg:items-center lg:justify-between lg:space-y-0 lg:py-0">
+ <div className="mx-auto flex w-[90%] lg:w-[95%] xl:w-[80%] flex-col space-y-10 py-6 lg:flex-row lg:items-center lg:justify-between lg:space-y-0 lg:py-0">
<div className="flex flex-col gap-2">
{/* <div className="flex items-center gap-2"> */}
{/* <Image
@@ -56,7 +57,7 @@ function Footer() {
height={100}
className="w-10 h-10"
/> */}
- <p className="font-outfit text-4xl">moopa</p>
+ <div className="flex gap-2 font-outfit text-4xl">moopa</div>
<p className="font-karla lg:text-[0.8rem] text-[0.65rem] text-[#9c9c9c] lg:w-[520px] italic">
This site does not store any files on our server, we only linked
to the media which is hosted on 3rd party services.
@@ -80,7 +81,7 @@ function Footer() {
<Link href={`/${lang}/search/manga`}>Popular Manga</Link>
</li>
<li className="cursor-pointer hover:text-action">
- <Link href={`https://ko-fi.com/factiven`}>Donate</Link>
+ <Link href={`/donate`}>Donate</Link>
</li>
</ul>
<ul className="flex flex-col gap-y-[0.7rem]">
@@ -156,10 +157,7 @@ function Footer() {
</Link>
{/* Kofi */}
- <Link
- href="https://ko-fi.com/factiven"
- className="w-6 h-6 hover:opacity-75"
- >
+ <Link href="/donate" className="w-6 h-6 hover:opacity-75">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="#fff"
diff --git a/components/videoPlayer.js b/components/videoPlayer.js
deleted file mode 100644
index f35f4f0..0000000
--- a/components/videoPlayer.js
+++ /dev/null
@@ -1,412 +0,0 @@
-import Player from "../lib/Artplayer";
-import { useEffect, useState } from "react";
-import { useAniList } from "../lib/anilist/useAnilist";
-import artplayerPluginHlsQuality from "artplayer-plugin-hls-quality";
-import { useRouter } from "next/router";
-
-const fontSize = [
- {
- html: "Small",
- size: "16px",
- },
- {
- html: "Medium",
- size: "36px",
- },
- {
- html: "Large",
- size: "56px",
- },
-];
-
-export default function VideoPlayer({
- info,
- data,
- id,
- progress,
- session,
- aniId,
- skip,
- title,
- poster,
- proxy,
- provider,
- track,
- aniTitle,
- timeWatched,
- dub,
-}) {
- const [url, setUrl] = useState("");
- const [source, setSource] = useState([]);
- const { markProgress } = useAniList(session);
-
- const router = useRouter();
-
- const [resolution, setResolution] = useState("auto");
- const [subSize, setSubSize] = useState({ size: "16px", html: "Small" });
- const [defSize, setDefSize] = useState();
- const [subtitle, setSubtitle] = useState();
- const [defSub, setDefSub] = useState();
-
- const [autoPlay, setAutoPlay] = useState(false);
-
- useEffect(() => {
- const resol = localStorage.getItem("quality");
- const sub = JSON.parse(localStorage.getItem("subSize"));
- if (resol) {
- setResolution(resol);
- }
-
- if (provider === "zoro") {
- const size = fontSize.map((i) => {
- const isDefault = !sub ? i.html === "Small" : i.html === sub?.html;
- return {
- ...(isDefault && { default: true }),
- html: i.html,
- size: i.size,
- };
- });
-
- const defSize = size?.find((i) => i?.default === true);
- setDefSize(defSize);
- setSubSize(size);
- }
-
- async function compiler() {
- try {
- const referer = JSON.stringify(data?.headers);
- const source = data.sources.map((items) => {
- const isDefault =
- provider !== "gogoanime"
- ? items.quality === "default" || items.quality === "auto"
- : resolution === "auto"
- ? items.quality === "default" || items.quality === "auto"
- : items.quality === resolution;
- return {
- ...(isDefault && { default: true }),
- html: items.quality === "default" ? "adaptive" : items.quality,
- url: `${proxy}/proxy/m3u8/${encodeURIComponent(
- String(items.url)
- )}/${encodeURIComponent(String(referer))}`,
- };
- });
-
- const defSource = source?.find((i) => i?.default === true);
-
- if (defSource) {
- setUrl(defSource.url);
- }
-
- if (provider === "zoro") {
- const subtitle = data?.subtitles
- .filter((subtitle) => subtitle.lang !== "Thumbnails")
- .map((subtitle) => {
- const isEnglish = subtitle.lang === "English";
- return {
- ...(isEnglish && { default: true }),
- url: subtitle.url,
- html: `${subtitle.lang}`,
- };
- });
-
- const defSub = data?.subtitles.find((i) => i.lang === "English");
-
- setDefSub(defSub?.url);
-
- setSubtitle(subtitle);
- }
-
- setSource(source);
- } catch (error) {
- console.error(error);
- }
- }
- compiler();
- }, [data, resolution]);
-
- return (
- <>
- {url && (
- <Player
- key={url}
- option={{
- url: `${url}`,
- title: `${title}`,
- autoplay: true,
- screenshot: true,
- moreVideoAttr: {
- crossOrigin: "anonymous",
- },
- poster: poster ? poster : "",
- ...(provider !== "gogoanime" && {
- plugins: [
- artplayerPluginHlsQuality({
- // Show quality in setting
- setting: true,
-
- // Get the resolution text from level
- getResolution: (level) => level.height + "P",
-
- // I18n
- title: "Quality",
- auto: "Auto",
- }),
- ],
- }),
- ...(provider === "zoro" && {
- subtitle: {
- url: `${defSub}`,
- // type: "vtt",
- encoding: "utf-8",
- default: true,
- name: "English",
- escape: false,
- style: {
- color: "#FFFF",
- fontSize: `${defSize?.size}`,
- fontFamily: localStorage.getItem("font")
- ? localStorage.getItem("font")
- : "Arial",
- textShadow: localStorage.getItem("subShadow")
- ? JSON.parse(localStorage.getItem("subShadow")).value
- : "0px 0px 10px #000000",
- },
- },
- }),
- }}
- id={aniId}
- res={resolution}
- quality={source}
- subSize={subSize}
- subtitles={subtitle}
- provider={provider}
- track={track}
- autoplay={autoPlay}
- setautoplay={setAutoPlay}
- style={{
- width: "100%",
- height: "100%",
- margin: "0 auto 0",
- }}
- getInstance={(art) => {
- art.on("ready", () => {
- const seek = art.storage.get(id);
- const seekTime = seek?.timeWatched || 0;
- const duration = art.duration;
- const percentage = seekTime / duration;
- const percentagedb = timeWatched / duration;
-
- if (subSize) {
- art.subtitle.style.fontSize = subSize?.size;
- }
-
- if (percentage >= 0.9 || percentagedb >= 0.9) {
- art.currentTime = 0;
- console.log("Video started from the beginning");
- } else if (timeWatched) {
- art.currentTime = timeWatched;
- } else {
- art.currentTime = seekTime;
- }
- });
-
- let marked = 0;
-
- art.on("video:playing", () => {
- if (!session) return;
- const intervalId = setInterval(async () => {
- const resp = await fetch("/api/user/update/episode", {
- method: "PUT",
- body: JSON.stringify({
- name: session?.user?.name,
- id: String(aniId),
- watchId: id,
- title: track.playing?.title || aniTitle,
- aniTitle: aniTitle,
- image: track.playing?.image || info?.coverImage?.extraLarge,
- number: Number(progress),
- duration: art.duration,
- timeWatched: art.currentTime,
- provider: provider,
- nextId: track.next?.id,
- nextNumber: Number(track.next?.number),
- dub: dub ? true : false,
- }),
- });
- // console.log("updating db", { track });
- }, 5000);
-
- art.on("video:pause", () => {
- clearInterval(intervalId);
- });
-
- art.on("video:ended", () => {
- clearInterval(intervalId);
- });
-
- art.on("destroy", () => {
- clearInterval(intervalId);
- // console.log("clearing interval");
- });
- });
-
- art.on("video:playing", () => {
- const interval = setInterval(async () => {
- art.storage.set(id, {
- aniId: String(aniId),
- watchId: id,
- title: track?.playing?.title || aniTitle,
- aniTitle: aniTitle,
- image: track?.playing?.image || info?.coverImage?.extraLarge,
- episode: Number(progress),
- duration: art.duration,
- timeWatched: art.currentTime,
- provider: provider,
- nextId: track?.next?.id,
- nextNumber: track?.next?.number,
- dub: dub ? true : false,
- createdAt: new Date().toISOString(),
- });
- }, 5000);
-
- art.on("video:pause", () => {
- clearInterval(interval);
- });
-
- art.on("video:ended", () => {
- clearInterval(interval);
- });
-
- art.on("destroy", () => {
- clearInterval(interval);
- });
- });
-
- art.on("resize", () => {
- art.subtitle.style({
- fontSize: art.height * 0.05 + "px",
- });
- });
-
- art.on("video:timeupdate", async () => {
- if (!session) return;
-
- var currentTime = art.currentTime;
- const duration = art.duration;
- const percentage = currentTime / duration;
-
- if (percentage >= 0.9) {
- // use >= instead of >
- if (marked < 1) {
- marked = 1;
- markProgress(aniId, progress);
- }
- }
- });
-
- art.on("video:ended", () => {
- if (!track?.next) return;
- if (localStorage.getItem("autoplay") === "true") {
- art.controls.add({
- name: "next-button",
- position: "top",
- html: '<div class="vid-con"><button class="next-button progress">Play Next</button></div>',
- click: function (...args) {
- if (track?.next) {
- router.push(
- `/en/anime/watch/${aniId}/${provider}?id=${encodeURIComponent(
- track?.next?.id
- )}&num=${track?.next?.number}${
- dub ? `&dub=${dub}` : ""
- }`
- );
- }
- },
- });
-
- const button = document.querySelector(".next-button");
-
- function stopTimeout() {
- clearTimeout(timeoutId);
- button.classList.remove("progress");
- }
-
- let timeoutId = setTimeout(() => {
- art.controls.remove("next-button");
- if (track?.next) {
- router.push(
- `/en/anime/watch/${aniId}/${provider}?id=${encodeURIComponent(
- track?.next?.id
- )}&num=${track?.next?.number}${dub ? `&dub=${dub}` : ""}`
- );
- }
- }, 7000);
-
- button.addEventListener("mouseover", stopTimeout);
- }
- });
-
- art.on("video:timeupdate", () => {
- var currentTime = art.currentTime;
- // console.log(art.currentTime);
-
- if (
- 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"]) {
- // Remove the other control if it's already added
- if (art.controls["ed"]) {
- art.controls.remove("ed");
- }
-
- // Add the control
- art.controls.add({
- name: "op",
- position: "top",
- html: '<button class="skip-button">Skip Opening</button>',
- click: function (...args) {
- art.seek = skip.op.interval.endTime;
- },
- });
- }
- } else if (
- 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"]) {
- // Remove the other control if it's already added
- if (art.controls["op"]) {
- art.controls.remove("op");
- }
-
- // Add the control
- art.controls.add({
- name: "ed",
- position: "top",
- html: '<button class="skip-button">Skip Ending</button>',
- click: function (...args) {
- art.seek = skip.ed.interval.endTime;
- },
- });
- }
- } else {
- // Remove the controls if they're added
- if (art.controls["op"]) {
- art.controls.remove("op");
- }
- if (art.controls["ed"]) {
- art.controls.remove("ed");
- }
- }
- });
- }}
- />
- )}
- </>
- );
-}
diff --git a/components/watch/player/artplayer.js b/components/watch/player/artplayer.js
new file mode 100644
index 0000000..4eb766d
--- /dev/null
+++ b/components/watch/player/artplayer.js
@@ -0,0 +1,325 @@
+import { useEffect, useRef } from "react";
+import Artplayer from "artplayer";
+import Hls from "hls.js";
+import { useWatchProvider } from "../../../lib/hooks/watchPageProvider";
+import { seekBackward, seekForward } from "./component/overlay";
+import artplayerPluginHlsQuality from "artplayer-plugin-hls-quality";
+
+export default function NewPlayer({
+ playerRef,
+ option,
+ getInstance,
+ provider,
+ defSub,
+ defSize,
+ subtitles,
+ subSize,
+ res,
+ quality,
+ ...rest
+}) {
+ const artRef = useRef(null);
+ const { setTheaterMode, setPlayerState, setAutoPlay } = useWatchProvider();
+
+ function playM3u8(video, url, art) {
+ if (Hls.isSupported()) {
+ if (art.hls) art.hls.destroy();
+ const hls = new Hls();
+ hls.loadSource(url);
+ hls.attachMedia(video);
+ art.hls = hls;
+ art.on("destroy", () => hls.destroy());
+ } else if (video.canPlayType("application/vnd.apple.mpegurl")) {
+ video.src = url;
+ } else {
+ art.notice.show = "Unsupported playback format: m3u8";
+ }
+ }
+
+ useEffect(() => {
+ const art = new Artplayer({
+ ...option,
+ container: artRef.current,
+ type: "m3u8",
+ customType: {
+ m3u8: playM3u8,
+ },
+ ...(provider === "zoro" && {
+ subtitle: {
+ url: `${defSub}`,
+ // type: "vtt",
+ encoding: "utf-8",
+ default: true,
+ name: "English",
+ escape: false,
+ style: {
+ color: "#FFFF",
+ fontSize: `${defSize?.size}`,
+ fontFamily: localStorage.getItem("font")
+ ? localStorage.getItem("font")
+ : "Arial",
+ textShadow: localStorage.getItem("subShadow")
+ ? JSON.parse(localStorage.getItem("subShadow")).value
+ : "0px 0px 10px #000000",
+ },
+ },
+ }),
+
+ plugins: [
+ artplayerPluginHlsQuality({
+ // Show quality in setting
+ setting: true,
+
+ // Get the resolution text from level
+ getResolution: (level) => level.height + "P",
+
+ // I18n
+ title: "Quality",
+ auto: "Auto",
+ }),
+ ],
+
+ settings: [
+ // provider === "gogoanime" &&
+ {
+ html: "Autoplay Next",
+ icon: '<svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em" viewBox="0 0 24 24"><path fill="currentColor" d="M4.05 16.975q-.5.35-1.025.05t-.525-.9v-8.25q0-.6.525-.888t1.025.038l6.2 4.15q.45.3.45.825t-.45.825l-6.2 4.15Zm10 0q-.5.35-1.025.05t-.525-.9v-8.25q0-.6.525-.888t1.025.038l6.2 4.15q.45.3.45.825t-.45.825l-6.2 4.15Z"></path></svg>',
+ tooltip: "ON/OFF",
+ switch: localStorage.getItem("autoplay") === "true" ? true : false,
+ onSwitch: function (item) {
+ // setPlayNext(!item.switch);
+ localStorage.setItem("autoplay", !item.switch);
+ return !item.switch;
+ },
+ },
+ {
+ html: "Autoplay Video",
+ icon: '<svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em" viewBox="0 0 24 24"><path fill="currentColor" d="M4.05 16.975q-.5.35-1.025.05t-.525-.9v-8.25q0-.6.525-.888t1.025.038l6.2 4.15q.45.3.45.825t-.45.825l-6.2 4.15Zm10 0q-.5.35-1.025.05t-.525-.9v-8.25q0-.6.525-.888t1.025.038l6.2 4.15q.45.3.45.825t-.45.825l-6.2 4.15Z"></path></svg>',
+ // icon: '<svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em" viewBox="0 0 24 24"><path fill="currentColor" d="M5.59 7.41L7 6l6 6l-6 6l-1.41-1.41L10.17 12L5.59 7.41m6 0L13 6l6 6l-6 6l-1.41-1.41L16.17 12l-4.58-4.59Z"></path></svg>',
+ tooltip: "ON/OFF",
+ switch:
+ localStorage.getItem("autoplay_video") === "true" ? true : false,
+ onSwitch: function (item) {
+ setAutoPlay(!item.switch);
+ localStorage.setItem("autoplay_video", !item.switch);
+ return !item.switch;
+ },
+ },
+ {
+ html: "Alternative Quality",
+ width: 250,
+ tooltip: `${res}`,
+ selector: quality?.alt,
+ icon: '<svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em" viewBox="0 0 512 512"><path fill="currentColor" d="M381.25 112a48 48 0 0 0-90.5 0H48v32h242.75a48 48 0 0 0 90.5 0H464v-32ZM176 208a48.09 48.09 0 0 0-45.25 32H48v32h82.75a48 48 0 0 0 90.5 0H464v-32H221.25A48.09 48.09 0 0 0 176 208Zm160 128a48.09 48.09 0 0 0-45.25 32H48v32h242.75a48 48 0 0 0 90.5 0H464v-32h-82.75A48.09 48.09 0 0 0 336 336Z"></path></svg>',
+ onSelect: function (item) {
+ art.switchQuality(item.url, item.html);
+ localStorage.setItem("quality", item.html);
+ return item.html;
+ },
+ },
+ {
+ html: "Server",
+ width: 250,
+ tooltip: `${quality?.server[0].html}`,
+ icon: '<svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em" viewBox="0 0 32 32"><path fill="currentColor" d="m24.6 24.4l2.6 2.6l-2.6 2.6L26 31l4-4l-4-4zm-2.2 0L19.8 27l2.6 2.6L21 31l-4-4l4-4z"></path><circle cx="11" cy="8" r="1" fill="currentColor"></circle><circle cx="11" cy="16" r="1" fill="currentColor"></circle><circle cx="11" cy="24" r="1" fill="currentColor"></circle><path fill="currentColor" d="M24 3H8c-1.1 0-2 .9-2 2v22c0 1.1.9 2 2 2h7v-2H8v-6h18V5c0-1.1-.9-2-2-2zm0 16H8v-6h16v6zm0-8H8V5h16v6z"></path></svg>',
+ selector: quality?.server,
+ onSelect: function (item) {
+ art.switchQuality(item.url, item.html);
+ localStorage.setItem("quality", item.html);
+ return item.html;
+ },
+ },
+ provider === "zoro" && {
+ html: "Subtitles",
+ icon: '<svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em" viewBox="0 0 24 24"><path fill="currentColor" d="M4 20q-.825 0-1.413-.588T2 18V6q0-.825.588-1.413T4 4h16q.825 0 1.413.588T22 6v12q0 .825-.588 1.413T20 20H4Zm2-4h8v-2H6v2Zm10 0h2v-2h-2v2ZM6 12h2v-2H6v2Zm4 0h8v-2h-8v2Z"></path></svg>',
+ width: 300,
+ tooltip: "Settings",
+ selector: [
+ {
+ html: "Display",
+ icon: '<svg xmlns="http://www.w3.org/2000/svg" width="35" height="26" viewBox="0 -960 960 960"><path d="M480.169-341.796q65.754 0 111.894-46.31 46.141-46.309 46.141-112.063t-46.31-111.894q-46.309-46.141-112.063-46.141t-111.894 46.31q-46.141 46.309-46.141 112.063t46.31 111.894q46.309 46.141 112.063 46.141zm-.371-48.307q-45.875 0-77.785-32.112-31.91-32.112-31.91-77.987 0-45.875 32.112-77.785 32.112-31.91 77.987-31.91 45.875 0 77.785 32.112 31.91 32.112 31.91 77.987 0 45.875-32.112 77.785-32.112 31.91-77.987 31.91zm.226 170.102q-130.921 0-239.6-69.821-108.679-69.82-167.556-186.476-2.687-4.574-3.892-10.811Q67.77-493.347 67.77-500t1.205-12.891q1.205-6.237 3.892-10.811Q131.745-640.358 240.4-710.178q108.655-69.821 239.576-69.821t239.6 69.821q108.679 69.82 167.556 186.476 2.687 4.574 3.892 10.811 1.205 6.238 1.205 12.891t-1.205 12.891q-1.205 6.237-3.892 10.811Q828.255-359.642 719.6-289.822q-108.655 69.821-239.576 69.821zM480-500zm-.112 229.744q117.163 0 215.048-62.347Q792.821-394.949 844.308-500q-51.487-105.051-149.26-167.397-97.772-62.347-214.936-62.347-117.163 0-215.048 62.347Q167.179-605.051 115.282-500q51.897 105.051 149.67 167.397 97.772 62.347 214.936 62.347z"></path></svg>',
+ tooltip: "Show",
+ switch: true,
+ onSwitch: function (item) {
+ item.tooltip = item.switch ? "Hide" : "Show";
+ art.subtitle.show = !item.switch;
+ return !item.switch;
+ },
+ },
+ {
+ html: "Font Size",
+ icon: '<svg xmlns="http://www.w3.org/2000/svg" width="35" height="26" viewBox="0 -960 960 960"><path d="M619.861-177.694q-15.655 0-26.475-10.918-10.821-10.918-10.821-26.516v-492.309H415.128q-15.598 0-26.516-10.959-10.918-10.959-10.918-26.615 0-15.655 10.918-26.475 10.918-10.82 26.516-10.82h409.744q15.598 0 26.516 10.958 10.918 10.959 10.918 26.615 0 15.656-10.918 26.476-10.918 10.82-26.516 10.82H657.435v492.309q0 15.598-10.959 26.516-10.959 10.918-26.615 10.918zm-360 0q-15.655 0-26.475-10.918-10.821-10.918-10.821-26.516v-292.309h-87.437q-15.598 0-26.516-10.959-10.918-10.959-10.918-26.615 0-15.655 10.918-26.475 10.918-10.82 26.516-10.82h249.744q15.598 0 26.516 10.958 10.918 10.959 10.918 26.615 0 15.656-10.918 26.476-10.918 10.82-26.516 10.82h-87.437v292.309q0 15.598-10.959 26.516-10.959 10.918-26.615 10.918z"></path></svg>',
+ selector: subSize,
+ onSelect: function (item) {
+ if (item.html === "Small") {
+ art.subtitle.style({ fontSize: "16px" });
+ localStorage.setItem(
+ "subSize",
+ JSON.stringify({
+ size: "16px",
+ html: "Small",
+ })
+ );
+ } else if (item.html === "Medium") {
+ art.subtitle.style({ fontSize: "36px" });
+ localStorage.setItem(
+ "subSize",
+ JSON.stringify({
+ size: "36px",
+ html: "Medium",
+ })
+ );
+ } else if (item.html === "Large") {
+ art.subtitle.style({ fontSize: "56px" });
+ localStorage.setItem(
+ "subSize",
+ JSON.stringify({
+ size: "56px",
+ html: "Large",
+ })
+ );
+ }
+ },
+ },
+ {
+ html: "Language",
+ icon: '<svg xmlns="http://www.w3.org/2000/svg" width="35" height="26" viewBox="0 -960 960 960"><path d="M528.282-110.771q-21.744 0-31.308-14.013t-2.205-34.295l135.952-359.307q5.304-14.793 20.292-25.126 14.988-10.334 31.152-10.334 15.398 0 30.85 10.388 15.451 10.387 20.932 25.125l137.128 357.485q8.025 20.949-1.83 35.513-9.855 14.564-33.24 14.564-10.366 0-19.392-6.616-9.025-6.615-12.72-16.242l-30.997-91.808H594.769l-33.381 91.869q-3.645 9.181-13.148 15.989-9.504 6.808-19.958 6.808zm87.871-179.281h131.64l-64.615-180.717h-2.41l-64.615 180.717zM302.104-608.384q14.406 25.624 31.074 48.184 16.669 22.559 37.643 47.021 41.333-44.128 68.628-90.461t46.038-97.897H111.499q-15.674 0-26.278-10.615-10.603-10.616-10.603-26.308t10.615-26.307q10.616-10.616 26.308-10.616h221.537v-36.923q0-15.692 10.615-26.307 10.616-10.616 26.308-10.616t26.307 10.616q10.616 10.615 10.616 26.307v36.923h221.537q15.692 0 26.307 10.616 10.616 10.615 10.616 26.307 0 15.692-10.616 26.308-10.615 10.615-26.307 10.615h-69.088q-19.912 64.153-53.237 125.74-33.325 61.588-82.341 116.412l89.384 90.974-27.692 75.179-115.486-112.922-158.948 158.947q-10.615 10.616-25.667 10.616-15.051 0-25.666-11.026-11.026-10.615-11.026-25.666 0-15.052 11.026-26.077l161.614-161.358q-24.666-28.308-45.551-57.307-20.884-29-37.756-60.103-10.641-19.871-1.346-34.717t33.038-14.846q9.088 0 18.429 5.73 9.34 5.731 13.956 13.577z"></path></svg>',
+ tooltip: "English",
+ selector: [...subtitles],
+ onSelect: function (item) {
+ art.subtitle.switch(item.url, {
+ name: item.html,
+ });
+ return item.html;
+ },
+ },
+ {
+ html: "Font Family",
+ tooltip: localStorage.getItem("font")
+ ? localStorage.getItem("font")
+ : "Arial",
+ selector: [
+ { html: "Arial" },
+ { html: "Comic Sans MS" },
+ { html: "Verdana" },
+ { html: "Tahoma" },
+ { html: "Trebuchet MS" },
+ { html: "Times New Roman" },
+ { html: "Georgia" },
+ { html: "Impact " },
+ { html: "Andalé Mono" },
+ { html: "Palatino" },
+ { html: "Baskerville" },
+ { html: "Garamond" },
+ { html: "Courier New" },
+ { html: "Brush Script MT" },
+ ],
+ onSelect: function (item) {
+ art.subtitle.style({ fontFamily: item.html });
+ localStorage.setItem("font", item.html);
+ return item.html;
+ },
+ },
+ {
+ html: "Font Shadow",
+ tooltip: localStorage.getItem("subShadow")
+ ? JSON.parse(localStorage.getItem("subShadow")).shadow
+ : "Default",
+ selector: [
+ { html: "None", value: "none" },
+ {
+ html: "Uniform",
+ value:
+ "2px 2px 0px #000, -2px -2px 0px #000, 2px -2px 0px #000, -2px 2px 0px #000",
+ },
+ { html: "Raised", value: "-1px 2px 3px rgba(0, 0, 0, 1)" },
+ { html: "Depressed", value: "-2px -3px 3px rgba(0, 0, 0, 1)" },
+ { html: "Glow", value: "0 0 10px rgba(0, 0, 0, 0.8)" },
+ {
+ html: "Block",
+ value:
+ "-3px 3px 4px rgba(0, 0, 0, 1),2px 2px 4px rgba(0, 0, 0, 1),1px -1px 3px rgba(0, 0, 0, 1),-3px -2px 4px rgba(0, 0, 0, 1)",
+ },
+ ],
+ onSelect: function (item) {
+ art.subtitle.style({ textShadow: item.value });
+ localStorage.setItem(
+ "subShadow",
+ JSON.stringify({ shadow: item.html, value: item.value })
+ );
+ return item.html;
+ },
+ },
+ ],
+ },
+ ].filter(Boolean),
+ controls: [
+ {
+ name: "theater-button",
+ index: 11,
+ position: "right",
+ tooltip: "Theater (t)",
+ html: '<p class="theater"><svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewBox="0 0 20 20"><path fill="currentColor" d="M19 3H1a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h18a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1zm-1 12H2V5h16v10z"></path></svg></p>',
+ click: function (...args) {
+ setPlayerState((prev) => ({
+ ...prev,
+ currentTime: art.currentTime,
+ isPlaying: art.playing,
+ }));
+ setTheaterMode((prev) => !prev);
+ },
+ },
+ seekBackward,
+ seekForward,
+ ],
+ });
+
+ playerRef.current = art;
+
+ art.events.proxy(document, "keydown", (event) => {
+ // Check if the focus is on an input field or textarea
+ const isInputFocused =
+ document.activeElement.tagName === "INPUT" ||
+ document.activeElement.tagName === "TEXTAREA";
+
+ if (!isInputFocused) {
+ if (event.key === "f" || event.key === "F") {
+ art.fullscreen = !art.fullscreen;
+ }
+
+ if (event.key === "t" || event.key === "T") {
+ setPlayerState((prev) => ({
+ ...prev,
+ currentTime: art.currentTime,
+ isPlaying: art.playing,
+ }));
+ setTheaterMode((prev) => !prev);
+ }
+ }
+ });
+
+ art.events.proxy(document, "keypress", (event) => {
+ // Check if the focus is on an input field or textarea
+ const isInputFocused =
+ document.activeElement.tagName === "INPUT" ||
+ document.activeElement.tagName === "TEXTAREA";
+
+ if (!isInputFocused && event.code === "Space") {
+ event.preventDefault();
+ art.playing ? art.pause() : art.play();
+ }
+ });
+
+ if (getInstance && typeof getInstance === "function") {
+ getInstance(art);
+ }
+
+ return () => {
+ if (art && art.destroy) {
+ art.destroy(false);
+ }
+ };
+ }, []);
+
+ return <div ref={artRef} {...rest}></div>;
+}
diff --git a/components/watch/player/component/controls/quality.js b/components/watch/player/component/controls/quality.js
new file mode 100644
index 0000000..08dbd0e
--- /dev/null
+++ b/components/watch/player/component/controls/quality.js
@@ -0,0 +1,15 @@
+import artplayerPluginHlsQuality from "artplayer-plugin-hls-quality";
+
+export const QualityPlugins = [
+ artplayerPluginHlsQuality({
+ // Show quality in setting
+ setting: true,
+
+ // Get the resolution text from level
+ getResolution: (level) => level.height + "P",
+
+ // I18n
+ title: "Quality",
+ auto: "Auto",
+ }),
+];
diff --git a/components/watch/player/component/controls/subtitle.js b/components/watch/player/component/controls/subtitle.js
new file mode 100644
index 0000000..02075f7
--- /dev/null
+++ b/components/watch/player/component/controls/subtitle.js
@@ -0,0 +1,3 @@
+import { useState } from "react";
+
+export default function getSubtitles() {}
diff --git a/components/watch/player/component/overlay.js b/components/watch/player/component/overlay.js
new file mode 100644
index 0000000..1d5ac27
--- /dev/null
+++ b/components/watch/player/component/overlay.js
@@ -0,0 +1,57 @@
+/**
+ * @type {import("artplayer/types/icons".Icons)}
+ */
+export const icons = {
+ screenshot:
+ '<svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="0 0 20 20"><path fill="currentColor" d="M10 8a3 3 0 1 0 0 6a3 3 0 0 0 0-6zm8-3h-2.4a.888.888 0 0 1-.789-.57l-.621-1.861A.89.89 0 0 0 13.4 2H6.6c-.33 0-.686.256-.789.568L5.189 4.43A.889.889 0 0 1 4.4 5H2C.9 5 0 5.9 0 7v9c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm-8 11a5 5 0 0 1-5-5a5 5 0 1 1 10 0a5 5 0 0 1-5 5zm7.5-7.8a.7.7 0 1 1 0-1.4a.7.7 0 0 1 0 1.4z"></path></svg>',
+ play: '<svg xmlns="http://www.w3.org/2000/svg" width="25px" height="25px" viewBox="0 0 20 20"><path fill="currentColor" d="M15 10.001c0 .299-.305.514-.305.514l-8.561 5.303C5.51 16.227 5 15.924 5 15.149V4.852c0-.777.51-1.078 1.135-.67l8.561 5.305c-.001 0 .304.215.304.514z"></path></svg>',
+ pause:
+ '<svg xmlns="http://www.w3.org/2000/svg" width="25px" height="25px" viewBox="0 0 20 20"><path fill="currentColor" d="M15 3h-2c-.553 0-1 .048-1 .6v12.8c0 .552.447.6 1 .6h2c.553 0 1-.048 1-.6V3.6c0-.552-.447-.6-1-.6zM7 3H5c-.553 0-1 .048-1 .6v12.8c0 .552.447.6 1 .6h2c.553 0 1-.048 1-.6V3.6c0-.552-.447-.6-1-.6z"></path></svg>',
+ volume:
+ '<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewBox="0 0 20 20"><path fill="currentColor" d="M19 13.805c0 .657-.538 1.195-1.195 1.195H1.533c-.88 0-.982-.371-.229-.822l16.323-9.055C18.382 4.67 19 5.019 19 5.9v7.905z"></path></svg>',
+ fullscreenOff:
+ '<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewBox="0 0 20 20"><path fill="currentColor" d="M3.28 2.22a.75.75 0 0 0-1.06 1.06L5.44 6.5H2.75a.75.75 0 0 0 0 1.5h4.5A.75.75 0 0 0 8 7.25v-4.5a.75.75 0 0 0-1.5 0v2.69L3.28 2.22Zm10.22.53a.75.75 0 0 0-1.5 0v4.5c0 .414.336.75.75.75h4.5a.75.75 0 0 0 0-1.5h-2.69l3.22-3.22a.75.75 0 0 0-1.06-1.06L13.5 5.44V2.75ZM3.28 17.78l3.22-3.22v2.69a.75.75 0 0 0 1.5 0v-4.5a.75.75 0 0 0-.75-.75h-4.5a.75.75 0 0 0 0 1.5h2.69l-3.22 3.22a.75.75 0 1 0 1.06 1.06Zm10.22-3.22l3.22 3.22a.75.75 0 1 0 1.06-1.06l-3.22-3.22h2.69a.75.75 0 0 0 0-1.5h-4.5a.75.75 0 0 0-.75.75v4.5a.75.75 0 0 0 1.5 0v-2.69Z"></path></svg>',
+ fullscreenOn:
+ '<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewBox="0 0 20 20"><path fill="currentColor" d="m13.28 7.78l3.22-3.22v2.69a.75.75 0 0 0 1.5 0v-4.5a.75.75 0 0 0-.75-.75h-4.5a.75.75 0 0 0 0 1.5h2.69l-3.22 3.22a.75.75 0 0 0 1.06 1.06ZM2 17.25v-4.5a.75.75 0 0 1 1.5 0v2.69l3.22-3.22a.75.75 0 0 1 1.06 1.06L4.56 16.5h2.69a.75.75 0 0 1 0 1.5h-4.5a.747.747 0 0 1-.75-.75Zm10.22-3.97l3.22 3.22h-2.69a.75.75 0 0 0 0 1.5h4.5a.747.747 0 0 0 .75-.75v-4.5a.75.75 0 0 0-1.5 0v2.69l-3.22-3.22a.75.75 0 1 0-1.06 1.06ZM3.5 4.56l3.22 3.22a.75.75 0 0 0 1.06-1.06L4.56 3.5h2.69a.75.75 0 0 0 0-1.5h-4.5a.75.75 0 0 0-.75.75v4.5a.75.75 0 0 0 1.5 0V4.56Z"></path></svg>',
+};
+
+export const backButton = {
+ name: "back-button",
+ index: 10,
+ position: "top",
+ html: "<div class='parent-player-title'><div></div><div className='flex gap-2'><p className='pt-1'><ChevronLeftIcon className='w-7 h-7'/></p><div class='flex flex-col text-white'><p className='font-outfit font-bold text-2xl'>Komi-san wa, Komyushou desu.</p><p className=''>Episode 1</p></div></div></div>",
+ // tooltip: "Your Button",
+ click: function (...args) {
+ console.info("click", args);
+ },
+ mounted: function (...args) {
+ console.info("mounted", args);
+ },
+};
+
+export const seekBackward = {
+ index: 10,
+ name: "fast-rewind",
+ position: "left",
+ html: '<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewBox="0 0 20 20"><path fill="currentColor" d="M17.959 4.571L10.756 9.52s-.279.201-.279.481s.279.479.279.479l7.203 4.951c.572.38 1.041.099 1.041-.626V5.196c0-.727-.469-1.008-1.041-.625zm-9.076 0L1.68 9.52s-.279.201-.279.481s.279.479.279.479l7.203 4.951c.572.381 1.041.1 1.041-.625v-9.61c0-.727-.469-1.008-1.041-.625z"></path></svg>',
+ tooltip: "Backward 5s",
+ click: function () {
+ art.backward = 5;
+ },
+};
+
+export const seekForward = {
+ index: 11,
+ name: "fast-forward",
+ position: "left",
+ html: '<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewBox="0 0 20 20"><path fill="currentColor" d="M9.244 9.52L2.041 4.571C1.469 4.188 1 4.469 1 5.196v9.609c0 .725.469 1.006 1.041.625l7.203-4.951s.279-.199.279-.478c0-.28-.279-.481-.279-.481zm9.356.481c0 .279-.279.478-.279.478l-7.203 4.951c-.572.381-1.041.1-1.041-.625V5.196c0-.727.469-1.008 1.041-.625L18.32 9.52s.28.201.28.481z"></path></svg>',
+ tooltip: "Forward 5s",
+ click: function () {
+ art.forward = 5;
+ },
+};
+
+// /**
+// * @type {import("artplayer/types/component").ComponentOption}
+// */
+// export const
diff --git a/components/watch/player/playerComponent.js b/components/watch/player/playerComponent.js
new file mode 100644
index 0000000..d498384
--- /dev/null
+++ b/components/watch/player/playerComponent.js
@@ -0,0 +1,478 @@
+import React, { useEffect, useState } from "react";
+import NewPlayer from "./artplayer";
+import { icons } from "./component/overlay";
+import { useWatchProvider } from "../../../lib/hooks/watchPageProvider";
+import { useRouter } from "next/router";
+import { useAniList } from "../../../lib/anilist/useAnilist";
+
+export function calculateAspectRatio(width, height) {
+ const gcd = (a, b) => (b === 0 ? a : gcd(b, a % b));
+ const divisor = gcd(width, height);
+ const aspectRatio = `${width / divisor}/${height / divisor}`;
+ return aspectRatio;
+}
+
+const fontSize = [
+ {
+ html: "Small",
+ size: "16px",
+ },
+ {
+ html: "Medium",
+ size: "36px",
+ },
+ {
+ html: "Large",
+ size: "56px",
+ },
+];
+
+export default function PlayerComponent({
+ playerRef,
+ session,
+ id,
+ info,
+ watchId,
+ proxy,
+ dub,
+ timeWatched,
+ skip,
+ track,
+ data,
+ provider,
+ className,
+}) {
+ const {
+ aspectRatio,
+ setAspectRatio,
+ playerState,
+ setPlayerState,
+ autoplay,
+ marked,
+ setMarked,
+ } = useWatchProvider();
+
+ const router = useRouter();
+
+ const { markProgress } = useAniList(session);
+
+ const [url, setUrl] = useState("");
+ const [resolution, setResolution] = useState("auto");
+ const [source, setSource] = useState([]);
+ const [subSize, setSubSize] = useState({ size: "16px", html: "Small" });
+ const [defSize, setDefSize] = useState();
+ const [subtitle, setSubtitle] = useState();
+ const [defSub, setDefSub] = useState();
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ setLoading(true);
+ const resol = localStorage.getItem("quality");
+ const sub = JSON.parse(localStorage.getItem("subSize"));
+ if (resol) {
+ setResolution(resol);
+ }
+
+ if (provider === "zoro") {
+ const size = fontSize.map((i) => {
+ const isDefault = !sub ? i.html === "Small" : i.html === sub?.html;
+ return {
+ ...(isDefault && { default: true }),
+ html: i.html,
+ size: i.size,
+ };
+ });
+
+ const defSize = size?.find((i) => i?.default === true);
+ setDefSize(defSize);
+ setSubSize(size);
+ }
+
+ async function compiler() {
+ try {
+ const referer = JSON.stringify(data?.headers);
+ const source = data?.sources?.map((items) => {
+ const isDefault =
+ provider !== "gogoanime"
+ ? items.quality === "default" || items.quality === "auto"
+ : resolution === "auto"
+ ? items.quality === "default" || items.quality === "auto"
+ : items.quality === resolution;
+ return {
+ ...(isDefault && { default: true }),
+ html: items.quality === "default" ? "main" : items.quality,
+ url: `${proxy}/proxy/m3u8/${encodeURIComponent(
+ String(items.url)
+ )}/${encodeURIComponent(String(referer))}`,
+ };
+ });
+
+ const defSource = source?.find((i) => i?.default === true);
+
+ if (defSource) {
+ setUrl(defSource.url);
+ }
+
+ if (provider === "zoro") {
+ const subtitle = data?.subtitles
+ .filter((subtitle) => subtitle.lang !== "Thumbnails")
+ .map((subtitle) => {
+ const isEnglish = subtitle.lang === "English";
+ return {
+ ...(isEnglish && { default: true }),
+ url: subtitle.url,
+ html: `${subtitle.lang}`,
+ };
+ });
+
+ const defSub = data?.subtitles.find((i) => i.lang === "English");
+
+ setDefSub(defSub?.url);
+
+ setSubtitle(subtitle);
+ }
+
+ const alt = source?.filter(
+ (i) =>
+ i?.html !== "main" &&
+ i?.html !== "auto" &&
+ i?.html !== "default" &&
+ i?.html !== "backup"
+ );
+ const server = source?.filter(
+ (i) =>
+ i?.html === "main" ||
+ i?.html === "auto" ||
+ i?.html === "default" ||
+ i?.html === "backup"
+ );
+
+ setSource({ alt, server });
+ setLoading(false);
+ } catch (error) {
+ console.error(error);
+ }
+ }
+ compiler();
+
+ return () => {
+ setUrl("");
+ setSource([]);
+ setSubtitle([]);
+ setLoading(true);
+ };
+ }, [provider, data]);
+
+ /**
+ * @param {import("artplayer")} art
+ */
+ function getInstance(art) {
+ art.on("ready", () => {
+ const autoplay = localStorage.getItem("autoplay_video") || false;
+
+ if (autoplay === "true" || autoplay === true) {
+ if (playerState.currentTime === 0) {
+ art.play();
+ } else {
+ if (playerState.isPlaying) {
+ art.play();
+ } else {
+ art.pause();
+ }
+ }
+ } else {
+ if (playerState.isPlaying) {
+ art.play();
+ } else {
+ art.pause();
+ }
+ }
+ art.seek = playerState.currentTime;
+ });
+
+ art.on("ready", () => {
+ if (playerState.currentTime !== 0) return;
+ const seek = art.storage.get(id);
+ const seekTime = seek?.timeWatched || 0;
+ const duration = art.duration;
+ const percentage = seekTime / duration;
+ const percentagedb = timeWatched / duration;
+
+ if (subSize) {
+ art.subtitle.style.fontSize = subSize?.size;
+ }
+
+ if (percentage >= 0.9 || percentagedb >= 0.9) {
+ art.currentTime = 0;
+ console.log("Video started from the beginning");
+ } else if (timeWatched) {
+ art.currentTime = timeWatched;
+ } else {
+ art.currentTime = seekTime;
+ }
+ });
+
+ art.on("play", () => {
+ art.notice.show = "";
+ setPlayerState({ ...playerState, isPlaying: true });
+ });
+ art.on("pause", () => {
+ art.notice.show = "";
+ setPlayerState({ ...playerState, isPlaying: false });
+ });
+
+ art.on("resize", () => {
+ art.subtitle.style({
+ fontSize: art.height * 0.05 + "px",
+ });
+ });
+
+ let mark = 0;
+
+ art.on("video:timeupdate", async () => {
+ if (!session) return;
+
+ var currentTime = art.currentTime;
+ const duration = art.duration;
+ const percentage = currentTime / duration;
+
+ if (percentage >= 0.9) {
+ // use >= instead of >
+ if (mark < 1 && marked < 1) {
+ mark = 1;
+ setMarked(1);
+ markProgress(info.id, track.playing.number);
+ }
+ }
+ });
+
+ art.on("video:playing", () => {
+ if (!session) return;
+ const intervalId = setInterval(async () => {
+ await fetch("/api/user/update/episode", {
+ method: "PUT",
+ body: JSON.stringify({
+ name: session?.user?.name,
+ id: String(info?.id),
+ watchId: watchId,
+ title:
+ track.playing?.title || info.title?.romaji || info.title?.english,
+ aniTitle: info.title?.romaji || info.title?.english,
+ image: track.playing?.img || info?.coverImage?.extraLarge,
+ number: Number(track.playing?.number),
+ duration: art.duration,
+ timeWatched: art.currentTime,
+ provider: provider,
+ nextId: track.next?.id,
+ nextNumber: Number(track.next?.number),
+ dub: dub ? true : false,
+ }),
+ });
+ // console.log("updating db", { track });
+ }, 5000);
+
+ art.on("video:pause", () => {
+ clearInterval(intervalId);
+ });
+
+ art.on("video:ended", () => {
+ clearInterval(intervalId);
+ });
+
+ art.on("destroy", () => {
+ clearInterval(intervalId);
+ // console.log("clearing interval");
+ });
+ });
+
+ art.on("video:playing", () => {
+ const interval = setInterval(async () => {
+ art.storage.set(watchId, {
+ aniId: String(info.id),
+ watchId: watchId,
+ title:
+ track.playing?.title || info.title?.romaji || info.title?.english,
+ aniTitle: info.title?.romaji || info.title?.english,
+ image: track?.playing?.img || info?.coverImage?.extraLarge,
+ episode: Number(track.playing?.number),
+ duration: art.duration,
+ timeWatched: art.currentTime,
+ provider: provider,
+ nextId: track?.next?.id,
+ nextNumber: track?.next?.number,
+ dub: dub ? true : false,
+ createdAt: new Date().toISOString(),
+ });
+ }, 5000);
+
+ art.on("video:pause", () => {
+ clearInterval(interval);
+ });
+
+ art.on("video:ended", () => {
+ clearInterval(interval);
+ });
+
+ art.on("destroy", () => {
+ clearInterval(interval);
+ });
+ });
+
+ art.on("video:loadedmetadata", () => {
+ // get raw video width and height
+ // console.log(art.video.videoWidth, art.video.videoHeight);
+ const aspect = calculateAspectRatio(
+ art.video.videoWidth,
+ art.video.videoHeight
+ );
+
+ setAspectRatio(aspect);
+ });
+
+ art.on("video:timeupdate", () => {
+ var currentTime = art.currentTime;
+ // console.log(art.currentTime);
+
+ if (
+ 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"]) {
+ // Remove the other control if it's already added
+ if (art.controls["ed"]) {
+ art.controls.remove("ed");
+ }
+
+ // Add the control
+ art.controls.add({
+ name: "op",
+ position: "top",
+ html: '<button class="skip-button">Skip Opening</button>',
+ click: function (...args) {
+ art.seek = skip.op.interval.endTime;
+ },
+ });
+ }
+ } else if (
+ 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"]) {
+ // Remove the other control if it's already added
+ if (art.controls["op"]) {
+ art.controls.remove("op");
+ }
+
+ // Add the control
+ art.controls.add({
+ name: "ed",
+ position: "top",
+ html: '<button class="skip-button">Skip Ending</button>',
+ click: function (...args) {
+ art.seek = skip.ed.interval.endTime;
+ },
+ });
+ }
+ } else {
+ // Remove the controls if they're added
+ if (art.controls["op"]) {
+ art.controls.remove("op");
+ }
+ if (art.controls["ed"]) {
+ art.controls.remove("ed");
+ }
+ }
+ });
+
+ art.on("video:ended", () => {
+ if (!track?.next) return;
+ if (localStorage.getItem("autoplay") === "true") {
+ art.controls.add({
+ name: "next-button",
+ position: "top",
+ html: '<div class="vid-con"><button class="next-button progress">Play Next</button></div>',
+ click: function (...args) {
+ if (track?.next) {
+ router.push(
+ `/en/anime/watch/${
+ info?.id
+ }/${provider}?id=${encodeURIComponent(track?.next?.id)}&num=${
+ track?.next?.number
+ }${dub ? `&dub=${dub}` : ""}`
+ );
+ }
+ },
+ });
+
+ const button = document.querySelector(".next-button");
+
+ function stopTimeout() {
+ clearTimeout(timeoutId);
+ button.classList.remove("progress");
+ }
+
+ let timeoutId = setTimeout(() => {
+ art.controls.remove("next-button");
+ if (track?.next) {
+ router.push(
+ `/en/anime/watch/${info?.id}/${provider}?id=${encodeURIComponent(
+ track?.next?.id
+ )}&num=${track?.next?.number}${dub ? `&dub=${dub}` : ""}`
+ );
+ }
+ }, 7000);
+
+ button.addEventListener("mouseover", stopTimeout);
+ }
+ });
+ }
+
+ /**
+ * @type {import("artplayer/types/option").Option}
+ */
+ const option = {
+ url: url,
+ title: "title",
+ autoplay: autoplay ? true : false,
+ autoSize: false,
+ fullscreen: true,
+ autoOrientation: true,
+ icons: icons,
+ setting: true,
+ screenshot: true,
+ hotkey: true,
+ };
+
+ return (
+ <div
+ id={id}
+ className={`${className} bg-black`}
+ style={{ aspectRatio: aspectRatio }}
+ >
+ <div className="w-full h-full">
+ {!loading && track && url && (
+ <NewPlayer
+ playerRef={playerRef}
+ res={resolution}
+ quality={source}
+ option={option}
+ provider={provider}
+ defSize={defSize}
+ defSub={defSub}
+ subSize={subSize}
+ subtitles={subtitle}
+ getInstance={getInstance}
+ style={{
+ width: "100%",
+ height: "100%",
+ }}
+ />
+ )}
+ </div>
+ </div>
+ );
+}
diff --git a/components/watch/player/utils/getZoroSource.js b/components/watch/player/utils/getZoroSource.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/components/watch/player/utils/getZoroSource.js
diff --git a/components/anime/watch/primary/details.js b/components/watch/primary/details.js
index f092879..32e1391 100644
--- a/components/anime/watch/primary/details.js
+++ b/components/watch/primary/details.js
@@ -1,7 +1,7 @@
import { useEffect, useState } from "react";
-import { useAniList } from "../../../../lib/anilist/useAnilist";
+import { useAniList } from "../../../lib/anilist/useAnilist";
import Skeleton from "react-loading-skeleton";
-import DisqusComments from "../../../disqus";
+import DisqusComments from "../../disqus";
import Image from "next/image";
export default function Details({
@@ -17,7 +17,6 @@ export default function Details({
}) {
const [showComments, setShowComments] = useState(false);
const { markPlanning } = useAniList(session);
- const [url, setUrl] = useState(null);
function handlePlan() {
if (onList === false) {
@@ -27,14 +26,13 @@ export default function Details({
}
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="px-4 pt-7 pb-4 h-full flex"> */}
+ <div className="pb-4 h-full flex">
<div className="aspect-[9/13] h-[240px]">
{info ? (
<Image
@@ -58,7 +56,11 @@ export default function Details({
Studios
</h2>
<div className="row-start-2">
- {info ? info.studios.edges[0].node.name : <Skeleton width={80} />}
+ {info ? (
+ info.studios?.edges[0].node.name
+ ) : (
+ <Skeleton width={80} />
+ )}
</div>
<div className="hidden xxs:grid col-start-2 place-content-end relative">
<div>
@@ -114,7 +116,8 @@ export default function Details({
</div>
</div>
</div>
- <div className="flex flex-wrap gap-3 px-4 pt-3">
+ {/* <div className="flex flex-wrap gap-3 px-4 pt-3"> */}
+ <div className="flex flex-wrap gap-3 pt-3">
{info &&
info.genres?.map((item, index) => (
<div
@@ -125,7 +128,8 @@ export default function Details({
</div>
))}
</div>
- <div className={`bg-secondary rounded-md mt-3 mx-3`}>
+ {/* <div className={`bg-secondary rounded-md mt-3 mx-3`}> */}
+ <div className={`bg-secondary rounded-md mt-3`}>
{info && (
<p
dangerouslySetInnerHTML={{ __html: description }}
@@ -135,7 +139,7 @@ export default function Details({
</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">
+ <div className="w-full flex justify-center py-2 font-karla lg:px-0">
<button
onClick={() => setShowComments(true)}
className={
@@ -164,14 +168,14 @@ export default function Details({
)}
{showComments && (
<div>
- {info && url && (
+ {info && (
<div className="mt-5 px-5">
<DisqusComments
key={id}
post={{
id: id,
title: info.title.romaji,
- url: url,
+ url: window.location.href,
episode: epiNumber,
name: disqus,
}}
diff --git a/components/anime/watch/secondarySide.js b/components/watch/secondary/episodeLists.js
index c9ef684..8a057ce 100644
--- a/components/anime/watch/secondarySide.js
+++ b/components/watch/secondary/episodeLists.js
@@ -2,7 +2,7 @@ import Skeleton from "react-loading-skeleton";
import Image from "next/image";
import Link from "next/link";
-export default function SecondarySide({
+export default function EpisodeLists({
info,
map,
providerId,
@@ -13,11 +13,15 @@ export default function SecondarySide({
}) {
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="w-screen lg:max-w-sm xl:max-w-xl">
+ <h1 className="text-xl font-karla pl-5 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 ? (
+ map?.some(
+ (item) =>
+ (item?.img || item?.image) &&
+ !item?.img?.includes("https://s4.anilist.co/")
+ ) > 0 ? (
episode.map((item) => {
const time = artStorage?.[item.id]?.timeWatched;
const duration = artStorage?.[item.id]?.duration;
@@ -41,9 +45,14 @@ export default function SecondarySide({
>
<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 && ( */}
+ {/* <div className="absolute inset-0 w-full h-full z-40" /> */}
<Image
- src={mapData?.image || info?.coverImage?.extraLarge}
+ src={
+ mapData?.img ||
+ mapData?.image ||
+ info?.coverImage?.extraLarge
+ }
+ draggable={false}
alt="Anime Cover"
width={1000}
height={1000}
@@ -88,10 +97,10 @@ export default function SecondarySide({
}`}
>
<h1 className="font-karla font-bold italic line-clamp-1">
- {mapData?.title}
+ {mapData?.title || info?.title?.romaji}
</h1>
<p className="line-clamp-2 text-xs italic font-outfit font-extralight">
- {mapData?.description}
+ {mapData?.description || `Episode ${item.number}`}
</p>
</div>
</Link>
@@ -107,7 +116,7 @@ export default function SecondarySide({
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 ${
+ className={`bg-secondary flex-center 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"