Added:
- toolbar refactor
This commit is contained in:
@@ -5,55 +5,79 @@ export default class extends Controller {
|
|||||||
static targets = ["dropdown"]
|
static targets = ["dropdown"]
|
||||||
static values = {
|
static values = {
|
||||||
currentSort: String,
|
currentSort: String,
|
||||||
currentOrder: String
|
currentOrder: String,
|
||||||
|
currentStatus: String
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh() {
|
refreshMetadata() {
|
||||||
console.log("Refreshing...")
|
console.log("Refreshing...");
|
||||||
}
|
}
|
||||||
|
|
||||||
syncRss() {
|
searchLastChapter() {
|
||||||
console.log("Syncing RSS...")
|
console.log("Searching last chapter...");
|
||||||
}
|
|
||||||
|
|
||||||
search() {
|
|
||||||
console.log("Searching...")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
import() {
|
import() {
|
||||||
console.log("Importing...")
|
console.log("Importing...");
|
||||||
}
|
}
|
||||||
|
|
||||||
editMangas() {
|
editMangas() {
|
||||||
console.log("Editing mangas...")
|
console.log("Editing mangas...");
|
||||||
|
}
|
||||||
|
|
||||||
|
editManga() {
|
||||||
|
console.log("Editing manga...");
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteMangas() {
|
||||||
|
console.log("Deleting mangas...");
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteManga() {
|
||||||
|
console.log("Deleting manga...");
|
||||||
}
|
}
|
||||||
|
|
||||||
showOptions() {
|
showOptions() {
|
||||||
console.log("Showing options...")
|
console.log("Showing options...");
|
||||||
}
|
}
|
||||||
|
|
||||||
changeView() {
|
changeView(event) {
|
||||||
console.log("Changing view...")
|
event.preventDefault();
|
||||||
|
const viewOption = event.currentTarget.dataset.view;
|
||||||
|
|
||||||
|
const url = new URL(window.location);
|
||||||
|
url.searchParams.set('view', viewOption);
|
||||||
|
|
||||||
|
window.location = url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
sort(event) {
|
sort(event) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
const sortOption = event.currentTarget.dataset.sortOption
|
const sortOption = event.currentTarget.dataset.sort;
|
||||||
let order = 'asc'
|
let order = 'asc';
|
||||||
|
|
||||||
if (sortOption === this.currentSortValue && this.currentOrderValue === 'asc') {
|
if (sortOption === this.currentSortValue && this.currentOrderValue === 'asc') {
|
||||||
order = 'desc'
|
order = 'desc';
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = new URL(window.location)
|
const url = new URL(window.location);
|
||||||
url.searchParams.set('sort', sortOption)
|
url.searchParams.set('sort', sortOption);
|
||||||
url.searchParams.set('order', order)
|
url.searchParams.set('order', order);
|
||||||
|
|
||||||
window.location = url.toString()
|
window.location = url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
filter() {
|
filter(event) {
|
||||||
console.log("Filtering...")
|
event.preventDefault();
|
||||||
|
const filterOption = event.currentTarget.dataset.filter;
|
||||||
|
|
||||||
|
const url = new URL(window.location);
|
||||||
|
url.searchParams.set('status', filterOption);
|
||||||
|
|
||||||
|
// Réinitialiser la page à 1 si on utilise la pagination
|
||||||
|
// url.searchParams.set('page', '1');
|
||||||
|
|
||||||
|
window.location = url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class ImportController extends AbstractController
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/import', name: 'app_import')]
|
#[Route('/manga/import', name: 'app_manga_import')]
|
||||||
public function index(Request $request, SessionInterface $session): Response
|
public function index(Request $request, SessionInterface $session): Response
|
||||||
{
|
{
|
||||||
if ($request->isMethod('post')) {
|
if ($request->isMethod('post')) {
|
||||||
@@ -73,7 +73,7 @@ class ImportController extends AbstractController
|
|||||||
$filePath = $session->get('import_file_path');
|
$filePath = $session->get('import_file_path');
|
||||||
$originalFileName = $session->get('import_original_file_name');
|
$originalFileName = $session->get('import_original_file_name');
|
||||||
if (!$filePath || !$originalFileName) {
|
if (!$filePath || !$originalFileName) {
|
||||||
return $this->redirectToRoute('app_import');
|
return $this->redirectToRoute('app_manga_import');
|
||||||
}
|
}
|
||||||
|
|
||||||
$metadata = $this->cbzService->extractMetadata($filePath, $originalFileName);
|
$metadata = $this->cbzService->extractMetadata($filePath, $originalFileName);
|
||||||
@@ -82,7 +82,7 @@ class ImportController extends AbstractController
|
|||||||
'type' => 'error',
|
'type' => 'error',
|
||||||
'message' => 'Impossible de détecter le titre du manga.'
|
'message' => 'Impossible de détecter le titre du manga.'
|
||||||
]);
|
]);
|
||||||
return $this->redirectToRoute('app_import');
|
return $this->redirectToRoute('app_manga_import');
|
||||||
}
|
}
|
||||||
|
|
||||||
$mangas = $this->mangaRepository->findBySlug($metadata['title']);
|
$mangas = $this->mangaRepository->findBySlug($metadata['title']);
|
||||||
@@ -130,7 +130,7 @@ class ImportController extends AbstractController
|
|||||||
public function confirm(Request $request, SessionInterface $session): Response
|
public function confirm(Request $request, SessionInterface $session): Response
|
||||||
{
|
{
|
||||||
if (!$request->isMethod('POST')) {
|
if (!$request->isMethod('POST')) {
|
||||||
return $this->redirectToRoute('app_import');
|
return $this->redirectToRoute('app_manga_import');
|
||||||
}
|
}
|
||||||
|
|
||||||
$action = $request->request->get('action');
|
$action = $request->request->get('action');
|
||||||
@@ -145,7 +145,7 @@ class ImportController extends AbstractController
|
|||||||
'type' => 'error',
|
'type' => 'error',
|
||||||
'message' => 'Manga non trouvé.'
|
'message' => 'Manga non trouvé.'
|
||||||
]);
|
]);
|
||||||
return $this->redirectToRoute('app_import');
|
return $this->redirectToRoute('app_manga_import');
|
||||||
}
|
}
|
||||||
|
|
||||||
$filePath = $session->get('import_file_path');
|
$filePath = $session->get('import_file_path');
|
||||||
@@ -154,7 +154,7 @@ class ImportController extends AbstractController
|
|||||||
'type' => 'error',
|
'type' => 'error',
|
||||||
'message' => 'Fichier d\'import non trouvé.'
|
'message' => 'Fichier d\'import non trouvé.'
|
||||||
]);
|
]);
|
||||||
return $this->redirectToRoute('app_import');
|
return $this->redirectToRoute('app_manga_import');
|
||||||
}
|
}
|
||||||
$originalFileName = $session->get('import_original_file_name');
|
$originalFileName = $session->get('import_original_file_name');
|
||||||
|
|
||||||
@@ -192,6 +192,6 @@ class ImportController extends AbstractController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->redirectToRoute('app_import');
|
return $this->redirectToRoute('app_manga_import');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Controller;
|
|||||||
|
|
||||||
use App\Entity\Chapter;
|
use App\Entity\Chapter;
|
||||||
use App\Entity\Manga;
|
use App\Entity\Manga;
|
||||||
|
use App\Manager\Toolbar\Factory\ToolbarFactory;
|
||||||
use App\Manager\ToolbarManager;
|
use App\Manager\ToolbarManager;
|
||||||
use App\Message\DownloadChapter;
|
use App\Message\DownloadChapter;
|
||||||
use App\Repository\ChapterRepository;
|
use App\Repository\ChapterRepository;
|
||||||
@@ -36,7 +37,7 @@ class MangaController extends AbstractController
|
|||||||
private readonly MangaUpdatesMetadataProvider $mangaUpdatesDbProvider,
|
private readonly MangaUpdatesMetadataProvider $mangaUpdatesDbProvider,
|
||||||
private readonly MessageBusInterface $bus,
|
private readonly MessageBusInterface $bus,
|
||||||
private readonly CbzService $cbzService,
|
private readonly CbzService $cbzService,
|
||||||
private readonly ToolbarManager $toolbarManager
|
private readonly ToolbarFactory $toolbarFactory
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -46,12 +47,16 @@ class MangaController extends AbstractController
|
|||||||
{
|
{
|
||||||
$sort = $request->query->get('sort', 'title');
|
$sort = $request->query->get('sort', 'title');
|
||||||
$order = $request->query->get('order', 'asc');
|
$order = $request->query->get('order', 'asc');
|
||||||
|
$status = $request->query->get('status', 'all');
|
||||||
|
$view = $request->query->get('view', 'poster');
|
||||||
|
|
||||||
$mangas = $this->mangaRepository->findAllSorted($sort, $order);
|
$mangas = $this->mangaRepository->findAllSortedAndFiltered($sort, $order, $status);
|
||||||
|
|
||||||
return $this->render('manga/index.html.twig', [
|
return $this->render('manga/index.html.twig', [
|
||||||
'mangas' => $mangas,
|
'mangas' => $mangas,
|
||||||
'toolbarItems' => $this->toolbarManager->getToolbarItems(),
|
'toolbar' => $this->toolbarFactory->createToolbar('manga_list')->getGroups(),
|
||||||
|
'currentStatus' => $status,
|
||||||
|
'currentView' => $view,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +99,7 @@ class MangaController extends AbstractController
|
|||||||
return $this->render('manga/show_chapters.html.twig', [
|
return $this->render('manga/show_chapters.html.twig', [
|
||||||
'chapters_by_volume' => $chaptersByVolume,
|
'chapters_by_volume' => $chaptersByVolume,
|
||||||
'manga' => $manga,
|
'manga' => $manga,
|
||||||
'toolbarItems' => $this->toolbarManager->getToolbarItems(),
|
'toolbar' => $this->toolbarFactory->createToolbar('chapter_list')->getGroups(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
21
src/Manager/Toolbar/Definition/ChapterListToolbar.php
Normal file
21
src/Manager/Toolbar/Definition/ChapterListToolbar.php
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Manager\Toolbar\Definition;
|
||||||
|
|
||||||
|
use App\Manager\Toolbar\Element\ToolbarButton;
|
||||||
|
use App\Manager\Toolbar\Element\ToolbarDivider;
|
||||||
|
|
||||||
|
class ChapterListToolbar extends Toolbar
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->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('chevron-down', 'Expand all', 'expandAll'));
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/Manager/Toolbar/Definition/MangaListToolbar.php
Normal file
37
src/Manager/Toolbar/Definition/MangaListToolbar.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Manager\Toolbar\Definition;
|
||||||
|
|
||||||
|
use App\Manager\Toolbar\Element\ToolbarButton;
|
||||||
|
use App\Manager\Toolbar\Element\ToolbarDivider;
|
||||||
|
use App\Manager\Toolbar\Element\ToolbarDropdown;
|
||||||
|
|
||||||
|
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'))
|
||||||
|
|
||||||
|
->addToRightGroup(new ToolbarButton('th-large', 'Options', 'options'))
|
||||||
|
->addToRightGroup(new ToolbarDivider())
|
||||||
|
->addToRightGroup(new ToolbarDropdown('eye', 'View', 'changeView', [
|
||||||
|
['text' => 'Poster View', 'action' => 'changeView', 'data' => ['view' => 'poster']],
|
||||||
|
['text' => 'Table View', 'action' => 'changeView', 'data' => ['view' => 'table']],
|
||||||
|
['text' => 'Resume View', 'action' => 'changeView', 'data' => ['view' => 'resume']]
|
||||||
|
]))
|
||||||
|
->addToRightGroup(new ToolbarDropdown('sort', 'Sort', 'sort', [
|
||||||
|
['text' => 'Par titre', 'action' => 'sort', 'data' => ['sort' => 'title']],
|
||||||
|
['text' => 'Par année de publication', 'action' => 'sort', 'data' => ['sort' => 'publicationYear']],
|
||||||
|
['text' => 'Par date d\'ajout', 'action' => 'sort', 'data' => ['sort' => 'createdAt']]
|
||||||
|
]))
|
||||||
|
->addToRightGroup(new ToolbarDropdown('filter', 'Filter', 'filter', [
|
||||||
|
['text' => 'Tous les mangas', 'action' => 'filter', 'data' => ['filter' => 'all']],
|
||||||
|
['text' => 'Mangas en cours', 'action' => 'filter', 'data' => ['filter' => 'ongoing']],
|
||||||
|
['text' => 'Mangas terminés', 'action' => 'filter', 'data' => ['filter' => 'completed']]
|
||||||
|
]))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/Manager/Toolbar/Definition/Toolbar.php
Normal file
31
src/Manager/Toolbar/Definition/Toolbar.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Manager\Toolbar\Definition;
|
||||||
|
|
||||||
|
use App\Manager\Toolbar\Element\ToolbarElement;
|
||||||
|
|
||||||
|
class Toolbar
|
||||||
|
{
|
||||||
|
private array $leftGroup = [];
|
||||||
|
private array $rightGroup = [];
|
||||||
|
|
||||||
|
public function addToLeftGroup(ToolbarElement $element): self
|
||||||
|
{
|
||||||
|
$this->leftGroup[] = $element;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addToRightGroup(ToolbarElement $element): self
|
||||||
|
{
|
||||||
|
$this->rightGroup[] = $element;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGroups(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'leftGroup' => $this->leftGroup,
|
||||||
|
'rightGroup' => $this->rightGroup,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/Manager/Toolbar/Element/AbstractToolbarElement.php
Normal file
37
src/Manager/Toolbar/Element/AbstractToolbarElement.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Manager\Toolbar\Element;
|
||||||
|
|
||||||
|
abstract class AbstractToolbarElement implements ToolbarElement
|
||||||
|
{
|
||||||
|
protected string $icon;
|
||||||
|
protected string|array $text;
|
||||||
|
protected string $action;
|
||||||
|
|
||||||
|
public function __construct(string $icon, string|array $text, string $action)
|
||||||
|
{
|
||||||
|
$this->icon = $icon;
|
||||||
|
$this->text = $text;
|
||||||
|
$this->action = $action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIcon(): string
|
||||||
|
{
|
||||||
|
return $this->icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getText(): string|array
|
||||||
|
{
|
||||||
|
return $this->text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAction(): string
|
||||||
|
{
|
||||||
|
return $this->action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAdditionalProperties(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/Manager/Toolbar/Element/ToolbarButton.php
Normal file
14
src/Manager/Toolbar/Element/ToolbarButton.php
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Manager\Toolbar\Element;
|
||||||
|
|
||||||
|
use App\Manager\Toolbar\Element\AbstractToolbarElement;
|
||||||
|
|
||||||
|
class ToolbarButton extends AbstractToolbarElement
|
||||||
|
{
|
||||||
|
|
||||||
|
public function getType(): string
|
||||||
|
{
|
||||||
|
return 'button';
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/Manager/Toolbar/Element/ToolbarDivider.php
Normal file
15
src/Manager/Toolbar/Element/ToolbarDivider.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Manager\Toolbar\Element;
|
||||||
|
|
||||||
|
class ToolbarDivider extends AbstractToolbarElement
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct('divider', '', '');
|
||||||
|
}
|
||||||
|
public function getType(): string
|
||||||
|
{
|
||||||
|
return 'divider';
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/Manager/Toolbar/Element/ToolbarDropdown.php
Normal file
24
src/Manager/Toolbar/Element/ToolbarDropdown.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Manager\Toolbar\Element;
|
||||||
|
|
||||||
|
class ToolbarDropdown extends AbstractToolbarElement
|
||||||
|
{
|
||||||
|
private array $items;
|
||||||
|
|
||||||
|
public function __construct(string $icon, string $text, string $action, array $items)
|
||||||
|
{
|
||||||
|
parent::__construct($icon, $text, $action);
|
||||||
|
$this->items = $items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType(): string
|
||||||
|
{
|
||||||
|
return 'dropdown';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAdditionalProperties(): array
|
||||||
|
{
|
||||||
|
return ['items' => $this->items];
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/Manager/Toolbar/Element/ToolbarElement.php
Normal file
12
src/Manager/Toolbar/Element/ToolbarElement.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Manager\Toolbar\Element;
|
||||||
|
|
||||||
|
interface ToolbarElement
|
||||||
|
{
|
||||||
|
public function getIcon(): string;
|
||||||
|
public function getText(): string|array;
|
||||||
|
public function getAction(): string;
|
||||||
|
public function getType(): string;
|
||||||
|
public function getAdditionalProperties(): array;
|
||||||
|
}
|
||||||
19
src/Manager/Toolbar/Factory/ToolbarFactory.php
Normal file
19
src/Manager/Toolbar/Factory/ToolbarFactory.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Manager\Toolbar\Factory;
|
||||||
|
|
||||||
|
use App\Manager\Toolbar\Definition\ChapterListToolbar;
|
||||||
|
use App\Manager\Toolbar\Definition\MangaListToolbar;
|
||||||
|
use App\Manager\Toolbar\Definition\Toolbar;
|
||||||
|
|
||||||
|
class ToolbarFactory
|
||||||
|
{
|
||||||
|
public function createToolbar(string $type): Toolbar
|
||||||
|
{
|
||||||
|
return match ($type) {
|
||||||
|
'manga_list' => new MangaListToolbar(),
|
||||||
|
'chapter_list' => new ChapterListToolbar(),
|
||||||
|
default => throw new \InvalidArgumentException("Unknown toolbar type: $type"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,17 +25,18 @@ class ToolbarManager
|
|||||||
private function getFilterItems(): array
|
private function getFilterItems(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
['text' => 'Tous les genres', 'action' => 'filter', 'data' => ['filter-option' => 'allGenres']],
|
['text' => 'Tous les mangas', 'action' => 'filter', 'data' => ['filter-option' => 'all']],
|
||||||
['text' => 'Mangas terminés', 'action' => 'filter', 'data' => ['filter-option' => 'completed']],
|
['text' => 'Mangas en cours', 'action' => 'filter', 'data' => ['filter-option' => 'ongoing']],
|
||||||
['text' => 'Mangas en cours', 'action' => 'filter', 'data' => ['filter-option' => 'ongoing']]
|
['text' => 'Mangas terminés', 'action' => 'filter', 'data' => ['filter-option' => 'completed']]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getViewOptions(): array
|
private function getViewOptions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
['text' => 'Vue grille', 'action' => 'changeView', 'data' => ['view-option' => 'grid']],
|
['text' => 'Vue poster', 'action' => 'changeView', 'data' => ['view-option' => 'poster']],
|
||||||
['text' => 'Vue liste', 'action' => 'changeView', 'data' => ['view-option' => 'list']]
|
['text' => 'Vue résumé', 'action' => 'changeView', 'data' => ['view-option' => 'resume']],
|
||||||
|
['text' => 'Vue table', 'action' => 'changeView', 'data' => ['view-option' => 'table']]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,23 +103,14 @@ class MangaRepository extends ServiceEntityRepository
|
|||||||
return $query->getQuery()->getOneOrNullResult();
|
return $query->getQuery()->getOneOrNullResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findAllSorted(string $sort = 'title', string $order = 'asc'): array
|
public function findAllSortedAndFiltered($sort = 'title', $order = 'asc', $status = 'all')
|
||||||
{
|
{
|
||||||
$qb = $this->createQueryBuilder('m');
|
$qb = $this->createQueryBuilder('m')
|
||||||
|
->orderBy('m.' . $sort, $order);
|
||||||
|
|
||||||
switch ($sort) {
|
if ($status !== 'all') {
|
||||||
case 'title':
|
$qb->andWhere('m.status = :status')
|
||||||
$qb->orderBy('m.title', $order);
|
->setParameter('status', $status);
|
||||||
break;
|
|
||||||
case 'publicationYear':
|
|
||||||
$qb->orderBy('m.publicationYear', $order);
|
|
||||||
break;
|
|
||||||
case 'createdAt':
|
|
||||||
$qb->orderBy('m.createdAt', $order);
|
|
||||||
break;
|
|
||||||
// Ajoutez d'autres cas pour les différentes options de tri
|
|
||||||
default:
|
|
||||||
$qb->orderBy('m.title', 'asc');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $qb->getQuery()->getResult();
|
return $qb->getQuery()->getResult();
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ readonly class MangadexProvider implements MetadataProviderInterface
|
|||||||
->setSlug($this->slugger->slug($result['attributes']['title']['en'])->lower())
|
->setSlug($this->slugger->slug($result['attributes']['title']['en'])->lower())
|
||||||
->setDescription($result['attributes']['description']['fr'] ?? $result['attributes']['description']['en'] ?? '')
|
->setDescription($result['attributes']['description']['fr'] ?? $result['attributes']['description']['en'] ?? '')
|
||||||
->setPublicationYear($result['attributes']['year'])
|
->setPublicationYear($result['attributes']['year'])
|
||||||
|
->setStatus($result['attributes']['status'])
|
||||||
;
|
;
|
||||||
$tags = [];
|
$tags = [];
|
||||||
foreach($result['attributes']['tags'] as $tag){
|
foreach($result['attributes']['tags'] as $tag){
|
||||||
|
|||||||
@@ -1,41 +1,8 @@
|
|||||||
{% extends 'base.html.twig' %}
|
{% extends 'base.html.twig' %}
|
||||||
{% block toolbar %}
|
{% block toolbar %}
|
||||||
{% set left_group %}
|
{% if toolbar is defined %}
|
||||||
<twig:ToolBarButton icon="sync-alt" text="Tout actualiser" action="refresh"/>
|
<twig:Toolbar toolbar="{{ toolbar }}"/>
|
||||||
<twig:ToolBarButton icon="rss" text="Synchro RSS" action="syncRss"/>
|
{% endif %}
|
||||||
<twig:Divider/>
|
|
||||||
<twig:ToolBarButton icon="search" text="Rechercher tout" action="search"/>
|
|
||||||
<twig:ToolBarButton icon="user-plus" text="Importation manuelle" action="import"/>
|
|
||||||
<twig:Divider/>
|
|
||||||
<twig:ToolBarButton icon="wrench" text="Modifier Mangas" action="editMangas"/>
|
|
||||||
|
|
||||||
{% endset %}
|
|
||||||
|
|
||||||
{% set right_group %}
|
|
||||||
<twig:ToolBarButton icon="th-large" text="Options" action="showOptions"/>
|
|
||||||
<twig:DropdownMenu
|
|
||||||
icon="eye"
|
|
||||||
text="Vue"
|
|
||||||
items="{{ toolbarItems.viewOptions }}"
|
|
||||||
/>
|
|
||||||
<twig:Divider/>
|
|
||||||
<twig:DropdownMenu
|
|
||||||
icon="sort"
|
|
||||||
text="Trier"
|
|
||||||
items="{{ toolbarItems.sortItems }}"
|
|
||||||
/>
|
|
||||||
<twig:DropdownMenu
|
|
||||||
icon="filter"
|
|
||||||
text="Filtre"
|
|
||||||
items="{{ toolbarItems.filterItems }}"
|
|
||||||
/>
|
|
||||||
{% endset %}
|
|
||||||
|
|
||||||
<twig:Toolbar
|
|
||||||
left_group="{{ left_group }}"
|
|
||||||
right_group="{{ right_group }}"
|
|
||||||
data-action="click@window->toolbar#clickOutside"
|
|
||||||
/>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="container mx-auto mt-2">
|
<div class="container mx-auto mt-2">
|
||||||
|
|||||||
@@ -2,10 +2,45 @@
|
|||||||
<div class="bg-gray-800 p-3 min-h-14" {{ stimulus_controller('toolbar') }}>
|
<div class="bg-gray-800 p-3 min-h-14" {{ stimulus_controller('toolbar') }}>
|
||||||
<div class="flex flex-row items-center justify-between">
|
<div class="flex flex-row items-center justify-between">
|
||||||
<div class="flex mr-2 items-center">
|
<div class="flex mr-2 items-center">
|
||||||
{{ left_group|raw }}
|
{% for element in toolbar.leftGroup %}
|
||||||
|
{% if element.type == 'button' %}
|
||||||
|
<twig:ToolBarButton
|
||||||
|
icon="{{ element.icon }}"
|
||||||
|
text="{{ element.text }}"
|
||||||
|
action="{{ element.action }}"
|
||||||
|
/>
|
||||||
|
{% elseif element.type == 'divider' %}
|
||||||
|
<twig:Divider/>
|
||||||
|
{% elseif element.type == 'dropdown' %}
|
||||||
|
<twig:DropdownMenu
|
||||||
|
icon="{{ element.icon }}"
|
||||||
|
text="{{ element.text }}"
|
||||||
|
action="{{ element.action }}"
|
||||||
|
items="{{ element.additionalProperties.items }}"
|
||||||
|
/>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex mr-2 items-center">
|
<div class="flex mr-2 items-center">
|
||||||
{{ right_group|raw }}
|
{% for element in toolbar.rightGroup %}
|
||||||
|
{% if element.type == 'button' %}
|
||||||
|
<twig:ToolBarButton
|
||||||
|
icon="{{ element.icon }}"
|
||||||
|
text="{{ element.text }}"
|
||||||
|
action="{{ element.action }}"
|
||||||
|
/>
|
||||||
|
{% elseif element.type == 'divider' %}
|
||||||
|
<twig:Divider/>
|
||||||
|
{% elseif element.type == 'dropdown' %}
|
||||||
|
<twig:DropdownMenu
|
||||||
|
icon="{{ element.icon }}"
|
||||||
|
text="{{ element.text }}"
|
||||||
|
action="{{ element.action }}"
|
||||||
|
items="{{ element.additionalProperties.items }}"
|
||||||
|
/>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,43 +1,12 @@
|
|||||||
{% extends 'base.html.twig' %}
|
{% extends 'base.html.twig' %}
|
||||||
{% block toolbar %}
|
{% block toolbar %}
|
||||||
{% set left_group %}
|
{% if toolbar %}
|
||||||
<twig:ToolBarButton icon="sync-alt" text="Tout actualiser" action="refresh"/>
|
<twig:Toolbar toolbar="{{ toolbar }}"/>
|
||||||
<twig:ToolBarButton icon="rss" text="Synchro RSS" action="syncRss"/>
|
{% endif %}
|
||||||
<twig:Divider/>
|
|
||||||
<twig:ToolBarButton icon="search" text="Rechercher tout" action="search"/>
|
|
||||||
<twig:ToolBarButton icon="user-plus" text="Importation manuelle" action="import"/>
|
|
||||||
<twig:Divider/>
|
|
||||||
<twig:ToolBarButton icon="wrench" text="Modifier Mangas" action="editMangas"/>
|
|
||||||
|
|
||||||
{% endset %}
|
|
||||||
|
|
||||||
{% set right_group %}
|
|
||||||
<twig:ToolBarButton icon="th-large" text="Options" action="showOptions"/>
|
|
||||||
<twig:DropdownMenu
|
|
||||||
icon="eye"
|
|
||||||
text="Vue"
|
|
||||||
items="{{ toolbarItems.viewOptions }}"
|
|
||||||
/>
|
|
||||||
<twig:Divider/>
|
|
||||||
<twig:DropdownMenu
|
|
||||||
icon="sort"
|
|
||||||
text="Trier"
|
|
||||||
items="{{ toolbarItems.sortItems }}"
|
|
||||||
/>
|
|
||||||
<twig:DropdownMenu
|
|
||||||
icon="filter"
|
|
||||||
text="Filtre"
|
|
||||||
items="{{ toolbarItems.filterItems }}"
|
|
||||||
/>
|
|
||||||
{% endset %}
|
|
||||||
|
|
||||||
<twig:Toolbar
|
|
||||||
left_group="{{ left_group }}"
|
|
||||||
right_group="{{ right_group }}"
|
|
||||||
data-action="click@window->toolbar#clickOutside"
|
|
||||||
/>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
{% if currentView == 'poster' %}
|
||||||
|
{# Vue poster actuelle #}
|
||||||
<div class="w-full p-4 grid sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-8 2xl:grid-cols-12 gap-4">
|
<div class="w-full p-4 grid sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-8 2xl:grid-cols-12 gap-4">
|
||||||
{% for manga in mangas %}
|
{% for manga in mangas %}
|
||||||
<div
|
<div
|
||||||
@@ -59,4 +28,67 @@
|
|||||||
<p class="col-span-full text-center text-gray-500">Aucun manga trouvé.</p>
|
<p class="col-span-full text-center text-gray-500">Aucun manga trouvé.</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
{% elseif currentView == 'resume' %}
|
||||||
|
{# Vue résumé #}
|
||||||
|
<div class="w-full p-4 space-y-4">
|
||||||
|
{% for manga in mangas %}
|
||||||
|
<div
|
||||||
|
class="bg-white overflow-hidden border border-gray-200 hover:shadow-lg hover:border-gray-400 transition-all duration-300 flex">
|
||||||
|
<img src="{{ manga.imageUrl ?? 'https://placehold.co/150x220' }}" alt="{{ manga.title }}"
|
||||||
|
class="w-32 h-48 object-cover">
|
||||||
|
<div class="p-4 flex flex-col justify-between flex-grow">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-semibold">{{ manga.title }}</h3>
|
||||||
|
<p class="text-sm text-gray-500">{{ manga.publicationYear }}</p>
|
||||||
|
<p class="text-sm text-gray-600 mt-2">{{ manga.description|truncate(200) }}</p>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-gray-400 mt-2">Added: {{ manga.createdAt|date('M d, Y') }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<p class="text-center text-gray-500">Aucun manga trouvé.</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% elseif currentView == 'table' %}
|
||||||
|
<div class="p-4">
|
||||||
|
<table class="min-w-full bg-white">
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-gray-100 text-gray-600 uppercase text-sm leading-normal">
|
||||||
|
<th class="py-3 px-6 text-left">Manga Title</th>
|
||||||
|
{# <th class="py-3 px-6 text-center">Volumes</th> #}
|
||||||
|
<th class="py-3 px-6 text-center">Chapters</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="text-gray-600 text-sm">
|
||||||
|
{% for manga in mangas %}
|
||||||
|
<tr class="border-b border-gray-200 hover:bg-gray-100">
|
||||||
|
<td class="py-3 px-6 text-left whitespace-nowrap">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="font-medium">{{ manga.title }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
{# <td class="py-3 px-6 text-center"> #}
|
||||||
|
{# {{ manga.volumes|length }} #}
|
||||||
|
{# </td> #}
|
||||||
|
<td class="py-3 px-6 text-center">
|
||||||
|
{% set total_chapters = manga.chapters|length %}
|
||||||
|
{% set available_chapters = manga.chapters|filter(chapter => chapter.cbzPath is not null)|length %}
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<div class="w-48 bg-gray-200 rounded-full h-2.5">
|
||||||
|
<div class="bg-blue-600 h-2.5 rounded-full"
|
||||||
|
style="width: {{ (available_chapters / total_chapters * 100)|round }}%"></div>
|
||||||
|
</div>
|
||||||
|
<span class="ml-2">{{ available_chapters }} / {{ total_chapters }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="6" class="py-3 px-6 text-center">Aucun manga trouvé.</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,41 +1,8 @@
|
|||||||
{% extends 'base.html.twig' %}
|
{% extends 'base.html.twig' %}
|
||||||
{% block toolbar %}
|
{% block toolbar %}
|
||||||
{% set left_group %}
|
{% if toolbar is defined %}
|
||||||
<twig:ToolBarButton icon="sync-alt" text="Tout actualiser" action="refresh"/>
|
<twig:Toolbar toolbar="{{ toolbar }}"/>
|
||||||
<twig:ToolBarButton icon="rss" text="Synchro RSS" action="syncRss"/>
|
{% endif %}
|
||||||
<twig:Divider/>
|
|
||||||
<twig:ToolBarButton icon="search" text="Rechercher tout" action="search"/>
|
|
||||||
<twig:ToolBarButton icon="user-plus" text="Importation manuelle" action="import"/>
|
|
||||||
<twig:Divider/>
|
|
||||||
<twig:ToolBarButton icon="wrench" text="Modifier Mangas" action="editMangas"/>
|
|
||||||
|
|
||||||
{% endset %}
|
|
||||||
|
|
||||||
{% set right_group %}
|
|
||||||
<twig:ToolBarButton icon="th-large" text="Options" action="showOptions"/>
|
|
||||||
<twig:DropdownMenu
|
|
||||||
icon="eye"
|
|
||||||
text="Vue"
|
|
||||||
items="{{ toolbarItems.viewOptions }}"
|
|
||||||
/>
|
|
||||||
<twig:Divider/>
|
|
||||||
<twig:DropdownMenu
|
|
||||||
icon="sort"
|
|
||||||
text="Trier"
|
|
||||||
items="{{ toolbarItems.sortItems }}"
|
|
||||||
/>
|
|
||||||
<twig:DropdownMenu
|
|
||||||
icon="filter"
|
|
||||||
text="Filtre"
|
|
||||||
items="{{ toolbarItems.filterItems }}"
|
|
||||||
/>
|
|
||||||
{% endset %}
|
|
||||||
|
|
||||||
<twig:Toolbar
|
|
||||||
left_group="{{ left_group }}"
|
|
||||||
right_group="{{ right_group }}"
|
|
||||||
data-action="click@window->toolbar#clickOutside"
|
|
||||||
/>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
@@ -100,18 +67,21 @@
|
|||||||
<i class="fas fa-bookmark text-gray-500 text-3xl"></i>
|
<i class="fas fa-bookmark text-gray-500 text-3xl"></i>
|
||||||
<h2 class="text-xl font-semibold">Volume {{ '%02d'|format(volume) }}</h2>
|
<h2 class="text-xl font-semibold">Volume {{ '%02d'|format(volume) }}</h2>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span class="px-2 py-1 text-sm rounded {{ available_chapters|length > 0 ? 'bg-green-500 text-white' : 'bg-red-500 text-white' }}">
|
<span
|
||||||
|
class="px-2 py-1 text-sm rounded {{ available_chapters|length > 0 ? 'bg-green-500 text-white' : 'bg-red-500 text-white' }}">
|
||||||
{{ available_chapters|length }} / {{ total_chapters }}
|
{{ available_chapters|length }} / {{ total_chapters }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span class="text-gray-600 mr-2">{{ chapters|length }} Chapters</span>
|
<span class="text-gray-600 mr-2">{{ chapters|length }} Chapters</span>
|
||||||
<i data-table-target="toggleIcon" data-action="click->table#toggle" class="fas fa-chevron-{{ is_first ? 'up' : 'down' }} cursor-pointer"></i>
|
<i data-table-target="toggleIcon" data-action="click->table#toggle"
|
||||||
|
class="fas fa-chevron-{{ is_first ? 'up' : 'down' }} cursor-pointer"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div data-table-target="body" class="p-4 border-t" {{ not is_first ? 'style="display: none;"' : '' }}>
|
<div data-table-target="body"
|
||||||
|
class="p-4 border-t" {{ not is_first ? 'style="display: none;"' : '' }}>
|
||||||
<table class="min-w-full table-auto">
|
<table class="min-w-full table-auto">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -134,7 +104,8 @@
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-2 flex justify-end gap-2">
|
<td class="px-4 py-2 flex justify-end gap-2">
|
||||||
<a href="{{ path('download_cbz', {chapterId: chapters|first.id}) }}" class="text-gray-500 hover:text-green-500">
|
<a href="{{ path('download_cbz', {chapterId: chapters|first.id}) }}"
|
||||||
|
class="text-gray-500 hover:text-green-500">
|
||||||
<i class="fas fa-download"></i>
|
<i class="fas fa-download"></i>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
@@ -169,7 +140,8 @@
|
|||||||
data-url="{{ path('add_chapter', {id: chapter.id}) }}"
|
data-url="{{ path('add_chapter', {id: chapter.id}) }}"
|
||||||
>
|
>
|
||||||
<span class="text-gray-500 hover:text-green-500">
|
<span class="text-gray-500 hover:text-green-500">
|
||||||
<i data-download-chapter-target="icon" class="fas fa-search"></i>
|
<i data-download-chapter-target="icon"
|
||||||
|
class="fas fa-search"></i>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -179,7 +151,8 @@
|
|||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{{ path('download_cbz', {chapterId: chapter.id}) }}" class="text-gray-500 hover:text-green-500">
|
<a href="{{ path('download_cbz', {chapterId: chapter.id}) }}"
|
||||||
|
class="text-gray-500 hover:text-green-500">
|
||||||
<i class="fas fa-download"></i>
|
<i class="fas fa-download"></i>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
{% if app.request.get('_route') starts with 'app_manga' %}
|
{% if app.request.get('_route') starts with 'app_manga' %}
|
||||||
<ul class="ml-8 mt-2 space-y-4">
|
<ul class="ml-8 mt-2 space-y-4">
|
||||||
<li><a href="{{ path('app_manga_new') }}" class="hover:text-green-600">Ajouter un nouveau</a></li>
|
<li><a href="{{ path('app_manga_new') }}" class="hover:text-green-600">Ajouter un nouveau</a></li>
|
||||||
<li><a href="{{ path('app_import') }}" class="hover:text-green-600">Import bibliothèque</a></li>
|
<li><a href="{{ path('app_manga_import') }}" class="hover:text-green-600">Import bibliothèque</a></li>
|
||||||
<li><a href="#" class="hover:text-green-600">Découvrir</a></li>
|
<li><a href="#" class="hover:text-green-600">Découvrir</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
Reference in New Issue
Block a user