feat: ajout des composants MangaChapter, MangaChapterList, MangaHeader, MangaVolume, MangaVolumeList et mise à jour de la page MangaDetails pour une meilleure gestion des chapitres et volumes de manga
This commit is contained in:
parent
eeb8447d7a
commit
22cf4eb186
@@ -0,0 +1,75 @@
|
|||||||
|
<template>
|
||||||
|
<tr class="border-t hover:bg-green-100">
|
||||||
|
<td class="px-4 py-2" :class="{ 'text-green-500': chapter.isDownloaded }">
|
||||||
|
{{ String(chapter.number).padStart(2, '0') }}
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-2 w-full text-left">
|
||||||
|
<router-link
|
||||||
|
v-if="chapter.isDownloaded"
|
||||||
|
:to="{
|
||||||
|
name: 'reader',
|
||||||
|
params: {
|
||||||
|
mangaSlug: mangaSlug,
|
||||||
|
chapterNumber: chapter.number,
|
||||||
|
pageNumber: 1
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
class="hover:text-green-500">
|
||||||
|
{{ chapter.title || 'Sans titre' }}
|
||||||
|
</router-link>
|
||||||
|
<span v-else>{{ chapter.title || 'Sans titre' }}</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-2 flex justify-end gap-2">
|
||||||
|
<button v-if="!chapter.isDownloaded" @click="handleSearch" class="text-gray-500 hover:text-green-500">
|
||||||
|
<MagnifyingGlassIcon class="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
<button v-else @click="handleDelete" class="text-gray-500 hover:text-green-500">
|
||||||
|
<XMarkIcon class="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
<button @click="handleDownload" class="text-gray-500 hover:text-green-500">
|
||||||
|
<ArrowDownTrayIcon class="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
<button @click="handleHide" class="text-gray-500 hover:text-green-500">
|
||||||
|
<TrashIcon class="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { MagnifyingGlassIcon, ArrowDownTrayIcon, XMarkIcon, TrashIcon } from '@heroicons/vue/24/solid';
|
||||||
|
import { useMangaStore } from '../../application/store/mangaStore';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
chapter: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
mangaSlug: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = useMangaStore();
|
||||||
|
|
||||||
|
const handleSearch = async () => {
|
||||||
|
// TODO: Implémenter la recherche de chapitre
|
||||||
|
console.log('Recherche du chapitre:', props.chapter.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
// TODO: Implémenter la suppression du chapitre
|
||||||
|
console.log('Suppression du chapitre:', props.chapter.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDownload = async () => {
|
||||||
|
// TODO: Implémenter le téléchargement du chapitre
|
||||||
|
console.log('Téléchargement du chapitre:', props.chapter.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleHide = async () => {
|
||||||
|
// TODO: Implémenter le masquage du chapitre
|
||||||
|
console.log('Masquage du chapitre:', props.chapter.id);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<template>
|
||||||
|
<div class="p-2 border-t">
|
||||||
|
<table class="min-w-full table-auto">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="px-4 py-2 text-left">#</th>
|
||||||
|
<th class="px-4 py-2 text-left">Titre</th>
|
||||||
|
<th class="px-4 py-2 text-right">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<MangaChapter
|
||||||
|
v-for="chapter in chapters"
|
||||||
|
:key="chapter.id"
|
||||||
|
:chapter="chapter"
|
||||||
|
:manga-slug="mangaSlug" />
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import MangaChapter from './MangaChapter.vue';
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
chapters: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
mangaSlug: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<template>
|
||||||
|
<div class="shadow-lg text-white">
|
||||||
|
<div class="relative h-96 bg-cover bg-center" :style="{ backgroundImage: `url('${manga.imageUrl}')` }">
|
||||||
|
<div class="absolute inset-0 bg-black opacity-50"></div>
|
||||||
|
<div class="absolute inset-0 flex flex-row justify-center p-4">
|
||||||
|
<!-- Image de couverture -->
|
||||||
|
<div class="hidden mr-12 xl:block 2xl:block">
|
||||||
|
<img :src="manga.thumbnailUrl" :alt="manga.title" class="max-w-72 max-h-72 ml-4" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Informations du manga -->
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<BookmarkIcon class="h-8 w-8 text-white" />
|
||||||
|
<h1 class="text-3xl font-bold ml-4">{{ manga.title }}</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<span class="mr-4">{{ manga.year }}</span>
|
||||||
|
<span>Chapitres: {{ manga.totalChapters }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<FolderIcon class="h-6 w-6 text-gray-400 mr-2" />
|
||||||
|
<span class="truncate">/media/mangas/{{ manga.title }} ({{ manga.year }})</span>
|
||||||
|
<span class="ml-auto bg-green-600 py-1 px-2 rounded">{{ manga.status || 'Terminé' }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<template v-if="manga.tags?.length">
|
||||||
|
<template v-for="(tag, index) in manga.tags.slice(0, 5)" :key="index">
|
||||||
|
<span class="bg-gray-700 py-1 px-2 rounded-sm mr-2">{{ tag }}</span>
|
||||||
|
</template>
|
||||||
|
<span v-if="manga.tags.length > 5" class="bg-gray-700 py-1 px-2 rounded-sm mr-2">...</span>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<div class="flex items-center mb-2">
|
||||||
|
<HeartIcon class="h-6 w-6 text-red-500 mr-2" />
|
||||||
|
<span>{{ manga.rating }}</span>
|
||||||
|
</div>
|
||||||
|
<p>{{ manga.description }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { BookmarkIcon, FolderIcon, HeartIcon } from '@heroicons/vue/24/outline';
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
manga: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
<template>
|
||||||
|
<div class="bg-white rounded-sm shadow mb-2">
|
||||||
|
<!-- En-tête du volume -->
|
||||||
|
<div class="relative flex items-center justify-between bg-white p-4 rounded-t-sm">
|
||||||
|
<!-- Partie gauche -->
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
|
<BookmarkIcon class="h-8 w-8 text-gray-500" />
|
||||||
|
<h2 class="text-xl font-semibold w-28">Volume {{ String(volume.number).padStart(2, '0') }}</h2>
|
||||||
|
<div class="flex items-center w-16">
|
||||||
|
<span
|
||||||
|
:class="[
|
||||||
|
'px-2 py-1 text-sm rounded w-full text-center text-white',
|
||||||
|
{
|
||||||
|
'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>
|
||||||
|
|
||||||
|
<!-- Bouton toggle -->
|
||||||
|
<div class="absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2">
|
||||||
|
<component
|
||||||
|
:is="isOpen ? ChevronUpIcon : ChevronDownIcon"
|
||||||
|
class="h-6 w-6 bg-gray-400 rounded-full p-1 text-white hover:bg-green-500 cursor-pointer"
|
||||||
|
@click="toggleVolume" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Actions du volume -->
|
||||||
|
<div class="flex space-x-2 text-xl text-bold">
|
||||||
|
<button class="w-8 text-center" @click="handleSearch">
|
||||||
|
<MagnifyingGlassIcon class="h-6 w-6 text-gray-500 hover:text-green-500" />
|
||||||
|
</button>
|
||||||
|
<button class="w-8 text-center" @click="handleDownload">
|
||||||
|
<ArrowDownTrayIcon class="h-6 w-6 text-gray-500 hover:text-green-500" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Liste des chapitres -->
|
||||||
|
<MangaChapterList v-show="isOpen" :chapters="volume.chapters" :manga-slug="mangaSlug" />
|
||||||
|
|
||||||
|
<!-- Chevron de fermeture -->
|
||||||
|
<div v-show="isOpen" class="flex justify-center p-2 py bg-white rounded-b-sm">
|
||||||
|
<ChevronUpIcon
|
||||||
|
class="h-6 w-6 bg-gray-400 rounded-full p-1 text-white hover:bg-green-500 cursor-pointer"
|
||||||
|
@click="toggleVolume" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import {
|
||||||
|
BookmarkIcon,
|
||||||
|
ChevronUpIcon,
|
||||||
|
ChevronDownIcon,
|
||||||
|
MagnifyingGlassIcon,
|
||||||
|
ArrowDownTrayIcon
|
||||||
|
} from '@heroicons/vue/24/outline';
|
||||||
|
import MangaChapterList from './MangaChapterList.vue';
|
||||||
|
import { useMangaStore } from '../../application/store/mangaStore';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
volume: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
mangaSlug: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
isOpen: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = useMangaStore();
|
||||||
|
const isOpen = ref(props.isOpen);
|
||||||
|
|
||||||
|
const toggleVolume = () => {
|
||||||
|
isOpen.value = !isOpen.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = async () => {
|
||||||
|
// TODO: Implémenter la recherche du volume
|
||||||
|
console.log('Recherche du volume:', props.volume.number);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDownload = async () => {
|
||||||
|
// TODO: Implémenter le téléchargement du volume
|
||||||
|
console.log('Téléchargement du volume:', props.volume.number);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<template>
|
||||||
|
<div class="p-4">
|
||||||
|
<MangaVolume
|
||||||
|
v-for="(volume, index) in volumes"
|
||||||
|
:key="volume.number"
|
||||||
|
:volume="volume"
|
||||||
|
:mangaSlug="mangaSlug"
|
||||||
|
:isOpen="index === 0" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import MangaVolume from './MangaVolume.vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
volumes: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
mangaSlug: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- États de chargement et d'erreur -->
|
|
||||||
<div v-if="loading" class="flex justify-center items-center h-64">
|
<div v-if="loading" class="flex justify-center items-center h-64">
|
||||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-primary"></div>
|
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-primary"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -9,200 +8,41 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="currentManga" class="relative">
|
<div v-else-if="currentManga" class="relative">
|
||||||
<!-- Bannière avec image de fond -->
|
<MangaHeader :manga="currentManga" />
|
||||||
<div class="shadow-lg text-white">
|
<MangaVolumeList :volumes="volumes" :manga-slug="currentManga.slug" />
|
||||||
<div class="relative h-96 bg-cover bg-center" :style="{ backgroundImage: `url('${currentManga.imageUrl}')` }">
|
|
||||||
<div class="absolute inset-0 bg-black opacity-50"></div>
|
|
||||||
<div class="absolute inset-0 flex flex-row justify-center p-4">
|
|
||||||
<!-- Image de couverture -->
|
|
||||||
<div class="hidden mr-12 xl:block 2xl:block">
|
|
||||||
<img :src="currentManga.thumbnailUrl" :alt="currentManga.title" class="max-w-72 max-h-72 ml-4">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Informations du manga -->
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<div class="flex items-center mb-4">
|
|
||||||
<BookmarkIcon class="h-8 w-8 text-white" />
|
|
||||||
<h1 class="text-3xl font-bold ml-4">{{ currentManga.title }}</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center mb-4">
|
|
||||||
<span class="mr-4">{{ currentManga.year }}</span>
|
|
||||||
<span>Chapitres: {{ currentManga.totalChapters }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center mb-4">
|
|
||||||
<FolderIcon class="h-6 w-6 text-gray-400 mr-2" />
|
|
||||||
<span class="truncate">/media/mangas/{{ currentManga.title }} ({{ currentManga.year }})</span>
|
|
||||||
<span class="ml-auto bg-green-600 py-1 px-2 rounded">{{ currentManga.status || 'Terminé' }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center mb-4">
|
|
||||||
<template v-if="currentManga.tags?.length">
|
|
||||||
<template v-for="(tag, index) in currentManga.tags.slice(0, 5)" :key="index">
|
|
||||||
<span class="bg-gray-700 py-1 px-2 rounded-sm mr-2">{{ tag }}</span>
|
|
||||||
</template>
|
|
||||||
<span v-if="currentManga.tags.length > 5" class="bg-gray-700 py-1 px-2 rounded-sm mr-2">...</span>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-4">
|
|
||||||
<div class="flex items-center mb-2">
|
|
||||||
<HeartIcon class="h-6 w-6 text-red-500 mr-2" />
|
|
||||||
<span>{{ currentManga.rating }}</span>
|
|
||||||
</div>
|
|
||||||
<p>{{ currentManga.description }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Liste des volumes et chapitres -->
|
|
||||||
<div class="p-4">
|
|
||||||
<div v-for="volume in volumes" :key="volume.number" class="mb-4">
|
|
||||||
<div class="bg-white rounded-sm shadow">
|
|
||||||
<!-- En-tête du volume -->
|
|
||||||
<div class="relative flex items-center justify-between bg-white p-4 rounded-t-sm">
|
|
||||||
<!-- Partie gauche -->
|
|
||||||
<div class="flex items-center space-x-4">
|
|
||||||
<BookmarkIcon class="h-8 w-8 text-gray-500" />
|
|
||||||
<h2 class="text-xl font-semibold w-28">Volume {{ String(volume.number).padStart(2, '0') }}</h2>
|
|
||||||
<div class="flex items-center w-16">
|
|
||||||
<span :class="[
|
|
||||||
'px-2 py-1 text-sm rounded w-full text-center text-white',
|
|
||||||
{
|
|
||||||
'bg-red-500': volume.progress === '0/0',
|
|
||||||
'bg-yellow-500': volume.progress !== volume.chapters.length + '/0',
|
|
||||||
'bg-green-500': volume.progress === volume.chapters.length + '/0'
|
|
||||||
}
|
|
||||||
]">
|
|
||||||
{{ volume.progress }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Bouton toggle -->
|
|
||||||
<div class="absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2">
|
|
||||||
<component
|
|
||||||
:is="isVolumeOpen(volume.number) ? ChevronUpIcon : ChevronDownIcon"
|
|
||||||
class="h-6 w-6 bg-gray-400 rounded-full p-1 text-white hover:bg-green-500 cursor-pointer"
|
|
||||||
@click="toggleVolume(volume)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Actions du volume -->
|
|
||||||
<div class="flex space-x-2 text-xl text-bold">
|
|
||||||
<button class="w-8 text-center" @click="searchVolume(volume)">
|
|
||||||
<MagnifyingGlassIcon class="h-6 w-6 text-gray-500 hover:text-green-500" />
|
|
||||||
</button>
|
|
||||||
<button class="w-8 text-center" @click="downloadVolume(volume)">
|
|
||||||
<ArrowDownTrayIcon class="h-6 w-6 text-gray-500 hover:text-green-500" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Liste des chapitres -->
|
|
||||||
<div v-show="isVolumeOpen(volume.number)" class="p-4 border-t">
|
|
||||||
<table class="min-w-full table-auto">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="px-4 py-2 text-left">#</th>
|
|
||||||
<th class="px-4 py-2 text-left">Titre</th>
|
|
||||||
<th class="px-4 py-2 text-right">Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="chapter in volume.chapters"
|
|
||||||
:key="chapter.id"
|
|
||||||
class="border-t hover:bg-green-100">
|
|
||||||
<td class="px-4 py-2" :class="{ 'text-green-500': chapter.isDownloaded }">
|
|
||||||
{{ String(chapter.number).padStart(2, '0') }}
|
|
||||||
</td>
|
|
||||||
<td class="px-4 py-2 w-full text-left">
|
|
||||||
<router-link
|
|
||||||
v-if="chapter.isDownloaded"
|
|
||||||
:to="{ name: 'reader', params: { mangaSlug: currentManga.slug, chapterNumber: chapter.number, pageNumber: 1 }}"
|
|
||||||
class="hover:text-green-500">
|
|
||||||
{{ chapter.title || 'Sans titre' }}
|
|
||||||
</router-link>
|
|
||||||
<span v-else>{{ chapter.title || 'Sans titre' }}</span>
|
|
||||||
</td>
|
|
||||||
<td class="px-4 py-2 flex justify-end gap-2">
|
|
||||||
<button v-if="!chapter.isDownloaded"
|
|
||||||
@click="searchChapter(chapter)"
|
|
||||||
class="text-gray-500 hover:text-green-500">
|
|
||||||
<MagnifyingGlassIcon class="h-5 w-5" />
|
|
||||||
</button>
|
|
||||||
<button v-else
|
|
||||||
@click="deleteChapter(chapter)"
|
|
||||||
class="text-gray-500 hover:text-green-500">
|
|
||||||
<XMarkIcon class="h-5 w-5" />
|
|
||||||
</button>
|
|
||||||
<button @click="downloadChapter(chapter)"
|
|
||||||
class="text-gray-500 hover:text-green-500">
|
|
||||||
<ArrowDownTrayIcon class="h-5 w-5" />
|
|
||||||
</button>
|
|
||||||
<button @click="hideChapter(chapter)"
|
|
||||||
class="text-gray-500 hover:text-green-500">
|
|
||||||
<TrashIcon class="h-5 w-5" />
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onUnmounted, computed, watch } from 'vue';
|
import { computed, onMounted, onUnmounted, watch } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useMangaStore } from '../../application/store/mangaStore';
|
import { useMangaStore } from '../../application/store/mangaStore';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import {
|
import MangaHeader from '../components/MangaHeader.vue';
|
||||||
BookmarkIcon,
|
import MangaVolumeList from '../components/MangaVolumeList.vue';
|
||||||
FolderIcon,
|
|
||||||
HeartIcon,
|
|
||||||
ChevronUpIcon,
|
|
||||||
ChevronDownIcon,
|
|
||||||
MagnifyingGlassIcon,
|
|
||||||
ArrowDownTrayIcon,
|
|
||||||
XMarkIcon,
|
|
||||||
TrashIcon
|
|
||||||
} from '@heroicons/vue/24/outline';
|
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
|
||||||
const store = useMangaStore();
|
const store = useMangaStore();
|
||||||
const { currentManga, chapters, loading, error } = storeToRefs(store);
|
const { currentManga, chapters, loading, error } = storeToRefs(store);
|
||||||
|
|
||||||
// Map réactif pour stocker l'état d'ouverture des volumes
|
|
||||||
const openVolumes = ref(new Map());
|
|
||||||
|
|
||||||
// Organise les chapitres par volumes
|
|
||||||
const volumes = computed(() => {
|
const volumes = computed(() => {
|
||||||
if (!chapters.value) return [];
|
if (!chapters.value) return [];
|
||||||
|
|
||||||
const chaptersArray = Array.isArray(chapters.value) ? chapters.value :
|
const chaptersArray = Array.isArray(chapters.value)
|
||||||
(chapters.value.items ? chapters.value.items : []);
|
? chapters.value
|
||||||
|
: chapters.value.items
|
||||||
|
? chapters.value.items
|
||||||
|
: [];
|
||||||
|
|
||||||
const volumeMap = new Map();
|
const volumeMap = new Map();
|
||||||
|
|
||||||
chaptersArray.forEach(chapter => {
|
chaptersArray.forEach(chapter => {
|
||||||
const volumeNumber = chapter.volume || 'Unknown';
|
const volumeNumber = chapter.volume || 'Unknown';
|
||||||
if (!volumeMap.has(volumeNumber)) {
|
if (!volumeMap.has(volumeNumber)) {
|
||||||
// Initialiser l'état d'ouverture si ce n'est pas déjà fait
|
|
||||||
if (!openVolumes.value.has(volumeNumber)) {
|
|
||||||
openVolumes.value.set(volumeNumber, volumeNumber === chaptersArray[0]?.volume);
|
|
||||||
}
|
|
||||||
|
|
||||||
volumeMap.set(volumeNumber, {
|
volumeMap.set(volumeNumber, {
|
||||||
number: volumeNumber,
|
number: volumeNumber,
|
||||||
progress: '0/0',
|
downloadedChapter: 0,
|
||||||
|
totalChapter: 0,
|
||||||
chapters: []
|
chapters: []
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -212,69 +52,28 @@ const volumes = computed(() => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Calcul du progrès pour chaque volume
|
|
||||||
for (const volume of volumeMap.values()) {
|
for (const volume of volumeMap.values()) {
|
||||||
const downloaded = volume.chapters.filter(c => c.isDownloaded).length;
|
volume.downloadedChapter = volume.chapters.filter(c => c.isDownloaded).length;
|
||||||
const total = volume.chapters.length;
|
volume.totalChapter = volume.chapters.length;
|
||||||
volume.progress = `${downloaded}/${total}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Array.from(volumeMap.values()).sort((a, b) => b.number - a.number);
|
return Array.from(volumeMap.values()).sort((a, b) => b.number - a.number);
|
||||||
});
|
});
|
||||||
|
|
||||||
const isVolumeOpen = (volumeNumber) => {
|
|
||||||
return openVolumes.value.get(volumeNumber) || false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleVolume = (volume) => {
|
|
||||||
openVolumes.value.set(volume.number, !openVolumes.value.get(volume.number));
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
const mangaId = route.params.id;
|
const mangaId = route.params.id;
|
||||||
await Promise.all([
|
await Promise.all([store.fetchMangaDetails(mangaId), store.fetchMangaChapters(mangaId)]);
|
||||||
store.fetchMangaDetails(mangaId),
|
|
||||||
store.fetchMangaChapters(mangaId)
|
|
||||||
]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ajouter le watcher sur l'ID de la route
|
// Ajouter le watcher sur l'ID de la route
|
||||||
watch(() => route.params.id, (newId, oldId) => {
|
watch(
|
||||||
|
() => route.params.id,
|
||||||
|
(newId, oldId) => {
|
||||||
if (newId !== oldId) {
|
if (newId !== oldId) {
|
||||||
loadData();
|
loadData();
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
// Actions sur les chapitres et volumes
|
|
||||||
const searchChapter = async (chapter) => {
|
|
||||||
// TODO: Implémenter la recherche de chapitre
|
|
||||||
console.log('Recherche du chapitre:', chapter.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const downloadChapter = async (chapter) => {
|
|
||||||
// TODO: Implémenter le téléchargement du chapitre
|
|
||||||
console.log('Téléchargement du chapitre:', chapter.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteChapter = async (chapter) => {
|
|
||||||
// TODO: Implémenter la suppression du chapitre
|
|
||||||
console.log('Suppression du chapitre:', chapter.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const hideChapter = async (chapter) => {
|
|
||||||
// TODO: Implémenter le masquage du chapitre
|
|
||||||
console.log('Masquage du chapitre:', chapter.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const searchVolume = async (volume) => {
|
|
||||||
// TODO: Implémenter la recherche du volume
|
|
||||||
console.log('Recherche du volume:', volume.number);
|
|
||||||
};
|
|
||||||
|
|
||||||
const downloadVolume = async (volume) => {
|
|
||||||
// TODO: Implémenter le téléchargement du volume
|
|
||||||
console.log('Téléchargement du volume:', volume.number);
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadData();
|
loadData();
|
||||||
|
|||||||
Reference in New Issue
Block a user