feat: dark mode complet + préférences utilisateur

- Ajout du store userPreferencesStore (thème, vue, tri, pagination, lecteur)
- Page UserPreferencesPage pour configurer toutes les préférences
- Câblage des prefs dans HomePage (viewMode, sortBy, itemsPerPage), readerStore (fallback prefs), ChapterReader (autoHide, autoFullscreen, sync), useNotifications (toastDuration)
- Thème sombre (dark: Tailwind) sur tous les composants Vue : Layout, Pagination, NotificationToast, MangaCard, MangaVolume, MangaDetails, AddManga, HomePage, ActivityPage, JobItem, MangaDeleteModal, MangaEditModal, MangaPreferredSourcesModal, ManageChaptersModal, MangaChapterList, MangaChapter, ConversionPage, FileUploadArea, ConversionProgress, NewImportPage, FileImportCard, MangaMatchCard, StatusBadge, ImportResults
- i18n partiellement initialisé

Jeremy Guillot
This commit is contained in:
ext.jeremy.guillot@maxicoffee.domains
2026-03-12 20:38:29 +01:00
parent 48d819ba72
commit ec1ef8fe68
36 changed files with 2832 additions and 317 deletions

View File

@@ -1,5 +1,5 @@
<template>
<div class="min-h-screen bg-gray-50 flex">
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 flex">
<Header
:show-menu-button="isReaderMode"
@menu-click="toggleSidebar"

View File

@@ -9,7 +9,7 @@
v-for="notification in notifications"
:key="notification.id"
:class="[
'max-w-md w-full bg-white shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden',
'max-w-md w-full bg-white dark:bg-gray-800 shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden',
getNotificationClass(notification.type)
]"
>
@@ -18,14 +18,14 @@
<div class="flex-shrink-0 mr-3">
<button
@click="removeNotification(notification.id)"
class="bg-white rounded-md inline-flex text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
class="bg-white dark:bg-gray-800 rounded-md inline-flex text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
<span class="sr-only">Close</span>
<XMarkIcon class="h-5 w-5" />
</button>
</div>
<div class="flex-1 pt-0.5 min-w-0">
<p class="text-sm font-medium text-gray-900 break-words">
<p class="text-sm font-medium text-gray-900 dark:text-gray-100 break-words">
{{ notification.message }}
</p>
</div>

View File

@@ -1,7 +1,7 @@
<template>
<div v-if="totalPages > 1" class="flex items-center justify-between px-4 py-3 bg-white border-t border-gray-200">
<div v-if="totalPages > 1" class="flex items-center justify-between px-4 py-3 bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700">
<!-- Informations de pagination -->
<div class="flex items-center text-sm text-gray-700">
<div class="flex items-center text-sm text-gray-700 dark:text-gray-300">
<span>
Affichage de
<span class="font-medium">{{ startItem }}</span>
@@ -22,8 +22,8 @@
:class="[
'relative inline-flex items-center px-2 py-2 text-sm font-medium rounded-md',
hasPreviousPage
? 'text-gray-500 bg-white border border-gray-300 hover:bg-gray-50'
: 'text-gray-300 bg-gray-100 border border-gray-200 cursor-not-allowed'
? 'text-gray-500 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600'
: 'text-gray-300 dark:text-gray-600 bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 cursor-not-allowed'
]">
<span class="sr-only">Précédent</span>
<ChevronLeftIcon class="h-5 w-5" />
@@ -38,14 +38,14 @@
:class="[
'relative inline-flex items-center px-3 py-2 text-sm font-medium rounded-md',
currentPage === 1
? 'z-10 bg-indigo-50 border-indigo-500 text-indigo-600'
: 'bg-white border-gray-300 text-gray-500 hover:bg-gray-50'
? 'z-10 bg-indigo-50 dark:bg-indigo-900/30 border-indigo-500 text-indigo-600 dark:text-indigo-400'
: 'bg-white dark:bg-gray-700 border-gray-300 dark:border-gray-600 text-gray-500 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600'
]">
1
</button>
<!-- Points de suspension gauche -->
<span v-if="showLeftDots" class="relative inline-flex items-center px-3 py-2 text-sm font-medium text-gray-700">
<span v-if="showLeftDots" class="relative inline-flex items-center px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300">
...
</span>
@@ -57,14 +57,14 @@
:class="[
'relative inline-flex items-center px-3 py-2 text-sm font-medium rounded-md',
currentPage === page
? 'z-10 bg-indigo-50 border-indigo-500 text-indigo-600'
: 'bg-white border-gray-300 text-gray-500 hover:bg-gray-50'
? 'z-10 bg-indigo-50 dark:bg-indigo-900/30 border-indigo-500 text-indigo-600 dark:text-indigo-400'
: 'bg-white dark:bg-gray-700 border-gray-300 dark:border-gray-600 text-gray-500 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600'
]">
{{ page }}
</button>
<!-- Points de suspension droite -->
<span v-if="showRightDots" class="relative inline-flex items-center px-3 py-2 text-sm font-medium text-gray-700">
<span v-if="showRightDots" class="relative inline-flex items-center px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300">
...
</span>
@@ -75,8 +75,8 @@
:class="[
'relative inline-flex items-center px-3 py-2 text-sm font-medium rounded-md',
currentPage === totalPages
? 'z-10 bg-indigo-50 border-indigo-500 text-indigo-600'
: 'bg-white border-gray-300 text-gray-500 hover:bg-gray-50'
? 'z-10 bg-indigo-50 dark:bg-indigo-900/30 border-indigo-500 text-indigo-600 dark:text-indigo-400'
: 'bg-white dark:bg-gray-700 border-gray-300 dark:border-gray-600 text-gray-500 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600'
]">
{{ totalPages }}
</button>
@@ -84,7 +84,7 @@
<!-- Pagination mobile -->
<div class="md:hidden flex items-center space-x-2">
<span class="text-sm text-gray-700">
<span class="text-sm text-gray-700 dark:text-gray-300">
{{ currentPage }} / {{ totalPages }}
</span>
</div>
@@ -96,8 +96,8 @@
:class="[
'relative inline-flex items-center px-2 py-2 text-sm font-medium rounded-md',
hasNextPage
? 'text-gray-500 bg-white border border-gray-300 hover:bg-gray-50'
: 'text-gray-300 bg-gray-100 border border-gray-200 cursor-not-allowed'
? 'text-gray-500 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600'
: 'text-gray-300 dark:text-gray-600 bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 cursor-not-allowed'
]">
<span class="sr-only">Suivant</span>
<ChevronRightIcon class="h-5 w-5" />

