179 lines
7.0 KiB
Vue
179 lines
7.0 KiB
Vue
<template>
|
|
<div class="bg-white rounded-sm shadow mb-2">
|
|
<!-- En-tête du volume -->
|
|
<div class="relative bg-white p-3 sm:p-4 rounded-t-sm">
|
|
<!-- Layout mobile/desktop -->
|
|
<div class="flex items-center justify-between">
|
|
<!-- Partie gauche -->
|
|
<div class="flex items-center space-x-1 sm:space-x-4 flex-1 min-w-0">
|
|
<BookmarkIcon class="h-6 w-6 sm:h-8 sm:w-8 text-gray-500 flex-shrink-0" />
|
|
<h2 class="text-lg sm:text-xl font-semibold w-20 sm:w-28 flex-shrink-0">Vol {{ String(volume.number).padStart(2, '0') }}</h2>
|
|
<div class="flex items-center">
|
|
<span
|
|
:class="[
|
|
'px-2 py-1 text-xs sm:text-sm rounded text-center text-white min-w-[3rem] sm:min-w-[4rem]',
|
|
{
|
|
'bg-red-500': volume.downloadedChapter === 0,
|
|
'bg-yellow-500':
|
|
volume.downloadedChapter < volume.totalChapter && volume.downloadedChapter > 0,
|
|
'bg-green-500': volume.downloadedChapter === volume.totalChapter
|
|
}
|
|
]">
|
|
{{ volume.downloadedChapter }}/{{ volume.totalChapter }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Actions du volume -->
|
|
<div class="flex items-center space-x-1 sm:space-x-2">
|
|
<button
|
|
class="w-8 sm:w-10 h-8 sm:h-10 flex items-center justify-center"
|
|
@click="handleSearch"
|
|
:class="{
|
|
'text-yellow-500 cursor-wait': isSearching,
|
|
'text-gray-500 hover:text-green-500': !isSearching
|
|
}">
|
|
<MagnifyingGlassIcon class="h-5 w-5 sm:h-6 sm:w-6" />
|
|
</button>
|
|
<button
|
|
class="w-8 sm:w-10 h-8 sm:h-10 flex items-center justify-center"
|
|
@click="handleDownload"
|
|
:class="{
|
|
'text-yellow-500 cursor-wait': isDownloading,
|
|
'text-gray-500 hover:text-green-500': !isDownloading
|
|
}"
|
|
:disabled="isDownloading">
|
|
<ArrowDownTrayIcon class="h-5 w-5 sm:h-6 sm:w-6" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bouton toggle centré -->
|
|
<div class="absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2">
|
|
<button
|
|
@click="toggleVolume"
|
|
class="w-8 sm:w-10 h-8 sm:h-10 flex items-center justify-center">
|
|
<component
|
|
:is="isOpen ? ChevronUpIcon : ChevronDownIcon"
|
|
class="h-5 w-5 sm:h-6 sm:w-6 bg-gray-400 rounded-full p-1 text-white hover:bg-green-500 cursor-pointer"
|
|
/>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Liste des chapitres -->
|
|
<MangaChapterList v-show="isOpen" :chapters="volume.chapters" :manga-slug="mangaSlug" :manga-id="mangaId" />
|
|
|
|
<!-- Chevron de fermeture -->
|
|
<div v-show="isOpen" class="flex justify-center p-2 bg-white rounded-b-sm">
|
|
<button @click="toggleVolume" class="w-8 h-8 flex items-center justify-center">
|
|
<ChevronUpIcon
|
|
class="h-5 w-5 sm:h-6 sm:w-6 bg-gray-400 rounded-full p-1 text-white hover:bg-green-500 cursor-pointer"
|
|
/>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import {
|
|
ArrowDownTrayIcon,
|
|
BookmarkIcon,
|
|
ChevronDownIcon,
|
|
ChevronUpIcon,
|
|
MagnifyingGlassIcon
|
|
} from '@heroicons/vue/24/outline';
|
|
import { ref, watch } from 'vue';
|
|
import { useMangaStore } from '../../application/store/mangaStore';
|
|
import MangaChapterList from './MangaChapterList.vue';
|
|
|
|
const props = defineProps({
|
|
volume: {
|
|
type: Object,
|
|
required: true
|
|
},
|
|
mangaSlug: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
mangaId: {
|
|
type: Number,
|
|
required: true
|
|
},
|
|
isOpen: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
});
|
|
|
|
const emit = defineEmits(['toggle']);
|
|
|
|
const store = useMangaStore();
|
|
const isOpen = ref(props.isOpen);
|
|
const isSearching = ref(false);
|
|
const isDownloading = ref(false);
|
|
|
|
// Synchroniser l'état local avec la prop
|
|
watch(() => props.isOpen, (newValue) => {
|
|
isOpen.value = newValue;
|
|
});
|
|
|
|
const toggleVolume = () => {
|
|
isOpen.value = !isOpen.value;
|
|
emit('toggle', props.volume.number);
|
|
};
|
|
|
|
const handleSearch = async () => {
|
|
if (isSearching.value) return; // Éviter les clicks multiples
|
|
|
|
try {
|
|
isSearching.value = true;
|
|
console.log(
|
|
`Recherche du volume ${props.volume.number} - Lancement du scraping de ${props.volume.chapters.length} chapitres`
|
|
);
|
|
|
|
// Récupérer tous les chapitres non disponibles du volume
|
|
const chaptersToSearch = props.volume.chapters
|
|
.filter(chapter => !chapter.isAvailable)
|
|
.map(chapter => chapter.id);
|
|
|
|
if (chaptersToSearch.length === 0) {
|
|
console.log('Tous les chapitres sont déjà disponibles !');
|
|
isSearching.value = false;
|
|
return;
|
|
}
|
|
|
|
console.log(`Chapitres à scraper: ${chaptersToSearch.length}`);
|
|
|
|
// Lancer le scraping de chaque chapitre non disponible en séquentiel
|
|
for (const chapterId of chaptersToSearch) {
|
|
console.log(`Scraping du chapitre ${chapterId}...`);
|
|
await store.searchChapter(chapterId);
|
|
// Petite pause entre chaque requête pour éviter de surcharger le serveur
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
}
|
|
|
|
console.log(`Scraping des chapitres du volume ${props.volume.number} terminé`);
|
|
} catch (error) {
|
|
console.error(`Erreur lors du scraping du volume ${props.volume.number}:`, error);
|
|
} finally {
|
|
isSearching.value = false;
|
|
}
|
|
};
|
|
|
|
const handleDownload = async () => {
|
|
try {
|
|
console.log(`MangaVolume: Téléchargement du volume ${props.volume.number} (Manga ID: ${props.mangaId})`);
|
|
// Montrer l'indicateur de chargement
|
|
isDownloading.value = true;
|
|
|
|
await store.downloadVolume(props.mangaId, props.volume.number);
|
|
} catch (error) {
|
|
console.error('Erreur lors du téléchargement du volume:', error);
|
|
} finally {
|
|
// Arrêter l'indicateur de chargement
|
|
isDownloading.value = false;
|
|
}
|
|
};
|
|
</script>
|