From 17f9feea7be3f7774d9d0747389b013914ed656d Mon Sep 17 00:00:00 2001 From: "ext.jeremy.guillot@maxicoffee.domains" Date: Sun, 29 Jun 2025 23:25:33 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20ajout=20des=20fonctionnalit=C3=A9s=20de?= =?UTF-8?q?=20t=C3=A9l=C3=A9chargement=20et=20de=20masquage=20des=20chapit?= =?UTF-8?q?res,=20avec=20mise=20=C3=A0=20jour=20des=20composants=20et=20de?= =?UTF-8?q?=20l'API=20pour=20g=C3=A9rer=20ces=20actions.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../manga/application/store/mangaStore.js | 22 ++++++++ .../infrastructure/api/apiMangaRepository.js | 55 +++++++++++++++++++ .../presentation/components/MangaChapter.vue | 52 ++++++++++++++++-- .../components/MangaChapterList.vue | 7 ++- .../presentation/components/MangaVolume.vue | 24 ++++---- .../components/MangaVolumeList.vue | 5 ++ .../manga/presentation/pages/MangaDetails.vue | 2 +- .../CommandHandler/DeleteChapterHandler.php | 21 +++---- 8 files changed, 160 insertions(+), 28 deletions(-) diff --git a/assets/vue/app/domain/manga/application/store/mangaStore.js b/assets/vue/app/domain/manga/application/store/mangaStore.js index 48cdc88..b8c4926 100644 --- a/assets/vue/app/domain/manga/application/store/mangaStore.js +++ b/assets/vue/app/domain/manga/application/store/mangaStore.js @@ -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; + } } } }); diff --git a/assets/vue/app/domain/manga/infrastructure/api/apiMangaRepository.js b/assets/vue/app/domain/manga/infrastructure/api/apiMangaRepository.js index d9272fc..c86c67c 100644 --- a/assets/vue/app/domain/manga/infrastructure/api/apiMangaRepository.js +++ b/assets/vue/app/domain/manga/infrastructure/api/apiMangaRepository.js @@ -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; + } } } diff --git a/assets/vue/app/domain/manga/presentation/components/MangaChapter.vue b/assets/vue/app/domain/manga/presentation/components/MangaChapter.vue index 7be15d8..aab28fd 100644 --- a/assets/vue/app/domain/manga/presentation/components/MangaChapter.vue +++ b/assets/vue/app/domain/manga/presentation/components/MangaChapter.vue @@ -23,10 +23,10 @@ - - @@ -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; + } }; diff --git a/assets/vue/app/domain/manga/presentation/components/MangaChapterList.vue b/assets/vue/app/domain/manga/presentation/components/MangaChapterList.vue index 6ce92a4..2248cbe 100644 --- a/assets/vue/app/domain/manga/presentation/components/MangaChapterList.vue +++ b/assets/vue/app/domain/manga/presentation/components/MangaChapterList.vue @@ -13,7 +13,8 @@ v-for="chapter in chapters" :key="chapter.id" :chapter="chapter" - :manga-slug="mangaSlug" /> + :manga-slug="mangaSlug" + :manga-id="mangaId" /> @@ -30,6 +31,10 @@ mangaSlug: { type: String, required: true + }, + mangaId: { + type: Number, + required: true } }); diff --git a/assets/vue/app/domain/manga/presentation/components/MangaVolume.vue b/assets/vue/app/domain/manga/presentation/components/MangaVolume.vue index 4934fa2..abc1da6 100644 --- a/assets/vue/app/domain/manga/presentation/components/MangaVolume.vue +++ b/assets/vue/app/domain/manga/presentation/components/MangaVolume.vue @@ -48,7 +48,7 @@ - +
@@ -61,15 +61,15 @@ diff --git a/assets/vue/app/domain/manga/presentation/pages/MangaDetails.vue b/assets/vue/app/domain/manga/presentation/pages/MangaDetails.vue index 5a4a41b..ae83d46 100644 --- a/assets/vue/app/domain/manga/presentation/pages/MangaDetails.vue +++ b/assets/vue/app/domain/manga/presentation/pages/MangaDetails.vue @@ -22,7 +22,7 @@
{{ errorVolumes.message || 'Une erreur est survenue lors du chargement des volumes.' }}
- +
diff --git a/src/Domain/Manga/Application/CommandHandler/DeleteChapterHandler.php b/src/Domain/Manga/Application/CommandHandler/DeleteChapterHandler.php index d4ad5c7..4a96ea7 100644 --- a/src/Domain/Manga/Application/CommandHandler/DeleteChapterHandler.php +++ b/src/Domain/Manga/Application/CommandHandler/DeleteChapterHandler.php @@ -5,6 +5,8 @@ namespace App\Domain\Manga\Application\CommandHandler; use App\Domain\Manga\Application\Command\DeleteChapter; use App\Domain\Manga\Domain\Contract\Repository\ChapterRepositoryInterface; use App\Domain\Manga\Domain\Exception\ChapterNotFoundException; +use App\Domain\Manga\Domain\Model\Chapter; +use App\Domain\Manga\Domain\Model\ValueObject\ChapterId; use App\Domain\Shared\Domain\Contract\CommandHandlerInterface; use App\Domain\Shared\Domain\Contract\CommandInterface; @@ -24,16 +26,15 @@ readonly class DeleteChapterHandler implements CommandHandlerInterface throw new ChapterNotFoundException($command->chapterId); } - // Soft delete by setting isVisible to false - $updatedChapter = new \App\Domain\Manga\Domain\Model\Chapter( - new \App\Domain\Manga\Domain\Model\ValueObject\ChapterId($chapter->getId()), - $chapter->getMangaId(), - $chapter->getNumber(), - $chapter->getTitle(), - $chapter->getVolume(), - false, // isVisible = false (soft delete) - $chapter->isAvailable(), - $chapter->getCreatedAt() + $updatedChapter = new Chapter( + id: new ChapterId($chapter->getId()), + mangaId: $chapter->getMangaId(), + number: $chapter->getNumber(), + title: $chapter->getTitle(), + volume: $chapter->getVolume(), + isVisible: false, + cbzPath: $chapter->getCbzPath(), + createdAt: $chapter->getCreatedAt() ); $this->chapterRepository->save($updatedChapter);