View File

@@ -1,4 +1,5 @@
import { ref } from 'vue';
import { useUserPreferencesStore } from '../../domain/setting/application/store/userPreferencesStore';
const notifications = ref([]);
let nextId = 1;
@@ -36,20 +37,24 @@ export function useNotifications() {
notifications.value = [];
};
const showSuccess = (message, duration = 4000) => {
return addNotification(message, 'success', duration);
const showSuccess = (message, duration) => {
const prefs = useUserPreferencesStore();
return addNotification(message, 'success', duration ?? prefs.toastDuration);
};
const showError = (message, duration = 6000) => {
return addNotification(message, 'error', duration);
const showError = (message, duration) => {
const prefs = useUserPreferencesStore();
return addNotification(message, 'error', duration ?? prefs.toastDuration);
};
const showWarning = (message, duration = 5000) => {
return addNotification(message, 'warning', duration);
const showWarning = (message, duration) => {
const prefs = useUserPreferencesStore();
return addNotification(message, 'warning', duration ?? prefs.toastDuration);
};
const showInfo = (message, duration = 4000) => {
return addNotification(message, 'info', duration);
const showInfo = (message, duration) => {
const prefs = useUserPreferencesStore();
return addNotification(message, 'info', duration ?? prefs.toastDuration);
};
return {

View File

@@ -0,0 +1,10 @@
import { createI18n } from 'vue-i18n';
import fr from './locales/fr.json';
import en from './locales/en.json';
export const i18n = createI18n({
legacy: false,
locale: 'fr',
fallbackLocale: 'fr',
messages: { fr, en },
});

View File

@@ -0,0 +1,67 @@
{
"nav": {
"preferences": "Preferences"
},
"preferences": {
"title": "Preferences",
"subtitle": "Customize the interface to your liking",
"reset": "Reset",
"resetConfirm": "Reset to default values?",
"sections": {
"appearance": "Appearance",
"collection": "Collection display",
"reading": "Reading",
"notifications": "Notifications"
},
"theme": {
"label": "Theme",
"light": "Light",
"dark": "Dark",
"system": "System (automatic)"
},
"language": {
"label": "Language",
"fr": "Français",
"en": "English"
},
"defaultView": {
"label": "Default view",
"grid": "Grid",
"list": "List"
},
"itemsPerPage": {
"label": "Mangas per page"
},
"sortBy": {
"label": "Default sort",
"title": "Title",
"addedAt": "Date added",
"progress": "Progress"
},
"readingDirection": {
"label": "Reading direction",
"ltr": "Left → Right (western)",
"rtl": "Right → Left (manga)"
},
"readingMode": {
"label": "Display mode",
"scroll": "Vertical scroll",
"single": "Single page",
"double": "Double page"
},
"autoFullscreen": {
"label": "Auto fullscreen",
"description": "Enter fullscreen when starting the reader"
},
"autoHideHeaderReader": {
"label": "Auto-hide header",
"description": "Hide the navigation bar in reading mode"
},
"toastDuration": {
"label": "Notification duration",
"3s": "3 seconds",
"5s": "5 seconds",
"10s": "10 seconds"
}
}
}

View File

@@ -0,0 +1,67 @@
{
"nav": {
"preferences": "Préférences"
},
"preferences": {
"title": "Préférences",
"subtitle": "Personnalisez l'interface selon vos goûts",
"reset": "Réinitialiser",
"resetConfirm": "Remettre les valeurs par défaut ?",
"sections": {
"appearance": "Apparence",
"collection": "Affichage de la collection",
"reading": "Lecture",
"notifications": "Notifications"
},
"theme": {
"label": "Thème",
"light": "Clair",
"dark": "Sombre",
"system": "Système (automatique)"
},
"language": {
"label": "Langue",
"fr": "Français",
"en": "English"
},
"defaultView": {
"label": "Vue par défaut",
"grid": "Grille",
"list": "Liste"
},
"itemsPerPage": {
"label": "Mangas par page"
},
"sortBy": {
"label": "Tri par défaut",
"title": "Titre",
"addedAt": "Date d'ajout",
"progress": "Progression"
},
"readingDirection": {
"label": "Direction de lecture",
"ltr": "Gauche → Droite (occidental)",
"rtl": "Droite → Gauche (manga)"
},
"readingMode": {
"label": "Mode d'affichage",
"scroll": "Défilement vertical",
"single": "Page unique",
"double": "Double page"
},
"autoFullscreen": {
"label": "Plein écran automatique",
"description": "Passer en plein écran au démarrage du lecteur"
},
"autoHideHeaderReader": {
"label": "Masquer automatiquement l'en-tête",
"description": "Masquer la barre de navigation en mode lecture"
},
"toastDuration": {
"label": "Durée des notifications",
"3s": "3 secondes",
"5s": "5 secondes",
"10s": "10 secondes"
}
}
}