feat: ajout de la fonctionnalité de conversion de fichiers CBR en CBZ, intégration d'un nouveau store pour gérer l'état de conversion, création de composants Vue pour l'upload de fichiers et le suivi de la progression, ainsi que la mise à jour de l'API pour gérer les conversions. Amélioration de la documentation API pour inclure les nouveaux endpoints et formats de fichiers supportés.
This commit is contained in:
parent
7a05934116
commit
d9e78b5229
@@ -0,0 +1,247 @@
|
||||
<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">
|
||||
{{ statusMessage }}
|
||||
</p>
|
||||
<p v-if="fileName" class="text-xs text-gray-500">
|
||||
{{ 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">
|
||||
<span>Progression</span>
|
||||
<span>{{ Math.round(progress) }}%</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-200 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 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 text-gray-700 text-sm font-medium rounded-md hover:bg-gray-50 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 border border-red-200 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">
|
||||
Erreur de conversion
|
||||
</h3>
|
||||
<p class="mt-1 text-sm text-red-700">
|
||||
{{ 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>
|
||||
Reference in New Issue
Block a user