mirror of
https://github.com/JustAnimeCore/JustAnime.git
synced 2026-04-17 22:01:45 +00:00
Landing page
This commit is contained in:
64
src/hooks/useSearch.js
Normal file
64
src/hooks/useSearch.js
Normal file
@@ -0,0 +1,64 @@
|
||||
import { useState, useEffect, useRef, useCallback } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useSearchContext } from "@/src/context/SearchContext";
|
||||
|
||||
const useSearch = () => {
|
||||
const { isSearchVisible, setIsSearchVisible } = useSearchContext();
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
const [debouncedValue, setDebouncedValue] = useState("");
|
||||
const suggestionRefs = useRef([]);
|
||||
const location = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setDebouncedValue(searchValue);
|
||||
}, 500);
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
};
|
||||
}, [searchValue]);
|
||||
|
||||
useEffect(() => {
|
||||
setIsSearchVisible(false);
|
||||
setSearchValue("");
|
||||
setDebouncedValue("");
|
||||
// setIsFocused(false);
|
||||
}, [location, setIsSearchVisible]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
const isInsideSuggestionBox = suggestionRefs.current.some(
|
||||
(ref) => ref && ref.contains(event.target)
|
||||
);
|
||||
const isInsideInput = document.activeElement === event.target;
|
||||
if (!isInsideSuggestionBox && !isInsideInput) {
|
||||
setIsFocused(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
const addSuggestionRef = useCallback((ref) => {
|
||||
if (ref && !suggestionRefs.current.includes(ref)) {
|
||||
suggestionRefs.current.push(ref);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return {
|
||||
isSearchVisible,
|
||||
setIsSearchVisible,
|
||||
searchValue,
|
||||
setSearchValue,
|
||||
isFocused,
|
||||
setIsFocused,
|
||||
debouncedValue,
|
||||
suggestionRefs,
|
||||
addSuggestionRef,
|
||||
};
|
||||
};
|
||||
|
||||
export default useSearch;
|
||||
49
src/hooks/useToolTipPosition.js
Normal file
49
src/hooks/useToolTipPosition.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
const useToolTipPosition = (hoveredItem, data) => {
|
||||
const cardRefs = useRef([]);
|
||||
const [tooltipPosition, setTooltipPosition] = useState("top-1/2");
|
||||
const [tooltipHorizontalPosition, setTooltipHorizontalPosition] =
|
||||
useState("left-1/2");
|
||||
|
||||
const updateToolTipPosition = () => {
|
||||
if (hoveredItem !== null) {
|
||||
const refIndex = data.findIndex(
|
||||
(item, index) => item.id + index === hoveredItem
|
||||
);
|
||||
const ref = cardRefs.current[refIndex];
|
||||
if (ref) {
|
||||
const { top, height, left, width } = ref.getBoundingClientRect();
|
||||
const adjustedTop = top + height / 2 - 64;
|
||||
const bottomY = window.innerHeight - adjustedTop;
|
||||
if (adjustedTop < bottomY) {
|
||||
setTooltipPosition("top-1/2");
|
||||
} else {
|
||||
setTooltipPosition("bottom-1/2");
|
||||
}
|
||||
const adjustedLeft = left + width / 2;
|
||||
const spaceRight = window.innerWidth - adjustedLeft;
|
||||
if (spaceRight > 320) {
|
||||
setTooltipHorizontalPosition("left-1/2");
|
||||
} else {
|
||||
setTooltipHorizontalPosition("right-1/2");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
updateToolTipPosition();
|
||||
const handleScroll = () => {
|
||||
updateToolTipPosition();
|
||||
};
|
||||
window.addEventListener("scroll", handleScroll);
|
||||
return () => {
|
||||
window.removeEventListener("scroll", handleScroll);
|
||||
};
|
||||
}, [hoveredItem, data]);
|
||||
|
||||
return { tooltipPosition, tooltipHorizontalPosition, cardRefs };
|
||||
};
|
||||
|
||||
export default useToolTipPosition;
|
||||
269
src/hooks/useWatch.js
Normal file
269
src/hooks/useWatch.js
Normal file
@@ -0,0 +1,269 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import getAnimeInfo from "@/src/utils/getAnimeInfo.utils";
|
||||
import getEpisodes from "@/src/utils/getEpisodes.utils";
|
||||
import getNextEpisodeSchedule from "../utils/getNextEpisodeSchedule.utils";
|
||||
import getServers from "../utils/getServers.utils";
|
||||
import getStreamInfo from "../utils/getStreamInfo.utils";
|
||||
|
||||
export const useWatch = (animeId, initialEpisodeId) => {
|
||||
const [error, setError] = useState(null);
|
||||
const [buffering, setBuffering] = useState(true);
|
||||
const [streamInfo, setStreamInfo] = useState(null);
|
||||
const [animeInfo, setAnimeInfo] = useState(null);
|
||||
const [episodes, setEpisodes] = useState(null);
|
||||
const [animeInfoLoading, setAnimeInfoLoading] = useState(false);
|
||||
const [totalEpisodes, setTotalEpisodes] = useState(null);
|
||||
const [seasons, setSeasons] = useState(null);
|
||||
const [servers, setServers] = useState(null);
|
||||
const [streamUrl, setStreamUrl] = useState(null);
|
||||
const [isFullOverview, setIsFullOverview] = useState(false);
|
||||
const [subtitles, setSubtitles] = useState([]);
|
||||
const [thumbnail, setThumbnail] = useState(null);
|
||||
const [intro, setIntro] = useState(null);
|
||||
const [outro, setOutro] = useState(null);
|
||||
const [episodeId, setEpisodeId] = useState(null);
|
||||
const [activeEpisodeNum, setActiveEpisodeNum] = useState(null);
|
||||
const [activeServerId, setActiveServerId] = useState(null);
|
||||
const [activeServerType, setActiveServerType] = useState(null);
|
||||
const [activeServerName, setActiveServerName] = useState(null);
|
||||
const [serverLoading, setServerLoading] = useState(true);
|
||||
const [nextEpisodeSchedule, setNextEpisodeSchedule] = useState(null);
|
||||
const isServerFetchInProgress = useRef(false);
|
||||
const isStreamFetchInProgress = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
setEpisodes(null);
|
||||
setEpisodeId(null);
|
||||
setActiveEpisodeNum(null);
|
||||
setServers(null);
|
||||
setActiveServerId(null);
|
||||
setStreamInfo(null);
|
||||
setStreamUrl(null);
|
||||
setSubtitles([]);
|
||||
setThumbnail(null);
|
||||
setIntro(null);
|
||||
setOutro(null);
|
||||
setBuffering(true);
|
||||
setServerLoading(true);
|
||||
setError(null);
|
||||
setAnimeInfo(null);
|
||||
setSeasons(null);
|
||||
setTotalEpisodes(null);
|
||||
setAnimeInfoLoading(true);
|
||||
isServerFetchInProgress.current = false;
|
||||
isStreamFetchInProgress.current = false;
|
||||
}, [animeId]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchInitialData = async () => {
|
||||
try {
|
||||
setAnimeInfoLoading(true);
|
||||
const [animeData, episodesData] = await Promise.all([
|
||||
getAnimeInfo(animeId, false),
|
||||
getEpisodes(animeId),
|
||||
]);
|
||||
setAnimeInfo(animeData?.data);
|
||||
setSeasons(animeData?.seasons);
|
||||
setEpisodes(episodesData?.episodes);
|
||||
setTotalEpisodes(episodesData?.totalEpisodes);
|
||||
const newEpisodeId =
|
||||
initialEpisodeId ||
|
||||
(episodesData?.episodes?.length > 0
|
||||
? episodesData.episodes[0].id.match(/ep=(\d+)/)?.[1]
|
||||
: null);
|
||||
setEpisodeId(newEpisodeId);
|
||||
} catch (err) {
|
||||
console.error("Error fetching initial data:", err);
|
||||
setError(err.message || "An error occurred.");
|
||||
} finally {
|
||||
setAnimeInfoLoading(false);
|
||||
}
|
||||
};
|
||||
fetchInitialData();
|
||||
}, [animeId]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchNextEpisodeSchedule = async () => {
|
||||
try {
|
||||
const data = await getNextEpisodeSchedule(animeId);
|
||||
setNextEpisodeSchedule(data);
|
||||
} catch (err) {
|
||||
console.error("Error fetching next episode schedule:", err);
|
||||
}
|
||||
};
|
||||
fetchNextEpisodeSchedule();
|
||||
}, [animeId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!episodes || !episodeId) {
|
||||
setActiveEpisodeNum(null);
|
||||
return;
|
||||
}
|
||||
const activeEpisode = episodes.find((episode) => {
|
||||
const match = episode.id.match(/ep=(\d+)/);
|
||||
return match && match[1] === episodeId;
|
||||
});
|
||||
const newActiveEpisodeNum = activeEpisode ? activeEpisode.episode_no : null;
|
||||
if (activeEpisodeNum !== newActiveEpisodeNum) {
|
||||
setActiveEpisodeNum(newActiveEpisodeNum);
|
||||
}
|
||||
}, [episodeId, episodes]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!episodeId || !episodes || isServerFetchInProgress.current) return;
|
||||
|
||||
const fetchServers = async () => {
|
||||
isServerFetchInProgress.current = true;
|
||||
setServerLoading(true);
|
||||
try {
|
||||
const data = await getServers(animeId, episodeId);
|
||||
console.log(data);
|
||||
|
||||
const filteredServers = data?.filter(
|
||||
(server) =>
|
||||
server.serverName === "HD-1" ||
|
||||
server.serverName === "HD-2" ||
|
||||
server.serverName === "HD-3"
|
||||
);
|
||||
if (filteredServers.some((s) => s.type === "sub")) {
|
||||
filteredServers.push({
|
||||
type: "sub",
|
||||
data_id: "69696969",
|
||||
server_id: "41",
|
||||
serverName: "HD-4",
|
||||
});
|
||||
}
|
||||
if (filteredServers.some((s) => s.type === "dub")) {
|
||||
filteredServers.push({
|
||||
type: "dub",
|
||||
data_id: "96969696",
|
||||
server_id: "42",
|
||||
serverName: "HD-4",
|
||||
});
|
||||
}
|
||||
const savedServerName = localStorage.getItem("server_name");
|
||||
const savedServerType = localStorage.getItem("server_type");
|
||||
let initialServer =
|
||||
data.find(
|
||||
(s) =>
|
||||
s.serverName === savedServerName && s.type === savedServerType
|
||||
) ||
|
||||
data.find((s) => s.serverName === savedServerName) ||
|
||||
data.find((s) => s.type === savedServerType) ||
|
||||
data.find(
|
||||
(s) => s.serverName === "HD-1" && s.type === savedServerType
|
||||
) ||
|
||||
data.find(
|
||||
(s) => s.serverName === "HD-2" && s.type === savedServerType
|
||||
) ||
|
||||
data.find(
|
||||
(s) => s.serverName === "HD-3" && s.type === savedServerType
|
||||
) ||
|
||||
data.find(
|
||||
(s) => s.serverName === "HD-4" && s.type === savedServerType
|
||||
) ||
|
||||
filteredServers[0];
|
||||
setServers(filteredServers);
|
||||
setActiveServerType(initialServer?.type);
|
||||
setActiveServerName(initialServer?.serverName);
|
||||
setActiveServerId(initialServer?.data_id);
|
||||
} catch (error) {
|
||||
console.error("Error fetching servers:", error);
|
||||
setError(error.message || "An error occurred.");
|
||||
} finally {
|
||||
setServerLoading(false);
|
||||
isServerFetchInProgress.current = false;
|
||||
}
|
||||
};
|
||||
fetchServers();
|
||||
}, [episodeId, episodes]);
|
||||
// Fetch stream info only when episodeId, activeServerId, and servers are ready
|
||||
useEffect(() => {
|
||||
if (
|
||||
!episodeId ||
|
||||
!activeServerId ||
|
||||
!servers ||
|
||||
isServerFetchInProgress.current ||
|
||||
isStreamFetchInProgress.current
|
||||
)
|
||||
return;
|
||||
if (
|
||||
(activeServerName?.toLowerCase() === "hd-1"
|
||||
|| activeServerName?.toLowerCase() === "hd-2"|| activeServerName?.toLowerCase() === "hd-3"|| activeServerName?.toLowerCase() === "hd-4")
|
||||
&&
|
||||
!serverLoading
|
||||
) {
|
||||
setBuffering(false);
|
||||
return;
|
||||
}
|
||||
const fetchStreamInfo = async () => {
|
||||
isStreamFetchInProgress.current = true;
|
||||
setBuffering(true);
|
||||
try {
|
||||
const server = servers.find((srv) => srv.data_id === activeServerId);
|
||||
if (server) {
|
||||
const data = await getStreamInfo(
|
||||
animeId,
|
||||
episodeId,
|
||||
server.serverName.toLowerCase(),
|
||||
server.type.toLowerCase()
|
||||
);
|
||||
setStreamInfo(data);
|
||||
setStreamUrl(data?.streamingLink?.link?.file || null);
|
||||
setIntro(data?.streamingLink?.intro || null);
|
||||
setOutro(data?.streamingLink?.outro || null);
|
||||
const subtitles =
|
||||
data?.streamingLink?.tracks
|
||||
?.filter((track) => track.kind === "captions")
|
||||
.map(({ file, label }) => ({ file, label })) || [];
|
||||
setSubtitles(subtitles);
|
||||
const thumbnailTrack = data?.streamingLink?.tracks?.find(
|
||||
(track) => track.kind === "thumbnails" && track.file
|
||||
);
|
||||
if (thumbnailTrack) setThumbnail(thumbnailTrack.file);
|
||||
} else {
|
||||
setError("No server found with the activeServerId.");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error fetching stream info:", err);
|
||||
setError(err.message || "An error occurred.");
|
||||
} finally {
|
||||
setBuffering(false);
|
||||
isStreamFetchInProgress.current = false;
|
||||
}
|
||||
};
|
||||
fetchStreamInfo();
|
||||
}, [episodeId, activeServerId, servers]);
|
||||
|
||||
return {
|
||||
error,
|
||||
buffering,
|
||||
serverLoading,
|
||||
streamInfo,
|
||||
animeInfo,
|
||||
episodes,
|
||||
nextEpisodeSchedule,
|
||||
animeInfoLoading,
|
||||
totalEpisodes,
|
||||
seasons,
|
||||
servers,
|
||||
streamUrl,
|
||||
isFullOverview,
|
||||
setIsFullOverview,
|
||||
subtitles,
|
||||
thumbnail,
|
||||
intro,
|
||||
outro,
|
||||
episodeId,
|
||||
setEpisodeId,
|
||||
activeEpisodeNum,
|
||||
setActiveEpisodeNum,
|
||||
activeServerId,
|
||||
setActiveServerId,
|
||||
activeServerType,
|
||||
setActiveServerType,
|
||||
activeServerName,
|
||||
setActiveServerName,
|
||||
};
|
||||
};
|
||||
34
src/hooks/useWatchControl.js
Normal file
34
src/hooks/useWatchControl.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
export default function useWatchControl() {
|
||||
const [autoPlay, setAutoPlay] = useState(
|
||||
() => JSON.parse(localStorage.getItem("autoPlay")) || false
|
||||
);
|
||||
const [autoSkipIntro, setAutoSkipIntro] = useState(
|
||||
() => JSON.parse(localStorage.getItem("autoSkipIntro")) || false
|
||||
);
|
||||
const [autoNext, setAutoNext] = useState(
|
||||
() => JSON.parse(localStorage.getItem("autoNext")) || false
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem("autoPlay", JSON.stringify(autoPlay));
|
||||
}, [autoPlay]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem("autoSkipIntro", JSON.stringify(autoSkipIntro));
|
||||
}, [autoSkipIntro]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem("autoNext", JSON.stringify(autoNext));
|
||||
}, [autoNext]);
|
||||
|
||||
return {
|
||||
autoPlay,
|
||||
setAutoPlay,
|
||||
autoSkipIntro,
|
||||
setAutoSkipIntro,
|
||||
autoNext,
|
||||
setAutoNext,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user