style(conversion): aligner l'UI de conversion sur le design system import #20
@@ -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());
|
||||||
|
|||||||
Reference in New Issue
Block a user