revert base

This commit is contained in:
Francesco Grazioso 2024-05-27 12:51:59 +02:00
parent ad132b7c53
commit 28b0338f23
3 changed files with 310 additions and 420 deletions

View File

@ -1,81 +1,44 @@
import axios from "axios"; import axios from 'axios'
import type { AxiosResponse } from "axios"; import type {MediaItemResponse} from '@/api/interfaces'
import type { DownloadResponse, MediaItemResponse } from "@/api/interfaces";
const BASE_URL = 'http://localhost:8000/api'
const BASE_URL = "http://localhost:8000/api";
function get(url: string): Promise<any> {
const api = axios.create({ return axios.get(`${BASE_URL}${url}`)
baseURL: BASE_URL, .then(response => response.data)
}); .catch(error => {
throw error;
async function get<T>(url: string): Promise<AxiosResponse<T>> { });
return api.get(url); }
}
function post(url: string, data: any): Promise<any> {
async function post<T>(url: string, data: any): Promise<AxiosResponse<T>> { return axios.post(`${BASE_URL}${url}`, data)
return api.post(url, data); .then(response => response.data)
} .catch(error => {
throw error;
export default function search( });
query: string, }
type: string
): Promise<AxiosResponse<MediaItemResponse>> { export default function search(query: string, type: string) : Promise<MediaItemResponse> {
return get(`/search?search_terms=${query}&type=${type}`); return get(`/search?search_terms=${query}&type=${type}`)
} }
export async function getEpisodesInfo( export async function getEpisodesInfo(mediaId: number, mediaSlug: string, mediaType: string): Promise<Response> {
mediaId: number, const url = `${BASE_URL}/search/get_episodes_info?media_id=${mediaId}&media_slug=${mediaSlug}&type_media=${mediaType}`;
mediaSlug: string, return await fetch(url, {
mediaType: string method: 'GET',
): Promise<Response> { headers: {
const url = `/search/get_episodes_info?media_id=${mediaId}&media_slug=${mediaSlug}&type_media=${mediaType}`; 'Content-Type': 'text/event-stream'
return fetch(`${BASE_URL}${url}`, { }
method: "GET", });
headers: { }
"Content-Type": "text/event-stream",
}, export async function downloadFilm(mediaId: number, mediaSlug: string, mediaType: string): Promise<Response> {
}); const url = `/download/`;
} const data = {
media_id: mediaId,
export async function getPreview( media_slug: mediaSlug,
mediaId: number, type_media: mediaType
mediaSlug: string, };
mediaType: string return post(url, data);
): Promise<AxiosResponse<any>> { }
const url = `/search/get_preview?media_id=${mediaId}&media_slug=${mediaSlug}&type_media=${mediaType}`;
return get(url);
}
async function downloadMedia(
mediaId: number,
mediaSlug: string,
mediaType: string,
downloadId?: number,
tvSeriesEpisodeId?: number
): Promise<AxiosResponse<DownloadResponse>> {
const url = `/download/`;
const data = {
media_id: mediaId,
media_slug: mediaSlug,
type_media: mediaType,
download_id: downloadId,
tv_series_episode_id: tvSeriesEpisodeId,
};
return post(url, data);
}
export const downloadFilm = (mediaId: number, mediaSlug: string) =>
downloadMedia(mediaId, mediaSlug, "MOVIE");
export const downloadTvSeries = (
mediaId: number,
mediaSlug: string,
downloadId: number,
tvSeriesEpisodeId?: number
) => downloadMedia(mediaId, mediaSlug, "TV", downloadId, tvSeriesEpisodeId);
export const downloadAnimeFilm = (mediaId: number, mediaSlug: string) =>
downloadMedia(mediaId, mediaSlug, "OVA");
export const downloadAnimeSeries = (
mediaId: number,
mediaSlug: string,
downloadId: number
) => downloadMedia(mediaId, mediaSlug, "TV_ANIME", downloadId);

View File

