diff --git a/assets/controllers/addmanga_controller.js b/assets/controllers/addmanga_controller.js new file mode 100644 index 0000000..b78e347 --- /dev/null +++ b/assets/controllers/addmanga_controller.js @@ -0,0 +1,14 @@ +// assets/controllers/addmanga_controller.js +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static values = { + index: Number + } + + openModal(event) { + event.preventDefault() + const openEvent = new CustomEvent(`openAddMangaModal${this.indexValue}`) + document.dispatchEvent(openEvent) + } +} diff --git a/assets/controllers/bootstrap-modal_controller.js b/assets/controllers/bootstrap-modal_controller.js deleted file mode 100644 index 8967945..0000000 --- a/assets/controllers/bootstrap-modal_controller.js +++ /dev/null @@ -1,21 +0,0 @@ -import { Controller } from '@hotwired/stimulus'; -import { Modal } from 'bootstrap'; - -/** - * Allows you to dispatch a "modal:close" JavaScript event to close it. - * - * This is useful inside a LiveComponent, where you can emit a browser event - * to open or close the modal. - * - * See templates/components/BootstrapModal.html.twig to see how this is - * attached to Bootstrap modal. - */ -/* stimulusFetch: 'lazy' */ -export default class extends Controller { - modal = null; - - connect() { - this.modal = Modal.getOrCreateInstance(this.element); - document.addEventListener('modal:close', () => this.modal.hide()); - } -} diff --git a/assets/controllers/loading_button_controller.js b/assets/controllers/loading_button_controller.js new file mode 100644 index 0000000..a2a3255 --- /dev/null +++ b/assets/controllers/loading_button_controller.js @@ -0,0 +1,21 @@ +// assets/controllers/loading_button_controller.js +import {Controller} from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["text", "loader"]; + static values = {form: String}; + + startLoading(event) { + event.preventDefault(); + this.textTarget.classList.add("hidden"); + this.loaderTarget.classList.remove("hidden"); + this.element.disabled = true; + + if (this.hasFormValue) { + const form = document.getElementById(this.formValue); + if (form) { + form.submit(); + } + } + } +} diff --git a/assets/controllers/modal_controller.js b/assets/controllers/modal_controller.js new file mode 100644 index 0000000..c8c1dac --- /dev/null +++ b/assets/controllers/modal_controller.js @@ -0,0 +1,37 @@ +// assets/controllers/modal_controller.js +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["modal"] + static values = { + openTrigger: String, + closeTrigger: String + } + + connect() { + if (this.hasOpenTriggerValue) { + document.addEventListener(this.openTriggerValue, this.open.bind(this)) + } + if (this.hasCloseTriggerValue) { + document.addEventListener(this.closeTriggerValue, this.close.bind(this)) + } + } + + disconnect() { + if (this.hasOpenTriggerValue) { + document.removeEventListener(this.openTriggerValue, this.open.bind(this)) + } + if (this.hasCloseTriggerValue) { + document.removeEventListener(this.closeTriggerValue, this.close.bind(this)) + } + } + + open() { + console.log("Opening modal...") + this.modalTarget.classList.remove('hidden') + } + + close() { + this.modalTarget.classList.add('hidden') + } +} diff --git a/assets/controllers/toolbar_controller.js b/assets/controllers/toolbar_controller.js index 00f5526..a4afda0 100644 --- a/assets/controllers/toolbar_controller.js +++ b/assets/controllers/toolbar_controller.js @@ -26,7 +26,8 @@ export default class extends Controller { } editManga() { - console.log("Editing manga..."); + const event = new CustomEvent('openEditModal'); + document.dispatchEvent(event); } deleteMangas() { @@ -34,13 +35,17 @@ export default class extends Controller { } deleteManga() { - console.log("Deleting manga..."); + const event = new CustomEvent('openDeleteModal'); + document.dispatchEvent(event); } showOptions() { console.log("Showing options..."); } + expandAll() { + console.log("Expanding all..."); + } changeView(event) { event.preventDefault(); const viewOption = event.currentTarget.dataset.view; diff --git a/src/Controller/MangaController.php b/src/Controller/MangaController.php index 5e29666..e62a7aa 100644 --- a/src/Controller/MangaController.php +++ b/src/Controller/MangaController.php @@ -5,39 +5,35 @@ namespace App\Controller; use App\Entity\Chapter; use App\Entity\Manga; use App\Manager\Toolbar\Factory\ToolbarFactory; -use App\Manager\ToolbarManager; use App\Message\DownloadChapter; use App\Repository\ChapterRepository; use App\Repository\MangaRepository; use App\Service\CbzService; -use App\Service\MangaExportService; -use App\Service\LelScansProviderService; -use App\Service\MangaScraperServiceOld; -use App\Service\MangaUpdatesMetadataProvider; +use App\Service\MangadexProvider; +use Doctrine\DBAL\Exception\UniqueConstraintViolationException; +use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\NonUniqueResultException; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\ResponseHeaderBag; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Routing\Attribute\Route; -use Symfony\Component\String\Slugger\AsciiSlugger; class MangaController extends AbstractController { public function __construct( - private readonly MangaScraperServiceOld $mangaScraperService, - private readonly MangaExportService $mangaExportService, - private readonly LelScansProviderService $mangaProviderService, private readonly MangaRepository $mangaRepository, private readonly ChapterRepository $chapterRepository, - private readonly MangaUpdatesMetadataProvider $mangaUpdatesDbProvider, private readonly MessageBusInterface $bus, private readonly CbzService $cbzService, - private readonly ToolbarFactory $toolbarFactory + private readonly ToolbarFactory $toolbarFactory, + private MangadexProvider $mangadexProvider, + private EntityManagerInterface $entityManager ) { } @@ -146,21 +142,70 @@ class MangaController extends AbstractController ]); } - #[Route('/manga/new/{query}', name: 'app_manga_new')] - public function addNew(string $query = ''): Response + #[Route('/manga/search/{query}', name: 'app_manga_search')] + public function search(string $query = ''): Response { return $this->render('manga/add_new.html.twig', [ 'query' => $query, ]); } - public function search(string $title): Response - { - $mangas = $this->mangaUpdatesDbProvider->search($title); - return $this->render('manga/add_new.html.twig', [ - 'mangas' => $mangas, - ]); + #[Route('/addManga', name: 'app_manga_add')] + public function addManga(Request $request): Response + { + $manga = new Manga(); + $manga->setTitle($request->request->get('title')) + ->setSlug($request->request->get('slug')) + ->setDescription($request->request->get('description')) + ->setImageUrl($request->request->get('imageUrl')) + ->setStatus($request->request->get('status')) + ->setGenres(explode(',', $request->request->get('genres'))) + ->setAuthor($request->request->get('author')) + ->setPublicationYear($request->request->get('publicationYear')) + ->setRating($request->request->get('rating')) + ->setExternalId($request->request->get('externalId')); + + $mangaFeed = $this->mangadexProvider->getFeed($manga); + $mangaAggregate = $this->mangadexProvider->getMangaAggregate($manga); + + $allChapters = array_merge($mangaFeed, $mangaAggregate); + + $mergedChapters = []; + foreach ($allChapters as $chapter) { + $number = $chapter->getNumber(); + + if (isset($mergedChapters[$number])) { + $existingChapter = $mergedChapters[$number]; + + if (!empty($chapter->getExternalId()) || + (empty($existingChapter->getExternalId()) && !strpos($chapter->getTitle(), 'Chapter ') == 0)) { + $mergedChapters[$number] = $chapter; + } + } else { + $mergedChapters[$number] = $chapter; + } + } + + foreach($mergedChapters as $chapter) { + $manga->addChapter($chapter); + } + + try { + foreach ($manga->getChapters() as $chapter) { + $this->entityManager->persist($chapter); + } + + $this->entityManager->persist($manga); + $this->entityManager->flush(); + } catch (\Exception $e) { + if ($e instanceof UniqueConstraintViolationException) { + return $this->redirectToRoute('app_manga_show', ['mangaSlug' => $manga->getSlug()]); + } + throw $e; + } + + return $this->redirectToRoute('app_manga_show', ['mangaSlug' => $manga->getSlug()]); } #[Route('/addChapter/{id}', name: 'add_chapter')] @@ -226,108 +271,4 @@ class MangaController extends AbstractController return true; } - - #[Route('/scrape', name: 'manga_scrape', methods: 'POST')] - public function scrapeByMangaAndChapter(Request $request): Response - { - $mangaSlug = $request->request->get('mangaSlug'); - $chapterNumber = $request->request->get('chapterNumber'); - - $response = $this->scrapeChapter($mangaSlug, $chapterNumber); - - $manga = $this->mangaRepository->findOneBy(['slug' => $mangaSlug]); - - $availableChapters = $this->mangaProviderService->getChapterList($mangaSlug); - - return $this->render('manga/show_chapters.html.twig', [ - 'controller_name' => 'MangaController', - 'manga' => $manga, - 'availableChapters' => $availableChapters, - ]); - } - - #[Route('/scrapeFrom', name: 'manga_scrape_from_chapter', methods: 'POST')] - public function scrapeByMangaFromChapter(Request $request): Response - { - $mangaSlug = $request->request->get('mangaSlug'); - $chapterNumber = $request->request->get('chapterNumber'); - - do { - $response = $this->scrapeChapter($mangaSlug, $chapterNumber); - $chapterNumber++; - } while ($response !== false); - - $availableChapters = $this->mangaProviderService->getChapterList($mangaSlug); - - return $this->redirectToRoute('app_manga_show', ['mangaSlug' => $mangaSlug, 'availableChapters' => $availableChapters]); - } - - #[Route('/manga/exportFrom/{mangaSlug}/{chapterNumber}', name: 'manga_export')] - public function exportMangaCbz(string $mangaSlug, float $chapterNumber) - { - $response = $this->exportCbz($this->slugToTitle($mangaSlug), $chapterNumber); - - dd($response); - } - - #[Route('/getList', name: 'get_manga_list')] - public function getMangaList() - { - $list = $this->mangaProviderService->getMangaList(); - - } - - private function scrapeChapter(string $mangaSlug, float $chapterNumber): array|bool - { - $url = 'https://lelscans.net/scan-' . $mangaSlug . '/' . $chapterNumber; - - $manga = $this->mangaRepository->findOneBy(['slug' => $mangaSlug]); - - if (!is_null($manga)) { - $scrapedManga = $this->mangaScraperService->scrapeMangaChapter($url, $manga->getTitle(), $chapterNumber); - } else { - $title = $this->slugToTitle($mangaSlug); - $manga = new Manga(); - $manga->setTitle($title); - $manga->setSlug($mangaSlug); - $this->mangaRepository->save($manga); - $scrapedManga = $this->mangaScraperService->scrapeMangaChapter($url, $title, $chapterNumber); - } - - return $scrapedManga; - } - - private function exportCbz(string $mangaSlug, float $chapterNumber): array - { - $exported = []; - do { - $response = $this->mangaExportService->exportMangaChapter($mangaSlug, $chapterNumber); - - if ($response === 'already_exported') { - $exported[] = $mangaSlug . ' - ' . $chapterNumber . ' ' . $response; - } elseif ($response === true) { - $exported[] = $mangaSlug . ' - ' . $chapterNumber . ' exported'; - } else { - $exported[] = $mangaSlug . ' - ' . $chapterNumber . ' something went wrong'; - } - - $chapterNumber++; - } while ($response !== false); - - return $exported; - } - - private function slugToTitle(string $slug): string - { - $slugger = new AsciiSlugger(); - $title = $slugger->slug($slug)->replace('-', ' ')->title(true)->toString(); - - return $title; - } - - private function titleToSlug(string $title): string - { - $slugger = new AsciiSlugger(); - return $slugger->slug($title)->lower()->toString(); - } } diff --git a/src/Manager/Toolbar/Definition/ChapterListToolbar.php b/src/Manager/Toolbar/Definition/ChapterListToolbar.php index a075e7d..8133e4e 100644 --- a/src/Manager/Toolbar/Definition/ChapterListToolbar.php +++ b/src/Manager/Toolbar/Definition/ChapterListToolbar.php @@ -9,13 +9,18 @@ class ChapterListToolbar extends Toolbar { public function __construct() { - $this->addToLeftGroup(new ToolbarButton('keyboard', 'Rename chapters', 'renameChapters')) + $this + ->addToLeftGroup(new ToolbarButton('arrows-rotate', 'Refresh metadata', 'refreshMetadata')) + ->addToLeftGroup(new ToolbarDivider()) + ->addToLeftGroup(new ToolbarButton('keyboard', 'Rename chapters', 'renameChapters')) ->addToLeftGroup(new ToolbarButton('file-zipper', 'Manage cbz', 'manageCbz')) ->addToLeftGroup(new ToolbarButton('history', 'History', 'history')) - ->addToLeftGroup(new ToolbarDivider()) - ->addToLeftGroup(new ToolbarButton('bookmark', 'Monitoring', 'monitoring')) - ->addToLeftGroup(new ToolbarButton('wrench', 'Edit', 'edit')) - ->addToLeftGroup(new ToolbarButton('trash-can', 'Delete', 'delete')) + + + ->addToRightGroup(new ToolbarButton('bookmark', 'Monitoring', 'monitoring')) + ->addToRightGroup(new ToolbarButton('wrench', 'Edit', 'editManga')) + ->addToRightGroup(new ToolbarButton('trash-can', 'Delete', 'deleteManga')) + ->addToRightGroup(new ToolbarDivider()) ->addToRightGroup(new ToolbarButton('chevron-down', 'Expand all', 'expandAll')); } } diff --git a/src/Manager/Toolbar/Definition/MangaListToolbar.php b/src/Manager/Toolbar/Definition/MangaListToolbar.php index 63ebf16..d25fdcc 100644 --- a/src/Manager/Toolbar/Definition/MangaListToolbar.php +++ b/src/Manager/Toolbar/Definition/MangaListToolbar.php @@ -10,10 +10,8 @@ class MangaListToolbar extends Toolbar { public function __construct() { - $this->addToLeftGroup(new ToolbarButton('arrows-rotate', 'Refresh metadata', 'refreshMetadata')) - ->addToLeftGroup(new ToolbarButton('search', 'Search last chapter', 'searchLastChapter')) - ->addToLeftGroup(new ToolbarDivider()) - ->addToLeftGroup(new ToolbarButton('plus', 'Add Manga', 'addManga')) + $this->addToLeftGroup(new ToolbarButton('arrows-rotate', 'Refresh', 'refreshMetadata')) + ->addToLeftGroup(new ToolbarButton('search', 'Search', 'searchLastChapter')) ->addToRightGroup(new ToolbarButton('th-large', 'Options', 'options')) ->addToRightGroup(new ToolbarDivider()) diff --git a/src/Twig/Components/MangaSearch.php b/src/Twig/Components/MangaSearch.php index bce6c1c..6189456 100644 --- a/src/Twig/Components/MangaSearch.php +++ b/src/Twig/Components/MangaSearch.php @@ -2,16 +2,12 @@ namespace App\Twig\Components; -use App\Repository\MangaRepository; use App\Service\MangadexProvider; -use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Exception; use Symfony\UX\LiveComponent\Attribute\AsLiveComponent; use Symfony\UX\LiveComponent\Attribute\LiveProp; -use Symfony\UX\LiveComponent\ComponentToolsTrait; use Symfony\UX\LiveComponent\DefaultActionTrait; -use Symfony\UX\LiveComponent\ValidatableComponentTrait; #[AsLiveComponent] class MangaSearch @@ -21,7 +17,7 @@ class MangaSearch #[LiveProp(writable: true)] public ?string $query = null; - public function __construct(private readonly MangadexProvider $mangadexProvider, private MangaRepository $mangaRepository) + public function __construct(private readonly MangadexProvider $mangadexProvider) { } @@ -30,8 +26,6 @@ class MangaSearch */ public function getMangas(): Collection|null { -// return new ArrayCollection($this->mangaRepository->findAll()); - if ($this->query === null || $this->query === '') { return null; } diff --git a/templates/activity/index.html.twig b/templates/activity/index.html.twig index caf457e..574423b 100644 --- a/templates/activity/index.html.twig +++ b/templates/activity/index.html.twig @@ -5,7 +5,7 @@ {% endif %} {% endblock %} {% block body %} -
+
diff --git a/templates/base.html.twig b/templates/base.html.twig index eebe243..c4c83fc 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -15,7 +15,7 @@
-
+
-
- -
+
+
{# -
- + +
+
@@ -59,7 +59,7 @@
diff --git a/templates/components/BootstrapModal.html.twig b/templates/components/BootstrapModal.html.twig deleted file mode 100644 index 7f0976e..0000000 --- a/templates/components/BootstrapModal.html.twig +++ /dev/null @@ -1,34 +0,0 @@ -
- -
diff --git a/templates/components/DropdownMenu.html.twig b/templates/components/DropdownMenu.html.twig index bd9f09c..e5778ab 100644 --- a/templates/components/DropdownMenu.html.twig +++ b/templates/components/DropdownMenu.html.twig @@ -6,6 +6,7 @@ text="{{ text }}" action="toggle" data-dropdown-target="button" + controller="dropdown" />
+
+
+ +
+
{% endfor %}
+ {# Modal d'édition #} + + {% block content %} +
+
+ + +
+
+ + +
+ {# Ajoutez d'autres champs selon vos besoins #} +
+ {% endblock %} + {% block footer %} + + + {% endblock %} +
+ + {# Modal de confirmation de suppression #} + + +

+ Are you sure you want to delete this manga? This action cannot be undone. +

+
+ +
+ +
+ +
+
{% endblock %} diff --git a/templates/menu/base_menu.html.twig b/templates/menu/base_menu.html.twig deleted file mode 100644 index 4b1b60b..0000000 --- a/templates/menu/base_menu.html.twig +++ /dev/null @@ -1,10 +0,0 @@ - -{#
#} -{# #} -{#
#} - - diff --git a/templates/menu/menu.html.twig b/templates/menu/menu.html.twig index 36e9db0..5b44651 100644 --- a/templates/menu/menu.html.twig +++ b/templates/menu/menu.html.twig @@ -1,4 +1,4 @@ -