feat(frontend): add a release page

add a video player to a release page without release info with ability to save and view wathed episodes
This commit is contained in:
Kentai Radiquum 2024-04-30 05:19:42 +05:00
parent bbf8fc5436
commit 75dd8d83bd
Signed by: Radiquum
GPG key ID: 858E8EE696525EED
4 changed files with 187 additions and 27 deletions

View file

@ -1,7 +1,9 @@
import requests
from fastapi import APIRouter from fastapi import APIRouter
from fastapi import Request from fastapi import Request
from modules.proxy import apiRequest from modules.proxy import apiRequest
from modules.proxy import ENDPOINTS from modules.proxy import ENDPOINTS
from modules.proxy import USER_AGENT
router = APIRouter() router = APIRouter()
@ -33,40 +35,39 @@ async def GetReleaseVoiceoverPlayer(
summary="Get available episodes for selected voiceover and a player of a release", summary="Get available episodes for selected voiceover and a player of a release",
) )
async def GetReleaseEpisodes( async def GetReleaseEpisodes(
request: Request, release_id: str, voiceover_id: str, source_id: str request: Request,
release_id: str,
voiceover_id: str,
source_id: str,
token: str = "",
): ):
return await apiRequest( return await apiRequest(
request, request,
ENDPOINTS["release"]["episode"], ENDPOINTS["release"]["episode"],
f"{release_id}/{voiceover_id}/{source_id}", f"{release_id}/{voiceover_id}/{source_id}",
query=f"?token={token}",
) )
@router.get( @router.get(
"/{release_id}/{episode}/markWatched", "/{release_id}/{source_id}/{episode}/saveToHistory",
summary="mark episode of a selected voiceover as watched", summary="mark episode of a selected voiceover as watched and save it to watch history",
) )
async def MarkEpisodeAsWatched( async def MarkEpisodeAsWatched(
request: Request, release_id: str, source_id: str, episode: str, token: str request: Request, release_id: str, source_id: str, episode: str, token: str
): ):
return await apiRequest( headers = {
request, "User-Agent": USER_AGENT,
ENDPOINTS["statistic"]["markWatched"], "Content-Type": "application/json; charset=UTF-8",
f"${release_id}/${source_id}/${episode}", }
query=f"?token={token}",
requests.get(
f"{ENDPOINTS['statistic']['markWatched']}/{release_id}/{source_id}/{episode}?token={token}",
headers=headers,
)
requests.get(
f"{ENDPOINTS['statistic']['addHistory']}/{release_id}/{source_id}/{episode}?token={token}",
headers=headers,
) )
return {"success"}
@router.get(
"/{release_id}/{episode}/addHistory",
summary="add episode of a selected voiceover to history",
)
async def AddEpisodeToHistory(
request: Request, release_id: str, source_id: str, episode: str, token: str
):
return await apiRequest(
request,
ENDPOINTS["statistic"]["addHistory"],
f"${release_id}/${source_id}/${episode}",
query=f"?token={token}",
)

View file

@ -20,4 +20,5 @@ export const endpoints = {
abandoned: `${API_URL}/favorites/abandoned`, abandoned: `${API_URL}/favorites/abandoned`,
}, },
}, },
release: `${API_URL}/release`,
}; };

View file

