aboutsummaryrefslogtreecommitdiff
path: root/src/app/anime/components
diff options
context:
space:
mode:
authorreal-zephex <[email protected]>2024-05-11 19:14:29 +0530
committerGitHub <[email protected]>2024-05-11 19:14:29 +0530
commite8875b008268c5873454b120bfb358cb4180093f (patch)
tree2a0f8a15fb42f093e1d35f52eb3f7e14f1b0fbe5 /src/app/anime/components
parentMerge pull request #24 from zephex-alt/master (diff)
parentadded anime history section (diff)
downloaddramalama-e8875b008268c5873454b120bfb358cb4180093f.tar.xz
dramalama-e8875b008268c5873454b120bfb358cb4180093f.zip
Merge pull request #25 from real-zephex/improvement-2
Improvement 2
Diffstat (limited to 'src/app/anime/components')
-rw-r--r--src/app/anime/components/cacher.js29
-rw-r--r--src/app/anime/components/episode_buttons.jsx205
-rw-r--r--src/app/anime/components/popularAnimes.jsx51
-rw-r--r--src/app/anime/components/recentEpisodes.jsx54
-rw-r--r--src/app/anime/components/search.jsx55
-rw-r--r--src/app/anime/components/search_results.jsx42
-rw-r--r--src/app/anime/components/storeHistory.js35
-rw-r--r--src/app/anime/components/topAiring.jsx54
8 files changed, 525 insertions, 0 deletions
diff --git a/src/app/anime/components/cacher.js b/src/app/anime/components/cacher.js
new file mode 100644
index 0000000..d3008fa
--- /dev/null
+++ b/src/app/anime/components/cacher.js
@@ -0,0 +1,29 @@
+"use server";
+
+import { info_url, watch_url } from "../../../../utils/anime_urls";
+
+export async function preFetchAnimeInfo(data) {
+ try {
+ const fetchPromises = data.results.map(async (element) => {
+ await fetch(info_url(element.id), { next: { revalidate: 21600 } });
+ });
+
+ await Promise.all(fetchPromises);
+ console.log("Anime info pre-fetched successfully!");
+ } catch (error) {
+ console.error("Error occurred while pre-fetching anime info: ", error);
+ }
+}
+
+export async function preFetchVideoLinks(data) {
+ try {
+ const fetchPromises = data.map(async (element) => {
+ await fetch(watch_url(element.id), { next: { revalidate: 21600 } });
+ });
+
+ await Promise.all(fetchPromises);
+ console.log("Anime video links info pre-fetched successfully!");
+ } catch (error) {
+ console.error("Error occurred while pre-fetching anime info: ", error);
+ }
+}
diff --git a/src/app/anime/components/episode_buttons.jsx b/src/app/anime/components/episode_buttons.jsx
new file mode 100644
index 0000000..013dee1
--- /dev/null
+++ b/src/app/anime/components/episode_buttons.jsx
@@ -0,0 +1,205 @@
+"use client";
+import { useState, useEffect } from "react";
+import { MediaPlayer, MediaProvider } from "@vidstack/react";
+import "@vidstack/react/player/styles/default/theme.css";
+import "@vidstack/react/player/styles/default/layouts/video.css";
+import {
+ defaultLayoutIcons,
+ DefaultVideoLayout,
+} from "@vidstack/react/player/layouts/default";
+
+import styles from "../styles/buttons.module.css";
+import { video_url } from "../data-fetch/request";
+import { preFetchVideoLinks } from "./cacher";
+import { storeLocal } from "./storeHistory";
+
+const EpisodesButtons = ({ data: data }) => {
+ const [videoLink, setVideoLink] = useState(null);
+ const [buttonGroups, setButtonGroups] = useState(null);
+ const [videoLoading, setVideoLoading] = useState(null);
+
+ useEffect(() => {
+ setButtonGroups(createButtonGroups(0, 50));
+ }, []);
+
+ const groups = createGroups(data.episodes, 50);
+
+ /**
+ * Retrieves the video URL for a given episode ID.
+ * This function handles the initializing, changing URL, and hiding the video player.
+ * @param {string} epID - The episode ID.
+ */
+ async function getVideoURL(epID) {
+ setVideoLoading(true);
+ const data = await video_url(epID);
+ setVideoLink(data.sources[data.sources.length - 2].url);
+ setVideoLoading(false);
+ }
+
+ /**
+ * Creates button groups for a range of episodes.
+ * @param {number} start - The starting index of episodes.
+ * @param {number} end - The ending index of episodes.
+ * @returns {JSX.Element} - Button groups JSX element.
+ */
+ function createButtonGroups(start, end) {
+ try {
+ const buttons = document.getElementsByClassName("episode-button");
+ for (let i = 0; i < buttons.length; i++) {
+ buttons[i].style.backgroundColor = "#1f1f1fd2";
+ }
+ } catch (error) {
+ console.error(
+ "ERROR: This error is expected. This is done in order to reset the background color of the buttons. I can't think of a better way than this....so yeah.",
+ error.message
+ );
+ }
+
+ return (
+ <div className={styles.animeButtonContainer}>
+ {data.episodes &&
+ data.episodes.slice(start, end).map((item, index) => (
+ <button
+ className={styles.dramaButton + " episode-button"}
+ key={index}
+ onClick={(event) => {
+ event.target.style.backgroundColor =
+ "var(--soft-purple)";
+ getVideoURL(item.id);
+ store_to_local(
+ data.title,
+ data.image,
+ item.number,
+ data.id
+ );
+ }}
+ >
+ {item.number}
+ </button>
+ ))}
+ </div>
+ );
+ }
+
+ /**
+ * Handles the change event of the select element.
+ * @param {Event} event - The change event object.
+ */
+ function handleSelectChange(event) {
+ const selectedIndex = event.target.selectedIndex;
+ const selectedGroup = groups[selectedIndex];
+ if (selectedGroup) {
+ setButtonGroups(
+ createButtonGroups(
+ selectedGroup[0].number - 1,
+ selectedGroup[selectedGroup.length - 1].number
+ )
+ );
+ preFetchVideoLinks(
+ data.episodes.slice(
+ selectedGroup[0].number - 1,
+ selectedGroup[selectedGroup.length - 1].number
+ )
+ );
+ }
+ }
+
+ return (
+ <main className={styles.Main}>
+ {data.episodes && (
+ <select
+ onChange={(event) => handleSelectChange(event)}
+ className={styles.SelectClass}
+ >
+ {groups &&
+ groups.map((item, index) => (
+ <option key={index}>
+ {item[0].number} -{" "}
+ {item[item.length - 1].number}
+ </option>
+ ))}
+ </select>
+ )}
+ {buttonGroups}
+ {videoLoading && (
+ <p style={{ margin: "0.5rem 0 0 0" }}>Loading...</p>
+ )}
+ {videoLink && (
+ <div className={styles.videoPopUp} id="popup">
+ <div className={styles.video}>
+ <MediaPlayer
+ title="dramaPlayer"
+ src={videoLink}
+ aspectRatio="16/9"
+ load="eager"
+ className={styles.VideoPlayer}
+ playsInline
+ id="videoPlayer"
+ volume={0.8}
+ >
+ <MediaProvider />
+ <DefaultVideoLayout icons={defaultLayoutIcons} />
+ </MediaPlayer>
+ <button
+ className={styles.closeButton}
+ onClick={() => {
+ setVideoLink("");
+ }}
+ >
+ Close
+ </button>
+ </div>
+ </div>
+ )}
+ </main>
+ );
+};
+
+/**
+ * Divides an array into groups of a specified size.
+ * @param {Array} array - The array to be divided.
+ * @param {number} size - The size of each group.
+ * @returns {Array} - An array containing groups of elements.
+ */
+function createGroups(array, size) {
+ const groups = [];
+ for (let i = 0; i < array.length; i += size) {
+ groups.push(array.slice(i, i + size));
+ }
+ return groups;
+}
+
+/**
+ * Stores watch history to local storage.
+ * @param {string} name - The name of the episode.
+ * @param {string} image - The image URL of the episode.
+ * @param {number} episode - The episode number.
+ * @param {string} id - The ID of the episode.
+ */
+function store_to_local(name, image, episode, id) {
+ const currentDate = new Date();
+
+ try {
+ let newData = {
+ name: name,
+ image: image,
+ episode: episode,
+ id: id,
+ type: "anime",
+ date: `${currentDate.getDate()}-${String(
+ currentDate.getMonth() + 1
+ ).padStart(2, "0")}`,
+ time: `${currentDate.getHours()}:${String(
+ currentDate.getMinutes()
+ ).padStart(2, "0")}`,
+ };
+ storeLocal(newData);
+ } catch (error) {
+ console.error(
+ "Some error occurred during the process of saving your watch history to local storage. Please try again or contact us on GitHub if this issue persists.",
+ error.message
+ );
+ }
+}
+
+export default EpisodesButtons; \ No newline at end of file
diff --git a/src/app/anime/components/popularAnimes.jsx b/src/app/anime/components/popularAnimes.jsx
new file mode 100644
index 0000000..2259a42
--- /dev/null
+++ b/src/app/anime/components/popularAnimes.jsx
@@ -0,0 +1,51 @@
+import Link from "next/link";
+import Image from "next/image";
+import { Atkinson_Hyperlegible } from "next/font/google";
+
+import styles from "../styles/pop_recent_top.module.css";
+import { popular, anime_info } from "../data-fetch/request";
+import { preFetchAnimeInfo, preFetchVideoLinks } from "./cacher";
+
+const atkinson = Atkinson_Hyperlegible({ subsets: ["latin"], weight: "400" });
+
+const PopularAnimes = async () => {
+ const data = await popular();
+
+ preFetchAnimeInfo(data);
+
+ return (
+ <main className={styles.Main}>
+ <section>
+ <h2 className={styles.AnimeHeaderText}>Popular Animes</h2>
+ <div className={styles.AnimeContainer}>
+ {data &&
+ data.results.map((item, index) => (
+ <Link
+ key={index}
+ href={`/anime/${item.id}`}
+ shallow
+ style={{
+ color: "white",
+ textDecoration: "none",
+ }}
+ className={atkinson.className}
+ title={item.title}
+ >
+ <section className={styles.AnimeEntry}>
+ <Image
+ src={item.image}
+ width={167}
+ height={267}
+ alt="Anime Poster Image"
+ />
+ <p>{item.title}</p>
+ </section>
+ </Link>
+ ))}
+ </div>
+ </section>
+ </main>
+ );
+};
+
+export default PopularAnimes;
diff --git a/src/app/anime/components/recentEpisodes.jsx b/src/app/anime/components/recentEpisodes.jsx
new file mode 100644
index 0000000..43ad1de
--- /dev/null
+++ b/src/app/anime/components/recentEpisodes.jsx
@@ -0,0 +1,54 @@
+import Link from "next/link";
+import Image from "next/image";
+import { Atkinson_Hyperlegible } from "next/font/google";
+
+import styles from "../styles/pop_recent_top.module.css";
+import { recent } from "../data-fetch/request";
+import { preFetchAnimeInfo } from "./cacher";
+
+const atkinson = Atkinson_Hyperlegible({ subsets: ["latin"], weight: "400" });
+
+const RecentAnimes = async () => {
+ const data = await recent();
+
+ preFetchAnimeInfo(data);
+
+ return (
+ <main className={styles.Main}>
+ <section>
+ <h2 className={styles.AnimeHeaderText}>Recent Releases</h2>
+ <div className={styles.AnimeContainer}>
+ {data &&
+ data.results.map((item, index) => (
+ <Link
+ key={index}
+ href={`/anime/${item.id}`}
+ shallow
+ style={{
+ color: "white",
+ textDecoration: "none",
+ }}
+ className={atkinson.className}
+ title={item.title}
+ >
+ <section className={styles.AnimeEntry}>
+ <Image
+ src={item.image}
+ width={167}
+ height={267}
+ alt="Anime Poster Image"
+ />
+ <p>{item.title}</p>
+ <p className={styles.EpisodeText}>
+ Episode: {item.episodeNumber}
+ </p>
+ </section>
+ </Link>
+ ))}
+ </div>
+ </section>
+ </main>
+ );
+};
+
+export default RecentAnimes;
diff --git a/src/app/anime/components/search.jsx b/src/app/anime/components/search.jsx
new file mode 100644
index 0000000..f916217
--- /dev/null
+++ b/src/app/anime/components/search.jsx
@@ -0,0 +1,55 @@
+"use client";
+
+import { FaSearch } from "react-icons/fa";
+import { useState } from "react";
+import Link from "next/link";
+
+import styles from "../styles/search.module.css";
+import SearchResults from "./search_results";
+
+const SearcBar = () => {
+ const [title, setTitle] = useState("");
+ const [searchResults, setSearchResults] = useState(null);
+
+ const handleSearchInput = async (title) => {
+ setSearchResults(await SearchResults(title));
+ };
+
+ return (
+ <main>
+ <section className={styles.SearchBarContainer}>
+ <div className={styles.SearchInputContainer}>
+ <FaSearch color="white" />
+ <input
+ placeholder="Enter anime title"
+ name="Anime Title"
+ type="text"
+ onChange={(event) => {
+ if (event.target.value.trim() != "") {
+ setTitle(event.target.value);
+ }
+ }}
+ autoComplete="off"
+ onKeyDown={async (event) => {
+ if (
+ event.code === "Enter" ||
+ event.key === "Enter" ||
+ event.code === 13
+ ) {
+ await handleSearchInput(title);
+ }
+ }}
+ ></input>
+ </div>
+ <Link shallow href={"/"}>
+ <button className={styles.animeHistoryButton}>
+ History
+ </button>
+ </Link>
+ </section>
+ {searchResults}
+ </main>
+ );
+};
+
+export default SearcBar;
diff --git a/src/app/anime/components/search_results.jsx b/src/app/anime/components/search_results.jsx
new file mode 100644
index 0000000..d4c8146
--- /dev/null
+++ b/src/app/anime/components/search_results.jsx
@@ -0,0 +1,42 @@
+import { Atkinson_Hyperlegible } from "next/font/google";
+import Link from "next/link";
+import Image from "next/image";
+
+import styles from "../styles/search.module.css";
+import { search_results } from "../data-fetch/request";
+import { preFetchAnimeInfo } from "./cacher";
+
+const atkinson = Atkinson_Hyperlegible({ subsets: ["latin"], weight: "400" });
+
+const SearchResults = async (title) => {
+ const data = await search_results(title);
+
+ preFetchAnimeInfo(data);
+
+ return (
+ <section className={styles.SearchResultsContainer}>
+ {data &&
+ data.results.map((item, index) => (
+ <Link
+ shallow
+ href={`/anime/${item.id}`}
+ key={index}
+ className={atkinson.className}
+ style={{ color: "white", textDecoration: "none" }}
+ >
+ <div className={styles.AnimeEntry}>
+ <p>{item.title}</p>
+ <Image
+ src={item.image}
+ width={140}
+ height={200}
+ alt="Anime Poster"
+ />
+ </div>
+ </Link>
+ ))}
+ </section>
+ );
+};
+
+export default SearchResults;
diff --git a/src/app/anime/components/storeHistory.js b/src/app/anime/components/storeHistory.js
new file mode 100644
index 0000000..bd41815
--- /dev/null
+++ b/src/app/anime/components/storeHistory.js
@@ -0,0 +1,35 @@
+"use client";
+
+export function storeLocal(watchData) {
+ const currentDate = new Date();
+ const jsonData = localStorage.getItem("data");
+ const dataObject = jsonData ? JSON.parse(jsonData) : {};
+
+ if (!dataObject.watchHis) {
+ dataObject.watchHis = [];
+ }
+
+ let found = false;
+ dataObject.watchHis.forEach((element) => {
+ if (element.name === watchData.name) {
+ let episode = watchData.episode;
+ let date = `${currentDate.getDate()}-${String(
+ currentDate.getMonth() + 1
+ ).padStart(2, "0")}`;
+ let time = `${currentDate.getHours()}:${String(
+ currentDate.getMinutes()
+ ).padStart(2, "0")}`;
+ element.episode = episode;
+ element.date = date;
+ element.time = time;
+ found = true;
+ }
+ });
+
+ if (!found) {
+ dataObject.watchHis.push(watchData);
+ }
+
+ let updatedData = JSON.stringify(dataObject);
+ localStorage.setItem("data", updatedData);
+}
diff --git a/src/app/anime/components/topAiring.jsx b/src/app/anime/components/topAiring.jsx
new file mode 100644
index 0000000..28d3f6d
--- /dev/null
+++ b/src/app/anime/components/topAiring.jsx
@@ -0,0 +1,54 @@
+import Link from "next/link";
+import Image from "next/image";
+import { Atkinson_Hyperlegible } from "next/font/google";
+
+import styles from "../styles/pop_recent_top.module.css";
+import { top_airing } from "../data-fetch/request";
+import { preFetchAnimeInfo } from "./cacher";
+
+const atkinson = Atkinson_Hyperlegible({ subsets: ["latin"], weight: "400" });
+
+const TopAiringAnimes = async () => {
+ const data = await top_airing();
+
+ preFetchAnimeInfo(data);
+
+ return (
+ <main className={styles.Main}>
+ <section>
+ <h2 className={styles.AnimeHeaderText}>Top Airing Animes</h2>
+ <div className={styles.AnimeContainer}>
+ {data &&
+ data.results.map((item, index) => (
+ <Link
+ key={index}
+ href={`/anime/${item.id}`}
+ shallow
+ style={{
+ color: "white",
+ textDecoration: "none",
+ }}
+ className={atkinson.className}
+ title={item.title}
+ >
+ <section className={styles.AnimeEntry}>
+ <Image
+ src={item.image}
+ width={167}
+ height={267}
+ alt="Anime Poster Image"
+ />
+ <p>{item.title}</p>
+ <p className={styles.EpisodeText}>
+ Episode: {item.episodeNumber}
+ </p>
+ </section>
+ </Link>
+ ))}
+ </div>
+ </section>
+ </main>
+ );
+};
+
+export default TopAiringAnimes;