Files
Mangarr/assets/vue/app/domain/conversion/presentation/components/ConversionProgress.vue
ext.jeremy.guillot@maxicoffee.domains ec1ef8fe68 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
2026-03-12 20:38:29 +01:00

248 lines
6.7 KiB
Vue

<template>
<div class="space-y-4">
<!-- Statut de la conversion -->
<div class="flex items-center space-x-3">
<!-- Icône de statut -->
<div class="flex-shrink-0">
<ArrowPathIcon
v-if="isConverting"
class="w-6 h-6 text-blue-500 animate-spin"
/>
<CheckCircleIcon
v-else-if="isSuccess"
class="w-6 h-6 text-green-500"
/>
<ExclamationTriangleIcon
v-else-if="hasError"
class="w-6 h-6 text-red-500"
/>
<ClockIcon
v-else
class="w-6 h-6 text-gray-400"
/>
</div>
<!-- Message de statut -->
<div class="flex-1">
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">
{{ statusMessage }}
</p>
<p v-if="fileName" class="text-xs text-gray-500 dark:text-gray-400">
{{ fileName }}
</p>
</div>
</div>
<!-- Barre de progression -->
<div v-if="showProgress" class="space-y-2">
<div class="flex justify-between text-xs text-gray-600 dark:text-gray-400">
<span>Progression</span>
<span>{{ Math.round(progress) }}%</span>
</div>
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
<div
class="bg-blue-500 h-2 rounded-full transition-all duration-300 ease-out"
:style="{ width: `${progress}%` }"
/>
</div>
</div>
<!-- Détails de la conversion -->
<div v-if="showDetails && (originalSize || convertedSize)" class="text-xs text-gray-500 dark:text-gray-400 space-y-1">
<div v-if="originalSize" class="flex justify-between">
<span>Taille originale:</span>
<span>{{ formatFileSize(originalSize) }}</span>
</div>
<div v-if="convertedSize" class="flex justify-between">
<span>Taille convertie:</span>
<span>{{ formatFileSize(convertedSize) }}</span>
</div>
<div v-if="originalSize && convertedSize" class="flex justify-between font-medium">
<span>Gain d'espace:</span>
<span :class="spaceSavingClass">{{ spaceSavingText }}</span>
</div>
</div>
<!-- Actions -->
<div v-if="showActions" class="flex space-x-3">
<button
v-if="canDownload"
@click="$emit('download')"
class="flex items-center space-x-2 px-4 py-2 bg-green-600 text-white text-sm font-medium rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 transition-colors"
>
<ArrowDownTrayIcon class="w-4 h-4" />
<span>Télécharger CBZ</span>
</button>
<button
v-if="canReset"
@click="$emit('reset')"
class="flex items-center space-x-2 px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 text-sm font-medium rounded-md hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors"
>
<ArrowPathIcon class="w-4 h-4" />
<span>Convertir un autre fichier</span>
</button>
</div>
<!-- Message d'erreur détaillé -->
<div v-if="hasError && errorMessage" class="p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-md">
<div class="flex">
<ExclamationTriangleIcon class="w-5 h-5 text-red-400 flex-shrink-0" />
<div class="ml-3">
<h3 class="text-sm font-medium text-red-800 dark:text-red-300">
Erreur de conversion
</h3>
<p class="mt-1 text-sm text-red-700 dark:text-red-400">
{{ errorMessage }}
</p>
</div>
</div>
</div>
</div>
</template>
<script>
import {
ArrowDownTrayIcon,
ArrowPathIcon,
CheckCircleIcon,
ClockIcon,
ExclamationTriangleIcon,
} from '@heroicons/vue/24/outline';
import { computed } from 'vue';
export default {
name: 'ConversionProgress',
components: {
ArrowPathIcon,
CheckCircleIcon,
ExclamationTriangleIcon,
ClockIcon,
ArrowDownTrayIcon,
},
props: {
isConverting: {
type: Boolean,
default: false,
},
progress: {
type: Number,
default: 0,
},
isSuccess: {
type: Boolean,
default: false,
},
hasError: {
type: Boolean,
default: false,
},
errorMessage: {
type: String,
default: '',
},
fileName: {
type: String,
default: '',
},
originalSize: {
type: Number,
default: 0,
},
convertedSize: {
type: Number,
default: 0,
},
showActions: {
type: Boolean,
default: true,
},
showDetails: {
type: Boolean,
default: true,
},
},
emits: ['download', 'reset'],
setup(props) {
// Message de statut calculé
const statusMessage = computed(() => {
if (props.isConverting) {
return 'Conversion en cours...';
}
if (props.isSuccess) {
return 'Conversion terminée avec succès !';
}
if (props.hasError) {
return 'Erreur lors de la conversion';
}
return 'En attente de fichier';
});
// Affichage de la barre de progression
const showProgress = computed(() => {
return props.isConverting && props.progress > 0;
});
// Actions disponibles
const canDownload = computed(() => {
return props.isSuccess && !props.isConverting;
});
const canReset = computed(() => {
return (props.isSuccess || props.hasError) && !props.isConverting;
});
// Calcul du gain d'espace
const spaceSaving = computed(() => {
if (!props.originalSize || !props.convertedSize) return 0;
return ((props.originalSize - props.convertedSize) / props.originalSize) * 100;
});
const spaceSavingText = computed(() => {
const saving = spaceSaving.value;
if (saving > 0) {
return `-${saving.toFixed(1)}%`;
} else if (saving < 0) {
return `+${Math.abs(saving).toFixed(1)}%`;
}
return '0%';
});
const spaceSavingClass = computed(() => {
const saving = spaceSaving.value;
if (saving > 0) {
return 'text-green-600';
} else if (saving < 0) {
return 'text-red-600';
}
return 'text-gray-600';
});
// Formatage de la taille de fichier
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]}`;
};
return {
statusMessage,
showProgress,
canDownload,
canReset,
spaceSavingText,
spaceSavingClass,
formatFileSize,
};
},
};
</script>