mirror of
https://github.com/Radiquum/AniX.git
synced 2025-04-05 15:54:39 +00:00
385 lines
12 KiB
TypeScript
385 lines
12 KiB
TypeScript
"use client";
|
||
|
||
import { Spinner } from "#/components/Spinner/Spinner";
|
||
import { useUserStore } from "#/store/auth";
|
||
import { useUserPlayerPreferencesStore } from "#/store/player";
|
||
import { Card, Dropdown, Button } from "flowbite-react";
|
||
import { ENDPOINTS } from "#/api/config";
|
||
import { useState, useEffect } from "react";
|
||
import { Swiper, SwiperSlide } from "swiper/react";
|
||
import "swiper/css";
|
||
import "swiper/css/navigation";
|
||
import "swiper/css/mousewheel";
|
||
import "swiper/css/scrollbar";
|
||
import { Navigation, Mousewheel, Scrollbar } from "swiper/modules";
|
||
|
||
const DropdownTheme = {
|
||
floating: {
|
||
target: "w-full md:min-w-[256px] md:w-fit",
|
||
},
|
||
};
|
||
|
||
async function _fetch(url: string) {
|
||
const data = fetch(url)
|
||
.then((res) => {
|
||
if (res.ok) {
|
||
return res.json();
|
||
} else {
|
||
throw new Error("Error fetching data");
|
||
}
|
||
})
|
||
.catch((err) => console.log(err));
|
||
return data;
|
||
}
|
||
|
||
export const getAnonEpisodesWatched = (
|
||
Release: number,
|
||
Source: number,
|
||
Voiceover: number
|
||
) => {
|
||
const anonEpisodesWatched =
|
||
JSON.parse(localStorage.getItem("anonEpisodesWatched")) || {};
|
||
|
||
if (!anonEpisodesWatched.hasOwnProperty(Release)) {
|
||
anonEpisodesWatched[Release] = {};
|
||
}
|
||
if (!anonEpisodesWatched[Release].hasOwnProperty(Source)) {
|
||
anonEpisodesWatched[Release][Source] = {};
|
||
}
|
||
if (!anonEpisodesWatched[Release][Source].hasOwnProperty(Voiceover)) {
|
||
anonEpisodesWatched[Release][Source][Voiceover] = {};
|
||
}
|
||
|
||
return anonEpisodesWatched;
|
||
};
|
||
|
||
const getAnonCurrentEpisodeWatched = (
|
||
Release: number,
|
||
Source: number,
|
||
Voiceover: number,
|
||
Episode: number
|
||
) => {
|
||
const anonEpisodesWatched =
|
||
JSON.parse(localStorage.getItem("anonEpisodesWatched")) || {};
|
||
|
||
if (!anonEpisodesWatched.hasOwnProperty(Release)) {
|
||
return false;
|
||
}
|
||
if (!anonEpisodesWatched[Release].hasOwnProperty(Source)) {
|
||
return false;
|
||
}
|
||
if (!anonEpisodesWatched[Release][Source].hasOwnProperty(Voiceover)) {
|
||
return false;
|
||
}
|
||
if (
|
||
!anonEpisodesWatched[Release][Source][Voiceover].hasOwnProperty(Episode)
|
||
) {
|
||
return false;
|
||
}
|
||
|
||
return anonEpisodesWatched[Release][Source][Voiceover][Episode];
|
||
};
|
||
|
||
export const saveAnonEpisodeWatched = (
|
||
Release: number,
|
||
Source: number,
|
||
Voiceover: number,
|
||
Episode: number
|
||
) => {
|
||
const anonEpisodesWatched = getAnonEpisodesWatched(
|
||
Release,
|
||
Source,
|
||
Voiceover
|
||
);
|
||
localStorage.setItem(
|
||
"anonEpisodesWatched",
|
||
JSON.stringify({
|
||
...anonEpisodesWatched,
|
||
[Release]: {
|
||
...anonEpisodesWatched[Release],
|
||
[Source]: {
|
||
...anonEpisodesWatched[Release][Source],
|
||
[Voiceover]: {
|
||
...anonEpisodesWatched[Release][Source][Voiceover],
|
||
[Episode]: true,
|
||
},
|
||
},
|
||
},
|
||
})
|
||
);
|
||
};
|
||
|
||
export const ReleasePlayer = (props: { id: number }) => {
|
||
const userStore = useUserStore();
|
||
const preferredVoiceoverStore = useUserPlayerPreferencesStore();
|
||
const storedPreferredVoiceover =
|
||
preferredVoiceoverStore.getPreferredVoiceover(props.id);
|
||
const storedPreferredPlayer = preferredVoiceoverStore.getPreferredPlayer(
|
||
props.id
|
||
);
|
||
const [voiceoverInfo, setVoiceoverInfo] = useState(null);
|
||
const [selectedVoiceover, setSelectedVoiceover] = useState(null);
|
||
const [sourcesInfo, setSourcesInfo] = useState(null);
|
||
const [selectedSource, setSelectedSource] = useState(null);
|
||
const [episodeInfo, setEpisodeInfo] = useState(null);
|
||
const [selectedEpisode, setSelectedEpisode] = useState(null);
|
||
const [error, setError] = useState(null);
|
||
const setSelectedVoiceoverAndSaveAsPreferred = (voiceover: any) => {
|
||
setSelectedVoiceover(voiceover);
|
||
preferredVoiceoverStore.setPreferredVoiceover(props.id, voiceover.name);
|
||
};
|
||
const setSelectedPlayerAndSaveAsPreferred = (player: any) => {
|
||
setSelectedSource(player);
|
||
preferredVoiceoverStore.setPreferredPlayer(props.id, player.name);
|
||
};
|
||
|
||
function _setError(error: string) {
|
||
setVoiceoverInfo(null);
|
||
setSelectedVoiceover(null);
|
||
setSourcesInfo(null);
|
||
setSelectedSource(null);
|
||
setEpisodeInfo(null);
|
||
setSelectedEpisode(null);
|
||
setError(error);
|
||
}
|
||
|
||
async function _fetchInfo(
|
||
url: string,
|
||
type: "voiceover" | "sources" | "episodes"
|
||
) {
|
||
let data: any = {};
|
||
data = await fetch(url)
|
||
.then((res) => {
|
||
if (res.ok) {
|
||
return res.json();
|
||
} else {
|
||
throw new Error("Error fetching data");
|
||
}
|
||
})
|
||
.catch((err) => {
|
||
_setError("Ошибка получение ответа от сервера");
|
||
return;
|
||
});
|
||
|
||
if (!data || (data && Object.keys(data).length == 0)) {
|
||
_setError("Ошибка получение данных с сервера");
|
||
return;
|
||
}
|
||
|
||
if (type == "voiceover") {
|
||
setVoiceoverInfo(data.types);
|
||
const preferredVoiceover =
|
||
data.types.find(
|
||
(voiceover: any) => voiceover.name === storedPreferredVoiceover
|
||
) || data.types[0];
|
||
setSelectedVoiceover(preferredVoiceover);
|
||
} else if (type == "sources") {
|
||
setSourcesInfo(data.sources);
|
||
const preferredSource =
|
||
data.sources.find(
|
||
(source: any) => source.name === storedPreferredPlayer
|
||
) || data.sources[0];
|
||
setSelectedSource(preferredSource);
|
||
} else if (type == "episodes") {
|
||
if (data.episodes.length === 0) {
|
||
const remSources = sourcesInfo.filter(
|
||
(source) => source.id !== selectedSource.id
|
||
);
|
||
setSourcesInfo(remSources);
|
||
setSelectedSource(remSources[0]);
|
||
return;
|
||
} else {
|
||
setEpisodeInfo(data.episodes);
|
||
setSelectedEpisode(data.episodes[0]);
|
||
|
||
const WatchedEpisodes = getAnonEpisodesWatched(
|
||
props.id,
|
||
selectedSource.id,
|
||
selectedVoiceover.id
|
||
);
|
||
if (
|
||
Object.keys(
|
||
WatchedEpisodes[props.id][selectedSource.id][selectedVoiceover.id]
|
||
).length != 0
|
||
) {
|
||
const watchedEpisodes =
|
||
WatchedEpisodes[props.id][selectedSource.id][selectedVoiceover.id];
|
||
let lastWatchedEpisode = Number(Object.keys(watchedEpisodes).pop());
|
||
if (
|
||
!["Sibnet", "Sibnet (не работает)"].includes(selectedSource.name)
|
||
) {
|
||
lastWatchedEpisode = Number(lastWatchedEpisode) - 1;
|
||
}
|
||
setSelectedEpisode(data.episodes[lastWatchedEpisode]);
|
||
}
|
||
}
|
||
} else {
|
||
_setError("Неизвестный тип запроса");
|
||
}
|
||
}
|
||
|
||
useEffect(() => {
|
||
_fetchInfo(`${ENDPOINTS.release.episode}/${props.id}`, "voiceover");
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
}, [props.id]);
|
||
|
||
useEffect(() => {
|
||
if (selectedVoiceover) {
|
||
_fetchInfo(
|
||
`${ENDPOINTS.release.episode}/${props.id}/${selectedVoiceover.id}`,
|
||
"sources"
|
||
);
|
||
}
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
}, [props.id, selectedVoiceover]);
|
||
|
||
useEffect(() => {
|
||
if (selectedSource) {
|
||
let url = `${ENDPOINTS.release.episode}/${props.id}/${selectedVoiceover.id}/${selectedSource.id}`;
|
||
if (userStore.token) {
|
||
url = `${ENDPOINTS.release.episode}/${props.id}/${selectedVoiceover.id}/${selectedSource.id}?token=${userStore.token}`;
|
||
}
|
||
_fetchInfo(url, "episodes");
|
||
}
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
}, [props.id, selectedSource, userStore.token]);
|
||
|
||
async function _addToHistory(episode: any) {
|
||
if (episode && userStore.token) {
|
||
_fetch(
|
||
`${ENDPOINTS.statistic.addHistory}/${props.id}/${selectedSource.id}/${episode.position}?token=${userStore.token}`
|
||
);
|
||
_fetch(
|
||
`${ENDPOINTS.statistic.markWatched}/${props.id}/${selectedSource.id}/${episode.position}?token=${userStore.token}`
|
||
);
|
||
}
|
||
}
|
||
|
||
return (
|
||
<Card>
|
||
{!voiceoverInfo || !sourcesInfo || !episodeInfo ? (
|
||
<div className="flex items-center justify-center w-full aspect-video">
|
||
{!error ? <Spinner /> : <p>{error}</p>}
|
||
</div>
|
||
) : (
|
||
<>
|
||
<div className="flex flex-wrap gap-2">
|
||
<Dropdown
|
||
label={`Озвучка: ${selectedVoiceover.name}`}
|
||
color="blue"
|
||
theme={DropdownTheme}
|
||
>
|
||
{voiceoverInfo.map((voiceover: any) => (
|
||
<Dropdown.Item
|
||
key={`voiceover_${voiceover.id}`}
|
||
onClick={() =>
|
||
setSelectedVoiceoverAndSaveAsPreferred(voiceover)
|
||
}
|
||
>
|
||
{voiceover.name}
|
||
</Dropdown.Item>
|
||
))}
|
||
</Dropdown>
|
||
<Dropdown
|
||
label={`Плеер: ${selectedSource.name}`}
|
||
color="blue"
|
||
theme={DropdownTheme}
|
||
>
|
||
{sourcesInfo.map((source: any) => (
|
||
<Dropdown.Item
|
||
key={`source_${source.id}`}
|
||
onClick={() => setSelectedPlayerAndSaveAsPreferred(source)}
|
||
>
|
||
{source.name}
|
||
</Dropdown.Item>
|
||
))}
|
||
</Dropdown>
|
||
</div>
|
||
<div className="aspect-video">
|
||
{selectedEpisode ? (
|
||
<iframe
|
||
allowFullScreen={true}
|
||
src={selectedEpisode.url}
|
||
className="w-full h-full rounded-md"
|
||
></iframe>
|
||
) : (
|
||
<p>Ошибка загрузки плеера</p>
|
||
)}
|
||
</div>
|
||
<div>
|
||
<Swiper
|
||
modules={[Navigation, Mousewheel, Scrollbar]}
|
||
spaceBetween={8}
|
||
slidesPerView={"auto"}
|
||
direction={"horizontal"}
|
||
mousewheel={{
|
||
enabled: true,
|
||
sensitivity: 4,
|
||
}}
|
||
scrollbar={{
|
||
enabled: true,
|
||
draggable: true,
|
||
}}
|
||
allowTouchMove={true}
|
||
style={
|
||
{
|
||
"--swiper-scrollbar-bottom": "0",
|
||
} as React.CSSProperties
|
||
}
|
||
>
|
||
{episodeInfo.map((episode: any) => (
|
||
<SwiperSlide
|
||
key={`episode_${episode.position}`}
|
||
style={{ maxWidth: "fit-content" }}
|
||
className="pb-2"
|
||
>
|
||
<Button
|
||
color={
|
||
selectedEpisode.position === episode.position
|
||
? "blue"
|
||
: "light"
|
||
}
|
||
theme={{ base: "w-full disabled:opacity-100" }}
|
||
onClick={() => {
|
||
setSelectedEpisode(episode);
|
||
episode.is_watched = true;
|
||
_addToHistory(episode);
|
||
saveAnonEpisodeWatched(
|
||
props.id,
|
||
selectedSource.id,
|
||
selectedVoiceover.id,
|
||
episode.position
|
||
);
|
||
}}
|
||
disabled={selectedEpisode.position === episode.position}
|
||
>
|
||
{episode.name
|
||
? episode.name
|
||
: `${
|
||
!["Sibnet", "Sibnet (не работает)"].includes(
|
||
selectedSource.name
|
||
)
|
||
? episode.position
|
||
: episode.position + 1
|
||
} серия`}
|
||
{episode.is_watched ||
|
||
getAnonCurrentEpisodeWatched(
|
||
props.id,
|
||
selectedSource.id,
|
||
selectedVoiceover.id,
|
||
episode.position
|
||
) ? (
|
||
<span className="w-5 h-5 ml-2 iconify material-symbols--check-circle"></span>
|
||
) : (
|
||
<span className="w-5 h-5 ml-2 opacity-10 iconify material-symbols--check-circle"></span>
|
||
)}
|
||
</Button>
|
||
</SwiperSlide>
|
||
))}
|
||
</Swiper>
|
||
</div>
|
||
</>
|
||
)}
|
||
</Card>
|
||
);
|
||
};
|