Merge pull request 'style(conversion): aligner l'UI de conversion sur le design system import' (#20) from style/conversion-ui-align-import into main
All checks were successful
Deploy / deploy (push) Successful in 2m51s

Reviewed-on: #20
This commit was merged in pull request #20.
This commit is contained in:
2026-03-15 20:24:42 +01:00

View File

@@ -1,84 +1,77 @@
<template> <template>
<div class="flex flex-col h-full bg-gray-50 dark:bg-gray-900"> <div class="flex flex-col h-full">
<div class="overflow-y-auto flex-1"> <Toolbar :config="toolbarConfig" />
<div class="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
<FileUploadArea <div class="overflow-y-auto flex-1">
:selected-file="conversionStore.currentFile" <div class="px-6 py-8">
:disabled="conversionStore.isProcessing"
@file-selected="handleFileSelected"
@file-cleared="handleFileClear"
/>
<div v-if="conversionStore.hasSelectedFile && !conversionStore.hasSucceeded" class="mt-6 flex justify-center"> <!-- Zone d'upload -->
<button <section class="border-t border-gray-200 dark:border-gray-700 pt-6">
@click="handleConvert" <h2 class="text-xs font-semibold text-gray-400 dark:text-gray-500 uppercase tracking-wider mb-4">Fichier</h2>
:disabled="conversionStore.isProcessing" <FileUploadArea
:class="[ :selected-file="conversionStore.currentFile"
'flex items-center gap-2 px-6 py-3 text-white font-medium rounded-lg transition-colors', :disabled="conversionStore.isProcessing"
conversionStore.isProcessing @file-selected="handleFileSelected"
? 'bg-gray-400 cursor-not-allowed' @file-cleared="handleFileClear"
: 'bg-green-600 hover:bg-green-700' />
]" </section>
>
<ArrowPathIcon :class="['w-5 h-5', conversionStore.isProcessing && 'animate-spin']" />
{{ conversionStore.isProcessing ? 'Conversion en cours...' : 'Convertir en CBZ' }}
</button>
</div>
<ConversionProgress <!-- Progression -->
v-if="showProgress" <section v-if="showProgress" class="border-t border-gray-200 dark:border-gray-700 pt-6 mt-6">
class="mt-6" <ConversionProgress
:is-converting="conversionStore.isProcessing" :is-converting="conversionStore.isProcessing"
:progress="conversionStore.conversionProgress" :progress="conversionStore.conversionProgress"
:is-success="conversionStore.hasSucceeded" :is-success="conversionStore.hasSucceeded"
:has-error="conversionStore.hasError" :has-error="conversionStore.hasError"
:error-message="conversionStore.conversionError" :error-message="conversionStore.conversionError"
:file-name="conversionStore.currentFileName" :file-name="conversionStore.currentFileName"
:original-size="conversionStore.currentFile?.size || 0" :original-size="conversionStore.currentFile?.size || 0"
:converted-size="conversionStore.convertedFile?.size || 0" :converted-size="conversionStore.convertedFile?.size || 0"
@download="handleDownload" @download="handleDownload"
@reset="handleReset" @reset="handleReset"
/> />
</section>
<!-- Historique -->
<section v-if="conversionStore.conversionCount > 0" class="border-t border-gray-200 dark:border-gray-700 pt-6 mt-6">
<div class="flex items-center justify-between mb-3">
<h2 class="text-xs font-semibold text-gray-400 dark:text-gray-500 uppercase tracking-wider">Historique</h2>
<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-100 dark:divide-gray-700/50">
<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>
</section>
<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>
</div> </div>
</div>
</template> </template>
<script setup> <script setup>
import { ArrowPathIcon } from '@heroicons/vue/24/outline'; import { ArrowPathIcon } from '@heroicons/vue/24/outline';
import { computed, onMounted } from 'vue'; import { computed, onMounted } from 'vue';
import Toolbar from '../../../../shared/components/ui/Toolbar.vue';
import { useConversionStore } from '../../application/store/conversionStore'; import { useConversionStore } from '../../application/store/conversionStore';
import { useNotifications } from '../../../../shared/composables/useNotifications'; import { useNotifications } from '../../../../shared/composables/useNotifications';
import ConversionProgress from '../components/ConversionProgress.vue'; import ConversionProgress from '../components/ConversionProgress.vue';
@@ -88,53 +81,68 @@ const conversionStore = useConversionStore();
const { showSuccess, showError } = useNotifications(); const { showSuccess, showError } = useNotifications();
const showProgress = computed(() => const showProgress = computed(() =>
conversionStore.hasSelectedFile && conversionStore.hasSelectedFile &&
(conversionStore.isProcessing || conversionStore.hasSucceeded || conversionStore.hasError) (conversionStore.isProcessing || conversionStore.hasSucceeded || conversionStore.hasError)
); );
const toolbarConfig = computed(() => ({
leftSection: [
{ type: 'label', text: 'Conversion CBR CBZ', class: 'text-sm font-medium' },
],
rightSection: [
...(conversionStore.hasSelectedFile && !conversionStore.hasSucceeded ? [{
type: 'button',
icon: ArrowPathIcon,
label: conversionStore.isProcessing ? 'Conversion en cours...' : 'Convertir en CBZ',
onClick: handleConvert,
disabled: conversionStore.isProcessing,
}] : []),
],
}));
const handleFileSelected = (file) => { const handleFileSelected = (file) => {
conversionStore.selectFile(file); conversionStore.selectFile(file);
}; };
const handleFileClear = () => { const handleFileClear = () => {
conversionStore.resetConversion(); conversionStore.resetConversion();
}; };
const handleConvert = async () => { const handleConvert = async () => {
if (!conversionStore.currentFile) return; if (!conversionStore.currentFile) return;
const success = await conversionStore.convertCurrentFile(); const success = await conversionStore.convertCurrentFile();
if (success) { if (success) {
showSuccess('Conversion réussie !'); showSuccess('Conversion réussie !');
} else { } else {
showError(conversionStore.conversionError ?? 'Échec de la conversion'); showError(conversionStore.conversionError ?? 'Échec de la conversion');
} }
}; };
const handleDownload = () => conversionStore.downloadConvertedFile(); const handleDownload = () => conversionStore.downloadConvertedFile();
const handleReset = () => conversionStore.resetConversion(); const handleReset = () => conversionStore.resetConversion();
const formatFileSize = (bytes) => { const formatFileSize = (bytes) => {
if (bytes === 0) return '0 octets'; if (bytes === 0) return '0 octets';
const k = 1024; const k = 1024;
const sizes = ['octets', 'Ko', 'Mo', 'Go']; const sizes = ['octets', 'Ko', 'Mo', 'Go'];
const i = Math.floor(Math.log(bytes) / Math.log(k)); const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`; return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
}; };
const formatDate = (isoString) => const formatDate = (isoString) =>
new Intl.DateTimeFormat('fr-FR', { new Intl.DateTimeFormat('fr-FR', {
day: 'numeric', day: 'numeric',
month: 'short', month: 'short',
hour: '2-digit', hour: '2-digit',
minute: '2-digit', minute: '2-digit',
}).format(new Date(isoString)); }).format(new Date(isoString));
const calculateSaving = (originalSize, convertedSize) => { const calculateSaving = (originalSize, convertedSize) => {
if (!originalSize || !convertedSize) return ''; if (!originalSize || !convertedSize) return '';
const saving = ((originalSize - convertedSize) / originalSize) * 100; const saving = ((originalSize - convertedSize) / originalSize) * 100;
if (saving > 0) return `-${saving.toFixed(1)}%`; if (saving > 0) return `-${saving.toFixed(1)}%`;
if (saving < 0) return `+${Math.abs(saving).toFixed(1)}%`; if (saving < 0) return `+${Math.abs(saving).toFixed(1)}%`;
return '0%'; return '0%';
}; };
onMounted(() => conversionStore.resetConversion()); onMounted(() => conversionStore.resetConversion());