feat: ajout des fonctionnalités de téléchargement et de masquage des chapitres, avec mise à jour des composants et de l'API pour gérer ces actions.
This commit is contained in:
parent
8692fa14c6
commit
17f9feea7b
@@ -209,6 +209,28 @@ export const useMangaStore = defineStore('manga', {
|
||||
console.error('Erreur lors de la suppression du chapitre:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// --- Download Chapter Action ---
|
||||
async downloadChapter(chapterId) {
|
||||
try {
|
||||
await mangaRepository.downloadChapter(chapterId);
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du téléchargement du chapitre:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// --- Hide Chapter Action ---
|
||||
async hideChapter(chapterId, mangaId) {
|
||||
try {
|
||||
await mangaRepository.hideChapter(chapterId);
|
||||
// Recharger la liste des chapitres depuis l'API
|
||||
await this.loadChapters(mangaId);
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du masquage du chapitre:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -187,5 +187,60 @@ export class ApiMangaRepository {
|
||||
console.error('API Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async downloadChapter(chapterId) {
|
||||
try {
|
||||
const response = await fetch(`/api/manga/chapters/${chapterId}/download`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to download chapter');
|
||||
}
|
||||
|
||||
// Récupérer le nom du fichier depuis les headers
|
||||
const contentDisposition = response.headers.get('Content-Disposition');
|
||||
let filename = `chapter-${chapterId}.cbz`;
|
||||
|
||||
if (contentDisposition) {
|
||||
const filenameMatch = contentDisposition.match(/filename="?(.+)"?/);
|
||||
if (filenameMatch) {
|
||||
filename = filenameMatch[1];
|
||||
}
|
||||
}
|
||||
|
||||
// Créer un blob à partir de la réponse
|
||||
const blob = await response.blob();
|
||||
|
||||
// Créer un lien de téléchargement temporaire
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
||||
// Nettoyer
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('API Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async hideChapter(chapterId) {
|
||||
try {
|
||||
const response = await fetch(`/api/manga/chapters/${chapterId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to hide chapter');
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('API Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,10 +23,10 @@
|
||||
<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">
|
||||
<button @click="handleDownload" :class="downloadButtonClass" :disabled="isDownloading || !chapter.isAvailable">
|
||||
<ArrowDownTrayIcon class="h-5 w-5" />
|
||||
</button>
|
||||
<button @click="handleHide" class="text-gray-500 hover:text-green-500">
|
||||
<button @click="handleHide" :class="hideButtonClass" :disabled="isHiding">
|
||||
<TrashIcon class="h-5 w-5" />
|
||||
</button>
|
||||
</td>
|
||||
@@ -46,16 +46,36 @@ import { useMangaStore } from '../../application/store/mangaStore';
|
||||
mangaSlug: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
mangaId: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const store = useMangaStore();
|
||||
const isLoading = ref(false);
|
||||
const isDownloading = ref(false);
|
||||
const isHiding = ref(false);
|
||||
|
||||
const buttonClass = computed(() => {
|
||||
return isLoading.value ? 'text-yellow-500 cursor-wait' : 'text-gray-500 hover:text-green-500';
|
||||
});
|
||||
|
||||
const downloadButtonClass = computed(() => {
|
||||
if (isDownloading.value) {
|
||||
return 'text-yellow-500 cursor-wait';
|
||||
}
|
||||
if (!props.chapter.isAvailable) {
|
||||
return 'text-gray-300 cursor-not-allowed';
|
||||
}
|
||||
return 'text-gray-500 hover:text-green-500';
|
||||
});
|
||||
|
||||
const hideButtonClass = computed(() => {
|
||||
return isHiding.value ? 'text-yellow-500 cursor-wait' : 'text-gray-500 hover:text-green-500';
|
||||
});
|
||||
|
||||
// Surveiller les changements d'état du chapitre
|
||||
watch(
|
||||
() => props.chapter.isAvailable,
|
||||
@@ -96,12 +116,32 @@ import { useMangaStore } from '../../application/store/mangaStore';
|
||||
};
|
||||
|
||||
const handleDownload = async () => {
|
||||
// TODO: Implémenter le téléchargement du chapitre
|
||||
console.log('Téléchargement du chapitre:', props.chapter.id);
|
||||
try {
|
||||
console.log(`MangaChapter: Téléchargement du chapitre ${props.chapter.number} (ID: ${props.chapter.id})`);
|
||||
// Montrer l'indicateur de chargement
|
||||
isDownloading.value = true;
|
||||
|
||||
await store.downloadChapter(props.chapter.id);
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du téléchargement du chapitre:', error);
|
||||
} finally {
|
||||
// Arrêter l'indicateur de chargement
|
||||
isDownloading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleHide = async () => {
|
||||
// TODO: Implémenter le masquage du chapitre
|
||||
console.log('Masquage du chapitre:', props.chapter.id);
|
||||
try {
|
||||
console.log(`MangaChapter: Masquage du chapitre ${props.chapter.number} (ID: ${props.chapter.id})`);
|
||||
// Montrer l'indicateur de chargement
|
||||
isHiding.value = true;
|
||||
|
||||
await store.hideChapter(props.chapter.id, props.mangaId);
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du masquage du chapitre:', error);
|
||||
} finally {
|
||||
// Arrêter l'indicateur de chargement
|
||||
isHiding.value = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
v-for="chapter in chapters"
|
||||
:key="chapter.id"
|
||||
:chapter="chapter"
|
||||
:manga-slug="mangaSlug" />
|
||||
:manga-slug="mangaSlug"
|
||||
:manga-id="mangaId" />
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -30,6 +31,10 @@
|
||||
mangaSlug: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
mangaId: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Liste des chapitres -->
|
||||
<MangaChapterList v-show="isOpen" :chapters="volume.chapters" :manga-slug="mangaSlug" />
|
||||
<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 py bg-white rounded-b-sm">
|
||||
@@ -61,15 +61,15 @@
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
ArrowDownTrayIcon,
|
||||
BookmarkIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronUpIcon,
|
||||
MagnifyingGlassIcon
|
||||
} from '@heroicons/vue/24/outline';
|
||||
import { ref } from 'vue';
|
||||
import { useMangaStore } from '../../application/store/mangaStore';
|
||||
import MangaChapterList from './MangaChapterList.vue';
|
||||
ArrowDownTrayIcon,
|
||||
BookmarkIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronUpIcon,
|
||||
MagnifyingGlassIcon
|
||||
} from '@heroicons/vue/24/outline';
|
||||
import { ref } from 'vue';
|
||||
import { useMangaStore } from '../../application/store/mangaStore';
|
||||
import MangaChapterList from './MangaChapterList.vue';
|
||||
|
||||
const props = defineProps({
|
||||
volume: {
|
||||
@@ -80,6 +80,10 @@
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
mangaId: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
isOpen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
:key="volume.number"
|
||||
:volume="volume"
|
||||
:mangaSlug="mangaSlug"
|
||||
:mangaId="mangaId"
|
||||
:isOpen="index === 0" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -20,6 +21,10 @@
|
||||
mangaSlug: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
mangaId: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<div v-else-if="errorVolumes" class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
|
||||
{{ errorVolumes.message || 'Une erreur est survenue lors du chargement des volumes.' }}
|
||||
</div>
|
||||
<MangaVolumeList v-else :volumes="volumes" :manga-slug="currentManga.slug" />
|
||||
<MangaVolumeList v-else :volumes="volumes" :manga-slug="currentManga.slug" :manga-id="mangaId" />
|
||||
</div>
|
||||
|
||||
<!-- Modale des sources préférées -->
|
||||
|
||||
Reference in New Issue
Block a user