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
142 lines
5.3 KiB
Vue
142 lines
5.3 KiB
Vue
<template>
|
|
<div class="flex flex-col h-full bg-gray-50 dark:bg-gray-900">
|
|
<div class="overflow-y-auto flex-1">
|
|
<div class="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
|
|
<FileUploadArea
|
|
:selected-file="conversionStore.currentFile"
|
|
:disabled="conversionStore.isProcessing"
|
|
@file-selected="handleFileSelected"
|
|
@file-cleared="handleFileClear"
|
|
/>
|
|
|
|
<div v-if="conversionStore.hasSelectedFile && !conversionStore.hasSucceeded" class="mt-6 flex justify-center">
|
|
<button
|
|
@click="handleConvert"
|
|
:disabled="conversionStore.isProcessing"
|
|
:class="[
|
|
'flex items-center gap-2 px-6 py-3 text-white font-medium rounded-lg transition-colors',
|
|
conversionStore.isProcessing
|
|
? 'bg-gray-400 cursor-not-allowed'
|
|
: 'bg-green-600 hover:bg-green-700'
|
|
]"
|
|
>
|
|
<ArrowPathIcon :class="['w-5 h-5', conversionStore.isProcessing && 'animate-spin']" />
|
|
{{ conversionStore.isProcessing ? 'Conversion en cours...' : 'Convertir en CBZ' }}
|
|
</button>
|
|
</div>
|
|
|
|
<ConversionProgress
|
|
v-if="showProgress"
|
|
class="mt-6"
|
|
:is-converting="conversionStore.isProcessing"
|
|
:progress="conversionStore.conversionProgress"
|
|
:is-success="conversionStore.hasSucceeded"
|
|
:has-error="conversionStore.hasError"
|
|
:error-message="conversionStore.conversionError"
|
|
:file-name="conversionStore.currentFileName"
|
|
:original-size="conversionStore.currentFile?.size || 0"
|
|
:converted-size="conversionStore.convertedFile?.size || 0"
|
|
@download="handleDownload"
|
|
@reset="handleReset"
|
|
/>
|
|
|
|
<div v-if="conversionStore.conversionCount > 0" class="mt-8">
|
|
<div class="flex items-center justify-between mb-3">
|
|
<h3 class="text-sm font-medium text-gray-700 dark:text-gray-300">Historique</h3>
|
|
<button
|
|
@click="conversionStore.clearHistory()"
|
|
class="text-sm text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 transition-colors"
|
|
>
|
|
Effacer
|
|
</button>
|
|
</div>
|
|
<div class="divide-y divide-gray-200 dark:divide-gray-700">
|
|
<div
|
|
v-for="(conversion, index) in conversionStore.conversionHistory"
|
|
:key="index"
|
|
class="flex items-center justify-between py-3"
|
|
>
|
|
<div>
|
|
<p class="text-sm text-gray-900 dark:text-gray-100">{{ conversion.originalName }}</p>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400">{{ formatDate(conversion.timestamp) }}</p>
|
|
</div>
|
|
<div class="text-right text-sm">
|
|
<p class="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>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ArrowPathIcon } from '@heroicons/vue/24/outline';
|
|
import { computed, onMounted } from 'vue';
|
|
import { useConversionStore } from '../../application/store/conversionStore';
|
|
import { useNotifications } from '../../../../shared/composables/useNotifications';
|
|
import ConversionProgress from '../components/ConversionProgress.vue';
|
|
import FileUploadArea from '../components/FileUploadArea.vue';
|
|
|
|
const conversionStore = useConversionStore();
|
|
const { showSuccess, showError } = useNotifications();
|
|
|
|
const showProgress = computed(() =>
|
|
conversionStore.hasSelectedFile &&
|
|
(conversionStore.isProcessing || conversionStore.hasSucceeded || conversionStore.hasError)
|
|
);
|
|
|
|
const handleFileSelected = (file) => {
|
|
conversionStore.selectFile(file);
|
|
};
|
|
|
|
const handleFileClear = () => {
|
|
conversionStore.resetConversion();
|
|
};
|
|
|
|
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>
|