@ -0,0 +1,162 @@
"use client";
import { useEffect, useState } from "react";
import { getData } from "@/app/api/api-utils";
import { endpoints } from "@/app/api/config";
import { useUserStore } from "@/app/store/user-store";
import { useSettingsStore } from "@/app/store/settings-store";
export default function Release(props) {
const userStore = useUserStore();
const settingsStore = useSettingsStore();
const [releaseInfo, setReleaseInfo] = useState();
const [voiceoverInfo, setVoiceoverInfo] = useState();
const [selectedVoiceover, setSelectedVoiceover] = useState();
const [sourcesInfo, setSourcesInfo] = useState();
const [selectedSources, setSelectedSources] = useState();
const [episodeInfo, setEpisodeInfo] = useState();
const [selectedEpisode, setSelectedEpisode] = useState();
const [episodeURL, setEpisodeURL] = useState();
useEffect(() => {
async function _fetchInfo() {
const release = await getData(`${endpoints.release}/${props.params.id}`);
const voiceover = await getData(
`${endpoints.release}/${props.params.id}/voiceover`,
);
setReleaseInfo(release);
setVoiceoverInfo(voiceover);
setSelectedVoiceover(voiceover.types[0].id);
}
if (props.params.id) {
_fetchInfo();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
async function _fetchInfo() {
const sources = await getData(
`${endpoints.release}/${props.params.id}/${selectedVoiceover}`,
);
setSourcesInfo(sources);
setSelectedSources(sources.sources[0].id);
}
if (selectedVoiceover) {
_fetchInfo();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedVoiceover]);
useEffect(() => {
async function _fetchInfo() {
let url = `${endpoints.release}/${props.params.id}/${selectedVoiceover}/${selectedSources}`;
if (userStore.token) {
url = `${endpoints.release}/${props.params.id}/${selectedVoiceover}/${selectedSources}?token=${userStore.token}`;
}
const episodes = await getData(url);
setEpisodeInfo(episodes);
setSelectedEpisode(episodes.episodes[0].position);
setEpisodeURL(episodes.episodes[0].url);
}
if (selectedSources) {
_fetchInfo();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedSources, userStore.token]);
useEffect(() => {
async function _markAsWatched() {
const url = `${endpoints.release}/${props.params.id}/${selectedSources}/${selectedEpisode}`;
await getData(`${url}/saveToHistory?token=${userStore.token}`);
}
if (userStore.token && settingsStore.saveToHistory) {
_markAsWatched();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedEpisode]);
return (
<article className="fill grid">
<iframe
src={episodeURL}
className="s9"
style={{ aspectRatio: "16/9", width: "100%", height: "auto" }}
/>
<div className="s3">
<div className="tabs">
<a data-ui="#vo" className="active">
озвучка
</a>
{sourcesInfo && <a data-ui="#src">плеер</a>}
</div>
<div
className="page padding active scroll"
style={{ Height: "438px" }}
id="vo"
>
{voiceoverInfo &&
voiceoverInfo.types.map((item) => {
return (
<button
key={item.id}
className={`small responsive ${
item.id == selectedVoiceover ? "primary" : "secondary"
}`}
style={{ marginTop: "8px" }}
onClick={() => {
setSelectedVoiceover(item.id);
}}
>
{item.name}
</button>
);
})}
</div>
{sourcesInfo && (
<div className="page center-align padding" id="src">
{sourcesInfo.sources.map((item) => {
return (
<button
key={item.id}
className={`small responsive ${
item.id == selectedSources ? "primary" : "secondary"
}`}
style={{ marginTop: "8px" }}
onClick={() => {
setSelectedSources(item.id);
}}
>
{item.name}
</button>
);
})}
</div>
)}
</div>
<div className="s12 scroll row" style={{ paddingBottom: "8px" }}>
{episodeInfo &&
episodeInfo.episodes.map((item) => {
return (
<button
key={item.position}
className={`${
item.position == selectedEpisode ? "primary" : "secondary"
}`}
onClick={() => {
setSelectedEpisode(item.position);
setEpisodeURL(item.url);
item.is_watched = true;
}}
>
{item.is_watched && <i className="small">check</i>}
{item.name}
</button>
);
})}
</div>
</article>
);
}

View file

@ -23,11 +23,7 @@ export const useUserStore = create((set, get) => ({
`${endpoints.user.profile}/${jwt.user_id}`, `${endpoints.user.profile}/${jwt.user_id}`,
jwt.jwt, jwt.jwt,
); );
if (me.is_my_profile) { get().login(me, jwt.jwt, jwt.user_id);
get().login(me, jwt.jwt, jwt.user_id);
} else {
get().logout();
}
} else { } else {
get().logout(); get().logout();
} }