diff --git a/assets/vue/app/domain/manga/domain/entities/manga.js b/assets/vue/app/domain/manga/domain/entities/manga.js index 78b1e3d..98d0c9b 100644 --- a/assets/vue/app/domain/manga/domain/entities/manga.js +++ b/assets/vue/app/domain/manga/domain/entities/manga.js @@ -4,6 +4,7 @@ export class Manga { slug, title, description = null, + author = null, authors = [], imageUrl = null, thumbnailUrl = null, @@ -20,7 +21,7 @@ export class Manga { this.slug = slug; this.title = title; this.description = description; - this.authors = authors; + this.authors = authors.length ? authors : (author ? [author] : []); this.imageUrl = imageUrl; this.thumbnailUrl = thumbnailUrl; this.publicationYear = publicationYear; diff --git a/assets/vue/app/domain/manga/presentation/components/HomeDisplaySettingsModal.vue b/assets/vue/app/domain/manga/presentation/components/HomeDisplaySettingsModal.vue new file mode 100644 index 0000000..0103d2c --- /dev/null +++ b/assets/vue/app/domain/manga/presentation/components/HomeDisplaySettingsModal.vue @@ -0,0 +1,161 @@ + + + + diff --git a/assets/vue/app/domain/manga/presentation/components/MangaCard.vue b/assets/vue/app/domain/manga/presentation/components/MangaCard.vue index 8ef99f5..8d282af 100644 --- a/assets/vue/app/domain/manga/presentation/components/MangaCard.vue +++ b/assets/vue/app/domain/manga/presentation/components/MangaCard.vue @@ -35,12 +35,13 @@ - + -

{{ manga.title }}

- {{ manga.publicationYear }} +

{{ manga.title }}

+ {{ manga.publicationYear }} + {{ manga.authors[0] }}
@@ -53,6 +54,10 @@ defineProps({ manga: { type: Object, required: true + }, + options: { + type: Object, + default: () => ({ showTitle: true, showYear: true, showAuthor: false }) } }); diff --git a/assets/vue/app/domain/manga/presentation/components/MangaGrid.vue b/assets/vue/app/domain/manga/presentation/components/MangaGrid.vue index 089299f..0fe83a3 100644 --- a/assets/vue/app/domain/manga/presentation/components/MangaGrid.vue +++ b/assets/vue/app/domain/manga/presentation/components/MangaGrid.vue @@ -4,6 +4,7 @@ v-for="manga in mangas" :key="manga.id" :manga="manga" + :options="options" @edit="openEdit" @sources="openSources" @refresh="doRefresh" /> @@ -41,6 +42,10 @@ defineProps({ mangas: { type: Array, required: true + }, + options: { + type: Object, + default: () => ({ showTitle: true, showYear: true, showAuthor: false }) } }); diff --git a/assets/vue/app/domain/manga/presentation/components/MangaOverview.vue b/assets/vue/app/domain/manga/presentation/components/MangaOverview.vue index 00e666a..35d435e 100644 --- a/assets/vue/app/domain/manga/presentation/components/MangaOverview.vue +++ b/assets/vue/app/domain/manga/presentation/components/MangaOverview.vue @@ -8,6 +8,7 @@ {{ manga.status }} -

+

+ + {{ manga.authors.join(', ') }} + + + {{ manga.publicationYear }} + +
+

{{ manga.description }}

@@ -100,6 +109,10 @@ const props = defineProps({ mangas: { type: Array, required: true + }, + options: { + type: Object, + default: () => ({ showCover: true, showStatus: true, showDescription: true, showAuthor: false, showYear: false }) } }); diff --git a/assets/vue/app/domain/manga/presentation/components/MangaTable.vue b/assets/vue/app/domain/manga/presentation/components/MangaTable.vue index bb204c5..62ddf0a 100644 --- a/assets/vue/app/domain/manga/presentation/components/MangaTable.vue +++ b/assets/vue/app/domain/manga/presentation/components/MangaTable.vue @@ -4,10 +4,13 @@ - + - - + + + + + @@ -18,7 +21,7 @@ class="hover:bg-gray-50 dark:hover:bg-gray-700/40 transition-colors"> - + + + + + + + - -
TitreSource préféréeChapitresAuteurAnnéeStatutSource préféréeChapitres Actions
+ + {{ manga.publicationYear || '—' }} + + + {{ manga.status }} + + + + +
@@ -139,9 +163,19 @@ const props = defineProps({ mangas: { type: Array, required: true + }, + options: { + type: Object, + default: () => ({ showMonitoring: true, showPreferredSource: true, showChapters: true, showStatus: false, showAuthor: false, showYear: false }) } }); +function statusClass(status) { + if (status === 'ongoing') return 'text-blue-600 bg-blue-50 dark:bg-blue-900/20'; + if (status === 'completed') return 'text-green-600 bg-green-50 dark:bg-green-900/20'; + return 'text-gray-500 bg-gray-100 dark:bg-gray-700'; +} + function progressPercent(manga) { if (!manga.chaptersTotal) return 0; return Math.round((manga.chaptersScraped / manga.chaptersTotal) * 100); diff --git a/assets/vue/app/domain/manga/presentation/pages/HomePage.vue b/assets/vue/app/domain/manga/presentation/pages/HomePage.vue index b27c481..0b2eca6 100644 --- a/assets/vue/app/domain/manga/presentation/pages/HomePage.vue +++ b/assets/vue/app/domain/manga/presentation/pages/HomePage.vue @@ -3,12 +3,13 @@
- + - +
+ +
@@ -44,6 +51,7 @@ import { useUserPreferencesStore } from '../../../../domain/setting/application/ import Pagination from '../../../../shared/components/ui/Pagination.vue'; import Toolbar from '../../../../shared/components/ui/Toolbar.vue'; import { useMangaStore } from '../../application/store/mangaStore'; +import HomeDisplaySettingsModal from '../components/HomeDisplaySettingsModal.vue'; import MangaGrid from '../components/MangaGrid.vue'; import MangaOverview from '../components/MangaOverview.vue'; import MangaTable from '../components/MangaTable.vue'; @@ -61,6 +69,7 @@ import MangaTable from '../components/MangaTable.vue'; const viewMode = ref(prefs.defaultView); const currentPage = ref(1); + const isDisplaySettingsOpen = ref(false); onMounted(() => { mangaStore.loadCollection(); @@ -71,7 +80,12 @@ import MangaTable from '../components/MangaTable.vue'; }; const sortedCollection = computed(() => { - const items = [...(collection.value?.items || [])]; + let items = [...(collection.value?.items || [])]; + if (prefs.filterBy === 'completed') { + items = items.filter(m => m.status?.toLowerCase() === 'completed'); + } else if (prefs.filterBy === 'ongoing') { + items = items.filter(m => m.status?.toLowerCase() === 'ongoing'); + } if (prefs.sortBy === 'title') { items.sort((a, b) => a.title.localeCompare(b.title)); } else if (prefs.sortBy === 'addedAt') { @@ -91,7 +105,7 @@ import MangaTable from '../components/MangaTable.vue'; currentPage.value = 1; }); - const toolbarConfig = { + const toolbarConfig = computed(() => ({ leftSection: [ { icon: ArrowPathIcon, @@ -103,15 +117,15 @@ import MangaTable from '../components/MangaTable.vue'; { icon: MagnifyingGlassIcon, label: 'Search', type: 'button', onClick: () => {} } ], rightSection: [ - { icon: Cog6ToothIcon, type: 'button', onClick: () => {} }, + { icon: Cog6ToothIcon, label: 'Options', type: 'button', onClick: () => { isDisplaySettingsOpen.value = true; } }, { icon: EyeIcon, type: 'dropdown', label: 'View', items: [ - { label: 'Overview', onClick: () => { viewMode.value = 'list'; prefs.setDefaultView('list'); } }, - { label: 'Grid', onClick: () => { viewMode.value = 'grid'; prefs.setDefaultView('grid'); } }, - { label: 'Table', onClick: () => { viewMode.value = 'table'; prefs.setDefaultView('table'); } } + { label: 'Overview', isSelected: prefs.defaultView === 'list', onClick: () => { viewMode.value = 'list'; prefs.setDefaultView('list'); } }, + { label: 'Grid', isSelected: prefs.defaultView === 'grid', onClick: () => { viewMode.value = 'grid'; prefs.setDefaultView('grid'); } }, + { label: 'Table', isSelected: prefs.defaultView === 'table', onClick: () => { viewMode.value = 'table'; prefs.setDefaultView('table'); } } ] }, { @@ -119,9 +133,9 @@ import MangaTable from '../components/MangaTable.vue'; type: 'dropdown', label: 'Sort', items: [ - { label: 'Title', onClick: () => prefs.setSortBy('title') }, - { label: "Date d'ajout", onClick: () => prefs.setSortBy('addedAt') }, - { label: 'Progression', onClick: () => prefs.setSortBy('progress') } + { label: 'Title', isSelected: prefs.sortBy === 'title', onClick: () => prefs.setSortBy('title') }, + { label: "Date d'ajout", isSelected: prefs.sortBy === 'addedAt', onClick: () => prefs.setSortBy('addedAt') }, + { label: 'Progression', isSelected: prefs.sortBy === 'progress', onClick: () => prefs.setSortBy('progress') } ] }, { @@ -129,11 +143,11 @@ import MangaTable from '../components/MangaTable.vue'; type: 'dropdown', label: 'Filter', items: [ - { label: 'All', onClick: () => {} }, - { label: 'Completed', onClick: () => {} }, - { label: 'In Progress', onClick: () => {} } + { label: 'All', isSelected: prefs.filterBy === 'all', onClick: () => prefs.setFilterBy('all') }, + { label: 'Completed', isSelected: prefs.filterBy === 'completed', onClick: () => prefs.setFilterBy('completed') }, + { label: 'In Progress', isSelected: prefs.filterBy === 'ongoing', onClick: () => prefs.setFilterBy('ongoing') } ] } ] - }; + })); diff --git a/assets/vue/app/domain/setting/application/store/userPreferencesStore.js b/assets/vue/app/domain/setting/application/store/userPreferencesStore.js index a185066..9a60879 100644 --- a/assets/vue/app/domain/setting/application/store/userPreferencesStore.js +++ b/assets/vue/app/domain/setting/application/store/userPreferencesStore.js @@ -8,6 +8,12 @@ const defaultState = { defaultView: 'grid', itemsPerPage: 20, sortBy: 'title', + filterBy: 'all', + displayOptions: { + grid: { showTitle: true, showYear: true, showAuthor: false }, + overview: { showCover: true, showStatus: true, showDescription: true, showAuthor: false, showYear: false }, + table: { showMonitoring: true, showPreferredSource: true, showChapters: true, showStatus: false, showAuthor: false, showYear: false } + }, readingDirection: 'ltr', readingMode: 'scroll', autoFullscreen: false, @@ -88,6 +94,16 @@ export const useUserPreferencesStore = defineStore('userPreferences', { this.persist(); }, + setFilterBy(filter) { + this.filterBy = filter; + this.persist(); + }, + + setDisplayOption(view, key, value) { + this.displayOptions[view][key] = value; + this.persist(); + }, + setReadingDirection(direction) { this.readingDirection = direction; this.persist(); @@ -127,6 +143,8 @@ export const useUserPreferencesStore = defineStore('userPreferences', { defaultView: this.defaultView, itemsPerPage: this.itemsPerPage, sortBy: this.sortBy, + filterBy: this.filterBy, + displayOptions: this.displayOptions, readingDirection: this.readingDirection, readingMode: this.readingMode, autoFullscreen: this.autoFullscreen, diff --git a/assets/vue/app/shared/components/ui/ToggleRow.vue b/assets/vue/app/shared/components/ui/ToggleRow.vue new file mode 100644 index 0000000..5828666 --- /dev/null +++ b/assets/vue/app/shared/components/ui/ToggleRow.vue @@ -0,0 +1,37 @@ + + + diff --git a/assets/vue/app/shared/components/ui/Toolbar.vue b/assets/vue/app/shared/components/ui/Toolbar.vue index 35a58d8..ebeedf9 100644 --- a/assets/vue/app/shared/components/ui/Toolbar.vue +++ b/assets/vue/app/shared/components/ui/Toolbar.vue @@ -8,7 +8,7 @@ - +
diff --git a/assets/vue/app/shared/components/ui/ToolbarDropdown.vue b/assets/vue/app/shared/components/ui/ToolbarDropdown.vue index b605fa1..9cb0a0d 100644 --- a/assets/vue/app/shared/components/ui/ToolbarDropdown.vue +++ b/assets/vue/app/shared/components/ui/ToolbarDropdown.vue @@ -13,7 +13,10 @@ + :class="[ + 'absolute mt-2 w-max rounded-sm bg-gray-800 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none z-10', + align === 'right' ? 'right-0 origin-top-right' : 'left-0 origin-top-left' + ]">