style(header): ajouter bouton toggle dark mode dans le header
All checks were successful
Deploy / deploy (push) Successful in 2m46s
All checks were successful
Deploy / deploy (push) Successful in 2m46s
feat(conversion): simplifier ConversionPage et brancher les toasts style(manga): réécriture de la liste de résultats dans AddManga chore(task): ajouter tâche conversion CBR→CBZ dans TASK.md
This commit is contained in:
parent
b609fe0a45
commit
cc702cff19
9
TASK.md
9
TASK.md
@@ -75,5 +75,14 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## [Style] Page conversion CBR → CBZ — Simplification UI + notifications toast
|
||||||
|
|
||||||
|
**Objectif :** Revoir le style de la page de conversion CBR → CBZ pour le simplifier, et remplacer le message statique "Conversion réussie" par les notifications toast de l'application.
|
||||||
|
|
||||||
|
- [ ] Auditer le composant/template actuel de la page de conversion
|
||||||
|
- [ ] Simplifier la mise en page (réduire la complexité visuelle, harmoniser avec le reste de l'UI)
|
||||||
|
- [ ] Supprimer l'affichage inline "Conversion réussie"
|
||||||
|
- [ ] Brancher les notifications toast existantes pour signaler le succès (et l'échec) de la conversion
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ export const useConversionStore = defineStore('conversion', {
|
|||||||
|
|
||||||
// État de l'interface
|
// État de l'interface
|
||||||
isDragOver: false,
|
isDragOver: false,
|
||||||
showSuccessMessage: false,
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
@@ -86,7 +85,6 @@ export const useConversionStore = defineStore('conversion', {
|
|||||||
this.clearError();
|
this.clearError();
|
||||||
this.conversionSuccess = false;
|
this.conversionSuccess = false;
|
||||||
this.convertedFile = null;
|
this.convertedFile = null;
|
||||||
this.showSuccessMessage = false;
|
|
||||||
|
|
||||||
// Stockage du fichier
|
// Stockage du fichier
|
||||||
this.currentFile = file;
|
this.currentFile = file;
|
||||||
@@ -125,7 +123,6 @@ export const useConversionStore = defineStore('conversion', {
|
|||||||
// Stockage du fichier converti
|
// Stockage du fichier converti
|
||||||
this.convertedFile = convertedFileBlob;
|
this.convertedFile = convertedFileBlob;
|
||||||
this.conversionSuccess = true;
|
this.conversionSuccess = true;
|
||||||
this.showSuccessMessage = true;
|
|
||||||
|
|
||||||
// Ajout à l'historique
|
// Ajout à l'historique
|
||||||
this.addToHistory({
|
this.addToHistory({
|
||||||
@@ -171,7 +168,6 @@ export const useConversionStore = defineStore('conversion', {
|
|||||||
this.currentFile = null;
|
this.currentFile = null;
|
||||||
this.convertedFile = null;
|
this.convertedFile = null;
|
||||||
this.conversionSuccess = false;
|
this.conversionSuccess = false;
|
||||||
this.showSuccessMessage = false;
|
|
||||||
this.conversionProgress = 0;
|
this.conversionProgress = 0;
|
||||||
this.clearError();
|
this.clearError();
|
||||||
},
|
},
|
||||||
@@ -183,7 +179,6 @@ export const useConversionStore = defineStore('conversion', {
|
|||||||
setError(message) {
|
setError(message) {
|
||||||
this.conversionError = message;
|
this.conversionError = message;
|
||||||
this.conversionSuccess = false;
|
this.conversionSuccess = false;
|
||||||
this.showSuccessMessage = false;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -193,13 +188,6 @@ export const useConversionStore = defineStore('conversion', {
|
|||||||
this.conversionError = null;
|
this.conversionError = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Cache le message de succès
|
|
||||||
*/
|
|
||||||
hideSuccessMessage() {
|
|
||||||
this.showSuccessMessage = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gère l'état du drag and drop
|
* Gère l'état du drag and drop
|
||||||
* @param {boolean} isDragOver - Indique si un fichier est survolé
|
* @param {boolean} isDragOver - Indique si un fichier est survolé
|
||||||
|
|||||||
@@ -1,33 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="overflow-y-auto h-full"><div class="container mx-auto px-4 py-8 max-w-4xl">
|
<div class="flex flex-col h-full bg-gray-50 dark:bg-gray-900">
|
||||||
<!-- En-tête -->
|
<div class="overflow-y-auto flex-1">
|
||||||
<div class="mb-8">
|
<div class="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<div class="flex items-center space-x-3 mb-4">
|
|
||||||
<ArrowPathIcon class="w-8 h-8 text-green-600" />
|
|
||||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-gray-100">
|
|
||||||
Convertir CBR en CBZ
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
<p class="text-lg text-gray-600 dark:text-gray-400">
|
|
||||||
Convertissez vos fichiers CBR (Comic Book RAR) en CBZ (Comic Book ZIP) pour une meilleure compatibilité.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Zone principale -->
|
|
||||||
<div class="bg-white dark:bg-gray-800 shadow-lg rounded-lg overflow-hidden">
|
|
||||||
<!-- En-tête de la carte -->
|
|
||||||
<div class="bg-gray-800 text-white p-6">
|
|
||||||
<div class="flex items-center space-x-3">
|
|
||||||
<ArchiveBoxIcon class="w-6 h-6" />
|
|
||||||
<h2 class="text-xl font-semibold">
|
|
||||||
Conversion de fichiers
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Contenu de la carte -->
|
|
||||||
<div class="p-6 space-y-6">
|
|
||||||
<!-- Zone d'upload -->
|
|
||||||
<FileUploadArea
|
<FileUploadArea
|
||||||
:selected-file="conversionStore.currentFile"
|
:selected-file="conversionStore.currentFile"
|
||||||
:disabled="conversionStore.isProcessing"
|
:disabled="conversionStore.isProcessing"
|
||||||
@@ -35,33 +10,25 @@
|
|||||||
@file-cleared="handleFileClear"
|
@file-cleared="handleFileClear"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Bouton de conversion -->
|
<div v-if="conversionStore.hasSelectedFile && !conversionStore.hasSucceeded" class="mt-6 flex justify-center">
|
||||||
<div v-if="conversionStore.hasSelectedFile && !conversionStore.hasSucceeded" class="flex justify-center">
|
|
||||||
<button
|
<button
|
||||||
@click="handleConvert"
|
@click="handleConvert"
|
||||||
:disabled="conversionStore.isProcessing"
|
:disabled="conversionStore.isProcessing"
|
||||||
:class="[
|
:class="[
|
||||||
'flex items-center space-x-2 px-6 py-3 text-white font-medium rounded-lg transition-all duration-200',
|
'flex items-center gap-2 px-6 py-3 text-white font-medium rounded-lg transition-colors',
|
||||||
conversionStore.isProcessing
|
conversionStore.isProcessing
|
||||||
? 'bg-gray-400 cursor-not-allowed'
|
? 'bg-gray-400 cursor-not-allowed'
|
||||||
: 'bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2'
|
: 'bg-green-600 hover:bg-green-700'
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<ArrowPathIcon
|
<ArrowPathIcon :class="['w-5 h-5', conversionStore.isProcessing && 'animate-spin']" />
|
||||||
:class="[
|
{{ conversionStore.isProcessing ? 'Conversion en cours...' : 'Convertir en CBZ' }}
|
||||||
'w-5 h-5',
|
|
||||||
conversionStore.isProcessing && 'animate-spin'
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
<span>
|
|
||||||
{{ conversionStore.isProcessing ? 'Conversion en cours...' : 'Convertir en CBZ' }}
|
|
||||||
</span>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Progression et résultat -->
|
|
||||||
<ConversionProgress
|
<ConversionProgress
|
||||||
v-if="showProgress"
|
v-if="showProgress"
|
||||||
|
class="mt-6"
|
||||||
:is-converting="conversionStore.isProcessing"
|
:is-converting="conversionStore.isProcessing"
|
||||||
:progress="conversionStore.conversionProgress"
|
:progress="conversionStore.conversionProgress"
|
||||||
:is-success="conversionStore.hasSucceeded"
|
:is-success="conversionStore.hasSucceeded"
|
||||||
@@ -74,212 +41,101 @@
|
|||||||
@reset="handleReset"
|
@reset="handleReset"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Message d'information -->
|
<div v-if="conversionStore.conversionCount > 0" class="mt-8">
|
||||||
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
|
<div class="flex items-center justify-between mb-3">
|
||||||
<div class="flex">
|
<h3 class="text-sm font-medium text-gray-700 dark:text-gray-300">Historique</h3>
|
||||||
<InformationCircleIcon class="w-5 h-5 text-blue-500 flex-shrink-0" />
|
|
||||||
<div class="ml-3">
|
|
||||||
<h3 class="text-sm font-medium text-blue-800 dark:text-blue-300">
|
|
||||||
À propos de la conversion
|
|
||||||
</h3>
|
|
||||||
<div class="mt-2 text-sm text-blue-700 dark:text-blue-400 space-y-1">
|
|
||||||
<p>• Les fichiers CBZ sont plus largement supportés par les lecteurs de bandes dessinées</p>
|
|
||||||
<p>• La compression ZIP permet généralement une meilleure accessibilité</p>
|
|
||||||
<p>• Aucune perte de qualité lors de la conversion</p>
|
|
||||||
<p>• Taille maximale supportée: 150MB</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Historique des conversions -->
|
|
||||||
<div v-if="conversionStore.conversionCount > 0" class="space-y-4">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
|
||||||
Historique des conversions
|
|
||||||
</h3>
|
|
||||||
<button
|
<button
|
||||||
@click="handleClearHistory"
|
@click="conversionStore.clearHistory()"
|
||||||
class="text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-colors"
|
class="text-sm text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 transition-colors"
|
||||||
>
|
>
|
||||||
Effacer l'historique
|
Effacer
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
<div class="bg-gray-50 dark:bg-gray-700/50 rounded-lg p-4">
|
<div
|
||||||
<div class="space-y-3">
|
v-for="(conversion, index) in conversionStore.conversionHistory"
|
||||||
<div
|
:key="index"
|
||||||
v-for="(conversion, index) in conversionStore.conversionHistory"
|
class="flex items-center justify-between py-3"
|
||||||
:key="index"
|
>
|
||||||
class="flex items-center justify-between py-2 border-b border-gray-200 dark:border-gray-600 last:border-b-0"
|
<div>
|
||||||
>
|
<p class="text-sm text-gray-900 dark:text-gray-100">{{ conversion.originalName }}</p>
|
||||||
<div class="flex-1">
|
<p class="text-xs text-gray-500 dark:text-gray-400">{{ formatDate(conversion.timestamp) }}</p>
|
||||||
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
</div>
|
||||||
{{ conversion.originalName }}
|
<div class="text-right text-sm">
|
||||||
</p>
|
<p class="text-gray-600 dark:text-gray-300">
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
{{ formatFileSize(conversion.originalSize) }} → {{ formatFileSize(conversion.convertedSize) }}
|
||||||
{{ formatDate(conversion.timestamp) }}
|
</p>
|
||||||
</p>
|
<p class="text-xs text-green-600">{{ calculateSaving(conversion.originalSize, conversion.convertedSize) }}</p>
|
||||||
</div>
|
|
||||||
<div class="text-right">
|
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-300">
|
|
||||||
{{ formatFileSize(conversion.originalSize) }} → {{ formatFileSize(conversion.convertedSize) }}
|
|
||||||
</p>
|
|
||||||
<p class="text-xs text-green-600">
|
|
||||||
{{ calculateSaving(conversion.originalSize, conversion.convertedSize) }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<!-- Toast de notification -->
|
|
||||||
<div
|
|
||||||
v-if="conversionStore.showSuccessMessage"
|
|
||||||
class="fixed bottom-4 right-4 bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg flex items-center space-x-3 z-50"
|
|
||||||
>
|
|
||||||
<CheckCircleIcon class="w-5 h-5" />
|
|
||||||
<span class="font-medium">Conversion réussie !</span>
|
|
||||||
<button
|
|
||||||
@click="conversionStore.hideSuccessMessage()"
|
|
||||||
class="ml-2 text-green-100 hover:text-white transition-colors"
|
|
||||||
>
|
|
||||||
<XMarkIcon class="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div></div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import {
|
import { ArrowPathIcon } from '@heroicons/vue/24/outline';
|
||||||
ArchiveBoxIcon,
|
|
||||||
ArrowPathIcon,
|
|
||||||
CheckCircleIcon,
|
|
||||||
InformationCircleIcon,
|
|
||||||
XMarkIcon,
|
|
||||||
} from '@heroicons/vue/24/outline';
|
|
||||||
import { computed, onMounted } from 'vue';
|
import { computed, onMounted } from 'vue';
|
||||||
import { useConversionStore } from '../../application/store/conversionStore';
|
import { useConversionStore } from '../../application/store/conversionStore';
|
||||||
|
import { useNotifications } from '../../../../shared/composables/useNotifications';
|
||||||
import ConversionProgress from '../components/ConversionProgress.vue';
|
import ConversionProgress from '../components/ConversionProgress.vue';
|
||||||
import FileUploadArea from '../components/FileUploadArea.vue';
|
import FileUploadArea from '../components/FileUploadArea.vue';
|
||||||
|
|
||||||
export default {
|
const conversionStore = useConversionStore();
|
||||||
name: 'ConversionPage',
|
const { showSuccess, showError } = useNotifications();
|
||||||
|
|
||||||
components: {
|
const showProgress = computed(() =>
|
||||||
FileUploadArea,
|
conversionStore.hasSelectedFile &&
|
||||||
ConversionProgress,
|
(conversionStore.isProcessing || conversionStore.hasSucceeded || conversionStore.hasError)
|
||||||
ArrowPathIcon,
|
);
|
||||||
ArchiveBoxIcon,
|
|
||||||
InformationCircleIcon,
|
|
||||||
CheckCircleIcon,
|
|
||||||
XMarkIcon,
|
|
||||||
},
|
|
||||||
|
|
||||||
setup() {
|
const handleFileSelected = (file) => {
|
||||||
const conversionStore = useConversionStore();
|
conversionStore.selectFile(file);
|
||||||
|
|
||||||
// Computed properties
|
|
||||||
const showProgress = computed(() => {
|
|
||||||
return conversionStore.hasSelectedFile &&
|
|
||||||
(conversionStore.isProcessing || conversionStore.hasSucceeded || conversionStore.hasError);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Event handlers
|
|
||||||
const handleFileSelected = (file) => {
|
|
||||||
const success = conversionStore.selectFile(file);
|
|
||||||
if (!success) {
|
|
||||||
// L'erreur est déjà gérée par le store
|
|
||||||
console.warn('Fichier non valide:', file);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFileClear = () => {
|
|
||||||
conversionStore.resetConversion();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleConvert = async () => {
|
|
||||||
if (!conversionStore.currentFile) return;
|
|
||||||
|
|
||||||
const success = await conversionStore.convertCurrentFile();
|
|
||||||
if (success) {
|
|
||||||
console.log('Conversion réussie');
|
|
||||||
} else {
|
|
||||||
console.error('Échec de la conversion');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDownload = () => {
|
|
||||||
conversionStore.downloadConvertedFile();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleReset = () => {
|
|
||||||
conversionStore.resetConversion();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClearHistory = () => {
|
|
||||||
conversionStore.clearHistory();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Utility functions
|
|
||||||
const formatFileSize = (bytes) => {
|
|
||||||
if (bytes === 0) return '0 octets';
|
|
||||||
|
|
||||||
const k = 1024;
|
|
||||||
const sizes = ['octets', 'Ko', 'Mo', 'Go'];
|
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
||||||
|
|
||||||
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatDate = (isoString) => {
|
|
||||||
const date = new Date(isoString);
|
|
||||||
return new Intl.DateTimeFormat('fr-FR', {
|
|
||||||
day: 'numeric',
|
|
||||||
month: 'short',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
}).format(date);
|
|
||||||
};
|
|
||||||
|
|
||||||
const calculateSaving = (originalSize, convertedSize) => {
|
|
||||||
if (!originalSize || !convertedSize) return '';
|
|
||||||
|
|
||||||
const saving = ((originalSize - convertedSize) / originalSize) * 100;
|
|
||||||
if (saving > 0) {
|
|
||||||
return `-${saving.toFixed(1)}%`;
|
|
||||||
} else if (saving < 0) {
|
|
||||||
return `+${Math.abs(saving).toFixed(1)}%`;
|
|
||||||
}
|
|
||||||
return '0%';
|
|
||||||
};
|
|
||||||
|
|
||||||
// Lifecycle
|
|
||||||
onMounted(() => {
|
|
||||||
// Réinitialiser l'état au montage de la page
|
|
||||||
conversionStore.resetConversion();
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
conversionStore,
|
|
||||||
showProgress,
|
|
||||||
handleFileSelected,
|
|
||||||
handleFileClear,
|
|
||||||
handleConvert,
|
|
||||||
handleDownload,
|
|
||||||
handleReset,
|
|
||||||
handleClearHistory,
|
|
||||||
formatFileSize,
|
|
||||||
formatDate,
|
|
||||||
calculateSaving,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
const handleFileClear = () => {
|
||||||
/* Styles spécifiques si nécessaires */
|
conversionStore.resetConversion();
|
||||||
</style>
|
};
|
||||||
|
|
||||||
|
const handleConvert = async () => {
|
||||||
|
if (!conversionStore.currentFile) return;
|
||||||
|
const success = await conversionStore.convertCurrentFile();
|
||||||
|
if (success) {
|
||||||
|
showSuccess('Conversion réussie !');
|
||||||
|
} else {
|
||||||
|
showError(conversionStore.conversionError ?? 'Échec de la conversion');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDownload = () => conversionStore.downloadConvertedFile();
|
||||||
|
const handleReset = () => conversionStore.resetConversion();
|
||||||
|
|
||||||
|
const formatFileSize = (bytes) => {
|
||||||
|
if (bytes === 0) return '0 octets';
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ['octets', 'Ko', 'Mo', 'Go'];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDate = (isoString) =>
|
||||||
|
new Intl.DateTimeFormat('fr-FR', {
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
}).format(new Date(isoString));
|
||||||
|
|
||||||
|
const calculateSaving = (originalSize, convertedSize) => {
|
||||||
|
if (!originalSize || !convertedSize) return '';
|
||||||
|
const saving = ((originalSize - convertedSize) / originalSize) * 100;
|
||||||
|
if (saving > 0) return `-${saving.toFixed(1)}%`;
|
||||||
|
if (saving < 0) return `+${Math.abs(saving).toFixed(1)}%`;
|
||||||
|
return '0%';
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => conversionStore.resetConversion());
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div class="overflow-y-auto h-full">
|
||||||
<div class="container mx-auto px-4 py-8">
|
<div class="container mx-auto px-4 py-8">
|
||||||
<!-- Barre de recherche -->
|
<!-- Barre de recherche -->
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
@@ -29,10 +30,24 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Résultats de recherche -->
|
<!-- Résultats de recherche -->
|
||||||
<div class="max-w-full overflow-hidden">
|
<div v-if="searchResults.length > 0" class="border-t border-gray-200 dark:border-gray-700">
|
||||||
<MangaOverview v-if="searchResults.length > 0" :mangas="searchResults" @manga-click="openMangaModal" />
|
<div
|
||||||
<p v-else-if="!loading && searchQuery" class="text-center text-gray-600 dark:text-gray-400">Aucun résultat trouvé</p>
|
v-for="manga in searchResults"
|
||||||
|
:key="manga.externalId"
|
||||||
|
class="flex items-center gap-4 px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-700/40 transition-colors border-b border-gray-100 dark:border-gray-700 cursor-pointer"
|
||||||
|
@click="openMangaModal(manga)">
|
||||||
|
<img
|
||||||
|
:src="manga.thumbnailUrl || manga.imageUrl || '/placeholder-cover.png'"
|
||||||
|
alt=""
|
||||||
|
class="h-36 w-24 object-cover flex-shrink-0 self-start"
|
||||||
|
referrerpolicy="no-referrer" />
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<p class="text-xl font-semibold text-gray-900 dark:text-gray-100">{{ manga.title }}</p>
|
||||||
|
<p v-if="manga.description" class="text-sm text-gray-600 dark:text-gray-300 mt-2 line-clamp-4">{{ manga.description }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<p v-else-if="!loading && searchQuery" class="text-center text-gray-600 dark:text-gray-400">Aucun résultat trouvé</p>
|
||||||
|
|
||||||
<!-- Modal de confirmation -->
|
<!-- Modal de confirmation -->
|
||||||
<Dialog :open="isModalOpen" @close="closeModal" class="relative z-50">
|
<Dialog :open="isModalOpen" @close="closeModal" class="relative z-50">
|
||||||
@@ -79,6 +94,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -88,7 +104,6 @@ import { storeToRefs } from 'pinia';
|
|||||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
|
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { useMangaStore } from '../../application/store/mangaStore';
|
import { useMangaStore } from '../../application/store/mangaStore';
|
||||||
import MangaOverview from '../components/MangaOverview.vue';
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|||||||
@@ -20,15 +20,36 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
<SearchBar />
|
<SearchBar />
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
@click="toggleDarkMode"
|
||||||
|
class="mr-4 text-white p-2 hover:text-green-200 transition-colors"
|
||||||
|
:title="isDark ? 'Passer en mode clair' : 'Passer en mode sombre'"
|
||||||
|
>
|
||||||
|
<SunIcon v-if="isDark" class="h-6 w-6" />
|
||||||
|
<MoonIcon v-else class="h-6 w-6" />
|
||||||
|
</button>
|
||||||
</header>
|
</header>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Bars3Icon } from '@heroicons/vue/24/outline';
|
import { computed } from 'vue';
|
||||||
|
import { Bars3Icon, SunIcon, MoonIcon } from '@heroicons/vue/24/outline';
|
||||||
import { useHeaderStore } from '../../stores/headerStore';
|
import { useHeaderStore } from '../../stores/headerStore';
|
||||||
|
import { useUserPreferencesStore } from '../../../domain/setting/application/store/userPreferencesStore';
|
||||||
import SearchBar from './SearchBar.vue';
|
import SearchBar from './SearchBar.vue';
|
||||||
|
|
||||||
const headerStore = useHeaderStore();
|
const headerStore = useHeaderStore();
|
||||||
|
const preferencesStore = useUserPreferencesStore();
|
||||||
|
|
||||||
|
const isDark = computed(() => {
|
||||||
|
if (preferencesStore.theme === 'dark') return true;
|
||||||
|
if (preferencesStore.theme === 'light') return false;
|
||||||
|
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
});
|
||||||
|
|
||||||
|
function toggleDarkMode() {
|
||||||
|
preferencesStore.setTheme(isDark.value ? 'light' : 'dark');
|
||||||
|
}
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
showMenuButton: {
|
showMenuButton: {
|
||||||
|
|||||||
Reference in New Issue
Block a user