@ -56,9 +56,4 @@ export interface Season {
export interface SeasonResponse { export interface SeasonResponse {
episodes: Season; episodes: Season;
}
export interface DownloadResponse {
error: string;
message: string;
} }

View File

@ -1,335 +1,267 @@
<script setup lang="ts"> <script setup lang="ts">
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import type {Episode, MediaItem, SeasonResponse} from "@/api/interfaces"; import type {Episode, MediaItem, Season, SeasonResponse} from "@/api/interfaces";
import { onBeforeMount, onMounted, ref } from "vue"; import { onMounted, ref } from "vue";
import { getEpisodesInfo, getPreview } from "@/api/api"; import {downloadFilm, getEpisodesInfo} from "@/api/api";
import {
alertDownload, const route = useRoute()
handleMovieDownload,
handleOVADownload, const item: MediaItem = JSON.parse(<string>route.params.item)
handleTVAnimeDownload, const imageUrl: string = <string>route.params.imageUrl
handleTvAnimeEpisodesDownload, const animeEpisodes = ref<Episode[]>([])
handleTVDownload, handleTVEpisodesDownload const tvShowEpisodes = ref<any[]>([])
} from "@/api/utils"; const loading = ref(false)
const selectingEpisodes = ref(false)
const route = useRoute()
onMounted(async () => {
const item: MediaItem = JSON.parse(<string>route.params.item) if (['MOVIE', 'OVA', 'SPECIAL'].includes(item.type)) {
const imageUrl: string = <string>route.params.imageUrl return
const animeEpisodes = ref<Episode[]>([]) } else {
const totalEpisodes = ref<number>(0) loading.value = true;
const tvShowEpisodes = ref<any[]>([]) const response = await getEpisodesInfo(item.id, item.slug, item.type)
const loading = ref(false) if (response && response.body) {
const selectingEpisodes = ref(false) loading.value = false;
const selectedEpisodes = ref<Episode[]>([]) const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
const previewItem = ref<MediaItem>(item) while (true) {
const {value, done} = await reader.read();
onBeforeMount(async () => { if (done) {
const res = await getPreview(item.id, item.slug, item.type) window.scrollTo(0, 0)
if (res && res.data) { break;
previewItem.plot = res.data.plot }
} if (item.type === 'TV_ANIME') {
}) const episodesData:Episode = JSON.parse(value.trim());
animeEpisodes.value.push(episodesData);
onMounted(async () => { } else {
loading.value = true; const episodesData:SeasonResponse = JSON.parse(value.trim());
if (['MOVIE', 'OVA', 'SPECIAL'].includes(item.type)) { for (const seasonKey in episodesData.episodes) {
loading.value = false; const season = episodesData.episodes[seasonKey];
return const episodes:Episode[] = [];
} else { for (const episodeKey in season) {
const response = await getEpisodesInfo(item.id, item.slug, item.type) const episode:Episode = season[episodeKey];
if (response && response.body) { episodes.push(episode);
loading.value = false; }
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader(); tvShowEpisodes.value.push(episodes);
while (true) { }
const {value, done} = await reader.read(); }
if (done) { }
window.scrollTo(0, 0) }
break; }
} })
if (item.type === 'TV_ANIME') {
const episodesData:Episode = JSON.parse(value.trim()); const toggleEpisodeSelection = () => {
animeEpisodes.value.push(episodesData); selectingEpisodes.value = !selectingEpisodes.value
totalEpisodes.value = episodesData.episode_total; }
} else {
const episodesData:SeasonResponse = JSON.parse(value.trim()); const downloadItems = async () => {
for (const seasonKey in episodesData.episodes) { try {
const season = episodesData.episodes[seasonKey]; if (item.type === 'MOVIE') {
const episodes:Episode[] = []; const res = await downloadFilm(item.id, item.slug, item.type)
for (const episodeKey in season) { if (res.error) {
const episode:Episode = season[episodeKey]; throw new Error(res.error + ' - ' + res.message)
episodes.push(episode); }
} alertDownload()
tvShowEpisodes.value.push(episodes); return
} }
} } catch (error) {
} alertDownload(error)
} }
} }
})
const alertDownload = (message?: any) => {
const toggleEpisodeSelection = () => { if (message) {
selectingEpisodes.value = !selectingEpisodes.value alert(message)
selectedEpisodes.value = [] return;
} }
alert('Il downlaod è iniziato, il file sarà disponibile tra qualche minuto nella cartella \'Video\' del progetto...')
const toggleEpisodeSelect = (episode: Episode, seasonNumber?: number) => { }
if (selectedEpisodes.value.includes(episode)) { </script>
selectedEpisodes.value = selectedEpisodes.value.filter(e => e !== episode)
} else { <template>
if (seasonNumber) { <div class="details-container">
episode.season_index = seasonNumber <div class="details-card">
}
selectedEpisodes.value.push(episode) <!--HEADER SECTION-->
} <div class="details-header">
} <img :src="imageUrl" :alt="item.name" class="details-image" />
<div class="details-title-container">
const downloadSelectedEpisodes = async () => { <h1 class="details-title">{{ item.name }}</h1>
try { <h3> {{ item.score }}</h3>
switch (item.type) { <div class="details-description">
case 'TV': <p v-if="item.type == 'TV_ANIME'">{{ item.plot }}</p>
await handleTVEpisodesDownload(selectedEpisodes.value, item); <p v-else-if="tvShowEpisodes.length > 0">{{ tvShowEpisodes[0][0].plot }}</p>
case 'TV_ANIME': </div>
await handleTvAnimeEpisodesDownload(selectedEpisodes.value, item); <h3 v-if="animeEpisodes.length > 0 && !loading">Numero episodi: {{ animeEpisodes[0].episode_total }}</h3>
break; <h3 v-if="tvShowEpisodes.length > 0 && !loading">Numero stagioni: {{ tvShowEpisodes.length }}</h3>
default: <hr style="opacity: 0.2"/>
throw new Error('Tipo di media non supportato'); <div class="download-section">
} <button :disabled="loading || selectingEpisodes" @click="downloadItems">Scarica {{['TV_ANIME', 'TV'].includes(item.type)? 'tutto' : ''}}</button>
toggleEpisodeSelection(); <template v-if="!loading && ['TV_ANIME', 'TV'].includes(item.type)">
} catch (error) { <button @click="toggleEpisodeSelection">{{selectingEpisodes ? 'Disattiva' : 'Attiva'}} selezione episodi</button>
alertDownload(error); <button>Download episodi</button>
} </template>
}; </div>
</div>
const downloadAllItems = async () => { </div>
try {
switch (item.type) { <!--SERIES SECTION-->
case 'TV': <div v-if="!loading && ['TV_ANIME', 'TV'].includes(item.type)" :class="item.type == 'TV_ANIME' ? 'episodes-container' : 'season-container'">
await handleTVDownload(tvShowEpisodes.value, item); <div v-if="animeEpisodes.length == 0 && tvShowEpisodes.length == 0">
case 'MOVIE': <p>Non ci sono episodi...</p>
await handleMovieDownload(item); </div>
break; <div v-else-if="item.type == 'TV_ANIME'" v-for="episode in animeEpisodes" :key="episode.id" class="episode-item">
case 'TV_ANIME': <div class="episode-title">Episodio {{ episode.number }}</div>
await handleTVAnimeDownload(totalEpisodes.value, item); </div>
break; <div v-else-if="item.type == 'TV'" v-for="(season, index) in tvShowEpisodes" class="season-item">
case 'OVA': <div class="season-title">Stagione {{ index + 1 }}</div>
case 'SPECIAL': <div class="episode-container">
await handleOVADownload(item); <div v-for="episode in season" :key="episode.id" class="episode-item">
break; <div class="episode-title">
default: Episodio {{ episode.number }} -
throw new Error('Tipo di media non supportato'); {{episode.name.slice(0, 40) + (episode.name.length > 39 ? '...' : '')}}
} </div>
} catch (error) { </div>
alertDownload(error); </div>
} </div>
}; </div>
</script>
<!--MOVIES SECTION-->
<template> <div v-else-if="!loading && ['MOVIE', 'OVA', 'SPECIAL'].includes(item.type)">
<div class="details-container"> <p>Questo è un {{item.type}}</p>
<div class="details-card"> </div>
<!--HEADER SECTION--> <!--LOADING SECTION-->
<div class="details-header"> <div v-else-if="loading">
<img :src="imageUrl" :alt="item.name" class="details-image" /> <p>Loading...</p>
<div class="details-title-container"> </div>
<h1 class="details-title">{{ item.name }}</h1> </div>
<h3> {{ item.score }}</h3> </div>
<div class="details-description"> </template>
<p>{{ item.plot }}</p>
</div> <style scoped>
<h3 v-if="animeEpisodes.length > 0 && !loading">Numero episodi: {{ totalEpisodes }}</h3> h3 {
<h3 v-if="tvShowEpisodes.length > 0 && !loading">Numero stagioni: {{ tvShowEpisodes.length }}</h3> padding-top: 10px;
<hr style="opacity: 0.2; margin-top: 10px"/> padding-bottom: 10px;
font-weight: bold;
<!--DOWNLOAD SECTION--> }
<div class="download-section"> .details-container {
<button :disabled="loading || selectingEpisodes" padding-top: 10px;
@click.prevent="downloadAllItems"> justify-content: center;
Scarica {{['TV_ANIME', 'TV'].includes(item.type)? 'tutto' : ''}} align-items: center;
</button> min-height: 100vh;
<template v-if="!loading && ['TV_ANIME', 'TV'].includes(item.type)"> width: 200%;
<button @click="toggleEpisodeSelection"> color: #fff;
{{selectingEpisodes ? 'Disattiva' : 'Attiva'}} selezione episodi }
</button>
<button :disabled="selectedEpisodes.length == 0" .details-card {
@click="downloadSelectedEpisodes"> width: 100%;
Download episodi selezionati max-width: 1200px;
</button> background-color: #232323;
</template> padding: 2rem;
</div> border-radius: 0.5rem;
</div> }
</div>
.details-header {
<!--SERIES SECTION--> display: flex;
<div v-if="!loading && ['TV_ANIME', 'TV'].includes(item.type)" align-items: flex-start;
:class="item.type == 'TV_ANIME' ? 'episodes-container' : 'season-container'"> margin-bottom: 2rem;
<div v-if="animeEpisodes.length == 0 && tvShowEpisodes.length == 0"> }
<p>Non ci sono episodi...</p>
</div> .details-image {
<div v-else-if="item.type == 'TV_ANIME'" width: 295px;
v-for="episode in animeEpisodes" margin-right: 2rem;
:key="episode.id" border-radius: 0.5rem;
class="episode-item" }
:style="{ backgroundColor: selectedEpisodes.includes(episode) ? '#42b883' : '#333' }"
@click="selectingEpisodes ? toggleEpisodeSelect(episode) : null"> @media (max-width: 1008px) {
<div class="episode-title">Episodio {{ episode.number }}</div> .details-container {
</div> width: 100%;
<div v-else-if="item.type == 'TV'" v-for="(season, index) in tvShowEpisodes" v-bind:key="season.number" class="season-item"> }
<div class="season-title">Stagione {{ index + 1 }}</div> .details-header {
<div class="episode-container"> flex-direction: column;
<div v-for="episode in season" :key="episode.id"> align-items: center;
<div class="episode-item" }
:style="{ backgroundColor: selectedEpisodes.includes(episode) ? '#42b883' : '#333' }"
@click="selectingEpisodes ? toggleEpisodeSelect(episode, index) : null"> .details-image {
<div class="episode-title"> max-width: 100%;
Episodio {{ episode.number }} - margin-right: 0;
{{episode.name.slice(0, 40) + (episode.name.length > 39 ? '...' : '')}} margin-bottom: 1rem;
</div> }
</div> }
</div>
</div> .details-title-container {
</div> flex: 1;
</div> }
<!--MOVIES SECTION--> .details-title {
<div v-else-if="!loading && ['MOVIE', 'OVA', 'SPECIAL'].includes(item.type)"> font-size: 2rem;
<p>Questo è un {{item.type}} (QUESTO TESTO E' A SCOPO DI TEST)</p> margin-bottom: 1rem;
</div> }
<!--LOADING SECTION--> .details-description {
<div v-else-if="loading"> line-height: 1.5;
<p>Loading...</p> }
</div>
</div> .episodes-container {
</div> display: grid;
</template> grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
<style scoped> }
h3 {
padding-top: 10px; .episode-item {
padding-bottom: 10px; background-color: #333;
font-weight: bold; padding: 1rem;
} border-radius: 0.5rem;
.details-container { box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding-top: 10px; transition: transform 0.3s ease;
justify-content: center; cursor: pointer;
align-items: center; }
min-height: 100vh;
width: 200%; .season-item {
color: #fff; background-color: #2a2a2a;
} padding: 1rem;
margin-top: 5px;
.details-card { margin-bottom: 5px;
width: 100%; border-radius: 0.5rem;
max-width: 1200px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
background-color: #232323; }
padding: 2rem;
border-radius: 0.5rem; .season-item div {
} display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
.details-header { gap: 1rem;
display: flex; }
align-items: flex-start;
margin-bottom: 2rem; .season-title {
} font-size: 1.5rem;
font-weight: bold;
.details-image { padding-bottom: 15px;
width: 295px; }
margin-right: 2rem;
border-radius: 0.5rem; .episode-item:hover {
} transform: translateY(-5px);
}
@media (max-width: 1008px) {
.details-container { .episode-title {
width: 100%; font-size: 1.2rem;
} font-weight: bold;
.details-header { }
flex-direction: column;
align-items: center; .download-section {
} margin-top: 1rem;
flex: fit-content;
.details-image { flex-direction: row;
max-width: 100%; button {
margin-right: 0; margin-right: 5px;
margin-bottom: 1rem; }
} }
}
@media (max-width: 768px) {
.details-title-container { .episodes-container {
flex: 1; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
} }
.details-title { .season-item div {
font-size: 2rem; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
margin-bottom: 1rem; }
} }
.details-description {
line-height: 1.5;
}
.episodes-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
.episode-item {
background-color: #333;
padding: 1rem;
border-radius: 0.5rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease;
cursor: pointer;
}
.season-item {
background-color: #2a2a2a;
padding: 1rem;
margin-top: 5px;
margin-bottom: 5px;
border-radius: 0.5rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.season-item div {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 1rem;
}
.season-title {
font-size: 1.5rem;
font-weight: bold;
padding-bottom: 15px;
}
.episode-item:hover {
transform: translateY(-5px);
}
.episode-title {
font-size: 1.2rem;
font-weight: bold;
}
.download-section {
margin-top: 1rem;
flex: fit-content;
flex-direction: row;
button {
margin-right: 5px;
}
}
@media (max-width: 768px) {
.episodes-container {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.season-item div {
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
}
</style> </style>